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}