agentic_coding_protocol/
schema.rs

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
250// requests sent from the client (the IDE) to the agent
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
291// requests sent from the agent to the client (the IDE)
292acp_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// --- Messages sent from the client to the agent --- \\
333
334/// Initialize sets up the agent's state. It should be called before any other method,
335/// and no other methods should be called until it has completed.
336///
337/// If the agent is not authenticated, then the client should prompt the user to authenticate,
338/// and then call the `authenticate` method.
339/// Otherwise the client can send other messages to the agent.
340#[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    /// Indicates whether the agent is authenticated and
348    /// ready to handle requests.
349    pub is_authenticated: bool,
350}
351
352/// Triggers authentication on the agent side.
353///
354/// This method should only be called if the initialize response indicates the user isn't already authenticated.
355/// If this succceeds then the client can send other messasges to the agent,
356/// If it fails then the error message should be shown and the user prompted to authenticate.
357///
358/// The implementation of authentication is left up to the agent, typically an oauth
359/// flow is run by opening a browser window in the background.
360#[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/// sendUserMessage allows the user to send a message to the agent.
369/// This method should complete after the agent is finished, during
370/// which time the agent may update the client by calling
371/// streamAssistantMessageChunk and other methods.
372#[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/// A part in a user message
383#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
384#[serde(untagged, rename_all = "camelCase")]
385pub enum UserMessageChunk {
386    /// A chunk of text in user message
387    Text { text: String },
388    /// A file path mention in a user message
389    Path { path: PathBuf },
390}
391
392/// cancelSendMessage allows the client to request that the agent
393/// stop running. The agent should resolve or reject the current sendUserMessage call.
394#[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// --- Messages sent from the agent to the client --- \\
403
404/// Streams part of an assistant response to the client
405#[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/// Request confirmation before running a tool
423///
424/// When allowed, the client returns a [`ToolCallId`] which can be used
425/// to update the tool call's `status` and `content` as it runs.
426#[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")]
438// todo? make this `pub enum ToolKind { Edit, Search, Read, Fetch, ...}?`
439// avoids being to UI centric.
440pub 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 this one call
495    Allow,
496    /// Always allow this kind of operation
497    AlwaysAllow,
498    /// Always allow any tool from this MCP server
499    AlwaysAllowMcpServer,
500    /// Always allow this tool from this MCP server
501    AlwaysAllowTool,
502    /// Reject this tool call
503    Reject,
504    /// The generation was canceled before a confirming
505    Cancel,
506}
507
508/// pushToolCall allows the agent to start a tool call
509/// when it does not need to request permission to do so.
510///
511/// The returned id can be used to update the UI for the tool
512/// call as needed.
513#[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/// updateToolCall allows the agent to update the content and status of the tool call.
533///
534/// The new content replaces what is currently displayed in the UI.
535///
536/// The [`ToolCallId`] is included in the response of
537/// `pushToolCall` or `requestToolCallConfirmation` respectively.
538#[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    /// The tool call is currently running
553    Running,
554    /// The tool call completed successfully
555    Finished,
556    /// The tool call failed
557    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}