mirror of
https://github.com/openai/codex.git
synced 2026-06-02 03:11:59 +00:00
Compare commits
5 Commits
fcoury/tok
...
jif/tool-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f247d7e7fd | ||
|
|
8667f50b42 | ||
|
|
1199207c89 | ||
|
|
6adb23b821 | ||
|
|
074b7692d8 |
2
codex-rs/Cargo.lock
generated
2
codex-rs/Cargo.lock
generated
@@ -3689,6 +3689,7 @@ dependencies = [
|
||||
name = "codex-tool-api"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-protocol",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
@@ -3702,6 +3703,7 @@ dependencies = [
|
||||
"codex-code-mode",
|
||||
"codex-features",
|
||||
"codex-protocol",
|
||||
"codex-tool-api",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-pty",
|
||||
"pretty_assertions",
|
||||
|
||||
@@ -544,7 +544,7 @@ fn test_tool_runtime(session: Arc<Session>, turn_context: Arc<TurnContext>) -> T
|
||||
deferred_mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: Vec::new(),
|
||||
extension_tool_definitions: Vec::new(),
|
||||
dynamic_tools: turn_context.dynamic_tools.as_slice(),
|
||||
},
|
||||
));
|
||||
@@ -8613,7 +8613,7 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
|
||||
mcp_tools: Some(tools),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: Vec::new(),
|
||||
extension_tool_definitions: Vec::new(),
|
||||
dynamic_tools: turn_context.dynamic_tools.as_slice(),
|
||||
},
|
||||
);
|
||||
@@ -9118,7 +9118,7 @@ async fn unified_exec_rejects_escalated_permissions_when_policy_not_on_request()
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
|
||||
|
||||
let handler = ExecCommandHandler::default();
|
||||
let handler = ExecCommandHandler;
|
||||
let resp = handler
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::clone(&session),
|
||||
|
||||
@@ -498,7 +498,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
|
||||
|
||||
let handler = ExecCommandHandler::default();
|
||||
let handler = ExecCommandHandler;
|
||||
let resp = handler
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::clone(&session),
|
||||
|
||||
@@ -55,7 +55,7 @@ use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::parallel::ToolCallRuntime;
|
||||
use crate::tools::registry::ToolArgumentDiffConsumer;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
use crate::tools::router::extension_tool_bundles;
|
||||
use crate::tools::router::extension_tool_definitions;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use crate::turn_timing::record_turn_ttft_metric;
|
||||
use crate::unavailable_tool::collect_unavailable_called_tools;
|
||||
@@ -1267,7 +1267,7 @@ pub(crate) async fn built_tools(
|
||||
deferred_mcp_tools,
|
||||
unavailable_called_tools,
|
||||
discoverable_tools,
|
||||
extension_tool_bundles: extension_tool_bundles(sess),
|
||||
extension_tool_definitions: extension_tool_definitions(sess),
|
||||
dynamic_tools: turn_context.dynamic_tools.as_slice(),
|
||||
},
|
||||
)))
|
||||
|
||||
@@ -3,22 +3,37 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_code_mode::ToolDefinition as CodeModeToolDefinition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::ExecContext;
|
||||
use super::PUBLIC_TOOL_NAME;
|
||||
use super::build_enabled_tools;
|
||||
use super::execute_spec::create_code_mode_tool;
|
||||
use super::handle_runtime_response;
|
||||
use super::is_exec_tool_name;
|
||||
|
||||
pub struct CodeModeExecuteHandler {
|
||||
spec: ToolSpec,
|
||||
}
|
||||
pub struct CodeModeExecuteHandler;
|
||||
|
||||
impl CodeModeExecuteHandler {
|
||||
pub(crate) fn new(spec: ToolSpec) -> Self {
|
||||
Self { spec }
|
||||
pub(crate) fn definition(
|
||||
enabled_tools: &[CodeModeToolDefinition],
|
||||
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
|
||||
code_mode_only: bool,
|
||||
deferred_tools_available: bool,
|
||||
) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(
|
||||
Self,
|
||||
create_code_mode_tool(
|
||||
enabled_tools,
|
||||
namespace_descriptions,
|
||||
code_mode_only,
|
||||
deferred_tools_available,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
@@ -89,10 +104,6 @@ impl ToolHandler for CodeModeExecuteHandler {
|
||||
ToolName::plain(PUBLIC_TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(self.spec.clone())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Custom { .. })
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::tools::parallel::ToolCallRuntime;
|
||||
use crate::tools::router::ToolCall;
|
||||
use crate::tools::router::ToolCallSource;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
use crate::tools::router::extension_tool_bundles;
|
||||
use crate::tools::router::extension_tool_definitions;
|
||||
use crate::unified_exec::resolve_max_tokens;
|
||||
use codex_features::Feature;
|
||||
use codex_tools::ToolName;
|
||||
@@ -284,7 +284,7 @@ async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
|
||||
mcp_tools: Some(listed_mcp_tools),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: extension_tool_bundles(exec.session.as_ref()),
|
||||
extension_tool_definitions: extension_tool_definitions(exec.session.as_ref()),
|
||||
dynamic_tools: exec.turn.dynamic_tools.as_slice(),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -5,17 +5,24 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::DEFAULT_WAIT_YIELD_TIME_MS;
|
||||
use super::ExecContext;
|
||||
use super::WAIT_TOOL_NAME;
|
||||
use super::handle_runtime_response;
|
||||
use super::wait_spec::create_wait_tool;
|
||||
use super::handle_runtime_response;
|
||||
|
||||
pub struct CodeModeWaitHandler;
|
||||
|
||||
impl CodeModeWaitHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_wait_tool())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ExecWaitArgs {
|
||||
cell_id: String,
|
||||
@@ -47,10 +54,6 @@ impl ToolHandler for CodeModeWaitHandler {
|
||||
ToolName::plain(WAIT_TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_wait_tool())
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
|
||||
@@ -4,13 +4,20 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::agent_jobs_spec::create_report_agent_job_result_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct ReportAgentJobResultHandler;
|
||||
|
||||
impl ReportAgentJobResultHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_report_agent_job_result_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ReportAgentJobResultHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -18,10 +25,6 @@ impl ToolHandler for ReportAgentJobResultHandler {
|
||||
ToolName::plain("report_agent_job_result")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_report_agent_job_result_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -4,13 +4,20 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::agent_jobs_spec::create_spawn_agents_on_csv_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct SpawnAgentsOnCsvHandler;
|
||||
|
||||
impl SpawnAgentsOnCsvHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_spawn_agents_on_csv_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for SpawnAgentsOnCsvHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -18,10 +25,6 @@ impl ToolHandler for SpawnAgentsOnCsvHandler {
|
||||
ToolName::plain("spawn_agents_on_csv")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_spawn_agents_on_csv_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolArgumentDiffConsumer;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use crate::tools::runtimes::apply_patch::ApplyPatchRequest;
|
||||
use crate::tools::runtimes::apply_patch::ApplyPatchRuntime;
|
||||
use crate::tools::sandboxing::ToolCtx;
|
||||
@@ -49,7 +51,6 @@ use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
|
||||
use codex_sandboxing::policy_transforms::merge_permission_profiles;
|
||||
use codex_sandboxing::policy_transforms::normalize_additional_permissions;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
const APPLY_PATCH_ARGUMENT_DIFF_BUFFER_INTERVAL: Duration = Duration::from_millis(500);
|
||||
@@ -64,6 +65,13 @@ impl ApplyPatchHandler {
|
||||
pub(crate) fn new(multi_environment: bool) -> Self {
|
||||
Self { multi_environment }
|
||||
}
|
||||
|
||||
pub(crate) fn definition(multi_environment: bool) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(
|
||||
Self::new(multi_environment),
|
||||
create_apply_patch_freeform_tool(multi_environment),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -303,10 +311,6 @@ impl ToolHandler for ApplyPatchHandler {
|
||||
ToolName::plain("apply_patch")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_apply_patch_freeform_tool(self.multi_environment))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Custom { .. })
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_tool_api::ToolBundle as ExtensionToolBundle;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolError as ExtensionToolError;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tool_api::ToolExecutor;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
@@ -18,11 +18,11 @@ use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
||||
pub(crate) struct BundledToolOutput {
|
||||
pub(crate) struct ExtensionToolOutput {
|
||||
value: Value,
|
||||
}
|
||||
|
||||
impl ToolOutput for BundledToolOutput {
|
||||
impl ToolOutput for ExtensionToolOutput {
|
||||
fn log_preview(&self) -> String {
|
||||
self.value.to_string()
|
||||
}
|
||||
@@ -50,14 +50,13 @@ impl ToolOutput for BundledToolOutput {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BundledToolHandler {
|
||||
bundle: ExtensionToolBundle,
|
||||
spec: ToolSpec,
|
||||
pub(crate) struct ExtensionToolHandler {
|
||||
definition: ToolDefinition<Arc<dyn ToolExecutor>>,
|
||||
}
|
||||
|
||||
impl BundledToolHandler {
|
||||
pub(crate) fn new(bundle: ExtensionToolBundle, spec: ToolSpec) -> Self {
|
||||
Self { bundle, spec }
|
||||
impl ExtensionToolHandler {
|
||||
pub(crate) fn new(definition: ToolDefinition<Arc<dyn ToolExecutor>>) -> Self {
|
||||
Self { definition }
|
||||
}
|
||||
|
||||
fn arguments_from_payload<'a>(&self, payload: &'a ToolPayload) -> Option<&'a str> {
|
||||
@@ -68,15 +67,11 @@ impl BundledToolHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for BundledToolHandler {
|
||||
type Output = BundledToolOutput;
|
||||
impl ToolHandler for ExtensionToolHandler {
|
||||
type Output = ExtensionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
ToolName::plain(self.bundle.tool_name())
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(self.spec.clone())
|
||||
self.definition.tool_name().clone()
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
@@ -116,36 +111,23 @@ impl ToolHandler for BundledToolHandler {
|
||||
.ok_or_else(|| {
|
||||
FunctionCallError::Fatal(format!(
|
||||
"tool {} invoked with incompatible payload",
|
||||
self.bundle.tool_name()
|
||||
self.definition.tool_name()
|
||||
))
|
||||
})?
|
||||
.to_string();
|
||||
let value = self
|
||||
.bundle
|
||||
.executor()
|
||||
.definition
|
||||
.runtime()
|
||||
.execute(codex_tool_api::ToolCall {
|
||||
call_id: invocation.call_id,
|
||||
arguments,
|
||||
})
|
||||
.await
|
||||
.map_err(map_extension_tool_error)?;
|
||||
Ok(BundledToolOutput { value })
|
||||
Ok(ExtensionToolOutput { value })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extension_tool_spec(
|
||||
spec: &codex_tool_api::FunctionToolSpec,
|
||||
) -> Result<ToolSpec, serde_json::Error> {
|
||||
Ok(ToolSpec::Function(ResponsesApiTool {
|
||||
name: spec.name.clone(),
|
||||
description: spec.description.clone(),
|
||||
strict: spec.strict,
|
||||
defer_loading: None,
|
||||
parameters: codex_tools::parse_tool_input_schema(&spec.parameters)?,
|
||||
output_schema: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn map_extension_tool_error(error: ExtensionToolError) -> FunctionCallError {
|
||||
match error {
|
||||
ExtensionToolError::RespondToModel(message) => FunctionCallError::RespondToModel(message),
|
||||
@@ -168,9 +150,8 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
use super::BundledToolHandler;
|
||||
use super::BundledToolOutput;
|
||||
use super::extension_tool_spec;
|
||||
use super::ExtensionToolHandler;
|
||||
use super::ExtensionToolOutput;
|
||||
use crate::tools::context::ToolCallSource;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
@@ -190,7 +171,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn exposes_generic_hook_payloads_and_is_conservatively_mutating() {
|
||||
let bundle = codex_tool_api::ToolBundle::new(
|
||||
let definition = codex_tool_api::ToolDefinition::from_function_spec(
|
||||
codex_tool_api::FunctionToolSpec {
|
||||
name: "extension_echo".to_string(),
|
||||
description: "Echoes arguments.".to_string(),
|
||||
@@ -204,10 +185,9 @@ mod tests {
|
||||
"additionalProperties": false,
|
||||
}),
|
||||
},
|
||||
Arc::new(StubExtensionExecutor),
|
||||
Arc::new(StubExtensionExecutor) as Arc<dyn codex_tool_api::ToolExecutor>,
|
||||
);
|
||||
let spec = extension_tool_spec(bundle.spec()).expect("extension spec should convert");
|
||||
let handler = BundledToolHandler::new(bundle, spec);
|
||||
let handler = ExtensionToolHandler::new(definition);
|
||||
let (session, turn) = crate::session::tests::make_session_and_context().await;
|
||||
let invocation = ToolInvocation {
|
||||
session: session.into(),
|
||||
@@ -221,7 +201,7 @@ mod tests {
|
||||
arguments: json!({ "message": "hello" }).to_string(),
|
||||
},
|
||||
};
|
||||
let output = BundledToolOutput {
|
||||
let output = ExtensionToolOutput {
|
||||
value: json!({ "ok": true }),
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@ use crate::goals::CreateGoalRequest;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::goal_spec::CREATE_GOAL_TOOL_NAME;
|
||||
use crate::tools::handlers::goal_spec::create_create_goal_tool;
|
||||
use crate::tools::handlers::goal_spec::CREATE_GOAL_TOOL_NAME;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::CompletionBudgetReport;
|
||||
use super::CreateGoalArgs;
|
||||
@@ -17,6 +18,12 @@ use super::goal_response;
|
||||
|
||||
pub struct CreateGoalHandler;
|
||||
|
||||
impl CreateGoalHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_create_goal_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for CreateGoalHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -24,10 +31,6 @@ impl ToolHandler for CreateGoalHandler {
|
||||
ToolName::plain(CREATE_GOAL_TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_create_goal_tool())
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
|
||||
@@ -2,11 +2,12 @@ use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::goal_spec::GET_GOAL_TOOL_NAME;
|
||||
use crate::tools::handlers::goal_spec::create_get_goal_tool;
|
||||
use crate::tools::handlers::goal_spec::GET_GOAL_TOOL_NAME;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::CompletionBudgetReport;
|
||||
use super::format_goal_error;
|
||||
@@ -14,6 +15,12 @@ use super::goal_response;
|
||||
|
||||
pub struct GetGoalHandler;
|
||||
|
||||
impl GetGoalHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_get_goal_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for GetGoalHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -21,10 +28,6 @@ impl ToolHandler for GetGoalHandler {
|
||||
ToolName::plain(GET_GOAL_TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_get_goal_tool())
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session, payload, ..
|
||||
|
||||
@@ -4,13 +4,14 @@ use crate::goals::SetGoalRequest;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::goal_spec::UPDATE_GOAL_TOOL_NAME;
|
||||
use crate::tools::handlers::goal_spec::create_update_goal_tool;
|
||||
use crate::tools::handlers::goal_spec::UPDATE_GOAL_TOOL_NAME;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_protocol::protocol::ThreadGoalStatus;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::CompletionBudgetReport;
|
||||
use super::UpdateGoalArgs;
|
||||
@@ -19,6 +20,12 @@ use super::goal_response;
|
||||
|
||||
pub struct UpdateGoalHandler;
|
||||
|
||||
impl UpdateGoalHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_update_goal_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for UpdateGoalHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -26,10 +33,6 @@ impl ToolHandler for UpdateGoalHandler {
|
||||
ToolName::plain(UPDATE_GOAL_TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_update_goal_tool())
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
|
||||
@@ -6,10 +6,11 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::mcp_resource_spec::create_list_mcp_resource_templates_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_protocol::protocol::McpInvocation;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use rmcp::model::PaginatedRequestParams;
|
||||
|
||||
@@ -25,6 +26,12 @@ use super::serialize_function_output;
|
||||
|
||||
pub struct ListMcpResourceTemplatesHandler;
|
||||
|
||||
impl ListMcpResourceTemplatesHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_list_mcp_resource_templates_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ListMcpResourceTemplatesHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -32,10 +39,6 @@ impl ToolHandler for ListMcpResourceTemplatesHandler {
|
||||
ToolName::plain("list_mcp_resource_templates")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_list_mcp_resource_templates_tool())
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::mcp_resource_spec::create_list_mcp_resources_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_protocol::protocol::McpInvocation;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use rmcp::model::PaginatedRequestParams;
|
||||
|
||||
@@ -25,6 +26,12 @@ use super::serialize_function_output;
|
||||
|
||||
pub struct ListMcpResourcesHandler;
|
||||
|
||||
impl ListMcpResourcesHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_list_mcp_resources_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ListMcpResourcesHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -32,10 +39,6 @@ impl ToolHandler for ListMcpResourcesHandler {
|
||||
ToolName::plain("list_mcp_resources")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_list_mcp_resources_tool())
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::mcp_resource_spec::create_read_mcp_resource_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_protocol::protocol::McpInvocation;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use rmcp::model::ReadResourceRequestParams;
|
||||
|
||||
@@ -25,6 +26,12 @@ use super::serialize_function_output;
|
||||
|
||||
pub struct ReadMcpResourceHandler;
|
||||
|
||||
impl ReadMcpResourceHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_read_mcp_resource_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ReadMcpResourceHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -32,10 +39,6 @@ impl ToolHandler for ReadMcpResourceHandler {
|
||||
ToolName::plain("read_mcp_resource")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_read_mcp_resource_tool())
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -66,14 +66,11 @@ pub use request_user_input::RequestUserInputHandler;
|
||||
pub use shell::ContainerExecHandler;
|
||||
pub use shell::LocalShellHandler;
|
||||
pub use shell::ShellCommandHandler;
|
||||
pub(crate) use shell::ShellCommandHandlerOptions;
|
||||
pub use shell::ShellHandler;
|
||||
pub use test_sync::TestSyncHandler;
|
||||
pub use tool_search::ToolSearchHandler;
|
||||
pub use unavailable_tool::UnavailableToolHandler;
|
||||
pub(crate) use unavailable_tool::unavailable_tool_message;
|
||||
pub use unified_exec::ExecCommandHandler;
|
||||
pub(crate) use unified_exec::ExecCommandHandlerOptions;
|
||||
pub use unified_exec::WriteStdinHandler;
|
||||
pub use view_image::ViewImageHandler;
|
||||
|
||||
|
||||
@@ -14,8 +14,17 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
pub(crate) use crate::tools::handlers::multi_agents_common::*;
|
||||
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
|
||||
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
|
||||
use crate::tools::handlers::multi_agents_spec::create_close_agent_tool_v1;
|
||||
use crate::tools::handlers::multi_agents_spec::create_resume_agent_tool;
|
||||
use crate::tools::handlers::multi_agents_spec::create_send_input_tool_v1;
|
||||
use crate::tools::handlers::multi_agents_spec::create_spawn_agent_tool_v1;
|
||||
use crate::tools::handlers::multi_agents_spec::create_wait_agent_tool_v1;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use super::*;
|
||||
use crate::tools::handlers::multi_agents_spec::create_close_agent_tool_v1;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_close_agent_tool_v1())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
type Output = CloseAgentResult;
|
||||
|
||||
@@ -12,10 +16,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("close_agent")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_close_agent_tool_v1())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use super::*;
|
||||
use crate::agent::next_thread_spawn_depth;
|
||||
use crate::tools::handlers::multi_agents_spec::create_resume_agent_tool;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_tools::ToolSpec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_resume_agent_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
type Output = ResumeAgentResult;
|
||||
|
||||
@@ -14,10 +18,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("resume_agent")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_resume_agent_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use super::*;
|
||||
use crate::agent::control::render_input_preview;
|
||||
use crate::tools::handlers::multi_agents_spec::create_send_input_tool_v1;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_send_input_tool_v1())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
type Output = SendInputResult;
|
||||
|
||||
@@ -13,10 +17,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("send_input")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_send_input_tool_v1())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -6,19 +6,14 @@ use crate::agent::exceeds_thread_spawn_depth_limit;
|
||||
use crate::agent::next_thread_spawn_depth;
|
||||
use crate::agent::role::DEFAULT_ROLE_NAME;
|
||||
use crate::agent::role::apply_role_to_config;
|
||||
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
|
||||
use crate::tools::handlers::multi_agents_spec::create_spawn_agent_tool_v1;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Handler {
|
||||
options: SpawnAgentToolOptions,
|
||||
}
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn new(options: SpawnAgentToolOptions) -> Self {
|
||||
Self { options }
|
||||
pub(crate) fn definition(options: SpawnAgentToolOptions) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_spawn_agent_tool_v1(options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +24,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("spawn_agent")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_spawn_agent_tool_v1(self.options.clone()))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use super::*;
|
||||
use crate::agent::status::is_final;
|
||||
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
|
||||
use crate::tools::handlers::multi_agents_spec::create_wait_agent_tool_v1;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_tools::ToolSpec;
|
||||
use futures::FutureExt;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
@@ -17,13 +14,11 @@ use tokio::time::Instant;
|
||||
use tokio::time::timeout_at;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Handler {
|
||||
options: WaitAgentTimeoutOptions,
|
||||
}
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn new(options: WaitAgentTimeoutOptions) -> Self {
|
||||
Self { options }
|
||||
pub(crate) fn definition(options: WaitAgentTimeoutOptions) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_wait_agent_tool_v1(options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +29,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("wait_agent")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_wait_agent_tool_v1(self.options))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ async fn handler_rejects_non_function_payloads() {
|
||||
input: "hello".to_string(),
|
||||
},
|
||||
);
|
||||
let Err(err) = SpawnAgentHandler::default().handle(invocation).await else {
|
||||
let Err(err) = SpawnAgentHandler.handle(invocation).await else {
|
||||
panic!("payload should be rejected");
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -201,7 +201,7 @@ async fn spawn_agent_rejects_empty_message() {
|
||||
"spawn_agent",
|
||||
function_payload(json!({"message": " "})),
|
||||
);
|
||||
let Err(err) = SpawnAgentHandler::default().handle(invocation).await else {
|
||||
let Err(err) = SpawnAgentHandler.handle(invocation).await else {
|
||||
panic!("empty message should be rejected");
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -222,7 +222,7 @@ async fn spawn_agent_rejects_when_message_and_items_are_both_set() {
|
||||
"items": [{"type": "mention", "name": "drive", "path": "app://drive"}]
|
||||
})),
|
||||
);
|
||||
let Err(err) = SpawnAgentHandler::default().handle(invocation).await else {
|
||||
let Err(err) = SpawnAgentHandler.handle(invocation).await else {
|
||||
panic!("message+items should be rejected");
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -269,7 +269,7 @@ async fn spawn_agent_uses_explorer_role_and_preserves_approval_policy() {
|
||||
"agent_type": "explorer"
|
||||
})),
|
||||
);
|
||||
let output = SpawnAgentHandler::default()
|
||||
let output = SpawnAgentHandler
|
||||
.handle(invocation)
|
||||
.await
|
||||
.expect("spawn_agent should succeed");
|
||||
@@ -304,7 +304,7 @@ async fn spawn_agent_fork_context_rejects_agent_type_override() {
|
||||
.expect("root thread should start");
|
||||
session.services.agent_control = manager.agent_control();
|
||||
session.conversation_id = root.thread_id;
|
||||
let err = SpawnAgentHandler::default()
|
||||
let err = SpawnAgentHandler
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -337,7 +337,7 @@ async fn spawn_agent_fork_context_rejects_child_model_overrides() {
|
||||
session.services.agent_control = manager.agent_control();
|
||||
session.conversation_id = root.thread_id;
|
||||
|
||||
let err = SpawnAgentHandler::default()
|
||||
let err = SpawnAgentHandler
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -381,7 +381,7 @@ async fn multi_agent_v2_spawn_fork_turns_all_rejects_agent_type_override() {
|
||||
..turn
|
||||
};
|
||||
|
||||
let err = SpawnAgentHandlerV2::default()
|
||||
let err = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -421,7 +421,7 @@ async fn multi_agent_v2_spawn_defaults_to_full_fork_and_rejects_child_model_over
|
||||
.expect("test config should allow feature update");
|
||||
turn.config = Arc::new(config);
|
||||
|
||||
let err = SpawnAgentHandlerV2::default()
|
||||
let err = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -465,7 +465,7 @@ async fn multi_agent_v2_spawn_partial_fork_turns_allows_agent_type_override() {
|
||||
..turn
|
||||
};
|
||||
|
||||
let output = SpawnAgentHandlerV2::default()
|
||||
let output = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -507,7 +507,7 @@ async fn spawn_agent_returns_agent_id_without_task_name() {
|
||||
let manager = thread_manager();
|
||||
session.services.agent_control = manager.agent_control();
|
||||
|
||||
let output = SpawnAgentHandler::default()
|
||||
let output = SpawnAgentHandler
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -553,7 +553,7 @@ async fn multi_agent_v2_spawn_requires_task_name() {
|
||||
"message": "inspect this repo"
|
||||
})),
|
||||
);
|
||||
let Err(err) = SpawnAgentHandlerV2::default().handle(invocation).await else {
|
||||
let Err(err) = SpawnAgentHandlerV2.handle(invocation).await else {
|
||||
panic!("missing task_name should be rejected");
|
||||
};
|
||||
let FunctionCallError::RespondToModel(message) = err else {
|
||||
@@ -589,7 +589,7 @@ async fn multi_agent_v2_spawn_rejects_legacy_items_field() {
|
||||
"task_name": "worker"
|
||||
})),
|
||||
);
|
||||
let Err(err) = SpawnAgentHandlerV2::default().handle(invocation).await else {
|
||||
let Err(err) = SpawnAgentHandlerV2.handle(invocation).await else {
|
||||
panic!("legacy items field should be rejected");
|
||||
};
|
||||
let FunctionCallError::RespondToModel(message) = err else {
|
||||
@@ -607,7 +607,7 @@ async fn spawn_agent_errors_when_manager_dropped() {
|
||||
"spawn_agent",
|
||||
function_payload(json!({"message": "hello"})),
|
||||
);
|
||||
let Err(err) = SpawnAgentHandler::default().handle(invocation).await else {
|
||||
let Err(err) = SpawnAgentHandler.handle(invocation).await else {
|
||||
panic!("spawn should fail without a manager");
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -641,7 +641,7 @@ async fn multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_pat
|
||||
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
let spawn_output = SpawnAgentHandlerV2::default()
|
||||
let spawn_output = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -736,7 +736,7 @@ async fn multi_agent_v2_spawn_rejects_legacy_fork_context() {
|
||||
.expect("test config should allow feature update");
|
||||
turn.config = Arc::new(config);
|
||||
|
||||
let err = SpawnAgentHandlerV2::default()
|
||||
let err = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -775,7 +775,7 @@ async fn multi_agent_v2_spawn_rejects_invalid_fork_turns_string() {
|
||||
.expect("test config should allow feature update");
|
||||
turn.config = Arc::new(config);
|
||||
|
||||
let err = SpawnAgentHandlerV2::default()
|
||||
let err = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -814,7 +814,7 @@ async fn multi_agent_v2_spawn_rejects_zero_fork_turns() {
|
||||
.expect("test config should allow feature update");
|
||||
turn.config = Arc::new(config);
|
||||
|
||||
let err = SpawnAgentHandlerV2::default()
|
||||
let err = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -1009,7 +1009,7 @@ async fn multi_agent_v2_list_agents_returns_completed_status_and_last_task_messa
|
||||
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
let spawn_output = SpawnAgentHandlerV2::default()
|
||||
let spawn_output = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -1190,7 +1190,7 @@ async fn multi_agent_v2_list_agents_omits_closed_agents() {
|
||||
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
let spawn_output = SpawnAgentHandlerV2::default()
|
||||
let spawn_output = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -1254,7 +1254,7 @@ async fn multi_agent_v2_send_message_rejects_legacy_items_field() {
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -1310,7 +1310,7 @@ async fn multi_agent_v2_send_message_rejects_interrupt_parameter() {
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -1383,7 +1383,7 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn()
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -1518,7 +1518,7 @@ async fn multi_agent_v2_followup_task_rejects_legacy_items_field() {
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -1571,7 +1571,7 @@ async fn multi_agent_v2_interrupted_turn_does_not_notify_parent() {
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -1649,7 +1649,7 @@ async fn multi_agent_v2_spawn_omits_agent_id_when_named() {
|
||||
.expect("test config should allow feature update");
|
||||
turn.config = Arc::new(config);
|
||||
|
||||
let output = SpawnAgentHandlerV2::default()
|
||||
let output = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
@@ -1697,7 +1697,7 @@ async fn multi_agent_v2_spawn_surfaces_task_name_validation_errors() {
|
||||
"task_name": "BadName"
|
||||
})),
|
||||
);
|
||||
let Err(err) = SpawnAgentHandlerV2::default().handle(invocation).await else {
|
||||
let Err(err) = SpawnAgentHandlerV2.handle(invocation).await else {
|
||||
panic!("invalid agent name should be rejected");
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -1755,7 +1755,7 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
"agent_type": "explorer"
|
||||
})),
|
||||
);
|
||||
let output = SpawnAgentHandler::default()
|
||||
let output = SpawnAgentHandler
|
||||
.handle(invocation)
|
||||
.await
|
||||
.expect("spawn_agent should succeed");
|
||||
@@ -1816,7 +1816,7 @@ async fn spawn_agent_rejects_when_depth_limit_exceeded() {
|
||||
"spawn_agent",
|
||||
function_payload(json!({"message": "hello"})),
|
||||
);
|
||||
let Err(err) = SpawnAgentHandler::default().handle(invocation).await else {
|
||||
let Err(err) = SpawnAgentHandler.handle(invocation).await else {
|
||||
panic!("spawn should fail when depth limit exceeded");
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -1856,7 +1856,7 @@ async fn spawn_agent_allows_depth_up_to_configured_max_depth() {
|
||||
"spawn_agent",
|
||||
function_payload(json!({"message": "hello"})),
|
||||
);
|
||||
let output = SpawnAgentHandler::default()
|
||||
let output = SpawnAgentHandler
|
||||
.handle(invocation)
|
||||
.await
|
||||
.expect("spawn should succeed within configured depth");
|
||||
@@ -1915,7 +1915,7 @@ async fn multi_agent_v2_spawn_agent_ignores_configured_max_depth() {
|
||||
"fork_turns": "none"
|
||||
})),
|
||||
);
|
||||
let output = SpawnAgentHandlerV2::default()
|
||||
let output = SpawnAgentHandlerV2
|
||||
.handle(invocation)
|
||||
.await
|
||||
.expect("multi-agent v2 spawn should ignore max depth");
|
||||
@@ -2307,7 +2307,7 @@ async fn wait_agent_rejects_non_positive_timeout() {
|
||||
"timeout_ms": 0
|
||||
})),
|
||||
);
|
||||
let Err(err) = WaitAgentHandler::default().handle(invocation).await else {
|
||||
let Err(err) = WaitAgentHandler.handle(invocation).await else {
|
||||
panic!("non-positive timeout should be rejected");
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -2325,7 +2325,7 @@ async fn wait_agent_rejects_invalid_target() {
|
||||
"wait_agent",
|
||||
function_payload(json!({"targets": ["invalid"]})),
|
||||
);
|
||||
let Err(err) = WaitAgentHandler::default().handle(invocation).await else {
|
||||
let Err(err) = WaitAgentHandler.handle(invocation).await else {
|
||||
panic!("invalid id should be rejected");
|
||||
};
|
||||
let FunctionCallError::RespondToModel(msg) = err else {
|
||||
@@ -2343,7 +2343,7 @@ async fn wait_agent_rejects_empty_targets() {
|
||||
"wait_agent",
|
||||
function_payload(json!({"targets": []})),
|
||||
);
|
||||
let Err(err) = WaitAgentHandler::default().handle(invocation).await else {
|
||||
let Err(err) = WaitAgentHandler.handle(invocation).await else {
|
||||
panic!("empty ids should be rejected");
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -2371,7 +2371,7 @@ async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument() {
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -2401,7 +2401,7 @@ async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument() {
|
||||
let session = session.clone();
|
||||
let turn = turn.clone();
|
||||
async move {
|
||||
WaitAgentHandlerV2::default()
|
||||
WaitAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session,
|
||||
turn,
|
||||
@@ -2453,7 +2453,7 @@ async fn multi_agent_v2_wait_agent_uses_configured_min_timeout() {
|
||||
|
||||
let early = timeout(
|
||||
Duration::from_millis(/*millis*/ 20),
|
||||
WaitAgentHandlerV2::default().handle(invocation(
|
||||
WaitAgentHandlerV2.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
"wait_agent",
|
||||
@@ -2468,7 +2468,7 @@ async fn multi_agent_v2_wait_agent_uses_configured_min_timeout() {
|
||||
|
||||
let output = timeout(
|
||||
Duration::from_secs(/*secs*/ 1),
|
||||
WaitAgentHandlerV2::default().handle(invocation(
|
||||
WaitAgentHandlerV2.handle(invocation(
|
||||
session,
|
||||
turn,
|
||||
"wait_agent",
|
||||
@@ -2507,7 +2507,7 @@ async fn wait_agent_returns_not_found_for_missing_agents() {
|
||||
"timeout_ms": 1000
|
||||
})),
|
||||
);
|
||||
let output = WaitAgentHandler::default()
|
||||
let output = WaitAgentHandler
|
||||
.handle(invocation)
|
||||
.await
|
||||
.expect("wait_agent should succeed");
|
||||
@@ -2547,7 +2547,7 @@ async fn wait_agent_times_out_when_status_is_not_final() {
|
||||
"timeout_ms": MIN_WAIT_TIMEOUT_MS
|
||||
})),
|
||||
);
|
||||
let output = WaitAgentHandler::default()
|
||||
let output = WaitAgentHandler
|
||||
.handle(invocation)
|
||||
.await
|
||||
.expect("wait_agent should succeed");
|
||||
@@ -2593,7 +2593,7 @@ async fn wait_agent_clamps_short_timeouts_to_minimum() {
|
||||
|
||||
let early = timeout(
|
||||
Duration::from_millis(50),
|
||||
WaitAgentHandler::default().handle(invocation),
|
||||
WaitAgentHandler.handle(invocation),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
@@ -2643,7 +2643,7 @@ async fn wait_agent_returns_final_status_without_timeout() {
|
||||
"timeout_ms": 1000
|
||||
})),
|
||||
);
|
||||
let output = WaitAgentHandler::default()
|
||||
let output = WaitAgentHandler
|
||||
.handle(invocation)
|
||||
.await
|
||||
.expect("wait_agent should succeed");
|
||||
@@ -2679,7 +2679,7 @@ async fn multi_agent_v2_wait_agent_returns_summary_for_mailbox_activity() {
|
||||
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
let spawn_output = SpawnAgentHandlerV2::default()
|
||||
let spawn_output = SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -2714,7 +2714,7 @@ async fn multi_agent_v2_wait_agent_returns_summary_for_mailbox_activity() {
|
||||
let session = session.clone();
|
||||
let turn = turn.clone();
|
||||
async move {
|
||||
WaitAgentHandlerV2::default()
|
||||
WaitAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session,
|
||||
turn,
|
||||
@@ -2770,7 +2770,7 @@ async fn multi_agent_v2_wait_agent_returns_for_already_queued_mail() {
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -2806,7 +2806,7 @@ async fn multi_agent_v2_wait_agent_returns_for_already_queued_mail() {
|
||||
|
||||
let output = timeout(
|
||||
Duration::from_millis(500),
|
||||
WaitAgentHandlerV2::default().handle(invocation(
|
||||
WaitAgentHandlerV2.handle(invocation(
|
||||
session,
|
||||
turn,
|
||||
"wait_agent",
|
||||
@@ -2849,7 +2849,7 @@ async fn multi_agent_v2_wait_agent_wakes_on_any_mailbox_notification() {
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
for task_name in ["worker_a", "worker_b"] {
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -2880,7 +2880,7 @@ async fn multi_agent_v2_wait_agent_wakes_on_any_mailbox_notification() {
|
||||
let session = session.clone();
|
||||
let turn = turn.clone();
|
||||
async move {
|
||||
WaitAgentHandlerV2::default()
|
||||
WaitAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session,
|
||||
turn,
|
||||
@@ -2936,7 +2936,7 @@ async fn multi_agent_v2_wait_agent_does_not_return_completed_content() {
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -2965,7 +2965,7 @@ async fn multi_agent_v2_wait_agent_does_not_return_completed_content() {
|
||||
let session = session.clone();
|
||||
let turn = turn.clone();
|
||||
async move {
|
||||
WaitAgentHandlerV2::default()
|
||||
WaitAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session,
|
||||
turn,
|
||||
@@ -3022,7 +3022,7 @@ async fn multi_agent_v2_close_agent_accepts_task_name_target() {
|
||||
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
SpawnAgentHandlerV2::default()
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
@@ -3179,7 +3179,7 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr
|
||||
let parent_thread_id = parent.thread_id;
|
||||
let parent_session = parent.thread.codex.session.clone();
|
||||
|
||||
let child_spawn_output = SpawnAgentHandler::default()
|
||||
let child_spawn_output = SpawnAgentHandler
|
||||
.handle(invocation(
|
||||
parent_session.clone(),
|
||||
parent_session.new_default_turn().await,
|
||||
@@ -3204,7 +3204,7 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr
|
||||
.await
|
||||
.expect("child thread should exist");
|
||||
let child_session = child_thread.codex.session.clone();
|
||||
let grandchild_spawn_output = SpawnAgentHandler::default()
|
||||
let grandchild_spawn_output = SpawnAgentHandler
|
||||
.handle(invocation(
|
||||
child_session.clone(),
|
||||
child_session.new_default_turn().await,
|
||||
|
||||
@@ -7,8 +7,18 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::multi_agents_common::*;
|
||||
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
|
||||
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
|
||||
use crate::tools::handlers::multi_agents_spec::create_close_agent_tool_v2;
|
||||
use crate::tools::handlers::multi_agents_spec::create_followup_task_tool;
|
||||
use crate::tools::handlers::multi_agents_spec::create_list_agents_tool;
|
||||
use crate::tools::handlers::multi_agents_spec::create_send_message_tool;
|
||||
use crate::tools::handlers::multi_agents_spec::create_spawn_agent_tool_v2;
|
||||
use crate::tools::handlers::multi_agents_spec::create_wait_agent_tool_v2;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use super::*;
|
||||
use crate::tools::handlers::multi_agents_spec::create_close_agent_tool_v2;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_close_agent_tool_v2())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
type Output = CloseAgentResult;
|
||||
|
||||
@@ -12,10 +16,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("close_agent")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_close_agent_tool_v2())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@ use super::message_tool::MessageDeliveryMode;
|
||||
use super::message_tool::handle_message_string_tool;
|
||||
use super::*;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::handlers::multi_agents_spec::create_followup_task_tool;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_followup_task_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -15,10 +19,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("followup_task")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_followup_task_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use super::*;
|
||||
use crate::agent::control::ListedAgent;
|
||||
use crate::tools::handlers::multi_agents_spec::create_list_agents_tool;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_list_agents_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
type Output = ListAgentsResult;
|
||||
|
||||
@@ -12,10 +16,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("list_agents")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_list_agents_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@ use super::message_tool::SendMessageArgs;
|
||||
use super::message_tool::handle_message_string_tool;
|
||||
use super::*;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::handlers::multi_agents_spec::create_send_message_tool;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_send_message_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -15,10 +19,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("send_message")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_send_message_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -5,22 +5,17 @@ use crate::agent::control::render_input_preview;
|
||||
use crate::agent::next_thread_spawn_depth;
|
||||
use crate::agent::role::DEFAULT_ROLE_NAME;
|
||||
use crate::agent::role::apply_role_to_config;
|
||||
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
|
||||
use crate::tools::handlers::multi_agents_spec::create_spawn_agent_tool_v2;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Handler {
|
||||
options: SpawnAgentToolOptions,
|
||||
}
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn new(options: SpawnAgentToolOptions) -> Self {
|
||||
Self { options }
|
||||
pub(crate) fn definition(options: SpawnAgentToolOptions) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_spawn_agent_tool_v2(options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +26,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("spawn_agent")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_spawn_agent_tool_v2(self.options.clone()))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
use super::*;
|
||||
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
|
||||
use crate::tools::handlers::multi_agents_spec::create_wait_agent_tool_v2;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_tools::ToolSpec;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
use tokio::time::timeout_at;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Handler {
|
||||
options: WaitAgentTimeoutOptions,
|
||||
}
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn new(options: WaitAgentTimeoutOptions) -> Self {
|
||||
Self { options }
|
||||
pub(crate) fn definition(options: WaitAgentTimeoutOptions) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_wait_agent_tool_v2(options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +21,6 @@ impl ToolHandler for Handler {
|
||||
ToolName::plain("wait_agent")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_wait_agent_tool_v2(self.options))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -4,17 +4,24 @@ use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::plan_spec::create_update_plan_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
pub struct PlanHandler;
|
||||
|
||||
impl PlanHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_update_plan_tool())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlanToolOutput;
|
||||
|
||||
const PLAN_UPDATED_MESSAGE: &str = "Plan updated";
|
||||
@@ -50,10 +57,6 @@ impl ToolHandler for PlanHandler {
|
||||
ToolName::plain("update_plan")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_update_plan_tool())
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
|
||||
@@ -9,11 +9,21 @@ use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::handlers::shell_spec::create_request_permissions_tool;
|
||||
use crate::tools::handlers::shell_spec::request_permissions_tool_description;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub struct RequestPermissionsHandler;
|
||||
|
||||
impl RequestPermissionsHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(
|
||||
Self,
|
||||
create_request_permissions_tool(request_permissions_tool_description()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for RequestPermissionsHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -21,12 +31,6 @@ impl ToolHandler for RequestPermissionsHandler {
|
||||
ToolName::plain("request_permissions")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_request_permissions_tool(
|
||||
request_permissions_tool_description(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
|
||||
@@ -11,14 +11,12 @@ use codex_tools::DiscoverableToolType;
|
||||
use codex_tools::REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE;
|
||||
use codex_tools::REQUEST_PLUGIN_INSTALL_PERSIST_KEY;
|
||||
use codex_tools::REQUEST_PLUGIN_INSTALL_TOOL_NAME;
|
||||
use codex_tools::RequestPluginInstallArgs;
|
||||
use codex_tools::RequestPluginInstallEntry;
|
||||
use codex_tools::RequestPluginInstallArgs;
|
||||
use codex_tools::RequestPluginInstallResult;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::all_requested_connectors_picked_up;
|
||||
use codex_tools::build_request_plugin_install_elicitation_request;
|
||||
use codex_tools::collect_request_plugin_install_entries;
|
||||
use codex_tools::filter_request_plugin_install_discoverable_tools_for_client;
|
||||
use codex_tools::verified_connector_install_completed;
|
||||
use rmcp::model::RequestId;
|
||||
@@ -35,17 +33,20 @@ use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::handlers::request_plugin_install_spec::create_request_plugin_install_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RequestPluginInstallHandler {
|
||||
discoverable_tools: Vec<RequestPluginInstallEntry>,
|
||||
}
|
||||
pub struct RequestPluginInstallHandler;
|
||||
|
||||
impl RequestPluginInstallHandler {
|
||||
pub(crate) fn new(discoverable_tools: &[DiscoverableTool]) -> Self {
|
||||
Self {
|
||||
discoverable_tools: collect_request_plugin_install_entries(discoverable_tools),
|
||||
}
|
||||
pub(crate) fn definition(
|
||||
discoverable_tools: &[RequestPluginInstallEntry],
|
||||
) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(
|
||||
Self,
|
||||
create_request_plugin_install_tool(discoverable_tools),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,10 +57,6 @@ impl ToolHandler for RequestPluginInstallHandler {
|
||||
ToolName::plain(REQUEST_PLUGIN_INSTALL_TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_request_plugin_install_tool(&self.discoverable_tools))
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -9,15 +9,26 @@ use crate::tools::handlers::request_user_input_spec::normalize_request_user_inpu
|
||||
use crate::tools::handlers::request_user_input_spec::request_user_input_tool_description;
|
||||
use crate::tools::handlers::request_user_input_spec::request_user_input_unavailable_message;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::request_user_input::RequestUserInputArgs;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub struct RequestUserInputHandler {
|
||||
pub available_modes: Vec<ModeKind>,
|
||||
}
|
||||
|
||||
impl RequestUserInputHandler {
|
||||
pub(crate) fn definition(available_modes: Vec<ModeKind>) -> RuntimeToolDefinition {
|
||||
let description = request_user_input_tool_description(&available_modes);
|
||||
runtime_tool_definition(
|
||||
Self { available_modes },
|
||||
create_request_user_input_tool(description),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for RequestUserInputHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -25,12 +36,6 @@ impl ToolHandler for RequestUserInputHandler {
|
||||
ToolName::plain(REQUEST_USER_INPUT_TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_request_user_input_tool(
|
||||
request_user_input_tool_description(&self.available_modes),
|
||||
))
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
|
||||
@@ -41,7 +41,6 @@ mod shell_handler;
|
||||
pub use container_exec::ContainerExecHandler;
|
||||
pub use local_shell::LocalShellHandler;
|
||||
pub use shell_command::ShellCommandHandler;
|
||||
pub(crate) use shell_command::ShellCommandHandlerOptions;
|
||||
pub use shell_handler::ShellHandler;
|
||||
|
||||
fn shell_function_payload_command(payload: &ToolPayload) -> Option<String> {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use codex_shell_command::is_safe_command::is_known_safe_command;
|
||||
use codex_tools::ToolName;
|
||||
|
||||
use super::RunExecLikeArgs;
|
||||
use super::local_shell_payload_command;
|
||||
use super::run_exec_like;
|
||||
use super::shell_handler::ShellHandler;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
@@ -11,23 +15,25 @@ use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use crate::tools::runtimes::shell::ShellRuntimeBackend;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::super::shell_spec::create_local_shell_tool;
|
||||
use super::RunExecLikeArgs;
|
||||
use super::local_shell_payload_command;
|
||||
use super::run_exec_like;
|
||||
use super::shell_handler::ShellHandler;
|
||||
use crate::tools::handlers::shell_spec::create_local_shell_tool;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LocalShellHandler {
|
||||
include_spec: bool,
|
||||
supports_parallel_tool_calls: bool,
|
||||
}
|
||||
|
||||
impl LocalShellHandler {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { include_spec: true }
|
||||
Self {
|
||||
supports_parallel_tool_calls: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self::new(), create_local_shell_tool())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +44,8 @@ impl ToolHandler for LocalShellHandler {
|
||||
ToolName::plain("local_shell")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
self.include_spec.then(create_local_shell_tool)
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.include_spec
|
||||
self.supports_parallel_tool_calls
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
|
||||
@@ -4,6 +4,9 @@ use codex_shell_command::is_safe_command::is_known_safe_command;
|
||||
use codex_tools::ShellCommandBackendConfig;
|
||||
use codex_tools::ToolName;
|
||||
|
||||
use super::RunExecLikeArgs;
|
||||
use super::run_exec_like;
|
||||
use super::shell_command_payload_command;
|
||||
use crate::exec::ExecCapturePolicy;
|
||||
use crate::exec::ExecParams;
|
||||
use crate::exec_env::create_env;
|
||||
@@ -23,14 +26,11 @@ use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use crate::tools::runtimes::shell::ShellRuntimeBackend;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::super::shell_spec::CommandToolOptions;
|
||||
use super::super::shell_spec::create_shell_command_tool;
|
||||
use super::RunExecLikeArgs;
|
||||
use super::run_exec_like;
|
||||
use super::shell_command_payload_command;
|
||||
use crate::tools::handlers::shell_spec::CommandToolOptions;
|
||||
use crate::tools::handlers::shell_spec::create_shell_command_tool;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ShellCommandBackend {
|
||||
@@ -40,24 +40,24 @@ enum ShellCommandBackend {
|
||||
|
||||
pub struct ShellCommandHandler {
|
||||
backend: ShellCommandBackend,
|
||||
options: Option<ShellCommandHandlerOptions>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct ShellCommandHandlerOptions {
|
||||
pub(crate) backend_config: ShellCommandBackendConfig,
|
||||
pub(crate) allow_login_shell: bool,
|
||||
pub(crate) exec_permission_approvals_enabled: bool,
|
||||
supports_parallel_tool_calls: bool,
|
||||
}
|
||||
|
||||
impl ShellCommandHandler {
|
||||
pub(crate) fn new(options: ShellCommandHandlerOptions) -> Self {
|
||||
pub(crate) fn new(backend_config: ShellCommandBackendConfig) -> Self {
|
||||
Self {
|
||||
options: Some(options),
|
||||
..Self::from(options.backend_config)
|
||||
supports_parallel_tool_calls: true,
|
||||
..Self::from(backend_config)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn definition(
|
||||
backend_config: ShellCommandBackendConfig,
|
||||
options: CommandToolOptions,
|
||||
) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self::new(backend_config), create_shell_command_tool(options))
|
||||
}
|
||||
|
||||
fn shell_runtime_backend(&self) -> ShellRuntimeBackend {
|
||||
match self.backend {
|
||||
ShellCommandBackend::Classic => ShellRuntimeBackend::ShellCommandClassic,
|
||||
@@ -120,7 +120,7 @@ impl From<ShellCommandBackendConfig> for ShellCommandHandler {
|
||||
};
|
||||
Self {
|
||||
backend,
|
||||
options: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,17 +132,8 @@ impl ToolHandler for ShellCommandHandler {
|
||||
ToolName::plain("shell_command")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
self.options.map(|options| {
|
||||
create_shell_command_tool(CommandToolOptions {
|
||||
allow_login_shell: options.allow_login_shell,
|
||||
exec_permission_approvals_enabled: options.exec_permission_approvals_enabled,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.options.is_some()
|
||||
self.supports_parallel_tool_calls
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
|
||||
@@ -3,6 +3,11 @@ use codex_protocol::models::ShellToolCallParams;
|
||||
use codex_shell_command::is_safe_command::is_known_safe_command;
|
||||
use codex_tools::ToolName;
|
||||
|
||||
use super::RunExecLikeArgs;
|
||||
use super::rewrite_shell_function_updated_hook_input;
|
||||
use super::run_exec_like;
|
||||
use super::shell_function_post_tool_use_payload;
|
||||
use super::shell_function_pre_tool_use_payload;
|
||||
use crate::exec::ExecCapturePolicy;
|
||||
use crate::exec::ExecParams;
|
||||
use crate::exec_env::create_env;
|
||||
@@ -16,29 +21,28 @@ use crate::tools::handlers::resolve_workdir_base_path;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use crate::tools::runtimes::shell::ShellRuntimeBackend;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use super::super::shell_spec::ShellToolOptions;
|
||||
use super::super::shell_spec::create_shell_tool;
|
||||
use super::RunExecLikeArgs;
|
||||
use super::rewrite_shell_function_updated_hook_input;
|
||||
use super::run_exec_like;
|
||||
use super::shell_function_post_tool_use_payload;
|
||||
use super::shell_function_pre_tool_use_payload;
|
||||
use crate::tools::handlers::shell_spec::ShellToolOptions;
|
||||
use crate::tools::handlers::shell_spec::create_shell_tool;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ShellHandler {
|
||||
options: Option<ShellToolOptions>,
|
||||
supports_parallel_tool_calls: bool,
|
||||
}
|
||||
|
||||
impl ShellHandler {
|
||||
pub(crate) fn new(options: ShellToolOptions) -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
options: Some(options),
|
||||
supports_parallel_tool_calls: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn definition(options: ShellToolOptions) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self::new(), create_shell_tool(options))
|
||||
}
|
||||
|
||||
pub(super) fn to_exec_params(
|
||||
params: &ShellToolCallParams,
|
||||
turn_context: &TurnContext,
|
||||
@@ -70,12 +74,8 @@ impl ToolHandler for ShellHandler {
|
||||
ToolName::plain("shell")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
self.options.map(create_shell_tool)
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.options.is_some()
|
||||
self.supports_parallel_tool_calls
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
|
||||
@@ -15,11 +15,18 @@ use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::handlers::test_sync_spec::create_test_sync_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub struct TestSyncHandler;
|
||||
|
||||
impl TestSyncHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_test_sync_tool())
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_TIMEOUT_MS: u64 = 1_000;
|
||||
|
||||
static BARRIERS: OnceLock<tokio::sync::Mutex<HashMap<String, BarrierState>>> = OnceLock::new();
|
||||
@@ -62,10 +69,6 @@ impl ToolHandler for TestSyncHandler {
|
||||
ToolName::plain("test_sync_tool")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_test_sync_tool())
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::context::ToolSearchOutput;
|
||||
use crate::tools::handlers::tool_search_spec::create_tool_search_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use crate::tools::tool_search_entry::ToolSearchEntry;
|
||||
use crate::tools::handlers::tool_search_spec::create_tool_search_tool;
|
||||
use bm25::Document;
|
||||
use bm25::Language;
|
||||
use bm25::SearchEngine;
|
||||
@@ -13,8 +15,6 @@ use codex_tools::LoadableToolSpec;
|
||||
use codex_tools::TOOL_SEARCH_DEFAULT_LIMIT;
|
||||
use codex_tools::TOOL_SEARCH_TOOL_NAME;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSearchSourceInfo;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::coalesce_loadable_tool_specs;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -23,15 +23,11 @@ const COMPUTER_USE_TOOL_SEARCH_LIMIT: usize = 20;
|
||||
|
||||
pub struct ToolSearchHandler {
|
||||
entries: Vec<ToolSearchEntry>,
|
||||
search_source_infos: Vec<ToolSearchSourceInfo>,
|
||||
search_engine: SearchEngine<usize>,
|
||||
}
|
||||
|
||||
impl ToolSearchHandler {
|
||||
pub(crate) fn new(
|
||||
entries: Vec<ToolSearchEntry>,
|
||||
search_source_infos: Vec<ToolSearchSourceInfo>,
|
||||
) -> Self {
|
||||
pub(crate) fn new(entries: Vec<ToolSearchEntry>) -> Self {
|
||||
let documents: Vec<Document<usize>> = entries
|
||||
.iter()
|
||||
.map(|entry| entry.search_text.clone())
|
||||
@@ -43,10 +39,19 @@ impl ToolSearchHandler {
|
||||
|
||||
Self {
|
||||
entries,
|
||||
search_source_infos,
|
||||
search_engine,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn definition(
|
||||
entries: Vec<ToolSearchEntry>,
|
||||
source_infos: Vec<codex_tools::ToolSearchSourceInfo>,
|
||||
) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(
|
||||
Self::new(entries),
|
||||
create_tool_search_tool(&source_infos, TOOL_SEARCH_DEFAULT_LIMIT),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ToolSearchHandler {
|
||||
@@ -56,13 +61,6 @@ impl ToolHandler for ToolSearchHandler {
|
||||
ToolName::plain(TOOL_SEARCH_TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_tool_search_tool(
|
||||
&self.search_source_infos,
|
||||
TOOL_SEARCH_DEFAULT_LIMIT,
|
||||
))
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -431,9 +429,6 @@ mod tests {
|
||||
mcp_tools: Option<&[ToolInfo]>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> ToolSearchHandler {
|
||||
ToolSearchHandler::new(
|
||||
build_tool_search_entries(mcp_tools, dynamic_tools),
|
||||
Vec::new(),
|
||||
)
|
||||
ToolSearchHandler::new(build_tool_search_entries(mcp_tools, dynamic_tools))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,28 +2,43 @@ use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::flat_tool_name;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::AdditionalProperties;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub struct UnavailableToolHandler {
|
||||
tool_name: ToolName,
|
||||
spec: Option<ToolSpec>,
|
||||
}
|
||||
|
||||
impl UnavailableToolHandler {
|
||||
pub fn new(tool_name: ToolName, spec: ToolSpec) -> Self {
|
||||
Self {
|
||||
tool_name,
|
||||
spec: Some(spec),
|
||||
}
|
||||
pub fn new(tool_name: ToolName) -> Self {
|
||||
Self { tool_name }
|
||||
}
|
||||
|
||||
pub fn without_spec(tool_name: ToolName) -> Self {
|
||||
Self {
|
||||
tool_name,
|
||||
spec: None,
|
||||
}
|
||||
pub(crate) fn definition(tool_name: ToolName) -> RuntimeToolDefinition {
|
||||
let name = flat_tool_name(&tool_name).into_owned();
|
||||
let spec = ToolSpec::Function(ResponsesApiTool {
|
||||
name: name.clone(),
|
||||
description: unavailable_tool_message(
|
||||
&name,
|
||||
"Calling this placeholder returns an error explaining that the tool is unavailable.",
|
||||
),
|
||||
strict: false,
|
||||
parameters: JsonSchema::object(
|
||||
Default::default(),
|
||||
/*required*/ None,
|
||||
Some(AdditionalProperties::Boolean(false)),
|
||||
),
|
||||
output_schema: None,
|
||||
defer_loading: None,
|
||||
});
|
||||
runtime_tool_definition(Self::new(tool_name), spec)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +58,6 @@ impl ToolHandler for UnavailableToolHandler {
|
||||
self.tool_name.clone()
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
self.spec.clone()
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation { payload, .. } = invocation;
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ mod exec_command;
|
||||
mod write_stdin;
|
||||
|
||||
pub use exec_command::ExecCommandHandler;
|
||||
pub(crate) use exec_command::ExecCommandHandlerOptions;
|
||||
pub use write_stdin::WriteStdinHandler;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
@@ -18,6 +18,8 @@ use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use crate::unified_exec::ExecCommandRequest;
|
||||
use crate::unified_exec::UnifiedExecContext;
|
||||
use crate::unified_exec::UnifiedExecError;
|
||||
@@ -28,43 +30,28 @@ use codex_otel::SessionTelemetry;
|
||||
use codex_otel::TOOL_CALL_UNIFIED_EXEC_METRIC;
|
||||
use codex_shell_command::is_safe_command::is_known_safe_command;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_utils_output_truncation::approx_token_count;
|
||||
|
||||
use super::super::shell_spec::CommandToolOptions;
|
||||
use super::super::shell_spec::create_exec_command_tool_with_environment_id;
|
||||
use super::ExecCommandArgs;
|
||||
use super::ExecCommandEnvironmentArgs;
|
||||
use super::effective_max_output_tokens;
|
||||
use super::get_command;
|
||||
use super::post_unified_exec_tool_use_payload;
|
||||
use crate::tools::handlers::shell_spec::CommandToolOptions;
|
||||
use crate::tools::handlers::shell_spec::create_exec_command_tool_with_environment_id;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct ExecCommandHandlerOptions {
|
||||
pub(crate) allow_login_shell: bool,
|
||||
pub(crate) exec_permission_approvals_enabled: bool,
|
||||
pub(crate) include_environment_id: bool,
|
||||
}
|
||||
|
||||
pub struct ExecCommandHandler {
|
||||
options: ExecCommandHandlerOptions,
|
||||
}
|
||||
|
||||
impl Default for ExecCommandHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
options: ExecCommandHandlerOptions {
|
||||
allow_login_shell: false,
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct ExecCommandHandler;
|
||||
|
||||
impl ExecCommandHandler {
|
||||
pub(crate) fn new(options: ExecCommandHandlerOptions) -> Self {
|
||||
Self { options }
|
||||
pub(crate) fn definition(
|
||||
options: CommandToolOptions,
|
||||
include_environment_id: bool,
|
||||
) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(
|
||||
Self,
|
||||
create_exec_command_tool_with_environment_id(options, include_environment_id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,16 +62,6 @@ impl ToolHandler for ExecCommandHandler {
|
||||
ToolName::plain("exec_command")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_exec_command_tool_with_environment_id(
|
||||
CommandToolOptions {
|
||||
allow_login_shell: self.options.allow_login_shell,
|
||||
exec_permission_approvals_enabled: self.options.exec_permission_approvals_enabled,
|
||||
},
|
||||
self.options.include_environment_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -3,16 +3,17 @@ use crate::tools::context::ExecCommandToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::handlers::shell_spec::create_write_stdin_tool;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use crate::unified_exec::WriteStdinRequest;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::TerminalInteractionEvent;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::super::shell_spec::create_write_stdin_tool;
|
||||
use super::effective_max_output_tokens;
|
||||
use super::post_unified_exec_tool_use_payload;
|
||||
|
||||
@@ -30,6 +31,12 @@ struct WriteStdinArgs {
|
||||
|
||||
pub struct WriteStdinHandler;
|
||||
|
||||
impl WriteStdinHandler {
|
||||
pub(crate) fn definition() -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_write_stdin_tool())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for WriteStdinHandler {
|
||||
type Output = ExecCommandToolOutput;
|
||||
|
||||
@@ -37,10 +44,6 @@ impl ToolHandler for WriteStdinHandler {
|
||||
ToolName::plain("write_stdin")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_write_stdin_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ async fn exec_command_pre_tool_use_payload_uses_raw_command() {
|
||||
arguments: serde_json::json!({ "cmd": "printf exec command" }).to_string(),
|
||||
};
|
||||
let (session, turn) = make_session_and_context().await;
|
||||
let handler = ExecCommandHandler::default();
|
||||
let handler = ExecCommandHandler;
|
||||
|
||||
assert_eq!(
|
||||
handler.pre_tool_use_payload(&ToolInvocation {
|
||||
@@ -244,7 +244,7 @@ async fn exec_command_post_tool_use_payload_uses_output_for_noninteractive_one_s
|
||||
hook_command: Some("echo three".to_string()),
|
||||
};
|
||||
let invocation = invocation_for_payload("exec_command", "call-43", payload).await;
|
||||
let handler = ExecCommandHandler::default();
|
||||
let handler = ExecCommandHandler;
|
||||
assert_eq!(
|
||||
handler.post_tool_use_payload(&invocation, &output),
|
||||
Some(crate::tools::registry::PostToolUsePayload {
|
||||
@@ -273,7 +273,7 @@ async fn exec_command_post_tool_use_payload_uses_output_for_interactive_completi
|
||||
hook_command: Some("echo three".to_string()),
|
||||
};
|
||||
let invocation = invocation_for_payload("exec_command", "call-44", payload).await;
|
||||
let handler = ExecCommandHandler::default();
|
||||
let handler = ExecCommandHandler;
|
||||
|
||||
assert_eq!(
|
||||
handler.post_tool_use_payload(&invocation, &output),
|
||||
@@ -303,7 +303,7 @@ async fn exec_command_post_tool_use_payload_skips_running_sessions() {
|
||||
hook_command: Some("echo three".to_string()),
|
||||
};
|
||||
let invocation = invocation_for_payload("exec_command", "call-45", payload).await;
|
||||
let handler = ExecCommandHandler::default();
|
||||
let handler = ExecCommandHandler;
|
||||
assert_eq!(handler.post_tool_use_payload(&invocation, &output), None);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,27 +21,16 @@ use crate::tools::handlers::resolve_tool_environment;
|
||||
use crate::tools::handlers::view_image_spec::ViewImageToolOptions;
|
||||
use crate::tools::handlers::view_image_spec::create_view_image_tool;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::runtime_definition::runtime_tool_definition;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub struct ViewImageHandler {
|
||||
options: ViewImageToolOptions,
|
||||
}
|
||||
|
||||
impl Default for ViewImageHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
options: ViewImageToolOptions {
|
||||
can_request_original_image_detail: false,
|
||||
include_environment_id: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct ViewImageHandler;
|
||||
|
||||
impl ViewImageHandler {
|
||||
pub(crate) fn new(options: ViewImageToolOptions) -> Self {
|
||||
Self { options }
|
||||
pub(crate) fn definition(options: ViewImageToolOptions) -> RuntimeToolDefinition {
|
||||
runtime_tool_definition(Self, create_view_image_tool(options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +57,6 @@ impl ToolHandler for ViewImageHandler {
|
||||
ToolName::plain("view_image")
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
Some(create_view_image_tool(self.options))
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -280,7 +265,7 @@ mod tests {
|
||||
std::fs::write(image_path.as_path(), b"not a real image").expect("write test image");
|
||||
turn.permission_profile = PermissionProfile::read_only();
|
||||
|
||||
let result = ViewImageHandler::default()
|
||||
let result = ViewImageHandler
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::new(session),
|
||||
turn: Arc::new(turn),
|
||||
|
||||
@@ -9,6 +9,7 @@ pub(crate) mod orchestrator;
|
||||
pub(crate) mod parallel;
|
||||
pub(crate) mod registry;
|
||||
pub(crate) mod router;
|
||||
pub(crate) mod runtime_definition;
|
||||
pub(crate) mod runtimes;
|
||||
pub(crate) mod sandboxing;
|
||||
pub(crate) mod spec;
|
||||
|
||||
@@ -17,14 +17,11 @@ 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::BundledToolHandler;
|
||||
use crate::tools::handlers::extension_tools::extension_tool_spec;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::tool_dispatch_trace::ToolDispatchTrace;
|
||||
use crate::util::error_or_panic;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_tool_api::ToolBundle as ExtensionToolBundle;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_utils_readiness::Readiness;
|
||||
@@ -40,10 +37,6 @@ pub trait ToolHandler: Send + Sync {
|
||||
/// The concrete tool name handled by this handler instance.
|
||||
fn tool_name(&self) -> ToolName;
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
false
|
||||
}
|
||||
@@ -180,7 +173,7 @@ pub(crate) struct PostToolUsePayload {
|
||||
pub(crate) tool_response: Value,
|
||||
}
|
||||
|
||||
trait AnyToolHandler: Send + Sync {
|
||||
pub(crate) trait AnyToolHandler: Send + Sync {
|
||||
fn supports_parallel_tool_calls(&self) -> bool;
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool;
|
||||
@@ -585,34 +578,22 @@ impl ToolRegistryBuilder {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(spec) = handler.spec() {
|
||||
self.push_spec(spec);
|
||||
}
|
||||
|
||||
let handler: Arc<dyn AnyToolHandler> = handler;
|
||||
self.handlers.insert(name, handler);
|
||||
}
|
||||
|
||||
pub fn register_tool_bundle(&mut self, bundle: ExtensionToolBundle) {
|
||||
let tool_name = ToolName::plain(bundle.tool_name());
|
||||
pub(crate) fn register_erased_handler(
|
||||
&mut self,
|
||||
tool_name: ToolName,
|
||||
handler: Arc<dyn AnyToolHandler>,
|
||||
) -> bool {
|
||||
if self.handlers.contains_key(&tool_name) {
|
||||
warn!("Skipping extension tool `{tool_name}`: handler already registered");
|
||||
return;
|
||||
warn!("Skipping tool handler `{tool_name}`: handler already registered");
|
||||
return false;
|
||||
}
|
||||
|
||||
let spec = match extension_tool_spec(bundle.spec()) {
|
||||
Ok(spec) => spec,
|
||||
Err(error) => {
|
||||
error_or_panic(format!(
|
||||
"failed to convert extension tool `{tool_name}` to a host spec: {error}"
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.push_spec(spec.clone());
|
||||
|
||||
let handler: Arc<dyn AnyToolHandler> = Arc::new(BundledToolHandler::new(bundle, spec));
|
||||
self.handlers.insert(tool_name, handler);
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn specs(&self) -> &[ToolSpec] {
|
||||
|
||||
@@ -63,9 +63,10 @@ fn handler_looks_up_namespaced_aliases_explicitly() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_handler_adds_handler_and_augments_specs_for_code_mode() {
|
||||
fn handlers_and_specs_are_registered_explicitly() {
|
||||
let mut builder = ToolRegistryBuilder::new(/*code_mode_enabled*/ true);
|
||||
builder.register_handler(Arc::new(GetGoalHandler));
|
||||
builder.push_spec(create_get_goal_tool());
|
||||
|
||||
let (specs, registry) = builder.build();
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@ use codex_protocol::models::LocalShellAction;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::SearchToolCallParams;
|
||||
use codex_protocol::models::ShellToolCallParams;
|
||||
use codex_tool_api::ToolBundle as ExtensionToolBundle;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolExecutor;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::ToolsConfig;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::instrument;
|
||||
@@ -46,7 +46,7 @@ pub(crate) struct ToolRouterParams<'a> {
|
||||
pub(crate) deferred_mcp_tools: Option<Vec<ToolInfo>>,
|
||||
pub(crate) unavailable_called_tools: Vec<ToolName>,
|
||||
pub(crate) discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
pub(crate) extension_tool_bundles: Vec<ExtensionToolBundle>,
|
||||
pub(crate) extension_tool_definitions: Vec<ToolDefinition<Arc<dyn ToolExecutor>>>,
|
||||
pub(crate) dynamic_tools: &'a [DynamicToolSpec],
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ impl ToolRouter {
|
||||
deferred_mcp_tools,
|
||||
unavailable_called_tools,
|
||||
discoverable_tools,
|
||||
extension_tool_bundles,
|
||||
extension_tool_definitions,
|
||||
dynamic_tools,
|
||||
} = params;
|
||||
let builder = build_specs_with_discoverable_tools(
|
||||
@@ -66,15 +66,10 @@ impl ToolRouter {
|
||||
deferred_mcp_tools,
|
||||
unavailable_called_tools,
|
||||
discoverable_tools,
|
||||
&extension_tool_bundles,
|
||||
&extension_tool_definitions,
|
||||
dynamic_tools,
|
||||
);
|
||||
let (specs, registry) = builder.build();
|
||||
let deferred_dynamic_tools = dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading)
|
||||
.map(|tool| ToolName::new(tool.namespace.clone(), tool.name.clone()))
|
||||
.collect::<HashSet<_>>();
|
||||
let model_visible_specs = specs
|
||||
.iter()
|
||||
.filter_map(|spec| {
|
||||
@@ -84,7 +79,7 @@ impl ToolRouter {
|
||||
return None;
|
||||
}
|
||||
|
||||
filter_deferred_dynamic_tool_spec(spec.clone(), &deferred_dynamic_tools)
|
||||
filter_deferred_tool_spec(spec.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -251,7 +246,9 @@ impl ToolRouter {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extension_tool_bundles(session: &Session) -> Vec<ExtensionToolBundle> {
|
||||
pub(crate) fn extension_tool_definitions(
|
||||
session: &Session,
|
||||
) -> Vec<ToolDefinition<Arc<dyn ToolExecutor>>> {
|
||||
session
|
||||
.services
|
||||
.extensions
|
||||
@@ -266,28 +263,18 @@ pub(crate) fn extension_tool_bundles(session: &Session) -> Vec<ExtensionToolBund
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn filter_deferred_dynamic_tool_spec(
|
||||
spec: ToolSpec,
|
||||
deferred_dynamic_tools: &HashSet<ToolName>,
|
||||
) -> Option<ToolSpec> {
|
||||
if deferred_dynamic_tools.is_empty() {
|
||||
return Some(spec);
|
||||
}
|
||||
|
||||
fn filter_deferred_tool_spec(spec: ToolSpec) -> Option<ToolSpec> {
|
||||
match spec {
|
||||
ToolSpec::Function(tool) => {
|
||||
if deferred_dynamic_tools.contains(&ToolName::plain(tool.name.as_str())) {
|
||||
if tool.defer_loading == Some(true) {
|
||||
None
|
||||
} else {
|
||||
Some(ToolSpec::Function(tool))
|
||||
}
|
||||
}
|
||||
ToolSpec::Namespace(mut namespace) => {
|
||||
let namespace_name = namespace.name.clone();
|
||||
namespace.tools.retain(|tool| match tool {
|
||||
ResponsesApiNamespaceTool::Function(tool) => !deferred_dynamic_tools.contains(
|
||||
&ToolName::namespaced(namespace_name.as_str(), tool.name.as_str()),
|
||||
),
|
||||
ResponsesApiNamespaceTool::Function(tool) => tool.defer_loading != Some(true),
|
||||
});
|
||||
if namespace.tools.is_empty() {
|
||||
None
|
||||
|
||||
@@ -8,7 +8,7 @@ use codex_extension_api::ExtensionData;
|
||||
use codex_extension_api::ExtensionRegistry;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::FunctionToolSpec;
|
||||
use codex_extension_api::ToolBundle;
|
||||
use codex_extension_api::ToolDefinition;
|
||||
use codex_extension_api::ToolExecutor;
|
||||
use codex_extension_api::ToolFuture;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
@@ -27,7 +27,7 @@ use super::ToolCall;
|
||||
use super::ToolCallSource;
|
||||
use super::ToolRouter;
|
||||
use super::ToolRouterParams;
|
||||
use super::extension_tool_bundles;
|
||||
use super::extension_tool_definitions;
|
||||
|
||||
struct ExtensionEchoContributor;
|
||||
|
||||
@@ -36,8 +36,8 @@ impl codex_extension_api::ToolContributor for ExtensionEchoContributor {
|
||||
&self,
|
||||
_session_store: &ExtensionData,
|
||||
_thread_store: &ExtensionData,
|
||||
) -> Vec<ToolBundle> {
|
||||
vec![ToolBundle::new(
|
||||
) -> Vec<ToolDefinition<Arc<dyn ToolExecutor>>> {
|
||||
vec![ToolDefinition::from_function_spec(
|
||||
FunctionToolSpec {
|
||||
name: "extension_echo".to_string(),
|
||||
description: "Echoes arguments through an extension tool.".to_string(),
|
||||
@@ -99,7 +99,7 @@ async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow
|
||||
mcp_tools: Some(mcp_tools),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: Vec::new(),
|
||||
extension_tool_definitions: Vec::new(),
|
||||
dynamic_tools: turn.dynamic_tools.as_slice(),
|
||||
},
|
||||
);
|
||||
@@ -179,7 +179,7 @@ async fn mcp_parallel_support_uses_handler_data() -> anyhow::Result<()> {
|
||||
]),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: Vec::new(),
|
||||
extension_tool_definitions: Vec::new(),
|
||||
dynamic_tools: turn.dynamic_tools.as_slice(),
|
||||
},
|
||||
);
|
||||
@@ -215,7 +215,7 @@ async fn tools_without_handlers_do_not_support_parallel() -> anyhow::Result<()>
|
||||
mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: Vec::new(),
|
||||
extension_tool_definitions: Vec::new(),
|
||||
dynamic_tools: turn.dynamic_tools.as_slice(),
|
||||
},
|
||||
);
|
||||
@@ -268,7 +268,7 @@ async fn model_visible_specs_filter_deferred_dynamic_tools() -> anyhow::Result<(
|
||||
mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: Vec::new(),
|
||||
extension_tool_definitions: Vec::new(),
|
||||
dynamic_tools: &dynamic_tools,
|
||||
},
|
||||
);
|
||||
@@ -323,7 +323,7 @@ fn mcp_tool_info(
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn extension_tool_bundles_are_model_visible_and_dispatchable() -> anyhow::Result<()> {
|
||||
async fn extension_tool_definitions_are_model_visible_and_dispatchable() -> anyhow::Result<()> {
|
||||
let (mut session, turn) = make_session_and_context().await;
|
||||
session.services.extensions = extension_tool_test_registry();
|
||||
|
||||
@@ -334,7 +334,7 @@ async fn extension_tool_bundles_are_model_visible_and_dispatchable() -> anyhow::
|
||||
mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: extension_tool_bundles(&session),
|
||||
extension_tool_definitions: extension_tool_definitions(&session),
|
||||
dynamic_tools: turn.dynamic_tools.as_slice(),
|
||||
},
|
||||
);
|
||||
|
||||
22
codex-rs/core/src/tools/runtime_definition.rs
Normal file
22
codex-rs/core/src/tools/runtime_definition.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
use crate::tools::registry::AnyToolHandler;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
||||
pub(crate) type RuntimeToolDefinition = ToolDefinition<Arc<dyn AnyToolHandler>, ToolSpec>;
|
||||
|
||||
pub(crate) fn runtime_tool_definition<H>(
|
||||
handler: H,
|
||||
spec: ToolSpec,
|
||||
) -> RuntimeToolDefinition
|
||||
where
|
||||
H: ToolHandler + 'static,
|
||||
{
|
||||
let handler = Arc::new(handler);
|
||||
let tool_name = handler.tool_name();
|
||||
let runtime: Arc<dyn AnyToolHandler> = handler;
|
||||
ToolDefinition::new(tool_name, spec, runtime)
|
||||
}
|
||||
@@ -11,11 +11,9 @@ use crate::tools::spec_plan_types::ToolNamespace;
|
||||
use crate::tools::spec_plan_types::ToolRegistryBuildParams;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tool_api::ToolBundle as ExtensionToolBundle;
|
||||
use codex_tools::AdditionalProperties;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolExecutor;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolUserShellType;
|
||||
use codex_tools::ToolsConfig;
|
||||
@@ -62,13 +60,10 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
deferred_mcp_tools: Option<Vec<ToolInfo>>,
|
||||
unavailable_called_tools: Vec<ToolName>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
extension_tool_bundles: &[ExtensionToolBundle],
|
||||
extension_tool_definitions: &[ToolDefinition<Arc<dyn ToolExecutor>>],
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> ToolRegistryBuilder {
|
||||
use crate::tools::handlers::UnavailableToolHandler;
|
||||
use crate::tools::handlers::unavailable_tool_message;
|
||||
use crate::tools::tool_search_entry::build_tool_search_entries_for_config;
|
||||
|
||||
let mcp_tool_plan_inputs = mcp_tools.as_deref().map(map_mcp_tools_for_plan);
|
||||
let deferred_mcp_tool_sources = deferred_mcp_tools.as_deref();
|
||||
let default_agent_type_description =
|
||||
@@ -83,16 +78,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
};
|
||||
let default_wait_timeout_ms =
|
||||
DEFAULT_WAIT_TIMEOUT_MS.clamp(min_wait_timeout_ms, MAX_WAIT_TIMEOUT_MS);
|
||||
let deferred_dynamic_tools = dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading && (config.namespace_tools || tool.namespace.is_none()))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let tool_search_entries = build_tool_search_entries_for_config(
|
||||
config,
|
||||
deferred_mcp_tool_sources,
|
||||
&deferred_dynamic_tools,
|
||||
);
|
||||
let mut builder = build_tool_registry_builder(
|
||||
config,
|
||||
ToolRegistryBuildParams {
|
||||
@@ -104,7 +89,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
.as_ref()
|
||||
.map(|inputs| &inputs.tool_namespaces),
|
||||
discoverable_tools: discoverable_tools.as_deref(),
|
||||
extension_tool_bundles,
|
||||
extension_tool_definitions,
|
||||
dynamic_tools,
|
||||
default_agent_type_description: &default_agent_type_description,
|
||||
wait_agent_timeouts: WaitAgentTimeoutOptions {
|
||||
@@ -112,7 +97,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
min_timeout_ms: min_wait_timeout_ms,
|
||||
max_timeout_ms: MAX_WAIT_TIMEOUT_MS,
|
||||
},
|
||||
tool_search_entries: &tool_search_entries,
|
||||
},
|
||||
);
|
||||
let mut existing_spec_names = builder
|
||||
@@ -124,29 +108,15 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
for unavailable_tool in unavailable_called_tools {
|
||||
let tool_name = flat_tool_name(&unavailable_tool).into_owned();
|
||||
if existing_spec_names.insert(tool_name.clone()) {
|
||||
let spec = codex_tools::ToolSpec::Function(ResponsesApiTool {
|
||||
name: tool_name.clone(),
|
||||
description: unavailable_tool_message(
|
||||
&tool_name,
|
||||
"Calling this placeholder returns an error explaining that the tool is unavailable.",
|
||||
),
|
||||
strict: false,
|
||||
parameters: JsonSchema::object(
|
||||
Default::default(),
|
||||
/*required*/ None,
|
||||
Some(AdditionalProperties::Boolean(false)),
|
||||
),
|
||||
output_schema: None,
|
||||
defer_loading: None,
|
||||
});
|
||||
builder.register_handler(Arc::new(UnavailableToolHandler::new(
|
||||
unavailable_tool,
|
||||
spec,
|
||||
)));
|
||||
let definition = UnavailableToolHandler::definition(unavailable_tool);
|
||||
if builder.register_erased_handler(
|
||||
definition.tool_name().clone(),
|
||||
Arc::clone(definition.runtime()),
|
||||
) {
|
||||
builder.push_spec(definition.spec().clone());
|
||||
}
|
||||
} else {
|
||||
builder.register_handler(Arc::new(UnavailableToolHandler::without_spec(
|
||||
unavailable_tool,
|
||||
)));
|
||||
builder.register_handler(Arc::new(UnavailableToolHandler::new(unavailable_tool)));
|
||||
}
|
||||
}
|
||||
builder
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::tools::code_mode::execute_spec::create_code_mode_tool;
|
||||
use crate::tools::handlers::ApplyPatchHandler;
|
||||
use crate::tools::handlers::CodeModeExecuteHandler;
|
||||
use crate::tools::handlers::CodeModeWaitHandler;
|
||||
@@ -6,7 +5,6 @@ use crate::tools::handlers::ContainerExecHandler;
|
||||
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;
|
||||
@@ -18,7 +16,6 @@ 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::ShellHandler;
|
||||
use crate::tools::handlers::TestSyncHandler;
|
||||
use crate::tools::handlers::ToolSearchHandler;
|
||||
@@ -27,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;
|
||||
@@ -39,30 +37,39 @@ use crate::tools::handlers::multi_agents_v2::ListAgentsHandler as ListAgentsHand
|
||||
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 crate::tools::handlers::shell_spec::CommandToolOptions;
|
||||
use crate::tools::handlers::shell_spec::ShellToolOptions;
|
||||
use crate::tools::handlers::view_image_spec::ViewImageToolOptions;
|
||||
use crate::tools::hosted_spec::WebSearchToolOptions;
|
||||
use crate::tools::hosted_spec::create_image_generation_tool;
|
||||
use crate::tools::hosted_spec::create_web_search_tool;
|
||||
use crate::tools::registry::AnyToolHandler;
|
||||
use crate::tools::registry::ToolRegistryBuilder;
|
||||
use crate::tools::runtime_definition::RuntimeToolDefinition;
|
||||
use crate::tools::spec_plan_types::ToolNamespace;
|
||||
use crate::tools::spec_plan_types::ToolRegistryBuildParams;
|
||||
use crate::tools::spec_plan_types::agent_type_description;
|
||||
use crate::tools::tool_search_entry::ToolSearchEntry;
|
||||
use crate::tools::tool_search_entry::dynamic_tool_search_entry;
|
||||
use crate::tools::tool_search_entry::mcp_tool_search_entry;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolExecutor;
|
||||
use codex_tool_api::ToolExposure;
|
||||
use codex_tools::ToolEnvironmentMode;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSearchSource;
|
||||
use codex_tools::ToolSearchSourceInfo;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::ToolsConfig;
|
||||
use codex_tools::coalesce_loadable_tool_specs;
|
||||
use codex_tools::collect_code_mode_exec_prompt_tool_definitions;
|
||||
use codex_tools::collect_request_plugin_install_entries;
|
||||
use codex_tools::collect_tool_search_source_infos;
|
||||
use codex_tools::default_namespace_description;
|
||||
use codex_tools::dynamic_tool_to_loadable_tool_spec;
|
||||
use codex_tools::mcp_tool_to_responses_api_tool;
|
||||
use codex_tools::mcp_tool_definition;
|
||||
use codex_tools::parse_dynamic_tool;
|
||||
use codex_tools::tool_definition_to_loadable_tool_spec;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
@@ -73,6 +80,16 @@ pub fn build_tool_registry_builder(
|
||||
) -> ToolRegistryBuilder {
|
||||
let mut builder = ToolRegistryBuilder::new(config.code_mode_enabled);
|
||||
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
|
||||
let mcp_definitions = mcp_tool_definitions(params.mcp_tools);
|
||||
let dynamic_definitions = dynamic_tool_definitions(params.dynamic_tools);
|
||||
let deferred_mcp_definitions = deferred_mcp_tool_definitions(params.deferred_mcp_tools);
|
||||
let tool_search_plan = build_tool_search_plan(
|
||||
config,
|
||||
params.deferred_mcp_tools,
|
||||
&deferred_mcp_definitions,
|
||||
params.dynamic_tools,
|
||||
&dynamic_definitions,
|
||||
);
|
||||
|
||||
if config.code_mode_enabled {
|
||||
let namespace_descriptions = params
|
||||
@@ -101,18 +118,21 @@ pub fn build_tool_registry_builder(
|
||||
collect_code_mode_exec_prompt_tool_definitions(nested_builder.specs().iter());
|
||||
enabled_tools
|
||||
.sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions));
|
||||
builder.register_handler(Arc::new(CodeModeExecuteHandler::new(
|
||||
create_code_mode_tool(
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
CodeModeExecuteHandler::definition(
|
||||
&enabled_tools,
|
||||
&namespace_descriptions,
|
||||
config.code_mode_only_enabled,
|
||||
config.search_tool
|
||||
&& params
|
||||
.deferred_mcp_tools
|
||||
.is_some_and(|tools| !tools.is_empty()),
|
||||
tool_search_plan.deferred_tools_available(),
|
||||
),
|
||||
)));
|
||||
builder.register_handler(Arc::new(CodeModeWaitHandler));
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
CodeModeWaitHandler::definition(),
|
||||
);
|
||||
}
|
||||
|
||||
if config.environment_mode.has_environment() {
|
||||
@@ -120,32 +140,54 @@ pub fn build_tool_registry_builder(
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
match &config.shell_type {
|
||||
ConfigShellToolType::Default => {
|
||||
builder.register_handler(Arc::new(ShellHandler::new(ShellToolOptions {
|
||||
let shell_options = ShellToolOptions {
|
||||
exec_permission_approvals_enabled,
|
||||
})));
|
||||
};
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ShellHandler::definition(shell_options),
|
||||
);
|
||||
}
|
||||
ConfigShellToolType::Local => {
|
||||
builder.register_handler(Arc::new(LocalShellHandler::new()));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
LocalShellHandler::definition(),
|
||||
);
|
||||
}
|
||||
ConfigShellToolType::UnifiedExec => {
|
||||
builder.register_handler(Arc::new(ExecCommandHandler::new(
|
||||
ExecCommandHandlerOptions {
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ExecCommandHandler::definition(
|
||||
CommandToolOptions {
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
},
|
||||
include_environment_id,
|
||||
},
|
||||
)));
|
||||
builder.register_handler(Arc::new(WriteStdinHandler));
|
||||
),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
WriteStdinHandler::definition(),
|
||||
);
|
||||
}
|
||||
ConfigShellToolType::Disabled => {}
|
||||
ConfigShellToolType::ShellCommand => {
|
||||
builder.register_handler(Arc::new(ShellCommandHandler::new(
|
||||
ShellCommandHandlerOptions {
|
||||
backend_config: config.shell_command_backend,
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
},
|
||||
)));
|
||||
let shell_command_options = CommandToolOptions {
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
};
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ShellCommandHandler::definition(
|
||||
config.shell_command_backend,
|
||||
shell_command_options,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,78 +228,94 @@ pub fn build_tool_registry_builder(
|
||||
}
|
||||
|
||||
if params.mcp_tools.is_some() {
|
||||
builder.register_handler(Arc::new(ListMcpResourcesHandler));
|
||||
builder.register_handler(Arc::new(ListMcpResourceTemplatesHandler));
|
||||
builder.register_handler(Arc::new(ReadMcpResourceHandler));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ListMcpResourcesHandler::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ListMcpResourceTemplatesHandler::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ReadMcpResourceHandler::definition(),
|
||||
);
|
||||
}
|
||||
|
||||
builder.register_handler(Arc::new(PlanHandler));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
PlanHandler::definition(),
|
||||
);
|
||||
if config.goal_tools {
|
||||
builder.register_handler(Arc::new(GetGoalHandler));
|
||||
builder.register_handler(Arc::new(CreateGoalHandler));
|
||||
builder.register_handler(Arc::new(UpdateGoalHandler));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
GetGoalHandler::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
CreateGoalHandler::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
UpdateGoalHandler::definition(),
|
||||
);
|
||||
}
|
||||
|
||||
builder.register_handler(Arc::new(RequestUserInputHandler {
|
||||
available_modes: config.request_user_input_available_modes.clone(),
|
||||
}));
|
||||
let available_modes = config.request_user_input_available_modes.clone();
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
RequestUserInputHandler::definition(available_modes),
|
||||
);
|
||||
|
||||
if config.request_permissions_tool_enabled {
|
||||
builder.register_handler(Arc::new(RequestPermissionsHandler));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
RequestPermissionsHandler::definition(),
|
||||
);
|
||||
}
|
||||
|
||||
let deferred_dynamic_tools = params
|
||||
.dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading && (config.namespace_tools || tool.namespace.is_none()))
|
||||
.collect::<Vec<_>>();
|
||||
let deferred_mcp_tools_for_search = if config.namespace_tools {
|
||||
params.deferred_mcp_tools
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if config.search_tool
|
||||
&& (deferred_mcp_tools_for_search.is_some() || !deferred_dynamic_tools.is_empty())
|
||||
{
|
||||
let mut search_source_infos = deferred_mcp_tools_for_search
|
||||
.map(|deferred_mcp_tools| {
|
||||
collect_tool_search_source_infos(deferred_mcp_tools.iter().map(|tool| {
|
||||
ToolSearchSource {
|
||||
server_name: tool.server_name.as_str(),
|
||||
connector_name: tool.connector_name.as_deref(),
|
||||
description: tool.namespace_description.as_deref(),
|
||||
}
|
||||
}))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if !deferred_dynamic_tools.is_empty() {
|
||||
search_source_infos.push(ToolSearchSourceInfo {
|
||||
name: "Dynamic tools".to_string(),
|
||||
description: Some("Tools provided by the current Codex thread.".to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
builder.register_handler(Arc::new(ToolSearchHandler::new(
|
||||
params.tool_search_entries.to_vec(),
|
||||
search_source_infos,
|
||||
)));
|
||||
if tool_search_plan.should_register() {
|
||||
let ToolSearchPlan {
|
||||
entries,
|
||||
source_infos,
|
||||
..
|
||||
} = tool_search_plan;
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ToolSearchHandler::definition(entries, source_infos),
|
||||
);
|
||||
}
|
||||
|
||||
if config.tool_suggest
|
||||
&& let Some(discoverable_tools) =
|
||||
params.discoverable_tools.filter(|tools| !tools.is_empty())
|
||||
{
|
||||
builder.register_handler(Arc::new(RequestPluginInstallHandler::new(
|
||||
discoverable_tools,
|
||||
)));
|
||||
let discoverable_tool_entries = collect_request_plugin_install_entries(discoverable_tools);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
RequestPluginInstallHandler::definition(&discoverable_tool_entries),
|
||||
);
|
||||
}
|
||||
|
||||
if config.environment_mode.has_environment() && config.apply_patch_tool_type.is_some() {
|
||||
let include_environment_id =
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
builder.register_handler(Arc::new(ApplyPatchHandler::new(include_environment_id)));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ApplyPatchHandler::definition(include_environment_id),
|
||||
);
|
||||
}
|
||||
|
||||
if config
|
||||
@@ -265,7 +323,11 @@ pub fn build_tool_registry_builder(
|
||||
.iter()
|
||||
.any(|tool| tool == "test_sync_tool")
|
||||
{
|
||||
builder.register_handler(Arc::new(TestSyncHandler));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
TestSyncHandler::definition(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(web_search_tool) = create_web_search_tool(WebSearchToolOptions {
|
||||
@@ -283,158 +345,422 @@ pub fn build_tool_registry_builder(
|
||||
if config.environment_mode.has_environment() {
|
||||
let include_environment_id =
|
||||
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
|
||||
builder.register_handler(Arc::new(ViewImageHandler::new(ViewImageToolOptions {
|
||||
let view_image_options = ViewImageToolOptions {
|
||||
can_request_original_image_detail: config.can_request_original_image_detail,
|
||||
include_environment_id,
|
||||
})));
|
||||
};
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ViewImageHandler::definition(view_image_options),
|
||||
);
|
||||
}
|
||||
|
||||
if config.collab_tools {
|
||||
if config.multi_agent_v2 {
|
||||
let agent_type_description =
|
||||
agent_type_description(config, params.default_agent_type_description);
|
||||
builder.register_handler(Arc::new(SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
|
||||
let spawn_agent_options = SpawnAgentToolOptions {
|
||||
available_models: config.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,
|
||||
})));
|
||||
builder.register_handler(Arc::new(SendMessageHandlerV2));
|
||||
builder.register_handler(Arc::new(FollowupTaskHandlerV2));
|
||||
builder.register_handler(Arc::new(WaitAgentHandlerV2::new(
|
||||
params.wait_agent_timeouts,
|
||||
)));
|
||||
builder.register_handler(Arc::new(CloseAgentHandlerV2));
|
||||
builder.register_handler(Arc::new(ListAgentsHandlerV2));
|
||||
};
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
SpawnAgentHandlerV2::definition(spawn_agent_options),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
SendMessageHandlerV2::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
FollowupTaskHandlerV2::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
WaitAgentHandlerV2::definition(params.wait_agent_timeouts),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
CloseAgentHandlerV2::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ListAgentsHandlerV2::definition(),
|
||||
);
|
||||
} else {
|
||||
let agent_type_description =
|
||||
agent_type_description(config, params.default_agent_type_description);
|
||||
builder.register_handler(Arc::new(SpawnAgentHandler::new(SpawnAgentToolOptions {
|
||||
let spawn_agent_options = SpawnAgentToolOptions {
|
||||
available_models: config.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,
|
||||
})));
|
||||
builder.register_handler(Arc::new(SendInputHandler));
|
||||
builder.register_handler(Arc::new(ResumeAgentHandler));
|
||||
builder.register_handler(Arc::new(WaitAgentHandler::new(params.wait_agent_timeouts)));
|
||||
builder.register_handler(Arc::new(CloseAgentHandler));
|
||||
};
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
SpawnAgentHandler::definition(spawn_agent_options),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
SendInputHandler::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ResumeAgentHandler::definition(),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
WaitAgentHandler::definition(params.wait_agent_timeouts),
|
||||
);
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
CloseAgentHandler::definition(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if config.agent_jobs_tools {
|
||||
builder.register_handler(Arc::new(SpawnAgentsOnCsvHandler));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
SpawnAgentsOnCsvHandler::definition(),
|
||||
);
|
||||
if config.agent_jobs_worker_tools {
|
||||
builder.register_handler(Arc::new(ReportAgentJobResultHandler));
|
||||
register_and_publish_tool_definition(
|
||||
&mut builder,
|
||||
config,
|
||||
ReportAgentJobResultHandler::definition(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mcp_tools) = params.mcp_tools {
|
||||
let mut entries = mcp_tools
|
||||
.iter()
|
||||
.map(|tool| (tool.canonical_tool_name(), tool))
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||
let mut namespace_entries = BTreeMap::new();
|
||||
register_and_publish_function_tool_definitions(
|
||||
&mut builder,
|
||||
config,
|
||||
params.tool_namespaces,
|
||||
mcp_definitions
|
||||
.into_iter()
|
||||
.chain(dynamic_definitions)
|
||||
.chain(extension_tool_definitions(
|
||||
params.extension_tool_definitions,
|
||||
)),
|
||||
);
|
||||
|
||||
for (tool_name, tool) in entries {
|
||||
let Some(namespace) = tool_name.namespace.as_ref() else {
|
||||
tracing::error!("Skipping MCP tool `{tool_name}`: MCP tools must be namespaced");
|
||||
continue;
|
||||
};
|
||||
namespace_entries
|
||||
.entry(namespace.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((tool_name, tool));
|
||||
}
|
||||
|
||||
for (namespace, mut entries) in namespace_entries {
|
||||
entries.sort_by_key(|(tool_name, _)| tool_name.name.clone());
|
||||
let tool_namespace = params
|
||||
.tool_namespaces
|
||||
.and_then(|namespaces| namespaces.get(&namespace));
|
||||
let description = tool_namespace
|
||||
.and_then(|namespace| namespace.description.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| {
|
||||
let namespace_name = tool_namespace
|
||||
.map(|namespace| namespace.name.as_str())
|
||||
.unwrap_or(namespace.as_str());
|
||||
default_namespace_description(namespace_name)
|
||||
});
|
||||
let mut tools = Vec::new();
|
||||
for (tool_name, tool) in entries {
|
||||
match mcp_tool_to_responses_api_tool(&tool_name, &tool.tool) {
|
||||
Ok(converted_tool) => {
|
||||
tools.push(ResponsesApiNamespaceTool::Function(converted_tool));
|
||||
builder.register_handler(Arc::new(McpHandler::new(tool.clone())));
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"Failed to convert `{tool_name}` MCP tool to OpenAI tool: {error:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.namespace_tools && !tools.is_empty() {
|
||||
builder.push_spec(ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: namespace,
|
||||
description,
|
||||
tools,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dynamic_tool_specs = Vec::new();
|
||||
for tool in params.dynamic_tools {
|
||||
match dynamic_tool_to_loadable_tool_spec(tool) {
|
||||
Ok(loadable_tool) => {
|
||||
let handler_name = ToolName::new(tool.namespace.clone(), tool.name.clone());
|
||||
dynamic_tool_specs.push(loadable_tool);
|
||||
builder.register_handler(Arc::new(DynamicToolHandler::new(handler_name)));
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"Failed to convert dynamic tool {:?} to OpenAI tool: {error:?}",
|
||||
tool.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for spec in coalesce_loadable_tool_specs(dynamic_tool_specs) {
|
||||
let spec = spec.into();
|
||||
if config.namespace_tools || !matches!(spec, ToolSpec::Namespace(_)) {
|
||||
builder.push_spec(spec);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(deferred_mcp_tools) = params.deferred_mcp_tools {
|
||||
if !deferred_mcp_definitions.is_empty() {
|
||||
let directly_registered_mcp_tools = params
|
||||
.mcp_tools
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(ToolInfo::canonical_tool_name)
|
||||
.collect::<HashSet<_>>();
|
||||
for tool in deferred_mcp_tools {
|
||||
if !directly_registered_mcp_tools.contains(&tool.canonical_tool_name()) {
|
||||
builder.register_handler(Arc::new(McpHandler::new(tool.clone())));
|
||||
for definition in deferred_mcp_definitions {
|
||||
if !directly_registered_mcp_tools.contains(definition.tool_name()) {
|
||||
register_tool_definition_handler(&mut builder, &definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for bundle in params.extension_tool_bundles.iter().cloned() {
|
||||
builder.register_tool_bundle(bundle);
|
||||
builder
|
||||
}
|
||||
|
||||
type FunctionRuntimeToolDefinition = ToolDefinition<Arc<dyn AnyToolHandler>>;
|
||||
|
||||
struct ToolSearchPlan {
|
||||
register: bool,
|
||||
entries: Vec<ToolSearchEntry>,
|
||||
source_infos: Vec<ToolSearchSourceInfo>,
|
||||
}
|
||||
|
||||
impl ToolSearchPlan {
|
||||
fn should_register(&self) -> bool {
|
||||
self.register
|
||||
}
|
||||
|
||||
builder
|
||||
fn deferred_tools_available(&self) -> bool {
|
||||
self.register && !self.entries.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn build_tool_search_plan(
|
||||
config: &ToolsConfig,
|
||||
deferred_mcp_tools: Option<&[ToolInfo]>,
|
||||
deferred_mcp_definitions: &[FunctionRuntimeToolDefinition],
|
||||
dynamic_tools: &[codex_protocol::dynamic_tools::DynamicToolSpec],
|
||||
dynamic_definitions: &[FunctionRuntimeToolDefinition],
|
||||
) -> ToolSearchPlan {
|
||||
debug_assert_eq!(
|
||||
deferred_mcp_tools.map_or(0, <[codex_mcp::ToolInfo]>::len),
|
||||
deferred_mcp_definitions.len()
|
||||
);
|
||||
debug_assert_eq!(dynamic_tools.len(), dynamic_definitions.len());
|
||||
|
||||
let mut entries = Vec::new();
|
||||
let mcp_search_enabled = config.namespace_tools && deferred_mcp_tools.is_some();
|
||||
let deferred_mcp_tools = deferred_mcp_tools.unwrap_or_default();
|
||||
if config.namespace_tools {
|
||||
let mut searchable_mcp_tools = deferred_mcp_tools
|
||||
.iter()
|
||||
.zip(deferred_mcp_definitions.iter())
|
||||
.collect::<Vec<_>>();
|
||||
searchable_mcp_tools.sort_by_key(|(tool, _)| tool.canonical_tool_name());
|
||||
for (tool, definition) in searchable_mcp_tools {
|
||||
match mcp_tool_search_entry(tool, definition) {
|
||||
Ok(entry) => entries.push(entry),
|
||||
Err(error) => {
|
||||
let tool_name = tool.canonical_tool_name();
|
||||
tracing::error!(
|
||||
"Failed to convert deferred MCP tool `{tool_name}` to OpenAI tool: {error:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut searchable_dynamic_tools = dynamic_tools
|
||||
.iter()
|
||||
.zip(dynamic_definitions.iter())
|
||||
.filter(|(_, definition)| {
|
||||
matches!(definition.exposure(), ToolExposure::Deferred)
|
||||
&& (config.namespace_tools || definition.tool_name().namespace.is_none())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let has_searchable_dynamic_tools = !searchable_dynamic_tools.is_empty();
|
||||
searchable_dynamic_tools.sort_by(|(left, _), (right, _)| {
|
||||
left.namespace
|
||||
.cmp(&right.namespace)
|
||||
.then(left.name.cmp(&right.name))
|
||||
});
|
||||
for (tool, definition) in searchable_dynamic_tools {
|
||||
match dynamic_tool_search_entry(tool, definition) {
|
||||
Ok(entry) => entries.push(entry),
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
"Failed to convert deferred dynamic tool {:?} to OpenAI tool: {error:?}",
|
||||
tool.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut source_infos = if config.namespace_tools {
|
||||
collect_tool_search_source_infos(deferred_mcp_tools.iter().map(|tool| ToolSearchSource {
|
||||
server_name: tool.server_name.as_str(),
|
||||
connector_name: tool.connector_name.as_deref(),
|
||||
description: tool.namespace_description.as_deref(),
|
||||
}))
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
if has_searchable_dynamic_tools {
|
||||
source_infos.push(ToolSearchSourceInfo {
|
||||
name: "Dynamic tools".to_string(),
|
||||
description: Some("Tools provided by the current Codex thread.".to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
ToolSearchPlan {
|
||||
register: config.search_tool && (mcp_search_enabled || has_searchable_dynamic_tools),
|
||||
entries,
|
||||
source_infos,
|
||||
}
|
||||
}
|
||||
|
||||
fn register_and_publish_tool_definition(
|
||||
builder: &mut ToolRegistryBuilder,
|
||||
config: &ToolsConfig,
|
||||
definition: RuntimeToolDefinition,
|
||||
) {
|
||||
register_and_publish_tool_definitions(builder, config, std::iter::once(definition));
|
||||
}
|
||||
|
||||
fn register_and_publish_tool_definitions(
|
||||
builder: &mut ToolRegistryBuilder,
|
||||
config: &ToolsConfig,
|
||||
definitions: impl IntoIterator<Item = RuntimeToolDefinition>,
|
||||
) {
|
||||
let mut specs = Vec::new();
|
||||
|
||||
for definition in definitions {
|
||||
if register_tool_definition_handler(builder, &definition) {
|
||||
specs.push(definition.spec().clone());
|
||||
}
|
||||
}
|
||||
|
||||
for spec in coalesce_tool_specs(specs) {
|
||||
if config.namespace_tools || !matches!(spec, ToolSpec::Namespace(_)) {
|
||||
builder.push_spec(spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_and_publish_function_tool_definitions(
|
||||
builder: &mut ToolRegistryBuilder,
|
||||
config: &ToolsConfig,
|
||||
tool_namespaces: Option<&std::collections::HashMap<String, ToolNamespace>>,
|
||||
definitions: impl IntoIterator<Item = FunctionRuntimeToolDefinition>,
|
||||
) {
|
||||
register_and_publish_tool_definitions(
|
||||
builder,
|
||||
config,
|
||||
definitions
|
||||
.into_iter()
|
||||
.filter_map(|definition| render_function_tool_definition(definition, tool_namespaces)),
|
||||
);
|
||||
}
|
||||
|
||||
fn render_function_tool_definition(
|
||||
definition: FunctionRuntimeToolDefinition,
|
||||
tool_namespaces: Option<&std::collections::HashMap<String, ToolNamespace>>,
|
||||
) -> Option<RuntimeToolDefinition> {
|
||||
let tool_name = definition.tool_name().clone();
|
||||
let namespace_description = namespace_description_for_tool(&tool_name, tool_namespaces);
|
||||
let spec = match tool_definition_to_loadable_tool_spec(&definition, namespace_description) {
|
||||
Ok(spec) => ToolSpec::from(spec),
|
||||
Err(error) => {
|
||||
tracing::error!("Failed to convert tool `{tool_name}` to OpenAI tool: {error:?}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let exposure = definition.exposure();
|
||||
let mut rendered = ToolDefinition::new(tool_name, spec, Arc::clone(definition.runtime()));
|
||||
if matches!(exposure, ToolExposure::Deferred) {
|
||||
rendered = rendered.deferred();
|
||||
}
|
||||
Some(rendered)
|
||||
}
|
||||
|
||||
fn coalesce_tool_specs(specs: Vec<ToolSpec>) -> Vec<ToolSpec> {
|
||||
let mut coalesced_specs = Vec::new();
|
||||
for spec in specs {
|
||||
match spec {
|
||||
ToolSpec::Namespace(mut namespace) => {
|
||||
if let Some(existing_namespace) =
|
||||
coalesced_specs.iter_mut().find_map(|spec| match spec {
|
||||
ToolSpec::Namespace(existing_namespace)
|
||||
if existing_namespace.name == namespace.name =>
|
||||
{
|
||||
Some(existing_namespace)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
existing_namespace.tools.append(&mut namespace.tools);
|
||||
} else {
|
||||
coalesced_specs.push(ToolSpec::Namespace(namespace));
|
||||
}
|
||||
}
|
||||
spec => coalesced_specs.push(spec),
|
||||
}
|
||||
}
|
||||
coalesced_specs
|
||||
}
|
||||
|
||||
fn register_tool_definition_handler<S>(
|
||||
builder: &mut ToolRegistryBuilder,
|
||||
definition: &ToolDefinition<Arc<dyn AnyToolHandler>, S>,
|
||||
) -> bool {
|
||||
builder.register_erased_handler(
|
||||
definition.tool_name().clone(),
|
||||
Arc::clone(definition.runtime()),
|
||||
)
|
||||
}
|
||||
|
||||
fn mcp_tool_definitions(mcp_tools: Option<&[ToolInfo]>) -> Vec<FunctionRuntimeToolDefinition> {
|
||||
let mut tools = mcp_tools.into_iter().flatten().collect::<Vec<_>>();
|
||||
tools.sort_by_key(|tool| tool.canonical_tool_name());
|
||||
|
||||
tools
|
||||
.into_iter()
|
||||
.filter_map(|tool| {
|
||||
let tool_name = tool.canonical_tool_name();
|
||||
if tool_name.namespace.is_none() {
|
||||
tracing::error!("Skipping MCP tool `{tool_name}`: MCP tools must be namespaced");
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
mcp_tool_definition(tool_name, &tool.tool)
|
||||
.with_runtime(
|
||||
Arc::new(McpHandler::new(tool.clone())) as Arc<dyn AnyToolHandler>
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn deferred_mcp_tool_definitions(
|
||||
mcp_tools: Option<&[ToolInfo]>,
|
||||
) -> Vec<FunctionRuntimeToolDefinition> {
|
||||
mcp_tools
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|tool| {
|
||||
mcp_tool_definition(tool.canonical_tool_name(), &tool.tool)
|
||||
.deferred()
|
||||
.with_runtime(Arc::new(McpHandler::new(tool.clone())) as Arc<dyn AnyToolHandler>)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn dynamic_tool_definitions(
|
||||
dynamic_tools: &[codex_protocol::dynamic_tools::DynamicToolSpec],
|
||||
) -> Vec<FunctionRuntimeToolDefinition> {
|
||||
dynamic_tools
|
||||
.iter()
|
||||
.map(|tool| {
|
||||
let definition = parse_dynamic_tool(tool);
|
||||
let handler = Arc::new(DynamicToolHandler::new(definition.tool_name().clone()))
|
||||
as Arc<dyn AnyToolHandler>;
|
||||
definition.with_runtime(handler)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn extension_tool_definitions(
|
||||
definitions: &[ToolDefinition<Arc<dyn ToolExecutor>>],
|
||||
) -> Vec<FunctionRuntimeToolDefinition> {
|
||||
definitions
|
||||
.iter()
|
||||
.map(|definition| {
|
||||
let handler =
|
||||
Arc::new(ExtensionToolHandler::new(definition.clone())) as Arc<dyn AnyToolHandler>;
|
||||
definition.clone().with_runtime(handler)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn namespace_description_for_tool(
|
||||
tool_name: &ToolName,
|
||||
tool_namespaces: Option<&std::collections::HashMap<String, ToolNamespace>>,
|
||||
) -> Option<String> {
|
||||
let namespace = tool_name.namespace.as_ref()?;
|
||||
let tool_namespace = tool_namespaces.and_then(|namespaces| namespaces.get(namespace));
|
||||
tool_namespace.map(|tool_namespace| {
|
||||
tool_namespace
|
||||
.description
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| default_namespace_description(tool_namespace.name.as_str()))
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_code_mode_tools(
|
||||
|
||||
@@ -43,7 +43,7 @@ use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_tool_api::FunctionToolSpec;
|
||||
use codex_tool_api::ToolBundle as ExtensionToolBundle;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolExecutor;
|
||||
use codex_tool_api::ToolFuture;
|
||||
use codex_tools::AdditionalProperties;
|
||||
@@ -83,8 +83,11 @@ impl ToolExecutor for UnusedExtensionExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
fn extension_tool_bundle(name: &str, description: &str) -> ExtensionToolBundle {
|
||||
ExtensionToolBundle::new(
|
||||
fn extension_tool_definition(
|
||||
name: &str,
|
||||
description: &str,
|
||||
) -> ToolDefinition<std::sync::Arc<dyn ToolExecutor>> {
|
||||
ToolDefinition::from_function_spec(
|
||||
FunctionToolSpec {
|
||||
name: name.to_string(),
|
||||
description: description.to_string(),
|
||||
@@ -117,7 +120,7 @@ fn extension_tools_do_not_replace_builtin_tools() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let extension_tool_bundles = vec![extension_tool_bundle(
|
||||
let extension_tool_definitions = vec![extension_tool_definition(
|
||||
"update_plan",
|
||||
"Extension attempt to replace a built-in tool.",
|
||||
)];
|
||||
@@ -126,7 +129,7 @@ fn extension_tools_do_not_replace_builtin_tools() {
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
/*discoverable_tools*/ None,
|
||||
&extension_tool_bundles,
|
||||
&extension_tool_definitions,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1908,7 +1911,7 @@ fn request_plugin_install_is_not_registered_without_feature_flag() {
|
||||
"Google Calendar",
|
||||
"Plan events and schedules.",
|
||||
)]),
|
||||
/*extension_tool_bundles*/ &[],
|
||||
/*extension_tool_definitions*/ &[],
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1949,7 +1952,7 @@ fn request_plugin_install_can_be_registered_without_search_tool() {
|
||||
"Google Calendar",
|
||||
"Plan events and schedules.",
|
||||
)]),
|
||||
/*extension_tool_bundles*/ &[],
|
||||
/*extension_tool_definitions*/ &[],
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -2014,7 +2017,7 @@ fn request_plugin_install_description_lists_discoverable_tools() {
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(discoverable_tools),
|
||||
/*extension_tool_bundles*/ &[],
|
||||
/*extension_tool_definitions*/ &[],
|
||||
&[],
|
||||
);
|
||||
assert!(registry.has_handler(&ToolName::plain(REQUEST_PLUGIN_INSTALL_TOOL_NAME)));
|
||||
@@ -2307,7 +2310,7 @@ fn code_mode_only_exec_description_includes_extension_tool_details() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let extension_tool_bundles = vec![extension_tool_bundle(
|
||||
let extension_tool_definitions = vec![extension_tool_definition(
|
||||
"extension_echo",
|
||||
"Echoes arguments through an extension tool.",
|
||||
)];
|
||||
@@ -2316,7 +2319,7 @@ fn code_mode_only_exec_description_includes_extension_tool_details() {
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
/*discoverable_tools*/ None,
|
||||
&extension_tool_bundles,
|
||||
&extension_tool_definitions,
|
||||
&[],
|
||||
);
|
||||
let ToolSpec::Freeform(FreeformTool { description, .. }) = find_tool(&tools, "exec") else {
|
||||
@@ -2414,7 +2417,7 @@ fn build_specs(
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
/*discoverable_tools*/ None,
|
||||
/*extension_tool_bundles*/ &[],
|
||||
/*extension_tool_definitions*/ &[],
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
@@ -2424,7 +2427,7 @@ fn build_specs_with_discoverable_tools(
|
||||
mcp_tools: Option<HashMap<ToolName, rmcp::model::Tool>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolInfo>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
extension_tool_bundles: &[codex_tool_api::ToolBundle],
|
||||
extension_tool_definitions: &[ToolDefinition<std::sync::Arc<dyn ToolExecutor>>],
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ToolSpec>, ToolRegistry) {
|
||||
build_specs_with_optional_tool_namespaces(
|
||||
@@ -2433,7 +2436,7 @@ fn build_specs_with_discoverable_tools(
|
||||
deferred_mcp_tools,
|
||||
/*tool_namespaces*/ None,
|
||||
discoverable_tools,
|
||||
extension_tool_bundles,
|
||||
extension_tool_definitions,
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
@@ -2444,7 +2447,7 @@ fn build_specs_with_optional_tool_namespaces(
|
||||
deferred_mcp_tools: Option<Vec<ToolInfo>>,
|
||||
tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
extension_tool_bundles: &[codex_tool_api::ToolBundle],
|
||||
extension_tool_definitions: &[ToolDefinition<std::sync::Arc<dyn ToolExecutor>>],
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ToolSpec>, ToolRegistry) {
|
||||
let mcp_tool_inputs = mcp_tools.as_ref().map(|mcp_tools| {
|
||||
@@ -2460,11 +2463,10 @@ fn build_specs_with_optional_tool_namespaces(
|
||||
deferred_mcp_tools: deferred_mcp_tools.as_deref(),
|
||||
tool_namespaces: tool_namespaces.as_ref(),
|
||||
discoverable_tools: discoverable_tools.as_deref(),
|
||||
extension_tool_bundles,
|
||||
extension_tool_definitions,
|
||||
dynamic_tools,
|
||||
default_agent_type_description: DEFAULT_AGENT_TYPE_DESCRIPTION,
|
||||
wait_agent_timeouts: wait_agent_timeout_options(),
|
||||
tool_search_entries: &[],
|
||||
},
|
||||
);
|
||||
builder.build()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tool_api::ToolBundle as ExtensionToolBundle;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolExecutor;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::ToolsConfig;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ToolRegistryBuildParams<'a> {
|
||||
@@ -12,11 +14,10 @@ pub struct ToolRegistryBuildParams<'a> {
|
||||
pub deferred_mcp_tools: Option<&'a [ToolInfo]>,
|
||||
pub tool_namespaces: Option<&'a HashMap<String, ToolNamespace>>,
|
||||
pub discoverable_tools: Option<&'a [DiscoverableTool]>,
|
||||
pub extension_tool_bundles: &'a [ExtensionToolBundle],
|
||||
pub extension_tool_definitions: &'a [ToolDefinition<Arc<dyn ToolExecutor>>],
|
||||
pub dynamic_tools: &'a [DynamicToolSpec],
|
||||
pub default_agent_type_description: &'a str,
|
||||
pub wait_agent_timeouts: WaitAgentTimeoutOptions,
|
||||
pub tool_search_entries: &'a [crate::tools::tool_search_entry::ToolSearchEntry],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -19,7 +19,6 @@ use codex_protocol::protocol::SessionSource;
|
||||
use codex_tools::AdditionalProperties;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::LoadableToolSpec;
|
||||
use codex_tools::REQUEST_PLUGIN_INSTALL_TOOL_NAME;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
@@ -40,7 +39,6 @@ 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 {
|
||||
@@ -295,7 +293,7 @@ fn build_specs_with_unavailable_tools(
|
||||
deferred_mcp_tools,
|
||||
unavailable_called_tools,
|
||||
/*discoverable_tools*/ None,
|
||||
/*extension_tool_bundles*/ &[],
|
||||
/*extension_tool_definitions*/ &[],
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
@@ -356,7 +354,7 @@ async fn assert_model_tools(
|
||||
deferred_mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
discoverable_tools: None,
|
||||
extension_tool_bundles: Vec::new(),
|
||||
extension_tool_definitions: Vec::new(),
|
||||
dynamic_tools: &[],
|
||||
},
|
||||
);
|
||||
@@ -826,7 +824,7 @@ async fn request_plugin_install_requires_apps_and_plugins_features() {
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Vec::new(),
|
||||
discoverable_tools.clone(),
|
||||
/*extension_tool_bundles*/ &[],
|
||||
/*extension_tool_definitions*/ &[],
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -1002,59 +1000,6 @@ 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 = vec![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;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use crate::tools::flat_tool_name;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
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 codex_tools::tool_definition_to_loadable_tool_spec;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ToolSearchEntry {
|
||||
@@ -14,6 +12,7 @@ pub(crate) struct ToolSearchEntry {
|
||||
pub(crate) limit_bucket: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn build_tool_search_entries(
|
||||
mcp_tools: Option<&[ToolInfo]>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
@@ -25,7 +24,9 @@ pub(crate) fn build_tool_search_entries(
|
||||
.unwrap_or_default();
|
||||
mcp_tools.sort_by_key(|info| info.canonical_tool_name());
|
||||
for info in mcp_tools {
|
||||
match mcp_tool_search_entry(info) {
|
||||
let definition =
|
||||
codex_tools::mcp_tool_definition(info.canonical_tool_name(), &info.tool).deferred();
|
||||
match mcp_tool_search_entry(info, &definition) {
|
||||
Ok(entry) => entries.push(entry),
|
||||
Err(error) => {
|
||||
let tool_name = info.canonical_tool_name();
|
||||
@@ -39,7 +40,8 @@ pub(crate) fn build_tool_search_entries(
|
||||
let mut dynamic_tools = dynamic_tools.iter().collect::<Vec<_>>();
|
||||
dynamic_tools.sort_by(|a, b| a.namespace.cmp(&b.namespace).then(a.name.cmp(&b.name)));
|
||||
for tool in dynamic_tools {
|
||||
match dynamic_tool_search_entry(tool) {
|
||||
let definition = codex_tools::parse_dynamic_tool(tool);
|
||||
match dynamic_tool_search_entry(tool, &definition) {
|
||||
Ok(entry) => entries.push(entry),
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
@@ -53,47 +55,48 @@ pub(crate) fn build_tool_search_entries(
|
||||
entries
|
||||
}
|
||||
|
||||
pub(crate) fn build_tool_search_entries_for_config(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<&[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> {
|
||||
pub(crate) fn mcp_tool_search_entry<R>(
|
||||
info: &ToolInfo,
|
||||
definition: &ToolDefinition<R>,
|
||||
) -> Result<ToolSearchEntry, serde_json::Error> {
|
||||
Ok(ToolSearchEntry {
|
||||
search_text: build_mcp_search_text(info),
|
||||
output: tool_search_result_source_to_loadable_tool_spec(ToolSearchResultSource {
|
||||
server_name: info.server_name.as_str(),
|
||||
tool_namespace: info.callable_namespace.as_str(),
|
||||
tool_name: info.callable_name.as_str(),
|
||||
tool: &info.tool,
|
||||
connector_name: info.connector_name.as_deref(),
|
||||
description: info.namespace_description.as_deref(),
|
||||
})?,
|
||||
output: tool_definition_to_loadable_tool_spec(
|
||||
definition,
|
||||
mcp_tool_search_namespace_description(info),
|
||||
)?,
|
||||
limit_bucket: Some(info.server_name.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
fn dynamic_tool_search_entry(tool: &DynamicToolSpec) -> Result<ToolSearchEntry, serde_json::Error> {
|
||||
pub(crate) fn dynamic_tool_search_entry<R>(
|
||||
tool: &DynamicToolSpec,
|
||||
definition: &ToolDefinition<R>,
|
||||
) -> Result<ToolSearchEntry, serde_json::Error> {
|
||||
Ok(ToolSearchEntry {
|
||||
search_text: build_dynamic_search_text(tool),
|
||||
output: dynamic_tool_to_loadable_tool_spec(tool)?,
|
||||
output: tool_definition_to_loadable_tool_spec(
|
||||
definition, /*namespace_description*/ None,
|
||||
)?,
|
||||
limit_bucket: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn mcp_tool_search_namespace_description(info: &ToolInfo) -> Option<String> {
|
||||
info.namespace_description
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.or_else(|| {
|
||||
info.connector_name
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|connector_name| !connector_name.is_empty())
|
||||
.map(|connector_name| format!("Tools for working with {connector_name}."))
|
||||
})
|
||||
}
|
||||
|
||||
fn build_mcp_search_text(info: &ToolInfo) -> String {
|
||||
let tool_name = info.canonical_tool_name();
|
||||
let mut parts = vec![
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_tool_api::ToolBundle;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolExecutor;
|
||||
|
||||
use crate::ExtensionData;
|
||||
|
||||
@@ -28,8 +30,11 @@ pub trait ContextContributor: Send + Sync {
|
||||
/// Extension contribution that exposes native tools owned by a feature.
|
||||
pub trait ToolContributor: Send + Sync {
|
||||
/// Returns the native tools visible for the supplied extension stores.
|
||||
fn tools(&self, session_store: &ExtensionData, thread_store: &ExtensionData)
|
||||
-> Vec<ToolBundle>;
|
||||
fn tools(
|
||||
&self,
|
||||
session_store: &ExtensionData,
|
||||
thread_store: &ExtensionData,
|
||||
) -> Vec<ToolDefinition<Arc<dyn ToolExecutor>>>;
|
||||
}
|
||||
|
||||
/// Future returned by one ordered turn-item contribution.
|
||||
|
||||
@@ -3,8 +3,8 @@ mod registry;
|
||||
mod state;
|
||||
|
||||
pub use codex_tool_api::FunctionToolSpec;
|
||||
pub use codex_tool_api::ToolBundle;
|
||||
pub use codex_tool_api::ToolCall;
|
||||
pub use codex_tool_api::ToolDefinition;
|
||||
pub use codex_tool_api::ToolError;
|
||||
pub use codex_tool_api::ToolExecutor;
|
||||
pub use codex_tool_api::ToolFuture;
|
||||
|
||||
@@ -13,6 +13,7 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-protocol = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
|
||||
@@ -6,15 +6,22 @@ depend on the tool owner's crate.
|
||||
|
||||
Crates that define contributed tools should depend on this crate. It owns:
|
||||
|
||||
- the executable bundle contract: `ToolBundle`, `ToolExecutor`, `ToolCall`,
|
||||
- the shared definition envelope: `ToolDefinition`, `ToolExposure`, and
|
||||
`ToolName`
|
||||
- the executable runtime contract: `ToolExecutor`, `ToolCall`, `ToolFuture`,
|
||||
and `ToolError`
|
||||
- the one model-visible spec an extension may contribute directly:
|
||||
`FunctionToolSpec`
|
||||
|
||||
The contract is intentionally narrow: contributed tools receive a call id plus
|
||||
raw JSON arguments and return a JSON value. If a feature needs richer host
|
||||
integration, its extension is expected to do that wiring before exposing the
|
||||
tool rather than widening this crate around the hardest native tools.
|
||||
The contract is intentionally narrow: a definition keeps one tool's canonical
|
||||
name, model metadata, exposure mode, and opaque runtime together. Hosts may use
|
||||
that envelope with richer internal metadata, while contributed extension tools
|
||||
use `FunctionToolSpec`. Contributed tools receive a call id plus raw JSON
|
||||
arguments and return a JSON value. If a feature needs richer host integration,
|
||||
its extension is expected to do that wiring before exposing the tool rather than
|
||||
widening this crate around the hardest native tools. For ordinary flat function
|
||||
tools, `ToolDefinition::from_function_spec(...)` derives the canonical tool
|
||||
name from the contributed spec and keeps construction terse.
|
||||
|
||||
The intended dependency direction is:
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::FunctionToolSpec;
|
||||
use crate::ToolCall;
|
||||
use crate::ToolError;
|
||||
|
||||
/// Future returned by one contributed function-tool invocation.
|
||||
pub type ToolFuture<'a> = Pin<Box<dyn Future<Output = Result<Value, ToolError>> + Send + 'a>>;
|
||||
|
||||
/// Model-visible definition plus executable implementation for one contributed
|
||||
/// function tool.
|
||||
#[derive(Clone)]
|
||||
pub struct ToolBundle {
|
||||
spec: FunctionToolSpec,
|
||||
executor: Arc<dyn ToolExecutor>,
|
||||
}
|
||||
|
||||
impl ToolBundle {
|
||||
/// Creates one contributed function-tool bundle.
|
||||
pub fn new(spec: FunctionToolSpec, executor: Arc<dyn ToolExecutor>) -> Self {
|
||||
Self { spec, executor }
|
||||
}
|
||||
|
||||
/// Returns the contributed function-tool spec.
|
||||
pub fn spec(&self) -> &FunctionToolSpec {
|
||||
&self.spec
|
||||
}
|
||||
|
||||
/// Returns the contributed function-tool name.
|
||||
pub fn tool_name(&self) -> &str {
|
||||
self.spec.name.as_str()
|
||||
}
|
||||
|
||||
/// Returns the executable implementation.
|
||||
pub fn executor(&self) -> Arc<dyn ToolExecutor> {
|
||||
Arc::clone(&self.executor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Executable behavior for one contributed function tool.
|
||||
///
|
||||
/// Implementations receive the model-supplied call id and JSON arguments and
|
||||
/// return the JSON value that should be exposed to the model.
|
||||
pub trait ToolExecutor: Send + Sync {
|
||||
fn execute<'a>(&'a self, call: ToolCall) -> ToolFuture<'a>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
use super::ToolBundle;
|
||||
use super::ToolExecutor;
|
||||
use super::ToolFuture;
|
||||
use crate::FunctionToolSpec;
|
||||
use crate::ToolCall;
|
||||
|
||||
struct StubExecutor;
|
||||
|
||||
impl ToolExecutor for StubExecutor {
|
||||
fn execute<'a>(&'a self, _call: ToolCall) -> ToolFuture<'a> {
|
||||
Box::pin(async { Ok(json!({ "ok": true })) })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bundle_derives_name_from_function_spec() {
|
||||
let bundle = ToolBundle::new(
|
||||
FunctionToolSpec {
|
||||
name: "echo".to_string(),
|
||||
description: "Echo arguments.".to_string(),
|
||||
strict: false,
|
||||
parameters: json!({ "type": "object" }),
|
||||
},
|
||||
Arc::new(StubExecutor),
|
||||
);
|
||||
|
||||
assert_eq!(bundle.tool_name(), "echo");
|
||||
}
|
||||
}
|
||||
154
codex-rs/tool-api/src/definition.rs
Normal file
154
codex-rs/tool-api/src/definition.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use codex_protocol::ToolName;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::FunctionToolSpec;
|
||||
|
||||
/// One callable function tool, its exposure mode, and the runtime object that
|
||||
/// executes it.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ToolDefinition<R, S = FunctionToolSpec> {
|
||||
tool_name: ToolName,
|
||||
spec: S,
|
||||
output_schema: Option<Value>,
|
||||
exposure: ToolExposure,
|
||||
runtime: R,
|
||||
}
|
||||
|
||||
/// Whether a tool is advertised immediately or marked for deferred loading.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum ToolExposure {
|
||||
#[default]
|
||||
Published,
|
||||
Deferred,
|
||||
}
|
||||
|
||||
impl<R, S> ToolDefinition<R, S> {
|
||||
/// Creates one immediately-published tool definition.
|
||||
pub fn new(tool_name: ToolName, spec: S, runtime: R) -> Self {
|
||||
Self {
|
||||
tool_name,
|
||||
spec,
|
||||
output_schema: None,
|
||||
exposure: ToolExposure::Published,
|
||||
runtime,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the callable tool name, including any namespace.
|
||||
pub fn tool_name(&self) -> &ToolName {
|
||||
&self.tool_name
|
||||
}
|
||||
|
||||
/// Returns the model-visible metadata bound to this definition.
|
||||
pub fn spec(&self) -> &S {
|
||||
&self.spec
|
||||
}
|
||||
|
||||
/// Returns the optional tool-output schema kept alongside the model spec.
|
||||
pub fn output_schema(&self) -> Option<&Value> {
|
||||
self.output_schema.as_ref()
|
||||
}
|
||||
|
||||
/// Returns how this tool should be exposed to the model.
|
||||
pub fn exposure(&self) -> ToolExposure {
|
||||
self.exposure
|
||||
}
|
||||
|
||||
/// Returns the runtime object bound to this tool definition.
|
||||
pub fn runtime(&self) -> &R {
|
||||
&self.runtime
|
||||
}
|
||||
|
||||
/// Rebinds the same tool definition to a different runtime object.
|
||||
pub fn with_runtime<T>(self, runtime: T) -> ToolDefinition<T, S> {
|
||||
ToolDefinition {
|
||||
tool_name: self.tool_name,
|
||||
spec: self.spec,
|
||||
output_schema: self.output_schema,
|
||||
exposure: self.exposure,
|
||||
runtime,
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks this tool as deferred. Deferred tools intentionally omit output
|
||||
/// schema metadata until they are loaded.
|
||||
pub fn deferred(mut self) -> Self {
|
||||
self.exposure = ToolExposure::Deferred;
|
||||
self.output_schema = None;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> ToolDefinition<R> {
|
||||
/// Creates one immediately-published flat function-tool definition and
|
||||
/// derives its callable name from the function spec.
|
||||
pub fn from_function_spec(spec: FunctionToolSpec, runtime: R) -> Self {
|
||||
let tool_name = ToolName::plain(spec.name.clone());
|
||||
Self::new(tool_name, spec, runtime)
|
||||
}
|
||||
|
||||
/// Attaches a tool-output schema to an ordinary function definition.
|
||||
pub fn with_output_schema(mut self, output_schema: Value) -> Self {
|
||||
self.output_schema = Some(output_schema);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
use super::ToolDefinition;
|
||||
use super::ToolExposure;
|
||||
use crate::FunctionToolSpec;
|
||||
use crate::ToolName;
|
||||
|
||||
fn definition() -> ToolDefinition<()> {
|
||||
ToolDefinition::new(
|
||||
ToolName::namespaced("mcp__calendar", "create_event"),
|
||||
FunctionToolSpec {
|
||||
name: "create_event".to_string(),
|
||||
description: "Create an event.".to_string(),
|
||||
strict: false,
|
||||
parameters: json!({ "type": "object" }),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.with_output_schema(json!({ "type": "object" }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deferred_tools_drop_output_schema() {
|
||||
let definition = definition().deferred();
|
||||
|
||||
assert_eq!(definition.exposure(), ToolExposure::Deferred);
|
||||
assert_eq!(definition.output_schema(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_can_be_rebound_without_rebuilding_metadata() {
|
||||
let definition = definition().with_runtime("handler");
|
||||
|
||||
assert_eq!(
|
||||
definition.tool_name(),
|
||||
&ToolName::namespaced("mcp__calendar", "create_event")
|
||||
);
|
||||
assert_eq!(definition.runtime(), &"handler");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flat_function_definitions_derive_their_tool_name_from_the_spec() {
|
||||
let definition = ToolDefinition::from_function_spec(
|
||||
FunctionToolSpec {
|
||||
name: "echo".to_string(),
|
||||
description: "Echo arguments.".to_string(),
|
||||
strict: false,
|
||||
parameters: json!({ "type": "object" }),
|
||||
},
|
||||
(),
|
||||
);
|
||||
|
||||
assert_eq!(definition.tool_name(), &ToolName::plain("echo"));
|
||||
}
|
||||
}
|
||||
18
codex-rs/tool-api/src/executor.rs
Normal file
18
codex-rs/tool-api/src/executor.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::ToolCall;
|
||||
use crate::ToolError;
|
||||
|
||||
/// Future returned by one contributed function-tool invocation.
|
||||
pub type ToolFuture<'a> = Pin<Box<dyn Future<Output = Result<Value, ToolError>> + Send + 'a>>;
|
||||
|
||||
/// Executable behavior for one contributed function tool.
|
||||
///
|
||||
/// Implementations receive the model-supplied call id and JSON arguments and
|
||||
/// return the JSON value that should be exposed to the model.
|
||||
pub trait ToolExecutor: Send + Sync {
|
||||
fn execute<'a>(&'a self, call: ToolCall) -> ToolFuture<'a>;
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
//! Minimal function-tool contracts shared between hosts and extension-owned
|
||||
//! tool crates.
|
||||
|
||||
mod bundle;
|
||||
mod call;
|
||||
mod definition;
|
||||
mod error;
|
||||
mod executor;
|
||||
mod spec;
|
||||
|
||||
pub use bundle::ToolBundle;
|
||||
pub use bundle::ToolExecutor;
|
||||
pub use bundle::ToolFuture;
|
||||
pub use call::ToolCall;
|
||||
pub use codex_protocol::ToolName;
|
||||
pub use definition::ToolDefinition;
|
||||
pub use definition::ToolExposure;
|
||||
pub use error::ToolError;
|
||||
pub use executor::ToolExecutor;
|
||||
pub use executor::ToolFuture;
|
||||
pub use spec::FunctionToolSpec;
|
||||
|
||||
@@ -12,6 +12,7 @@ codex-app-server-protocol = { workspace = true }
|
||||
codex-code-mode = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-tool-api = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-pty = { workspace = true }
|
||||
rmcp = { workspace = true, default-features = false, features = [
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
use crate::ToolDefinition;
|
||||
use crate::parse_tool_input_schema;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tool_api::FunctionToolSpec;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolName;
|
||||
|
||||
pub fn parse_dynamic_tool(tool: &DynamicToolSpec) -> Result<ToolDefinition, serde_json::Error> {
|
||||
Ok(ToolDefinition {
|
||||
name: tool.name.clone(),
|
||||
description: tool.description.clone(),
|
||||
input_schema: parse_tool_input_schema(&tool.input_schema)?,
|
||||
output_schema: None,
|
||||
defer_loading: tool.defer_loading,
|
||||
})
|
||||
pub fn parse_dynamic_tool(tool: &DynamicToolSpec) -> ToolDefinition<()> {
|
||||
let definition = ToolDefinition::new(
|
||||
ToolName::new(tool.namespace.clone(), tool.name.clone()),
|
||||
FunctionToolSpec {
|
||||
name: tool.name.clone(),
|
||||
description: tool.description.clone(),
|
||||
strict: false,
|
||||
parameters: tool.input_schema.clone(),
|
||||
},
|
||||
(),
|
||||
);
|
||||
|
||||
if tool.defer_loading {
|
||||
definition.deferred()
|
||||
} else {
|
||||
definition
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use super::parse_dynamic_tool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolExposure;
|
||||
use crate::ToolName;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tool_api::FunctionToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn parse_dynamic_tool_sanitizes_input_schema() {
|
||||
fn parse_dynamic_tool_preserves_definition_metadata() {
|
||||
let tool = DynamicToolSpec {
|
||||
namespace: None,
|
||||
name: "lookup_ticket".to_string(),
|
||||
@@ -22,21 +23,23 @@ fn parse_dynamic_tool_sanitizes_input_schema() {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
parse_dynamic_tool(&tool).expect("parse dynamic tool"),
|
||||
ToolDefinition {
|
||||
name: "lookup_ticket".to_string(),
|
||||
description: "Fetch a ticket".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
"id".to_string(),
|
||||
JsonSchema::string(Some("Ticket identifier".to_string()),),
|
||||
)]),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
defer_loading: false,
|
||||
}
|
||||
parse_dynamic_tool(&tool),
|
||||
ToolDefinition::new(
|
||||
ToolName::plain("lookup_ticket"),
|
||||
FunctionToolSpec {
|
||||
name: "lookup_ticket".to_string(),
|
||||
description: "Fetch a ticket".to_string(),
|
||||
strict: false,
|
||||
parameters: serde_json::json!({
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Ticket identifier"
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,18 +56,8 @@ fn parse_dynamic_tool_preserves_defer_loading() {
|
||||
defer_loading: true,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
parse_dynamic_tool(&tool).expect("parse dynamic tool"),
|
||||
ToolDefinition {
|
||||
name: "lookup_ticket".to_string(),
|
||||
description: "Fetch a ticket".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
defer_loading: true,
|
||||
}
|
||||
);
|
||||
let definition = parse_dynamic_tool(&tool);
|
||||
|
||||
assert_eq!(definition.exposure(), ToolExposure::Deferred);
|
||||
assert_eq!(definition.tool_name(), &ToolName::plain("lookup_ticket"));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ mod mcp_tool;
|
||||
mod request_plugin_install;
|
||||
mod responses_api;
|
||||
mod tool_config;
|
||||
mod tool_definition;
|
||||
mod tool_discovery;
|
||||
mod tool_spec;
|
||||
|
||||
@@ -19,6 +18,8 @@ pub use code_mode::collect_code_mode_exec_prompt_tool_definitions;
|
||||
pub use code_mode::collect_code_mode_tool_definitions;
|
||||
pub use code_mode::tool_spec_to_code_mode_tool_definition;
|
||||
pub use codex_protocol::ToolName;
|
||||
pub use codex_tool_api::ToolDefinition;
|
||||
pub use codex_tool_api::ToolExposure;
|
||||
pub use dynamic_tool::parse_dynamic_tool;
|
||||
pub use image_detail::can_request_original_image_detail;
|
||||
pub use image_detail::normalize_output_image_detail;
|
||||
@@ -29,7 +30,7 @@ pub use json_schema::JsonSchemaPrimitiveType;
|
||||
pub use json_schema::JsonSchemaType;
|
||||
pub use json_schema::parse_tool_input_schema;
|
||||
pub use mcp_tool::mcp_call_tool_result_output_schema;
|
||||
pub use mcp_tool::parse_mcp_tool;
|
||||
pub use mcp_tool::mcp_tool_definition;
|
||||
pub use request_plugin_install::REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE;
|
||||
pub use request_plugin_install::REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE;
|
||||
pub use request_plugin_install::REQUEST_PLUGIN_INSTALL_PERSIST_KEY;
|
||||
@@ -50,7 +51,7 @@ pub use responses_api::default_namespace_description;
|
||||
pub use responses_api::dynamic_tool_to_loadable_tool_spec;
|
||||
pub use responses_api::dynamic_tool_to_responses_api_tool;
|
||||
pub use responses_api::mcp_tool_to_deferred_responses_api_tool;
|
||||
pub use responses_api::mcp_tool_to_responses_api_tool;
|
||||
pub use responses_api::tool_definition_to_loadable_tool_spec;
|
||||
pub use responses_api::tool_definition_to_responses_api_tool;
|
||||
pub use tool_config::ShellCommandBackendConfig;
|
||||
pub use tool_config::ToolEnvironmentMode;
|
||||
@@ -60,7 +61,6 @@ 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_definition::ToolDefinition;
|
||||
pub use tool_discovery::DiscoverablePluginInfo;
|
||||
pub use tool_discovery::DiscoverableTool;
|
||||
pub use tool_discovery::DiscoverableToolAction;
|
||||
@@ -69,13 +69,11 @@ pub use tool_discovery::REQUEST_PLUGIN_INSTALL_TOOL_NAME;
|
||||
pub use tool_discovery::RequestPluginInstallEntry;
|
||||
pub use tool_discovery::TOOL_SEARCH_DEFAULT_LIMIT;
|
||||
pub use tool_discovery::TOOL_SEARCH_TOOL_NAME;
|
||||
pub use tool_discovery::ToolSearchResultSource;
|
||||
pub use tool_discovery::ToolSearchSource;
|
||||
pub use tool_discovery::ToolSearchSourceInfo;
|
||||
pub use tool_discovery::collect_request_plugin_install_entries;
|
||||
pub use tool_discovery::collect_tool_search_source_infos;
|
||||
pub use tool_discovery::filter_request_plugin_install_discoverable_tools_for_client;
|
||||
pub use tool_discovery::tool_search_result_source_to_loadable_tool_spec;
|
||||
pub use tool_spec::ResponsesApiWebSearchFilters;
|
||||
pub use tool_spec::ResponsesApiWebSearchUserLocation;
|
||||
pub use tool_spec::ToolSpec;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::ToolDefinition;
|
||||
use crate::parse_tool_input_schema;
|
||||
use codex_tool_api::FunctionToolSpec;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolName;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::json;
|
||||
|
||||
pub fn parse_mcp_tool(tool: &rmcp::model::Tool) -> Result<ToolDefinition, serde_json::Error> {
|
||||
pub fn mcp_tool_definition(tool_name: ToolName, tool: &rmcp::model::Tool) -> ToolDefinition<()> {
|
||||
let mut serialized_input_schema = serde_json::Value::Object(tool.input_schema.as_ref().clone());
|
||||
|
||||
// OpenAI models mandate the "properties" field in the schema. Some MCP
|
||||
@@ -18,22 +19,25 @@ pub fn parse_mcp_tool(tool: &rmcp::model::Tool) -> Result<ToolDefinition, serde_
|
||||
);
|
||||
}
|
||||
|
||||
let input_schema = parse_tool_input_schema(&serialized_input_schema)?;
|
||||
let structured_content_schema = tool
|
||||
.output_schema
|
||||
.as_ref()
|
||||
.map(|output_schema| serde_json::Value::Object(output_schema.as_ref().clone()))
|
||||
.unwrap_or_else(|| JsonValue::Object(serde_json::Map::new()));
|
||||
|
||||
Ok(ToolDefinition {
|
||||
name: tool.name.to_string(),
|
||||
description: tool.description.clone().map(Into::into).unwrap_or_default(),
|
||||
input_schema,
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(
|
||||
structured_content_schema,
|
||||
)),
|
||||
defer_loading: false,
|
||||
})
|
||||
ToolDefinition::new(
|
||||
tool_name.clone(),
|
||||
FunctionToolSpec {
|
||||
name: tool_name.name,
|
||||
description: tool.description.clone().map(Into::into).unwrap_or_default(),
|
||||
strict: false,
|
||||
parameters: serialized_input_schema,
|
||||
},
|
||||
(),
|
||||
)
|
||||
.with_output_schema(mcp_call_tool_result_output_schema(
|
||||
structured_content_schema,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn mcp_call_tool_result_output_schema(structured_content_schema: JsonValue) -> JsonValue {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::mcp_call_tool_result_output_schema;
|
||||
use super::parse_mcp_tool;
|
||||
use crate::JsonSchema;
|
||||
use super::mcp_tool_definition;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolName;
|
||||
use codex_tool_api::FunctionToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> rmcp::model::Tool {
|
||||
rmcp::model::Tool {
|
||||
@@ -30,21 +30,42 @@ fn parse_mcp_tool_inserts_empty_properties() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_mcp_tool(&tool).expect("parse MCP tool"),
|
||||
ToolDefinition {
|
||||
name: "no_props".to_string(),
|
||||
description: "No properties".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({}))),
|
||||
defer_loading: false,
|
||||
}
|
||||
mcp_tool_definition(ToolName::plain("no_props"), &tool),
|
||||
ToolDefinition::new(
|
||||
ToolName::plain("no_props"),
|
||||
FunctionToolSpec {
|
||||
name: "no_props".to_string(),
|
||||
description: "No properties".to_string(),
|
||||
strict: false,
|
||||
parameters: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.with_output_schema(mcp_call_tool_result_output_schema(serde_json::json!({})))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_tool_definition_uses_callable_name() {
|
||||
let tool = mcp_tool(
|
||||
"calendar_create_event",
|
||||
"Create an event",
|
||||
serde_json::json!({
|
||||
"type": "object"
|
||||
}),
|
||||
);
|
||||
|
||||
let definition = mcp_tool_definition(
|
||||
ToolName::namespaced("mcp__codex_apps__calendar", "_create_event"),
|
||||
&tool,
|
||||
);
|
||||
|
||||
assert_eq!(definition.spec().name, "_create_event");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mcp_tool_preserves_top_level_output_schema() {
|
||||
let mut tool = mcp_tool(
|
||||
@@ -68,27 +89,30 @@ fn parse_mcp_tool_preserves_top_level_output_schema() {
|
||||
)));
|
||||
|
||||
assert_eq!(
|
||||
parse_mcp_tool(&tool).expect("parse MCP tool"),
|
||||
ToolDefinition {
|
||||
name: "with_output".to_string(),
|
||||
description: "Has output schema".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({
|
||||
"properties": {
|
||||
"result": {
|
||||
"properties": {
|
||||
"nested": {}
|
||||
}
|
||||
mcp_tool_definition(ToolName::plain("with_output"), &tool),
|
||||
ToolDefinition::new(
|
||||
ToolName::plain("with_output"),
|
||||
FunctionToolSpec {
|
||||
name: "with_output".to_string(),
|
||||
description: "Has output schema".to_string(),
|
||||
strict: false,
|
||||
parameters: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.with_output_schema(mcp_call_tool_result_output_schema(serde_json::json!({
|
||||
"properties": {
|
||||
"result": {
|
||||
"properties": {
|
||||
"nested": {}
|
||||
}
|
||||
},
|
||||
"required": ["result"]
|
||||
}))),
|
||||
defer_loading: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["result"]
|
||||
})))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,19 +132,22 @@ fn parse_mcp_tool_preserves_output_schema_without_inferred_type() {
|
||||
)));
|
||||
|
||||
assert_eq!(
|
||||
parse_mcp_tool(&tool).expect("parse MCP tool"),
|
||||
ToolDefinition {
|
||||
name: "with_enum_output".to_string(),
|
||||
description: "Has enum output schema".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({
|
||||
"enum": ["ok", "error"]
|
||||
}))),
|
||||
defer_loading: false,
|
||||
}
|
||||
mcp_tool_definition(ToolName::plain("with_enum_output"), &tool),
|
||||
ToolDefinition::new(
|
||||
ToolName::plain("with_enum_output"),
|
||||
FunctionToolSpec {
|
||||
name: "with_enum_output".to_string(),
|
||||
description: "Has enum output schema".to_string(),
|
||||
strict: false,
|
||||
parameters: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.with_output_schema(mcp_call_tool_result_output_schema(serde_json::json!({
|
||||
"enum": ["ok", "error"]
|
||||
})))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolName;
|
||||
use crate::mcp_tool_definition;
|
||||
use crate::parse_dynamic_tool;
|
||||
use crate::parse_mcp_tool;
|
||||
use crate::parse_tool_input_schema;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tool_api::ToolDefinition;
|
||||
use codex_tool_api::ToolExposure;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
@@ -69,24 +71,16 @@ pub enum ResponsesApiNamespaceTool {
|
||||
pub fn dynamic_tool_to_responses_api_tool(
|
||||
tool: &DynamicToolSpec,
|
||||
) -> Result<ResponsesApiTool, serde_json::Error> {
|
||||
Ok(tool_definition_to_responses_api_tool(parse_dynamic_tool(
|
||||
tool,
|
||||
)?))
|
||||
tool_definition_to_responses_api_tool(&parse_dynamic_tool(tool))
|
||||
}
|
||||
|
||||
pub fn dynamic_tool_to_loadable_tool_spec(
|
||||
tool: &DynamicToolSpec,
|
||||
) -> Result<LoadableToolSpec, serde_json::Error> {
|
||||
let output_tool = dynamic_tool_to_responses_api_tool(tool)?;
|
||||
Ok(match tool.namespace.as_ref() {
|
||||
Some(namespace) => LoadableToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: namespace.clone(),
|
||||
// the user doesn't provide a description for dynamic tools, so we use the default
|
||||
description: default_namespace_description(namespace),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(output_tool)],
|
||||
}),
|
||||
None => LoadableToolSpec::Function(output_tool),
|
||||
})
|
||||
tool_definition_to_loadable_tool_spec(
|
||||
&parse_dynamic_tool(tool),
|
||||
/*namespace_description*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn coalesce_loadable_tool_specs(
|
||||
@@ -119,35 +113,46 @@ pub fn coalesce_loadable_tool_specs(
|
||||
coalesced_specs
|
||||
}
|
||||
|
||||
pub fn mcp_tool_to_responses_api_tool(
|
||||
tool_name: &ToolName,
|
||||
tool: &rmcp::model::Tool,
|
||||
) -> Result<ResponsesApiTool, serde_json::Error> {
|
||||
Ok(tool_definition_to_responses_api_tool(
|
||||
parse_mcp_tool(tool)?.renamed(tool_name.name.clone()),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn mcp_tool_to_deferred_responses_api_tool(
|
||||
tool_name: &ToolName,
|
||||
tool: &rmcp::model::Tool,
|
||||
) -> Result<ResponsesApiTool, serde_json::Error> {
|
||||
Ok(tool_definition_to_responses_api_tool(
|
||||
parse_mcp_tool(tool)?
|
||||
.renamed(tool_name.name.clone())
|
||||
.into_deferred(),
|
||||
))
|
||||
tool_definition_to_responses_api_tool(&mcp_tool_definition(tool_name.clone(), tool).deferred())
|
||||
}
|
||||
|
||||
pub fn tool_definition_to_responses_api_tool(tool_definition: ToolDefinition) -> ResponsesApiTool {
|
||||
ResponsesApiTool {
|
||||
name: tool_definition.name,
|
||||
description: tool_definition.description,
|
||||
strict: false,
|
||||
defer_loading: tool_definition.defer_loading.then_some(true),
|
||||
parameters: tool_definition.input_schema,
|
||||
output_schema: tool_definition.output_schema,
|
||||
}
|
||||
pub fn tool_definition_to_responses_api_tool<R>(
|
||||
tool_definition: &ToolDefinition<R>,
|
||||
) -> Result<ResponsesApiTool, serde_json::Error> {
|
||||
let spec = tool_definition.spec();
|
||||
debug_assert_eq!(
|
||||
tool_definition.tool_name().name,
|
||||
spec.name,
|
||||
"tool definition name must match its function spec name"
|
||||
);
|
||||
Ok(ResponsesApiTool {
|
||||
name: tool_definition.tool_name().name.clone(),
|
||||
description: spec.description.clone(),
|
||||
strict: spec.strict,
|
||||
defer_loading: matches!(tool_definition.exposure(), ToolExposure::Deferred).then_some(true),
|
||||
parameters: parse_tool_input_schema(&spec.parameters)?,
|
||||
output_schema: tool_definition.output_schema().cloned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tool_definition_to_loadable_tool_spec<R>(
|
||||
tool_definition: &ToolDefinition<R>,
|
||||
namespace_description: Option<String>,
|
||||
) -> Result<LoadableToolSpec, serde_json::Error> {
|
||||
let output_tool = tool_definition_to_responses_api_tool(tool_definition)?;
|
||||
Ok(match tool_definition.tool_name().namespace.as_ref() {
|
||||
Some(namespace) => LoadableToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: namespace.clone(),
|
||||
description: namespace_description
|
||||
.unwrap_or_else(|| default_namespace_description(namespace)),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(output_tool)],
|
||||
}),
|
||||
None => LoadableToolSpec::Function(output_tool),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolName;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tool_api::FunctionToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
@@ -16,20 +17,27 @@ use std::collections::BTreeMap;
|
||||
#[test]
|
||||
fn tool_definition_to_responses_api_tool_omits_false_defer_loading() {
|
||||
assert_eq!(
|
||||
tool_definition_to_responses_api_tool(ToolDefinition {
|
||||
name: "lookup_order".to_string(),
|
||||
description: "Look up an order".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
"order_id".to_string(),
|
||||
JsonSchema::string(/*description*/ None),
|
||||
)]),
|
||||
Some(vec!["order_id".to_string()]),
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: Some(json!({"type": "object"})),
|
||||
defer_loading: false,
|
||||
}),
|
||||
tool_definition_to_responses_api_tool(
|
||||
&ToolDefinition::new(
|
||||
ToolName::plain("lookup_order"),
|
||||
FunctionToolSpec {
|
||||
name: "lookup_order".to_string(),
|
||||
description: "Look up an order".to_string(),
|
||||
strict: false,
|
||||
parameters: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"order_id": {"type": "string"}
|
||||
},
|
||||
"required": ["order_id"],
|
||||
"additionalProperties": false,
|
||||
}),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.with_output_schema(json!({"type": "object"})),
|
||||
)
|
||||
.expect("convert definition"),
|
||||
ResponsesApiTool {
|
||||
name: "lookup_order".to_string(),
|
||||
description: "Look up an order".to_string(),
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
use crate::JsonSchema;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
/// Tool metadata and schemas that downstream crates can adapt into higher-level
|
||||
/// tool specs.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ToolDefinition {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub input_schema: JsonSchema,
|
||||
pub output_schema: Option<JsonValue>,
|
||||
pub defer_loading: bool,
|
||||
}
|
||||
|
||||
impl ToolDefinition {
|
||||
pub fn renamed(mut self, name: String) -> Self {
|
||||
self.name = name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_deferred(mut self) -> Self {
|
||||
self.output_schema = None;
|
||||
self.defer_loading = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tool_definition_tests.rs"]
|
||||
mod tests;
|
||||
@@ -1,43 +0,0 @@
|
||||
use super::ToolDefinition;
|
||||
use crate::JsonSchema;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn tool_definition() -> ToolDefinition {
|
||||
ToolDefinition {
|
||||
name: "lookup_order".to_string(),
|
||||
description: "Look up an order".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None,
|
||||
),
|
||||
output_schema: Some(serde_json::json!({
|
||||
"type": "object",
|
||||
})),
|
||||
defer_loading: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_overrides_name_only() {
|
||||
assert_eq!(
|
||||
tool_definition().renamed("mcp__orders__lookup_order".to_string()),
|
||||
ToolDefinition {
|
||||
name: "mcp__orders__lookup_order".to_string(),
|
||||
..tool_definition()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_deferred_drops_output_schema_and_sets_defer_loading() {
|
||||
assert_eq!(
|
||||
tool_definition().into_deferred(),
|
||||
ToolDefinition {
|
||||
output_schema: None,
|
||||
defer_loading: true,
|
||||
..tool_definition()
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,3 @@
|
||||
use crate::LoadableToolSpec;
|
||||
use crate::ResponsesApiNamespace;
|
||||
use crate::ResponsesApiNamespaceTool;
|
||||
use crate::ToolName;
|
||||
use crate::default_namespace_description;
|
||||
use crate::mcp_tool_to_deferred_responses_api_tool;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -26,16 +20,6 @@ pub struct ToolSearchSource<'a> {
|
||||
pub description: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ToolSearchResultSource<'a> {
|
||||
pub server_name: &'a str,
|
||||
pub tool_namespace: &'a str,
|
||||
pub tool_name: &'a str,
|
||||
pub tool: &'a rmcp::model::Tool,
|
||||
pub connector_name: Option<&'a str>,
|
||||
pub description: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DiscoverableToolType {
|
||||
@@ -133,40 +117,6 @@ pub struct RequestPluginInstallEntry {
|
||||
pub app_connector_ids: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn tool_search_result_source_to_loadable_tool_spec(
|
||||
source: ToolSearchResultSource<'_>,
|
||||
) -> Result<LoadableToolSpec, serde_json::Error> {
|
||||
Ok(LoadableToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: source.tool_namespace.to_string(),
|
||||
description: tool_search_result_source_namespace_description(source),
|
||||
tools: vec![tool_search_result_source_to_namespace_tool(source)?],
|
||||
}))
|
||||
}
|
||||
|
||||
fn tool_search_result_source_namespace_description(source: ToolSearchResultSource<'_>) -> String {
|
||||
source
|
||||
.description
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.or_else(|| {
|
||||
source
|
||||
.connector_name
|
||||
.map(str::trim)
|
||||
.filter(|connector_name| !connector_name.is_empty())
|
||||
.map(|connector_name| format!("Tools for working with {connector_name}."))
|
||||
})
|
||||
.unwrap_or_else(|| default_namespace_description(source.tool_namespace))
|
||||
}
|
||||
|
||||
fn tool_search_result_source_to_namespace_tool(
|
||||
source: ToolSearchResultSource<'_>,
|
||||
) -> Result<ResponsesApiNamespaceTool, serde_json::Error> {
|
||||
let tool_name = ToolName::namespaced(source.tool_namespace, source.tool_name);
|
||||
mcp_tool_to_deferred_responses_api_tool(&tool_name, source.tool)
|
||||
.map(ResponsesApiNamespaceTool::Function)
|
||||
}
|
||||
|
||||
pub fn collect_tool_search_source_infos<'a>(
|
||||
searchable_tools: impl IntoIterator<Item = ToolSearchSource<'a>>,
|
||||
) -> Vec<ToolSearchSourceInfo> {
|
||||
|
||||
Reference in New Issue
Block a user