use anyhow::{Result, anyhow};
use std::io::{self, Read};
use std::path::Path;
use kjarni::ModelType;
pub fn resolve_input(input: Option<&str>) -> Result<String> {
match input {
Some(text) => {
let path = Path::new(text);
if path.exists() && path.is_file() {
std::fs::read_to_string(path)
.map_err(|e| anyhow!("Failed to read file '{}': {}", text, e))
} else {
Ok(text.to_string())
}
}
None => {
let stdin = io::stdin();
let mut buffer = String::new();
stdin.lock().read_to_string(&mut buffer)?;
if buffer.is_empty() {
return Err(anyhow!(
"No input provided. Pass text as argument, a file path, or pipe via stdin."
));
}
Ok(buffer)
}
}
}
pub fn resolve_model(name: &str, arch_hint: Option<&str>) -> anyhow::Result<ModelType> {
ModelType::from_cli_name(name)
.ok_or_else(|| anyhow::anyhow!(model_not_found_error(name, arch_hint)))
}
pub fn model_not_found_error(name: &str, arch_hint: Option<&str>) -> String {
let mut msg = format!("Unknown model: '{}'.", name);
if let Some(arch) = arch_hint {
msg.push_str(&format!(
" Run 'kjarni model list --arch {}' to see available models.",
arch
));
} else {
msg.push_str(" Run 'kjarni model list' to see available models.");
}
let suggestions = ModelType::find_similar(name);
if !suggestions.is_empty() {
msg.push_str("\n\nDid you mean?");
for (suggestion, _) in suggestions {
msg.push_str(&format!("\n - {}", suggestion));
}
}
msg
}
pub fn check_gpu_capacity(model_type: kjarni::ModelType) -> anyhow::Result<()> {
let Some(free_bytes) = free_vram_bytes() else {
return Ok(());
};
let dir = model_type.cache_dir(&kjarni::registry::cache_dir());
let weight_bytes: u64 = std::fs::read_dir(&dir)
.into_iter()
.flatten()
.flatten()
.filter(|e| {
let name = e.file_name();
let name = name.to_string_lossy();
name.ends_with(".safetensors") || name.ends_with(".gguf")
})
.filter_map(|e| e.metadata().ok())
.map(|m| m.len())
.sum();
if weight_bytes == 0 {
return Ok(());
}
let needed = weight_bytes + weight_bytes / 5;
if needed <= free_bytes {
return Ok(());
}
let gb = |b: u64| b as f64 / 1_073_741_824.0;
let name = model_type.cli_name();
Err(anyhow::anyhow!(
"{name} needs about {:.1} GB of VRAM ({:.1} GB of weights plus working \
memory) and only {:.1} GB is free.\n\n\
The quantized build is roughly a quarter the size:\n \
kjarni model download {name} --gguf\n\n\
Or run on the CPU by dropping --gpu.",
gb(needed),
gb(weight_bytes),
gb(free_bytes)
))
}
fn free_vram_bytes() -> Option<u64> {
let out = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=memory.free", "--format=csv,noheader,nounits"])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let mib: u64 = String::from_utf8_lossy(&out.stdout)
.lines()
.next()?
.trim()
.parse()
.ok()?;
Some(mib * 1_048_576)
}