pub mod claude_adapter;
pub mod codex_adapter;
pub mod compat_matrix;
pub mod error_propagator;
pub mod executor_version;
pub mod opencode_adapter;
use crate::errors::AppError;
use async_trait::async_trait;
use executor_version::ExecutorVersion;
use std::collections::BTreeMap;
use std::process::Stdio;
#[derive(Debug, Clone)]
pub struct ParsedOutput {
pub items: Vec<serde_json::Value>,
pub raw_stdout: String,
pub raw_stderr: String,
pub exit_code: i32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecutorCapabilities {
pub supports_mcp_map: bool,
pub supports_ask_for_approval_flag: bool,
pub supports_strict_schema: bool,
pub default_flags: Vec<String>,
pub removed_flags: Vec<String>,
}
impl ExecutorCapabilities {
pub fn empty() -> Self {
Self {
supports_mcp_map: false,
supports_ask_for_approval_flag: false,
supports_strict_schema: false,
default_flags: Vec::new(),
removed_flags: Vec::new(),
}
}
}
#[async_trait]
pub trait VersionAdapter: Send + Sync {
fn name(&self) -> &'static str;
async fn detect(&self) -> Result<ExecutorVersion, AppError>;
fn capabilities_for(&self, version: &ExecutorVersion) -> ExecutorCapabilities;
fn build_args(
&self,
prompt: &str,
caps: &ExecutorCapabilities,
compat_mode: CompatMode,
) -> Vec<String>;
fn parse_output(&self, raw_stdout: &str, raw_stderr: &str, exit_code: i32) -> ParsedOutput;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompatMode {
Strict,
Lenient,
Auto,
}
impl CompatMode {
pub fn parse(s: &str) -> Self {
match s.to_ascii_lowercase().as_str() {
"strict" => Self::Strict,
"lenient" => Self::Lenient,
_ => Self::Auto,
}
}
}
#[derive(Debug, Default)]
pub struct VersionCache {
inner: std::sync::Mutex<BTreeMap<String, ExecutorVersion>>,
}
impl VersionCache {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, name: &str) -> Option<ExecutorVersion> {
self.inner.lock().ok().and_then(|m| m.get(name).cloned())
}
pub fn put(&self, name: &str, version: ExecutorVersion) {
if let Ok(mut m) = self.inner.lock() {
m.insert(name.to_string(), version);
}
}
pub fn clear(&self) {
if let Ok(mut m) = self.inner.lock() {
m.clear();
}
}
}
static VERSION_CACHE: std::sync::OnceLock<VersionCache> = std::sync::OnceLock::new();
pub fn global_version_cache() -> &'static VersionCache {
VERSION_CACHE.get_or_init(VersionCache::new)
}
pub fn base_command(binary: &str) -> std::process::Command {
let mut cmd = std::process::Command::new(binary);
cmd.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
cmd
}