agent-client-protocol-schema 1.15.0

A protocol for standardizing communication between code editors and AI coding agents
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
//! Proxy chain types: messages exchanged between a conductor and the proxies it manages.
//!
//! Ordinary clients and agents never send or receive these messages.
//! See the [Proxy Chains RFD](https://agentclientprotocol.com/rfds/proxy-chains).

use std::sync::Arc;

use derive_more::From;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use serde_with::{DefaultOnError, serde_as, skip_serializing_none};

use super::{InitializeRequest, InitializeResponse, Meta};
use crate::IntoOption;

// Initialize proxy

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Request parameters for `proxy/initialize`.
///
/// A conductor sends `proxy/initialize` instead of `initialize` to every component that has a
/// successor, which tells the component to run as a proxy. The params and the response are
/// the same as for `initialize`. A conductor sends `initialize` to the final agent.
///
/// See the [Proxy Chains RFD](https://agentclientprotocol.com/rfds/proxy-chains).
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "proxy", "x-method" = PROXY_INITIALIZE_METHOD_NAME)))]
#[serde(transparent)]
#[non_exhaustive]
pub struct InitializeProxyRequest {
    /// The `initialize` params, sent unchanged.
    pub initialize: InitializeRequest,
}

impl InitializeProxyRequest {
    /// Builds [`InitializeProxyRequest`] from the `initialize` params to send.
    #[must_use]
    pub fn new(initialize: InitializeRequest) -> Self {
        Self { initialize }
    }
}

impl From<InitializeRequest> for InitializeProxyRequest {
    fn from(initialize: InitializeRequest) -> Self {
        Self::new(initialize)
    }
}

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Response to `proxy/initialize`.
///
/// The same as the `initialize` response. The proxy answers with the capabilities of the
/// chain from itself onward.
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "proxy", "x-method" = PROXY_INITIALIZE_METHOD_NAME)))]
#[serde(transparent)]
#[non_exhaustive]
pub struct InitializeProxyResponse {
    /// The `initialize` response, sent unchanged.
    pub initialize: InitializeResponse,
}

impl InitializeProxyResponse {
    /// Builds [`InitializeProxyResponse`] from the `initialize` response to send.
    #[must_use]
    pub fn new(initialize: InitializeResponse) -> Self {
        Self { initialize }
    }
}

impl From<InitializeResponse> for InitializeProxyResponse {
    fn from(initialize: InitializeResponse) -> Self {
        Self::new(initialize)
    }
}

// Successor messages

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Parameters of a `proxy/successor` request.
///
/// A proxy sends it to forward a message to its successor, and the conductor sends it to
/// deliver a message from the successor to the proxy. The envelope wraps one request: `method`
/// and `params` are the wrapped request's method and params.
///
/// The result or error of the wrapped request is returned unchanged as the result or error
/// of the `proxy/successor` request.
///
/// See the [Proxy Chains RFD](https://agentclientprotocol.com/rfds/proxy-chains).
#[serde_as]
#[skip_serializing_none]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "proxy", "x-method" = PROXY_SUCCESSOR_METHOD_NAME)))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ProxySuccessorRequest {
    /// Method name of the wrapped request.
    pub method: Arc<str>,
    /// Params of the wrapped request, forwarded unchanged.
    ///
    /// Optional. Omitted and `null` are equivalent and mean the wrapped request has no params.
    /// The wrapped request's own `_meta` stays inside these params.
    #[cfg_attr(feature = "schemars", schemars(with = "Option<serde_json::Value>"))]
    #[serde(default)]
    pub params: Option<Arc<RawValue>>,
    /// Metadata for the `proxy/successor` envelope itself, separate from the wrapped
    /// message's `_meta`.
    ///
    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
    /// Implementations MUST NOT assign meaning to keys they do not recognize.
    ///
    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
    #[serde_as(deserialize_as = "DefaultOnError")]
    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
    #[serde(default)]
    #[serde(rename = "_meta")]
    pub meta: Option<Meta>,
}

impl ProxySuccessorRequest {
    /// Builds [`ProxySuccessorRequest`] with the required fields set. Optional fields start unset or empty.
    #[must_use]
    pub fn new(method: impl Into<Arc<str>>) -> Self {
        Self {
            method: method.into(),
            params: None,
            meta: None,
        }
    }

