1use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::{BTreeMap, BTreeSet};
7
8pub type ViewResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
9
10pub const AGENT_NATIVE_SOURCE: &str = "agent_native_session";
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct TokenSummary {
14 pub group: String,
15 pub input_tokens: i64,
16 pub output_tokens: i64,
17 pub cache_creation_tokens: i64,
18 pub cache_read_tokens: i64,
19 pub total_tokens: i64,
20 pub calls: i64,
21 pub sessions: i64,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct TokenUsageRow {
26 pub id: String,
27 pub llm_call_id: String,
28 pub timestamp_ms: u64,
29 pub pid: Option<u32>,
30 pub comm: Option<String>,
31 pub provider: Option<String>,
32 pub model: Option<String>,
33 pub input_tokens: i64,
34 pub output_tokens: i64,
35 pub cache_creation_tokens: i64,
36 pub cache_read_tokens: i64,
37 pub total_tokens: i64,
38 pub source: String,
39 pub view_source: String,
40 pub confidence: Option<f32>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct LlmCallRow {
45 pub id: String,
46 pub session_id: Option<String>,
47 pub conversation_id: Option<String>,
48 pub start_timestamp_ms: u64,
49 pub end_timestamp_ms: Option<u64>,
50 pub pid: Option<u32>,
51 pub comm: Option<String>,
52 pub provider: Option<String>,
53 pub model: Option<String>,
54 pub call_kind: Option<String>,
55 pub status: String,
56 pub error_type: Option<String>,
57 pub finish_reason: Option<String>,
58 pub host: Option<String>,
59 pub path: Option<String>,
60 pub status_code: Option<u16>,
61 pub input_tokens: i64,
62 pub output_tokens: i64,
63 pub total_tokens: i64,
64 pub request: Value,
65 pub response: Value,
66}
67
68#[derive(Debug, Clone, Copy)]
69pub struct SnapshotOptions {
70 pub audit_limit: usize,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct Snapshot {
75 pub schema_version: u16,
76 pub generated_at: String,
77 pub summary: SnapshotSummary,
78 pub token_summary: Vec<TokenSummary>,
79 pub network_targets: Vec<NetworkTargetRow>,
80 pub process_nodes: Vec<ProcessNodeRow>,
81 pub audit_events: Vec<AuditEventRow>,
82 pub resource_samples: Vec<ResourceSampleRow>,
83 pub sessions: Vec<SessionRow>,
84 pub tool_calls: Vec<ToolCallRow>,
85}
86
87impl Snapshot {
88 pub fn empty(source: impl Into<String>) -> Self {
89 Self {
90 schema_version: 1,
91 generated_at: String::new(),
92 summary: SnapshotSummary::empty(source),
93 token_summary: Vec::new(),
94 network_targets: Vec::new(),
95 process_nodes: Vec::new(),
96 audit_events: Vec::new(),
97 resource_samples: Vec::new(),
98 sessions: Vec::new(),
99 tool_calls: Vec::new(),
100 }
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct SnapshotSummary {
106 pub source: String,
107 pub view_events: i64,
108 pub llm_calls: i64,
109 pub token_usage_rows: i64,
110 pub audit_events: i64,
111 pub sessions: i64,
112 pub input_tokens: i64,
113 pub output_tokens: i64,
114 pub total_tokens: i64,
115 pub start_timestamp_ms: Option<u64>,
116 pub end_timestamp_ms: Option<u64>,
117 pub audit_limit: usize,
118}
119
120impl SnapshotSummary {
121 pub fn empty(source: impl Into<String>) -> Self {
122 Self {
123 source: source.into(),
124 view_events: 0,
125 llm_calls: 0,
126 token_usage_rows: 0,
127 audit_events: 0,
128 sessions: 0,
129 input_tokens: 0,
130 output_tokens: 0,
131 total_tokens: 0,
132 start_timestamp_ms: None,
133 end_timestamp_ms: None,
134 audit_limit: 0,
135 }
136 }
137
138 pub fn duration_s(&self) -> f64 {
139 match (self.start_timestamp_ms, self.end_timestamp_ms) {
140 (Some(start), Some(end)) if end > start => (end - start) as f64 / 1000.0,
141 _ => 0.0,
142 }
143 }
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct NetworkTargetRow {
148 pub pid: Option<u32>,
149 pub comm: Option<String>,
150 pub host: String,
151 pub path: Option<String>,
152 pub count: i64,
153 pub error_count: i64,
154 pub first_timestamp_ms: Option<u64>,
155 pub last_timestamp_ms: Option<u64>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct ResourceSampleRow {
160 pub timestamp_ms: u64,
161 pub pid: Option<u32>,
162 pub comm: Option<String>,
163 pub cpu_percent: Option<f64>,
164 pub rss_mb: Option<i64>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct AuditEventRow {
169 pub id: String,
170 pub timestamp_ms: u64,
171 pub audit_type: String,
172 pub pid: Option<u32>,
173 pub comm: Option<String>,
174 pub subject: Option<String>,
175 pub action: Option<String>,
176 pub target: Option<String>,
177 pub status: Option<String>,
178 pub summary: Option<String>,
179 pub details: Value,
180}
181
182#[derive(Debug, Clone, Default)]
183pub struct AuditCounters {
184 pub process_execs: usize,
185 pub process_exits: usize,
186 pub process_exit_success: usize,
187 pub process_exit_failure: usize,
188 pub file_events: usize,
189 pub network_events: usize,
190 pub unique_files: BTreeSet<String>,
191}
192
193impl AuditCounters {
194 pub fn by_pid<'a>(rows: impl IntoIterator<Item = &'a AuditEventRow>) -> BTreeMap<u32, Self> {
195 let mut by_pid = BTreeMap::new();
196 for row in rows {
197 if let Some(pid) = row.pid {
198 by_pid.entry(pid).or_insert_with(Self::default).observe(row);
199 }
200 }
201 by_pid
202 }
203
204 fn observe(&mut self, row: &AuditEventRow) {
205 match row.audit_type.as_str() {
206 "process" if row.action.as_deref() == Some("exec") => self.process_execs += 1,
207 "process" if row.action.as_deref() == Some("exit") => {
208 self.process_exits += 1;
209 match row.status.as_deref() {
210 Some("success") => self.process_exit_success += 1,
211 Some("failure") => self.process_exit_failure += 1,
212 _ => {}
213 }
214 }
215 "file" => {
216 self.file_events += 1;
217 if let Some(target) = &row.target {
218 self.unique_files.insert(target.clone());
219 }
220 }
221 "network" => self.network_events += 1,
222 _ => {}
223 }
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct ProcessNodeRow {
229 pub id: String,
230 pub pid: u32,
231 pub ppid: Option<u32>,
232 pub root_pid: Option<u32>,
233 pub start_timestamp_ms: Option<u64>,
234 pub end_timestamp_ms: Option<u64>,
235 pub comm: Option<String>,
236 pub command: Option<String>,
237 pub argv: Vec<String>,
238 pub cwd: Option<String>,
239 pub exit_code: Option<i32>,
240 pub status: Option<String>,
241 pub view_source: String,
242 pub confidence: Option<f32>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct ToolCallRow {
247 pub id: String,
248 pub session_id: Option<String>,
249 pub conversation_id: Option<String>,
250 pub timestamp_ms: u64,
251 pub tool_name: Option<String>,
252 pub tool_call_id: Option<String>,
253 pub start_timestamp_ms: Option<u64>,
254 pub end_timestamp_ms: Option<u64>,
255 pub duration_ms: Option<u64>,
256 pub status: Option<String>,
257 pub input: Value,
258 pub output: Value,
259 pub related_pid: Option<u32>,
260 pub related_event_id: Option<String>,
261 pub view_source: String,
262 pub confidence: Option<f32>,
263}
264
265#[derive(Debug, Clone, Default, Serialize, Deserialize)]
266pub struct SessionRow {
267 pub id: String,
268 pub agent_type: String,
269 pub start_timestamp_ms: u64,
270 pub end_timestamp_ms: Option<u64>,
271 pub status: String,
272 pub model: Option<String>,
273 pub input_tokens: i64,
274 pub output_tokens: i64,
275 pub total_tokens: i64,
276 pub view_source: String,
277 pub confidence: Option<f64>,
278 pub attributes: Value,
279}
280
281pub trait ViewSink: Send {
282 fn llm_call(&mut self, _row: &LlmCallRow) -> ViewResult<()> {
283 Ok(())
284 }
285
286 fn token_usage(&mut self, _row: &TokenUsageRow) -> ViewResult<()> {
287 Ok(())
288 }
289
290 fn audit_event(&mut self, _row: &AuditEventRow) -> ViewResult<()> {
291 Ok(())
292 }
293
294 fn process_node(&mut self, _row: &ProcessNodeRow) -> ViewResult<()> {
295 Ok(())
296 }
297
298 fn tool_call(&mut self, _row: &ToolCallRow) -> ViewResult<()> {
299 Ok(())
300 }
301
302 fn network_target(&mut self, _row: &NetworkTargetRow) -> ViewResult<()> {
303 Ok(())
304 }
305
306 fn resource_sample(&mut self, _row: &ResourceSampleRow) -> ViewResult<()> {
307 Ok(())
308 }
309}