ag_ui/server/emit/tool.rs
1//! Streaming one tool call.
2
3use crate::{Event, MessageId, ReasoningEncryptedValueSubtype, ToolCallId};
4use serde::Serialize;
5use serde::de::DeserializeOwned;
6
7use crate::server::agent::AgentState;
8use crate::server::emit::EventSink;
9use crate::server::error::Result;
10use crate::server::state::RunState;
11
12/// One open tool call.
13///
14/// Created by [`RunContext::tool_call`](crate::server::RunContext::tool_call).
15/// `TOOL_CALL_START` has already gone out; `Drop` emits `TOOL_CALL_END`.
16///
17/// Arguments stream as text because providers stream them as text — a partial
18/// delta is usually not valid JSON. The handle keeps what it emitted, so
19/// [`parse_args`](Self::parse_args) can hand you the finished struct to execute
20/// against:
21///
22/// ```
23/// # use ag_ui::RunAgentInput;
24/// # use ag_ui::server::RunContext;
25/// # use serde::Deserialize;
26/// #[derive(Deserialize)]
27/// struct Query { city: String }
28///
29/// # let (mut ctx, _events) = RunContext::<()>::new(RunAgentInput::new("t", "r"))?;
30/// let mut call = ctx.tool_call("get_weather")?;
31/// call.args(r#"{"city":"#)?; // as the provider streams them
32/// call.args(r#""Seoul"}"#)?;
33/// let query: Query = call.parse_args()?;
34/// assert_eq!(query.city, "Seoul");
35/// call.result(r#"{"tempC":21}"#)?; // emits TOOL_CALL_END then TOOL_CALL_RESULT
36/// # Ok::<(), ag_ui::server::Error>(())
37/// ```
38///
39/// # Doing the work while the call is open
40///
41/// The handle borrows the run's event sink and its state, not the run context,
42/// so the tool's own work belongs *between* the arguments and the result. The
43/// protocol treats the `STATE_*` family as unordered, so a publish inside the
44/// brackets is a legal stream — and the one that lets a client watch the call
45/// land instead of seeing it land already done:
46///
47/// ```
48/// # use ag_ui::RunAgentInput;
49/// # use ag_ui::server::RunContext;
50/// # use serde::{Deserialize, Serialize};
51/// # use serde_json::json;
52/// #[derive(Default, Serialize, Deserialize)]
53/// struct Board { tasks: Vec<String> }
54///
55/// # let (mut ctx, _events) = RunContext::<Board>::new(RunAgentInput::new("t", "r"))?;
56/// let mut call = ctx.tool_call("add_task")?;
57/// call.args_json(&json!({"title": "ship it"}))?;
58///
59/// call.state_mut().tasks.push("ship it".to_owned());
60/// call.publish_state()?; // STATE_SNAPSHOT, with the call open
61///
62/// call.result_json(&json!({"ok": true}))?;
63/// assert_eq!(ctx.state().tasks, ["ship it"]);
64/// # Ok::<(), ag_ui::server::Error>(())
65/// ```
66///
67/// What the handle still cannot do is open a second message, reasoning block or
68/// tool call: it holds no run context to open one with, and the context it came
69/// from stays borrowed until it drops.
70#[derive(Debug)]
71pub struct ToolCallHandle<'a, S> {
72 sink: &'a mut EventSink,
73 state: &'a mut RunState<S>,
74 id: ToolCallId,
75 result_message_id: MessageId,
76 args: String,
77 ended: bool,
78}
79
80impl<'a, S> ToolCallHandle<'a, S> {
81 /// Emits `TOOL_CALL_START`.
82 ///
83 /// `result_message_id` is allocated up front so the handle can emit
84 /// `TOOL_CALL_RESULT` without reaching back into the run context — which is
85 /// what keeps a second overlapping handle a borrow-check error.
86 pub(crate) fn start(
87 sink: &'a mut EventSink,
88 state: &'a mut RunState<S>,
89 id: ToolCallId,
90 name: &str,
91 parent_message_id: Option<MessageId>,
92 result_message_id: MessageId,
93 ) -> Result<Self> {
94 let mut start = crate::ToolCallStartEvent::new(id.clone(), name);
95 start.parent_message_id = parent_message_id;
96 sink.emit(start.into())?;
97 Ok(Self {
98 sink,
99 state,
100 id,
101 result_message_id,
102 args: String::new(),
103 ended: false,
104 })
105 }
106
107 /// The id every event of this call carries.
108 pub fn id(&self) -> &ToolCallId {
109 &self.id
110 }
111
112 /// The id the result message will carry.
113 pub fn result_message_id(&self) -> &MessageId {
114 &self.result_message_id
115 }
116
117 /// Appends a fragment of the argument JSON — `TOOL_CALL_ARGS`.
118 pub fn args(&mut self, delta: impl AsRef<str>) -> Result<()> {
119 let delta = delta.as_ref();
120 self.args.push_str(delta);
121 self.sink
122 .emit(Event::tool_call_args(self.id.clone(), delta))
123 }
124
125 /// Serializes `value` and emits it as the call's arguments in one delta.
126 pub fn args_json<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<()> {
127 self.args(serde_json::to_string(value)?)
128 }
129
130 /// The argument JSON emitted so far, unparsed.
131 pub fn raw_args(&self) -> &str {
132 &self.args
133 }
134
135 /// Parses everything emitted through [`args`](Self::args) into `T`.
136 ///
137 /// Fails while the arguments are still partial, which is the point: call it
138 /// once the provider has finished streaming them.
139 pub fn parse_args<T: DeserializeOwned>(&self) -> Result<T> {
140 Ok(serde_json::from_str(&self.args)?)
141 }
142
143 /// Attaches the provider's opaque reasoning signature for this call —
144 /// `REASONING_ENCRYPTED_VALUE`.
145 pub fn encrypted_value(&mut self, value: impl Into<String>) -> Result<()> {
146 self.sink.emit(Event::reasoning_encrypted_value(
147 ReasoningEncryptedValueSubtype::ToolCall,
148 self.id.clone(),
149 value,
150 ))
151 }
152
153 /// Emits an unrelated event without closing the call. See
154 /// [`MessageHandle::emit`](crate::server::MessageHandle::emit).
155 pub fn emit(&mut self, event: Event) -> Result<()> {
156 self.sink.emit(event)
157 }
158
159 /// Emits `TOOL_CALL_END` and consumes the handle, leaving the call
160 /// unanswered.
161 ///
162 /// Use it when the client executes the tool — a front-end tool's result
163 /// arrives as a message on the next request, not from here.
164 pub fn end(mut self) -> Result<()> {
165 self.ended = true;
166 self.sink.emit(Event::tool_call_end(self.id.clone()))
167 }
168
169 /// Emits `TOOL_CALL_END` then `TOOL_CALL_RESULT`, and consumes the handle.
170 ///
171 /// Returns the id of the tool message carrying the result.
172 pub fn result(mut self, content: impl Into<String>) -> Result<MessageId> {
173 self.ended = true;
174 self.sink.emit(Event::tool_call_end(self.id.clone()))?;
175 let mut result = crate::ToolCallResultEvent::new(
176 self.result_message_id.clone(),
177 self.id.clone(),
178 content,
179 );
180 result.role = Some(crate::ToolResultRole::Tool);
181 self.sink.emit(result.into())?;
182 Ok(self.result_message_id.clone())
183 }
184
185 /// Serializes `value` and reports it as the call's result.
186 pub fn result_json<T: Serialize + ?Sized>(self, value: &T) -> Result<MessageId> {
187 let content = serde_json::to_string(value)?;
188 self.result(content)
189 }
190}
191
192/// The run's state, reachable while the call is open. Same three methods as on
193/// [`RunContext`](crate::server::RunContext), forwarded — a tool that changes the state
194/// is the ordinary case, and it changes it in the middle of the call.
195impl<S: AgentState> ToolCallHandle<'_, S> {
196 /// The typed state, as of the last publish.
197 pub fn state(&self) -> &S {
198 self.state.get()
199 }
200
201 /// The typed state, mutably. Nothing is emitted until you call
202 /// [`publish_state`](Self::publish_state).
203 pub fn state_mut(&mut self) -> &mut S {
204 self.state.get_mut()
205 }
206
207 /// Publishes whatever [`state_mut`](Self::state_mut) left behind, as a
208 /// `STATE_SNAPSHOT` or a `STATE_DELTA` between this call's `TOOL_CALL_START`
209 /// and its `TOOL_CALL_END`.
210 ///
211 /// A no-op when nothing changed since the last publish.
212 pub fn publish_state(&mut self) -> Result<()> {
213 self.state.publish(self.sink)
214 }
215}
216
217impl<S> Drop for ToolCallHandle<'_, S> {
218 fn drop(&mut self) {
219 if !self.ended {
220 let _ = self.sink.emit(Event::tool_call_end(self.id.clone()));
221 }
222 }
223}