use std::io::Write;
use crate::canonical::{CanonicalError, ErrorKind, Model};
use crate::config::{
config_path, defaults, partial_from_env, read_config_file, OutMode, ResolvedConfig,
};
use crate::store::{Clock, CredStore, ModelCache};
use crate::transport::Transport;
mod fetch;
use fetch::fetch_models;
#[cfg(test)]
pub(crate) use fetch::models_req;
pub struct ListIo<'a> {
pub stdout: &'a mut dyn Write,
pub stderr: &'a mut dyn Write,
pub transport: &'a dyn Transport,
pub store: &'a dyn CredStore,
pub cache: &'a dyn ModelCache,
pub clock: &'a dyn Clock,
}
pub fn list_models(args: &crate::cli::Args, io: &mut ListIo) -> u8 {
match run_list(args, io) {
Ok(code) => code,
Err(e) => {
let _ = writeln!(io.stderr, "{}", e.message);
e.exit_code()
}
}
}
fn run_list(args: &crate::cli::Args, io: &mut ListIo) -> Result<u8, CanonicalError> {
let flags = crate::cli::parse_args(&args.argv)?;
if flags.help {
return Ok(super::emit(io.stdout, super::HELP));
}
if flags.skill {
return Ok(super::emit(io.stdout, super::SKILL));
}
if flags.version {
return Ok(super::emit(io.stdout, super::VERSION_LINE));
}
let file = read_config_file(&config_path(flags.config_path, &args.env))?;
let env = partial_from_env(&args.env).map_err(CanonicalError::from)?;
let merged = flags.config.or(env).or(file).or(defaults());
let cfg: ResolvedConfig = merged
.into_resolved(None, Some(io.cache))
.map_err(CanonicalError::from)?;
let json = cfg.output == OutMode::Ndjson;
let models = fetch_models(&cfg, io.transport, io.store, io.clock)?;
let prior = io.cache.get(&cfg.provider.name).unwrap_or_default();
io.cache
.put(&cfg.provider.name, &prior.relist(models.clone()));
print_models(io.stdout, &models, json).map_err(write_failed)?;
if models.is_empty() {
let _ = writeln!(io.stderr, "no models returned for `{}`", cfg.provider.name);
}
Ok(0)
}
fn print_models(out: &mut dyn Write, models: &[Model], json: bool) -> std::io::Result<()> {
if json {
let obj = serde_json::json!({ "models": models });
writeln!(out, "{obj}")
} else {
for m in models {
let suffix = if m.default { " (default)" } else { "" };
writeln!(out, "{}{suffix}", m.id)?;
}
Ok(())
}
}
fn write_failed(e: std::io::Error) -> CanonicalError {
CanonicalError {
kind: ErrorKind::Transport,
message: format!("failed to write model list: {e}"),
provider_detail: None,
retry_after_seconds: None,
}
}
#[cfg(test)]
mod tests {
use super::print_models;
use crate::canonical::Model;
#[test]
fn text_suffixes_the_default_flagged_id() {
let models = [
Model {
id: "fast".into(),
default: false,
..Default::default()
},
Model {
id: "smart".into(),
default: true,
..Default::default()
},
];
let mut out = Vec::new();
print_models(&mut out, &models, false).unwrap();
assert_eq!(String::from_utf8(out).unwrap(), "fast\nsmart (default)\n");
}
}