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: ToolCall,
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)]
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    Cancelled,
101    #[serde(rename_all = "camelCase")]
102    Selected {
103        option_id: PermissionOptionId,
104    },
105}
106
107// Write text file
108
109#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
110#[serde(rename_all = "camelCase")]
111pub struct WriteTextFileRequest {
112    pub session_id: SessionId,
113    pub path: PathBuf,
114    pub content: String,
115}
116
117// Read text file
118
119#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
120#[serde(rename_all = "camelCase")]
121pub struct ReadTextFileRequest {
122    pub session_id: SessionId,
123    pub path: PathBuf,
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub line: Option<u32>,
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub limit: Option<u32>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
131#[serde(rename_all = "camelCase")]
132pub struct ReadTextFileResponse {
133    pub content: String,
134}
135
136// Capabilities
137
138/// Capabilities supported by the client
139#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
140#[serde(rename_all = "camelCase")]
141pub struct ClientCapabilities {
142    /// FileSystem capabilities supported by the client.
143    #[serde(default)]
144    pub fs: FileSystemCapability,
145}
146
147#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
148#[serde(rename_all = "camelCase")]
149pub struct FileSystemCapability {
150    /// Client supports `fs/read_text_file`
151    #[serde(default)]
152    pub read_text_file: bool,
153    /// Client supports `fs/write_text_file`
154    #[serde(default)]
155    pub write_text_file: bool,
156}
157
158// Method schema
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ClientMethodNames {
162    pub session_request_permission: &'static str,
163    pub session_update: &'static str,
164    pub fs_write_text_file: &'static str,
165    pub fs_read_text_file: &'static str,
166}
167
168pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
169    session_update: SESSION_UPDATE_NOTIFICATION,
170    session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
171    fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
172    fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
173};
174
175pub const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
176pub const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
177pub const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
178pub const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
179
180/// Requests the agent sends to the client
181#[derive(Debug, Serialize, Deserialize, JsonSchema)]
182#[serde(untagged)]
183pub enum AgentRequest {
184    WriteTextFileRequest(WriteTextFileRequest),
185    ReadTextFileRequest(ReadTextFileRequest),
186    RequestPermissionRequest(RequestPermissionRequest),
187}
188
189/// Responses the client sends to the agent
190#[derive(Debug, Serialize, Deserialize, JsonSchema)]
191#[serde(untagged)]
192pub enum ClientResponse {
193    WriteTextFileResponse,
194    ReadTextFileResponse(ReadTextFileResponse),
195    RequestPermissionResponse(RequestPermissionResponse),
196}
197
198/// Notifications the agent sends to the client
199#[derive(Debug, Serialize, Deserialize, JsonSchema)]
200#[serde(untagged)]
201pub enum AgentNotification {
202    SessionNotification(SessionNotification),
203}