Skip to main content

agent_client_protocol_schema/v1/
content.rs

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