agent_client_protocol_schema/
content.rs

1//! Content blocks for representing various types of information in the Agent Client Protocol.
2//!
3//! This module defines the core content types used throughout the protocol for communication
4//! between agents and clients. Content blocks provide a flexible, extensible way to represent
5//! text, images, audio, and other resources in prompts, responses, and tool call results.
6//!
7//! The content block structure is designed to be compatible with the Model Context Protocol (MCP),
8//! allowing seamless integration between ACP and MCP-based tools.
9//!
10//! See: [Content](https://agentclientprotocol.com/protocol/content)
11
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14
15/// Content blocks represent displayable information in the Agent Client Protocol.
16///
17/// They provide a structured way to handle various types of user-facing content—whether
18/// it's text from language models, images for analysis, or embedded resources for context.
19///
20/// Content blocks appear in:
21/// - User prompts sent via `session/prompt`
22/// - Language model output streamed through `session/update` notifications
23/// - Progress updates and results from tool calls
24///
25/// This structure is compatible with the Model Context Protocol (MCP), enabling
26/// agents to seamlessly forward content from MCP tool outputs without transformation.
27///
28/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/content)
29#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
30#[serde(tag = "type", rename_all = "snake_case")]
31#[schemars(extend("discriminator" = {"propertyName": "type"}))]
32#[non_exhaustive]
33pub enum ContentBlock {
34    /// Text content. May be plain text or formatted with Markdown.
35    ///
36    /// All agents MUST support text content blocks in prompts.
37    /// Clients SHOULD render this text as Markdown.
38    Text(TextContent),
39    /// Images for visual context or analysis.
40    ///
41    /// Requires the `image` prompt capability when included in prompts.
42    Image(ImageContent),
43    /// Audio data for transcription or analysis.
44    ///
45    /// Requires the `audio` prompt capability when included in prompts.
46    Audio(AudioContent),
47    /// References to resources that the agent can access.
48    ///
49    /// All agents MUST support resource links in prompts.
50    ResourceLink(ResourceLink),
51    /// Complete resource contents embedded directly in the message.
52    ///
53    /// Preferred for including context as it avoids extra round-trips.
54    ///
55    /// Requires the `embeddedContext` prompt capability when included in prompts.
56    Resource(EmbeddedResource),
57}
58
59/// Text provided to or from an LLM.
60#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
61#[non_exhaustive]
62pub struct TextContent {
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub annotations: Option<Annotations>,
65    pub text: String,
66    /// Extension point for implementations
67    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
68    pub meta: Option<serde_json::Value>,
69}
70
71impl TextContent {
72    pub fn new(text: impl Into<String>) -> Self {
73        Self {
74            annotations: None,
75            text: text.into(),
76            meta: None,
77        }
78    }
79
80    #[must_use]
81    pub fn annotations(mut self, annotations: Annotations) -> Self {
82        self.annotations = Some(annotations);
83        self
84    }
85
86    /// Extension point for implementations
87    #[must_use]
88    pub fn meta(mut self, meta: serde_json::Value) -> Self {
89        self.meta = Some(meta);
90        self
91    }
92}
93
94impl<T: Into<String>> From<T> for ContentBlock {
95    fn from(value: T) -> Self {
96        Self::Text(TextContent::new(value))
97    }
98}
99
100/// An image provided to or from an LLM.
101#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
102#[serde(rename_all = "camelCase")]
103#[non_exhaustive]
104pub struct ImageContent {
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub annotations: Option<Annotations>,
107    pub data: String,
108    pub mime_type: String,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub uri: Option<String>,
111    /// Extension point for implementations
112    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
113    pub meta: Option<serde_json::Value>,
114}
115
116impl ImageContent {
117    pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
118        Self {
119            annotations: None,
120            data: data.into(),
121            mime_type: mime_type.into(),
122            uri: None,
123            meta: None,
124        }
125    }
126
127    #[must_use]
128    pub fn annotations(mut self, annotations: Annotations) -> Self {
129        self.annotations = Some(annotations);
130        self
131    }
132
133    #[must_use]
134    pub fn uri(mut self, uri: impl Into<String>) -> Self {
135        self.uri = Some(uri.into());
136        self
137    }
138
139    /// Extension point for implementations
140    #[must_use]
141    pub fn meta(mut self, meta: serde_json::Value) -> Self {
142        self.meta = Some(meta);
143        self
144    }
145}
146
147/// Audio provided to or from an LLM.
148#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
149#[serde(rename_all = "camelCase")]
150#[non_exhaustive]
151pub struct AudioContent {
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub annotations: Option<Annotations>,
154    pub data: String,
155    pub mime_type: String,
156    /// Extension point for implementations
157    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
158    pub meta: Option<serde_json::Value>,
159}
160
161impl AudioContent {
162    pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
163        Self {
164            annotations: None,
165            data: data.into(),
166            mime_type: mime_type.into(),
167            meta: None,
168        }
169    }
170
171    #[must_use]
172    pub fn annotations(mut self, annotations: Annotations) -> Self {
173        self.annotations = Some(annotations);
174        self
175    }
176
177    /// Extension point for implementations
178    #[must_use]
179    pub fn meta(mut self, meta: serde_json::Value) -> Self {
180        self.meta = Some(meta);
181        self
182    }
183}
184
185/// The contents of a resource, embedded into a prompt or tool call result.
186#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
187#[non_exhaustive]
188pub struct EmbeddedResource {
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub annotations: Option<Annotations>,
191    pub resource: EmbeddedResourceResource,
192    /// Extension point for implementations
193    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
194    pub meta: Option<serde_json::Value>,
195}
196
197impl EmbeddedResource {
198    #[must_use]
199    pub fn new(resource: EmbeddedResourceResource) -> Self {
200        Self {
201            annotations: None,
202            resource,
203            meta: None,
204        }
205    }
206
207    #[must_use]
208    pub fn annotations(mut self, annotations: Annotations) -> Self {
209        self.annotations = Some(annotations);
210        self
211    }
212
213    /// Extension point for implementations
214    #[must_use]
215    pub fn meta(mut self, meta: serde_json::Value) -> Self {
216        self.meta = Some(meta);
217        self
218    }
219}
220
221/// Resource content that can be embedded in a message.
222#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
223#[serde(untagged)]
224#[non_exhaustive]
225pub enum EmbeddedResourceResource {
226    TextResourceContents(TextResourceContents),
227    BlobResourceContents(BlobResourceContents),
228}
229
230/// Text-based resource contents.
231#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
232#[serde(rename_all = "camelCase")]
233#[non_exhaustive]
234pub struct TextResourceContents {
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub mime_type: Option<String>,
237    pub text: String,
238    pub uri: String,
239    /// Extension point for implementations
240    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
241    pub meta: Option<serde_json::Value>,
242}
243
244impl TextResourceContents {
245    pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
246        Self {
247            mime_type: None,
248            text: text.into(),
249            uri: uri.into(),
250            meta: None,
251        }
252    }
253
254    #[must_use]
255    pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
256        self.mime_type = Some(mime_type.into());
257        self
258    }
259
260    /// Extension point for implementations
261    #[must_use]
262    pub fn meta(mut self, meta: serde_json::Value) -> Self {
263        self.meta = Some(meta);
264        self
265    }
266}
267
268/// Binary resource contents.
269#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
270#[serde(rename_all = "camelCase")]
271#[non_exhaustive]
272pub struct BlobResourceContents {
273    pub blob: String,
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub mime_type: Option<String>,
276    pub uri: String,
277    /// Extension point for implementations
278    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
279    pub meta: Option<serde_json::Value>,
280}
281
282impl BlobResourceContents {
283    pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
284        Self {
285            blob: blob.into(),
286            mime_type: None,
287            uri: uri.into(),
288            meta: None,
289        }
290    }
291
292    #[must_use]
293    pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
294        self.mime_type = Some(mime_type.into());
295        self
296    }
297
298    /// Extension point for implementations
299    #[must_use]
300    pub fn meta(mut self, meta: serde_json::Value) -> Self {
301        self.meta = Some(meta);
302        self
303    }
304}
305
306/// A resource that the server is capable of reading, included in a prompt or tool call result.
307#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
308#[serde(rename_all = "camelCase")]
309#[non_exhaustive]
310pub struct ResourceLink {
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub annotations: Option<Annotations>,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub description: Option<String>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub mime_type: Option<String>,
317    pub name: String,
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub size: Option<i64>,
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub title: Option<String>,
322    pub uri: String,
323    /// Extension point for implementations
324    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
325    pub meta: Option<serde_json::Value>,
326}
327
328impl ResourceLink {
329    pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
330        Self {
331            annotations: None,
332            description: None,
333            mime_type: None,
334            name: name.into(),
335            size: None,
336            title: None,
337            uri: uri.into(),
338            meta: None,
339        }
340    }
341
342    #[must_use]
343    pub fn annotations(mut self, annotations: Annotations) -> Self {
344        self.annotations = Some(annotations);
345        self
346    }
347
348    #[must_use]
349    pub fn description(mut self, description: impl Into<String>) -> Self {
350        self.description = Some(description.into());
351        self
352    }
353
354    #[must_use]
355    pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
356        self.mime_type = Some(mime_type.into());
357        self
358    }
359
360    #[must_use]
361    pub fn size(mut self, size: i64) -> Self {
362        self.size = Some(size);
363        self
364    }
365
366    #[must_use]
367    pub fn title(mut self, title: impl Into<String>) -> Self {
368        self.title = Some(title.into());
369        self
370    }
371
372    /// Extension point for implementations
373    #[must_use]
374    pub fn meta(mut self, meta: serde_json::Value) -> Self {
375        self.meta = Some(meta);
376        self
377    }
378}
379
380/// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
381#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, Default)]
382#[serde(rename_all = "camelCase")]
383#[non_exhaustive]
384pub struct Annotations {
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub audience: Option<Vec<Role>>,
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub last_modified: Option<String>,
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub priority: Option<f64>,
391    /// Extension point for implementations
392    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
393    pub meta: Option<serde_json::Value>,
394}
395
396impl Annotations {
397    #[must_use]
398    pub fn new() -> Self {
399        Self::default()
400    }
401
402    #[must_use]
403    pub fn audience(mut self, audience: Vec<Role>) -> Self {
404        self.audience = Some(audience);
405        self
406    }
407
408    #[must_use]
409    pub fn last_modified(mut self, last_modified: impl Into<String>) -> Self {
410        self.last_modified = Some(last_modified.into());
411        self
412    }
413
414    #[must_use]
415    pub fn priority(mut self, priority: f64) -> Self {
416        self.priority = Some(priority);
417        self
418    }
419
420    /// Extension point for implementations
421    #[must_use]
422    pub fn meta(mut self, meta: serde_json::Value) -> Self {
423        self.meta = Some(meta);
424        self
425    }
426}
427
428/// The sender or recipient of messages and data in a conversation.
429#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
430#[serde(rename_all = "camelCase")]
431#[non_exhaustive]
432pub enum Role {
433    Assistant,
434    User,
435}