vtcode_commons/
reasoning.rs1use serde::{Deserialize, Deserializer, Serialize};
9use std::fmt;
10
11pub mod constants {
13 pub const NONE: &str = "none";
14 pub const MINIMAL: &str = "minimal";
15 pub const LOW: &str = "low";
16 pub const MEDIUM: &str = "medium";
17 pub const HIGH: &str = "high";
18 pub const XHIGH: &str = "xhigh";
19 pub const MAX: &str = "max";
20 pub const PARSEABLE_LEVELS: &[&str] = &[NONE, MINIMAL, LOW, MEDIUM, HIGH, XHIGH, MAX];
24 pub const ALLOWED_LEVELS: &[&str] = match PARSEABLE_LEVELS.split_first() {
28 Some((_none, levels)) => levels,
29 None => &[],
30 };
31 pub const LABEL_LOW: &str = "Low";
32 pub const LABEL_MEDIUM: &str = "Medium";
33 pub const LABEL_HIGH: &str = "High";
34 pub const DESCRIPTION_LOW: &str = "Fast responses with lightweight reasoning.";
35 pub const DESCRIPTION_MEDIUM: &str = "Balanced depth and speed. (Note: Mapped to high on some models)";
36 pub const DESCRIPTION_HIGH: &str = "Deep reasoning for complex problems.";
37}
38
39#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
45#[serde(rename_all = "lowercase")]
46#[derive(Default)]
47pub enum ReasoningEffortLevel {
48 None,
50 Minimal,
52 Low,
54 #[default]
56 Medium,
57 High,
59 XHigh,
62 Max,
65 Unknown,
67}
68
69impl ReasoningEffortLevel {
70 pub fn as_str(self) -> &'static str {
72 match self {
73 Self::None => constants::NONE,
74 Self::Minimal => constants::MINIMAL,
75 Self::Low => constants::LOW,
76 Self::Medium => constants::MEDIUM,
77 Self::High => constants::HIGH,
78 Self::XHigh => constants::XHIGH,
79 Self::Max => constants::MAX,
80 Self::Unknown => "unknown",
81 }
82 }
83
84 pub fn parse(value: &str) -> Option<Self> {
86 let normalized = value.trim();
87 if normalized.eq_ignore_ascii_case(constants::NONE) {
88 Some(Self::None)
89 } else if normalized.eq_ignore_ascii_case(constants::MINIMAL) {
90 Some(Self::Minimal)
91 } else if normalized.eq_ignore_ascii_case(constants::LOW) {
92 Some(Self::Low)
93 } else if normalized.eq_ignore_ascii_case(constants::MEDIUM) {
94 Some(Self::Medium)
95 } else if normalized.eq_ignore_ascii_case(constants::HIGH) {
96 Some(Self::High)
97 } else if normalized.eq_ignore_ascii_case(constants::XHIGH) {
98 Some(Self::XHigh)
99 } else if normalized.eq_ignore_ascii_case(constants::MAX) {
100 Some(Self::Max)
101 } else {
102 None
103 }
104 }
105
106 pub fn allowed_values() -> &'static [&'static str] {
108 constants::ALLOWED_LEVELS
109 }
110}
111
112impl fmt::Display for ReasoningEffortLevel {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 f.write_str(self.as_str())
115 }
116}
117
118impl<'de> Deserialize<'de> for ReasoningEffortLevel {
119 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120 where
121 D: Deserializer<'de>,
122 {
123 let raw = String::deserialize(deserializer)?;
124 if let Some(parsed) = Self::parse(&raw) {
125 Ok(parsed)
126 } else {
127 Ok(Self::Unknown)
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_reasoning_effort_parse_and_allowed_values_include_max() {
138 assert_eq!(ReasoningEffortLevel::parse("max"), Some(ReasoningEffortLevel::Max));
139 assert_eq!(ReasoningEffortLevel::Max.as_str(), "max");
140 assert!(ReasoningEffortLevel::allowed_values().contains(&"max"));
141 }
142
143 fn named_levels() -> Vec<ReasoningEffortLevel> {
146 use ReasoningEffortLevel::*;
147 let levels = vec![None, Minimal, Low, Medium, High, XHigh, Max];
148 for level in &levels {
149 match level {
150 None | Minimal | Low | Medium | High | XHigh | Max | Unknown => {}
151 }
152 }
153 levels
154 }
155
156 #[test]
157 fn parseable_levels_match_the_parser_in_both_directions() {
158 for value in constants::PARSEABLE_LEVELS {
159 let parsed = ReasoningEffortLevel::parse(value).unwrap_or_else(|| panic!("{value} must parse"));
160 assert_eq!(parsed.as_str(), *value);
161 }
162 let named = named_levels();
163 assert_eq!(named.len(), constants::PARSEABLE_LEVELS.len());
164 for level in named {
165 assert!(constants::PARSEABLE_LEVELS.contains(&level.as_str()), "{level} missing from PARSEABLE_LEVELS");
166 }
167 assert_eq!(ReasoningEffortLevel::parse("unknown"), None);
168 }
169
170 #[test]
171 fn allowed_levels_are_parseable_levels_without_none() {
172 assert_eq!(constants::PARSEABLE_LEVELS[0], constants::NONE);
173 assert_eq!(constants::ALLOWED_LEVELS, &constants::PARSEABLE_LEVELS[1..]);
174 }
175}