Remove the legacy code-mode tool metadata inventory (#37500)

## What changed

- Stop adding `code_mode_tool_names` to Responses Lite turn metadata.
- Keep `code_mode_tool_names` reserved so client metadata cannot reintroduce the removed, unbounded inventory.
- Use the opt-in `tool_namespaces_info` metadata, including each function's `code_mode_name`, when authoritative tool details are requested.

## Testing

- Update Responses Lite and code-mode tests to verify the opt-in namespace metadata and normalized code-mode names.

GitOrigin-RevId: cbfb9af984de71c7cc175d0cd47ae799a5275309
This commit is contained in:
rka-oai
2026-08-07 20:26:08 +00:00
committed by copyberry
parent 6db53df37f
commit 8e4b10446e
8 changed files with 32 additions and 112 deletions
+4 -10
View File
@@ -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<ThreadSource>,
pub(crate) sandbox: Option<String>,
pub(crate) workspaces: BTreeMap<String, TurnMetadataWorkspace>,
pub(crate) code_mode_tool_names: Option<BTreeMap<String, ToolName>>,
pub(crate) tool_namespaces_info: Option<TurnToolNamespacesInfo>,
pub(crate) turn_started_at_unix_ms: Option<i64>,
pub(crate) extra: BTreeMap<String, String>,
@@ -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<String, TurnMetadataWorkspace>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
code_mode_tool_names: Option<&'a BTreeMap<String, ToolName>>,
#[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<i64>,
-6
View File
@@ -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());
+17 -6
View File
@@ -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 {
-20
View File
@@ -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<String>,
enriched_workspaces: RwLock<Option<BTreeMap<String, TurnMetadataWorkspace>>>,
code_mode_tool_names: RwLock<Option<BTreeMap<String, ToolName>>>,
tool_namespaces_info: RwLock<Option<TurnToolNamespacesInfo>>,
turn_started_at_unix_ms: RwLock<Option<i64>>,
responsesapi_client_metadata: RwLock<BTreeMap<String, String>>,
@@ -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<serde_json::Value> {
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<String, ToolName>,
) {
*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()
+7 -27
View File
@@ -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());
@@ -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")
+4 -12
View File
@@ -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
@@ -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(())
}