agentic_coding_protocol/
schema.rs

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    /// Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
31    pub fn parse_error() -> Self {
32        Error::new(ErrorCode::PARSE_ERROR)
33    }
34
35    /// The JSON sent is not a valid Request object.
36    pub fn invalid_request() -> Self {
37        Error::new(ErrorCode::INVALID_REQUEST)
38    }
39
40    /// The method does not exist / is not available.
41    pub fn method_not_found() -> Self {
42        Error::new(ErrorCode::METHOD_NOT_FOUND)
43    }
44
45    /// Invalid method parameter(s).
46    pub fn invalid_params() -> Self {
47        Error::new(ErrorCode::INVALID_PARAMS)
48    }
49
50    /// Internal JSON-RPC error.
51    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
362// requests sent from the client (the IDE) to the agent
363acp_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
419// requests sent from the agent to the client (the IDE)
420acp_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// --- Messages sent from the client to the agent --- \\
461
462/// Initialize sets up the agent's state. It should be called before any other method,
463/// and no other methods should be called until it has completed.
464///
465/// If the agent is not authenticated, then the client should prompt the user to authenticate,
466/// and then call the `authenticate` method.
467/// Otherwise the client can send other messages to the agent.
468#[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    /// Indicates whether the agent is authenticated and
476    /// ready to handle requests.
477    pub is_authenticated: bool,
478}
479
480/// Triggers authentication on the agent side.
481///
482/// This method should only be called if the initialize response indicates the user isn't already authenticated.
483/// If this succceeds then the client can send other messasges to the agent,
484/// If it fails then the error message should be shown and the user prompted to authenticate.
485///
486/// The implementation of authentication is left up to the agent, typically an oauth
487/// flow is run by opening a browser window in the background.
488#[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/// sendUserMessage allows the user to send a message to the agent.
497/// This method should complete after the agent is finished, during
498/// which time the agent may update the client by calling
499/// streamAssistantMessageChunk and other methods.
500#[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/// A part in a user message
511#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
512#[serde(untagged, rename_all = "camelCase")]
513pub enum UserMessageChunk {
514    /// A chunk of text in user message
515    Text { text: String },
516    /// A file path mention in a user message
517    Path { path: PathBuf },
518}
519
520/// cancelSendMessage allows the client to request that the agent
521/// stop running. The agent should resolve or reject the current sendUserMessage call.
522#[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// --- Messages sent from the agent to the client --- \\
531
532/// Streams part of an assistant response to the client
533#[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/// Request confirmation before running a tool
551///
552/// When allowed, the client returns a [`ToolCallId`] which can be used
553/// to update the tool call's `status` and `content` as it runs.
554#[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")]
564// todo? make this `pub enum ToolKind { Edit, Search, Read, Fetch, ...}?`
565// avoids being to UI centric.
566pub 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 this one call
621    Allow,
622    /// Always allow this kind of operation
623    AlwaysAllow,
624    /// Always allow any tool from this MCP server
625    AlwaysAllowMcpServer,
626    /// Always allow this tool from this MCP server
627    AlwaysAllowTool,
628    /// Reject this tool call
629    Reject,
630    /// The generation was canceled before a confirming
631    Cancel,
632}
633
634/// pushToolCall allows the agent to start a tool call
635/// when it does not need to request permission to do so.
636///
637/// The returned id can be used to update the UI for the tool
638/// call as needed.
639#[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/// updateToolCall allows the agent to update the content and status of the tool call.
669///
670/// The new content replaces what is currently displayed in the UI.
671///
672/// The [`ToolCallId`] is included in the response of
673/// `pushToolCall` or `requestToolCallConfirmation` respectively.
674#[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    /// The tool call is currently running
689    Running,
690    /// The tool call completed successfully
691    Finished,
692    /// The tool call failed
693    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}