openai_compat/types/responses/
input.rs1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(tag = "type", rename_all = "snake_case")]
11pub enum InputContent {
12 InputText {
13 text: String,
14 },
15 InputImage {
16 detail: String,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 file_id: Option<String>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
21 image_url: Option<String>,
22 },
23 InputFile {
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 file_id: Option<String>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 file_url: Option<String>,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 filename: Option<String>,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 file_data: Option<String>,
32 },
33}
34
35impl InputContent {
36 pub fn text(text: impl Into<String>) -> Self {
37 Self::InputText { text: text.into() }
38 }
39
40 pub fn image_url(url: impl Into<String>) -> Self {
41 Self::InputImage {
42 detail: "auto".to_string(),
43 file_id: None,
44 image_url: Some(url.into()),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(untagged)]
52pub enum EasyContent {
53 Text(String),
54 Parts(Vec<InputContent>),
55}
56
57impl From<&str> for EasyContent {
58 fn from(text: &str) -> Self {
59 Self::Text(text.to_string())
60 }
61}
62
63impl From<String> for EasyContent {
64 fn from(text: String) -> Self {
65 Self::Text(text)
66 }
67}
68
69impl From<Vec<InputContent>> for EasyContent {
70 fn from(parts: Vec<InputContent>) -> Self {
71 Self::Parts(parts)
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(tag = "type", rename_all = "snake_case")]
80pub enum KnownInputItem {
81 Message {
82 role: String,
83 content: EasyContent,
84 },
85 FunctionCall {
86 call_id: String,
87 name: String,
88 arguments: String,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 id: Option<String>,
93 },
94 FunctionCallOutput {
95 call_id: String,
96 output: String,
99 },
100 Reasoning {
101 id: String,
102 summary: Vec<serde_json::Value>,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
104 content: Option<Vec<serde_json::Value>>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 encrypted_content: Option<String>,
107 },
108 ItemReference {
109 id: String,
110 },
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(untagged)]
122pub enum InputItem {
123 Known(KnownInputItem),
124 Other(serde_json::Value),
125}
126
127impl InputItem {
128 pub fn user(content: impl Into<EasyContent>) -> Self {
130 Self::Known(KnownInputItem::Message {
131 role: "user".to_string(),
132 content: content.into(),
133 })
134 }
135
136 pub fn message(role: impl Into<String>, content: impl Into<EasyContent>) -> Self {
139 Self::Known(KnownInputItem::Message {
140 role: role.into(),
141 content: content.into(),
142 })
143 }
144
145 pub fn function_call(
147 call_id: impl Into<String>,
148 name: impl Into<String>,
149 arguments: impl Into<String>,
150 ) -> Self {
151 Self::Known(KnownInputItem::FunctionCall {
152 call_id: call_id.into(),
153 name: name.into(),
154 arguments: arguments.into(),
155 id: None,
156 })
157 }
158
159 pub fn function_call_output(call_id: impl Into<String>, output: impl Into<String>) -> Self {
161 Self::Known(KnownInputItem::FunctionCallOutput {
162 call_id: call_id.into(),
163 output: output.into(),
164 })
165 }
166
167 pub fn item_reference(id: impl Into<String>) -> Self {
169 Self::Known(KnownInputItem::ItemReference { id: id.into() })
170 }
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize, Default)]
175#[serde(untagged)]
176pub enum Input {
177 #[default]
178 Empty,
179 Text(String),
180 Items(Vec<InputItem>),
181}
182
183impl From<&str> for Input {
184 fn from(text: &str) -> Self {
185 Self::Text(text.to_string())
186 }
187}
188
189impl From<String> for Input {
190 fn from(text: String) -> Self {
191 Self::Text(text)
192 }
193}
194
195impl From<Vec<InputItem>> for Input {
196 fn from(items: Vec<InputItem>) -> Self {
197 Self::Items(items)
198 }
199}
200
201impl Input {
202 pub fn is_empty(&self) -> bool {
206 matches!(self, Self::Empty)
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn string_input_serializes_as_string() {
216 let input: Input = "hi".into();
217 assert_eq!(serde_json::to_value(&input).unwrap(), "hi");
218 }
219
220 #[test]
221 fn user_message_item_serializes() {
222 let item = InputItem::user("hi");
223 assert_eq!(
224 serde_json::to_value(&item).unwrap(),
225 serde_json::json!({"type": "message", "role": "user", "content": "hi"})
226 );
227 }
228
229 #[test]
230 fn function_call_output_roundtrip() {
231 let item = InputItem::function_call_output("call_1", "72F and sunny");
232 let json = serde_json::to_value(&item).unwrap();
233 assert_eq!(
234 json,
235 serde_json::json!({
236 "type": "function_call_output",
237 "call_id": "call_1",
238 "output": "72F and sunny"
239 })
240 );
241 let roundtrip: InputItem = serde_json::from_value(json).unwrap();
242 assert!(matches!(
243 roundtrip,
244 InputItem::Known(KnownInputItem::FunctionCallOutput { .. })
245 ));
246 }
247
248 #[test]
249 fn unknown_input_item_preserved_as_other() {
250 let json = serde_json::json!({
251 "type": "computer_call",
252 "call_id": "call_1",
253 "action": {"type": "click", "x": 1, "y": 2}
254 });
255 let item: InputItem = serde_json::from_value(json.clone()).unwrap();
256 assert!(matches!(item, InputItem::Other(_)));
257 assert_eq!(serde_json::to_value(&item).unwrap(), json);
258 }
259
260 #[test]
261 fn multimodal_content_serializes() {
262 let item = InputItem::user(vec![
263 InputContent::text("What is this?"),
264 InputContent::image_url("https://example.com/cat.png"),
265 ]);
266 let json = serde_json::to_value(&item).unwrap();
267 assert_eq!(json["content"][0]["type"], "input_text");
268 assert_eq!(json["content"][1]["type"], "input_image");
269 }
270}