use std::{fmt, str::FromStr};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::Error;
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum Event {
SessionStart,
SessionEnd,
PromptSubmit,
PreTool,
PostTool,
Stop,
PreCompact,
}
impl Event {
pub const ALL: [Event; 7] = [
Event::SessionStart,
Event::SessionEnd,
Event::PromptSubmit,
Event::PreTool,
Event::PostTool,
Event::Stop,
Event::PreCompact,
];
pub fn as_str(self) -> &'static str {
match self {
Event::SessionStart => "session-start",
Event::SessionEnd => "session-end",
Event::PromptSubmit => "prompt-submit",
Event::PreTool => "pre-tool",
Event::PostTool => "post-tool",
Event::Stop => "stop",
Event::PreCompact => "pre-compact",
}
}
}
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Event {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Event::ALL
.into_iter()
.find(|e| e.as_str() == s)
.ok_or_else(|| Error::UnknownEvent {
name: s.to_string(),
})
}
}
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum ToolKind {
Shell,
Read,
Write,
Mcp,
Other,
}
impl ToolKind {
pub fn as_str(self) -> &'static str {
match self {
ToolKind::Shell => "shell",
ToolKind::Read => "read",
ToolKind::Write => "write",
ToolKind::Mcp => "mcp",
ToolKind::Other => "other",
}
}
}
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ToolCall {
pub name: String,
pub kind: ToolKind,
#[serde(default)]
pub input: Value,
}
impl ToolCall {
pub fn new(name: impl Into<String>, kind: ToolKind, input: Value) -> Self {
ToolCall {
name: name.into(),
kind,
input,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tool_kinds_print_as_their_json() {
for k in [
ToolKind::Shell,
ToolKind::Read,
ToolKind::Write,
ToolKind::Mcp,
ToolKind::Other,
] {
assert_eq!(
serde_json::to_value(k).unwrap(),
serde_json::json!(k.as_str())
);
}
}
#[test]
fn events_parse_and_print() {
for e in Event::ALL {
assert_eq!(e.as_str().parse::<Event>().unwrap(), e);
assert_eq!(e.to_string(), e.as_str());
assert_eq!(
serde_json::to_value(e).unwrap(),
serde_json::json!(e.as_str())
);
}
assert_eq!(
"nope".parse::<Event>().unwrap_err().to_string(),
"no hook event named `nope`"
);
assert_eq!(
serde_json::to_value(ToolKind::Shell).unwrap(),
serde_json::json!("shell")
);
let c = ToolCall::new("Bash", ToolKind::Shell, serde_json::json!({}));
assert_eq!(c.name, "Bash");
}
}