From 320312fa255deeb31966d4f684652e20e94b2edd Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 8 Aug 2025 14:57:38 +0200 Subject: [PATCH] Handle streaming input in title updates Co-authored-by: Antonio Scandurra --- crates/agent2/src/tests/mod.rs | 32 +++++++++++++++++++++++++++++--- crates/agent2/src/thread.rs | 29 ++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/crates/agent2/src/tests/mod.rs b/crates/agent2/src/tests/mod.rs index b70f54ac0a..cd1249849e 100644 --- a/crates/agent2/src/tests/mod.rs +++ b/crates/agent2/src/tests/mod.rs @@ -647,6 +647,19 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { let mut events = thread.update(cx, |thread, cx| thread.send(model.clone(), "Think", cx)); cx.run_until_parked(); + // Simulate streaming partial input. + let input = json!({}); + fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse( + LanguageModelToolUse { + id: "1".into(), + name: ThinkingTool.name().into(), + raw_input: input.to_string(), + input, + is_input_complete: false, + }, + )); + + // Input streaming completed let input = json!({ "content": "Thinking hard!" }); fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse( LanguageModelToolUse { @@ -665,12 +678,12 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { tool_call, acp::ToolCall { id: acp::ToolCallId("1".into()), - title: "Thinking".into(), + title: "thinking".into(), kind: acp::ToolKind::Think, status: acp::ToolCallStatus::Pending, content: vec![], locations: vec![], - raw_input: Some(json!({ "content": "Thinking hard!" })), + raw_input: Some(json!({})), raw_output: None, } ); @@ -680,7 +693,20 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { acp::ToolCallUpdate { id: acp::ToolCallId("1".into()), fields: acp::ToolCallUpdateFields { - status: Some(acp::ToolCallStatus::InProgress,), + title: Some("Thinking".into()), + kind: Some(acp::ToolKind::Think), + raw_input: Some(json!({ "content": "Thinking hard!" })), + ..Default::default() + }, + } + ); + let update = expect_tool_call_update(&mut events).await; + assert_eq!( + update, + acp::ToolCallUpdate { + id: acp::ToolCallId("1".into()), + fields: acp::ToolCallUpdateFields { + status: Some(acp::ToolCallStatus::InProgress), ..Default::default() }, } diff --git a/crates/agent2/src/thread.rs b/crates/agent2/src/thread.rs index 98f2d0651d..78d4fb1f0d 100644 --- a/crates/agent2/src/thread.rs +++ b/crates/agent2/src/thread.rs @@ -474,8 +474,17 @@ impl Thread { } }); + let mut title = SharedString::from(&tool_use.name); + let mut kind = acp::ToolKind::Other; + if let Some(tool) = tool.as_ref() { + if let Ok(initial_title) = tool.initial_title(tool_use.input.clone()) { + title = initial_title; + } + kind = tool.kind(); + } + if push_new_tool_use { - event_stream.send_tool_call(tool.as_ref(), &tool_use); + event_stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone()); last_message .content .push(MessageContent::ToolUse(tool_use.clone())); @@ -483,6 +492,8 @@ impl Thread { event_stream.send_tool_call_update( &tool_use.id, acp::ToolCallUpdateFields { + title: Some(title.into()), + kind: Some(kind), raw_input: Some(tool_use.input.clone()), ..Default::default() }, @@ -842,17 +853,17 @@ impl AgentResponseEventStream { fn send_tool_call( &self, - tool: Option<&Arc>, - tool_use: &LanguageModelToolUse, + id: &LanguageModelToolUseId, + title: SharedString, + kind: acp::ToolKind, + input: serde_json::Value, ) { self.0 .unbounded_send(Ok(AgentResponseEvent::ToolCall(Self::initial_tool_call( - &tool_use.id, - tool.and_then(|t| t.initial_title(tool_use.input.clone()).ok()) - .map(|i| i.into()) - .unwrap_or_else(|| tool_use.name.to_string()), - tool.map(|t| t.kind()).unwrap_or(acp::ToolKind::Other), - tool_use.input.clone(), + id, + title.to_string(), + kind, + input, )))) .ok(); }