Skip to main content

coding_agent_hooks/agents/
amazonq.rs

1//! Amazon Q Developer CLI hook protocol implementation.
2//!
3//! Amazon Q uses "action": "allow"/"deny" instead of "decision".
4
5use 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 AmazonQProtocol;
13
14impl HookProtocol for AmazonQProtocol {
15    fn agent(&self) -> AgentKind {
16        AgentKind::AmazonQ
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::AmazonQ, &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::AmazonQ),
38            original_tool_name: Some(original),
39        })
40    }
41
42    // Amazon Q uses "action" instead of "decision"
43    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!({ "action": "allow" });
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!({ "action": "deny", "reason": reason })
58    }
59
60    fn format_ask(&self, _reason: Option<&str>, _context: Option<&str>) -> Value {
61        serde_json::json!({ "action": "allow" })
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn parse_amazonq_execute_bash() {
71        let raw = serde_json::json!({
72            "session_id": "q-123",
73            "cwd": "/home/user",
74            "hook_event_name": "preToolUse",
75            "tool_name": "execute_bash",
76            "tool_input": {"command": "npm test"}
77        });
78        let input = AmazonQProtocol.parse_tool_use(&raw).unwrap();
79        assert_eq!(input.tool_name, "Bash");
80        assert_eq!(input.original_tool_name.as_deref(), Some("execute_bash"));
81    }
82
83    #[test]
84    fn format_deny_amazonq() {
85        let out = AmazonQProtocol.format_deny("no", None);
86        assert_eq!(out["action"], "deny");
87    }
88}