1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::budget::{BudgetEnforcementBoundary, BudgetExhaustion, BudgetUsageSnapshot};
5use crate::checkpoint::{
6 OperationKind, OperationState, ReconciliationDecisionKind, ResumeObservation, ToolIdempotency,
7};
8use crate::types::{AgentStatus, ModelCallOperation, TokenUsage, ToolDirective};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(tag = "type", rename_all = "snake_case")]
12pub enum RunEventPayload {
13 RunStarted {
14 input: String,
15 },
16 RunStateChanged {
17 state: String,
18 },
19 AgentStarted,
20 CycleStarted,
21 ModelCallStarted {
22 call_id: String,
23 operation_id: String,
24 attempt: u32,
25 operation: ModelCallOperation,
26 backend: String,
27 model: String,
28 },
29 ModelCallCompleted {
30 call_id: String,
31 operation_id: String,
32 attempt: u32,
33 operation: ModelCallOperation,
34 backend: String,
35 model: String,
36 usage: TokenUsage,
37 },
38 ModelCallFailed {
39 call_id: String,
40 operation_id: String,
41 attempt: u32,
42 operation: ModelCallOperation,
43 backend: String,
44 model: String,
45 outcome: ModelCallFailureOutcome,
46 usage: TokenUsage,
47 error_code: String,
48 },
49 Diagnostic {
50 level: DiagnosticLevel,
51 code: String,
52 details: serde_json::Map<String, Value>,
53 },
54 AssistantDelta {
55 delta: String,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 content_chars: Option<u64>,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 estimated_tokens: Option<u64>,
60 },
61 ReasoningDelta {
62 delta: String,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 reasoning_chars: Option<u64>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 estimated_tokens: Option<u64>,
67 },
68 ModelToolCallStarted {
69 tool_call_id: String,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 tool_call_index: Option<u64>,
72 tool_name: String,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 arguments_chars: Option<u64>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 estimated_tokens: Option<u64>,
77 },
78 ModelToolCallProgress {
79 tool_call_id: String,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 tool_call_index: Option<u64>,
82 tool_name: String,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
84 arguments_chars: Option<u64>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 estimated_tokens: Option<u64>,
87 },
88 ToolCallPlanned {
89 tool_call_id: String,
90 tool_name: String,
91 arguments: Value,
92 },
93 ToolCallStarted {
94 tool_call_id: String,
95 tool_name: String,
96 arguments: Value,
97 },
98 ApprovalRequested {
99 request_id: String,
100 tool_call_id: String,
101 tool_name: String,
102 message: String,
103 },
104 ApprovalResolved {
105 request_id: String,
106 tool_name: String,
107 tool_call_id: String,
108 action: ApprovalAction,
109 },
110 ToolCallCompleted {
111 tool_call_id: String,
112 tool_name: String,
113 status: ToolStatus,
114 directive: ToolDirective,
115 error_code: Option<String>,
116 execution_started: bool,
117 duration_ms: Option<u64>,
118 },
119 MemoryCompactStarted {
120 message_count: usize,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 estimated_tokens: Option<u64>,
123 trigger: MemoryCompactTrigger,
124 configured_threshold: u64,
125 effective_threshold: u64,
126 microcompact_threshold: u64,
127 microcompact_target: u64,
128 candidate_count: usize,
129 estimated_reclaimable_tokens: u64,
130 model_context_window: u64,
131 model_max_output_tokens: Option<u64>,
132 reserved_output_tokens: u64,
133 reserved_output_source: ReservedOutputSource,
134 autocompact_buffer_tokens: u64,
135 },
136 MemoryCompactCompleted {
137 before_count: usize,
138 after_count: usize,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
140 summary_tokens: Option<u64>,
141 mode: MemoryCompactMode,
142 changed: bool,
143 archived_count: usize,
144 reclaimed_tokens: u64,
145 artifact_failure_count: usize,
146 },
147 SubRunStarted {
148 parent_tool_call_id: String,
149 #[serde(default, skip_serializing_if = "Option::is_none")]
150 child_session_id: Option<String>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 task_id: Option<String>,
153 },
154 SubRunCompleted {
155 parent_tool_call_id: String,
156 status: AgentStatus,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 final_output: Option<String>,
159 },
160 HandoffStarted {
161 source_agent: String,
162 target_agent: String,
163 tool_call_id: String,
164 },
165 HandoffCompleted {
166 source_agent: String,
167 target_agent: String,
168 tool_call_id: String,
169 },
170 SessionPersisted,
171 BudgetSnapshot {
172 enforcement_boundary: BudgetEnforcementBoundary,
173 budget_usage: BudgetUsageSnapshot,
174 },
175 BudgetExhausted {
176 enforcement_boundary: BudgetEnforcementBoundary,
177 budget_usage: BudgetUsageSnapshot,
178 budget_exhaustion: BudgetExhaustion,
179 },
180 CheckpointCreated {
181 checkpoint_key: String,
182 resume_attempt: u64,
183 },
184 CheckpointResumed {
185 checkpoint_key: String,
186 resume_attempt: u64,
187 },
188 OperationReplayed {
189 checkpoint_key: String,
190 operation_id: String,
191 operation_kind: OperationKind,
192 receipt_state: OperationState,
193 },
194 OperationAmbiguous {
195 checkpoint_key: String,
196 operation_id: String,
197 operation_kind: OperationKind,
198 risk: String,
199 idempotency_support: Option<ToolIdempotency>,
200 },
201 ReconciliationRequired {
202 checkpoint_key: String,
203 operation_id: String,
204 operation_kind: OperationKind,
205 interruption_reason: String,
206 resume_observation: ResumeObservation,
207 },
208 ModelRetryDuplicateRisk {
209 checkpoint_key: String,
210 operation_id: String,
211 operation_kind: OperationKind,
212 risk: String,
213 },
214 ReconciliationResolved {
215 checkpoint_key: String,
216 operation_id: String,
217 operation_kind: OperationKind,
218 decision: ReconciliationDecisionKind,
219 },
220 RunCompleted {
221 status: AgentStatus,
222 },
223 RunFailed {
224 error: String,
225 },
226 RunCancelled {
227 reason: String,
228 },
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(rename_all = "snake_case")]
233pub enum MemoryCompactTrigger {
234 MicroThreshold,
235 FullThreshold,
236 PromptTooLong,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
240#[serde(rename_all = "snake_case")]
241pub enum MemoryCompactMode {
242 None,
243 Micro,
244 Structural,
245 Summary,
246 Emergency,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
250#[serde(rename_all = "snake_case")]
251pub enum ReservedOutputSource {
252 ModelSettings,
253 TaskMetadata,
254 FrameworkFallback,
255 FrameworkFallbackCappedByModelCapability,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
259#[serde(rename_all = "snake_case")]
260pub enum ModelCallFailureOutcome {
261 Definitive,
262 Ambiguous,
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
266#[serde(rename_all = "snake_case")]
267pub enum DiagnosticLevel {
268 Debug,
269 Info,
270 Warning,
271 Error,
272}
273
274impl DiagnosticLevel {
275 pub fn as_str(self) -> &'static str {
276 match self {
277 Self::Debug => "debug",
278 Self::Info => "info",
279 Self::Warning => "warning",
280 Self::Error => "error",
281 }
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
286#[serde(rename_all = "snake_case")]
287pub enum ApprovalAction {
288 Allow,
289 AllowSession,
290 Deny,
291 Timeout,
292}
293
294impl ApprovalAction {
295 pub fn parse(value: &str) -> Option<Self> {
296 match value {
297 "allow" => Some(Self::Allow),
298 "allow_session" => Some(Self::AllowSession),
299 "deny" => Some(Self::Deny),
300 "timeout" => Some(Self::Timeout),
301 _ => None,
302 }
303 }
304
305 pub fn as_str(self) -> &'static str {
306 match self {
307 Self::Allow => "allow",
308 Self::AllowSession => "allow_session",
309 Self::Deny => "deny",
310 Self::Timeout => "timeout",
311 }
312 }
313
314 pub fn is_approved(self) -> bool {
315 matches!(self, Self::Allow | Self::AllowSession)
316 }
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
320#[serde(rename_all = "snake_case")]
321pub enum ToolStatus {
322 Started,
323 Success,
324 Error,
325 WaitResponse,
326 Running,
327 PendingCompress,
328}
329
330#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
331pub struct AgentErrorPayload {
332 pub message: String,
333 pub code: Option<String>,
334}
335
336impl AgentErrorPayload {
337 pub fn new(message: impl Into<String>) -> Self {
338 Self {
339 message: message.into(),
340 code: None,
341 }
342 }
343}