use once_cell::sync::Lazy;
use regex::Regex;
use vtcode_core::config::ReasoningDisplayMode;
use vtcode_core::config::loader::VTCodeConfig;
use vtcode_core::llm::provider as uni;
static RUSHING_PATTERNS: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)(wrapping up|running out of time|quick summary|to conclude|finishing up|just to be safe|that.?s it|time to stop)").unwrap()
});
static UNCERTAIN_PATTERNS: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)(not sure|i.?m not certain|might be wrong|could be|sorry|apologies)").unwrap()
});
static COMPLEXITY_AVOIDANCE_PATTERNS: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)(too complex|too many|let.?s skip|for now|we can worry about|later)").unwrap()
});
pub(crate) fn model_supports_reasoning(provider: &dyn uni::LLMProvider, model: &str) -> bool {
uni::get_cached_capabilities(provider, model).reasoning
}
pub(crate) fn resolve_reasoning_visibility(
config: Option<&VTCodeConfig>,
supports_reasoning: bool,
) -> bool {
if !supports_reasoning {
return false;
}
let Some(cfg) = config else {
return true;
};
match cfg.ui.reasoning_display_mode {
ReasoningDisplayMode::Always => true,
ReasoningDisplayMode::Hidden => false,
ReasoningDisplayMode::Toggle => cfg.ui.reasoning_visible_default,
}
}
pub(crate) fn is_giving_up_reasoning(text: &str) -> bool {
let lower = text.to_lowercase();
let giving_up_patterns = [
"stop",
"can't continue",
"cannot continue",
"too complex",
"probably stop",
"should stop",
"unable to continue",
"give up",
];
giving_up_patterns
.iter()
.any(|pattern| lower.contains(pattern))
}
pub(crate) fn is_rushing_to_conclude(text: &str) -> bool {
RUSHING_PATTERNS.is_match(text)
}
pub(crate) fn has_high_uncertainty(text: &str) -> bool {
UNCERTAIN_PATTERNS.is_match(text)
}
pub(crate) fn is_avoiding_complexity(text: &str) -> bool {
COMPLEXITY_AVOIDANCE_PATTERNS.is_match(text)
}
pub(crate) fn analyze_reasoning(text: &str) -> ReasoningAnalysis {
ReasoningAnalysis {
is_giving_up: is_giving_up_reasoning(text),
is_rushing: is_rushing_to_conclude(text),
has_uncertainty: has_high_uncertainty(text),
is_avoiding_complexity: is_avoiding_complexity(text),
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ReasoningAnalysis {
pub(crate) is_giving_up: bool,
pub(crate) is_rushing: bool,
pub(crate) has_uncertainty: bool,
pub(crate) is_avoiding_complexity: bool,
}
impl ReasoningAnalysis {
pub(crate) fn has_concerns(&self) -> bool {
self.is_giving_up || self.is_rushing || self.has_uncertainty || self.is_avoiding_complexity
}
pub(crate) fn priority_concern(&self) -> Option<ReasoningConcern> {
if self.is_giving_up {
Some(ReasoningConcern::GivingUp)
} else if self.is_rushing {
Some(ReasoningConcern::Rushing)
} else if self.has_uncertainty {
Some(ReasoningConcern::Uncertainty)
} else if self.is_avoiding_complexity {
Some(ReasoningConcern::AvoidingComplexity)
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ReasoningConcern {
GivingUp,
Rushing,
Uncertainty,
AvoidingComplexity,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_giving_up_detection() {
assert!(is_giving_up_reasoning("I think we should stop here"));
assert!(is_giving_up_reasoning("I can't continue with this task"));
assert!(is_giving_up_reasoning("This is too complex"));
assert!(!is_giving_up_reasoning("Let me try another approach"));
}
#[test]
fn test_rushing_detection() {
assert!(is_rushing_to_conclude("Wrapping up now"));
assert!(is_rushing_to_conclude("Just to be safe, let's conclude"));
assert!(!is_rushing_to_conclude("Let me continue investigating"));
}
#[test]
fn test_uncertainty_detection() {
assert!(has_high_uncertainty("I'm not sure about this"));
assert!(has_high_uncertainty("This could be wrong"));
assert!(!has_high_uncertainty("This is the correct approach"));
}
#[test]
fn test_complexity_avoidance() {
assert!(is_avoiding_complexity("This is too complex"));
assert!(is_avoiding_complexity("Let's skip that for now"));
assert!(!is_avoiding_complexity("Let me tackle this step by step"));
}
#[test]
fn test_analysis() {
let analysis = analyze_reasoning("I think we should stop here, it's too complex");
assert!(analysis.has_concerns());
assert_eq!(
analysis.priority_concern(),
Some(ReasoningConcern::GivingUp)
);
}
#[test]
fn test_analysis_no_concerns() {
let analysis = analyze_reasoning("Let me continue with the next step");
assert!(!analysis.has_concerns());
assert_eq!(analysis.priority_concern(), None);
}
#[test]
fn resolve_reasoning_visibility_requires_capability() {
let mut cfg = VTCodeConfig::default();
cfg.ui.reasoning_display_mode = ReasoningDisplayMode::Always;
cfg.ui.reasoning_visible_default = true;
assert!(!resolve_reasoning_visibility(Some(&cfg), false));
assert!(!resolve_reasoning_visibility(None, false));
}
#[test]
fn resolve_reasoning_visibility_honors_display_mode() {
let mut cfg = VTCodeConfig::default();
cfg.ui.reasoning_display_mode = ReasoningDisplayMode::Always;
assert!(resolve_reasoning_visibility(Some(&cfg), true));
cfg.ui.reasoning_display_mode = ReasoningDisplayMode::Hidden;
assert!(!resolve_reasoning_visibility(Some(&cfg), true));
cfg.ui.reasoning_display_mode = ReasoningDisplayMode::Toggle;
cfg.ui.reasoning_visible_default = true;
assert!(resolve_reasoning_visibility(Some(&cfg), true));
cfg.ui.reasoning_visible_default = false;
assert!(!resolve_reasoning_visibility(Some(&cfg), true));
}
#[test]
fn resolve_reasoning_visibility_defaults_to_enabled_without_config() {
assert!(resolve_reasoning_visibility(None, true));
}
#[test]
fn resolve_reasoning_visibility_uses_visible_default_config() {
let cfg = VTCodeConfig::default();
assert!(resolve_reasoning_visibility(Some(&cfg), true));
}
}