1use std::rc::Rc;
7use std::{fmt, path::PathBuf, sync::Arc};
8
9use anyhow::Result;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use serde_json::value::RawValue;
13
14use crate::ext::ExtRequest;
15use crate::{ContentBlock, Error, ExtNotification, Plan, SessionId, ToolCall, ToolCallUpdate};
16use crate::{ExtResponse, SessionModeId};
17
18#[async_trait::async_trait(?Send)]
24pub trait Client {
25 async fn request_permission(
36 &self,
37 args: RequestPermissionRequest,
38 ) -> Result<RequestPermissionResponse, Error>;
39
40 async fn write_text_file(
47 &self,
48 args: WriteTextFileRequest,
49 ) -> Result<WriteTextFileResponse, Error>;
50
51 async fn read_text_file(
58 &self,
59 args: ReadTextFileRequest,
60 ) -> Result<ReadTextFileResponse, Error>;
61
62 async fn session_notification(&self, args: SessionNotification) -> Result<(), Error>;
74
75 async fn create_terminal(
90 &self,
91 args: CreateTerminalRequest,
92 ) -> Result<CreateTerminalResponse, Error>;
93
94 async fn terminal_output(
101 &self,
102 args: TerminalOutputRequest,
103 ) -> Result<TerminalOutputResponse, Error>;
104
105 async fn release_terminal(
118 &self,
119 args: ReleaseTerminalRequest,
120 ) -> Result<ReleaseTerminalResponse, Error>;
121
122 async fn wait_for_terminal_exit(
126 &self,
127 args: WaitForTerminalExitRequest,
128 ) -> Result<WaitForTerminalExitResponse, Error>;
129
130 async fn kill_terminal_command(
143 &self,
144 args: KillTerminalCommandRequest,
145 ) -> Result<KillTerminalCommandResponse, Error>;
146
147 async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error>;
155
156 async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error>;
164}
165
166#[async_trait::async_trait(?Send)]
167impl<T: Client> Client for Rc<T> {
168 async fn request_permission(
169 &self,
170 args: RequestPermissionRequest,
171 ) -> Result<RequestPermissionResponse, Error> {
172 self.as_ref().request_permission(args).await
173 }
174 async fn write_text_file(
175 &self,
176 args: WriteTextFileRequest,
177 ) -> Result<WriteTextFileResponse, Error> {
178 self.as_ref().write_text_file(args).await
179 }
180 async fn read_text_file(
181 &self,
182 args: ReadTextFileRequest,
183 ) -> Result<ReadTextFileResponse, Error> {
184 self.as_ref().read_text_file(args).await
185 }
186 async fn session_notification(&self, args: SessionNotification) -> Result<(), Error> {
187 self.as_ref().session_notification(args).await
188 }
189 async fn create_terminal(
190 &self,
191 args: CreateTerminalRequest,
192 ) -> Result<CreateTerminalResponse, Error> {
193 self.as_ref().create_terminal(args).await
194 }
195 async fn terminal_output(
196 &self,
197 args: TerminalOutputRequest,
198 ) -> Result<TerminalOutputResponse, Error> {
199 self.as_ref().terminal_output(args).await
200 }
201 async fn release_terminal(
202 &self,
203 args: ReleaseTerminalRequest,
204 ) -> Result<ReleaseTerminalResponse, Error> {
205 self.as_ref().release_terminal(args).await
206 }
207 async fn wait_for_terminal_exit(
208 &self,
209 args: WaitForTerminalExitRequest,
210 ) -> Result<WaitForTerminalExitResponse, Error> {
211 self.as_ref().wait_for_terminal_exit(args).await
212 }
213 async fn kill_terminal_command(
214 &self,
215 args: KillTerminalCommandRequest,
216 ) -> Result<KillTerminalCommandResponse, Error> {
217 self.as_ref().kill_terminal_command(args).await
218 }
219 async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error> {
220 self.as_ref().ext_method(args).await
221 }
222 async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error> {
223 self.as_ref().ext_notification(args).await
224 }
225}
226
227#[async_trait::async_trait(?Send)]
228impl<T: Client> Client for Arc<T> {
229 async fn request_permission(
230 &self,
231 args: RequestPermissionRequest,
232 ) -> Result<RequestPermissionResponse, Error> {
233 self.as_ref().request_permission(args).await
234 }
235 async fn write_text_file(
236 &self,
237 args: WriteTextFileRequest,
238 ) -> Result<WriteTextFileResponse, Error> {
239 self.as_ref().write_text_file(args).await
240 }
241 async fn read_text_file(
242 &self,
243 args: ReadTextFileRequest,
244 ) -> Result<ReadTextFileResponse, Error> {
245 self.as_ref().read_text_file(args).await
246 }
247 async fn session_notification(&self, args: SessionNotification) -> Result<(), Error> {
248 self.as_ref().session_notification(args).await
249 }
250 async fn create_terminal(
251 &self,
252 args: CreateTerminalRequest,
253 ) -> Result<CreateTerminalResponse, Error> {
254 self.as_ref().create_terminal(args).await
255 }
256 async fn terminal_output(
257 &self,
258 args: TerminalOutputRequest,
259 ) -> Result<TerminalOutputResponse, Error> {
260 self.as_ref().terminal_output(args).await
261 }
262 async fn release_terminal(
263 &self,
264 args: ReleaseTerminalRequest,
265 ) -> Result<ReleaseTerminalResponse, Error> {
266 self.as_ref().release_terminal(args).await
267 }
268 async fn wait_for_terminal_exit(
269 &self,
270 args: WaitForTerminalExitRequest,
271 ) -> Result<WaitForTerminalExitResponse, Error> {
272 self.as_ref().wait_for_terminal_exit(args).await
273 }
274 async fn kill_terminal_command(
275 &self,
276 args: KillTerminalCommandRequest,
277 ) -> Result<KillTerminalCommandResponse, Error> {
278 self.as_ref().kill_terminal_command(args).await
279 }
280 async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error> {
281 self.as_ref().ext_method(args).await
282 }
283 async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error> {
284 self.as_ref().ext_notification(args).await
285 }
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
296#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
297#[serde(rename_all = "camelCase")]
298pub struct SessionNotification {
299 pub session_id: SessionId,
301 pub update: SessionUpdate,
303 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
305 pub meta: Option<serde_json::Value>,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
314#[serde(rename_all = "snake_case", tag = "sessionUpdate")]
315pub enum SessionUpdate {
316 UserMessageChunk { content: ContentBlock },
318 AgentMessageChunk { content: ContentBlock },
320 AgentThoughtChunk { content: ContentBlock },
322 ToolCall(ToolCall),
324 ToolCallUpdate(ToolCallUpdate),
326 Plan(Plan),
329 #[serde(rename_all = "camelCase")]
331 AvailableCommandsUpdate {
332 available_commands: Vec<AvailableCommand>,
333 },
334 #[serde(rename_all = "camelCase")]
338 CurrentModeUpdate { current_mode_id: SessionModeId },
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
343#[serde(rename_all = "camelCase")]
344pub struct AvailableCommand {
345 pub name: String,
347 pub description: String,
349 pub input: Option<AvailableCommandInput>,
351 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
353 pub meta: Option<serde_json::Value>,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
358#[serde(untagged, rename_all = "camelCase")]
359pub enum AvailableCommandInput {
360 #[schemars(rename = "UnstructuredCommandInput")]
362 Unstructured {
363 hint: String,
365 },
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
376#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
377#[serde(rename_all = "camelCase")]
378pub struct RequestPermissionRequest {
379 pub session_id: SessionId,
381 pub tool_call: ToolCallUpdate,
383 pub options: Vec<PermissionOption>,
385 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
387 pub meta: Option<serde_json::Value>,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
392pub struct PermissionOption {
393 #[serde(rename = "optionId")]
395 pub id: PermissionOptionId,
396 pub name: String,
398 pub kind: PermissionOptionKind,
400 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
402 pub meta: Option<serde_json::Value>,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
407#[serde(transparent)]
408pub struct PermissionOptionId(pub Arc<str>);
409
410impl fmt::Display for PermissionOptionId {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 self.0.fmt(f)
413 }
414}
415
416#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
420#[serde(rename_all = "snake_case")]
421pub enum PermissionOptionKind {
422 AllowOnce,
424 AllowAlways,
426 RejectOnce,
428 RejectAlways,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
434#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
435#[serde(rename_all = "camelCase")]
436pub struct RequestPermissionResponse {
437 pub outcome: RequestPermissionOutcome,
440 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
442 pub meta: Option<serde_json::Value>,
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
447#[serde(tag = "outcome", rename_all = "snake_case")]
448pub enum RequestPermissionOutcome {
449 Cancelled,
457 #[serde(rename_all = "camelCase")]
459 Selected {
460 option_id: PermissionOptionId,
462 },
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
471#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))]
472#[serde(rename_all = "camelCase")]
473pub struct WriteTextFileRequest {
474 pub session_id: SessionId,
476 pub path: PathBuf,
478 pub content: String,
480 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
482 pub meta: Option<serde_json::Value>,
483}
484
485#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
487#[serde(rename_all = "camelCase")]
488#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))]
489#[serde(default)]
490pub struct WriteTextFileResponse {
491 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
493 pub meta: Option<serde_json::Value>,
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
502#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))]
503#[serde(rename_all = "camelCase")]
504pub struct ReadTextFileRequest {
505 pub session_id: SessionId,
507 pub path: PathBuf,
509 #[serde(default, skip_serializing_if = "Option::is_none")]
511 pub line: Option<u32>,
512 #[serde(default, skip_serializing_if = "Option::is_none")]
514 pub limit: Option<u32>,
515 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
517 pub meta: Option<serde_json::Value>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
522#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))]
523#[serde(rename_all = "camelCase")]
524pub struct ReadTextFileResponse {
525 pub content: String,
526 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
528 pub meta: Option<serde_json::Value>,
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
534#[serde(transparent)]
535pub struct TerminalId(pub Arc<str>);
536
537impl std::fmt::Display for TerminalId {
538 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539 write!(f, "{}", self.0)
540 }
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
545#[serde(rename_all = "camelCase")]
546#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))]
547pub struct CreateTerminalRequest {
548 pub session_id: SessionId,
550 pub command: String,
552 #[serde(default, skip_serializing_if = "Vec::is_empty")]
554 pub args: Vec<String>,
555 #[serde(default, skip_serializing_if = "Vec::is_empty")]
557 pub env: Vec<crate::EnvVariable>,
558 #[serde(default, skip_serializing_if = "Option::is_none")]
560 pub cwd: Option<PathBuf>,
561 #[serde(default, skip_serializing_if = "Option::is_none")]
570 pub output_byte_limit: Option<u64>,
571 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
573 pub meta: Option<serde_json::Value>,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
578#[serde(rename_all = "camelCase")]
579#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))]
580pub struct CreateTerminalResponse {
581 pub terminal_id: TerminalId,
583 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
585 pub meta: Option<serde_json::Value>,
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
590#[serde(rename_all = "camelCase")]
591#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))]
592pub struct TerminalOutputRequest {
593 pub session_id: SessionId,
595 pub terminal_id: TerminalId,
597 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
599 pub meta: Option<serde_json::Value>,
600}
601
602#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
604#[serde(rename_all = "camelCase")]
605#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))]
606pub struct TerminalOutputResponse {
607 pub output: String,
609 pub truncated: bool,
611 pub exit_status: Option<TerminalExitStatus>,
613 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
615 pub meta: Option<serde_json::Value>,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
620#[serde(rename_all = "camelCase")]
621#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))]
622pub struct ReleaseTerminalRequest {
623 pub session_id: SessionId,
625 pub terminal_id: TerminalId,
627 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
629 pub meta: Option<serde_json::Value>,
630}
631
632#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
634#[serde(rename_all = "camelCase")]
635#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))]
636pub struct ReleaseTerminalResponse {
637 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
639 pub meta: Option<serde_json::Value>,
640}
641
642#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
644#[serde(rename_all = "camelCase")]
645#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))]
646pub struct KillTerminalCommandRequest {
647 pub session_id: SessionId,
649 pub terminal_id: TerminalId,
651 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
653 pub meta: Option<serde_json::Value>,
654}
655
656#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
658#[serde(rename_all = "camelCase")]
659#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))]
660pub struct KillTerminalCommandResponse {
661 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
663 pub meta: Option<serde_json::Value>,
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
668#[serde(rename_all = "camelCase")]
669#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))]
670pub struct WaitForTerminalExitRequest {
671 pub session_id: SessionId,
673 pub terminal_id: TerminalId,
675 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
677 pub meta: Option<serde_json::Value>,
678}
679
680#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
682#[serde(rename_all = "camelCase")]
683#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))]
684pub struct WaitForTerminalExitResponse {
685 #[serde(flatten)]
687 pub exit_status: TerminalExitStatus,
688 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
690 pub meta: Option<serde_json::Value>,
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
695#[serde(rename_all = "camelCase")]
696pub struct TerminalExitStatus {
697 pub exit_code: Option<u32>,
699 pub signal: Option<String>,
701 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
703 pub meta: Option<serde_json::Value>,
704}
705
706#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
715#[serde(rename_all = "camelCase")]
716pub struct ClientCapabilities {
717 #[serde(default)]
720 pub fs: FileSystemCapability,
721 #[serde(default)]
723 pub terminal: bool,
724 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
726 pub meta: Option<serde_json::Value>,
727}
728
729#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
733#[serde(rename_all = "camelCase")]
734pub struct FileSystemCapability {
735 #[serde(default)]
737 pub read_text_file: bool,
738 #[serde(default)]
740 pub write_text_file: bool,
741 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
743 pub meta: Option<serde_json::Value>,
744}
745
746#[derive(Debug, Clone, Serialize, Deserialize)]
752pub struct ClientMethodNames {
753 pub session_request_permission: &'static str,
755 pub session_update: &'static str,
757 pub fs_write_text_file: &'static str,
759 pub fs_read_text_file: &'static str,
761 pub terminal_create: &'static str,
763 pub terminal_output: &'static str,
765 pub terminal_release: &'static str,
767 pub terminal_wait_for_exit: &'static str,
769 pub terminal_kill: &'static str,
771}
772
773pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
775 session_update: SESSION_UPDATE_NOTIFICATION,
776 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
777 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
778 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
779 terminal_create: TERMINAL_CREATE_METHOD_NAME,
780 terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
781 terminal_release: TERMINAL_RELEASE_METHOD_NAME,
782 terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
783 terminal_kill: TERMINAL_KILL_METHOD_NAME,
784};
785
786pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
788pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
790pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
792pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
794pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
796pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
798pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
800pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
802pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
804
805#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
812#[serde(untagged)]
813#[schemars(extend("x-docs-ignore" = true))]
814pub enum AgentRequest {
815 WriteTextFileRequest(WriteTextFileRequest),
816 ReadTextFileRequest(ReadTextFileRequest),
817 RequestPermissionRequest(RequestPermissionRequest),
818 CreateTerminalRequest(CreateTerminalRequest),
819 TerminalOutputRequest(TerminalOutputRequest),
820 ReleaseTerminalRequest(ReleaseTerminalRequest),
821 WaitForTerminalExitRequest(WaitForTerminalExitRequest),
822 KillTerminalCommandRequest(KillTerminalCommandRequest),
823 ExtMethodRequest(ExtRequest),
824}
825
826#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
833#[serde(untagged)]
834#[schemars(extend("x-docs-ignore" = true))]
835pub enum ClientResponse {
836 WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
837 ReadTextFileResponse(ReadTextFileResponse),
838 RequestPermissionResponse(RequestPermissionResponse),
839 CreateTerminalResponse(CreateTerminalResponse),
840 TerminalOutputResponse(TerminalOutputResponse),
841 ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
842 WaitForTerminalExitResponse(WaitForTerminalExitResponse),
843 KillTerminalResponse(#[serde(default)] KillTerminalCommandResponse),
844 ExtMethodResponse(#[schemars(with = "serde_json::Value")] Arc<RawValue>),
845}
846
847#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
854#[serde(untagged)]
855#[allow(clippy::large_enum_variant)]
856#[schemars(extend("x-docs-ignore" = true))]
857pub enum AgentNotification {
858 SessionNotification(SessionNotification),
859 ExtNotification(ExtNotification),
860}