use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HookEventType {
PreToolUse,
PostToolUse,
GenerateStart,
GenerateEnd,
SessionStart,
SessionEnd,
SkillLoad,
SkillUnload,
PrePrompt,
PostResponse,
OnError,
PreContextPerception,
PostContextPerception,
OnSuccess,
PreMemoryRecall,
PostMemoryRecall,
PrePlanning,
PostPlanning,
PreReasoning,
PostReasoning,
OnRateLimit,
OnConfirmation,
IntentDetection,
}
impl std::fmt::Display for HookEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HookEventType::PreToolUse => write!(f, "pre_tool_use"),
HookEventType::PostToolUse => write!(f, "post_tool_use"),
HookEventType::GenerateStart => write!(f, "generate_start"),
HookEventType::GenerateEnd => write!(f, "generate_end"),
HookEventType::SessionStart => write!(f, "session_start"),
HookEventType::SessionEnd => write!(f, "session_end"),
HookEventType::SkillLoad => write!(f, "skill_load"),
HookEventType::SkillUnload => write!(f, "skill_unload"),
HookEventType::PrePrompt => write!(f, "pre_prompt"),
HookEventType::PostResponse => write!(f, "post_response"),
HookEventType::OnError => write!(f, "on_error"),
HookEventType::PreContextPerception => write!(f, "pre_context_perception"),
HookEventType::PostContextPerception => write!(f, "post_context_perception"),
HookEventType::OnSuccess => write!(f, "on_success"),
HookEventType::PreMemoryRecall => write!(f, "pre_memory_recall"),
HookEventType::PostMemoryRecall => write!(f, "post_memory_recall"),
HookEventType::PrePlanning => write!(f, "pre_planning"),
HookEventType::PostPlanning => write!(f, "post_planning"),
HookEventType::PreReasoning => write!(f, "pre_reasoning"),
HookEventType::PostReasoning => write!(f, "post_reasoning"),
HookEventType::OnRateLimit => write!(f, "on_rate_limit"),
HookEventType::OnConfirmation => write!(f, "on_confirmation"),
HookEventType::IntentDetection => write!(f, "intent_detection"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResultData {
pub success: bool,
pub output: String,
pub exit_code: Option<i32>,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreToolUseEvent {
pub session_id: String,
pub tool: String,
pub args: serde_json::Value,
pub working_directory: String,
pub recent_tools: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostToolUseEvent {
pub session_id: String,
pub tool: String,
pub args: serde_json::Value,
pub result: ToolResultData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerateStartEvent {
pub session_id: String,
pub prompt: String,
pub system_prompt: Option<String>,
pub model_provider: String,
pub model_name: String,
pub available_tools: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerateEndEvent {
pub session_id: String,
pub prompt: String,
pub response_text: String,
pub tool_calls: Vec<ToolCallInfo>,
pub usage: TokenUsageInfo,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallInfo {
pub name: String,
pub args: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenUsageInfo {
pub prompt_tokens: i32,
pub completion_tokens: i32,
pub total_tokens: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionStartEvent {
pub session_id: String,
pub system_prompt: Option<String>,
pub model_provider: String,
pub model_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionEndEvent {
pub session_id: String,
pub total_tokens: i32,
pub total_tool_calls: i32,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillLoadEvent {
pub skill_name: String,
pub tool_names: Vec<String>,
pub version: Option<String>,
pub description: Option<String>,
pub loaded_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillUnloadEvent {
pub skill_name: String,
pub tool_names: Vec<String>,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrePromptEvent {
pub session_id: String,
pub prompt: String,
pub system_prompt: Option<String>,
pub message_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostResponseEvent {
pub session_id: String,
pub response_text: String,
pub tool_calls_count: usize,
pub usage: TokenUsageInfo,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorType {
ToolFailure,
LlmFailure,
PermissionDenied,
Timeout,
Other,
}
impl std::fmt::Display for ErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorType::ToolFailure => write!(f, "tool_failure"),
ErrorType::LlmFailure => write!(f, "llm_failure"),
ErrorType::PermissionDenied => write!(f, "permission_denied"),
ErrorType::Timeout => write!(f, "timeout"),
ErrorType::Other => write!(f, "other"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnErrorEvent {
pub session_id: String,
pub error_type: ErrorType,
pub error_message: String,
pub context: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreContextPerceptionEvent {
pub session_id: String,
pub intent: String,
pub target_type: String,
pub target_name: String,
pub domain: String,
pub query: Option<String>,
pub working_directory: String,
pub urgency: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostContextPerceptionEvent {
pub session_id: String,
pub intent: String,
pub target_type: String,
pub success: bool,
pub facts_retrieved: usize,
pub files_retrieved: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnSuccessEvent {
pub session_id: String,
pub action_type: String,
pub action_summary: String,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreMemoryRecallEvent {
pub session_id: String,
pub query: String,
pub memory_type: String,
pub max_results: usize,
pub working_directory: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostMemoryRecallEvent {
pub session_id: String,
pub query: String,
pub memory_type: String,
pub facts_retrieved: usize,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PlanningStrategy {
None,
StepByStep,
TreeOfThoughts,
GraphPlanning,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrePlanningEvent {
pub session_id: String,
pub task_description: String,
pub available_strategies: Vec<PlanningStrategy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub constraints: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostPlanningEvent {
pub session_id: String,
pub task_description: String,
pub strategy_used: PlanningStrategy,
pub subtasks: Vec<String>,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReasoningType {
ChainOfThought,
TreeOfThoughts,
ReAct,
Reflexion,
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreReasoningEvent {
pub session_id: String,
pub reasoning_type: ReasoningType,
pub problem_statement: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostReasoningEvent {
pub session_id: String,
pub reasoning_type: ReasoningType,
pub conclusion: String,
pub steps_count: usize,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RateLimitType {
LlmTokenLimit,
LlmRequestLimit,
ApiRequestLimit,
ToolExecutionLimit,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnRateLimitEvent {
pub session_id: String,
pub limit_type: RateLimitType,
pub retry_after_ms: u64,
pub current_usage: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConfirmationType {
SafetyConfirm,
UserConfirm,
CostConfirm,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnConfirmationEvent {
pub session_id: String,
pub confirmation_type: ConfirmationType,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IntentDetectionEvent {
pub session_id: String,
pub prompt: String,
pub workspace: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub language_hint: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event_type", content = "payload")]
pub enum HookEvent {
#[serde(rename = "pre_tool_use")]
PreToolUse(PreToolUseEvent),
#[serde(rename = "post_tool_use")]
PostToolUse(PostToolUseEvent),
#[serde(rename = "generate_start")]
GenerateStart(GenerateStartEvent),
#[serde(rename = "generate_end")]
GenerateEnd(GenerateEndEvent),
#[serde(rename = "session_start")]
SessionStart(SessionStartEvent),
#[serde(rename = "session_end")]
SessionEnd(SessionEndEvent),
#[serde(rename = "skill_load")]
SkillLoad(SkillLoadEvent),
#[serde(rename = "skill_unload")]
SkillUnload(SkillUnloadEvent),
#[serde(rename = "pre_prompt")]
PrePrompt(PrePromptEvent),
#[serde(rename = "post_response")]
PostResponse(PostResponseEvent),
#[serde(rename = "on_error")]
OnError(OnErrorEvent),
#[serde(rename = "pre_context_perception")]
PreContextPerception(PreContextPerceptionEvent),
#[serde(rename = "post_context_perception")]
PostContextPerception(PostContextPerceptionEvent),
#[serde(rename = "on_success")]
OnSuccess(OnSuccessEvent),
#[serde(rename = "pre_memory_recall")]
PreMemoryRecall(PreMemoryRecallEvent),
#[serde(rename = "post_memory_recall")]
PostMemoryRecall(PostMemoryRecallEvent),
#[serde(rename = "pre_planning")]
PrePlanning(PrePlanningEvent),
#[serde(rename = "post_planning")]
PostPlanning(PostPlanningEvent),
#[serde(rename = "pre_reasoning")]
PreReasoning(PreReasoningEvent),
#[serde(rename = "post_reasoning")]
PostReasoning(PostReasoningEvent),
#[serde(rename = "on_rate_limit")]
OnRateLimit(OnRateLimitEvent),
#[serde(rename = "on_confirmation")]
OnConfirmation(OnConfirmationEvent),
#[serde(rename = "intent_detection")]
IntentDetection(IntentDetectionEvent),
}
impl HookEvent {
pub fn event_type(&self) -> HookEventType {
match self {
HookEvent::PreToolUse(_) => HookEventType::PreToolUse,
HookEvent::PostToolUse(_) => HookEventType::PostToolUse,
HookEvent::GenerateStart(_) => HookEventType::GenerateStart,
HookEvent::GenerateEnd(_) => HookEventType::GenerateEnd,
HookEvent::SessionStart(_) => HookEventType::SessionStart,
HookEvent::SessionEnd(_) => HookEventType::SessionEnd,
HookEvent::SkillLoad(_) => HookEventType::SkillLoad,
HookEvent::SkillUnload(_) => HookEventType::SkillUnload,
HookEvent::PrePrompt(_) => HookEventType::PrePrompt,
HookEvent::PostResponse(_) => HookEventType::PostResponse,
HookEvent::OnError(_) => HookEventType::OnError,
HookEvent::PreContextPerception(_) => HookEventType::PreContextPerception,
HookEvent::PostContextPerception(_) => HookEventType::PostContextPerception,
HookEvent::OnSuccess(_) => HookEventType::OnSuccess,
HookEvent::PreMemoryRecall(_) => HookEventType::PreMemoryRecall,
HookEvent::PostMemoryRecall(_) => HookEventType::PostMemoryRecall,
HookEvent::PrePlanning(_) => HookEventType::PrePlanning,
HookEvent::PostPlanning(_) => HookEventType::PostPlanning,
HookEvent::PreReasoning(_) => HookEventType::PreReasoning,
HookEvent::PostReasoning(_) => HookEventType::PostReasoning,
HookEvent::OnRateLimit(_) => HookEventType::OnRateLimit,
HookEvent::OnConfirmation(_) => HookEventType::OnConfirmation,
HookEvent::IntentDetection(_) => HookEventType::IntentDetection,
}
}
pub fn session_id(&self) -> &str {
match self {
HookEvent::PreToolUse(e) => &e.session_id,
HookEvent::PostToolUse(e) => &e.session_id,
HookEvent::GenerateStart(e) => &e.session_id,
HookEvent::GenerateEnd(e) => &e.session_id,
HookEvent::SessionStart(e) => &e.session_id,
HookEvent::SessionEnd(e) => &e.session_id,
HookEvent::PrePrompt(e) => &e.session_id,
HookEvent::PostResponse(e) => &e.session_id,
HookEvent::OnError(e) => &e.session_id,
HookEvent::PreContextPerception(e) => &e.session_id,
HookEvent::PostContextPerception(e) => &e.session_id,
HookEvent::OnSuccess(e) => &e.session_id,
HookEvent::PreMemoryRecall(e) => &e.session_id,
HookEvent::PostMemoryRecall(e) => &e.session_id,
HookEvent::PrePlanning(e) => &e.session_id,
HookEvent::PostPlanning(e) => &e.session_id,
HookEvent::PreReasoning(e) => &e.session_id,
HookEvent::PostReasoning(e) => &e.session_id,
HookEvent::OnRateLimit(e) => &e.session_id,
HookEvent::OnConfirmation(e) => &e.session_id,
HookEvent::IntentDetection(e) => &e.session_id,
HookEvent::SkillLoad(_) => "",
HookEvent::SkillUnload(_) => "",
}
}
pub fn tool_name(&self) -> Option<&str> {
match self {
HookEvent::PreToolUse(e) => Some(&e.tool),
HookEvent::PostToolUse(e) => Some(&e.tool),
_ => None,
}
}
pub fn tool_args(&self) -> Option<&serde_json::Value> {
match self {
HookEvent::PreToolUse(e) => Some(&e.args),
HookEvent::PostToolUse(e) => Some(&e.args),
_ => None,
}
}
pub fn skill_name(&self) -> Option<&str> {
match self {
HookEvent::SkillLoad(e) => Some(&e.skill_name),
HookEvent::SkillUnload(e) => Some(&e.skill_name),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hook_event_type_display() {
assert_eq!(HookEventType::PreToolUse.to_string(), "pre_tool_use");
assert_eq!(HookEventType::PostToolUse.to_string(), "post_tool_use");
assert_eq!(HookEventType::GenerateStart.to_string(), "generate_start");
assert_eq!(HookEventType::GenerateEnd.to_string(), "generate_end");
assert_eq!(HookEventType::SessionStart.to_string(), "session_start");
assert_eq!(HookEventType::SessionEnd.to_string(), "session_end");
assert_eq!(HookEventType::SkillLoad.to_string(), "skill_load");
assert_eq!(HookEventType::SkillUnload.to_string(), "skill_unload");
}
#[test]
fn test_pre_tool_use_event() {
let event = PreToolUseEvent {
session_id: "session-1".to_string(),
tool: "Bash".to_string(),
args: serde_json::json!({"command": "echo hello"}),
working_directory: "/workspace".to_string(),
recent_tools: vec!["Read".to_string()],
};
assert_eq!(event.session_id, "session-1");
assert_eq!(event.tool, "Bash");
}
#[test]
fn test_post_tool_use_event() {
let event = PostToolUseEvent {
session_id: "session-1".to_string(),
tool: "Bash".to_string(),
args: serde_json::json!({"command": "echo hello"}),
result: ToolResultData {
success: true,
output: "hello\n".to_string(),
exit_code: Some(0),
duration_ms: 50,
},
};
assert!(event.result.success);
assert_eq!(event.result.exit_code, Some(0));
}
#[test]
fn test_hook_event_type() {
let pre_tool = HookEvent::PreToolUse(PreToolUseEvent {
session_id: "s1".to_string(),
tool: "Bash".to_string(),
args: serde_json::json!({}),
working_directory: "/".to_string(),
recent_tools: vec![],
});
assert_eq!(pre_tool.event_type(), HookEventType::PreToolUse);
assert_eq!(pre_tool.session_id(), "s1");
assert_eq!(pre_tool.tool_name(), Some("Bash"));
}
#[test]
fn test_hook_event_serialization() {
let event = HookEvent::PreToolUse(PreToolUseEvent {
session_id: "s1".to_string(),
tool: "Bash".to_string(),
args: serde_json::json!({"command": "ls"}),
working_directory: "/workspace".to_string(),
recent_tools: vec![],
});
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("pre_tool_use"));
assert!(json.contains("Bash"));
let parsed: HookEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.event_type(), HookEventType::PreToolUse);
}
#[test]
fn test_generate_events() {
let start = GenerateStartEvent {
session_id: "s1".to_string(),
prompt: "Hello".to_string(),
system_prompt: Some("You are helpful".to_string()),
model_provider: "anthropic".to_string(),
model_name: "claude-3".to_string(),
available_tools: vec!["Bash".to_string(), "Read".to_string()],
};
let end = GenerateEndEvent {
session_id: "s1".to_string(),
prompt: "Hello".to_string(),
response_text: "Hi there!".to_string(),
tool_calls: vec![],
usage: TokenUsageInfo {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
},
duration_ms: 500,
};
assert_eq!(start.prompt, "Hello");
assert_eq!(end.response_text, "Hi there!");
assert_eq!(end.usage.total_tokens, 15);
}
#[test]
fn test_session_events() {
let start = SessionStartEvent {
session_id: "s1".to_string(),
system_prompt: Some("System".to_string()),
model_provider: "anthropic".to_string(),
model_name: "claude-3".to_string(),
};
let end = SessionEndEvent {
session_id: "s1".to_string(),
total_tokens: 1000,
total_tool_calls: 5,
duration_ms: 60000,
};
let start_event = HookEvent::SessionStart(start);
let end_event = HookEvent::SessionEnd(end);
assert_eq!(start_event.event_type(), HookEventType::SessionStart);
assert_eq!(end_event.event_type(), HookEventType::SessionEnd);
assert!(start_event.tool_name().is_none());
}
#[test]
fn test_skill_load_event() {
let event = SkillLoadEvent {
skill_name: "test-skill".to_string(),
tool_names: vec!["tool1".to_string(), "tool2".to_string()],
version: Some("1.0.0".to_string()),
description: Some("A test skill".to_string()),
loaded_at: 1234567890,
};
assert_eq!(event.skill_name, "test-skill");
assert_eq!(event.tool_names.len(), 2);
assert_eq!(event.version, Some("1.0.0".to_string()));
assert_eq!(event.loaded_at, 1234567890);
}
#[test]
fn test_skill_unload_event() {
let event = SkillUnloadEvent {
skill_name: "test-skill".to_string(),
tool_names: vec!["tool1".to_string(), "tool2".to_string()],
duration_ms: 60000,
};
assert_eq!(event.skill_name, "test-skill");
assert_eq!(event.tool_names.len(), 2);
assert_eq!(event.duration_ms, 60000);
}
#[test]
fn test_hook_event_skill_name() {
let load_event = HookEvent::SkillLoad(SkillLoadEvent {
skill_name: "my-skill".to_string(),
tool_names: vec!["tool1".to_string()],
version: None,
description: None,
loaded_at: 0,
});
let unload_event = HookEvent::SkillUnload(SkillUnloadEvent {
skill_name: "my-skill".to_string(),
tool_names: vec!["tool1".to_string()],
duration_ms: 1000,
});
assert_eq!(load_event.event_type(), HookEventType::SkillLoad);
assert_eq!(load_event.skill_name(), Some("my-skill"));
assert_eq!(load_event.session_id(), "");
assert_eq!(unload_event.event_type(), HookEventType::SkillUnload);
assert_eq!(unload_event.skill_name(), Some("my-skill"));
assert_eq!(unload_event.session_id(), "");
let pre_tool = HookEvent::PreToolUse(PreToolUseEvent {
session_id: "s1".to_string(),
tool: "Bash".to_string(),
args: serde_json::json!({}),
working_directory: "/".to_string(),
recent_tools: vec![],
});
assert!(pre_tool.skill_name().is_none());
}
#[test]
fn test_skill_event_serialization() {
let event = HookEvent::SkillLoad(SkillLoadEvent {
skill_name: "test-skill".to_string(),
tool_names: vec!["tool1".to_string()],
version: Some("1.0.0".to_string()),
description: None,
loaded_at: 1234567890,
});
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("skill_load"));
assert!(json.contains("test-skill"));
assert!(json.contains("1.0.0"));
let parsed: HookEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.event_type(), HookEventType::SkillLoad);
assert_eq!(parsed.skill_name(), Some("test-skill"));
}
#[test]
fn test_hook_event_type_display_new_variants() {
assert_eq!(HookEventType::PrePrompt.to_string(), "pre_prompt");
assert_eq!(HookEventType::PostResponse.to_string(), "post_response");
assert_eq!(HookEventType::OnError.to_string(), "on_error");
}
#[test]
fn test_pre_prompt_event() {
let event = PrePromptEvent {
session_id: "s1".to_string(),
prompt: "Fix the bug".to_string(),
system_prompt: Some("You are helpful".to_string()),
message_count: 5,
};
assert_eq!(event.session_id, "s1");
assert_eq!(event.prompt, "Fix the bug");
assert_eq!(event.message_count, 5);
let hook_event = HookEvent::PrePrompt(event);
assert_eq!(hook_event.event_type(), HookEventType::PrePrompt);
assert_eq!(hook_event.session_id(), "s1");
assert!(hook_event.tool_name().is_none());
assert!(hook_event.skill_name().is_none());
}
#[test]
fn test_post_response_event() {
let event = PostResponseEvent {
session_id: "s1".to_string(),
response_text: "Done!".to_string(),
tool_calls_count: 3,
usage: TokenUsageInfo {
prompt_tokens: 100,
completion_tokens: 50,
total_tokens: 150,
},
duration_ms: 2000,
};
assert_eq!(event.response_text, "Done!");
assert_eq!(event.tool_calls_count, 3);
assert_eq!(event.usage.total_tokens, 150);
let hook_event = HookEvent::PostResponse(event);
assert_eq!(hook_event.event_type(), HookEventType::PostResponse);
assert_eq!(hook_event.session_id(), "s1");
}
#[test]
fn test_on_error_event() {
let event = OnErrorEvent {
session_id: "s1".to_string(),
error_type: ErrorType::ToolFailure,
error_message: "Command failed with exit code 1".to_string(),
context: serde_json::json!({"tool": "Bash", "command": "false"}),
};
assert_eq!(event.error_type.to_string(), "tool_failure");
assert_eq!(event.error_message, "Command failed with exit code 1");
let hook_event = HookEvent::OnError(event);
assert_eq!(hook_event.event_type(), HookEventType::OnError);
assert_eq!(hook_event.session_id(), "s1");
}
#[test]
fn test_error_type_display() {
assert_eq!(ErrorType::ToolFailure.to_string(), "tool_failure");
assert_eq!(ErrorType::LlmFailure.to_string(), "llm_failure");
assert_eq!(ErrorType::PermissionDenied.to_string(), "permission_denied");
assert_eq!(ErrorType::Timeout.to_string(), "timeout");
assert_eq!(ErrorType::Other.to_string(), "other");
}
#[test]
fn test_new_event_serialization() {
let event = HookEvent::PrePrompt(PrePromptEvent {
session_id: "s1".to_string(),
prompt: "Hello".to_string(),
system_prompt: None,
message_count: 0,
});
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("pre_prompt"));
let parsed: HookEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.event_type(), HookEventType::PrePrompt);
let event = HookEvent::PostResponse(PostResponseEvent {
session_id: "s1".to_string(),
response_text: "Hi".to_string(),
tool_calls_count: 0,
usage: TokenUsageInfo {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
},
duration_ms: 100,
});
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("post_response"));
let parsed: HookEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.event_type(), HookEventType::PostResponse);
let event = HookEvent::OnError(OnErrorEvent {
session_id: "s1".to_string(),
error_type: ErrorType::LlmFailure,
error_message: "API timeout".to_string(),
context: serde_json::json!({}),
});
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("on_error"));
let parsed: HookEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.event_type(), HookEventType::OnError);
}
}