mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: disable capabilities by model provider (#19442)
## Why Unsupported features must fail closed and Codex must not expose OpenAI-hosted fallback paths when the active provider cannot support them. In practice, Bedrock should not surface app connectors, MCP servers, tool search/suggestions, image generation, web search, or JS REPL until those paths are explicitly supported for that provider. This PR moves that decision into provider-owned capability metadata instead of scattering Bedrock-specific checks across callers. ## What changed - Adds `ProviderCapabilities` to `codex-model-provider`, with default support for existing providers and a Bedrock override that disables unsupported launch surfaces. - Adds `ToolCapabilityBounds` to `codex-tools` so provider capability limits can clamp otherwise-enabled tool config. - Applies capability bounds when building session and review-thread tool config. - Routes MCP/app connector configuration through `McpManager::mcp_config`, which filters configured MCP servers and app connectors based on the active provider. - Updates app-server MCP list/read paths to use the filtered MCP config. - Adds coverage for default provider capabilities, Bedrock disabled capabilities, and optional tool-surface clamping. ## Testing built locally and verified that bedrock responses api now return without errors calling unsupported tools.
This commit is contained in:
committed by
GitHub
Unverified
parent
cb8b1bbcd6
commit
f8fe96d548
@@ -107,7 +107,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
use crate::tools::handlers::multi_agents_v2::SpawnAgentHandler as SpawnAgentHandlerV2;
|
||||
use crate::tools::handlers::multi_agents_v2::WaitAgentHandler as WaitAgentHandlerV2;
|
||||
use crate::tools::handlers::unavailable_tool_message;
|
||||
use crate::tools::tool_search_entry::build_tool_search_entries;
|
||||
use crate::tools::tool_search_entry::build_tool_search_entries_for_config;
|
||||
|
||||
let mut builder = ToolRegistryBuilder::new();
|
||||
let mcp_tool_plan_inputs = mcp_tools.as_ref().map(map_mcp_tools_for_plan);
|
||||
@@ -170,7 +170,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
});
|
||||
let deferred_dynamic_tools = dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading)
|
||||
.filter(|tool| tool.defer_loading && (config.namespace_tools || tool.namespace.is_none()))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let mut tool_search_handler = None;
|
||||
@@ -270,7 +270,8 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
}
|
||||
ToolHandlerKind::ToolSearch => {
|
||||
if tool_search_handler.is_none() {
|
||||
let entries = build_tool_search_entries(
|
||||
let entries = build_tool_search_entries_for_config(
|
||||
config,
|
||||
deferred_mcp_tools.as_ref(),
|
||||
&deferred_dynamic_tools,
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ use codex_tools::AdditionalProperties;
|
||||
use codex_tools::ConfiguredToolSpec;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::LoadableToolSpec;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ShellCommandBackendConfig;
|
||||
@@ -40,6 +41,7 @@ use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
use crate::tools::tool_search_entry::build_tool_search_entries_for_config;
|
||||
|
||||
fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> rmcp::model::Tool {
|
||||
rmcp::model::Tool {
|
||||
@@ -1030,6 +1032,62 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() {
|
||||
assert!(registry.has_handler(&mcp_alias));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tool_search_entries_skip_namespace_outputs_when_namespace_tools_are_disabled() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ToolSearch);
|
||||
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.namespace_tools = false;
|
||||
let mcp_tools = HashMap::from([(
|
||||
"mcp__test_server__echo".to_string(),
|
||||
mcp_tool_info(mcp_tool(
|
||||
"echo",
|
||||
"Echo",
|
||||
serde_json::json!({"type": "object"}),
|
||||
)),
|
||||
)]);
|
||||
let dynamic_tools = vec![
|
||||
DynamicToolSpec {
|
||||
namespace: Some("codex_app".to_string()),
|
||||
name: "automation_update".to_string(),
|
||||
description: "Create or update automations.".to_string(),
|
||||
input_schema: serde_json::json!({"type": "object", "properties": {}}),
|
||||
defer_loading: true,
|
||||
},
|
||||
DynamicToolSpec {
|
||||
namespace: None,
|
||||
name: "plain_dynamic".to_string(),
|
||||
description: "Plain dynamic tool.".to_string(),
|
||||
input_schema: serde_json::json!({"type": "object", "properties": {}}),
|
||||
defer_loading: true,
|
||||
},
|
||||
];
|
||||
|
||||
let entries =
|
||||
build_tool_search_entries_for_config(&tools_config, Some(&mcp_tools), &dynamic_tools);
|
||||
let outputs = entries
|
||||
.into_iter()
|
||||
.map(|entry| entry.output)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(outputs.len(), 1);
|
||||
match &outputs[0] {
|
||||
LoadableToolSpec::Function(tool) => assert_eq!(tool.name, "plain_dynamic"),
|
||||
LoadableToolSpec::Namespace(_) => panic!("namespace tool_search output should be hidden"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn direct_mcp_tools_register_namespaced_handlers() {
|
||||
let config = test_config().await;
|
||||
|
||||
@@ -2,6 +2,7 @@ use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tools::LoadableToolSpec;
|
||||
use codex_tools::ToolSearchResultSource;
|
||||
use codex_tools::ToolsConfig;
|
||||
use codex_tools::dynamic_tool_to_loadable_tool_spec;
|
||||
use codex_tools::tool_search_result_source_to_loadable_tool_spec;
|
||||
use std::collections::HashMap;
|
||||
@@ -52,6 +53,24 @@ pub(crate) fn build_tool_search_entries(
|
||||
entries
|
||||
}
|
||||
|
||||
pub(crate) fn build_tool_search_entries_for_config(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<&HashMap<String, ToolInfo>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> Vec<ToolSearchEntry> {
|
||||
let mcp_tools = if config.namespace_tools {
|
||||
mcp_tools
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let dynamic_tools = dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| config.namespace_tools || tool.namespace.is_none())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
build_tool_search_entries(mcp_tools, &dynamic_tools)
|
||||
}
|
||||
|
||||
fn mcp_tool_search_entry(info: &ToolInfo) -> Result<ToolSearchEntry, serde_json::Error> {
|
||||
Ok(ToolSearchEntry {
|
||||
search_text: build_mcp_search_text(info),
|
||||
|
||||
Reference in New Issue
Block a user