agent_client_protocol/
client.rs

1//! Methods and notifications the client handles/receives.
2//!
3//! This module defines the Client trait and all associated types for implementing
4//! a client that interacts with AI coding agents via the Agent Client Protocol (ACP).
5
6use 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/// Defines the interface that ACP-compliant clients must implement.
19///
20/// Clients are typically code editors (IDEs, text editors) that provide the interface
21/// between users and AI agents. They manage the environment, handle user interactions,
22/// and control access to resources.
23#[async_trait::async_trait(?Send)]
24pub trait Client {
25    /// Requests permission from the user for a tool call operation.
26    ///
27    /// Called by the agent when it needs user authorization before executing
28    /// a potentially sensitive operation. The client should present the options
29    /// to the user and return their decision.
30    ///
31    /// If the client cancels the prompt turn via `session/cancel`, it MUST
32    /// respond to this request with `RequestPermissionOutcome::Cancelled`.
33    ///
34    /// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
35    async fn request_permission(
36        &self,
37        args: RequestPermissionRequest,
38    ) -> Result<RequestPermissionResponse, Error>;
39
40    /// Handles session update notifications from the agent.
41    ///
42    /// This is a notification endpoint (no response expected) that receives
43    /// real-time updates about session progress, including message chunks,
44    /// tool calls, and execution plans.
45    ///
46    /// Note: Clients SHOULD continue accepting tool call updates even after
47    /// sending a `session/cancel` notification, as the agent may send final
48    /// updates before responding with the cancelled stop reason.
49    ///
50    /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
51    async fn session_notification(&self, args: SessionNotification) -> Result<(), Error>;
52
53    /// Writes content to a text file in the client's file system.
54    ///
55    /// Only available if the client advertises the `fs.writeTextFile` capability.
56    /// Allows the agent to create or modify files within the client's environment.
57    ///
58    /// See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
59    async fn write_text_file(
60        &self,
61        _args: WriteTextFileRequest,
62    ) -> Result<WriteTextFileResponse, Error> {
63        Err(Error::method_not_found())
64    }
65
66    /// Reads content from a text file in the client's file system.
67    ///
68    /// Only available if the client advertises the `fs.readTextFile` capability.
69    /// Allows the agent to access file contents within the client's environment.
70    ///
71    /// See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
72    async fn read_text_file(
73        &self,
74        _args: ReadTextFileRequest,
75    ) -> Result<ReadTextFileResponse, Error> {
76        Err(Error::method_not_found())
77    }
78
79    /// Executes a command in a new terminal
80    ///
81    /// Only available if the `terminal` Client capability is set to `true`.
82    ///
83    /// Returns a `TerminalId` that can be used with other terminal methods
84    /// to get the current output, wait for exit, and kill the command.
85    ///
86    /// The `TerminalId` can also be used to embed the terminal in a tool call
87    /// by using the `ToolCallContent::Terminal` variant.
88    ///
89    /// The Agent is responsible for releasing the terminal by using the `terminal/release`
90    /// method.
91    ///
92    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
93    async fn create_terminal(
94        &self,
95        _args: CreateTerminalRequest,
96    ) -> Result<CreateTerminalResponse, Error> {
97        Err(Error::method_not_found())
98    }
99
100    /// Gets the terminal output and exit status
101    ///
102    /// Returns the current content in the terminal without waiting for the command to exit.
103    /// If the command has already exited, the exit status is included.
104    ///
105    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
106    async fn terminal_output(
107        &self,
108        _args: TerminalOutputRequest,
109    ) -> Result<TerminalOutputResponse, Error> {
110        Err(Error::method_not_found())
111    }
112
113    /// Releases a terminal
114    ///
115    /// The command is killed if it hasn't exited yet. Use `terminal/wait_for_exit`
116    /// to wait for the command to exit before releasing the terminal.
117    ///
118    /// After release, the `TerminalId` can no longer be used with other `terminal/*` methods,
119    /// but tool calls that already contain it, continue to display its output.
120    ///
121    /// The `terminal/kill` method can be used to terminate the command without releasing
122    /// the terminal, allowing the Agent to call `terminal/output` and other methods.
123    ///
124    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
125    async fn release_terminal(
126        &self,
127        _args: ReleaseTerminalRequest,
128    ) -> Result<ReleaseTerminalResponse, Error> {
129        Err(Error::method_not_found())
130    }
131
132    /// Waits for the terminal command to exit and return its exit status
133    ///
134    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
135    async fn wait_for_terminal_exit(
136        &self,
137        _args: WaitForTerminalExitRequest,
138    ) -> Result<WaitForTerminalExitResponse, Error> {
139        Err(Error::method_not_found())
140    }
141
142    /// Kills the terminal command without releasing the terminal
143    ///
144    /// While `terminal/release` will also kill the command, this method will keep
145    /// the `TerminalId` valid so it can be used with other methods.
146    ///
147    /// This method can be helpful when implementing command timeouts which terminate
148    /// the command as soon as elapsed, and then get the final output so it can be sent
149    /// to the model.
150    ///
151    /// Note: `terminal/release` when `TerminalId` is no longer needed.
152    ///
153    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
154    async fn kill_terminal_command(
155        &self,
156        _args: KillTerminalCommandRequest,
157    ) -> Result<KillTerminalCommandResponse, Error> {
158        Err(Error::method_not_found())
159    }
160
161    /// Handles extension method requests from the agent.
162    ///
163    /// Allows the Agent to send an arbitrary request that is not part of the ACP spec.
164    /// Extension methods provide a way to add custom functionality while maintaining
165    /// protocol compatibility.
166    ///
167    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
168    async fn ext_method(&self, _args: ExtRequest) -> Result<ExtResponse, Error> {
169        Ok(RawValue::NULL.to_owned().into())
170    }
171
172    /// Handles extension notifications from the agent.
173    ///
174    /// Allows the Agent to send an arbitrary notification that is not part of the ACP spec.
175    /// Extension notifications provide a way to send one-way messages for custom functionality
176    /// while maintaining protocol compatibility.
177    ///
178    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
179    async fn ext_notification(&self, _args: ExtNotification) -> Result<(), Error> {
180        Ok(())
181    }
182}
183
184#[async_trait::async_trait(?Send)]
185impl<T: Client> Client for Rc<T> {
186    async fn request_permission(
187        &self,
188        args: RequestPermissionRequest,
189    ) -> Result<RequestPermissionResponse, Error> {
190        self.as_ref().request_permission(args).await
191    }
192    async fn write_text_file(
193        &self,
194        args: WriteTextFileRequest,
195    ) -> Result<WriteTextFileResponse, Error> {
196        self.as_ref().write_text_file(args).await
197    }
198    async fn read_text_file(
199        &self,
200        args: ReadTextFileRequest,
201    ) -> Result<ReadTextFileResponse, Error> {
202        self.as_ref().read_text_file(args).await
203    }
204    async fn session_notification(&self, args: SessionNotification) -> Result<(), Error> {
205        self.as_ref().session_notification(args).await
206    }
207    async fn create_terminal(
208        &self,
209        args: CreateTerminalRequest,
210    ) -> Result<CreateTerminalResponse, Error> {
211        self.as_ref().create_terminal(args).await
212    }
213    async fn terminal_output(
214        &self,
215        args: TerminalOutputRequest,
216    ) -> Result<TerminalOutputResponse, Error> {
217        self.as_ref().terminal_output(args).await
218    }
219    async fn release_terminal(
220        &self,
221        args: ReleaseTerminalRequest,
222    ) -> Result<ReleaseTerminalResponse, Error> {
223        self.as_ref().release_terminal(args).await
224    }
225    async fn wait_for_terminal_exit(
226        &self,
227        args: WaitForTerminalExitRequest,
228    ) -> Result<WaitForTerminalExitResponse, Error> {
229        self.as_ref().wait_for_terminal_exit(args).await
230    }
231    async fn kill_terminal_command(
232        &self,
233        args: KillTerminalCommandRequest,
234    ) -> Result<KillTerminalCommandResponse, Error> {
235        self.as_ref().kill_terminal_command(args).await
236    }
237    async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error> {
238        self.as_ref().ext_method(args).await
239    }
240    async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error> {
241        self.as_ref().ext_notification(args).await
242    }
243}
244
245#[async_trait::async_trait(?Send)]
246impl<T: Client> Client for Arc<T> {
247    async fn request_permission(
248        &self,
249        args: RequestPermissionRequest,
250    ) -> Result<RequestPermissionResponse, Error> {
251        self.as_ref().request_permission(args).await
252    }
253    async fn write_text_file(
254        &self,
255        args: WriteTextFileRequest,
256    ) -> Result<WriteTextFileResponse, Error> {
257        self.as_ref().write_text_file(args).await
258    }
259    async fn read_text_file(
260        &self,
261        args: ReadTextFileRequest,
262    ) -> Result<ReadTextFileResponse, Error> {
263        self.as_ref().read_text_file(args).await
264    }
265    async fn session_notification(&self, args: SessionNotification) -> Result<(), Error> {
266        self.as_ref().session_notification(args).await
267    }
268    async fn create_terminal(
269        &self,
270        args: CreateTerminalRequest,
271    ) -> Result<CreateTerminalResponse, Error> {
272        self.as_ref().create_terminal(args).await
273    }
274    async fn terminal_output(
275        &self,
276        args: TerminalOutputRequest,
277    ) -> Result<TerminalOutputResponse, Error> {
278        self.as_ref().terminal_output(args).await
279    }
280    async fn release_terminal(
281        &self,
282        args: ReleaseTerminalRequest,
283    ) -> Result<ReleaseTerminalResponse, Error> {
284        self.as_ref().release_terminal(args).await
285    }
286    async fn wait_for_terminal_exit(
287        &self,
288        args: WaitForTerminalExitRequest,
289    ) -> Result<WaitForTerminalExitResponse, Error> {
290        self.as_ref().wait_for_terminal_exit(args).await
291    }
292    async fn kill_terminal_command(
293        &self,
294        args: KillTerminalCommandRequest,
295    ) -> Result<KillTerminalCommandResponse, Error> {
296        self.as_ref().kill_terminal_command(args).await
297    }
298    async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error> {
299        self.as_ref().ext_method(args).await
300    }
301    async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error> {
302        self.as_ref().ext_notification(args).await
303    }
304}
305
306// Session updates
307
308/// Notification containing a session update from the agent.
309///
310/// Used to stream real-time progress and results during prompt processing.
311///
312/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
313#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
314#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
315#[serde(rename_all = "camelCase")]
316pub struct SessionNotification {
317    /// The ID of the session this update pertains to.
318    pub session_id: SessionId,
319    /// The actual update content.
320    pub update: SessionUpdate,
321    /// Extension point for implementations
322    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
323    pub meta: Option<serde_json::Value>,
324}
325
326/// Different types of updates that can be sent during session processing.
327///
328/// These updates provide real-time feedback about the agent's progress.
329///
330/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
331#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
332#[serde(rename_all = "snake_case", tag = "sessionUpdate")]
333pub enum SessionUpdate {
334    /// A chunk of the user's message being streamed.
335    UserMessageChunk { content: ContentBlock },
336    /// A chunk of the agent's response being streamed.
337    AgentMessageChunk { content: ContentBlock },
338    /// A chunk of the agent's internal reasoning being streamed.
339    AgentThoughtChunk { content: ContentBlock },
340    /// Notification that a new tool call has been initiated.
341    ToolCall(ToolCall),
342    /// Update on the status or results of a tool call.
343    ToolCallUpdate(ToolCallUpdate),
344    /// The agent's execution plan for complex tasks.
345    /// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
346    Plan(Plan),
347    /// Available commands are ready or have changed
348    #[serde(rename_all = "camelCase")]
349    AvailableCommandsUpdate {
350        available_commands: Vec<AvailableCommand>,
351    },
352    /// The current mode of the session has changed
353    ///
354    /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes)
355    #[serde(rename_all = "camelCase")]
356    CurrentModeUpdate { current_mode_id: SessionModeId },
357}
358
359/// Information about a command.
360#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
361#[serde(rename_all = "camelCase")]
362pub struct AvailableCommand {
363    /// Command name (e.g., `create_plan`, `research_codebase`).
364    pub name: String,
365    /// Human-readable description of what the command does.
366    pub description: String,
367    /// Input for the command if required
368    pub input: Option<AvailableCommandInput>,
369    /// Extension point for implementations
370    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
371    pub meta: Option<serde_json::Value>,
372}
373
374/// The input specification for a command.
375#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
376#[serde(untagged, rename_all = "camelCase")]
377pub enum AvailableCommandInput {
378    /// All text that was typed after the command name is provided as input.
379    #[schemars(rename = "UnstructuredCommandInput")]
380    Unstructured {
381        /// A hint to display when the input hasn't been provided yet
382        hint: String,
383    },
384}
385
386// Permission
387
388/// Request for user permission to execute a tool call.
389///
390/// Sent when the agent needs authorization before performing a sensitive operation.
391///
392/// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
393#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
394#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
395#[serde(rename_all = "camelCase")]
396pub struct RequestPermissionRequest {
397    /// The session ID for this request.
398    pub session_id: SessionId,
399    /// Details about the tool call requiring permission.
400    pub tool_call: ToolCallUpdate,
401    /// Available permission options for the user to choose from.
402    pub options: Vec<PermissionOption>,
403    /// Extension point for implementations
404    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
405    pub meta: Option<serde_json::Value>,
406}
407
408/// An option presented to the user when requesting permission.
409#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
410pub struct PermissionOption {
411    /// Unique identifier for this permission option.
412    #[serde(rename = "optionId")]
413    pub id: PermissionOptionId,
414    /// Human-readable label to display to the user.
415    pub name: String,
416    /// Hint about the nature of this permission option.
417    pub kind: PermissionOptionKind,
418    /// Extension point for implementations
419    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
420    pub meta: Option<serde_json::Value>,
421}
422
423/// Unique identifier for a permission option.
424#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
425#[serde(transparent)]
426pub struct PermissionOptionId(pub Arc<str>);
427
428impl fmt::Display for PermissionOptionId {
429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430        self.0.fmt(f)
431    }
432}
433
434/// The type of permission option being presented to the user.
435///
436/// Helps clients choose appropriate icons and UI treatment.
437#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
438#[serde(rename_all = "snake_case")]
439pub enum PermissionOptionKind {
440    /// Allow this operation only this time.
441    AllowOnce,
442    /// Allow this operation and remember the choice.
443    AllowAlways,
444    /// Reject this operation only this time.
445    RejectOnce,
446    /// Reject this operation and remember the choice.
447    RejectAlways,
448}
449
450/// Response to a permission request.
451#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
452#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
453#[serde(rename_all = "camelCase")]
454pub struct RequestPermissionResponse {
455    /// The user's decision on the permission request.
456    // This extra-level is unfortunately needed because the output must be an object
457    pub outcome: RequestPermissionOutcome,
458    /// Extension point for implementations
459    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
460    pub meta: Option<serde_json::Value>,
461}
462
463/// The outcome of a permission request.
464#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
465#[serde(tag = "outcome", rename_all = "snake_case")]
466pub enum RequestPermissionOutcome {
467    /// The prompt turn was cancelled before the user responded.
468    ///
469    /// When a client sends a `session/cancel` notification to cancel an ongoing
470    /// prompt turn, it MUST respond to all pending `session/request_permission`
471    /// requests with this `Cancelled` outcome.
472    ///
473    /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation)
474    Cancelled,
475    /// The user selected one of the provided options.
476    #[serde(rename_all = "camelCase")]
477    Selected {
478        /// The ID of the option the user selected.
479        option_id: PermissionOptionId,
480    },
481}
482
483// Write text file
484
485/// Request to write content to a text file.
486///
487/// Only available if the client supports the `fs.writeTextFile` capability.
488#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
489#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))]
490#[serde(rename_all = "camelCase")]
491pub struct WriteTextFileRequest {
492    /// The session ID for this request.
493    pub session_id: SessionId,
494    /// Absolute path to the file to write.
495    pub path: PathBuf,
496    /// The text content to write to the file.
497    pub content: String,
498    /// Extension point for implementations
499    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
500    pub meta: Option<serde_json::Value>,
501}
502
503/// Response to `fs/write_text_file`
504#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
505#[serde(rename_all = "camelCase")]
506#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))]
507#[serde(default)]
508pub struct WriteTextFileResponse {
509    /// Extension point for implementations
510    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
511    pub meta: Option<serde_json::Value>,
512}
513
514// Read text file
515
516/// Request to read content from a text file.
517///
518/// Only available if the client supports the `fs.readTextFile` capability.
519#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
520#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))]
521#[serde(rename_all = "camelCase")]
522pub struct ReadTextFileRequest {
523    /// The session ID for this request.
524    pub session_id: SessionId,
525    /// Absolute path to the file to read.
526    pub path: PathBuf,
527    /// Line number to start reading from (1-based).
528    #[serde(default, skip_serializing_if = "Option::is_none")]
529    pub line: Option<u32>,
530    /// Maximum number of lines to read.
531    #[serde(default, skip_serializing_if = "Option::is_none")]
532    pub limit: Option<u32>,
533    /// Extension point for implementations
534    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
535    pub meta: Option<serde_json::Value>,
536}
537
538/// Response containing the contents of a text file.
539#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
540#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))]
541#[serde(rename_all = "camelCase")]
542pub struct ReadTextFileResponse {
543    pub content: String,
544    /// Extension point for implementations
545    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
546    pub meta: Option<serde_json::Value>,
547}
548
549// Terminals
550
551#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
552#[serde(transparent)]
553pub struct TerminalId(pub Arc<str>);
554
555impl std::fmt::Display for TerminalId {
556    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557        write!(f, "{}", self.0)
558    }
559}
560
561/// Request to create a new terminal and execute a command.
562#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
563#[serde(rename_all = "camelCase")]
564#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))]
565pub struct CreateTerminalRequest {
566    /// The session ID for this request.
567    pub session_id: SessionId,
568    /// The command to execute.
569    pub command: String,
570    /// Array of command arguments.
571    #[serde(default, skip_serializing_if = "Vec::is_empty")]
572    pub args: Vec<String>,
573    /// Environment variables for the command.
574    #[serde(default, skip_serializing_if = "Vec::is_empty")]
575    pub env: Vec<crate::EnvVariable>,
576    /// Working directory for the command (absolute path).
577    #[serde(default, skip_serializing_if = "Option::is_none")]
578    pub cwd: Option<PathBuf>,
579    /// Maximum number of output bytes to retain.
580    ///
581    /// When the limit is exceeded, the Client truncates from the beginning of the output
582    /// to stay within the limit.
583    ///
584    /// The Client MUST ensure truncation happens at a character boundary to maintain valid
585    /// string output, even if this means the retained output is slightly less than the
586    /// specified limit.
587    #[serde(default, skip_serializing_if = "Option::is_none")]
588    pub output_byte_limit: Option<u64>,
589    /// Extension point for implementations
590    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
591    pub meta: Option<serde_json::Value>,
592}
593
594/// Response containing the ID of the created terminal.
595#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
596#[serde(rename_all = "camelCase")]
597#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))]
598pub struct CreateTerminalResponse {
599    /// The unique identifier for the created terminal.
600    pub terminal_id: TerminalId,
601    /// Extension point for implementations
602    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
603    pub meta: Option<serde_json::Value>,
604}
605
606/// Request to get the current output and status of a terminal.
607#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
608#[serde(rename_all = "camelCase")]
609#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))]
610pub struct TerminalOutputRequest {
611    /// The session ID for this request.
612    pub session_id: SessionId,
613    /// The ID of the terminal to get output from.
614    pub terminal_id: TerminalId,
615    /// Extension point for implementations
616    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
617    pub meta: Option<serde_json::Value>,
618}
619
620/// Response containing the terminal output and exit status.
621#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
622#[serde(rename_all = "camelCase")]
623#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))]
624pub struct TerminalOutputResponse {
625    /// The terminal output captured so far.
626    pub output: String,
627    /// Whether the output was truncated due to byte limits.
628    pub truncated: bool,
629    /// Exit status if the command has completed.
630    pub exit_status: Option<TerminalExitStatus>,
631    /// Extension point for implementations
632    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
633    pub meta: Option<serde_json::Value>,
634}
635
636/// Request to release a terminal and free its resources.
637#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
638#[serde(rename_all = "camelCase")]
639#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))]
640pub struct ReleaseTerminalRequest {
641    /// The session ID for this request.
642    pub session_id: SessionId,
643    /// The ID of the terminal to release.
644    pub terminal_id: TerminalId,
645    /// Extension point for implementations
646    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
647    pub meta: Option<serde_json::Value>,
648}
649
650/// Response to terminal/release method
651#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
652#[serde(rename_all = "camelCase")]
653#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))]
654pub struct ReleaseTerminalResponse {
655    /// Extension point for implementations
656    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
657    pub meta: Option<serde_json::Value>,
658}
659
660/// Request to kill a terminal command without releasing the terminal.
661#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
662#[serde(rename_all = "camelCase")]
663#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))]
664pub struct KillTerminalCommandRequest {
665    /// The session ID for this request.
666    pub session_id: SessionId,
667    /// The ID of the terminal to kill.
668    pub terminal_id: TerminalId,
669    /// Extension point for implementations
670    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
671    pub meta: Option<serde_json::Value>,
672}
673
674/// Response to terminal/kill command method
675#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
676#[serde(rename_all = "camelCase")]
677#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))]
678pub struct KillTerminalCommandResponse {
679    /// Extension point for implementations
680    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
681    pub meta: Option<serde_json::Value>,
682}
683
684/// Request to wait for a terminal command to exit.
685#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
686#[serde(rename_all = "camelCase")]
687#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))]
688pub struct WaitForTerminalExitRequest {
689    /// The session ID for this request.
690    pub session_id: SessionId,
691    /// The ID of the terminal to wait for.
692    pub terminal_id: TerminalId,
693    /// Extension point for implementations
694    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
695    pub meta: Option<serde_json::Value>,
696}
697
698/// Response containing the exit status of a terminal command.
699#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
700#[serde(rename_all = "camelCase")]
701#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))]
702pub struct WaitForTerminalExitResponse {
703    /// The exit status of the terminal command.
704    #[serde(flatten)]
705    pub exit_status: TerminalExitStatus,
706    /// Extension point for implementations
707    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
708    pub meta: Option<serde_json::Value>,
709}
710
711/// Exit status of a terminal command.
712#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
713#[serde(rename_all = "camelCase")]
714pub struct TerminalExitStatus {
715    /// The process exit code (may be null if terminated by signal).
716    pub exit_code: Option<u32>,
717    /// The signal that terminated the process (may be null if exited normally).
718    pub signal: Option<String>,
719    /// Extension point for implementations
720    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
721    pub meta: Option<serde_json::Value>,
722}
723
724// Capabilities
725
726/// Capabilities supported by the client.
727///
728/// Advertised during initialization to inform the agent about
729/// available features and methods.
730///
731/// See protocol docs: [Client Capabilities](https://agentclientprotocol.com/protocol/initialization#client-capabilities)
732#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
733#[serde(rename_all = "camelCase")]
734pub struct ClientCapabilities {
735    /// File system capabilities supported by the client.
736    /// Determines which file operations the agent can request.
737    #[serde(default)]
738    pub fs: FileSystemCapability,
739    /// Whether the Client support all `terminal/*` methods.
740    #[serde(default)]
741    pub terminal: bool,
742    /// Extension point for implementations
743    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
744    pub meta: Option<serde_json::Value>,
745}
746
747/// File system capabilities that a client may support.
748///
749/// See protocol docs: [FileSystem](https://agentclientprotocol.com/protocol/initialization#filesystem)
750#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
751#[serde(rename_all = "camelCase")]
752pub struct FileSystemCapability {
753    /// Whether the Client supports `fs/read_text_file` requests.
754    #[serde(default)]
755    pub read_text_file: bool,
756    /// Whether the Client supports `fs/write_text_file` requests.
757    #[serde(default)]
758    pub write_text_file: bool,
759    /// Extension point for implementations
760    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
761    pub meta: Option<serde_json::Value>,
762}
763
764// Method schema
765
766/// Names of all methods that clients handle.
767///
768/// Provides a centralized definition of method names used in the protocol.
769#[derive(Debug, Clone, Serialize, Deserialize)]
770pub struct ClientMethodNames {
771    /// Method for requesting permission from the user.
772    pub session_request_permission: &'static str,
773    /// Notification for session updates.
774    pub session_update: &'static str,
775    /// Method for writing text files.
776    pub fs_write_text_file: &'static str,
777    /// Method for reading text files.
778    pub fs_read_text_file: &'static str,
779    /// Method for creating new terminals.
780    pub terminal_create: &'static str,
781    /// Method for getting terminals output.
782    pub terminal_output: &'static str,
783    /// Method for releasing a terminal.
784    pub terminal_release: &'static str,
785    /// Method for waiting for a terminal to finish.
786    pub terminal_wait_for_exit: &'static str,
787    /// Method for killing a terminal.
788    pub terminal_kill: &'static str,
789}
790
791/// Constant containing all client method names.
792pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
793    session_update: SESSION_UPDATE_NOTIFICATION,
794    session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
795    fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
796    fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
797    terminal_create: TERMINAL_CREATE_METHOD_NAME,
798    terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
799    terminal_release: TERMINAL_RELEASE_METHOD_NAME,
800    terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
801    terminal_kill: TERMINAL_KILL_METHOD_NAME,
802};
803
804/// Notification name for session updates.
805pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
806/// Method name for requesting user permission.
807pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
808/// Method name for writing text files.
809pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
810/// Method name for reading text files.
811pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
812/// Method name for creating a new terminal.
813pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
814/// Method for getting terminals output.
815pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
816/// Method for releasing a terminal.
817pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
818/// Method for waiting for a terminal to finish.
819pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
820/// Method for killing a terminal.
821pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
822
823/// All possible requests that an agent can send to a client.
824///
825/// This enum is used internally for routing RPC requests. You typically won't need
826/// to use this directly - instead, use the methods on the [`Client`] trait.
827///
828/// This enum encompasses all method calls from agent to client.
829#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
830#[serde(untagged)]
831#[schemars(extend("x-docs-ignore" = true))]
832pub enum AgentRequest {
833    WriteTextFileRequest(WriteTextFileRequest),
834    ReadTextFileRequest(ReadTextFileRequest),
835    RequestPermissionRequest(RequestPermissionRequest),
836    CreateTerminalRequest(CreateTerminalRequest),
837    TerminalOutputRequest(TerminalOutputRequest),
838    ReleaseTerminalRequest(ReleaseTerminalRequest),
839    WaitForTerminalExitRequest(WaitForTerminalExitRequest),
840    KillTerminalCommandRequest(KillTerminalCommandRequest),
841    ExtMethodRequest(ExtRequest),
842}
843
844/// All possible responses that a client can send to an agent.
845///
846/// This enum is used internally for routing RPC responses. You typically won't need
847/// to use this directly - the responses are handled automatically by the connection.
848///
849/// These are responses to the corresponding `AgentRequest` variants.
850#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
851#[serde(untagged)]
852#[schemars(extend("x-docs-ignore" = true))]
853pub enum ClientResponse {
854    WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
855    ReadTextFileResponse(ReadTextFileResponse),
856    RequestPermissionResponse(RequestPermissionResponse),
857    CreateTerminalResponse(CreateTerminalResponse),
858    TerminalOutputResponse(TerminalOutputResponse),
859    ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
860    WaitForTerminalExitResponse(WaitForTerminalExitResponse),
861    KillTerminalResponse(#[serde(default)] KillTerminalCommandResponse),
862    ExtMethodResponse(#[schemars(with = "serde_json::Value")] Arc<RawValue>),
863}
864
865/// All possible notifications that an agent can send to a client.
866///
867/// This enum is used internally for routing RPC notifications. You typically won't need
868/// to use this directly - use the notification methods on the [`Client`] trait instead.
869///
870/// Notifications do not expect a response.
871#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
872#[serde(untagged)]
873#[allow(clippy::large_enum_variant)]
874#[schemars(extend("x-docs-ignore" = true))]
875pub enum AgentNotification {
876    SessionNotification(SessionNotification),
877    ExtNotification(ExtNotification),
878}