diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index cabea808865..26daadf5f51 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -401,6 +401,7 @@ fn sample_turn_steer_request( }, ], responsesapi_client_metadata: None, + additional_context: None, }, } } diff --git a/codex-rs/analytics/src/client_tests.rs b/codex-rs/analytics/src/client_tests.rs index bfb224d8990..c6a928afa3f 100644 --- a/codex-rs/analytics/src/client_tests.rs +++ b/codex-rs/analytics/src/client_tests.rs @@ -103,6 +103,7 @@ fn sample_turn_steer_request() -> ClientRequest { expected_turn_id: "turn-1".to_string(), input: Vec::new(), responsesapi_client_metadata: None, + additional_context: None, }, } } diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index d2bec3d2ed8..552defad1bd 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -12,6 +12,28 @@ ], "type": "string" }, + "AdditionalContextEntry": { + "properties": { + "kind": { + "$ref": "#/definitions/AdditionalContextKind" + }, + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ], + "type": "object" + }, + "AdditionalContextKind": { + "enum": [ + "untrusted", + "application" + ], + "type": "string" + }, "ApprovalsReviewer": { "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", "enum": [ 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 4c6d5a6eeee..ab19f3e770f 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 @@ -5768,6 +5768,28 @@ ], "type": "string" }, + "AdditionalContextEntry": { + "properties": { + "kind": { + "$ref": "#/definitions/v2/AdditionalContextKind" + }, + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ], + "type": "object" + }, + "AdditionalContextKind": { + "enum": [ + "untrusted", + "application" + ], + "type": "string" + }, "AdditionalFileSystemPermissions": { "properties": { "entries": { 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 e74323745ec..b12b0eedd87 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 @@ -164,6 +164,28 @@ ], "type": "string" }, + "AdditionalContextEntry": { + "properties": { + "kind": { + "$ref": "#/definitions/AdditionalContextKind" + }, + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ], + "type": "object" + }, + "AdditionalContextKind": { + "enum": [ + "untrusted", + "application" + ], + "type": "string" + }, "AdditionalFileSystemPermissions": { "properties": { "entries": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index ecea8f19979..ffc130dcc9c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -5,6 +5,28 @@ "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", "type": "string" }, + "AdditionalContextEntry": { + "properties": { + "kind": { + "$ref": "#/definitions/AdditionalContextKind" + }, + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ], + "type": "object" + }, + "AdditionalContextKind": { + "enum": [ + "untrusted", + "application" + ], + "type": "string" + }, "ApprovalsReviewer": { "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json index 1b7cfbf4008..ef05b276730 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json @@ -1,6 +1,28 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { + "AdditionalContextEntry": { + "properties": { + "kind": { + "$ref": "#/definitions/AdditionalContextKind" + }, + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ], + "type": "object" + }, + "AdditionalContextKind": { + "enum": [ + "untrusted", + "application" + ], + "type": "string" + }, "ByteRange": { "properties": { "end": { diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalContextEntry.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalContextEntry.ts new file mode 100644 index 00000000000..8d959269d42 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalContextEntry.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AdditionalContextKind } from "./AdditionalContextKind"; + +export type AdditionalContextEntry = { value: string, kind: AdditionalContextKind, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalContextKind.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalContextKind.ts new file mode 100644 index 00000000000..cd60bd7a8dc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AdditionalContextKind.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AdditionalContextKind = "untrusted" | "application"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 5b4f2ed2831..2a4220cb605 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -7,6 +7,8 @@ export type { AccountUpdatedNotification } from "./AccountUpdatedNotification"; export type { ActivePermissionProfile } from "./ActivePermissionProfile"; export type { AddCreditsNudgeCreditType } from "./AddCreditsNudgeCreditType"; export type { AddCreditsNudgeEmailStatus } from "./AddCreditsNudgeEmailStatus"; +export type { AdditionalContextEntry } from "./AdditionalContextEntry"; +export type { AdditionalContextKind } from "./AdditionalContextKind"; export type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions"; export type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions"; export type { AdditionalPermissionProfile } from "./AdditionalPermissionProfile"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 8f6824f397b..c2b4f24b51d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -3428,6 +3428,7 @@ fn turn_start_params_preserve_explicit_null_service_tier() { thread_id: "thread_123".to_string(), input: vec![], responsesapi_client_metadata: None, + additional_context: None, environments: None, cwd: None, runtime_workspace_roots: None, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs index 50660af1d7d..ab5e59a4644 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs @@ -41,6 +41,23 @@ pub struct TurnEnvironmentParams { pub cwd: AbsolutePathBuf, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "lowercase")] +#[ts(rename_all = "lowercase")] +#[ts(export_to = "v2/")] +pub enum AdditionalContextKind { + Untrusted, + Application, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct AdditionalContextEntry { + pub value: String, + pub kind: AdditionalContextKind, +} + #[derive( Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi, )] @@ -53,6 +70,10 @@ pub struct TurnStartParams { #[experimental("turn/start.responsesapiClientMetadata")] #[ts(optional = nullable)] pub responsesapi_client_metadata: Option>, + /// Optional client-provided context fragments keyed by an opaque source identifier. + #[experimental("turn/start.additionalContext")] + #[ts(optional = nullable)] + pub additional_context: Option>, /// Optional turn-scoped environments. /// /// Omitted uses the thread sticky environments. Empty disables @@ -141,6 +162,10 @@ pub struct TurnSteerParams { #[experimental("turn/steer.responsesapiClientMetadata")] #[ts(optional = nullable)] pub responsesapi_client_metadata: Option>, + /// Optional client-provided context fragments keyed by an opaque source identifier. + #[experimental("turn/steer.additionalContext")] + #[ts(optional = nullable)] + pub additional_context: Option>, /// Required active turn id precondition. The request fails when it does not /// match the currently active turn. pub expected_turn_id: String, diff --git a/codex-rs/app-server/src/message_processor_tracing_tests.rs b/codex-rs/app-server/src/message_processor_tracing_tests.rs index a9625d3086c..97c497b8771 100644 --- a/codex-rs/app-server/src/message_processor_tracing_tests.rs +++ b/codex-rs/app-server/src/message_processor_tracing_tests.rs @@ -658,6 +658,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> { text_elements: Vec::new(), }], responsesapi_client_metadata: None, + additional_context: None, cwd: None, runtime_workspace_roots: None, approval_policy: None, diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index d0958e80ab9..66ab8e3a1da 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -25,6 +25,8 @@ use codex_app_server_protocol::AccountLoginCompletedNotification; use codex_app_server_protocol::AccountUpdatedNotification; use codex_app_server_protocol::AddCreditsNudgeCreditType; use codex_app_server_protocol::AddCreditsNudgeEmailStatus; +use codex_app_server_protocol::AdditionalContextEntry; +use codex_app_server_protocol::AdditionalContextKind; use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::AppListUpdatedNotification; use codex_app_server_protocol::AppSummary; @@ -421,6 +423,7 @@ use codex_thread_store::ThreadStore; use codex_thread_store::ThreadStoreError; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP; +use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::io::Error as IoError; diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index ff2825be678..975ff22d071 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -1,4 +1,6 @@ use super::*; +use codex_protocol::protocol::AdditionalContextEntry as CoreAdditionalContextEntry; +use codex_protocol::protocol::AdditionalContextKind as CoreAdditionalContextKind; #[derive(Clone)] pub(crate) struct TurnRequestProcessor { @@ -30,6 +32,29 @@ fn resolve_runtime_workspace_roots( resolved_roots } +fn map_additional_context( + additional_context: Option>, +) -> BTreeMap { + additional_context + .unwrap_or_default() + .into_iter() + .map(|(key, entry)| { + ( + key, + CoreAdditionalContextEntry { + value: entry.value, + kind: match entry.kind { + AdditionalContextKind::Untrusted => CoreAdditionalContextKind::Untrusted, + AdditionalContextKind::Application => { + CoreAdditionalContextKind::Application + } + }, + }, + ) + }) + .collect() +} + struct ThreadSettingsBuildParams { method: &'static str, cwd: Option, @@ -391,6 +416,7 @@ impl TurnRequestProcessor { .into_iter() .map(V2UserInput::into_core) .collect(); + let additional_context = map_additional_context(params.additional_context); let turn_has_input = !mapped_items.is_empty(); let thread_settings = self .build_thread_settings_overrides( @@ -419,6 +445,7 @@ impl TurnRequestProcessor { environments: environment_selections, final_output_json_schema: params.output_schema, responsesapi_client_metadata: params.responsesapi_client_metadata, + additional_context, thread_settings, }; let turn_id = self @@ -746,10 +773,12 @@ impl TurnRequestProcessor { .into_iter() .map(V2UserInput::into_core) .collect(); + let additional_context = map_additional_context(params.additional_context); let turn_id = thread .steer_input( mapped_items, + additional_context, Some(¶ms.expected_turn_id), params.responsesapi_client_metadata, ) diff --git a/codex-rs/app-server/tests/suite/v2/client_metadata.rs b/codex-rs/app-server/tests/suite/v2/client_metadata.rs index 8d68888e7e2..80f41d7b113 100644 --- a/codex-rs/app-server/tests/suite/v2/client_metadata.rs +++ b/codex-rs/app-server/tests/suite/v2/client_metadata.rs @@ -179,6 +179,7 @@ async fn turn_steer_updates_client_metadata_on_follow_up_responses_request_v2() text_elements: Vec::new(), }], responsesapi_client_metadata: Some(steer_metadata.clone()), + additional_context: None, expected_turn_id: turn_id.clone(), }) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 236d57a0eee..88596b46399 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -16,6 +16,8 @@ use app_test_support::write_mock_responses_config_toml_with_chatgpt_base_url; use app_test_support::write_models_cache; use codex_app_server::INPUT_TOO_LARGE_ERROR_CODE; use codex_app_server::INVALID_PARAMS_ERROR_CODE; +use codex_app_server_protocol::AdditionalContextEntry; +use codex_app_server_protocol::AdditionalContextKind; use codex_app_server_protocol::ByteRange; use codex_app_server_protocol::ClientInfo; use codex_app_server_protocol::CollabAgentStatus; @@ -301,6 +303,82 @@ async fn turn_start_with_empty_input_runs_model_request() -> Result<()> { Ok(()) } +#[tokio::test] +async fn turn_start_additional_context_flows_to_model_input() -> Result<()> { + let responses = vec![create_final_assistant_message_sse_response("Done")?]; + let server = create_mock_responses_server_sequence_unchecked(responses).await; + + let codex_home = TempDir::new()?; + create_config_toml( + codex_home.path(), + &server.uri(), + "never", + &BTreeMap::default(), + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; + + let turn_req = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id, + input: vec![V2UserInput::Text { + text: "inspect tab".to_string(), + text_elements: Vec::new(), + }], + additional_context: Some(HashMap::from([( + "custom_source".to_string(), + AdditionalContextEntry { + value: "source value".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + )])), + ..Default::default() + }) + .await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req)), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let requests = server + .received_requests() + .await + .context("failed to fetch received requests")?; + let request = requests + .iter() + .find(|request| request.url.path().ends_with("/responses")) + .context("expected model request")?; + let body = request + .body_json::() + .context("request body should be JSON")?; + assert!( + body.to_string() + .contains("source value") + ); + + Ok(()) +} + #[tokio::test] async fn turn_start_sends_originator_header() -> Result<()> { let responses = vec![create_final_assistant_message_sse_response("Done")?]; @@ -2069,6 +2147,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> { text_elements: Vec::new(), }], responsesapi_client_metadata: None, + additional_context: None, cwd: Some(first_cwd.clone()), runtime_workspace_roots: None, approval_policy: Some(codex_app_server_protocol::AskForApproval::Never), @@ -2111,6 +2190,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> { text_elements: Vec::new(), }], responsesapi_client_metadata: None, + additional_context: None, cwd: Some(second_cwd.clone()), runtime_workspace_roots: None, approval_policy: Some(codex_app_server_protocol::AskForApproval::Never), diff --git a/codex-rs/app-server/tests/suite/v2/turn_steer.rs b/codex-rs/app-server/tests/suite/v2/turn_steer.rs index a92b2db5286..66ac33972b1 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_steer.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_steer.rs @@ -1,5 +1,6 @@ #![cfg(unix)] +use anyhow::Context; use anyhow::Result; use app_test_support::McpProcess; use app_test_support::create_mock_responses_server_sequence; @@ -9,6 +10,8 @@ use app_test_support::to_response; use app_test_support::write_mock_responses_config_toml_with_chatgpt_base_url; use codex_app_server::INPUT_TOO_LARGE_ERROR_CODE; use codex_app_server::INVALID_PARAMS_ERROR_CODE; +use codex_app_server_protocol::AdditionalContextEntry; +use codex_app_server_protocol::AdditionalContextKind; use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCNotification; use codex_app_server_protocol::JSONRPCResponse; @@ -21,6 +24,8 @@ use codex_app_server_protocol::TurnSteerParams; use codex_app_server_protocol::TurnSteerResponse; use codex_app_server_protocol::UserInput as V2UserInput; use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS; +use serde_json::Value; +use std::collections::HashMap; use tempfile::TempDir; use tokio::time::timeout; @@ -67,6 +72,7 @@ async fn turn_steer_requires_active_turn() -> Result<()> { text_elements: Vec::new(), }], responsesapi_client_metadata: None, + additional_context: None, expected_turn_id: "turn-does-not-exist".to_string(), }) .await?; @@ -176,6 +182,7 @@ async fn turn_steer_rejects_oversized_text_input() -> Result<()> { text_elements: Vec::new(), }], responsesapi_client_metadata: None, + additional_context: None, expected_turn_id: turn.id.clone(), }) .await?; @@ -284,6 +291,7 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { text_elements: Vec::new(), }], responsesapi_client_metadata: None, + additional_context: None, expected_turn_id: turn.id.clone(), }) .await?; @@ -312,3 +320,118 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn turn_steer_rejects_context_only_input_without_merging_context() -> Result<()> { + let tmp = TempDir::new()?; + let codex_home = tmp.path().join("codex_home"); + std::fs::create_dir(&codex_home)?; + let working_directory = tmp.path().join("workdir"); + std::fs::create_dir(&working_directory)?; + + let server = create_mock_responses_server_sequence_unchecked(vec![ + create_shell_command_sse_response( + vec!["sleep".to_string(), "1".to_string()], + Some(&working_directory), + Some(10_000), + "call_sleep", + )?, + app_test_support::create_final_assistant_message_sse_response("Done")?, + ]) + .await; + write_mock_responses_config_toml_with_chatgpt_base_url( + &codex_home, + &server.uri(), + &server.uri(), + )?; + mount_analytics_capture(&server, &codex_home).await?; + + let mut mcp = McpProcess::new_without_managed_config(&codex_home).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; + + let turn_req = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id.clone(), + input: vec![V2UserInput::Text { + text: "run sleep".to_string(), + text_elements: Vec::new(), + }], + cwd: Some(working_directory), + ..Default::default() + }) + .await?; + let turn_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req)), + ) + .await??; + let TurnStartResponse { turn } = to_response::(turn_resp)?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/started"), + ) + .await??; + + let additional_context = Some(HashMap::from([( + "browser_info".to_string(), + AdditionalContextEntry { + value: "tab one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + )])); + let steer_req = mcp + .send_turn_steer_request(TurnSteerParams { + thread_id: thread.id.clone(), + input: Vec::new(), + responsesapi_client_metadata: None, + additional_context, + expected_turn_id: turn.id, + }) + .await?; + let steer_error: JSONRPCError = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(steer_req)), + ) + .await??; + assert_eq!(steer_error.error.code, -32600); + assert_eq!(steer_error.error.message, "input must not be empty"); + + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let requests = server + .received_requests() + .await + .context("failed to fetch received requests")?; + let response_requests = requests + .iter() + .filter(|request| request.url.path().ends_with("/responses")) + .collect::>(); + assert_eq!(response_requests.len(), 2); + let body = response_requests[1] + .body_json::() + .context("request body should be JSON")?; + assert!( + !body + .to_string() + .contains("tab one") + ); + + Ok(()) +} diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index ed39df1548a..8209272f102 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -444,6 +444,7 @@ async fn send_input_submits_user_message() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }, ); @@ -598,6 +599,7 @@ async fn spawn_agent_creates_thread_and_sends_prompt() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }, ); @@ -770,6 +772,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }, ); diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 4ed6b2368cb..b27cf745c96 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -192,6 +192,7 @@ pub(crate) async fn run_codex_thread_one_shot( items: input, final_output_json_schema, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index 4b77efdac98..1113a12c8aa 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -21,6 +21,7 @@ use codex_protocol::models::PermissionProfile; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ReasoningEffort; +use codex_protocol::protocol::AdditionalContextEntry; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::Event; use codex_protocol::protocol::Op; @@ -41,6 +42,7 @@ use codex_thread_store::ThreadStoreError; use codex_thread_store::ThreadStoreResult; use codex_utils_absolute_path::AbsolutePathBuf; use rmcp::model::ReadResourceRequestParams; +use std::collections::BTreeMap; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -236,11 +238,17 @@ impl CodexThread { pub async fn steer_input( &self, input: Vec, + additional_context: BTreeMap, expected_turn_id: Option<&str>, responsesapi_client_metadata: Option>, ) -> Result { self.codex - .steer_input(input, expected_turn_id, responsesapi_client_metadata) + .steer_input( + input, + additional_context, + expected_turn_id, + responsesapi_client_metadata, + ) .await } diff --git a/codex-rs/core/src/context/contextual_user_message.rs b/codex-rs/core/src/context/contextual_user_message.rs index c7ada2317f0..940a938fcee 100644 --- a/codex-rs/core/src/context/contextual_user_message.rs +++ b/codex-rs/core/src/context/contextual_user_message.rs @@ -2,6 +2,7 @@ use codex_protocol::items::HookPromptItem; use codex_protocol::items::parse_hook_prompt_fragment; use codex_protocol::models::ContentItem; +use super::AdditionalContextUserFragment; use super::EnvironmentContext; use super::FragmentRegistration; use super::FragmentRegistrationProxy; @@ -19,6 +20,8 @@ static USER_INSTRUCTIONS_REGISTRATION: FragmentRegistrationProxy = FragmentRegistrationProxy::new(); +static ADDITIONAL_CONTEXT_REGISTRATION: FragmentRegistrationProxy = + FragmentRegistrationProxy::new(); static SKILL_INSTRUCTIONS_REGISTRATION: FragmentRegistrationProxy = FragmentRegistrationProxy::new(); static USER_SHELL_COMMAND_REGISTRATION: FragmentRegistrationProxy = @@ -42,6 +45,7 @@ static LEGACY_MODEL_MISMATCH_WARNING_REGISTRATION: FragmentRegistrationProxy< static CONTEXTUAL_USER_FRAGMENTS: &[&dyn FragmentRegistration] = &[ &USER_INSTRUCTIONS_REGISTRATION, &ENVIRONMENT_CONTEXT_REGISTRATION, + &ADDITIONAL_CONTEXT_REGISTRATION, &SKILL_INSTRUCTIONS_REGISTRATION, &USER_SHELL_COMMAND_REGISTRATION, &TURN_ABORTED_REGISTRATION, diff --git a/codex-rs/core/src/context/fragment.rs b/codex-rs/core/src/context/fragment.rs index 4a9e284d126..5c500fe8efd 100644 --- a/codex-rs/core/src/context/fragment.rs +++ b/codex-rs/core/src/context/fragment.rs @@ -1,4 +1,5 @@ use codex_protocol::models::ContentItem; +use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use std::marker::PhantomData; @@ -81,6 +82,19 @@ pub trait ContextualUserFragment { phase: None, } } + + fn into_response_input_item(self) -> ResponseInputItem + where + Self: Sized, + { + ResponseInputItem::Message { + role: Self::role().to_string(), + content: vec![ContentItem::InputText { + text: self.render(), + }], + phase: None, + } + } } fn matches_marked_text(start_marker: &str, end_marker: &str, text: &str) -> bool { diff --git a/codex-rs/core/src/context/fragments.rs b/codex-rs/core/src/context/fragments.rs new file mode 100644 index 00000000000..e23f96f59f3 --- /dev/null +++ b/codex-rs/core/src/context/fragments.rs @@ -0,0 +1,92 @@ +use super::ContextualUserFragment; +use codex_utils_string::truncate_middle_with_token_budget; + +const MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS: usize = 1_000; +const ADDITIONAL_CONTEXT_END_MARKER_SUFFIX: &str = ">"; +const ADDITIONAL_CONTEXT_START_MARKER_PREFIX: &str = " Self { + Self { key, value } + } +} + +impl ContextualUserFragment for AdditionalContextUserFragment { + fn role() -> &'static str { + "user" + } + + fn markers(&self) -> (&'static str, &'static str) { + Self::type_markers() + } + + fn type_markers() -> (&'static str, &'static str) { + ( + ADDITIONAL_CONTEXT_START_MARKER_PREFIX, + ADDITIONAL_CONTEXT_END_MARKER_SUFFIX, + ) + } + + fn matches_text(text: &str) -> bool { + let trimmed = text.trim(); + let Some(rest) = trimmed.strip_prefix(ADDITIONAL_CONTEXT_START_MARKER_PREFIX) else { + return false; + }; + let Some((key, value_and_close)) = rest.split_once(ADDITIONAL_CONTEXT_END_MARKER_SUFFIX) + else { + return false; + }; + + value_and_close.ends_with(&format!("")) + } + + fn body(&self) -> String { + additional_context_body(&self.key, &self.value) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct AdditionalContextDeveloperFragment { + key: String, + value: String, +} + +impl AdditionalContextDeveloperFragment { + pub(crate) fn new(key: String, value: String) -> Self { + Self { key, value } + } +} + +impl ContextualUserFragment for AdditionalContextDeveloperFragment { + fn role() -> &'static str { + "developer" + } + + fn markers(&self) -> (&'static str, &'static str) { + Self::type_markers() + } + + fn type_markers() -> (&'static str, &'static str) { + ("", "") + } + + fn body(&self) -> String { + additional_context_developer_body(&self.key, &self.value) + } +} + +fn additional_context_body(key: &str, value: &str) -> String { + let value = truncate_middle_with_token_budget(value, MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS).0; + format!("{key}>{value} String { + let value = truncate_middle_with_token_budget(value, MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS).0; + format!("<{key}>{value}") +} diff --git a/codex-rs/core/src/context/goal_context.rs b/codex-rs/core/src/context/goal_context.rs index 13892bf4666..ad4c47ef062 100644 --- a/codex-rs/core/src/context/goal_context.rs +++ b/codex-rs/core/src/context/goal_context.rs @@ -1,8 +1,6 @@ //! Hidden user-context fragment for runtime-owned goal steering prompts. use super::ContextualUserFragment; -use codex_protocol::models::ContentItem; -use codex_protocol::models::ResponseInputItem; /// Hidden runtime-owned goal steering context injected into model input. #[derive(Debug, Clone, PartialEq)] @@ -17,17 +15,6 @@ impl GoalContext { prompt: prompt.into(), } } - - /// Converts the registered fragment into an active-turn injectable item. - pub fn into_response_input_item(self) -> ResponseInputItem { - ResponseInputItem::Message { - role: ::role().to_string(), - content: vec![ContentItem::InputText { - text: self.render(), - }], - phase: None, - } - } } impl ContextualUserFragment for GoalContext { diff --git a/codex-rs/core/src/context/mod.rs b/codex-rs/core/src/context/mod.rs index 99575f6309e..6d88df87e27 100644 --- a/codex-rs/core/src/context/mod.rs +++ b/codex-rs/core/src/context/mod.rs @@ -8,6 +8,7 @@ mod collaboration_mode_instructions; mod contextual_user_message; mod environment_context; mod fragment; +mod fragments; mod goal_context; mod guardian_followup_review_reminder; mod hook_additional_context; @@ -40,6 +41,8 @@ pub(crate) use environment_context::EnvironmentContext; pub use fragment::ContextualUserFragment; pub(crate) use fragment::FragmentRegistration; pub(crate) use fragment::FragmentRegistrationProxy; +pub(crate) use fragments::AdditionalContextDeveloperFragment; +pub(crate) use fragments::AdditionalContextUserFragment; pub use goal_context::GoalContext; pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder; pub(crate) use hook_additional_context::HookAdditionalContext; diff --git a/codex-rs/core/src/goals.rs b/codex-rs/core/src/goals.rs index 75b84093e70..6df681d4b01 100644 --- a/codex-rs/core/src/goals.rs +++ b/codex-rs/core/src/goals.rs @@ -5,6 +5,7 @@ //! events, and owns helper hooks used by goal lifecycle behavior. use crate::StateDbHandle; +use crate::context::ContextualUserFragment; use crate::context::GoalContext; use crate::session::TurnInput; use crate::session::session::Session; diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index 15d6c142cae..e56d5ce170d 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -714,6 +714,7 @@ async fn run_review_on_session( environments: None, final_output_json_schema: Some(params.schema.clone()), responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { #[allow(deprecated)] cwd: Some(params.parent_turn.cwd.to_path_buf()), diff --git a/codex-rs/core/src/session/handlers.rs b/codex-rs/core/src/session/handlers.rs index b1e36b03472..cf46ecba9ac 100644 --- a/codex-rs/core/src/session/handlers.rs +++ b/codex-rs/core/src/session/handlers.rs @@ -10,6 +10,7 @@ use tracing::debug_span; use tracing::info_span; use crate::session::SteerInputError; +use crate::session::TurnInput; use crate::session::session::Session; use crate::session::session::SessionSettingsUpdate; @@ -194,6 +195,7 @@ pub(super) async fn user_input_or_turn_inner( environments, final_output_json_schema, responsesapi_client_metadata, + additional_context, thread_settings, } = op else { @@ -224,6 +226,7 @@ pub(super) async fn user_input_or_turn_inner( let accepted_items = match sess .steer_input( items.clone(), + additional_context.clone(), /*expected_turn_id*/ None, responsesapi_client_metadata.clone(), ) @@ -246,9 +249,20 @@ pub(super) async fn user_input_or_turn_inner( ) .await; let accepted_items = items.clone(); + let additional_context_input = { + let mut state = sess.state.lock().await; + state.additional_context.merge(additional_context) + }; + let mut task_input = additional_context_input + .into_iter() + .map(TurnInput::ResponseInputItem) + .collect::>(); + if !items.is_empty() { + task_input.push(TurnInput::UserInput(items)); + } sess.spawn_task( Arc::clone(¤t_context), - items, + task_input, crate::tasks::RegularTask::new(), ) .await; diff --git a/codex-rs/core/src/session/input_queue.rs b/codex-rs/core/src/session/input_queue.rs index 98e912c7e57..620c410eb81 100644 --- a/codex-rs/core/src/session/input_queue.rs +++ b/codex-rs/core/src/session/input_queue.rs @@ -156,13 +156,13 @@ impl InputQueue { .accept_mailbox_delivery_for_current_turn(); } - pub(super) async fn push_pending_input_and_accept_mailbox_delivery_for_turn_state( + pub(super) async fn extend_pending_input_and_accept_mailbox_delivery_for_turn_state( &self, turn_state: &Mutex, - input: TurnInput, + input: Vec, ) { let mut turn_state = turn_state.lock().await; - turn_state.pending_input.items.push(input); + turn_state.pending_input.items.extend(input); turn_state.accept_mailbox_delivery_for_current_turn(); } diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 67cc5484fc3..1876cbf9801 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::fmt::Debug; @@ -97,6 +98,7 @@ use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ModelPreset; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; +use codex_protocol::protocol::AdditionalContextEntry; use codex_protocol::protocol::FileChange; use codex_protocol::protocol::HasLegacyEvent; use codex_protocol::protocol::InterAgentCommunication; @@ -742,11 +744,17 @@ impl Codex { pub async fn steer_input( &self, input: Vec, + additional_context: BTreeMap, expected_turn_id: Option<&str>, responsesapi_client_metadata: Option>, ) -> Result { self.session - .steer_input(input, expected_turn_id, responsesapi_client_metadata) + .steer_input( + input, + additional_context, + expected_turn_id, + responsesapi_client_metadata, + ) .await } @@ -1086,6 +1094,7 @@ impl Session { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }, /*mirror_user_text_to_realtime*/ None, @@ -3152,6 +3161,7 @@ impl Session { pub async fn steer_input( &self, input: Vec, + additional_context: BTreeMap, expected_turn_id: Option<&str>, responsesapi_client_metadata: Option>, ) -> Result { @@ -3192,6 +3202,11 @@ impl Session { return Err(SteerInputError::EmptyInput); } + let additional_context_input = { + let mut state = self.state.lock().await; + state.additional_context.merge(additional_context) + }; + if let Some(responsesapi_client_metadata) = responsesapi_client_metadata { active_task .turn_context @@ -3199,10 +3214,15 @@ impl Session { .set_responsesapi_client_metadata(responsesapi_client_metadata); } + let mut pending_input = additional_context_input + .into_iter() + .map(TurnInput::ResponseInputItem) + .collect::>(); + pending_input.push(TurnInput::UserInput(input)); self.input_queue - .push_pending_input_and_accept_mailbox_delivery_for_turn_state( + .extend_pending_input_and_accept_mailbox_delivery_for_turn_state( active_turn.turn_state.as_ref(), - TurnInput::UserInput(input), + pending_input, ) .await; Ok(active_turn_id.clone()) diff --git a/codex-rs/core/src/session/review.rs b/codex-rs/core/src/session/review.rs index 059dc8abbd7..32122873cb2 100644 --- a/codex-rs/core/src/session/review.rs +++ b/codex-rs/core/src/session/review.rs @@ -132,11 +132,11 @@ pub(super) async fn spawn_review_thread( }; // Seed the child task with the review prompt as the initial user message. - let input: Vec = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: review_prompt, // Review prompt is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), - }]; + }])]; let tc = Arc::new(review_turn_context); tc.turn_metadata_state.spawn_git_enrichment_task(); // TODO(ccunningham): Review turns currently rely on `spawn_task` for TurnComplete but do not diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 428e7622446..06381684223 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -2287,6 +2287,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2333,6 +2334,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: ThreadSettingsOverrides { approval_policy: Some(AskForApproval::Never), collaboration_mode: Some(collaboration_mode), @@ -5430,6 +5432,7 @@ fn op_kind_for_input_and_context_ops() { items: vec![], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), } .kind(), @@ -5460,6 +5463,7 @@ async fn user_turn_updates_approvals_reviewer() { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(config.cwd.to_path_buf()), approval_policy: Some(config.permissions.approval_policy.value()), @@ -5781,10 +5785,10 @@ async fn spawn_task_turn_span_inherits_dispatch_trace_context() { async { sess.spawn_task( Arc::clone(&tc), - vec![UserInput::Text { + vec![TurnInput::UserInput(vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }], + }])], TraceCaptureTask { captured_trace: Arc::clone(&captured_trace), }, @@ -6592,10 +6596,10 @@ async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_task let (sess, tc, _rx) = make_session_and_context_with_rx().await; sess.set_previous_turn_settings(/*previous_turn_settings*/ None) .await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task( Arc::clone(&tc), @@ -7861,10 +7865,10 @@ impl SessionTask for GuardianDeniedApprovalTask { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn guardian_auto_review_interrupts_after_three_consecutive_denials() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "trigger guardian denials".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task(Arc::clone(&tc), input, GuardianDeniedApprovalTask) .await; @@ -7892,10 +7896,10 @@ async fn guardian_auto_review_interrupts_after_three_consecutive_denials() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn guardian_helper_review_interrupts_after_three_consecutive_denials() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "keep turn active for helper reviews".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task( Arc::clone(&tc), input, @@ -7952,10 +7956,10 @@ async fn guardian_helper_review_interrupts_after_three_consecutive_denials() { #[test_log::test] async fn abort_regular_task_emits_turn_aborted_only() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task( Arc::clone(&tc), input, @@ -7985,10 +7989,10 @@ async fn abort_regular_task_emits_turn_aborted_only() { #[tokio::test] async fn abort_gracefully_emits_turn_aborted_only() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task( Arc::clone(&tc), input, @@ -8018,10 +8022,10 @@ async fn abort_gracefully_emits_turn_aborted_only() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task( Arc::clone(&tc), input, @@ -8044,6 +8048,7 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() }]; sess.steer_input( pending_user_input.clone(), + /*additional_context*/ Default::default(), Some(&tc.sub_id), /*responsesapi_client_metadata*/ None, ) @@ -8140,7 +8145,10 @@ async fn steer_input_requires_active_turn() { let err = sess .steer_input( - input, /*expected_turn_id*/ None, /*responsesapi_client_metadata*/ None, + input, + /*additional_context*/ Default::default(), + /*expected_turn_id*/ None, + /*responsesapi_client_metadata*/ None, ) .await .expect_err("steering without active turn should fail"); @@ -8151,10 +8159,10 @@ async fn steer_input_requires_active_turn() { #[tokio::test] async fn steer_input_enforces_expected_turn_id() { let (sess, tc, _rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task( Arc::clone(&tc), input, @@ -8172,6 +8180,7 @@ async fn steer_input_enforces_expected_turn_id() { let err = sess .steer_input( steer_input, + /*additional_context*/ Default::default(), Some("different-turn-id"), /*responsesapi_client_metadata*/ None, ) @@ -8196,10 +8205,10 @@ async fn steer_input_rejects_non_regular_turns() { (TaskKind::Compact, NonSteerableTurnKind::Compact), ] { let (sess, _tc, _rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }]; + }])]; let turn_context = sess.new_default_turn_with_sub_id("turn".to_string()).await; sess.spawn_task( turn_context, @@ -8218,6 +8227,7 @@ async fn steer_input_rejects_non_regular_turns() { let err = sess .steer_input( steer_input, + /*additional_context*/ Default::default(), /*expected_turn_id*/ None, /*responsesapi_client_metadata*/ None, ) @@ -8233,10 +8243,10 @@ async fn steer_input_rejects_non_regular_turns() { #[tokio::test] async fn steer_input_returns_active_turn_id() { let (sess, tc, _rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task( Arc::clone(&tc), input, @@ -8254,6 +8264,7 @@ async fn steer_input_returns_active_turn_id() { let turn_id = sess .steer_input( steer_input, + /*additional_context*/ Default::default(), Some(&tc.sub_id), /*responsesapi_client_metadata*/ None, ) @@ -8478,6 +8489,7 @@ async fn active_goal_continuation_runs_again_after_no_tool_turn() -> anyhow::Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -8583,6 +8595,7 @@ async fn pending_request_user_input_does_not_spawn_extra_goal_continuation() -> }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -9130,6 +9143,7 @@ async fn completed_goal_accounts_current_turn_tokens_before_tool_response() -> a }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -9296,6 +9310,7 @@ async fn steered_input_reopens_mailbox_delivery_for_current_turn() { text: "follow up".to_string(), text_elements: Vec::new(), }], + /*additional_context*/ Default::default(), Some(&tc.sub_id), /*responsesapi_client_metadata*/ None, ) @@ -9345,6 +9360,7 @@ async fn stale_defer_mailbox_delivery_does_not_override_steered_input() { text: "follow up".to_string(), text_elements: Vec::new(), }], + /*additional_context*/ Default::default(), Some(&tc.sub_id), /*responsesapi_client_metadata*/ None, ) @@ -9426,10 +9442,10 @@ async fn tool_calls_reopen_mailbox_delivery_for_current_turn() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn abort_review_task_emits_exited_then_aborted_and_records_history() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![UserInput::Text { + let input = vec![TurnInput::UserInput(vec![UserInput::Text { text: "start review".to_string(), text_elements: Vec::new(), - }]; + }])]; sess.spawn_task(Arc::clone(&tc), input, ReviewTask::new()) .await; diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index 7d5433ff3e7..5dcc7ff831e 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -406,14 +406,16 @@ async fn run_hooks_and_record_inputs( input: &[TurnInput], ) -> bool { let mut blocked_input = false; - let mut accepted_input = false; + let mut accepted_user_input = false; for input_item in input { let hook_outcome = inspect_pending_input(sess, turn_context, input_item).await; if hook_outcome.should_stop { blocked_input = true; record_additional_contexts(sess, turn_context, hook_outcome.additional_contexts).await; } else { - accepted_input = true; + if matches!(input_item, TurnInput::UserInput(items) if !items.is_empty()) { + accepted_user_input = true; + } record_pending_input( sess, turn_context, @@ -423,7 +425,7 @@ async fn run_hooks_and_record_inputs( .await; } } - blocked_input && !accepted_input + blocked_input && !accepted_user_input } #[expect( diff --git a/codex-rs/core/src/state/additional_context.rs b/codex-rs/core/src/state/additional_context.rs new file mode 100644 index 00000000000..eb5727ac23c --- /dev/null +++ b/codex-rs/core/src/state/additional_context.rs @@ -0,0 +1,37 @@ +use std::collections::BTreeMap; + +use crate::context::AdditionalContextDeveloperFragment; +use crate::context::AdditionalContextUserFragment; +use crate::context::ContextualUserFragment; +use codex_protocol::models::ResponseInputItem; +use codex_protocol::protocol::AdditionalContextEntry; +use codex_protocol::protocol::AdditionalContextKind; + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct AdditionalContextStore { + values: BTreeMap, +} + +impl AdditionalContextStore { + pub(crate) fn merge( + &mut self, + values: BTreeMap, + ) -> Vec { + let fragments = values + .iter() + .filter(|(key, value)| self.values.get(*key) != Some(*value)) + .map(|(key, entry)| match entry.kind { + AdditionalContextKind::Untrusted => { + AdditionalContextUserFragment::new(key.clone(), entry.value.clone()) + .into_response_input_item() + } + AdditionalContextKind::Application => { + AdditionalContextDeveloperFragment::new(key.clone(), entry.value.clone()) + .into_response_input_item() + } + }) + .collect(); + self.values = values; + fragments + } +} diff --git a/codex-rs/core/src/state/mod.rs b/codex-rs/core/src/state/mod.rs index 3122ec5f259..c693221bedc 100644 --- a/codex-rs/core/src/state/mod.rs +++ b/codex-rs/core/src/state/mod.rs @@ -1,8 +1,10 @@ +mod additional_context; mod auto_compact_window; mod service; mod session; mod turn; +pub(crate) use additional_context::AdditionalContextStore; pub(crate) use auto_compact_window::AutoCompactWindowSnapshot; pub(crate) use service::SessionServices; pub(crate) use session::SessionState; diff --git a/codex-rs/core/src/state/session.rs b/codex-rs/core/src/state/session.rs index baeaddbbf91..ac59cb32d56 100644 --- a/codex-rs/core/src/state/session.rs +++ b/codex-rs/core/src/state/session.rs @@ -6,6 +6,7 @@ use codex_sandboxing::policy_transforms::merge_permission_profiles; use std::collections::HashSet; use std::collections::VecDeque; +use super::AdditionalContextStore; use super::auto_compact_window::AutoCompactWindow; use super::auto_compact_window::AutoCompactWindowSnapshot; use crate::context_manager::ContextManager; @@ -25,6 +26,7 @@ pub(crate) struct SessionState { pub(crate) latest_rate_limits: Option, pub(crate) server_reasoning_included: bool, pub(crate) mcp_dependency_prompted: HashSet, + pub(crate) additional_context: AdditionalContextStore, /// Settings used by the latest regular user turn, used for turn-to-turn /// model/realtime handling on subsequent regular turns (including full-context /// reinjection after resume or `/compact`). @@ -49,6 +51,7 @@ impl SessionState { latest_rate_limits: None, server_reasoning_included: false, mcp_dependency_prompted: HashSet::new(), + additional_context: AdditionalContextStore::default(), previous_turn_settings: None, auto_compact_window: AutoCompactWindow::new(), startup_prewarm: None, diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index 51d236526ee..b782344351d 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -50,7 +50,6 @@ use codex_protocol::protocol::TurnAbortReason; use codex_protocol::protocol::TurnAbortedEvent; use codex_protocol::protocol::TurnCompleteEvent; use codex_protocol::protocol::WarningEvent; -use codex_protocol::user_input::UserInput; use codex_features::Feature; use codex_protocol::models::ContentItem; @@ -290,7 +289,7 @@ impl Session { pub async fn spawn_task( self: &Arc, turn_context: Arc, - input: Vec, + input: Vec, task: T, ) { self.abort_all_tasks(TurnAbortReason::Replaced).await; @@ -301,7 +300,7 @@ impl Session { pub(crate) async fn start_task( self: &Arc, turn_context: Arc, - input: Vec, + input: Vec, task: T, ) { let task: Arc = Arc::new(task); @@ -369,11 +368,7 @@ impl Session { )); let ctx = Arc::clone(&turn_context); let task_for_run = Arc::clone(&task); - let task_input = if input.is_empty() { - Vec::new() - } else { - vec![TurnInput::UserInput(input)] - }; + let task_input = input; let task_cancellation_token = cancellation_token.child_token(); // Task-owned turn spans keep a core-owned span open for the // full task lifecycle after the submission dispatch span ends. diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index a582805ed29..68b19adea9e 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -2592,6 +2592,7 @@ async fn send_input_accepts_structured_items() { ], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }; let captured = manager diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index a3987582917..73e9e0ece88 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -767,6 +767,7 @@ impl TestCodex { environments, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(self.config.cwd.to_path_buf()), approval_policy: Some(approval_policy), diff --git a/codex-rs/core/tests/suite/abort_tasks.rs b/codex-rs/core/tests/suite/abort_tasks.rs index c41502a9dc9..82bb8c87987 100644 --- a/codex-rs/core/tests/suite/abort_tasks.rs +++ b/codex-rs/core/tests/suite/abort_tasks.rs @@ -53,6 +53,7 @@ async fn interrupt_long_running_tool_emits_turn_aborted() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -110,6 +111,7 @@ async fn interrupt_tool_records_history_entries() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -131,6 +133,7 @@ async fn interrupt_tool_records_history_entries() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -214,6 +217,7 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -235,6 +239,7 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/additional_context.rs b/codex-rs/core/tests/suite/additional_context.rs new file mode 100644 index 00000000000..f9bfdb966b6 --- /dev/null +++ b/codex-rs/core/tests/suite/additional_context.rs @@ -0,0 +1,581 @@ +use anyhow::Result; +use codex_protocol::items::TurnItem; +use codex_protocol::protocol::AdditionalContextEntry; +use codex_protocol::protocol::AdditionalContextKind; +use codex_protocol::protocol::EventMsg; +use codex_protocol::protocol::ItemCompletedEvent; +use codex_protocol::protocol::Op; +use codex_protocol::user_input::UserInput; +use core_test_support::context_snapshot; +use core_test_support::context_snapshot::ContextSnapshotOptions; +use core_test_support::context_snapshot::ContextSnapshotRenderMode; +use core_test_support::responses::ev_completed; +use core_test_support::responses::ev_response_created; +use core_test_support::responses::mount_sse_once; +use core_test_support::responses::sse; +use core_test_support::responses::start_mock_server; +use core_test_support::skip_if_no_network; +use core_test_support::test_codex::test_codex; +use core_test_support::wait_for_event_match; +use pretty_assertions::assert_eq; +use std::collections::BTreeMap; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn additional_context_is_model_visible_but_not_a_user_message_item() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let test = test_codex() + .with_config(|config| config.include_environment_context = false) + .build(&server) + .await?; + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![UserInput::Text { + text: "inspect the active tab".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: BTreeMap::from([ + ( + "browser_info".to_string(), + AdditionalContextEntry { + value: "tab one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ( + "automation_info".to_string(), + AdditionalContextEntry { + value: "run one".to_string(), + kind: AdditionalContextKind::Application, + }, + ), + ]), + thread_settings: Default::default(), + }) + .await?; + + let user_item = wait_for_event_match(&test.codex, |event| match event { + EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::UserMessage(item), + .. + }) => Some(item.clone()), + _ => None, + }) + .await; + assert_eq!( + user_item.content, + vec![UserInput::Text { + text: "inspect the active tab".to_string(), + text_elements: Vec::new(), + }] + ); + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + let request = request.single_request(); + insta::assert_snapshot!( + "additional_context_simple_input", + context_snapshot::format_labeled_requests_snapshot( + "additional context is inserted before the user turn input.", + &[("Request", &request)], + &ContextSnapshotOptions::default() + .strip_capability_instructions() + .render_mode(ContextSnapshotRenderMode::KindWithTextPrefix { max_chars: 160 }), + ) + ); + let developer_context_texts = request + .message_input_texts("developer") + .into_iter() + .filter(|text| text.starts_with("")) + .collect::>(); + assert_eq!( + developer_context_texts, + vec!["run one"] + ); + assert_eq!( + request.message_input_texts("user"), + vec![ + "tab one", + "inspect the active tab", + ] + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn external_context_like_user_text_remains_a_user_message_item() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let test = test_codex() + .with_config(|config| config.include_environment_context = false) + .build(&server) + .await?; + let user_input = UserInput::Text { + text: "".to_string(), + text_elements: Vec::new(), + }; + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![user_input.clone()], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: BTreeMap::new(), + thread_settings: Default::default(), + }) + .await?; + + let user_item = wait_for_event_match(&test.codex, |event| match event { + EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::UserMessage(item), + .. + }) => Some(item.clone()), + _ => None, + }) + .await; + assert_eq!(user_item.content, vec![user_input]); + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + let request = request.single_request(); + assert_eq!(request.message_input_texts("user"), vec![""]); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn additional_context_trust_controls_message_role() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let test = test_codex() + .with_config(|config| config.include_environment_context = false) + .build(&server) + .await?; + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![UserInput::Text { + text: "inspect context".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: BTreeMap::from([ + ( + "browser_info".to_string(), + AdditionalContextEntry { + value: "tab one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ( + "automation_info".to_string(), + AdditionalContextEntry { + value: "run one".to_string(), + kind: AdditionalContextKind::Application, + }, + ), + ]), + thread_settings: Default::default(), + }) + .await?; + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + let request = request.single_request(); + let developer_context_texts = request + .message_input_texts("developer") + .into_iter() + .filter(|text| text.starts_with("")) + .collect::>(); + assert_eq!( + developer_context_texts, + vec!["run one"] + ); + assert_eq!( + request.message_input_texts("user"), + vec![ + "tab one", + "inspect context", + ] + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn additional_context_is_deduplicated_between_turns_while_retained() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let first_request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let second_request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; + let test = test_codex() + .with_config(|config| config.include_environment_context = false) + .build(&server) + .await?; + let additional_context = BTreeMap::from([( + "browser_info".to_string(), + AdditionalContextEntry { + value: "same tab".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + )]); + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![UserInput::Text { + text: "first turn".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: additional_context.clone(), + thread_settings: Default::default(), + }) + .await?; + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![UserInput::Text { + text: "second turn".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context, + thread_settings: Default::default(), + }) + .await?; + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + assert_eq!( + first_request.single_request().message_input_texts("user"), + vec![ + "same tab", + "first turn", + ] + ); + assert_eq!( + second_request.single_request().message_input_texts("user"), + vec![ + "same tab", + "first turn", + "second turn", + ] + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn additional_context_removes_one_value_while_adding_another() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let first_request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let second_request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; + let third_request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-3"), ev_completed("resp-3")]), + ) + .await; + let test = test_codex() + .with_config(|config| config.include_environment_context = false) + .build(&server) + .await?; + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![UserInput::Text { + text: "first turn".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: BTreeMap::from([ + ( + "automation_info".to_string(), + AdditionalContextEntry { + value: "run one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ( + "browser_info".to_string(), + AdditionalContextEntry { + value: "tab one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ]), + thread_settings: Default::default(), + }) + .await?; + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![UserInput::Text { + text: "second turn".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: BTreeMap::from([ + ( + "automation_info".to_string(), + AdditionalContextEntry { + value: "run one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ( + "terminal_info".to_string(), + AdditionalContextEntry { + value: "pty one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ]), + thread_settings: Default::default(), + }) + .await?; + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![UserInput::Text { + text: "third turn".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: BTreeMap::from([ + ( + "automation_info".to_string(), + AdditionalContextEntry { + value: "run one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ( + "browser_info".to_string(), + AdditionalContextEntry { + value: "tab one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ( + "terminal_info".to_string(), + AdditionalContextEntry { + value: "pty one".to_string(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ]), + thread_settings: Default::default(), + }) + .await?; + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + assert_eq!( + first_request.single_request().message_input_texts("user"), + vec![ + "run one", + "tab one", + "first turn", + ] + ); + assert_eq!( + second_request.single_request().message_input_texts("user"), + vec![ + "run one", + "tab one", + "first turn", + "pty one", + "second turn", + ] + ); + assert_eq!( + third_request.single_request().message_input_texts("user"), + vec![ + "run one", + "tab one", + "first turn", + "pty one", + "second turn", + "tab one", + "third turn", + ] + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn additional_context_values_are_truncated_before_model_input() -> Result<()> { + skip_if_no_network!(Ok(())); + + const MAX_EXPECTED_EXTERNAL_CONTEXT_TEXT_BYTES: usize = 5 * 1024; + + let server = start_mock_server().await; + let request = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let test = test_codex() + .with_config(|config| config.include_environment_context = false) + .build(&server) + .await?; + let long_browser_value = format!("browser-head-{}browser-tail", "b".repeat(40_000)); + let long_automation_value = format!("automation-head-{}automation-tail", "a".repeat(40_000)); + let untruncated_browser_fragment = + format!("{long_browser_value}"); + let untruncated_automation_fragment = + format!("{long_automation_value}"); + + test.codex + .submit(Op::UserInput { + environments: None, + items: vec![UserInput::Text { + text: "summarize context".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + responsesapi_client_metadata: None, + additional_context: BTreeMap::from([ + ( + "automation_info".to_string(), + AdditionalContextEntry { + value: long_automation_value.clone(), + kind: AdditionalContextKind::Application, + }, + ), + ( + "browser_info".to_string(), + AdditionalContextEntry { + value: long_browser_value.clone(), + kind: AdditionalContextKind::Untrusted, + }, + ), + ]), + thread_settings: Default::default(), + }) + .await?; + wait_for_event_match(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)).then_some(()) + }) + .await; + + let request = request.single_request(); + let developer_texts = request + .message_input_texts("developer") + .into_iter() + .filter(|text| text.starts_with("")) + .collect::>(); + let [automation_text] = developer_texts.as_slice() else { + panic!("expected application additional context, got {developer_texts:?}"); + }; + assert!(automation_text.starts_with(&format!( + "automation-head-{}", + "a".repeat(1024) + ))); + assert!(automation_text.contains("tokens truncated")); + assert!(automation_text.ends_with("automation-tail")); + assert!(automation_text.len() < untruncated_automation_fragment.len()); + assert!( + automation_text.len() <= MAX_EXPECTED_EXTERNAL_CONTEXT_TEXT_BYTES, + "application additional context was not capped before model input: {} bytes", + automation_text.len() + ); + + let user_texts = request.message_input_texts("user"); + let [external_text, user_text] = user_texts.as_slice() else { + panic!("expected external context plus user input, got {user_texts:?}"); + }; + assert_eq!(user_text, "summarize context"); + assert!(external_text.starts_with(&format!( + "browser-head-{}", + "b".repeat(1024) + ))); + assert!(external_text.contains("tokens truncated")); + assert!(external_text.ends_with("browser-tail")); + assert!(external_text.len() < untruncated_browser_fragment.len()); + assert!( + external_text.len() <= MAX_EXPECTED_EXTERNAL_CONTEXT_TEXT_BYTES, + "untrusted additional context was not capped before model input: {} bytes", + external_text.len() + ); + + Ok(()) +} diff --git a/codex-rs/core/tests/suite/apply_patch_cli.rs b/codex-rs/core/tests/suite/apply_patch_cli.rs index f5a35003f82..da138851f55 100644 --- a/codex-rs/core/tests/suite/apply_patch_cli.rs +++ b/codex-rs/core/tests/suite/apply_patch_cli.rs @@ -91,6 +91,7 @@ async fn submit_without_wait_with_turn_permissions( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(harness.cwd().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/approvals.rs b/codex-rs/core/tests/suite/approvals.rs index 924253d8b80..9b727a2a2cb 100644 --- a/codex-rs/core/tests/suite/approvals.rs +++ b/codex-rs/core/tests/suite/approvals.rs @@ -653,6 +653,7 @@ async fn submit_turn( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd.path().to_path_buf()), approval_policy: Some(approval_policy), @@ -2597,6 +2598,7 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd.path().to_path_buf()), approval_policy: Some(approval_policy), diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index f6731a0ea4f..7234c59c0fd 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -392,6 +392,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -758,6 +759,7 @@ async fn includes_session_id_thread_id_and_model_headers_in_request() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -969,6 +971,7 @@ async fn includes_base_instructions_override_in_request() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1026,6 +1029,7 @@ async fn chatgpt_auth_sends_correct_request() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1150,6 +1154,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1189,6 +1194,7 @@ async fn includes_user_instructions_message_in_request() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1277,6 +1283,7 @@ async fn includes_apps_guidance_as_developer_message_for_chatgpt_auth() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1340,6 +1347,7 @@ async fn omits_apps_guidance_for_api_key_auth_even_when_feature_enabled() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1399,6 +1407,7 @@ async fn omits_apps_guidance_when_configured_off() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1441,6 +1450,7 @@ async fn omits_environment_context_when_configured_off() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1498,6 +1508,7 @@ async fn skills_append_to_developer_message() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1581,6 +1592,7 @@ async fn skills_use_aliases_in_developer_message_under_budget_pressure() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1642,6 +1654,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1684,6 +1697,7 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1727,6 +1741,7 @@ async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1778,6 +1793,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(config.cwd.to_path_buf()), approval_policy: Some(config.permissions.approval_policy.value()), @@ -1834,6 +1850,7 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1899,6 +1916,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default() environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(config.cwd.to_path_buf()), approval_policy: Some(config.permissions.approval_policy.value()), @@ -1959,6 +1977,7 @@ async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2018,6 +2037,7 @@ async fn reasoning_summary_none_overrides_model_catalog_default() -> anyhow::Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2057,6 +2077,7 @@ async fn includes_default_verbosity_in_request() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2105,6 +2126,7 @@ async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::R }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2152,6 +2174,7 @@ async fn configured_verbosity_is_sent() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2204,6 +2227,7 @@ async fn includes_developer_instructions_message_in_request() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2500,6 +2524,7 @@ async fn token_count_includes_rate_limits_snapshot() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2639,6 +2664,7 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2716,6 +2742,7 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2731,6 +2758,7 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2816,6 +2844,7 @@ async fn incomplete_response_emits_content_filter_error_message() -> anyhow::Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2927,6 +2956,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3016,6 +3046,7 @@ async fn env_var_overrides_loaded_auth() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3073,6 +3104,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3089,6 +3121,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3105,6 +3138,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 74eb053325e..09d0093f990 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -1319,6 +1319,7 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1408,6 +1409,7 @@ async fn responses_websocket_invalid_request_error_with_status_is_forwarded() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 3cd5c672499..fb9e7fb4207 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -2991,6 +2991,7 @@ text( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index 0f9bee9431f..bbbb0f1aa23 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -79,6 +79,7 @@ async fn no_collaboration_instructions_by_default() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -133,6 +134,7 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -170,6 +172,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(test.config.permissions.approval_policy.value()), @@ -220,6 +223,7 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(test.config.permissions.approval_policy.value()), @@ -279,6 +283,7 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -327,6 +332,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(test.config.permissions.approval_policy.value()), @@ -391,6 +397,7 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -414,6 +421,7 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -466,6 +474,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -489,6 +498,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -543,6 +553,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -569,6 +580,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -624,6 +636,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -650,6 +663,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -708,6 +722,7 @@ async fn resume_replays_collaboration_instructions() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -724,6 +739,7 @@ async fn resume_replays_collaboration_instructions() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -776,6 +792,7 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index 140b4ec2b11..be6d30c6fa0 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -97,6 +97,7 @@ fn disabled_permission_user_turn(text: impl Into, cwd: PathBuf, model: S environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), @@ -408,6 +409,7 @@ async fn summarize_context_three_requests_and_instructions() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -433,6 +435,7 @@ async fn summarize_context_three_requests_and_instructions() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -610,6 +613,7 @@ async fn manual_pre_compact_block_decision_does_not_block_compaction() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -683,6 +687,7 @@ async fn compact_hooks_respect_matchers_and_post_runs_after_compaction() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -753,6 +758,7 @@ async fn manual_compact_uses_custom_prompt() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -900,6 +906,7 @@ async fn manual_compact_emits_context_compaction_items() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1066,6 +1073,7 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1538,6 +1546,7 @@ async fn auto_compact_runs_after_token_limit_hit() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1554,6 +1563,7 @@ async fn auto_compact_runs_after_token_limit_hit() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1570,6 +1580,7 @@ async fn auto_compact_runs_after_token_limit_hit() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1741,6 +1752,7 @@ async fn auto_compact_emits_context_compaction_items() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1822,6 +1834,7 @@ async fn auto_compact_starts_after_turn_started() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1837,6 +1850,7 @@ async fn auto_compact_starts_after_turn_started() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1852,6 +1866,7 @@ async fn auto_compact_starts_after_turn_started() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2380,6 +2395,7 @@ async fn auto_compact_persists_rollout_entries() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2395,6 +2411,7 @@ async fn auto_compact_persists_rollout_entries() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2410,6 +2427,7 @@ async fn auto_compact_persists_rollout_entries() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2499,6 +2517,7 @@ async fn manual_compact_retries_after_context_window_error() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2603,6 +2622,7 @@ async fn manual_compact_non_context_failure_retries_then_emits_task_error() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2698,6 +2718,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2716,6 +2737,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2734,6 +2756,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -2898,6 +2921,7 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_ }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3003,6 +3027,7 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3434,6 +3459,7 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3553,6 +3579,7 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3615,6 +3642,7 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3647,6 +3675,7 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess ], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3836,6 +3865,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3851,6 +3881,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -3924,6 +3955,7 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index b614c09d3a5..d8779bfb058 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -326,6 +326,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -343,6 +344,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -518,6 +520,7 @@ async fn assert_remote_manual_compact_request_parity( }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -538,6 +541,7 @@ async fn assert_remote_manual_compact_request_parity( ], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -552,6 +556,7 @@ async fn assert_remote_manual_compact_request_parity( }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -572,6 +577,7 @@ async fn assert_remote_manual_compact_request_parity( ], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -586,6 +592,7 @@ async fn assert_remote_manual_compact_request_parity( }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -754,6 +761,7 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -771,6 +779,7 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -874,6 +883,7 @@ async fn remote_compact_v2_retries_failures_with_stream_retry_budget() -> Result }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -891,6 +901,7 @@ async fn remote_compact_v2_retries_failures_with_stream_retry_budget() -> Result }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -977,6 +988,7 @@ async fn remote_compact_v2_accepts_additional_output_items_before_compaction() - }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -994,6 +1006,7 @@ async fn remote_compact_v2_accepts_additional_output_items_before_compaction() - }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1085,6 +1098,7 @@ async fn remote_compact_filters_deferred_dynamic_tools() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1157,6 +1171,7 @@ async fn remote_compact_runs_automatically() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1238,6 +1253,7 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1252,6 +1268,7 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1370,6 +1387,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1384,6 +1402,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1404,6 +1423,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1504,6 +1524,7 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1518,6 +1539,7 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1612,6 +1634,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1629,6 +1652,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1720,6 +1744,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1737,6 +1762,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1808,6 +1834,7 @@ async fn remote_manual_compact_emits_context_compaction_items() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1889,6 +1916,7 @@ async fn remote_manual_compact_failure_emits_task_error_event() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1973,6 +2001,7 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()> }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2116,6 +2145,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2134,6 +2164,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2159,6 +2190,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2255,6 +2287,7 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2272,6 +2305,7 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2344,6 +2378,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2358,6 +2393,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2425,6 +2461,7 @@ async fn remote_request_uses_custom_experimental_realtime_start_instructions() - }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2486,6 +2523,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2502,6 +2540,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2577,6 +2616,7 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2594,6 +2634,7 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2677,6 +2718,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2693,6 +2735,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2784,6 +2827,7 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() -> }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2814,6 +2858,7 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() -> }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2903,6 +2948,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2990,6 +3036,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3012,6 +3059,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3131,6 +3179,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3145,6 +3194,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3230,6 +3280,7 @@ async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3308,6 +3359,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3394,6 +3446,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3411,6 +3464,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3493,6 +3547,7 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/compact_remote_parity.rs b/codex-rs/core/tests/suite/compact_remote_parity.rs index ccbd427b1f5..8979b2cced1 100644 --- a/codex-rs/core/tests/suite/compact_remote_parity.rs +++ b/codex-rs/core/tests/suite/compact_remote_parity.rs @@ -606,6 +606,7 @@ async fn submit_user_input(codex: &codex_core::CodexThread, items: Vec, text: &str) { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/exec_policy.rs b/codex-rs/core/tests/suite/exec_policy.rs index f1e01c22ce7..9e350d27730 100644 --- a/codex-rs/core/tests/suite/exec_policy.rs +++ b/codex-rs/core/tests/suite/exec_policy.rs @@ -54,6 +54,7 @@ async fn submit_user_turn( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd_path().to_path_buf()), approval_policy: Some(approval_policy), @@ -141,6 +142,7 @@ async fn execpolicy_blocks_shell_invocation() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd_path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/fork_thread.rs b/codex-rs/core/tests/suite/fork_thread.rs index 68a167f7902..544c530aa08 100644 --- a/codex-rs/core/tests/suite/fork_thread.rs +++ b/codex-rs/core/tests/suite/fork_thread.rs @@ -57,6 +57,7 @@ async fn fork_thread_twice_drops_to_first_message() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -181,6 +182,7 @@ async fn fork_thread_from_history_does_not_require_source_rollout_path() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/hooks.rs b/codex-rs/core/tests/suite/hooks.rs index 37917465511..a535eb4b8e2 100644 --- a/codex-rs/core/tests/suite/hooks.rs +++ b/codex-rs/core/tests/suite/hooks.rs @@ -1850,6 +1850,7 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1869,6 +1870,7 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/image_rollout.rs b/codex-rs/core/tests/suite/image_rollout.rs index 085ca96f377..67a79d603fc 100644 --- a/codex-rs/core/tests/suite/image_rollout.rs +++ b/codex-rs/core/tests/suite/image_rollout.rs @@ -127,6 +127,7 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), @@ -223,6 +224,7 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()> environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index 82e01e24518..a56b2ab95f5 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -58,6 +58,7 @@ fn disabled_plan_turn( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), @@ -119,6 +120,7 @@ async fn user_message_item_is_emitted() -> anyhow::Result<()> { items: vec![expected_input.clone()], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -178,6 +180,7 @@ async fn assistant_message_item_is_emitted() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -239,6 +242,7 @@ async fn reasoning_item_is_emitted() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -301,6 +305,7 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -381,6 +386,7 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -468,6 +474,7 @@ async fn image_generation_call_event_is_emitted_when_image_save_fails() -> anyho }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -524,6 +531,7 @@ async fn agent_message_content_delta_has_item_metadata() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1108,6 +1116,7 @@ async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1163,6 +1172,7 @@ async fn reasoning_raw_content_delta_respects_flag() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/json_result.rs b/codex-rs/core/tests/suite/json_result.rs index e81d7aa82a1..67275d31468 100644 --- a/codex-rs/core/tests/suite/json_result.rs +++ b/codex-rs/core/tests/suite/json_result.rs @@ -83,6 +83,7 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> { environments: None, final_output_json_schema: Some(serde_json::from_str(SCHEMA)?), responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/mcp_turn_metadata.rs b/codex-rs/core/tests/suite/mcp_turn_metadata.rs index 16e6cbe37de..897d8621628 100644 --- a/codex-rs/core/tests/suite/mcp_turn_metadata.rs +++ b/codex-rs/core/tests/suite/mcp_turn_metadata.rs @@ -76,6 +76,7 @@ async fn submit_user_turn( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd.path().to_path_buf()), approval_policy: Some(approval_policy), diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 87772aa2797..f47de672bd5 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -29,6 +29,7 @@ pub static CODEX_ALIASES_TEMP_DIR: Option = { #[cfg(not(target_os = "windows"))] mod abort_tasks; +mod additional_context; mod agent_jobs; mod agent_websocket; mod agents_md; diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs index 7e77fb79d36..fd237a8d5cd 100644 --- a/codex-rs/core/tests/suite/model_switching.rs +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -48,6 +48,7 @@ fn read_only_user_turn(test: &TestCodex, items: Vec, model: String) - environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd_path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/model_visible_layout.rs b/codex-rs/core/tests/suite/model_visible_layout.rs index 17bc0f67037..e02e1ffaefe 100644 --- a/codex-rs/core/tests/suite/model_visible_layout.rs +++ b/codex-rs/core/tests/suite/model_visible_layout.rs @@ -125,6 +125,7 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(first_turn_cwd), approval_policy: Some(AskForApproval::Never), @@ -160,6 +161,7 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(preturn_context_diff_cwd), approval_policy: Some(AskForApproval::OnRequest), @@ -249,6 +251,7 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_one.clone()), approval_policy: Some(AskForApproval::Never), @@ -282,6 +285,7 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_two), approval_policy: Some(AskForApproval::Never), @@ -365,6 +369,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -406,6 +411,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(resume_override_cwd), approval_policy: Some(AskForApproval::Never), @@ -479,6 +485,7 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() - }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -520,6 +527,7 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() - }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index 6d11b47cba7..fcd90cbaeab 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -100,6 +100,7 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd_path().to_path_buf()), approval_policy: Some(codex_protocol::protocol::AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/models_etag_responses.rs b/codex-rs/core/tests/suite/models_etag_responses.rs index 49f34d8644c..cbd73fffe9c 100644 --- a/codex-rs/core/tests/suite/models_etag_responses.rs +++ b/codex-rs/core/tests/suite/models_etag_responses.rs @@ -112,6 +112,7 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_path), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/otel.rs b/codex-rs/core/tests/suite/otel.rs index 9d2df4e60e7..7f5fa260cd6 100644 --- a/codex-rs/core/tests/suite/otel.rs +++ b/codex-rs/core/tests/suite/otel.rs @@ -122,6 +122,7 @@ async fn responses_api_emits_api_request_event() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -168,6 +169,7 @@ async fn process_sse_emits_tracing_for_output_item() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -214,6 +216,7 @@ async fn process_sse_emits_failed_event_on_parse_error() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -261,6 +264,7 @@ async fn process_sse_records_failed_event_when_stream_closes_without_completed() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -328,6 +332,7 @@ async fn process_sse_failed_event_records_response_error_message() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -393,6 +398,7 @@ async fn process_sse_failed_event_logs_parse_error() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -445,6 +451,7 @@ async fn process_sse_failed_event_logs_missing_error() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -506,6 +513,7 @@ async fn process_sse_failed_event_logs_response_completed_parse_error() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -561,6 +569,7 @@ async fn process_sse_emits_completed_telemetry() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -640,6 +649,7 @@ async fn turn_and_completed_response_spans_record_token_usage() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -728,6 +738,7 @@ async fn handle_responses_span_records_response_kind_and_tool_name() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -822,6 +833,7 @@ async fn record_responses_sets_span_fields_for_response_events() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -911,6 +923,7 @@ async fn handle_response_item_records_tool_result_for_custom_tool_call() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -987,6 +1000,7 @@ async fn handle_response_item_records_tool_result_for_function_call() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1064,6 +1078,7 @@ async fn handle_response_item_records_tool_result_for_shell_command_call() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1173,6 +1188,7 @@ async fn handle_shell_command_autoapprove_from_config_records_tool_decision() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1228,6 +1244,7 @@ async fn handle_shell_command_user_approved_records_tool_decision() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1298,6 +1315,7 @@ async fn handle_shell_command_user_approved_for_session_records_tool_decision() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1368,6 +1386,7 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1438,6 +1457,7 @@ async fn handle_shell_command_user_denies_records_tool_decision() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1508,6 +1528,7 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -1579,6 +1600,7 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/pending_input.rs b/codex-rs/core/tests/suite/pending_input.rs index b057b86171b..c8eec9bc8ed 100644 --- a/codex-rs/core/tests/suite/pending_input.rs +++ b/codex-rs/core/tests/suite/pending_input.rs @@ -103,6 +103,7 @@ async fn submit_user_input(codex: &CodexThread, text: &str) { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -121,6 +122,7 @@ async fn submit_danger_full_access_user_turn(test: &TestCodex, text: &str) { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(AskForApproval::Never), @@ -148,6 +150,7 @@ async fn steer_user_input(codex: &CodexThread, text: &str) { text: text.to_string(), text_elements: Vec::new(), }], + /*additional_context*/ Default::default(), /*expected_turn_id*/ None, /*responsesapi_client_metadata*/ None, ) @@ -291,6 +294,7 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -310,6 +314,7 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index b77e65c0f52..45d2e32549f 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -58,6 +58,7 @@ async fn permissions_message_sent_once_on_start() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -98,6 +99,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -121,6 +123,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -167,6 +170,7 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -181,6 +185,7 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -227,6 +232,7 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -250,6 +256,7 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -309,6 +316,7 @@ async fn resume_replays_permissions_messages() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -333,6 +341,7 @@ async fn resume_replays_permissions_messages() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -349,6 +358,7 @@ async fn resume_replays_permissions_messages() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -409,6 +419,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -433,6 +444,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -455,6 +467,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -491,6 +504,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -552,6 +566,7 @@ async fn permissions_message_includes_writable_roots() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 96695769c9a..06781dc11d4 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -68,6 +68,7 @@ fn read_only_text_turn_with_personality( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd_path().to_path_buf()), approval_policy: Some(approval_policy), diff --git a/codex-rs/core/tests/suite/plugins.rs b/codex-rs/core/tests/suite/plugins.rs index 917ab085545..b89b611a83c 100644 --- a/codex-rs/core/tests/suite/plugins.rs +++ b/codex-rs/core/tests/suite/plugins.rs @@ -227,6 +227,7 @@ async fn capability_sections_render_in_developer_message_in_order() -> Result<() }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -306,6 +307,7 @@ async fn explicit_plugin_mentions_inject_plugin_guidance() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -388,6 +390,7 @@ async fn explicit_plugin_mentions_track_plugin_used_analytics() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 4ce137a557c..836761961a3 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -153,6 +153,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -167,6 +168,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -250,6 +252,7 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -264,6 +267,7 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -329,6 +333,7 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -343,6 +348,7 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -426,6 +432,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -464,6 +471,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -545,6 +553,7 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -699,6 +708,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -724,6 +734,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(new_cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), @@ -838,6 +849,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(default_cwd.to_path_buf()), approval_policy: Some(default_approval_policy), @@ -866,6 +878,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(default_cwd.to_path_buf()), approval_policy: Some(default_approval_policy), @@ -977,6 +990,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(default_cwd.to_path_buf()), approval_policy: Some(default_approval_policy), @@ -1007,6 +1021,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(default_cwd.to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/quota_exceeded.rs b/codex-rs/core/tests/suite/quota_exceeded.rs index 413855f6413..904c116cfbc 100644 --- a/codex-rs/core/tests/suite/quota_exceeded.rs +++ b/codex-rs/core/tests/suite/quota_exceeded.rs @@ -48,6 +48,7 @@ async fn quota_exceeded_emits_single_error_event() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/realtime_conversation.rs b/codex-rs/core/tests/suite/realtime_conversation.rs index 232d2380b0a..05428799ad3 100644 --- a/codex-rs/core/tests/suite/realtime_conversation.rs +++ b/codex-rs/core/tests/suite/realtime_conversation.rs @@ -2168,6 +2168,7 @@ async fn conversation_user_text_turn_is_sent_to_realtime_when_active() -> Result }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -2303,6 +2304,7 @@ async fn conversation_user_text_turn_is_capped_when_mirrored_to_realtime() -> Re }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -3499,6 +3501,7 @@ async fn inbound_handoff_request_steers_active_turn() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/remote_env.rs b/codex-rs/core/tests/suite/remote_env.rs index 873796ff6ce..34cd119f66a 100644 --- a/codex-rs/core/tests/suite/remote_env.rs +++ b/codex-rs/core/tests/suite/remote_env.rs @@ -75,6 +75,7 @@ async fn submit_turn_with_approval_and_environments( environments: Some(environments), final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::OnRequest), diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index 18ebd732b7f..b7b16f9c292 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -160,6 +160,7 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -227,6 +228,7 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -293,6 +295,7 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -372,6 +375,7 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -422,6 +426,7 @@ async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning() environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -575,6 +580,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_path), approval_policy: Some(AskForApproval::Never), @@ -798,6 +804,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_path), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/request_compression.rs b/codex-rs/core/tests/suite/request_compression.rs index 2d66389d534..fe9cb1e5f84 100644 --- a/codex-rs/core/tests/suite/request_compression.rs +++ b/codex-rs/core/tests/suite/request_compression.rs @@ -47,6 +47,7 @@ async fn request_body_is_zstd_compressed_for_codex_backend_when_enabled() -> any }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -97,6 +98,7 @@ async fn request_body_is_not_compressed_for_api_key_auth_even_when_enabled() -> }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/request_permissions.rs b/codex-rs/core/tests/suite/request_permissions.rs index 98001046859..a78e4a016eb 100644 --- a/codex-rs/core/tests/suite/request_permissions.rs +++ b/codex-rs/core/tests/suite/request_permissions.rs @@ -198,6 +198,7 @@ async fn submit_turn( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd.path().to_path_buf()), approval_policy: Some(approval_policy), diff --git a/codex-rs/core/tests/suite/request_permissions_tool.rs b/codex-rs/core/tests/suite/request_permissions_tool.rs index 84e101e73ae..a122cc399b3 100644 --- a/codex-rs/core/tests/suite/request_permissions_tool.rs +++ b/codex-rs/core/tests/suite/request_permissions_tool.rs @@ -150,6 +150,7 @@ async fn submit_turn( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd.path().to_path_buf()), approval_policy: Some(approval_policy), diff --git a/codex-rs/core/tests/suite/request_user_input.rs b/codex-rs/core/tests/suite/request_user_input.rs index e0ec684797d..d09071d72d9 100644 --- a/codex-rs/core/tests/suite/request_user_input.rs +++ b/codex-rs/core/tests/suite/request_user_input.rs @@ -144,6 +144,7 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), @@ -287,6 +288,7 @@ async fn request_user_input_interrupt_emits_deferred_token_count() -> anyhow::Re environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), @@ -391,6 +393,7 @@ where environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/responses_api_proxy_headers.rs b/codex-rs/core/tests/suite/responses_api_proxy_headers.rs index c18b4319844..809a8bb7010 100644 --- a/codex-rs/core/tests/suite/responses_api_proxy_headers.rs +++ b/codex-rs/core/tests/suite/responses_api_proxy_headers.rs @@ -143,6 +143,7 @@ async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()> environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::OnRequest), diff --git a/codex-rs/core/tests/suite/resume.rs b/codex-rs/core/tests/suite/resume.rs index fc7a23215f8..0dc7eaddcbd 100644 --- a/codex-rs/core/tests/suite/resume.rs +++ b/codex-rs/core/tests/suite/resume.rs @@ -93,6 +93,7 @@ async fn resume_includes_initial_messages_from_rollout_events() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -179,6 +180,7 @@ async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()> }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -269,6 +271,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -312,6 +315,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -330,6 +334,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -403,6 +408,7 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -441,6 +447,7 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/review.rs b/codex-rs/core/tests/suite/review.rs index 05ec967d09d..802035d7dc2 100644 --- a/codex-rs/core/tests/suite/review.rs +++ b/codex-rs/core/tests/suite/review.rs @@ -684,6 +684,7 @@ async fn review_history_surfaces_in_parent_session() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 260edb3f648..2c86cb393ce 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -131,6 +131,7 @@ fn user_turn_with_permission_profile( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/safety_check_downgrade.rs b/codex-rs/core/tests/suite/safety_check_downgrade.rs index ab985ce0f79..3d4cb93b9c5 100644 --- a/codex-rs/core/tests/suite/safety_check_downgrade.rs +++ b/codex-rs/core/tests/suite/safety_check_downgrade.rs @@ -45,6 +45,7 @@ fn disabled_text_turn(test: &TestCodex, text: &str) -> Op { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd_path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/search_tool.rs b/codex-rs/core/tests/suite/search_tool.rs index bbf16b3657c..e9ffeb62ef7 100644 --- a/codex-rs/core/tests/suite/search_tool.rs +++ b/codex-rs/core/tests/suite/search_tool.rs @@ -452,6 +452,7 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() - }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -865,6 +866,7 @@ async fn tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call() - }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1173,6 +1175,7 @@ async fn tool_search_surfaced_mcp_tool_errors_are_returned_to_model() -> Result< }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; @@ -1493,6 +1496,7 @@ async fn tool_search_matches_dynamic_tools_by_name_description_namespace_and_sch }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/shell_snapshot.rs b/codex-rs/core/tests/suite/shell_snapshot.rs index 14241ffafb6..69db30b21b4 100644 --- a/codex-rs/core/tests/suite/shell_snapshot.rs +++ b/codex-rs/core/tests/suite/shell_snapshot.rs @@ -167,6 +167,7 @@ async fn run_snapshot_command_with_options( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), @@ -267,6 +268,7 @@ async fn run_shell_command_snapshot_with_options( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), @@ -347,6 +349,7 @@ async fn run_tool_turn_on_harness( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), @@ -590,6 +593,7 @@ async fn shell_command_snapshot_still_intercepts_apply_patch() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd.clone()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/skill_approval.rs b/codex-rs/core/tests/suite/skill_approval.rs index ad8ad6a91a5..2335ba64155 100644 --- a/codex-rs/core/tests/suite/skill_approval.rs +++ b/codex-rs/core/tests/suite/skill_approval.rs @@ -54,6 +54,7 @@ async fn submit_turn_with_policies( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd_path().to_path_buf()), approval_policy: Some(approval_policy), diff --git a/codex-rs/core/tests/suite/skills.rs b/codex-rs/core/tests/suite/skills.rs index 9173f60c4a7..e21668fa842 100644 --- a/codex-rs/core/tests/suite/skills.rs +++ b/codex-rs/core/tests/suite/skills.rs @@ -88,6 +88,7 @@ async fn user_turn_includes_skill_instructions() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__additional_context__additional_context_simple_input.snap b/codex-rs/core/tests/suite/snapshots/all__suite__additional_context__additional_context_simple_input.snap new file mode 100644 index 00000000000..fefdfde60cd --- /dev/null +++ b/codex-rs/core/tests/suite/snapshots/all__suite__additional_context__additional_context_simple_input.snap @@ -0,0 +1,11 @@ +--- +source: core/tests/suite/additional_context.rs +expression: "context_snapshot::format_labeled_requests_snapshot(\"additional context is inserted before the user turn input.\",\n&[(\"Request\", &request)], &additional_context_snapshot_options(),)" +--- +Scenario: additional context is inserted before the user turn input. + +## Request +00:message/developer: +01:message/developer:run one +02:message/user:tab one +03:message/user:inspect the active tab diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 6f7a51069a5..99b7fb0ec61 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -413,6 +413,7 @@ async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result< environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs index 5709cdd12b8..d82692c26c9 100644 --- a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs +++ b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs @@ -101,6 +101,7 @@ async fn continue_after_stream_error() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await @@ -123,6 +124,7 @@ async fn continue_after_stream_error() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/stream_no_completed.rs b/codex-rs/core/tests/suite/stream_no_completed.rs index 40763960f40..471c60db5a9 100644 --- a/codex-rs/core/tests/suite/stream_no_completed.rs +++ b/codex-rs/core/tests/suite/stream_no_completed.rs @@ -83,6 +83,7 @@ async fn retries_on_early_close() { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/core/tests/suite/subagent_notifications.rs b/codex-rs/core/tests/suite/subagent_notifications.rs index d3c07a11539..8891f6ffec8 100644 --- a/codex-rs/core/tests/suite/subagent_notifications.rs +++ b/codex-rs/core/tests/suite/subagent_notifications.rs @@ -771,6 +771,7 @@ async fn subagent_stop_replaces_stop_and_skips_internal_subagents() -> Result<() environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/tool_harness.rs b/codex-rs/core/tests/suite/tool_harness.rs index 2227e5b34e1..a6f808b9d79 100644 --- a/codex-rs/core/tests/suite/tool_harness.rs +++ b/codex-rs/core/tests/suite/tool_harness.rs @@ -110,6 +110,7 @@ async fn shell_command_tool_executes_command_and_streams_output() -> anyhow::Res environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_path), approval_policy: Some(AskForApproval::Never), @@ -191,6 +192,7 @@ async fn update_plan_tool_emits_plan_update_event() -> anyhow::Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_path), approval_policy: Some(AskForApproval::Never), @@ -282,6 +284,7 @@ async fn update_plan_tool_rejects_malformed_payload() -> anyhow::Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_path), approval_policy: Some(AskForApproval::Never), @@ -383,6 +386,7 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<() environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_path), approval_policy: Some(AskForApproval::Never), @@ -521,6 +525,7 @@ async fn apply_patch_reports_parse_diagnostics() -> anyhow::Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd_path), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/tool_parallelism.rs b/codex-rs/core/tests/suite/tool_parallelism.rs index 6663991e020..76bb27c4230 100644 --- a/codex-rs/core/tests/suite/tool_parallelism.rs +++ b/codex-rs/core/tests/suite/tool_parallelism.rs @@ -45,6 +45,7 @@ async fn run_turn(test: &TestCodex, prompt: &str) -> anyhow::Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), @@ -371,6 +372,7 @@ async fn shell_tools_start_before_response_completed_when_stream_delayed() -> an environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/truncation.rs b/codex-rs/core/tests/suite/truncation.rs index 04b59a3331d..fe078977ffb 100644 --- a/codex-rs/core/tests/suite/truncation.rs +++ b/codex-rs/core/tests/suite/truncation.rs @@ -523,6 +523,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(fixture.cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index 9eb831b084d..ba754b025f3 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -200,6 +200,7 @@ async fn submit_unified_exec_turn( environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(AskForApproval::Never), @@ -291,6 +292,7 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), @@ -2146,6 +2148,7 @@ async fn unified_exec_keeps_long_running_session_after_turn_end() -> Result<()> environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(turn_cwd), approval_policy: Some(AskForApproval::Never), @@ -2249,6 +2252,7 @@ async fn unified_exec_interrupt_preserves_long_running_session() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(turn_cwd), approval_policy: Some(AskForApproval::Never), @@ -2721,6 +2725,7 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(turn_cwd), approval_policy: Some(AskForApproval::Never), @@ -2843,6 +2848,7 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(turn_cwd), approval_policy: Some(AskForApproval::Never), @@ -2981,6 +2987,7 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(turn_cwd), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/user_notification.rs b/codex-rs/core/tests/suite/user_notification.rs index 343afd8d533..054d926b2b8 100644 --- a/codex-rs/core/tests/suite/user_notification.rs +++ b/codex-rs/core/tests/suite/user_notification.rs @@ -64,6 +64,7 @@ mv "${tmp_path}" "${payload_path}""#, }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/core/tests/suite/user_shell_cmd.rs b/codex-rs/core/tests/suite/user_shell_cmd.rs index 536ee7b4959..3e2263705ab 100644 --- a/codex-rs/core/tests/suite/user_shell_cmd.rs +++ b/codex-rs/core/tests/suite/user_shell_cmd.rs @@ -182,6 +182,7 @@ async fn user_shell_command_does_not_replace_active_turn() -> anyhow::Result<()> environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/view_image.rs b/codex-rs/core/tests/suite/view_image.rs index 867ba108588..e6320ba1a72 100644 --- a/codex-rs/core/tests/suite/view_image.rs +++ b/codex-rs/core/tests/suite/view_image.rs @@ -77,6 +77,7 @@ fn disabled_user_turn(test: &TestCodex, items: Vec, model: String) -> environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(test.config.cwd.to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/websocket_fallback.rs b/codex-rs/core/tests/suite/websocket_fallback.rs index 32a6e30caff..be33467d6a5 100644 --- a/codex-rs/core/tests/suite/websocket_fallback.rs +++ b/codex-rs/core/tests/suite/websocket_fallback.rs @@ -160,6 +160,7 @@ async fn websocket_fallback_hides_first_websocket_retry_stream_error() -> Result environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(cwd.path().to_path_buf()), approval_policy: Some(AskForApproval::Never), diff --git a/codex-rs/core/tests/suite/window_headers.rs b/codex-rs/core/tests/suite/window_headers.rs index 76fcacf7a99..953b075f656 100644 --- a/codex-rs/core/tests/suite/window_headers.rs +++ b/codex-rs/core/tests/suite/window_headers.rs @@ -112,6 +112,7 @@ async fn submit_user_turn(codex: &Arc, text: &str) -> Result<()> { }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await?; diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 9061227b6b9..2df97b5bd74 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -781,6 +781,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { thread_id: primary_thread_id_for_span.clone(), input: items.into_iter().map(Into::into).collect(), responsesapi_client_metadata: None, + additional_context: None, environments: None, cwd: Some(default_cwd), runtime_workspace_roots: None, diff --git a/codex-rs/ext/goal/src/steering.rs b/codex-rs/ext/goal/src/steering.rs index e08c47ae263..83f41ea538d 100644 --- a/codex-rs/ext/goal/src/steering.rs +++ b/codex-rs/ext/goal/src/steering.rs @@ -1,3 +1,4 @@ +use codex_core::context::ContextualUserFragment; use codex_core::context::GoalContext; use codex_protocol::models::ResponseInputItem; use codex_protocol::protocol::ThreadGoal; diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 167d56da5bf..78714d1eaa0 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -116,6 +116,7 @@ pub async fn run_codex_tool_session( }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }, trace: None, @@ -166,6 +167,7 @@ pub async fn run_codex_tool_session_reply( }], final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/memories/write/src/runtime.rs b/codex-rs/memories/write/src/runtime.rs index b1ffb2d215f..7bf2fe159d1 100644 --- a/codex-rs/memories/write/src/runtime.rs +++ b/codex-rs/memories/write/src/runtime.rs @@ -261,6 +261,7 @@ impl MemoryStartupContext { environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index d3b8a9e820c..a675362b621 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -3,6 +3,7 @@ //! Uses a SQ (Submission Queue) / EQ (Event Queue) pattern to asynchronously communicate //! between user and agent. +use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt; use std::ops::Mul; @@ -471,6 +472,21 @@ pub struct ThreadSettingsOverrides { pub personality: Option, } +/// Source classification for client-supplied context. +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AdditionalContextKind { + Untrusted, + Application, +} + +/// Client-supplied context keyed by an opaque source identifier. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +pub struct AdditionalContextEntry { + pub value: String, + pub kind: AdditionalContextKind, +} + /// Submission operation #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)] #[serde(tag = "type", rename_all = "snake_case")] @@ -513,6 +529,9 @@ pub enum Op { /// Optional turn-scoped Responses API `client_metadata`. #[serde(default, skip_serializing_if = "Option::is_none")] responsesapi_client_metadata: Option>, + /// Client-supplied context fragments keyed by an opaque source identifier. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + additional_context: BTreeMap, /// Persistent thread-settings overrides to apply before the input. #[serde(default, flatten)] @@ -655,6 +674,7 @@ impl From> for Op { items: value, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: ThreadSettingsOverrides::default(), } } @@ -4954,6 +4974,7 @@ mod tests { items: Vec::new(), final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }; @@ -4974,6 +4995,7 @@ mod tests { items: Vec::new(), final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), } ); @@ -4996,6 +5018,7 @@ mod tests { items: Vec::new(), final_output_json_schema: Some(schema.clone()), responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }; @@ -5022,6 +5045,7 @@ mod tests { "fiber_run_id".to_string(), "fiber-123".to_string(), )])), + additional_context: Default::default(), thread_settings: Default::default(), }; diff --git a/codex-rs/thread-manager-sample/src/main.rs b/codex-rs/thread-manager-sample/src/main.rs index 634327d007d..6f9ebd868a5 100644 --- a/codex-rs/thread-manager-sample/src/main.rs +++ b/codex-rs/thread-manager-sample/src/main.rs @@ -297,6 +297,7 @@ async fn run_turn(thread: &CodexThread, thread_id: &str, prompt: String) -> anyh environments: None, final_output_json_schema: None, responsesapi_client_metadata: None, + additional_context: Default::default(), thread_settings: Default::default(), }) .await diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 91ed9176ff6..aa2d5a645b2 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -670,6 +670,7 @@ impl AppServerSession { thread_id: thread_id.to_string(), input: items, responsesapi_client_metadata: None, + additional_context: None, environments: None, cwd: Some(cwd), runtime_workspace_roots: Some( @@ -733,6 +734,7 @@ impl AppServerSession { thread_id: thread_id.to_string(), input: items, responsesapi_client_metadata: None, + additional_context: None, expected_turn_id: turn_id, }, })