Skip to main content

tiy_core/thinking/
config.rs

1//! Thinking level and configuration.
2
3use serde::{Deserialize, Serialize};
4
5/// Thinking/Reasoning level for models that support it.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum ThinkingLevel {
9    /// No thinking.
10    Off,
11    /// Minimal thinking.
12    Minimal,
13    /// Low thinking.
14    Low,
15    /// Medium thinking.
16    Medium,
17    /// High thinking.
18    High,
19    /// Extra high thinking (OpenAI GPT-5 only).
20    XHigh,
21}
22
23impl Default for ThinkingLevel {
24    fn default() -> Self {
25        ThinkingLevel::Off
26    }
27}
28
29impl std::fmt::Display for ThinkingLevel {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            ThinkingLevel::Off => write!(f, "off"),
33            ThinkingLevel::Minimal => write!(f, "minimal"),
34            ThinkingLevel::Low => write!(f, "low"),
35            ThinkingLevel::Medium => write!(f, "medium"),
36            ThinkingLevel::High => write!(f, "high"),
37            ThinkingLevel::XHigh => write!(f, "xhigh"),
38        }
39    }
40}
41
42impl From<&str> for ThinkingLevel {
43    fn from(s: &str) -> Self {
44        match s.to_lowercase().as_str() {
45            "off" => ThinkingLevel::Off,
46            "minimal" => ThinkingLevel::Minimal,
47            "low" => ThinkingLevel::Low,
48            "medium" => ThinkingLevel::Medium,
49            "high" => ThinkingLevel::High,
50            "xhigh" => ThinkingLevel::XHigh,
51            _ => ThinkingLevel::Off,
52        }
53    }
54}
55
56/// Thinking configuration for models.
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub struct ThinkingConfig {
59    /// Thinking level.
60    pub level: ThinkingLevel,
61    /// Budget tokens for thinking (provider-specific).
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub budget_tokens: Option<u32>,
64}
65
66impl Default for ThinkingConfig {
67    fn default() -> Self {
68        Self {
69            level: ThinkingLevel::Off,
70            budget_tokens: None,
71        }
72    }
73}
74
75impl ThinkingConfig {
76    /// Create a new thinking config with the given level.
77    pub fn new(level: ThinkingLevel) -> Self {
78        Self {
79            level,
80            budget_tokens: None,
81        }
82    }
83
84    /// Create a thinking config with budget tokens.
85    pub fn with_budget(level: ThinkingLevel, budget_tokens: u32) -> Self {
86        Self {
87            level,
88            budget_tokens: Some(budget_tokens),
89        }
90    }
91
92    /// Get the default budget tokens for a level.
93    pub fn default_budget(level: ThinkingLevel) -> u32 {
94        match level {
95            ThinkingLevel::Off => 0,
96            ThinkingLevel::Minimal => 128,
97            ThinkingLevel::Low => 512,
98            ThinkingLevel::Medium => 1024,
99            ThinkingLevel::High => 2048,
100            ThinkingLevel::XHigh => 4096,
101        }
102    }
103}
104
105/// OpenAI-specific thinking options.
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107pub struct OpenAIThinkingOptions {
108    /// Reasoning effort level.
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub reasoning_effort: Option<String>,
111    /// Reasoning summary mode.
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub reasoning_summary: Option<String>,
114}
115
116impl OpenAIThinkingOptions {
117    /// Convert thinking level to OpenAI reasoning effort.
118    pub fn from_level(level: ThinkingLevel) -> Self {
119        let effort = match level {
120            ThinkingLevel::Off => None,
121            ThinkingLevel::Minimal => Some("minimal".to_string()),
122            ThinkingLevel::Low => Some("low".to_string()),
123            ThinkingLevel::Medium => Some("medium".to_string()),
124            ThinkingLevel::High => Some("high".to_string()),
125            ThinkingLevel::XHigh => Some("xhigh".to_string()),
126        };
127
128        Self {
129            reasoning_effort: effort,
130            reasoning_summary: None,
131        }
132    }
133}
134
135/// Anthropic-specific thinking options.
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137pub struct AnthropicThinkingOptions {
138    /// Whether thinking is enabled.
139    pub thinking_enabled: bool,
140    /// Budget tokens for thinking.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub budget_tokens: Option<u32>,
143    /// Whether to use adaptive thinking (Opus 4.6 / Sonnet 4.6).
144    #[serde(default)]
145    pub adaptive: bool,
146}
147
148impl AnthropicThinkingOptions {
149    /// Convert thinking config to Anthropic options.
150    pub fn from_config(config: &ThinkingConfig) -> Self {
151        Self {
152            thinking_enabled: config.level != ThinkingLevel::Off,
153            budget_tokens: config.budget_tokens.or_else(|| {
154                if config.level == ThinkingLevel::Off {
155                    None
156                } else {
157                    Some(ThinkingConfig::default_budget(config.level))
158                }
159            }),
160            adaptive: false,
161        }
162    }
163
164    /// Create adaptive thinking options.
165    pub fn adaptive(budget_tokens: Option<u32>) -> Self {
166        Self {
167            thinking_enabled: true,
168            budget_tokens,
169            adaptive: true,
170        }
171    }
172}
173
174/// Google-specific thinking options.
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176pub struct GoogleThinkingOptions {
177    /// Whether thinking is enabled.
178    pub enabled: bool,
179    /// Budget tokens for thinking.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub budget_tokens: Option<u32>,
182}
183
184impl GoogleThinkingOptions {
185    /// Convert thinking config to Google options.
186    pub fn from_config(config: &ThinkingConfig) -> Self {
187        Self {
188            enabled: config.level != ThinkingLevel::Off,
189            budget_tokens: config.budget_tokens,
190        }
191    }
192}