fix: add more fields to ThreadStartResponse and ThreadResumeResponse

This commit is contained in:
Michael Bolin
2025-11-18 17:04:11 -08:00
parent 526eb3ff82
commit b00a738989
20 changed files with 152 additions and 50 deletions

View File

@@ -118,6 +118,7 @@ use codex_core::parse_cursor;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_core::protocol::ReviewRequest;
use codex_core::protocol::SessionConfiguredEvent;
use codex_core::read_head_for_summary;
use codex_feedback::CodexFeedback;
use codex_login::ServerOptions as LoginServerOptions;
@@ -1303,8 +1304,12 @@ impl CodexMessageProcessor {
match self.conversation_manager.new_conversation(config).await {
Ok(new_conv) => {
let conversation_id = new_conv.conversation_id;
let rollout_path = new_conv.session_configured.rollout_path.clone();
let NewConversation {
conversation_id,
session_configured,
..
} = new_conv;
let rollout_path = session_configured.rollout_path.clone();
let fallback_provider = self.config.model_provider_id.as_str();
// A bit hacky, but the summary contains a lot of useful information for the thread
@@ -1315,7 +1320,7 @@ impl CodexMessageProcessor {
)
.await
{
Ok(summary) => summary_to_thread(summary),
Ok(summary) => summary_to_thread(&summary),
Err(err) => {
self.send_internal_error(
request_id,
@@ -1329,8 +1334,22 @@ impl CodexMessageProcessor {
}
};
let SessionConfiguredEvent {
model,
model_provider_id,
cwd,
approval_policy,
sandbox_policy,
..
} = session_configured;
let response = ThreadStartResponse {
thread: thread.clone(),
model,
model_provider: model_provider_id,
cwd,
approval_policy: approval_policy.into(),
sandbox: sandbox_policy.into(),
reasoning_effort: session_configured.reasoning_effort,
};
// Auto-attach a conversation listener when starting a thread.
@@ -1464,7 +1483,7 @@ impl CodexMessageProcessor {
}
};
let data = summaries.into_iter().map(summary_to_thread).collect();
let data = summaries.iter().map(summary_to_thread).collect();
let response = ThreadListResponse { data, next_cursor };
self.outgoing.send_response(request_id, response).await;
@@ -1624,13 +1643,13 @@ impl CodexMessageProcessor {
);
}
let thread = match read_summary_from_rollout(
let summary = match read_summary_from_rollout(
session_configured.rollout_path.as_path(),
fallback_model_provider.as_str(),
)
.await
{
Ok(summary) => summary_to_thread(summary),
Ok(summary) => summary,
Err(err) => {
self.send_internal_error(
request_id,
@@ -1643,7 +1662,16 @@ impl CodexMessageProcessor {
return;
}
};
let response = ThreadResumeResponse { thread };
let thread = summary_to_thread(&summary);
let response = ThreadResumeResponse {
thread,
model: session_configured.model,
model_provider: session_configured.model_provider_id,
cwd: session_configured.cwd,
approval_policy: session_configured.approval_policy.into(),
sandbox: session_configured.sandbox_policy.into(),
reasoning_effort: session_configured.reasoning_effort,
};
self.outgoing.send_response(request_id, response).await;
}
Err(err) => {
@@ -2923,13 +2951,12 @@ fn parse_datetime(timestamp: Option<&str>) -> Option<DateTime<Utc>> {
})
}
fn summary_to_thread(summary: ConversationSummary) -> Thread {
fn summary_to_thread(summary: &ConversationSummary) -> Thread {
let ConversationSummary {
conversation_id,
path,
preview,
timestamp,
model_provider,
..
} = summary;
@@ -2937,10 +2964,9 @@ fn summary_to_thread(summary: ConversationSummary) -> Thread {
Thread {
id: conversation_id.to_string(),
preview,
model_provider,
preview: preview.clone(),
created_at: created_at.map(|dt| dt.timestamp()).unwrap_or(0),
path,
path: path.clone(),
}
}

View File

@@ -251,7 +251,7 @@ async fn start_default_thread(mcp: &mut McpProcess) -> Result<String> {
mcp.read_stream_until_response_message(RequestId::Integer(thread_req)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(thread_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
Ok(thread.id)
}

View File

@@ -35,7 +35,7 @@ async fn thread_archive_moves_rollout_into_archived_directory() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(start_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
assert!(!thread.id.is_empty());
// Locate the rollout path recorded for this thread id.

View File

@@ -102,7 +102,6 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> {
assert_eq!(data1.len(), 2);
for thread in &data1 {
assert_eq!(thread.preview, "Hello");
assert_eq!(thread.model_provider, "mock_provider");
assert!(thread.created_at > 0);
}
let cursor1 = cursor1.expect("expected nextCursor on first page");
@@ -127,7 +126,6 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> {
assert!(data2.len() <= 2);
for thread in &data2 {
assert_eq!(thread.preview, "Hello");
assert_eq!(thread.model_provider, "mock_provider");
assert!(thread.created_at > 0);
}
assert_eq!(cursor2, None, "expected nextCursor to be null on last page");
@@ -177,7 +175,6 @@ async fn thread_list_respects_provider_filter() -> Result<()> {
assert_eq!(next_cursor, None);
let thread = &data[0];
assert_eq!(thread.preview, "X");
assert_eq!(thread.model_provider, "other_provider");
let expected_ts = chrono::DateTime::parse_from_rfc3339("2025-01-02T11:00:00Z")?.timestamp();
assert_eq!(thread.created_at, expected_ts);

View File

@@ -36,7 +36,7 @@ async fn thread_resume_returns_original_thread() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(start_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
// Resume it via v2 API.
let resume_id = mcp
@@ -50,8 +50,9 @@ async fn thread_resume_returns_original_thread() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(resume_id)),
)
.await??;
let ThreadResumeResponse { thread: resumed } =
to_response::<ThreadResumeResponse>(resume_resp)?;
let ThreadResumeResponse {
thread: resumed, ..
} = to_response::<ThreadResumeResponse>(resume_resp)?;
assert_eq!(resumed, thread);
Ok(())
@@ -77,7 +78,7 @@ async fn thread_resume_prefers_path_over_thread_id() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(start_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
let thread_path = thread.path.clone();
let resume_id = mcp
@@ -93,8 +94,9 @@ async fn thread_resume_prefers_path_over_thread_id() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(resume_id)),
)
.await??;
let ThreadResumeResponse { thread: resumed } =
to_response::<ThreadResumeResponse>(resume_resp)?;
let ThreadResumeResponse {
thread: resumed, ..
} = to_response::<ThreadResumeResponse>(resume_resp)?;
assert_eq!(resumed, thread);
Ok(())
@@ -121,7 +123,7 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(start_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
let history_text = "Hello from history";
let history = vec![ResponseItem::Message {
@@ -147,10 +149,13 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(resume_id)),
)
.await??;
let ThreadResumeResponse { thread: resumed } =
to_response::<ThreadResumeResponse>(resume_resp)?;
let ThreadResumeResponse {
thread: resumed,
model_provider,
..
} = to_response::<ThreadResumeResponse>(resume_resp)?;
assert!(!resumed.id.is_empty());
assert_eq!(resumed.model_provider, "mock_provider");
assert_eq!(model_provider, "mock_provider");
assert_eq!(resumed.preview, history_text);
Ok(())

View File

@@ -40,13 +40,17 @@ async fn thread_start_creates_thread_and_emits_started() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(req_id)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(resp)?;
let ThreadStartResponse {
thread,
model_provider,
..
} = to_response::<ThreadStartResponse>(resp)?;
assert!(!thread.id.is_empty(), "thread id should not be empty");
assert!(
thread.preview.is_empty(),
"new threads should start with an empty preview"
);
assert_eq!(thread.model_provider, "mock_provider");
assert_eq!(model_provider, "mock_provider");
assert!(
thread.created_at > 0,
"created_at should be a positive UNIX timestamp"

View File

@@ -62,7 +62,7 @@ async fn turn_interrupt_aborts_running_turn() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(thread_req)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(thread_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
// Start a turn that triggers a long-running command.
let turn_req = mcp

View File

@@ -57,7 +57,7 @@ async fn turn_start_emits_notifications_and_accepts_model_override() -> Result<(
mcp.read_stream_until_response_message(RequestId::Integer(thread_req)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(thread_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
// Start a turn with only input and thread_id set (no overrides).
let turn_req = mcp
@@ -157,7 +157,7 @@ async fn turn_start_accepts_local_image_input() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(thread_req)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(thread_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
let image_path = codex_home.path().join("image.png");
// No need to actually write the file; we just exercise the input path.
@@ -233,7 +233,7 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(start_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
// turn/start — expect CommandExecutionRequestApproval request from server
let first_turn_id = mcp
@@ -362,7 +362,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread } = to_response::<ThreadStartResponse>(start_resp)?;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
// first turn with workspace-write sandbox and first_cwd
let first_turn = mcp