use std::collections::{BTreeMap, HashMap};
use std::sync::{Mutex, OnceLock};
#[derive(Default)]
struct Seen {
reported: Option<String>,
on_events: Option<String>,
}
fn seen() -> &'static Mutex<HashMap<String, Seen>> {
static SEEN: OnceLock<Mutex<HashMap<String, Seen>>> = OnceLock::new();
SEEN.get_or_init(Mutex::default)
}
fn installed() -> &'static Mutex<BTreeMap<String, String>> {
static INSTALLED: OnceLock<Mutex<BTreeMap<String, String>>> = OnceLock::new();
INSTALLED.get_or_init(Mutex::default)
}
pub fn refresh_installed() {
if cfg!(test) {
return;
}
let found: BTreeMap<String, String> = crate::hooks::detect_agents()
.iter()
.flat_map(|agent| agent.binding.installed_versions())
.filter(|(key, version)| !key.is_empty() && looks_like_version(version))
.collect();
tracing::info!(versions = ?found, "installed agent versions read");
set_installed(found);
}
#[cfg(test)]
pub(crate) fn set_installed_for_tests(key: &str, version: &str) {
installed()
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(key.to_string(), version.to_string());
}
fn set_installed(found: BTreeMap<String, String>) {
*installed().lock().unwrap_or_else(|e| e.into_inner()) = found;
}
pub(crate) fn looks_like_version(value: &str) -> bool {
!value.is_empty()
&& value.len() <= 64
&& value.starts_with(|c: char| c.is_ascii_digit())
&& value
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '+'))
}
pub fn record_reported(agent: &str, version: &str) {
record(agent, version, |s| &mut s.reported);
}
pub fn record_on_events(agent: &str, version: &str) {
record(agent, version, |s| &mut s.on_events);
}
fn record(agent: &str, version: &str, slot: impl FnOnce(&mut Seen) -> &mut Option<String>) {
if agent.is_empty() || !looks_like_version(version) {
return;
}
let mut seen = seen().lock().unwrap_or_else(|e| e.into_inner());
*slot(seen.entry(agent.to_string()).or_default()) = Some(version.to_string());
}
pub fn latest(agent: &str) -> Option<String> {
let ran = {
let seen = seen().lock().unwrap_or_else(|e| e.into_inner());
seen.get(agent)
.and_then(|s| s.reported.clone().or_else(|| s.on_events.clone()))
};
ran.or_else(|| {
installed()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(agent)
.cloned()
})
}
pub fn all() -> BTreeMap<String, String> {
let mut all = installed()
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone();
let seen = seen().lock().unwrap_or_else(|e| e.into_inner());
for (agent, s) in seen.iter() {
if let Some(version) = s.reported.clone().or_else(|| s.on_events.clone()) {
all.insert(agent.clone(), version);
}
}
all
}
pub fn from_codex_user_agent(user_agent: &str) -> Option<&str> {
let product = user_agent.split_whitespace().next()?;
let (_, version) = product.split_once('/')?;
looks_like_version(version).then_some(version)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_reported_version_wins_over_one_seen_on_events() {
assert_eq!(latest("test-agent-a"), None);
record_on_events("test-agent-a", "0.129.0");
assert_eq!(latest("test-agent-a").as_deref(), Some("0.129.0"));
record_reported("test-agent-a", "0.130.0");
record_on_events("test-agent-a", "0.131.0");
assert_eq!(latest("test-agent-a").as_deref(), Some("0.130.0"));
}
#[test]
fn all_lists_every_known_agent_by_the_same_precedence() {
record_on_events("test-agent-c", "1.0.0");
record_reported("test-agent-c", "1.0.1");
record_on_events("test-agent-d", "2.0.0");
let all = all();
assert_eq!(all.get("test-agent-c").map(String::as_str), Some("1.0.1"));
assert_eq!(all.get("test-agent-d").map(String::as_str), Some("2.0.0"));
assert!(!all.contains_key("test-agent-never-seen"));
}
#[test]
fn installs_fill_in_and_keep_their_own_keys() {
set_installed_for_tests("test-agent-e", "3.0.0");
set_installed_for_tests("test-agent-e:cli", "2.9.0");
assert_eq!(latest("test-agent-e").as_deref(), Some("3.0.0"));
record_on_events("test-agent-e", "3.0.1");
assert_eq!(
latest("test-agent-e").as_deref(),
Some("3.0.1"),
"what ran wins"
);
let all = all();
assert_eq!(all.get("test-agent-e").map(String::as_str), Some("3.0.1"));
assert_eq!(
all.get("test-agent-e:cli").map(String::as_str),
Some("2.9.0")
);
}
#[test]
fn what_is_not_a_version_is_not_recorded() {
for bad in ["", "v", "latest", "0.1 rm", "/etc/passwd", &"9".repeat(65)] {
record_on_events("test-agent-b", bad);
}
record_reported("", "0.130.0");
assert_eq!(latest("test-agent-b"), None);
assert!(looks_like_version("0.164.0-alpha.6"));
}
#[test]
fn the_codex_user_agent_names_its_version() {
assert_eq!(
from_codex_user_agent(
"openlatch/0.162.1 (Mac OS 26.6.2; arm64) iTerm.app/3.7.6 (openlatch; 0.6.23)"
),
Some("0.162.1")
);
assert_eq!(
from_codex_user_agent("openlatch/0.130.0 (Windows 11; x86_64)"),
Some("0.130.0")
);
assert_eq!(from_codex_user_agent("no-slash-here"), None);
assert_eq!(from_codex_user_agent(""), None);
}
}