Skip to main content

ag_ui/
capabilities.rs

1//! What an agent can do — a categorized snapshot for discovery UIs, routing,
2//! and debugging.
3//!
4//! Every field is optional and omission means *not declared*, which is not the
5//! same as *unsupported*. Agents fill in only what they mean to advertise.
6
7use serde::{Deserialize, Serialize};
8
9use crate::JsonObject;
10use crate::tool::Tool;
11
12/// A sub-agent a parent agent can invoke.
13#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
15#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
16pub struct SubAgentInfo {
17    /// Unique name or identifier of the sub-agent.
18    pub name: String,
19    /// What this sub-agent specializes in. Helps clients build selection UIs.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub description: Option<String>,
22}
23
24/// Agent identity and metadata.
25#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
28#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
29pub struct IdentityCapabilities {
30    /// Human-readable name shown in UIs and agent selectors.
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub name: Option<String>,
33    /// The framework powering this agent, for example `"langgraph"`.
34    #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
35    pub kind: Option<String>,
36    /// What this agent does — helps users and routing logic pick it.
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub description: Option<String>,
39    /// Semantic version of the agent, for compatibility checks.
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub version: Option<String>,
42    /// Organization or team that maintains this agent.
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub provider: Option<String>,
45    /// URL of the agent's documentation or homepage.
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub documentation_url: Option<String>,
48    /// Integration-specific identity extras.
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    #[cfg_attr(
51        feature = "schemars",
52        schemars(with = "Option<std::collections::BTreeMap<String, serde_json::Value>>")
53    )]
54    #[cfg_attr(feature = "utoipa", schema(value_type = Option<Object>))]
55    pub metadata: Option<JsonObject>,
56}
57
58/// Transports the agent speaks.
59#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
62#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
63pub struct TransportCapabilities {
64    /// The agent streams responses over SSE. Most agents set this.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub streaming: Option<bool>,
67    /// The agent accepts persistent WebSocket connections.
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub websocket: Option<bool>,
70    /// The agent supports the AG-UI binary protocol (protobuf over HTTP).
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub http_binary: Option<bool>,
73    /// The agent can push async updates via webhooks after a run finishes.
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub push_notifications: Option<bool>,
76    /// The agent supports resuming interrupted streams via sequence numbers.
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub resumable: Option<bool>,
79}
80
81/// Tool-calling support, and the tools the agent brings itself.
82#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
85#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
86pub struct ToolsCapabilities {
87    /// The agent can make tool calls at all. `Some(false)` disables tool
88    /// calling explicitly even when `items` is populated.
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub supported: Option<bool>,
91    /// Tools the agent provides on its own, distinct from the client-provided
92    /// tools in [`RunAgentInput::tools`](crate::input::RunAgentInput::tools).
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub items: Option<Vec<Tool>>,
95    /// The agent can invoke several tools concurrently within one step.
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub parallel_calls: Option<bool>,
98    /// The agent uses tools the client passes at runtime.
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub client_provided: Option<bool>,
101}
102
103/// Output formats the agent can produce.
104#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
107#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
108pub struct OutputCapabilities {
109    /// The agent can return JSON matching a supplied schema.
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub structured_output: Option<bool>,
112    /// MIME types the agent can produce. Omit when it only produces plain text.
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub supported_mime_types: Option<Vec<String>>,
115}
116
117/// State and memory handling.
118#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
119#[serde(rename_all = "camelCase")]
120#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
121#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
122pub struct StateCapabilities {
123    /// The agent emits `STATE_SNAPSHOT` events.
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub snapshots: Option<bool>,
126    /// The agent emits `STATE_DELTA` events.
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub deltas: Option<bool>,
129    /// The agent has long-term memory beyond the current thread.
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub memory: Option<bool>,
132    /// State survives across runs within a thread. When `Some(false)`, state
133    /// resets each run.
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub persistent_state: Option<bool>,
136}
137
138/// Multi-agent coordination.
139#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
142#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
143pub struct MultiAgentCapabilities {
144    /// The agent takes part in multi-agent coordination at all.
145    #[serde(default, skip_serializing_if = "Option::is_none")]
146    pub supported: Option<bool>,
147    /// The agent delegates subtasks while retaining control.
148    #[serde(default, skip_serializing_if = "Option::is_none")]
149    pub delegation: Option<bool>,
150    /// The agent can hand the conversation over entirely.
151    #[serde(default, skip_serializing_if = "Option::is_none")]
152    pub handoffs: Option<bool>,
153    /// Sub-agents this agent can invoke.
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub sub_agents: Option<Vec<SubAgentInfo>>,
156}
157
158/// Visibility into the agent's reasoning.
159#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
162#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
163pub struct ReasoningCapabilities {
164    /// The agent produces reasoning visible to the client.
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub supported: Option<bool>,
167    /// Reasoning is streamed incrementally rather than delivered at once.
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub streaming: Option<bool>,
170    /// Reasoning is encrypted (zero-data-retention). Clients should expect
171    /// opaque `encryptedValue` fields instead of readable content.
172    #[serde(default, skip_serializing_if = "Option::is_none")]
173    pub encrypted: Option<bool>,
174}
175
176/// Modalities the agent accepts as input.
177#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
178#[serde(rename_all = "camelCase")]
179#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
180#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
181pub struct MultimodalInputCapabilities {
182    /// Images.
183    #[serde(default, skip_serializing_if = "Option::is_none")]
184    pub image: Option<bool>,
185    /// Audio.
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub audio: Option<bool>,
188    /// Video.
189    #[serde(default, skip_serializing_if = "Option::is_none")]
190    pub video: Option<bool>,
191    /// PDF documents.
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub pdf: Option<bool>,
194    /// Arbitrary file uploads.
195    #[serde(default, skip_serializing_if = "Option::is_none")]
196    pub file: Option<bool>,
197}
198
199/// Modalities the agent can produce.
200#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
203#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
204pub struct MultimodalOutputCapabilities {
205    /// The agent can generate images.
206    #[serde(default, skip_serializing_if = "Option::is_none")]
207    pub image: Option<bool>,
208    /// The agent can produce audio.
209    #[serde(default, skip_serializing_if = "Option::is_none")]
210    pub audio: Option<bool>,
211}
212
213/// Multimodal input and output support, split so clients can query each side
214/// independently.
215#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
218#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
219pub struct MultimodalCapabilities {
220    /// What the agent accepts.
221    #[serde(default, skip_serializing_if = "Option::is_none")]
222    pub input: Option<MultimodalInputCapabilities>,
223    /// What the agent produces.
224    #[serde(default, skip_serializing_if = "Option::is_none")]
225    pub output: Option<MultimodalOutputCapabilities>,
226}
227
228/// Execution controls and limits.
229///
230/// The two caps are integers, not floats. The upstream Zod schema types them as
231/// a bare `z.number()`, which is a JavaScript double — but the Python SDK
232/// declares them `Optional[int]`, and both an iteration count and a millisecond
233/// budget are whole numbers everywhere they are produced. Modelling them as
234/// `f64` would re-emit a received `"maxIterations": 10` as `10.0`, so a Rust
235/// proxy between two upstream implementations would silently rewrite the
236/// payload. They are signed because Python's `int` is: some frameworks spell
237/// "no limit" as `-1`, and rejecting that at parse time would fail the whole
238/// capabilities document over a value the reference implementations accept.
239#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
242#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
243pub struct ExecutionCapabilities {
244    /// The agent can execute code during a run.
245    #[serde(default, skip_serializing_if = "Option::is_none")]
246    pub code_execution: Option<bool>,
247    /// Code execution is sandboxed. Only meaningful with `code_execution`.
248    #[serde(default, skip_serializing_if = "Option::is_none")]
249    pub sandboxed: Option<bool>,
250    /// Cap on tool-call / reasoning iterations per run.
251    #[serde(default, skip_serializing_if = "Option::is_none")]
252    pub max_iterations: Option<i64>,
253    /// Wall-clock cap per run, in milliseconds.
254    #[serde(default, skip_serializing_if = "Option::is_none")]
255    pub max_execution_time: Option<i64>,
256}
257
258/// Human-in-the-loop support.
259#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
262#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
263pub struct HumanInTheLoopCapabilities {
264    /// The agent supports human-in-the-loop interaction at all.
265    #[serde(default, skip_serializing_if = "Option::is_none")]
266    pub supported: Option<bool>,
267    /// The agent pauses for explicit approval before sensitive actions.
268    #[serde(default, skip_serializing_if = "Option::is_none")]
269    pub approvals: Option<bool>,
270    /// Humans can modify the agent's plan mid-execution.
271    #[serde(default, skip_serializing_if = "Option::is_none")]
272    pub interventions: Option<bool>,
273    /// The agent incorporates user feedback within a session.
274    #[serde(default, skip_serializing_if = "Option::is_none")]
275    pub feedback: Option<bool>,
276    /// The agent speaks the interrupt protocol: it emits `RUN_FINISHED` with
277    /// [`RunOutcome::Interrupt`](crate::outcome::RunOutcome::Interrupt) and
278    /// accepts `resume` entries.
279    #[serde(default, skip_serializing_if = "Option::is_none")]
280    pub interrupts: Option<bool>,
281    /// Tool-call interrupts accept `editedArgs` in the resume payload. Only
282    /// meaningful with `interrupts`.
283    #[serde(default, skip_serializing_if = "Option::is_none")]
284    pub approve_with_edits: Option<bool>,
285}
286
287/// A typed, categorized snapshot of what an agent supports.
288#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
289#[serde(rename_all = "camelCase")]
290#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
291#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
292pub struct AgentCapabilities {
293    /// Identity and metadata.
294    #[serde(default, skip_serializing_if = "Option::is_none")]
295    pub identity: Option<IdentityCapabilities>,
296    /// Supported transports.
297    #[serde(default, skip_serializing_if = "Option::is_none")]
298    pub transport: Option<TransportCapabilities>,
299    /// Tool calling and agent-provided tools.
300    #[serde(default, skip_serializing_if = "Option::is_none")]
301    pub tools: Option<ToolsCapabilities>,
302    /// Output formats.
303    #[serde(default, skip_serializing_if = "Option::is_none")]
304    pub output: Option<OutputCapabilities>,
305    /// State and memory.
306    #[serde(default, skip_serializing_if = "Option::is_none")]
307    pub state: Option<StateCapabilities>,
308    /// Multi-agent coordination.
309    #[serde(default, skip_serializing_if = "Option::is_none")]
310    pub multi_agent: Option<MultiAgentCapabilities>,
311    /// Reasoning visibility.
312    #[serde(default, skip_serializing_if = "Option::is_none")]
313    pub reasoning: Option<ReasoningCapabilities>,
314    /// Multimodal input and output.
315    #[serde(default, skip_serializing_if = "Option::is_none")]
316    pub multimodal: Option<MultimodalCapabilities>,
317    /// Execution controls and limits.
318    #[serde(default, skip_serializing_if = "Option::is_none")]
319    pub execution: Option<ExecutionCapabilities>,
320    /// Human-in-the-loop support.
321    #[serde(default, skip_serializing_if = "Option::is_none")]
322    pub human_in_the_loop: Option<HumanInTheLoopCapabilities>,
323    /// Escape hatch for capabilities the standard categories do not cover.
324    #[serde(default, skip_serializing_if = "Option::is_none")]
325    #[cfg_attr(
326        feature = "schemars",
327        schemars(with = "Option<std::collections::BTreeMap<String, serde_json::Value>>")
328    )]
329    #[cfg_attr(feature = "utoipa", schema(value_type = Option<Object>))]
330    pub custom: Option<JsonObject>,
331}