1use thiserror::Error;
4
5use crate::{TerminalId, WorkflowId};
6
7pub const MAX_TRACE_PAGE_SIZE: usize = 200;
8
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
10pub struct TraceLaneId(pub u64);
11
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
13pub struct TraceSpanId(pub u64);
14
15#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
16pub struct TraceEventId(pub u64);
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct TraceLane {
20 pub lane_id: TraceLaneId,
21 pub workflow_id: WorkflowId,
22 pub agent_id: Option<crate::AgentId>,
23 pub name: String,
24 pub is_agent: bool,
25 pub role: Option<String>,
26 pub harness: Option<String>,
27}
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum TraceSpanStatus {
31 Running,
32 Completed,
33 Exited,
34 Failed,
35 Stopped,
36}
37
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub struct TraceSpan {
40 pub span_id: TraceSpanId,
41 pub lane_id: TraceLaneId,
42 pub title: String,
43 pub started_at: u64,
45 pub ended_at: Option<u64>,
46 pub status: TraceSpanStatus,
47 pub terminal_id: Option<TerminalId>,
48 pub is_live: bool,
51}
52
53#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
54pub struct TraceAnchor {
55 pub terminal_id: TerminalId,
56 pub byte_offset: u64,
58 pub boundary_sizes: Option<Vec<crate::TerminalSize>>,
60}
61
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub enum TraceEventKind {
64 ProcessStarted,
65 ProcessExited,
66 ProcessFailed,
67 ProcessStopped,
68 WorkflowEvent,
69}
70
71#[derive(Clone, Debug, Eq, PartialEq)]
72pub struct TraceEvent {
73 pub event_id: TraceEventId,
74 pub workflow_id: WorkflowId,
75 pub span_id: Option<TraceSpanId>,
76 pub timestamp: u64,
77 pub kind: TraceEventKind,
78 pub message: String,
79 pub anchor: Option<TraceAnchor>,
80}
81
82#[derive(Clone, Debug, Eq, PartialEq)]
83pub struct TraceSummary {
84 pub workflow_id: WorkflowId,
85 pub revision: u64,
87 pub span_count: u64,
88 pub agent_count: u64,
89}
90
91#[derive(Clone, Debug, Eq, PartialEq)]
92pub struct WorkflowTracePage {
93 pub summary: TraceSummary,
94 pub lanes: Vec<TraceLane>,
95 pub spans: Vec<TraceSpan>,
97 pub next_before: Option<TraceSpanId>,
98}
99
100#[derive(Clone, Debug, Eq, PartialEq)]
101pub struct TraceEventsPage {
102 pub workflow_id: WorkflowId,
103 pub span_id: TraceSpanId,
104 pub revision: u64,
105 pub events: Vec<TraceEvent>,
106 pub next_after: Option<TraceEventId>,
107}
108
109#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
111pub struct TraceActivityId(pub u64);
112
113#[derive(Clone, Copy, Debug, Eq, PartialEq)]
114pub enum TraceActivityKind {
115 Tool,
116 Subagent,
117 Model,
118 Note,
119}
120
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122pub enum TraceActivityStatus {
123 Running,
124 Completed,
125 Failed,
126 Interrupted,
127}
128
129#[derive(Clone, Debug, Eq, PartialEq)]
130pub struct TraceActivity {
131 pub activity_id: TraceActivityId,
132 pub span_id: TraceSpanId,
133 pub parent_activity_id: Option<TraceActivityId>,
134 pub kind: TraceActivityKind,
135 pub title: String,
136 pub started_at: Option<u64>,
138 pub ended_at: Option<u64>,
139 pub status: TraceActivityStatus,
140 pub input: String,
141 pub output: String,
142 pub anchor: Option<TraceAnchor>,
143 pub metadata: serde_json::Value,
144 pub input_bytes: Option<u64>,
145 pub output_bytes: Option<u64>,
146 pub input_version: Option<String>,
147 pub output_version: Option<String>,
148}
149
150#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize)]
151#[serde(rename_all = "camelCase")]
152pub struct TraceActivityCounts {
153 pub tools: u64,
154 pub subagents: u64,
155 pub models: u64,
156 pub notes: u64,
157 pub failures: u64,
158}
159
160#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
161#[serde(rename_all = "camelCase")]
162pub struct TraceDetailPage {
163 pub activity_id: u64,
164 pub output: bool,
165 pub offset: u64,
166 pub next_offset: Option<u64>,
167 pub total_bytes: u64,
168 pub text: String,
169 pub version: Option<String>,
170}
171
172#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
173#[serde(rename_all = "camelCase")]
174pub struct TraceStorageStatus {
175 pub payload_bytes: u64,
176 pub payload_files: u64,
177 pub budget_bytes: u64,
178 pub retention_days: u32,
179 pub updated_at: u64,
180 pub pinned: bool,
181 pub clear_generation: u64,
182 pub completed_clear_generation: u64,
183}
184
185#[derive(Clone, Debug, Eq, PartialEq)]
186pub struct TraceActivitiesPage {
187 pub workflow_id: WorkflowId,
188 pub span_id: TraceSpanId,
189 pub revision: u64,
190 pub activities: Vec<TraceActivity>,
191 pub next_after: Option<TraceActivityId>,
192 pub counts: TraceActivityCounts,
193}
194
195#[derive(Debug, Error)]
196pub enum TraceError {
197 #[error("trace page size must be between 1 and {MAX_TRACE_PAGE_SIZE}")]
198 InvalidLimit,
199 #[error("the workflow does not exist")]
200 WorkflowNotFound,
201 #[error("the trace span does not exist")]
202 SpanNotFound,
203 #[error(transparent)]
204 Store(#[from] crate::StoreError),
205}
206
207pub(crate) fn validate_limit(limit: usize) -> Result<(), TraceError> {
208 if !(1..=MAX_TRACE_PAGE_SIZE).contains(&limit) {
209 return Err(TraceError::InvalidLimit);
210 }
211 Ok(())
212}