1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum Provider {
7 #[default]
8 Anthropic,
9 #[serde(alias = "openai")]
10 OpenAiCompatible,
11}
12
13impl Provider {
14 pub fn as_str(&self) -> &'static str {
15 match self {
16 Self::Anthropic => "anthropic",
17 Self::OpenAiCompatible => "openai",
18 }
19 }
20
21 pub fn default_base_url(&self) -> &'static str {
22 match self {
23 Self::Anthropic => "https://api.anthropic.com",
24 Self::OpenAiCompatible => "https://api.openai.com",
25 }
26 }
27}
28
29impl std::fmt::Display for Provider {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 f.write_str(self.as_str())
32 }
33}
34
35impl std::str::FromStr for Provider {
36 type Err = String;
37
38 fn from_str(s: &str) -> Result<Self, Self::Err> {
39 match s {
40 "anthropic" => Ok(Self::Anthropic),
41 "openai" => Ok(Self::OpenAiCompatible),
42 _ => Err(format!("unknown provider: {s}")),
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56#[serde(tag = "type", rename_all = "lowercase")]
57pub enum ThinkingConfig {
58 Adaptive {
60 #[serde(default)]
61 effort: EffortLevel,
62 },
63 Enabled { budget_tokens: u32 },
65}
66
67#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "lowercase")]
70pub enum EffortLevel {
71 Max,
72 #[default]
73 High,
74 Medium,
75 Low,
76}
77
78impl EffortLevel {
79 pub fn as_str(&self) -> &'static str {
80 match self {
81 Self::Max => "max",
82 Self::High => "high",
83 Self::Medium => "medium",
84 Self::Low => "low",
85 }
86 }
87
88 pub fn all() -> &'static [EffortLevel] {
89 &[Self::Max, Self::High, Self::Medium, Self::Low]
90 }
91}
92
93impl std::fmt::Display for EffortLevel {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.write_str(self.as_str())
96 }
97}
98
99impl std::str::FromStr for EffortLevel {
100 type Err = String;
101
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 match s {
104 "max" => Ok(Self::Max),
105 "high" => Ok(Self::High),
106 "medium" | "med" => Ok(Self::Medium),
107 "low" | "minimal" => Ok(Self::Low),
108 _ => Err(format!("unknown effort level: {s}")),
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ToolDefinition {
116 pub name: String,
117 pub description: String,
118 pub input_schema: serde_json::Value,
119}
120
121impl ToolDefinition {
122 pub fn new(
123 name: impl Into<String>,
124 description: impl Into<String>,
125 input_schema: serde_json::Value,
126 ) -> Self {
127 Self {
128 name: name.into(),
129 description: description.into(),
130 input_schema,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Default, Serialize, Deserialize)]
137pub struct Usage {
138 #[serde(default)]
139 pub input_tokens: u64,
140 #[serde(default)]
141 pub output_tokens: u64,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub cache_creation_input_tokens: Option<u64>,
144 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub cache_read_input_tokens: Option<u64>,
146}
147
148impl Usage {
149 pub fn total_tokens(&self) -> u64 {
150 self.input_tokens + self.output_tokens
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct ResponseFormat {
157 #[serde(rename = "type")]
158 pub format_type: String,
159}
160
161impl ResponseFormat {
162 pub fn json_object() -> Self {
163 Self {
164 format_type: "json_object".to_string(),
165 }
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
173pub enum StopReason {
174 EndTurn,
175 ToolUse,
176 MaxTokens,
177 Other(String),
178}
179
180impl StopReason {
181 pub fn from_anthropic(s: &str) -> Self {
182 match s {
183 "end_turn" => Self::EndTurn,
184 "tool_use" => Self::ToolUse,
185 "max_tokens" => Self::MaxTokens,
186 other => Self::Other(other.to_string()),
187 }
188 }
189
190 pub fn from_openai(s: &str) -> Self {
191 match s {
192 "stop" => Self::EndTurn,
193 "tool_calls" => Self::ToolUse,
194 "length" => Self::MaxTokens,
195 other => Self::Other(other.to_string()),
196 }
197 }
198
199 pub fn to_anthropic(&self) -> &str {
200 match self {
201 Self::EndTurn => "end_turn",
202 Self::ToolUse => "tool_use",
203 Self::MaxTokens => "max_tokens",
204 Self::Other(s) => s,
205 }
206 }
207
208 pub fn to_openai(&self) -> &str {
209 match self {
210 Self::EndTurn => "stop",
211 Self::ToolUse => "tool_calls",
212 Self::MaxTokens => "length",
213 Self::Other(s) => s,
214 }
215 }
216
217 pub fn is_tool_use(&self) -> bool {
218 matches!(self, Self::ToolUse)
219 }
220}
221
222impl std::fmt::Display for StopReason {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 f.write_str(self.to_anthropic())
225 }
226}
227
228impl Serialize for StopReason {
229 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
230 serializer.serialize_str(self.to_anthropic())
231 }
232}
233
234impl<'de> Deserialize<'de> for StopReason {
235 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
236 let s = String::deserialize(deserializer)?;
237 Ok(Self::from_anthropic(&s))
238 }
239}