lib_client_openrouter/
types.rs

1//! Data types for the OpenRouter API.
2
3use serde::{Deserialize, Serialize};
4
5/// Message role.
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "lowercase")]
8pub enum Role {
9    System,
10    User,
11    Assistant,
12    Tool,
13}
14
15/// A message in the conversation.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Message {
18    /// Message role.
19    pub role: Role,
20    /// Message content.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub content: Option<String>,
23    /// Tool calls made by the assistant.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub tool_calls: Option<Vec<ToolCall>>,
26    /// Tool call ID (for tool role messages).
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub tool_call_id: Option<String>,
29}
30
31impl Message {
32    /// Create a system message.
33    pub fn system(content: impl Into<String>) -> Self {
34        Self {
35            role: Role::System,
36            content: Some(content.into()),
37            tool_calls: None,
38            tool_call_id: None,
39        }
40    }
41
42    /// Create a user message.
43    pub fn user(content: impl Into<String>) -> Self {
44        Self {
45            role: Role::User,
46            content: Some(content.into()),
47            tool_calls: None,
48            tool_call_id: None,
49        }
50    }
51
52    /// Create an assistant message.
53    pub fn assistant(content: impl Into<String>) -> Self {
54        Self {
55            role: Role::Assistant,
56            content: Some(content.into()),
57            tool_calls: None,
58            tool_call_id: None,
59        }
60    }
61
62    /// Create an assistant message with tool calls.
63    pub fn assistant_with_tool_calls(tool_calls: Vec<ToolCall>) -> Self {
64        Self {
65            role: Role::Assistant,
66            content: None,
67            tool_calls: Some(tool_calls),
68            tool_call_id: None,
69        }
70    }
71
72    /// Create a tool result message.
73    pub fn tool(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
74        Self {
75            role: Role::Tool,
76            content: Some(content.into()),
77            tool_calls: None,
78            tool_call_id: Some(tool_call_id.into()),
79        }
80    }
81}
82
83/// Tool call made by the assistant.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct ToolCall {
86    /// Tool call ID.
87    pub id: String,
88    /// Tool type (always "function").
89    #[serde(rename = "type")]
90    pub tool_type: String,
91    /// Function call details.
92    pub function: FunctionCall,
93}
94
95impl ToolCall {
96    /// Create a new tool call.
97    pub fn new(
98        id: impl Into<String>,
99        name: impl Into<String>,
100        arguments: impl Into<String>,
101    ) -> Self {
102        Self {
103            id: id.into(),
104            tool_type: "function".to_string(),
105            function: FunctionCall {
106                name: name.into(),
107                arguments: arguments.into(),
108            },
109        }
110    }
111}
112
113/// Function call details.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct FunctionCall {
116    /// Function name.
117    pub name: String,
118    /// JSON-encoded arguments.
119    pub arguments: String,
120}
121
122/// Tool definition.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct Tool {
125    /// Tool type (always "function").
126    #[serde(rename = "type")]
127    pub tool_type: String,
128    /// Function definition.
129    pub function: FunctionDefinition,
130}
131
132impl Tool {
133    /// Create a new function tool.
134    pub fn function(
135        name: impl Into<String>,
136        description: impl Into<String>,
137        parameters: serde_json::Value,
138    ) -> Self {
139        Self {
140            tool_type: "function".to_string(),
141            function: FunctionDefinition {
142                name: name.into(),
143                description: description.into(),
144                parameters,
145            },
146        }
147    }
148}
149
150/// Function definition.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct FunctionDefinition {
153    /// Function name.
154    pub name: String,
155    /// Function description.
156    pub description: String,
157    /// JSON schema for parameters.
158    pub parameters: serde_json::Value,
159}
160
161/// Provider preferences for routing.
162#[derive(Debug, Clone, Serialize, Deserialize, Default)]
163pub struct ProviderPreferences {
164    /// Allow fallback to other providers if primary fails.
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub allow_fallbacks: Option<bool>,
167    /// Require specific providers.
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub require_parameters: Option<bool>,
170    /// Data collection consent.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub data_collection: Option<String>,
173    /// Provider order preference.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub order: Option<Vec<String>>,
176    /// Providers to ignore.
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub ignore: Option<Vec<String>>,
179    /// Quantization preference (e.g., "int4", "int8", "fp6", "fp8", "fp16", "bf16").
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub quantizations: Option<Vec<String>>,
182}
183
184/// Request to create a chat completion.
185#[derive(Debug, Clone, Serialize)]
186pub struct CreateChatCompletionRequest {
187    /// Model to use (e.g., "openai/gpt-4o", "anthropic/claude-3.5-sonnet").
188    pub model: String,
189    /// Messages in the conversation.
190    pub messages: Vec<Message>,
191    /// Maximum tokens to generate.
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub max_tokens: Option<usize>,
194    /// Temperature for sampling.
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub temperature: Option<f32>,
197    /// Top-p sampling.
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub top_p: Option<f32>,
200    /// Stop sequences.
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub stop: Option<Vec<String>>,
203    /// Available tools.
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub tools: Option<Vec<Tool>>,
206    /// Whether to stream the response.
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub stream: Option<bool>,
209    /// Number of completions to generate.
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub n: Option<usize>,
212    /// Presence penalty.
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub presence_penalty: Option<f32>,
215    /// Frequency penalty.
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub frequency_penalty: Option<f32>,
218    /// Provider routing preferences (OpenRouter-specific).
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub provider: Option<ProviderPreferences>,
221    /// Model fallback list (OpenRouter-specific).
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub models: Option<Vec<String>>,
224    /// Route to select model based on prompt (OpenRouter-specific).
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub route: Option<String>,
227}
228
229impl CreateChatCompletionRequest {
230    /// Create a new chat completion request.
231    pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
232        Self {
233            model: model.into(),
234            messages,
235            max_tokens: None,
236            temperature: None,
237            top_p: None,
238            stop: None,
239            tools: None,
240            stream: None,
241            n: None,
242            presence_penalty: None,
243            frequency_penalty: None,
244            provider: None,
245            models: None,
246            route: None,
247        }
248    }
249
250    /// Set max tokens.
251    pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
252        self.max_tokens = Some(max_tokens);
253        self
254    }
255
256    /// Set temperature.
257    pub fn with_temperature(mut self, temperature: f32) -> Self {
258        self.temperature = Some(temperature);
259        self
260    }
261
262    /// Set top-p sampling.
263    pub fn with_top_p(mut self, top_p: f32) -> Self {
264        self.top_p = Some(top_p);
265        self
266    }
267
268    /// Set stop sequences.
269    pub fn with_stop(mut self, stop: Vec<String>) -> Self {
270        self.stop = Some(stop);
271        self
272    }
273
274    /// Set available tools.
275    pub fn with_tools(mut self, tools: Vec<Tool>) -> Self {
276        self.tools = Some(tools);
277        self
278    }
279
280    /// Set provider preferences.
281    pub fn with_provider(mut self, provider: ProviderPreferences) -> Self {
282        self.provider = Some(provider);
283        self
284    }
285
286    /// Set fallback models.
287    pub fn with_fallback_models(mut self, models: Vec<String>) -> Self {
288        self.models = Some(models);
289        self
290    }
291
292    /// Set route (e.g., "fallback" for auto-routing).
293    pub fn with_route(mut self, route: impl Into<String>) -> Self {
294        self.route = Some(route.into());
295        self
296    }
297}
298
299/// Token usage statistics.
300#[derive(Debug, Clone, Deserialize)]
301pub struct Usage {
302    /// Prompt tokens.
303    pub prompt_tokens: usize,
304    /// Completion tokens.
305    pub completion_tokens: usize,
306    /// Total tokens.
307    pub total_tokens: usize,
308}
309
310/// A completion choice.
311#[derive(Debug, Clone, Deserialize)]
312pub struct Choice {
313    /// Choice index.
314    pub index: usize,
315    /// Generated message.
316    pub message: Message,
317    /// Finish reason.
318    pub finish_reason: Option<String>,
319}
320
321/// Response from creating a chat completion.
322#[derive(Debug, Clone, Deserialize)]
323pub struct CreateChatCompletionResponse {
324    /// Response ID.
325    pub id: String,
326    /// Object type.
327    pub object: String,
328    /// Creation timestamp.
329    pub created: u64,
330    /// Model used.
331    pub model: String,
332    /// Completion choices.
333    pub choices: Vec<Choice>,
334    /// Token usage.
335    pub usage: Option<Usage>,
336}
337
338impl CreateChatCompletionResponse {
339    /// Get the first choice's message content.
340    pub fn content(&self) -> Option<&str> {
341        self.choices
342            .first()
343            .and_then(|c| c.message.content.as_deref())
344    }
345
346    /// Get the first choice's tool calls.
347    pub fn tool_calls(&self) -> Option<&Vec<ToolCall>> {
348        self.choices
349            .first()
350            .and_then(|c| c.message.tool_calls.as_ref())
351    }
352
353    /// Check if the response contains tool calls.
354    pub fn has_tool_calls(&self) -> bool {
355        self.choices
356            .first()
357            .is_some_and(|c| c.message.tool_calls.is_some())
358    }
359}
360
361/// Model pricing information.
362#[derive(Debug, Clone, Deserialize)]
363pub struct ModelPricing {
364    /// Price per prompt token (in USD).
365    pub prompt: String,
366    /// Price per completion token (in USD).
367    pub completion: String,
368    /// Price per image (if applicable).
369    #[serde(default)]
370    pub image: Option<String>,
371    /// Price per request (if applicable).
372    #[serde(default)]
373    pub request: Option<String>,
374}
375
376/// Model information from OpenRouter.
377#[derive(Debug, Clone, Deserialize)]
378pub struct Model {
379    /// Model ID (e.g., "openai/gpt-4o").
380    pub id: String,
381    /// Display name.
382    pub name: String,
383    /// Model description.
384    #[serde(default)]
385    pub description: Option<String>,
386    /// Context length in tokens.
387    pub context_length: usize,
388    /// Pricing information.
389    pub pricing: ModelPricing,
390    /// Top provider for this model.
391    #[serde(default)]
392    pub top_provider: Option<TopProvider>,
393    /// Model architecture.
394    #[serde(default)]
395    pub architecture: Option<ModelArchitecture>,
396}
397
398/// Top provider details.
399#[derive(Debug, Clone, Deserialize)]
400pub struct TopProvider {
401    /// Context length from this provider.
402    #[serde(default)]
403    pub context_length: Option<usize>,
404    /// Max completion tokens.
405    #[serde(default)]
406    pub max_completion_tokens: Option<usize>,
407    /// Whether provider is moderated.
408    #[serde(default)]
409    pub is_moderated: Option<bool>,
410}
411
412/// Model architecture details.
413#[derive(Debug, Clone, Deserialize)]
414pub struct ModelArchitecture {
415    /// Modality (e.g., "text->text", "text+image->text").
416    #[serde(default)]
417    pub modality: Option<String>,
418    /// Tokenizer used.
419    #[serde(default)]
420    pub tokenizer: Option<String>,
421    /// Instruction type.
422    #[serde(default)]
423    pub instruct_type: Option<String>,
424}
425
426/// List of models.
427#[derive(Debug, Clone, Deserialize)]
428pub struct ModelList {
429    /// Models.
430    pub data: Vec<Model>,
431}
432
433/// Generation statistics (returned by /api/v1/generation).
434#[derive(Debug, Clone, Deserialize)]
435pub struct GenerationStats {
436    /// Generation ID.
437    pub id: String,
438    /// Total cost in USD.
439    #[serde(default)]
440    pub total_cost: Option<f64>,
441    /// Tokens used.
442    #[serde(default)]
443    pub tokens_prompt: Option<usize>,
444    /// Tokens generated.
445    #[serde(default)]
446    pub tokens_completion: Option<usize>,
447    /// Native tokens used.
448    #[serde(default)]
449    pub native_tokens_prompt: Option<usize>,
450    /// Native tokens generated.
451    #[serde(default)]
452    pub native_tokens_completion: Option<usize>,
453}
454
455/// Credit balance response.
456#[derive(Debug, Clone, Deserialize)]
457pub struct CreditsResponse {
458    /// Remaining credits in USD.
459    #[serde(default)]
460    pub data: Option<CreditsData>,
461}
462
463/// Credit balance data.
464#[derive(Debug, Clone, Deserialize)]
465pub struct CreditsData {
466    /// Label (usually "default").
467    #[serde(default)]
468    pub label: Option<String>,
469    /// Remaining balance in USD.
470    #[serde(default)]
471    pub balance: Option<f64>,
472    /// Usage limit per interval.
473    #[serde(default)]
474    pub usage: Option<f64>,
475    /// Rate limit interval in seconds.
476    #[serde(default)]
477    pub limit: Option<f64>,
478    /// Whether rate limited.
479    #[serde(default)]
480    pub is_free_tier: Option<bool>,
481}
482
483/// Error response from the API.
484#[derive(Debug, Clone, Deserialize)]
485pub struct ErrorResponse {
486    /// Error details.
487    pub error: ErrorDetail,
488}
489
490/// Error detail.
491#[derive(Debug, Clone, Deserialize)]
492pub struct ErrorDetail {
493    /// Error message.
494    pub message: String,
495    /// Error type.
496    #[serde(rename = "type")]
497    #[serde(default)]
498    pub error_type: Option<String>,
499    /// Error code.
500    #[serde(default)]
501    pub code: Option<i32>,
502}