1use crate::{CacheRetention, ThinkingLevel};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt;
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15pub struct ProviderOptions {
16 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub anthropic: Option<AnthropicOptions>,
19
20 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub openai: Option<OpenAiOptions>,
23
24 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub google: Option<GoogleOptions>,
27
28 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub openai_compatible: Option<OpenAiCompatibleOptions>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct AnthropicOptions {
36 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub thinking_type: Option<String>,
41
42 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub thinking_budget: Option<usize>,
45
46 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub effort: Option<String>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct OpenAiOptions {
55 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub store: Option<bool>,
58
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub reasoning_effort: Option<String>,
62
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub reasoning_summary: Option<String>,
66
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub include_encrypted_reasoning: Option<bool>,
70
71 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub text_verbosity: Option<String>,
74
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub prompt_cache_key: Option<String>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct GoogleOptions {
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub include_thoughts: Option<bool>,
86
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub thinking_level: Option<String>,
90
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub thinking_budget: Option<usize>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct OpenAiCompatibleOptions {
99 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub reasoning_effort: Option<String>,
102
103 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub enable_thinking: Option<bool>,
106
107 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub cache_control: Option<String>,
110}
111
112#[derive(Clone, Default, Serialize, Deserialize)]
114pub struct StreamOptions {
115 #[serde(default)]
117 pub temperature: Option<f64>,
118
119 #[serde(default)]
121 pub max_tokens: Option<usize>,
122
123 #[serde(skip)]
126 pub api_key: Option<String>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub cache_retention: Option<CacheRetention>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub session_id: Option<String>,
135
136 #[serde(default)]
138 pub headers: HashMap<String, String>,
139
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub thinking_level: Option<ThinkingLevel>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub thinking_budgets: Option<ThinkingBudgets>,
147
148 #[serde(default, skip_serializing_if = "Option::is_none")]
151 pub tool_choice: Option<crate::tools::ToolChoice>,
152
153 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub provider_options: Option<ProviderOptions>,
161}
162
163impl fmt::Debug for StreamOptions {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 f.debug_struct("StreamOptions")
166 .field("temperature", &self.temperature)
167 .field("max_tokens", &self.max_tokens)
168 .field("api_key", &self.api_key.as_ref().map(|_| "[REDACTED]"))
169 .field("cache_retention", &self.cache_retention)
170 .field("session_id", &self.session_id)
171 .field("headers", &self.headers)
172 .field("thinking_level", &self.thinking_level)
173 .field("thinking_budgets", &self.thinking_budgets)
174 .field("provider_options", &self.provider_options)
175 .finish()
176 }
177}
178
179impl StreamOptions {
180 pub fn new() -> Self {
182 Self::default()
183 }
184
185 pub fn temperature(mut self, temp: f64) -> Self {
187 self.temperature = Some(temp);
188 self
189 }
190
191 pub fn max_tokens(mut self, tokens: usize) -> Self {
193 self.max_tokens = Some(tokens);
194 self
195 }
196
197 pub fn api_key(mut self, key: impl Into<String>) -> Self {
199 self.api_key = Some(key.into());
200 self
201 }
202
203 pub fn cache_retention(mut self, retention: CacheRetention) -> Self {
205 self.cache_retention = Some(retention);
206 self
207 }
208
209 pub fn session_id(mut self, id: impl Into<String>) -> Self {
211 self.session_id = Some(id.into());
212 self
213 }
214
215 pub fn thinking_level(mut self, level: ThinkingLevel) -> Self {
217 self.thinking_level = Some(level);
218 self
219 }
220}
221
222#[derive(Debug, Clone, Default, Serialize, Deserialize)]
224pub struct ThinkingBudgets {
225 #[serde(default)]
226 pub minimal: Option<usize>,
227 #[serde(default)]
228 pub low: Option<usize>,
229 #[serde(default)]
230 pub medium: Option<usize>,
231 #[serde(default)]
232 pub high: Option<usize>,
233}
234
235impl ThinkingBudgets {
236 pub fn new() -> Self {
237 Self::default()
238 }
239
240 pub fn minimal(mut self, tokens: usize) -> Self {
241 self.minimal = Some(tokens);
242 self
243 }
244
245 pub fn low(mut self, tokens: usize) -> Self {
246 self.low = Some(tokens);
247 self
248 }
249
250 pub fn medium(mut self, tokens: usize) -> Self {
251 self.medium = Some(tokens);
252 self
253 }
254
255 pub fn high(mut self, tokens: usize) -> Self {
256 self.high = Some(tokens);
257 self
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::tools::ToolChoice;
265
266 #[test]
267 fn stream_options_default_has_no_tool_choice() {
268 let opts = StreamOptions::default();
269 assert!(opts.tool_choice.is_none());
270 }
271
272 #[test]
273 fn tool_choice_named_round_trips_through_serde() {
274 let tc = ToolChoice::Named("todo".to_string());
275 let json = serde_json::to_string(&tc).unwrap();
276 let back: ToolChoice = serde_json::from_str(&json).unwrap();
277 assert_eq!(back, tc);
278 }
279}