Skip to main content

ai_agent/utils/
thinking.rs

1// Source: /data/home/swei/claudecode/openclaudecode/src/utils/thinking.ts
2//! Thinking configuration and utilities
3//! Translated from /data/home/swei/claudecode/openclaudecode/src/utils/config.ts (thinking section)
4//! and /data/home/swei/claudecode/openclaudecode/src/utils/thinking.ts
5
6use std::env;
7
8/// Thinking configuration types
9#[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
23/// Check if text contains the "ultrathink" keyword (case insensitive)
24pub fn has_ultrathink_keyword(text: &str) -> bool {
25    text.to_lowercase().contains("ultrathink")
26}
27
28/// Find positions of "ultrathink" keyword in text for UI highlighting/notification
29/// Returns a vector of (word, start, end) tuples
30pub 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        // Get the original case from the text
40        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/// Get rainbow color for a character index
52/// Note: Theme color types need to be defined in the SDK
53#[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
83/// Check if ultrathink is enabled
84/// This checks the build-time feature flag and runtime GrowthBook flag
85/// Note: In Rust, we use environment variable AI_ULTRATHINK instead of bun:bundle feature
86pub fn is_ultrathink_enabled() -> bool {
87    // Check environment variable (AI_ prefix for localization)
88    if env::var("AI_ULTRATHINK").is_err() && env::var("ULTRATHINK").is_err() {
89        return false;
90    }
91    // GrowthBook feature check would go here if we have analytics integrated
92    // For now, we just check the env var
93    true
94}
95
96/// Provider-aware thinking support detection
97/// Note: This references getCanonicalName and getAPIProvider which need model utilities
98pub fn model_supports_thinking(model: &str) -> bool {
99    // TODO: Integrate with model capability overrides
100    // TODO: Check USER_TYPE environment variable
101    // TODO: Implement getCanonicalName and getAPIProvider
102
103    let model_lower = model.to_lowercase();
104
105    // Anthropic models: all Claude 4+ support thinking
106    if model_lower.contains("claude") {
107        // Check for Claude 3 models (don't support thinking)
108        if model_lower.contains("claude-3-") || model_lower.contains("claude 3") {
109            return false;
110        }
111        // Claude 4+ supports thinking
112        return true;
113    }
114
115    // Other providers: check for specific model versions
116    // Sonnet 4+ and Opus 4+ support thinking
117    model_lower.contains("sonnet-4") || model_lower.contains("opus-4")
118}
119
120/// Check if model supports adaptive thinking
121/// Adaptive thinking is supported by a subset of Claude 4 models
122pub fn model_supports_adaptive_thinking(model: &str) -> bool {
123    let canonical = model.to_lowercase();
124
125    // Supported by opus-4-6 and sonnet-4-6 variants
126    if canonical.contains("opus-4-6") || canonical.contains("sonnet-4-6") {
127        return true;
128    }
129
130    // Exclude legacy models
131    if canonical.contains("opus") || canonical.contains("sonnet") || canonical.contains("haiku") {
132        return false;
133    }
134
135    // Default to true for unknown models on first-party and foundry
136    // (these are proxies that should support adaptive thinking)
137    // TODO: Check getAPIProvider() - need to implement
138    true
139}
140
141/// Check if thinking should be enabled by default
142pub fn should_enable_thinking_by_default() -> bool {
143    // Check MAX_THINKING_TOKENS environment variable
144    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    // Check AI_MAX_THINKING_TOKENS (localized env var)
151    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    // Check settings for alwaysThinkingEnabled
158    // TODO: Integrate with settings system
159    // For now, default to enabled
160    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        // Without env var, should default to true
201        assert!(should_enable_thinking_by_default());
202    }
203}