From 85f522a73e9392cbc9f7b3bb4e401d6ea2a664b4 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 1 May 2026 12:18:36 +0100 Subject: [PATCH] add session ID everywhere --- codex-rs/analytics/src/analytics_client_tests.rs | 2 ++ codex-rs/analytics/src/client_tests.rs | 2 ++ .../schema/json/codex_app_server_protocol.schemas.json | 10 ++++++++++ .../json/codex_app_server_protocol.v2.schemas.json | 10 ++++++++++ .../schema/json/v2/ThreadResumeResponse.json | 5 +++++ .../schema/json/v2/ThreadStartResponse.json | 5 +++++ .../schema/typescript/v2/ThreadResumeResponse.ts | 5 ++++- .../schema/typescript/v2/ThreadStartResponse.ts | 5 ++++- codex-rs/app-server-protocol/src/protocol/common.rs | 2 ++ codex-rs/app-server-protocol/src/protocol/v2.rs | 6 ++++++ codex-rs/app-server/src/codex_message_processor.rs | 4 ++++ codex-rs/app-server/tests/suite/v2/thread_resume.rs | 5 ++++- codex-rs/app-server/tests/suite/v2/thread_start.rs | 2 ++ codex-rs/core/src/codex_thread.rs | 2 +- codex-rs/exec/src/lib.rs | 7 ++++++- codex-rs/exec/src/lib_tests.rs | 9 +++++++++ codex-rs/mcp-server/src/outgoing_message.rs | 6 +++--- codex-rs/tui/src/app_server_session.rs | 1 + 18 files changed, 80 insertions(+), 8 deletions(-) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 52ece67a13..bba6e2014f 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -148,6 +148,7 @@ fn sample_thread_start_response( model: &str, ) -> ClientResponsePayload { ClientResponsePayload::ThreadStart(ThreadStartResponse { + session_id: format!("session-{thread_id}"), thread: sample_thread(thread_id, ephemeral), model: model.to_string(), model_provider: "openai".to_string(), @@ -202,6 +203,7 @@ fn sample_thread_resume_response_with_source( source: AppServerSessionSource, ) -> ClientResponsePayload { ClientResponsePayload::ThreadResume(ThreadResumeResponse { + session_id: format!("session-{thread_id}"), thread: sample_thread_with_source(thread_id, ephemeral, source), model: model.to_string(), model_provider: "openai".to_string(), diff --git a/codex-rs/analytics/src/client_tests.rs b/codex-rs/analytics/src/client_tests.rs index 4b6fb54e95..af6d82c0ae 100644 --- a/codex-rs/analytics/src/client_tests.rs +++ b/codex-rs/analytics/src/client_tests.rs @@ -101,6 +101,7 @@ fn sample_permission_profile() -> AppServerPermissionProfile { fn sample_thread_start_response() -> ClientResponsePayload { ClientResponsePayload::ThreadStart(ThreadStartResponse { + session_id: "session-1".to_string(), thread: sample_thread("thread-1"), model: "gpt-5".to_string(), model_provider: "openai".to_string(), @@ -118,6 +119,7 @@ fn sample_thread_start_response() -> ClientResponsePayload { fn sample_thread_resume_response() -> ClientResponsePayload { ClientResponsePayload::ThreadResume(ThreadResumeResponse { + session_id: "session-2".to_string(), thread: sample_thread("thread-2"), model: "gpt-5".to_string(), model_provider: "openai".to_string(), diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index f856b43d66..f524fefce0 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -16754,6 +16754,11 @@ } ] }, + "sessionId": { + "default": "", + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "thread": { "$ref": "#/definitions/v2/Thread" } @@ -17058,6 +17063,11 @@ } ] }, + "sessionId": { + "default": "", + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "thread": { "$ref": "#/definitions/v2/Thread" } diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index c17efe7a45..f9eea7909a 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -14640,6 +14640,11 @@ } ] }, + "sessionId": { + "default": "", + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "thread": { "$ref": "#/definitions/Thread" } @@ -14944,6 +14949,11 @@ } ] }, + "sessionId": { + "default": "", + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "thread": { "$ref": "#/definitions/Thread" } diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 27cf47f2fc..133bd5a6b6 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -2566,6 +2566,11 @@ } ] }, + "sessionId": { + "default": "", + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "thread": { "$ref": "#/definitions/Thread" } diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 7d93606aa4..15f6e0b86a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -2566,6 +2566,11 @@ } ] }, + "sessionId": { + "default": "", + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, "thread": { "$ref": "#/definitions/Thread" } diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts index f7627c07ae..cc4c2440f7 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts @@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { Thread } from "./Thread"; -export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** +export type ThreadResumeResponse = {/** + * Session id shared by threads that belong to the same session tree. + */ +sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** * Instruction source files currently loaded for this thread. */ instructionSources: Array, approvalPolicy: AskForApproval, /** diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts index ce28a4a1d7..962ed2437e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts @@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { Thread } from "./Thread"; -export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** +export type ThreadStartResponse = {/** + * Session id shared by threads that belong to the same session tree. + */ +sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /** * Instruction source files currently loaded for this thread. */ instructionSources: Array, approvalPolicy: AskForApproval, /** diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index c5a7d61f01..857cb460b1 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -2126,6 +2126,7 @@ mod tests { let response = ClientResponse::ThreadStart { request_id: RequestId::Integer(7), response: v2::ThreadStartResponse { + session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(), thread: v2::Thread { id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(), forked_from_id: None, @@ -2166,6 +2167,7 @@ mod tests { "method": "thread/start", "id": 7, "response": { + "sessionId": "67e55044-10b1-426f-9247-bb680e5fe0c7", "thread": { "id": "67e55044-10b1-426f-9247-bb680e5fe0c8", "forkedFromId": null, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index cbcc12c3a7..ad8f909554 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -3638,6 +3638,9 @@ pub struct MockExperimentalMethodResponse { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartResponse { + /// Session id shared by threads that belong to the same session tree. + #[serde(default)] + pub session_id: String, pub thread: Thread, pub model: String, pub model_provider: String, @@ -3752,6 +3755,9 @@ pub struct ThreadResumeParams { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadResumeResponse { + /// Session id shared by threads that belong to the same session tree. + #[serde(default)] + pub session_id: String, pub thread: Thread, pub model: String, pub model_provider: String, diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 6bf8ff8f50..f7ca166593 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -2931,6 +2931,7 @@ impl CodexMessageProcessor { ); let response = ThreadStartResponse { + session_id: session_configured.session_id.to_string(), thread: thread.clone(), model: config_snapshot.model, model_provider: config_snapshot.model_provider_id, @@ -4472,6 +4473,7 @@ impl CodexMessageProcessor { ); let response = ThreadResumeResponse { + session_id: session_configured.session_id.to_string(), thread, model: session_configured.model, model_provider: session_configured.model_provider_id, @@ -8477,8 +8479,10 @@ async fn handle_pending_thread_resume_request( let sandbox = thread_response_sandbox_policy(&permission_profile, cwd.as_path()); let active_permission_profile = thread_response_active_permission_profile(active_permission_profile); + let session_id = conversation.session_configured().session_id.to_string(); let response = ThreadResumeResponse { + session_id, thread, model, model_provider: model_provider_id, diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 48673387b8..a475267fc6 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -263,7 +263,10 @@ async fn thread_resume_tracks_thread_initialized_analytics() -> Result<()> { mcp.read_stream_until_response_message(RequestId::Integer(resume_id)), ) .await??; - let ThreadResumeResponse { thread, .. } = to_response::(resume_resp)?; + let ThreadResumeResponse { + session_id, thread, .. + } = to_response::(resume_resp)?; + assert!(!session_id.is_empty(), "session id should not be empty"); let payload = wait_for_analytics_payload(&server, DEFAULT_READ_TIMEOUT).await?; let event = thread_initialized_event(&payload)?; diff --git a/codex-rs/app-server/tests/suite/v2/thread_start.rs b/codex-rs/app-server/tests/suite/v2/thread_start.rs index d8a50b88a4..6efb5ac8fb 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_start.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_start.rs @@ -78,10 +78,12 @@ async fn thread_start_creates_thread_and_emits_started() -> Result<()> { .await??; let resp_result = resp.result.clone(); let ThreadStartResponse { + session_id, thread, model_provider, .. } = to_response::(resp)?; + assert!(!session_id.is_empty(), "session id should not be empty"); assert!(!thread.id.is_empty(), "thread id should not be empty"); assert!( thread.preview.is_empty(), diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index cc83c0a7c1..17ab3e0ae4 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -381,7 +381,7 @@ impl CodexThread { self.rollout_path.clone() } - pub(crate) fn session_configured(&self) -> SessionConfiguredEvent { + pub fn session_configured(&self) -> SessionConfiguredEvent { self.session_configured.clone() } diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 4070adeb79..9fddb603f1 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -1052,6 +1052,7 @@ fn session_configured_from_thread_start_response( config: &Config, ) -> Result { session_configured_from_thread_response( + &response.session_id, &response.thread.id, response.thread.name.clone(), response.thread.path.clone(), @@ -1076,6 +1077,7 @@ fn session_configured_from_thread_resume_response( config: &Config, ) -> Result { session_configured_from_thread_response( + &response.session_id, &response.thread.id, response.thread.name.clone(), response.thread.path.clone(), @@ -1109,6 +1111,7 @@ fn review_target_to_api(target: ReviewTarget) -> ApiReviewTarget { reason = "session mapping keeps explicit fields" )] fn session_configured_from_thread_response( + session_id: &str, thread_id: &str, thread_name: Option, rollout_path: Option, @@ -1122,11 +1125,13 @@ fn session_configured_from_thread_response( cwd: AbsolutePathBuf, reasoning_effort: Option, ) -> Result { + let session_id = SessionId::from_string(session_id) + .map_err(|err| format!("session id `{session_id}` is invalid: {err}"))?; let thread_id = ThreadId::from_string(thread_id) .map_err(|err| format!("thread id `{thread_id}` is invalid: {err}"))?; Ok(SessionConfiguredEvent { - session_id: SessionId::from(thread_id), + session_id, thread_id, forked_from_id: None, thread_name, diff --git a/codex-rs/exec/src/lib_tests.rs b/codex-rs/exec/src/lib_tests.rs index 648d512689..6596d85b0f 100644 --- a/codex-rs/exec/src/lib_tests.rs +++ b/codex-rs/exec/src/lib_tests.rs @@ -437,6 +437,14 @@ async fn session_configured_from_thread_response_uses_review_policy_from_respons let event = session_configured_from_thread_start_response(&response, &config) .expect("build bootstrap session configured event"); + assert_eq!( + event.session_id.to_string(), + "67e55044-10b1-426f-9247-bb680e5fe0c7" + ); + assert_eq!( + event.thread_id.to_string(), + "67e55044-10b1-426f-9247-bb680e5fe0c8" + ); assert_eq!(event.approvals_reviewer, ApprovalsReviewer::AutoReview); } @@ -461,6 +469,7 @@ async fn session_configured_from_thread_response_uses_permission_profile_from_re fn sample_thread_start_response() -> ThreadStartResponse { ThreadStartResponse { + session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(), thread: codex_app_server_protocol::Thread { id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(), forked_from_id: None, diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index 92a58a8bba..2407ec1551 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -296,7 +296,7 @@ mod tests { let event = Event { id: "1".to_string(), msg: EventMsg::SessionConfigured(SessionConfiguredEvent { - session_id: codex_protocol::SessionId::from(thread_id), + session_id: codex_protocol::SessionId::new(), thread_id, forked_from_id: None, thread_name: None, @@ -342,7 +342,7 @@ mod tests { let thread_id = ThreadId::new(); let rollout_file = NamedTempFile::new()?; let session_configured_event = SessionConfiguredEvent { - session_id: codex_protocol::SessionId::from(thread_id), + session_id: codex_protocol::SessionId::new(), thread_id, forked_from_id: None, thread_name: None, @@ -412,7 +412,7 @@ mod tests { let thread_id = ThreadId::new(); let rollout_file = NamedTempFile::new()?; let session_configured_event = SessionConfiguredEvent { - session_id: codex_protocol::SessionId::from(thread_id), + session_id: codex_protocol::SessionId::new(), thread_id, forked_from_id: None, thread_name: None, diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index c698a76dcc..4166ef3921 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -1791,6 +1791,7 @@ mod tests { let forked_from_id = ThreadId::new(); let read_only_profile = PermissionProfile::read_only(); let response = ThreadResumeResponse { + session_id: ThreadId::new().to_string(), thread: codex_app_server_protocol::Thread { id: thread_id.to_string(), forked_from_id: Some(forked_from_id.to_string()),