1use serde::{Deserialize, Serialize};
21use serde_json::Value;
22
23use crate::message::{MessageIdentity, SessionMessage, SessionMessageDelta};
24use crate::session::{SessionInfo, SessionStatus};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(tag = "type")]
29pub enum DaemonMessage {
30 #[serde(rename = "auth.ok")]
31 AuthOk(AuthOkMsg),
32
33 #[serde(rename = "response.ok", rename_all = "camelCase")]
34 ResponseOk {
35 request_id: String,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 data: Option<Value>,
38 },
39
40 #[serde(rename = "response.error", rename_all = "camelCase")]
41 ResponseError {
42 request_id: String,
43 error: String,
44 code: ErrorCode,
45 },
46
47 #[serde(rename = "session.list.result", rename_all = "camelCase")]
48 SessionListResult {
49 request_id: String,
50 sessions: Vec<SessionInfo>,
51 },
52
53 #[serde(rename = "models.list.result", rename_all = "camelCase")]
54 ModelsListResult {
55 request_id: String,
56 models: Vec<ModelInfo>,
57 live: bool,
59 },
60
61 #[serde(rename = "session.message")]
62 SessionMessage(SessionMessage),
63
64 #[serde(rename = "session.message.delta")]
65 SessionMessageDelta(SessionMessageDelta),
66
67 #[serde(rename = "session.status_change", rename_all = "camelCase")]
68 SessionStatusChange {
69 session_id: String,
70 status: SessionStatus,
71 timestamp: String,
72 },
73
74 #[serde(rename = "session.info_update", rename_all = "camelCase")]
75 SessionInfoUpdate {
76 session: SessionInfo,
77 timestamp: String,
78 },
79
80 #[serde(rename = "scrollback.replay", rename_all = "camelCase")]
81 ScrollbackReplay {
82 session_id: String,
83 messages: Vec<SessionMessage>,
84 },
85
86 #[serde(rename = "session.search.result", rename_all = "camelCase")]
87 SessionSearchResult {
88 request_id: String,
89 query: String,
90 sessions: Vec<SessionSearchHit>,
91 workspace_id: String,
92 limit: u32,
93 },
94
95 #[serde(rename = "claude.config.result", rename_all = "camelCase")]
96 ClaudeConfigResult {
97 request_id: String,
98 workdir: String,
99 agents: Vec<ClaudeConfigAgent>,
100 skills: Vec<ClaudeConfigSkill>,
101 mcp_servers: Vec<ClaudeConfigMcpServer>,
102 hooks: Vec<ClaudeConfigHook>,
103 },
104
105 #[serde(rename = "session.export.result", rename_all = "camelCase")]
106 SessionExportResult {
107 request_id: String,
108 manifest: SessionExportManifest,
109 payload: SessionExportPayload,
110 },
111
112 #[serde(rename = "session.import.result", rename_all = "camelCase")]
113 SessionImportResult {
114 request_id: String,
115 new_session_id: String,
116 imported_messages: u32,
117 imported_episodes: u32,
118 imported_turns: u32,
119 pinned_files_written: u32,
120 warnings: Vec<String>,
121 },
122
123 #[serde(other)]
125 Unknown,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
130#[serde(rename_all = "snake_case")]
131pub enum ClaudeConfigScope {
132 Global,
133 Workdir,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct ClaudeConfigAgent {
139 pub name: String,
140 pub description: Option<String>,
141 pub path: String,
142 pub scope: ClaudeConfigScope,
143 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub tools: Option<Vec<String>>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct ClaudeConfigSkill {
150 pub name: String,
151 pub description: Option<String>,
152 pub path: String,
153 pub scope: ClaudeConfigScope,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct ClaudeConfigMcpServer {
159 pub name: String,
160 pub scope: ClaudeConfigScope,
161 pub path: String,
162 pub command: Option<String>,
163 pub args: Vec<String>,
164 pub env_keys: Vec<String>,
165 pub url: Option<String>,
166 #[serde(rename = "type")]
167 pub server_type: Option<String>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
170 pub header_keys: Option<Vec<String>>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct SessionExportManifest {
176 pub exported_at: String,
177 pub session: SessionExportMetaSlim,
178 pub workdir: SessionExportWorkdir,
179 pub counts: SessionExportCounts,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct SessionExportMetaSlim {
185 pub id: String,
186 pub name: String,
187 pub created_at: String,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub model: Option<String>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub mode: Option<String>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(rename_all = "camelCase")]
196pub struct SessionExportWorkdir {
197 pub alias: String,
198 pub alias_source: String,
199 pub original_absolute: String,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct SessionExportCounts {
205 pub messages: u32,
206 pub episodes: u32,
207 pub turns: u32,
208 pub pinned_files: u32,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(tag = "kind", rename_all = "camelCase")]
213pub enum SessionExportPayload {
214 Inline {
215 bundle: serde_json::Value,
216 size_bytes: u64,
217 },
218 File {
219 path: String,
220 size_bytes: u64,
221 },
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct ClaudeConfigHook {
227 pub event: String,
228 pub scope: ClaudeConfigScope,
229 pub path: String,
230 pub matcher: Option<String>,
231 pub kind: String,
232 pub command: String,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct ModelInfo {
240 pub value: String,
242 pub display_name: String,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub description: Option<String>,
247 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub is_default: Option<bool>,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct AuthOkMsg {
256 pub identity: MessageIdentity,
257 pub scopes: Vec<String>,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
262 pub protocol_version: Option<u32>,
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
266#[serde(rename_all = "snake_case")]
267pub enum ErrorCode {
268 Unauthorized,
269 Forbidden,
270 NotFound,
271 InvalidRequest,
272 RateLimited,
273 Internal,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278#[serde(rename_all = "camelCase")]
279pub struct SessionSearchHit {
280 pub session_id: String,
281 pub session_name: String,
282 pub workdir: String,
283 pub match_count: u32,
284 pub first_match_at: i64,
285 pub last_match_at: i64,
286 pub aggregate_score: f64,
287 pub snippets: Vec<SessionSearchSnippet>,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291#[serde(rename_all = "camelCase")]
292pub struct SessionSearchSnippet {
293 pub episode_id: String,
294 pub kind: SearchSnippetKind,
295 #[serde(default, skip_serializing_if = "Option::is_none")]
296 pub tool_name: Option<String>,
297 pub summary: String,
298 pub excerpt: String,
299 pub created_at: i64,
300 pub score: f64,
301 pub file_paths: Vec<String>,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
305#[serde(rename_all = "snake_case")]
306pub enum SearchSnippetKind {
307 UserTurn,
308 AssistantTurn,
309 ToolCall,
310 Error,
311}