Skip to main content

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}