Skip to main content

agent_client_protocol_schema/v1/
content.rs

1//! Content blocks: the text, images, audio, and resources that appear in prompts,
2//! session updates, and tool call results.
3//!
4//! The content block structure is compatible with the Model Context Protocol (MCP), so agents
5//! can forward MCP tool output without converting it.
6//!
7//! See: [Content](https://agentclientprotocol.com/protocol/v1/content)
8
9use serde::{Deserialize, Serialize};
10use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
11
12use crate::IntoOption;
13
14use super::Meta;
15
16/// Content blocks represent displayable information in the Agent Client Protocol.
17///
18/// Content blocks appear in:
19/// - User prompts sent via `session/prompt`
20/// - Language model output streamed through `session/update` notifications
21/// - Progress updates and results from tool calls
22///
23/// This structure is compatible with the Model Context Protocol (MCP), so agents can
24/// forward content from MCP tool outputs without transformation.
25///
26/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/v1/content)
27#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
28#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
29#[serde(tag = "type", rename_all = "snake_case")]
30#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "type"})))]
31#[non_exhaustive]
32pub enum ContentBlock {
33    /// Text content. May be plain text or formatted with Markdown.
34    ///
35    /// All agents MUST support text content blocks in prompts.
36    /// Clients SHOULD render this text as Markdown.
37    Text(TextContent),
38    /// Images for visual context or analysis.
39    ///
40    /// Requires the `image` prompt capability when included in prompts.
41    Image(ImageContent),
42    /// Audio data for transcription or analysis.
43    ///
44    /// Requires the `audio` prompt capability when included in prompts.
45    Audio(AudioContent),
46    /// References to resources that the agent can access.
47    ///
48    /// All agents MUST support resource links in prompts.
49    ResourceLink(ResourceLink),
50    /// Complete resource contents embedded directly in the message.
51    ///
52    /// Preferred for including context as it avoids extra round-trips.
53    ///
54    /// Requires the `embeddedContext` prompt capability when included in prompts.
55    Resource(EmbeddedResource),
56}
57
58/// Text provided to or from an LLM.
59#[serde_as]
60#[skip_serializing_none]
61#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
62#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
63#[non_exhaustive]
64pub struct TextContent {
65    /// Optional annotations that help clients decide how to display or route this content.
66    #[serde_as(deserialize_as = "DefaultOnError")]
67    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
68    #[serde(default)]
69    pub annotations: Option<Annotations>,
70    /// Text payload carried by this content block.
71    pub text: String,
72    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
73    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
74    /// these keys.
75    ///
76    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
77    #[serde_as(deserialize_as = "DefaultOnError")]
78    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
79    #[serde(default)]
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/v1/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#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
124#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
125#[serde(rename_all = "camelCase")]
126#[non_exhaustive]
127pub struct ImageContent {
128    /// Optional annotations that help clients decide how to display or route this content.
129    #[serde_as(deserialize_as = "DefaultOnError")]
130    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
131    #[serde(default)]
132    pub annotations: Option<Annotations>,
133    /// Base64-encoded media payload.
134    pub data: String,
135    /// MIME type describing the encoded media payload.
136    pub mime_type: String,
137    /// URI associated with this resource or media payload.
138    #[serde_as(deserialize_as = "DefaultOnError")]
139    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
140    #[serde(default)]
141    pub uri: Option<String>,
142    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
143    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
144    /// these keys.
145    ///
146    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
147    #[serde_as(deserialize_as = "DefaultOnError")]
148    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
149    #[serde(default)]
150    #[serde(rename = "_meta")]
151    pub meta: Option<Meta>,
152}
153
154impl ImageContent {
155    /// Builds [`ImageContent`] with its required content payload. Optional annotations and metadata start unset.
156    #[must_use]
157    pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
158        Self {
159            annotations: None,
160            data: data.into(),
161            mime_type: mime_type.into(),
162            uri: None,
163            meta: None,
164        }
165    }
166
167    /// Sets or clears the optional `annotations` field.
168    #[must_use]
169    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
170        self.annotations = annotations.into_option();
171        self
172    }
173
174    /// Sets or clears the optional `uri` field.
175    #[must_use]
176    pub fn uri(mut self, uri: impl IntoOption<String>) -> Self {
177        self.uri = uri.into_option();
178        self
179    }
180
181    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
182    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
183    /// these keys.
184    ///
185    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
186    #[must_use]
187    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
188        self.meta = meta.into_option();
189        self
190    }
191}
192
193/// Audio provided to or from an LLM.
194#[serde_as]
195#[skip_serializing_none]
196#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
197#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
198#[serde(rename_all = "camelCase")]
199#[non_exhaustive]
200pub struct AudioContent {
201    /// Optional annotations that help clients decide how to display or route this content.
202    #[serde_as(deserialize_as = "DefaultOnError")]
203    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
204    #[serde(default)]
205    pub annotations: Option<Annotations>,
206    /// Base64-encoded media payload.
207    pub data: String,
208    /// MIME type describing the encoded media payload.
209    pub mime_type: String,
210    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
211    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
212    /// these keys.
213    ///
214    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
215    #[serde_as(deserialize_as = "DefaultOnError")]
216    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
217    #[serde(default)]
218    #[serde(rename = "_meta")]
219    pub meta: Option<Meta>,
220}
221
222impl AudioContent {
223    /// Builds [`AudioContent`] with its required content payload. Optional annotations and metadata start unset.
224    #[must_use]
225    pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
226        Self {
227            annotations: None,
228            data: data.into(),
229            mime_type: mime_type.into(),
230            meta: None,
231        }
232    }
233
234    /// Sets or clears the optional `annotations` field.
235    #[must_use]
236    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
237        self.annotations = annotations.into_option();
238        self
239    }
240
241    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
242    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
243    /// these keys.
244    ///
245    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
246    #[must_use]
247    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
248        self.meta = meta.into_option();
249        self
250    }
251}
252
253/// The contents of a resource, embedded into a prompt or tool call result.
254#[serde_as]
255#[skip_serializing_none]
256#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
257#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
258#[non_exhaustive]
259pub struct EmbeddedResource {
260    /// Optional annotations that help clients decide how to display or route this content.
261    #[serde_as(deserialize_as = "DefaultOnError")]
262    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
263    #[serde(default)]
264    pub annotations: Option<Annotations>,
265    /// Embedded resource payload, either text or binary data.
266    pub resource: EmbeddedResourceResource,
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/v1/extensibility)
272    #[serde_as(deserialize_as = "DefaultOnError")]
273    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
274    #[serde(default)]
275    #[serde(rename = "_meta")]
276    pub meta: Option<Meta>,
277}
278
279impl EmbeddedResource {
280    /// Builds [`EmbeddedResource`] with its required content payload. Optional annotations and metadata start unset.
281    #[must_use]
282    pub fn new(resource: EmbeddedResourceResource) -> Self {
283        Self {
284            annotations: None,
285            resource,
286            meta: None,
287        }
288    }
289
290    /// Sets or clears the optional `annotations` field.
291    #[must_use]
292    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
293        self.annotations = annotations.into_option();
294        self
295    }
296
297    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
298    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
299    /// these keys.
300    ///
301    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
302    #[must_use]
303    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
304        self.meta = meta.into_option();
305        self
306    }
307}
308
309/// Resource content that can be embedded in a message.
310#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
311#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
312#[serde(untagged)]
313#[non_exhaustive]
314pub enum EmbeddedResourceResource {
315    /// Text resource contents embedded directly in the message.
316    TextResourceContents(TextResourceContents),
317    /// Binary resource contents embedded directly in the message.
318    BlobResourceContents(BlobResourceContents),
319}
320
321/// Text-based resource contents.
322#[serde_as]
323#[skip_serializing_none]
324#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
325#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
326#[serde(rename_all = "camelCase")]
327#[non_exhaustive]
328pub struct TextResourceContents {
329    /// MIME type describing the encoded media payload.
330    #[serde_as(deserialize_as = "DefaultOnError")]
331    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
332    #[serde(default)]
333    pub mime_type: Option<String>,
334    /// Text payload carried by this content block.
335    pub text: String,
336    /// URI associated with this resource or media payload.
337    pub uri: String,
338    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
339    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
340    /// these keys.
341    ///
342    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
343    #[serde_as(deserialize_as = "DefaultOnError")]
344    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
345    #[serde(default)]
346    #[serde(rename = "_meta")]
347    pub meta: Option<Meta>,
348}
349
350impl TextResourceContents {
351    /// Builds [`TextResourceContents`] with its required content payload. Optional annotations and metadata start unset.
352    #[must_use]
353    pub fn new(text: impl Into<String>, uri: impl Into<String>) -> Self {
354        Self {
355            mime_type: None,
356            text: text.into(),
357            uri: uri.into(),
358            meta: None,
359        }
360    }
361
362    /// Sets or clears the optional `mimeType` field.
363    #[must_use]
364    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
365        self.mime_type = mime_type.into_option();
366        self
367    }
368
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/v1/extensibility)
374    #[must_use]
375    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
376        self.meta = meta.into_option();
377        self
378    }
379}
380
381/// Binary resource contents.
382#[serde_as]
383#[skip_serializing_none]
384#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
385#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
386#[serde(rename_all = "camelCase")]
387#[non_exhaustive]
388pub struct BlobResourceContents {
389    /// Base64-encoded bytes for a binary resource payload.
390    pub blob: String,
391    /// MIME type describing the encoded media payload.
392    #[serde_as(deserialize_as = "DefaultOnError")]
393    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
394    #[serde(default)]
395    pub mime_type: Option<String>,
396    /// URI associated with this resource or media payload.
397    pub uri: String,
398    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
399    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
400    /// these keys.
401    ///
402    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
403    #[serde_as(deserialize_as = "DefaultOnError")]
404    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
405    #[serde(default)]
406    #[serde(rename = "_meta")]
407    pub meta: Option<Meta>,
408}
409
410impl BlobResourceContents {
411    /// Builds [`BlobResourceContents`] with its required content payload. Optional annotations and metadata start unset.
412    #[must_use]
413    pub fn new(blob: impl Into<String>, uri: impl Into<String>) -> Self {
414        Self {
415            blob: blob.into(),
416            mime_type: None,
417            uri: uri.into(),
418            meta: None,
419        }
420    }
421
422    /// Sets or clears the optional `mimeType` field.
423    #[must_use]
424    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
425        self.mime_type = mime_type.into_option();
426        self
427    }
428
429    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
430    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
431    /// these keys.
432    ///
433    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
434    #[must_use]
435    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
436        self.meta = meta.into_option();
437        self
438    }
439}
440
441/// A resource that the server is capable of reading, included in a prompt or tool call result.
442#[serde_as]
443#[skip_serializing_none]
444#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
445#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
446#[serde(rename_all = "camelCase")]
447#[non_exhaustive]
448pub struct ResourceLink {
449    /// Optional annotations that help clients decide how to display or route this content.
450    #[serde_as(deserialize_as = "DefaultOnError")]
451    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
452    #[serde(default)]
453    pub annotations: Option<Annotations>,
454    /// Optional human-readable details shown with this protocol object.
455    #[serde_as(deserialize_as = "DefaultOnError")]
456    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
457    #[serde(default)]
458    pub description: Option<String>,
459    /// MIME type describing the encoded media payload.
460    #[serde_as(deserialize_as = "DefaultOnError")]
461    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
462    #[serde(default)]
463    pub mime_type: Option<String>,
464    /// Human-readable name shown for this protocol object.
465    pub name: String,
466    /// Optional size of the linked resource in bytes, if known.
467    #[serde_as(deserialize_as = "DefaultOnError")]
468    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
469    #[serde(default)]
470    pub size: Option<i64>,
471    /// Optional display title for end-user UI.
472    #[serde_as(deserialize_as = "DefaultOnError")]
473    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
474    #[serde(default)]
475    pub title: Option<String>,
476    /// URI associated with this resource or media payload.
477    pub uri: String,
478    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
479    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
480    /// these keys.
481    ///
482    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
483    #[serde_as(deserialize_as = "DefaultOnError")]
484    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
485    #[serde(default)]
486    #[serde(rename = "_meta")]
487    pub meta: Option<Meta>,
488}
489
490impl ResourceLink {
491    /// Builds [`ResourceLink`] with its required content payload. Optional annotations and metadata start unset.
492    #[must_use]
493    pub fn new(name: impl Into<String>, uri: impl Into<String>) -> Self {
494        Self {
495            annotations: None,
496            description: None,
497            mime_type: None,
498            name: name.into(),
499            size: None,
500            title: None,
501            uri: uri.into(),
502            meta: None,
503        }
504    }
505
506    /// Sets or clears the optional `annotations` field.
507    #[must_use]
508    pub fn annotations(mut self, annotations: impl IntoOption<Annotations>) -> Self {
509        self.annotations = annotations.into_option();
510        self
511    }
512
513    /// Sets or clears the optional `description` field.
514    #[must_use]
515    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
516        self.description = description.into_option();
517        self
518    }
519
520    /// Sets or clears the optional `mimeType` field.
521    #[must_use]
522    pub fn mime_type(mut self, mime_type: impl IntoOption<String>) -> Self {
523        self.mime_type = mime_type.into_option();
524        self
525    }
526
527    /// Sets or clears the optional `size` field.
528    #[must_use]
529    pub fn size(mut self, size: impl IntoOption<i64>) -> Self {
530        self.size = size.into_option();
531        self
532    }
533
534    /// Sets or clears the optional `title` field.
535    #[must_use]
536    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
537        self.title = title.into_option();
538        self
539    }
540
541    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
542    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
543    /// these keys.
544    ///
545    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
546    #[must_use]
547    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
548        self.meta = meta.into_option();
549        self
550    }
551}
552
553/// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
554#[serde_as]
555#[skip_serializing_none]
556#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
557#[derive(Debug, Clone, Deserialize, Serialize, Default)]
558#[serde(rename_all = "camelCase")]
559#[non_exhaustive]
560pub struct Annotations {
561    /// Intended recipients for this content, such as the user or assistant.
562    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
563    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
564    #[serde(default)]
565    pub audience: Option<Vec<Role>>,
566    /// Timestamp indicating when the underlying resource was last modified.
567    #[serde_as(deserialize_as = "DefaultOnError")]
568    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
569    #[serde(default)]
570    pub last_modified: Option<String>,
571    /// Relative importance of this content when clients choose what to surface.
572    #[serde_as(deserialize_as = "DefaultOnError")]
573    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
574    #[serde(default)]
575    pub priority: Option<f64>,
576    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
577    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
578    /// these keys.
579    ///
580    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
581    #[serde_as(deserialize_as = "DefaultOnError")]
582    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
583    #[serde(default)]
584    #[serde(rename = "_meta")]
585    pub meta: Option<Meta>,
586}
587
588/// Priorities are compared with [`f64::total_cmp`] (the IEEE 754 total order), so
589/// equality is reflexive even for NaN and annotations, and the content that
590/// carries them, can be `Eq`. Unlike `==`, it treats `0.0` and `-0.0` as
591/// different, matching how they serialize.
592impl PartialEq for Annotations {
593    fn eq(&self, other: &Self) -> bool {
594        let Self {
595            audience,
596            last_modified,
597            priority,
598            meta,
599        } = self;
600        *audience == other.audience
601            && *last_modified == other.last_modified
602            && priority_eq(*priority, other.priority)
603            && *meta == other.meta
604    }
605}
606
607impl Eq for Annotations {}
608
609fn priority_eq(a: Option<f64>, b: Option<f64>) -> bool {
610    match (a, b) {
611        (Some(a), Some(b)) => a.total_cmp(&b).is_eq(),
612        (None, None) => true,
613        _ => false,
614    }
615}
616
617impl Annotations {
618    /// Creates annotations with no audience, priority, or timestamp hints set.
619    #[must_use]
620    pub fn new() -> Self {
621        Self::default()
622    }
623
624    /// Sets or clears the optional `audience` field.
625    #[must_use]
626    pub fn audience(mut self, audience: impl IntoOption<Vec<Role>>) -> Self {
627        self.audience = audience.into_option();
628        self
629    }
630
631    /// Sets or clears the optional `lastModified` field.
632    #[must_use]
633    pub fn last_modified(mut self, last_modified: impl IntoOption<String>) -> Self {
634        self.last_modified = last_modified.into_option();
635        self
636    }
637
638    /// Sets or clears the optional `priority` field.
639    #[must_use]
640    pub fn priority(mut self, priority: impl IntoOption<f64>) -> Self {
641        self.priority = priority.into_option();
642        self
643    }
644
645    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
646    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
647    /// these keys.
648    ///
649    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
650    #[must_use]
651    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
652        self.meta = meta.into_option();
653        self
654    }
655}
656
657/// The sender or recipient of messages and data in a conversation.
658#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
659#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
660#[serde(rename_all = "camelCase")]
661#[non_exhaustive]
662pub enum Role {
663    /// The assistant side of a conversation.
664    Assistant,
665    /// The user side of a conversation.
666    User,
667}
668
669#[cfg(test)]
670mod tests {
671    use super::*;
672
673    #[test]
674    fn test_annotations_equality_is_reflexive() {
675        let nan = Annotations::new().priority(f64::NAN);
676        assert_eq!(nan, nan.clone());
677        assert_eq!(
678            Annotations::new().priority(0.5),
679            Annotations::new().priority(0.5)
680        );
681        assert_ne!(
682            Annotations::new().priority(0.0),
683            Annotations::new().priority(-0.0)
684        );
685        assert_ne!(
686            serde_json::to_string(&Annotations::new().priority(0.0)).unwrap(),
687            serde_json::to_string(&Annotations::new().priority(-0.0)).unwrap()
688        );
689        assert_ne!(
690            Annotations::new().priority(0.5),
691            Annotations::new().priority(1.0)
692        );
693        assert_ne!(nan, Annotations::new().priority(0.5));
694        assert_ne!(Annotations::new().priority(0.5), Annotations::new());
695        assert_ne!(
696            Annotations::new().priority(0.5).audience(vec![Role::User]),
697            Annotations::new().priority(0.5)
698        );
699    }
700
701    #[test]
702    fn test_text_content_roundtrip() {
703        let content = TextContent::new("hello world");
704        let json = serde_json::to_value(&content).unwrap();
705        let parsed: TextContent = serde_json::from_value(json).unwrap();
706        assert_eq!(content, parsed);
707    }
708
709    #[test]
710    fn test_text_content_omits_optional_fields() {
711        let content = TextContent::new("hello");
712        let json = serde_json::to_value(&content).unwrap();
713        assert!(!json.as_object().unwrap().contains_key("annotations"));
714        assert!(!json.as_object().unwrap().contains_key("meta"));
715    }
716
717    #[test]
718    fn test_text_content_meta_defaults_on_missing_or_malformed_value() {
719        let missing: TextContent = serde_json::from_value(serde_json::json!({
720            "text": "hello"
721        }))
722        .unwrap();
723        assert_eq!(missing.meta, None);
724
725        let malformed: TextContent = serde_json::from_value(serde_json::json!({
726            "text": "hello",
727            "_meta": false
728        }))
729        .unwrap();
730        assert_eq!(malformed.meta, None);
731    }
732
733    #[test]
734    fn test_text_content_from_string() {
735        let block: ContentBlock = "hello".into();
736        match block {
737            ContentBlock::Text(c) => assert_eq!(c.text, "hello"),
738            _ => panic!("Expected Text variant"),
739        }
740    }
741
742    #[test]
743    fn test_image_content_roundtrip() {
744        let content = ImageContent::new("base64data", "image/png");
745        let json = serde_json::to_value(&content).unwrap();
746        let parsed: ImageContent = serde_json::from_value(json).unwrap();
747        assert_eq!(content, parsed);
748    }
749
750    #[test]
751    fn test_image_content_omits_optional_fields() {
752        let content = ImageContent::new("data", "image/png");
753        let json = serde_json::to_value(&content).unwrap();
754        assert!(!json.as_object().unwrap().contains_key("uri"));
755        assert!(!json.as_object().unwrap().contains_key("annotations"));
756        assert!(!json.as_object().unwrap().contains_key("meta"));
757    }
758
759    #[test]
760    fn test_image_content_with_uri() {
761        let content = ImageContent::new("data", "image/png").uri("https://example.com/image.png");
762        let json = serde_json::to_value(&content).unwrap();
763        assert_eq!(json["uri"], "https://example.com/image.png");
764    }
765
766    #[test]
767    fn test_audio_content_roundtrip() {
768        let content = AudioContent::new("base64audio", "audio/mp3");
769        let json = serde_json::to_value(&content).unwrap();
770        let parsed: AudioContent = serde_json::from_value(json).unwrap();
771        assert_eq!(content, parsed);
772    }
773
774    #[test]
775    fn test_audio_content_omits_optional_fields() {
776        let content = AudioContent::new("data", "audio/mp3");
777        let json = serde_json::to_value(&content).unwrap();
778        assert!(!json.as_object().unwrap().contains_key("annotations"));
779        assert!(!json.as_object().unwrap().contains_key("meta"));
780    }
781}