1use std::fmt;
9
10use serde::{Deserialize, Deserializer, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum Role {
16 System,
17 User,
18 Assistant,
19 Tool,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
26pub enum AssistantBlock {
27 Text {
28 text: String,
29 },
30 Resource {
31 resource_id: String,
32 media_type: String,
33 },
34 Data {
35 slot: String,
36 value: serde_json::Value,
37 },
38 Citation {
39 resource_id: String,
40 label: String,
41 uri: String,
42 #[serde(default, skip_serializing_if = "Option::is_none")]
43 excerpt: Option<String>,
44 },
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ChatMessage {
52 pub role: Role,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub content: Option<String>,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub tool_calls: Option<Vec<ToolCall>>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub tool_call_id: Option<String>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub name: Option<String>,
68}
69
70impl ChatMessage {
71 pub fn system(content: impl Into<String>) -> Self {
72 Self::text(Role::System, content)
73 }
74 pub fn user(content: impl Into<String>) -> Self {
75 Self::text(Role::User, content)
76 }
77 pub fn assistant(content: impl Into<String>) -> Self {
78 Self::text(Role::Assistant, content)
79 }
80
81 fn text(role: Role, content: impl Into<String>) -> Self {
82 Self {
83 role,
84 content: Some(content.into()),
85 tool_calls: None,
86 tool_call_id: None,
87 name: None,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct Tool {
95 #[serde(rename = "type")]
96 pub kind: String,
97 pub function: FunctionDef,
98}
99
100impl Tool {
101 pub fn function(
103 name: impl Into<String>,
104 description: impl Into<String>,
105 parameters: serde_json::Value,
106 ) -> Self {
107 Self {
108 kind: "function".to_string(),
109 function: FunctionDef {
110 name: name.into(),
111 description: Some(description.into()),
112 parameters: Some(parameters),
113 },
114 }
115 }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct FunctionDef {
120 pub name: String,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub description: Option<String>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub parameters: Option<serde_json::Value>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ToolCall {
131 pub id: String,
132 #[serde(rename = "type")]
133 pub kind: String,
134 pub function: FunctionCall,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct FunctionCall {
139 pub name: String,
140 pub arguments: String,
142}
143
144#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
146#[serde(rename_all = "lowercase")]
147pub enum ToolChoice {
148 Auto,
149 None,
150 Required,
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
154#[serde(rename_all = "lowercase")]
155pub enum ReasoningEffort {
156 Low,
157 Medium,
158 High,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct CompletionRequest {
164 pub model: String,
165 pub messages: Vec<ChatMessage>,
166
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub tools: Option<Vec<Tool>>,
169
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub tool_choice: Option<ToolChoice>,
172
173 pub temperature: f32,
174 pub max_tokens: u32,
175
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub reasoning_effort: Option<ReasoningEffort>,
178
179 #[serde(skip)]
181 pub provider_attempt_id: Option<String>,
182
183 #[serde(skip_serializing_if = "std::ops::Not::not")]
186 pub stream: bool,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
191 pub stream_options: Option<StreamOptions>,
192}
193
194#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
195pub struct StreamOptions {
196 pub include_usage: bool,
197}
198
199impl CompletionRequest {
200 pub fn new(model: impl Into<String>, messages: Vec<ChatMessage>) -> Self {
203 Self {
204 model: model.into(),
205 messages,
206 tools: None,
207 tool_choice: None,
208 temperature: 0.3,
209 max_tokens: 4096,
210 reasoning_effort: None,
211 provider_attempt_id: None,
212 stream: false,
213 stream_options: None,
214 }
215 }
216
217 pub fn stream(mut self, enabled: bool) -> Self {
218 self.stream = enabled;
219 self
220 }
221
222 pub fn temperature(mut self, t: f32) -> Self {
223 self.temperature = t;
224 self
225 }
226
227 pub fn max_tokens(mut self, n: u32) -> Self {
228 self.max_tokens = n;
229 self
230 }
231
232 pub fn reasoning_effort(mut self, effort: ReasoningEffort) -> Self {
233 self.reasoning_effort = Some(effort);
234 self
235 }
236
237 pub fn tools(mut self, tools: Vec<Tool>) -> Self {
240 if !tools.is_empty() && self.tool_choice.is_none() {
241 self.tool_choice = Some(ToolChoice::Auto);
242 }
243 self.tools = Some(tools);
244 self
245 }
246
247 pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
248 self.tool_choice = Some(choice);
249 self
250 }
251}
252
253#[derive(Debug, Clone, Deserialize)]
255pub struct CompletionResponse {
256 #[serde(default)]
257 pub id: String,
258 pub choices: Vec<Choice>,
259 #[serde(default)]
260 pub usage: Option<Usage>,
261}
262
263impl CompletionResponse {
264 pub fn first_content(&self) -> Option<&str> {
266 self.choices
267 .first()
268 .and_then(|c| c.message.content.as_deref())
269 }
270
271 pub fn first_tool_calls(&self) -> Option<&[ToolCall]> {
273 self.choices
274 .first()
275 .and_then(|c| c.message.tool_calls.as_deref())
276 }
277
278 pub fn first_finish_reason(&self) -> Option<&FinishReason> {
280 self.choices
281 .first()
282 .and_then(|choice| choice.finish_reason.as_ref())
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Eq)]
288pub enum FinishReason {
289 Stop,
290 ToolCalls,
291 Length,
292 ContentFilter,
293 Unknown(String),
294}
295
296impl FinishReason {
297 pub fn as_str(&self) -> &str {
298 match self {
299 Self::Stop => "stop",
300 Self::ToolCalls => "tool_calls",
301 Self::Length => "length",
302 Self::ContentFilter => "content_filter",
303 Self::Unknown(reason) => reason,
304 }
305 }
306}
307
308impl fmt::Display for FinishReason {
309 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
310 formatter.write_str(self.as_str())
311 }
312}
313
314impl From<&str> for FinishReason {
315 fn from(reason: &str) -> Self {
316 match reason {
317 "stop" => Self::Stop,
318 "tool_calls" => Self::ToolCalls,
319 "length" => Self::Length,
320 "content_filter" => Self::ContentFilter,
321 unknown => Self::Unknown(unknown.to_string()),
322 }
323 }
324}
325
326impl From<String> for FinishReason {
327 fn from(reason: String) -> Self {
328 Self::from(reason.as_str())
329 }
330}
331
332impl<'de> Deserialize<'de> for FinishReason {
333 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
334 where
335 D: Deserializer<'de>,
336 {
337 String::deserialize(deserializer).map(Into::into)
338 }
339}
340
341#[derive(Debug, Clone, Deserialize)]
342pub struct Choice {
343 #[serde(default)]
344 pub index: u32,
345 pub message: ChatMessage,
346 #[serde(default)]
347 pub finish_reason: Option<FinishReason>,
348 #[serde(default, alias = "content_blocks")]
349 pub output_blocks: Vec<AssistantBlock>,
350}
351
352#[derive(Debug, Clone, Copy, Default, Deserialize)]
353pub struct Usage {
354 #[serde(default)]
355 pub prompt_tokens: u32,
356 #[serde(default)]
357 pub completion_tokens: u32,
358 #[serde(default)]
359 pub total_tokens: u32,
360}