diff --git a/codex-rs/config/src/strict_config_tests.rs b/codex-rs/config/src/strict_config_tests.rs index b818366846..e22aa3fd79 100644 --- a/codex-rs/config/src/strict_config_tests.rs +++ b/codex-rs/config/src/strict_config_tests.rs @@ -95,6 +95,8 @@ fn strict_config_accepts_tool_registry_config() { for contents in [ "[features.tool_registry]\nerror_on_tool_collisions = true\n", "[profiles.work.features.tool_registry]\nerror_on_tool_collisions = true\n", + "[features.tool_registry]\ninclude_tool_namespaces_info = true\n", + "[profiles.work.features.tool_registry]\ninclude_tool_namespaces_info = true\n", ] { assert_eq!( config_error_from_ignored_toml_fields::(path, contents), diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 72543f224f..d35f67fc4a 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -3218,6 +3218,10 @@ "error_on_tool_collisions": { "description": "Fail the turn when multiple tools share the same effective name.", "type": "boolean" + }, + "include_tool_namespaces_info": { + "description": "Include the resolved tool namespace inventory in per-turn request metadata.", + "type": "boolean" } }, "type": "object" diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index af14c87f34..d83b08c3ef 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -574,11 +574,17 @@ disable_in_process_fallback = true async fn load_config_resolves_tool_registry_config() -> std::io::Result<()> { let codex_home = tempdir()?; - for (config_toml, error_on_tool_collisions) in [ - ("", false), + for (config_toml, error_on_tool_collisions, include_tool_namespaces_info) in [ + ("", false, false), ( "[features.tool_registry]\nerror_on_tool_collisions = true\n", true, + false, + ), + ( + "[features.tool_registry]\ninclude_tool_namespaces_info = true\n", + false, + true, ), ] { let config_toml: ConfigToml = @@ -594,6 +600,10 @@ async fn load_config_resolves_tool_registry_config() -> std::io::Result<()> { config.tool_registry.error_on_tool_collisions, error_on_tool_collisions ); + assert_eq!( + config.tool_registry.include_tool_namespaces_info, + include_tool_namespaces_info + ); assert!(!config.features.enabled(Feature::CodeMode)); } diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 1eaf5f096e..3730d77aed 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1115,6 +1115,8 @@ pub struct Config { pub struct ToolRegistryConfig { /// Fail the turn when multiple tools share the same effective name. pub error_on_tool_collisions: bool, + /// Include the resolved tool namespace inventory in per-turn request metadata. + pub include_tool_namespaces_info: bool, } const DEFAULT_CODE_MODE_EXEC_YIELD_TIME_MS: u64 = 30_000; @@ -3717,6 +3719,12 @@ impl Config { .and_then(|features| features.tool_registry.as_ref()) .and_then(|config| config.error_on_tool_collisions) .unwrap_or_default(), + include_tool_namespaces_info: cfg + .features + .as_ref() + .and_then(|features| features.tool_registry.as_ref()) + .and_then(|config| config.include_tool_namespaces_info) + .unwrap_or_default(), }; let code_mode = resolve_code_mode_config(&cfg); let multi_agent_v2 = resolve_multi_agent_v2_config(&cfg); diff --git a/codex-rs/core/src/session/config_lock.rs b/codex-rs/core/src/session/config_lock.rs index ecbe73091e..b38c614bf5 100644 --- a/codex-rs/core/src/session/config_lock.rs +++ b/codex-rs/core/src/session/config_lock.rs @@ -154,9 +154,13 @@ fn save_config_resolved_fields( .features .get_or_insert_with(FeaturesToml::default); features.materialize_resolved_enabled(config.features.get()); - if config.tool_registry.error_on_tool_collisions || features.tool_registry.is_some() { + if config.tool_registry.error_on_tool_collisions + || config.tool_registry.include_tool_namespaces_info + || features.tool_registry.is_some() + { features.tool_registry = Some(ToolRegistryConfigToml { error_on_tool_collisions: Some(config.tool_registry.error_on_tool_collisions), + include_tool_namespaces_info: Some(config.tool_registry.include_tool_namespaces_info), }); } let mut multi_agent_v2: MultiAgentV2ConfigToml = @@ -269,6 +273,7 @@ mod tests { let mut sc = crate::session::tests::make_session_configuration_for_tests().await; let mut config = (*sc.original_config_do_not_use).clone(); config.tool_registry.error_on_tool_collisions = true; + config.tool_registry.include_tool_namespaces_info = true; config.multi_agent_v2.subagent_developer_instructions = Some("Locked subagent developer instructions.".to_string()); config.token_budget = Some(crate::config::TokenBudgetConfig { @@ -329,6 +334,7 @@ mod tests { features.tool_registry, Some(ToolRegistryConfigToml { error_on_tool_collisions: Some(true), + include_tool_namespaces_info: Some(true), }) ); let feature_entries = features.entries(); diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index 0f3e9bec15..af31402f31 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -10,6 +10,9 @@ pub struct ToolRegistryConfigToml { /// Fail the turn when multiple tools share the same effective name. #[serde(skip_serializing_if = "Option::is_none")] pub error_on_tool_collisions: Option, + /// Include the resolved tool namespace inventory in per-turn request metadata. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_tool_namespaces_info: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index a7c003fbcb..a028c0bca5 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -29,14 +29,16 @@ fn under_development_features_are_disabled_by_default() { #[test] fn tool_registry_config_is_not_a_feature_toggle() { - let features: FeaturesToml = - toml::from_str("[tool_registry]\nerror_on_tool_collisions = true\n") - .expect("tool registry settings should deserialize"); + let features: FeaturesToml = toml::from_str( + "[tool_registry]\nerror_on_tool_collisions = true\ninclude_tool_namespaces_info = true\n", + ) + .expect("tool registry settings should deserialize"); assert_eq!( features.tool_registry, Some(crate::ToolRegistryConfigToml { error_on_tool_collisions: Some(true), + include_tool_namespaces_info: Some(true), }) ); assert!(features.entries().is_empty()); @@ -552,6 +554,7 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config( let mut features_toml = FeaturesToml { tool_registry: Some(crate::ToolRegistryConfigToml { error_on_tool_collisions: Some(true), + include_tool_namespaces_info: Some(true), }), code_mode_host: Some(FeatureToml::Config(crate::CodeModeHostConfigToml { enabled: Some(false), @@ -584,6 +587,7 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config( features_toml.tool_registry, Some(crate::ToolRegistryConfigToml { error_on_tool_collisions: Some(true), + include_tool_namespaces_info: Some(true), }) ); let entries = features_toml.entries();