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