From c424f468bc01bfd18fee4d9b4f388ed571dab1eb Mon Sep 17 00:00:00 2001 From: lancepereira-oai Date: Wed, 20 May 2026 16:56:49 -0700 Subject: [PATCH] Add exclude output to streamed Responses requests --- codex-rs/codex-api/src/common.rs | 5 +++ codex-rs/codex-api/tests/clients.rs | 2 ++ codex-rs/core/src/client.rs | 1 + codex-rs/core/src/client_common_tests.rs | 28 +++++++++++++++++ codex-rs/core/src/client_tests.rs | 39 ++++++++++++++++++++++++ codex-rs/core/tests/suite/cli_stream.rs | 4 +++ 6 files changed, 79 insertions(+) diff --git a/codex-rs/codex-api/src/common.rs b/codex-rs/codex-api/src/common.rs index 91b251c41f..4bc1eb6b8e 100644 --- a/codex-rs/codex-api/src/common.rs +++ b/codex-rs/codex-api/src/common.rs @@ -179,6 +179,8 @@ pub struct ResponsesApiRequest { pub store: bool, pub stream: bool, pub include: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub exclude: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -203,6 +205,7 @@ impl From<&ResponsesApiRequest> for ResponseCreateWsRequest { store: request.store, stream: request.stream, include: request.include.clone(), + exclude: request.exclude.clone(), service_tier: request.service_tier.clone(), prompt_cache_key: request.prompt_cache_key.clone(), text: request.text.clone(), @@ -227,6 +230,8 @@ pub struct ResponseCreateWsRequest { pub store: bool, pub stream: bool, pub include: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub exclude: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/codex-rs/codex-api/tests/clients.rs b/codex-rs/codex-api/tests/clients.rs index afaf82389f..44dafd07e9 100644 --- a/codex-rs/codex-api/tests/clients.rs +++ b/codex-rs/codex-api/tests/clients.rs @@ -331,6 +331,7 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> { store: false, stream: true, include: Vec::new(), + exclude: Vec::new(), service_tier: None, prompt_cache_key: None, text: None, @@ -432,6 +433,7 @@ async fn azure_default_store_attaches_ids_and_headers() -> Result<()> { store: true, stream: true, include: Vec::new(), + exclude: Vec::new(), service_tier: None, prompt_cache_key: None, text: None, diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index cbffab7b33..15b0e934ca 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -760,6 +760,7 @@ impl ModelClient { store: provider.is_azure_responses_endpoint(), stream: true, include, + exclude: vec!["output".to_string()], service_tier, prompt_cache_key, text, diff --git a/codex-rs/core/src/client_common_tests.rs b/codex-rs/core/src/client_common_tests.rs index 45d4334222..ce5eafb80b 100644 --- a/codex-rs/core/src/client_common_tests.rs +++ b/codex-rs/core/src/client_common_tests.rs @@ -22,6 +22,7 @@ fn serializes_text_verbosity_when_set() { store: false, stream: true, include: vec![], + exclude: vec![], prompt_cache_key: None, service_tier: None, text: Some(TextControls { @@ -69,6 +70,7 @@ fn serializes_text_schema_with_strict_format() { store: false, stream: true, include: vec![], + exclude: vec![], prompt_cache_key: None, service_tier: None, text: Some(text_controls), @@ -130,6 +132,7 @@ fn omits_text_when_not_set() { store: false, stream: true, include: vec![], + exclude: vec![], prompt_cache_key: None, service_tier: None, text: None, @@ -140,6 +143,30 @@ fn omits_text_when_not_set() { assert!(v.get("text").is_none()); } +#[test] +fn omits_exclude_when_not_set() { + let req = ResponsesApiRequest { + model: "gpt-5.1".to_string(), + instructions: "i".to_string(), + input: vec![], + tools: vec![], + tool_choice: "auto".to_string(), + parallel_tool_calls: true, + reasoning: None, + store: false, + stream: true, + include: vec![], + exclude: vec![], + prompt_cache_key: None, + service_tier: None, + text: None, + client_metadata: None, + }; + + let v = serde_json::to_value(&req).expect("json"); + assert!(v.get("exclude").is_none()); +} + #[test] fn serializes_flex_service_tier_when_set() { let req = ResponsesApiRequest { @@ -153,6 +180,7 @@ fn serializes_flex_service_tier_when_set() { store: false, stream: true, include: vec![], + exclude: vec![], prompt_cache_key: None, service_tier: Some(ServiceTier::Flex.to_string()), text: None, diff --git a/codex-rs/core/src/client_tests.rs b/codex-rs/core/src/client_tests.rs index b9d9172c83..1d246397e1 100644 --- a/codex-rs/core/src/client_tests.rs +++ b/codex-rs/core/src/client_tests.rs @@ -23,6 +23,8 @@ use codex_model_provider_info::create_oss_provider_with_base_url; use codex_otel::SessionTelemetry; use codex_protocol::SessionId; use codex_protocol::ThreadId; +use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; +use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ModelInfo; @@ -123,6 +125,43 @@ fn test_session_telemetry() -> SessionTelemetry { ) } +#[tokio::test] +async fn build_responses_request_excludes_completed_output() { + let session = test_model_client(SessionSource::Cli).new_session(); + let prompt = super::Prompt { + input: Vec::new(), + tools: Vec::new(), + parallel_tool_calls: false, + base_instructions: BaseInstructions { + text: "base instructions".to_string(), + }, + personality: None, + output_schema: None, + output_schema_strict: false, + }; + let provider = session + .client + .state + .provider + .api_provider() + .await + .expect("resolve api provider"); + + let request = session + .client + .build_responses_request( + &provider, + &prompt, + &test_model_info(), + /*effort*/ None, + ReasoningSummaryConfig::None, + /*service_tier*/ None, + ) + .expect("build responses request"); + + assert_eq!(request.exclude, vec!["output".to_string()]); +} + #[derive(Default)] struct TagCollectorVisitor { tags: BTreeMap, diff --git a/codex-rs/core/tests/suite/cli_stream.rs b/codex-rs/core/tests/suite/cli_stream.rs index bcf4919737..00127ee4c7 100644 --- a/codex-rs/core/tests/suite/cli_stream.rs +++ b/codex-rs/core/tests/suite/cli_stream.rs @@ -68,6 +68,10 @@ async fn responses_mode_stream_cli() { let request = resp_mock.single_request(); assert_eq!(request.path(), "/v1/responses"); + assert_eq!( + request.body_json()["exclude"], + serde_json::json!(["output"]) + ); // TODO(jif) fix // // Verify a new session rollout was created and is discoverable via list_conversations