1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3use std::borrow::Cow;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(untagged)]
7pub enum ChatMessageContent {
8 Text(String),
9 Parts(Vec<ChatContentPart>),
10}
11
12impl ChatMessageContent {
13 pub fn text(text: impl Into<String>) -> Self {
14 Self::Text(text.into())
15 }
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(untagged)]
20pub enum ChatContentPart {
21 Text(ChatContentPartText),
22 ImageUrl(ChatContentPartImageUrl),
23 InputAudio(ChatContentPartInputAudio),
24 File(ChatContentPartFile),
25 Other(Value),
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ChatContentPartText {
30 #[serde(rename = "type")]
31 pub kind: Cow<'static, str>,
32 pub text: String,
33 #[serde(skip_serializing_if = "Option::is_none")]
36 pub cache_control: Option<Value>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ChatContentPartImageUrl {
41 #[serde(rename = "type")]
42 pub kind: Cow<'static, str>,
43 pub image_url: ChatImageUrl,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47#[serde(untagged)]
48pub enum ChatImageUrl {
49 Url(String),
50 Object(ChatImageUrlObject),
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ChatImageUrlObject {
55 pub url: String,
56 pub detail: Option<String>,
57 pub format: Option<String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ChatContentPartInputAudio {
62 #[serde(rename = "type")]
63 pub kind: Cow<'static, str>,
64 pub input_audio: ChatInputAudio,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ChatInputAudio {
69 pub data: String,
70 pub format: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct ChatContentPartFile {
75 #[serde(rename = "type")]
76 pub kind: Cow<'static, str>,
77 pub file: ChatFile,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ChatFile {
82 pub file_id: Option<String>,
83 pub file_data: Option<String>,
84 pub format: Option<String>,
85 pub detail: Option<String>,
86 pub video_metadata: Option<Value>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ChatMessage {
91 pub role: String,
92 pub content: ChatMessageContent,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub name: Option<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub tool_call_id: Option<String>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub tool_calls: Option<Value>,
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub function_call: Option<Value>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub provider_specific_fields: Option<Value>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ChatRequest {
107 pub model: String,
108 pub messages: Vec<ChatMessage>,
109 pub temperature: Option<f32>,
110 pub max_tokens: Option<u32>,
111 pub response_format: Option<Value>,
112 pub max_completion_tokens: Option<u32>,
113 pub tools: Option<Value>,
114 pub tool_choice: Option<Value>,
115 pub parallel_tool_calls: Option<bool>,
116 pub stop: Option<Value>,
117 pub top_p: Option<f32>,
118 pub presence_penalty: Option<f32>,
119 pub frequency_penalty: Option<f32>,
120 pub seed: Option<u64>,
121 pub user: Option<String>,
122 pub metadata: Option<Value>,
123 pub reasoning_effort: Option<Value>,
124 pub thinking: Option<Value>,
125 pub extra_body: Option<Map<String, Value>>,
130}
131
132impl ChatRequest {
133 pub fn new(model: impl Into<String>) -> Self {
134 Self {
135 model: model.into(),
136 messages: Vec::new(),
137 temperature: None,
138 max_tokens: None,
139 response_format: None,
140 max_completion_tokens: None,
141 tools: None,
142 tool_choice: None,
143 parallel_tool_calls: None,
144 stop: None,
145 top_p: None,
146 presence_penalty: None,
147 frequency_penalty: None,
148 seed: None,
149 user: None,
150 metadata: None,
151 reasoning_effort: None,
152 thinking: None,
153 extra_body: None,
154 }
155 }
156
157 pub fn message(mut self, role: impl Into<String>, content: impl Into<String>) -> Self {
158 self.messages.push(ChatMessage {
159 role: role.into(),
160 content: ChatMessageContent::Text(content.into()),
161 name: None,
162 tool_call_id: None,
163 tool_calls: None,
164 function_call: None,
165 provider_specific_fields: None,
166 });
167 self
168 }
169
170 pub fn message_with_content(
171 mut self,
172 role: impl Into<String>,
173 content: ChatMessageContent,
174 ) -> Self {
175 self.messages.push(ChatMessage {
176 role: role.into(),
177 content,
178 name: None,
179 tool_call_id: None,
180 tool_calls: None,
181 function_call: None,
182 provider_specific_fields: None,
183 });
184 self
185 }
186
187 pub fn temperature(mut self, temperature: f32) -> Self {
188 self.temperature = Some(temperature);
189 self
190 }
191
192 pub fn max_tokens(mut self, max_tokens: u32) -> Self {
193 self.max_tokens = Some(max_tokens);
194 self
195 }
196
197 pub fn response_format(mut self, format: Value) -> Self {
198 self.response_format = Some(format);
199 self
200 }
201
202 pub fn extra_body(mut self, body: Map<String, Value>) -> Self {
203 self.extra_body = Some(body);
204 self
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize, Default)]
209pub struct ChatResponse {
210 pub content: String,
211 pub reasoning: Option<Reasoning>,
213 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub tool_calls: Option<Value>,
219 pub usage: Usage,
220 pub response_id: Option<String>,
221 pub header_cost: Option<f64>,
222 pub raw: Option<Value>,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct Reasoning {
231 pub details: Vec<Value>,
232}
233
234impl Reasoning {
235 pub fn from_text(text: impl Into<String>) -> Option<Self> {
237 let text = text.into();
238 if text.trim().is_empty() {
239 return None;
240 }
241 Some(Self {
242 details: vec![serde_json::json!({
243 "type": "reasoning.text",
244 "text": text,
245 })],
246 })
247 }
248
249 pub fn from_details(details: Vec<Value>) -> Option<Self> {
250 (!details.is_empty()).then_some(Self { details })
251 }
252
253 pub fn text(&self) -> Option<String> {
254 let mut out = String::new();
255 for detail in &self.details {
256 collect_reasoning_text(detail, &mut out);
257 }
258 (!out.trim().is_empty()).then_some(out)
259 }
260}
261
262fn collect_reasoning_text(value: &Value, out: &mut String) {
263 match value {
264 Value::String(text) => out.push_str(text),
265 Value::Array(items) => {
266 for item in items {
267 collect_reasoning_text(item, out);
268 }
269 }
270 Value::Object(map) => {
271 if let Some(text) = map.get("text") {
272 collect_reasoning_text(text, out);
273 } else if let Some(summary) = map.get("summary") {
274 collect_reasoning_text(summary, out);
275 }
276 }
277 _ => {}
278 }
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize, Default)]
286pub struct Usage {
287 pub prompt_tokens: Option<u64>,
288 pub completion_tokens: Option<u64>,
289 pub thoughts_tokens: Option<u64>,
290 pub total_tokens: Option<u64>,
291 pub cost_usd: Option<f64>,
292 pub cache_read_input_tokens: Option<u64>,
295 pub cache_creation_input_tokens: Option<u64>,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct EmbeddingRequest {
302 pub model: String,
303 pub input: Value,
304 pub extra_body: Option<Map<String, Value>>,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct EmbeddingResponse {
310 pub vectors: Vec<Vec<f32>>,
311 pub usage: Usage,
312 pub raw: Option<Value>,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct ImageRequest {
317 pub model: String,
318 pub prompt: String,
319 pub n: Option<u32>,
320 pub size: Option<String>,
321 pub quality: Option<String>,
322 pub background: Option<String>,
323 pub extra_body: Option<Map<String, Value>>,
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct ImageData {
329 pub b64_json: Option<String>,
330 pub url: Option<String>,
331 pub revised_prompt: Option<String>,
332 pub mime_type: Option<String>,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct ImageEditRequest {
337 pub model: String,
338 pub prompt: String,
339 pub images: Vec<ImageInputData>,
340 pub n: Option<u32>,
341 pub size: Option<String>,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct ImageInputData {
346 pub b64_json: Option<String>,
347 pub url: Option<String>,
348 pub mime_type: Option<String>,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct ImageResponse {
353 pub images: Vec<ImageData>,
354 pub usage: Usage,
355 pub raw: Option<Value>,
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct VideoRequest {
360 pub model: String,
361 pub prompt: String,
362 pub seconds: Option<u32>,
363 pub size: Option<String>,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct VideoResponse {
368 pub video_url: Option<String>,
369 pub raw: Option<Value>,
370}