mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
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`
This commit is contained in:
@@ -3,7 +3,6 @@ use std::collections::HashSet;
|
||||
use codex_features::Feature;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use codex_mcp::ToolInfo as McpToolInfo;
|
||||
use codex_tools::ToolsConfig;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::connectors;
|
||||
@@ -19,7 +18,7 @@ pub(crate) fn build_mcp_tool_exposure(
|
||||
all_mcp_tools: &[McpToolInfo],
|
||||
connectors: Option<&[connectors::AppInfo]>,
|
||||
config: &Config,
|
||||
tools_config: &ToolsConfig,
|
||||
search_tool_enabled: bool,
|
||||
) -> McpToolExposure {
|
||||
let mut deferred_tools = filter_non_codex_apps_mcp_tools_only(all_mcp_tools);
|
||||
if let Some(connectors) = connectors {
|
||||
@@ -30,7 +29,7 @@ pub(crate) fn build_mcp_tool_exposure(
|
||||
));
|
||||
}
|
||||
|
||||
let should_defer = tools_config.search_tool
|
||||
let should_defer = search_tool_enabled
|
||||
&& (config
|
||||
.features
|
||||
.enabled(Feature::ToolSearchAlwaysDeferMcpTools)
|
||||
|
||||
@@ -2,17 +2,9 @@ use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_models_manager::test_support::construct_model_info_offline_for_tests;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolsConfig;
|
||||
use codex_tools::ToolsConfigParams;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::model::JsonObject;
|
||||
use rmcp::model::Tool;
|
||||
@@ -94,34 +86,14 @@ fn tool_names(tools: &[ToolInfo]) -> HashSet<ToolName> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn tools_config_for_mcp_tool_exposure(search_tool: bool) -> ToolsConfig {
|
||||
let config = test_config().await;
|
||||
let model_info =
|
||||
construct_model_info_offline_for_tests("gpt-5.4", &config.to_models_manager_config());
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
let mut tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
tools_config.search_tool = search_tool;
|
||||
tools_config
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn directly_exposes_small_effective_tool_sets() {
|
||||
let config = test_config().await;
|
||||
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
|
||||
let mcp_tools = numbered_mcp_tools(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD - 1);
|
||||
|
||||
let exposure =
|
||||
build_mcp_tool_exposure(&mcp_tools, /*connectors*/ None, &config, &tools_config);
|
||||
let exposure = build_mcp_tool_exposure(
|
||||
&mcp_tools, /*connectors*/ None, &config, /*search_tool_enabled*/ true,
|
||||
);
|
||||
|
||||
assert_eq!(tool_names(&exposure.direct_tools), tool_names(&mcp_tools));
|
||||
assert!(exposure.deferred_tools.is_none());
|
||||
@@ -130,11 +102,11 @@ async fn directly_exposes_small_effective_tool_sets() {
|
||||
#[tokio::test]
|
||||
async fn searches_large_effective_tool_sets() {
|
||||
let config = test_config().await;
|
||||
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
|
||||
let mcp_tools = numbered_mcp_tools(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD);
|
||||
|
||||
let exposure =
|
||||
build_mcp_tool_exposure(&mcp_tools, /*connectors*/ None, &config, &tools_config);
|
||||
let exposure = build_mcp_tool_exposure(
|
||||
&mcp_tools, /*connectors*/ None, &config, /*search_tool_enabled*/ true,
|
||||
);
|
||||
|
||||
assert!(exposure.direct_tools.is_empty());
|
||||
let deferred_tools = exposure
|
||||
@@ -151,7 +123,6 @@ async fn always_defer_feature_defers_apps_too() {
|
||||
.features
|
||||
.enable(Feature::ToolSearchAlwaysDeferMcpTools)
|
||||
.expect("test config should allow feature update");
|
||||
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
|
||||
let mcp_tools = vec![
|
||||
make_mcp_tool(
|
||||
"rmcp",
|
||||
@@ -176,7 +147,7 @@ async fn always_defer_feature_defers_apps_too() {
|
||||
&mcp_tools,
|
||||
Some(connectors.as_slice()),
|
||||
&config,
|
||||
&tools_config,
|
||||
/*search_tool_enabled*/ true,
|
||||
);
|
||||
|
||||
assert!(exposure.direct_tools.is_empty());
|
||||
|
||||
@@ -43,14 +43,16 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
|
||||
(None, "shell_command") => serde_json::from_str::<ShellCommandToolCallParams>(arguments)
|
||||
.ok()
|
||||
.map(|params| {
|
||||
if !invocation.turn.tools_config.allow_login_shell && params.login == Some(true) {
|
||||
if !invocation.turn.config.permissions.allow_login_shell
|
||||
&& params.login == Some(true)
|
||||
{
|
||||
#[allow(deprecated)]
|
||||
let cwd = invocation.turn.resolve_path(params.workdir).to_path_buf();
|
||||
return (Vec::new(), cwd);
|
||||
}
|
||||
let use_login_shell = params
|
||||
.login
|
||||
.unwrap_or(invocation.turn.tools_config.allow_login_shell);
|
||||
.unwrap_or(invocation.turn.config.permissions.allow_login_shell);
|
||||
let command = invocation
|
||||
.session
|
||||
.user_shell()
|
||||
@@ -65,8 +67,8 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
|
||||
let command = crate::tools::handlers::unified_exec::get_command(
|
||||
¶ms,
|
||||
invocation.session.user_shell(),
|
||||
&invocation.turn.tools_config.unified_exec_shell_mode,
|
||||
invocation.turn.tools_config.allow_login_shell,
|
||||
&invocation.turn.unified_exec_shell_mode,
|
||||
invocation.turn.config.permissions.allow_login_shell,
|
||||
)
|
||||
.ok()?;
|
||||
#[allow(deprecated)]
|
||||
|
||||
@@ -93,6 +93,7 @@ use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::models::format_allow_prefixes;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::FileChange;
|
||||
@@ -355,8 +356,8 @@ use codex_protocol::protocol::TokenUsageInfo;
|
||||
use codex_protocol::protocol::WarningEvent;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_tools::ToolEnvironmentMode;
|
||||
use codex_tools::ToolsConfig;
|
||||
use codex_tools::ToolsConfigParams;
|
||||
use codex_tools::UnifiedExecShellMode;
|
||||
use codex_tools::shell_command_backend_for_features;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
#[cfg(test)]
|
||||
use codex_utils_stream_parser::ProposedPlanSegment;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use super::turn_context::image_generation_tool_auth_allowed;
|
||||
use super::*;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
@@ -23,61 +22,21 @@ pub(super) async fn spawn_review_thread(
|
||||
let mut review_features = sess.features.clone();
|
||||
let _ = review_features.disable(Feature::WebSearchRequest);
|
||||
let _ = review_features.disable(Feature::WebSearchCached);
|
||||
let _ = review_features.disable(Feature::Goals);
|
||||
let review_web_search_mode = WebSearchMode::Disabled;
|
||||
let goal_tools_supported = !config.ephemeral && parent_turn_context.tools_config.goal_tools;
|
||||
let provider_capabilities = parent_turn_context.provider.capabilities();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &review_model_info,
|
||||
available_models: &sess
|
||||
.services
|
||||
.models_manager
|
||||
.list_models(RefreshStrategy::OnlineIfUncached)
|
||||
.await,
|
||||
features: &review_features,
|
||||
image_generation_tool_auth_allowed: image_generation_tool_auth_allowed(Some(
|
||||
sess.services.auth_manager.as_ref(),
|
||||
)),
|
||||
web_search_mode: Some(review_web_search_mode),
|
||||
session_source: parent_turn_context.session_source.clone(),
|
||||
permission_profile: &parent_turn_context.permission_profile,
|
||||
windows_sandbox_level: parent_turn_context.windows_sandbox_level,
|
||||
})
|
||||
.with_namespace_tools_capability(provider_capabilities.namespace_tools)
|
||||
.with_image_generation_capability(provider_capabilities.image_generation)
|
||||
.with_web_search_capability(provider_capabilities.web_search)
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
let goal_tools_supported = !config.ephemeral && parent_turn_context.goal_tools_enabled();
|
||||
let available_models = sess
|
||||
.services
|
||||
.models_manager
|
||||
.list_models(RefreshStrategy::OnlineIfUncached)
|
||||
.await;
|
||||
let shell_command_backend = shell_command_backend_for_features(review_features.get());
|
||||
let unified_exec_shell_mode = UnifiedExecShellMode::for_session(
|
||||
shell_command_backend,
|
||||
crate::tools::tool_user_shell_type(sess.services.user_shell.as_ref()),
|
||||
sess.services.shell_zsh_path.as_ref(),
|
||||
sess.services.main_execve_wrapper_exe.as_ref(),
|
||||
)
|
||||
.with_web_search_config(/*web_search_config*/ None)
|
||||
.with_allow_login_shell(config.permissions.allow_login_shell)
|
||||
.with_environment_mode(parent_turn_context.tools_config.environment_mode)
|
||||
.with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_multi_agent_v2_tool_namespace(config.multi_agent_v2.tool_namespace.clone())
|
||||
.with_multi_agent_v2_non_code_mode_only(config.multi_agent_v2.non_code_mode_only)
|
||||
.with_goal_tools_allowed(goal_tools_supported)
|
||||
.with_max_concurrent_threads_per_session(config.agent_max_threads)
|
||||
.with_wait_agent_min_timeout_ms(
|
||||
review_features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(config.multi_agent_v2.min_wait_timeout_ms),
|
||||
)
|
||||
.with_wait_agent_max_timeout_ms(
|
||||
review_features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(config.multi_agent_v2.max_wait_timeout_ms),
|
||||
)
|
||||
.with_wait_agent_default_timeout_ms(
|
||||
review_features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(config.multi_agent_v2.default_wait_timeout_ms),
|
||||
)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&config.agent_roles,
|
||||
));
|
||||
);
|
||||
|
||||
let review_prompt = resolved.prompt.clone();
|
||||
let provider = parent_turn_context.provider.clone();
|
||||
@@ -139,8 +98,10 @@ pub(super) async fn spawn_review_thread(
|
||||
session_source,
|
||||
thread_source: parent_turn_context.thread_source,
|
||||
environments: parent_turn_context.environments.clone(),
|
||||
tools_config,
|
||||
features: parent_turn_context.features.clone(),
|
||||
available_models,
|
||||
unified_exec_shell_mode,
|
||||
goal_tools_supported,
|
||||
features: review_features,
|
||||
ghost_snapshot: parent_turn_context.ghost_snapshot.clone(),
|
||||
current_date: parent_turn_context.current_date.clone(),
|
||||
timezone: parent_turn_context.timezone.clone(),
|
||||
|
||||
@@ -536,8 +536,8 @@ async fn preview_session_start_hooks(
|
||||
}
|
||||
|
||||
fn test_tool_runtime(session: Arc<Session>, turn_context: Arc<TurnContext>) -> ToolCallRuntime {
|
||||
let router = Arc::new(ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
let router = Arc::new(ToolRouter::from_turn_context(
|
||||
&turn_context,
|
||||
crate::tools::router::ToolRouterParams {
|
||||
mcp_tools: None,
|
||||
deferred_mcp_tools: None,
|
||||
@@ -9259,8 +9259,8 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
|
||||
.await
|
||||
};
|
||||
let deferred_mcp_tools = Some(tools.clone());
|
||||
let router = ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
let router = ToolRouter::from_turn_context(
|
||||
&turn_context,
|
||||
crate::tools::router::ToolRouterParams {
|
||||
deferred_mcp_tools,
|
||||
mcp_tools: Some(tools),
|
||||
@@ -9828,9 +9828,10 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
|
||||
turn_context_mut.permission_profile = PermissionProfile::Disabled;
|
||||
|
||||
let file_system_sandbox_policy = turn_context.file_system_sandbox_policy();
|
||||
let command = session
|
||||
.user_shell()
|
||||
.derive_exec_args(command_script, turn_context.tools_config.allow_login_shell);
|
||||
let command = session.user_shell().derive_exec_args(
|
||||
command_script,
|
||||
turn_context.config.permissions.allow_login_shell,
|
||||
);
|
||||
let exec_approval_requirement = session
|
||||
.services
|
||||
.exec_policy
|
||||
|
||||
@@ -55,6 +55,8 @@ use crate::tools::parallel::ToolCallRuntime;
|
||||
use crate::tools::registry::ToolArgumentDiffConsumer;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
use crate::tools::router::extension_tool_executors;
|
||||
use crate::tools::spec_plan::search_tool_enabled;
|
||||
use crate::tools::spec_plan::tool_suggest_enabled;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use crate::turn_timing::record_turn_ttft_metric;
|
||||
use crate::util::backoff;
|
||||
@@ -1120,7 +1122,7 @@ pub(crate) async fn built_tools(
|
||||
None
|
||||
};
|
||||
let auth = sess.services.auth_manager.auth().await;
|
||||
let discoverable_tools = if apps_enabled && turn_context.tools_config.tool_suggest {
|
||||
let discoverable_tools = if apps_enabled && tool_suggest_enabled(turn_context) {
|
||||
if let Some(accessible_connectors) = accessible_connectors_with_enabled_state.as_ref() {
|
||||
match connectors::list_tool_suggest_discoverable_tools_with_auth(
|
||||
&turn_context.config,
|
||||
@@ -1152,12 +1154,12 @@ pub(crate) async fn built_tools(
|
||||
&all_mcp_tools,
|
||||
connectors.as_deref(),
|
||||
&turn_context.config,
|
||||
&turn_context.tools_config,
|
||||
search_tool_enabled(turn_context),
|
||||
);
|
||||
let mcp_tools = has_mcp_servers.then_some(mcp_tool_exposure.direct_tools);
|
||||
let deferred_mcp_tools = mcp_tool_exposure.deferred_tools;
|
||||
Ok(Arc::new(ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
Ok(Arc::new(ToolRouter::from_turn_context(
|
||||
turn_context,
|
||||
ToolRouterParams {
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
|
||||
@@ -14,10 +14,6 @@ use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub(super) fn image_generation_tool_auth_allowed(auth_manager: Option<&AuthManager>) -> bool {
|
||||
auth_manager.is_some_and(AuthManager::current_auth_uses_codex_backend)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct TurnSkillsContext {
|
||||
pub(crate) outcome: Arc<SkillLoadOutcome>,
|
||||
@@ -84,7 +80,9 @@ pub struct TurnContext {
|
||||
pub(crate) network: Option<NetworkProxy>,
|
||||
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
|
||||
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
|
||||
pub(crate) tools_config: ToolsConfig,
|
||||
pub(crate) available_models: Vec<ModelPreset>,
|
||||
pub(crate) unified_exec_shell_mode: UnifiedExecShellMode,
|
||||
pub(crate) goal_tools_supported: bool,
|
||||
pub features: ManagedFeatures,
|
||||
pub(crate) ghost_snapshot: GhostSnapshotConfig,
|
||||
pub(crate) final_output_json_schema: Option<Value>,
|
||||
@@ -156,6 +154,14 @@ impl TurnContext {
|
||||
self.features.apps_enabled_for_auth(uses_codex_backend)
|
||||
}
|
||||
|
||||
pub(crate) fn tool_environment_mode(&self) -> ToolEnvironmentMode {
|
||||
ToolEnvironmentMode::from_count(self.environments.turn_environments.len())
|
||||
}
|
||||
|
||||
pub(crate) fn goal_tools_enabled(&self) -> bool {
|
||||
self.goal_tools_supported && self.features.get().enabled(Feature::Goals)
|
||||
}
|
||||
|
||||
pub(crate) async fn with_model(
|
||||
&self,
|
||||
model: String,
|
||||
@@ -195,61 +201,9 @@ impl TurnContext {
|
||||
/*developer_instructions*/ None,
|
||||
);
|
||||
let features = self.features.clone();
|
||||
let provider_capabilities = self.provider.capabilities();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &models_manager
|
||||
.list_models(RefreshStrategy::OnlineIfUncached)
|
||||
.await,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: image_generation_tool_auth_allowed(
|
||||
self.auth_manager.as_deref(),
|
||||
),
|
||||
web_search_mode: self.tools_config.web_search_mode,
|
||||
session_source: self.session_source.clone(),
|
||||
permission_profile: &self.permission_profile,
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
})
|
||||
.with_namespace_tools_capability(provider_capabilities.namespace_tools)
|
||||
.with_image_generation_capability(provider_capabilities.image_generation)
|
||||
.with_web_search_capability(provider_capabilities.web_search)
|
||||
.with_unified_exec_shell_mode(self.tools_config.unified_exec_shell_mode.clone())
|
||||
.with_web_search_config(self.tools_config.web_search_config.clone())
|
||||
.with_allow_login_shell(self.tools_config.allow_login_shell)
|
||||
.with_environment_mode(self.tools_config.environment_mode)
|
||||
.with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_multi_agent_v2_tool_namespace(config.multi_agent_v2.tool_namespace.clone())
|
||||
.with_multi_agent_v2_non_code_mode_only(config.multi_agent_v2.non_code_mode_only)
|
||||
.with_goal_tools_allowed(self.tools_config.goal_tools)
|
||||
.with_max_concurrent_threads_per_session(
|
||||
config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(config.multi_agent_v2.max_concurrent_threads_per_session),
|
||||
)
|
||||
.with_wait_agent_min_timeout_ms(
|
||||
config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(config.multi_agent_v2.min_wait_timeout_ms),
|
||||
)
|
||||
.with_wait_agent_max_timeout_ms(
|
||||
config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(config.multi_agent_v2.max_wait_timeout_ms),
|
||||
)
|
||||
.with_wait_agent_default_timeout_ms(
|
||||
config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(config.multi_agent_v2.default_wait_timeout_ms),
|
||||
)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&config.agent_roles,
|
||||
));
|
||||
let available_models = models_manager
|
||||
.list_models(RefreshStrategy::OnlineIfUncached)
|
||||
.await;
|
||||
|
||||
Self {
|
||||
sub_id: self.sub_id.clone(),
|
||||
@@ -283,7 +237,9 @@ impl TurnContext {
|
||||
network: self.network.clone(),
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
shell_environment_policy: self.shell_environment_policy.clone(),
|
||||
tools_config,
|
||||
available_models,
|
||||
unified_exec_shell_mode: self.unified_exec_shell_mode.clone(),
|
||||
goal_tools_supported: self.goal_tools_supported,
|
||||
features,
|
||||
ghost_snapshot: self.ghost_snapshot.clone(),
|
||||
final_output_json_schema: self.final_output_json_schema.clone(),
|
||||
@@ -503,72 +459,18 @@ impl Session {
|
||||
model_info.slug.as_str(),
|
||||
);
|
||||
let session_source = session_configuration.session_source.clone();
|
||||
let image_generation_tool_auth_allowed =
|
||||
image_generation_tool_auth_allowed(auth_manager.as_deref());
|
||||
let auth_manager_for_context = auth_manager.clone();
|
||||
let provider_for_context = create_model_provider(provider, auth_manager);
|
||||
let provider_capabilities = provider_for_context.capabilities();
|
||||
let session_telemetry_for_context = session_telemetry;
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &models_manager.try_list_models().unwrap_or_default(),
|
||||
features: &per_turn_config.features,
|
||||
image_generation_tool_auth_allowed,
|
||||
web_search_mode: Some(per_turn_config.web_search_mode.value()),
|
||||
session_source: session_source.clone(),
|
||||
permission_profile: &session_configuration.permission_profile(),
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
})
|
||||
.with_namespace_tools_capability(provider_capabilities.namespace_tools)
|
||||
.with_image_generation_capability(provider_capabilities.image_generation)
|
||||
.with_web_search_capability(provider_capabilities.web_search)
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
let available_models = models_manager.try_list_models().unwrap_or_default();
|
||||
let shell_command_backend =
|
||||
shell_command_backend_for_features(per_turn_config.features.get());
|
||||
let unified_exec_shell_mode = UnifiedExecShellMode::for_session(
|
||||
shell_command_backend,
|
||||
crate::tools::tool_user_shell_type(user_shell),
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
)
|
||||
.with_web_search_config(per_turn_config.web_search_config.clone())
|
||||
.with_allow_login_shell(per_turn_config.permissions.allow_login_shell)
|
||||
.with_environment_mode(ToolEnvironmentMode::from_count(
|
||||
environments.turn_environments.len(),
|
||||
))
|
||||
.with_spawn_agent_usage_hint(per_turn_config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(per_turn_config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(per_turn_config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_multi_agent_v2_tool_namespace(per_turn_config.multi_agent_v2.tool_namespace.clone())
|
||||
.with_multi_agent_v2_non_code_mode_only(per_turn_config.multi_agent_v2.non_code_mode_only)
|
||||
.with_goal_tools_allowed(goal_tools_supported)
|
||||
.with_max_concurrent_threads_per_session(
|
||||
per_turn_config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(
|
||||
per_turn_config
|
||||
.multi_agent_v2
|
||||
.max_concurrent_threads_per_session,
|
||||
),
|
||||
)
|
||||
.with_wait_agent_min_timeout_ms(
|
||||
per_turn_config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(per_turn_config.multi_agent_v2.min_wait_timeout_ms),
|
||||
)
|
||||
.with_wait_agent_max_timeout_ms(
|
||||
per_turn_config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(per_turn_config.multi_agent_v2.max_wait_timeout_ms),
|
||||
)
|
||||
.with_wait_agent_default_timeout_ms(
|
||||
per_turn_config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentV2)
|
||||
.then_some(per_turn_config.multi_agent_v2.default_wait_timeout_ms),
|
||||
)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&per_turn_config.agent_roles,
|
||||
));
|
||||
);
|
||||
|
||||
let mut per_turn_config = per_turn_config;
|
||||
per_turn_config.service_tier = per_turn_config
|
||||
@@ -616,7 +518,9 @@ impl Session {
|
||||
network,
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(),
|
||||
tools_config,
|
||||
available_models,
|
||||
unified_exec_shell_mode,
|
||||
goal_tools_supported,
|
||||
features: per_turn_config.features.clone(),
|
||||
ghost_snapshot: per_turn_config.ghost_snapshot.clone(),
|
||||
final_output_json_schema: None,
|
||||
|
||||
@@ -268,8 +268,8 @@ async fn handle_output_item_done_returns_contributed_last_agent_message() {
|
||||
session.services.extensions = Arc::new(builder.build());
|
||||
let session = Arc::new(session);
|
||||
let turn_context = Arc::new(turn_context);
|
||||
let router = Arc::new(ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
let router = Arc::new(ToolRouter::from_turn_context(
|
||||
&turn_context,
|
||||
crate::tools::router::ToolRouterParams {
|
||||
mcp_tools: None,
|
||||
deferred_mcp_tools: None,
|
||||
|
||||
@@ -183,7 +183,7 @@ impl ToolExecutor<ToolInvocation> for ShellCommandHandler {
|
||||
session.as_ref(),
|
||||
turn.as_ref(),
|
||||
session.conversation_id,
|
||||
turn.tools_config.allow_login_shell,
|
||||
turn.config.permissions.allow_login_shell,
|
||||
)?;
|
||||
let shell_type = Some(session.user_shell().shell_type.clone());
|
||||
run_exec_like(RunExecLikeArgs {
|
||||
|
||||
@@ -144,8 +144,8 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
|
||||
let resolved_command = get_command(
|
||||
&args,
|
||||
session.user_shell(),
|
||||
&turn.tools_config.unified_exec_shell_mode,
|
||||
turn.tools_config.allow_login_shell,
|
||||
&turn.unified_exec_shell_mode,
|
||||
turn.config.permissions.allow_login_shell,
|
||||
)
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
let command = resolved_command.command;
|
||||
|
||||
@@ -14,6 +14,7 @@ pub(crate) mod runtimes;
|
||||
pub(crate) mod sandboxing;
|
||||
pub(crate) mod spec_plan;
|
||||
pub(crate) mod tool_dispatch_trace;
|
||||
pub(crate) mod tool_family;
|
||||
pub(crate) mod tool_search_entry;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
@@ -287,8 +287,15 @@ impl ToolRegistry {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn has_tool(&self, name: &ToolName) -> bool {
|
||||
self.tool(name).is_some()
|
||||
pub(crate) fn tool_names_for_test(&self) -> Vec<ToolName> {
|
||||
let mut names = self.tools.keys().cloned().collect::<Vec<_>>();
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn tool_exposure(&self, name: &ToolName) -> Option<ToolExposure> {
|
||||
self.tools.get(name).map(|tool| tool.exposure())
|
||||
}
|
||||
|
||||
pub(crate) fn create_diff_consumer(
|
||||
|
||||
@@ -17,7 +17,6 @@ use codex_tools::ToolCall as ExtensionToolCall;
|
||||
use codex_tools::ToolExecutor;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::ToolsConfig;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@@ -46,8 +45,8 @@ pub(crate) struct ToolRouterParams<'a> {
|
||||
}
|
||||
|
||||
impl ToolRouter {
|
||||
pub fn from_config(config: &ToolsConfig, params: ToolRouterParams<'_>) -> Self {
|
||||
build_tool_router(config, params)
|
||||
pub fn from_turn_context(turn_context: &TurnContext, params: ToolRouterParams<'_>) -> Self {
|
||||
build_tool_router(turn_context, params)
|
||||
}
|
||||
|
||||
pub(crate) fn from_parts(registry: ToolRegistry, model_visible_specs: Vec<ToolSpec>) -> Self {
|
||||
@@ -61,6 +60,19 @@ impl ToolRouter {
|
||||
self.model_visible_specs.clone()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn registered_tool_names_for_test(&self) -> Vec<ToolName> {
|
||||
self.registry.tool_names_for_test()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn tool_exposure_for_test(
|
||||
&self,
|
||||
name: &ToolName,
|
||||
) -> Option<crate::tools::registry::ToolExposure> {
|
||||
self.registry.tool_exposure(name)
|
||||
}
|
||||
|
||||
pub(crate) fn create_diff_consumer(
|
||||
&self,
|
||||
tool_name: &ToolName,
|
||||
|
||||
@@ -106,8 +106,8 @@ async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow
|
||||
.await
|
||||
.list_all_tools()
|
||||
.await;
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
let router = ToolRouter::from_turn_context(
|
||||
&turn,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: Some(mcp_tools),
|
||||
@@ -172,8 +172,8 @@ async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()
|
||||
#[tokio::test]
|
||||
async fn mcp_parallel_support_uses_handler_data() -> anyhow::Result<()> {
|
||||
let (_, turn) = make_session_and_context().await;
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
let router = ToolRouter::from_turn_context(
|
||||
&turn,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: Some(vec![
|
||||
@@ -220,8 +220,8 @@ async fn mcp_parallel_support_uses_handler_data() -> anyhow::Result<()> {
|
||||
#[tokio::test]
|
||||
async fn tools_without_handlers_do_not_support_parallel() -> anyhow::Result<()> {
|
||||
let (_, turn) = make_session_and_context().await;
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
let router = ToolRouter::from_turn_context(
|
||||
&turn,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: None,
|
||||
@@ -272,8 +272,8 @@ async fn specs_filter_deferred_dynamic_tools() -> anyhow::Result<()> {
|
||||
},
|
||||
];
|
||||
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
let router = ToolRouter::from_turn_context(
|
||||
&turn,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: None,
|
||||
@@ -328,8 +328,8 @@ async fn extension_tool_executors_are_model_visible_and_dispatchable() -> anyhow
|
||||
let (mut session, turn) = make_session_and_context().await;
|
||||
session.services.extensions = extension_tool_test_registry();
|
||||
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
let router = ToolRouter::from_turn_context(
|
||||
&turn,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: None,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use crate::config::DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS;
|
||||
use crate::config::DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS;
|
||||
use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::tools::code_mode::execute_spec::create_code_mode_tool;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::handlers::ApplyPatchHandler;
|
||||
@@ -8,8 +6,6 @@ use crate::tools::handlers::CodeModeExecuteHandler;
|
||||
use crate::tools::handlers::CodeModeWaitHandler;
|
||||
use crate::tools::handlers::CreateGoalHandler;
|
||||
use crate::tools::handlers::DynamicToolHandler;
|
||||
use crate::tools::handlers::ExecCommandHandler;
|
||||
use crate::tools::handlers::ExecCommandHandlerOptions;
|
||||
use crate::tools::handlers::GetGoalHandler;
|
||||
use crate::tools::handlers::ListMcpResourceTemplatesHandler;
|
||||
use crate::tools::handlers::ListMcpResourcesHandler;
|
||||
@@ -19,13 +15,10 @@ use crate::tools::handlers::ReadMcpResourceHandler;
|
||||
use crate::tools::handlers::RequestPermissionsHandler;
|
||||
use crate::tools::handlers::RequestPluginInstallHandler;
|
||||
use crate::tools::handlers::RequestUserInputHandler;
|
||||
use crate::tools::handlers::ShellCommandHandler;
|
||||
use crate::tools::handlers::ShellCommandHandlerOptions;
|
||||
use crate::tools::handlers::TestSyncHandler;
|
||||
use crate::tools::handlers::ToolSearchHandler;
|
||||
use crate::tools::handlers::UpdateGoalHandler;
|
||||
use crate::tools::handlers::ViewImageHandler;
|
||||
use crate::tools::handlers::WriteStdinHandler;
|
||||
use crate::tools::handlers::agent_jobs::ReportAgentJobResultHandler;
|
||||
use crate::tools::handlers::agent_jobs::SpawnAgentsOnCsvHandler;
|
||||
use crate::tools::handlers::extension_tools::ExtensionToolAdapter;
|
||||
@@ -55,9 +48,15 @@ use crate::tools::registry::ToolRegistry;
|
||||
use crate::tools::registry::override_tool_exposure;
|
||||
use crate::tools::router::ToolRouter;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
use crate::tools::tool_family::shell::ShellToolsOptions;
|
||||
use crate::tools::tool_family::shell::build_shell_tools;
|
||||
use codex_features::Feature;
|
||||
use codex_login::AuthManager;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
@@ -68,9 +67,12 @@ use codex_tools::ToolExecutor;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolOutput;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::ToolsConfig;
|
||||
use codex_tools::can_request_original_image_detail;
|
||||
use codex_tools::collect_code_mode_exec_prompt_tool_definitions;
|
||||
use codex_tools::default_namespace_description;
|
||||
use codex_tools::request_user_input_available_modes;
|
||||
use codex_tools::shell_command_backend_for_features;
|
||||
use codex_tools::shell_type_for_model_and_features;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
@@ -78,8 +80,34 @@ use tracing::warn;
|
||||
|
||||
const MULTI_AGENT_V2_NAMESPACE_DESCRIPTION: &str = "Tools for spawning and managing sub-agents.";
|
||||
|
||||
type PlannedRuntime = Arc<dyn CoreToolRuntime>;
|
||||
|
||||
#[derive(Default)]
|
||||
struct PlannedTools {
|
||||
runtimes: Vec<PlannedRuntime>,
|
||||
hosted_specs: Vec<ToolSpec>,
|
||||
}
|
||||
|
||||
impl PlannedTools {
|
||||
fn add_runtime<T>(&mut self, runtime: T)
|
||||
where
|
||||
T: CoreToolRuntime + 'static,
|
||||
{
|
||||
self.runtimes.push(Arc::new(runtime));
|
||||
}
|
||||
|
||||
fn add_runtime_arc(&mut self, runtime: PlannedRuntime) {
|
||||
self.runtimes.push(runtime);
|
||||
}
|
||||
|
||||
fn runtimes(&self) -> &[PlannedRuntime] {
|
||||
&self.runtimes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ToolRegistryBuildParams<'a> {
|
||||
struct CoreToolPlanContext<'a> {
|
||||
turn_context: &'a TurnContext,
|
||||
mcp_tools: Option<&'a [ToolInfo]>,
|
||||
deferred_mcp_tools: Option<&'a [ToolInfo]>,
|
||||
discoverable_tools: Option<&'a [DiscoverableTool]>,
|
||||
@@ -89,13 +117,16 @@ struct ToolRegistryBuildParams<'a> {
|
||||
wait_agent_timeouts: WaitAgentTimeoutOptions,
|
||||
}
|
||||
|
||||
pub(crate) fn build_tool_router(config: &ToolsConfig, params: ToolRouterParams<'_>) -> ToolRouter {
|
||||
let (model_visible_specs, registry) = build_tool_specs_and_registry(config, params);
|
||||
pub(crate) fn build_tool_router(
|
||||
turn_context: &TurnContext,
|
||||
params: ToolRouterParams<'_>,
|
||||
) -> ToolRouter {
|
||||
let (model_visible_specs, registry) = build_tool_specs_and_registry(turn_context, params);
|
||||
ToolRouter::from_parts(registry, model_visible_specs)
|
||||
}
|
||||
|
||||
fn build_tool_specs_and_registry(
|
||||
config: &ToolsConfig,
|
||||
turn_context: &TurnContext,
|
||||
params: ToolRouterParams<'_>,
|
||||
) -> (Vec<ToolSpec>, ToolRegistry) {
|
||||
let ToolRouterParams {
|
||||
@@ -107,65 +138,73 @@ fn build_tool_specs_and_registry(
|
||||
} = params;
|
||||
let default_agent_type_description =
|
||||
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
|
||||
let mut executors = collect_tool_executors(
|
||||
config,
|
||||
ToolRegistryBuildParams {
|
||||
mcp_tools: mcp_tools.as_deref(),
|
||||
deferred_mcp_tools: deferred_mcp_tools.as_deref(),
|
||||
discoverable_tools: discoverable_tools.as_deref(),
|
||||
extension_tool_executors: &extension_tool_executors,
|
||||
dynamic_tools,
|
||||
default_agent_type_description: &default_agent_type_description,
|
||||
wait_agent_timeouts: wait_agent_timeout_options(config),
|
||||
},
|
||||
);
|
||||
append_tool_search_executor(config, &mut executors);
|
||||
prepend_code_mode_executors(config, &mut executors);
|
||||
build_model_visible_specs_and_registry(config, executors, hosted_model_tool_specs(config))
|
||||
let context = CoreToolPlanContext {
|
||||
turn_context,
|
||||
mcp_tools: mcp_tools.as_deref(),
|
||||
deferred_mcp_tools: deferred_mcp_tools.as_deref(),
|
||||
discoverable_tools: discoverable_tools.as_deref(),
|
||||
extension_tool_executors: &extension_tool_executors,
|
||||
dynamic_tools,
|
||||
default_agent_type_description: &default_agent_type_description,
|
||||
wait_agent_timeouts: wait_agent_timeout_options(turn_context),
|
||||
};
|
||||
let mut planned_tools = PlannedTools::default();
|
||||
add_tool_sources(&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 build_model_visible_specs_and_registry(
|
||||
config: &ToolsConfig,
|
||||
executors: Vec<Arc<dyn CoreToolRuntime>>,
|
||||
hosted_specs: Vec<ToolSpec>,
|
||||
turn_context: &TurnContext,
|
||||
planned_tools: PlannedTools,
|
||||
) -> (Vec<ToolSpec>, ToolRegistry) {
|
||||
let PlannedTools {
|
||||
runtimes,
|
||||
hosted_specs,
|
||||
} = planned_tools;
|
||||
let mut specs = Vec::new();
|
||||
let mut seen_tool_names = HashSet::new();
|
||||
for executor in &executors {
|
||||
let tool_name = executor.tool_name();
|
||||
for runtime in &runtimes {
|
||||
let tool_name = runtime.tool_name();
|
||||
if !seen_tool_names.insert(tool_name.clone()) {
|
||||
continue;
|
||||
}
|
||||
let exposure = executor.exposure();
|
||||
let exposure = runtime.exposure();
|
||||
if exposure.is_direct()
|
||||
&& !is_hidden_by_code_mode_only(config, &tool_name, exposure)
|
||||
&& let Some(spec) = executor.spec()
|
||||
&& !is_hidden_by_code_mode_only(turn_context, &tool_name, exposure)
|
||||
&& let Some(spec) = runtime.spec()
|
||||
{
|
||||
specs.push(spec_for_model_request(config, exposure, spec));
|
||||
specs.push(spec_for_model_request(turn_context, exposure, spec));
|
||||
}
|
||||
}
|
||||
for spec in hosted_specs {
|
||||
if !is_hidden_by_code_mode_only(config, &ToolName::plain(spec.name()), ToolExposure::Direct)
|
||||
{
|
||||
if !is_hidden_by_code_mode_only(
|
||||
turn_context,
|
||||
&ToolName::plain(spec.name()),
|
||||
ToolExposure::Direct,
|
||||
) {
|
||||
specs.push(spec);
|
||||
}
|
||||
}
|
||||
|
||||
let registry = ToolRegistry::from_tools(executors);
|
||||
let registry = ToolRegistry::from_tools(runtimes);
|
||||
let model_visible_specs = merge_into_namespaces(specs)
|
||||
.into_iter()
|
||||
.filter(|spec| config.namespace_tools || !matches!(spec, ToolSpec::Namespace(_)))
|
||||
.filter(|spec| {
|
||||
namespace_tools_enabled(turn_context) || !matches!(spec, ToolSpec::Namespace(_))
|
||||
})
|
||||
.collect();
|
||||
|
||||
(model_visible_specs, registry)
|
||||
}
|
||||
|
||||
fn spec_for_model_request(
|
||||
config: &ToolsConfig,
|
||||
turn_context: &TurnContext,
|
||||
exposure: ToolExposure,
|
||||
spec: ToolSpec,
|
||||
) -> ToolSpec {
|
||||
if config.code_mode_enabled
|
||||
if code_mode_enabled(turn_context)
|
||||
&& exposure != ToolExposure::DirectModelOnly
|
||||
&& codex_code_mode::is_code_mode_nested_tool(spec.name())
|
||||
{
|
||||
@@ -175,33 +214,104 @@ fn spec_for_model_request(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hosted_model_tool_specs(config: &ToolsConfig) -> Vec<ToolSpec> {
|
||||
pub(crate) fn hosted_model_tool_specs(turn_context: &TurnContext) -> Vec<ToolSpec> {
|
||||
let mut specs = Vec::new();
|
||||
let provider_capabilities = turn_context.provider.capabilities();
|
||||
let web_search_mode = provider_capabilities
|
||||
.web_search
|
||||
.then_some(turn_context.config.web_search_mode.value());
|
||||
let web_search_config = if provider_capabilities.web_search {
|
||||
turn_context.config.web_search_config.as_ref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(web_search_tool) = create_web_search_tool(WebSearchToolOptions {
|
||||
web_search_mode: config.web_search_mode,
|
||||
web_search_config: config.web_search_config.as_ref(),
|
||||
web_search_tool_type: config.web_search_tool_type,
|
||||
web_search_mode,
|
||||
web_search_config,
|
||||
web_search_tool_type: turn_context.model_info.web_search_tool_type,
|
||||
}) {
|
||||
specs.push(web_search_tool);
|
||||
}
|
||||
if config.image_gen_tool {
|
||||
if image_generation_tool_enabled(turn_context) {
|
||||
specs.push(create_image_generation_tool("png"));
|
||||
}
|
||||
specs
|
||||
}
|
||||
|
||||
fn wait_agent_timeout_options(config: &ToolsConfig) -> WaitAgentTimeoutOptions {
|
||||
if config.multi_agent_v2 {
|
||||
pub(crate) fn search_tool_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.model_info.supports_search_tool
|
||||
}
|
||||
|
||||
pub(crate) fn tool_suggest_enabled(turn_context: &TurnContext) -> bool {
|
||||
let features = turn_context.features.get();
|
||||
features.enabled(Feature::ToolSuggest)
|
||||
&& features.enabled(Feature::Apps)
|
||||
&& features.enabled(Feature::Plugins)
|
||||
}
|
||||
|
||||
fn namespace_tools_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.provider.capabilities().namespace_tools
|
||||
}
|
||||
|
||||
fn code_mode_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.features.get().enabled(Feature::CodeMode)
|
||||
}
|
||||
|
||||
fn code_mode_only_enabled(turn_context: &TurnContext) -> bool {
|
||||
code_mode_enabled(turn_context) && turn_context.features.get().enabled(Feature::CodeModeOnly)
|
||||
}
|
||||
|
||||
fn multi_agent_v2_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.features.get().enabled(Feature::MultiAgentV2)
|
||||
}
|
||||
|
||||
fn collab_tools_enabled(turn_context: &TurnContext) -> bool {
|
||||
multi_agent_v2_enabled(turn_context) || turn_context.features.get().enabled(Feature::Collab)
|
||||
}
|
||||
|
||||
fn goal_tools_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.goal_tools_enabled()
|
||||
&& !matches!(
|
||||
turn_context.session_source,
|
||||
SessionSource::SubAgent(SubAgentSource::Review)
|
||||
)
|
||||
}
|
||||
|
||||
fn agent_jobs_tools_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context.features.get().enabled(Feature::SpawnCsv)
|
||||
}
|
||||
|
||||
fn agent_jobs_worker_tools_enabled(turn_context: &TurnContext) -> bool {
|
||||
agent_jobs_tools_enabled(turn_context)
|
||||
&& matches!(
|
||||
&turn_context.session_source,
|
||||
SessionSource::SubAgent(SubAgentSource::Other(label))
|
||||
if label.starts_with("agent_job:")
|
||||
)
|
||||
}
|
||||
|
||||
fn image_generation_tool_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context
|
||||
.auth_manager
|
||||
.as_deref()
|
||||
.is_some_and(AuthManager::current_auth_uses_codex_backend)
|
||||
&& turn_context.provider.capabilities().image_generation
|
||||
&& turn_context
|
||||
.features
|
||||
.get()
|
||||
.enabled(Feature::ImageGeneration)
|
||||
&& turn_context
|
||||
.model_info
|
||||
.input_modalities
|
||||
.contains(&InputModality::Image)
|
||||
}
|
||||
|
||||
fn wait_agent_timeout_options(turn_context: &TurnContext) -> WaitAgentTimeoutOptions {
|
||||
if multi_agent_v2_enabled(turn_context) {
|
||||
return WaitAgentTimeoutOptions {
|
||||
default_timeout_ms: config
|
||||
.wait_agent_default_timeout_ms
|
||||
.unwrap_or(DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS),
|
||||
min_timeout_ms: config
|
||||
.wait_agent_min_timeout_ms
|
||||
.unwrap_or(DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS),
|
||||
max_timeout_ms: config
|
||||
.wait_agent_max_timeout_ms
|
||||
.unwrap_or(DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS),
|
||||
default_timeout_ms: turn_context.config.multi_agent_v2.default_wait_timeout_ms,
|
||||
min_timeout_ms: turn_context.config.multi_agent_v2.min_wait_timeout_ms,
|
||||
max_timeout_ms: turn_context.config.multi_agent_v2.max_wait_timeout_ms,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,20 +322,34 @@ fn wait_agent_timeout_options(config: &ToolsConfig) -> WaitAgentTimeoutOptions {
|
||||
}
|
||||
}
|
||||
|
||||
fn agent_type_description(config: &ToolsConfig, default_agent_type_description: &str) -> String {
|
||||
if config.agent_type_description.is_empty() {
|
||||
fn max_concurrent_threads_per_session(turn_context: &TurnContext) -> Option<usize> {
|
||||
multi_agent_v2_enabled(turn_context).then_some(
|
||||
turn_context
|
||||
.config
|
||||
.multi_agent_v2
|
||||
.max_concurrent_threads_per_session,
|
||||
)
|
||||
}
|
||||
|
||||
fn agent_type_description(
|
||||
turn_context: &TurnContext,
|
||||
default_agent_type_description: &str,
|
||||
) -> String {
|
||||
let agent_type_description =
|
||||
crate::agent::role::spawn_tool_spec::build(&turn_context.config.agent_roles);
|
||||
if agent_type_description.is_empty() {
|
||||
default_agent_type_description.to_string()
|
||||
} else {
|
||||
config.agent_type_description.clone()
|
||||
agent_type_description
|
||||
}
|
||||
}
|
||||
|
||||
fn is_hidden_by_code_mode_only(
|
||||
config: &ToolsConfig,
|
||||
turn_context: &TurnContext,
|
||||
tool_name: &ToolName,
|
||||
exposure: ToolExposure,
|
||||
) -> bool {
|
||||
config.code_mode_only_enabled
|
||||
code_mode_only_enabled(turn_context)
|
||||
&& exposure != ToolExposure::DirectModelOnly
|
||||
&& codex_code_mode::is_code_mode_nested_tool(&codex_tools::code_mode_name_for_tool_name(
|
||||
tool_name,
|
||||
@@ -233,11 +357,11 @@ fn is_hidden_by_code_mode_only(
|
||||
}
|
||||
|
||||
fn build_code_mode_executors(
|
||||
config: &ToolsConfig,
|
||||
turn_context: &TurnContext,
|
||||
executors: &[Arc<dyn CoreToolRuntime>],
|
||||
deferred_tools_available: bool,
|
||||
) -> Vec<Arc<dyn CoreToolRuntime>> {
|
||||
if !config.code_mode_enabled {
|
||||
if !code_mode_enabled(turn_context) {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
@@ -262,7 +386,7 @@ fn build_code_mode_executors(
|
||||
create_code_mode_tool(
|
||||
&enabled_tools,
|
||||
&namespace_descriptions,
|
||||
config.code_mode_only_enabled,
|
||||
code_mode_only_enabled(turn_context),
|
||||
deferred_tools_available,
|
||||
),
|
||||
code_mode_nested_tool_specs,
|
||||
@@ -339,203 +463,201 @@ fn code_mode_namespace_descriptions(
|
||||
namespace_descriptions
|
||||
}
|
||||
|
||||
fn collect_tool_executors(
|
||||
config: &ToolsConfig,
|
||||
params: ToolRegistryBuildParams<'_>,
|
||||
) -> Vec<Arc<dyn CoreToolRuntime>> {
|
||||
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
|
||||
let mut executors = Vec::<Arc<dyn CoreToolRuntime>>::new();
|
||||
fn add_tool_sources(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
add_shell_tools(context, planned_tools);
|
||||
add_mcp_resource_tools(context, planned_tools);
|
||||
add_core_utility_tools(context, planned_tools);
|
||||
add_collaboration_tools(context, planned_tools);
|
||||
add_mcp_runtime_tools(context, planned_tools);
|
||||
add_dynamic_tools(context, planned_tools);
|
||||
add_extension_tools(context, planned_tools);
|
||||
planned_tools
|
||||
.hosted_specs
|
||||
.extend(hosted_model_tool_specs(context.turn_context));
|
||||
}
|
||||
|
||||
if config.environment_mode.has_environment() {
|
||||
let include_environment_id =
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
match &config.shell_type {
|
||||
ConfigShellToolType::UnifiedExec => {
|
||||
executors.push(Arc::new(ExecCommandHandler::new(
|
||||
ExecCommandHandlerOptions {
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
include_environment_id,
|
||||
},
|
||||
)));
|
||||
executors.push(Arc::new(WriteStdinHandler));
|
||||
}
|
||||
ConfigShellToolType::Disabled => {}
|
||||
ConfigShellToolType::Default
|
||||
| ConfigShellToolType::Local
|
||||
| ConfigShellToolType::ShellCommand => {
|
||||
executors.push(Arc::new(ShellCommandHandler::new(
|
||||
ShellCommandHandlerOptions {
|
||||
backend_config: config.shell_command_backend,
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
},
|
||||
)));
|
||||
}
|
||||
}
|
||||
fn add_shell_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
let turn_context = context.turn_context;
|
||||
let features = turn_context.features.get();
|
||||
planned_tools
|
||||
.runtimes
|
||||
.extend(build_shell_tools(ShellToolsOptions {
|
||||
shell_type: shell_type_for_model_and_features(&turn_context.model_info, features),
|
||||
shell_command_backend: shell_command_backend_for_features(features),
|
||||
environment_mode: turn_context.tool_environment_mode(),
|
||||
allow_login_shell: turn_context.config.permissions.allow_login_shell,
|
||||
exec_permission_approvals_enabled: features.enabled(Feature::ExecPermissionApprovals),
|
||||
}));
|
||||
}
|
||||
|
||||
fn add_mcp_resource_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
if context.mcp_tools.is_some() {
|
||||
planned_tools.add_runtime(ListMcpResourcesHandler);
|
||||
planned_tools.add_runtime(ListMcpResourceTemplatesHandler);
|
||||
planned_tools.add_runtime(ReadMcpResourceHandler);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
let turn_context = context.turn_context;
|
||||
let features = turn_context.features.get();
|
||||
let environment_mode = turn_context.tool_environment_mode();
|
||||
|
||||
planned_tools.add_runtime(PlanHandler);
|
||||
if goal_tools_enabled(turn_context) {
|
||||
planned_tools.add_runtime(GetGoalHandler);
|
||||
planned_tools.add_runtime(CreateGoalHandler);
|
||||
planned_tools.add_runtime(UpdateGoalHandler);
|
||||
}
|
||||
|
||||
if config.environment_mode.has_environment()
|
||||
&& config.shell_type != ConfigShellToolType::Disabled
|
||||
{
|
||||
match &config.shell_type {
|
||||
ConfigShellToolType::UnifiedExec => {
|
||||
executors.push(Arc::new(ShellCommandHandler::from(
|
||||
config.shell_command_backend,
|
||||
)));
|
||||
}
|
||||
ConfigShellToolType::Default
|
||||
| ConfigShellToolType::Local
|
||||
| ConfigShellToolType::ShellCommand
|
||||
| ConfigShellToolType::Disabled => {}
|
||||
}
|
||||
planned_tools.add_runtime(RequestUserInputHandler {
|
||||
available_modes: request_user_input_available_modes(features),
|
||||
});
|
||||
|
||||
if features.enabled(Feature::RequestPermissionsTool) {
|
||||
planned_tools.add_runtime(RequestPermissionsHandler);
|
||||
}
|
||||
|
||||
if params.mcp_tools.is_some() {
|
||||
executors.push(Arc::new(ListMcpResourcesHandler));
|
||||
executors.push(Arc::new(ListMcpResourceTemplatesHandler));
|
||||
executors.push(Arc::new(ReadMcpResourceHandler));
|
||||
}
|
||||
|
||||
executors.push(Arc::new(PlanHandler));
|
||||
if config.goal_tools {
|
||||
executors.push(Arc::new(GetGoalHandler));
|
||||
executors.push(Arc::new(CreateGoalHandler));
|
||||
executors.push(Arc::new(UpdateGoalHandler));
|
||||
}
|
||||
|
||||
executors.push(Arc::new(RequestUserInputHandler {
|
||||
available_modes: config.request_user_input_available_modes.clone(),
|
||||
}));
|
||||
|
||||
if config.request_permissions_tool_enabled {
|
||||
executors.push(Arc::new(RequestPermissionsHandler));
|
||||
}
|
||||
|
||||
if config.tool_suggest
|
||||
if tool_suggest_enabled(turn_context)
|
||||
&& let Some(discoverable_tools) =
|
||||
params.discoverable_tools.filter(|tools| !tools.is_empty())
|
||||
context.discoverable_tools.filter(|tools| !tools.is_empty())
|
||||
{
|
||||
executors.push(Arc::new(RequestPluginInstallHandler::new(
|
||||
discoverable_tools,
|
||||
)));
|
||||
planned_tools.add_runtime(RequestPluginInstallHandler::new(discoverable_tools));
|
||||
}
|
||||
|
||||
if config.environment_mode.has_environment() && config.apply_patch_tool_type.is_some() {
|
||||
let include_environment_id =
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
executors.push(Arc::new(ApplyPatchHandler::new(include_environment_id)));
|
||||
if environment_mode.has_environment() && turn_context.model_info.apply_patch_tool_type.is_some()
|
||||
{
|
||||
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
||||
planned_tools.add_runtime(ApplyPatchHandler::new(include_environment_id));
|
||||
}
|
||||
|
||||
if config
|
||||
if turn_context
|
||||
.model_info
|
||||
.experimental_supported_tools
|
||||
.iter()
|
||||
.any(|tool| tool == "test_sync_tool")
|
||||
{
|
||||
executors.push(Arc::new(TestSyncHandler));
|
||||
planned_tools.add_runtime(TestSyncHandler);
|
||||
}
|
||||
|
||||
if config.environment_mode.has_environment() {
|
||||
let include_environment_id =
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
executors.push(Arc::new(ViewImageHandler::new(ViewImageToolOptions {
|
||||
can_request_original_image_detail: config.can_request_original_image_detail,
|
||||
if environment_mode.has_environment() {
|
||||
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
||||
planned_tools.add_runtime(ViewImageHandler::new(ViewImageToolOptions {
|
||||
can_request_original_image_detail: can_request_original_image_detail(
|
||||
&turn_context.model_info,
|
||||
),
|
||||
include_environment_id,
|
||||
})));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if config.collab_tools {
|
||||
if config.multi_agent_v2 {
|
||||
let exposure = if config.multi_agent_v2_non_code_mode_only {
|
||||
fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
let turn_context = context.turn_context;
|
||||
if collab_tools_enabled(turn_context) {
|
||||
if multi_agent_v2_enabled(turn_context) {
|
||||
let exposure = if turn_context.config.multi_agent_v2.non_code_mode_only {
|
||||
ToolExposure::DirectModelOnly
|
||||
} else {
|
||||
ToolExposure::Direct
|
||||
};
|
||||
let tool_namespace = config
|
||||
.namespace_tools
|
||||
.then_some(config.multi_agent_v2_tool_namespace.as_deref())
|
||||
let tool_namespace = namespace_tools_enabled(turn_context)
|
||||
.then_some(turn_context.config.multi_agent_v2.tool_namespace.as_deref())
|
||||
.flatten();
|
||||
let agent_type_description =
|
||||
agent_type_description(config, params.default_agent_type_description);
|
||||
executors.push(multi_agent_v2_handler(
|
||||
agent_type_description(turn_context, context.default_agent_type_description);
|
||||
planned_tools.add_runtime_arc(multi_agent_v2_handler(
|
||||
SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
|
||||
available_models: config.available_models.clone(),
|
||||
available_models: turn_context.available_models.clone(),
|
||||
agent_type_description,
|
||||
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
|
||||
include_usage_hint: config.spawn_agent_usage_hint,
|
||||
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
|
||||
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
|
||||
hide_agent_type_model_reasoning: turn_context
|
||||
.config
|
||||
.multi_agent_v2
|
||||
.hide_spawn_agent_metadata,
|
||||
include_usage_hint: turn_context.config.multi_agent_v2.usage_hint_enabled,
|
||||
usage_hint_text: turn_context.config.multi_agent_v2.usage_hint_text.clone(),
|
||||
max_concurrent_threads_per_session: max_concurrent_threads_per_session(
|
||||
turn_context,
|
||||
),
|
||||
}),
|
||||
exposure,
|
||||
tool_namespace,
|
||||
));
|
||||
executors.push(multi_agent_v2_handler(
|
||||
planned_tools.add_runtime_arc(multi_agent_v2_handler(
|
||||
SendMessageHandlerV2,
|
||||
exposure,
|
||||
tool_namespace,
|
||||
));
|
||||
executors.push(multi_agent_v2_handler(
|
||||
planned_tools.add_runtime_arc(multi_agent_v2_handler(
|
||||
FollowupTaskHandlerV2,
|
||||
exposure,
|
||||
tool_namespace,
|
||||
));
|
||||
executors.push(multi_agent_v2_handler(
|
||||
WaitAgentHandlerV2::new(params.wait_agent_timeouts),
|
||||
planned_tools.add_runtime_arc(multi_agent_v2_handler(
|
||||
WaitAgentHandlerV2::new(context.wait_agent_timeouts),
|
||||
exposure,
|
||||
tool_namespace,
|
||||
));
|
||||
executors.push(multi_agent_v2_handler(
|
||||
planned_tools.add_runtime_arc(multi_agent_v2_handler(
|
||||
CloseAgentHandlerV2,
|
||||
exposure,
|
||||
tool_namespace,
|
||||
));
|
||||
executors.push(multi_agent_v2_handler(
|
||||
planned_tools.add_runtime_arc(multi_agent_v2_handler(
|
||||
ListAgentsHandlerV2,
|
||||
exposure,
|
||||
tool_namespace,
|
||||
));
|
||||
} else {
|
||||
let agent_type_description =
|
||||
agent_type_description(config, params.default_agent_type_description);
|
||||
executors.push(Arc::new(SpawnAgentHandler::new(SpawnAgentToolOptions {
|
||||
available_models: config.available_models.clone(),
|
||||
agent_type_description(turn_context, context.default_agent_type_description);
|
||||
planned_tools.add_runtime(SpawnAgentHandler::new(SpawnAgentToolOptions {
|
||||
available_models: turn_context.available_models.clone(),
|
||||
agent_type_description,
|
||||
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
|
||||
include_usage_hint: config.spawn_agent_usage_hint,
|
||||
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
|
||||
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
|
||||
})));
|
||||
executors.push(Arc::new(SendInputHandler));
|
||||
executors.push(Arc::new(ResumeAgentHandler));
|
||||
executors.push(Arc::new(WaitAgentHandler::new(params.wait_agent_timeouts)));
|
||||
executors.push(Arc::new(CloseAgentHandler));
|
||||
hide_agent_type_model_reasoning: turn_context
|
||||
.config
|
||||
.multi_agent_v2
|
||||
.hide_spawn_agent_metadata,
|
||||
include_usage_hint: turn_context.config.multi_agent_v2.usage_hint_enabled,
|
||||
usage_hint_text: turn_context.config.multi_agent_v2.usage_hint_text.clone(),
|
||||
max_concurrent_threads_per_session: max_concurrent_threads_per_session(
|
||||
turn_context,
|
||||
),
|
||||
}));
|
||||
planned_tools.add_runtime(SendInputHandler);
|
||||
planned_tools.add_runtime(ResumeAgentHandler);
|
||||
planned_tools.add_runtime(WaitAgentHandler::new(context.wait_agent_timeouts));
|
||||
planned_tools.add_runtime(CloseAgentHandler);
|
||||
}
|
||||
}
|
||||
|
||||
if config.agent_jobs_tools {
|
||||
executors.push(Arc::new(SpawnAgentsOnCsvHandler));
|
||||
if config.agent_jobs_worker_tools {
|
||||
executors.push(Arc::new(ReportAgentJobResultHandler));
|
||||
if agent_jobs_tools_enabled(turn_context) {
|
||||
planned_tools.add_runtime(SpawnAgentsOnCsvHandler);
|
||||
if agent_jobs_worker_tools_enabled(turn_context) {
|
||||
planned_tools.add_runtime(ReportAgentJobResultHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mcp_tools) = params.mcp_tools {
|
||||
fn add_mcp_runtime_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
if let Some(mcp_tools) = context.mcp_tools {
|
||||
for tool in mcp_tools {
|
||||
executors.push(Arc::new(McpHandler::new(tool.clone())));
|
||||
planned_tools.add_runtime(McpHandler::new(tool.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(deferred_mcp_tools) = params.deferred_mcp_tools {
|
||||
if let Some(deferred_mcp_tools) = context.deferred_mcp_tools {
|
||||
for tool in deferred_mcp_tools {
|
||||
executors.push(Arc::new(McpHandler::with_exposure(
|
||||
planned_tools.add_runtime(McpHandler::with_exposure(
|
||||
tool.clone(),
|
||||
ToolExposure::Deferred,
|
||||
)));
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for tool in params.dynamic_tools {
|
||||
let Some(handler) = DynamicToolHandler::new(tool).map(Arc::new) else {
|
||||
fn add_dynamic_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
for tool in context.dynamic_tools {
|
||||
let Some(handler) = DynamicToolHandler::new(tool) else {
|
||||
tracing::error!(
|
||||
"Failed to convert dynamic tool {:?} to OpenAI tool",
|
||||
tool.name
|
||||
@@ -543,23 +665,31 @@ fn collect_tool_executors(
|
||||
continue;
|
||||
};
|
||||
|
||||
executors.push(handler);
|
||||
planned_tools.add_runtime(handler);
|
||||
}
|
||||
}
|
||||
|
||||
append_extension_tool_executors(config, params.extension_tool_executors, &mut executors);
|
||||
|
||||
executors
|
||||
fn add_extension_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
// Extension ToolContributor implementations are resolved into executors
|
||||
// before planning. Core only adapts those executors into its runtime set.
|
||||
append_extension_tool_executors(
|
||||
context.turn_context,
|
||||
context.extension_tool_executors,
|
||||
planned_tools,
|
||||
);
|
||||
}
|
||||
|
||||
fn append_tool_search_executor(
|
||||
config: &ToolsConfig,
|
||||
executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
|
||||
context: &CoreToolPlanContext<'_>,
|
||||
planned_tools: &mut PlannedTools,
|
||||
) {
|
||||
if !(config.search_tool && config.namespace_tools) {
|
||||
let turn_context = context.turn_context;
|
||||
if !(search_tool_enabled(turn_context) && namespace_tools_enabled(turn_context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let search_infos = executors
|
||||
let search_infos = planned_tools
|
||||
.runtimes()
|
||||
.iter()
|
||||
.filter(|executor| executor.exposure() == ToolExposure::Deferred)
|
||||
.filter_map(|executor| executor.search_info())
|
||||
@@ -568,42 +698,49 @@ fn append_tool_search_executor(
|
||||
return;
|
||||
}
|
||||
|
||||
executors.push(Arc::new(ToolSearchHandler::new(search_infos)));
|
||||
planned_tools.add_runtime(ToolSearchHandler::new(search_infos));
|
||||
}
|
||||
|
||||
fn prepend_code_mode_executors(
|
||||
config: &ToolsConfig,
|
||||
executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
|
||||
context: &CoreToolPlanContext<'_>,
|
||||
planned_tools: &mut PlannedTools,
|
||||
) {
|
||||
let deferred_tools_available = config.search_tool
|
||||
&& executors
|
||||
let turn_context = context.turn_context;
|
||||
let deferred_tools_available = search_tool_enabled(turn_context)
|
||||
&& planned_tools
|
||||
.runtimes()
|
||||
.iter()
|
||||
.any(|executor| executor.exposure() == ToolExposure::Deferred);
|
||||
let code_mode_executors =
|
||||
build_code_mode_executors(config, executors, deferred_tools_available);
|
||||
executors.splice(0..0, code_mode_executors);
|
||||
let code_mode_executors = build_code_mode_executors(
|
||||
turn_context,
|
||||
planned_tools.runtimes(),
|
||||
deferred_tools_available,
|
||||
);
|
||||
planned_tools.runtimes.splice(0..0, code_mode_executors);
|
||||
}
|
||||
|
||||
fn append_extension_tool_executors(
|
||||
config: &ToolsConfig,
|
||||
turn_context: &TurnContext,
|
||||
executors: &[Arc<dyn ToolExecutor<ExtensionToolCall>>],
|
||||
registered_executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
|
||||
planned_tools: &mut PlannedTools,
|
||||
) {
|
||||
if executors.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut reserved_tool_names = registered_executors
|
||||
let mut reserved_tool_names = planned_tools
|
||||
.runtimes()
|
||||
.iter()
|
||||
.map(|executor| executor.tool_name())
|
||||
.collect::<HashSet<_>>();
|
||||
if config.code_mode_enabled {
|
||||
if code_mode_enabled(turn_context) {
|
||||
reserved_tool_names.insert(ToolName::plain(codex_code_mode::PUBLIC_TOOL_NAME));
|
||||
reserved_tool_names.insert(ToolName::plain(codex_code_mode::WAIT_TOOL_NAME));
|
||||
}
|
||||
if config.search_tool
|
||||
&& config.namespace_tools
|
||||
&& registered_executors
|
||||
if search_tool_enabled(turn_context)
|
||||
&& namespace_tools_enabled(turn_context)
|
||||
&& planned_tools
|
||||
.runtimes()
|
||||
.iter()
|
||||
.any(|executor| executor.exposure() == ToolExposure::Deferred)
|
||||
{
|
||||
@@ -616,7 +753,7 @@ fn append_extension_tool_executors(
|
||||
warn!("Skipping extension tool `{tool_name}`: tool already registered");
|
||||
continue;
|
||||
}
|
||||
registered_executors.push(Arc::new(ExtensionToolAdapter::new(executor)));
|
||||
planned_tools.add_runtime(ExtensionToolAdapter::new(executor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,9 +851,6 @@ fn code_mode_namespace_name<'a>(
|
||||
.map(|namespace_description| namespace_description.name.as_str())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "spec_plan_model_tests.rs"]
|
||||
mod model_tests;
|
||||
#[cfg(test)]
|
||||
#[path = "spec_plan_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
pub(crate) mod shell;
|
||||
@@ -0,0 +1,72 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_tools::ShellCommandBackendConfig;
|
||||
use codex_tools::ToolEnvironmentMode;
|
||||
|
||||
use crate::tools::handlers::ExecCommandHandler;
|
||||
use crate::tools::handlers::ExecCommandHandlerOptions;
|
||||
use crate::tools::handlers::ShellCommandHandler;
|
||||
use crate::tools::handlers::ShellCommandHandlerOptions;
|
||||
use crate::tools::handlers::WriteStdinHandler;
|
||||
use crate::tools::registry::CoreToolRuntime;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct ShellToolsOptions {
|
||||
pub(crate) shell_type: ConfigShellToolType,
|
||||
pub(crate) shell_command_backend: ShellCommandBackendConfig,
|
||||
pub(crate) environment_mode: ToolEnvironmentMode,
|
||||
pub(crate) allow_login_shell: bool,
|
||||
pub(crate) exec_permission_approvals_enabled: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn build_shell_tools(options: ShellToolsOptions) -> Vec<Arc<dyn CoreToolRuntime>> {
|
||||
let mut runtimes = Vec::new();
|
||||
if !options.environment_mode.has_environment() {
|
||||
return runtimes;
|
||||
}
|
||||
|
||||
let include_environment_id = matches!(options.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
match options.shell_type {
|
||||
ConfigShellToolType::UnifiedExec => {
|
||||
add_runtime(
|
||||
&mut runtimes,
|
||||
ExecCommandHandler::new(ExecCommandHandlerOptions {
|
||||
allow_login_shell: options.allow_login_shell,
|
||||
exec_permission_approvals_enabled: options.exec_permission_approvals_enabled,
|
||||
include_environment_id,
|
||||
}),
|
||||
);
|
||||
add_runtime(&mut runtimes, WriteStdinHandler);
|
||||
|
||||
// Keep the legacy shell tool registered as a hidden runtime while
|
||||
// unified exec is model-visible.
|
||||
add_runtime(
|
||||
&mut runtimes,
|
||||
ShellCommandHandler::from(options.shell_command_backend),
|
||||
);
|
||||
}
|
||||
ConfigShellToolType::Disabled => {}
|
||||
ConfigShellToolType::Default
|
||||
| ConfigShellToolType::Local
|
||||
| ConfigShellToolType::ShellCommand => {
|
||||
add_runtime(
|
||||
&mut runtimes,
|
||||
ShellCommandHandler::new(ShellCommandHandlerOptions {
|
||||
backend_config: options.shell_command_backend,
|
||||
allow_login_shell: options.allow_login_shell,
|
||||
exec_permission_approvals_enabled: options.exec_permission_approvals_enabled,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
runtimes
|
||||
}
|
||||
|
||||
fn add_runtime<T>(runtimes: &mut Vec<Arc<dyn CoreToolRuntime>>, runtime: T)
|
||||
where
|
||||
T: CoreToolRuntime + 'static,
|
||||
{
|
||||
runtimes.push(Arc::new(runtime));
|
||||
}
|
||||
@@ -1014,10 +1014,8 @@ impl UnifiedExecProcessManager {
|
||||
local_policy_env,
|
||||
};
|
||||
let mut orchestrator = ToolOrchestrator::new();
|
||||
let mut runtime = UnifiedExecRuntime::new(
|
||||
self,
|
||||
context.turn.tools_config.unified_exec_shell_mode.clone(),
|
||||
);
|
||||
let mut runtime =
|
||||
UnifiedExecRuntime::new(self, context.turn.unified_exec_shell_mode.clone());
|
||||
let file_system_sandbox_policy = context.turn.file_system_sandbox_policy();
|
||||
let exec_approval_requirement = context
|
||||
.session
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# codex-tools
|
||||
|
||||
`codex-tools` is the shared support crate for building, adapting, planning, and
|
||||
executing model-visible tool sets outside `codex-core`.
|
||||
`codex-tools` is the shared support crate for building, adapting, and executing
|
||||
model-visible tools outside `codex-core`.
|
||||
|
||||
Today this crate owns the host-facing tool models and helpers that no longer
|
||||
need to live in `core/src/tools/spec.rs` or `core/src/client_common.rs`:
|
||||
@@ -9,8 +9,8 @@ need to live in `core/src/tools/spec.rs` or `core/src/client_common.rs`:
|
||||
- aggregate host models such as `ToolSpec`, `ConfiguredToolSpec`,
|
||||
`LoadableToolSpec`, `ResponsesApiNamespace`, and
|
||||
`ResponsesApiNamespaceTool`
|
||||
- host config and discovery models used while assembling tool sets, including
|
||||
`ToolsConfig`, discoverable-tool models, and request-plugin-install helpers
|
||||
- host discovery models used while assembling tool sets, including
|
||||
discoverable-tool models and request-plugin-install helpers
|
||||
- host adapters such as schema sanitization, MCP/dynamic conversion, code-mode
|
||||
augmentation, and image-detail normalization
|
||||
- shared executable-tool contracts such as `ToolExecutor`, `ToolCall`, and
|
||||
|
||||
@@ -61,11 +61,11 @@ pub use tool_call::ToolCall;
|
||||
pub use tool_config::ShellCommandBackendConfig;
|
||||
pub use tool_config::ToolEnvironmentMode;
|
||||
pub use tool_config::ToolUserShellType;
|
||||
pub use tool_config::ToolsConfig;
|
||||
pub use tool_config::ToolsConfigParams;
|
||||
pub use tool_config::UnifiedExecShellMode;
|
||||
pub use tool_config::ZshForkConfig;
|
||||
pub use tool_config::request_user_input_available_modes;
|
||||
pub use tool_config::shell_command_backend_for_features;
|
||||
pub use tool_config::shell_type_for_model_and_features;
|
||||
pub use tool_definition::ToolDefinition;
|
||||
pub use tool_discovery::DiscoverablePluginInfo;
|
||||
pub use tool_discovery::DiscoverableTool;
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
use crate::can_request_original_image_detail;
|
||||
use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES;
|
||||
use codex_protocol::config_types::WebSearchConfig;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ApplyPatchToolType;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -44,6 +33,44 @@ pub fn request_user_input_available_modes(features: &Features) -> Vec<ModeKind>
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn shell_command_backend_for_features(features: &Features) -> ShellCommandBackendConfig {
|
||||
if features.enabled(Feature::ShellTool) && features.enabled(Feature::ShellZshFork) {
|
||||
ShellCommandBackendConfig::ZshFork
|
||||
} else {
|
||||
ShellCommandBackendConfig::Classic
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shell_type_for_model_and_features(
|
||||
model_info: &ModelInfo,
|
||||
features: &Features,
|
||||
) -> ConfigShellToolType {
|
||||
let unified_exec_enabled = features.enabled(Feature::UnifiedExec);
|
||||
let model_shell_type = match model_info.shell_type {
|
||||
ConfigShellToolType::UnifiedExec if !unified_exec_enabled => {
|
||||
ConfigShellToolType::ShellCommand
|
||||
}
|
||||
ConfigShellToolType::Default | ConfigShellToolType::Local => {
|
||||
ConfigShellToolType::ShellCommand
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
|
||||
if !features.enabled(Feature::ShellTool) {
|
||||
ConfigShellToolType::Disabled
|
||||
} else if features.enabled(Feature::ShellZshFork) {
|
||||
ConfigShellToolType::ShellCommand
|
||||
} else if unified_exec_enabled {
|
||||
if codex_utils_pty::conpty_supported() {
|
||||
ConfigShellToolType::UnifiedExec
|
||||
} else {
|
||||
ConfigShellToolType::ShellCommand
|
||||
}
|
||||
} else {
|
||||
model_shell_type
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum UnifiedExecShellMode {
|
||||
Direct,
|
||||
@@ -93,57 +120,6 @@ impl UnifiedExecShellMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToolsConfig {
|
||||
pub available_models: Vec<ModelPreset>,
|
||||
pub shell_type: ConfigShellToolType,
|
||||
pub shell_command_backend: ShellCommandBackendConfig,
|
||||
pub unified_exec_shell_mode: UnifiedExecShellMode,
|
||||
pub environment_mode: ToolEnvironmentMode,
|
||||
pub allow_login_shell: bool,
|
||||
pub apply_patch_tool_type: Option<ApplyPatchToolType>,
|
||||
pub web_search_mode: Option<WebSearchMode>,
|
||||
pub web_search_config: Option<WebSearchConfig>,
|
||||
pub web_search_tool_type: WebSearchToolType,
|
||||
pub image_gen_tool: bool,
|
||||
pub search_tool: bool,
|
||||
pub namespace_tools: bool,
|
||||
pub tool_suggest: bool,
|
||||
pub exec_permission_approvals_enabled: bool,
|
||||
pub request_permissions_tool_enabled: bool,
|
||||
pub code_mode_enabled: bool,
|
||||
pub code_mode_only_enabled: bool,
|
||||
pub can_request_original_image_detail: bool,
|
||||
pub collab_tools: bool,
|
||||
pub goal_tools: bool,
|
||||
pub multi_agent_v2: bool,
|
||||
pub multi_agent_v2_non_code_mode_only: bool,
|
||||
pub hide_spawn_agent_metadata: bool,
|
||||
pub spawn_agent_usage_hint: bool,
|
||||
pub spawn_agent_usage_hint_text: Option<String>,
|
||||
pub multi_agent_v2_tool_namespace: Option<String>,
|
||||
pub max_concurrent_threads_per_session: Option<usize>,
|
||||
pub wait_agent_min_timeout_ms: Option<i64>,
|
||||
pub wait_agent_max_timeout_ms: Option<i64>,
|
||||
pub wait_agent_default_timeout_ms: Option<i64>,
|
||||
pub request_user_input_available_modes: Vec<ModeKind>,
|
||||
pub experimental_supported_tools: Vec<String>,
|
||||
pub agent_jobs_tools: bool,
|
||||
pub agent_jobs_worker_tools: bool,
|
||||
pub agent_type_description: String,
|
||||
}
|
||||
|
||||
pub struct ToolsConfigParams<'a> {
|
||||
pub model_info: &'a ModelInfo,
|
||||
pub available_models: &'a [ModelPreset],
|
||||
pub features: &'a Features,
|
||||
pub image_generation_tool_auth_allowed: bool,
|
||||
pub web_search_mode: Option<WebSearchMode>,
|
||||
pub session_source: SessionSource,
|
||||
pub permission_profile: &'a PermissionProfile,
|
||||
pub windows_sandbox_level: WindowsSandboxLevel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ToolEnvironmentMode {
|
||||
None,
|
||||
@@ -165,262 +141,6 @@ impl ToolEnvironmentMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolsConfig {
|
||||
pub fn new(params: &ToolsConfigParams<'_>) -> Self {
|
||||
let ToolsConfigParams {
|
||||
model_info,
|
||||
available_models,
|
||||
features,
|
||||
image_generation_tool_auth_allowed,
|
||||
web_search_mode,
|
||||
session_source,
|
||||
..
|
||||
} = params;
|
||||
let include_code_mode = features.enabled(Feature::CodeMode);
|
||||
let include_code_mode_only = include_code_mode && features.enabled(Feature::CodeModeOnly);
|
||||
let include_goal_tools = features.enabled(Feature::Goals);
|
||||
let include_multi_agent_v2 = features.enabled(Feature::MultiAgentV2);
|
||||
let include_collab_tools = include_multi_agent_v2 || features.enabled(Feature::Collab);
|
||||
let include_agent_jobs = features.enabled(Feature::SpawnCsv);
|
||||
let include_search_tool = model_info.supports_search_tool;
|
||||
let include_tool_suggest = features.enabled(Feature::ToolSuggest)
|
||||
&& features.enabled(Feature::Apps)
|
||||
&& features.enabled(Feature::Plugins);
|
||||
let include_original_image_detail = can_request_original_image_detail(model_info);
|
||||
// API-key auth bypasses Codex backend entitlement/tool normalization, so
|
||||
// callers must confirm ChatGPT auth before exposing the built-in tool.
|
||||
let include_image_gen_tool = *image_generation_tool_auth_allowed
|
||||
&& features.enabled(Feature::ImageGeneration)
|
||||
&& supports_image_generation(model_info);
|
||||
let exec_permission_approvals_enabled = features.enabled(Feature::ExecPermissionApprovals);
|
||||
let request_permissions_tool_enabled = features.enabled(Feature::RequestPermissionsTool);
|
||||
let shell_command_backend =
|
||||
if features.enabled(Feature::ShellTool) && features.enabled(Feature::ShellZshFork) {
|
||||
ShellCommandBackendConfig::ZshFork
|
||||
} else {
|
||||
ShellCommandBackendConfig::Classic
|
||||
};
|
||||
let unified_exec_enabled = features.enabled(Feature::UnifiedExec);
|
||||
let model_shell_type = match model_info.shell_type {
|
||||
ConfigShellToolType::UnifiedExec if !unified_exec_enabled => {
|
||||
ConfigShellToolType::ShellCommand
|
||||
}
|
||||
ConfigShellToolType::Default | ConfigShellToolType::Local => {
|
||||
ConfigShellToolType::ShellCommand
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
let shell_type = if !features.enabled(Feature::ShellTool) {
|
||||
ConfigShellToolType::Disabled
|
||||
} else if features.enabled(Feature::ShellZshFork) {
|
||||
ConfigShellToolType::ShellCommand
|
||||
} else if unified_exec_enabled {
|
||||
if codex_utils_pty::conpty_supported() {
|
||||
ConfigShellToolType::UnifiedExec
|
||||
} else {
|
||||
ConfigShellToolType::ShellCommand
|
||||
}
|
||||
} else {
|
||||
model_shell_type
|
||||
};
|
||||
|
||||
let apply_patch_tool_type = model_info.apply_patch_tool_type.clone();
|
||||
|
||||
let agent_jobs_worker_tools = include_agent_jobs
|
||||
&& matches!(
|
||||
session_source,
|
||||
SessionSource::SubAgent(SubAgentSource::Other(label))
|
||||
if label.starts_with("agent_job:")
|
||||
);
|
||||
|
||||
Self {
|
||||
available_models: available_models.to_vec(),
|
||||
shell_type,
|
||||
shell_command_backend,
|
||||
unified_exec_shell_mode: UnifiedExecShellMode::Direct,
|
||||
environment_mode: ToolEnvironmentMode::Single,
|
||||
allow_login_shell: true,
|
||||
apply_patch_tool_type,
|
||||
web_search_mode: *web_search_mode,
|
||||
web_search_config: None,
|
||||
web_search_tool_type: model_info.web_search_tool_type,
|
||||
image_gen_tool: include_image_gen_tool,
|
||||
search_tool: include_search_tool,
|
||||
namespace_tools: true,
|
||||
tool_suggest: include_tool_suggest,
|
||||
exec_permission_approvals_enabled,
|
||||
request_permissions_tool_enabled,
|
||||
code_mode_enabled: include_code_mode,
|
||||
code_mode_only_enabled: include_code_mode_only,
|
||||
can_request_original_image_detail: include_original_image_detail,
|
||||
collab_tools: include_collab_tools,
|
||||
goal_tools: include_goal_tools,
|
||||
multi_agent_v2: include_multi_agent_v2,
|
||||
multi_agent_v2_non_code_mode_only: false,
|
||||
hide_spawn_agent_metadata: false,
|
||||
spawn_agent_usage_hint: true,
|
||||
spawn_agent_usage_hint_text: None,
|
||||
multi_agent_v2_tool_namespace: None,
|
||||
max_concurrent_threads_per_session: None,
|
||||
wait_agent_min_timeout_ms: None,
|
||||
wait_agent_max_timeout_ms: None,
|
||||
wait_agent_default_timeout_ms: None,
|
||||
request_user_input_available_modes: request_user_input_available_modes(features),
|
||||
experimental_supported_tools: model_info.experimental_supported_tools.clone(),
|
||||
agent_jobs_tools: include_agent_jobs,
|
||||
agent_jobs_worker_tools,
|
||||
agent_type_description: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_agent_type_description(mut self, agent_type_description: String) -> Self {
|
||||
self.agent_type_description = agent_type_description;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_namespace_tools_capability(mut self, namespace_tools: bool) -> Self {
|
||||
if !namespace_tools {
|
||||
self.namespace_tools = false;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_image_generation_capability(mut self, image_generation: bool) -> Self {
|
||||
if !image_generation {
|
||||
self.image_gen_tool = false;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_web_search_capability(mut self, web_search: bool) -> Self {
|
||||
if !web_search {
|
||||
self.web_search_mode = None;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_spawn_agent_usage_hint(mut self, spawn_agent_usage_hint: bool) -> Self {
|
||||
self.spawn_agent_usage_hint = spawn_agent_usage_hint;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_spawn_agent_usage_hint_text(
|
||||
mut self,
|
||||
spawn_agent_usage_hint_text: Option<String>,
|
||||
) -> Self {
|
||||
self.spawn_agent_usage_hint_text = spawn_agent_usage_hint_text;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hide_spawn_agent_metadata(mut self, hide_spawn_agent_metadata: bool) -> Self {
|
||||
self.hide_spawn_agent_metadata = hide_spawn_agent_metadata;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_multi_agent_v2_tool_namespace(
|
||||
mut self,
|
||||
multi_agent_v2_tool_namespace: Option<String>,
|
||||
) -> Self {
|
||||
self.multi_agent_v2_tool_namespace = multi_agent_v2_tool_namespace;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_multi_agent_v2_non_code_mode_only(
|
||||
mut self,
|
||||
multi_agent_v2_non_code_mode_only: bool,
|
||||
) -> Self {
|
||||
self.multi_agent_v2_non_code_mode_only =
|
||||
self.multi_agent_v2 && multi_agent_v2_non_code_mode_only;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_goal_tools_allowed(mut self, allowed: bool) -> Self {
|
||||
self.goal_tools = self.goal_tools && allowed;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_max_concurrent_threads_per_session(
|
||||
mut self,
|
||||
max_concurrent_threads_per_session: Option<usize>,
|
||||
) -> Self {
|
||||
self.max_concurrent_threads_per_session = max_concurrent_threads_per_session;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_wait_agent_min_timeout_ms(
|
||||
mut self,
|
||||
wait_agent_min_timeout_ms: Option<i64>,
|
||||
) -> Self {
|
||||
self.wait_agent_min_timeout_ms = wait_agent_min_timeout_ms;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_wait_agent_max_timeout_ms(
|
||||
mut self,
|
||||
wait_agent_max_timeout_ms: Option<i64>,
|
||||
) -> Self {
|
||||
self.wait_agent_max_timeout_ms = wait_agent_max_timeout_ms;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_wait_agent_default_timeout_ms(
|
||||
mut self,
|
||||
wait_agent_default_timeout_ms: Option<i64>,
|
||||
) -> Self {
|
||||
self.wait_agent_default_timeout_ms = wait_agent_default_timeout_ms;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_allow_login_shell(mut self, allow_login_shell: bool) -> Self {
|
||||
self.allow_login_shell = allow_login_shell;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_environment_mode(mut self, environment_mode: ToolEnvironmentMode) -> Self {
|
||||
self.environment_mode = environment_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_unified_exec_shell_mode(
|
||||
mut self,
|
||||
unified_exec_shell_mode: UnifiedExecShellMode,
|
||||
) -> Self {
|
||||
self.unified_exec_shell_mode = unified_exec_shell_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_unified_exec_shell_mode_for_session(
|
||||
mut self,
|
||||
user_shell_type: ToolUserShellType,
|
||||
shell_zsh_path: Option<&PathBuf>,
|
||||
main_execve_wrapper_exe: Option<&PathBuf>,
|
||||
) -> Self {
|
||||
self.unified_exec_shell_mode = UnifiedExecShellMode::for_session(
|
||||
self.shell_command_backend,
|
||||
user_shell_type,
|
||||
shell_zsh_path,
|
||||
main_execve_wrapper_exe,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_web_search_config(mut self, web_search_config: Option<WebSearchConfig>) -> Self {
|
||||
self.web_search_config = web_search_config;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn for_code_mode_nested_tools(&self) -> Self {
|
||||
let mut nested = self.clone();
|
||||
nested.code_mode_enabled = false;
|
||||
nested.code_mode_only_enabled = false;
|
||||
nested
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_image_generation(model_info: &ModelInfo) -> bool {
|
||||
model_info.input_modalities.contains(&InputModality::Image)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tool_config_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,294 +1,153 @@
|
||||
use super::*;
|
||||
use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_protocol::openai_models::ModelVisibility;
|
||||
use codex_protocol::openai_models::TruncationPolicyConfig;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn model_info() -> ModelInfo {
|
||||
serde_json::from_value(json!({
|
||||
"slug": "test-model",
|
||||
"display_name": "Test Model",
|
||||
"description": null,
|
||||
"supported_reasoning_levels": [],
|
||||
"shell_type": "unified_exec",
|
||||
"visibility": "list",
|
||||
"supported_in_api": true,
|
||||
"priority": 1,
|
||||
"availability_nux": null,
|
||||
"upgrade": null,
|
||||
"base_instructions": "base",
|
||||
"model_messages": null,
|
||||
"supports_reasoning_summaries": false,
|
||||
"default_reasoning_summary": "auto",
|
||||
"support_verbosity": false,
|
||||
"default_verbosity": null,
|
||||
"apply_patch_tool_type": null,
|
||||
"truncation_policy": {
|
||||
"mode": "bytes",
|
||||
"limit": 10000
|
||||
},
|
||||
"supports_parallel_tool_calls": false,
|
||||
"supports_image_detail_original": false,
|
||||
"context_window": null,
|
||||
"auto_compact_token_limit": null,
|
||||
"effective_context_window_percent": 95,
|
||||
"experimental_supported_tools": [],
|
||||
"input_modalities": ["text", "image"],
|
||||
"supports_search_tool": false
|
||||
}))
|
||||
.expect("deserialize test model")
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_provided_unified_exec_requires_feature_flag() {
|
||||
let model_info = model_info();
|
||||
fn shell_features() -> Features {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ShellTool);
|
||||
features.disable(Feature::ShellZshFork);
|
||||
features.disable(Feature::UnifiedExec);
|
||||
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
assert_eq!(tools_config.shell_type, ConfigShellToolType::ShellCommand);
|
||||
features
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_exec_can_be_enabled_for_restricted_token_workspace_write() {
|
||||
let model_info = model_info();
|
||||
let mut features = Features::with_defaults();
|
||||
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 available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::workspace_write(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
|
||||
});
|
||||
|
||||
let expected_shell_type = if codex_utils_pty::conpty_supported() {
|
||||
let expected_unified_exec = if codex_utils_pty::conpty_supported() {
|
||||
ConfigShellToolType::UnifiedExec
|
||||
} else {
|
||||
ConfigShellToolType::ShellCommand
|
||||
};
|
||||
assert_eq!(tools_config.shell_type, expected_shell_type);
|
||||
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_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
let model_info = model_info();
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
features.enable(Feature::ShellZshFork);
|
||||
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
assert_eq!(tools_config.shell_type, ConfigShellToolType::ShellCommand);
|
||||
fn shell_command_backend_requires_both_shell_tool_and_zsh_fork() {
|
||||
let mut features = shell_features();
|
||||
assert_eq!(
|
||||
tools_config.shell_command_backend,
|
||||
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!(
|
||||
tools_config.unified_exec_shell_mode,
|
||||
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
|
||||
);
|
||||
assert_eq!(
|
||||
tools_config
|
||||
.with_unified_exec_shell_mode_for_session(
|
||||
ToolUserShellType::Zsh,
|
||||
Some(&PathBuf::from(if cfg!(windows) {
|
||||
r"C:\opt\codex\zsh"
|
||||
} else {
|
||||
"/opt/codex/zsh"
|
||||
})),
|
||||
Some(&PathBuf::from(if cfg!(windows) {
|
||||
r"C:\opt\codex\codex-execve-wrapper"
|
||||
} else {
|
||||
"/opt/codex/codex-execve-wrapper"
|
||||
})),
|
||||
)
|
||||
.unified_exec_shell_mode,
|
||||
if cfg!(unix) {
|
||||
UnifiedExecShellMode::ZshFork(ZshForkConfig {
|
||||
shell_zsh_path: AbsolutePathBuf::from_absolute_path("/opt/codex/zsh").unwrap(),
|
||||
main_execve_wrapper_exe: AbsolutePathBuf::from_absolute_path(
|
||||
"/opt/codex/codex-execve-wrapper",
|
||||
)
|
||||
.unwrap(),
|
||||
})
|
||||
} else {
|
||||
UnifiedExecShellMode::Direct
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_apply_patch_models_do_not_use_freeform_tool_by_default() {
|
||||
let model_info = model_info();
|
||||
let features = Features::with_defaults();
|
||||
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
assert_eq!(tools_config.apply_patch_tool_type, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagents_keep_request_user_input_config_and_agent_jobs_workers_opt_in_by_label() {
|
||||
let model_info = model_info();
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::DefaultModeRequestUserInput);
|
||||
features.enable(Feature::SpawnCsv);
|
||||
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::SubAgent(SubAgentSource::Other(
|
||||
"agent_job:test".to_string(),
|
||||
)),
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
tools_config.request_user_input_available_modes,
|
||||
request_user_input_available_modes(&features)
|
||||
);
|
||||
assert!(tools_config.agent_jobs_tools);
|
||||
assert!(tools_config.agent_jobs_worker_tools);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_generation_requires_feature_and_supported_model() {
|
||||
let supported_model_info = model_info();
|
||||
let mut unsupported_model_info = supported_model_info.clone();
|
||||
unsupported_model_info.input_modalities = vec![InputModality::Text];
|
||||
|
||||
let mut image_generation_disabled_features = Features::with_defaults();
|
||||
image_generation_disabled_features.disable(Feature::ImageGeneration);
|
||||
let mut image_generation_features = Features::with_defaults();
|
||||
image_generation_features.enable(Feature::ImageGeneration);
|
||||
|
||||
let available_models = Vec::new();
|
||||
let default_tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &supported_model_info,
|
||||
available_models: &available_models,
|
||||
features: &image_generation_disabled_features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let supported_tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &supported_model_info,
|
||||
available_models: &available_models,
|
||||
features: &image_generation_features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let auth_disallowed_tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &supported_model_info,
|
||||
available_models: &available_models,
|
||||
features: &image_generation_features,
|
||||
image_generation_tool_auth_allowed: false,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let unsupported_tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &unsupported_model_info,
|
||||
available_models: &available_models,
|
||||
features: &image_generation_features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
assert!(!default_tools_config.image_gen_tool);
|
||||
assert!(supported_tools_config.image_gen_tool);
|
||||
assert!(!auth_disallowed_tools_config.image_gen_tool);
|
||||
assert!(!unsupported_tools_config.image_gen_tool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_methods_disable_provider_bound_tool_surfaces() {
|
||||
let model_info = model_info();
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
let mut tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
tools_config.search_tool = true;
|
||||
tools_config.tool_suggest = true;
|
||||
tools_config.image_gen_tool = true;
|
||||
tools_config.namespace_tools = true;
|
||||
|
||||
let tools_config = tools_config
|
||||
.with_namespace_tools_capability(/*namespace_tools*/ false)
|
||||
.with_image_generation_capability(/*image_generation*/ false)
|
||||
.with_web_search_capability(/*web_search*/ false);
|
||||
|
||||
assert!(tools_config.search_tool);
|
||||
assert!(tools_config.tool_suggest);
|
||||
assert!(!tools_config.image_gen_tool);
|
||||
assert!(!tools_config.namespace_tools);
|
||||
assert_eq!(tools_config.web_search_mode, None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user