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, word boundary)
24pub fn has_ultrathink_keyword(text: &str) -> bool {
25    regex::Regex::new(r"(?i)\bultrathink\b")
26        .unwrap()
27        .is_match(text)
28}
29
30/// Find positions of "ultrathink" keyword in text for UI highlighting/notification
31/// Returns a vector of (word, start, end) tuples
32pub fn find_thinking_trigger_positions(text: &str) -> Vec<(String, usize, usize)> {
33    let mut positions = Vec::new();
34    // Use fresh /g regex each call — shared regex would leak lastIndex state
35    let re = regex::Regex::new(r"(?i)\bultrathink\b").unwrap();
36    for cap in re.find_iter(text) {
37        positions.push((cap.as_str().to_string(), cap.start(), cap.end()));
38    }
39    positions
40}
41
42#[cfg(feature = "theme")]
43use crate::types::Theme;
44
45/// Get rainbow color for a character index
46/// Note: Theme color types need to be defined in the SDK
47#[cfg(feature = "theme")]
48pub fn get_rainbow_color(char_index: usize, shimmer: bool) -> &'static str {
49    const RAINBOW_COLORS: &[&str] = &[
50        "rainbow_red",
51        "rainbow_orange",
52        "rainbow_yellow",
53        "rainbow_green",
54        "rainbow_blue",
55        "rainbow_indigo",
56        "rainbow_violet",
57    ];
58
59    const RAINBOW_SHIMMER_COLORS: &[&str] = &[
60        "rainbow_red_shimmer",
61        "rainbow_orange_shimmer",
62        "rainbow_yellow_shimmer",
63        "rainbow_green_shimmer",
64        "rainbow_blue_shimmer",
65        "rainbow_indigo_shimmer",
66        "rainbow_violet_shimmer",
67    ];
68
69    let colors = if shimmer {
70        RAINBOW_SHIMMER_COLORS
71    } else {
72        RAINBOW_COLORS
73    };
74    colors[char_index % colors.len()]
75}
76
77/// Check if ultrathink is enabled
78/// This checks the build-time feature flag and runtime GrowthBook flag
79/// Note: In Rust, we use environment variable AI_ULTRATHINK instead of bun:bundle feature
80pub fn is_ultrathink_enabled() -> bool {
81    // Check environment variable (AI_ prefix for localization)
82    // feature('ULTRATHINK') in TS — we check the env var as the runtime gate
83    if !crate::utils::env_utils::is_env_truthy(std::env::var("AI_ULTRATHINK").ok().as_deref())
84        && !crate::utils::env_utils::is_env_truthy(std::env::var("ULTRATHINK").ok().as_deref())
85    {
86        return false;
87    }
88    // GrowthBook feature check would go here if we have analytics integrated
89    // For now, we just check the env var
90    true
91}
92
93/// Provider-aware thinking support detection
94/// Note: This references getCanonicalName and getAPIProvider which need model utilities
95pub fn model_supports_thinking(model: &str) -> bool {
96    // TODO: Integrate with model capability overrides
97    // TODO: Check USER_TYPE environment variable
98    // TODO: Implement getCanonicalName and getAPIProvider
99
100    let model_lower = model.to_lowercase();
101
102    // Anthropic models: all Claude 4+ support thinking
103    if model_lower.contains("claude") {
104        // Check for Claude 3 models (don't support thinking)
105        if model_lower.contains("claude-3-") || model_lower.contains("claude 3") {
106            return false;
107        }
108        // Claude 4+ supports thinking
109        return true;
110    }
111
112    // Other providers: check for specific model versions
113    // Sonnet 4+ and Opus 4+ support thinking
114    model_lower.contains("sonnet-4") || model_lower.contains("opus-4")
115}
116
117/// Check if model supports adaptive thinking
118/// Adaptive thinking is supported by a subset of Claude 4 models
119pub fn model_supports_adaptive_thinking(model: &str) -> bool {
120    let canonical = model.to_lowercase();
121
122    // Supported by opus-4-6 and sonnet-4-6 variants
123    if canonical.contains("opus-4-6") || canonical.contains("sonnet-4-6") {
124        return true;
125    }
126
127    // Exclude legacy models
128    if canonical.contains("opus") || canonical.contains("sonnet") || canonical.contains("haiku") {
129        return false;
130    }
131
132    // Default to true for unknown models on first-party and foundry
133    // (these are proxies that should support adaptive thinking)
134    // TODO: Check getAPIProvider() - need to implement
135    true
136}
137
138/// Check if thinking should be enabled by default
139pub fn should_enable_thinking_by_default() -> bool {
140    // Check MAX_THINKING_TOKENS environment variable
141    if let Ok(val) = env::var("MAX_THINKING_TOKENS") {
142        if let Ok(tokens) = val.parse::<u32>() {
143            return tokens > 0;
144        }
145    }
146
147    // Check AI_MAX_THINKING_TOKENS (localized env var)
148    if let Ok(val) = env::var("AI_MAX_THINKING_TOKENS") {
149        if let Ok(tokens) = val.parse::<u32>() {
150            return tokens > 0;
151        }
152    }
153
154    // Check settings for alwaysThinkingEnabled
155    // TODO: Integrate with settings system
156    // For now, default to enabled
157    true
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_has_ultrathink_keyword() {
166        assert!(has_ultrathink_keyword("Let's ultrathink this problem"));
167        assert!(has_ultrathink_keyword("ULTRATHINK"));
168        assert!(has_ultrathink_keyword("UltraThink"));
169        assert!(!has_ultrathink_keyword("thinking is good"));
170    }
171
172    #[test]
173    fn test_find_thinking_trigger_positions() {
174        let text = "Use ultrathink for this. Also ULTRATHINK again.";
175        let positions = find_thinking_trigger_positions(text);
176        assert_eq!(positions.len(), 2);
177        assert_eq!(positions[0].0, "ultrathink");
178        assert_eq!(positions[1].0, "ULTRATHINK");
179    }
180
181    #[test]
182    fn test_model_supports_thinking() {
183        assert!(model_supports_thinking("claude-opus-4-20250514"));
184        assert!(model_supports_thinking("claude-sonnet-4-20250514"));
185        assert!(!model_supports_thinking("claude-3-5-sonnet-20241022"));
186    }
187
188    #[test]
189    fn test_model_supports_adaptive_thinking() {
190        assert!(model_supports_adaptive_thinking("opus-4-6-20250514"));
191        assert!(model_supports_adaptive_thinking("sonnet-4-6-20250514"));
192        assert!(!model_supports_adaptive_thinking("claude-3-5-sonnet"));
193    }
194
195    #[test]
196    fn test_should_enable_thinking_by_default() {
197        // Without env var, should default to true
198        assert!(should_enable_thinking_by_default());
199    }
200}