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