Skip to main content

ito_core/harness/
opencode.rs

1use super::streaming_cli::CliHarness;
2use super::types::{HarnessName, HarnessRunConfig};
3
4/// Harness implementation that executes the `opencode` CLI (`opencode run`).
5///
6/// Selected via `ito ralph --harness opencode`; requires the OpenCode CLI on PATH.
7///
8/// # Examples
9///
10/// ```
11/// use ito_core::harness::{Harness, HarnessName, OpencodeHarness};
12///
13/// let h = OpencodeHarness;
14/// assert_eq!(h.name(), HarnessName::Opencode);
15/// assert!(h.streams_output());
16/// ```
17#[derive(Debug, Default)]
18pub struct OpencodeHarness;
19
20impl CliHarness for OpencodeHarness {
21    fn harness_name(&self) -> HarnessName {
22        HarnessName::Opencode
23    }
24
25    fn binary(&self) -> &str {
26        "opencode"
27    }
28
29    fn build_args(&self, config: &HarnessRunConfig) -> Vec<String> {
30        let mut args = vec!["run".to_string()];
31        if let Some(model) = config.model.as_deref() {
32            args.push("-m".to_string());
33            args.push(model.to_string());
34        }
35        args.push(config.prompt.clone());
36        args
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use std::collections::BTreeMap;
44
45    enum Allow {
46        #[allow(dead_code)]
47        All,
48        None,
49    }
50
51    fn config(allow: Allow, model: Option<&str>) -> HarnessRunConfig {
52        let allow_all = match allow {
53            Allow::All => true,
54            Allow::None => false,
55        };
56        HarnessRunConfig {
57            prompt: "do stuff".to_string(),
58            model: model.map(String::from),
59            cwd: std::env::temp_dir(),
60            env: BTreeMap::new(),
61            interactive: false,
62            allow_all,
63            inactivity_timeout: None,
64        }
65    }
66
67    #[test]
68    fn harness_name_is_opencode() {
69        let harness = OpencodeHarness;
70        assert_eq!(harness.harness_name(), HarnessName::Opencode);
71    }
72
73    #[test]
74    fn binary_is_opencode() {
75        let harness = OpencodeHarness;
76        assert_eq!(harness.binary(), "opencode");
77    }
78
79    #[test]
80    fn build_args_with_model() {
81        let harness = OpencodeHarness;
82        let cfg = config(Allow::None, Some("gpt-4"));
83        let args = harness.build_args(&cfg);
84        assert_eq!(args, vec!["run", "-m", "gpt-4", "do stuff"]);
85    }
86
87    #[test]
88    fn build_args_without_model() {
89        let harness = OpencodeHarness;
90        let cfg = config(Allow::None, None);
91        let args = harness.build_args(&cfg);
92        assert_eq!(args, vec!["run", "do stuff"]);
93    }
94}