coding_agent_hooks/agents/
opencode.rs1use anyhow::Result;
7use serde_json::Value;
8
9use super::{AgentKind, resolve_tool_name};
10use crate::input::ToolUseHookInput;
11use crate::protocol::{HookProtocol, json_str_any, json_value_any};
12
13pub struct OpenCodeProtocol;
14
15impl HookProtocol for OpenCodeProtocol {
16 fn agent(&self) -> AgentKind {
17 AgentKind::OpenCode
18 }
19
20 fn parse_tool_use(&self, raw: &Value) -> Result<ToolUseHookInput> {
21 let tool_name = json_str_any(raw, &["tool_name", "tool"]).to_string();
22 let original = tool_name.clone();
23 let resolved = resolve_tool_name(AgentKind::OpenCode, &tool_name).to_string();
24
25 Ok(ToolUseHookInput {
26 session_id: json_str_any(raw, &["session_id", "sessionID"]).to_string(),
27 transcript_path: String::new(),
28 cwd: json_str_any(raw, &["cwd", "directory"]).to_string(),
29 permission_mode: "default".to_string(),
30 hook_event_name: json_str_any(raw, &["hook_event_name", "event"]).to_string(),
31 tool_name: resolved,
32 tool_input: json_value_any(raw, &["tool_input", "args"])
33 .unwrap_or(Value::Object(serde_json::Map::new())),
34 tool_use_id: None,
35 tool_response: raw.get("tool_response").cloned(),
36 agent: Some(AgentKind::OpenCode),
37 original_tool_name: Some(original),
38 })
39 }
40
41 fn format_allow(
43 &self,
44 _reason: Option<&str>,
45 _context: Option<&str>,
46 updated_input: Option<Value>,
47 ) -> Value {
48 let mut output = serde_json::json!({ "action": "allow" });
49 if let Some(ui) = updated_input {
50 output["args"] = ui;
51 }
52 output
53 }
54
55 fn format_deny(&self, reason: &str, _context: Option<&str>) -> Value {
56 serde_json::json!({ "action": "deny", "reason": reason })
57 }
58
59 fn format_ask(&self, _reason: Option<&str>, _context: Option<&str>) -> Value {
60 serde_json::json!({ "action": "ask" })
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn parse_opencode_bash() {
70 let raw = serde_json::json!({
71 "tool": "bash",
72 "sessionID": "oc-123",
73 "directory": "/home/user",
74 "args": {"command": "ls"}
75 });
76 let input = OpenCodeProtocol.parse_tool_use(&raw).unwrap();
77 assert_eq!(input.tool_name, "Bash");
78 assert_eq!(input.session_id, "oc-123");
79 assert_eq!(input.cwd, "/home/user");
80 }
81
82 #[test]
83 fn format_deny_opencode() {
84 assert_eq!(OpenCodeProtocol.format_deny("no", None)["action"], "deny");
85 }
86}