use std::sync::Arc;
use argonauts_adapter_antigravity_cli::AntigravityCli;
use argonauts_adapter_claude_code::ClaudeCodeAdapter;
use argonauts_adapter_codex::CodexAdapter;
use argonauts_adapter_opencode::{LocalUrls, OpenCodeAdapter};
use argonauts_conductor::doctor::resolve_binary;
use argonauts_core::Adapter;
use argonauts_core::runtimes::RUNTIMES;
use argonauts_core::settings::Settings;
use argonauts_registry::models::endpoint_settings;
use crate::host::clis::RepoModels;
pub fn builtin_adapters() -> Vec<Arc<dyn Adapter>> {
vec![
Arc::new(ClaudeCodeAdapter::new()),
Arc::new(CodexAdapter::new()),
Arc::new(AntigravityCli::new()),
Arc::new(OpenCodeAdapter::new()),
]
}
pub(crate) fn builtin_adapters_for(
settings: &Settings,
repo: Option<&RepoModels>,
) -> Vec<Arc<dyn Adapter>> {
builtin_adapters()
.into_iter()
.map(|adapter| match adapter.id() {
"opencode" => Arc::new(opencode(settings, repo)) as Arc<dyn Adapter>,
_ => adapter,
})
.collect()
}
pub(crate) fn opencode(settings: &Settings, repo: Option<&RepoModels>) -> OpenCodeAdapter {
let adapter = OpenCodeAdapter::with_local_urls(opencode_local_urls(settings));
let configured = settings
.agents
.get(adapter.id())
.and_then(|agent| agent.binary.as_deref());
match resolve_binary(&adapter, configured) {
Ok(program) => adapter.with_program(program, crate::confine::read_only(settings, repo)),
Err(_) => adapter,
}
}
pub(crate) fn opencode_local_urls(settings: &Settings) -> LocalUrls {
RUNTIMES
.iter()
.map(|runtime| runtime.id)
.fold(LocalUrls::new(), |urls, id| {
match endpoint_settings(settings, id).and_then(|endpoint| endpoint.base_url) {
Some(base_url) => urls.with(id, base_url),
None => urls,
}
})
}
#[cfg(test)]
mod tests {
use argonauts_core::cheapest::{cheapest_listed, price_of};
use argonauts_core::registry::{ModelRegistry, default_run};
use argonauts_core::settings::Settings;
use argonauts_registry::models::discovery::{parse_agy_models, parse_codex_models};
use argonauts_registry::models::{FileRegistry, ModelCache};
use super::builtin_adapters;
fn listed(home: &std::path::Path) -> FileRegistry {
let fixtures = concat!(env!("CARGO_MANIFEST_DIR"), "/../registry/tests/fixtures");
let read = |name: &str| std::fs::read_to_string(format!("{fixtures}/{name}")).unwrap();
let mut models = parse_agy_models(&read("agy/models.json")).unwrap();
models.extend(parse_codex_models(&read("codex/debug-models.json")).unwrap());
let cache = home.join("models.json");
ModelCache {
refreshed: time::OffsetDateTime::now_utc(),
models,
skipped: Vec::new(),
claude_listed: None,
catalog: None,
sources: Vec::new(),
}
.write(&cache)
.unwrap();
FileRegistry::from_files(&Settings::default(), None, None).with_cache(&cache)
}
#[test]
fn each_cli_names_the_cheapest_model_its_list_offers() {
let home = tempfile::tempdir().unwrap();
let registry = listed(home.path());
let catalog = registry.catalog();
for adapter in builtin_adapters() {
let cli = adapter.id();
if cli == "opencode" {
assert!(cheapest_listed(®istry, cli).is_none());
continue;
}
let (model, effort) = adapter.cheapest_model();
let runs = catalog
.runs(&argonauts_core::names::ModelRef::new(
cli,
Some(model.to_owned()),
))
.unwrap_or_else(|| model.to_owned());
assert!(
price_of(&catalog, &runs).is_some(),
"the catalog has no price for {model}, {cli}'s own pick"
);
let cheapest = cheapest_listed(®istry, cli)
.unwrap_or_else(|| panic!("the catalog prices none of the models {cli} lists"));
assert_eq!(
(cheapest.model.as_str(), cheapest.effort.as_deref()),
(model, effort),
"{cli}'s list offers {} cheaper than {model}, its own pick",
cheapest.model,
);
}
}
#[test]
fn a_cli_named_alone_runs_the_default_its_list_gives() {
let home = tempfile::tempdir().unwrap();
let registry = listed(home.path());
assert_eq!(
default_run(®istry, "antigravity-cli").as_deref(),
Some("gemini-3.8-flash-high")
);
assert_eq!(
default_run(®istry, "codex").as_deref(),
Some("gpt-6-astra")
);
assert_eq!(
default_run(®istry, "claude-code").as_deref(),
Some("claude-opus-5-5")
);
}
}