agentic_coding_protocol/
schema.rs

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;