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/// The Client trait 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///
20/// See: <https://agentclientprotocol.com/protocol/prompt-turn#cancellation>
21pub trait Client {
22 /// Requests permission from the user for a tool call operation.
23 ///
24 /// Called by the agent when it needs user authorization before executing
25 /// a potentially sensitive operation. The client should present the options
26 /// to the user and return their decision.
27 ///
28 /// If the client cancels the prompt turn via `session/cancel`, it MUST
29 /// respond to this request with `RequestPermissionOutcome::Cancelled`.
30 ///
31 /// See: <https://agentclientprotocol.com/protocol/tool-calls#requesting-permission>
32 fn request_permission(
33 &self,
34 args: RequestPermissionRequest,
35 ) -> impl Future<Output = Result<RequestPermissionResponse, Error>>;
36
37 /// Writes content to a text file in the client's file system.
38 ///
39 /// Only available if the client advertises the `fs.writeTextFile` capability.
40 /// Allows the agent to create or modify files within the client's environment.
41 ///
42 /// See: <https://agentclientprotocol.com/protocol/overview#client>
43 fn write_text_file(
44 &self,
45 args: WriteTextFileRequest,
46 ) -> impl Future<Output = Result<(), Error>>;
47
48 /// Reads content from a text file in the client's file system.
49 ///
50 /// Only available if the client advertises the `fs.readTextFile` capability.
51 /// Allows the agent to access file contents within the client's environment.
52 ///
53 /// See: <https://agentclientprotocol.com/protocol/overview#client>
54 fn read_text_file(
55 &self,
56 args: ReadTextFileRequest,
57 ) -> impl Future<Output = Result<ReadTextFileResponse, Error>>;
58
59 /// Handles session update notifications from the agent.
60 ///
61 /// This is a notification endpoint (no response expected) that receives
62 /// real-time updates about session progress, including message chunks,
63 /// tool calls, and execution plans.
64 ///
65 /// Note: Clients SHOULD continue accepting tool call updates even after
66 /// sending a `session/cancel` notification, as the agent may send final
67 /// updates before responding with the cancelled stop reason.
68 ///
69 /// See: <https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output>
70 fn session_notification(
71 &self,
72 args: SessionNotification,
73 ) -> impl Future<Output = Result<(), Error>>;
74}
75
76// Session updates
77
78/// Notification containing a session update from the agent.
79///
80/// Used to stream real-time progress and results during prompt processing.
81///
82/// See: <https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output>
83#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
84#[serde(rename_all = "camelCase")]
85pub struct SessionNotification {
86 /// The ID of the session this update pertains to.
87 pub session_id: SessionId,
88 /// The actual update content.
89 pub update: SessionUpdate,
90}
91
92/// Different types of updates that can be sent during session processing.
93///
94/// These updates provide real-time feedback about the agent's progress.
95///
96/// See: <https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output>
97#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
98#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
99pub enum SessionUpdate {
100 /// A chunk of the user's message being streamed.
101 UserMessageChunk { content: ContentBlock },
102 /// A chunk of the agent's response being streamed.
103 AgentMessageChunk { content: ContentBlock },
104 /// A chunk of the agent's internal reasoning being streamed.
105 AgentThoughtChunk { content: ContentBlock },
106 /// Notification that a new tool call has been initiated.
107 ToolCall(ToolCall),
108 /// Update on the status or results of a tool call.
109 ToolCallUpdate(ToolCallUpdate),
110 /// The agent's execution plan for complex tasks.
111 /// See: <https://agentclientprotocol.com/protocol/agent-plan>
112 Plan(Plan),
113}
114
115// Permission
116
117/// Request for user permission to execute a tool call.
118///
119/// Sent when the agent needs authorization before performing a sensitive operation.
120///
121/// See: <https://agentclientprotocol.com/protocol/tool-calls#requesting-permission>
122#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
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#[serde(rename_all = "camelCase")]
175pub struct RequestPermissionResponse {
176 /// The user's decision on the permission request.
177 // This extra-level is unfortunately needed because the output must be an object
178 pub outcome: RequestPermissionOutcome,
179}
180
181/// The outcome of a permission request.
182#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
183#[serde(tag = "outcome", rename_all = "snake_case")]
184pub enum RequestPermissionOutcome {
185 /// The prompt turn was cancelled before the user responded.
186 ///
187 /// When a client sends a `session/cancel` notification to cancel an ongoing
188 /// prompt turn, it MUST respond to all pending `session/request_permission`
189 /// requests with this `Cancelled` outcome.
190 ///
191 /// See: <https://agentclientprotocol.com/protocol/prompt-turn#cancellation>
192 Cancelled,
193 /// The user selected one of the provided options.
194 #[serde(rename_all = "camelCase")]
195 Selected {
196 /// The ID of the option the user selected.
197 option_id: PermissionOptionId,
198 },
199}
200
201// Write text file
202
203/// Request to write content to a text file.
204///
205/// Only available if the client supports the `fs.writeTextFile` capability.
206#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
207#[serde(rename_all = "camelCase")]
208pub struct WriteTextFileRequest {
209 /// The session ID for this request.
210 pub session_id: SessionId,
211 /// Path to the file to write (relative to the session's working directory).
212 pub path: PathBuf,
213 /// The text content to write to the file.
214 /// The text content read from the file.
215 pub content: String,
216}
217
218// Read text file
219
220/// Request to read content from a text file.
221///
222/// Only available if the client supports the `fs.readTextFile` capability.
223#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
224#[serde(rename_all = "camelCase")]
225pub struct ReadTextFileRequest {
226 /// The session ID for this request.
227 pub session_id: SessionId,
228 /// Path to the file to read (relative to the session's working directory).
229 pub path: PathBuf,
230 /// Optional line number to start reading from (1-based).
231 #[serde(default, skip_serializing_if = "Option::is_none")]
232 pub line: Option<u32>,
233 /// Optional maximum number of lines to read.
234 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub limit: Option<u32>,
236}
237
238/// Response containing the contents of a text file.
239#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
240#[serde(rename_all = "camelCase")]
241pub struct ReadTextFileResponse {
242 pub content: String,
243}
244
245// Capabilities
246
247/// Capabilities supported by the client.
248///
249/// Advertised during initialization to inform the agent about
250/// available features and methods.
251///
252/// See: <https://agentclientprotocol.com/protocol/initialization#client-capabilities>
253#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
254#[serde(rename_all = "camelCase")]
255pub struct ClientCapabilities {
256 /// File system capabilities supported by the client.
257 /// Determines which file operations the agent can request.
258 #[serde(default)]
259 pub fs: FileSystemCapability,
260}
261
262/// File system capabilities that a client may support.
263///
264/// See: <https://agentclientprotocol.com/protocol/initialization#filesystem>
265#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
266#[serde(rename_all = "camelCase")]
267pub struct FileSystemCapability {
268 /// Whether the Client supports `fs/read_text_file` requests.
269 #[serde(default)]
270 pub read_text_file: bool,
271 /// Whether the Client supports `fs/write_text_file` requests.
272 #[serde(default)]
273 pub write_text_file: bool,
274}
275
276// Method schema
277
278/// Names of all methods that clients handle.
279///
280/// Provides a centralized definition of method names used in the protocol.
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct ClientMethodNames {
283 /// Method for requesting permission from the user.
284 pub session_request_permission: &'static str,
285 /// Notification for session updates.
286 pub session_update: &'static str,
287 /// Method for writing text files.
288 pub fs_write_text_file: &'static str,
289 /// Method for reading text files.
290 pub fs_read_text_file: &'static str,
291}
292
293/// Constant containing all client method names.
294pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
295 session_update: SESSION_UPDATE_NOTIFICATION,
296 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
297 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
298 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
299};
300
301/// Notification name for session updates.
302pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
303/// Method name for requesting user permission.
304pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
305/// Method name for writing text files.
306pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
307/// Method name for reading text files.
308pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
309
310/// All possible requests that an agent can send to a client.
311///
312/// This enum is used internally for routing RPC requests. You typically won't need
313/// to use this directly - instead, use the methods on the [`Client`] trait.
314///
315/// This enum encompasses all method calls from agent to client.
316#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
317#[serde(untagged)]
318pub enum AgentRequest {
319 WriteTextFileRequest(WriteTextFileRequest),
320 ReadTextFileRequest(ReadTextFileRequest),
321 RequestPermissionRequest(RequestPermissionRequest),
322}
323
324/// All possible responses that a client can send to an agent.
325///
326/// This enum is used internally for routing RPC responses. You typically won't need
327/// to use this directly - the responses are handled automatically by the connection.
328///
329/// These are responses to the corresponding AgentRequest variants.
330#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
331#[serde(untagged)]
332pub enum ClientResponse {
333 WriteTextFileResponse,
334 ReadTextFileResponse(ReadTextFileResponse),
335 RequestPermissionResponse(RequestPermissionResponse),
336}
337
338/// All possible notifications that an agent can send to a client.
339///
340/// This enum is used internally for routing RPC notifications. You typically won't need
341/// to use this directly - use the notification methods on the [`Client`] trait instead.
342///
343/// Notifications do not expect a response.
344#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
345#[serde(untagged)]
346pub enum AgentNotification {
347 SessionNotification(SessionNotification),
348}