Skip to main content

hivemind/adapters/
claude_code.rs

1//! Claude Code adapter - wrapper for Claude Code CLI runtime.
2
3use super::opencode::{OpenCodeAdapter, OpenCodeConfig};
4use super::runtime::{
5    AdapterConfig, ExecutionInput, ExecutionReport, InteractiveAdapterEvent,
6    InteractiveExecutionResult, RuntimeAdapter, RuntimeError,
7};
8use std::path::PathBuf;
9use std::time::Duration;
10use uuid::Uuid;
11
12/// Claude Code adapter configuration.
13#[derive(Debug, Clone)]
14pub struct ClaudeCodeConfig {
15    /// Base adapter config.
16    pub base: AdapterConfig,
17    /// Optional model identifier.
18    pub model: Option<String>,
19}
20
21impl ClaudeCodeConfig {
22    /// Creates a new Claude Code config.
23    pub fn new(binary_path: PathBuf) -> Self {
24        Self {
25            base: AdapterConfig::new("claude-code", binary_path)
26                .with_timeout(Duration::from_secs(600)),
27            model: None,
28        }
29    }
30
31    /// Sets the model.
32    #[must_use]
33    pub fn with_model(mut self, model: impl Into<String>) -> Self {
34        self.model = Some(model.into());
35        self
36    }
37}
38
39impl Default for ClaudeCodeConfig {
40    fn default() -> Self {
41        let mut cfg = Self::new(PathBuf::from("claude"));
42        cfg.base.args = vec!["-p".to_string()];
43        cfg
44    }
45}
46
47/// Claude Code runtime adapter.
48pub struct ClaudeCodeAdapter {
49    inner: OpenCodeAdapter,
50}
51
52impl ClaudeCodeAdapter {
53    /// Creates a new adapter.
54    pub fn new(config: ClaudeCodeConfig) -> Self {
55        let mut inner = OpenCodeConfig::new(config.base.binary_path.clone());
56        inner.base.name = "claude-code".to_string();
57        inner.base.args = config.base.args;
58        inner.base.env = config.base.env;
59        inner.base.timeout = config.base.timeout;
60        inner.model.clone_from(&config.model);
61
62        if let Some(model) = config.model {
63            let has_model_flag = inner
64                .base
65                .args
66                .iter()
67                .any(|a| a == "--model" || a.starts_with("--model="));
68            if !has_model_flag {
69                inner.base.args.extend(["--model".to_string(), model]);
70            }
71        }
72
73        Self {
74            inner: OpenCodeAdapter::new(inner),
75        }
76    }
77
78    /// Creates an adapter with defaults.
79    pub fn with_defaults() -> Self {
80        Self::new(ClaudeCodeConfig::default())
81    }
82
83    pub fn execute_interactive<F>(
84        &mut self,
85        input: &ExecutionInput,
86        on_event: F,
87    ) -> Result<InteractiveExecutionResult, RuntimeError>
88    where
89        F: FnMut(InteractiveAdapterEvent) -> std::result::Result<(), String>,
90    {
91        self.inner.execute_interactive(input, on_event)
92    }
93}
94
95impl RuntimeAdapter for ClaudeCodeAdapter {
96    fn name(&self) -> &str {
97        self.inner.name()
98    }
99
100    fn initialize(&mut self) -> Result<(), RuntimeError> {
101        self.inner.initialize()
102    }
103
104    fn prepare(&mut self, task_id: Uuid, worktree: &std::path::Path) -> Result<(), RuntimeError> {
105        self.inner.prepare(task_id, worktree)
106    }
107
108    fn execute(&mut self, input: ExecutionInput) -> Result<ExecutionReport, RuntimeError> {
109        self.inner.execute(input)
110    }
111
112    fn terminate(&mut self) -> Result<(), RuntimeError> {
113        self.inner.terminate()
114    }
115
116    fn config(&self) -> &AdapterConfig {
117        self.inner.config()
118    }
119}