1use serde::{Deserialize, Serialize};
16use serde_json;
17
18use crate::session::SessionMode;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(tag = "type")]
23pub enum ClientMessage {
24 #[serde(rename = "session.create", rename_all = "camelCase")]
25 SessionCreate {
26 id: String,
27 name: String,
28 workdir: String,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
32 provider_id: Option<String>,
33 },
34
35 #[serde(rename = "session.list", rename_all = "camelCase")]
36 SessionList { id: String },
37
38 #[serde(rename = "models.list", rename_all = "camelCase")]
41 ModelsList {
42 id: String,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
46 provider: Option<String>,
47 },
48
49 #[serde(rename = "session.attach", rename_all = "camelCase")]
50 SessionAttach { id: String, session_id: String },
51
52 #[serde(rename = "session.detach", rename_all = "camelCase")]
53 SessionDetach { id: String, session_id: String },
54
55 #[serde(rename = "session.send", rename_all = "camelCase")]
56 SessionSend {
57 id: String,
58 session_id: String,
59 text: String,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
61 attachments: Option<Vec<Attachment>>,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 priority: Option<SendPriority>,
64 },
65
66 #[serde(rename = "session.interrupt", rename_all = "camelCase")]
67 SessionInterrupt { id: String, session_id: String },
68
69 #[serde(rename = "session.approve", rename_all = "camelCase")]
70 SessionApprove {
71 id: String,
72 session_id: String,
73 approval_id: String,
74 approved: bool,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
80 updated_input: Option<serde_json::Value>,
81 },
82
83 #[serde(rename = "session.ui_response", rename_all = "camelCase")]
90 SessionUiResponse {
91 id: String,
92 session_id: String,
93 request_id: String,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 value: Option<String>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 confirmed: Option<bool>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 cancelled: Option<bool>,
100 },
101
102 #[serde(rename = "session.part_action", rename_all = "camelCase")]
106 SessionPartAction {
107 id: String,
108 session_id: String,
109 message_id: String,
110 action: String,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 data: Option<serde_json::Value>,
113 },
114
115 #[serde(rename = "session.commands", rename_all = "camelCase")]
119 SessionCommands { id: String, session_id: String },
120
121 #[serde(rename = "session.destroy", rename_all = "camelCase")]
122 SessionDestroy { id: String, session_id: String },
123
124 #[serde(rename = "session.set_mode", rename_all = "camelCase")]
125 SessionSetMode {
126 id: String,
127 session_id: String,
128 mode: SessionMode,
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 max_turns: Option<u32>,
131 },
132
133 #[serde(rename = "session.pin", rename_all = "camelCase")]
134 SessionPin {
135 id: String,
136 session_id: String,
137 path: String,
138 },
139
140 #[serde(rename = "session.unpin", rename_all = "camelCase")]
141 SessionUnpin {
142 id: String,
143 session_id: String,
144 path: String,
145 },
146
147 #[serde(rename = "session.rotate", rename_all = "camelCase")]
148 SessionRotate { id: String, session_id: String },
149
150 #[serde(rename = "session.search", rename_all = "camelCase")]
151 SessionSearch {
152 id: String,
153 query: String,
154 #[serde(default, skip_serializing_if = "Option::is_none")]
155 scope: Option<SearchScope>,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 workdir: Option<String>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
159 limit: Option<u32>,
160 },
161
162 #[serde(rename = "session.set_provider", rename_all = "camelCase")]
167 SessionSetProvider {
168 id: String,
169 session_id: String,
170 provider_id: String,
171 },
172
173 #[serde(rename = "session.fork", rename_all = "camelCase")]
180 SessionFork {
181 id: String,
182 session_id: String,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 name: Option<String>,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
186 provider_id: Option<String>,
187 },
188
189 #[serde(rename = "scrollback.page", rename_all = "camelCase")]
193 ScrollbackPage {
194 id: String,
195 session_id: String,
196 before_message_id: String,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 max_bytes: Option<u64>,
200 },
201
202 #[serde(rename = "session.set_model", rename_all = "camelCase")]
203 SessionSetModel {
204 id: String,
205 session_id: String,
206 model: String,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 fallback_model: Option<Option<String>>,
209 },
210
211 #[serde(rename = "session.rename", rename_all = "camelCase")]
212 SessionRename {
213 id: String,
214 session_id: String,
215 name: String,
216 },
217
218 #[serde(rename = "claude.config", rename_all = "camelCase")]
222 ClaudeConfig { id: String, session_id: String },
223
224 #[serde(rename = "session.export", rename_all = "camelCase")]
226 SessionExport {
227 id: String,
228 session_id: String,
229 #[serde(default, skip_serializing_if = "Option::is_none")]
230 include_memory: Option<bool>,
231 #[serde(default, skip_serializing_if = "Option::is_none")]
232 include_pinned_files: Option<bool>,
233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 alias_override: Option<String>,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 to_file: Option<bool>,
237 },
238
239 #[serde(rename = "session.import", rename_all = "camelCase")]
242 SessionImport {
243 id: String,
244 source: SessionImportSource,
245 target_workdir: String,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
247 name_override: Option<String>,
248 #[serde(default, skip_serializing_if = "Option::is_none")]
249 write_pinned_files: Option<bool>,
250 },
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255#[serde(tag = "kind", rename_all = "camelCase")]
256pub enum SessionImportSource {
257 Inline { bundle: serde_json::Value },
258 File { path: String },
259}
260
261impl ClientMessage {
262 #[must_use]
265 pub fn request_id(&self) -> &str {
266 match self {
267 Self::SessionCreate { id, .. }
268 | Self::SessionList { id }
269 | Self::ModelsList { id, .. }
270 | Self::SessionAttach { id, .. }
271 | Self::SessionDetach { id, .. }
272 | Self::SessionSend { id, .. }
273 | Self::SessionInterrupt { id, .. }
274 | Self::SessionApprove { id, .. }
275 | Self::SessionUiResponse { id, .. }
276 | Self::SessionPartAction { id, .. }
277 | Self::SessionCommands { id, .. }
278 | Self::SessionDestroy { id, .. }
279 | Self::SessionSetMode { id, .. }
280 | Self::SessionPin { id, .. }
281 | Self::SessionUnpin { id, .. }
282 | Self::SessionRotate { id, .. }
283 | Self::SessionSearch { id, .. }
284 | Self::SessionSetProvider { id, .. }
285 | Self::SessionFork { id, .. }
286 | Self::ScrollbackPage { id, .. }
287 | Self::SessionSetModel { id, .. }
288 | Self::SessionRename { id, .. }
289 | Self::ClaudeConfig { id, .. }
290 | Self::SessionExport { id, .. }
291 | Self::SessionImport { id, .. } => id,
292 }
293 }
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
298#[serde(rename_all = "camelCase")]
299pub struct Attachment {
300 pub path: String,
301 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub content: Option<String>,
303 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub mime_type: Option<String>,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub data: Option<String>,
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
316#[serde(rename_all = "snake_case")]
317pub enum SendPriority {
318 Now,
319 Next,
320 Later,
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
324#[serde(rename_all = "snake_case")]
325pub enum SearchScope {
326 Workspace,
327 All,
328}