Skip to main content

ag_ui/
lib.rs

1//! A Rust SDK for the [AG-UI protocol] — hosting an agent and consuming one.
2//!
3//! AG-UI is the protocol between a user-facing application and an agent
4//! backend. A run is a stream of [`Event`]s: the agent opens messages, streams
5//! text and reasoning, calls tools, publishes state, and finishes — or pauses
6//! for human input.
7//!
8//! It implements all 36 event types, both halves of the protocol, and a drift
9//! check in CI that fails the build when upstream's event set moves — held to
10//! what an official SDK would have to be, because becoming the official AG-UI
11//! Rust SDK is the goal. It is not that yet: this crate is not affiliated with
12//! or endorsed by the AG-UI protocol organisation.
13//!
14//! # What is in the box
15//!
16//! The crate root is the shared vocabulary: the types, their exact JSON
17//! representation, and the SSE framing that carries them. No runtime, no I/O,
18//! no async — that part compiles for everyone.
19//!
20//! Everything past it is a feature, because most programs want one side of the
21//! protocol and should not pay to compile the other. Each entry below is
22//! gated the same way its module is: a link to an item this build does not
23//! have is a rustdoc error, not a dead link, which is what the `doc-features`
24//! CI job exists to catch.
25//!
26#![cfg_attr(
27    feature = "server",
28    doc = "- [`server`] — host an agent. Implement [`server::Agent`], hand it to",
29    doc = "  [`server::run()`], and you have a stream a transport can serialize."
30)]
31#![cfg_attr(
32    not(feature = "server"),
33    doc = "- `server` *(off in this build)* — host an agent: the `Agent` trait, and",
34    doc = "  `run()` to turn one into a stream a transport can serialize."
35)]
36#![cfg_attr(
37    feature = "client",
38    doc = "- [`client`] — consume a remote agent, materializing its events into",
39    doc = "  messages and state."
40)]
41#![cfg_attr(
42    not(feature = "client"),
43    doc = "- `client` *(off in this build)* — consume a remote agent, materializing",
44    doc = "  its events into messages and state."
45)]
46#![cfg_attr(
47    feature = "axum",
48    doc = "- [`axum`] — mount a hosted agent on an axum router, one call."
49)]
50#![cfg_attr(
51    not(feature = "axum"),
52    doc = "- `axum` *(off in this build)* — mount a hosted agent on an axum router."
53)]
54//!
55//! Each runtime keeps its own `Error` and `Result` under its own module. A
56//! bare [`Error`] is always a protocol error, `ag_ui::server::Error` is a
57//! hosting error, and collapsing them into the root would hide a distinction
58//! that matters at every `?`.
59//!
60//! ```
61//! # #[cfg(feature = "sse")] {
62//! use ag_ui::{Event, EventStreamFormatter, SseFormatter, TextMessageRole};
63//!
64//! let formatter = SseFormatter::new();
65//! let run = [
66//!     Event::run_started("thread-1", "run-1"),
67//!     Event::text_message_start("msg-1", TextMessageRole::Assistant),
68//!     Event::text_message_content("msg-1", "Hello"),
69//!     Event::text_message_end("msg-1"),
70//!     Event::run_finished_success("thread-1", "run-1"),
71//! ];
72//!
73//! let body: String = run
74//!     .iter()
75//!     .map(|event| formatter.encode_to_string(event).unwrap())
76//!     .collect();
77//!
78//! assert!(body.starts_with(r#"data: {"type":"RUN_STARTED","threadId":"thread-1""#));
79//! # }
80//! ```
81//!
82//! # Identifiers are strings
83//!
84//! [`ThreadId`], [`RunId`] and friends wrap [`String`], not `Uuid`. Producers
85//! send arbitrary strings and a stricter type would reject valid traffic — see
86//! the [`ids`] module for the history.
87//!
88//! # Features
89//!
90// A feature list is the one place a doc link is guaranteed to name something
91// the current build may not have. Gated so the link stays live where the item
92// exists — see `doc-features` in CI.
93#![cfg_attr(
94    feature = "sse",
95    doc = "- `sse` *(default)* — [`SseFormatter`] and `text/event-stream` framing."
96)]
97#![cfg_attr(
98    not(feature = "sse"),
99    doc = "- `sse` *(default, off in this build)* — `SseFormatter` and `text/event-stream` framing."
100)]
101//! - `protobuf` — the binary transport's media type and a documented stub; the
102//!   `encode::protobuf` module explains why there is no encoder.
103//! - `schemars` — derives `schemars::JsonSchema` on the public types.
104//! - `utoipa` — derives `utoipa::ToSchema` on the public types.
105//! - `server` — the server runtime: the agent trait, typestate emitters, state
106//!   deltas. Executor-agnostic; no tokio.
107//! - `verify` *(default)* — `server`'s ordering state machine. Off, the whole
108//!   verifier is a zero-sized type whose checks compile away. Listed in
109//!   `default` rather than implied by `server` so `default-features = false`
110//!   can drop it.
111//! - `client` — the client runtime, transport-agnostic.
112//! - `http` — adds the reqwest-backed transport to `client`. What most
113//!   consumers want; leave it off for wasm or a custom transport.
114//! - `axum` — mounts a hosted agent on an axum router. Implies `server` and
115//!   `sse`, and is the one feature that pulls in tokio.
116//!
117//! ```toml
118//! [dependencies]
119//! # host an agent behind axum
120//! ag-ui = { version = "0.3", features = ["axum"] }
121//! # or consume one over HTTP
122//! ag-ui = { version = "0.3", features = ["http"] }
123//! ```
124//!
125//! [AG-UI protocol]: https://github.com/ag-ui-protocol/ag-ui
126
127#![forbid(unsafe_code)]
128#![warn(missing_docs)]
129#![warn(missing_debug_implementations)]
130// Stamps "Available on crate feature X" on every gated item in the rendered
131// docs. Only ever set by docs.rs and the `cargo doc` recipe in CONTRIBUTING, so
132// it costs a stable build nothing.
133#![cfg_attr(docsrs, feature(doc_cfg))]
134
135// `readme = "README.md"` in Cargo.toml makes that file the crate's front page
136// wherever the package is presented, so its examples are doctested: a stale one
137// is a red build rather than a bad first impression. `cfg(doctest)` is what
138// keeps this module out of the rendered docs — it compiles the examples rather
139// than publishing them.
140// Gated on `sse` because that is the feature the example demonstrates.
141#[cfg(all(doctest, feature = "sse"))]
142#[doc = include_str!("../README.md")]
143mod readme {}
144
145pub mod capabilities;
146pub mod context;
147pub mod error;
148pub mod event;
149pub mod ids;
150pub mod input;
151pub mod message;
152pub mod metadata;
153pub mod outcome;
154pub mod patch;
155pub mod token_usage;
156pub mod tool;
157
158mod serde_util;
159
160#[cfg(any(feature = "sse", feature = "protobuf"))]
161pub mod encode;
162
163// The runtimes. Each carries its own `Error` and `Result`, which is why they
164// stay behind a module path instead of being flattened into the root the way
165// the protocol types are: `ag_ui::Error` is a protocol error, `ag_ui::server::Error`
166// is a hosting error, and collapsing them would make the distinction invisible.
167#[cfg(feature = "axum")]
168pub mod axum;
169#[cfg(feature = "client")]
170pub mod client;
171#[cfg(feature = "server")]
172pub mod server;
173
174/// A JSON object — the Rust spelling of TypeScript's `Record<string, any>`.
175///
176/// Key order is preserved, so a payload that round-trips through this crate
177/// comes back out in the order it arrived.
178pub type JsonObject = serde_json::Map<String, serde_json::Value>;
179
180pub use capabilities::{
181    AgentCapabilities, ExecutionCapabilities, HumanInTheLoopCapabilities, IdentityCapabilities,
182    MultiAgentCapabilities, MultimodalCapabilities, MultimodalInputCapabilities,
183    MultimodalOutputCapabilities, OutputCapabilities, ReasoningCapabilities, StateCapabilities,
184    SubAgentInfo, ToolsCapabilities, TransportCapabilities,
185};
186pub use context::Context;
187pub use error::{Error, Result};
188pub use event::{
189    ActivityDeltaEvent, ActivitySnapshotEvent, BaseEvent, CustomEvent, Event, EventType,
190    MessagesSnapshotEvent, RawEvent, ReasoningEncryptedValueEvent, ReasoningEncryptedValueSubtype,
191    ReasoningEndEvent, ReasoningMessageChunkEvent, ReasoningMessageContentEvent,
192    ReasoningMessageEndEvent, ReasoningMessageStartEvent, ReasoningRole, ReasoningStartEvent,
193    RunErrorEvent, RunFinishedEvent, RunStartedEvent, StateDeltaEvent, StateSnapshotEvent,
194    StepFinishedEvent, StepStartedEvent, SubagentErrorEvent, SubagentFinishedEvent,
195    SubagentOutcome, SubagentStartedEvent, TextMessageChunkEvent, TextMessageContentEvent,
196    TextMessageEndEvent, TextMessageRole, TextMessageStartEvent, ToolCallArgsEvent,
197    ToolCallChunkEvent, ToolCallEndEvent, ToolCallResultEvent, ToolCallStartEvent, ToolResultRole,
198};
199// Still part of the protocol, so still re-exported; downstream users get the
200// deprecation warning at their use site, not here.
201#[allow(deprecated)]
202pub use event::{
203    ThinkingEndEvent, ThinkingStartEvent, ThinkingTextMessageContentEvent,
204    ThinkingTextMessageEndEvent, ThinkingTextMessageStartEvent,
205};
206pub use ids::{AgentId, MessageId, RunId, StepName, SubagentRunId, ThreadId, ToolCallId};
207pub use input::RunAgentInput;
208pub use message::{
209    ActivityMessage, AssistantMessage, BinaryInputContent, DeveloperMessage, InputContent,
210    InputContentSource, MediaInputContent, Message, ReasoningMessage, Role, SystemMessage,
211    TextInputContent, ToolMessage, UserContent, UserMessage,
212};
213pub use metadata::{AGUI_METADATA_KEY, merge_metadata};
214pub use outcome::{Interrupt, ResumeEntry, ResumeStatus, RunOutcome};
215pub use patch::{JsonPatch, PatchOperation};
216pub use token_usage::{TokenUsage, aggregate_token_usage};
217pub use tool::{FunctionCall, Tool, ToolCall, ToolCallKind};
218
219#[cfg(feature = "protobuf")]
220pub use encode::protobuf::ProtobufFormatter;
221#[cfg(feature = "sse")]
222pub use encode::sse::SseFormatter;
223#[cfg(any(feature = "sse", feature = "protobuf"))]
224pub use encode::{
225    EventStreamFormatter, PROTOBUF_MEDIA_TYPE, SSE_MEDIA_TYPE, media_type, supported_media_types,
226};