1use std::path::{Path, PathBuf};
2
3use anyhow::{Result, anyhow};
4use chrono::{DateTime, Utc};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use serde_json::value::RawValue;
8
9#[derive(Serialize, JsonSchema)]
10#[serde(rename_all = "camelCase")]
11pub struct Method {
12 pub name: &'static str,
13 pub request_type: &'static str,
14 pub param_payload: bool,
15 pub response_type: &'static str,
16 pub response_payload: bool,
17}
18
19pub trait AnyRequest: Serialize + Sized + 'static {
20 type Response: Serialize + 'static;
21 fn from_method_and_params(method: &str, params: &RawValue) -> Result<Self>;
22 fn response_from_method_and_result(method: &str, params: &RawValue) -> Result<Self::Response>;
23}
24
25macro_rules! acp_peer {
26 (
27 $handler_trait_name:ident,
28 $request_trait_name:ident,
29 $request_enum_name:ident,
30 $response_enum_name:ident,
31 $method_map_name:ident,
32 $(($request_method:ident, $request_method_string:expr, $request_name:ident, $param_payload: tt, $response_name:ident, $response_payload: tt)),*
33 $(,)?
34 ) => {
35 macro_rules! handler_trait_call_req {
36 ($self: ident, $method: ident, false, $resp_name: ident, false, $params: ident) => {
37 {
38 $self.$method().await?;
39 Ok($response_enum_name::$resp_name($resp_name))
40 }
41 };
42 ($self: ident, $method: ident, false, $resp_name: ident, true, $params: ident) => {
43 {
44 let resp = $self.$method().await?;
45 Ok($response_enum_name::$resp_name(resp))
46 }
47 };
48 ($self: ident, $method: ident, true, $resp_name: ident, false, $params: ident) => {
49 {
50 $self.$method($params).await?;
51 Ok($response_enum_name::$resp_name($resp_name))
52 }
53 };
54 ($self: ident, $method: ident, true, $resp_name: ident, true, $params: ident) => {
55 {
56 let resp = $self.$method($params).await?;
57 Ok($response_enum_name::$resp_name(resp))
58 }
59 }
60 }
61
62 macro_rules! handler_trait_req_method {
63 ($method: ident, $req: ident, false, $resp: tt, false) => {
64 fn $method(&self) -> impl Future<Output = Result<()>>;
65 };
66 ($method: ident, $req: ident, false, $resp: tt, true) => {
67 fn $method(&self) -> impl Future<Output = Result<$resp>>;
68 };
69 ($method: ident, $req: ident, true, $resp: tt, false) => {
70 fn $method(&self, request: $req) -> impl Future<Output = Result<()>>;
71 };
72 ($method: ident, $req: ident, true, $resp: tt, true) => {
73 fn $method(&self, request: $req) -> impl Future<Output = Result<$resp>>;
74 }
75 }
76
77 pub trait $handler_trait_name {
78 fn call(&self, params: $request_enum_name) -> impl Future<Output = Result<$response_enum_name>> {
79 async move {
80 match params {
81 $(#[allow(unused_variables)]
82 $request_enum_name::$request_name(params) => {
83 handler_trait_call_req!(self, $request_method, $param_payload, $response_name, $response_payload, params)
84 }),*
85 }
86 }
87 }
88
89 $(
90 handler_trait_req_method!($request_method, $request_name, $param_payload, $response_name, $response_payload);
91 )*
92 }
93
94 pub trait $request_trait_name {
95 type Response;
96 fn into_any(self) -> $request_enum_name;
97 fn response_from_any(any: $response_enum_name) -> Option<Self::Response>;
98 }
99
100 #[derive(Serialize, JsonSchema)]
101 #[serde(untagged)]
102 pub enum $request_enum_name {
103 $(
104 $request_name($request_name),
105 )*
106 }
107
108 #[derive(Serialize, Deserialize, JsonSchema)]
109 #[serde(untagged)]
110 pub enum $response_enum_name {
111 $(
112 $response_name($response_name),
113 )*
114 }
115
116 macro_rules! request_from_method_and_params {
117 ($req_name: ident, false, $params: tt) => {
118 Ok($request_enum_name::$req_name($req_name))
119 };
120 ($req_name: ident, true, $params: tt) => {
121 match serde_json::from_str($params.get()) {
122 Ok(params) => Ok($request_enum_name::$req_name(params)),
123 Err(e) => Err(anyhow!(e.to_string())),
124 }
125 };
126 }
127
128 macro_rules! response_from_method_and_result {
129 ($resp_name: ident, false, $result: tt) => {
130 Ok($response_enum_name::$resp_name($resp_name))
131 };
132 ($resp_name: ident, true, $result: tt) => {
133 match serde_json::from_str($result.get()) {
134 Ok(result) => Ok($response_enum_name::$resp_name(result)),
135 Err(e) => Err(anyhow!(e.to_string())),
136 }
137 };
138 }
139
140 impl AnyRequest for $request_enum_name {
141 type Response = $response_enum_name;
142
143 fn from_method_and_params(method: &str, params: &RawValue) -> Result<Self> {
144 match method {
145 $(
146 $request_method_string => {
147 request_from_method_and_params!($request_name, $param_payload, params)
148 }
149 )*
150 _ => Err(anyhow!("invalid method string {}", method)),
151 }
152 }
153
154 fn response_from_method_and_result(method: &str, params: &RawValue) -> Result<Self::Response> {
155 match method {
156 $(
157 $request_method_string => {
158 response_from_method_and_result!($response_name, $response_payload, params)
159 }
160 )*
161 _ => Err(anyhow!("invalid method string {}", method)),
162 }
163 }
164 }
165
166 impl $request_enum_name {
167 pub fn method_name(&self) -> &'static str {
168 match self {
169 $(
170 $request_enum_name::$request_name(_) => $request_method_string,
171 )*
172 }
173 }
174 }
175
176
177
178 pub static $method_map_name: &[Method] = &[
179 $(
180 Method {
181 name: $request_method_string,
182 request_type: stringify!($request_name),
183 param_payload: $param_payload,
184 response_type: stringify!($response_name),
185 response_payload: $response_payload,
186 },
187 )*
188 ];
189
190 macro_rules! req_into_any {
191 ($self: ident, $req_name: ident, false) => {
192 $request_enum_name::$req_name($req_name)
193 };
194 ($self: ident, $req_name: ident, true) => {
195 $request_enum_name::$req_name($self)
196 };
197 }
198
199 macro_rules! resp_type {
200 ($resp_name: ident, false) => {
201 ()
202 };
203 ($resp_name: ident, true) => {
204 $resp_name
205 };
206 }
207
208 macro_rules! resp_from_any {
209 ($any: ident, $resp_name: ident, false) => {
210 match $any {
211 $response_enum_name::$resp_name(_) => Some(()),
212 _ => None
213 }
214 };
215 ($any: ident, $resp_name: ident, true) => {
216 match $any {
217 $response_enum_name::$resp_name(this) => Some(this),
218 _ => None
219 }
220 };
221 }
222
223 $(
224 impl $request_trait_name for $request_name {
225 type Response = resp_type!($response_name, $response_payload);
226
227 fn into_any(self) -> $request_enum_name {
228 req_into_any!(self, $request_name, $param_payload)
229 }
230
231 fn response_from_any(any: $response_enum_name) -> Option<Self::Response> {
232 resp_from_any!(any, $response_name, $response_payload)
233 }
234 }
235 )*
236 };
237}
238
239acp_peer!(
240 Client,
241 ClientRequest,
242 AnyClientRequest,
243 AnyClientResult,
244 CLIENT_METHODS,
245 (
246 stream_assistant_message_chunk,
247 "streamAssistantMessageChunk",
248 StreamAssistantMessageChunkParams,
249 true,
250 StreamAssistantMessageChunkResponse,
251 false
252 ),
253 (
254 request_tool_call_confirmation,
255 "requestToolCallConfirmation",
256 RequestToolCallConfirmationParams,
257 true,
258 RequestToolCallConfirmationResponse,
259 true
260 ),
261 (
262 push_tool_call,
263 "pushToolCall",
264 PushToolCallParams,
265 true,
266 PushToolCallResponse,
267 true
268 ),
269 (
270 update_tool_call,
271 "updateToolCall",
272 UpdateToolCallParams,
273 true,
274 UpdateToolCallResponse,
275 false
276 ),
277);
278
279acp_peer!(
280 Agent,
281 AgentRequest,
282 AnyAgentRequest,
283 AnyAgentResult,
284 AGENT_METHODS,
285 (
286 initialize,
287 "initialize",
288 InitializeParams,
289 false,
290 InitializeResponse,
291 true
292 ),
293 (
294 authenticate,
295 "authenticate",
296 AuthenticateParams,
297 false,
298 AuthenticateResponse,
299 false
300 ),
301 (
302 send_user_message,
303 "sendUserMessage",
304 SendUserMessageParams,
305 true,
306 SendUserMessageResponse,
307 false
308 ),
309 (
310 cancel_send_message,
311 "cancelSendMessage",
312 CancelSendMessageParams,
313 false,
314 CancelSendMessageResponse,
315 false
316 )
317);
318
319#[derive(Debug, Serialize, Deserialize, JsonSchema)]
320#[serde(rename_all = "camelCase")]
321pub struct InitializeParams;
322
323#[derive(Debug, Serialize, Deserialize, JsonSchema)]
324#[serde(rename_all = "camelCase")]
325pub struct InitializeResponse {
326 pub is_authenticated: bool,
327}
328
329#[derive(Debug, Serialize, Deserialize, JsonSchema)]
330#[serde(rename_all = "camelCase")]
331pub struct AuthenticateParams;
332
333#[derive(Debug, Serialize, Deserialize, JsonSchema)]
334#[serde(rename_all = "camelCase")]
335pub struct AuthenticateResponse;
336
337#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
338#[serde(rename_all = "camelCase")]
339pub struct UserMessage {
340 pub chunks: Vec<UserMessageChunk>,
341}
342
343impl<T> From<T> for UserMessage
344where
345 T: Into<UserMessageChunk>,
346{
347 fn from(value: T) -> Self {
348 Self {
349 chunks: vec![value.into()],
350 }
351 }
352}
353
354#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
355#[serde(tag = "type", rename_all = "camelCase")]
356pub enum UserMessageChunk {
357 Text { chunk: String },
358 Path { path: PathBuf },
359}
360
361impl From<&str> for UserMessageChunk {
362 fn from(value: &str) -> Self {
363 Self::Text {
364 chunk: value.into(),
365 }
366 }
367}
368
369impl From<&String> for UserMessageChunk {
370 fn from(value: &String) -> Self {
371 Self::Text {
372 chunk: value.clone(),
373 }
374 }
375}
376
377impl From<String> for UserMessageChunk {
378 fn from(value: String) -> Self {
379 Self::Text { chunk: value }
380 }
381}
382
383impl From<PathBuf> for UserMessageChunk {
384 fn from(value: PathBuf) -> Self {
385 Self::Path { path: value }
386 }
387}
388
389impl From<&Path> for UserMessageChunk {
390 fn from(value: &Path) -> Self {
391 Self::Path { path: value.into() }
392 }
393}
394
395#[derive(Debug, Serialize, Deserialize, JsonSchema)]
396#[serde(tag = "type", rename_all = "camelCase")]
397pub enum AssistantMessageChunk {
398 Text { chunk: String },
399 Thought { chunk: String },
400}
401
402#[derive(Debug, Serialize, Deserialize, JsonSchema)]
403#[serde(rename_all = "camelCase")]
404pub struct ThreadMetadata {
405 pub title: String,
406 pub modified_at: DateTime<Utc>,
407}
408
409#[derive(Debug, Serialize, Deserialize, JsonSchema)]
410#[serde(rename_all = "camelCase")]
411pub struct SendUserMessageParams {
412 pub message: UserMessage,
413}
414
415#[derive(Debug, Serialize, Deserialize, JsonSchema)]
416#[serde(rename_all = "camelCase")]
417pub struct SendUserMessageResponse;
418
419#[derive(Debug, Serialize, Deserialize, JsonSchema)]
420#[serde(rename_all = "camelCase")]
421pub struct StreamAssistantMessageChunkParams {
422 pub chunk: AssistantMessageChunk,
423}
424
425#[derive(Debug, Serialize, Deserialize, JsonSchema)]
426#[serde(rename_all = "camelCase")]
427pub struct StreamAssistantMessageChunkResponse;
428
429#[derive(Debug, Serialize, Deserialize, JsonSchema)]
430#[serde(rename_all = "camelCase")]
431pub struct RequestToolCallConfirmationParams {
432 pub label: String,
433 pub icon: Icon,
434 pub confirmation: ToolCallConfirmation,
435 #[serde(skip_serializing_if = "Option::is_none")]
436 pub content: Option<ToolCallContent>,
437}
438
439#[derive(Debug, Serialize, Deserialize, JsonSchema)]
440#[serde(rename_all = "camelCase")]
441pub enum Icon {
442 FileSearch,
443 Folder,
444 Globe,
445 Hammer,
446 LightBulb,
447 Pencil,
448 Regex,
449 Terminal,
450}
451
452#[derive(Debug, Serialize, Deserialize, JsonSchema)]
453#[serde(tag = "type", rename_all = "camelCase")]
454pub enum ToolCallConfirmation {
455 #[serde(rename_all = "camelCase")]
456 Edit {
457 #[serde(skip_serializing_if = "Option::is_none")]
458 description: Option<String>,
459 },
460 #[serde(rename_all = "camelCase")]
461 Execute {
462 command: String,
463 root_command: String,
464 #[serde(skip_serializing_if = "Option::is_none")]
465 description: Option<String>,
466 },
467 #[serde(rename_all = "camelCase")]
468 Mcp {
469 server_name: String,
470 tool_name: String,
471 tool_display_name: String,
472 #[serde(skip_serializing_if = "Option::is_none")]
473 description: Option<String>,
474 },
475 #[serde(rename_all = "camelCase")]
476 Fetch {
477 urls: Vec<String>,
478 #[serde(skip_serializing_if = "Option::is_none")]
479 description: Option<String>,
480 },
481 #[serde(rename_all = "camelCase")]
482 Other { description: String },
483}
484
485#[derive(Debug, Serialize, Deserialize, JsonSchema)]
486#[serde(tag = "type", rename_all = "camelCase")]
487pub struct RequestToolCallConfirmationResponse {
488 pub id: ToolCallId,
489 pub outcome: ToolCallConfirmationOutcome,
490}
491
492#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
493#[serde(rename_all = "camelCase")]
494pub enum ToolCallConfirmationOutcome {
495 Allow,
496 AlwaysAllow,
497 AlwaysAllowMcpServer,
498 AlwaysAllowTool,
499 Reject,
500 Cancel,
501}
502
503#[derive(Debug, Serialize, Deserialize, JsonSchema)]
504#[serde(rename_all = "camelCase")]
505pub struct PushToolCallParams {
506 pub label: String,
507 pub icon: Icon,
508 #[serde(skip_serializing_if = "Option::is_none")]
509 pub content: Option<ToolCallContent>,
510}
511
512#[derive(Debug, Serialize, Deserialize, JsonSchema)]
513#[serde(tag = "type", rename_all = "camelCase")]
514pub struct PushToolCallResponse {
515 pub id: ToolCallId,
516}
517
518#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Hash)]
519#[serde(rename_all = "camelCase")]
520pub struct ToolCallId(pub u64);
521
522#[derive(Debug, Serialize, Deserialize, JsonSchema)]
523#[serde(rename_all = "camelCase")]
524pub struct UpdateToolCallParams {
525 pub tool_call_id: ToolCallId,
526 pub status: ToolCallStatus,
527 pub content: Option<ToolCallContent>,
528}
529
530#[derive(Debug, Serialize, Deserialize, JsonSchema)]
531pub struct UpdateToolCallResponse;
532
533#[derive(Debug, Serialize, Deserialize, JsonSchema)]
534#[serde(rename_all = "camelCase")]
535pub enum ToolCallStatus {
536 Running,
537 Finished,
538 Error,
539}
540
541#[derive(Debug, Serialize, Deserialize, JsonSchema)]
542#[serde(tag = "type", rename_all = "camelCase")]
543pub enum ToolCallContent {
544 #[serde(rename_all = "camelCase")]
545 Markdown { markdown: String },
546 #[serde(rename_all = "camelCase")]
547 Diff {
548 #[serde(flatten)]
549 diff: Diff,
550 },
551}
552
553#[derive(Debug, Serialize, Deserialize, JsonSchema)]
554#[serde(rename_all = "camelCase")]
555pub struct Diff {
556 pub path: PathBuf,
557 pub old_text: Option<String>,
558 pub new_text: String,
559}
560
561#[derive(Debug, Serialize, Deserialize, JsonSchema)]
562#[serde(rename_all = "camelCase")]
563pub struct CancelSendMessageParams;
564
565#[derive(Debug, Serialize, Deserialize, JsonSchema)]
566#[serde(rename_all = "camelCase")]
567pub struct CancelSendMessageResponse;