agentic_coding_protocol/
schema.rs

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;