1use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use ts_rs::TS;
14
15#[derive(Debug, Clone, Serialize, Deserialize, TS)]
21#[serde(tag = "type", content = "data")]
22#[ts(export)]
23pub enum FrontendEvent {
24 MessageStart(MessageStartPayload),
26 MessageChunk(MessageChunkPayload),
27 MessageComplete(MessageCompletePayload),
28
29 ToolCall(ToolCallPayload),
31 ToolResult(ToolResultPayload),
32
33 ThinkingBlock(ThinkingBlockPayload),
35
36 SubagentStarted(SubagentStartedPayload),
38 SubagentCompleted(SubagentCompletedPayload),
39 NestedToolCall(NestedToolCallPayload),
40 NestedToolResult(NestedToolResultPayload),
41
42 StatusUpdate(StatusUpdatePayload),
44 Progress(ProgressPayload),
45
46 ApprovalRequired(ApprovalRequiredPayload),
48 AskUserRequired(AskUserRequiredPayload),
49 PlanApprovalRequired(PlanApprovalRequiredPayload),
50
51 SessionActivity(SessionActivityPayload),
53
54 Error(ErrorPayload),
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, TS)]
61#[ts(export)]
62pub struct MessageStartPayload {
63 pub session_id: String,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, TS)]
67#[ts(export)]
68pub struct MessageChunkPayload {
69 pub session_id: String,
70 pub content: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, TS)]
74#[ts(export)]
75pub struct MessageCompletePayload {
76 pub session_id: String,
77 pub role: String,
78 pub content: String,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, TS)]
82#[ts(export)]
83pub struct ToolCallPayload {
84 pub session_id: String,
85 pub tool_id: String,
86 pub tool_name: String,
87 #[ts(type = "Record<string, any>")]
88 pub arguments: HashMap<String, serde_json::Value>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, TS)]
92#[ts(export)]
93pub struct ToolResultPayload {
94 pub session_id: String,
95 pub tool_id: String,
96 pub tool_name: String,
97 pub output: String,
98 pub success: bool,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub todos: Option<Vec<TodoItemPayload>>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, TS)]
105#[ts(export)]
106pub struct TodoItemPayload {
107 pub id: String,
108 pub title: String,
109 pub status: String,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub active_form: Option<String>,
112 #[serde(default)]
113 pub children: Vec<TodoChildPayload>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, TS)]
117#[ts(export)]
118pub struct TodoChildPayload {
119 pub title: String,
120 pub status: String,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, TS)]
124#[ts(export)]
125pub struct ThinkingBlockPayload {
126 pub session_id: String,
127 pub content: String,
128 #[serde(default)]
129 pub block_start: bool,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub level: Option<String>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, TS)]
135#[ts(export)]
136pub struct SubagentStartedPayload {
137 pub subagent_id: String,
138 pub agent_type: String,
139 pub description: String,
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub tool_call_id: Option<String>,
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub session_id: Option<String>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, TS)]
147#[ts(export)]
148pub struct SubagentCompletedPayload {
149 pub subagent_id: String,
150 pub success: bool,
151 pub result_summary: String,
152 pub tool_call_count: usize,
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub shallow_warning: Option<String>,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 pub session_id: Option<String>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, TS)]
160#[ts(export)]
161pub struct NestedToolCallPayload {
162 pub subagent_id: String,
163 pub tool_name: String,
164 pub tool_id: String,
165 pub arguments: HashMap<String, serde_json::Value>,
166 #[serde(default = "default_depth")]
167 pub depth: u32,
168}
169
170fn default_depth() -> u32 {
171 1
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize, TS)]
175#[ts(export)]
176pub struct NestedToolResultPayload {
177 pub subagent_id: String,
178 pub tool_name: String,
179 pub tool_id: String,
180 pub success: bool,
181 #[serde(default = "default_depth")]
182 pub depth: u32,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, TS)]
186#[ts(export)]
187pub struct StatusUpdatePayload {
188 pub session_id: String,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub model: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub provider: Option<String>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub input_tokens: Option<u64>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub output_tokens: Option<u64>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub context_usage_pct: Option<f64>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub session_cost_usd: Option<f64>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub git_branch: Option<String>,
203 #[serde(skip_serializing_if = "Option::is_none")]
204 pub autonomy_level: Option<String>,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 pub thinking_level: Option<String>,
207 #[serde(skip_serializing_if = "Option::is_none")]
208 pub file_changes: Option<FileChangesPayload>,
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub todos: Option<Vec<TodoItemPayload>>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, TS)]
214#[ts(export)]
215pub struct FileChangesPayload {
216 pub files: usize,
217 pub additions: u64,
218 pub deletions: u64,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize, TS)]
222#[ts(export)]
223pub struct ProgressPayload {
224 pub session_id: String,
225 pub status: String,
226 pub message: String,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize, TS)]
230#[ts(export)]
231pub struct ApprovalRequiredPayload {
232 pub id: String,
233 pub tool_name: String,
234 pub description: String,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub command: Option<String>,
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub session_id: Option<String>,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, TS)]
242#[ts(export)]
243pub struct AskUserRequiredPayload {
244 pub request_id: String,
245 pub question: String,
246 #[serde(default)]
247 pub options: Vec<String>,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub default: Option<String>,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 pub session_id: Option<String>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize, TS)]
255#[ts(export)]
256pub struct PlanApprovalRequiredPayload {
257 pub request_id: String,
258 pub plan_content: String,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub session_id: Option<String>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize, TS)]
264#[ts(export)]
265pub struct SessionActivityPayload {
266 pub session_id: String,
267 pub running: bool,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, TS)]
271#[ts(export)]
272pub struct ErrorPayload {
273 pub message: String,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub session_id: Option<String>,
276}
277
278#[cfg(test)]
281#[path = "frontend_event_tests.rs"]
282mod tests;