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