use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReasoningDetail {
#[serde(rename = "type")]
pub block_type: String,
#[serde(alias = "content", default)]
pub text: Option<String>,
#[serde(default)]
pub data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<u32>,
}
impl ReasoningDetail {
pub fn content(&self) -> Option<&str> {
self.text.as_deref().or(self.data.as_deref())
}
pub fn reasoning_type(&self) -> &str {
&self.block_type
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ResponseUsage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ToolCall {
pub id: String,
#[serde(rename = "type")]
pub type_: String, pub function: FunctionCall,
#[serde(default)]
pub index: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct PartialFunctionCall {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub arguments: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct PartialToolCall {
#[serde(default)]
pub id: Option<String>,
#[serde(default, rename = "type")]
pub type_: Option<String>,
#[serde(default)]
pub function: Option<PartialFunctionCall>,
#[serde(default)]
pub index: Option<u32>,
}
impl ToolCall {
pub fn parse_params<T>(&self) -> Result<T, crate::error::OpenRouterError>
where
T: crate::types::typed_tool::TypedTool,
{
serde_json::from_str(&self.function.arguments)
.map_err(|e| crate::error::OpenRouterError::Serialization(e))
}
pub fn is_tool<T>(&self) -> bool
where
T: crate::types::typed_tool::TypedTool,
{
self.function.name == T::name()
}
pub fn name(&self) -> &str {
&self.function.name
}
pub fn arguments_json(&self) -> &str {
&self.function.arguments
}
pub fn id(&self) -> &str {
&self.id
}
pub fn tool_type(&self) -> &str {
&self.type_
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ErrorResponse {
pub code: i32,
pub message: String,
pub metadata: Option<HashMap<String, Value>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Choice {
NonChat(NonChatChoice),
NonStreaming(NonStreamingChoice),
Streaming(StreamingChoice),
}
impl Choice {
pub fn content(&self) -> Option<&str> {
match self {
Choice::NonChat(choice) => Some(choice.text.as_str()),
Choice::NonStreaming(choice) => choice.message.content.as_deref(),
Choice::Streaming(choice) => choice.delta.content.as_deref(),
}
}
pub fn role(&self) -> Option<&str> {
match self {
Choice::NonChat(_) => None,
Choice::NonStreaming(choice) => choice.message.role.as_deref(),
Choice::Streaming(choice) => choice.delta.role.as_deref(),
}
}
pub fn tool_calls(&self) -> Option<&[ToolCall]> {
match self {
Choice::NonChat(_) => None,
Choice::NonStreaming(choice) => choice.message.tool_calls.as_deref(),
Choice::Streaming(_) => None,
}
}
pub fn partial_tool_calls(&self) -> Option<&[PartialToolCall]> {
match self {
Choice::NonChat(_) => None,
Choice::NonStreaming(_) => None,
Choice::Streaming(choice) => choice.delta.tool_calls.as_deref(),
}
}
pub fn finish_reason(&self) -> Option<&FinishReason> {
match self {
Choice::NonChat(choice) => choice.finish_reason.as_ref(),
Choice::NonStreaming(choice) => choice.finish_reason.as_ref(),
Choice::Streaming(choice) => choice.finish_reason.as_ref(),
}
}
pub fn native_finish_reason(&self) -> Option<&str> {
match self {
Choice::NonChat(_) => None,
Choice::NonStreaming(choice) => choice.native_finish_reason.as_deref(),
Choice::Streaming(choice) => choice.native_finish_reason.as_deref(),
}
}
pub fn error(&self) -> Option<&ErrorResponse> {
match self {
Choice::NonChat(choice) => choice.error.as_ref(),
Choice::NonStreaming(choice) => choice.error.as_ref(),
Choice::Streaming(choice) => choice.error.as_ref(),
}
}
pub fn index(&self) -> Option<u32> {
match self {
Choice::NonChat(choice) => choice.index,
Choice::NonStreaming(choice) => choice.index,
Choice::Streaming(choice) => choice.index,
}
}
pub fn reasoning(&self) -> Option<&str> {
match self {
Choice::NonChat(_) => None,
Choice::NonStreaming(choice) => choice.message.reasoning.as_deref(),
Choice::Streaming(choice) => choice.delta.reasoning.as_deref(),
}
}
pub fn reasoning_details(&self) -> Option<&[ReasoningDetail]> {
match self {
Choice::NonChat(_) => None,
Choice::NonStreaming(choice) => choice.message.reasoning_details.as_deref(),
Choice::Streaming(choice) => choice.delta.reasoning_details.as_deref(),
}
}
pub fn logprobs(&self) -> Option<&Value> {
match self {
Choice::NonChat(choice) => choice.logprobs.as_ref(),
Choice::NonStreaming(choice) => choice.logprobs.as_ref(),
Choice::Streaming(choice) => choice.logprobs.as_ref(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
ToolCalls,
Stop,
Length,
ContentFilter,
Error,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NonChatChoice {
pub finish_reason: Option<FinishReason>,
pub text: String,
pub error: Option<ErrorResponse>,
pub index: Option<u32>,
pub logprobs: Option<Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NonStreamingChoice {
pub finish_reason: Option<FinishReason>,
pub native_finish_reason: Option<String>,
pub message: Message,
pub error: Option<ErrorResponse>,
pub index: Option<u32>,
pub logprobs: Option<Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StreamingChoice {
pub finish_reason: Option<FinishReason>,
pub native_finish_reason: Option<String>,
pub delta: Delta,
pub error: Option<ErrorResponse>,
pub index: Option<u32>,
pub logprobs: Option<Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Message {
pub content: Option<String>,
pub role: Option<String>,
pub tool_calls: Option<Vec<ToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_details: Option<Vec<ReasoningDetail>>,
pub refusal: Option<String>,
#[serde(default)]
pub annotations: Option<Vec<Value>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Delta {
pub content: Option<String>,
pub role: Option<String>,
pub tool_calls: Option<Vec<PartialToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_details: Option<Vec<ReasoningDetail>>,
pub refusal: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ObjectType {
#[serde(rename = "chat.completion")]
ChatCompletion,
#[serde(rename = "chat.completion.chunk")]
ChatCompletionChunk,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CompletionsResponse {
pub id: String,
pub choices: Vec<Choice>,
pub created: u64, pub model: String,
#[serde(rename = "object")]
pub object_type: ObjectType,
pub provider: Option<String>,
pub system_fingerprint: Option<String>,
pub usage: Option<ResponseUsage>,
}