mirror of
https://github.com/openai/codex.git
synced 2026-10-27 16:02:59 +00:00
feat(core): emit canonical dynamic tool call items (#31298)
This PR depends on [#31296](https://github.com/openai/codex/pull/31296) for the canonical-to-legacy event mappings. ## Description This PR makes dynamic tools emit canonical `TurnItem::DynamicToolCall` lifecycle instead of `DynamicToolCallRequest` / `DynamicToolCallResponse` directly. App-server v2 now sends the client `DynamicToolCall` request from the canonical item start. It ignores the mapped legacy request/response events, so clients receive one item start and one tool request. ## Why Dynamic tools are a separate migration boundary because their start event also drives a client request. Keeping that routing change isolated makes it easier to verify that the request still happens exactly once. ## What changed - Emit in-progress and completed/failed dynamic tool items from the dynamic tool handler. - Move app-server client request dispatch onto canonical dynamic item starts. - Add focused app-server coverage for the canonical start notification and client request.
This commit is contained in:
@@ -9,14 +9,13 @@ use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::CoreToolRuntime;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolExposure;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallRequest;
|
||||
use codex_protocol::dynamic_tools::DynamicToolFunctionSpec;
|
||||
use codex_protocol::dynamic_tools::DynamicToolNamespaceSpec;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
||||
use codex_protocol::items::DynamicToolCallItem;
|
||||
use codex_protocol::items::DynamicToolCallStatus;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::protocol::DynamicToolCallResponseEvent;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ToolName;
|
||||
@@ -178,7 +177,6 @@ async fn request_dynamic_tool(
|
||||
) -> Option<DynamicToolResponse> {
|
||||
let namespace = tool_name.namespace;
|
||||
let tool = tool_name.name;
|
||||
let turn_id = turn_context.sub_id.clone();
|
||||
let (tx_response, rx_response) = oneshot::channel();
|
||||
let event_id = call_id.clone();
|
||||
let prev_entry = {
|
||||
@@ -196,45 +194,55 @@ async fn request_dynamic_tool(
|
||||
}
|
||||
|
||||
let started_at = Instant::now();
|
||||
let started_at_ms = now_unix_timestamp_ms();
|
||||
let event = EventMsg::DynamicToolCallRequest(DynamicToolCallRequest {
|
||||
call_id: call_id.clone(),
|
||||
turn_id: turn_id.clone(),
|
||||
started_at_ms,
|
||||
namespace: namespace.clone(),
|
||||
tool: tool.clone(),
|
||||
arguments: arguments.clone(),
|
||||
});
|
||||
session.send_event(turn_context, event).await;
|
||||
session
|
||||
.emit_turn_item_started(
|
||||
turn_context,
|
||||
&TurnItem::DynamicToolCall(DynamicToolCallItem {
|
||||
id: call_id.clone(),
|
||||
namespace: namespace.clone(),
|
||||
tool: tool.clone(),
|
||||
arguments: arguments.clone(),
|
||||
status: DynamicToolCallStatus::InProgress,
|
||||
content_items: None,
|
||||
success: None,
|
||||
error: None,
|
||||
duration: None,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let response = rx_response.await.ok();
|
||||
|
||||
let response_event = match &response {
|
||||
Some(response) => EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
|
||||
call_id,
|
||||
turn_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
let item = match &response {
|
||||
Some(response) => DynamicToolCallItem {
|
||||
id: call_id,
|
||||
namespace,
|
||||
tool,
|
||||
arguments,
|
||||
content_items: response.content_items.clone(),
|
||||
success: response.success,
|
||||
status: if response.success {
|
||||
DynamicToolCallStatus::Completed
|
||||
} else {
|
||||
DynamicToolCallStatus::Failed
|
||||
},
|
||||
content_items: Some(response.content_items.clone()),
|
||||
success: Some(response.success),
|
||||
error: None,
|
||||
duration: started_at.elapsed(),
|
||||
}),
|
||||
None => EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
|
||||
call_id,
|
||||
turn_id,
|
||||
completed_at_ms: now_unix_timestamp_ms(),
|
||||
duration: Some(started_at.elapsed()),
|
||||
},
|
||||
None => DynamicToolCallItem {
|
||||
id: call_id,
|
||||
namespace,
|
||||
tool,
|
||||
arguments,
|
||||
content_items: Vec::new(),
|
||||
success: false,
|
||||
status: DynamicToolCallStatus::Failed,
|
||||
content_items: Some(Vec::new()),
|
||||
success: Some(false),
|
||||
error: Some("dynamic tool call was cancelled before receiving a response".to_string()),
|
||||
duration: started_at.elapsed(),
|
||||
}),
|
||||
duration: Some(started_at.elapsed()),
|
||||
},
|
||||
};
|
||||
session.send_event(turn_context, response_event).await;
|
||||
session
|
||||
.emit_turn_item_completed(turn_context, TurnItem::DynamicToolCall(item))
|
||||
.await;
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user