agent_client_protocol/
client.rs

1//! Methods and notifications the client handles/receives
2
3use std::{fmt, path::PathBuf, sync::Arc};
4
5use anyhow::Result;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9use crate::{ContentBlock, Error, Plan, SessionId, ToolCall, ToolCallUpdate};
10
11pub trait Client {
12    fn request_permission(
13        &self,
14        args: RequestPermissionRequest,
15    ) -> impl Future<Output = Result<RequestPermissionResponse, Error>>;
16
17    fn write_text_file(
18        &self,
19        args: WriteTextFileRequest,
20    ) -> impl Future<Output = Result<(), Error>>;
21
22    fn read_text_file(
23        &self,
24        args: ReadTextFileRequest,
25    ) -> impl Future<Output = Result<ReadTextFileResponse, Error>>;
26
27    fn session_notification(
28        &self,
29        args: SessionNotification,
30    ) -> impl Future<Output = Result<(), Error>>;
31}
32
33// Session updates
34
35#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
36#[serde(rename_all = "camelCase")]
37pub struct SessionNotification {
38    pub session_id: SessionId,
39    pub update: SessionUpdate,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
43#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
44pub enum SessionUpdate {
45    UserMessageChunk { content: ContentBlock },
46    AgentMessageChunk { content: ContentBlock },
47    AgentThoughtChunk { content: ContentBlock },
48    ToolCall(ToolCall),
49    ToolCallUpdate(ToolCallUpdate),
50    Plan(Plan),
51}
52
53// Permission
54
55#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
56#[serde(rename_all = "camelCase")]
57pub struct RequestPermissionRequest {
58    pub session_id: SessionId,
59    pub tool_call: ToolCallUpdate,
60    pub options: Vec<PermissionOption>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
64pub struct PermissionOption {
65    #[serde(rename = "optionId")]
66    pub id: PermissionOptionId,
67    pub name: String,
68    pub kind: PermissionOptionKind,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
72#[serde(transparent)]
73pub struct PermissionOptionId(pub Arc<str>);
74
75impl fmt::Display for PermissionOptionId {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        self.0.fmt(f)
78    }
79}
80
81#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
82#[serde(rename_all = "snake_case")]
83pub enum PermissionOptionKind {
84    AllowOnce,
85    AllowAlways,
86    RejectOnce,
87    RejectAlways,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
91#[serde(rename_all = "camelCase")]
92pub struct RequestPermissionResponse {
93    // This extra-level is unfortunately needed because the output must be an object
94    pub outcome: RequestPermissionOutcome,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
98#[serde(tag = "outcome", rename_all = "snake_case")]
99pub enum RequestPermissionOutcome {
100    #[serde(alias = "cancelled")]
101    Canceled,
102    #[serde(rename_all = "camelCase")]
103    Selected { option_id: PermissionOptionId },
104}
105
106// Write text file
107
108#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
109#[serde(rename_all = "camelCase")]
110pub struct WriteTextFileRequest {
111    pub session_id: SessionId,
112    pub path: PathBuf,
113    pub content: String,
114}
115
116// Read text file
117
118#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
119#[serde(rename_all = "camelCase")]
120pub struct ReadTextFileRequest {
121    pub session_id: SessionId,
122    pub path: PathBuf,
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub line: Option<u32>,
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub limit: Option<u32>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
130#[serde(rename_all = "camelCase")]
131pub struct ReadTextFileResponse {
132    pub content: String,
133}
134
135// Capabilities
136
137/// Capabilities supported by the client
138#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
139#[serde(rename_all = "camelCase")]
140pub struct ClientCapabilities {
141    /// FileSystem capabilities supported by the client.
142    #[serde(default)]
143    pub fs: FileSystemCapability,
144}
145
146#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
147#[serde(rename_all = "camelCase")]
148pub struct FileSystemCapability {
149    /// Whether the Client supports `fs/read_text_file` requests.
150    #[serde(default)]
151    pub read_text_file: bool,
152    /// Whether the Client supports `fs/write_text_file` requests.
153    #[serde(default)]
154    pub write_text_file: bool,
155}
156
157// Method schema
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ClientMethodNames {
161    pub session_request_permission: &'static str,
162    pub session_update: &'static str,
163    pub fs_write_text_file: &'static str,
164    pub fs_read_text_file: &'static str,
165}
166
167pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
168    session_update: SESSION_UPDATE_NOTIFICATION,
169    session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
170    fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
171    fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
172};
173
174pub const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
175pub const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
176pub const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
177pub const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
178
179/// Requests the agent sends to the client
180#[derive(Debug, Serialize, Deserialize, JsonSchema)]
181#[serde(untagged)]
182pub enum AgentRequest {
183    WriteTextFileRequest(WriteTextFileRequest),
184    ReadTextFileRequest(ReadTextFileRequest),
185    RequestPermissionRequest(RequestPermissionRequest),
186}
187
188/// Responses the client sends to the agent
189#[derive(Debug, Serialize, Deserialize, JsonSchema)]
190#[serde(untagged)]
191pub enum ClientResponse {
192    WriteTextFileResponse,
193    ReadTextFileResponse(ReadTextFileResponse),
194    RequestPermissionResponse(RequestPermissionResponse),
195}
196
197/// Notifications the agent sends to the client
198#[derive(Debug, Serialize, Deserialize, JsonSchema)]
199#[serde(untagged)]
200pub enum AgentNotification {
201    SessionNotification(SessionNotification),
202}