Files
codex/codex-rs/tools/src/tool_config_tests.rs
T
jif-oai 05e171094d Remove ToolsConfig from tool planning (#22835)
## Why

`codex-tools` is meant to hold reusable tool primitives, but
`ToolsConfig` had become a second copy of core runtime decisions instead
of a small shared contract. It carried provider capabilities, auth/model
gates, permission and environment state, web/search/image feature gates,
multi-agent settings, and goal availability from core into `codex-tools`
([definition](https://github.com/openai/codex/blob/22dd9ad3929253ed24d7ee4f10f238e95ab25f37/codex-rs/tools/src/tool_config.rs#L97),
[stored on each
`TurnContext`](https://github.com/openai/codex/blob/22dd9ad3929253ed24d7ee4f10f238e95ab25f37/codex-rs/core/src/session/turn_context.rs#L87)).
Every session/context variant then had to build and mutate that snapshot
before assembling tools.

This PR removes that master object instead of renaming it. Tool planning
now reads the live `TurnContext`, where `codex-core` already owns those
decisions, while `codex-tools` keeps only reusable primitives and a
generic `ToolSetBuilder`/`ToolSet` accumulator.

## What Changed

- Removed `ToolsConfig` / `ToolsConfigParams` from `codex-tools`; the
crate keeps the shared helpers that still belong there, including
request-user-input mode selection, shell backend/type resolution,
`UnifiedExecShellMode`, and `ToolEnvironmentMode`.
- Replaced config-snapshot planning with `ToolRouter::from_turn_context`
and a `spec_plan` pipeline over `CoreToolPlanContext`, deriving provider
capabilities, auth gates, model support, feature gates, environment
count, goal support, multi-agent options, web search, and image
generation from the authoritative turn state.
- Added generic `codex_tools::ToolSetBuilder` / `ToolSet`, plus the
small core adapter needed to accumulate `CoreToolRuntime` values and
hosted model specs.
- Added the `tool_family::shell` registration module and moved
shell/unified-exec/memory accounting call sites to read the narrow
per-turn fields directly.
- Narrowed `TurnContext` to the remaining explicit per-turn fields
needed by planning: `available_models`, `unified_exec_shell_mode`, and
`goal_tools_supported`.
- Reworked MCP exposure and tool-search setup so deferred/direct MCP
behavior is driven by the current turn rather than a precomputed config
snapshot.
- Replaced the large expected-spec fixture tests with focused
behavior-level coverage for shell tools, environments, goal and
agent-job gates, MCP direct/deferred exposure, tool search,
request-plugin-install, code mode, multi-agent mode, hosted tools, and
extension executor dispatch.

## Verification

- `cargo check -p codex-tools`
- `cargo check -p codex-core --lib`
- `cargo test -p codex-tools`
- `cargo test -p codex-core spec_plan --lib`
- `cargo test -p codex-core router --lib`
2026-05-19 11:24:09 +02:00

154 lines
4.7 KiB
Rust

use codex_features::Feature;
use codex_features::Features;
use codex_protocol::config_types::ModeKind;
use codex_protocol::openai_models::ConfigShellToolType;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelVisibility;
use codex_protocol::openai_models::TruncationPolicyConfig;
use pretty_assertions::assert_eq;
use super::*;
fn model_with_shell_type(shell_type: ConfigShellToolType) -> ModelInfo {
ModelInfo {
slug: "test-model".to_string(),
display_name: "Test Model".to_string(),
description: None,
default_reasoning_level: None,
supported_reasoning_levels: Vec::new(),
shell_type,
visibility: ModelVisibility::List,
supported_in_api: true,
priority: 0,
additional_speed_tiers: Vec::new(),
service_tiers: Vec::new(),
availability_nux: None,
upgrade: None,
base_instructions: String::new(),
model_messages: None,
supports_reasoning_summaries: false,
default_reasoning_summary: Default::default(),
support_verbosity: false,
default_verbosity: None,
apply_patch_tool_type: None,
web_search_tool_type: Default::default(),
truncation_policy: TruncationPolicyConfig::tokens(/*limit*/ 1024),
supports_parallel_tool_calls: true,
supports_image_detail_original: false,
context_window: None,
max_context_window: None,
auto_compact_token_limit: None,
effective_context_window_percent: 95,
experimental_supported_tools: Vec::new(),
input_modalities: codex_protocol::openai_models::default_input_modalities(),
used_fallback_model_metadata: false,
supports_search_tool: false,
}
}
fn shell_features() -> Features {
let mut features = Features::with_defaults();
features.enable(Feature::ShellTool);
features.disable(Feature::ShellZshFork);
features.disable(Feature::UnifiedExec);
features
}
#[test]
fn shell_type_is_derived_from_model_and_feature_gates() {
let model = model_with_shell_type(ConfigShellToolType::UnifiedExec);
let mut features = shell_features();
assert_eq!(
shell_type_for_model_and_features(&model, &features),
ConfigShellToolType::ShellCommand
);
features.enable(Feature::UnifiedExec);
let expected_unified_exec = if codex_utils_pty::conpty_supported() {
ConfigShellToolType::UnifiedExec
} else {
ConfigShellToolType::ShellCommand
};
assert_eq!(
shell_type_for_model_and_features(&model, &features),
expected_unified_exec
);
features.enable(Feature::ShellZshFork);
assert_eq!(
shell_type_for_model_and_features(&model, &features),
ConfigShellToolType::ShellCommand
);
features.disable(Feature::ShellTool);
assert_eq!(
shell_type_for_model_and_features(&model, &features),
ConfigShellToolType::Disabled
);
}
#[test]
fn shell_command_backend_requires_both_shell_tool_and_zsh_fork() {
let mut features = shell_features();
assert_eq!(
shell_command_backend_for_features(&features),
ShellCommandBackendConfig::Classic
);
features.enable(Feature::ShellZshFork);
assert_eq!(
shell_command_backend_for_features(&features),
ShellCommandBackendConfig::ZshFork
);
features.disable(Feature::ShellTool);
assert_eq!(
shell_command_backend_for_features(&features),
ShellCommandBackendConfig::Classic
);
}
#[test]
fn request_user_input_modes_follow_default_mode_feature() {
let mut features = Features::with_defaults();
features.disable(Feature::DefaultModeRequestUserInput);
assert_eq!(
request_user_input_available_modes(&features),
vec![ModeKind::Plan]
);
features.enable(Feature::DefaultModeRequestUserInput);
assert_eq!(
request_user_input_available_modes(&features),
vec![ModeKind::Default, ModeKind::Plan]
);
}
#[test]
fn unified_exec_shell_mode_uses_zsh_fork_only_when_all_inputs_match() {
let exe = std::env::current_exe().expect("current exe path");
let shell = exe.clone();
let mode = UnifiedExecShellMode::for_session(
ShellCommandBackendConfig::ZshFork,
ToolUserShellType::Zsh,
Some(&shell),
Some(&exe),
);
if cfg!(unix) {
assert!(matches!(mode, UnifiedExecShellMode::ZshFork(_)));
} else {
assert_eq!(mode, UnifiedExecShellMode::Direct);
}
assert_eq!(
UnifiedExecShellMode::for_session(
ShellCommandBackendConfig::Classic,
ToolUserShellType::Zsh,
Some(&shell),
Some(&exe),
),
UnifiedExecShellMode::Direct
);
}