coding_agent_hooks/agents/
copilot.rs1use anyhow::Result;
6use serde_json::Value;
7
8use super::{AgentKind, resolve_tool_name};
9use crate::input::ToolUseHookInput;
10use crate::protocol::{HookProtocol, json_str};
11
12pub struct CopilotProtocol;
13
14impl HookProtocol for CopilotProtocol {
15 fn agent(&self) -> AgentKind {
16 AgentKind::Copilot
17 }
18
19 fn parse_tool_use(&self, raw: &Value) -> Result<ToolUseHookInput> {
20 let tool_name = json_str(raw, "tool_name").to_string();
21 let original = tool_name.clone();
22 let resolved = resolve_tool_name(AgentKind::Copilot, &tool_name).to_string();
23
24 Ok(ToolUseHookInput {
25 session_id: json_str(raw, "session_id").to_string(),
26 transcript_path: json_str(raw, "transcript_path").to_string(),
27 cwd: json_str(raw, "cwd").to_string(),
28 permission_mode: "default".to_string(),
29 hook_event_name: json_str(raw, "hook_event_name").to_string(),
30 tool_name: resolved,
31 tool_input: raw
32 .get("tool_input")
33 .cloned()
34 .unwrap_or(Value::Object(serde_json::Map::new())),
35 tool_use_id: None,
36 tool_response: raw.get("tool_response").cloned(),
37 agent: Some(AgentKind::Copilot),
38 original_tool_name: Some(original),
39 })
40 }
41
42 fn format_allow(
44 &self,
45 reason: Option<&str>,
46 _context: Option<&str>,
47 _updated_input: Option<Value>,
48 ) -> Value {
49 let mut output = serde_json::json!({ "decision": "approve" });
50 if let Some(r) = reason {
51 output["reason"] = Value::String(r.to_string());
52 }
53 output
54 }
55
56 fn format_deny(&self, reason: &str, _context: Option<&str>) -> Value {
57 serde_json::json!({ "decision": "deny", "reason": reason })
58 }
59
60 fn format_ask(&self, _reason: Option<&str>, _context: Option<&str>) -> Value {
61 serde_json::json!({ "decision": "approve" })
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn parse_copilot_bash() {
71 let raw = serde_json::json!({
72 "session_id": "cp-123",
73 "cwd": "/home/user",
74 "hook_event_name": "preToolUse",
75 "tool_name": "bash",
76 "tool_input": {"command": "git status"}
77 });
78 let input = CopilotProtocol.parse_tool_use(&raw).unwrap();
79 assert_eq!(input.tool_name, "Bash");
80 }
81
82 #[test]
83 fn format_allow_copilot() {
84 assert_eq!(
85 CopilotProtocol.format_allow(None, None, None)["decision"],
86 "approve"
87 );
88 }
89
90 #[test]
91 fn format_deny_copilot() {
92 assert_eq!(CopilotProtocol.format_deny("no", None)["decision"], "deny");
93 }
94}