@@ -17,7 +17,9 @@ use crate::prompt::Prompt;
1717use crate :: rpc:: { RpcClient , StreamEvent } ;
1818use crate :: theme:: { self , Palette } ;
1919use crate :: tool:: ToolCall ;
20- use crate :: types:: { HistoryEntry , ModelList , Part , SessionInfo , ToolResultContent , UsageReport } ;
20+ use crate :: types:: {
21+ HistoryEntry , ModelList , Part , ServerInfo , SessionInfo , ToolResultContent , UsageReport ,
22+ } ;
2123
2224/// What a leading slash offers. Only commands the app can actually perform are listed, and an
2325/// entry's name is what choosing it matches on.
@@ -144,6 +146,8 @@ pub struct App {
144146 pub popup : Option < Popup > ,
145147 /// A line of plain truth about the prompt or RPC status, shown in place of the key hints.
146148 pub notice : Option < String > ,
149+ /// Verified server identity (`pid` and `version`) from `server.info`.
150+ pub server_info : Option < ServerInfo > ,
147151 /// Active server session, once created or attached.
148152 pub session : Option < SessionInfo > ,
149153 /// Available models and the resolved settings default from `model.list`.
@@ -184,6 +188,7 @@ impl App {
184188 prompt : Prompt :: default ( ) ,
185189 popup : None ,
186190 notice : None ,
191+ server_info : None ,
187192 session : None ,
188193 models : None ,
189194 model : None ,
@@ -198,6 +203,7 @@ impl App {
198203 keymap : Keymap :: new ( ) ,
199204 tx,
200205 } ;
206+ app. spawn_server_info ( ) ;
201207 app. spawn_list_models ( ) ;
202208 app
203209 }
@@ -476,6 +482,14 @@ impl App {
476482
477483 fn handle_rpc ( & mut self , event : RpcEvent ) -> bool {
478484 match event {
485+ RpcEvent :: ServerInfoLoaded ( Ok ( info) ) => {
486+ self . server_info = Some ( info) ;
487+ true
488+ }
489+ RpcEvent :: ServerInfoLoaded ( Err ( err) ) => {
490+ self . notice = Some ( err) ;
491+ true
492+ }
479493 RpcEvent :: ModelsLoaded ( Ok ( models) ) => {
480494 self . models = Some ( models) ;
481495 let active_model = self . model_label ( ) ;
@@ -907,6 +921,16 @@ impl App {
907921 false
908922 }
909923
924+ fn spawn_server_info ( & self ) {
925+ let tx = self . tx . clone ( ) ;
926+ if let Ok ( handle) = tokio:: runtime:: Handle :: try_current ( ) {
927+ handle. spawn ( async move {
928+ let res = RpcClient :: default ( ) . verify_server ( ) . await ;
929+ let _ = tx. send ( AppEvent :: Rpc ( RpcEvent :: ServerInfoLoaded ( res) ) ) ;
930+ } ) ;
931+ }
932+ }
933+
910934 fn spawn_list_models ( & self ) {
911935 let host_dir = self . active_host_dir ( ) ;
912936 let tx = self . tx . clone ( ) ;
@@ -1754,4 +1778,30 @@ mod tests {
17541778 assert_eq ! ( ids, vec![ "off" , "low" , "medium" , "high" ] ) ;
17551779 assert_eq ! ( menu. selected( ) . map( |e| e. id. as_ref( ) ) , Some ( "high" ) ) ;
17561780 }
1781+
1782+ #[ test]
1783+ fn records_server_info_and_surfaces_mismatch_notice ( ) {
1784+ let mut app = App :: default ( ) ;
1785+ assert ! ( app. server_info. is_none( ) ) ;
1786+ assert ! ( app. notice. is_none( ) ) ;
1787+
1788+ app. update ( AppEvent :: Rpc ( RpcEvent :: ServerInfoLoaded ( Ok ( ServerInfo {
1789+ pid : 4242 ,
1790+ version : "0.3.0" . to_string ( ) ,
1791+ } ) ) ) ) ;
1792+ assert_eq ! (
1793+ app. server_info,
1794+ Some ( ServerInfo {
1795+ pid: 4242 ,
1796+ version: "0.3.0" . to_string( ) ,
1797+ } )
1798+ ) ;
1799+ assert ! ( app. notice. is_none( ) ) ;
1800+
1801+ let mismatch = "Server at ws://127.0.0.1:7533/rpc/v1 runs version 0.2.0, expected 0.3.0 from service.json" . to_string ( ) ;
1802+ app. update ( AppEvent :: Rpc ( RpcEvent :: ServerInfoLoaded ( Err (
1803+ mismatch. clone ( ) ,
1804+ ) ) ) ) ;
1805+ assert_eq ! ( app. notice. as_deref( ) , Some ( mismatch. as_str( ) ) ) ;
1806+ }
17571807}
0 commit comments