use crate::adapters::{json_top_level_keys, HealthFuture};
use crate::health::{base_health, run_status_probe, HealthStatus};
use crate::types::{AdapterId, AuthKind, Capabilities};
use serde_json::json;
use std::path::Path;
pub(super) const CAPABILITIES: Capabilities = Capabilities {
tool_use: true,
mcp: false,
hooks: false,
sessions: true,
streaming: true,
};
pub(super) fn parse_version(stdout: &str) -> Option<String> {
crate::adapters::first_versionish_token(stdout)
}
pub(super) fn probe_auth(home: &Path) -> AuthKind {
let dir = home.join(".codex");
let path = dir.join("auth.json");
if !path.exists() {
return if dir.is_dir() {
AuthKind::Unknown
} else {
AuthKind::Unauthenticated
};
}
let Some(keys) = json_top_level_keys(&path) else {
return AuthKind::Unknown;
};
let lower: Vec<String> = keys.iter().map(|k| k.to_ascii_lowercase()).collect();
let has_oauth = lower
.iter()
.any(|k| k.contains("token") || k.contains("chatgpt") || k.contains("session"));
let has_api_key = lower
.iter()
.any(|k| k.contains("openai_api_key") || k.contains("api_key") || k.contains("apikey"));
match (has_oauth, has_api_key) {
(true, _) => AuthKind::Subscription,
(false, true) => AuthKind::ApiKey,
(false, false) => AuthKind::Unknown,
}
}
pub(super) fn health_check(binary_path: &Path) -> HealthFuture {
let bin = binary_path.to_path_buf();
Box::pin(async move {
let mut h = base_health(AdapterId::Codex);
match run_status_probe(&bin, &["login", "status"]).await {
Ok((stdout, stderr, code)) => {
let combined = format!("{}\n{}", stdout.trim(), stderr.trim());
let lower = combined.to_ascii_lowercase();
if code == 0 && lower.contains("logged in") && !lower.contains("not logged in") {
h.status = HealthStatus::Ready;
let method = if lower.contains("chatgpt") {
"ChatGPT"
} else if lower.contains("api key") {
"API key"
} else {
"Unknown"
};
h.details = json!({ "method": method, "raw": combined.trim() });
} else if lower.contains("not logged in") || lower.contains("logged out") {
h.status = HealthStatus::NotConfigured;
h.details = json!({ "raw": combined.trim() });
h.reason = Some("Run `codex login` to authenticate".to_string());
} else {
h.status = HealthStatus::Unknown;
h.reason = Some(format!(
"`codex login status` exited {code}: {}",
combined.trim()
));
}
}
Err(e) => {
h.status = HealthStatus::NetworkError;
h.reason = Some(format!("status probe failed: {e}"));
}
}
h
})
}