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