Skip to main content

codeoid_protocol/
session.rs

1//! Session metadata — the `SessionInfo` struct and its satellites.
2
3use serde::{Deserialize, Serialize};
4
5/// Lifecycle status of a session.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum SessionStatus {
9    Idle,
10    Working,
11    WaitingApproval,
12    Error,
13}
14
15/// Execution mode — controls tool approval and autonomous budgeting.
16///
17/// * `Guarded` (daemon default) — Read/Grep/Glob/memory are auto-approved;
18///   Write/Edit/Bash/Agent still ask. (≈ Claude Code's default mode.)
19/// * `Interactive` — every tool call asks for approval, including reads.
20/// * `Autonomous` — every tool auto-approved until the turn budget is exhausted.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "kebab-case")]
23pub enum SessionMode {
24    Interactive,
25    /// Serializes as `"guarded"`. `auto-allow` accepted as a backward-compat
26    /// alias on the wire (the mode was renamed from `auto-allow`).
27    #[serde(alias = "auto-allow")]
28    Guarded,
29    Autonomous,
30}
31
32impl Default for SessionMode {
33    fn default() -> Self {
34        Self::Interactive
35    }
36}
37
38/// Top-level session metadata — broadcast on attach, list, and every
39/// `session.info_update`.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct SessionInfo {
43    pub id: String,
44    pub name: String,
45    pub workdir: String,
46    pub status: SessionStatus,
47    pub created_by: String,
48    pub created_at: String,
49    pub attached_clients: u32,
50
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub mode: Option<SessionMode>,
53
54    /// Remaining turns budget for autonomous mode. `None` = unbounded, `Some(0)` = exhausted.
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub turns_remaining: Option<u32>,
57
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub pinned_files: Option<Vec<String>>,
60
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub agent_uri: Option<String>,
63
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub subagents: Option<Vec<Subagent>>,
66
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub usage: Option<SessionUsage>,
69
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub rotation: Option<RotationInfo>,
72
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub queued_messages: Option<u32>,
75
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub model: Option<String>,
78
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub fallback_model: Option<String>,
81}
82
83/// Rotation telemetry — how many times the backing Claude Code session has
84/// been rolled over to avoid context compaction.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct RotationInfo {
88    pub count: u32,
89    /// Unix ms of last rotation, or null if never rotated.
90    pub last_rotated_at: Option<i64>,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub claude_code_session_id: Option<String>,
93}
94
95/// Cumulative usage totals for a session. Aggregated from each SDK `result` event.
96#[derive(Debug, Clone, Default, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct SessionUsage {
99    pub input_tokens: u64,
100    pub output_tokens: u64,
101    pub cache_read_tokens: u64,
102    pub cache_creation_tokens: u64,
103    pub total_cost_usd: f64,
104    pub num_turns: u32,
105    pub duration_ms: u64,
106
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub recent_turns: Option<Vec<TurnUsage>>,
109
110    /// Max PRIMARY-AGENT context size ever seen on a single turn.
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub peak_input_tokens: Option<u64>,
113
114    /// Most recent turn's PRIMARY-AGENT context size.
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub last_turn_input_tokens: Option<u64>,
117
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub last_turn_output_tokens: Option<u64>,
120
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub last_turn_cost_usd: Option<f64>,
123
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub last_turn_cache_hit_rate: Option<f64>,
126}
127
128/// Per-turn usage record — one entry per SDK `result` event.
129#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct TurnUsage {
132    pub turn_number: u32,
133    pub created_at: i64,
134    pub input_tokens: u64,
135    pub output_tokens: u64,
136    pub cache_read_tokens: u64,
137    pub cache_creation_tokens: u64,
138    pub total_cost_usd: f64,
139    pub duration_ms: u64,
140
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub stop_reason: Option<String>,
143
144    pub total_input_tokens: u64,
145    pub billable_input_tokens: u64,
146    pub cache_hit_rate: f64,
147}
148
149/// Active sub-agent for the identity chain display.
150#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct Subagent {
153    pub agent_id: String,
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub wimse_uri: Option<String>,
156    pub agent_type: String,
157    pub spawned_at: i64,
158    pub active: bool,
159}