Skip to main content

agent_io/llm/types/
content.rs

1//! Content part types for messages
2
3use serde::{Deserialize, Serialize};
4
5/// JSON Schema for tool parameters
6pub type JsonSchema = serde_json::Map<String, serde_json::Value>;
7
8/// Text content part
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct ContentPartText {
11    #[serde(rename = "type")]
12    pub content_type: String,
13    pub text: String,
14}
15
16impl ContentPartText {
17    pub fn new(text: impl Into<String>) -> Self {
18        Self {
19            content_type: "text".to_string(),
20            text: text.into(),
21        }
22    }
23}
24
25impl From<String> for ContentPartText {
26    fn from(text: String) -> Self {
27        Self::new(text)
28    }
29}
30
31impl From<&str> for ContentPartText {
32    fn from(text: &str) -> Self {
33        Self::new(text)
34    }
35}
36
37/// Image content part
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ContentPartImage {
40    #[serde(rename = "type")]
41    pub content_type: String,
42    pub image_url: ImageUrl,
43}
44
45/// Image URL structure
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ImageUrl {
48    pub url: String,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub detail: Option<String>,
51}
52
53impl ContentPartImage {
54    /// Create from URL
55    pub fn from_url(url: impl Into<String>) -> Self {
56        Self {
57            content_type: "image_url".to_string(),
58            image_url: ImageUrl {
59                url: url.into(),
60                detail: None,
61            },
62        }
63    }
64
65    /// Create from base64 data
66    pub fn from_base64(media_type: &str, data: &str) -> Self {
67        Self {
68            content_type: "image_url".to_string(),
69            image_url: ImageUrl {
70                url: format!("data:{};base64,{}", media_type, data),
71                detail: None,
72            },
73        }
74    }
75}
76
77/// Document content part (for PDFs etc.)
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ContentPartDocument {
80    #[serde(rename = "type")]
81    pub content_type: String,
82    pub source: DocumentSource,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct DocumentSource {
87    #[serde(rename = "type")]
88    pub source_type: String,
89    pub media_type: String,
90    pub data: String,
91}
92
93impl ContentPartDocument {
94    pub fn from_base64(media_type: impl Into<String>, data: impl Into<String>) -> Self {
95        Self {
96            content_type: "document".to_string(),
97            source: DocumentSource {
98                source_type: "base64".to_string(),
99                media_type: media_type.into(),
100                data: data.into(),
101            },
102        }
103    }
104}
105
106/// Thinking content part
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ContentPartThinking {
109    #[serde(rename = "type")]
110    pub content_type: String,
111    pub thinking: String,
112}
113
114impl ContentPartThinking {
115    pub fn new(thinking: impl Into<String>) -> Self {
116        Self {
117            content_type: "thinking".to_string(),
118            thinking: thinking.into(),
119        }
120    }
121}
122
123/// Redacted thinking content
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ContentPartRedactedThinking {
126    #[serde(rename = "type")]
127    pub content_type: String,
128    pub data: String,
129}
130
131/// Refusal content part
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct ContentPartRefusal {
134    #[serde(rename = "type")]
135    pub content_type: String,
136    pub refusal: String,
137}
138
139/// Union type for all content parts
140#[derive(Debug, Clone, Serialize, Deserialize)]
141#[serde(untagged)]
142pub enum ContentPart {
143    Text(ContentPartText),
144    Image(ContentPartImage),
145    Document(ContentPartDocument),
146    Thinking(ContentPartThinking),
147    RedactedThinking(ContentPartRedactedThinking),
148    Refusal(ContentPartRefusal),
149}
150
151impl ContentPart {
152    pub fn text(content: impl Into<String>) -> Self {
153        ContentPart::Text(ContentPartText::new(content))
154    }
155
156    pub fn is_text(&self) -> bool {
157        matches!(self, ContentPart::Text(_))
158    }
159
160    pub fn as_text(&self) -> Option<&str> {
161        match self {
162            ContentPart::Text(t) => Some(&t.text),
163            _ => None,
164        }
165    }
166}
167
168impl From<String> for ContentPart {
169    fn from(text: String) -> Self {
170        ContentPart::text(text)
171    }
172}
173
174impl From<&str> for ContentPart {
175    fn from(text: &str) -> Self {
176        ContentPart::text(text)
177    }
178}