use crate::{
Result,
common::{extension, plugin},
harness::{Context, Harness, Part, PartResult, Reads, Scope},
hook::{Answer, Event, HookInput, Output, ToolCall, claude_names},
integration::{Integration, Item},
};
#[derive(Debug, Clone, Copy, Default)]
pub struct OpenCode;
const ID: &str = "opencode";
const LAYOUT: plugin::Layout = plugin::Layout {
harness: ID,
agent: "OpenCode",
types: "@opencode-ai/plugin",
project_dir: ".opencode",
user_dir: ".config/opencode",
config: "opencode.json",
also_config: &[],
};
impl Harness for OpenCode {
fn id(&self) -> &str {
ID
}
fn hook_events(&self) -> &[Event] {
&[
Event::SessionStart,
Event::PromptSubmit,
Event::PreTool,
Event::PostTool,
Event::Stop,
Event::PreCompact,
]
}
fn program(&self) -> Option<&str> {
Some("opencode")
}
fn claude_tool(&self, call: &ToolCall) -> ToolCall {
claude_names::named(call, plugin::claude_name(&call.name))
}
fn scopes(&self) -> &[Scope] {
&[Scope::Project, Scope::User]
}
fn render(&self, integration: &Integration, cx: &Context) -> Result<Vec<Part>> {
LAYOUT.render(integration, cx)
}
fn reads(&self, item: Item, cx: &Context) -> Result<Reads> {
Ok(LAYOUT.reads(item, cx))
}
fn parse_hook(&self, event: Event, text: &str) -> serde_json::Result<HookInput> {
extension::parse(ID, event, text, plugin::tool_kind)
}
fn answer(&self, event: Event, answer: &Answer) -> Result<Output> {
extension::answer(ID, event, answer)
}
fn notes(&self, _cx: &Context, parts: &[PartResult]) -> Vec<String> {
if parts.iter().any(PartResult::changed) {
vec!["restart OpenCode to load the changes".into()]
} else {
Vec::new()
}
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;