Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions codex-rs/codex-mcp/src/mcp_connection_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1191,11 +1191,14 @@ impl McpConnectionManager {
.with_context(|| format!("resources/read failed for `{server}` ({uri})"))
}

pub async fn parse_tool_name(&self, tool_name: &str) -> Option<(String, String)> {
self.list_all_tools()
.await
.get(tool_name)
.map(|tool| (tool.server_name.clone(), tool.tool.name.to_string()))
pub async fn resolve_tool_info(&self, name: &str, namespace: Option<&str>) -> Option<ToolInfo> {
let qualified_name = match namespace {
Some(namespace) if name.starts_with(namespace) => name.to_string(),
Some(namespace) => format!("{namespace}{name}"),
None => name.to_string(),
};

self.list_all_tools().await.get(&qualified_name).cloned()
}

pub async fn notify_sandbox_state_change(&self, sandbox_state: &SandboxState) -> Result<()> {
Expand Down
18 changes: 5 additions & 13 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
use codex_login::default_client::originator;
use codex_mcp::McpConnectionManager;
use codex_mcp::SandboxState;
use codex_mcp::ToolInfo;
use codex_mcp::codex_apps_tools_cache_key;
#[cfg(test)]
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
Expand Down Expand Up @@ -4412,25 +4413,16 @@ impl Session {
.await
}

pub(crate) async fn parse_mcp_tool_name(
pub(crate) async fn resolve_mcp_tool_info(
&self,
name: &str,
namespace: &Option<String>,
) -> Option<(String, String)> {
let tool_name = if let Some(namespace) = namespace {
if name.starts_with(namespace.as_str()) {
name
} else {
&format!("{namespace}{name}")
}
} else {
name
};
namespace: Option<&str>,
) -> Option<ToolInfo> {
self.services
.mcp_connection_manager
.read()
.await
.parse_tool_name(tool_name)
.resolve_tool_info(name, namespace)
.await
}

Expand Down
6 changes: 2 additions & 4 deletions codex-rs/core/src/codex_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5618,8 +5618,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
turn: Arc::clone(&turn_context),
tracker: Arc::clone(&turn_diff_tracker),
call_id,
tool_name: tool_name.to_string(),
tool_namespace: None,
tool_name: codex_tools::ToolName::plain(tool_name),
payload: ToolPayload::Function {
arguments: serde_json::json!({
"command": params.command.clone(),
Expand Down Expand Up @@ -5697,8 +5696,7 @@ async fn unified_exec_rejects_escalated_permissions_when_policy_not_on_request()
turn: Arc::clone(&turn_context),
tracker: Arc::clone(&tracker),
call_id: "exec-call".to_string(),
tool_name: "exec_command".to_string(),
tool_namespace: None,
tool_name: codex_tools::ToolName::plain("exec_command"),
payload: ToolPayload::Function {
arguments: serde_json::json!({
"cmd": "echo hi",
Expand Down
9 changes: 3 additions & 6 deletions codex-rs/core/src/codex_tests_guardian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
turn: Arc::clone(&turn_context),
tracker: Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())),
call_id: "test-call".to_string(),
tool_name: "shell".to_string(),
tool_namespace: None,
tool_name: codex_tools::ToolName::plain("shell"),
payload: ToolPayload::Function {
arguments: serde_json::json!({
"command": params.command.clone(),
Expand Down Expand Up @@ -211,8 +210,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic
turn: Arc::clone(&turn_context),
tracker: Arc::clone(&tracker),
call_id: "exec-call".to_string(),
tool_name: "exec_command".to_string(),
tool_namespace: None,
tool_name: codex_tools::ToolName::plain("exec_command"),
payload: ToolPayload::Function {
arguments: serde_json::json!({
"cmd": "echo hi",
Expand Down Expand Up @@ -325,8 +323,7 @@ async fn shell_handler_allows_sticky_turn_permissions_without_inline_request_per
turn: Arc::clone(&turn_context),
tracker: Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())),
call_id: "sticky-turn-grant".to_string(),
tool_name: "shell".to_string(),
tool_namespace: None,
tool_name: codex_tools::ToolName::plain("shell"),
payload: ToolPayload::Function {
arguments: serde_json::json!({
"command": [
Expand Down
16 changes: 10 additions & 6 deletions codex-rs/core/src/memories/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ pub(crate) async fn emit_metric_for_tool_read(invocation: &ToolInvocation, succe
}

let success = if success { "true" } else { "false" };
let tool_name = invocation.tool_name.display();
for kind in kinds {
invocation.turn.session_telemetry.counter(
MEMORIES_USAGE_METRIC,
/*inc*/ 1,
&[
("kind", kind.as_tag()),
("tool", invocation.tool_name.as_str()),
("tool", &tool_name),
("success", success),
],
);
Expand Down Expand Up @@ -77,16 +78,19 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
return None;
};

match invocation.tool_name.as_str() {
"shell" => serde_json::from_str::<ShellToolCallParams>(arguments)
match (
invocation.tool_name.namespace.as_deref(),
invocation.tool_name.name.as_str(),
) {
(None, "shell") => serde_json::from_str::<ShellToolCallParams>(arguments)
.ok()
.map(|params| {
(
params.command,
invocation.turn.resolve_path(params.workdir).to_path_buf(),
)
}),
"shell_command" => serde_json::from_str::<ShellCommandToolCallParams>(arguments)
(None, "shell_command") => serde_json::from_str::<ShellCommandToolCallParams>(arguments)
.ok()
.map(|params| {
if !invocation.turn.tools_config.allow_login_shell && params.login == Some(true) {
Expand All @@ -107,7 +111,7 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
invocation.turn.resolve_path(params.workdir).to_path_buf(),
)
}),
"exec_command" => serde_json::from_str::<ExecCommandArgs>(arguments)
(None, "exec_command") => serde_json::from_str::<ExecCommandArgs>(arguments)
.ok()
.and_then(|params| {
let command = crate::tools::handlers::unified_exec::get_command(
Expand All @@ -122,7 +126,7 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
invocation.turn.resolve_path(params.workdir).to_path_buf(),
))
}),
_ => None,
(Some(_), _) | (None, _) => None,
}
}

Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/stream_events_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ pub(crate) async fn handle_output_item_done(
tracing::info!(
thread_id = %ctx.sess.conversation_id,
"ToolCall: {} {}",
call.tool_name,
Comment thread
sayan-oai marked this conversation as resolved.
call.tool_name.display(),
payload_preview
);

Expand Down
4 changes: 3 additions & 1 deletion codex-rs/core/src/tools/code_mode/execute_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ impl ToolHandler for CodeModeExecuteHandler {
} = invocation;

match payload {
ToolPayload::Custom { input } if tool_name == PUBLIC_TOOL_NAME => {
ToolPayload::Custom { input }
if tool_name.namespace.is_none() && tool_name.name.as_str() == PUBLIC_TOOL_NAME =>
{
self.execute(session, turn, call_id, input).await
}
_ => Err(FunctionCallError::RespondToModel(format!(
Expand Down
39 changes: 21 additions & 18 deletions codex-rs/core/src/tools/code_mode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use crate::tools::router::ToolCallSource;
use crate::tools::router::ToolRouterParams;
use crate::unified_exec::resolve_max_tokens;
use codex_features::Feature;
use codex_tools::ToolName;
use codex_tools::ToolSpec;
use codex_tools::collect_code_mode_tool_definitions;
use codex_utils_output_truncation::TruncationPolicy;
Expand Down Expand Up @@ -283,27 +284,29 @@ async fn call_nested_tool(
)));
}

let payload =
if let Some((server, tool)) = exec.session.parse_mcp_tool_name(&tool_name, &None).await {
match serialize_function_tool_arguments(&tool_name, input) {
Ok(raw_arguments) => ToolPayload::Mcp {
server,
tool,
raw_arguments,
},
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
}
} else {
match build_nested_tool_payload(tool_runtime.find_spec(&tool_name), &tool_name, input) {
Ok(payload) => payload,
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
}
};
let payload = if let Some(tool_info) = exec
.session
.resolve_mcp_tool_info(&tool_name, /*namespace*/ None)
.await
{
match serialize_function_tool_arguments(&tool_name, input) {
Ok(raw_arguments) => ToolPayload::Mcp {
server: tool_info.server_name,
tool: tool_info.tool.name.to_string(),
raw_arguments,
},
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
}
} else {
match build_nested_tool_payload(tool_runtime.find_spec(&tool_name), &tool_name, input) {
Ok(payload) => payload,
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
}
};

let call = ToolCall {
tool_name: tool_name.clone(),
tool_name: ToolName::plain(tool_name.clone()),
call_id: format!("{PUBLIC_TOOL_NAME}-{}", uuid::Uuid::new_v4()),
tool_namespace: None,
payload,
};
let result = tool_runtime
Expand Down
4 changes: 3 additions & 1 deletion codex-rs/core/src/tools/code_mode/wait_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ impl ToolHandler for CodeModeWaitHandler {
} = invocation;

match payload {
ToolPayload::Function { arguments } if tool_name == WAIT_TOOL_NAME => {
ToolPayload::Function { arguments }
if tool_name.namespace.is_none() && tool_name.name.as_str() == WAIT_TOOL_NAME =>
{
let args: ExecWaitArgs = parse_arguments(&arguments)?;
let exec = ExecContext { session, turn };
let started_at = std::time::Instant::now();
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/core/src/tools/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::SearchToolCallParams;
use codex_protocol::models::ShellToolCallParams;
use codex_protocol::models::function_call_output_content_items_to_text;
use codex_tools::ToolName;
use codex_tools::ToolSearchOutputTool;
use codex_utils_output_truncation::TruncationPolicy;
use codex_utils_output_truncation::formatted_truncate_text;
Expand All @@ -39,8 +40,7 @@ pub struct ToolInvocation {
pub turn: Arc<TurnContext>,
pub tracker: SharedTurnDiffTracker,
pub call_id: String,
pub tool_name: String,
pub tool_namespace: Option<String>,
pub tool_name: ToolName,
pub payload: ToolPayload,
}

Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/tools/handlers/agent_jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ impl ToolHandler for BatchJobHandler {
}
};

match tool_name.as_str() {
match tool_name.name.as_str() {
"spawn_agents_on_csv" => spawn_agents_on_csv::handle(session, turn, arguments).await,
"report_agent_job_result" => report_agent_job_result::handle(session, arguments).await,
other => Err(FunctionCallError::RespondToModel(format!(
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/tools/handlers/apply_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ impl ToolHandler for ApplyPatchHandler {
session: session.clone(),
turn: turn.clone(),
call_id: call_id.clone(),
tool_name: tool_name.to_string(),
tool_name: tool_name.display(),
};
let out = orchestrator
.run(
Expand Down
15 changes: 8 additions & 7 deletions codex-rs/core/src/tools/handlers/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ impl ToolHandler for DynamicToolHandler {
};

let args: Value = parse_arguments(&arguments)?;
let response = request_dynamic_tool(&session, turn.as_ref(), call_id, tool_name, args)
.await
.ok_or_else(|| {
FunctionCallError::RespondToModel(
"dynamic tool call was cancelled before receiving a response".to_string(),
)
})?;
let response =
request_dynamic_tool(&session, turn.as_ref(), call_id, tool_name.display(), args)
.await
.ok_or_else(|| {
FunctionCallError::RespondToModel(
"dynamic tool call was cancelled before receiving a response".to_string(),
)
})?;

let DynamicToolResponse {
content_items,
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/tools/handlers/mcp_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ impl ToolHandler for McpResourceHandler {

let arguments_value = parse_arguments(arguments.as_str())?;

match tool_name.as_str() {
match tool_name.name.as_str() {
"list_mcp_resources" => {
handle_list_resources(
Arc::clone(&session),
Expand Down
3 changes: 1 addition & 2 deletions codex-rs/core/src/tools/handlers/multi_agents_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ fn invocation(
turn,
tracker: Arc::new(Mutex::new(TurnDiffTracker::default())),
call_id: "call-1".to_string(),
tool_name: tool_name.to_string(),
tool_namespace: None,
tool_name: codex_tools::ToolName::plain(tool_name),
payload,
}
}
Expand Down
3 changes: 1 addition & 2 deletions codex-rs/core/src/tools/handlers/request_user_input_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ async fn multi_agent_v2_request_user_input_rejects_subagent_threads() {
turn: Arc::new(turn),
tracker: Arc::new(Mutex::new(TurnDiffTracker::default())),
call_id: "call-1".to_string(),
tool_name: REQUEST_USER_INPUT_TOOL_NAME.to_string(),
tool_namespace: None,
tool_name: codex_tools::ToolName::plain(REQUEST_USER_INPUT_TOOL_NAME),
payload: ToolPayload::Function {
arguments: json!({
"questions": [{
Expand Down
12 changes: 7 additions & 5 deletions codex-rs/core/src/tools/handlers/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl ToolHandler for ShellHandler {
let exec_params =
Self::to_exec_params(&params, turn.as_ref(), session.conversation_id);
Self::run_exec_like(RunExecLikeArgs {
tool_name: tool_name.clone(),
tool_name: tool_name.display(),
exec_params,
additional_permissions: params.additional_permissions.clone(),
prefix_rule,
Expand All @@ -256,7 +256,7 @@ impl ToolHandler for ShellHandler {
let exec_params =
Self::to_exec_params(&params, turn.as_ref(), session.conversation_id);
Self::run_exec_like(RunExecLikeArgs {
tool_name: tool_name.clone(),
tool_name: tool_name.display(),
exec_params,
additional_permissions: None,
prefix_rule: None,
Expand All @@ -270,7 +270,8 @@ impl ToolHandler for ShellHandler {
.await
}
_ => Err(FunctionCallError::RespondToModel(format!(
"unsupported payload for shell handler: {tool_name}"
"unsupported payload for shell handler: {}",
tool_name.display()
))),
}
}
Expand Down Expand Up @@ -339,7 +340,8 @@ impl ToolHandler for ShellCommandHandler {

let ToolPayload::Function { arguments } = payload else {
return Err(FunctionCallError::RespondToModel(format!(
"unsupported payload for shell_command handler: {tool_name}"
"unsupported payload for shell_command handler: {}",
tool_name.display()
)));
};

Expand All @@ -362,7 +364,7 @@ impl ToolHandler for ShellCommandHandler {
turn.tools_config.allow_login_shell,
)?;
ShellHandler::run_exec_like(RunExecLikeArgs {
tool_name,
tool_name: tool_name.display(),
exec_params,
additional_permissions: params.additional_permissions.clone(),
prefix_rule,
Expand Down
Loading
Loading