use std::process::Command;
#[must_use]
pub fn parse_nvidia_smi_mib(output: &str) -> Option<f64> {
let first = output.lines().next()?.trim();
let mib: f64 = first.parse().ok()?;
if mib <= 0.0 {
return None;
}
Some(mib / 1024.0)
}
#[must_use]
pub fn detect_vram_gb() -> Option<f64> {
let out = Command::new("nvidia-smi")
.args(["--query-gpu=memory.total", "--format=csv,noheader,nounits"])
.output()
.ok()?;
if !out.status.success() {
return None;
}
parse_nvidia_smi_mib(&String::from_utf8_lossy(&out.stdout))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VramSource {
Detected,
EnvVar,
Default,
}
#[must_use]
pub fn resolve_vram_gb() -> (f64, VramSource) {
if let Some(gb) = detect_vram_gb() {
return (gb, VramSource::Detected);
}
if let Some(gb) = std::env::var("CLAUDETTE_VRAM_GB")
.ok()
.and_then(|s| s.parse::<f64>().ok())
{
return (gb, VramSource::EnvVar);
}
(8.0, VramSource::Default)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BrainRec {
pub model: &'static str,
pub why: &'static str,
pub alternatives: &'static str,
}
const FLAGSHIP_TIER_GIB: f64 = 15.0;
#[must_use]
pub fn recommend_brain(vram_gb: f64, openai_compat: bool) -> BrainRec {
if vram_gb >= FLAGSHIP_TIER_GIB {
if openai_compat {
BrainRec {
model: "qwen3.6-35b-a3b@q3_k_xl",
why: "92% on the 50-task battery — best accuracy. Pin q3_k_xl: it fits \
16 GB; q4_k_xl spills to RAM and loses tasks to timeouts",
alternatives: "",
}
} else {
BrainRec {
model: "qwen3.5:9b",
why: "88% on the 50-task battery — the best brain packaged on Ollama",
alternatives: "the 92% flagship qwen3.6-35b-a3b is LM Studio-only: set \
CLAUDETTE_OPENAI_COMPAT=1 + OLLAMA_HOST=http://localhost:1234 + \
CLAUDETTE_MODEL=qwen3.6-35b-a3b@q3_k_xl (full setup: \
docs/power-user.md; worth the switch on 16 GB)",
}
}
} else {
BrainRec {
model: if openai_compat {
"qwen3.5-4b"
} else {
"qwen3.5:4b"
},
why: "90% on the 50-task battery in 8 min on ~3.4 GB — best value; runs on an \
8 GB GPU or plain CPU",
alternatives: "qwen3.5:9b (88%, 11 GB) or gpt-oss-20b (86%, 13 GB, fastest) \
if you have the headroom",
}
}
}
#[cfg(test)]
mod tests {
use super::{parse_nvidia_smi_mib, recommend_brain, VramSource};
#[test]
fn parse_mib_happy_path() {
assert_eq!(parse_nvidia_smi_mib("16384"), Some(16.0));
assert_eq!(parse_nvidia_smi_mib("16384\n"), Some(16.0));
assert_eq!(parse_nvidia_smi_mib(" 8192 "), Some(8.0));
}
#[test]
fn parse_mib_takes_first_gpu_on_multi_gpu_boxes() {
assert_eq!(parse_nvidia_smi_mib("16384\n8192\n"), Some(16.0));
}
#[test]
fn parse_mib_rejects_garbage() {
assert_eq!(parse_nvidia_smi_mib(""), None);
assert_eq!(parse_nvidia_smi_mib("N/A"), None);
assert_eq!(parse_nvidia_smi_mib("[Insufficient Permissions]"), None);
assert_eq!(parse_nvidia_smi_mib("-1"), None);
assert_eq!(parse_nvidia_smi_mib("0"), None);
}
#[test]
fn recommend_boundaries_match_the_certified_table() {
assert_eq!(recommend_brain(15.9, true).model, "qwen3.6-35b-a3b@q3_k_xl");
assert_eq!(recommend_brain(16.0, true).model, "qwen3.6-35b-a3b@q3_k_xl");
assert_eq!(recommend_brain(24.0, true).model, "qwen3.6-35b-a3b@q3_k_xl");
let ollama16 = recommend_brain(16.0, false);
assert_eq!(ollama16.model, "qwen3.5:9b");
assert!(ollama16.alternatives.contains("CLAUDETTE_OPENAI_COMPAT=1"));
assert_eq!(recommend_brain(14.9, false).model, "qwen3.5:4b");
assert_eq!(recommend_brain(8.0, false).model, "qwen3.5:4b");
assert_eq!(recommend_brain(7.9, true).model, "qwen3.5-4b");
assert_eq!(recommend_brain(0.0, false).model, "qwen3.5:4b");
}
#[test]
fn vram_source_is_comparable() {
assert_ne!(VramSource::Detected, VramSource::Default);
}
}