agent_client_protocol/
client.rs

1use std::{rc::Rc, sync::Arc};
2
3use agent_client_protocol_schema::{
4    CreateTerminalRequest, CreateTerminalResponse, Error, ExtNotification, ExtRequest, ExtResponse,
5    KillTerminalCommandRequest, KillTerminalCommandResponse, ReadTextFileRequest,
6    ReadTextFileResponse, ReleaseTerminalRequest, ReleaseTerminalResponse,
7    RequestPermissionRequest, RequestPermissionResponse, Result, SessionNotification,
8    TerminalOutputRequest, TerminalOutputResponse, WaitForTerminalExitRequest,
9    WaitForTerminalExitResponse, WriteTextFileRequest, WriteTextFileResponse,
10};
11use serde_json::value::RawValue;
12
13/// Defines the interface that ACP-compliant clients must implement.
14///
15/// Clients are typically code editors (IDEs, text editors) that provide the interface
16/// between users and AI agents. They manage the environment, handle user interactions,
17/// and control access to resources.
18#[async_trait::async_trait(?Send)]
19pub trait Client {
20    /// Requests permission from the user for a tool call operation.
21    ///
22    /// Called by the agent when it needs user authorization before executing
23    /// a potentially sensitive operation. The client should present the options
24    /// to the user and return their decision.
25    ///
26    /// If the client cancels the prompt turn via `session/cancel`, it MUST
27    /// respond to this request with `RequestPermissionOutcome::Cancelled`.
28    ///
29    /// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
30    async fn request_permission(
31        &self,
32        args: RequestPermissionRequest,
33    ) -> Result<RequestPermissionResponse>;
34
35    /// Handles session update notifications from the agent.
36    ///
37    /// This is a notification endpoint (no response expected) that receives
38    /// real-time updates about session progress, including message chunks,
39    /// tool calls, and execution plans.
40    ///
41    /// Note: Clients SHOULD continue accepting tool call updates even after
42    /// sending a `session/cancel` notification, as the agent may send final
43    /// updates before responding with the cancelled stop reason.
44    ///
45    /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
46    async fn session_notification(&self, args: SessionNotification) -> Result<()>;
47
48    /// Writes content to a text file in the client's file system.
49    ///
50    /// Only available if the client advertises the `fs.writeTextFile` capability.
51    /// Allows the agent to create or modify files within the client's environment.
52    ///
53    /// See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
54    async fn write_text_file(&self, _args: WriteTextFileRequest) -> Result<WriteTextFileResponse> {
55        Err(Error::method_not_found())
56    }
57
58    /// Reads content from a text file in the client's file system.
59    ///
60    /// Only available if the client advertises the `fs.readTextFile` capability.
61    /// Allows the agent to access file contents within the client's environment.
62    ///
63    /// See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
64    async fn read_text_file(&self, _args: ReadTextFileRequest) -> Result<ReadTextFileResponse> {
65        Err(Error::method_not_found())
66    }
67
68    /// Executes a command in a new terminal
69    ///
70    /// Only available if the `terminal` Client capability is set to `true`.
71    ///
72    /// Returns a `TerminalId` that can be used with other terminal methods
73    /// to get the current output, wait for exit, and kill the command.
74    ///
75    /// The `TerminalId` can also be used to embed the terminal in a tool call
76    /// by using the `ToolCallContent::Terminal` variant.
77    ///
78    /// The Agent is responsible for releasing the terminal by using the `terminal/release`
79    /// method.
80    ///
81    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
82    async fn create_terminal(
83        &self,
84        _args: CreateTerminalRequest,
85    ) -> Result<CreateTerminalResponse> {
86        Err(Error::method_not_found())
87    }
88
89    /// Gets the terminal output and exit status
90    ///
91    /// Returns the current content in the terminal without waiting for the command to exit.
92    /// If the command has already exited, the exit status is included.
93    ///
94    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
95    async fn terminal_output(
96        &self,
97        _args: TerminalOutputRequest,
98    ) -> Result<TerminalOutputResponse> {
99        Err(Error::method_not_found())
100    }
101
102    /// Releases a terminal
103    ///
104    /// The command is killed if it hasn't exited yet. Use `terminal/wait_for_exit`
105    /// to wait for the command to exit before releasing the terminal.
106    ///
107    /// After release, the `TerminalId` can no longer be used with other `terminal/*` methods,
108    /// but tool calls that already contain it, continue to display its output.
109    ///
110    /// The `terminal/kill` method can be used to terminate the command without releasing
111    /// the terminal, allowing the Agent to call `terminal/output` and other methods.
112    ///
113    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
114    async fn release_terminal(
115        &self,
116        _args: ReleaseTerminalRequest,
117    ) -> Result<ReleaseTerminalResponse> {
118        Err(Error::method_not_found())
119    }
120
121    /// Waits for the terminal command to exit and return its exit status
122    ///
123    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
124    async fn wait_for_terminal_exit(
125        &self,
126        _args: WaitForTerminalExitRequest,
127    ) -> Result<WaitForTerminalExitResponse> {
128        Err(Error::method_not_found())
129    }
130
131    /// Kills the terminal command without releasing the terminal
132    ///
133    /// While `terminal/release` will also kill the command, this method will keep
134    /// the `TerminalId` valid so it can be used with other methods.
135    ///
136    /// This method can be helpful when implementing command timeouts which terminate
137    /// the command as soon as elapsed, and then get the final output so it can be sent
138    /// to the model.
139    ///
140    /// Note: `terminal/release` when `TerminalId` is no longer needed.
141    ///
142    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)
143    async fn kill_terminal_command(
144        &self,
145        _args: KillTerminalCommandRequest,
146    ) -> Result<KillTerminalCommandResponse> {
147        Err(Error::method_not_found())
148    }
149
150    /// Handles extension method requests from the agent.
151    ///
152    /// Allows the Agent to send an arbitrary request that is not part of the ACP spec.
153    /// Extension methods provide a way to add custom functionality while maintaining
154    /// protocol compatibility.
155    ///
156    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
157    async fn ext_method(&self, _args: ExtRequest) -> Result<ExtResponse> {
158        Ok(RawValue::NULL.to_owned().into())
159    }
160
161    /// Handles extension notifications from the agent.
162    ///
163    /// Allows the Agent to send an arbitrary notification that is not part of the ACP spec.
164    /// Extension notifications provide a way to send one-way messages for custom functionality
165    /// while maintaining protocol compatibility.
166    ///
167    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
168    async fn ext_notification(&self, _args: ExtNotification) -> Result<()> {
169        Ok(())
170    }
171}
172
173#[async_trait::async_trait(?Send)]
174impl<T: Client> Client for Rc<T> {
175    async fn request_permission(
176        &self,
177        args: RequestPermissionRequest,
178    ) -> Result<RequestPermissionResponse> {
179        self.as_ref().request_permission(args).await
180    }
181    async fn write_text_file(&self, args: WriteTextFileRequest) -> Result<WriteTextFileResponse> {
182        self.as_ref().write_text_file(args).await
183    }
184    async fn read_text_file(&self, args: ReadTextFileRequest) -> Result<ReadTextFileResponse> {
185        self.as_ref().read_text_file(args).await
186    }
187    async fn session_notification(&self, args: SessionNotification) -> Result<()> {
188        self.as_ref().session_notification(args).await
189    }
190    async fn create_terminal(&self, args: CreateTerminalRequest) -> Result<CreateTerminalResponse> {
191        self.as_ref().create_terminal(args).await
192    }
193    async fn terminal_output(&self, args: TerminalOutputRequest) -> Result<TerminalOutputResponse> {
194        self.as_ref().terminal_output(args).await
195    }
196    async fn release_terminal(
197        &self,
198        args: ReleaseTerminalRequest,
199    ) -> Result<ReleaseTerminalResponse> {
200        self.as_ref().release_terminal(args).await
201    }
202    async fn wait_for_terminal_exit(
203        &self,
204        args: WaitForTerminalExitRequest,
205    ) -> Result<WaitForTerminalExitResponse> {
206        self.as_ref().wait_for_terminal_exit(args).await
207    }
208    async fn kill_terminal_command(
209        &self,
210        args: KillTerminalCommandRequest,
211    ) -> Result<KillTerminalCommandResponse> {
212        self.as_ref().kill_terminal_command(args).await
213    }
214    async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse> {
215        self.as_ref().ext_method(args).await
216    }
217    async fn ext_notification(&self, args: ExtNotification) -> Result<()> {
218        self.as_ref().ext_notification(args).await
219    }
220}
221
222#[async_trait::async_trait(?Send)]
223impl<T: Client> Client for Arc<T> {
224    async fn request_permission(
225        &self,
226        args: RequestPermissionRequest,
227    ) -> Result<RequestPermissionResponse> {
228        self.as_ref().request_permission(args).await
229    }
230    async fn write_text_file(&self, args: WriteTextFileRequest) -> Result<WriteTextFileResponse> {
231        self.as_ref().write_text_file(args).await
232    }
233    async fn read_text_file(&self, args: ReadTextFileRequest) -> Result<ReadTextFileResponse> {
234        self.as_ref().read_text_file(args).await
235    }
236    async fn session_notification(&self, args: SessionNotification) -> Result<()> {
237        self.as_ref().session_notification(args).await
238    }
239    async fn create_terminal(&self, args: CreateTerminalRequest) -> Result<CreateTerminalResponse> {
240        self.as_ref().create_terminal(args).await
241    }
242    async fn terminal_output(&self, args: TerminalOutputRequest) -> Result<TerminalOutputResponse> {
243        self.as_ref().terminal_output(args).await
244    }
245    async fn release_terminal(
246        &self,
247        args: ReleaseTerminalRequest,
248    ) -> Result<ReleaseTerminalResponse> {
249        self.as_ref().release_terminal(args).await
250    }
251    async fn wait_for_terminal_exit(
252        &self,
253        args: WaitForTerminalExitRequest,
254    ) -> Result<WaitForTerminalExitResponse> {
255        self.as_ref().wait_for_terminal_exit(args).await
256    }
257    async fn kill_terminal_command(
258        &self,
259        args: KillTerminalCommandRequest,
260    ) -> Result<KillTerminalCommandResponse> {
261        self.as_ref().kill_terminal_command(args).await
262    }
263    async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse> {
264        self.as_ref().ext_method(args).await
265    }
266    async fn ext_notification(&self, args: ExtNotification) -> Result<()> {
267        self.as_ref().ext_notification(args).await
268    }
269}