Skip to main content

agent_client_protocol_schema/v2/
client.rs

1//! Methods and notifications the client handles/receives.
2//!
3//! This module defines the Client trait and all associated types for implementing
4//! a client that interacts with AI coding agents via the Agent Client Protocol (ACP).
5
6use std::{collections::BTreeMap, sync::Arc};
7
8use derive_more::{Display, From};
9use schemars::{JsonSchema, Schema};
10use serde::{Deserialize, Serialize};
11use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
12
13#[cfg(feature = "unstable_plan_operations")]
14use super::PlanRemoved;
15#[cfg(feature = "unstable_elicitation")]
16use super::{
17    CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
18    ElicitationCapabilities,
19};
20use super::{
21    ContentBlock, ExtNotification, ExtRequest, ExtResponse, Meta, PlanUpdate, SessionConfigOption,
22    SessionId, ToolCall, ToolCallUpdate,
23};
24use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined, SkipListener};
25
26#[cfg(feature = "unstable_mcp_over_acp")]
27use super::mcp::{
28    ConnectMcpRequest, ConnectMcpResponse, DisconnectMcpRequest, DisconnectMcpResponse,
29    MCP_CONNECT_METHOD_NAME, MCP_DISCONNECT_METHOD_NAME, MCP_MESSAGE_METHOD_NAME,
30    MessageMcpNotification, MessageMcpRequest, MessageMcpResponse,
31};
32
33#[cfg(feature = "unstable_nes")]
34use super::{ClientNesCapabilities, PositionEncodingKind};
35
36// Session updates
37
38/// Notification containing a session update from the agent.
39///
40/// Used to stream real-time progress and results during prompt processing.
41///
42/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
43#[skip_serializing_none]
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
45#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
46#[serde(rename_all = "camelCase")]
47#[non_exhaustive]
48pub struct SessionNotification {
49    /// The ID of the session this update pertains to.
50    pub session_id: SessionId,
51    /// The actual update content.
52    pub update: SessionUpdate,
53    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
54    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
55    /// these keys.
56    ///
57    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
58    #[serde(rename = "_meta")]
59    pub meta: Option<Meta>,
60}
61
62impl SessionNotification {
63    #[must_use]
64    pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
65        Self {
66            session_id: session_id.into(),
67            update,
68            meta: None,
69        }
70    }
71
72    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
73    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
74    /// these keys.
75    ///
76    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
77    #[must_use]
78    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
79        self.meta = meta.into_option();
80        self
81    }
82}
83
84/// Different types of updates that can be sent during session processing.
85///
86/// These updates provide real-time feedback about the agent's progress.
87///
88/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
89#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
90#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
91#[schemars(extend("discriminator" = {"propertyName": "sessionUpdate"}))]
92#[non_exhaustive]
93pub enum SessionUpdate {
94    /// A chunk of the user's message being streamed.
95    UserMessageChunk(ContentChunk),
96    /// A chunk of the agent's response being streamed.
97    AgentMessageChunk(ContentChunk),
98    /// A chunk of the agent's internal reasoning being streamed.
99    AgentThoughtChunk(ContentChunk),
100    /// Notification that a new tool call has been initiated.
101    ToolCall(ToolCall),
102    /// Update on the status or results of a tool call.
103    ToolCallUpdate(ToolCallUpdate),
104    /// A content update for a plan identified by ID.
105    /// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
106    PlanUpdate(PlanUpdate),
107    /// **UNSTABLE**
108    ///
109    /// This capability is not part of the spec yet, and may be removed or changed at any point.
110    ///
111    /// Removal notice for a plan identified by ID.
112    #[cfg(feature = "unstable_plan_operations")]
113    PlanRemoved(PlanRemoved),
114    /// Available commands are ready or have changed
115    AvailableCommandsUpdate(AvailableCommandsUpdate),
116    /// Session configuration options have been updated.
117    ConfigOptionUpdate(ConfigOptionUpdate),
118    /// Session metadata has been updated (title, timestamps, custom metadata)
119    SessionInfoUpdate(SessionInfoUpdate),
120    /// Context window and cost update for the session.
121    UsageUpdate(UsageUpdate),
122    /// Custom or future session update.
123    ///
124    /// Values beginning with `_` are reserved for implementation-specific
125    /// extensions. Unknown values that do not begin with `_` are reserved for
126    /// future ACP variants.
127    ///
128    /// Receivers that do not understand this update type should preserve the
129    /// raw payload when storing, replaying, proxying, or forwarding session
130    /// history, and otherwise ignore it or display it generically.
131    #[serde(untagged)]
132    Other(OtherSessionUpdate),
133}
134
135/// Custom or future session update payload.
136///
137/// This preserves the unknown `sessionUpdate` discriminator and the rest of the
138/// update object for clients that store, replay, proxy, or forward session
139/// history.
140#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
141#[schemars(inline)]
142#[schemars(transform = other_session_update_schema)]
143#[serde(rename_all = "camelCase")]
144#[non_exhaustive]
145pub struct OtherSessionUpdate {
146    /// Custom or future session update type.
147    ///
148    /// Values beginning with `_` are reserved for implementation-specific
149    /// extensions. Unknown values that do not begin with `_` are reserved for
150    /// future ACP variants.
151    #[serde(rename = "sessionUpdate")]
152    pub session_update: String,
153    /// Additional fields from the unknown update payload.
154    #[serde(flatten)]
155    pub fields: BTreeMap<String, serde_json::Value>,
156}
157
158impl OtherSessionUpdate {
159    #[must_use]
160    pub fn new(
161        session_update: impl Into<String>,
162        mut fields: BTreeMap<String, serde_json::Value>,
163    ) -> Self {
164        fields.remove("sessionUpdate");
165        Self {
166            session_update: session_update.into(),
167            fields,
168        }
169    }
170}
171
172impl<'de> Deserialize<'de> for OtherSessionUpdate {
173    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174    where
175        D: serde::Deserializer<'de>,
176    {
177        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
178        let session_update = fields
179            .remove("sessionUpdate")
180            .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
181        let serde_json::Value::String(session_update) = session_update else {
182            return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
183        };
184
185        if is_known_session_update(&session_update) {
186            return Err(serde::de::Error::custom(format!(
187                "known session update `{session_update}` did not match its schema"
188            )));
189        }
190
191        Ok(Self {
192            session_update,
193            fields,
194        })
195    }
196}
197
198fn is_known_session_update(session_update: &str) -> bool {
199    matches!(
200        session_update,
201        "user_message_chunk"
202            | "agent_message_chunk"
203            | "agent_thought_chunk"
204            | "tool_call"
205            | "tool_call_update"
206            | "plan_update"
207            | "available_commands_update"
208            | "config_option_update"
209            | "session_info_update"
210            | "usage_update"
211    )
212}
213
214fn other_session_update_schema(schema: &mut Schema) {
215    super::schema_util::reject_known_string_discriminators(
216        schema,
217        "sessionUpdate",
218        &[
219            "user_message_chunk",
220            "agent_message_chunk",
221            "agent_thought_chunk",
222            "tool_call",
223            "tool_call_update",
224            "plan_update",
225            "available_commands_update",
226            "config_option_update",
227            "session_info_update",
228            #[cfg(feature = "unstable_plan_operations")]
229            "plan_removed",
230            "usage_update",
231        ],
232    );
233}
234
235/// Session configuration options have been updated.
236#[serde_as]
237#[skip_serializing_none]
238#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
239#[serde(rename_all = "camelCase")]
240#[non_exhaustive]
241pub struct ConfigOptionUpdate {
242    /// The full set of configuration options and their current values.
243    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
244    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
245    pub config_options: Vec<SessionConfigOption>,
246    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
247    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
248    /// these keys.
249    ///
250    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
251    #[serde(rename = "_meta")]
252    pub meta: Option<Meta>,
253}
254
255impl ConfigOptionUpdate {
256    #[must_use]
257    pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
258        Self {
259            config_options,
260            meta: None,
261        }
262    }
263
264    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
265    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
266    /// these keys.
267    ///
268    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
269    #[must_use]
270    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
271        self.meta = meta.into_option();
272        self
273    }
274}
275
276/// Update to session metadata. All fields are optional to support partial updates.
277///
278/// Agents send this notification to update session information like title or custom metadata.
279/// This allows clients to display dynamic session names and track session state changes.
280#[skip_serializing_none]
281#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
282#[serde(rename_all = "camelCase")]
283#[non_exhaustive]
284pub struct SessionInfoUpdate {
285    /// Human-readable title for the session. Set to null to clear.
286    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
287    pub title: MaybeUndefined<String>,
288    /// ISO 8601 timestamp of last activity. Set to null to clear.
289    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
290    pub updated_at: MaybeUndefined<String>,
291    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
292    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
293    /// these keys.
294    ///
295    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
296    #[serde(rename = "_meta")]
297    pub meta: Option<Meta>,
298}
299
300impl SessionInfoUpdate {
301    #[must_use]
302    pub fn new() -> Self {
303        Self::default()
304    }
305
306    /// Human-readable title for the session. Set to null to clear.
307    #[must_use]
308    pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
309        self.title = title.into_maybe_undefined();
310        self
311    }
312
313    /// ISO 8601 timestamp of last activity. Set to null to clear.
314    #[must_use]
315    pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
316        self.updated_at = updated_at.into_maybe_undefined();
317        self
318    }
319
320    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
321    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
322    /// these keys.
323    ///
324    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
325    #[must_use]
326    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
327        self.meta = meta.into_option();
328        self
329    }
330}
331
332/// Context window and cost update for a session.
333#[serde_as]
334#[skip_serializing_none]
335#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
336#[serde(rename_all = "camelCase")]
337#[non_exhaustive]
338pub struct UsageUpdate {
339    /// Tokens currently in context.
340    pub used: u64,
341    /// Total context window size in tokens.
342    pub size: u64,
343    /// Cumulative session cost (optional).
344    #[serde_as(deserialize_as = "DefaultOnError")]
345    #[schemars(extend("x-deserialize-default-on-error" = true))]
346    #[serde(default)]
347    pub cost: Option<Cost>,
348    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
349    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
350    /// these keys.
351    ///
352    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
353    #[serde(rename = "_meta")]
354    pub meta: Option<Meta>,
355}
356
357impl UsageUpdate {
358    #[must_use]
359    pub fn new(used: u64, size: u64) -> Self {
360        Self {
361            used,
362            size,
363            cost: None,
364            meta: None,
365        }
366    }
367
368    /// Cumulative session cost (optional).
369    #[must_use]
370    pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
371        self.cost = cost.into_option();
372        self
373    }
374
375    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
376    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
377    /// these keys.
378    ///
379    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
380    #[must_use]
381    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
382        self.meta = meta.into_option();
383        self
384    }
385}
386
387/// Cost information for a session.
388#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
389#[serde(rename_all = "camelCase")]
390#[non_exhaustive]
391pub struct Cost {
392    /// Total cumulative cost for session.
393    pub amount: f64,
394    /// ISO 4217 currency code (e.g., "USD", "EUR").
395    pub currency: String,
396}
397
398impl Cost {
399    #[must_use]
400    pub fn new(amount: f64, currency: impl Into<String>) -> Self {
401        Self {
402            amount,
403            currency: currency.into(),
404        }
405    }
406}
407
408/// A streamed item of content
409#[skip_serializing_none]
410#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
411#[serde(rename_all = "camelCase")]
412#[non_exhaustive]
413pub struct ContentChunk {
414    /// A single item of content
415    pub content: ContentBlock,
416    /// A unique identifier for the message this chunk belongs to.
417    ///
418    /// All chunks belonging to the same message share the same `messageId`.
419    /// A change in `messageId` indicates a new message has started.
420    pub message_id: MessageId,
421    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
422    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
423    /// these keys.
424    ///
425    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
426    #[serde(rename = "_meta")]
427    pub meta: Option<Meta>,
428}
429
430impl ContentChunk {
431    #[must_use]
432    pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
433        Self {
434            content,
435            message_id: message_id.into(),
436            meta: None,
437        }
438    }
439
440    /// A unique identifier for the message this chunk belongs to.
441    ///
442    /// All chunks belonging to the same message share the same `messageId`.
443    /// A change in `messageId` indicates a new message has started.
444    #[must_use]
445    pub fn message_id(mut self, message_id: impl Into<MessageId>) -> Self {
446        self.message_id = message_id.into();
447        self
448    }
449
450    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
451    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
452    /// these keys.
453    ///
454    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
455    #[must_use]
456    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
457        self.meta = meta.into_option();
458        self
459    }
460}
461
462/// Unique identifier for a message within a session.
463#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
464#[serde(transparent)]
465#[from(Arc<str>, String, &'static str)]
466#[non_exhaustive]
467pub struct MessageId(pub Arc<str>);
468
469impl MessageId {
470    #[must_use]
471    pub fn new(id: impl Into<Arc<str>>) -> Self {
472        Self(id.into())
473    }
474}
475
476/// Available commands are ready or have changed
477#[serde_as]
478#[skip_serializing_none]
479#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
480#[serde(rename_all = "camelCase")]
481#[non_exhaustive]
482pub struct AvailableCommandsUpdate {
483    /// Commands the agent can execute
484    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
485    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
486    pub available_commands: Vec<AvailableCommand>,
487    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
488    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
489    /// these keys.
490    ///
491    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
492    #[serde(rename = "_meta")]
493    pub meta: Option<Meta>,
494}
495
496impl AvailableCommandsUpdate {
497    #[must_use]
498    pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
499        Self {
500            available_commands,
501            meta: None,
502        }
503    }
504
505    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
506    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
507    /// these keys.
508    ///
509    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
510    #[must_use]
511    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
512        self.meta = meta.into_option();
513        self
514    }
515}
516
517/// Information about a command.
518#[serde_as]
519#[skip_serializing_none]
520#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
521#[serde(rename_all = "camelCase")]
522#[non_exhaustive]
523pub struct AvailableCommand {
524    /// Command name (e.g., `create_plan`, `research_codebase`).
525    pub name: String,
526    /// Human-readable description of what the command does.
527    pub description: String,
528    /// Input for the command if required
529    #[serde_as(deserialize_as = "DefaultOnError")]
530    #[schemars(extend("x-deserialize-default-on-error" = true))]
531    #[serde(default)]
532    pub input: Option<AvailableCommandInput>,
533    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
534    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
535    /// these keys.
536    ///
537    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
538    #[serde(rename = "_meta")]
539    pub meta: Option<Meta>,
540}
541
542impl AvailableCommand {
543    #[must_use]
544    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
545        Self {
546            name: name.into(),
547            description: description.into(),
548            input: None,
549            meta: None,
550        }
551    }
552
553    /// Input for the command if required
554    #[must_use]
555    pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
556        self.input = input.into_option();
557        self
558    }
559
560    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
561    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
562    /// these keys.
563    ///
564    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
565    #[must_use]
566    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
567        self.meta = meta.into_option();
568        self
569    }
570}
571
572/// The input specification for a command.
573#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
574#[serde(untagged, rename_all = "camelCase")]
575#[non_exhaustive]
576pub enum AvailableCommandInput {
577    /// All text that was typed after the command name is provided as input.
578    Unstructured(UnstructuredCommandInput),
579    /// Custom or future command input specification.
580    ///
581    /// Values beginning with `_` are reserved for implementation-specific
582    /// extensions. Unknown values that do not begin with `_` are reserved for
583    /// future ACP variants.
584    ///
585    /// Clients that do not understand this input type should preserve the raw
586    /// payload when storing, replaying, proxying, or forwarding command
587    /// metadata, and otherwise ignore the input specification or display the
588    /// command without structured input.
589    Other(OtherAvailableCommandInput),
590}
591
592/// Custom or future command input specification.
593#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
594#[schemars(inline)]
595#[serde(rename_all = "camelCase")]
596#[non_exhaustive]
597pub struct OtherAvailableCommandInput {
598    /// Custom or future command input type.
599    ///
600    /// Values beginning with `_` are reserved for implementation-specific
601    /// extensions. Unknown values that do not begin with `_` are reserved for
602    /// future ACP variants.
603    #[serde(rename = "type")]
604    pub type_: String,
605    /// Additional fields from the unknown command input payload.
606    #[serde(flatten)]
607    pub fields: BTreeMap<String, serde_json::Value>,
608}
609
610impl OtherAvailableCommandInput {
611    #[must_use]
612    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
613        fields.remove("type");
614        Self {
615            type_: type_.into(),
616            fields,
617        }
618    }
619}
620
621impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
622    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
623    where
624        D: serde::Deserializer<'de>,
625    {
626        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
627        let type_ = fields
628            .remove("type")
629            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
630        let serde_json::Value::String(type_) = type_ else {
631            return Err(serde::de::Error::custom("`type` must be a string"));
632        };
633
634        Ok(Self { type_, fields })
635    }
636}
637
638/// All text that was typed after the command name is provided as input.
639#[skip_serializing_none]
640#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
641#[schemars(transform = unstructured_command_input_schema)]
642#[serde(rename_all = "camelCase")]
643#[non_exhaustive]
644pub struct UnstructuredCommandInput {
645    /// A hint to display when the input hasn't been provided yet
646    pub hint: String,
647    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
648    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
649    /// these keys.
650    ///
651    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
652    #[serde(rename = "_meta")]
653    pub meta: Option<Meta>,
654}
655
656impl UnstructuredCommandInput {
657    #[must_use]
658    pub fn new(hint: impl Into<String>) -> Self {
659        Self {
660            hint: hint.into(),
661            meta: None,
662        }
663    }
664
665    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
666    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
667    /// these keys.
668    ///
669    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
670    #[must_use]
671    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
672        self.meta = meta.into_option();
673        self
674    }
675}
676
677impl<'de> Deserialize<'de> for UnstructuredCommandInput {
678    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
679    where
680        D: serde::Deserializer<'de>,
681    {
682        #[derive(Deserialize)]
683        #[serde(rename_all = "camelCase")]
684        struct RawUnstructuredCommandInput {
685            hint: String,
686            #[serde(rename = "_meta")]
687            meta: Option<Meta>,
688            #[serde(flatten)]
689            fields: BTreeMap<String, serde_json::Value>,
690        }
691
692        let raw = RawUnstructuredCommandInput::deserialize(deserializer)?;
693        if raw.fields.contains_key("type") {
694            return Err(serde::de::Error::custom(
695                "unstructured command input cannot include a `type` field",
696            ));
697        }
698
699        Ok(Self {
700            hint: raw.hint,
701            meta: raw.meta,
702        })
703    }
704}
705
706fn unstructured_command_input_schema(schema: &mut Schema) {
707    super::schema_util::reject_property(schema, "type");
708}
709
710// Permission
711
712/// Request for user permission to execute a tool call.
713///
714/// Sent when the agent needs authorization before performing a sensitive operation.
715///
716/// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
717#[skip_serializing_none]
718#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
719#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
720#[serde(rename_all = "camelCase")]
721#[non_exhaustive]
722pub struct RequestPermissionRequest {
723    /// The session ID for this request.
724    pub session_id: SessionId,
725    /// Details about the tool call requiring permission.
726    pub tool_call: ToolCallUpdate,
727    /// Available permission options for the user to choose from.
728    pub options: Vec<PermissionOption>,
729    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
730    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
731    /// these keys.
732    ///
733    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
734    #[serde(rename = "_meta")]
735    pub meta: Option<Meta>,
736}
737
738impl RequestPermissionRequest {
739    #[must_use]
740    pub fn new(
741        session_id: impl Into<SessionId>,
742        tool_call: ToolCallUpdate,
743        options: Vec<PermissionOption>,
744    ) -> Self {
745        Self {
746            session_id: session_id.into(),
747            tool_call,
748            options,
749            meta: None,
750        }
751    }
752
753    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
754    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
755    /// these keys.
756    ///
757    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
758    #[must_use]
759    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
760        self.meta = meta.into_option();
761        self
762    }
763}
764
765/// An option presented to the user when requesting permission.
766#[skip_serializing_none]
767#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
768#[serde(rename_all = "camelCase")]
769#[non_exhaustive]
770pub struct PermissionOption {
771    /// Unique identifier for this permission option.
772    pub option_id: PermissionOptionId,
773    /// Human-readable label to display to the user.
774    pub name: String,
775    /// Hint about the nature of this permission option.
776    pub kind: PermissionOptionKind,
777    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
778    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
779    /// these keys.
780    ///
781    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
782    #[serde(rename = "_meta")]
783    pub meta: Option<Meta>,
784}
785
786impl PermissionOption {
787    #[must_use]
788    pub fn new(
789        option_id: impl Into<PermissionOptionId>,
790        name: impl Into<String>,
791        kind: PermissionOptionKind,
792    ) -> Self {
793        Self {
794            option_id: option_id.into(),
795            name: name.into(),
796            kind,
797            meta: None,
798        }
799    }
800
801    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
802    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
803    /// these keys.
804    ///
805    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
806    #[must_use]
807    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
808        self.meta = meta.into_option();
809        self
810    }
811}
812
813/// Unique identifier for a permission option.
814#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
815#[serde(transparent)]
816#[from(Arc<str>, String, &'static str)]
817#[non_exhaustive]
818pub struct PermissionOptionId(pub Arc<str>);
819
820impl PermissionOptionId {
821    #[must_use]
822    pub fn new(id: impl Into<Arc<str>>) -> Self {
823        Self(id.into())
824    }
825}
826
827/// The type of permission option being presented to the user.
828///
829/// Helps clients choose appropriate icons and UI treatment.
830#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
831#[serde(rename_all = "snake_case")]
832#[non_exhaustive]
833pub enum PermissionOptionKind {
834    /// Allow this operation only this time.
835    AllowOnce,
836    /// Allow this operation and remember the choice.
837    AllowAlways,
838    /// Reject this operation only this time.
839    RejectOnce,
840    /// Reject this operation and remember the choice.
841    RejectAlways,
842    /// Custom or future permission option kind.
843    ///
844    /// Values beginning with `_` are reserved for implementation-specific
845    /// extensions. Unknown values that do not begin with `_` are reserved for
846    /// future ACP variants.
847    #[serde(untagged)]
848    Other(String),
849}
850
851/// Response to a permission request.
852#[skip_serializing_none]
853#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
854#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
855#[serde(rename_all = "camelCase")]
856#[non_exhaustive]
857pub struct RequestPermissionResponse {
858    /// The user's decision on the permission request.
859    // This extra-level is unfortunately needed because the output must be an object
860    pub outcome: RequestPermissionOutcome,
861    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
862    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
863    /// these keys.
864    ///
865    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
866    #[serde(rename = "_meta")]
867    pub meta: Option<Meta>,
868}
869
870impl RequestPermissionResponse {
871    #[must_use]
872    pub fn new(outcome: RequestPermissionOutcome) -> Self {
873        Self {
874            outcome,
875            meta: None,
876        }
877    }
878
879    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
880    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
881    /// these keys.
882    ///
883    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
884    #[must_use]
885    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
886        self.meta = meta.into_option();
887        self
888    }
889}
890
891/// The outcome of a permission request.
892#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
893#[serde(tag = "outcome", rename_all = "snake_case")]
894#[schemars(extend("discriminator" = {"propertyName": "outcome"}))]
895#[non_exhaustive]
896pub enum RequestPermissionOutcome {
897    /// The prompt turn was cancelled before the user responded.
898    ///
899    /// When a client sends a `session/cancel` notification to cancel an ongoing
900    /// prompt turn, it MUST respond to all pending `session/request_permission`
901    /// requests with this `Cancelled` outcome.
902    ///
903    /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation)
904    Cancelled,
905    /// The user selected one of the provided options.
906    #[serde(rename_all = "camelCase")]
907    Selected(SelectedPermissionOutcome),
908}
909
910/// The user selected one of the provided options.
911#[skip_serializing_none]
912#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
913#[serde(rename_all = "camelCase")]
914#[non_exhaustive]
915pub struct SelectedPermissionOutcome {
916    /// The ID of the option the user selected.
917    pub option_id: PermissionOptionId,
918    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
919    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
920    /// these keys.
921    ///
922    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
923    #[serde(rename = "_meta")]
924    pub meta: Option<Meta>,
925}
926
927impl SelectedPermissionOutcome {
928    #[must_use]
929    pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
930        Self {
931            option_id: option_id.into(),
932            meta: None,
933        }
934    }
935
936    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
937    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
938    /// these keys.
939    ///
940    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
941    #[must_use]
942    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
943        self.meta = meta.into_option();
944        self
945    }
946}
947
948// Capabilities
949
950/// Capabilities supported by the client.
951///
952/// Advertised during initialization to inform the agent about
953/// available features and methods.
954///
955/// See protocol docs: [Client Capabilities](https://agentclientprotocol.com/protocol/initialization#client-capabilities)
956#[serde_as]
957#[skip_serializing_none]
958#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
959#[serde(rename_all = "camelCase")]
960#[non_exhaustive]
961pub struct ClientCapabilities {
962    /// **UNSTABLE**
963    ///
964    /// This capability is not part of the spec yet, and may be removed or changed at any point.
965    ///
966    /// Authentication capabilities supported by the client.
967    /// Determines which authentication method types the agent may include
968    /// in its `InitializeResponse`.
969    #[cfg(feature = "unstable_auth_methods")]
970    #[serde(default)]
971    pub auth: AuthCapabilities,
972    /// **UNSTABLE**
973    ///
974    /// This capability is not part of the spec yet, and may be removed or changed at any point.
975    ///
976    /// Elicitation capabilities supported by the client.
977    /// Determines which elicitation modes the agent may use.
978    #[cfg(feature = "unstable_elicitation")]
979    #[serde_as(deserialize_as = "DefaultOnError")]
980    #[schemars(extend("x-deserialize-default-on-error" = true))]
981    #[serde(default)]
982    pub elicitation: Option<ElicitationCapabilities>,
983    /// **UNSTABLE**
984    ///
985    /// This capability is not part of the spec yet, and may be removed or changed at any point.
986    ///
987    /// NES (Next Edit Suggestions) capabilities supported by the client.
988    #[cfg(feature = "unstable_nes")]
989    #[serde_as(deserialize_as = "DefaultOnError")]
990    #[schemars(extend("x-deserialize-default-on-error" = true))]
991    #[serde(default)]
992    pub nes: Option<ClientNesCapabilities>,
993    /// **UNSTABLE**
994    ///
995    /// This capability is not part of the spec yet, and may be removed or changed at any point.
996    ///
997    /// The position encodings supported by the client, in order of preference.
998    #[cfg(feature = "unstable_nes")]
999    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1000    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1001    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1002    pub position_encodings: Vec<PositionEncodingKind>,
1003
1004    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1005    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1006    /// these keys.
1007    ///
1008    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1009    #[serde(rename = "_meta")]
1010    pub meta: Option<Meta>,
1011}
1012
1013impl ClientCapabilities {
1014    #[must_use]
1015    pub fn new() -> Self {
1016        Self::default()
1017    }
1018
1019    /// **UNSTABLE**
1020    ///
1021    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1022    ///
1023    /// Authentication capabilities supported by the client.
1024    /// Determines which authentication method types the agent may include
1025    /// in its `InitializeResponse`.
1026    #[cfg(feature = "unstable_auth_methods")]
1027    #[must_use]
1028    pub fn auth(mut self, auth: AuthCapabilities) -> Self {
1029        self.auth = auth;
1030        self
1031    }
1032
1033    /// **UNSTABLE**
1034    ///
1035    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1036    ///
1037    /// Elicitation capabilities supported by the client.
1038    /// Determines which elicitation modes the agent may use.
1039    #[cfg(feature = "unstable_elicitation")]
1040    #[must_use]
1041    pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1042        self.elicitation = elicitation.into_option();
1043        self
1044    }
1045
1046    /// **UNSTABLE**
1047    ///
1048    /// NES (Next Edit Suggestions) capabilities supported by the client.
1049    #[cfg(feature = "unstable_nes")]
1050    #[must_use]
1051    pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1052        self.nes = nes.into_option();
1053        self
1054    }
1055
1056    /// **UNSTABLE**
1057    ///
1058    /// The position encodings supported by the client, in order of preference.
1059    #[cfg(feature = "unstable_nes")]
1060    #[must_use]
1061    pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1062        self.position_encodings = position_encodings;
1063        self
1064    }
1065
1066    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1067    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1068    /// these keys.
1069    ///
1070    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1071    #[must_use]
1072    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1073        self.meta = meta.into_option();
1074        self
1075    }
1076}
1077
1078/// **UNSTABLE**
1079///
1080/// This capability is not part of the spec yet, and may be removed or changed at any point.
1081///
1082/// Authentication capabilities supported by the client.
1083///
1084/// Advertised during initialization to inform the agent which authentication
1085/// method types the client can handle. This governs opt-in types that require
1086/// additional client-side support.
1087#[cfg(feature = "unstable_auth_methods")]
1088#[serde_as]
1089#[skip_serializing_none]
1090#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1091#[serde(rename_all = "camelCase")]
1092#[non_exhaustive]
1093pub struct AuthCapabilities {
1094    /// Whether the client supports `terminal` authentication methods.
1095    ///
1096    /// Optional. Omitted or `null` both mean the client does not advertise support.
1097    /// Supplying `{}` means the agent may include `terminal` entries in its authentication methods.
1098    #[serde_as(deserialize_as = "DefaultOnError")]
1099    #[schemars(extend("x-deserialize-default-on-error" = true))]
1100    #[serde(default)]
1101    pub terminal: Option<TerminalAuthCapabilities>,
1102    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1103    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1104    /// these keys.
1105    ///
1106    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1107    #[serde(rename = "_meta")]
1108    pub meta: Option<Meta>,
1109}
1110
1111#[cfg(feature = "unstable_auth_methods")]
1112impl AuthCapabilities {
1113    #[must_use]
1114    pub fn new() -> Self {
1115        Self::default()
1116    }
1117
1118    /// Whether the client supports `terminal` authentication methods.
1119    ///
1120    /// Omitted or `null` both mean the client does not advertise support.
1121    /// Supplying `{}` means the agent may include `AuthMethod::Terminal` entries in its authentication methods.
1122    #[must_use]
1123    pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
1124        self.terminal = terminal.into_option();
1125        self
1126    }
1127
1128    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1129    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1130    /// these keys.
1131    ///
1132    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1133    #[must_use]
1134    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1135        self.meta = meta.into_option();
1136        self
1137    }
1138}
1139
1140/// **UNSTABLE**
1141///
1142/// This capability is not part of the spec yet, and may be removed or changed at any point.
1143///
1144/// Capabilities for terminal authentication methods.
1145///
1146/// Supplying `{}` means the client supports terminal authentication methods.
1147#[cfg(feature = "unstable_auth_methods")]
1148#[skip_serializing_none]
1149#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1150#[non_exhaustive]
1151pub struct TerminalAuthCapabilities {
1152    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1153    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1154    /// these keys.
1155    ///
1156    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1157    #[serde(rename = "_meta")]
1158    pub meta: Option<Meta>,
1159}
1160
1161#[cfg(feature = "unstable_auth_methods")]
1162impl TerminalAuthCapabilities {
1163    #[must_use]
1164    pub fn new() -> Self {
1165        Self::default()
1166    }
1167
1168    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1169    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1170    /// these keys.
1171    ///
1172    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1173    #[must_use]
1174    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1175        self.meta = meta.into_option();
1176        self
1177    }
1178}
1179
1180// Method schema
1181
1182/// Names of all methods that clients handle.
1183///
1184/// Provides a centralized definition of method names used in the protocol.
1185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1186#[non_exhaustive]
1187pub struct ClientMethodNames {
1188    /// Method for requesting permission from the user.
1189    pub session_request_permission: &'static str,
1190    /// Notification for session updates.
1191    pub session_update: &'static str,
1192    /// Method for opening an MCP-over-ACP connection.
1193    #[cfg(feature = "unstable_mcp_over_acp")]
1194    pub mcp_connect: &'static str,
1195    /// Method for exchanging MCP-over-ACP messages.
1196    #[cfg(feature = "unstable_mcp_over_acp")]
1197    pub mcp_message: &'static str,
1198    /// Method for closing an MCP-over-ACP connection.
1199    #[cfg(feature = "unstable_mcp_over_acp")]
1200    pub mcp_disconnect: &'static str,
1201    /// Method for elicitation.
1202    #[cfg(feature = "unstable_elicitation")]
1203    pub elicitation_create: &'static str,
1204    /// Notification for elicitation completion.
1205    #[cfg(feature = "unstable_elicitation")]
1206    pub elicitation_complete: &'static str,
1207}
1208
1209/// Constant containing all client method names.
1210pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
1211    session_update: SESSION_UPDATE_NOTIFICATION,
1212    session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
1213    #[cfg(feature = "unstable_mcp_over_acp")]
1214    mcp_connect: MCP_CONNECT_METHOD_NAME,
1215    #[cfg(feature = "unstable_mcp_over_acp")]
1216    mcp_message: MCP_MESSAGE_METHOD_NAME,
1217    #[cfg(feature = "unstable_mcp_over_acp")]
1218    mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
1219    #[cfg(feature = "unstable_elicitation")]
1220    elicitation_create: ELICITATION_CREATE_METHOD_NAME,
1221    #[cfg(feature = "unstable_elicitation")]
1222    elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
1223};
1224
1225/// Notification name for session updates.
1226pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
1227/// Method name for requesting user permission.
1228pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
1229/// Method name for elicitation.
1230#[cfg(feature = "unstable_elicitation")]
1231pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
1232/// Notification name for elicitation completion.
1233#[cfg(feature = "unstable_elicitation")]
1234pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
1235
1236/// All possible requests that an agent can send to a client.
1237///
1238/// This enum is used internally for routing RPC requests. You typically won't need
1239/// to use this directly.
1240///
1241/// This enum encompasses all method calls from agent to client.
1242#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1243#[serde(untagged)]
1244#[schemars(inline)]
1245#[non_exhaustive]
1246pub enum AgentRequest {
1247    /// Requests permission from the user for a tool call operation.
1248    ///
1249    /// Called by the agent when it needs user authorization before executing
1250    /// a potentially sensitive operation. The client should present the options
1251    /// to the user and return their decision.
1252    ///
1253    /// If the client cancels the prompt turn via `session/cancel`, it MUST
1254    /// respond to this request with `RequestPermissionOutcome::Cancelled`.
1255    ///
1256    /// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
1257    RequestPermissionRequest(RequestPermissionRequest),
1258    /// **UNSTABLE**
1259    ///
1260    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1261    ///
1262    /// Requests structured user input via a form or URL.
1263    #[cfg(feature = "unstable_elicitation")]
1264    CreateElicitationRequest(CreateElicitationRequest),
1265    /// **UNSTABLE**
1266    ///
1267    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1268    ///
1269    /// Opens an MCP-over-ACP connection.
1270    #[cfg(feature = "unstable_mcp_over_acp")]
1271    ConnectMcpRequest(ConnectMcpRequest),
1272    /// **UNSTABLE**
1273    ///
1274    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1275    ///
1276    /// Exchanges an MCP-over-ACP message.
1277    #[cfg(feature = "unstable_mcp_over_acp")]
1278    MessageMcpRequest(MessageMcpRequest),
1279    /// **UNSTABLE**
1280    ///
1281    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1282    ///
1283    /// Closes an MCP-over-ACP connection.
1284    #[cfg(feature = "unstable_mcp_over_acp")]
1285    DisconnectMcpRequest(DisconnectMcpRequest),
1286    /// Handles extension method requests from the agent.
1287    ///
1288    /// Allows the Agent to send an arbitrary request that is not part of the ACP spec.
1289    /// Extension methods provide a way to add custom functionality while maintaining
1290    /// protocol compatibility.
1291    ///
1292    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1293    ExtMethodRequest(ExtRequest),
1294}
1295
1296impl AgentRequest {
1297    /// Returns the corresponding method name of the request.
1298    #[must_use]
1299    pub fn method(&self) -> &str {
1300        match self {
1301            Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
1302            #[cfg(feature = "unstable_elicitation")]
1303            Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
1304            #[cfg(feature = "unstable_mcp_over_acp")]
1305            Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
1306            #[cfg(feature = "unstable_mcp_over_acp")]
1307            Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
1308            #[cfg(feature = "unstable_mcp_over_acp")]
1309            Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
1310            Self::ExtMethodRequest(ext_request) => &ext_request.method,
1311        }
1312    }
1313}
1314
1315/// All possible responses that a client can send to an agent.
1316///
1317/// This enum is used internally for routing RPC responses. You typically won't need
1318/// to use this directly - the responses are handled automatically by the connection.
1319///
1320/// These are responses to the corresponding `AgentRequest` variants.
1321#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1322#[serde(untagged)]
1323#[schemars(inline)]
1324#[non_exhaustive]
1325pub enum ClientResponse {
1326    RequestPermissionResponse(RequestPermissionResponse),
1327    #[cfg(feature = "unstable_elicitation")]
1328    CreateElicitationResponse(CreateElicitationResponse),
1329    #[cfg(feature = "unstable_mcp_over_acp")]
1330    ConnectMcpResponse(ConnectMcpResponse),
1331    #[cfg(feature = "unstable_mcp_over_acp")]
1332    DisconnectMcpResponse(#[serde(default)] DisconnectMcpResponse),
1333    ExtMethodResponse(ExtResponse),
1334    #[cfg(feature = "unstable_mcp_over_acp")]
1335    MessageMcpResponse(MessageMcpResponse),
1336}
1337
1338/// All possible notifications that an agent can send to a client.
1339///
1340/// This enum is used internally for routing RPC notifications. You typically won't need
1341/// to use this directly.
1342///
1343/// Notifications do not expect a response.
1344#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1345#[serde(untagged)]
1346#[expect(clippy::large_enum_variant)]
1347#[schemars(inline)]
1348#[non_exhaustive]
1349pub enum AgentNotification {
1350    /// Handles session update notifications from the agent.
1351    ///
1352    /// This is a notification endpoint (no response expected) that receives
1353    /// real-time updates about session progress, including message chunks,
1354    /// tool calls, and execution plans.
1355    ///
1356    /// Note: Clients SHOULD continue accepting tool call updates even after
1357    /// sending a `session/cancel` notification, as the agent may send final
1358    /// updates before responding with the cancelled stop reason.
1359    ///
1360    /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
1361    SessionNotification(SessionNotification),
1362    /// **UNSTABLE**
1363    ///
1364    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1365    ///
1366    /// Notification that a URL-based elicitation has completed.
1367    #[cfg(feature = "unstable_elicitation")]
1368    CompleteElicitationNotification(CompleteElicitationNotification),
1369    /// **UNSTABLE**
1370    ///
1371    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1372    ///
1373    /// Receives an MCP-over-ACP notification.
1374    #[cfg(feature = "unstable_mcp_over_acp")]
1375    MessageMcpNotification(MessageMcpNotification),
1376    /// Handles extension notifications from the agent.
1377    ///
1378    /// Allows the Agent to send an arbitrary notification that is not part of the ACP spec.
1379    /// Extension notifications provide a way to send one-way messages for custom functionality
1380    /// while maintaining protocol compatibility.
1381    ///
1382    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1383    ExtNotification(ExtNotification),
1384}
1385
1386impl AgentNotification {
1387    /// Returns the corresponding method name of the notification.
1388    #[must_use]
1389    pub fn method(&self) -> &str {
1390        match self {
1391            Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
1392            #[cfg(feature = "unstable_elicitation")]
1393            Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
1394            #[cfg(feature = "unstable_mcp_over_acp")]
1395            Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
1396            Self::ExtNotification(ext_notification) => &ext_notification.method,
1397        }
1398    }
1399}
1400
1401#[cfg(test)]
1402mod tests {
1403    use super::*;
1404
1405    #[test]
1406    fn test_serialization_behavior() {
1407        use serde_json::json;
1408
1409        assert_eq!(
1410            serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
1411            SessionInfoUpdate {
1412                title: MaybeUndefined::Undefined,
1413                updated_at: MaybeUndefined::Undefined,
1414                meta: None
1415            }
1416        );
1417        assert_eq!(
1418            serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
1419                .unwrap(),
1420            SessionInfoUpdate {
1421                title: MaybeUndefined::Null,
1422                updated_at: MaybeUndefined::Null,
1423                meta: None
1424            }
1425        );
1426        assert_eq!(
1427            serde_json::from_value::<SessionInfoUpdate>(
1428                json!({"title": "title", "updatedAt": "timestamp"})
1429            )
1430            .unwrap(),
1431            SessionInfoUpdate {
1432                title: MaybeUndefined::Value("title".to_string()),
1433                updated_at: MaybeUndefined::Value("timestamp".to_string()),
1434                meta: None
1435            }
1436        );
1437
1438        assert_eq!(
1439            serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
1440            json!({})
1441        );
1442        assert_eq!(
1443            serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
1444            json!({"title": "title"})
1445        );
1446        assert_eq!(
1447            serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
1448            json!({"title": null})
1449        );
1450        assert_eq!(
1451            serde_json::to_value(
1452                SessionInfoUpdate::new()
1453                    .title("title")
1454                    .title(MaybeUndefined::Undefined)
1455            )
1456            .unwrap(),
1457            json!({})
1458        );
1459    }
1460
1461    #[test]
1462    fn test_content_chunk_message_id_serialization() {
1463        use serde_json::json;
1464
1465        assert_eq!(
1466            serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
1467                ContentBlock::Text(crate::v2::TextContent::new("Hello")),
1468                "msg_agent_c42b9",
1469            )))
1470            .unwrap(),
1471            json!({
1472                "sessionUpdate": "agent_message_chunk",
1473                "messageId": "msg_agent_c42b9",
1474                "content": {
1475                    "type": "text",
1476                    "text": "Hello"
1477                }
1478            })
1479        );
1480
1481        let err = serde_json::from_value::<ContentChunk>(json!({
1482            "content": {
1483                "type": "text",
1484                "text": "Hello"
1485            }
1486        }))
1487        .unwrap_err();
1488
1489        assert!(err.to_string().contains("messageId"), "{err}");
1490    }
1491
1492    #[test]
1493    fn test_usage_update_serialization() {
1494        use serde_json::json;
1495
1496        assert_eq!(
1497            serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
1498                53_000, 200_000
1499            )))
1500            .unwrap(),
1501            json!({
1502                "sessionUpdate": "usage_update",
1503                "used": 53000,
1504                "size": 200000
1505            })
1506        );
1507
1508        assert_eq!(
1509            serde_json::to_value(SessionUpdate::UsageUpdate(
1510                UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
1511            ))
1512            .unwrap(),
1513            json!({
1514                "sessionUpdate": "usage_update",
1515                "used": 53000,
1516                "size": 200000,
1517                "cost": {
1518                    "amount": 0.045,
1519                    "currency": "USD"
1520                }
1521            })
1522        );
1523
1524        let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
1525            "sessionUpdate": "usage_update",
1526            "used": 53000,
1527            "size": 200000,
1528            "cost": null
1529        }))
1530        .unwrap() else {
1531            panic!("expected usage update");
1532        };
1533
1534        assert_eq!(update.cost, None);
1535    }
1536
1537    #[test]
1538    fn session_update_preserves_unknown_variant() {
1539        use serde_json::json;
1540
1541        let update: SessionUpdate = serde_json::from_value(json!({
1542            "sessionUpdate": "_status_badge",
1543            "label": "Indexing",
1544            "progress": 0.5
1545        }))
1546        .unwrap();
1547
1548        let SessionUpdate::Other(unknown) = update else {
1549            panic!("expected unknown session update");
1550        };
1551
1552        assert_eq!(unknown.session_update, "_status_badge");
1553        assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
1554        assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
1555
1556        assert_eq!(
1557            serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
1558            json!({
1559                "sessionUpdate": "_status_badge",
1560                "label": "Indexing",
1561                "progress": 0.5
1562            })
1563        );
1564    }
1565
1566    #[test]
1567    fn test_plan_update_serialization() {
1568        use serde_json::json;
1569
1570        let plan_update =
1571            SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
1572                "plan-1",
1573                vec![crate::v2::PlanEntry::new(
1574                    "Step 1",
1575                    crate::v2::PlanEntryPriority::High,
1576                    crate::v2::PlanEntryStatus::Pending,
1577                )],
1578            )));
1579
1580        assert_eq!(
1581            serde_json::to_value(plan_update).unwrap(),
1582            json!({
1583                "sessionUpdate": "plan_update",
1584                "plan": {
1585                    "type": "items",
1586                    "id": "plan-1",
1587                    "entries": [
1588                        {
1589                            "content": "Step 1",
1590                            "priority": "high",
1591                            "status": "pending"
1592                        }
1593                    ]
1594                }
1595            })
1596        );
1597    }
1598
1599    #[cfg(feature = "unstable_plan_operations")]
1600    #[test]
1601    fn test_plan_removed_serialization() {
1602        use serde_json::json;
1603
1604        assert_eq!(
1605            serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
1606            json!({
1607                "sessionUpdate": "plan_removed",
1608                "id": "plan-1"
1609            })
1610        );
1611    }
1612
1613    #[test]
1614    fn available_command_input_preserves_unknown_typed_variant() {
1615        use serde_json::json;
1616
1617        let input: AvailableCommandInput = serde_json::from_value(json!({
1618            "type": "_choices",
1619            "hint": "Pick one",
1620            "options": ["fast", "careful"]
1621        }))
1622        .unwrap();
1623
1624        let AvailableCommandInput::Other(unknown) = input else {
1625            panic!("expected unknown command input");
1626        };
1627
1628        assert_eq!(unknown.type_, "_choices");
1629        assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
1630        assert_eq!(
1631            unknown.fields.get("options"),
1632            Some(&json!(["fast", "careful"]))
1633        );
1634        assert_eq!(
1635            serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
1636            json!({
1637                "type": "_choices",
1638                "hint": "Pick one",
1639                "options": ["fast", "careful"]
1640            })
1641        );
1642    }
1643
1644    #[test]
1645    fn available_command_input_unknown_does_not_hide_malformed_unstructured_variant() {
1646        use serde_json::json;
1647
1648        assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
1649        assert!(
1650            serde_json::from_value::<AvailableCommandInput>(json!({
1651                "type": 1,
1652                "hint": "Pick one"
1653            }))
1654            .is_err()
1655        );
1656    }
1657
1658    #[cfg(feature = "unstable_nes")]
1659    #[test]
1660    fn test_client_capabilities_position_encodings_serialization() {
1661        use serde_json::json;
1662
1663        let capabilities = ClientCapabilities::new().position_encodings(vec![
1664            PositionEncodingKind::Utf32,
1665            PositionEncodingKind::Utf16,
1666        ]);
1667        let json = serde_json::to_value(&capabilities).unwrap();
1668
1669        assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
1670    }
1671
1672    #[cfg(feature = "unstable_mcp_over_acp")]
1673    #[test]
1674    fn test_agent_mcp_request_method_names() {
1675        use serde_json::json;
1676
1677        let params: serde_json::Map<String, serde_json::Value> =
1678            [("cursor".to_string(), json!("abc"))].into_iter().collect();
1679
1680        assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
1681        assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
1682        assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
1683
1684        assert_eq!(
1685            AgentRequest::ConnectMcpRequest(ConnectMcpRequest::new("server-1")).method(),
1686            "mcp/connect"
1687        );
1688        assert_eq!(
1689            AgentRequest::MessageMcpRequest(MessageMcpRequest::new("conn-1", "tools/list"))
1690                .method(),
1691            "mcp/message"
1692        );
1693        assert_eq!(
1694            AgentRequest::DisconnectMcpRequest(DisconnectMcpRequest::new("conn-1")).method(),
1695            "mcp/disconnect"
1696        );
1697        assert_eq!(
1698            AgentNotification::MessageMcpNotification(MessageMcpNotification::new(
1699                "conn-1",
1700                "notifications/progress"
1701            ))
1702            .method(),
1703            "mcp/message"
1704        );
1705
1706        assert_eq!(
1707            serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
1708            json!({ "acpId": "server-1" })
1709        );
1710        assert_eq!(
1711            serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
1712            json!({ "connectionId": "conn-1" })
1713        );
1714        assert_eq!(
1715            serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
1716                .unwrap(),
1717            json!({
1718                "connectionId": "conn-1",
1719                "method": "tools/list",
1720                "params": { "cursor": "abc" }
1721            })
1722        );
1723        assert_eq!(
1724            serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
1725            json!({ "connectionId": "conn-1" })
1726        );
1727        assert_eq!(
1728            serde_json::to_value(MessageMcpNotification::new(
1729                "conn-1",
1730                "notifications/progress"
1731            ))
1732            .unwrap(),
1733            json!({
1734                "connectionId": "conn-1",
1735                "method": "notifications/progress"
1736            })
1737        );
1738
1739        let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
1740            "connectionId": "conn-1",
1741            "method": "tools/list",
1742            "params": null
1743        }))
1744        .unwrap();
1745        assert_eq!(request_with_null_params.params, None);
1746    }
1747
1748    #[cfg(feature = "unstable_auth_methods")]
1749    #[test]
1750    fn test_auth_capabilities_serialize_terminal_support_as_object() {
1751        use serde_json::json;
1752
1753        let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
1754
1755        assert_eq!(
1756            serde_json::to_value(&capabilities).unwrap(),
1757            json!({
1758                "terminal": {}
1759            })
1760        );
1761
1762        let deserialized: AuthCapabilities = serde_json::from_value(json!({
1763            "terminal": false
1764        }))
1765        .unwrap();
1766        assert!(deserialized.terminal.is_none());
1767    }
1768}