use serde::Deserialize;
use std::path::PathBuf;
#[derive(Debug, Clone, Deserialize)]
pub struct HookContext {
pub session_id: String,
pub transcript_path: PathBuf,
pub cwd: PathBuf,
pub hook_event_name: HookEvent,
#[serde(default)]
pub tool_name: String,
#[serde(default)]
pub tool_use_id: String,
#[serde(default)]
pub permission_mode: Option<String>,
#[serde(default)]
pub effort: Option<EffortLevel>,
#[serde(default)]
pub agent_id: Option<String>,
#[serde(default)]
pub agent_type: Option<String>,
}
impl HookContext {
pub fn agent_key(&self) -> &str {
self.agent_id.as_deref().unwrap_or("main")
}
}
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)]
pub enum HookEvent {
#[serde(rename = "PreToolUse")]
Pre,
#[serde(rename = "PostToolUse")]
Post,
#[serde(rename = "PostToolUseFailure")]
PostFailure,
#[serde(rename = "SubagentStop")]
SubagentStop,
}
#[derive(Debug, Clone, Deserialize)]
pub struct EffortLevel {
pub level: String,
}
#[cfg(test)]
mod tests {
use super::*;
const MAIN_AGENT_PAYLOAD: &str = r#"{
"session_id": "a9db5455-5a02-44b1-b807-0bf79d80e6b1",
"transcript_path": "/Users/brandon/.claude/projects/-private-tmp-ccprobe/a9db5455-5a02-44b1-b807-0bf79d80e6b1.jsonl",
"cwd": "/private/tmp/ccprobe",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_use_id": "toolu_01",
"permission_mode": "bypassPermissions",
"effort": {"level": "medium"}
}"#;
const SUBAGENT_PAYLOAD: &str = r#"{
"session_id": "a9db5455-5a02-44b1-b807-0bf79d80e6b1",
"transcript_path": "/Users/brandon/.claude/projects/-private-tmp-ccprobe/a9db5455-5a02-44b1-b807-0bf79d80e6b1.jsonl",
"cwd": "/private/tmp/ccprobe",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_use_id": "toolu_02",
"agent_id": "a56e70ccdc442bf74",
"agent_type": "general-purpose"
}"#;
#[test]
fn deserializes_main_agent_payload() {
let ctx: HookContext = serde_json::from_str(MAIN_AGENT_PAYLOAD).unwrap();
assert_eq!(ctx.tool_name, "Bash");
assert_eq!(ctx.hook_event_name, HookEvent::Pre);
assert!(ctx.agent_id.is_none());
assert!(ctx.agent_type.is_none());
assert_eq!(ctx.agent_key(), "main");
}
const SUBAGENT_STOP_PAYLOAD: &str = r#"{
"session_id": "abc",
"transcript_path": "/t",
"cwd": "/c",
"hook_event_name": "SubagentStop",
"agent_id": "subagent-xyz",
"agent_type": "general-purpose"
}"#;
#[test]
fn deserializes_subagent_stop_payload() {
let ctx: HookContext = serde_json::from_str(SUBAGENT_STOP_PAYLOAD).unwrap();
assert_eq!(ctx.hook_event_name, HookEvent::SubagentStop);
assert_eq!(ctx.agent_id.as_deref(), Some("subagent-xyz"));
assert_eq!(ctx.agent_key(), "subagent-xyz");
assert_eq!(ctx.tool_name, "");
assert_eq!(ctx.tool_use_id, "");
}
#[test]
fn deserializes_subagent_payload() {
let ctx: HookContext = serde_json::from_str(SUBAGENT_PAYLOAD).unwrap();
assert_eq!(ctx.agent_id.as_deref(), Some("a56e70ccdc442bf74"));
assert_eq!(ctx.agent_type.as_deref(), Some("general-purpose"));
assert_eq!(ctx.agent_key(), "a56e70ccdc442bf74");
}
}