use std::collections::BTreeMap;
use argonauts_core::app::AppEvent;
use argonauts_core::display_names::DisplayNames;
use argonauts_core::names::ModelRef;
use super::Ctx;
#[derive(Debug, Default)]
pub(crate) struct NamesSent(Option<DisplayNames>);
impl NamesSent {
pub(crate) fn worker(&self, worker: &ModelRef) -> String {
match &self.0 {
Some(names) => names.worker(worker),
None => DisplayNames::default().worker(worker),
}
}
pub(crate) fn running(&self, worker: &ModelRef) -> ModelRef {
match &self.0 {
Some(names) => names.running(worker),
None => worker.clone(),
}
}
pub(crate) fn cli(&self, id: &str) -> String {
match &self.0 {
Some(names) => names.cli(id),
None => DisplayNames::default().cli(id),
}
}
pub(crate) fn names(&self) -> DisplayNames {
self.0.clone().unwrap_or_default()
}
pub(crate) fn model(&self, cli: &str, model: &str) -> Option<String> {
match &self.0 {
Some(names) => names.model(cli, Some(model)),
None => DisplayNames::default().model(cli, Some(model)),
}
}
}
pub(crate) fn send(ctx: &mut Ctx) {
let names = names(ctx);
if ctx.names.0.as_ref() == Some(&names) {
return;
}
ctx.send(AppEvent::Names {
names: Box::new(names.clone()),
});
ctx.names.0 = Some(names);
}
fn names(ctx: &Ctx) -> DisplayNames {
let clis = crate::commands::adapters_here(Some(&ctx.env), ctx.settings.resolved())
.map(|adapters| {
adapters
.iter()
.map(|adapter| (adapter.id().to_owned(), adapter.display_name().to_owned()))
.collect()
})
.unwrap_or_else(|_| BTreeMap::new());
let configured: BTreeMap<String, String> = ctx
.settings()
.agents
.iter()
.filter_map(|(id, agent)| Some((id.clone(), agent.model.clone()?)))
.collect();
let defaults = cli_defaults(ctx, clis.keys().map(String::as_str));
DisplayNames {
clis,
catalog: ctx.services.registry.catalog(),
configured,
defaults,
}
}
pub(crate) fn cli_defaults<'a>(
ctx: &Ctx,
clis: impl IntoIterator<Item = &'a str>,
) -> BTreeMap<String, String> {
let registry = &ctx.services.registry;
let catalog = registry.catalog();
let listed = registry.list();
clis.into_iter()
.filter_map(|cli| {
let marked = listed
.iter()
.find(|entry| entry.cli_default && entry.id.agent == cli)
.and_then(|entry| entry.id.model.clone());
let id = ModelRef::new(cli, None);
let model = marked.or_else(|| catalog.entry(&id)?.runs.clone())?;
Some((cli.to_owned(), model))
})
.collect()
}
#[cfg(test)]
mod tests {
use argonauts_core::app::AppEvent;
use argonauts_core::names::ModelRef;
use crate::controller::test_support::TestDriver;
fn worker(text: &str) -> ModelRef {
text.parse().unwrap()
}
#[tokio::test]
async fn the_screen_learns_the_names_first() {
let mut driver = TestDriver::start().await;
driver
.until(|event| matches!(event, AppEvent::Status { .. }))
.await;
let AppEvent::Names { names } = &driver.seen()[0] else {
panic!("{:#?}", driver.seen());
};
let sets = driver
.seen()
.iter()
.filter(|event| matches!(event, AppEvent::Names { .. }))
.count();
assert_eq!(sets, 1, "an unchanged set goes out once");
assert_eq!(names.clis.get("codex").map(String::as_str), Some("Codex"));
assert_eq!(
names.worker(&worker("claude-code/opus")),
"Opus 5.5 on Claude Code"
);
assert_eq!(names.worker(&worker("antigravity-cli")), "agy");
driver.exit().await;
}
#[tokio::test]
async fn a_cli_default_session_is_named_by_the_default() {
let mut driver = TestDriver::start().await;
driver
.until(|event| matches!(event, AppEvent::Status { .. }))
.await;
let AppEvent::Names { names } = &driver.seen()[0] else {
panic!("{:#?}", driver.seen());
};
assert_eq!(
names.worker(&names.running(&worker("claude-code"))),
"Opus 5.5 on Claude Code"
);
assert_eq!(
names.worker(&names.running(&worker("claude-code/claude-haiku-4-5"))),
"Haiku 4.5 on Claude Code"
);
driver.exit().await;
}
}