1use std::collections::HashMap;
21
22use serde::{Deserialize, Serialize};
23use serde_json::Value;
24
25use crate::message::{MessageIdentity, SessionMessage, SessionMessageDelta};
26use crate::session::{SessionInfo, SessionStatus};
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(tag = "type")]
31pub enum DaemonMessage {
32 #[serde(rename = "auth.ok")]
33 AuthOk(AuthOkMsg),
34
35 #[serde(rename = "response.ok", rename_all = "camelCase")]
36 ResponseOk {
37 request_id: String,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
39 data: Option<Value>,
40 },
41
42 #[serde(rename = "response.error", rename_all = "camelCase")]
43 ResponseError {
44 request_id: String,
45 error: String,
46 code: ErrorCode,
47 },
48
49 #[serde(rename = "session.list.result", rename_all = "camelCase")]
50 SessionListResult {
51 request_id: String,
52 sessions: Vec<SessionInfo>,
53 },
54
55 #[serde(rename = "models.list.result", rename_all = "camelCase")]
56 ModelsListResult {
57 request_id: String,
58 models: Vec<ModelInfo>,
59 live: bool,
61 #[serde(default)]
64 provider: Option<String>,
65 },
66
67 #[serde(rename = "session.message")]
68 SessionMessage(SessionMessage),
69
70 #[serde(rename = "session.message.delta")]
71 SessionMessageDelta(SessionMessageDelta),
72
73 #[serde(rename = "session.status_change", rename_all = "camelCase")]
74 SessionStatusChange {
75 session_id: String,
76 status: SessionStatus,
77 timestamp: String,
78 },
79
80 #[serde(rename = "session.info_update", rename_all = "camelCase")]
81 SessionInfoUpdate {
82 session: SessionInfo,
83 timestamp: String,
84 },
85
86 #[serde(rename = "scrollback.replay", rename_all = "camelCase")]
87 ScrollbackReplay {
88 session_id: String,
89 messages: Vec<SessionMessage>,
90 #[serde(default)]
93 tail: Option<bool>,
94 #[serde(default)]
96 has_more: Option<bool>,
97 },
98
99 #[serde(rename = "scrollback.page.result", rename_all = "camelCase")]
102 ScrollbackPageResult {
103 request_id: String,
104 session_id: String,
105 messages: Vec<SessionMessage>,
106 has_more: bool,
107 source: String,
110 },
111
112 #[serde(rename = "session.search.result", rename_all = "camelCase")]
113 SessionSearchResult {
114 request_id: String,
115 query: String,
116 sessions: Vec<SessionSearchHit>,
117 workspace_id: String,
118 limit: u32,
119 },
120
121 #[serde(rename = "claude.config.result", rename_all = "camelCase")]
122 ClaudeConfigResult {
123 request_id: String,
124 workdir: String,
125 agents: Vec<ClaudeConfigAgent>,
126 skills: Vec<ClaudeConfigSkill>,
127 mcp_servers: Vec<ClaudeConfigMcpServer>,
128 hooks: Vec<ClaudeConfigHook>,
129 },
130
131 #[serde(rename = "session.export.result", rename_all = "camelCase")]
132 SessionExportResult {
133 request_id: String,
134 manifest: SessionExportManifest,
135 payload: SessionExportPayload,
136 },
137
138 #[serde(rename = "session.import.result", rename_all = "camelCase")]
139 SessionImportResult {
140 request_id: String,
141 new_session_id: String,
142 imported_messages: u32,
143 imported_episodes: u32,
144 imported_turns: u32,
145 pinned_files_written: u32,
146 warnings: Vec<String>,
147 },
148
149 #[serde(rename = "session.ui_request", rename_all = "camelCase")]
156 SessionUiRequest(SessionUiRequestMsg),
157
158 #[serde(rename = "session.ui_resolved", rename_all = "camelCase")]
161 SessionUiResolved {
162 session_id: String,
163 request_id: String,
164 reason: UiResolvedReason,
165 timestamp: String,
166 },
167
168 #[serde(rename = "session.commands.result", rename_all = "camelCase")]
172 SessionCommandsResult {
173 request_id: String,
174 session_id: String,
175 provider_id: String,
176 commands: Vec<ProviderCommand>,
177 },
178
179 #[serde(rename = "settings.schema.result", rename_all = "camelCase")]
181 SettingsSchemaResult {
182 request_id: String,
183 manifest: SettingsManifest,
184 },
185
186 #[serde(rename = "settings.get.result", rename_all = "camelCase")]
188 SettingsGetResult {
189 request_id: String,
190 snapshot: SettingsSnapshot,
191 },
192
193 #[serde(rename = "settings.set.result", rename_all = "camelCase")]
195 SettingsSetResult {
196 request_id: String,
197 ok: bool,
198 snapshot: SettingsSnapshot,
199 errors: Vec<SettingError>,
200 restart_required: bool,
201 },
202
203 #[serde(other)]
205 Unknown,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(rename_all = "camelCase")]
215pub struct SettingsManifest {
216 pub version: u32,
217 pub tabs: Vec<SettingsTab>,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct SettingsTab {
223 pub id: String,
224 pub title: String,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub icon: Option<String>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub description: Option<String>,
229 pub groups: Vec<SettingsGroup>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct SettingsGroup {
235 pub id: String,
236 pub title: String,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub description: Option<String>,
239 pub fields: Vec<SettingField>,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
243#[serde(rename_all = "camelCase")]
244pub struct SettingField {
245 pub key: String,
246 pub label: String,
247 #[serde(default)]
248 pub help: String,
249 pub kind: String,
251 pub backing: String,
253 #[serde(default, skip_serializing_if = "Option::is_none")]
254 pub path: Option<String>,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub env_var: Option<String>,
257 #[serde(default, skip_serializing_if = "Option::is_none")]
258 pub default: Option<Value>,
259 #[serde(default, skip_serializing_if = "Option::is_none")]
260 pub options: Option<Vec<SettingOption>>,
261 #[serde(default, skip_serializing_if = "Option::is_none")]
262 pub min: Option<f64>,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub max: Option<f64>,
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub placeholder: Option<String>,
267 #[serde(default)]
268 pub advanced: bool,
269 #[serde(default)]
270 pub secret: bool,
271 #[serde(default)]
273 pub applies: String,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct SettingOption {
279 pub value: String,
280 pub label: String,
281 #[serde(default, skip_serializing_if = "Option::is_none")]
282 pub description: Option<String>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct SettingsSnapshot {
288 pub values: HashMap<String, SettingState>,
290 pub secrets: HashMap<String, SecretStatus>,
292 pub config_path: String,
293 pub env_path: String,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
297#[serde(rename_all = "camelCase")]
298pub struct SettingState {
299 pub value: Value,
300 pub source: String,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct SecretStatus {
307 pub set: bool,
308 pub source: String,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
313#[serde(rename_all = "camelCase")]
314pub struct SettingError {
315 pub key: String,
316 pub message: String,
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
321#[serde(rename_all = "snake_case")]
322pub enum UiRequestMethod {
323 Select,
324 Confirm,
325 Input,
326 Editor,
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
332#[serde(rename_all = "snake_case")]
333pub enum UiResolvedReason {
334 Answered,
335 Cancelled,
336 Timeout,
337 Interrupted,
338 #[serde(other)]
339 Other,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct SessionUiRequestMsg {
346 pub session_id: String,
347 pub request_id: String,
349 pub method: UiRequestMethod,
350 pub title: String,
351 #[serde(default, skip_serializing_if = "Option::is_none")]
352 pub message: Option<String>,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
355 pub options: Option<Vec<String>>,
356 #[serde(default, skip_serializing_if = "Option::is_none")]
357 pub placeholder: Option<String>,
358 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub prefill: Option<String>,
360 #[serde(default, skip_serializing_if = "Option::is_none")]
362 pub timeout_ms: Option<u64>,
363 pub timestamp: String,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
368#[serde(rename_all = "camelCase")]
369pub struct ProviderCommand {
370 pub name: String,
372 #[serde(default, skip_serializing_if = "Option::is_none")]
373 pub description: Option<String>,
374 #[serde(default, skip_serializing_if = "Option::is_none")]
377 pub source: Option<String>,
378 #[serde(default, skip_serializing_if = "Option::is_none")]
379 pub argument_hint: Option<String>,
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
384#[serde(rename_all = "snake_case")]
385pub enum ClaudeConfigScope {
386 Global,
387 Workdir,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
391#[serde(rename_all = "camelCase")]
392pub struct ClaudeConfigAgent {
393 pub name: String,
394 pub description: Option<String>,
395 pub path: String,
396 pub scope: ClaudeConfigScope,
397 #[serde(default, skip_serializing_if = "Option::is_none")]
398 pub tools: Option<Vec<String>>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct ClaudeConfigSkill {
404 pub name: String,
405 pub description: Option<String>,
406 pub path: String,
407 pub scope: ClaudeConfigScope,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
411#[serde(rename_all = "camelCase")]
412pub struct ClaudeConfigMcpServer {
413 pub name: String,
414 pub scope: ClaudeConfigScope,
415 pub path: String,
416 pub command: Option<String>,
417 pub args: Vec<String>,
418 pub env_keys: Vec<String>,
419 pub url: Option<String>,
420 #[serde(rename = "type")]
421 pub server_type: Option<String>,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub header_keys: Option<Vec<String>>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
428#[serde(rename_all = "camelCase")]
429pub struct SessionExportManifest {
430 pub exported_at: String,
431 pub session: SessionExportMetaSlim,
432 pub workdir: SessionExportWorkdir,
433 pub counts: SessionExportCounts,
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
437#[serde(rename_all = "camelCase")]
438pub struct SessionExportMetaSlim {
439 pub id: String,
440 pub name: String,
441 pub created_at: String,
442 #[serde(default, skip_serializing_if = "Option::is_none")]
443 pub model: Option<String>,
444 #[serde(default, skip_serializing_if = "Option::is_none")]
445 pub mode: Option<String>,
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
449#[serde(rename_all = "camelCase")]
450pub struct SessionExportWorkdir {
451 pub alias: String,
452 pub alias_source: String,
453 pub original_absolute: String,
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize)]
457#[serde(rename_all = "camelCase")]
458pub struct SessionExportCounts {
459 pub messages: u32,
460 pub episodes: u32,
461 pub turns: u32,
462 pub pinned_files: u32,
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize)]
466#[serde(tag = "kind", rename_all = "camelCase")]
467pub enum SessionExportPayload {
468 Inline {
469 bundle: serde_json::Value,
470 size_bytes: u64,
471 },
472 File {
473 path: String,
474 size_bytes: u64,
475 },
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
479#[serde(rename_all = "camelCase")]
480pub struct ClaudeConfigHook {
481 pub event: String,
482 pub scope: ClaudeConfigScope,
483 pub path: String,
484 pub matcher: Option<String>,
485 pub kind: String,
486 pub command: String,
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct ModelInfo {
494 pub value: String,
496 pub display_name: String,
498 #[serde(default, skip_serializing_if = "Option::is_none")]
500 pub description: Option<String>,
501 #[serde(default, skip_serializing_if = "Option::is_none")]
503 pub is_default: Option<bool>,
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize)]
508#[serde(rename_all = "camelCase")]
509pub struct AuthOkMsg {
510 pub identity: MessageIdentity,
511 pub scopes: Vec<String>,
512 #[serde(default, skip_serializing_if = "Option::is_none")]
516 pub protocol_version: Option<u32>,
517 #[serde(default, skip_serializing_if = "Option::is_none")]
521 pub capabilities: Option<Vec<String>>,
522 #[serde(default, skip_serializing_if = "Option::is_none")]
526 pub providers: Option<Vec<String>>,
527}
528
529#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
530#[serde(rename_all = "snake_case")]
531pub enum ErrorCode {
532 Unauthorized,
533 Forbidden,
534 NotFound,
535 InvalidRequest,
536 RateLimited,
537 Internal,
538}
539
540#[derive(Debug, Clone, Serialize, Deserialize)]
542#[serde(rename_all = "camelCase")]
543pub struct SessionSearchHit {
544 pub session_id: String,
545 pub session_name: String,
546 pub workdir: String,
547 pub match_count: u32,
548 pub first_match_at: i64,
549 pub last_match_at: i64,
550 pub aggregate_score: f64,
551 pub snippets: Vec<SessionSearchSnippet>,
552}
553
554#[derive(Debug, Clone, Serialize, Deserialize)]
555#[serde(rename_all = "camelCase")]
556pub struct SessionSearchSnippet {
557 pub episode_id: String,
558 pub kind: SearchSnippetKind,
559 #[serde(default, skip_serializing_if = "Option::is_none")]
560 pub tool_name: Option<String>,
561 pub summary: String,
562 pub excerpt: String,
563 pub created_at: i64,
564 pub score: f64,
565 pub file_paths: Vec<String>,
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
569#[serde(rename_all = "snake_case")]
570pub enum SearchSnippetKind {
571 UserTurn,
572 AssistantTurn,
573 ToolCall,
574 Error,
575}