mirror of
https://github.com/openai/codex.git
synced 2026-10-25 18:28:38 +00:00
Include originating item IDs in MCP request metadata (#40866)
## What changed - Add the originating Responses item ID to MCP tool request metadata as `_meta.itemId`. - Preserve the initiating `exec` item ID across code-mode cells so nested MCP calls carry the same origin. ## Testing - Cover metadata generation for direct and code-mode MCP tool calls. GitOrigin-RevId: 1161cac64f1cc3a6d4dac4368e63804bd9d2c417
This commit is contained in:
@@ -7,6 +7,7 @@ use codex_code_mode::CodeModeNestedToolCall;
|
||||
use codex_code_mode::CodeModeSessionDelegate;
|
||||
use codex_code_mode::NotificationFuture;
|
||||
use codex_code_mode::ToolInvocationFuture;
|
||||
use codex_protocol::ResponseItemId;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use serde_json::Value as JsonValue;
|
||||
@@ -24,7 +25,13 @@ use crate::tools::parallel::ToolCallRuntime;
|
||||
pub(super) struct CodeModeDispatchBroker {
|
||||
dispatch_tx: async_channel::Sender<DispatchMessage>,
|
||||
dispatch_rx: async_channel::Receiver<DispatchMessage>,
|
||||
dispatch_gates: Arc<Mutex<HashMap<CellId, watch::Sender<bool>>>>,
|
||||
dispatch_gates: Arc<Mutex<HashMap<CellId, CellDispatchGate>>>,
|
||||
}
|
||||
|
||||
struct CellDispatchGate {
|
||||
ready: watch::Sender<bool>,
|
||||
// Keep the original exec item when later waits resume this cell.
|
||||
originating_item_id: Option<ResponseItemId>,
|
||||
}
|
||||
|
||||
impl CodeModeDispatchBroker {
|
||||
@@ -37,8 +44,34 @@ impl CodeModeDispatchBroker {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn mark_cell_ready_for_dispatch(&self, cell_id: &CellId) {
|
||||
dispatch_gate(&self.dispatch_gates, cell_id).send_replace(true);
|
||||
pub(super) fn mark_cell_ready_for_dispatch(
|
||||
&self,
|
||||
cell_id: &CellId,
|
||||
originating_item_id: Option<ResponseItemId>,
|
||||
) {
|
||||
let ready = {
|
||||
let mut dispatch_gates = self
|
||||
.dispatch_gates
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let gate = dispatch_gates
|
||||
.entry(cell_id.clone())
|
||||
.or_insert_with(|| CellDispatchGate {
|
||||
ready: watch::channel(false).0,
|
||||
originating_item_id: None,
|
||||
});
|
||||
gate.originating_item_id = originating_item_id;
|
||||
gate.ready.clone()
|
||||
};
|
||||
ready.send_replace(true);
|
||||
}
|
||||
|
||||
pub(super) fn cell_originating_item_id(&self, cell_id: &CellId) -> Option<ResponseItemId> {
|
||||
self.dispatch_gates
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.get(cell_id)
|
||||
.and_then(|gate| gate.originating_item_id.clone())
|
||||
}
|
||||
|
||||
pub(super) fn close_cell(&self, cell_id: &CellId) {
|
||||
@@ -135,7 +168,7 @@ impl CodeModeDispatchBroker {
|
||||
}
|
||||
|
||||
fn dispatch_gate(
|
||||
dispatch_gates: &Mutex<HashMap<CellId, watch::Sender<bool>>>,
|
||||
dispatch_gates: &Mutex<HashMap<CellId, CellDispatchGate>>,
|
||||
cell_id: &CellId,
|
||||
) -> watch::Sender<bool> {
|
||||
let mut dispatch_gates = match dispatch_gates.lock() {
|
||||
@@ -144,12 +177,16 @@ fn dispatch_gate(
|
||||
};
|
||||
dispatch_gates
|
||||
.entry(cell_id.clone())
|
||||
.or_insert_with(|| watch::channel(false).0)
|
||||
.or_insert_with(|| CellDispatchGate {
|
||||
ready: watch::channel(false).0,
|
||||
originating_item_id: None,
|
||||
})
|
||||
.ready
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn remove_dispatch_gate(
|
||||
dispatch_gates: &Mutex<HashMap<CellId, watch::Sender<bool>>>,
|
||||
dispatch_gates: &Mutex<HashMap<CellId, CellDispatchGate>>,
|
||||
cell_id: &CellId,
|
||||
) {
|
||||
let mut dispatch_gates = match dispatch_gates.lock() {
|
||||
@@ -160,7 +197,7 @@ fn remove_dispatch_gate(
|
||||
}
|
||||
|
||||
async fn wait_until_cell_ready_for_dispatch(
|
||||
dispatch_gates: &Mutex<HashMap<CellId, watch::Sender<bool>>>,
|
||||
dispatch_gates: &Mutex<HashMap<CellId, CellDispatchGate>>,
|
||||
cell_id: &CellId,
|
||||
cancellation_token: &CancellationToken,
|
||||
) -> bool {
|
||||
|
||||
@@ -35,6 +35,7 @@ impl CodeModeExecuteHandler {
|
||||
session: std::sync::Arc<crate::session::session::Session>,
|
||||
turn: std::sync::Arc<crate::session::turn_context::TurnContext>,
|
||||
call_id: String,
|
||||
originating_item_id: Option<codex_protocol::ResponseItemId>,
|
||||
code: String,
|
||||
telemetry: &mut CodeModeToolCallGuard,
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
@@ -103,7 +104,7 @@ impl CodeModeExecuteHandler {
|
||||
exec.session
|
||||
.services
|
||||
.code_mode_service
|
||||
.mark_cell_ready_for_dispatch(&cell_id);
|
||||
.mark_cell_ready_for_dispatch(&cell_id, originating_item_id);
|
||||
let response = started_cell
|
||||
.initial_response()
|
||||
.await
|
||||
@@ -155,6 +156,7 @@ impl CodeModeExecuteHandler {
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
|
||||
let originating_item_id = invocation.originating_item_id().await;
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
@@ -174,7 +176,14 @@ impl CodeModeExecuteHandler {
|
||||
);
|
||||
let result = match payload {
|
||||
ToolPayload::Custom { input } if is_exec_tool_name(&tool_name) => self
|
||||
.execute(session, turn, call_id, input, &mut telemetry)
|
||||
.execute(
|
||||
session,
|
||||
turn,
|
||||
call_id,
|
||||
originating_item_id,
|
||||
input,
|
||||
&mut telemetry,
|
||||
)
|
||||
.await
|
||||
.map(boxed_tool_output),
|
||||
_ => Err(FunctionCallError::RespondToModel(format!(
|
||||
|
||||
@@ -176,8 +176,20 @@ impl CodeModeService {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mark_cell_ready_for_dispatch(&self, cell_id: &codex_code_mode::CellId) {
|
||||
self.dispatch_broker.mark_cell_ready_for_dispatch(cell_id);
|
||||
pub(crate) fn mark_cell_ready_for_dispatch(
|
||||
&self,
|
||||
cell_id: &codex_code_mode::CellId,
|
||||
originating_item_id: Option<codex_protocol::ResponseItemId>,
|
||||
) {
|
||||
self.dispatch_broker
|
||||
.mark_cell_ready_for_dispatch(cell_id, originating_item_id);
|
||||
}
|
||||
|
||||
pub(crate) fn cell_originating_item_id(
|
||||
&self,
|
||||
cell_id: &codex_code_mode::CellId,
|
||||
) -> Option<codex_protocol::ResponseItemId> {
|
||||
self.dispatch_broker.cell_originating_item_id(cell_id)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_cell_dispatch(&self, cell_id: &CellId) {
|
||||
|
||||
@@ -6,11 +6,13 @@ use crate::session::turn_context::TurnContext;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use crate::unified_exec::format_output_omission_marker;
|
||||
use crate::unified_exec::resolve_max_tokens;
|
||||
use codex_protocol::ResponseItemId;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_tools::LoadableToolSpec;
|
||||
use codex_tools::ToolName;
|
||||
@@ -66,6 +68,34 @@ pub struct ToolInvocation {
|
||||
pub payload: ToolPayload,
|
||||
}
|
||||
|
||||
impl ToolInvocation {
|
||||
/// Returns the Responses item that requested this call or started its code-mode cell.
|
||||
pub(crate) async fn originating_item_id(&self) -> Option<ResponseItemId> {
|
||||
if let ToolCallSource::CodeMode { cell_id, .. } = &self.source {
|
||||
return self
|
||||
.session
|
||||
.services
|
||||
.code_mode_service
|
||||
.cell_originating_item_id(&codex_code_mode::CellId::new(cell_id.clone()));
|
||||
}
|
||||
|
||||
self.session
|
||||
.clone_history()
|
||||
.await
|
||||
.raw_items()
|
||||
.rev()
|
||||
.find_map(|item| match item {
|
||||
ResponseItem::FunctionCall { id, call_id, .. }
|
||||
| ResponseItem::CustomToolCall { id, call_id, .. }
|
||||
if call_id == &self.call_id =>
|
||||
{
|
||||
id.clone()
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct McpToolOutput {
|
||||
pub result: CallToolResult,
|
||||
|
||||
@@ -171,6 +171,7 @@ impl McpHandler {
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
|
||||
let originating_item_id = invocation.originating_item_id().await;
|
||||
let ToolInvocation {
|
||||
session,
|
||||
step_context,
|
||||
@@ -197,6 +198,7 @@ impl McpHandler {
|
||||
&step_context,
|
||||
&cancellation_token,
|
||||
call_id.clone(),
|
||||
originating_item_id,
|
||||
&self.tool_info,
|
||||
self.hook_tool_name(),
|
||||
tool_name,
|
||||
|
||||
Reference in New Issue
Block a user