use std::collections::BTreeMap;
use std::path::PathBuf;
use std::time::SystemTime;
use serde::{Deserialize, Serialize};
use crate::agent::string_id;
use crate::session::{SessionInfo, SessionStatus};
string_id!(SessionId);
string_id!(TurnId);
string_id!(PromptId);
string_id!(MessageId);
string_id!(ToolId);
string_id!(RequestId);
string_id!(QuestionId);
string_id!(ChoiceId);
pub type Extensions = BTreeMap<String, serde_json::Value>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Event {
pub sequence: u64,
#[serde(default = "unix_epoch")]
pub occurred_at: SystemTime,
pub session_id: SessionId,
pub turn_info: Option<TurnContext>,
pub kind: EventKind,
pub extensions: Extensions,
}
fn unix_epoch() -> SystemTime {
SystemTime::UNIX_EPOCH
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TurnContext {
pub id: TurnId,
pub parent_tool_id: Option<ToolId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum EventKind {
TurnStarted {
origin: TurnOrigin,
},
TextDelta {
message_id: MessageId,
text: String,
},
ReasoningDelta {
message_id: MessageId,
text: String,
},
UserMessage {
message_id: MessageId,
text: String,
},
MessageEnded {
message_id: MessageId,
},
ToolUpdated(ToolUpdate),
ToolOutputDelta {
tool_id: ToolId,
text: String,
},
PlanUpdated {
entries: Vec<PlanEntry>,
},
RequestOpened(Request),
RequestClosed {
request_id: RequestId,
},
SessionUpdated(SessionInfo),
StatusChanged(SessionStatus),
ContextUsage {
used_tokens: u64,
window_tokens: Option<u64>,
cost_usd: Option<f64>,
},
ContextCompacted,
PlanUsageUpdated(PlanUsage),
Diagnostic(Diagnostic),
TurnEnded {
stop: StopReason,
background: Vec<ToolId>, },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum TurnOrigin {
Prompt(PromptId),
Agent,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum StopReason {
Completed { source: CompletionSource },
Cancelled,
Refused,
Failed { message: String },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum CompletionSource {
Protocol,
Inferred,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ToolUpdate {
pub id: ToolId,
pub kind: ToolKind,
pub title: String,
pub status: ToolStatus,
pub input: ToolInput,
pub output: Option<String>,
pub diffs: Vec<FileDiff>,
pub locations: Vec<PathBuf>,
pub raw: Option<RawTool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum ToolKind {
Read,
Edit,
Delete,
Move,
Search,
Execute,
Fetch,
Think,
Mcp {
server: String,
tool: String,
},
Subagent,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum ToolStatus {
Pending,
Running,
Completed,
Failed,
Cancelled,
}
impl ToolStatus {
pub fn is_active(self) -> bool {
matches!(self, ToolStatus::Pending | ToolStatus::Running)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum ToolInput {
Path(PathBuf),
Command {
command: String,
cwd: Option<PathBuf>,
},
Pattern(String),
Url(String),
Query(String),
Text(String),
None,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RawTool {
pub name: String,
pub input: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct FileDiff {
pub path: PathBuf,
pub old_text: Option<String>,
pub new_text: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct PlanEntry {
pub text: String,
pub status: PlanStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum PlanStatus {
Pending,
InProgress,
Completed,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
#[allow(clippy::large_enum_variant)]
pub enum Request {
Permission(PermissionRequest),
Question(QuestionRequest),
}
impl Request {
pub fn id(&self) -> RequestId {
match self {
Request::Permission(r) => r.id.clone(),
Request::Question(r) => r.id.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct PermissionRequest {
pub id: RequestId,
pub tool: ToolUpdate,
pub options: Vec<PermissionChoice>,
pub detail: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum PermissionChoice {
AllowOnce,
AllowAlways,
DenyOnce,
DenyAlways,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct QuestionRequest {
pub id: RequestId,
pub questions: Vec<Question>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Question {
pub id: QuestionId,
pub text: String,
pub header: Option<String>,
pub choices: Vec<Choice>,
pub multi_select: bool,
pub allows_free_text: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Choice {
pub id: ChoiceId,
pub label: String,
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum Answer {
Permission(PermissionChoice),
Question(Vec<QuestionAnswer>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum QuestionAnswer {
Choices(Vec<ChoiceId>),
Text(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct PlanUsage {
pub plan: Option<String>,
pub windows: Vec<UsageWindow>,
pub fetched_at: SystemTime,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct UsageWindow {
pub label: String,
pub used_percent: u8,
pub resets_at: Option<SystemTime>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Diagnostic {
pub level: DiagnosticLevel,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum DiagnosticLevel {
Info,
Warning,
Error,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Delivery {
pub prompt_id: PromptId,
pub kind: DeliveryKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum DeliveryKind {
Started {
turn_id: TurnId,
},
Steered {
turn_id: TurnId,
},
Queued {
position: u32,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_event_stored_without_occurred_at_still_loads() {
let event = Event {
sequence: 1,
occurred_at: SystemTime::UNIX_EPOCH,
session_id: SessionId::new("s1"),
turn_info: None,
kind: EventKind::TurnStarted {
origin: TurnOrigin::Agent,
},
extensions: Extensions::new(),
};
let mut stored = serde_json::to_value(&event).unwrap();
stored.as_object_mut().unwrap().remove("occurred_at");
assert_eq!(serde_json::from_value::<Event>(stored).unwrap(), event);
}
}