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
9 changes: 5 additions & 4 deletions codex-rs/core/src/codex_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ use codex_app_server_protocol::AppInfo;
use codex_otel::TelemetryAuthMode;
use codex_protocol::models::BaseInstructions;
use codex_protocol::models::ContentItem;
use codex_protocol::models::McpToolOutput;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ModelsResponse;
Expand Down Expand Up @@ -1607,7 +1608,7 @@ fn prefers_structured_content_when_present() {
meta: None,
};

let got = FunctionCallOutputPayload::from(&ctr);
let got = McpToolOutput::from(&ctr).into_function_call_output_payload();
let expected = FunctionCallOutputPayload {
body: FunctionCallOutputBody::Text(
serde_json::to_string(&json!({
Expand Down Expand Up @@ -1689,7 +1690,7 @@ fn falls_back_to_content_when_structured_is_null() {
meta: None,
};

let got = FunctionCallOutputPayload::from(&ctr);
let got = McpToolOutput::from(&ctr).into_function_call_output_payload();
let expected = FunctionCallOutputPayload {
body: FunctionCallOutputBody::Text(
serde_json::to_string(&vec![text_block("hello"), text_block("world")]).unwrap(),
Expand All @@ -1709,7 +1710,7 @@ fn success_flag_reflects_is_error_true() {
meta: None,
};

let got = FunctionCallOutputPayload::from(&ctr);
let got = McpToolOutput::from(&ctr).into_function_call_output_payload();
let expected = FunctionCallOutputPayload {
body: FunctionCallOutputBody::Text(
serde_json::to_string(&json!({ "message": "bad" })).unwrap(),
Expand All @@ -1729,7 +1730,7 @@ fn success_flag_true_with_no_error_and_content_used() {
meta: None,
};

let got = FunctionCallOutputPayload::from(&ctr);
let got = McpToolOutput::from(&ctr).into_function_call_output_payload();
let expected = FunctionCallOutputPayload {
body: FunctionCallOutputBody::Text(
serde_json::to_string(&vec![text_block("alpha")]).unwrap(),
Expand Down
20 changes: 6 additions & 14 deletions codex-rs/core/src/mcp_tool_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ use crate::protocol::McpToolCallBeginEvent;
use crate::protocol::McpToolCallEndEvent;
use crate::state_db;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::McpToolOutput;
use codex_protocol::openai_models::InputModality;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ReviewDecision;
Expand All @@ -58,7 +56,7 @@ pub(crate) async fn handle_mcp_tool_call(
server: String,
tool_name: String,
arguments: String,
) -> ResponseInputItem {
) -> McpToolOutput {
// Parse the `arguments` as JSON. An empty string is OK, but invalid JSON
// is not.
let arguments_value = if arguments.trim().is_empty() {
Expand All @@ -68,13 +66,7 @@ pub(crate) async fn handle_mcp_tool_call(
Ok(value) => Some(value),
Err(e) => {
error!("failed to parse tool call arguments: {e}");
return ResponseInputItem::FunctionCallOutput {
call_id: call_id.clone(),
output: FunctionCallOutputPayload {
body: FunctionCallOutputBody::Text(format!("err: {e}")),
success: Some(false),
},
};
return McpToolOutput::from_error_text(format!("err: {e}"));
}
}
};
Expand Down Expand Up @@ -118,7 +110,7 @@ pub(crate) async fn handle_mcp_tool_call(
turn_context
.session_telemetry
.counter("codex.mcp.call", 1, &[("status", status)]);
return ResponseInputItem::McpToolCallOutput { call_id, result };
return McpToolOutput::from_result(result);
}

if let Some(decision) = maybe_request_mcp_tool_approval(
Expand Down Expand Up @@ -212,7 +204,7 @@ pub(crate) async fn handle_mcp_tool_call(
.session_telemetry
.counter("codex.mcp.call", 1, &[("status", status)]);

return ResponseInputItem::McpToolCallOutput { call_id, result };
return McpToolOutput::from_result(result);
}

let tool_call_begin_event = EventMsg::McpToolCallBegin(McpToolCallBeginEvent {
Expand Down Expand Up @@ -258,7 +250,7 @@ pub(crate) async fn handle_mcp_tool_call(
.session_telemetry
.counter("codex.mcp.call", 1, &[("status", status)]);

ResponseInputItem::McpToolCallOutput { call_id, result }
McpToolOutput::from_result(result)
}

async fn maybe_mark_thread_memory_mode_polluted(sess: &Session, turn_context: &TurnContext) {
Expand Down
10 changes: 2 additions & 8 deletions codex-rs/core/src/stream_events_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,8 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti
output: output.clone(),
})
}
ResponseInputItem::McpToolCallOutput { call_id, result } => {
let output = match result {
Ok(call_tool_result) => FunctionCallOutputPayload::from(call_tool_result),
Err(err) => FunctionCallOutputPayload {
body: FunctionCallOutputBody::Text(err.clone()),
success: Some(false),
},
};
ResponseInputItem::McpToolCallOutput { call_id, output } => {
let output = output.as_function_call_output_payload();
Some(ResponseItem::FunctionCallOutput {
call_id: call_id.clone(),
output,
Expand Down
23 changes: 10 additions & 13 deletions codex-rs/core/src/tools/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use crate::truncate::TruncationPolicy;
use crate::truncate::formatted_truncate_text;
use crate::turn_diff_tracker::TurnDiffTracker;
use crate::unified_exec::resolve_max_tokens;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::McpToolOutput;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ShellToolCallParams;
use codex_protocol::models::function_call_output_content_items_to_text;
Expand Down Expand Up @@ -82,23 +82,21 @@ pub trait ToolOutput: Send {
}
}

pub struct McpToolOutput {
pub result: Result<CallToolResult, String>,
}

impl ToolOutput for McpToolOutput {
fn log_preview(&self) -> String {
format!("{:?}", self.result)
let output = self.as_function_call_output_payload();
let preview = output.body.to_text().unwrap_or_else(|| output.to_string());
telemetry_preview(&preview)
}

fn success_for_logging(&self) -> bool {
self.result.is_ok()
self.success
}

fn to_response_item(&self, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem {
ResponseInputItem::McpToolCallOutput {
call_id: call_id.to_string(),
result: self.result.clone(),
output: self.clone(),
}
}
}
Expand Down Expand Up @@ -273,15 +271,14 @@ fn response_input_to_code_mode_result(response: ResponseInputItem) -> JsonValue
content_items_to_code_mode_result(&items)
}
},
ResponseInputItem::McpToolCallOutput { result, .. } => match result {
Ok(result) => match FunctionCallOutputPayload::from(&result).body {
ResponseInputItem::McpToolCallOutput { output, .. } => {
match output.as_function_call_output_payload().body {
FunctionCallOutputBody::Text(text) => JsonValue::String(text),
FunctionCallOutputBody::ContentItems(items) => {
content_items_to_code_mode_result(&items)
}
},
Err(error) => JsonValue::String(error),
},
}
}
}
}

Expand Down
59 changes: 4 additions & 55 deletions codex-rs/core/src/tools/handlers/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,16 @@ use std::sync::Arc;

use crate::function_tool::FunctionCallError;
use crate::mcp_tool_call::handle_mcp_tool_call;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::McpToolOutput;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolKind;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::McpToolOutput;

pub struct McpHandler;

pub enum McpHandlerOutput {
Mcp(McpToolOutput),
Function(FunctionToolOutput),
}

impl crate::tools::context::ToolOutput for McpHandlerOutput {
fn log_preview(&self) -> String {
match self {
Self::Mcp(output) => output.log_preview(),
Self::Function(output) => output.log_preview(),
}
}

fn success_for_logging(&self) -> bool {
match self {
Self::Mcp(output) => output.success_for_logging(),
Self::Function(output) => output.success_for_logging(),
}
}

fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {
match self {
Self::Mcp(output) => output.to_response_item(call_id, payload),
Self::Function(output) => output.to_response_item(call_id, payload),
}
}
}

#[async_trait]
impl ToolHandler for McpHandler {
type Output = McpHandlerOutput;
type Output = McpToolOutput;

fn kind(&self) -> ToolKind {
ToolKind::Mcp
Expand Down Expand Up @@ -74,7 +43,7 @@ impl ToolHandler for McpHandler {
let (server, tool, raw_arguments) = payload;
let arguments_str = raw_arguments;

let response = handle_mcp_tool_call(
let output = handle_mcp_tool_call(
Arc::clone(&session),
&turn,
call_id.clone(),
Expand All @@ -84,26 +53,6 @@ impl ToolHandler for McpHandler {
)
.await;

match response {
ResponseInputItem::McpToolCallOutput { result, .. } => {
Ok(McpHandlerOutput::Mcp(McpToolOutput { result }))
}
ResponseInputItem::FunctionCallOutput { output, .. } => {
let success = output.success;
match output.body {
codex_protocol::models::FunctionCallOutputBody::Text(text) => Ok(
McpHandlerOutput::Function(FunctionToolOutput::from_text(text, success)),
),
codex_protocol::models::FunctionCallOutputBody::ContentItems(content) => {
Ok(McpHandlerOutput::Function(
FunctionToolOutput::from_content(content, success),
))
}
}
}
_ => Err(FunctionCallError::RespondToModel(
"mcp handler received unexpected response variant".to_string(),
)),
}
Ok(output)
}
}
40 changes: 17 additions & 23 deletions codex-rs/core/src/tools/js_repl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,29 +620,23 @@ impl JsReplManager {
output,
)
}
ResponseInputItem::McpToolCallOutput { result, .. } => match result {
Ok(result) => {
let output = FunctionCallOutputPayload::from(result);
let mut summary = Self::summarize_function_output_payload(
"mcp_tool_call_output",
JsReplToolCallPayloadKind::McpResult,
&output,
);
summary.payload_item_count = Some(result.content.len());
summary.structured_content_present = Some(result.structured_content.is_some());
summary.result_is_error = Some(result.is_error.unwrap_or(false));
summary
}
Err(error) => {
let mut summary = Self::summarize_text_payload(
Some("mcp_tool_call_output"),
JsReplToolCallPayloadKind::McpErrorResult,
error,
);
summary.result_is_error = Some(true);
summary
}
},
ResponseInputItem::McpToolCallOutput { output, .. } => {
let function_output = output.as_function_call_output_payload();
let payload_kind = if output.success {
JsReplToolCallPayloadKind::McpResult
} else {
JsReplToolCallPayloadKind::McpErrorResult
};
let mut summary = Self::summarize_function_output_payload(
"mcp_tool_call_output",
payload_kind,
&function_output,
);
summary.payload_item_count = Some(output.content.len());
summary.structured_content_present = Some(output.structured_content.is_some());
summary.result_is_error = Some(!output.success);
summary
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion codex-rs/core/src/tools/parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ impl ToolCallRuntime {
},
ToolPayload::Mcp { .. } => ResponseInputItem::McpToolCallOutput {
call_id: call.call_id.clone(),
result: Err(Self::abort_message(call, secs)),
output: codex_protocol::models::McpToolOutput::from_error_text(
Self::abort_message(call, secs),
),
},
_ => ResponseInputItem::FunctionCallOutput {
call_id: call.call_id.clone(),
Expand Down
Loading
Loading