mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Expose selecte namespaces as direct model tools (#28825)
## Why Som tools, such as history and notes, must remain top-level when MCP deferral is enabled while staying unavailable through code-mode `exec`. ## What changed - Added `features.code_mode.direct_only_tool_namespaces`. - Classified matching MCP tools as `DirectModelOnly`. - Kept those tools top-level in `code_mode_only`. - Excluded them from `tool_search` deferral and the nested `exec` surface. - Updated the generated config schema. ## Validation - `code_mode_only_exposes_direct_model_only_mcp_namespaces` - `load_config_resolves_code_mode_config`
This commit is contained in:
@@ -438,6 +438,7 @@ async fn load_config_resolves_code_mode_config() -> std::io::Result<()> {
|
||||
[features.code_mode]
|
||||
enabled = true
|
||||
excluded_tool_namespaces = ["mcp__codex_apps", "multi_agent_v1"]
|
||||
direct_only_tool_namespaces = ["mcp__history", "mcp__notes"]
|
||||
"#,
|
||||
)
|
||||
.expect("TOML deserialization should succeed");
|
||||
@@ -452,6 +453,10 @@ excluded_tool_namespaces = ["mcp__codex_apps", "multi_agent_v1"]
|
||||
config.code_mode.excluded_tool_namespaces,
|
||||
vec!["mcp__codex_apps".to_string(), "multi_agent_v1".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
config.code_mode.direct_only_tool_namespaces,
|
||||
vec!["mcp__history".to_string(), "mcp__notes".to_string()]
|
||||
);
|
||||
assert!(config.features.enabled(Feature::CodeMode));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1061,6 +1061,7 @@ pub struct Config {
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
|
||||
pub struct CodeModeConfig {
|
||||
pub excluded_tool_namespaces: Vec<String>,
|
||||
pub direct_only_tool_namespaces: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
@@ -2404,6 +2405,10 @@ fn resolve_code_mode_config(config_toml: &ConfigToml) -> CodeModeConfig {
|
||||
.and_then(|config| config.excluded_tool_namespaces.as_ref())
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
direct_only_tool_namespaces: base
|
||||
.and_then(|config| config.direct_only_tool_namespaces.as_ref())
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,9 @@ impl DynamicToolHandler {
|
||||
namespace.map(|namespace| namespace.name.clone()),
|
||||
tool.name.clone(),
|
||||
);
|
||||
let output_tool = dynamic_tool_to_responses_api_tool(tool).ok()?;
|
||||
let mut output_tool = dynamic_tool_to_responses_api_tool(tool).ok()?;
|
||||
// Exposure controls deferral; tool search restores this marker for deferred results.
|
||||
output_tool.defer_loading = None;
|
||||
let spec = match namespace {
|
||||
Some(namespace) => ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: namespace.name.clone(),
|
||||
|
||||
@@ -192,11 +192,41 @@ fn build_tool_specs_and_registry(
|
||||
};
|
||||
let mut planned_tools = PlannedTools::default();
|
||||
add_tool_sources(&context, &mut planned_tools);
|
||||
apply_direct_model_only_namespace_overrides(turn_context, &mut planned_tools);
|
||||
append_tool_search_executor(&context, &mut planned_tools);
|
||||
prepend_code_mode_executors(&context, &mut planned_tools);
|
||||
build_model_visible_specs_and_registry(turn_context, planned_tools)
|
||||
}
|
||||
|
||||
fn apply_direct_model_only_namespace_overrides(
|
||||
turn_context: &TurnContext,
|
||||
planned_tools: &mut PlannedTools,
|
||||
) {
|
||||
for runtime in &mut planned_tools.runtimes {
|
||||
let configured = runtime
|
||||
.tool_name()
|
||||
.namespace
|
||||
.as_ref()
|
||||
.is_some_and(|namespace| {
|
||||
turn_context
|
||||
.config
|
||||
.code_mode
|
||||
.direct_only_tool_namespaces
|
||||
.contains(namespace)
|
||||
});
|
||||
match runtime.exposure() {
|
||||
ToolExposure::Direct | ToolExposure::Deferred if configured => {
|
||||
*runtime =
|
||||
override_tool_exposure(Arc::clone(runtime), ToolExposure::DirectModelOnly);
|
||||
}
|
||||
ToolExposure::Direct
|
||||
| ToolExposure::Deferred
|
||||
| ToolExposure::DirectModelOnly
|
||||
| ToolExposure::Hidden => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
fn build_model_visible_specs_and_registry(
|
||||
turn_context: &TurnContext,
|
||||
|
||||
@@ -1011,6 +1011,48 @@ async fn code_mode_only_exposes_code_executor_and_hides_nested_tools() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn code_mode_only_exposes_configured_dynamic_namespace_directly() {
|
||||
let plan = probe_with(
|
||||
|turn| {
|
||||
set_features(turn, &[Feature::CodeMode, Feature::CodeModeOnly]);
|
||||
turn.model_info.supports_search_tool = true;
|
||||
update_config(turn, |config| {
|
||||
config.code_mode.direct_only_tool_namespaces = vec!["direct_only".to_string()];
|
||||
});
|
||||
},
|
||||
ToolPlanInputs {
|
||||
dynamic_tools: vec![dynamic_tool(
|
||||
Some("direct_only"),
|
||||
"lookup",
|
||||
/*defer_loading*/ true,
|
||||
)],
|
||||
..ToolPlanInputs::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
plan.assert_visible_contains(&[
|
||||
codex_code_mode::PUBLIC_TOOL_NAME,
|
||||
codex_code_mode::WAIT_TOOL_NAME,
|
||||
"direct_only",
|
||||
]);
|
||||
plan.assert_visible_lacks(&["tool_search"]);
|
||||
assert_eq!(
|
||||
plan.exposure(&ToolName::namespaced("direct_only", "lookup").to_string()),
|
||||
ToolExposure::DirectModelOnly
|
||||
);
|
||||
let ToolSpec::Namespace(namespace) = plan.visible_spec("direct_only") else {
|
||||
panic!("expected direct-only namespace spec");
|
||||
};
|
||||
let ResponsesApiNamespaceTool::Function(tool) = &namespace.tools[0];
|
||||
assert_eq!(tool.defer_loading, None);
|
||||
let ToolSpec::Freeform(exec) = plan.visible_spec(codex_code_mode::PUBLIC_TOOL_NAME) else {
|
||||
panic!("expected code mode exec tool");
|
||||
};
|
||||
assert!(!exec.description.contains("direct_only_lookup(args:"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn excluded_deferred_namespaces_do_not_enable_nested_tool_guidance() {
|
||||
let plan = probe_with(
|
||||
|
||||
Reference in New Issue
Block a user