Skip to main content

agent_client_protocol_schema/v2/
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 std::collections::BTreeMap;
13
14use schemars::{JsonSchema, Schema};
15use serde::{Deserialize, Serialize};
16use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
17
18use super::Meta;
19use crate::{IntoOption, SkipListener};
20
21/// Content blocks represent displayable information in the Agent Client Protocol.
22///
23/// They provide a structured way to handle various types of user-facing content—whether
24/// it's text from language models, images for analysis, or embedded resources for context.
25///
26/// Content blocks appear in:
27/// - User prompts sent via `session/prompt`
28/// - Language model output reported through `session/update` notifications as
29///   message updates or streamed chunks
30/// - Progress updates and results from tool calls
31///
32/// This structure is compatible with the Model Context Protocol (MCP), enabling
33/// agents to seamlessly forward content from MCP tool outputs without transformation.
34///
35/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/content)
36#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
37#[serde(tag = "type", rename_all = "snake_case")]
38#[schemars(extend("discriminator" = {"propertyName": "type"}))]
39#[non_exhaustive]
40pub enum ContentBlock {
41    /// Text content. May be plain text or formatted with Markdown.
42    ///
43    /// All agents MUST support text content blocks in prompts.
44    /// Clients SHOULD render this text as Markdown.
45    Text(TextContent),
46    /// Images for visual context or analysis.
47    ///
48    /// Requires the `image` prompt capability when included in prompts.
49    Image(ImageContent),
50    /// Audio data for transcription or analysis.
51    ///
52    /// Requires the `audio` prompt capability when included in prompts.
53    Audio(AudioContent),
54    /// References to resources that the agent can access.
55    ///
56    /// All agents MUST support resource links in prompts.
57    ResourceLink(ResourceLink),
58    /// Complete resource contents embedded directly in the message.
59    ///
60    /// Preferred for including context as it avoids extra round-trips.
61    ///
62    /// Requires the `embeddedContext` prompt capability when included in prompts.
63    Resource(EmbeddedResource),
64    /// Custom or future content block.
65    ///
66    /// Values beginning with `_` are reserved for implementation-specific
67    /// extensions. Unknown values that do not begin with `_` are reserved for
68    /// future ACP variants.
69    ///
70    /// Receivers that do not understand this content block type should preserve
71    /// the raw payload when storing, replaying, proxying, or forwarding content,
72    /// and otherwise ignore it or display it generically.
73    #[serde(untagged)]
74    Other(OtherContentBlock),
75}
76
77/// Custom or future content block payload.
78#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
79#[schemars(inline)]
80#[schemars(transform = other_content_block_schema)]
81#[serde(rename_all = "camelCase")]
82#[non_exhaustive]
83pub struct OtherContentBlock {
84    /// Custom or future content block type.
85    ///
86    /// Values beginning with `_` are reserved for implementation-specific
87    /// extensions. Unknown values that do not begin with `_` are reserved for
88    /// future ACP variants.
89    #[serde(rename = "type")]
90    pub type_: String,
91    /// Additional fields from the unknown content block payload.
92    #[serde(flatten)]
93    pub fields: BTreeMap<String, serde_json::Value>,
94}
95
96impl OtherContentBlock {
97    /// Builds [`OtherContentBlock`] from an unknown discriminator and preserves the remaining extension fields.
98    #[must_use]
99    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
100        fields.remove("type");
101        Self {
102            type_: type_.into(),
103            fields,
104        }
105    }
106}
107
108impl<'de> Deserialize<'de> for OtherContentBlock {
109    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110    where
111        D: serde::Deserializer<'de>,
112    {
113        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
114        let type_ = fields
115            .remove("type")
116            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
117        let serde_json::Value::String(type_) = type_ else {
118            return Err(serde::de::Error::custom("`type` must be a string"));
119        };
120
121        if is_known_content_block_type(&type_) {
122            return Err(serde::de::Error::custom(format!(
123                "known content block `{type_}` did not match its schema"
124            )));
125        }
126
127        Ok(Self { type_, fields })
128    }
129}
130
131fn is_known_content_block_type(type_: &str) -> bool {
132    matches!(
133        type_,
134        "text" | "image" | "audio" | "resource_link" | "resource"
135    )
136}
137
138fn other_content_block_schema(schema: &mut Schema) {
139    super::schema_util::reject_known_string_discriminators(
140        schema,
141        "type",
142        &["text", "image", "audio", "resource_link", "resource"],
143    );
144}
145
146/// Text provided to or from an LLM.
147#[serde_as]
148#[skip_serializing_none]
149#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
150#[non_exhaustive]
151pub struct TextContent {
152    /// Text payload carried by this content block.
153    pub text: String,
154    /// Optional annotations that help clients decide how to display or route this content.
155    #[serde_as(deserialize_as = "DefaultOnError")]
156    #[schemars(extend("x-deserialize-default-on-error" = true))]
157    #[serde(default)]
158    pub annotations: Option<Annotations>,
159    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
160    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
161    /// these keys.
162    ///
163    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
164    #[serde_as(deserialize_as = "DefaultOnError")]
165    #[schemars(extend("x-deserialize-default-on-error" = true))]
166    #[serde(default)]
167    #[serde(rename = "_meta")]
168    pub meta: Option<Meta>,
169}
170
171impl TextContent {
172    /// Builds [`TextContent`] with its required content payload; optional annotations and metadata start unset.
173    #[must_use]
174    pub fn new(text: impl Into<String>) -> Self {
175        Self {
176            annotations: None,
177            text: text.into(),
178            meta: None,
179        }
180    }
181
182    /// Sets or clears the optional `annotations` field.
183    #[must_use]
184    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
185        self.annotations = annotations.into_option();
186        self
187    }
188
189    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
190    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
191    /// these keys.
192    ///
193    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
194    #[must_use]
195    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
196        self.meta = meta.into_option();
197        self
198    }
199}
200
201impl<T: Into<String>> From<T> for ContentBlock {
202    fn from(value: T) -> Self {
203        Self::Text(TextContent::new(value))
204    }
205}
206
207/// An image provided to or from an LLM.
208#[serde_as]
209#[skip_serializing_none]
210#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
211#[serde(rename_all = "camelCase")]
212#[non_exhaustive]
213pub struct ImageContent {
214    /// Base64-encoded media payload.
215    pub data: String,
216    /// MIME type describing the encoded media payload.
217    pub mime_type: String,
218    /// URI associated with this resource or media payload.
219    #[serde_as(deserialize_as = "DefaultOnError")]
220    #[schemars(extend("x-deserialize-default-on-error" = true))]
221    #[serde(default)]
222    pub uri: Option<String>,
223    /// Optional annotations that help clients decide how to display or route this content.
224    #[serde_as(deserialize_as = "DefaultOnError")]
225    #[schemars(extend("x-deserialize-default-on-error" = true))]
226    #[serde(default)]
227    pub annotations: Option<Annotations>,
228    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
229    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
230    /// these keys.
231    ///
232    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
233    #[serde_as(deserialize_as = "DefaultOnError")]
234    #[schemars(extend("x-deserialize-default-on-error" = true))]
235    #[serde(default)]
236    #[serde(rename = "_meta")]
237    pub meta: Option<Meta>,
238}
239
240impl ImageContent {
241    /// Builds [`ImageContent`] with its required content payload; optional annotations and metadata start unset.
242    #[must_use]
243    pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
244        Self {
245            annotations: None,
246            data: data.into(),
247            mime_type: mime_type.into(),
248            uri: None,
249            meta: None,
250        }
251    }
252
253    /// Sets or clears the optional `annotations` field.
254    #[must_use]
255    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
256        self.annotations = annotations.into_option();
257        self
258    }
259
260    /// Sets or clears the optional `uri` field.
261    #[must_use]
262    pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
263        self.uri = uri.into_option();
264        self
265    }
266
267    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
268    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
269    /// these keys.
270    ///
271    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
272    #[must_use]
273    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
274        self.meta = meta.into_option();
275        self
276    }
277}
278
279/// Audio provided to or from an LLM.
280#[serde_as]
281#[skip_serializing_none]
282#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
283#[serde(rename_all = "camelCase")]
284#[non_exhaustive]
285pub struct AudioContent {
286    /// Base64-encoded media payload.
287    pub data: String,
288    /// MIME type describing the encoded media payload.
289    pub mime_type: String,
290    /// Optional annotations that help clients decide how to display or route this content.
291    #[serde_as(deserialize_as = "DefaultOnError")]
292    #[schemars(extend("x-deserialize-default-on-error" = true))]
293    #[serde(default)]
294    pub annotations: Option<Annotations>,
295    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
296    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
297    /// these keys.
298    ///
299    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
300    #[serde_as(deserialize_as = "DefaultOnError")]
301    #[schemars(extend("x-deserialize-default-on-error" = true))]
302    #[serde(default)]
303    #[serde(rename = "_meta")]
304    pub meta: Option<Meta>,
305}
306
307impl AudioContent {
308    /// Builds [`AudioContent`] with its required content payload; optional annotations and metadata start unset.
309    #[must_use]
310    pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
311        Self {
312            annotations: None,
313            data: data.into(),
314            mime_type: mime_type.into(),
315            meta: None,
316        }
317    }
318
319    /// Sets or clears the optional `annotations` field.
320    #[must_use]
321    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
322        self.annotations = annotations.into_option();
323        self
324    }
325
326    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
327    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
328    /// these keys.
329    ///
330    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
331    #[must_use]
332    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
333        self.meta = meta.into_option();
334        self
335    }
336}
337
338/// The contents of a resource, embedded into a prompt or tool call result.
339#[serde_as]
340#[skip_serializing_none]
341#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
342#[non_exhaustive]
343pub struct EmbeddedResource {
344    /// Embedded resource payload, either text or binary data.
345    pub resource: EmbeddedResourceResource,
346    /// Optional annotations that help clients decide how to display or route this content.
347    #[serde_as(deserialize_as = "DefaultOnError")]
348    #[schemars(extend("x-deserialize-default-on-error" = true))]
349    #[serde(default)]
350    pub annotations: Option<Annotations>,
351    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
352    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
353    /// these keys.
354    ///
355    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
356    #[serde_as(deserialize_as = "DefaultOnError")]
357    #[schemars(extend("x-deserialize-default-on-error" = true))]
358    #[serde(default)]
359    #[serde(rename = "_meta")]
360    pub meta: Option<Meta>,
361}
362
363impl EmbeddedResource {
364    /// Builds [`EmbeddedResource`] with its required content payload; optional annotations and metadata start unset.
365    #[must_use]
366    pub fn new(resource: EmbeddedResourceResource) -> Self {
367        Self {
368            annotations: None,
369            resource,
370            meta: None,
371        }
372    }
373
374    /// Sets or clears the optional `annotations` field.
375    #[must_use]
376    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
377        self.annotations = annotations.into_option();
378        self
379    }
380
381    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
382    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
383    /// these keys.
384    ///
385    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
386    #[must_use]
387    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
388        self.meta = meta.into_option();
389        self
390    }
391}
392
393/// Resource content that can be embedded in a message.
394#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
395#[serde(untagged)]
396#[non_exhaustive]
397pub enum EmbeddedResourceResource {
398    /// Text resource contents embedded directly in the message.
399    TextResourceContents(TextResourceContents),
400    /// Binary resource contents embedded directly in the message.
401    BlobResourceContents(BlobResourceContents),
402}
403
404/// Text-based resource contents.
405#[serde_as]
406#[skip_serializing_none]
407#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
408#[serde(rename_all = "camelCase")]
409#[non_exhaustive]
410pub struct TextResourceContents {
411    /// Text payload carried by this content block.
412    pub text: String,
413    /// URI associated with this resource or media payload.
414    pub uri: String,
415    /// MIME type describing the encoded media payload.
416    #[serde_as(deserialize_as = "DefaultOnError")]
417    #[schemars(extend("x-deserialize-default-on-error" = true))]
418    #[serde(default)]
419    pub mime_type: Option<String>,
420    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
421    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
422    /// these keys.
423    ///
424    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
425    #[serde_as(deserialize_as = "DefaultOnError")]
426    #[schemars(extend("x-deserialize-default-on-error" = true))]
427    #[serde(default)]
428    #[serde(rename = "_meta")]
429    pub meta: Option<Meta>,
430}
431
432impl TextResourceContents {
433    /// Builds [`TextResourceContents`] with its required content payload; optional annotations and metadata start unset.
434    #[must_use]
435    pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
436        Self {
437            mime_type: None,
438            text: text.into(),
439            uri: uri.into(),
440            meta: None,
441        }
442    }
443
444    /// Sets or clears the optional `mimeType` field.
445    #[must_use]
446    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
447        self.mime_type = mime_type.into_option();
448        self
449    }
450
451    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
452    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
453    /// these keys.
454    ///
455    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
456    #[must_use]
457    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
458        self.meta = meta.into_option();
459        self
460    }
461}
462
463/// Binary resource contents.
464#[serde_as]
465#[skip_serializing_none]
466#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
467#[serde(rename_all = "camelCase")]
468#[non_exhaustive]
469pub struct BlobResourceContents {
470    /// Base64-encoded bytes for a binary resource payload.
471    pub blob: String,
472    /// URI associated with this resource or media payload.
473    pub uri: String,
474    /// MIME type describing the encoded media payload.
475    #[serde_as(deserialize_as = "DefaultOnError")]
476    #[schemars(extend("x-deserialize-default-on-error" = true))]
477    #[serde(default)]
478    pub mime_type: Option<String>,
479    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
480    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
481    /// these keys.
482    ///
483    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
484    #[serde_as(deserialize_as = "DefaultOnError")]
485    #[schemars(extend("x-deserialize-default-on-error" = true))]
486    #[serde(default)]
487    #[serde(rename = "_meta")]
488    pub meta: Option<Meta>,
489}
490
491impl BlobResourceContents {
492    /// Builds [`BlobResourceContents`] with its required content payload; optional annotations and metadata start unset.
493    #[must_use]
494    pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
495        Self {
496            blob: blob.into(),
497            mime_type: None,
498            uri: uri.into(),
499            meta: None,
500        }
501    }
502
503    /// Sets or clears the optional `mimeType` field.
504    #[must_use]
505    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
506        self.mime_type = mime_type.into_option();
507        self
508    }
509
510    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
511    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
512    /// these keys.
513    ///
514    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
515    #[must_use]
516    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
517        self.meta = meta.into_option();
518        self
519    }
520}
521
522/// A resource that the server is capable of reading, included in a prompt or tool call result.
523#[serde_as]
524#[skip_serializing_none]
525#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
526#[serde(rename_all = "camelCase")]
527#[non_exhaustive]
528pub struct ResourceLink {
529    /// Human-readable name shown for this protocol object.
530    pub name: String,
531    /// URI associated with this resource or media payload.
532    pub uri: String,
533    /// Optional display title for end-user UI.
534    #[serde_as(deserialize_as = "DefaultOnError")]
535    #[schemars(extend("x-deserialize-default-on-error" = true))]
536    #[serde(default)]
537    pub title: Option<String>,
538    /// Optional human-readable details shown with this protocol object.
539    #[serde_as(deserialize_as = "DefaultOnError")]
540    #[schemars(extend("x-deserialize-default-on-error" = true))]
541    #[serde(default)]
542    pub description: Option<String>,
543    /// MIME type describing the encoded media payload.
544    #[serde_as(deserialize_as = "DefaultOnError")]
545    #[schemars(extend("x-deserialize-default-on-error" = true))]
546    #[serde(default)]
547    pub mime_type: Option<String>,
548    /// Optional size of the linked resource in bytes, if known.
549    #[serde_as(deserialize_as = "DefaultOnError")]
550    #[schemars(extend("x-deserialize-default-on-error" = true))]
551    #[serde(default)]
552    pub size: Option<i64>,
553    /// Optional annotations that help clients decide how to display or route this content.
554    #[serde_as(deserialize_as = "DefaultOnError")]
555    #[schemars(extend("x-deserialize-default-on-error" = true))]
556    #[serde(default)]
557    pub annotations: Option<Annotations>,
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    #[serde_as(deserialize_as = "DefaultOnError")]
564    #[schemars(extend("x-deserialize-default-on-error" = true))]
565    #[serde(default)]
566    #[serde(rename = "_meta")]
567    pub meta: Option<Meta>,
568}
569
570impl ResourceLink {
571    /// Builds [`ResourceLink`] with its required content payload; optional annotations and metadata start unset.
572    #[must_use]
573    pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
574        Self {
575            annotations: None,
576            description: None,
577            mime_type: None,
578            name: name.into(),
579            size: None,
580            title: None,
581            uri: uri.into(),
582            meta: None,
583        }
584    }
585
586    /// Sets or clears the optional `annotations` field.
587    #[must_use]
588    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
589        self.annotations = annotations.into_option();
590        self
591    }
592
593    /// Sets or clears the optional `description` field.
594    #[must_use]
595    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
596        self.description = description.into_option();
597        self
598    }
599
600    /// Sets or clears the optional `mimeType` field.
601    #[must_use]
602    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
603        self.mime_type = mime_type.into_option();
604        self
605    }
606
607    /// Sets or clears the optional `size` field.
608    #[must_use]
609    pub fn size(mut self, size: impl IntoOption<i64>) -> Self {
610        self.size = size.into_option();
611        self
612    }
613
614    /// Sets or clears the optional `title` field.
615    #[must_use]
616    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
617        self.title = title.into_option();
618        self
619    }
620
621    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
622    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
623    /// these keys.
624    ///
625    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
626    #[must_use]
627    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
628        self.meta = meta.into_option();
629        self
630    }
631}
632
633/// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
634#[serde_as]
635#[skip_serializing_none]
636#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, Default)]
637#[serde(rename_all = "camelCase")]
638#[non_exhaustive]
639pub struct Annotations {
640    /// Intended recipients for this content, such as the user or assistant.
641    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
642    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
643    #[serde(default)]
644    pub audience: Option<Vec<Role>>,
645    /// Timestamp indicating when the underlying resource was last modified.
646    #[serde_as(deserialize_as = "DefaultOnError")]
647    #[schemars(extend("x-deserialize-default-on-error" = true))]
648    #[serde(default)]
649    pub last_modified: Option<String>,
650    /// Relative importance of this content when clients choose what to surface.
651    #[serde_as(deserialize_as = "DefaultOnError")]
652    #[schemars(extend("x-deserialize-default-on-error" = true))]
653    #[serde(default)]
654    pub priority: Option<f64>,
655    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
656    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
657    /// these keys.
658    ///
659    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
660    #[serde_as(deserialize_as = "DefaultOnError")]
661    #[schemars(extend("x-deserialize-default-on-error" = true))]
662    #[serde(default)]
663    #[serde(rename = "_meta")]
664    pub meta: Option<Meta>,
665}
666
667impl Annotations {
668    /// Creates annotations with no audience, priority, or timestamp hints set.
669    #[must_use]
670    pub fn new() -> Self {
671        Self::default()
672    }
673
674    /// Sets or clears the optional `audience` field.
675    #[must_use]
676    pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
677        self.audience = audience.into_option();
678        self
679    }
680
681    /// Sets or clears the optional `lastModified` field.
682    #[must_use]
683    pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
684        self.last_modified = last_modified.into_option();
685        self
686    }
687
688    /// Sets or clears the optional `priority` field.
689    #[must_use]
690    pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
691        self.priority = priority.into_option();
692        self
693    }
694
695    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
696    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
697    /// these keys.
698    ///
699    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
700    #[must_use]
701    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
702        self.meta = meta.into_option();
703        self
704    }
705}
706
707/// The sender or recipient of messages and data in a conversation.
708#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
709#[serde(rename_all = "camelCase")]
710#[non_exhaustive]
711pub enum Role {
712    /// The assistant side of a conversation.
713    Assistant,
714    /// The user side of a conversation.
715    User,
716    /// Custom or future role.
717    ///
718    /// Values beginning with `_` are reserved for implementation-specific
719    /// extensions. Unknown values that do not begin with `_` are reserved for
720    /// future ACP variants.
721    #[serde(untagged)]
722    Other(String),
723}
724
725#[cfg(test)]
726mod tests {
727    use super::*;
728
729    #[test]
730    fn test_text_content_roundtrip() {
731        let content = TextContent::new("hello world");
732        let json = serde_json::to_value(&content).unwrap();
733        let parsed: TextContent = serde_json::from_value(json).unwrap();
734        assert_eq!(content, parsed);
735    }
736
737    #[test]
738    fn test_text_content_omits_optional_fields() {
739        let content = TextContent::new("hello");
740        let json = serde_json::to_value(&content).unwrap();
741        assert!(!json.as_object().unwrap().contains_key("annotations"));
742        assert!(!json.as_object().unwrap().contains_key("meta"));
743    }
744
745    #[test]
746    fn test_text_content_meta_defaults_on_missing_or_malformed_value() {
747        let missing: TextContent = serde_json::from_value(serde_json::json!({
748            "text": "hello"
749        }))
750        .unwrap();
751        assert_eq!(missing.meta, None);
752
753        let malformed: TextContent = serde_json::from_value(serde_json::json!({
754            "text": "hello",
755            "_meta": false
756        }))
757        .unwrap();
758        assert_eq!(malformed.meta, None);
759    }
760
761    #[test]
762    fn test_text_content_from_string() {
763        let block: ContentBlock = "hello".into();
764        match block {
765            ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
766            _ => panic!("Expected Text variant"),
767        }
768    }
769
770    #[test]
771    fn role_preserves_unknown_variant() {
772        let role: Role = serde_json::from_str("\"critic\"").unwrap();
773        assert_eq!(role, Role::Other("critic".to_string()));
774        assert_eq!(serde_json::to_value(&role).unwrap(), "critic");
775    }
776
777    #[test]
778    fn content_block_preserves_unknown_variant() {
779        let block: ContentBlock = serde_json::from_value(serde_json::json!({
780            "type": "_widget",
781            "title": "Status",
782            "state": {"ok": true}
783        }))
784        .unwrap();
785
786        let ContentBlock::Other(unknown) = block else {
787            panic!("expected unknown content block");
788        };
789
790        assert_eq!(unknown.type_, "_widget");
791        assert_eq!(
792            unknown.fields.get("title"),
793            Some(&serde_json::json!("Status"))
794        );
795        assert_eq!(
796            serde_json::to_value(ContentBlock::Other(unknown)).unwrap(),
797            serde_json::json!({
798                "type": "_widget",
799                "title": "Status",
800                "state": {"ok": true}
801            })
802        );
803    }
804
805    #[test]
806    fn content_block_does_not_hide_malformed_known_variant() {
807        assert!(
808            serde_json::from_value::<ContentBlock>(serde_json::json!({
809                "type": "text"
810            }))
811            .is_err()
812        );
813    }
814
815    #[test]
816    fn test_image_content_roundtrip() {
817        let content = ImageContent::new("base64data", "image/png");
818        let json = serde_json::to_value(&content).unwrap();
819        let parsed: ImageContent = serde_json::from_value(json).unwrap();
820        assert_eq!(content, parsed);
821    }
822
823    #[test]
824    fn test_image_content_omits_optional_fields() {
825        let content = ImageContent::new("data", "image/png");
826        let json = serde_json::to_value(&content).unwrap();
827        assert!(!json.as_object().unwrap().contains_key("uri"));
828        assert!(!json.as_object().unwrap().contains_key("annotations"));
829        assert!(!json.as_object().unwrap().contains_key("meta"));
830    }
831
832    #[test]
833    fn test_image_content_with_uri() {
834        let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
835        let json = serde_json::to_value(&content).unwrap();
836        assert_eq!(json["uri"], "https://example.com/image.png");
837    }
838
839    #[test]
840    fn test_audio_content_roundtrip() {
841        let content = AudioContent::new("base64audio", "audio/mp3");
842        let json = serde_json::to_value(&content).unwrap();
843        let parsed: AudioContent = serde_json::from_value(json).unwrap();
844        assert_eq!(content, parsed);
845    }
846
847    #[test]
848    fn test_audio_content_omits_optional_fields() {
849        let content = AudioContent::new("data", "audio/mp3");
850        let json = serde_json::to_value(&content).unwrap();
851        assert!(!json.as_object().unwrap().contains_key("annotations"));
852        assert!(!json.as_object().unwrap().contains_key("meta"));
853    }
854}