Skip to main content

cairn_runtime/
event.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use crate::message::Message;
6use crate::model::{ModelResponse, Usage};
7
8/// One immutable fact in a run's history.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct Event {
11    pub run_id: String,
12    /// Position in the run's journal, starting at 0. Gap-free.
13    pub seq: u64,
14    pub at: DateTime<Utc>,
15    pub kind: EventKind,
16}
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19#[serde(tag = "type", rename_all = "snake_case")]
20pub enum EventKind {
21    RunStarted {
22        agent: String,
23        input: Message,
24    },
25    ModelCalled {
26        step: u32,
27        response: ModelResponse,
28        cost_usd: Option<f64>,
29        duration_ms: u64,
30    },
31    /// Recorded for audit; does not change state. A terminal failure is
32    /// followed by `RunFailed`.
33    ModelFailed {
34        step: u32,
35        error: String,
36        /// Tokens already paid for before the failure (e.g. rejected answers).
37        #[serde(default)]
38        usage: Usage,
39        #[serde(default)]
40        cost_usd: Option<f64>,
41    },
42    ToolDenied {
43        call_id: String,
44        tool: String,
45        reason: String,
46    },
47    ApprovalRequested {
48        call_id: String,
49        tool: String,
50        input: Value,
51        reason: String,
52    },
53    ApprovalDecided {
54        call_id: String,
55        approved: bool,
56        by: Option<String>,
57        note: Option<String>,
58    },
59    ToolCompleted {
60        call_id: String,
61        tool: String,
62        output: Value,
63        is_error: bool,
64        duration_ms: u64,
65    },
66    BudgetExceeded {
67        reason: String,
68    },
69    RunCompleted {
70        output: String,
71        /// The validated final answer, when the agent has a finish schema.
72        #[serde(default, skip_serializing_if = "Option::is_none")]
73        structured: Option<Value>,
74    },
75    /// A final answer failed the finish schema; the model is asked again.
76    FinishRejected {
77        step: u32,
78        reason: String,
79    },
80    RunFailed {
81        error: String,
82    },
83}
84
85impl EventKind {
86    pub fn name(&self) -> &'static str {
87        match self {
88            EventKind::RunStarted { .. } => "run_started",
89            EventKind::ModelCalled { .. } => "model_called",
90            EventKind::ModelFailed { .. } => "model_failed",
91            EventKind::ToolDenied { .. } => "tool_denied",
92            EventKind::ApprovalRequested { .. } => "approval_requested",
93            EventKind::ApprovalDecided { .. } => "approval_decided",
94            EventKind::ToolCompleted { .. } => "tool_completed",
95            EventKind::BudgetExceeded { .. } => "budget_exceeded",
96            EventKind::RunCompleted { .. } => "run_completed",
97            EventKind::FinishRejected { .. } => "finish_rejected",
98            EventKind::RunFailed { .. } => "run_failed",
99        }
100    }
101}