use serde::{Deserialize, Serialize};
pub const MAX_TEXT_BYTES: usize = 2_000;
pub const MAX_TARGET_BYTES: usize = 512;
pub const MAX_LABEL_BYTES: usize = 128;
pub const MAX_ROOT_BYTES: usize = 1_024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Kind {
UserMessage,
AgentMessage,
ToolCall,
FileRead,
FileWrite,
Shell,
Permission,
TurnStart,
TurnEnd,
Error,
}
impl Kind {
pub const ALL: [Kind; 10] = [
Kind::UserMessage,
Kind::AgentMessage,
Kind::ToolCall,
Kind::FileRead,
Kind::FileWrite,
Kind::Shell,
Kind::Permission,
Kind::TurnStart,
Kind::TurnEnd,
Kind::Error,
];
pub fn as_str(self) -> &'static str {
match self {
Kind::UserMessage => "user_message",
Kind::AgentMessage => "agent_message",
Kind::ToolCall => "tool_call",
Kind::FileRead => "file_read",
Kind::FileWrite => "file_write",
Kind::Shell => "shell",
Kind::Permission => "permission",
Kind::TurnStart => "turn_start",
Kind::TurnEnd => "turn_end",
Kind::Error => "error",
}
}
pub fn parse(name: &str) -> Option<Kind> {
Kind::ALL.into_iter().find(|kind| kind.as_str() == name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Source {
Hook,
Mcp,
Acp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Outcome {
Ok,
Error,
Denied,
Cancelled,
Asked,
Allowed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Draft {
pub session_id: String,
pub agent: String,
pub project_root: String,
pub kind: Kind,
pub source: Source,
pub tool: Option<String>,
pub target: Option<String>,
pub outcome: Option<Outcome>,
pub text: Option<String>,
}
impl Draft {
pub fn new(
session_id: impl Into<String>,
agent: impl Into<String>,
project_root: impl Into<String>,
kind: Kind,
source: Source,
) -> Self {
Self {
session_id: session_id.into(),
agent: agent.into(),
project_root: project_root.into(),
kind,
source,
tool: None,
target: None,
outcome: None,
text: None,
}
}
pub fn tool(mut self, tool: impl Into<String>) -> Self {
self.tool = Some(tool.into());
self
}
pub fn target(mut self, target: impl Into<String>) -> Self {
self.target = Some(target.into());
self
}
pub fn outcome(mut self, outcome: Outcome) -> Self {
self.outcome = Some(outcome);
self
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Event {
pub seq: u64,
pub at: String,
pub session_id: String,
pub agent: String,
pub project_root: String,
pub kind: Kind,
pub source: Source,
pub tool: Option<String>,
pub target: Option<String>,
pub outcome: Option<Outcome>,
pub text: Option<String>,
pub truncated: bool,
}
pub(crate) fn bound(text: &str, max: usize) -> (String, bool) {
if text.len() <= max {
return (text.to_owned(), false);
}
let mut end = max;
while end > 0 && !text.is_char_boundary(end) {
end -= 1;
}
(format!("{}…", &text[..end]), true)
}
pub(crate) fn present(value: Option<String>) -> Option<String> {
value.filter(|text| !text.trim().is_empty())
}