mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
feat: namespace in ext (#22556)
This commit is contained in:
@@ -18,12 +18,10 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::flat_tool_name;
|
||||
use crate::tools::handlers::extension_tools::ExtensionToolHandler;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::tool_dispatch_trace::ToolDispatchTrace;
|
||||
use crate::tools::tool_search_entry::ToolSearchInfo;
|
||||
use crate::util::error_or_panic;
|
||||
use codex_extension_api::ExtensionToolExecutor;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_tools::ToolName;
|
||||
@@ -671,17 +669,6 @@ impl ToolRegistryBuilder {
|
||||
self.handlers.insert(name, handler);
|
||||
}
|
||||
|
||||
pub fn register_extension_tool_executor(&mut self, executor: Arc<dyn ExtensionToolExecutor>) {
|
||||
let tool_name = executor.tool_name();
|
||||
if self.handlers.contains_key(&tool_name) {
|
||||
warn!("Skipping extension tool `{tool_name}`: handler already registered");
|
||||
return;
|
||||
}
|
||||
|
||||
let handler: Arc<dyn RegisteredTool> = Arc::new(ExtensionToolHandler::new(executor));
|
||||
self.register_tool_internal(handler, /*include_spec*/ true);
|
||||
}
|
||||
|
||||
pub fn build(self) -> (Vec<ToolSpec>, ToolRegistry) {
|
||||
let registry = ToolRegistry::new(self.handlers);
|
||||
(self.specs, registry)
|
||||
|
||||
@@ -15,9 +15,11 @@ use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::default_namespace_description;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@@ -44,25 +46,29 @@ struct ExtensionEchoExecutor;
|
||||
|
||||
impl ExtensionToolExecutor for ExtensionEchoExecutor {
|
||||
fn tool_name(&self) -> ToolName {
|
||||
ToolName::plain("extension_echo")
|
||||
ToolName::namespaced("extension/", "echo")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(ToolSpec::Function(ResponsesApiTool {
|
||||
name: "extension_echo".to_string(),
|
||||
description: "Echoes arguments through an extension tool.".to_string(),
|
||||
strict: true,
|
||||
parameters: codex_extension_api::parse_tool_input_schema(&json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": { "type": "string" },
|
||||
},
|
||||
"required": ["message"],
|
||||
"additionalProperties": false,
|
||||
}))
|
||||
.expect("extension schema should parse"),
|
||||
output_schema: None,
|
||||
defer_loading: None,
|
||||
Some(ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: "extension/".to_string(),
|
||||
description: default_namespace_description("extension/"),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: "echo".to_string(),
|
||||
description: "Echoes arguments through an extension tool.".to_string(),
|
||||
strict: true,
|
||||
parameters: codex_extension_api::parse_tool_input_schema(&json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": { "type": "string" },
|
||||
},
|
||||
"required": ["message"],
|
||||
"additionalProperties": false,
|
||||
}))
|
||||
.expect("extension schema should parse"),
|
||||
output_schema: None,
|
||||
defer_loading: None,
|
||||
})],
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -333,17 +339,21 @@ async fn extension_tool_executors_are_model_visible_and_dispatchable() -> anyhow
|
||||
);
|
||||
|
||||
assert!(
|
||||
router
|
||||
.model_visible_specs()
|
||||
.iter()
|
||||
.any(|spec| spec.name() == "extension_echo"),
|
||||
router.model_visible_specs().iter().any(
|
||||
|spec| matches!(spec, ToolSpec::Namespace(namespace)
|
||||
if namespace.name == "extension/"
|
||||
&& namespace.tools.iter().any(|tool| matches!(
|
||||
tool,
|
||||
ResponsesApiNamespaceTool::Function(tool) if tool.name == "echo"
|
||||
)))
|
||||
),
|
||||
"expected extension-provided tool to be visible to the model"
|
||||
);
|
||||
|
||||
let call = ToolRouter::build_tool_call(ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "extension_echo".to_string(),
|
||||
namespace: None,
|
||||
name: "echo".to_string(),
|
||||
namespace: Some("extension/".to_string()),
|
||||
arguments: json!({ "message": "hello" }).to_string(),
|
||||
call_id: "call-extension".to_string(),
|
||||
})?
|
||||
|
||||
@@ -24,6 +24,7 @@ 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::ExtensionToolHandler;
|
||||
use crate::tools::handlers::multi_agents::CloseAgentHandler;
|
||||
use crate::tools::handlers::multi_agents::ResumeAgentHandler;
|
||||
use crate::tools::handlers::multi_agents::SendInputHandler;
|
||||
@@ -49,13 +50,17 @@ use crate::tools::spec_plan_types::agent_type_description;
|
||||
use codex_extension_api::ExtensionToolExecutor;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::TOOL_SEARCH_TOOL_NAME;
|
||||
use codex_tools::ToolEnvironmentMode;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::ToolsConfig;
|
||||
use codex_tools::collect_code_mode_exec_prompt_tool_definitions;
|
||||
use codex_tools::default_namespace_description;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tracing::warn;
|
||||
|
||||
pub fn build_tool_registry_builder(
|
||||
config: &ToolsConfig,
|
||||
@@ -70,7 +75,6 @@ pub fn build_tool_registry_builder(
|
||||
for handler in build_code_mode_handlers(
|
||||
config,
|
||||
&handlers,
|
||||
params.extension_tool_executors,
|
||||
config.search_tool && deferred_tools_available,
|
||||
) {
|
||||
builder.register_tool(handler);
|
||||
@@ -130,39 +134,28 @@ pub fn build_tool_registry_builder(
|
||||
builder.register_handler(Arc::new(ToolSearchHandler::new(deferred_search_infos)));
|
||||
}
|
||||
|
||||
for executor in params.extension_tool_executors.iter().cloned() {
|
||||
builder.register_extension_tool_executor(executor);
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
fn build_code_mode_handlers(
|
||||
config: &ToolsConfig,
|
||||
handlers: &[Arc<dyn RegisteredTool>],
|
||||
extension_tool_executors: &[Arc<dyn ExtensionToolExecutor>],
|
||||
deferred_tools_available: bool,
|
||||
) -> Vec<Arc<dyn RegisteredTool>> {
|
||||
if !config.code_mode_enabled {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut code_mode_nested_tool_specs = handlers
|
||||
let code_mode_nested_tool_specs = handlers
|
||||
.iter()
|
||||
.filter_map(|handler| {
|
||||
if handler.exposure() == ToolExposure::DirectModelOnly {
|
||||
return None;
|
||||
}
|
||||
|
||||
let spec = handler.spec()?;
|
||||
Some(spec)
|
||||
handler.spec()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
code_mode_nested_tool_specs.extend(
|
||||
extension_tool_executors
|
||||
.iter()
|
||||
.filter_map(|executor| executor.spec()),
|
||||
);
|
||||
let namespace_descriptions = code_mode_namespace_descriptions(&code_mode_nested_tool_specs);
|
||||
let mut enabled_tools =
|
||||
collect_code_mode_exec_prompt_tool_definitions(code_mode_nested_tool_specs.iter());
|
||||
@@ -436,9 +429,47 @@ fn collect_handler_tools(
|
||||
handlers.push(handler);
|
||||
}
|
||||
|
||||
append_extension_tool_handlers(config, params.extension_tool_executors, &mut handlers);
|
||||
|
||||
handlers
|
||||
}
|
||||
|
||||
fn append_extension_tool_handlers(
|
||||
config: &ToolsConfig,
|
||||
executors: &[Arc<dyn ExtensionToolExecutor>],
|
||||
handlers: &mut Vec<Arc<dyn RegisteredTool>>,
|
||||
) {
|
||||
if executors.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut reserved_tool_names = handlers
|
||||
.iter()
|
||||
.map(|handler| handler.tool_name())
|
||||
.collect::<HashSet<_>>();
|
||||
if config.code_mode_enabled {
|
||||
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
|
||||
&& handlers
|
||||
.iter()
|
||||
.any(|handler| handler.exposure() == ToolExposure::Deferred)
|
||||
{
|
||||
reserved_tool_names.insert(ToolName::plain(TOOL_SEARCH_TOOL_NAME));
|
||||
}
|
||||
|
||||
for executor in executors.iter().cloned() {
|
||||
let tool_name = executor.tool_name();
|
||||
if !reserved_tool_names.insert(tool_name.clone()) {
|
||||
warn!("Skipping extension tool `{tool_name}`: handler already registered");
|
||||
continue;
|
||||
}
|
||||
handlers.push(Arc::new(ExtensionToolHandler::new(executor)));
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_agent_v2_handler(
|
||||
handler: impl RegisteredTool + 'static,
|
||||
exposure: ToolExposure,
|
||||
|
||||
Reference in New Issue
Block a user