    /// Params of the wrapped request, forwarded unchanged.
    #[must_use]
    pub fn params(mut self, params: impl IntoOption<Arc<RawValue>>) -> Self {
        self.params = params.into_option();
        self
    }

    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
    /// do not recognize.
    ///
    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
    #[must_use]
    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
        self.meta = meta.into_option();
        self
    }
}

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Parameters of a `proxy/successor` notification.
///
/// A proxy sends it to forward a message to its successor, and the conductor sends it to
/// deliver a message from the successor to the proxy. The envelope wraps one notification: `method`
/// and `params` are the wrapped notification's method and params.
///
/// See the [Proxy Chains RFD](https://agentclientprotocol.com/rfds/proxy-chains).
#[serde_as]
#[skip_serializing_none]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "proxy", "x-method" = PROXY_SUCCESSOR_METHOD_NAME)))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ProxySuccessorNotification {
    /// Method name of the wrapped notification.
    pub method: Arc<str>,
    /// Params of the wrapped notification, forwarded unchanged.
    ///
    /// Optional. Omitted and `null` are equivalent and mean the wrapped notification has no params.
    /// The wrapped notification's own `_meta` stays inside these params.
    #[cfg_attr(feature = "schemars", schemars(with = "Option<serde_json::Value>"))]
    #[serde(default)]
    pub params: Option<Arc<RawValue>>,
    /// Metadata for the `proxy/successor` envelope itself, separate from the wrapped
    /// message's `_meta`.
    ///
    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
    /// Implementations MUST NOT assign meaning to keys they do not recognize.
    ///
    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
    #[serde_as(deserialize_as = "DefaultOnError")]
    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
    #[serde(default)]
    #[serde(rename = "_meta")]
    pub meta: Option<Meta>,
}

impl ProxySuccessorNotification {
    /// Builds [`ProxySuccessorNotification`] with the required fields set. Optional fields start unset or empty.
    #[must_use]
    pub fn new(method: impl Into<Arc<str>>) -> Self {
        Self {
            method: method.into(),
            params: None,
            meta: None,
        }
    }

    /// Params of the wrapped notification, forwarded unchanged.
    #[must_use]
    pub fn params(mut self, params: impl IntoOption<Arc<RawValue>>) -> Self {
        self.params = params.into_option();
        self
    }

    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
    /// do not recognize.
    ///
    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
    #[must_use]
    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
        self.meta = meta.into_option();
        self
    }
}

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Result of a `proxy/successor` request: the wrapped request's result, forwarded unchanged.
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize, From)]
#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "proxy", "x-method" = PROXY_SUCCESSOR_METHOD_NAME)))]
#[serde(transparent)]
#[non_exhaustive]
pub struct ProxySuccessorResponse(
    #[cfg_attr(feature = "schemars", schemars(with = "serde_json::Value"))] pub Arc<RawValue>,
);

impl ProxySuccessorResponse {
    /// Wraps the successor's result.
    #[must_use]
    pub fn new(result: Arc<RawValue>) -> Self {
        Self(result)
    }
}

// Method schema

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Names of the methods exchanged between a conductor and its proxies.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ProxyMethodNames {
    /// Method for initializing a component as a proxy.
    pub proxy_initialize: &'static str,
    /// Method for forwarding a message to or from a proxy's successor.
    pub proxy_successor: &'static str,
}

/// Constant containing all proxy chain method names.
pub const PROXY_METHOD_NAMES: ProxyMethodNames = ProxyMethodNames {
    proxy_initialize: PROXY_INITIALIZE_METHOD_NAME,
    proxy_successor: PROXY_SUCCESSOR_METHOD_NAME,
};

/// Method name for initializing a component as a proxy.
pub(crate) const PROXY_INITIALIZE_METHOD_NAME: &str = "proxy/initialize";
/// Method name for forwarding a message to or from a proxy's successor.
pub(crate) const PROXY_SUCCESSOR_METHOD_NAME: &str = "proxy/successor";

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Requests exchanged between a conductor and its proxies.
///
/// A conductor sends `proxy/initialize` and `proxy/successor` to a proxy. A proxy sends
/// `proxy/successor` to its conductor.
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[non_exhaustive]
pub enum ProxyRequest {
    /// Initializes a component as a proxy.
    ///
    /// Sent by a conductor to each component that has a successor, instead of `initialize`.
    /// The params and response are the same as for `initialize`.
    InitializeProxyRequest(Box<InitializeProxyRequest>),
    /// Forwards a request to or from a proxy's successor.
    ///
    /// A proxy sends it to its conductor to forward a request downstream. The conductor sends
    /// it to the proxy to deliver a request from the successor. The wrapped request's result
    /// or error is returned unchanged.
    ProxySuccessorRequest(Box<ProxySuccessorRequest>),
}

