use crate::health::ExternalAgentHealth;
use crate::types::{AdapterId, AuthKind, Capabilities};
use std::future::Future;
use std::path::{Path, PathBuf};
use std::pin::Pin;
mod claude_code;
mod codex;
mod gemini;
pub type HealthFuture = Pin<Box<dyn Future<Output = ExternalAgentHealth> + Send>>;
pub struct Adapter {
pub id: AdapterId,
pub bin_name: &'static str,
pub capabilities: Capabilities,
pub parse_version: fn(&str) -> Option<String>,
pub probe_auth: fn(home: &Path) -> AuthKind,
pub health_check: fn(binary_path: &Path) -> HealthFuture,
}
pub fn all() -> &'static [Adapter] {
&ADAPTERS
}
const ADAPTERS: [Adapter; 3] = [
Adapter {
id: AdapterId::ClaudeCode,
bin_name: "claude",
capabilities: claude_code::CAPABILITIES,
parse_version: claude_code::parse_version,
probe_auth: claude_code::probe_auth,
health_check: claude_code::health_check,
},
Adapter {
id: AdapterId::Codex,
bin_name: "codex",
capabilities: codex::CAPABILITIES,
parse_version: codex::parse_version,
probe_auth: codex::probe_auth,
health_check: codex::health_check,
},
Adapter {
id: AdapterId::Gemini,
bin_name: "gemini",
capabilities: gemini::CAPABILITIES,
parse_version: gemini::parse_version,
probe_auth: gemini::probe_auth,
health_check: gemini::health_check,
},
];
pub(crate) fn json_top_level_keys(path: &PathBuf) -> Option<Vec<String>> {
let bytes = std::fs::read(path).ok()?;
let value: serde_json::Value = serde_json::from_slice(&bytes).ok()?;
let map = value.as_object()?;
Some(map.keys().cloned().collect())
}
pub(crate) fn first_versionish_token(stdout: &str) -> Option<String> {
for token in stdout.split_whitespace() {
if token
.chars()
.next()
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
return Some(
token
.trim_end_matches(|c: char| !c.is_ascii_alphanumeric() && c != '.' && c != '-')
.to_string(),
);
}
}
None
}