use clap::ValueEnum;
use serde::{Deserialize, Serialize};
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
Serialize,
Deserialize,
Default,
ValueEnum,
)]
#[serde(rename_all = "lowercase")]
#[value(rename_all = "lowercase")]
pub enum ReasoningLevel {
None,
Minimal,
Low,
#[default]
Medium,
High,
XHigh,
Max,
}
impl ReasoningLevel {
fn rank(self) -> u8 {
match self {
ReasoningLevel::None => 0,
ReasoningLevel::Minimal => 1,
ReasoningLevel::Low => 2,
ReasoningLevel::Medium => 3,
ReasoningLevel::High => 4,
ReasoningLevel::XHigh => 5,
ReasoningLevel::Max => 6,
}
}
pub fn as_str(self) -> &'static str {
match self {
ReasoningLevel::None => "none",
ReasoningLevel::Minimal => "minimal",
ReasoningLevel::Low => "low",
ReasoningLevel::Medium => "medium",
ReasoningLevel::High => "high",
ReasoningLevel::Max => "max",
ReasoningLevel::XHigh => "xhigh",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReasoningCapability {
Unsupported,
Binary,
Levels(Vec<ReasoningLevel>),
Budget { min: usize, max: usize },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReasoningChunk {
pub text: String,
pub signature: Option<String>,
}
pub fn nearest_effort(
requested: ReasoningLevel,
supported: &[ReasoningLevel],
) -> Option<ReasoningLevel> {
if supported.is_empty() {
return None;
}
let target = requested.rank();
let at_or_below = supported.iter().filter(|l| l.rank() <= target).max();
if let Some(level) = at_or_below {
return Some(*level);
}
supported.iter().min().copied()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reasoning_level_default_is_medium() {
assert_eq!(ReasoningLevel::default(), ReasoningLevel::Medium);
}
#[test]
fn reasoning_level_serde_roundtrip() {
for level in [
ReasoningLevel::None,
ReasoningLevel::Minimal,
ReasoningLevel::Low,
ReasoningLevel::Medium,
ReasoningLevel::High,
ReasoningLevel::Max,
ReasoningLevel::XHigh,
] {
let json = serde_json::to_string(&level).unwrap();
let back: ReasoningLevel = serde_json::from_str(&json).unwrap();
assert_eq!(level, back);
assert_eq!(json.trim_matches('"'), level.as_str());
}
}
#[test]
fn reasoning_level_ord_matches_rank() {
assert!(ReasoningLevel::None < ReasoningLevel::Minimal);
assert!(ReasoningLevel::Minimal < ReasoningLevel::Low);
assert!(ReasoningLevel::Low < ReasoningLevel::Medium);
assert!(ReasoningLevel::Medium < ReasoningLevel::High);
assert!(ReasoningLevel::High < ReasoningLevel::XHigh);
assert!(ReasoningLevel::XHigh < ReasoningLevel::Max);
}
#[test]
fn nearest_effort_empty_returns_none() {
assert_eq!(nearest_effort(ReasoningLevel::Medium, &[]), None);
}
#[test]
fn nearest_effort_exact_match() {
let supported = vec![
ReasoningLevel::Low,
ReasoningLevel::Medium,
ReasoningLevel::High,
];
assert_eq!(
nearest_effort(ReasoningLevel::Medium, &supported),
Some(ReasoningLevel::Medium),
);
}
#[test]
fn nearest_effort_downgrades_to_highest_at_or_below() {
let supported = vec![ReasoningLevel::Low, ReasoningLevel::Medium];
assert_eq!(
nearest_effort(ReasoningLevel::High, &supported),
Some(ReasoningLevel::Medium),
);
}
#[test]
fn nearest_effort_upgrades_when_all_above_request() {
let supported = vec![ReasoningLevel::Medium, ReasoningLevel::High];
assert_eq!(
nearest_effort(ReasoningLevel::None, &supported),
Some(ReasoningLevel::Medium),
);
}
#[test]
fn nearest_effort_max_request_with_lower_ceiling() {
let supported = vec![
ReasoningLevel::Low,
ReasoningLevel::Medium,
ReasoningLevel::High,
];
assert_eq!(
nearest_effort(ReasoningLevel::Max, &supported),
Some(ReasoningLevel::High),
);
}
#[test]
fn reasoning_chunk_construction() {
let chunk = ReasoningChunk {
text: "thinking through the problem".to_string(),
signature: None,
};
assert_eq!(chunk.text, "thinking through the problem");
assert!(chunk.signature.is_none());
}
#[test]
fn clap_value_enum_parses_all_seven_levels() {
use clap::ValueEnum as _;
for (s, expected) in [
("none", ReasoningLevel::None),
("minimal", ReasoningLevel::Minimal),
("low", ReasoningLevel::Low),
("medium", ReasoningLevel::Medium),
("high", ReasoningLevel::High),
("max", ReasoningLevel::Max),
("xhigh", ReasoningLevel::XHigh),
] {
let parsed = ReasoningLevel::from_str(s, true).expect(s);
assert_eq!(parsed, expected);
}
}
#[test]
fn clap_value_enum_rejects_unknown_levels() {
use clap::ValueEnum as _;
assert!(ReasoningLevel::from_str("foobar", true).is_err());
assert!(ReasoningLevel::from_str("medium ", true).is_err());
}
}