From 491b4946ae578c243f884db38cdb312309598be9 Mon Sep 17 00:00:00 2001 From: Curtis 'Fjord' Hawthorne Date: Wed, 18 Feb 2026 07:31:15 -0800 Subject: [PATCH] Stop filtering model tools in js_repl_tools_only mode (#12069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This change removes tool-list filtering in `js_repl_tools_only` mode and relies on the normal model tool descriptions, while still enforcing that tool execution must go through `js_repl` + `codex.tool(...)`. ## Motivation The previous `js_repl_tools_only` filtering hid most tools from the model request, which diverged from standard tool-list behavior and made signatures less discoverable. I tested that this filtering is not needed, and the model can follow the prompt to only call tools via `js_repl`. ## What Changed - `filter_tools_for_model(...)` in `core/src/tools/spec.rs` is now a pass-through (no filtering when `js_repl_tools_only` is enabled). - Updated tests to assert that model tools are not filtered in `js_repl_tools_only` mode. - Updated dynamic-tool test to assert dynamic tools remain visible in model tool specs. - Removed obsolete test helper used only by the old filtering assertions. ## Safety / Behavior - This commit does **not** relax execution policy. - Direct model tool calls remain blocked in `js_repl_tools_only` mode (except internal `js_repl` tools), and callers are instructed to use `js_repl` + `codex.tool(...)`. ## Testing - `cargo test -p codex-core js_repl_tools_only` - Manual rollout validation showed the model can follow the `js_repl` routing instructions without needing filtered tool lists. #### [git stack](https://github.com/magus/git-stack-cli) - 👉 `1` https://github.com/openai/codex/pull/12069 - ⏳ `2` https://github.com/openai/codex/pull/10673 - ⏳ `3` https://github.com/openai/codex/pull/10670 --- codex-rs/core/src/codex.rs | 3 +- codex-rs/core/src/tools/spec.rs | 103 -------------------------------- 2 files changed, 1 insertion(+), 105 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 5e4c9bd95..46a2087f2 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -4869,8 +4869,7 @@ async fn run_sampling_request( let model_supports_parallel = turn_context.model_info.supports_parallel_tool_calls; - let tools = - crate::tools::spec::filter_tools_for_model(router.specs(), &turn_context.tools_config); + let tools = router.specs(); let base_instructions = sess.get_base_instructions().await; let prompt = Prompt { diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index ca49c50c3..d0e372be2 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -114,17 +114,6 @@ impl ToolsConfig { } } -pub(crate) fn filter_tools_for_model(tools: Vec, config: &ToolsConfig) -> Vec { - if !config.js_repl_tools_only { - return tools; - } - - tools - .into_iter() - .filter(|spec| matches!(spec.name(), "js_repl" | "js_repl_reset")) - .collect() -} - /// Generic JSON‑Schema subset needed for our tool definitions #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "type", rename_all = "lowercase")] @@ -1711,27 +1700,6 @@ mod tests { } } - fn assert_contains_tool_specs(tools: &[ToolSpec], expected_subset: &[&str]) { - use std::collections::HashSet; - let mut names = HashSet::new(); - let mut duplicates = Vec::new(); - for name in tools.iter().map(tool_name) { - if !names.insert(name) { - duplicates.push(name); - } - } - assert!( - duplicates.is_empty(), - "duplicate tool entries detected: {duplicates:?}" - ); - for expected in expected_subset { - assert!( - names.contains(expected), - "expected tool {expected} to be present; had: {names:?}" - ); - } - } - fn shell_tool_name(config: &ToolsConfig) -> Option<&'static str> { match config.shell_type { ConfigShellToolType::Default => Some("shell"), @@ -1954,77 +1922,6 @@ mod tests { assert_contains_tool_names(&tools, &["js_repl", "js_repl_reset"]); } - #[test] - fn js_repl_tools_only_filters_model_tools() { - let config = test_config(); - let model_info = - ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config); - let mut features = Features::with_defaults(); - features.enable(Feature::JsRepl); - features.enable(Feature::JsReplToolsOnly); - - let tools_config = ToolsConfig::new(&ToolsConfigParams { - model_info: &model_info, - features: &features, - web_search_mode: Some(WebSearchMode::Cached), - }); - let (tools, _) = build_specs(&tools_config, None, None, &[]).build(); - let filtered = filter_tools_for_model( - tools.iter().map(|tool| tool.spec.clone()).collect(), - &tools_config, - ); - assert_contains_tool_specs(&filtered, &["js_repl", "js_repl_reset"]); - assert!( - !filtered.iter().any(|tool| tool_name(tool) == "shell"), - "expected non-js_repl tools to be hidden when js_repl_tools_only is enabled" - ); - } - - #[test] - fn js_repl_tools_only_hides_dynamic_tools_from_model_tools() { - let config = test_config(); - let model_info = - ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config); - let mut features = Features::with_defaults(); - features.enable(Feature::JsRepl); - features.enable(Feature::JsReplToolsOnly); - - let tools_config = ToolsConfig::new(&ToolsConfigParams { - model_info: &model_info, - features: &features, - web_search_mode: Some(WebSearchMode::Cached), - }); - let dynamic_tools = vec![DynamicToolSpec { - name: "dynamic_echo".to_string(), - description: "echo dynamic payload".to_string(), - input_schema: serde_json::json!({ - "type": "object", - "properties": { - "text": {"type": "string"} - }, - "required": ["text"], - "additionalProperties": false - }), - }]; - let (tools, _) = build_specs(&tools_config, None, None, &dynamic_tools).build(); - assert!( - tools.iter().any(|tool| tool.spec.name() == "dynamic_echo"), - "expected dynamic tool in full router specs" - ); - - let filtered = filter_tools_for_model( - tools.iter().map(|tool| tool.spec.clone()).collect(), - &tools_config, - ); - assert!( - !filtered - .iter() - .any(|tool| tool_name(tool) == "dynamic_echo"), - "expected dynamic tools to be hidden from direct model tools in js_repl_tools_only mode" - ); - assert_contains_tool_specs(&filtered, &["js_repl", "js_repl_reset"]); - } - fn assert_model_tools( model_slug: &str, features: &Features,