1use std::{fmt::Display, ops::Deref, path::PathBuf};
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use serde_json::value::RawValue;
6
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
8pub struct Error {
9 pub code: i32,
10 pub message: String,
11 #[serde(skip_serializing_if = "Option::is_none")]
12 pub data: Option<serde_json::Value>,
13}
14
15impl Error {
16 pub fn new(code: impl Into<(i32, String)>) -> Self {
17 let (code, message) = code.into();
18 Error {
19 code,
20 message,
21 data: None,
22 }
23 }
24
25 pub fn with_data(mut self, data: impl Into<serde_json::Value>) -> Self {
26 self.data = Some(data.into());
27 self
28 }
29
30 pub fn parse_error() -> Self {
32 Error::new(ErrorCode::PARSE_ERROR)
33 }
34
35 pub fn invalid_request() -> Self {
37 Error::new(ErrorCode::INVALID_REQUEST)
38 }
39
40 pub fn method_not_found() -> Self {
42 Error::new(ErrorCode::METHOD_NOT_FOUND)
43 }
44
45 pub fn invalid_params() -> Self {
47 Error::new(ErrorCode::INVALID_PARAMS)
48 }
49
50 pub fn internal_error() -> Self {
52 Error::new(ErrorCode::INTERNAL_ERROR)
53 }
54
55 pub fn into_internal_error(err: impl std::error::Error) -> Self {
56 Error::internal_error().with_data(err.to_string())
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
61pub struct ErrorCode {
62 code: i32,
63 message: &'static str,
64}
65
66impl ErrorCode {
67 pub const PARSE_ERROR: ErrorCode = ErrorCode {
68 code: -32700,
69 message: "Parse error",
70 };
71
72 pub const INVALID_REQUEST: ErrorCode = ErrorCode {
73 code: -32600,
74 message: "Invalid Request",
75 };
76
77 pub const METHOD_NOT_FOUND: ErrorCode = ErrorCode {
78 code: -32601,
79 message: "Method not found",
80 };
81
82 pub const INVALID_PARAMS: ErrorCode = ErrorCode {
83 code: -32602,
84 message: "Invalid params",
85 };
86
87 pub const INTERNAL_ERROR: ErrorCode = ErrorCode {
88 code: -32603,
89 message: "Internal error",
90 };
91}
92
93impl From<ErrorCode> for (i32, String) {
94 fn from(error_code: ErrorCode) -> Self {
95 (error_code.code, error_code.message.to_string())
96 }
97}
98
99impl From<ErrorCode> for Error {
100 fn from(error_code: ErrorCode) -> Self {
101 Error::new(error_code)
102 }
103}
104
105impl std::error::Error for Error {}
106
107impl Display for Error {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 if self.message.is_empty() {
110 write!(f, "{}", self.code)?;
111 } else {
112 write!(f, "{}", self.message)?;
113 }
114
115 if let Some(data) = &self.data {
116 write!(f, ": {data}")?;
117 }
118
119 Ok(())
120 }
121}
122
123impl From<anyhow::Error> for Error {
124 fn from(error: anyhow::Error) -> Self {
125 Error::into_internal_error(error.deref())
126 }
127}
128
129#[derive(Serialize, JsonSchema)]
130#[serde(rename_all = "camelCase")]
131pub struct Method {
132 pub name: &'static str,
133 pub request_type: &'static str,
134 pub param_payload: bool,
135 pub response_type: &'static str,
136 pub response_payload: bool,
137}
138
139pub trait AnyRequest: Serialize + Sized + 'static {
140 type Response: Serialize + 'static;
141 fn from_method_and_params(method: &str, params: &RawValue) -> Result<Self, Error>;
142 fn response_from_method_and_result(
143 method: &str,
144 params: &RawValue,
145 ) -> Result<Self::Response, Error>;
146}
147
148macro_rules! acp_peer {
149 (
150 $handler_trait_name:ident,
151 $request_trait_name:ident,
152 $request_enum_name:ident,
153 $response_enum_name:ident,
154 $method_map_name:ident,
155 $(($request_method:ident, $request_method_string:expr, $request_name:ident, $param_payload: tt, $response_name:ident, $response_payload: tt)),*
156 $(,)?
157 ) => {
158 macro_rules! handler_trait_call_req {
159 ($self: ident, $method: ident, false, $resp_name: ident, false, $params: ident) => {
160 {
161 $self.$method().await?;
162 Ok($response_enum_name::$resp_name($resp_name))
163 }
164 };
165 ($self: ident, $method: ident, false, $resp_name: ident, true, $params: ident) => {
166 {
167 let resp = $self.$method().await?;
168 Ok($response_enum_name::$resp_name(resp))
169 }
170 };
171 ($self: ident, $method: ident, true, $resp_name: ident, false, $params: ident) => {
172 {
173 $self.$method($params).await?;
174 Ok($response_enum_name::$resp_name($resp_name))
175 }
176 };
177 ($self: ident, $method: ident, true, $resp_name: ident, true, $params: ident) => {
178 {
179 let resp = $self.$method($params).await?;
180 Ok($response_enum_name::$resp_name(resp))
181 }
182 }
183 }
184
185 macro_rules! handler_trait_req_method {
186 ($method: ident, $req: ident, false, $resp: tt, false) => {
187 fn $method(&self) -> impl Future<Output = Result<(), Error>>;
188 };
189 ($method: ident, $req: ident, false, $resp: tt, true) => {
190 fn $method(&self) -> impl Future<Output = Result<$resp, Error>>;
191 };
192 ($method: ident, $req: ident, true, $resp: tt, false) => {
193 fn $method(&self, request: $req) -> impl Future<Output = Result<(), Error>>;
194 };
195 ($method: ident, $req: ident, true, $resp: tt, true) => {
196 fn $method(&self, request: $req) -> impl Future<Output = Result<$resp, Error>>;
197 }
198 }
199
200 pub trait $handler_trait_name {
201 fn call(&self, params: $request_enum_name) -> impl Future<Output = Result<$response_enum_name, Error>> {
202 async move {
203 match params {
204 $(#[allow(unused_variables)]
205 $request_enum_name::$request_name(params) => {
206 handler_trait_call_req!(self, $request_method, $param_payload, $response_name, $response_payload, params)
207 }),*
208 }
209 }
210 }
211
212 $(
213 handler_trait_req_method!($request_method, $request_name, $param_payload, $response_name, $response_payload);
214 )*
215 }
216
217 pub trait $request_trait_name {
218 type Response;
219 fn into_any(self) -> $request_enum_name;
220 fn response_from_any(any: $response_enum_name) -> Result<Self::Response, Error>;
221 }
222
223 #[derive(Serialize, JsonSchema)]
224 #[serde(untagged)]
225 pub enum $request_enum_name {
226 $(
227 $request_name($request_name),
228 )*
229 }
230
231 #[derive(Serialize, Deserialize, JsonSchema)]
232 #[serde(untagged)]
233 pub enum $response_enum_name {
234 $(
235 $response_name($response_name),
236 )*
237 }
238
239 macro_rules! request_from_method_and_params {
240 ($req_name: ident, false, $params: tt) => {
241 Ok($request_enum_name::$req_name($req_name))
242 };
243 ($req_name: ident, true, $params: tt) => {
244 match serde_json::from_str($params.get()) {
245 Ok(params) => Ok($request_enum_name::$req_name(params)),
246 Err(e) => Err(Error::parse_error().with_data(e.to_string())),
247 }
248 };
249 }
250
251 macro_rules! response_from_method_and_result {
252 ($resp_name: ident, false, $result: tt) => {
253 Ok($response_enum_name::$resp_name($resp_name))
254 };
255 ($resp_name: ident, true, $result: tt) => {
256 match serde_json::from_str($result.get()) {
257 Ok(result) => Ok($response_enum_name::$resp_name(result)),
258 Err(e) => Err(Error::parse_error().with_data(e.to_string())),
259 }
260 };
261 }
262
263 impl AnyRequest for $request_enum_name {
264 type Response = $response_enum_name;
265
266 fn from_method_and_params(method: &str, params: &RawValue) -> Result<Self, Error> {
267 match method {
268 $(
269 $request_method_string => {
270 request_from_method_and_params!($request_name, $param_payload, params)
271 }
272 )*
273 _ => Err(Error::method_not_found()),
274 }
275 }
276
277 fn response_from_method_and_result(method: &str, params: &RawValue) -> Result<Self::Response, Error> {
278 match method {
279 $(
280 $request_method_string => {
281 response_from_method_and_result!($response_name, $response_payload, params)
282 }
283 )*
284 _ => Err(Error::method_not_found()),
285 }
286 }
287 }
288
289 impl $request_enum_name {
290 pub fn method_name(&self) -> &'static str {
291 match self {
292 $(
293 $request_enum_name::$request_name(_) => $request_method_string,
294 )*
295 }
296 }
297 }
298
299
300
301 pub static $method_map_name: &[Method] = &[
302 $(
303 Method {
304 name: $request_method_string,
305 request_type: stringify!($request_name),
306 param_payload: $param_payload,
307 response_type: stringify!($response_name),
308 response_payload: $response_payload,
309 },
310 )*
311 ];
312
313 macro_rules! req_into_any {
314 ($self: ident, $req_name: ident, false) => {
315 $request_enum_name::$req_name($req_name)
316 };
317 ($self: ident, $req_name: ident, true) => {
318 $request_enum_name::$req_name($self)
319 };
320 }
321
322 macro_rules! resp_type {
323 ($resp_name: ident, false) => {
324 ()
325 };
326 ($resp_name: ident, true) => {
327 $resp_name
328 };
329 }
330
331 macro_rules! resp_from_any {
332 ($any: ident, $resp_name: ident, false) => {
333 match $any {
334 $response_enum_name::$resp_name(_) => Ok(()),
335 _ => Err(Error::internal_error().with_data("Unexpected Response"))
336 }
337 };
338 ($any: ident, $resp_name: ident, true) => {
339 match $any {
340 $response_enum_name::$resp_name(this) => Ok(this),
341 _ => Err(Error::internal_error().with_data("Unexpected Response"))
342 }
343 };
344 }
345
346 $(
347 impl $request_trait_name for $request_name {
348 type Response = resp_type!($response_name, $response_payload);
349
350 fn into_any(self) -> $request_enum_name {
351 req_into_any!(self, $request_name, $param_payload)
352 }
353
354 fn response_from_any(any: $response_enum_name) -> Result<Self::Response, Error> {
355 resp_from_any!(any, $response_name, $response_payload)
356 }
357 }
358 )*
359 };
360}
361
362acp_peer!(
364 Client,
365 ClientRequest,
366 AnyClientRequest,
367 AnyClientResult,
368 CLIENT_METHODS,
369 (
370 stream_assistant_message_chunk,
371 "streamAssistantMessageChunk",
372 StreamAssistantMessageChunkParams,
373 true,
374 StreamAssistantMessageChunkResponse,
375 false
376 ),
377 (
378 request_tool_call_confirmation,
379 "requestToolCallConfirmation",
380 RequestToolCallConfirmationParams,
381 true,
382 RequestToolCallConfirmationResponse,
383 true
384 ),
385 (
386 push_tool_call,
387 "pushToolCall",
388 PushToolCallParams,
389 true,
390 PushToolCallResponse,
391 true
392 ),
393 (
394 update_tool_call,
395 "updateToolCall",
396 UpdateToolCallParams,
397 true,
398 UpdateToolCallResponse,
399 false
400 ),
401 (
402 write_text_file,
403 "writeTextFile",
404 WriteTextFileParams,
405 true,
406 WriteTextFileResponse,
407 false
408 ),
409 (
410 read_text_file,
411 "readTextFile",
412 ReadTextFileParams,
413 true,
414 ReadTextFileResponse,
415 true
416 )
417);
418
419acp_peer!(
421 Agent,
422 AgentRequest,
423 AnyAgentRequest,
424 AnyAgentResult,
425 AGENT_METHODS,
426 (
427 initialize,
428 "initialize",
429 InitializeParams,
430 false,
431 InitializeResponse,
432 true
433 ),
434 (
435 authenticate,
436 "authenticate",
437 AuthenticateParams,
438 false,
439 AuthenticateResponse,
440 false
441 ),
442 (
443 send_user_message,
444 "sendUserMessage",
445 SendUserMessageParams,
446 true,
447 SendUserMessageResponse,
448 false
449 ),
450 (
451 cancel_send_message,
452 "cancelSendMessage",
453 CancelSendMessageParams,
454 false,
455 CancelSendMessageResponse,
456 false
457 )
458);
459
460#[derive(Debug, Serialize, Deserialize, JsonSchema)]
469#[serde(rename_all = "camelCase")]
470pub struct InitializeParams;
471
472#[derive(Debug, Serialize, Deserialize, JsonSchema)]
473#[serde(rename_all = "camelCase")]
474pub struct InitializeResponse {
475 pub is_authenticated: bool,
478}
479
480#[derive(Debug, Serialize, Deserialize, JsonSchema)]
489#[serde(rename_all = "camelCase")]
490pub struct AuthenticateParams;
491
492#[derive(Debug, Serialize, Deserialize, JsonSchema)]
493#[serde(rename_all = "camelCase")]
494pub struct AuthenticateResponse;
495
496#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
501#[serde(rename_all = "camelCase")]
502pub struct SendUserMessageParams {
503 pub chunks: Vec<UserMessageChunk>,
504}
505
506#[derive(Debug, Serialize, Deserialize, JsonSchema)]
507#[serde(rename_all = "camelCase")]
508pub struct SendUserMessageResponse;
509
510#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
512#[serde(untagged, rename_all = "camelCase")]
513pub enum UserMessageChunk {
514 Text { text: String },
516 Path { path: PathBuf },
518}
519
520#[derive(Debug, Serialize, Deserialize, JsonSchema)]
523#[serde(rename_all = "camelCase")]
524pub struct CancelSendMessageParams;
525
526#[derive(Debug, Serialize, Deserialize, JsonSchema)]
527#[serde(rename_all = "camelCase")]
528pub struct CancelSendMessageResponse;
529
530#[derive(Debug, Serialize, Deserialize, JsonSchema)]
534#[serde(rename_all = "camelCase")]
535pub struct StreamAssistantMessageChunkParams {
536 pub chunk: AssistantMessageChunk,
537}
538
539#[derive(Debug, Serialize, Deserialize, JsonSchema)]
540#[serde(rename_all = "camelCase")]
541pub struct StreamAssistantMessageChunkResponse;
542
543#[derive(Debug, Serialize, Deserialize, JsonSchema)]
544#[serde(untagged, rename_all = "camelCase")]
545pub enum AssistantMessageChunk {
546 Text { text: String },
547 Thought { thought: String },
548}
549
550#[derive(Debug, Serialize, Deserialize, JsonSchema)]
555#[serde(rename_all = "camelCase")]
556pub struct RequestToolCallConfirmationParams {
557 #[serde(flatten)]
558 pub tool_call: PushToolCallParams,
559 pub confirmation: ToolCallConfirmation,
560}
561
562#[derive(Debug, Serialize, Deserialize, JsonSchema)]
563#[serde(rename_all = "camelCase")]
564pub enum Icon {
567 FileSearch,
568 Folder,
569 Globe,
570 Hammer,
571 LightBulb,
572 Pencil,
573 Regex,
574 Terminal,
575}
576
577#[derive(Debug, Serialize, Deserialize, JsonSchema)]
578#[serde(tag = "type", rename_all = "camelCase")]
579pub enum ToolCallConfirmation {
580 #[serde(rename_all = "camelCase")]
581 Edit {
582 #[serde(skip_serializing_if = "Option::is_none")]
583 description: Option<String>,
584 },
585 #[serde(rename_all = "camelCase")]
586 Execute {
587 command: String,
588 root_command: String,
589 #[serde(skip_serializing_if = "Option::is_none")]
590 description: Option<String>,
591 },
592 #[serde(rename_all = "camelCase")]
593 Mcp {
594 server_name: String,
595 tool_name: String,
596 tool_display_name: String,
597 #[serde(skip_serializing_if = "Option::is_none")]
598 description: Option<String>,
599 },
600 #[serde(rename_all = "camelCase")]
601 Fetch {
602 urls: Vec<String>,
603 #[serde(skip_serializing_if = "Option::is_none")]
604 description: Option<String>,
605 },
606 #[serde(rename_all = "camelCase")]
607 Other { description: String },
608}
609
610#[derive(Debug, Serialize, Deserialize, JsonSchema)]
611#[serde(tag = "type", rename_all = "camelCase")]
612pub struct RequestToolCallConfirmationResponse {
613 pub id: ToolCallId,
614 pub outcome: ToolCallConfirmationOutcome,
615}
616
617#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
618#[serde(rename_all = "camelCase")]
619pub enum ToolCallConfirmationOutcome {
620 Allow,
622 AlwaysAllow,
624 AlwaysAllowMcpServer,
626 AlwaysAllowTool,
628 Reject,
630 Cancel,
632}
633
634#[derive(Debug, Serialize, Deserialize, JsonSchema)]
640#[serde(rename_all = "camelCase")]
641pub struct PushToolCallParams {
642 pub label: String,
643 pub icon: Icon,
644 #[serde(skip_serializing_if = "Option::is_none")]
645 pub content: Option<ToolCallContent>,
646 #[serde(default, skip_serializing_if = "Vec::is_empty")]
647 pub locations: Vec<ToolCallLocation>,
648}
649
650#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
651#[serde(tag = "type", rename_all = "camelCase")]
652pub struct ToolCallLocation {
653 pub path: PathBuf,
654 #[serde(skip_serializing_if = "Option::is_none")]
655 pub line: Option<u32>,
656}
657
658#[derive(Debug, Serialize, Deserialize, JsonSchema)]
659#[serde(tag = "type", rename_all = "camelCase")]
660pub struct PushToolCallResponse {
661 pub id: ToolCallId,
662}
663
664#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Hash)]
665#[serde(rename_all = "camelCase")]
666pub struct ToolCallId(pub u64);
667
668#[derive(Debug, Serialize, Deserialize, JsonSchema)]
675#[serde(rename_all = "camelCase")]
676pub struct UpdateToolCallParams {
677 pub tool_call_id: ToolCallId,
678 pub status: ToolCallStatus,
679 pub content: Option<ToolCallContent>,
680}
681
682#[derive(Debug, Serialize, Deserialize, JsonSchema)]
683pub struct UpdateToolCallResponse;
684
685#[derive(Debug, Serialize, Deserialize, JsonSchema)]
686#[serde(rename_all = "camelCase")]
687pub enum ToolCallStatus {
688 Running,
690 Finished,
692 Error,
694}
695
696#[derive(Debug, Serialize, Deserialize, JsonSchema)]
697#[serde(tag = "type", rename_all = "camelCase")]
698pub enum ToolCallContent {
699 #[serde(rename_all = "camelCase")]
700 Markdown { markdown: String },
701 #[serde(rename_all = "camelCase")]
702 Diff {
703 #[serde(flatten)]
704 diff: Diff,
705 },
706}
707
708#[derive(Debug, Serialize, Deserialize, JsonSchema)]
709#[serde(rename_all = "camelCase")]
710pub struct Diff {
711 pub path: PathBuf,
712 pub old_text: Option<String>,
713 pub new_text: String,
714}
715
716#[derive(Debug, Serialize, Deserialize, JsonSchema)]
717#[serde(rename_all = "camelCase")]
718pub struct WriteTextFileParams {
719 pub path: PathBuf,
720 pub content: String,
721}
722
723#[derive(Debug, Serialize, Deserialize, JsonSchema)]
724pub struct WriteTextFileResponse;
725
726#[derive(Debug, Serialize, Deserialize, JsonSchema)]
727#[serde(rename_all = "camelCase")]
728pub struct ReadTextFileParams {
729 pub path: PathBuf,
730 #[serde(skip_serializing_if = "Option::is_none")]
731 pub line: Option<u32>,
732 #[serde(skip_serializing_if = "Option::is_none")]
733 pub limit: Option<u32>,
734}
735
736#[derive(Debug, Serialize, Deserialize, JsonSchema)]
737pub struct ReadTextFileResponse {
738 pub content: String,
739}