use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct CanonicalRequest {
#[serde(default)]
pub model: String,
#[serde(default)]
pub system: Option<Vec<Content>>,
#[serde(default)]
pub messages: Vec<Message>,
#[serde(default)]
pub tools: Vec<Tool>,
#[serde(default)]
pub tool_choice: ToolChoice,
#[serde(default)]
pub parallel_tool_calls: Option<bool>,
#[serde(default)]
pub max_tokens: Option<u32>,
#[serde(default)]
pub temperature: Option<f32>,
#[serde(default)]
pub top_p: Option<f32>,
#[serde(default)]
pub reasoning: Option<ReasoningEffort>,
#[serde(default)]
pub stop: Vec<String>,
#[serde(default)]
pub stream: Option<bool>,
#[serde(default)]
pub output: Option<OutputFormat>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl CanonicalRequest {
pub fn tool_name(&self, tool_use_id: &str) -> Option<&str> {
self.messages
.iter()
.flat_map(|m| &m.content)
.find_map(|c| match c {
Content::ToolUse { id, name, .. } if id == tool_use_id => Some(name.as_str()),
_ => None,
})
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Message {
pub role: Role,
#[serde(deserialize_with = "crate::canonical::request_de::de_content_seq")]
pub content: Vec<Content>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum Role {
System,
User,
Assistant,
Tool,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Content {
Text(String),
Image {
source: ImageSource,
},
Document {
source: DocumentSource,
},
ToolUse {
id: String,
name: String,
input: Value,
signature: Option<String>,
},
ToolResult {
tool_use_id: String,
content: Vec<Content>,
is_error: bool,
},
Thinking {
text: String,
signature: Option<String>,
id: Option<String>,
encrypted_content: Option<String>,
},
RedactedThinking {
data: String,
},
ServerToolUse {
id: String,
name: String,
input: Value,
},
ServerToolResult {
kind: String,
tool_use_id: String,
content: Value,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ImageSource {
Base64 { media_type: String, data: String },
Url { url: String },
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum DocumentSource {
Base64 { media_type: String, data: String },
Url { url: String },
}
#[derive(Clone, Debug, PartialEq)] pub enum Tool {
Custom {
name: String,
description: Option<String>,
input_schema: Value,
strict: Option<bool>,
},
Provider {
kind: String,
name: String,
config: Map<String, Value>,
},
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ToolChoice {
#[default]
Auto,
Any,
Tool {
name: String,
},
None,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[non_exhaustive]
pub enum OutputFormat {
Json,
JsonSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
schema: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
strict: Option<bool>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum ReasoningEffort {
Low,
Medium,
High,
}
impl ReasoningEffort {
pub fn as_str(self) -> &'static str {
match self {
ReasoningEffort::Low => "low",
ReasoningEffort::Medium => "medium",
ReasoningEffort::High => "high",
}
}
pub fn budget(self) -> u32 {
match self {
ReasoningEffort::Low => 1024,
ReasoningEffort::Medium => 8192,
ReasoningEffort::High => 24576,
}
}
}
impl std::str::FromStr for ReasoningEffort {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
match s {
"low" => Ok(ReasoningEffort::Low),
"medium" => Ok(ReasoningEffort::Medium),
"high" => Ok(ReasoningEffort::High),
_ => Err(()),
}
}
}