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