#![allow(dead_code)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdapterCapabilities {
pub discover_sessions: bool,
pub monitor_state: bool,
pub send_input: bool,
pub deliver_interrupt: bool,
pub request_checkpoint: bool,
pub request_compaction: bool,
pub pause: bool,
pub resume: bool,
pub terminate: bool,
}
impl AdapterCapabilities {
pub fn full() -> Self {
Self {
discover_sessions: true,
monitor_state: true,
send_input: true,
deliver_interrupt: true,
request_checkpoint: false,
request_compaction: false,
pause: true,
resume: true,
terminate: true,
}
}
pub fn minimal() -> Self {
Self {
discover_sessions: true,
monitor_state: false,
send_input: false,
deliver_interrupt: false,
request_checkpoint: false,
request_compaction: false,
pause: false,
resume: false,
terminate: false,
}
}
pub fn count(&self) -> usize {
[
self.discover_sessions,
self.monitor_state,
self.send_input,
self.deliver_interrupt,
self.request_checkpoint,
self.request_compaction,
self.pause,
self.resume,
self.terminate,
]
.iter()
.filter(|&&v| v)
.count()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentIdentity {
pub agent_family: String,
pub session_id: String,
pub cwd: String,
pub branch: Option<String>,
pub pid: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentState {
pub status: String,
pub context_pressure: Option<f64>,
pub pending_tool: Option<String>,
pub last_output: Option<String>,
pub cost_usd: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AgentFamily {
ClaudeCode,
Codex,
}
impl AgentFamily {
pub fn as_str(&self) -> &'static str {
match self {
Self::ClaudeCode => "claude-code",
Self::Codex => "codex",
}
}
pub fn all() -> &'static [AgentFamily] {
&[AgentFamily::ClaudeCode, AgentFamily::Codex]
}
}
impl std::fmt::Display for AgentFamily {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
pub trait AgentAdapter {
fn family(&self) -> AgentFamily;
fn capabilities(&self) -> AdapterCapabilities;
fn discover_sessions(&self) -> Vec<AgentIdentity>;
fn get_state(&self, session_id: &str) -> Option<AgentState>;
fn send_input(&self, session_id: &str, text: &str) -> Result<(), String>;
fn pause(&self, session_id: &str) -> Result<(), String> {
let _ = session_id;
Err("not supported".into())
}
fn resume(&self, session_id: &str) -> Result<(), String> {
let _ = session_id;
Err("not supported".into())
}
fn terminate(&self, session_id: &str) -> Result<(), String> {
let _ = session_id;
Err("not supported".into())
}
}
pub fn all_adapters() -> Vec<Box<dyn AgentAdapter>> {
vec![
Box::new(super::adapter_claude::ClaudeCodeAdapter),
Box::new(super::adapter_codex::CodexAdapter),
]
}
pub fn get_adapter(family: &str) -> Option<Box<dyn AgentAdapter>> {
match family {
"claude-code" => Some(Box::new(super::adapter_claude::ClaudeCodeAdapter)),
"codex" => Some(Box::new(super::adapter_codex::CodexAdapter)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capabilities_full_count() {
let caps = AdapterCapabilities::full();
assert_eq!(caps.count(), 7);
}
#[test]
fn capabilities_minimal_count() {
let caps = AdapterCapabilities::minimal();
assert_eq!(caps.count(), 1);
}
#[test]
fn agent_family_display() {
assert_eq!(AgentFamily::ClaudeCode.to_string(), "claude-code");
assert_eq!(AgentFamily::Codex.to_string(), "codex");
}
#[test]
fn all_adapters_registered() {
let adapters = all_adapters();
assert_eq!(adapters.len(), 2);
assert_eq!(adapters[0].family(), AgentFamily::ClaudeCode);
assert_eq!(adapters[1].family(), AgentFamily::Codex);
}
#[test]
fn get_adapter_by_name() {
assert!(get_adapter("claude-code").is_some());
assert!(get_adapter("codex").is_some());
assert!(get_adapter("unknown").is_none());
}
}