ai_agent/utils/
thinking.rs1use std::env;
7
8#[derive(Debug, Clone)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub enum ThinkingConfig {
12 Adaptive,
13 Enabled { budget_tokens: u32 },
14 Disabled,
15}
16
17impl Default for ThinkingConfig {
18 fn default() -> Self {
19 ThinkingConfig::Disabled
20 }
21}
22
23pub fn has_ultrathink_keyword(text: &str) -> bool {
25 text.to_lowercase().contains("ultrathink")
26}
27
28pub fn find_thinking_trigger_positions(text: &str) -> Vec<(String, usize, usize)> {
31 let mut positions = Vec::new();
32 let text_lower = text.to_lowercase();
33 let search_term = "ultrathink";
34
35 let mut start = 0;
36 while let Some(pos) = text_lower[start..].find(search_term) {
37 let actual_start = start + pos;
38 let actual_end = actual_start + search_term.len();
39 let word = text[actual_start..actual_end].to_string();
41 positions.push((word, actual_start, actual_end));
42 start = actual_end;
43 }
44
45 positions
46}
47
48#[cfg(feature = "theme")]
49use crate::types::Theme;
50
51#[cfg(feature = "theme")]
54pub fn get_rainbow_color(char_index: usize, shimmer: bool) -> &'static str {
55 const RAINBOW_COLORS: &[&str] = &[
56 "rainbow_red",
57 "rainbow_orange",
58 "rainbow_yellow",
59 "rainbow_green",
60 "rainbow_blue",
61 "rainbow_indigo",
62 "rainbow_violet",
63 ];
64
65 const RAINBOW_SHIMMER_COLORS: &[&str] = &[
66 "rainbow_red_shimmer",
67 "rainbow_orange_shimmer",
68 "rainbow_yellow_shimmer",
69 "rainbow_green_shimmer",
70 "rainbow_blue_shimmer",
71 "rainbow_indigo_shimmer",
72 "rainbow_violet_shimmer",
73 ];
74
75 let colors = if shimmer {
76 RAINBOW_SHIMMER_COLORS
77 } else {
78 RAINBOW_COLORS
79 };
80 colors[char_index % colors.len()]
81}
82
83pub fn is_ultrathink_enabled() -> bool {
87 if env::var("AI_ULTRATHINK").is_err() && env::var("ULTRATHINK").is_err() {
89 return false;
90 }
91 true
94}
95
96pub fn model_supports_thinking(model: &str) -> bool {
99 let model_lower = model.to_lowercase();
104
105 if model_lower.contains("claude") {
107 if model_lower.contains("claude-3-") || model_lower.contains("claude 3") {
109 return false;
110 }
111 return true;
113 }
114
115 model_lower.contains("sonnet-4") || model_lower.contains("opus-4")
118}
119
120pub fn model_supports_adaptive_thinking(model: &str) -> bool {
123 let canonical = model.to_lowercase();
124
125 if canonical.contains("opus-4-6") || canonical.contains("sonnet-4-6") {
127 return true;
128 }
129
130 if canonical.contains("opus") || canonical.contains("sonnet") || canonical.contains("haiku") {
132 return false;
133 }
134
135 true
139}
140
141pub fn should_enable_thinking_by_default() -> bool {
143 if let Ok(val) = env::var("MAX_THINKING_TOKENS") {
145 if let Ok(tokens) = val.parse::<u32>() {
146 return tokens > 0;
147 }
148 }
149
150 if let Ok(val) = env::var("AI_MAX_THINKING_TOKENS") {
152 if let Ok(tokens) = val.parse::<u32>() {
153 return tokens > 0;
154 }
155 }
156
157 true
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_has_ultrathink_keyword() {
169 assert!(has_ultrathink_keyword("Let's ultrathink this problem"));
170 assert!(has_ultrathink_keyword("ULTRATHINK"));
171 assert!(has_ultrathink_keyword("UltraThink"));
172 assert!(!has_ultrathink_keyword("thinking is good"));
173 }
174
175 #[test]
176 fn test_find_thinking_trigger_positions() {
177 let text = "Use ultrathink for this. Also ULTRATHINK again.";
178 let positions = find_thinking_trigger_positions(text);
179 assert_eq!(positions.len(), 2);
180 assert_eq!(positions[0].0, "ultrathink");
181 assert_eq!(positions[1].0, "ULTRATHINK");
182 }
183
184 #[test]
185 fn test_model_supports_thinking() {
186 assert!(model_supports_thinking("claude-opus-4-20250514"));
187 assert!(model_supports_thinking("claude-sonnet-4-20250514"));
188 assert!(!model_supports_thinking("claude-3-5-sonnet-20241022"));
189 }
190
191 #[test]
192 fn test_model_supports_adaptive_thinking() {
193 assert!(model_supports_adaptive_thinking("opus-4-6-20250514"));
194 assert!(model_supports_adaptive_thinking("sonnet-4-6-20250514"));
195 assert!(!model_supports_adaptive_thinking("claude-3-5-sonnet"));
196 }
197
198 #[test]
199 fn test_should_enable_thinking_by_default() {
200 assert!(should_enable_thinking_by_default());
202 }
203}