impl ProxyRequest {
    /// Returns the corresponding method name of the request.
    #[must_use]
    pub fn method(&self) -> &str {
        match self {
            Self::InitializeProxyRequest(_) => PROXY_METHOD_NAMES.proxy_initialize,
            Self::ProxySuccessorRequest(_) => PROXY_METHOD_NAMES.proxy_successor,
        }
    }
}

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Responses to requests exchanged between a conductor and its proxies.
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[non_exhaustive]
pub enum ProxyResponse {
    /// Successful result returned for a `proxy/initialize` request.
    InitializeProxyResponse(Box<InitializeProxyResponse>),
    /// Successful result returned for a `proxy/successor` request.
    ProxySuccessorResponse(Box<ProxySuccessorResponse>),
}

/// **UNSTABLE**
///
/// This capability is not part of the spec yet, and may be removed or changed at any point.
///
/// Notifications exchanged between a conductor and its proxies.
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[non_exhaustive]
pub enum ProxyNotification {
    /// Forwards a notification to or from a proxy's successor.
    ///
    /// A proxy sends it to its conductor to forward a notification downstream. The conductor
    /// sends it to the proxy to deliver a notification from the successor.
    ProxySuccessorNotification(Box<ProxySuccessorNotification>),
}

impl ProxyNotification {
    /// Returns the corresponding method name of the notification.
    #[must_use]
    pub fn method(&self) -> &str {
        match self {
            Self::ProxySuccessorNotification(_) => PROXY_METHOD_NAMES.proxy_successor,
        }
    }
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::*;
    use crate::ProtocolVersion;

    fn raw(value: &serde_json::Value) -> Arc<RawValue> {
        RawValue::from_string(value.to_string()).unwrap().into()
    }

    #[test]
    fn test_initialize_proxy_request_matches_initialize_params() {
        let request = InitializeProxyRequest::new(InitializeRequest::new(
            ProtocolVersion::V2,
            crate::v2::Implementation::new("conductor", "1.0.0"),
        ));
        let value = serde_json::to_value(&request).unwrap();
        assert_eq!(value, serde_json::to_value(&request.initialize).unwrap());
        assert_eq!(
            ProxyRequest::InitializeProxyRequest(Box::new(request)).method(),
            "proxy/initialize"
        );
        let response = InitializeProxyResponse::new(InitializeResponse::new(
            ProtocolVersion::V2,
            crate::v2::Implementation::new("proxy", "1.0.0"),
        ));
        assert_eq!(
            serde_json::to_value(&response).unwrap(),
            serde_json::to_value(&response.initialize).unwrap()
        );
        let parsed: InitializeProxyRequest = serde_json::from_value(value).unwrap();
        assert_eq!(parsed.initialize.protocol_version, ProtocolVersion::V2);
    }

    #[test]
    fn test_proxy_successor_round_trip() {
        let request = ProxySuccessorRequest::new("session/prompt")
            .params(raw(
                &json!({"sessionId": "sess_abc123", "_meta": {"inner": true}}),
            ))
            .meta(json!({"outer": true}).as_object().cloned());
        let value = serde_json::to_value(&request).unwrap();
        assert_eq!(
            value,
            json!({
                "method": "session/prompt",
                "params": {"sessionId": "sess_abc123", "_meta": {"inner": true}},
                "_meta": {"outer": true}
            })
        );
        assert_eq!(
            ProxyRequest::ProxySuccessorRequest(Box::new(request)).method(),
            "proxy/successor"
        );

        for params in [
            json!({"method": "session/cancel"}),
            json!({"method": "session/cancel", "params": null}),
        ] {
            let notification: ProxySuccessorNotification = serde_json::from_value(params).unwrap();
            assert_eq!(&*notification.method, "session/cancel");
            assert!(notification.params.is_none());
        }
        assert!(
            serde_json::from_value::<ProxySuccessorNotification>(json!({"params": {}})).is_err()
        );

        let response = ProxySuccessorResponse::new(raw(&json!({"stopReason": "end_turn"})));
        assert_eq!(
            serde_json::to_value(&response).unwrap(),
            json!({"stopReason": "end_turn"})
        );
    }
}