From 1b8cc8b62513edafa3ec7b40e63b56ab50f44d4a Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Thu, 6 Nov 2025 14:13:24 -0800 Subject: [PATCH] [App Server] Add more session metadata to listConversations (#6337) This unlocks a few new product experience for app server consumers --- .../app-server-protocol/src/protocol/v1.rs | 13 +++ .../app-server/src/codex_message_processor.rs | 84 +++++++++++++++---- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index ea77e09a..d518abc1 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -11,6 +11,7 @@ use codex_protocol::models::ResponseItem; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::SandboxPolicy; +use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::TurnAbortReason; use schemars::JsonSchema; use serde::Deserialize; @@ -113,6 +114,18 @@ pub struct ConversationSummary { pub preview: String, pub timestamp: Option, pub model_provider: String, + pub cwd: PathBuf, + pub cli_version: String, + pub source: SessionSource, + pub git_info: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +pub struct ConversationGitInfo { + pub sha: Option, + pub branch: Option, + pub origin_url: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index ddf777de..ebfaed58 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -20,6 +20,7 @@ use codex_app_server_protocol::CancelLoginAccountParams; use codex_app_server_protocol::CancelLoginAccountResponse; use codex_app_server_protocol::CancelLoginChatGptResponse; use codex_app_server_protocol::ClientRequest; +use codex_app_server_protocol::ConversationGitInfo; use codex_app_server_protocol::ConversationSummary; use codex_app_server_protocol::ExecCommandApprovalParams; use codex_app_server_protocol::ExecCommandApprovalResponse; @@ -130,8 +131,10 @@ use codex_protocol::ConversationId; use codex_protocol::config_types::ForcedLoginMethod; use codex_protocol::items::TurnItem; use codex_protocol::models::ResponseItem; +use codex_protocol::protocol::GitInfo; use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot; use codex_protocol::protocol::RolloutItem; +use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::USER_MESSAGE_BEGIN; use codex_protocol::user_input::UserInput as CoreInputItem; use codex_utils_json_to_toml::json_to_toml; @@ -1510,7 +1513,18 @@ impl CodexMessageProcessor { let items = page .items .into_iter() - .filter_map(|it| extract_conversation_summary(it.path, &it.head, &fallback_provider)) + .filter_map(|it| { + let session_meta_line = it.head.first().and_then(|first| { + serde_json::from_value::(first.clone()).ok() + })?; + extract_conversation_summary( + it.path, + &it.head, + &session_meta_line.meta, + session_meta_line.git.as_ref(), + fallback_provider.as_str(), + ) + }) .collect::>(); // Encode next_cursor as a plain string @@ -2671,16 +2685,25 @@ async fn read_summary_from_rollout( ))); }; - let session_meta = serde_json::from_value::(first.clone()).map_err(|_| { - IoError::other(format!( - "rollout at {} does not start with session metadata", - path.display() - )) - })?; + let session_meta_line = + serde_json::from_value::(first.clone()).map_err(|_| { + IoError::other(format!( + "rollout at {} does not start with session metadata", + path.display() + )) + })?; + let SessionMetaLine { + meta: session_meta, + git, + } = session_meta_line; - if let Some(summary) = - extract_conversation_summary(path.to_path_buf(), &head, fallback_provider) - { + if let Some(summary) = extract_conversation_summary( + path.to_path_buf(), + &head, + &session_meta, + git.as_ref(), + fallback_provider, + ) { return Ok(summary); } @@ -2691,7 +2714,9 @@ async fn read_summary_from_rollout( }; let model_provider = session_meta .model_provider + .clone() .unwrap_or_else(|| fallback_provider.to_string()); + let git_info = git.as_ref().map(map_git_info); Ok(ConversationSummary { conversation_id: session_meta.id, @@ -2699,19 +2724,20 @@ async fn read_summary_from_rollout( path: path.to_path_buf(), preview: String::new(), model_provider, + cwd: session_meta.cwd, + cli_version: session_meta.cli_version, + source: session_meta.source, + git_info, }) } fn extract_conversation_summary( path: PathBuf, head: &[serde_json::Value], + session_meta: &SessionMeta, + git: Option<&GitInfo>, fallback_provider: &str, ) -> Option { - let session_meta = match head.first() { - Some(first_line) => serde_json::from_value::(first_line.clone()).ok()?, - None => return None, - }; - let preview = head .iter() .filter_map(|value| serde_json::from_value::(value.clone()).ok()) @@ -2733,7 +2759,9 @@ fn extract_conversation_summary( let conversation_id = session_meta.id; let model_provider = session_meta .model_provider + .clone() .unwrap_or_else(|| fallback_provider.to_string()); + let git_info = git.map(map_git_info); Some(ConversationSummary { conversation_id, @@ -2741,13 +2769,26 @@ fn extract_conversation_summary( path, preview: preview.to_string(), model_provider, + cwd: session_meta.cwd.clone(), + cli_version: session_meta.cli_version.clone(), + source: session_meta.source.clone(), + git_info, }) } +fn map_git_info(git_info: &GitInfo) -> ConversationGitInfo { + ConversationGitInfo { + sha: git_info.commit_hash.clone(), + branch: git_info.branch.clone(), + origin_url: git_info.repository_url.clone(), + } +} + #[cfg(test)] mod tests { use super::*; use anyhow::Result; + use codex_protocol::protocol::SessionSource; use pretty_assertions::assert_eq; use serde_json::json; use tempfile::TempDir; @@ -2786,8 +2827,11 @@ mod tests { }), ]; + let session_meta = serde_json::from_value::(head[0].clone())?; + let summary = - extract_conversation_summary(path.clone(), &head, "test-provider").expect("summary"); + extract_conversation_summary(path.clone(), &head, &session_meta, None, "test-provider") + .expect("summary"); let expected = ConversationSummary { conversation_id, @@ -2795,6 +2839,10 @@ mod tests { path, preview: "Count to 5".to_string(), model_provider: "test-provider".to_string(), + cwd: PathBuf::from("/"), + cli_version: "0.0.0".to_string(), + source: SessionSource::VSCode, + git_info: None, }; assert_eq!(summary, expected); @@ -2839,6 +2887,10 @@ mod tests { path: path.clone(), preview: String::new(), model_provider: "fallback".to_string(), + cwd: PathBuf::new(), + cli_version: String::new(), + source: SessionSource::VSCode, + git_info: None, }; assert_eq!(summary, expected);