1use std::collections::HashMap;
2
3use serde::{Deserialize, Deserializer, Serialize};
4use serde_json::Value;
5
6#[derive(Serialize, Deserialize, Debug, Clone)]
7#[non_exhaustive]
8pub struct ReasoningDetail {
9 #[serde(rename = "type")]
11 pub block_type: String,
12 #[serde(alias = "content", default)]
14 pub text: Option<String>,
15 #[serde(default)]
17 pub data: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub signature: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub format: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub id: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub index: Option<u32>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub tool_name: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub arguments: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub result: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub tool_call_id: Option<String>,
42}
43
44impl ReasoningDetail {
45 pub fn content(&self) -> Option<&str> {
47 self.text.as_deref().or(self.data.as_deref())
48 }
49
50 pub fn reasoning_type(&self) -> &str {
52 &self.block_type
53 }
54}
55
56#[derive(Serialize, Deserialize, Debug, Clone)]
57#[non_exhaustive]
58pub struct ResponseCostDetails {
59 pub upstream_inference_completions_cost: f64,
61 pub upstream_inference_prompt_cost: f64,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub upstream_inference_cost: Option<f64>,
66}
67
68#[derive(Serialize, Deserialize, Debug, Clone)]
69#[non_exhaustive]
70pub struct ServerToolUseDetails {
71 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub tool_calls_executed: Option<u32>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub tool_calls_requested: Option<u32>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub web_search_requests: Option<u32>,
80}
81
82#[derive(Serialize, Deserialize, Debug, Clone)]
84#[non_exhaustive]
85pub struct AnthropicCacheCreation {
86 pub ephemeral_5m_input_tokens: u64,
87 pub ephemeral_1h_input_tokens: u64,
88}
89
90#[derive(Serialize, Deserialize, Debug, Clone)]
109#[non_exhaustive]
110pub struct ResponseUsage {
111 pub prompt_tokens: u32,
113 pub completion_tokens: u32,
115 pub total_tokens: u32,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub cost: Option<f64>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub cost_details: Option<ResponseCostDetails>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub is_byok: Option<bool>,
126 #[serde(default, skip_serializing_if = "Option::is_none")]
128 pub server_tool_use_details: Option<ServerToolUseDetails>,
129}
130
131impl ResponseUsage {
132 pub fn new(prompt_tokens: u32, completion_tokens: u32, total_tokens: u32) -> Self {
133 Self {
134 prompt_tokens,
135 completion_tokens,
136 total_tokens,
137 cost: None,
138 cost_details: None,
139 is_byok: None,
140 server_tool_use_details: None,
141 }
142 }
143}
144
145#[derive(Serialize, Deserialize, Debug, Clone)]
146#[non_exhaustive]
147pub struct FunctionCall {
148 pub name: String,
149 pub arguments: String,
150}
151
152impl FunctionCall {
153 pub fn new(name: impl Into<String>, arguments: impl Into<String>) -> Self {
154 Self {
155 name: name.into(),
156 arguments: arguments.into(),
157 }
158 }
159}
160
161#[derive(Serialize, Deserialize, Debug, Clone)]
162#[non_exhaustive]
163pub struct ToolCall {
164 pub id: String,
165 #[serde(rename = "type")]
166 pub type_: String, pub function: FunctionCall,
168 #[serde(default)]
169 pub index: Option<u32>,
170}
171
172impl ToolCall {
173 pub fn new(
174 id: impl Into<String>,
175 name: impl Into<String>,
176 arguments: impl Into<String>,
177 ) -> Self {
178 Self {
179 id: id.into(),
180 type_: "function".to_string(),
181 function: FunctionCall::new(name, arguments),
182 index: None,
183 }
184 }
185
186 pub fn with_index(mut self, index: u32) -> Self {
187 self.index = Some(index);
188 self
189 }
190}
191
192#[derive(Serialize, Deserialize, Debug, Clone, Default)]
198#[non_exhaustive]
199pub struct PartialFunctionCall {
200 #[serde(default)]
201 pub name: Option<String>,
202 #[serde(default)]
203 pub arguments: Option<String>,
204}
205
206#[derive(Serialize, Deserialize, Debug, Clone, Default)]
217#[non_exhaustive]
218pub struct PartialToolCall {
219 #[serde(default)]
220 pub id: Option<String>,
221 #[serde(default, rename = "type")]
222 pub type_: Option<String>,
223 #[serde(default)]
224 pub function: Option<PartialFunctionCall>,
225 #[serde(default)]
226 pub index: Option<u32>,
227}
228
229impl ToolCall {
230 pub fn parse_params<T>(&self) -> Result<T, crate::error::OpenRouterError>
256 where
257 T: crate::types::typed_tool::TypedTool,
258 {
259 serde_json::from_str(&self.function.arguments)
260 .map_err(crate::error::OpenRouterError::Serialization)
261 }
262
263 pub fn is_tool<T>(&self) -> bool
286 where
287 T: crate::types::typed_tool::TypedTool,
288 {
289 self.function.name == T::name()
290 }
291
292 pub fn name(&self) -> &str {
306 &self.function.name
307 }
308
309 pub fn arguments_json(&self) -> &str {
319 &self.function.arguments
320 }
321
322 pub fn id(&self) -> &str {
332 &self.id
333 }
334
335 pub fn tool_type(&self) -> &str {
345 &self.type_
346 }
347}
348
349#[derive(Serialize, Deserialize, Debug, Clone)]
350#[non_exhaustive]
351pub struct ErrorResponse {
352 pub code: i32,
353 pub message: String,
354 pub metadata: Option<HashMap<String, Value>>,
355}
356
357fn extract_text_from_content_value(value: &Value) -> Option<String> {
358 match value {
359 Value::Null => None,
360 Value::String(text) => Some(text.clone()),
361 Value::Object(part) => extract_text_from_content_part(part),
362 Value::Array(parts) => {
363 let text = parts
364 .iter()
365 .filter_map(|part| match part {
366 Value::Object(part) => extract_text_from_content_part(part),
367 _ => None,
368 })
369 .collect::<String>();
370
371 (!text.is_empty()).then_some(text)
372 }
373 _ => None,
374 }
375}
376
377fn extract_text_from_content_part(part: &serde_json::Map<String, Value>) -> Option<String> {
378 let part_type = part.get("type").and_then(Value::as_str);
379 if let Some(kind) = part_type {
380 if !matches!(kind, "text" | "output_text" | "input_text") {
381 return None;
382 }
383 }
384
385 part.get("text")
386 .and_then(Value::as_str)
387 .or_else(|| part.get("content").and_then(Value::as_str))
388 .map(ToString::to_string)
389}
390
391fn deserialize_optional_text_content<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
392where
393 D: Deserializer<'de>,
394{
395 let value = Option::<Value>::deserialize(deserializer)?;
396 Ok(value.as_ref().and_then(extract_text_from_content_value))
397}
398
399#[derive(Serialize, Deserialize, Debug, Clone)]
400#[non_exhaustive]
401#[serde(untagged)]
402pub enum Choice {
403 NonChat(NonChatChoice),
404 NonStreaming(NonStreamingChoice),
405 Streaming(StreamingChoice),
406}
407
408impl Choice {
409 pub fn content(&self) -> Option<&str> {
410 match self {
411 Choice::NonChat(choice) => Some(choice.text.as_str()),
412 Choice::NonStreaming(choice) => choice.message.content.as_deref(),
413 Choice::Streaming(choice) => choice.delta.content.as_deref(),
414 }
415 }
416
417 pub fn role(&self) -> Option<&str> {
418 match self {
419 Choice::NonChat(_) => None,
420 Choice::NonStreaming(choice) => choice.message.role.as_deref(),
421 Choice::Streaming(choice) => choice.delta.role.as_deref(),
422 }
423 }
424
425 pub fn tool_calls(&self) -> Option<&[ToolCall]> {
432 match self {
433 Choice::NonChat(_) => None,
434 Choice::NonStreaming(choice) => choice.message.tool_calls.as_deref(),
435 Choice::Streaming(_) => None,
436 }
437 }
438
439 pub fn partial_tool_calls(&self) -> Option<&[PartialToolCall]> {
448 match self {
449 Choice::NonChat(_) => None,
450 Choice::NonStreaming(_) => None,
451 Choice::Streaming(choice) => choice.delta.tool_calls.as_deref(),
452 }
453 }
454
455 pub fn finish_reason(&self) -> Option<&FinishReason> {
456 match self {
457 Choice::NonChat(choice) => choice.finish_reason.as_ref(),
458 Choice::NonStreaming(choice) => choice.finish_reason.as_ref(),
459 Choice::Streaming(choice) => choice.finish_reason.as_ref(),
460 }
461 }
462
463 pub fn native_finish_reason(&self) -> Option<&str> {
464 match self {
465 Choice::NonChat(_) => None,
466 Choice::NonStreaming(choice) => choice.native_finish_reason.as_deref(),
467 Choice::Streaming(choice) => choice.native_finish_reason.as_deref(),
468 }
469 }
470
471 pub fn error(&self) -> Option<&ErrorResponse> {
472 match self {
473 Choice::NonChat(choice) => choice.error.as_ref(),
474 Choice::NonStreaming(choice) => choice.error.as_ref(),
475 Choice::Streaming(choice) => choice.error.as_ref(),
476 }
477 }
478
479 pub fn index(&self) -> Option<u32> {
480 match self {
481 Choice::NonChat(choice) => choice.index,
482 Choice::NonStreaming(choice) => choice.index,
483 Choice::Streaming(choice) => choice.index,
484 }
485 }
486
487 pub fn reasoning(&self) -> Option<&str> {
488 match self {
489 Choice::NonChat(_) => None,
490 Choice::NonStreaming(choice) => choice.message.reasoning.as_deref(),
491 Choice::Streaming(choice) => choice.delta.reasoning.as_deref(),
492 }
493 }
494
495 pub fn reasoning_details(&self) -> Option<&[ReasoningDetail]> {
496 match self {
497 Choice::NonChat(_) => None,
498 Choice::NonStreaming(choice) => choice.message.reasoning_details.as_deref(),
499 Choice::Streaming(choice) => choice.delta.reasoning_details.as_deref(),
500 }
501 }
502
503 pub fn logprobs(&self) -> Option<&Value> {
504 match self {
505 Choice::NonChat(choice) => choice.logprobs.as_ref(),
506 Choice::NonStreaming(choice) => choice.logprobs.as_ref(),
507 Choice::Streaming(choice) => choice.logprobs.as_ref(),
508 }
509 }
510}
511
512#[derive(Debug, Clone)]
521#[non_exhaustive]
522pub enum FinishReason {
523 ToolCalls,
524 Stop,
525 Length,
526 ContentFilter,
527 Error,
528 Other(String),
532}
533
534impl FinishReason {
535 fn as_str(&self) -> &str {
537 match self {
538 FinishReason::ToolCalls => "tool_calls",
539 FinishReason::Stop => "stop",
540 FinishReason::Length => "length",
541 FinishReason::ContentFilter => "content_filter",
542 FinishReason::Error => "error",
543 FinishReason::Other(value) => value,
544 }
545 }
546}
547
548impl Serialize for FinishReason {
549 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
550 serializer.serialize_str(self.as_str())
551 }
552}
553
554impl<'de> Deserialize<'de> for FinishReason {
555 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
556 struct Visitor;
557
558 impl<'de> serde::de::Visitor<'de> for Visitor {
559 type Value = FinishReason;
560
561 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
562 formatter.write_str("a finish reason string")
563 }
564
565 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
566 Ok(match value {
567 "tool_calls" => FinishReason::ToolCalls,
568 "stop" => FinishReason::Stop,
569 "length" => FinishReason::Length,
570 "content_filter" => FinishReason::ContentFilter,
571 "error" => FinishReason::Error,
572 other => FinishReason::Other(other.to_string()),
573 })
574 }
575 }
576
577 deserializer.deserialize_str(Visitor)
578 }
579}
580
581#[derive(Serialize, Deserialize, Debug, Clone)]
582#[non_exhaustive]
583pub struct NonChatChoice {
584 pub finish_reason: Option<FinishReason>,
585 pub text: String,
586 pub error: Option<ErrorResponse>,
587 pub index: Option<u32>,
588 pub logprobs: Option<Value>,
589}
590
591#[derive(Serialize, Deserialize, Debug, Clone)]
592#[non_exhaustive]
593pub struct NonStreamingChoice {
594 pub finish_reason: Option<FinishReason>,
595 pub native_finish_reason: Option<String>,
596 pub message: Message,
597 pub error: Option<ErrorResponse>,
598 pub index: Option<u32>,
599 pub logprobs: Option<Value>,
600}
601
602#[derive(Serialize, Deserialize, Debug, Clone)]
603#[non_exhaustive]
604pub struct StreamingChoice {
605 pub finish_reason: Option<FinishReason>,
606 pub native_finish_reason: Option<String>,
607 pub delta: Delta,
608 pub error: Option<ErrorResponse>,
609 pub index: Option<u32>,
610 pub logprobs: Option<Value>,
611}
612
613#[derive(Serialize, Deserialize, Debug, Clone)]
614#[non_exhaustive]
615pub struct Message {
616 #[serde(default, deserialize_with = "deserialize_optional_text_content")]
617 pub content: Option<String>,
618 #[serde(skip_serializing_if = "Option::is_none")]
619 pub model: Option<String>,
620 pub role: Option<String>,
621 #[serde(skip_serializing_if = "Option::is_none")]
622 pub name: Option<String>,
623 pub tool_calls: Option<Vec<ToolCall>>,
624 #[serde(skip_serializing_if = "Option::is_none")]
625 pub reasoning: Option<String>,
626 #[serde(skip_serializing_if = "Option::is_none")]
627 pub reasoning_details: Option<Vec<ReasoningDetail>>,
628 #[serde(skip_serializing_if = "Option::is_none")]
629 pub images: Option<Vec<Value>>,
630 #[serde(skip_serializing_if = "Option::is_none")]
631 pub audio: Option<Value>,
632 pub refusal: Option<String>,
633 #[serde(default)]
634 pub annotations: Option<Vec<Value>>,
635}
636
637#[derive(Serialize, Deserialize, Debug, Clone)]
638#[non_exhaustive]
639pub struct Delta {
640 #[serde(default, deserialize_with = "deserialize_optional_text_content")]
641 pub content: Option<String>,
642 pub role: Option<String>,
643 pub tool_calls: Option<Vec<PartialToolCall>>,
649 #[serde(skip_serializing_if = "Option::is_none")]
650 pub reasoning: Option<String>,
651 #[serde(skip_serializing_if = "Option::is_none")]
652 pub reasoning_details: Option<Vec<ReasoningDetail>>,
653 #[serde(skip_serializing_if = "Option::is_none")]
654 pub audio: Option<Value>,
655 pub refusal: Option<String>,
656}
657
658#[derive(Serialize, Deserialize, Debug, Clone)]
659#[non_exhaustive]
660pub enum ObjectType {
661 #[serde(rename = "chat.completion")]
662 ChatCompletion,
663 #[serde(rename = "chat.completion.chunk")]
664 ChatCompletionChunk,
665}
666
667#[derive(Serialize, Deserialize, Debug, Clone)]
668#[non_exhaustive]
669pub struct CompletionsResponse {
670 pub id: String,
671 pub choices: Vec<Choice>,
672 pub created: u64, pub model: String,
674 #[serde(rename = "object")]
675 pub object_type: ObjectType,
676 pub provider: Option<String>,
677 pub system_fingerprint: Option<String>,
678 pub usage: Option<ResponseUsage>,
679 #[serde(default, skip_serializing_if = "Option::is_none")]
680 pub service_tier: Option<String>,
681 #[serde(default, skip_serializing_if = "Option::is_none")]
682 pub openrouter_metadata: Option<Value>,
683}