diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst index c52bd5525b..8413b5cc50 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst differ diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread_sample.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread_sample.rs index e2fc4c9878..e02dcab80f 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread_sample.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread_sample.rs @@ -1,5 +1,6 @@ //! One model response on an ephemeral fork that inherits the source's configuration and context. +use super::ThreadSource; use crate::JsonSchema; use crate::RequestId; use crate::TS; @@ -21,6 +22,8 @@ pub struct ThreadSampleParams { #[ts(optional = nullable)] pub effort: Option, #[ts(optional = nullable)] + pub thread_source: Option, + #[ts(optional = nullable)] pub turn_trigger: Option, /// Optional metadata to enrich Codex's ResponsesAPI turn metadata. #[ts(optional = nullable)] diff --git a/codex-rs/app-server/src/thread_sample.rs b/codex-rs/app-server/src/thread_sample.rs index 2632fb0909..57c4cc1b0d 100644 --- a/codex-rs/app-server/src/thread_sample.rs +++ b/codex-rs/app-server/src/thread_sample.rs @@ -213,7 +213,12 @@ impl SampleTask { model.supports_reasoning_effort_updates && options.config.model_provider.is_openai() }); options.session_source = Some(SessionSource::Custom("thread_sample".to_string())); - options.thread_source = Some(ThreadSource::Feature("thread_sample".to_string())); + options.thread_source = Some( + params + .thread_source + .map(Into::into) + .unwrap_or_else(|| ThreadSource::Feature("thread_sample".to_string())), + ); options.parent_trace = trace.clone(); if cancellation.is_cancelled() { return Err(internal_error("sample canceled")); diff --git a/codex-rs/app-server/tests/suite/v2/thread_sample.rs b/codex-rs/app-server/tests/suite/v2/thread_sample.rs index 38729e563c..73b81b16ef 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_sample.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_sample.rs @@ -16,6 +16,7 @@ use codex_app_server_protocol::ThreadLoadedListResponse; use codex_app_server_protocol::ThreadSampleCancelResponse; use codex_app_server_protocol::ThreadSampleParams; use codex_app_server_protocol::ThreadSampleResponse; +use codex_app_server_protocol::ThreadSource; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::TurnCompletedNotification; use codex_app_server_protocol::TurnInterruptParams; @@ -304,6 +305,7 @@ async fn sample_preserves_context_and_keeps_source_unchanged( expected_turn_id: completed.turn.id.clone(), prompt: "Continue identically".to_string(), effort: Some(ReasoningEffort::Medium), + thread_source: Some(ThreadSource::Feature("composer_predictions".to_string())), turn_trigger: Some("composer_predictions".to_string()), responsesapi_client_metadata: Some(HashMap::from([ ( @@ -390,6 +392,7 @@ async fn sample_preserves_context_and_keeps_source_unchanged( .header("x-codex-turn-metadata") .expect("turn metadata"), )?; + assert_eq!(metadata["thread_source"], "composer_predictions"); assert_eq!(metadata["turn_trigger"], "composer_predictions"); assert_eq!(metadata["generation_trigger"], "composer_prediction"); let body_metadata: Value = serde_json::from_str( @@ -513,6 +516,7 @@ async fn sample_preserves_effort_baseline( .header("x-codex-turn-metadata") .expect("turn metadata"), )?; + assert_eq!(metadata["thread_source"], "thread_sample"); assert!(metadata.get("turn_trigger").is_none()); Ok(()) }