Skip to content

Commit f5b1ab9

Browse files
matt2eclaude
andcommitted
feat(staged): forward tool call raw_input params through ACP driver to DB
The ACP protocol provides raw_input on ToolCall and ToolCallUpdate, but it was never extracted or forwarded. This adds raw_input through the full pipeline: LiveAction enum, MessageWriter trait, and DB writer. When raw_input is present, tool calls are stored as JSON {"name", "input"} which the frontend already knows how to parse. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3ebd086 commit f5b1ab9

3 files changed

Lines changed: 114 additions & 26 deletions

File tree

apps/staged/src-tauri/examples/acp_stream_probe.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ impl MessageWriter for ProbeWriter {
6868
println!("[probe] finalize");
6969
}
7070

71-
async fn record_tool_call(&self, tool_call_id: &str, title: &str) {
71+
async fn record_tool_call(
72+
&self,
73+
tool_call_id: &str,
74+
title: &str,
75+
_raw_input: Option<&serde_json::Value>,
76+
) {
7277
let mut state = self.state.lock().expect("probe state lock poisoned");
7378
state.total_tool_calls += 1;
7479
let count_for_id = *state
@@ -85,7 +90,12 @@ impl MessageWriter for ProbeWriter {
8590
);
8691
}
8792

88-
async fn update_tool_call_title(&self, tool_call_id: &str, title: &str) {
93+
async fn update_tool_call_title(
94+
&self,
95+
tool_call_id: &str,
96+
title: &str,
97+
_raw_input: Option<&serde_json::Value>,
98+
) {
8999
let mut state = self.state.lock().expect("probe state lock poisoned");
90100
state.total_tool_title_updates += 1;
91101
println!(

apps/staged/src-tauri/src/agent/writer.rs

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ fn sanitize_title(title: &str) -> String {
5353
title.replace('`', "")
5454
}
5555

56+
/// Format a tool call for storage. When `raw_input` is present, produces a JSON
57+
/// object `{"name": title, "input": raw_input}` that the frontend can parse to
58+
/// display structured tool call info. Without raw_input, falls back to the plain
59+
/// title string.
60+
fn format_tool_call_content(title: &str, raw_input: Option<&serde_json::Value>) -> String {
61+
match raw_input {
62+
Some(input) => serde_json::json!({ "name": title, "input": input }).to_string(),
63+
None => title.to_string(),
64+
}
65+
}
66+
5667
impl MessageWriter {
5768
pub fn new(session_id: String, store: Arc<Store>) -> Self {
5869
Self {
@@ -98,22 +109,28 @@ impl MessageWriter {
98109

99110
/// Record a tool call. Finalizes any in-progress assistant text first
100111
/// to maintain correct message ordering.
101-
pub async fn record_tool_call(&self, tool_call_id: &str, title: &str) {
112+
pub async fn record_tool_call(
113+
&self,
114+
tool_call_id: &str,
115+
title: &str,
116+
raw_input: Option<&serde_json::Value>,
117+
) {
102118
self.finalize().await;
103119
*self.current_tool_result_msg_id.lock().await = None;
104120

105121
let title = sanitize_title(title);
122+
let content = format_tool_call_content(&title, raw_input);
106123

107124
// Some providers may resend ToolCall for the same ID while streaming.
108125
// Treat those as updates to the existing row.
109126
if let Some(&row_id) = self.tool_call_rows.lock().await.get(tool_call_id) {
110-
let _ = self.store.update_message_content(row_id, &title);
127+
let _ = self.store.update_message_content(row_id, &content);
111128
return;
112129
}
113130

114131
match self
115132
.store
116-
.add_session_message(&self.session_id, MessageRole::ToolCall, &title)
133+
.add_session_message(&self.session_id, MessageRole::ToolCall, &content)
117134
{
118135
Ok(id) => {
119136
self.tool_call_rows
@@ -125,12 +142,18 @@ impl MessageWriter {
125142
}
126143
}
127144

128-
/// Update a previously recorded tool call's title.
129-
pub async fn update_tool_call_title(&self, tool_call_id: &str, title: &str) {
145+
/// Update a previously recorded tool call's title and/or raw input.
146+
pub async fn update_tool_call_title(
147+
&self,
148+
tool_call_id: &str,
149+
title: &str,
150+
raw_input: Option<&serde_json::Value>,
151+
) {
130152
let title = sanitize_title(title);
153+
let content = format_tool_call_content(&title, raw_input);
131154
let rows = self.tool_call_rows.lock().await;
132155
if let Some(&row_id) = rows.get(tool_call_id) {
133-
let _ = self.store.update_message_content(row_id, &title);
156+
let _ = self.store.update_message_content(row_id, &content);
134157
}
135158
}
136159

@@ -208,12 +231,23 @@ impl acp_client::MessageWriter for MessageWriter {
208231
self.finalize().await
209232
}
210233

211-
async fn record_tool_call(&self, tool_call_id: &str, title: &str) {
212-
self.record_tool_call(tool_call_id, title).await
234+
async fn record_tool_call(
235+
&self,
236+
tool_call_id: &str,
237+
title: &str,
238+
raw_input: Option<&serde_json::Value>,
239+
) {
240+
self.record_tool_call(tool_call_id, title, raw_input).await
213241
}
214242

215-
async fn update_tool_call_title(&self, tool_call_id: &str, title: &str) {
216-
self.update_tool_call_title(tool_call_id, title).await
243+
async fn update_tool_call_title(
244+
&self,
245+
tool_call_id: &str,
246+
title: &str,
247+
raw_input: Option<&serde_json::Value>,
248+
) {
249+
self.update_tool_call_title(tool_call_id, title, raw_input)
250+
.await
217251
}
218252

219253
async fn record_tool_result(&self, content: &str) {
@@ -241,7 +275,9 @@ mod tests {
241275
async fn record_tool_result_updates_existing_row_for_streaming_updates() {
242276
let (store, session_id, writer) = setup_writer();
243277

244-
writer.record_tool_call("tc-1", "Run echo hello").await;
278+
writer
279+
.record_tool_call("tc-1", "Run echo hello", None)
280+
.await;
245281
writer.record_tool_result("first chunk").await;
246282
writer.record_tool_result("second chunk").await;
247283

@@ -258,8 +294,12 @@ mod tests {
258294
async fn record_tool_call_same_id_updates_instead_of_inserting() {
259295
let (store, session_id, writer) = setup_writer();
260296

261-
writer.record_tool_call("tc-dup", "Run first title").await;
262-
writer.record_tool_call("tc-dup", "Run updated title").await;
297+
writer
298+
.record_tool_call("tc-dup", "Run first title", None)
299+
.await;
300+
writer
301+
.record_tool_call("tc-dup", "Run updated title", None)
302+
.await;
263303

264304
let messages = store
265305
.get_session_messages(&session_id)

crates/acp-client/src/driver.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,21 @@ pub trait MessageWriter: Send + Sync {
4848
/// Flush all buffered text and close the current message block.
4949
async fn finalize(&self);
5050

51-
/// Record a tool call with its ID and title.
52-
async fn record_tool_call(&self, tool_call_id: &str, title: &str);
51+
/// Record a tool call with its ID, title, and optional raw input parameters.
52+
async fn record_tool_call(
53+
&self,
54+
tool_call_id: &str,
55+
title: &str,
56+
raw_input: Option<&serde_json::Value>,
57+
);
5358

54-
/// Update a previously recorded tool call's title.
55-
async fn update_tool_call_title(&self, tool_call_id: &str, title: &str);
59+
/// Update a previously recorded tool call's title and/or raw input.
60+
async fn update_tool_call_title(
61+
&self,
62+
tool_call_id: &str,
63+
title: &str,
64+
raw_input: Option<&serde_json::Value>,
65+
);
5666

5767
/// Record the result/output of a tool call.
5868
async fn record_tool_result(&self, content: &str);
@@ -839,10 +849,12 @@ impl agent_client_protocol::Client for AcpNotificationHandler {
839849
RecordToolCall {
840850
id: String,
841851
title: String,
852+
raw_input: Option<serde_json::Value>,
842853
},
843854
ToolCallUpdate {
844855
id: String,
845856
title: Option<String>,
857+
raw_input: Option<serde_json::Value>,
846858
result: Option<String>,
847859
},
848860
Ignore,
@@ -948,19 +960,22 @@ impl agent_client_protocol::Client for AcpNotificationHandler {
948960
SessionUpdate::ToolCall(tool_call) => LiveAction::RecordToolCall {
949961
id: tool_call.tool_call_id.0.to_string(),
950962
title: tool_call.title.clone(),
963+
raw_input: tool_call.raw_input.clone(),
951964
},
952965
SessionUpdate::ToolCallUpdate(update) => {
953966
let tc_id = update.tool_call_id.0.to_string();
954967
let title = update.fields.title.clone();
968+
let raw_input = update.fields.raw_input.clone();
955969
let result = update
956970
.fields
957971
.content
958972
.as_ref()
959973
.and_then(|c| extract_content_preview(c));
960-
if title.is_some() || result.is_some() {
974+
if title.is_some() || raw_input.is_some() || result.is_some() {
961975
LiveAction::ToolCallUpdate {
962976
id: tc_id,
963977
title,
978+
raw_input,
964979
result,
965980
}
966981
} else {
@@ -979,12 +994,25 @@ impl agent_client_protocol::Client for AcpNotificationHandler {
979994
LiveAction::AppendText(text) => {
980995
self.writer.append_text(&text).await;
981996
}
982-
LiveAction::RecordToolCall { id, title } => {
983-
self.writer.record_tool_call(&id, &title).await;
997+
LiveAction::RecordToolCall {
998+
id,
999+
title,
1000+
raw_input,
1001+
} => {
1002+
self.writer
1003+
.record_tool_call(&id, &title, raw_input.as_ref())
1004+
.await;
9841005
}
985-
LiveAction::ToolCallUpdate { id, title, result } => {
1006+
LiveAction::ToolCallUpdate {
1007+
id,
1008+
title,
1009+
raw_input,
1010+
result,
1011+
} => {
9861012
if let Some(title) = title {
987-
self.writer.update_tool_call_title(&id, &title).await;
1013+
self.writer
1014+
.update_tool_call_title(&id, &title, raw_input.as_ref())
1015+
.await;
9881016
}
9891017
if let Some(preview) = result {
9901018
self.writer.record_tool_result(&preview).await;
@@ -1253,12 +1281,22 @@ impl MessageWriter for BasicMessageWriter {
12531281
// Nothing to do for basic implementation
12541282
}
12551283

1256-
async fn record_tool_call(&self, _tool_call_id: &str, title: &str) {
1284+
async fn record_tool_call(
1285+
&self,
1286+
_tool_call_id: &str,
1287+
title: &str,
1288+
_raw_input: Option<&serde_json::Value>,
1289+
) {
12571290
let mut current = self.text.lock().await;
12581291
current.push_str(&format!("\n[Tool: {}]\n", title));
12591292
}
12601293

1261-
async fn update_tool_call_title(&self, _tool_call_id: &str, _title: &str) {
1294+
async fn update_tool_call_title(
1295+
&self,
1296+
_tool_call_id: &str,
1297+
_title: &str,
1298+
_raw_input: Option<&serde_json::Value>,
1299+
) {
12621300
// Nothing to do for basic implementation
12631301
}
12641302

0 commit comments

Comments
 (0)