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::{fmt, path::PathBuf, sync::Arc};
7
8use anyhow::Result;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use crate::{ContentBlock, Error, Plan, SessionId, ToolCall, ToolCallUpdate};
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.
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 fn request_permission(
31 &self,
32 args: RequestPermissionRequest,
33 ) -> impl Future<Output = Result<RequestPermissionResponse, Error>>;
34
35 /// Writes content to a text file in the client's file system.
36 ///
37 /// Only available if the client advertises the `fs.writeTextFile` capability.
38 /// Allows the agent to create or modify files within the client's environment.
39 ///
40 /// See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
41 fn write_text_file(
42 &self,
43 args: WriteTextFileRequest,
44 ) -> impl Future<Output = Result<(), Error>>;
45
46 /// Reads content from a text file in the client's file system.
47 ///
48 /// Only available if the client advertises the `fs.readTextFile` capability.
49 /// Allows the agent to access file contents within the client's environment.
50 ///
51 /// See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
52 fn read_text_file(
53 &self,
54 args: ReadTextFileRequest,
55 ) -> impl Future<Output = Result<ReadTextFileResponse, Error>>;
56
57 /// Handles session update notifications from the agent.
58 ///
59 /// This is a notification endpoint (no response expected) that receives
60 /// real-time updates about session progress, including message chunks,
61 /// tool calls, and execution plans.
62 ///
63 /// Note: Clients SHOULD continue accepting tool call updates even after
64 /// sending a `session/cancel` notification, as the agent may send final
65 /// updates before responding with the cancelled stop reason.
66 ///
67 /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
68 fn session_notification(
69 &self,
70 args: SessionNotification,
71 ) -> impl Future<Output = Result<(), Error>>;
72}
73
74// Session updates
75
76/// Notification containing a session update from the agent.
77///
78/// Used to stream real-time progress and results during prompt processing.
79///
80/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
81#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
82#[schemars(extend("x-side" = "client", "x-method" = "session/update"))]
83#[serde(rename_all = "camelCase")]
84pub struct SessionNotification {
85 /// The ID of the session this update pertains to.
86 pub session_id: SessionId,
87 /// The actual update content.
88 pub update: SessionUpdate,
89}
90
91/// Different types of updates that can be sent during session processing.
92///
93/// These updates provide real-time feedback about the agent's progress.
94///
95/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
96#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
97#[serde(rename_all = "snake_case", tag = "sessionUpdate")]
98pub enum SessionUpdate {
99 /// A chunk of the user's message being streamed.
100 UserMessageChunk { content: ContentBlock },
101 /// A chunk of the agent's response being streamed.
102 AgentMessageChunk { content: ContentBlock },
103 /// A chunk of the agent's internal reasoning being streamed.
104 AgentThoughtChunk { content: ContentBlock },
105 /// Notification that a new tool call has been initiated.
106 ToolCall(ToolCall),
107 /// Update on the status or results of a tool call.
108 ToolCallUpdate(ToolCallUpdate),
109 /// The agent's execution plan for complex tasks.
110 /// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
111 Plan(Plan),
112}
113
114// Permission
115
116/// Request for user permission to execute a tool call.
117///
118/// Sent when the agent needs authorization before performing a sensitive operation.
119///
120/// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
121#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
122#[schemars(extend("x-side" = "client", "x-method" = "session/request_permission"))]
123#[serde(rename_all = "camelCase")]
124pub struct RequestPermissionRequest {
125 /// The session ID for this request.
126 pub session_id: SessionId,
127 /// Details about the tool call requiring permission.
128 pub tool_call: ToolCallUpdate,
129 /// Available permission options for the user to choose from.
130 pub options: Vec<PermissionOption>,
131}
132
133/// An option presented to the user when requesting permission.
134#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
135pub struct PermissionOption {
136 /// Unique identifier for this permission option.
137 #[serde(rename = "optionId")]
138 pub id: PermissionOptionId,
139 /// Human-readable label to display to the user.
140 pub name: String,
141 /// Hint about the nature of this permission option.
142 pub kind: PermissionOptionKind,
143}
144
145/// Unique identifier for a permission option.
146#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
147#[serde(transparent)]
148pub struct PermissionOptionId(pub Arc<str>);
149
150impl fmt::Display for PermissionOptionId {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 self.0.fmt(f)
153 }
154}
155
156/// The type of permission option being presented to the user.
157///
158/// Helps clients choose appropriate icons and UI treatment.
159#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
160#[serde(rename_all = "snake_case")]
161pub enum PermissionOptionKind {
162 /// Allow this operation only this time.
163 AllowOnce,
164 /// Allow this operation and remember the choice.
165 AllowAlways,
166 /// Reject this operation only this time.
167 RejectOnce,
168 /// Reject this operation and remember the choice.
169 RejectAlways,
170}
171
172/// Response to a permission request.
173#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
174#[schemars(extend("x-side" = "client", "x-method" = "session/request_permission"))]
175#[serde(rename_all = "camelCase")]
176pub struct RequestPermissionResponse {
177 /// The user's decision on the permission request.
178 // This extra-level is unfortunately needed because the output must be an object
179 pub outcome: RequestPermissionOutcome,
180}
181
182/// The outcome of a permission request.
183#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
184#[serde(tag = "outcome", rename_all = "snake_case")]
185pub enum RequestPermissionOutcome {
186 /// The prompt turn was cancelled before the user responded.
187 ///
188 /// When a client sends a `session/cancel` notification to cancel an ongoing
189 /// prompt turn, it MUST respond to all pending `session/request_permission`
190 /// requests with this `Cancelled` outcome.
191 ///
192 /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation)
193 Cancelled,
194 /// The user selected one of the provided options.
195 #[serde(rename_all = "camelCase")]
196 Selected {
197 /// The ID of the option the user selected.
198 option_id: PermissionOptionId,
199 },
200}
201
202// Write text file
203
204/// Request to write content to a text file.
205///
206/// Only available if the client supports the `fs.writeTextFile` capability.
207#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
208#[schemars(extend("x-side" = "client", "x-method" = "fs/write_text_file"))]
209#[serde(rename_all = "camelCase")]
210pub struct WriteTextFileRequest {
211 /// The session ID for this request.
212 pub session_id: SessionId,
213 /// Absolute path to the file to write.
214 pub path: PathBuf,
215 /// The text content to write to the file.
216 pub content: String,
217}
218
219// Read text file
220
221/// Request to read content from a text file.
222///
223/// Only available if the client supports the `fs.readTextFile` capability.
224#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
225#[schemars(extend("x-side" = "client", "x-method" = "fs/read_text_file"))]
226#[serde(rename_all = "camelCase")]
227pub struct ReadTextFileRequest {
228 /// The session ID for this request.
229 pub session_id: SessionId,
230 /// Absolute path to the file to read.
231 pub path: PathBuf,
232 /// Optional line number to start reading from (1-based).
233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub line: Option<u32>,
235 /// Optional maximum number of lines to read.
236 #[serde(default, skip_serializing_if = "Option::is_none")]
237 pub limit: Option<u32>,
238}
239
240/// Response containing the contents of a text file.
241#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
242#[serde(rename_all = "camelCase")]
243pub struct ReadTextFileResponse {
244 pub content: String,
245}
246
247// Capabilities
248
249/// Capabilities supported by the client.
250///
251/// Advertised during initialization to inform the agent about
252/// available features and methods.
253///
254/// See protocol docs: [Client Capabilities](https://agentclientprotocol.com/protocol/initialization#client-capabilities)
255#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
256#[serde(rename_all = "camelCase")]
257pub struct ClientCapabilities {
258 /// File system capabilities supported by the client.
259 /// Determines which file operations the agent can request.
260 #[serde(default)]
261 pub fs: FileSystemCapability,
262}
263
264/// File system capabilities that a client may support.
265///
266/// See protocol docs: [FileSystem](https://agentclientprotocol.com/protocol/initialization#filesystem)
267#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
268#[serde(rename_all = "camelCase")]
269pub struct FileSystemCapability {
270 /// Whether the Client supports `fs/read_text_file` requests.
271 #[serde(default)]
272 pub read_text_file: bool,
273 /// Whether the Client supports `fs/write_text_file` requests.
274 #[serde(default)]
275 pub write_text_file: bool,
276}
277
278// Method schema
279
280/// Names of all methods that clients handle.
281///
282/// Provides a centralized definition of method names used in the protocol.
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct ClientMethodNames {
285 /// Method for requesting permission from the user.
286 pub session_request_permission: &'static str,
287 /// Notification for session updates.
288 pub session_update: &'static str,
289 /// Method for writing text files.
290 pub fs_write_text_file: &'static str,
291 /// Method for reading text files.
292 pub fs_read_text_file: &'static str,
293}
294
295/// Constant containing all client method names.
296pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
297 session_update: SESSION_UPDATE_NOTIFICATION,
298 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
299 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
300 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
301};
302
303/// Notification name for session updates.
304pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
305/// Method name for requesting user permission.
306pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
307/// Method name for writing text files.
308pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
309/// Method name for reading text files.
310pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
311
312/// All possible requests that an agent can send to a client.
313///
314/// This enum is used internally for routing RPC requests. You typically won't need
315/// to use this directly - instead, use the methods on the [`Client`] trait.
316///
317/// This enum encompasses all method calls from agent to client.
318#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
319#[serde(untagged)]
320#[schemars(extend("x-docs-ignore" = true))]
321pub enum AgentRequest {
322 WriteTextFileRequest(WriteTextFileRequest),
323 ReadTextFileRequest(ReadTextFileRequest),
324 RequestPermissionRequest(RequestPermissionRequest),
325}
326
327/// All possible responses that a client can send to an agent.
328///
329/// This enum is used internally for routing RPC responses. You typically won't need
330/// to use this directly - the responses are handled automatically by the connection.
331///
332/// These are responses to the corresponding AgentRequest variants.
333#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
334#[serde(untagged)]
335#[schemars(extend("x-docs-ignore" = true))]
336pub enum ClientResponse {
337 WriteTextFileResponse,
338 ReadTextFileResponse(ReadTextFileResponse),
339 RequestPermissionResponse(RequestPermissionResponse),
340}
341
342/// All possible notifications that an agent can send to a client.
343///
344/// This enum is used internally for routing RPC notifications. You typically won't need
345/// to use this directly - use the notification methods on the [`Client`] trait instead.
346///
347/// Notifications do not expect a response.
348#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
349#[serde(untagged)]
350#[schemars(extend("x-docs-ignore" = true))]
351pub enum AgentNotification {
352 SessionNotification(SessionNotification),
353}