Skip to main content

ag_ui/server/
run.rs

1//! The driver: an [`Agent`] plus a [`RunAgentInput`](https://kimsoungryoul.github.io/ag-ui-rust/api/ag_ui/input/struct.RunAgentInput.html) in, a [`Stream`] of
2//! events out.
3//!
4//! The stream owns the agent's future and polls it itself, so this crate needs
5//! no executor of its own — no `tokio::spawn`, nothing to configure, and the
6//! same code runs on wasm. Draining the stream *is* running the agent.
7//!
8//! ```
9//! # use ag_ui::{Event, RunAgentInput, RunOutcome};
10//! # use ag_ui::server::{Agent, Result, RunContext, run};
11//! # use futures_util::StreamExt;
12//! struct Greeter;
13//!
14//! impl Agent for Greeter {
15//!     type State = ();
16//!     async fn run(&self, ctx: &mut RunContext<()>) -> Result<RunOutcome> {
17//!         ctx.say("hello")?;
18//!         Ok(RunOutcome::Success)
19//!     }
20//! }
21//!
22//! # let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
23//! # rt.block_on(async {
24//! let events: Vec<Event> = run(Greeter, RunAgentInput::new("thread-1", "run-1"))
25//!     .map(|event| event.expect("the stream should not break"))
26//!     .collect()
27//!     .await;
28//!
29//! assert_eq!(events.first().map(Event::event_type), Some(ag_ui::EventType::RunStarted));
30//! assert_eq!(events.last().map(Event::event_type), Some(ag_ui::EventType::RunFinished));
31//! # });
32//! ```
33
34use std::future::Future;
35use std::pin::Pin;
36use std::task::{Context, Poll};
37
38use crate::{
39    Event, RunAgentInput, RunErrorEvent, RunFinishedEvent, RunId, RunOutcome, RunStartedEvent,
40    ThreadId,
41};
42use futures_channel::mpsc::{self, UnboundedReceiver};
43use futures_core::Stream;
44use futures_util::StreamExt as _;
45
46use crate::server::agent::Agent;
47use crate::server::cancel::CancellationToken;
48use crate::server::context::{RunContext, decode_state};
49use crate::server::emit::EventSink;
50use crate::server::error::{Error, Result};
51use crate::server::transform::{StreamTransformer, TransformerChain};
52
53/// Runs `agent` against `input` with no transformers and a fresh cancellation
54/// token.
55///
56/// [`Runner`] is the same thing with knobs.
57pub fn run<A: Agent>(agent: A, input: RunAgentInput) -> impl Stream<Item = Result<Event>> + Send {
58    Runner::new(agent).run(input)
59}
60
61/// A configured run: the agent, its transformer chain and its cancellation
62/// token.
63///
64/// ```
65/// # use ag_ui::{RunAgentInput, RunOutcome};
66/// # use ag_ui::server::{Agent, FilterToolCalls, Result, RunContext, Runner};
67/// # struct MyAgent;
68/// # impl Agent for MyAgent {
69/// #     type State = ();
70/// #     async fn run(&self, _ctx: &mut RunContext<()>) -> Result<RunOutcome> { Ok(RunOutcome::Success) }
71/// # }
72/// let runner = Runner::new(MyAgent).transformer(FilterToolCalls::deny(["internal_debug"]));
73/// let token = runner.cancellation_token();   // hand this to the transport
74/// let stream = runner.run(RunAgentInput::new("thread-1", "run-1"));
75/// # let _ = (token, stream);
76/// ```
77pub struct Runner<A> {
78    agent: A,
79    chain: TransformerChain,
80    cancel: CancellationToken,
81    echo_input: bool,
82}
83
84// Hand-written so that `Runner` is printable whatever the agent is: requiring
85// `A: Debug` would make the bound viral through every wrapper that holds one.
86impl<A> std::fmt::Debug for Runner<A> {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        f.debug_struct("Runner")
89            .field("agent", &std::any::type_name::<A>())
90            .field("chain", &self.chain)
91            .field("cancel", &self.cancel)
92            .field("echo_input", &self.echo_input)
93            .finish()
94    }
95}
96
97impl<A> Runner<A> {
98    /// Wraps an agent.
99    pub fn new(agent: A) -> Self {
100        Self {
101            agent,
102            chain: TransformerChain::new(),
103            cancel: CancellationToken::new(),
104            echo_input: false,
105        }
106    }
107
108    /// Appends a transformer to the chain. See [`StreamTransformer`].
109    #[must_use]
110    pub fn transformer(mut self, transformer: impl StreamTransformer + 'static) -> Self {
111        self.chain.push(transformer);
112        self
113    }
114
115    /// Replaces the whole transformer chain.
116    #[must_use]
117    pub fn transformers(mut self, chain: TransformerChain) -> Self {
118        self.chain = chain;
119        self
120    }
121
122    /// Uses an existing cancellation token instead of the fresh one.
123    #[must_use]
124    pub fn cancellation(mut self, token: CancellationToken) -> Self {
125        self.cancel = token;
126        self
127    }
128
129    /// A handle on this run's cancellation, for the transport to trip when the
130    /// client disconnects.
131    pub fn cancellation_token(&self) -> CancellationToken {
132        self.cancel.clone()
133    }
134
135    /// Echoes the request back on `RUN_STARTED`, so a recorded stream replays
136    /// without the original HTTP body. Off by default — it is the largest
137    /// payload in the protocol.
138    #[must_use]
139    pub fn echo_input(mut self, echo: bool) -> Self {
140        self.echo_input = echo;
141        self
142    }
143}
144
145impl<A: Agent> Runner<A> {
146    /// Starts the run.
147    ///
148    /// The returned stream emits `RUN_STARTED` first and exactly one of
149    /// `RUN_FINISHED` / `RUN_ERROR` last — including when the agent body does
150    /// nothing, and when it returns `Err` through a `?`.
151    ///
152    /// A panic inside the agent is not caught; it unwinds through whoever is
153    /// polling the stream, as it would through any other future. Use the
154    /// transport's own panic handling if you need a response body for that
155    /// case.
156    pub fn run(self, input: RunAgentInput) -> impl Stream<Item = Result<Event>> + Send {
157        let Self {
158            agent,
159            chain,
160            cancel,
161            echo_input,
162        } = self;
163        let (tx, rx) = mpsc::unbounded();
164        let sink = EventSink::new(tx, chain, cancel);
165        RunStream {
166            driver: Some(Box::pin(drive(agent, input, sink, echo_input))),
167            rx,
168        }
169    }
170}
171
172/// Emits `RUN_STARTED`, runs the agent, and emits the terminal event.
173///
174/// Takes the sink by value and only hands it to the context for the duration of
175/// the agent's call: whatever happens in there, the terminal event still goes
176/// through the same transformers and the same verifier.
177async fn drive<A: Agent>(agent: A, input: RunAgentInput, mut sink: EventSink, echo_input: bool) {
178    let thread_id = input.thread_id.clone();
179    let run_id = input.run_id.clone();
180
181    let mut started = RunStartedEvent::new(thread_id.clone(), run_id.clone());
182    started.parent_run_id = input.parent_run_id.clone();
183    if echo_input {
184        started.input = Some(Box::new(input.clone()));
185    }
186    if sink.emit_forced(started.into()).is_err() {
187        // Nobody is listening, or RUN_STARTED was rejected. Either way there is
188        // no run to report on.
189        return;
190    }
191
192    // Decoded before the match so the borrow of `input` ends here: the context
193    // takes the input by value on the next line.
194    let decoded = decode_state::<A::State>(&input.state);
195    let outcome = match decoded {
196        Ok(state) => {
197            let mut ctx = RunContext::from_parts(input, state, sink);
198            let outcome = agent.run(&mut ctx).await;
199            if ctx.is_terminated() {
200                // The agent emitted its own terminal event through `emit`.
201                return;
202            }
203            sink = ctx.into_sink();
204            outcome
205        }
206        Err(error) => Err(error),
207    };
208
209    terminate(&mut sink, outcome, &thread_id, &run_id);
210}
211
212/// Emits exactly one terminal event.
213fn terminate(
214    sink: &mut EventSink,
215    outcome: Result<RunOutcome>,
216    thread_id: &ThreadId,
217    run_id: &RunId,
218) {
219    let event = match outcome {
220        Ok(outcome) => match outcome.validate() {
221            Ok(()) => RunFinishedEvent::new(thread_id.clone(), run_id.clone())
222                .with_outcome(outcome)
223                .into(),
224            Err(error) => error_event(&Error::Protocol(error)),
225        },
226        Err(error) => error_event(&error),
227    };
228
229    let Err(rejected) = sink.emit_forced(event) else {
230        return;
231    };
232    // `RUN_FINISHED` can be rejected — by the verifier, for a message the agent
233    // left open. A run that ends with no terminal event at all is worse than
234    // one that ends badly, so say what went wrong instead. `RUN_ERROR` is
235    // exempt from the open-at-finish rule for exactly this reason.
236    if !rejected.is_disconnected() && !sink.is_terminated() {
237        let _ = sink.emit_forced(error_event(&rejected));
238    }
239}
240
241fn error_event(error: &Error) -> Event {
242    RunErrorEvent::new(error.to_string())
243        .with_code(error.code())
244        .into()
245}
246
247/// The stream half: drains queued events, and polls the agent when there are
248/// none.
249///
250/// The driver future is boxed so the stream is `Unpin` and needs no unsafe
251/// projection — one allocation per run.
252struct RunStream<F> {
253    driver: Option<Pin<Box<F>>>,
254    rx: UnboundedReceiver<Event>,
255}
256
257impl<F: Future<Output = ()>> Stream for RunStream<F> {
258    type Item = Result<Event>;
259
260    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
261        let this = self.get_mut();
262        loop {
263            // Events first: everything the agent has already emitted goes out
264            // before it is polled again, which is what keeps a slow agent's
265            // early output flowing.
266            match this.rx.poll_next_unpin(cx) {
267                Poll::Ready(Some(event)) => return Poll::Ready(Some(Ok(event))),
268                // The sender lives inside the driver future, so this only
269                // happens once the driver has been dropped below.
270                Poll::Ready(None) => return Poll::Ready(None),
271                Poll::Pending => {}
272            }
273
274            let Some(driver) = this.driver.as_mut() else {
275                return Poll::Pending;
276            };
277            match driver.as_mut().poll(cx) {
278                // Dropping the future drops the sink, which closes the channel
279                // once the events still queued behind it have been drained.
280                Poll::Ready(()) => this.driver = None,
281                Poll::Pending => return Poll::Pending,
282            }
283        }
284    }
285}