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