agentic_coding_protocol/
schema.rs

1use std::{fmt::Display, ops::Deref, path::PathBuf};
2
3use derive_more::{Deref, Display, FromStr};
4use schemars::JsonSchema;
5use semver::Version;
6use serde::{Deserialize, Serialize};
7use serde_json::value::RawValue;
8
9#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
10pub struct Error {
11    pub code: i32,
12    pub message: String,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub data: Option<serde_json::Value>,
15}
16
17impl Error {
18    pub fn new(code: impl Into<(i32, String)>) -> Self {
19        let (code, message) = code.into();
20        Error {
21            code,
22            message,
23            data: None,
24        }
25    }
26
27    pub fn with_data(mut self, data: impl Into<serde_json::Value>) -> Self {
28        self.data = Some(data.into());
29        self
30    }
31
32    /// Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
33    pub fn parse_error() -> Self {
34        Error::new(ErrorCode::PARSE_ERROR)
35    }
36
37    /// The JSON sent is not a valid Request object.
38    pub fn invalid_request() -> Self {
39        Error::new(ErrorCode::INVALID_REQUEST)
40    }
41
42    /// The method does not exist / is not available.
43    pub fn method_not_found() -> Self {
44        Error::new(ErrorCode::METHOD_NOT_FOUND)
45    }
46
47    /// Invalid method parameter(s).
48    pub fn invalid_params() -> Self {
49        Error::new(ErrorCode::INVALID_PARAMS)
50    }
51
52    /// Internal JSON-RPC error.
53    pub fn internal_error() -> Self {
54        Error::new(ErrorCode::INTERNAL_ERROR)
55    }
56
57    pub fn into_internal_error(err: impl std::error::Error) -> Self {
58        Error::internal_error().with_data(err.to_string())
59    }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
63pub struct ErrorCode {
64    code: i32,
65    message: &'static str,
66}
67
68impl ErrorCode {
69    pub const PARSE_ERROR: ErrorCode = ErrorCode {
70        code: -32700,
71        message: "Parse error",
72    };
73
74    pub const INVALID_REQUEST: ErrorCode = ErrorCode {
75        code: -32600,
76        message: "Invalid Request",
77    };
78
79    pub const METHOD_NOT_FOUND: ErrorCode = ErrorCode {
80        code: -32601,
81        message: "Method not found",
82    };
83
84    pub const INVALID_PARAMS: ErrorCode = ErrorCode {
85        code: -32602,
86        message: "Invalid params",
87    };
88
89    pub const INTERNAL_ERROR: ErrorCode = ErrorCode {
90        code: -32603,
91        message: "Internal error",
92    };
93}
94
95impl From<ErrorCode> for (i32, String) {
96    fn from(error_code: ErrorCode) -> Self {
97        (error_code.code, error_code.message.to_string())
98    }
99}
100
101impl From<ErrorCode> for Error {
102    fn from(error_code: ErrorCode) -> Self {
103        Error::new(error_code)
104    }
105}
106
107impl std::error::Error for Error {}
108
109impl Display for Error {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        if self.message.is_empty() {
112            write!(f, "{}", self.code)?;
113        } else {
114            write!(f, "{}", self.message)?;
115        }
116
117        if let Some(data) = &self.data {
118            write!(f, ": {data}")?;
119        }
120
121        Ok(())
122    }
123}
124
125impl From<anyhow::Error> for Error {
126    fn from(error: anyhow::Error) -> Self {
127        Error::into_internal_error(error.deref())
128    }
129}
130
131#[derive(Serialize, JsonSchema)]
132#[serde(rename_all = "camelCase")]
133pub struct Method {
134    pub name: &'static str,
135    pub request_type: &'static str,
136    pub param_payload: bool,
137    pub response_type: &'static str,
138    pub response_payload: bool,
139}
140
141pub trait AnyRequest: Serialize + Sized + 'static {
142    type Response: Serialize + 'static;
143    fn from_method_and_params(method: &str, params: &RawValue) -> Result<Self, Error>;
144    fn response_from_method_and_result(
145        method: &str,
146        params: &RawValue,
147    ) -> Result<Self::Response, Error>;
148}
149
150macro_rules! acp_peer {
151    (
152        $handler_trait_name:ident,
153        $request_trait_name:ident,
154        $request_enum_name:ident,
155        $response_enum_name:ident,
156        $method_map_name:ident,
157        $(($request_method:ident, $request_method_string:expr, $request_name:ident, $param_payload: tt, $response_name:ident, $response_payload: tt)),*
158        $(,)?
159    ) => {
160        macro_rules! handler_trait_call_req {
161            ($self: ident, $method: ident, false, $resp_name: ident, false, $params: ident) => {
162                {
163                    $self.$method().await?;
164                    Ok($response_enum_name::$resp_name($resp_name))
165                }
166            };
167            ($self: ident, $method: ident, false, $resp_name: ident, true, $params: ident) => {
168                {
169                    let resp = $self.$method().await?;
170                    Ok($response_enum_name::$resp_name(resp))
171                }
172            };
173            ($self: ident, $method: ident, true, $resp_name: ident, false, $params: ident) => {
174                {
175                    $self.$method($params).await?;
176                    Ok($response_enum_name::$resp_name($resp_name))
177                }
178            };
179            ($self: ident, $method: ident, true, $resp_name: ident, true, $params: ident) => {
180                {
181                    let resp = $self.$method($params).await?;
182                    Ok($response_enum_name::$resp_name(resp))
183                }
184            }
185        }
186
187        macro_rules! handler_trait_req_method {
188            ($method: ident, $req: ident, false, $resp: tt, false) => {
189                fn $method(&self) -> impl Future<Output = Result<(), Error>>;
190            };
191            ($method: ident, $req: ident, false, $resp: tt, true) => {
192                fn $method(&self) -> impl Future<Output = Result<$resp, Error>>;
193            };
194            ($method: ident, $req: ident, true, $resp: tt, false) => {
195                fn $method(&self, request: $req) -> impl Future<Output = Result<(), Error>>;
196            };
197            ($method: ident, $req: ident, true, $resp: tt, true) => {
198                fn $method(&self, request: $req) -> impl Future<Output = Result<$resp, Error>>;
199            }
200        }
201
202        pub trait $handler_trait_name {
203            fn call(&self, params: $request_enum_name) -> impl Future<Output = Result<$response_enum_name, Error>> {
204                async move {
205                    match params {
206                        $(#[allow(unused_variables)]
207                        $request_enum_name::$request_name(params) => {
208                            handler_trait_call_req!(self, $request_method, $param_payload, $response_name, $response_payload, params)
209                        }),*
210                    }
211                }
212            }
213
214            $(
215                handler_trait_req_method!($request_method, $request_name, $param_payload, $response_name, $response_payload);
216            )*
217        }
218
219        pub trait $request_trait_name {
220            type Response;
221            fn into_any(self) -> $request_enum_name;
222            fn response_from_any(any: $response_enum_name) -> Result<Self::Response, Error>;
223        }
224
225        #[derive(Serialize, JsonSchema)]
226        #[serde(untagged)]
227        pub enum $request_enum_name {
228            $(
229                $request_name($request_name),
230            )*
231        }
232
233        #[derive(Serialize, Deserialize, JsonSchema)]
234        #[serde(untagged)]
235        pub enum $response_enum_name {
236            $(
237                $response_name($response_name),
238            )*
239        }
240
241        macro_rules! request_from_method_and_params {
242            ($req_name: ident, false, $params: tt) => {
243                Ok($request_enum_name::$req_name($req_name))
244            };
245            ($req_name: ident, true, $params: tt) => {
246                match serde_json::from_str($params.get()) {
247                    Ok(params) => Ok($request_enum_name::$req_name(params)),
248                    Err(e) => Err(Error::parse_error().with_data(e.to_string())),
249                }
250            };
251        }
252
253        macro_rules! response_from_method_and_result {
254            ($resp_name: ident, false, $result: tt) => {
255                Ok($response_enum_name::$resp_name($resp_name))
256            };
257            ($resp_name: ident, true, $result: tt) => {
258                match serde_json::from_str($result.get()) {
259                    Ok(result) => Ok($response_enum_name::$resp_name(result)),
260                    Err(e) => Err(Error::parse_error().with_data(e.to_string())),
261                }
262            };
263        }
264
265        impl AnyRequest for $request_enum_name {
266            type Response = $response_enum_name;
267
268            fn from_method_and_params(method: &str, params: &RawValue) -> Result<Self, Error> {
269                match method {
270                    $(
271                        $request_method_string => {
272                            request_from_method_and_params!($request_name, $param_payload, params)
273                        }
274                    )*
275                    _ => Err(Error::method_not_found()),
276                }
277            }
278
279            fn response_from_method_and_result(method: &str, params: &RawValue) -> Result<Self::Response, Error> {
280                match method {
281                    $(
282                        $request_method_string => {
283                            response_from_method_and_result!($response_name, $response_payload, params)
284                        }
285                    )*
286                    _ => Err(Error::method_not_found()),
287                }
288            }
289        }
290
291        impl $request_enum_name {
292            pub fn method_name(&self) -> &'static str {
293                match self {
294                    $(
295                        $request_enum_name::$request_name(_) => $request_method_string,
296                    )*
297                }
298            }
299        }
300
301
302
303        pub static $method_map_name: &[Method] = &[
304            $(
305                Method {
306                    name: $request_method_string,
307                    request_type: stringify!($request_name),
308                    param_payload: $param_payload,
309                    response_type: stringify!($response_name),
310                    response_payload: $response_payload,
311                },
312            )*
313        ];
314
315        macro_rules! req_into_any {
316            ($self: ident, $req_name: ident, false) => {
317                $request_enum_name::$req_name($req_name)
318            };
319            ($self: ident, $req_name: ident, true) => {
320                $request_enum_name::$req_name($self)
321            };
322        }
323
324        macro_rules! resp_type {
325            ($resp_name: ident, false) => {
326                ()
327            };
328            ($resp_name: ident, true) => {
329                $resp_name
330            };
331        }
332
333        macro_rules! resp_from_any {
334            ($any: ident, $resp_name: ident, false) => {
335                match $any {
336                    $response_enum_name::$resp_name(_) => Ok(()),
337                    _ => Err(Error::internal_error().with_data("Unexpected Response"))
338                }
339            };
340            ($any: ident, $resp_name: ident, true) => {
341                match $any {
342                    $response_enum_name::$resp_name(this) => Ok(this),
343                    _ => Err(Error::internal_error().with_data("Unexpected Response"))
344                }
345            };
346        }
347
348        $(
349            impl $request_trait_name for $request_name {
350                type Response = resp_type!($response_name, $response_payload);
351
352                fn into_any(self) -> $request_enum_name {
353                    req_into_any!(self, $request_name, $param_payload)
354                }
355
356                fn response_from_any(any: $response_enum_name) -> Result<Self::Response, Error> {
357                    resp_from_any!(any, $response_name, $response_payload)
358                }
359            }
360        )*
361    };
362}
363
364// requests sent from the client (the IDE) to the agent
365acp_peer!(
366    Client,
367    ClientRequest,
368    AnyClientRequest,
369    AnyClientResult,
370    CLIENT_METHODS,
371    (
372        stream_assistant_message_chunk,
373        "streamAssistantMessageChunk",
374        StreamAssistantMessageChunkParams,
375        true,
376        StreamAssistantMessageChunkResponse,
377        false
378    ),
379    (
380        request_tool_call_confirmation,
381        "requestToolCallConfirmation",
382        RequestToolCallConfirmationParams,
383        true,
384        RequestToolCallConfirmationResponse,
385        true
386    ),
387    (
388        push_tool_call,
389        "pushToolCall",
390        PushToolCallParams,
391        true,
392        PushToolCallResponse,
393        true
394    ),
395    (
396        update_tool_call,
397        "updateToolCall",
398        UpdateToolCallParams,
399        true,
400        UpdateToolCallResponse,
401        false
402    ),
403    (
404        write_text_file,
405        "writeTextFile",
406        WriteTextFileParams,
407        true,
408        WriteTextFileResponse,
409        false
410    ),
411    (
412        read_text_file,
413        "readTextFile",
414        ReadTextFileParams,
415        true,
416        ReadTextFileResponse,
417        true
418    )
419);
420
421// requests sent from the agent to the client (the IDE)
422acp_peer!(
423    Agent,
424    AgentRequest,
425    AnyAgentRequest,
426    AnyAgentResult,
427    AGENT_METHODS,
428    (
429        initialize,
430        "initialize",
431        InitializeParams,
432        true,
433        InitializeResponse,
434        true
435    ),
436    (
437        authenticate,
438        "authenticate",
439        AuthenticateParams,
440        false,
441        AuthenticateResponse,
442        false
443    ),
444    (
445        send_user_message,
446        "sendUserMessage",
447        SendUserMessageParams,
448        true,
449        SendUserMessageResponse,
450        false
451    ),
452    (
453        cancel_send_message,
454        "cancelSendMessage",
455        CancelSendMessageParams,
456        false,
457        CancelSendMessageResponse,
458        false
459    )
460);
461
462// --- Messages sent from the client to the agent --- \\
463
464/// Initialize sets up the agent's state. It should be called before any other method,
465/// and no other methods should be called until it has completed.
466///
467/// If the agent is not authenticated, then the client should prompt the user to authenticate,
468/// and then call the `authenticate` method.
469/// Otherwise the client can send other messages to the agent.
470#[derive(Debug, Serialize, Deserialize, JsonSchema)]
471#[serde(rename_all = "camelCase")]
472pub struct InitializeParams {
473    /// The version of the protocol that the client supports.
474    /// This should be the latest version supported by the client.
475    pub protocol_version: ProtocolVersion,
476}
477
478#[derive(Clone, Debug, Deref, Display, FromStr, Serialize, Deserialize, JsonSchema)]
479#[serde(transparent)]
480pub struct ProtocolVersion(Version);
481
482impl ProtocolVersion {
483    pub fn latest() -> Self {
484        Self(env!("CARGO_PKG_VERSION").parse().expect("Invalid version"))
485    }
486}
487
488#[derive(Debug, Serialize, Deserialize, JsonSchema)]
489#[serde(rename_all = "camelCase")]
490pub struct InitializeResponse {
491    /// The version of the protocol that the agent supports.
492    /// If the agent supports the requested version, it should respond with the same version.
493    /// Otherwise, the agent should respond with the latest version it supports.
494    pub protocol_version: ProtocolVersion,
495    /// Indicates whether the agent is authenticated and
496    /// ready to handle requests.
497    pub is_authenticated: bool,
498}
499
500/// Triggers authentication on the agent side.
501///
502/// This method should only be called if the initialize response indicates the user isn't already authenticated.
503/// If this succceeds then the client can send other messasges to the agent,
504/// If it fails then the error message should be shown and the user prompted to authenticate.
505///
506/// The implementation of authentication is left up to the agent, typically an oauth
507/// flow is run by opening a browser window in the background.
508#[derive(Debug, Serialize, Deserialize, JsonSchema)]
509#[serde(rename_all = "camelCase")]
510pub struct AuthenticateParams;
511
512#[derive(Debug, Serialize, Deserialize, JsonSchema)]
513#[serde(rename_all = "camelCase")]
514pub struct AuthenticateResponse;
515
516/// sendUserMessage allows the user to send a message to the agent.
517/// This method should complete after the agent is finished, during
518/// which time the agent may update the client by calling
519/// streamAssistantMessageChunk and other methods.
520#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
521#[serde(rename_all = "camelCase")]
522pub struct SendUserMessageParams {
523    pub chunks: Vec<UserMessageChunk>,
524}
525
526#[derive(Debug, Serialize, Deserialize, JsonSchema)]
527#[serde(rename_all = "camelCase")]
528pub struct SendUserMessageResponse;
529
530/// A part in a user message
531#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
532#[serde(untagged, rename_all = "camelCase")]
533pub enum UserMessageChunk {
534    /// A chunk of text in user message
535    Text { text: String },
536    /// A file path mention in a user message
537    Path { path: PathBuf },
538}
539
540/// cancelSendMessage allows the client to request that the agent
541/// stop running. The agent should resolve or reject the current sendUserMessage call.
542#[derive(Debug, Serialize, Deserialize, JsonSchema)]
543#[serde(rename_all = "camelCase")]
544pub struct CancelSendMessageParams;
545
546#[derive(Debug, Serialize, Deserialize, JsonSchema)]
547#[serde(rename_all = "camelCase")]
548pub struct CancelSendMessageResponse;
549
550// --- Messages sent from the agent to the client --- \\
551
552/// Streams part of an assistant response to the client
553#[derive(Debug, Serialize, Deserialize, JsonSchema)]
554#[serde(rename_all = "camelCase")]
555pub struct StreamAssistantMessageChunkParams {
556    pub chunk: AssistantMessageChunk,
557}
558
559#[derive(Debug, Serialize, Deserialize, JsonSchema)]
560#[serde(rename_all = "camelCase")]
561pub struct StreamAssistantMessageChunkResponse;
562
563#[derive(Debug, Serialize, Deserialize, JsonSchema)]
564#[serde(untagged, rename_all = "camelCase")]
565pub enum AssistantMessageChunk {
566    Text { text: String },
567    Thought { thought: String },
568}
569
570/// Request confirmation before running a tool
571///
572/// When allowed, the client returns a [`ToolCallId`] which can be used
573/// to update the tool call's `status` and `content` as it runs.
574#[derive(Debug, Serialize, Deserialize, JsonSchema)]
575#[serde(rename_all = "camelCase")]
576pub struct RequestToolCallConfirmationParams {
577    #[serde(flatten)]
578    pub tool_call: PushToolCallParams,
579    pub confirmation: ToolCallConfirmation,
580}
581
582#[derive(Debug, Serialize, Deserialize, JsonSchema)]
583#[serde(rename_all = "camelCase")]
584// todo? make this `pub enum ToolKind { Edit, Search, Read, Fetch, ...}?`
585// avoids being to UI centric.
586pub enum Icon {
587    FileSearch,
588    Folder,
589    Globe,
590    Hammer,
591    LightBulb,
592    Pencil,
593    Regex,
594    Terminal,
595}
596
597#[derive(Debug, Serialize, Deserialize, JsonSchema)]
598#[serde(tag = "type", rename_all = "camelCase")]
599pub enum ToolCallConfirmation {
600    #[serde(rename_all = "camelCase")]
601    Edit {
602        #[serde(skip_serializing_if = "Option::is_none")]
603        description: Option<String>,
604    },
605    #[serde(rename_all = "camelCase")]
606    Execute {
607        command: String,
608        root_command: String,
609        #[serde(skip_serializing_if = "Option::is_none")]
610        description: Option<String>,
611    },
612    #[serde(rename_all = "camelCase")]
613    Mcp {
614        server_name: String,
615        tool_name: String,
616        tool_display_name: String,
617        #[serde(skip_serializing_if = "Option::is_none")]
618        description: Option<String>,
619    },
620    #[serde(rename_all = "camelCase")]
621    Fetch {
622        urls: Vec<String>,
623        #[serde(skip_serializing_if = "Option::is_none")]
624        description: Option<String>,
625    },
626    #[serde(rename_all = "camelCase")]
627    Other { description: String },
628}
629
630#[derive(Debug, Serialize, Deserialize, JsonSchema)]
631#[serde(tag = "type", rename_all = "camelCase")]
632pub struct RequestToolCallConfirmationResponse {
633    pub id: ToolCallId,
634    pub outcome: ToolCallConfirmationOutcome,
635}
636
637#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
638#[serde(rename_all = "camelCase")]
639pub enum ToolCallConfirmationOutcome {
640    /// Allow this one call
641    Allow,
642    /// Always allow this kind of operation
643    AlwaysAllow,
644    /// Always allow any tool from this MCP server
645    AlwaysAllowMcpServer,
646    /// Always allow this tool from this MCP server
647    AlwaysAllowTool,
648    /// Reject this tool call
649    Reject,
650    /// The generation was canceled before a confirming
651    Cancel,
652}
653
654/// pushToolCall allows the agent to start a tool call
655/// when it does not need to request permission to do so.
656///
657/// The returned id can be used to update the UI for the tool
658/// call as needed.
659#[derive(Debug, Serialize, Deserialize, JsonSchema)]
660#[serde(rename_all = "camelCase")]
661pub struct PushToolCallParams {
662    pub label: String,
663    pub icon: Icon,
664    #[serde(skip_serializing_if = "Option::is_none")]
665    pub content: Option<ToolCallContent>,
666    #[serde(default, skip_serializing_if = "Vec::is_empty")]
667    pub locations: Vec<ToolCallLocation>,
668}
669
670#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
671#[serde(tag = "type", rename_all = "camelCase")]
672pub struct ToolCallLocation {
673    pub path: PathBuf,
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub line: Option<u32>,
676}
677
678#[derive(Debug, Serialize, Deserialize, JsonSchema)]
679#[serde(tag = "type", rename_all = "camelCase")]
680pub struct PushToolCallResponse {
681    pub id: ToolCallId,
682}
683
684#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Hash)]
685#[serde(rename_all = "camelCase")]
686pub struct ToolCallId(pub u64);
687
688/// updateToolCall allows the agent to update the content and status of the tool call.
689///
690/// The new content replaces what is currently displayed in the UI.
691///
692/// The [`ToolCallId`] is included in the response of
693/// `pushToolCall` or `requestToolCallConfirmation` respectively.
694#[derive(Debug, Serialize, Deserialize, JsonSchema)]
695#[serde(rename_all = "camelCase")]
696pub struct UpdateToolCallParams {
697    pub tool_call_id: ToolCallId,
698    pub status: ToolCallStatus,
699    pub content: Option<ToolCallContent>,
700}
701
702#[derive(Debug, Serialize, Deserialize, JsonSchema)]
703pub struct UpdateToolCallResponse;
704
705#[derive(Debug, Serialize, Deserialize, JsonSchema)]
706#[serde(rename_all = "camelCase")]
707pub enum ToolCallStatus {
708    /// The tool call is currently running
709    Running,
710    /// The tool call completed successfully
711    Finished,
712    /// The tool call failed
713    Error,
714}
715
716#[derive(Debug, Serialize, Deserialize, JsonSchema)]
717#[serde(tag = "type", rename_all = "camelCase")]
718pub enum ToolCallContent {
719    #[serde(rename_all = "camelCase")]
720    Markdown { markdown: String },
721    #[serde(rename_all = "camelCase")]
722    Diff {
723        #[serde(flatten)]
724        diff: Diff,
725    },
726}
727
728#[derive(Debug, Serialize, Deserialize, JsonSchema)]
729#[serde(rename_all = "camelCase")]
730pub struct Diff {
731    pub path: PathBuf,
732    pub old_text: Option<String>,
733    pub new_text: String,
734}
735
736#[derive(Debug, Serialize, Deserialize, JsonSchema)]
737#[serde(rename_all = "camelCase")]
738pub struct WriteTextFileParams {
739    pub path: PathBuf,
740    pub content: String,
741}
742
743#[derive(Debug, Serialize, Deserialize, JsonSchema)]
744pub struct WriteTextFileResponse;
745
746#[derive(Debug, Serialize, Deserialize, JsonSchema)]
747#[serde(rename_all = "camelCase")]
748pub struct ReadTextFileParams {
749    pub path: PathBuf,
750    #[serde(skip_serializing_if = "Option::is_none")]
751    pub line: Option<u32>,
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub limit: Option<u32>,
754}
755
756#[derive(Debug, Serialize, Deserialize, JsonSchema)]
757pub struct ReadTextFileResponse {
758    pub content: String,
759}