mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
agent tools namespaced and legacy aliases preserved
This commit is contained in:
@@ -39,6 +39,8 @@ use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_tools::CommandToolOptions;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::DiscoverableToolType;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ShellToolOptions;
|
||||
use codex_tools::SpawnAgentToolOptions;
|
||||
use codex_tools::ToolSearchAppInfo;
|
||||
@@ -86,6 +88,7 @@ use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type JsonSchema = codex_tools::JsonSchema;
|
||||
|
||||
@@ -93,6 +96,8 @@ pub type JsonSchema = codex_tools::JsonSchema;
|
||||
pub(crate) use codex_tools::mcp_call_tool_result_output_schema;
|
||||
|
||||
const WEB_SEARCH_CONTENT_TYPES: [&str; 2] = ["text", "image"];
|
||||
const AGENT_TOOLS_NAMESPACE: &str = "agents";
|
||||
const AGENT_TOOLS_NAMESPACE_DESCRIPTION: &str = "Agent collaboration tools for spawning, messaging, waiting on, listing, and closing subagents.";
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum ShellCommandBackendConfig {
|
||||
@@ -382,6 +387,30 @@ fn push_tool_spec(
|
||||
}
|
||||
}
|
||||
|
||||
fn create_agent_tools_namespace(tools: Vec<ToolSpec>) -> ToolSpec {
|
||||
let tools = tools
|
||||
.into_iter()
|
||||
.filter_map(|tool| match tool {
|
||||
ToolSpec::Function(tool) => Some(ResponsesApiNamespaceTool::Function(tool)),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: AGENT_TOOLS_NAMESPACE.to_string(),
|
||||
description: AGENT_TOOLS_NAMESPACE_DESCRIPTION.to_string(),
|
||||
tools,
|
||||
})
|
||||
}
|
||||
|
||||
fn register_agent_tool_handler<H>(builder: &mut ToolRegistryBuilder, name: &str, handler: Arc<H>)
|
||||
where
|
||||
H: crate::tools::registry::ToolHandler + 'static,
|
||||
{
|
||||
builder.register_handler(name, handler.clone());
|
||||
builder.register_handler(tool_handler_key(name, Some(AGENT_TOOLS_NAMESPACE)), handler);
|
||||
}
|
||||
|
||||
/// Builds the tool registry builder while collecting tool specs for later serialization.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn build_specs(
|
||||
@@ -436,8 +465,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
use crate::tools::handlers::multi_agents_v2::SendMessageHandler as SendMessageHandlerV2;
|
||||
use crate::tools::handlers::multi_agents_v2::SpawnAgentHandler as SpawnAgentHandlerV2;
|
||||
use crate::tools::handlers::multi_agents_v2::WaitAgentHandler as WaitAgentHandlerV2;
|
||||
use std::sync::Arc;
|
||||
|
||||
let mut builder = ToolRegistryBuilder::new();
|
||||
|
||||
let shell_handler = Arc::new(ShellHandler);
|
||||
@@ -626,9 +653,9 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
builder.register_handler("request_permissions", request_permissions_handler);
|
||||
}
|
||||
|
||||
if config.search_tool
|
||||
&& let Some(app_tools) = app_tools
|
||||
{
|
||||
let include_tool_search = config.search_tool || config.collab_tools;
|
||||
if include_tool_search && (app_tools.is_some() || config.collab_tools) {
|
||||
let app_tools = app_tools.unwrap_or_default();
|
||||
let search_tool_handler = Arc::new(ToolSearchHandler::new(app_tools.clone()));
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
@@ -775,102 +802,67 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
|
||||
if config.collab_tools {
|
||||
if config.multi_agent_v2 {
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
let agent_tools = vec![
|
||||
create_spawn_agent_tool_v2(SpawnAgentToolOptions {
|
||||
available_models: &config.available_models,
|
||||
agent_type_description: crate::agent::role::spawn_tool_spec::build(
|
||||
&config.agent_roles,
|
||||
),
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_send_message_tool(),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_assign_task_tool(),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_wait_agent_tool_v2(WaitAgentTimeoutOptions {
|
||||
default_timeout_ms: DEFAULT_WAIT_TIMEOUT_MS,
|
||||
min_timeout_ms: MIN_WAIT_TIMEOUT_MS,
|
||||
max_timeout_ms: MAX_WAIT_TIMEOUT_MS,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_close_agent_tool_v2(),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_list_agents_tool(),
|
||||
];
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_agent_tools_namespace(agent_tools),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
builder.register_handler("spawn_agent", Arc::new(SpawnAgentHandlerV2));
|
||||
builder.register_handler("send_message", Arc::new(SendMessageHandlerV2));
|
||||
builder.register_handler("assign_task", Arc::new(AssignTaskHandlerV2));
|
||||
builder.register_handler("wait_agent", Arc::new(WaitAgentHandlerV2));
|
||||
builder.register_handler("close_agent", Arc::new(CloseAgentHandlerV2));
|
||||
builder.register_handler("list_agents", Arc::new(ListAgentsHandlerV2));
|
||||
} else {
|
||||
push_tool_spec(
|
||||
register_agent_tool_handler(&mut builder, "spawn_agent", Arc::new(SpawnAgentHandlerV2));
|
||||
register_agent_tool_handler(
|
||||
&mut builder,
|
||||
"send_message",
|
||||
Arc::new(SendMessageHandlerV2),
|
||||
);
|
||||
register_agent_tool_handler(&mut builder, "assign_task", Arc::new(AssignTaskHandlerV2));
|
||||
register_agent_tool_handler(&mut builder, "wait_agent", Arc::new(WaitAgentHandlerV2));
|
||||
register_agent_tool_handler(&mut builder, "close_agent", Arc::new(CloseAgentHandlerV2));
|
||||
register_agent_tool_handler(&mut builder, "list_agents", Arc::new(ListAgentsHandlerV2));
|
||||
} else {
|
||||
let agent_tools = vec![
|
||||
create_spawn_agent_tool_v1(SpawnAgentToolOptions {
|
||||
available_models: &config.available_models,
|
||||
agent_type_description: crate::agent::role::spawn_tool_spec::build(
|
||||
&config.agent_roles,
|
||||
),
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_send_input_tool_v1(),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_resume_agent_tool(),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
builder.register_handler("resume_agent", Arc::new(ResumeAgentHandler));
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_wait_agent_tool_v1(WaitAgentTimeoutOptions {
|
||||
default_timeout_ms: DEFAULT_WAIT_TIMEOUT_MS,
|
||||
min_timeout_ms: MIN_WAIT_TIMEOUT_MS,
|
||||
max_timeout_ms: MAX_WAIT_TIMEOUT_MS,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
create_close_agent_tool_v1(),
|
||||
];
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_close_agent_tool_v1(),
|
||||
create_agent_tools_namespace(agent_tools),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
builder.register_handler("spawn_agent", Arc::new(SpawnAgentHandler));
|
||||
builder.register_handler("send_input", Arc::new(SendInputHandler));
|
||||
builder.register_handler("wait_agent", Arc::new(WaitAgentHandler));
|
||||
builder.register_handler("close_agent", Arc::new(CloseAgentHandler));
|
||||
register_agent_tool_handler(&mut builder, "spawn_agent", Arc::new(SpawnAgentHandler));
|
||||
register_agent_tool_handler(&mut builder, "send_input", Arc::new(SendInputHandler));
|
||||
register_agent_tool_handler(&mut builder, "resume_agent", Arc::new(ResumeAgentHandler));
|
||||
register_agent_tool_handler(&mut builder, "wait_agent", Arc::new(WaitAgentHandler));
|
||||
register_agent_tool_handler(&mut builder, "close_agent", Arc::new(CloseAgentHandler));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ use codex_tools::ConfiguredToolSpec;
|
||||
use codex_tools::DiscoverablePluginInfo;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::FreeformTool;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ResponsesApiWebSearchFilters;
|
||||
use codex_tools::ResponsesApiWebSearchUserLocation;
|
||||
@@ -197,6 +198,25 @@ fn find_tool<'a>(tools: &'a [ConfiguredToolSpec], expected_name: &str) -> &'a Co
|
||||
.unwrap_or_else(|| panic!("expected tool {expected_name}"))
|
||||
}
|
||||
|
||||
fn find_namespaced_tool<'a>(
|
||||
tools: &'a [ConfiguredToolSpec],
|
||||
namespace: &str,
|
||||
expected_name: &str,
|
||||
) -> &'a ResponsesApiTool {
|
||||
let namespace_tool = find_tool(tools, namespace);
|
||||
let ToolSpec::Namespace(namespace) = &namespace_tool.spec else {
|
||||
panic!("expected {namespace} namespace tool");
|
||||
};
|
||||
namespace
|
||||
.tools
|
||||
.iter()
|
||||
.find_map(|tool| match tool {
|
||||
ResponsesApiNamespaceTool::Function(tool) if tool.name == expected_name => Some(tool),
|
||||
_ => None,
|
||||
})
|
||||
.expect("expected tool in namespace")
|
||||
}
|
||||
|
||||
fn strip_descriptions_schema(schema: &mut JsonSchema) {
|
||||
match schema {
|
||||
JsonSchema::Boolean { description }
|
||||
@@ -229,7 +249,8 @@ fn strip_descriptions_tool(spec: &mut ToolSpec) {
|
||||
ToolSpec::Function(ResponsesApiTool { parameters, .. }) => {
|
||||
strip_descriptions_schema(parameters);
|
||||
}
|
||||
ToolSpec::Freeform(_)
|
||||
ToolSpec::Namespace(_)
|
||||
| ToolSpec::Freeform(_)
|
||||
| ToolSpec::LocalShell {}
|
||||
| ToolSpec::ImageGeneration { .. }
|
||||
| ToolSpec::WebSearch { .. } => {}
|
||||
@@ -425,18 +446,12 @@ fn test_build_specs_collab_tools_enabled() {
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
&["spawn_agent", "send_input", "wait_agent", "close_agent"],
|
||||
);
|
||||
assert_contains_tool_names(&tools, &["agents"]);
|
||||
assert_lacks_tool_name(&tools, "spawn_agents_on_csv");
|
||||
assert_lacks_tool_name(&tools, "list_agents");
|
||||
|
||||
let spawn_agent = find_tool(&tools, "spawn_agent");
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &spawn_agent.spec else {
|
||||
panic!("spawn_agent should be a function tool");
|
||||
};
|
||||
let JsonSchema::Object { properties, .. } = parameters else {
|
||||
let spawn_agent = find_namespaced_tool(&tools, "agents", "spawn_agent");
|
||||
let JsonSchema::Object { properties, .. } = &spawn_agent.parameters else {
|
||||
panic!("spawn_agent should use object params");
|
||||
};
|
||||
assert!(properties.contains_key("fork_context"));
|
||||
@@ -467,64 +482,41 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
&[
|
||||
"spawn_agent",
|
||||
"send_message",
|
||||
"assign_task",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"list_agents",
|
||||
],
|
||||
);
|
||||
assert_contains_tool_names(&tools, &["agents"]);
|
||||
|
||||
let spawn_agent = find_tool(&tools, "spawn_agent");
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
parameters,
|
||||
output_schema,
|
||||
let spawn_agent = find_namespaced_tool(&tools, "agents", "spawn_agent");
|
||||
let JsonSchema::Object {
|
||||
properties: _,
|
||||
required,
|
||||
..
|
||||
}) = &spawn_agent.spec
|
||||
} = &spawn_agent.parameters
|
||||
else {
|
||||
panic!("spawn_agent should be a function tool");
|
||||
};
|
||||
let JsonSchema::Object {
|
||||
properties,
|
||||
required,
|
||||
..
|
||||
} = parameters
|
||||
else {
|
||||
panic!("spawn_agent should use object params");
|
||||
};
|
||||
assert!(properties.contains_key("task_name"));
|
||||
assert!(properties.contains_key("items"));
|
||||
assert!(properties.contains_key("fork_turns"));
|
||||
assert!(!properties.contains_key("message"));
|
||||
assert!(!properties.contains_key("fork_context"));
|
||||
let output_schema = spawn_agent
|
||||
.output_schema
|
||||
.as_ref()
|
||||
.expect("spawn_agent should define output schema");
|
||||
assert_eq!(
|
||||
required.as_ref(),
|
||||
Some(&vec!["task_name".to_string(), "items".to_string()])
|
||||
);
|
||||
let output_schema = output_schema
|
||||
.as_ref()
|
||||
.expect("spawn_agent should define output schema");
|
||||
assert_eq!(
|
||||
output_schema["required"],
|
||||
json!(["agent_id", "task_name", "nickname"])
|
||||
);
|
||||
|
||||
let send_message = find_tool(&tools, "send_message");
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &send_message.spec else {
|
||||
panic!("send_message should be a function tool");
|
||||
};
|
||||
let send_message = find_namespaced_tool(&tools, "agents", "send_message");
|
||||
let JsonSchema::Object {
|
||||
properties,
|
||||
required,
|
||||
..
|
||||
} = parameters
|
||||
} = &send_message.parameters
|
||||
else {
|
||||
panic!("send_message should use object params");
|
||||
panic!("send_message should be a function tool");
|
||||
};
|
||||
assert!(properties.contains_key("items"));
|
||||
assert!(!properties.contains_key("message"));
|
||||
assert!(properties.contains_key("target"));
|
||||
assert!(!properties.contains_key("interrupt"));
|
||||
assert!(!properties.contains_key("message"));
|
||||
@@ -533,17 +525,14 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
Some(&vec!["target".to_string(), "items".to_string()])
|
||||
);
|
||||
|
||||
let assign_task = find_tool(&tools, "assign_task");
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &assign_task.spec else {
|
||||
panic!("assign_task should be a function tool");
|
||||
};
|
||||
let assign_task = find_namespaced_tool(&tools, "agents", "assign_task");
|
||||
let JsonSchema::Object {
|
||||
properties,
|
||||
required,
|
||||
..
|
||||
} = parameters
|
||||
} = &assign_task.parameters
|
||||
else {
|
||||
panic!("assign_task should use object params");
|
||||
panic!("assign_task should be a function tool");
|
||||
};
|
||||
assert!(properties.contains_key("target"));
|
||||
assert!(!properties.contains_key("message"));
|
||||
@@ -552,56 +541,42 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
Some(&vec!["target".to_string(), "items".to_string()])
|
||||
);
|
||||
|
||||
let wait_agent = find_tool(&tools, "wait_agent");
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
}) = &wait_agent.spec
|
||||
else {
|
||||
panic!("wait_agent should be a function tool");
|
||||
};
|
||||
let wait_agent = find_namespaced_tool(&tools, "agents", "wait_agent");
|
||||
let JsonSchema::Object {
|
||||
properties,
|
||||
required,
|
||||
..
|
||||
} = parameters
|
||||
} = &wait_agent.parameters
|
||||
else {
|
||||
panic!("wait_agent should use object params");
|
||||
panic!("wait_agent should be a function tool");
|
||||
};
|
||||
let output_schema = wait_agent
|
||||
.output_schema
|
||||
.as_ref()
|
||||
.expect("wait_agent should define output schema");
|
||||
assert!(!properties.contains_key("targets"));
|
||||
assert!(properties.contains_key("timeout_ms"));
|
||||
assert_eq!(required, &None);
|
||||
let output_schema = output_schema
|
||||
.as_ref()
|
||||
.expect("wait_agent should define output schema");
|
||||
assert_eq!(
|
||||
output_schema["properties"]["message"]["description"],
|
||||
json!("Brief wait summary without the agent's final content.")
|
||||
);
|
||||
|
||||
let list_agents = find_tool(&tools, "list_agents");
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
}) = &list_agents.spec
|
||||
else {
|
||||
panic!("list_agents should be a function tool");
|
||||
};
|
||||
let list_agents = find_namespaced_tool(&tools, "agents", "list_agents");
|
||||
let JsonSchema::Object {
|
||||
properties,
|
||||
required,
|
||||
..
|
||||
} = parameters
|
||||
} = &list_agents.parameters
|
||||
else {
|
||||
panic!("list_agents should use object params");
|
||||
panic!("list_agents should be a function tool");
|
||||
};
|
||||
assert!(properties.contains_key("path_prefix"));
|
||||
assert_eq!(required.as_ref(), None);
|
||||
let output_schema = output_schema
|
||||
let output_schema = list_agents
|
||||
.output_schema
|
||||
.as_ref()
|
||||
.expect("list_agents should define output schema");
|
||||
assert!(properties.contains_key("path_prefix"));
|
||||
assert_eq!(required.as_ref(), None);
|
||||
assert_eq!(
|
||||
output_schema["properties"]["agents"]["items"]["required"],
|
||||
json!(["agent_name", "agent_status", "last_task_message"])
|
||||
@@ -634,16 +609,7 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
&[
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"spawn_agents_on_csv",
|
||||
],
|
||||
);
|
||||
assert_contains_tool_names(&tools, &["agents", "spawn_agents_on_csv"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -752,15 +718,7 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
.build();
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
&[
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"spawn_agents_on_csv",
|
||||
"report_agent_job_result",
|
||||
],
|
||||
&["agents", "spawn_agents_on_csv", "report_agent_job_result"],
|
||||
);
|
||||
assert_lacks_tool_name(&tools, "request_user_input");
|
||||
}
|
||||
@@ -1374,11 +1332,7 @@ fn test_build_specs_gpt5_codex_default() {
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1397,11 +1351,7 @@ fn test_build_specs_gpt51_codex_default() {
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1422,11 +1372,7 @@ fn test_build_specs_gpt5_codex_unified_exec_web_search() {
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1447,11 +1393,7 @@ fn test_build_specs_gpt51_codex_unified_exec_web_search() {
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1470,11 +1412,7 @@ fn test_gpt_5_1_codex_max_defaults() {
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1493,11 +1431,7 @@ fn test_codex_5_1_mini_defaults() {
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1515,11 +1449,7 @@ fn test_gpt_5_defaults() {
|
||||
"request_user_input",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1538,11 +1468,7 @@ fn test_gpt_5_1_defaults() {
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1563,11 +1489,7 @@ fn test_gpt_5_1_codex_max_unified_exec_web_search() {
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"spawn_agent",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"agents",
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -2227,7 +2149,8 @@ fn search_tool_description_handles_no_enabled_apps() {
|
||||
panic!("expected tool_search tool");
|
||||
};
|
||||
|
||||
assert!(description.contains("None currently enabled."));
|
||||
assert!(description.contains("agents"));
|
||||
assert!(description.contains("Agent collaboration tools for spawning, messaging, waiting on, listing, and closing subagents."));
|
||||
assert!(!description.contains("{{app_descriptions}}"));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ const SEARCH_TOOL_DESCRIPTION_SNIPPETS: [&str; 2] = [
|
||||
"- Calendar: Plan events and manage your calendar.",
|
||||
];
|
||||
const TOOL_SEARCH_TOOL_NAME: &str = "tool_search";
|
||||
const AGENTS_NAMESPACE_TOOL_NAME: &str = "agents";
|
||||
const CALENDAR_CREATE_TOOL: &str = "mcp__codex_apps__calendar_create_event";
|
||||
const CALENDAR_LIST_TOOL: &str = "mcp__codex_apps__calendar_list_events";
|
||||
const SEARCH_CALENDAR_NAMESPACE: &str = "mcp__codex_apps__calendar";
|
||||
@@ -217,7 +218,7 @@ async fn tool_search_disabled_by_default_exposes_apps_tools_directly() -> Result
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn search_tool_is_hidden_for_api_key_auth() -> Result<()> {
|
||||
async fn search_tool_keeps_agents_namespace_for_api_key_auth() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
@@ -247,8 +248,12 @@ async fn search_tool_is_hidden_for_api_key_auth() -> Result<()> {
|
||||
let body = mock.single_request().body_json();
|
||||
let tools = tool_names(&body);
|
||||
assert!(
|
||||
!tools.iter().any(|name| name == TOOL_SEARCH_TOOL_NAME),
|
||||
"tools list should not include {TOOL_SEARCH_TOOL_NAME} for API key auth: {tools:?}"
|
||||
tools.iter().any(|name| name == TOOL_SEARCH_TOOL_NAME),
|
||||
"tools list should include {TOOL_SEARCH_TOOL_NAME} for the always-present agents namespace: {tools:?}"
|
||||
);
|
||||
assert!(
|
||||
tools.iter().any(|name| name == AGENTS_NAMESPACE_TOOL_NAME),
|
||||
"tools list should include the {AGENTS_NAMESPACE_TOOL_NAME} namespace: {tools:?}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -122,8 +122,8 @@ async fn tool_suggest_is_available_without_search_tool_after_discovery_attempts(
|
||||
let body = mock.single_request().body_json();
|
||||
let tools = tool_names(&body);
|
||||
assert!(
|
||||
!tools.iter().any(|name| name == TOOL_SEARCH_TOOL_NAME),
|
||||
"tools list should not include {TOOL_SEARCH_TOOL_NAME}: {tools:?}"
|
||||
tools.iter().any(|name| name == TOOL_SEARCH_TOOL_NAME),
|
||||
"tools list should include {TOOL_SEARCH_TOOL_NAME}: {tools:?}"
|
||||
);
|
||||
assert!(
|
||||
tools.iter().any(|name| name == TOOL_SUGGEST_TOOL_NAME),
|
||||
|
||||
@@ -132,7 +132,8 @@ fn code_mode_tool_definition_for_spec(spec: &ToolSpec) -> Option<CodeModeToolDef
|
||||
input_schema: None,
|
||||
output_schema: None,
|
||||
}),
|
||||
ToolSpec::LocalShell {}
|
||||
ToolSpec::Namespace(_)
|
||||
| ToolSpec::LocalShell {}
|
||||
| ToolSpec::ImageGeneration { .. }
|
||||
| ToolSpec::ToolSearch { .. }
|
||||
| ToolSpec::WebSearch { .. } => None,
|
||||
|
||||
@@ -7,6 +7,8 @@ use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
const TUI_CLIENT_NAME: &str = "codex-tui";
|
||||
const AGENT_TOOLS_NAMESPACE: &str = "agents";
|
||||
const AGENT_TOOLS_NAMESPACE_DESCRIPTION: &str = "Agent collaboration tools for spawning, messaging, waiting on, listing, and closing subagents.";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ToolSearchAppInfo {
|
||||
@@ -138,7 +140,10 @@ pub fn create_tool_search_tool(app_tools: &[ToolSearchAppInfo], default_limit: u
|
||||
),
|
||||
]);
|
||||
|
||||
let mut app_descriptions = BTreeMap::new();
|
||||
let mut app_descriptions = BTreeMap::from([(
|
||||
AGENT_TOOLS_NAMESPACE.to_string(),
|
||||
Some(AGENT_TOOLS_NAMESPACE_DESCRIPTION.to_string()),
|
||||
)]);
|
||||
for app_tool in app_tools {
|
||||
app_descriptions
|
||||
.entry(app_tool.name.clone())
|
||||
|
||||
@@ -27,9 +27,9 @@ fn create_tool_search_tool_deduplicates_and_renders_enabled_apps() {
|
||||
],
|
||||
/*default_limit*/ 8,
|
||||
),
|
||||
ToolSpec::ToolSearch {
|
||||
execution: "client".to_string(),
|
||||
description: "# Apps (Connectors) tool discovery\n\nSearches over apps/connectors tool metadata with BM25 and exposes matching tools for the next model call.\n\nYou have access to all the tools of the following apps/connectors:\n- Google Drive: Use Google Drive as the single entrypoint for Drive, Docs, Sheets, and Slides work.\n- Slack\nSome of the tools may not have been provided to you upfront, and you should use this tool (`tool_search`) to search for the required tools and load them for the apps mentioned above. For the apps mentioned above, always use `tool_search` instead of `list_mcp_resources` or `list_mcp_resource_templates` for tool discovery.".to_string(),
|
||||
ToolSpec::ToolSearch {
|
||||
execution: "client".to_string(),
|
||||
description: "# Apps (Connectors) tool discovery\n\nSearches over apps/connectors tool metadata with BM25 and exposes matching tools for the next model call.\n\nYou have access to all the tools of the following apps/connectors:\n- Google Drive: Use Google Drive as the single entrypoint for Drive, Docs, Sheets, and Slides work.\n- Slack\n- agents: Agent collaboration tools for spawning, messaging, waiting on, listing, and closing subagents.\nSome of the tools may not have been provided to you upfront, and you should use this tool (`tool_search`) to search for the required tools and load them for the apps mentioned above. For the apps mentioned above, always use `tool_search` instead of `list_mcp_resources` or `list_mcp_resource_templates` for tool discovery.".to_string(),
|
||||
parameters: JsonSchema::Object {
|
||||
properties: BTreeMap::from([
|
||||
(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::FreeformTool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiNamespace;
|
||||
use crate::ResponsesApiTool;
|
||||
use codex_protocol::config_types::WebSearchContextSize;
|
||||
use codex_protocol::config_types::WebSearchFilters as ConfigWebSearchFilters;
|
||||
@@ -15,6 +16,8 @@ use serde_json::Value;
|
||||
pub enum ToolSpec {
|
||||
#[serde(rename = "function")]
|
||||
Function(ResponsesApiTool),
|
||||
#[serde(rename = "namespace")]
|
||||
Namespace(ResponsesApiNamespace),
|
||||
#[serde(rename = "tool_search")]
|
||||
ToolSearch {
|
||||
execution: String,
|
||||
@@ -52,6 +55,7 @@ impl ToolSpec {
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
ToolSpec::Function(tool) => tool.name.as_str(),
|
||||
ToolSpec::Namespace(namespace) => namespace.name.as_str(),
|
||||
ToolSpec::ToolSearch { .. } => "tool_search",
|
||||
ToolSpec::LocalShell {} => "local_shell",
|
||||
ToolSpec::ImageGeneration { .. } => "image_generation",
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::AdditionalProperties;
|
||||
use crate::FreeformTool;
|
||||
use crate::FreeformToolFormat;
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiNamespace;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::create_tools_json_for_responses_api;
|
||||
use codex_protocol::config_types::WebSearchContextSize;
|
||||
@@ -34,6 +35,15 @@ fn tool_spec_name_covers_all_variants() {
|
||||
.name(),
|
||||
"lookup_order"
|
||||
);
|
||||
assert_eq!(
|
||||
ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: "agents".to_string(),
|
||||
description: "Agent tools".to_string(),
|
||||
tools: Vec::new(),
|
||||
})
|
||||
.name(),
|
||||
"agents"
|
||||
);
|
||||
assert_eq!(
|
||||
ToolSpec::ToolSearch {
|
||||
execution: "sync".to_string(),
|
||||
@@ -166,6 +176,24 @@ fn create_tools_json_for_responses_api_includes_top_level_name() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_tool_spec_serializes_expected_wire_shape() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: "agents".to_string(),
|
||||
description: "Agent collaboration tools.".to_string(),
|
||||
tools: Vec::new(),
|
||||
}))
|
||||
.expect("serialize namespace"),
|
||||
json!({
|
||||
"type": "namespace",
|
||||
"name": "agents",
|
||||
"description": "Agent collaboration tools.",
|
||||
"tools": []
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_tool_spec_serializes_expected_wire_shape() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user