diff --git a/codex-rs/core/src/responses_metadata.rs b/codex-rs/core/src/responses_metadata.rs index ddf961e0f4..b269fdd79c 100644 --- a/codex-rs/core/src/responses_metadata.rs +++ b/codex-rs/core/src/responses_metadata.rs @@ -7,7 +7,6 @@ use codex_analytics::CompactionReason; use codex_analytics::CompactionStrategy; use codex_analytics::CompactionTrigger; use codex_protocol::ThreadId; -use codex_protocol::ToolName; use codex_protocol::protocol::InternalSessionSource; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; @@ -31,7 +30,8 @@ pub(crate) const TURN_ID_KEY: &str = "turn_id"; pub(crate) const WINDOW_ID_KEY: &str = "window_id"; pub(crate) const REQUEST_KIND_KEY: &str = "request_kind"; pub(crate) const COMPACTION_KEY: &str = "compaction"; -pub(crate) const CODE_MODE_TOOL_NAMES_KEY: &str = "code_mode_tool_names"; +// Keep the removed inventory reserved so callers cannot reintroduce oversized metadata. +pub(crate) const LEGACY_CODE_MODE_TOOL_NAMES_KEY: &str = "code_mode_tool_names"; pub(crate) const TOOL_NAMESPACES_INFO_KEY: &str = "tool_namespaces_info"; pub(crate) const TURN_STARTED_AT_UNIX_MS_KEY: &str = "turn_started_at_unix_ms"; @@ -58,7 +58,7 @@ const RESERVED_METADATA_KEYS: &[&str] = &[ X_OPENAI_SUBAGENT_HEADER, REQUEST_KIND_KEY, COMPACTION_KEY, - CODE_MODE_TOOL_NAMES_KEY, + LEGACY_CODE_MODE_TOOL_NAMES_KEY, TOOL_NAMESPACES_INFO_KEY, TURN_STARTED_AT_UNIX_MS_KEY, FORKED_FROM_THREAD_ID_KEY, @@ -202,7 +202,6 @@ pub struct CodexResponsesMetadata { pub(crate) thread_source: Option, pub(crate) sandbox: Option, pub(crate) workspaces: BTreeMap, - pub(crate) code_mode_tool_names: Option>, pub(crate) tool_namespaces_info: Option, pub(crate) turn_started_at_unix_ms: Option, pub(crate) extra: BTreeMap, @@ -231,7 +230,6 @@ impl CodexResponsesMetadata { thread_source: None, sandbox: None, workspaces: BTreeMap::new(), - code_mode_tool_names: None, tool_namespaces_info: None, turn_started_at_unix_ms: None, extra: BTreeMap::new(), @@ -289,11 +287,10 @@ impl CodexResponsesMetadata { pub(crate) fn compatibility_headers(&self) -> ApiHeaderMap { let mut headers = ApiHeaderMap::new(); insert_header(&mut headers, X_CODEX_WINDOW_ID_HEADER, &self.window_id); - // Direct x-codex-turn-metadata is compatibility output. Keep unbounded tool inventories + // Direct x-codex-turn-metadata is compatibility output. Keep the unbounded tool inventory // in client_metadata only so HTTP and WebSocket compatibility headers remain bounded. if self.has_turn_metadata() && let Ok(turn_metadata_json) = to_ascii_json_string(&CodexTurnMetadataPayload { - code_mode_tool_names: None, tool_namespaces_info: None, ..self.turn_metadata_payload() }) @@ -343,7 +340,6 @@ impl CodexResponsesMetadata { thread_source: self.thread_source.as_ref(), sandbox: self.sandbox.as_deref(), workspaces: non_empty_workspaces(&self.workspaces), - code_mode_tool_names: self.code_mode_tool_names.as_ref(), tool_namespaces_info: self.tool_namespaces_info.as_ref(), turn_started_at_unix_ms: self.turn_started_at_unix_ms, compaction, @@ -437,8 +433,6 @@ struct CodexTurnMetadataPayload<'a> { #[serde(default, skip_serializing_if = "Option::is_none")] workspaces: Option<&'a BTreeMap>, #[serde(default, skip_serializing_if = "Option::is_none")] - code_mode_tool_names: Option<&'a BTreeMap>, - #[serde(default, skip_serializing_if = "Option::is_none")] tool_namespaces_info: Option<&'a TurnToolNamespacesInfo>, #[serde(default, skip_serializing_if = "Option::is_none")] turn_started_at_unix_ms: Option, diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 3c6a5ab479..60a539a266 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -782,12 +782,6 @@ fn register_code_mode_executors( code_mode_nested_tool_specs.push((spec, cached_runtime)); } - if turn_context.model_info.use_responses_lite { - turn_context - .turn_metadata_state - .set_code_mode_tool_names(code_mode_tool_names.clone()); - } - let namespace_descriptions = code_mode_namespace_descriptions(&exec_prompt_tool_specs); let mut enabled_tools = collect_code_mode_exec_prompt_tool_definitions(exec_prompt_tool_specs.iter()); diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index b1a3749689..c144b6da30 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -1696,6 +1696,9 @@ async fn code_mode_uses_the_first_normalized_tool_identity() { let mut metadata_state = None; let plan = probe_with( |turn| { + update_config(turn, |config| { + config.tool_registry.turn_metadata_includes_tool_info = true; + }); set_feature(turn, Feature::CodeMode, /*enabled*/ true); if code_mode_only { set_feature(turn, Feature::CodeModeOnly, /*enabled*/ true); @@ -1741,15 +1744,23 @@ async fn code_mode_uses_the_first_normalized_tool_identity() { "window".to_string(), CodexResponsesRequestKind::Turn, ); - let code_mode_tool_names = metadata - .code_mode_tool_names + let tool_namespaces = metadata + .tool_namespaces_info .as_ref() - .expect("Responses Lite should receive the supported code-mode tools"); + .expect("opted-in Responses Lite should receive the supported tools"); assert_eq!( - code_mode_tool_names.get("normalized_alias__lookup"), - Some(&winner_name), + tool_namespaces["normalized-alias"].functions["lookup"] + .code_mode_name + .as_deref(), + Some("normalized_alias__lookup"), + ); + assert!( + tool_namespaces + .get("normalized_alias") + .and_then(|namespace| namespace.functions.get("lookup")) + .and_then(|function| function.code_mode_name.as_ref()) + .is_none() ); - assert!(!code_mode_tool_names.contains_key(codex_tools::TOOL_SEARCH_TOOL_NAME)); if !code_mode_only && !shadow_is_deferred { let ToolSpec::Namespace(namespace) = plan.visible_spec("normalized_alias") else { diff --git a/codex-rs/core/src/turn_metadata.rs b/codex-rs/core/src/turn_metadata.rs index 5d7c87e5f0..1efeeaadaa 100644 --- a/codex-rs/core/src/turn_metadata.rs +++ b/codex-rs/core/src/turn_metadata.rs @@ -26,7 +26,6 @@ use codex_git_utils::get_git_repo_root; use codex_git_utils::get_has_changes_in_repo; use codex_git_utils::get_head_commit_hash; use codex_protocol::ThreadId; -use codex_protocol::ToolName; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; @@ -103,7 +102,6 @@ pub(crate) struct TurnMetadataState { turn_id: String, sandbox: Option, enriched_workspaces: RwLock>>, - code_mode_tool_names: RwLock>>, tool_namespaces_info: RwLock>, turn_started_at_unix_ms: RwLock>, responsesapi_client_metadata: RwLock>, @@ -149,7 +147,6 @@ impl TurnMetadataState { turn_id, sandbox, enriched_workspaces: RwLock::new(None), - code_mode_tool_names: RwLock::new(None), tool_namespaces_info: RwLock::new(None), turn_started_at_unix_ms: RwLock::new(None), responsesapi_client_metadata: RwLock::new(BTreeMap::new()), @@ -164,7 +161,6 @@ impl TurnMetadataState { ) -> Option { let mut responses_metadata = self.responses_metadata_template(); // Never serialize harness-owned tool inventory for external MCP servers. - responses_metadata.code_mode_tool_names = None; responses_metadata.tool_namespaces_info = None; let Value::Object(mut metadata) = responses_metadata.turn_metadata_value()? else { return None; @@ -218,17 +214,6 @@ impl TurnMetadataState { .store(true, Ordering::Relaxed); } - pub(crate) fn set_code_mode_tool_names( - &self, - code_mode_tool_names: BTreeMap, - ) { - *self - .code_mode_tool_names - .write() - .unwrap_or_else(std::sync::PoisonError::into_inner) = - (!code_mode_tool_names.is_empty()).then_some(code_mode_tool_names); - } - pub(crate) fn set_tool_namespaces_info(&self, tool_namespaces_info: TurnToolNamespacesInfo) { *self .tool_namespaces_info @@ -274,11 +259,6 @@ impl TurnMetadataState { thread_source: self.thread_source.clone(), sandbox: self.sandbox.clone(), workspaces: self.current_workspaces(), - code_mode_tool_names: self - .code_mode_tool_names - .read() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .clone(), tool_namespaces_info: self .tool_namespaces_info .read() diff --git a/codex-rs/core/src/turn_metadata_tests.rs b/codex-rs/core/src/turn_metadata_tests.rs index 359d937767..1571a75844 100644 --- a/codex-rs/core/src/turn_metadata_tests.rs +++ b/codex-rs/core/src/turn_metadata_tests.rs @@ -1,9 +1,9 @@ use super::*; -use crate::responses_metadata::CODE_MODE_TOOL_NAMES_KEY; use crate::responses_metadata::CodexResponsesRequestKind; use crate::responses_metadata::CompactionTurnMetadata; use crate::responses_metadata::INSTALLATION_ID_KEY; +use crate::responses_metadata::LEGACY_CODE_MODE_TOOL_NAMES_KEY; use crate::responses_metadata::PARENT_TURN_ID_KEY; use crate::responses_metadata::TOOL_NAMESPACES_INFO_KEY; use crate::responses_metadata::TurnToolFunctionInfo; @@ -15,7 +15,6 @@ use codex_analytics::CompactionImplementation; use codex_analytics::CompactionPhase; use codex_analytics::CompactionReason; use codex_analytics::CompactionTrigger; -use codex_protocol::ToolName; use codex_protocol::models::PermissionProfile; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::SessionSource; @@ -550,7 +549,7 @@ fn turn_metadata_state_ignores_client_reserved_metadata_before_start() { ); state.set_responsesapi_client_metadata(HashMap::from([ ( - CODE_MODE_TOOL_NAMES_KEY.to_string(), + LEGACY_CODE_MODE_TOOL_NAMES_KEY.to_string(), "client-supplied".to_string(), ), ( @@ -576,7 +575,7 @@ fn turn_metadata_state_ignores_client_reserved_metadata_before_start() { let header = test_turn_metadata_header(&state); let json: Value = serde_json::from_str(&header).expect("json"); - assert!(json.get(CODE_MODE_TOOL_NAMES_KEY).is_none()); + assert!(json.get(LEGACY_CODE_MODE_TOOL_NAMES_KEY).is_none()); assert!(json.get(TOOL_NAMESPACES_INFO_KEY).is_none()); assert!(json.get("turn_started_at_unix_ms").is_none()); assert!(json.get("forked_from_thread_id").is_none()); @@ -650,7 +649,7 @@ fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields( ("parent_turn_id".to_string(), "client-supplied".to_string()), ("subagent_kind".to_string(), "client-supplied".to_string()), ( - CODE_MODE_TOOL_NAMES_KEY.to_string(), + LEGACY_CODE_MODE_TOOL_NAMES_KEY.to_string(), "client-supplied".to_string(), ), ( @@ -667,13 +666,6 @@ fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields( ), ])); state.set_turn_started_at_unix_ms(/*turn_started_at_unix_ms*/ 1_700_000_000_123); - state.set_code_mode_tool_names(BTreeMap::from([ - ("exec_command".to_string(), ToolName::plain("exec_command")), - ( - "mcp__calendar__lookup".to_string(), - ToolName::namespaced("mcp__calendar", "lookup"), - ), - ])); state.set_tool_namespaces_info(BTreeMap::from([( "mcp__calendar".to_string(), TurnToolNamespaceInfo { @@ -705,19 +697,7 @@ fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields( assert_eq!(json["reasoning_effort"].as_str(), Some("client-supplied")); assert_eq!(json["session_id"].as_str(), Some("session-a")); assert_eq!(json["thread_id"].as_str(), Some("thread-a")); - assert_eq!( - json[CODE_MODE_TOOL_NAMES_KEY], - serde_json::json!({ - "exec_command": { - "name": "exec_command", - "namespace": null, - }, - "mcp__calendar__lookup": { - "name": "lookup", - "namespace": "mcp__calendar", - }, - }) - ); + assert!(json.get(LEGACY_CODE_MODE_TOOL_NAMES_KEY).is_none()); assert_eq!( json[TOOL_NAMESPACES_INFO_KEY], serde_json::json!({ @@ -795,7 +775,7 @@ fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields( .expect("compatibility metadata json"); assert!( compatibility_metadata - .get(CODE_MODE_TOOL_NAMES_KEY) + .get(LEGACY_CODE_MODE_TOOL_NAMES_KEY) .is_none() ); assert!( @@ -809,7 +789,7 @@ fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields( .expect("turn metadata should be present"); assert_eq!(meta["model"].as_str(), Some("gpt-5.4")); assert_eq!(meta["reasoning_effort"].as_str(), Some("high")); - assert!(meta.get(CODE_MODE_TOOL_NAMES_KEY).is_none()); + assert!(meta.get(LEGACY_CODE_MODE_TOOL_NAMES_KEY).is_none()); assert!(meta.get(TOOL_NAMESPACES_INFO_KEY).is_none()); assert!(meta.get(PARENT_TURN_ID_KEY).is_none()); assert!(meta.get(WINDOW_ID_KEY).is_none()); diff --git a/codex-rs/core/tests/suite/agent_websocket.rs b/codex-rs/core/tests/suite/agent_websocket.rs index 9faff49c23..5677b03c9c 100644 --- a/codex-rs/core/tests/suite/agent_websocket.rs +++ b/codex-rs/core/tests/suite/agent_websocket.rs @@ -79,29 +79,6 @@ async fn websocket_model_switch_to_responses_lite_omits_top_level_tools() -> Res assert_eq!(non_lite_turn["model"].as_str(), Some("gpt-5.2")); assert_eq!(lite_turn["model"].as_str(), Some("gpt-5.4")); - let non_lite_turn_metadata: Value = serde_json::from_str( - non_lite_turn["client_metadata"]["x-codex-turn-metadata"] - .as_str() - .expect("non-lite turn metadata"), - )?; - assert!(non_lite_turn_metadata.get("code_mode_tool_names").is_none()); - let lite_turn_metadata: Value = serde_json::from_str( - lite_turn["client_metadata"]["x-codex-turn-metadata"] - .as_str() - .expect("Responses Lite turn metadata"), - )?; - assert_eq!( - lite_turn_metadata["code_mode_tool_names"]["view_image"], - serde_json::json!({ - "name": "view_image", - "namespace": null, - }) - ); - assert!( - lite_turn["client_metadata"] - .get("x-codex-code-mode-tool-names") - .is_none() - ); assert!( non_lite_turn .get("tools") diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 0fefaca196..270abe18d1 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -4853,6 +4853,7 @@ async fn code_mode_uses_the_first_dynamic_tool_for_a_normalized_name() -> Result model_info.tool_mode = Some(ToolMode::CodeMode); }) .with_config(|config| { + config.tool_registry.turn_metadata_includes_tool_info = true; config .features .enable(Feature::CodeMode) @@ -5041,18 +5042,9 @@ text(JSON.stringify({ .as_str() .expect("Responses Lite should contain serialized turn metadata"), )?; - assert_eq!( - metadata["code_mode_tool_names"]["foo_bar"], - serde_json::json!({ - "name": "foo-bar", - "namespace": null, - }), - ); - assert!( - metadata["code_mode_tool_names"] - .get("tool_search") - .is_none() - ); + let functions = &metadata["tool_namespaces_info"]["functions"]["functions"]; + assert_eq!(functions["foo-bar"]["code_mode_name"], "foo_bar"); + assert!(functions["foo_bar"]["code_mode_name"].is_null()); } let exec_description = model_tools diff --git a/codex-rs/core/tests/suite/responses_lite.rs b/codex-rs/core/tests/suite/responses_lite.rs index b06ccbb6a2..315be6ea55 100644 --- a/codex-rs/core/tests/suite/responses_lite.rs +++ b/codex-rs/core/tests/suite/responses_lite.rs @@ -157,15 +157,7 @@ async fn responses_lite_uses_input_items_for_instructions_and_tools() -> Result< .context("Responses request should include turn metadata")?, )?; - assert_eq!( - turn_metadata["code_mode_tool_names"]["view_image"], - serde_json::json!({ - "name": "view_image", - "namespace": null, - }) - ); assert!(turn_metadata.get("tool_namespaces_info").is_none()); - assert!(!client_metadata.contains_key("x-codex-code-mode-tool-names")); Ok(()) }