Skip to main content

agent_harness_adapter_core/opencode/
adapter.rs

1//! OpenCode (`opencode-ai`).
2//!
3//! Follows `packages/web/src/content/docs/` of
4//! <https://github.com/sst/opencode> (`rules.mdx`, `config.mdx`,
5//! `skills.mdx`, `agents.mdx`, `commands.mdx`, `mcp-servers.mdx`,
6//! `permissions.mdx`, `plugins.mdx`) and its source (`session/instruction.ts`,
7//! `config/plugin.ts`, `session/prompt.ts`, `session/tools.ts`,
8//! `cli/cmd/run.ts`), version 1.18.35 (checked 2026-10-07).
9// @zen-component: HAR-OpenCode
10
11use crate::{
12    Result,
13    common::{extension, plugin},
14    harness::{Context, Harness, Part, PartResult, Reads, Scope},
15    hook::{Answer, Event, HookInput, Output, ToolCall, claude_names},
16    integration::{Integration, Item},
17};
18
19/// OpenCode (`opencode`).
20#[derive(Debug, Clone, Copy, Default)]
21pub struct OpenCode;
22
23const ID: &str = "opencode";
24
25/// OpenCode's paths: the project's `.opencode` and `opencode.json`, or
26/// `~/.config/opencode` and its `opencode.json`. The instructions file is
27/// the first of `AGENTS.md`, `CLAUDE.md` that exists
28/// (`.config/opencode/AGENTS.md`, `.claude/CLAUDE.md` at user scope), else
29/// the first; OpenCode loads only that one.
30// @zen-impl: HAR-10_AC-2
31const LAYOUT: plugin::Layout = plugin::Layout {
32    harness: ID,
33    agent: "OpenCode",
34    types: "@opencode-ai/plugin",
35    project_dir: ".opencode",
36    user_dir: ".config/opencode",
37    config: "opencode.json",
38    also_config: &[],
39};
40
41impl Harness for OpenCode {
42    fn id(&self) -> &str {
43        ID
44    }
45
46    /// Every event but one: no session end.
47    // @zen-impl: HAR-10_AC-3
48    fn hook_events(&self) -> &[Event] {
49        &[
50            Event::SessionStart,
51            Event::PromptSubmit,
52            Event::PreTool,
53            Event::PostTool,
54            Event::Stop,
55            Event::PreCompact,
56        ]
57    }
58
59    // @zen-impl: HAR-15_AC-1
60    fn program(&self) -> Option<&str> {
61        Some("opencode")
62    }
63
64    fn claude_tool(&self, call: &ToolCall) -> ToolCall {
65        claude_names::named(call, plugin::claude_name(&call.name))
66    }
67
68    // @zen-impl: HAR-10_AC-1
69    fn scopes(&self) -> &[Scope] {
70        &[Scope::Project, Scope::User]
71    }
72
73    // @zen-impl: HAR-10_AC-3
74    // @zen-impl: HAR-10_AC-5
75    // @zen-impl: HAR-10_AC-6
76    // @zen-impl: HAR-10_AC-7
77    fn render(&self, integration: &Integration, cx: &Context) -> Result<Vec<Part>> {
78        LAYOUT.render(integration, cx)
79    }
80
81    fn reads(&self, item: Item, cx: &Context) -> Result<Reads> {
82        Ok(LAYOUT.reads(item, cx))
83    }
84
85    // @zen-impl: HAR-10_AC-4
86    fn parse_hook(&self, event: Event, text: &str) -> serde_json::Result<HookInput> {
87        extension::parse(ID, event, text, plugin::tool_kind)
88    }
89
90    // @zen-impl: HAR-10_AC-4
91    // @zen-impl: AHA-2_AC-3
92    fn answer(&self, event: Event, answer: &Answer) -> Result<Output> {
93        extension::answer(ID, event, answer)
94    }
95
96    // @zen-impl: HAR-10_AC-8
97    fn notes(&self, _cx: &Context, parts: &[PartResult]) -> Vec<String> {
98        if parts.iter().any(PartResult::changed) {
99            vec!["restart OpenCode to load the changes".into()]
100        } else {
101            Vec::new()
102        }
103    }
104}
105
106#[cfg(test)]
107#[path = "tests.rs"]
108mod tests;