Skip to main content

agent_client_protocol_schema/v1/
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};
14use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
15
16use crate::{IntoOption, Meta, SkipListener};
17
18/// Content blocks represent displayable information in the Agent Client Protocol.
19///
20/// They provide a structured way to handle various types of user-facing content—whether
21/// it's text from language models, images for analysis, or embedded resources for context.
22///
23/// Content blocks appear in:
24/// - User prompts sent via `session/prompt`
25/// - Language model output streamed through `session/update` notifications
26/// - Progress updates and results from tool calls
27///
28/// This structure is compatible with the Model Context Protocol (MCP), enabling
29/// agents to seamlessly forward content from MCP tool outputs without transformation.
30///
31/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/content)
32#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
33#[serde(tag = "type", rename_all = "snake_case")]
34#[schemars(extend("discriminator" = {"propertyName": "type"}))]
35#[non_exhaustive]
36pub enum ContentBlock {
37    /// Text content. May be plain text or formatted with Markdown.
38    ///
39    /// All agents MUST support text content blocks in prompts.
40    /// Clients SHOULD render this text as Markdown.
41    Text(TextContent),
42    /// Images for visual context or analysis.
43    ///
44    /// Requires the `image` prompt capability when included in prompts.
45    Image(ImageContent),
46    /// Audio data for transcription or analysis.
47    ///
48    /// Requires the `audio` prompt capability when included in prompts.
49    Audio(AudioContent),
50    /// References to resources that the agent can access.
51    ///
52    /// All agents MUST support resource links in prompts.
53    ResourceLink(ResourceLink),
54    /// Complete resource contents embedded directly in the message.
55    ///
56    /// Preferred for including context as it avoids extra round-trips.
57    ///
58    /// Requires the `embeddedContext` prompt capability when included in prompts.
59    Resource(EmbeddedResource),
60}
61
62/// Text provided to or from an LLM.
63#[serde_as]
64#[skip_serializing_none]
65#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
66#[non_exhaustive]
67pub struct TextContent {
68    /// Optional annotations that help clients decide how to display or route this content.
69    #[serde_as(deserialize_as = "DefaultOnError")]
70    #[schemars(extend("x-deserialize-default-on-error" = true))]
71    #[serde(default)]
72    pub annotations: Option<Annotations>,
73    /// Text payload carried by this content block.
74    pub text: String,
75    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
76    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
77    /// these keys.
78    ///
79    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
80    #[serde(rename = "_meta")]
81    pub meta: Option<Meta>,
82}
83
84impl TextContent {
85    /// Builds [`TextContent`] with its required content payload; optional annotations and metadata start unset.
86    #[must_use]
87    pub fn new(text: impl Into<String>) -> Self {
88        Self {
89            annotations: None,
90            text: text.into(),
91            meta: None,
92        }
93    }
94
95    /// Sets or clears the optional `annotations` field.
96    #[must_use]
97    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
98        self.annotations = annotations.into_option();
99        self
100    }
101
102    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
103    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
104    /// these keys.
105    ///
106    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
107    #[must_use]
108    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
109        self.meta = meta.into_option();
110        self
111    }
112}
113
114impl<T: Into<String>> From<T> for ContentBlock {
115    fn from(value: T) -> Self {
116        Self::Text(TextContent::new(value))
117    }
118}
119
120/// An image provided to or from an LLM.
121#[serde_as]
122#[skip_serializing_none]
123#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
124#[serde(rename_all = "camelCase")]
125#[non_exhaustive]
126pub struct ImageContent {
127    /// Optional annotations that help clients decide how to display or route this content.
128    #[serde_as(deserialize_as = "DefaultOnError")]
129    #[schemars(extend("x-deserialize-default-on-error" = true))]
130    #[serde(default)]
131    pub annotations: Option<Annotations>,
132    /// Base64-encoded media payload.
133    pub data: String,
134    /// MIME type describing the encoded media payload.
135    pub mime_type: String,
136    /// URI associated with this resource or media payload.
137    pub uri: Option<String>,
138    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
139    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
140    /// these keys.
141    ///
142    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
143    #[serde(rename = "_meta")]
144    pub meta: Option<Meta>,
145}
146
147impl ImageContent {
148    /// Builds [`ImageContent`] with its required content payload; optional annotations and metadata start unset.
149    #[must_use]
150    pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
151        Self {
152            annotations: None,
153            data: data.into(),
154            mime_type: mime_type.into(),
155            uri: None,
156            meta: None,
157        }
158    }
159
160    /// Sets or clears the optional `annotations` field.
161    #[must_use]
162    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
163        self.annotations = annotations.into_option();
164        self
165    }
166
167    /// Sets or clears the optional `uri` field.
168    #[must_use]
169    pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
170        self.uri = uri.into_option();
171        self
172    }
173
174    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
175    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
176    /// these keys.
177    ///
178    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
179    #[must_use]
180    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
181        self.meta = meta.into_option();
182        self
183    }
184}
185
186/// Audio provided to or from an LLM.
187#[serde_as]
188#[skip_serializing_none]
189#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
190#[serde(rename_all = "camelCase")]
191#[non_exhaustive]
192pub struct AudioContent {
193    /// Optional annotations that help clients decide how to display or route this content.
194    #[serde_as(deserialize_as = "DefaultOnError")]
195    #[schemars(extend("x-deserialize-default-on-error" = true))]
196    #[serde(default)]
197    pub annotations: Option<Annotations>,
198    /// Base64-encoded media payload.
199    pub data: String,
200    /// MIME type describing the encoded media payload.
201    pub mime_type: String,
202    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
203    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
204    /// these keys.
205    ///
206    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
207    #[serde(rename = "_meta")]
208    pub meta: Option<Meta>,
209}
210
211impl AudioContent {
212    /// Builds [`AudioContent`] with its required content payload; optional annotations and metadata start unset.
213    #[must_use]
214    pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
215        Self {
216            annotations: None,
217            data: data.into(),
218            mime_type: mime_type.into(),
219            meta: None,
220        }
221    }
222
223    /// Sets or clears the optional `annotations` field.
224    #[must_use]
225    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
226        self.annotations = annotations.into_option();
227        self
228    }
229
230    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
231    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
232    /// these keys.
233    ///
234    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
235    #[must_use]
236    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
237        self.meta = meta.into_option();
238        self
239    }
240}
241
242/// The contents of a resource, embedded into a prompt or tool call result.
243#[serde_as]
244#[skip_serializing_none]
245#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
246#[non_exhaustive]
247pub struct EmbeddedResource {
248    /// Optional annotations that help clients decide how to display or route this content.
249    #[serde_as(deserialize_as = "DefaultOnError")]
250    #[schemars(extend("x-deserialize-default-on-error" = true))]
251    #[serde(default)]
252    pub annotations: Option<Annotations>,
253    /// Embedded resource payload, either text or binary data.
254    pub resource: EmbeddedResourceResource,
255    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
256    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
257    /// these keys.
258    ///
259    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
260    #[serde(rename = "_meta")]
261    pub meta: Option<Meta>,
262}
263
264impl EmbeddedResource {
265    /// Builds [`EmbeddedResource`] with its required content payload; optional annotations and metadata start unset.
266    #[must_use]
267    pub fn new(resource: EmbeddedResourceResource) -> Self {
268        Self {
269            annotations: None,
270            resource,
271            meta: None,
272        }
273    }
274
275    /// Sets or clears the optional `annotations` field.
276    #[must_use]
277    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
278        self.annotations = annotations.into_option();
279        self
280    }
281
282    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
283    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
284    /// these keys.
285    ///
286    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
287    #[must_use]
288    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
289        self.meta = meta.into_option();
290        self
291    }
292}
293
294/// Resource content that can be embedded in a message.
295#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
296#[serde(untagged)]
297#[non_exhaustive]
298pub enum EmbeddedResourceResource {
299    /// Text resource contents embedded directly in the message.
300    TextResourceContents(TextResourceContents),
301    /// Binary resource contents embedded directly in the message.
302    BlobResourceContents(BlobResourceContents),
303}
304
305/// Text-based resource contents.
306#[skip_serializing_none]
307#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
308#[serde(rename_all = "camelCase")]
309#[non_exhaustive]
310pub struct TextResourceContents {
311    /// MIME type describing the encoded media payload.
312    pub mime_type: Option<String>,
313    /// Text payload carried by this content block.
314    pub text: String,
315    /// URI associated with this resource or media payload.
316    pub uri: String,
317    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
318    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
319    /// these keys.
320    ///
321    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
322    #[serde(rename = "_meta")]
323    pub meta: Option<Meta>,
324}
325
326impl TextResourceContents {
327    /// Builds [`TextResourceContents`] with its required content payload; optional annotations and metadata start unset.
328    #[must_use]
329    pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
330        Self {
331            mime_type: None,
332            text: text.into(),
333            uri: uri.into(),
334            meta: None,
335        }
336    }
337
338    /// Sets or clears the optional `mimeType` field.
339    #[must_use]
340    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
341        self.mime_type = mime_type.into_option();
342        self
343    }
344
345    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
346    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
347    /// these keys.
348    ///
349    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
350    #[must_use]
351    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
352        self.meta = meta.into_option();
353        self
354    }
355}
356
357/// Binary resource contents.
358#[skip_serializing_none]
359#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
360#[serde(rename_all = "camelCase")]
361#[non_exhaustive]
362pub struct BlobResourceContents {
363    /// Base64-encoded bytes for a binary resource payload.
364    pub blob: String,
365    /// MIME type describing the encoded media payload.
366    pub mime_type: Option<String>,
367    /// URI associated with this resource or media payload.
368    pub uri: String,
369    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
370    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
371    /// these keys.
372    ///
373    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
374    #[serde(rename = "_meta")]
375    pub meta: Option<Meta>,
376}
377
378impl BlobResourceContents {
379    /// Builds [`BlobResourceContents`] with its required content payload; optional annotations and metadata start unset.
380    #[must_use]
381    pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
382        Self {
383            blob: blob.into(),
384            mime_type: None,
385            uri: uri.into(),
386            meta: None,
387        }
388    }
389
390    /// Sets or clears the optional `mimeType` field.
391    #[must_use]
392    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
393        self.mime_type = mime_type.into_option();
394        self
395    }
396
397    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
398    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
399    /// these keys.
400    ///
401    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
402    #[must_use]
403    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
404        self.meta = meta.into_option();
405        self
406    }
407}
408
409/// A resource that the server is capable of reading, included in a prompt or tool call result.
410#[serde_as]
411#[skip_serializing_none]
412#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
413#[serde(rename_all = "camelCase")]
414#[non_exhaustive]
415pub struct ResourceLink {
416    /// Optional annotations that help clients decide how to display or route this content.
417    #[serde_as(deserialize_as = "DefaultOnError")]
418    #[schemars(extend("x-deserialize-default-on-error" = true))]
419    #[serde(default)]
420    pub annotations: Option<Annotations>,
421    /// Optional human-readable details shown with this protocol object.
422    pub description: Option<String>,
423    /// MIME type describing the encoded media payload.
424    pub mime_type: Option<String>,
425    /// Human-readable name shown for this protocol object.
426    pub name: String,
427    /// Optional size of the linked resource in bytes, if known.
428    pub size: Option<i64>,
429    /// Optional display title for end-user UI.
430    pub title: Option<String>,
431    /// URI associated with this resource or media payload.
432    pub uri: String,
433    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
434    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
435    /// these keys.
436    ///
437    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
438    #[serde(rename = "_meta")]
439    pub meta: Option<Meta>,
440}
441
442impl ResourceLink {
443    /// Builds [`ResourceLink`] with its required content payload; optional annotations and metadata start unset.
444    #[must_use]
445    pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
446        Self {
447            annotations: None,
448            description: None,
449            mime_type: None,
450            name: name.into(),
451            size: None,
452            title: None,
453            uri: uri.into(),
454            meta: None,
455        }
456    }
457
458    /// Sets or clears the optional `annotations` field.
459    #[must_use]
460    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
461        self.annotations = annotations.into_option();
462        self
463    }
464
465    /// Sets or clears the optional `description` field.
466    #[must_use]
467    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
468        self.description = description.into_option();
469        self
470    }
471
472    /// Sets or clears the optional `mimeType` field.
473    #[must_use]
474    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
475        self.mime_type = mime_type.into_option();
476        self
477    }
478
479    /// Sets or clears the optional `size` field.
480    #[must_use]
481    pub fn size(mut self, size: impl IntoOption<i64>) -> Self {
482        self.size = size.into_option();
483        self
484    }
485
486    /// Sets or clears the optional `title` field.
487    #[must_use]
488    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
489        self.title = title.into_option();
490        self
491    }
492
493    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
494    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
495    /// these keys.
496    ///
497    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
498    #[must_use]
499    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
500        self.meta = meta.into_option();
501        self
502    }
503}
504
505/// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
506#[serde_as]
507#[skip_serializing_none]
508#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, Default)]
509#[serde(rename_all = "camelCase")]
510#[non_exhaustive]
511pub struct Annotations {
512    /// Intended recipients for this content, such as the user or assistant.
513    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
514    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
515    #[serde(default)]
516    pub audience: Option<Vec<Role>>,
517    /// Timestamp indicating when the underlying resource was last modified.
518    pub last_modified: Option<String>,
519    /// Relative importance of this content when clients choose what to surface.
520    pub priority: Option<f64>,
521    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
522    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
523    /// these keys.
524    ///
525    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
526    #[serde(rename = "_meta")]
527    pub meta: Option<Meta>,
528}
529
530impl Annotations {
531    /// Creates annotations with no audience, priority, or timestamp hints set.
532    #[must_use]
533    pub fn new() -> Self {
534        Self::default()
535    }
536
537    /// Sets or clears the optional `audience` field.
538    #[must_use]
539    pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
540        self.audience = audience.into_option();
541        self
542    }
543
544    /// Sets or clears the optional `lastModified` field.
545    #[must_use]
546    pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
547        self.last_modified = last_modified.into_option();
548        self
549    }
550
551    /// Sets or clears the optional `priority` field.
552    #[must_use]
553    pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
554        self.priority = priority.into_option();
555        self
556    }
557
558    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
559    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
560    /// these keys.
561    ///
562    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
563    #[must_use]
564    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
565        self.meta = meta.into_option();
566        self
567    }
568}
569
570/// The sender or recipient of messages and data in a conversation.
571#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
572#[serde(rename_all = "camelCase")]
573#[non_exhaustive]
574pub enum Role {
575    /// The assistant side of a conversation.
576    Assistant,
577    /// The user side of a conversation.
578    User,
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584
585    #[test]
586    fn test_text_content_roundtrip() {
587        let content = TextContent::new("hello world");
588        let json = serde_json::to_value(&content).unwrap();
589        let parsed: TextContent = serde_json::from_value(json).unwrap();
590        assert_eq!(content, parsed);
591    }
592
593    #[test]
594    fn test_text_content_omits_optional_fields() {
595        let content = TextContent::new("hello");
596        let json = serde_json::to_value(&content).unwrap();
597        assert!(!json.as_object().unwrap().contains_key("annotations"));
598        assert!(!json.as_object().unwrap().contains_key("meta"));
599    }
600
601    #[test]
602    fn test_text_content_from_string() {
603        let block: ContentBlock = "hello".into();
604        match block {
605            ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
606            _ => panic!("Expected Text variant"),
607        }
608    }
609
610    #[test]
611    fn test_image_content_roundtrip() {
612        let content = ImageContent::new("base64data", "image/png");
613        let json = serde_json::to_value(&content).unwrap();
614        let parsed: ImageContent = serde_json::from_value(json).unwrap();
615        assert_eq!(content, parsed);
616    }
617
618    #[test]
619    fn test_image_content_omits_optional_fields() {
620        let content = ImageContent::new("data", "image/png");
621        let json = serde_json::to_value(&content).unwrap();
622        assert!(!json.as_object().unwrap().contains_key("uri"));
623        assert!(!json.as_object().unwrap().contains_key("annotations"));
624        assert!(!json.as_object().unwrap().contains_key("meta"));
625    }
626
627    #[test]
628    fn test_image_content_with_uri() {
629        let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
630        let json = serde_json::to_value(&content).unwrap();
631        assert_eq!(json["uri"], "https://example.com/image.png");
632    }
633
634    #[test]
635    fn test_audio_content_roundtrip() {
636        let content = AudioContent::new("base64audio", "audio/mp3");
637        let json = serde_json::to_value(&content).unwrap();
638        let parsed: AudioContent = serde_json::from_value(json).unwrap();
639        assert_eq!(content, parsed);
640    }
641
642    #[test]
643    fn test_audio_content_omits_optional_fields() {
644        let content = AudioContent::new("data", "audio/mp3");
645        let json = serde_json::to_value(&content).unwrap();
646        assert!(!json.as_object().unwrap().contains_key("annotations"));
647        assert!(!json.as_object().unwrap().contains_key("meta"));
648    }
649}