1use std::collections::HashMap;
2use std::fmt::Display;
3use std::path::PathBuf;
4
5use crate::config_types::ReasoningEffort;
6use crate::config_types::ReasoningSummary;
7use crate::config_types::SandboxMode;
8use crate::config_types::Verbosity;
9use crate::protocol::AskForApproval;
10use crate::protocol::EventMsg;
11use crate::protocol::FileChange;
12use crate::protocol::ReviewDecision;
13use crate::protocol::SandboxPolicy;
14use crate::protocol::TurnAbortReason;
15use mcp_types::JSONRPCNotification;
16use mcp_types::RequestId;
17use serde::Deserialize;
18use serde::Serialize;
19use strum_macros::Display;
20use ts_rs::TS;
21use uuid::Uuid;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, TS, Hash)]
24#[ts(type = "string")]
25pub struct ConversationId {
26 uuid: Uuid,
27}
28
29impl ConversationId {
30 pub fn new() -> Self {
31 Self {
32 uuid: Uuid::new_v4(),
33 }
34 }
35
36 pub fn from_string(s: &str) -> Result<Self, uuid::Error> {
37 Ok(Self {
38 uuid: Uuid::parse_str(s)?,
39 })
40 }
41}
42
43impl From<Uuid> for ConversationId {
44 fn from(uuid: Uuid) -> Self {
45 Self { uuid }
46 }
47}
48
49impl From<ConversationId> for Uuid {
50 fn from(id: ConversationId) -> Self {
51 id.uuid
52 }
53}
54
55impl Default for ConversationId {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl Display for ConversationId {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 write!(f, "{}", self.uuid)
64 }
65}
66
67impl Serialize for ConversationId {
68 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69 where
70 S: serde::Serializer,
71 {
72 serializer.collect_str(&self.uuid)
73 }
74}
75
76impl<'de> Deserialize<'de> for ConversationId {
77 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78 where
79 D: serde::Deserializer<'de>,
80 {
81 let value = String::deserialize(deserializer)?;
82 let uuid = Uuid::parse_str(&value).map_err(serde::de::Error::custom)?;
83 Ok(Self { uuid })
84 }
85}
86
87#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, TS)]
88#[ts(type = "string")]
89pub struct GitSha(pub String);
90
91impl GitSha {
92 pub fn new(sha: &str) -> Self {
93 Self(sha.to_string())
94 }
95}
96
97#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display, TS)]
98#[serde(rename_all = "lowercase")]
99pub enum AuthMode {
100 ApiKey,
101 ChatGPT,
102}
103
104#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
106#[serde(tag = "method", rename_all = "camelCase")]
107pub enum ClientRequest {
108 Initialize {
109 #[serde(rename = "id")]
110 #[ts(type = "string | number")]
111 request_id: RequestId,
112 params: InitializeParams,
113 },
114 NewConversation {
115 #[serde(rename = "id")]
116 #[ts(type = "string | number")]
117 request_id: RequestId,
118 params: NewConversationParams,
119 },
120 ListConversations {
122 #[serde(rename = "id")]
123 #[ts(type = "string | number")]
124 request_id: RequestId,
125 params: ListConversationsParams,
126 },
127 ResumeConversation {
129 #[serde(rename = "id")]
130 #[ts(type = "string | number")]
131 request_id: RequestId,
132 params: ResumeConversationParams,
133 },
134 ArchiveConversation {
135 #[serde(rename = "id")]
136 #[ts(type = "string | number")]
137 request_id: RequestId,
138 params: ArchiveConversationParams,
139 },
140 SendUserMessage {
141 #[serde(rename = "id")]
142 #[ts(type = "string | number")]
143 request_id: RequestId,
144 params: SendUserMessageParams,
145 },
146 SendUserTurn {
147 #[serde(rename = "id")]
148 #[ts(type = "string | number")]
149 request_id: RequestId,
150 params: SendUserTurnParams,
151 },
152 InterruptConversation {
153 #[serde(rename = "id")]
154 #[ts(type = "string | number")]
155 request_id: RequestId,
156 params: InterruptConversationParams,
157 },
158 AddConversationListener {
159 #[serde(rename = "id")]
160 #[ts(type = "string | number")]
161 request_id: RequestId,
162 params: AddConversationListenerParams,
163 },
164 RemoveConversationListener {
165 #[serde(rename = "id")]
166 #[ts(type = "string | number")]
167 request_id: RequestId,
168 params: RemoveConversationListenerParams,
169 },
170 GitDiffToRemote {
171 #[serde(rename = "id")]
172 #[ts(type = "string | number")]
173 request_id: RequestId,
174 params: GitDiffToRemoteParams,
175 },
176 LoginApiKey {
177 #[serde(rename = "id")]
178 #[ts(type = "string | number")]
179 request_id: RequestId,
180 params: LoginApiKeyParams,
181 },
182 LoginChatGpt {
183 #[serde(rename = "id")]
184 #[ts(type = "string | number")]
185 request_id: RequestId,
186
187 #[ts(type = "undefined")]
188 #[serde(skip_serializing_if = "Option::is_none")]
189 params: Option<()>,
190 },
191 CancelLoginChatGpt {
192 #[serde(rename = "id")]
193 #[ts(type = "string | number")]
194 request_id: RequestId,
195 params: CancelLoginChatGptParams,
196 },
197 LogoutChatGpt {
198 #[serde(rename = "id")]
199 #[ts(type = "string | number")]
200 request_id: RequestId,
201
202 #[ts(type = "undefined")]
203 #[serde(skip_serializing_if = "Option::is_none")]
204 params: Option<()>,
205 },
206 GetAuthStatus {
207 #[serde(rename = "id")]
208 #[ts(type = "string | number")]
209 request_id: RequestId,
210 params: GetAuthStatusParams,
211 },
212 GetUserSavedConfig {
213 #[serde(rename = "id")]
214 #[ts(type = "string | number")]
215 request_id: RequestId,
216
217 #[ts(type = "undefined")]
218 #[serde(skip_serializing_if = "Option::is_none")]
219 params: Option<()>,
220 },
221 SetDefaultModel {
222 #[serde(rename = "id")]
223 #[ts(type = "string | number")]
224 request_id: RequestId,
225 params: SetDefaultModelParams,
226 },
227 GetUserAgent {
228 #[serde(rename = "id")]
229 #[ts(type = "string | number")]
230 request_id: RequestId,
231
232 #[ts(type = "undefined")]
233 #[serde(skip_serializing_if = "Option::is_none")]
234 params: Option<()>,
235 },
236 UserInfo {
237 #[serde(rename = "id")]
238 #[ts(type = "string | number")]
239 request_id: RequestId,
240
241 #[ts(type = "undefined")]
242 #[serde(skip_serializing_if = "Option::is_none")]
243 params: Option<()>,
244 },
245 FuzzyFileSearch {
246 #[serde(rename = "id")]
247 #[ts(type = "string | number")]
248 request_id: RequestId,
249 params: FuzzyFileSearchParams,
250 },
251 ExecOneOffCommand {
253 #[serde(rename = "id")]
254 #[ts(type = "string | number")]
255 request_id: RequestId,
256 params: ExecOneOffCommandParams,
257 },
258}
259
260#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, TS)]
261#[serde(rename_all = "camelCase")]
262pub struct InitializeParams {
263 pub client_info: ClientInfo,
264}
265
266#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, TS)]
267#[serde(rename_all = "camelCase")]
268pub struct ClientInfo {
269 pub name: String,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 pub title: Option<String>,
272 pub version: String,
273}
274
275#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
276#[serde(rename_all = "camelCase")]
277pub struct InitializeResponse {
278 pub user_agent: String,
279}
280
281#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, TS)]
282#[serde(rename_all = "camelCase")]
283pub struct NewConversationParams {
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub model: Option<String>,
287
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub profile: Option<String>,
291
292 #[serde(skip_serializing_if = "Option::is_none")]
295 pub cwd: Option<String>,
296
297 #[serde(skip_serializing_if = "Option::is_none")]
300 pub approval_policy: Option<AskForApproval>,
301
302 #[serde(skip_serializing_if = "Option::is_none")]
304 pub sandbox: Option<SandboxMode>,
305
306 #[serde(skip_serializing_if = "Option::is_none")]
309 pub config: Option<HashMap<String, serde_json::Value>>,
310
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub base_instructions: Option<String>,
314
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub include_plan_tool: Option<bool>,
318
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub include_apply_patch_tool: Option<bool>,
322}
323
324#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
325#[serde(rename_all = "camelCase")]
326pub struct NewConversationResponse {
327 pub conversation_id: ConversationId,
328 pub model: String,
329 #[serde(skip_serializing_if = "Option::is_none")]
331 pub reasoning_effort: Option<ReasoningEffort>,
332 pub rollout_path: PathBuf,
333}
334
335#[derive(Serialize, Deserialize, Debug, Clone, TS)]
336#[serde(rename_all = "camelCase")]
337pub struct ResumeConversationResponse {
338 pub conversation_id: ConversationId,
339 pub model: String,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub initial_messages: Option<Vec<EventMsg>>,
342}
343
344#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, TS)]
345#[serde(rename_all = "camelCase")]
346pub struct ListConversationsParams {
347 #[serde(skip_serializing_if = "Option::is_none")]
349 pub page_size: Option<usize>,
350 #[serde(skip_serializing_if = "Option::is_none")]
352 pub cursor: Option<String>,
353}
354
355#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
356#[serde(rename_all = "camelCase")]
357pub struct ConversationSummary {
358 pub conversation_id: ConversationId,
359 pub path: PathBuf,
360 pub preview: String,
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub timestamp: Option<String>,
364}
365
366#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
367#[serde(rename_all = "camelCase")]
368pub struct ListConversationsResponse {
369 pub items: Vec<ConversationSummary>,
370 #[serde(skip_serializing_if = "Option::is_none")]
373 pub next_cursor: Option<String>,
374}
375
376#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
377#[serde(rename_all = "camelCase")]
378pub struct ResumeConversationParams {
379 pub path: PathBuf,
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub overrides: Option<NewConversationParams>,
384}
385
386#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
387#[serde(rename_all = "camelCase")]
388pub struct AddConversationSubscriptionResponse {
389 pub subscription_id: Uuid,
390}
391
392#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
394#[serde(rename_all = "camelCase")]
395pub struct ArchiveConversationParams {
396 pub conversation_id: ConversationId,
397 pub rollout_path: PathBuf,
398}
399
400#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
401#[serde(rename_all = "camelCase")]
402pub struct ArchiveConversationResponse {}
403
404#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
405#[serde(rename_all = "camelCase")]
406pub struct RemoveConversationSubscriptionResponse {}
407
408#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
409#[serde(rename_all = "camelCase")]
410pub struct LoginApiKeyParams {
411 pub api_key: String,
412}
413
414#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
415#[serde(rename_all = "camelCase")]
416pub struct LoginApiKeyResponse {}
417
418#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
419#[serde(rename_all = "camelCase")]
420pub struct LoginChatGptResponse {
421 pub login_id: Uuid,
422 pub auth_url: String,
424}
425
426#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
427#[serde(rename_all = "camelCase")]
428pub struct GitDiffToRemoteResponse {
429 pub sha: GitSha,
430 pub diff: String,
431}
432
433#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
434#[serde(rename_all = "camelCase")]
435pub struct CancelLoginChatGptParams {
436 pub login_id: Uuid,
437}
438
439#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
440#[serde(rename_all = "camelCase")]
441pub struct GitDiffToRemoteParams {
442 pub cwd: PathBuf,
443}
444
445#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
446#[serde(rename_all = "camelCase")]
447pub struct CancelLoginChatGptResponse {}
448
449#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
450#[serde(rename_all = "camelCase")]
451pub struct LogoutChatGptParams {}
452
453#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
454#[serde(rename_all = "camelCase")]
455pub struct LogoutChatGptResponse {}
456
457#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
458#[serde(rename_all = "camelCase")]
459pub struct GetAuthStatusParams {
460 #[serde(skip_serializing_if = "Option::is_none")]
462 pub include_token: Option<bool>,
463 #[serde(skip_serializing_if = "Option::is_none")]
465 pub refresh_token: Option<bool>,
466}
467
468#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
469#[serde(rename_all = "camelCase")]
470pub struct ExecOneOffCommandParams {
471 pub command: Vec<String>,
473 pub timeout_ms: Option<u64>,
476 #[serde(skip_serializing_if = "Option::is_none")]
478 pub cwd: Option<PathBuf>,
479 #[serde(skip_serializing_if = "Option::is_none")]
481 pub sandbox_policy: Option<SandboxPolicy>,
482}
483
484#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
485#[serde(rename_all = "camelCase")]
486pub struct ExecArbitraryCommandResponse {
487 pub exit_code: i32,
488 pub stdout: String,
489 pub stderr: String,
490}
491
492#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
493#[serde(rename_all = "camelCase")]
494pub struct GetAuthStatusResponse {
495 #[serde(skip_serializing_if = "Option::is_none")]
496 pub auth_method: Option<AuthMode>,
497 #[serde(skip_serializing_if = "Option::is_none")]
498 pub auth_token: Option<String>,
499
500 #[serde(skip_serializing_if = "Option::is_none")]
504 pub requires_openai_auth: Option<bool>,
505}
506
507#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
508#[serde(rename_all = "camelCase")]
509pub struct GetUserAgentResponse {
510 pub user_agent: String,
511}
512
513#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
514#[serde(rename_all = "camelCase")]
515pub struct UserInfoResponse {
516 pub alleged_user_email: Option<String>,
521}
522
523#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
524#[serde(rename_all = "camelCase")]
525pub struct GetUserSavedConfigResponse {
526 pub config: UserSavedConfig,
527}
528
529#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
530#[serde(rename_all = "camelCase")]
531pub struct SetDefaultModelParams {
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub model: Option<String>,
535 #[serde(skip_serializing_if = "Option::is_none")]
538 pub reasoning_effort: Option<ReasoningEffort>,
539}
540
541#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
542#[serde(rename_all = "camelCase")]
543pub struct SetDefaultModelResponse {}
544
545#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, TS)]
549#[serde(rename_all = "camelCase")]
550pub struct UserSavedConfig {
551 #[serde(skip_serializing_if = "Option::is_none")]
553 pub approval_policy: Option<AskForApproval>,
554 #[serde(skip_serializing_if = "Option::is_none")]
555 pub sandbox_mode: Option<SandboxMode>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 pub sandbox_settings: Option<SandboxSettings>,
558
559 #[serde(skip_serializing_if = "Option::is_none")]
561 pub model: Option<String>,
562 #[serde(skip_serializing_if = "Option::is_none")]
563 pub model_reasoning_effort: Option<ReasoningEffort>,
564 #[serde(skip_serializing_if = "Option::is_none")]
565 pub model_reasoning_summary: Option<ReasoningSummary>,
566 #[serde(skip_serializing_if = "Option::is_none")]
567 pub model_verbosity: Option<Verbosity>,
568
569 #[serde(skip_serializing_if = "Option::is_none")]
571 pub tools: Option<Tools>,
572
573 #[serde(skip_serializing_if = "Option::is_none")]
575 pub profile: Option<String>,
576 #[serde(default)]
577 pub profiles: HashMap<String, Profile>,
578}
579
580#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, TS)]
582#[serde(rename_all = "camelCase")]
583pub struct Profile {
584 pub model: Option<String>,
585 pub model_provider: Option<String>,
588 pub approval_policy: Option<AskForApproval>,
589 pub model_reasoning_effort: Option<ReasoningEffort>,
590 pub model_reasoning_summary: Option<ReasoningSummary>,
591 pub model_verbosity: Option<Verbosity>,
592 pub chatgpt_base_url: Option<String>,
593}
594#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, TS)]
596#[serde(rename_all = "camelCase")]
597pub struct Tools {
598 #[serde(skip_serializing_if = "Option::is_none")]
599 pub web_search: Option<bool>,
600 #[serde(skip_serializing_if = "Option::is_none")]
601 pub view_image: Option<bool>,
602}
603
604#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, TS)]
606#[serde(rename_all = "camelCase")]
607pub struct SandboxSettings {
608 #[serde(default)]
609 pub writable_roots: Vec<PathBuf>,
610 #[serde(skip_serializing_if = "Option::is_none")]
611 pub network_access: Option<bool>,
612 #[serde(skip_serializing_if = "Option::is_none")]
613 pub exclude_tmpdir_env_var: Option<bool>,
614 #[serde(skip_serializing_if = "Option::is_none")]
615 pub exclude_slash_tmp: Option<bool>,
616}
617
618#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
619#[serde(rename_all = "camelCase")]
620pub struct SendUserMessageParams {
621 pub conversation_id: ConversationId,
622 pub items: Vec<InputItem>,
623}
624
625#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
626#[serde(rename_all = "camelCase")]
627pub struct SendUserTurnParams {
628 pub conversation_id: ConversationId,
629 pub items: Vec<InputItem>,
630 pub cwd: PathBuf,
631 pub approval_policy: AskForApproval,
632 pub sandbox_policy: SandboxPolicy,
633 pub model: String,
634 #[serde(skip_serializing_if = "Option::is_none")]
635 pub effort: Option<ReasoningEffort>,
636 pub summary: ReasoningSummary,
637}
638
639#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
640#[serde(rename_all = "camelCase")]
641pub struct SendUserTurnResponse {}
642
643#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
644#[serde(rename_all = "camelCase")]
645pub struct InterruptConversationParams {
646 pub conversation_id: ConversationId,
647}
648
649#[derive(Serialize, Deserialize, Debug, Clone, TS)]
650#[serde(rename_all = "camelCase")]
651pub struct InterruptConversationResponse {
652 pub abort_reason: TurnAbortReason,
653}
654
655#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
656#[serde(rename_all = "camelCase")]
657pub struct SendUserMessageResponse {}
658
659#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
660#[serde(rename_all = "camelCase")]
661pub struct AddConversationListenerParams {
662 pub conversation_id: ConversationId,
663}
664
665#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
666#[serde(rename_all = "camelCase")]
667pub struct RemoveConversationListenerParams {
668 pub subscription_id: Uuid,
669}
670
671#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
672#[serde(rename_all = "camelCase")]
673#[serde(tag = "type", content = "data")]
674pub enum InputItem {
675 Text {
676 text: String,
677 },
678 Image {
680 image_url: String,
681 },
682
683 LocalImage {
686 path: PathBuf,
687 },
688}
689
690pub const APPLY_PATCH_APPROVAL_METHOD: &str = "applyPatchApproval";
693pub const EXEC_COMMAND_APPROVAL_METHOD: &str = "execCommandApproval";
694
695#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
697#[serde(tag = "method", rename_all = "camelCase")]
698pub enum ServerRequest {
699 ApplyPatchApproval {
701 #[serde(rename = "id")]
702 #[ts(type = "string | number")]
703 request_id: RequestId,
704 params: ApplyPatchApprovalParams,
705 },
706 ExecCommandApproval {
708 #[serde(rename = "id")]
709 #[ts(type = "string | number")]
710 request_id: RequestId,
711 params: ExecCommandApprovalParams,
712 },
713}
714
715#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
716pub struct ApplyPatchApprovalParams {
717 pub conversation_id: ConversationId,
718 pub call_id: String,
721 pub file_changes: HashMap<PathBuf, FileChange>,
722 #[serde(skip_serializing_if = "Option::is_none")]
724 pub reason: Option<String>,
725 #[serde(skip_serializing_if = "Option::is_none")]
728 pub grant_root: Option<PathBuf>,
729}
730
731#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
732pub struct ExecCommandApprovalParams {
733 pub conversation_id: ConversationId,
734 pub call_id: String,
737 pub command: Vec<String>,
738 pub cwd: PathBuf,
739 #[serde(skip_serializing_if = "Option::is_none")]
740 pub reason: Option<String>,
741}
742
743#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
744pub struct ExecCommandApprovalResponse {
745 pub decision: ReviewDecision,
746}
747
748#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
749pub struct ApplyPatchApprovalResponse {
750 pub decision: ReviewDecision,
751}
752
753#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
754#[serde(rename_all = "camelCase")]
755#[ts(rename_all = "camelCase")]
756pub struct FuzzyFileSearchParams {
757 pub query: String,
758 pub roots: Vec<String>,
759 #[serde(skip_serializing_if = "Option::is_none")]
761 pub cancellation_token: Option<String>,
762}
763
764#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
766pub struct FuzzyFileSearchResult {
767 pub root: String,
768 pub path: String,
769 pub file_name: String,
770 pub score: u32,
771 #[serde(skip_serializing_if = "Option::is_none")]
772 pub indices: Option<Vec<u32>>,
773}
774
775#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
776pub struct FuzzyFileSearchResponse {
777 pub files: Vec<FuzzyFileSearchResult>,
778}
779
780#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
781#[serde(rename_all = "camelCase")]
782pub struct LoginChatGptCompleteNotification {
783 pub login_id: Uuid,
784 pub success: bool,
785 #[serde(skip_serializing_if = "Option::is_none")]
786 pub error: Option<String>,
787}
788
789#[derive(Serialize, Deserialize, Debug, Clone, TS)]
790#[serde(rename_all = "camelCase")]
791pub struct SessionConfiguredNotification {
792 pub session_id: ConversationId,
794
795 pub model: String,
797
798 #[serde(skip_serializing_if = "Option::is_none")]
800 pub reasoning_effort: Option<ReasoningEffort>,
801
802 pub history_log_id: u64,
804
805 pub history_entry_count: usize,
807
808 #[serde(skip_serializing_if = "Option::is_none")]
811 pub initial_messages: Option<Vec<EventMsg>>,
812
813 pub rollout_path: PathBuf,
814}
815
816#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
817#[serde(rename_all = "camelCase")]
818pub struct AuthStatusChangeNotification {
819 #[serde(skip_serializing_if = "Option::is_none")]
821 pub auth_method: Option<AuthMode>,
822}
823
824#[derive(Serialize, Deserialize, Debug, Clone, TS, Display)]
826#[serde(tag = "method", content = "params", rename_all = "camelCase")]
827#[strum(serialize_all = "camelCase")]
828pub enum ServerNotification {
829 AuthStatusChange(AuthStatusChangeNotification),
831
832 LoginChatGptComplete(LoginChatGptCompleteNotification),
834
835 SessionConfigured(SessionConfiguredNotification),
837}
838
839impl ServerNotification {
840 pub fn to_params(self) -> Result<serde_json::Value, serde_json::Error> {
841 match self {
842 ServerNotification::AuthStatusChange(params) => serde_json::to_value(params),
843 ServerNotification::LoginChatGptComplete(params) => serde_json::to_value(params),
844 ServerNotification::SessionConfigured(params) => serde_json::to_value(params),
845 }
846 }
847}
848
849impl TryFrom<JSONRPCNotification> for ServerNotification {
850 type Error = serde_json::Error;
851
852 fn try_from(value: JSONRPCNotification) -> Result<Self, Self::Error> {
853 serde_json::from_value(serde_json::to_value(value)?)
854 }
855}
856
857#[derive(Serialize, Deserialize, Debug, Clone, TS, Display)]
859#[serde(tag = "method", content = "params", rename_all = "camelCase")]
860#[strum(serialize_all = "camelCase")]
861pub enum ClientNotification {
862 Initialized,
863}
864
865#[cfg(test)]
866mod tests {
867 use super::*;
868 use anyhow::Result;
869 use pretty_assertions::assert_eq;
870 use serde_json::json;
871
872 #[test]
873 fn serialize_new_conversation() -> Result<()> {
874 let request = ClientRequest::NewConversation {
875 request_id: RequestId::Integer(42),
876 params: NewConversationParams {
877 model: Some("gpt-5.1-codex".to_string()),
878 profile: None,
879 cwd: None,
880 approval_policy: Some(AskForApproval::OnRequest),
881 sandbox: None,
882 config: None,
883 base_instructions: None,
884 include_plan_tool: None,
885 include_apply_patch_tool: None,
886 },
887 };
888 assert_eq!(
889 json!({
890 "method": "newConversation",
891 "id": 42,
892 "params": {
893 "model": "gpt-5.1-codex",
894 "approvalPolicy": "on-request"
895 }
896 }),
897 serde_json::to_value(&request)?,
898 );
899 Ok(())
900 }
901
902 #[test]
903 fn test_conversation_id_default_is_not_zeroes() {
904 let id = ConversationId::default();
905 assert_ne!(id.uuid, Uuid::nil());
906 }
907
908 #[test]
909 fn conversation_id_serializes_as_plain_string() -> Result<()> {
910 let id = ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
911
912 assert_eq!(
913 json!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
914 serde_json::to_value(id)?
915 );
916 Ok(())
917 }
918
919 #[test]
920 fn conversation_id_deserializes_from_plain_string() -> Result<()> {
921 let id: ConversationId =
922 serde_json::from_value(json!("67e55044-10b1-426f-9247-bb680e5fe0c8"))?;
923
924 assert_eq!(
925 ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?,
926 id,
927 );
928 Ok(())
929 }
930
931 #[test]
932 fn serialize_client_notification() -> Result<()> {
933 let notification = ClientNotification::Initialized;
934 assert_eq!(
936 json!({
937 "method": "initialized",
938 }),
939 serde_json::to_value(¬ification)?,
940 );
941 Ok(())
942 }
943}