Skip to main content

opi_agent/
session_event.rs

1//! Session-level event protocol (S7.5).
2//!
3//! Events emitted during a session's lifetime that are not tied to a single
4//! agent loop invocation. These are the events serialized in JSON mode and
5//! persisted in session JSONL storage.
6
7use serde::{Deserialize, Serialize};
8
9use crate::event::AgentEvent;
10
11/// Reasons why compaction was triggered (S9.5).
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum CompactionReason {
15    Manual,
16    Threshold,
17    Overflow,
18}
19
20/// Result of a successful compaction (S9.5).
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct CompactionResult {
23    pub summary: String,
24    pub first_kept_entry_id: String,
25    pub tokens_before: u64,
26    pub tokens_after: u64,
27}
28
29/// Thinking/reasoning level configuration (S9.1).
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum ThinkingLevel {
33    None,
34    Low,
35    Medium,
36    High,
37}
38
39/// Events emitted during a session's lifetime (S7.5).
40#[non_exhaustive]
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(tag = "type")]
43pub enum AgentSessionEvent {
44    Agent {
45        #[serde(with = "agent_event_serde")]
46        event: AgentEvent,
47    },
48    QueueUpdate {
49        steering: Vec<String>,
50        follow_up: Vec<String>,
51    },
52    CompactionStart {
53        reason: CompactionReason,
54    },
55    CompactionEnd {
56        reason: CompactionReason,
57        result: Option<CompactionResult>,
58        aborted: bool,
59        will_retry: bool,
60        error_message: Option<String>,
61    },
62    AutoRetryStart {
63        attempt: u32,
64        max_attempts: u32,
65        delay_ms: u64,
66        error_message: String,
67    },
68    AutoRetryEnd {
69        success: bool,
70        attempt: u32,
71        final_error: Option<String>,
72    },
73    SessionInfoChanged {
74        session_id: String,
75        name: Option<String>,
76    },
77    ThinkingLevelChanged {
78        level: ThinkingLevel,
79    },
80    /// Cumulative token usage and (when known) cost breakdown for the
81    /// session. Emitted at the end of a non-interactive run, but may also be
82    /// emitted on demand. The wire `type` is `session_summary` to preserve
83    /// the ad-hoc shape that was used before this variant existed.
84    #[serde(rename = "session_summary")]
85    SessionSummary {
86        session_id: String,
87        model: String,
88        turns: u32,
89        tokens: SessionTokenTotals,
90        #[serde(skip_serializing_if = "Option::is_none")]
91        cost_usd: Option<SessionCostTotals>,
92    },
93}
94
95/// Token totals carried by `AgentSessionEvent::SessionSummary`.
96#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
97pub struct SessionTokenTotals {
98    pub input: u64,
99    pub output: u64,
100    pub cache_read: u64,
101    pub cache_write: u64,
102}
103
104/// Cost totals carried by `AgentSessionEvent::SessionSummary`.
105#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
106pub struct SessionCostTotals {
107    pub input: f64,
108    pub output: f64,
109    pub cache_read: f64,
110    pub cache_write: f64,
111    pub total: f64,
112}
113
114/// Serde bridge for `AgentEvent` (no derives on the source type).
115mod agent_event_serde {
116    use crate::event::AgentEvent;
117    use serde::{Deserialize, Deserializer, Serialize, Serializer};
118
119    pub fn serialize<S>(event: &AgentEvent, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: Serializer,
122    {
123        serde_json::to_value(event)
124            .map_err(serde::ser::Error::custom)
125            .and_then(|v| v.serialize(serializer))
126    }
127
128    pub fn deserialize<'de, D>(deserializer: D) -> Result<AgentEvent, D::Error>
129    where
130        D: Deserializer<'de>,
131    {
132        let v = serde_json::Value::deserialize(deserializer)?;
133        serde_json::from_value(v).map_err(serde::de::Error::custom)
134    }
135}