feat(app-server): add ThreadItem::DynamicToolCall (#12732)

Previously, clients would call `thread/start` with dynamic_tools set,
and when a model invokes a dynamic tool, it would just make the
server->client `item/tool/call` request and wait for the client's
response to complete the tool call. This works, but it doesn't have an
`item/started` or `item/completed` event.

Now we are doing this:
- [new] emit `item/started` with `DynamicToolCall` populated with the
call arguments
- send an `item/tool/call` server request
- [new] once the client responds, emit `item/completed` with
`DynamicToolCall` populated with the response.

Also, with `persistExtendedHistory: true`, dynamic tool calls are now
reconstructable in `thread/read` and `thread/resume` as
`ThreadItem::DynamicToolCall`.
This commit is contained in:
Owen Lin
2026-02-25 12:00:10 -08:00
committed by GitHub
parent 73eaebbd1c
commit a0fd94bde6
37 changed files with 2436 additions and 113 deletions

View File

@@ -20,6 +20,7 @@ use crate::config_types::Personality;
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
use crate::config_types::WindowsSandboxLevel;
use crate::custom_prompts::CustomPrompt;
use crate::dynamic_tools::DynamicToolCallOutputContentItem;
use crate::dynamic_tools::DynamicToolCallRequest;
use crate::dynamic_tools::DynamicToolResponse;
use crate::dynamic_tools::DynamicToolSpec;
@@ -1053,6 +1054,8 @@ pub enum EventMsg {
DynamicToolCallRequest(DynamicToolCallRequest),
DynamicToolCallResponse(DynamicToolCallResponseEvent),
SkillRequestApproval(SkillRequestApprovalEvent),
ElicitationRequest(ElicitationRequestEvent),
@@ -1781,6 +1784,27 @@ pub struct McpToolCallEndEvent {
pub result: Result<CallToolResult, String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS, PartialEq)]
pub struct DynamicToolCallResponseEvent {
/// Identifier for the corresponding DynamicToolCallRequest.
pub call_id: String,
/// Turn ID that this dynamic tool call belongs to.
pub turn_id: String,
/// Dynamic tool name.
pub tool: String,
/// Dynamic tool call arguments.
pub arguments: serde_json::Value,
/// Dynamic tool response content items.
pub content_items: Vec<DynamicToolCallOutputContentItem>,
/// Whether the tool call succeeded.
pub success: bool,
/// Optional error text when the tool call failed before producing a response.
pub error: Option<String>,
/// The duration of the dynamic tool call.
#[ts(type = "string")]
pub duration: Duration,
}
impl McpToolCallEndEvent {
pub fn is_success(&self) -> bool {
match &self.result {