#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum AdapterId {
ClaudeCode,
CodexCli,
OpenCode,
Cursor,
Cline,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum Capability {
Skills,
Memory,
Mcp,
OutputStyle,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct Detection {
pub installed: bool,
pub config_root: Option<std::path::PathBuf>,
#[serde(default)]
pub project_root: Option<std::path::PathBuf>,
pub version: Option<String>,
}
impl Detection {
pub fn new(
installed: bool,
config_root: Option<std::path::PathBuf>,
version: Option<String>,
) -> Self {
Self {
installed,
config_root,
project_root: None,
version,
}
}
pub fn with_project_root(
installed: bool,
config_root: Option<std::path::PathBuf>,
project_root: Option<std::path::PathBuf>,
version: Option<String>,
) -> Self {
Self {
installed,
config_root,
project_root,
version,
}
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AdapterError {
#[error("adapter id {0:?} is not supported in this build")]
Unsupported(AdapterId),
#[error("config root unreadable: {0}")]
ConfigRootUnreadable(std::path::PathBuf),
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("serialization: {0}")]
Serde(#[from] serde_json::Error),
}
#[async_trait::async_trait]
pub trait Adapter: Send + Sync {
fn id(&self) -> AdapterId;
fn supports(&self, _cap: Capability) -> bool {
false
}
async fn detect(&self) -> Result<Detection, AdapterError>;
async fn install_command(&self) -> Result<String, AdapterError>;
}
pub const DEFAULT_ADAPTER_IDS: &[AdapterId] = &[];
pub fn new_adapter(id: AdapterId) -> Result<Arc<dyn Adapter>, AdapterError> {
match id {
AdapterId::ClaudeCode
| AdapterId::CodexCli
| AdapterId::OpenCode
| AdapterId::Cursor
| AdapterId::Cline => Err(AdapterError::Unsupported(id)),
}
}
#[derive(Default)]
pub struct AdapterRegistry {
inner: std::collections::HashMap<AdapterId, Arc<dyn Adapter>>,
}
impl AdapterRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, adapter: Arc<dyn Adapter>) -> Option<Arc<dyn Adapter>> {
self.inner.insert(adapter.id(), adapter)
}
pub fn get(&self, id: AdapterId) -> Option<Arc<dyn Adapter>> {
self.inner.get(&id).cloned()
}
}
pub fn default_registry() -> Result<AdapterRegistry, AdapterError> {
let mut registry = AdapterRegistry::new();
for id in DEFAULT_ADAPTER_IDS {
let _ = registry.register(new_adapter(*id)?);
}
Ok(registry)
}