use std::path::Path;
use crate::agent::AgentId;
use crate::error::Result;
pub mod claude;
pub mod codex;
pub use claude::ClaudeConfig;
pub use codex::CodexConfig;
#[derive(Debug, Clone)]
pub enum Runtime {
Claude(ClaudeConfig),
Codex(CodexConfig),
}
impl Runtime {
pub fn name(&self) -> &'static str {
match self {
Self::Claude(_) => "claude",
Self::Codex(_) => "codex",
}
}
pub fn required_deps(&self) -> Vec<&'static str> {
match self {
Self::Claude(_) => claude::required_deps(),
Self::Codex(_) => codex::required_deps(),
}
}
pub fn build_command(&self, agent: AgentId, mcp_config: &Path, startup: &str) -> String {
match self {
Self::Claude(cfg) => claude::build_command(agent, cfg, mcp_config, startup),
Self::Codex(cfg) => codex::build_command(agent, cfg, mcp_config, startup),
}
}
pub fn post_spawn(&self, session: &str, startup: &str) -> Result<()> {
match self {
Self::Claude(_) => Ok(()),
Self::Codex(_) => codex::post_spawn(session, startup),
}
}
pub fn defaults_for(agent: AgentId) -> Self {
match std::env::var("AGENT_RUNTIME").ok().as_deref() {
Some("codex") => Self::Codex(CodexConfig::defaults_for()),
_ => Self::Claude(ClaudeConfig::defaults_for(agent)),
}
}
pub fn describe(&self) -> String {
match self {
Self::Claude(cfg) => format!("claude model={} effort={}", cfg.model, cfg.effort),
Self::Codex(cfg) => format!(
"codex model={} sandbox={} approval={}",
cfg.model, cfg.sandbox, cfg.approval
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn claude_post_spawn_is_noop_without_tmux() {
let rt = Runtime::Claude(ClaudeConfig {
model: "opus".to_string(),
effort: "high".to_string(),
});
rt.post_spawn("nonexistent-session-xyz", "/up")
.expect("claude post_spawn must be a no-op");
}
}