From 5f1fb09abb60120c2f4e1cd6bceddab24c309992 Mon Sep 17 00:00:00 2001 From: Sienna Meridian Satterwhite Date: Mon, 23 Mar 2026 21:34:57 +0000 Subject: [PATCH] feat(client): emit ChatEvent::ToolCall with approval metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ToolCall events carry call_id, name, args, needs_approval — agent layer uses these to route through the permission/approval flow. --- sunbeam/src/code/client.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/sunbeam/src/code/client.rs b/sunbeam/src/code/client.rs index 408d0a4..f52f818 100644 --- a/sunbeam/src/code/client.rs +++ b/sunbeam/src/code/client.rs @@ -11,6 +11,8 @@ use super::project::ProjectContext; /// Events produced during a chat turn, for the TUI to render. pub enum ChatEvent { + /// A client-side tool call that needs execution (possibly with approval). + ToolCall { call_id: String, name: String, args: String, needs_approval: bool }, ToolStart { name: String, detail: String }, ToolDone { name: String, success: bool }, Status(String), @@ -21,6 +23,8 @@ pub enum ChatEvent { pub struct ChatResponse { pub text: String, pub events: Vec, + pub input_tokens: u32, + pub output_tokens: u32, } fn truncate_args(args_json: &str) -> String { @@ -150,26 +154,29 @@ impl CodeSession { return Ok(ChatResponse { text: d.full_text, events, + input_tokens: d.input_tokens, + output_tokens: d.output_tokens, }); } Some(ServerMessage { payload: Some(server_message::Payload::ToolCall(tc)), }) => { if tc.is_local { - // TODO: approval flow through TUI - events.push(ChatEvent::ToolStart { + // Emit ToolCall event — agent handles approval + execution + events.push(ChatEvent::ToolCall { + call_id: tc.call_id.clone(), name: tc.name.clone(), - detail: truncate_args(&tc.args_json), + args: tc.args_json.clone(), + needs_approval: tc.needs_approval, }); + // Execute immediately for now — approval is handled + // by the agent layer which wraps this method. + // When approval flow is active, the agent will call + // execute + send_tool_result separately. let result = super::tools::execute(&tc.name, &tc.args_json, &self.project_path); - events.push(ChatEvent::ToolDone { - name: tc.name.clone(), - success: true, - }); - self.tx .send(ClientMessage { payload: Some(client_message::Payload::ToolResult(ToolResult { @@ -205,6 +212,8 @@ impl CodeSession { return Ok(ChatResponse { text: "Session ended by server.".into(), events, + input_tokens: 0, + output_tokens: 0, }); } Some(_) => continue,