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, ToolCallContentChunk, 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 user message has been created or updated.
97    ///
98    /// Agents can send this when they accept or replay a user message. When a
99    /// client receives another `user_message` update with the same `messageId`,
100    /// fields in the new update patch the previous fields for that message.
101    UserMessage(UserMessage),
102    /// A chunk of the agent's response being streamed.
103    AgentMessageChunk(ContentChunk),
104    /// An agent message has been created or updated.
105    ///
106    /// Agents can send this in addition to streamed chunks. When a client
107    /// receives another `agent_message` update with the same `messageId`,
108    /// fields in the new update patch the previous fields for that message.
109    AgentMessage(AgentMessage),
110    /// A chunk of the agent's internal reasoning being streamed.
111    AgentThoughtChunk(ContentChunk),
112    /// An agent thought or reasoning message has been created or updated.
113    ///
114    /// Agents can send this in addition to streamed chunks. When a client
115    /// receives another `agent_thought` update with the same `messageId`,
116    /// fields in the new update patch the previous fields for that message.
117    AgentThought(AgentThought),
118    /// A chunk of tool-call content being streamed.
119    ToolCallContentChunk(ToolCallContentChunk),
120    /// A tool call has been created or updated.
121    ToolCallUpdate(ToolCallUpdate),
122    /// A content update for a plan identified by ID.
123    /// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
124    PlanUpdate(PlanUpdate),
125    /// **UNSTABLE**
126    ///
127    /// This capability is not part of the spec yet, and may be removed or changed at any point.
128    ///
129    /// Removal notice for a plan identified by ID.
130    #[cfg(feature = "unstable_plan_operations")]
131    PlanRemoved(PlanRemoved),
132    /// Available commands are ready or have changed
133    AvailableCommandsUpdate(AvailableCommandsUpdate),
134    /// Session configuration options have been updated.
135    ConfigOptionUpdate(ConfigOptionUpdate),
136    /// Session metadata has been updated (title, timestamps, custom metadata)
137    SessionInfoUpdate(SessionInfoUpdate),
138    /// Context window and cost update for the session.
139    UsageUpdate(UsageUpdate),
140    /// Custom or future session update.
141    ///
142    /// Values beginning with `_` are reserved for implementation-specific
143    /// extensions. Unknown values that do not begin with `_` are reserved for
144    /// future ACP variants.
145    ///
146    /// Receivers that do not understand this update type should preserve the
147    /// raw payload when storing, replaying, proxying, or forwarding session
148    /// history, and otherwise ignore it or display it generically.
149    #[serde(untagged)]
150    Other(OtherSessionUpdate),
151}
152
153/// Custom or future session update payload.
154///
155/// This preserves the unknown `sessionUpdate` discriminator and the rest of the
156/// update object for clients that store, replay, proxy, or forward session
157/// history.
158#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
159#[schemars(inline)]
160#[schemars(transform = other_session_update_schema)]
161#[serde(rename_all = "camelCase")]
162#[non_exhaustive]
163pub struct OtherSessionUpdate {
164    /// Custom or future session update type.
165    ///
166    /// Values beginning with `_` are reserved for implementation-specific
167    /// extensions. Unknown values that do not begin with `_` are reserved for
168    /// future ACP variants.
169    #[serde(rename = "sessionUpdate")]
170    pub session_update: String,
171    /// Additional fields from the unknown update payload.
172    #[serde(flatten)]
173    pub fields: BTreeMap<String, serde_json::Value>,
174}
175
176impl OtherSessionUpdate {
177    #[must_use]
178    pub fn new(
179        session_update: impl Into<String>,
180        mut fields: BTreeMap<String, serde_json::Value>,
181    ) -> Self {
182        fields.remove("sessionUpdate");
183        Self {
184            session_update: session_update.into(),
185            fields,
186        }
187    }
188}
189
190impl<'de> Deserialize<'de> for OtherSessionUpdate {
191    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192    where
193        D: serde::Deserializer<'de>,
194    {
195        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
196        let session_update = fields
197            .remove("sessionUpdate")
198            .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
199        let serde_json::Value::String(session_update) = session_update else {
200            return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
201        };
202
203        if is_known_session_update(&session_update) {
204            return Err(serde::de::Error::custom(format!(
205                "known session update `{session_update}` did not match its schema"
206            )));
207        }
208
209        Ok(Self {
210            session_update,
211            fields,
212        })
213    }
214}
215
216fn is_known_session_update(session_update: &str) -> bool {
217    matches!(
218        session_update,
219        "user_message_chunk"
220            | "user_message"
221            | "agent_message_chunk"
222            | "agent_message"
223            | "agent_thought_chunk"
224            | "agent_thought"
225            | "tool_call_content_chunk"
226            | "tool_call_update"
227            | "plan_update"
228            | "available_commands_update"
229            | "config_option_update"
230            | "session_info_update"
231            | "usage_update"
232    )
233}
234
235fn other_session_update_schema(schema: &mut Schema) {
236    super::schema_util::reject_known_string_discriminators(
237        schema,
238        "sessionUpdate",
239        &[
240            "user_message_chunk",
241            "user_message",
242            "agent_message_chunk",
243            "agent_message",
244            "agent_thought_chunk",
245            "agent_thought",
246            "tool_call_content_chunk",
247            "tool_call_update",
248            "plan_update",
249            "available_commands_update",
250            "config_option_update",
251            "session_info_update",
252            #[cfg(feature = "unstable_plan_operations")]
253            "plan_removed",
254            "usage_update",
255        ],
256    );
257}
258
259/// Session configuration options have been updated.
260#[serde_as]
261#[skip_serializing_none]
262#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
263#[serde(rename_all = "camelCase")]
264#[non_exhaustive]
265pub struct ConfigOptionUpdate {
266    /// The full set of configuration options and their current values.
267    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
268    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
269    pub config_options: Vec<SessionConfigOption>,
270    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
271    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
272    /// these keys.
273    ///
274    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
275    #[serde(rename = "_meta")]
276    pub meta: Option<Meta>,
277}
278
279impl ConfigOptionUpdate {
280    #[must_use]
281    pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
282        Self {
283            config_options,
284            meta: None,
285        }
286    }
287
288    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
289    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
290    /// these keys.
291    ///
292    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
293    #[must_use]
294    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
295        self.meta = meta.into_option();
296        self
297    }
298}
299
300/// Update to session metadata. All fields are optional to support partial updates.
301///
302/// Agents send this notification to update session information like title or custom metadata.
303/// This allows clients to display dynamic session names and track session state changes.
304#[skip_serializing_none]
305#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
306#[serde(rename_all = "camelCase")]
307#[non_exhaustive]
308pub struct SessionInfoUpdate {
309    /// Human-readable title for the session. Set to null to clear.
310    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
311    pub title: MaybeUndefined<String>,
312    /// ISO 8601 timestamp of last activity. Set to null to clear.
313    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
314    pub updated_at: MaybeUndefined<String>,
315    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
316    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
317    /// these keys.
318    ///
319    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
320    #[serde(rename = "_meta")]
321    pub meta: Option<Meta>,
322}
323
324impl SessionInfoUpdate {
325    #[must_use]
326    pub fn new() -> Self {
327        Self::default()
328    }
329
330    /// Human-readable title for the session. Set to null to clear.
331    #[must_use]
332    pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
333        self.title = title.into_maybe_undefined();
334        self
335    }
336
337    /// ISO 8601 timestamp of last activity. Set to null to clear.
338    #[must_use]
339    pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
340        self.updated_at = updated_at.into_maybe_undefined();
341        self
342    }
343
344    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
345    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
346    /// these keys.
347    ///
348    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
349    #[must_use]
350    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
351        self.meta = meta.into_option();
352        self
353    }
354}
355
356/// Context window and cost update for a session.
357#[serde_as]
358#[skip_serializing_none]
359#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
360#[serde(rename_all = "camelCase")]
361#[non_exhaustive]
362pub struct UsageUpdate {
363    /// Tokens currently in context.
364    pub used: u64,
365    /// Total context window size in tokens.
366    pub size: u64,
367    /// Cumulative session cost (optional).
368    #[serde_as(deserialize_as = "DefaultOnError")]
369    #[schemars(extend("x-deserialize-default-on-error" = true))]
370    #[serde(default)]
371    pub cost: Option<Cost>,
372    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
373    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
374    /// these keys.
375    ///
376    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
377    #[serde(rename = "_meta")]
378    pub meta: Option<Meta>,
379}
380
381impl UsageUpdate {
382    #[must_use]
383    pub fn new(used: u64, size: u64) -> Self {
384        Self {
385            used,
386            size,
387            cost: None,
388            meta: None,
389        }
390    }
391
392    /// Cumulative session cost (optional).
393    #[must_use]
394    pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
395        self.cost = cost.into_option();
396        self
397    }
398
399    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
400    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
401    /// these keys.
402    ///
403    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
404    #[must_use]
405    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
406        self.meta = meta.into_option();
407        self
408    }
409}
410
411/// Cost information for a session.
412#[skip_serializing_none]
413#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
414#[serde(rename_all = "camelCase")]
415#[non_exhaustive]
416pub struct Cost {
417    /// Total cumulative cost for session.
418    pub amount: f64,
419    /// ISO 4217 currency code (e.g., "USD", "EUR").
420    pub currency: String,
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 Cost {
431    #[must_use]
432    pub fn new(amount: f64, currency: impl Into<String>) -> Self {
433        Self {
434            amount,
435            currency: currency.into(),
436            meta: None,
437        }
438    }
439
440    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
441    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
442    /// these keys.
443    ///
444    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
445    #[must_use]
446    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
447        self.meta = meta.into_option();
448        self
449    }
450}
451
452/// A streamed item of content
453#[skip_serializing_none]
454#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
455#[serde(rename_all = "camelCase")]
456#[non_exhaustive]
457pub struct ContentChunk {
458    /// A single item of content
459    pub content: ContentBlock,
460    /// A unique identifier for the message this chunk belongs to.
461    ///
462    /// All chunks belonging to the same message share the same `messageId`.
463    /// A change in `messageId` indicates a new message has started.
464    pub message_id: MessageId,
465    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
466    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
467    /// these keys.
468    ///
469    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
470    #[serde(rename = "_meta")]
471    pub meta: Option<Meta>,
472}
473
474impl ContentChunk {
475    #[must_use]
476    pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
477        Self {
478            content,
479            message_id: message_id.into(),
480            meta: None,
481        }
482    }
483
484    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
485    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
486    /// these keys.
487    ///
488    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
489    #[must_use]
490    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
491        self.meta = meta.into_option();
492        self
493    }
494}
495
496/// A user message upsert.
497///
498/// Only [`UserMessage::message_id`] is required. Other fields have patch
499/// semantics: omitted fields leave the existing message value unchanged, `null`
500/// clears or unsets the value, and concrete values replace the previous value.
501/// For a new `messageId`, omitted fields use client defaults. `content` is
502/// replaced as a whole array; send `[]` or `null` to clear it.
503///
504/// Message updates and chunks are applied in the order they are received. When
505/// a `user_message` update includes `content`, that array replaces any content
506/// previously accumulated for the message, including content from earlier
507/// chunks. Later chunks with the same `messageId` append to the current
508/// content.
509#[serde_as]
510#[skip_serializing_none]
511#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
512#[serde(rename_all = "camelCase")]
513#[non_exhaustive]
514pub struct UserMessage {
515    /// A unique identifier for the message.
516    pub message_id: MessageId,
517    /// Complete replacement content for this message.
518    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
519    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
520    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
521    pub content: MaybeUndefined<Vec<ContentBlock>>,
522    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
523    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
524    /// these keys.
525    ///
526    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
527    #[serde(
528        rename = "_meta",
529        default,
530        skip_serializing_if = "MaybeUndefined::is_undefined"
531    )]
532    pub meta: MaybeUndefined<Meta>,
533}
534
535impl UserMessage {
536    #[must_use]
537    pub fn new(message_id: impl Into<MessageId>) -> Self {
538        Self {
539            message_id: message_id.into(),
540            content: MaybeUndefined::Undefined,
541            meta: MaybeUndefined::Undefined,
542        }
543    }
544
545    /// Complete replacement content for this message.
546    #[must_use]
547    pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
548        self.content = content.into_maybe_undefined();
549        self
550    }
551
552    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
553    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
554    /// these keys.
555    ///
556    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
557    #[must_use]
558    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
559        self.meta = meta.into_maybe_undefined();
560        self
561    }
562}
563
564/// An agent message upsert.
565///
566/// Only [`AgentMessage::message_id`] is required. Other fields have patch
567/// semantics: omitted fields leave the existing message value unchanged, `null`
568/// clears or unsets the value, and concrete values replace the previous value.
569/// For a new `messageId`, omitted fields use client defaults. `content` is
570/// replaced as a whole array; send `[]` or `null` to clear it.
571///
572/// Message updates and chunks are applied in the order they are received. When
573/// an `agent_message` update includes `content`, that array replaces any
574/// content previously accumulated for the message, including content from
575/// earlier chunks. Later chunks with the same `messageId` append to the current
576/// content.
577#[serde_as]
578#[skip_serializing_none]
579#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
580#[serde(rename_all = "camelCase")]
581#[non_exhaustive]
582pub struct AgentMessage {
583    /// A unique identifier for the message.
584    pub message_id: MessageId,
585    /// Complete replacement content for this message.
586    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
587    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
588    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
589    pub content: MaybeUndefined<Vec<ContentBlock>>,
590    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
591    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
592    /// these keys.
593    ///
594    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
595    #[serde(
596        rename = "_meta",
597        default,
598        skip_serializing_if = "MaybeUndefined::is_undefined"
599    )]
600    pub meta: MaybeUndefined<Meta>,
601}
602
603impl AgentMessage {
604    #[must_use]
605    pub fn new(message_id: impl Into<MessageId>) -> Self {
606        Self {
607            message_id: message_id.into(),
608            content: MaybeUndefined::Undefined,
609            meta: MaybeUndefined::Undefined,
610        }
611    }
612
613    /// Complete replacement content for this message.
614    #[must_use]
615    pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
616        self.content = content.into_maybe_undefined();
617        self
618    }
619
620    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
621    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
622    /// these keys.
623    ///
624    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
625    #[must_use]
626    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
627        self.meta = meta.into_maybe_undefined();
628        self
629    }
630}
631
632/// An agent thought or reasoning message upsert.
633///
634/// Only [`AgentThought::message_id`] is required. Other fields have patch
635/// semantics: omitted fields leave the existing thought value unchanged, `null`
636/// clears or unsets the value, and concrete values replace the previous value.
637/// For a new `messageId`, omitted fields use client defaults. `content` is
638/// replaced as a whole array; send `[]` or `null` to clear it.
639///
640/// Message updates and chunks are applied in the order they are received. When
641/// an `agent_thought` update includes `content`, that array replaces any
642/// content previously accumulated for the thought, including content from
643/// earlier chunks. Later chunks with the same `messageId` append to the current
644/// content.
645#[serde_as]
646#[skip_serializing_none]
647#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
648#[serde(rename_all = "camelCase")]
649#[non_exhaustive]
650pub struct AgentThought {
651    /// A unique identifier for the thought message.
652    pub message_id: MessageId,
653    /// Complete replacement content for this thought message.
654    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
655    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
656    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
657    pub content: MaybeUndefined<Vec<ContentBlock>>,
658    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
659    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
660    /// these keys.
661    ///
662    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
663    #[serde(
664        rename = "_meta",
665        default,
666        skip_serializing_if = "MaybeUndefined::is_undefined"
667    )]
668    pub meta: MaybeUndefined<Meta>,
669}
670
671impl AgentThought {
672    #[must_use]
673    pub fn new(message_id: impl Into<MessageId>) -> Self {
674        Self {
675            message_id: message_id.into(),
676            content: MaybeUndefined::Undefined,
677            meta: MaybeUndefined::Undefined,
678        }
679    }
680
681    /// Complete replacement content for this thought message.
682    #[must_use]
683    pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
684        self.content = content.into_maybe_undefined();
685        self
686    }
687
688    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
689    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
690    /// these keys.
691    ///
692    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
693    #[must_use]
694    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
695        self.meta = meta.into_maybe_undefined();
696        self
697    }
698}
699
700/// Unique identifier for a message within a session.
701#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
702#[serde(transparent)]
703#[from(Arc<str>, String, &'static str)]
704#[non_exhaustive]
705pub struct MessageId(pub Arc<str>);
706
707impl MessageId {
708    #[must_use]
709    pub fn new(id: impl Into<Arc<str>>) -> Self {
710        Self(id.into())
711    }
712}
713
714/// Available commands are ready or have changed
715#[serde_as]
716#[skip_serializing_none]
717#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
718#[serde(rename_all = "camelCase")]
719#[non_exhaustive]
720pub struct AvailableCommandsUpdate {
721    /// Commands the agent can execute
722    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
723    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
724    pub available_commands: Vec<AvailableCommand>,
725    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
726    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
727    /// these keys.
728    ///
729    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
730    #[serde(rename = "_meta")]
731    pub meta: Option<Meta>,
732}
733
734impl AvailableCommandsUpdate {
735    #[must_use]
736    pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
737        Self {
738            available_commands,
739            meta: None,
740        }
741    }
742
743    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
744    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
745    /// these keys.
746    ///
747    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
748    #[must_use]
749    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
750        self.meta = meta.into_option();
751        self
752    }
753}
754
755/// Information about a command.
756#[serde_as]
757#[skip_serializing_none]
758#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
759#[serde(rename_all = "camelCase")]
760#[non_exhaustive]
761pub struct AvailableCommand {
762    /// Command name (e.g., `create_plan`, `research_codebase`).
763    pub name: String,
764    /// Human-readable description of what the command does.
765    pub description: String,
766    /// Input for the command if required
767    #[serde_as(deserialize_as = "DefaultOnError")]
768    #[schemars(extend("x-deserialize-default-on-error" = true))]
769    #[serde(default)]
770    pub input: Option<AvailableCommandInput>,
771    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
772    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
773    /// these keys.
774    ///
775    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
776    #[serde(rename = "_meta")]
777    pub meta: Option<Meta>,
778}
779
780impl AvailableCommand {
781    #[must_use]
782    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
783        Self {
784            name: name.into(),
785            description: description.into(),
786            input: None,
787            meta: None,
788        }
789    }
790
791    /// Input for the command if required
792    #[must_use]
793    pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
794        self.input = input.into_option();
795        self
796    }
797
798    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
799    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
800    /// these keys.
801    ///
802    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
803    #[must_use]
804    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
805        self.meta = meta.into_option();
806        self
807    }
808}
809
810/// The input specification for a command.
811#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
812#[serde(untagged, rename_all = "camelCase")]
813#[non_exhaustive]
814pub enum AvailableCommandInput {
815    /// All text that was typed after the command name is provided as input.
816    Unstructured(UnstructuredCommandInput),
817    /// Custom or future command input specification.
818    ///
819    /// Values beginning with `_` are reserved for implementation-specific
820    /// extensions. Unknown values that do not begin with `_` are reserved for
821    /// future ACP variants.
822    ///
823    /// Clients that do not understand this input type should preserve the raw
824    /// payload when storing, replaying, proxying, or forwarding command
825    /// metadata, and otherwise ignore the input specification or display the
826    /// command without structured input.
827    Other(OtherAvailableCommandInput),
828}
829
830/// Custom or future command input specification.
831#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
832#[schemars(inline)]
833#[serde(rename_all = "camelCase")]
834#[non_exhaustive]
835pub struct OtherAvailableCommandInput {
836    /// Custom or future command input type.
837    ///
838    /// Values beginning with `_` are reserved for implementation-specific
839    /// extensions. Unknown values that do not begin with `_` are reserved for
840    /// future ACP variants.
841    #[serde(rename = "type")]
842    pub type_: String,
843    /// Additional fields from the unknown command input payload.
844    #[serde(flatten)]
845    pub fields: BTreeMap<String, serde_json::Value>,
846}
847
848impl OtherAvailableCommandInput {
849    #[must_use]
850    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
851        fields.remove("type");
852        Self {
853            type_: type_.into(),
854            fields,
855        }
856    }
857}
858
859impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
860    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
861    where
862        D: serde::Deserializer<'de>,
863    {
864        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
865        let type_ = fields
866            .remove("type")
867            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
868        let serde_json::Value::String(type_) = type_ else {
869            return Err(serde::de::Error::custom("`type` must be a string"));
870        };
871
872        Ok(Self { type_, fields })
873    }
874}
875
876/// All text that was typed after the command name is provided as input.
877#[skip_serializing_none]
878#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
879#[schemars(transform = unstructured_command_input_schema)]
880#[serde(rename_all = "camelCase")]
881#[non_exhaustive]
882pub struct UnstructuredCommandInput {
883    /// A hint to display when the input hasn't been provided yet
884    pub hint: String,
885    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
886    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
887    /// these keys.
888    ///
889    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
890    #[serde(rename = "_meta")]
891    pub meta: Option<Meta>,
892}
893
894impl UnstructuredCommandInput {
895    #[must_use]
896    pub fn new(hint: impl Into<String>) -> Self {
897        Self {
898            hint: hint.into(),
899            meta: None,
900        }
901    }
902
903    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
904    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
905    /// these keys.
906    ///
907    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
908    #[must_use]
909    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
910        self.meta = meta.into_option();
911        self
912    }
913}
914
915impl<'de> Deserialize<'de> for UnstructuredCommandInput {
916    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
917    where
918        D: serde::Deserializer<'de>,
919    {
920        #[derive(Deserialize)]
921        #[serde(rename_all = "camelCase")]
922        struct RawUnstructuredCommandInput {
923            hint: String,
924            #[serde(rename = "_meta")]
925            meta: Option<Meta>,
926            #[serde(flatten)]
927            fields: BTreeMap<String, serde_json::Value>,
928        }
929
930        let raw = RawUnstructuredCommandInput::deserialize(deserializer)?;
931        if raw.fields.contains_key("type") {
932            return Err(serde::de::Error::custom(
933                "unstructured command input cannot include a `type` field",
934            ));
935        }
936
937        Ok(Self {
938            hint: raw.hint,
939            meta: raw.meta,
940        })
941    }
942}
943
944fn unstructured_command_input_schema(schema: &mut Schema) {
945    super::schema_util::reject_property(schema, "type");
946}
947
948// Permission
949
950/// Request for user permission to execute a tool call.
951///
952/// Sent when the agent needs authorization before performing a sensitive operation.
953///
954/// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
955#[skip_serializing_none]
956#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
957#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
958#[serde(rename_all = "camelCase")]
959#[non_exhaustive]
960pub struct RequestPermissionRequest {
961    /// The session ID for this request.
962    pub session_id: SessionId,
963    /// Details about the tool call requiring permission.
964    pub tool_call: ToolCallUpdate,
965    /// Available permission options for the user to choose from.
966    pub options: Vec<PermissionOption>,
967    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
968    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
969    /// these keys.
970    ///
971    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
972    #[serde(rename = "_meta")]
973    pub meta: Option<Meta>,
974}
975
976impl RequestPermissionRequest {
977    #[must_use]
978    pub fn new(
979        session_id: impl Into<SessionId>,
980        tool_call: ToolCallUpdate,
981        options: Vec<PermissionOption>,
982    ) -> Self {
983        Self {
984            session_id: session_id.into(),
985            tool_call,
986            options,
987            meta: None,
988        }
989    }
990
991    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
992    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
993    /// these keys.
994    ///
995    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
996    #[must_use]
997    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
998        self.meta = meta.into_option();
999        self
1000    }
1001}
1002
1003/// An option presented to the user when requesting permission.
1004#[skip_serializing_none]
1005#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1006#[serde(rename_all = "camelCase")]
1007#[non_exhaustive]
1008pub struct PermissionOption {
1009    /// Unique identifier for this permission option.
1010    pub option_id: PermissionOptionId,
1011    /// Human-readable label to display to the user.
1012    pub name: String,
1013    /// Hint about the nature of this permission option.
1014    pub kind: PermissionOptionKind,
1015    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1016    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1017    /// these keys.
1018    ///
1019    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1020    #[serde(rename = "_meta")]
1021    pub meta: Option<Meta>,
1022}
1023
1024impl PermissionOption {
1025    #[must_use]
1026    pub fn new(
1027        option_id: impl Into<PermissionOptionId>,
1028        name: impl Into<String>,
1029        kind: PermissionOptionKind,
1030    ) -> Self {
1031        Self {
1032            option_id: option_id.into(),
1033            name: name.into(),
1034            kind,
1035            meta: None,
1036        }
1037    }
1038
1039    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1040    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1041    /// these keys.
1042    ///
1043    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1044    #[must_use]
1045    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1046        self.meta = meta.into_option();
1047        self
1048    }
1049}
1050
1051/// Unique identifier for a permission option.
1052#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1053#[serde(transparent)]
1054#[from(Arc<str>, String, &'static str)]
1055#[non_exhaustive]
1056pub struct PermissionOptionId(pub Arc<str>);
1057
1058impl PermissionOptionId {
1059    #[must_use]
1060    pub fn new(id: impl Into<Arc<str>>) -> Self {
1061        Self(id.into())
1062    }
1063}
1064
1065/// The type of permission option being presented to the user.
1066///
1067/// Helps clients choose appropriate icons and UI treatment.
1068#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1069#[serde(rename_all = "snake_case")]
1070#[non_exhaustive]
1071pub enum PermissionOptionKind {
1072    /// Allow this operation only this time.
1073    AllowOnce,
1074    /// Allow this operation and remember the choice.
1075    AllowAlways,
1076    /// Reject this operation only this time.
1077    RejectOnce,
1078    /// Reject this operation and remember the choice.
1079    RejectAlways,
1080    /// Custom or future permission option kind.
1081    ///
1082    /// Values beginning with `_` are reserved for implementation-specific
1083    /// extensions. Unknown values that do not begin with `_` are reserved for
1084    /// future ACP variants.
1085    #[serde(untagged)]
1086    Other(String),
1087}
1088
1089/// Response to a permission request.
1090#[skip_serializing_none]
1091#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1092#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1093#[serde(rename_all = "camelCase")]
1094#[non_exhaustive]
1095pub struct RequestPermissionResponse {
1096    /// The user's decision on the permission request.
1097    // This extra-level is unfortunately needed because the output must be an object
1098    pub outcome: RequestPermissionOutcome,
1099    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1100    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1101    /// these keys.
1102    ///
1103    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1104    #[serde(rename = "_meta")]
1105    pub meta: Option<Meta>,
1106}
1107
1108impl RequestPermissionResponse {
1109    #[must_use]
1110    pub fn new(outcome: RequestPermissionOutcome) -> Self {
1111        Self {
1112            outcome,
1113            meta: None,
1114        }
1115    }
1116
1117    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1118    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1119    /// these keys.
1120    ///
1121    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1122    #[must_use]
1123    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1124        self.meta = meta.into_option();
1125        self
1126    }
1127}
1128
1129/// The outcome of a permission request.
1130#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1131#[serde(tag = "outcome", rename_all = "snake_case")]
1132#[schemars(extend("discriminator" = {"propertyName": "outcome"}))]
1133#[non_exhaustive]
1134pub enum RequestPermissionOutcome {
1135    /// The prompt turn was cancelled before the user responded.
1136    ///
1137    /// When a client sends a `session/cancel` notification to cancel an ongoing
1138    /// prompt turn, it MUST respond to all pending `session/request_permission`
1139    /// requests with this `Cancelled` outcome.
1140    ///
1141    /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation)
1142    Cancelled,
1143    /// The user selected one of the provided options.
1144    #[serde(rename_all = "camelCase")]
1145    Selected(SelectedPermissionOutcome),
1146}
1147
1148/// The user selected one of the provided options.
1149#[skip_serializing_none]
1150#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1151#[serde(rename_all = "camelCase")]
1152#[non_exhaustive]
1153pub struct SelectedPermissionOutcome {
1154    /// The ID of the option the user selected.
1155    pub option_id: PermissionOptionId,
1156    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1157    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1158    /// these keys.
1159    ///
1160    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1161    #[serde(rename = "_meta")]
1162    pub meta: Option<Meta>,
1163}
1164
1165impl SelectedPermissionOutcome {
1166    #[must_use]
1167    pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
1168        Self {
1169            option_id: option_id.into(),
1170            meta: None,
1171        }
1172    }
1173
1174    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1175    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1176    /// these keys.
1177    ///
1178    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1179    #[must_use]
1180    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1181        self.meta = meta.into_option();
1182        self
1183    }
1184}
1185
1186// Capabilities
1187
1188/// Capabilities supported by the client.
1189///
1190/// Advertised during initialization to inform the agent about
1191/// available features and methods.
1192///
1193/// See protocol docs: [Client Capabilities](https://agentclientprotocol.com/protocol/initialization#client-capabilities)
1194#[serde_as]
1195#[skip_serializing_none]
1196#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1197#[serde(rename_all = "camelCase")]
1198#[non_exhaustive]
1199pub struct ClientCapabilities {
1200    /// **UNSTABLE**
1201    ///
1202    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1203    ///
1204    /// Authentication capabilities supported by the client.
1205    /// Determines which authentication method types the agent may include
1206    /// in its `InitializeResponse`.
1207    #[cfg(feature = "unstable_auth_methods")]
1208    #[serde(default)]
1209    pub auth: AuthCapabilities,
1210    /// **UNSTABLE**
1211    ///
1212    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1213    ///
1214    /// Elicitation capabilities supported by the client.
1215    /// Determines which elicitation modes the agent may use.
1216    #[cfg(feature = "unstable_elicitation")]
1217    #[serde_as(deserialize_as = "DefaultOnError")]
1218    #[schemars(extend("x-deserialize-default-on-error" = true))]
1219    #[serde(default)]
1220    pub elicitation: Option<ElicitationCapabilities>,
1221    /// **UNSTABLE**
1222    ///
1223    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1224    ///
1225    /// NES (Next Edit Suggestions) capabilities supported by the client.
1226    #[cfg(feature = "unstable_nes")]
1227    #[serde_as(deserialize_as = "DefaultOnError")]
1228    #[schemars(extend("x-deserialize-default-on-error" = true))]
1229    #[serde(default)]
1230    pub nes: Option<ClientNesCapabilities>,
1231    /// **UNSTABLE**
1232    ///
1233    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1234    ///
1235    /// The position encodings supported by the client, in order of preference.
1236    #[cfg(feature = "unstable_nes")]
1237    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1238    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1239    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1240    pub position_encodings: Vec<PositionEncodingKind>,
1241
1242    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1243    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1244    /// these keys.
1245    ///
1246    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1247    #[serde(rename = "_meta")]
1248    pub meta: Option<Meta>,
1249}
1250
1251impl ClientCapabilities {
1252    #[must_use]
1253    pub fn new() -> Self {
1254        Self::default()
1255    }
1256
1257    /// **UNSTABLE**
1258    ///
1259    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1260    ///
1261    /// Authentication capabilities supported by the client.
1262    /// Determines which authentication method types the agent may include
1263    /// in its `InitializeResponse`.
1264    #[cfg(feature = "unstable_auth_methods")]
1265    #[must_use]
1266    pub fn auth(mut self, auth: AuthCapabilities) -> Self {
1267        self.auth = auth;
1268        self
1269    }
1270
1271    /// **UNSTABLE**
1272    ///
1273    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1274    ///
1275    /// Elicitation capabilities supported by the client.
1276    /// Determines which elicitation modes the agent may use.
1277    #[cfg(feature = "unstable_elicitation")]
1278    #[must_use]
1279    pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1280        self.elicitation = elicitation.into_option();
1281        self
1282    }
1283
1284    /// **UNSTABLE**
1285    ///
1286    /// NES (Next Edit Suggestions) capabilities supported by the client.
1287    #[cfg(feature = "unstable_nes")]
1288    #[must_use]
1289    pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1290        self.nes = nes.into_option();
1291        self
1292    }
1293
1294    /// **UNSTABLE**
1295    ///
1296    /// The position encodings supported by the client, in order of preference.
1297    #[cfg(feature = "unstable_nes")]
1298    #[must_use]
1299    pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1300        self.position_encodings = position_encodings;
1301        self
1302    }
1303
1304    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1305    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1306    /// these keys.
1307    ///
1308    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1309    #[must_use]
1310    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1311        self.meta = meta.into_option();
1312        self
1313    }
1314}
1315
1316/// **UNSTABLE**
1317///
1318/// This capability is not part of the spec yet, and may be removed or changed at any point.
1319///
1320/// Authentication capabilities supported by the client.
1321///
1322/// Advertised during initialization to inform the agent which authentication
1323/// method types the client can handle. This governs opt-in types that require
1324/// additional client-side support.
1325#[cfg(feature = "unstable_auth_methods")]
1326#[serde_as]
1327#[skip_serializing_none]
1328#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1329#[serde(rename_all = "camelCase")]
1330#[non_exhaustive]
1331pub struct AuthCapabilities {
1332    /// Whether the client supports `terminal` authentication methods.
1333    ///
1334    /// Optional. Omitted or `null` both mean the client does not advertise support.
1335    /// Supplying `{}` means the agent may include `terminal` entries in its authentication methods.
1336    #[serde_as(deserialize_as = "DefaultOnError")]
1337    #[schemars(extend("x-deserialize-default-on-error" = true))]
1338    #[serde(default)]
1339    pub terminal: Option<TerminalAuthCapabilities>,
1340    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1341    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1342    /// these keys.
1343    ///
1344    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1345    #[serde(rename = "_meta")]
1346    pub meta: Option<Meta>,
1347}
1348
1349#[cfg(feature = "unstable_auth_methods")]
1350impl AuthCapabilities {
1351    #[must_use]
1352    pub fn new() -> Self {
1353        Self::default()
1354    }
1355
1356    /// Whether the client supports `terminal` authentication methods.
1357    ///
1358    /// Omitted or `null` both mean the client does not advertise support.
1359    /// Supplying `{}` means the agent may include `AuthMethod::Terminal` entries in its authentication methods.
1360    #[must_use]
1361    pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
1362        self.terminal = terminal.into_option();
1363        self
1364    }
1365
1366    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1367    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1368    /// these keys.
1369    ///
1370    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1371    #[must_use]
1372    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1373        self.meta = meta.into_option();
1374        self
1375    }
1376}
1377
1378/// **UNSTABLE**
1379///
1380/// This capability is not part of the spec yet, and may be removed or changed at any point.
1381///
1382/// Capabilities for terminal authentication methods.
1383///
1384/// Supplying `{}` means the client supports terminal authentication methods.
1385#[cfg(feature = "unstable_auth_methods")]
1386#[skip_serializing_none]
1387#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1388#[non_exhaustive]
1389pub struct TerminalAuthCapabilities {
1390    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1391    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1392    /// these keys.
1393    ///
1394    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1395    #[serde(rename = "_meta")]
1396    pub meta: Option<Meta>,
1397}
1398
1399#[cfg(feature = "unstable_auth_methods")]
1400impl TerminalAuthCapabilities {
1401    #[must_use]
1402    pub fn new() -> Self {
1403        Self::default()
1404    }
1405
1406    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1407    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1408    /// these keys.
1409    ///
1410    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1411    #[must_use]
1412    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1413        self.meta = meta.into_option();
1414        self
1415    }
1416}
1417
1418// Method schema
1419
1420/// Names of all methods that clients handle.
1421///
1422/// Provides a centralized definition of method names used in the protocol.
1423#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1424#[non_exhaustive]
1425pub struct ClientMethodNames {
1426    /// Method for requesting permission from the user.
1427    pub session_request_permission: &'static str,
1428    /// Notification for session updates.
1429    pub session_update: &'static str,
1430    /// Method for opening an MCP-over-ACP connection.
1431    #[cfg(feature = "unstable_mcp_over_acp")]
1432    pub mcp_connect: &'static str,
1433    /// Method for exchanging MCP-over-ACP messages.
1434    #[cfg(feature = "unstable_mcp_over_acp")]
1435    pub mcp_message: &'static str,
1436    /// Method for closing an MCP-over-ACP connection.
1437    #[cfg(feature = "unstable_mcp_over_acp")]
1438    pub mcp_disconnect: &'static str,
1439    /// Method for elicitation.
1440    #[cfg(feature = "unstable_elicitation")]
1441    pub elicitation_create: &'static str,
1442    /// Notification for elicitation completion.
1443    #[cfg(feature = "unstable_elicitation")]
1444    pub elicitation_complete: &'static str,
1445}
1446
1447/// Constant containing all client method names.
1448pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
1449    session_update: SESSION_UPDATE_NOTIFICATION,
1450    session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
1451    #[cfg(feature = "unstable_mcp_over_acp")]
1452    mcp_connect: MCP_CONNECT_METHOD_NAME,
1453    #[cfg(feature = "unstable_mcp_over_acp")]
1454    mcp_message: MCP_MESSAGE_METHOD_NAME,
1455    #[cfg(feature = "unstable_mcp_over_acp")]
1456    mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
1457    #[cfg(feature = "unstable_elicitation")]
1458    elicitation_create: ELICITATION_CREATE_METHOD_NAME,
1459    #[cfg(feature = "unstable_elicitation")]
1460    elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
1461};
1462
1463/// Notification name for session updates.
1464pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
1465/// Method name for requesting user permission.
1466pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
1467/// Method name for elicitation.
1468#[cfg(feature = "unstable_elicitation")]
1469pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
1470/// Notification name for elicitation completion.
1471#[cfg(feature = "unstable_elicitation")]
1472pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
1473
1474/// All possible requests that an agent can send to a client.
1475///
1476/// This enum is used internally for routing RPC requests. You typically won't need
1477/// to use this directly.
1478///
1479/// This enum encompasses all method calls from agent to client.
1480#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1481#[serde(untagged)]
1482#[schemars(inline)]
1483#[non_exhaustive]
1484pub enum AgentRequest {
1485    /// Requests permission from the user for a tool call operation.
1486    ///
1487    /// Called by the agent when it needs user authorization before executing
1488    /// a potentially sensitive operation. The client should present the options
1489    /// to the user and return their decision.
1490    ///
1491    /// If the client cancels the prompt turn via `session/cancel`, it MUST
1492    /// respond to this request with `RequestPermissionOutcome::Cancelled`.
1493    ///
1494    /// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
1495    RequestPermissionRequest(Box<RequestPermissionRequest>),
1496    /// **UNSTABLE**
1497    ///
1498    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1499    ///
1500    /// Requests structured user input via a form or URL.
1501    #[cfg(feature = "unstable_elicitation")]
1502    CreateElicitationRequest(CreateElicitationRequest),
1503    /// **UNSTABLE**
1504    ///
1505    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1506    ///
1507    /// Opens an MCP-over-ACP connection.
1508    #[cfg(feature = "unstable_mcp_over_acp")]
1509    ConnectMcpRequest(ConnectMcpRequest),
1510    /// **UNSTABLE**
1511    ///
1512    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1513    ///
1514    /// Exchanges an MCP-over-ACP message.
1515    #[cfg(feature = "unstable_mcp_over_acp")]
1516    MessageMcpRequest(MessageMcpRequest),
1517    /// **UNSTABLE**
1518    ///
1519    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1520    ///
1521    /// Closes an MCP-over-ACP connection.
1522    #[cfg(feature = "unstable_mcp_over_acp")]
1523    DisconnectMcpRequest(DisconnectMcpRequest),
1524    /// Handles extension method requests from the agent.
1525    ///
1526    /// Allows the Agent to send an arbitrary request that is not part of the ACP spec.
1527    /// Extension methods provide a way to add custom functionality while maintaining
1528    /// protocol compatibility.
1529    ///
1530    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1531    ExtMethodRequest(ExtRequest),
1532}
1533
1534impl AgentRequest {
1535    /// Returns the corresponding method name of the request.
1536    #[must_use]
1537    pub fn method(&self) -> &str {
1538        match self {
1539            Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
1540            #[cfg(feature = "unstable_elicitation")]
1541            Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
1542            #[cfg(feature = "unstable_mcp_over_acp")]
1543            Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
1544            #[cfg(feature = "unstable_mcp_over_acp")]
1545            Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
1546            #[cfg(feature = "unstable_mcp_over_acp")]
1547            Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
1548            Self::ExtMethodRequest(ext_request) => &ext_request.method,
1549        }
1550    }
1551}
1552
1553/// All possible responses that a client can send to an agent.
1554///
1555/// This enum is used internally for routing RPC responses. You typically won't need
1556/// to use this directly - the responses are handled automatically by the connection.
1557///
1558/// These are responses to the corresponding `AgentRequest` variants.
1559#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1560#[serde(untagged)]
1561#[schemars(inline)]
1562#[non_exhaustive]
1563pub enum ClientResponse {
1564    RequestPermissionResponse(RequestPermissionResponse),
1565    #[cfg(feature = "unstable_elicitation")]
1566    CreateElicitationResponse(CreateElicitationResponse),
1567    #[cfg(feature = "unstable_mcp_over_acp")]
1568    ConnectMcpResponse(ConnectMcpResponse),
1569    #[cfg(feature = "unstable_mcp_over_acp")]
1570    DisconnectMcpResponse(#[serde(default)] DisconnectMcpResponse),
1571    ExtMethodResponse(ExtResponse),
1572    #[cfg(feature = "unstable_mcp_over_acp")]
1573    MessageMcpResponse(MessageMcpResponse),
1574}
1575
1576/// All possible notifications that an agent can send to a client.
1577///
1578/// This enum is used internally for routing RPC notifications. You typically won't need
1579/// to use this directly.
1580///
1581/// Notifications do not expect a response.
1582#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1583#[serde(untagged)]
1584#[schemars(inline)]
1585#[non_exhaustive]
1586pub enum AgentNotification {
1587    /// Handles session update notifications from the agent.
1588    ///
1589    /// This is a notification endpoint (no response expected) that receives
1590    /// real-time updates about session progress, including message updates,
1591    /// message chunks, tool calls, and execution plans.
1592    ///
1593    /// Note: Clients SHOULD continue accepting tool call updates even after
1594    /// sending a `session/cancel` notification, as the agent may send final
1595    /// updates before responding with the cancelled stop reason.
1596    ///
1597    /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
1598    SessionNotification(Box<SessionNotification>),
1599    /// **UNSTABLE**
1600    ///
1601    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1602    ///
1603    /// Notification that a URL-based elicitation has completed.
1604    #[cfg(feature = "unstable_elicitation")]
1605    CompleteElicitationNotification(CompleteElicitationNotification),
1606    /// **UNSTABLE**
1607    ///
1608    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1609    ///
1610    /// Receives an MCP-over-ACP notification.
1611    #[cfg(feature = "unstable_mcp_over_acp")]
1612    MessageMcpNotification(MessageMcpNotification),
1613    /// Handles extension notifications from the agent.
1614    ///
1615    /// Allows the Agent to send an arbitrary notification that is not part of the ACP spec.
1616    /// Extension notifications provide a way to send one-way messages for custom functionality
1617    /// while maintaining protocol compatibility.
1618    ///
1619    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1620    ExtNotification(ExtNotification),
1621}
1622
1623impl AgentNotification {
1624    /// Returns the corresponding method name of the notification.
1625    #[must_use]
1626    pub fn method(&self) -> &str {
1627        match self {
1628            Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
1629            #[cfg(feature = "unstable_elicitation")]
1630            Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
1631            #[cfg(feature = "unstable_mcp_over_acp")]
1632            Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
1633            Self::ExtNotification(ext_notification) => &ext_notification.method,
1634        }
1635    }
1636}
1637
1638#[cfg(test)]
1639mod tests {
1640    use super::*;
1641
1642    #[test]
1643    fn test_serialization_behavior() {
1644        use serde_json::json;
1645
1646        assert_eq!(
1647            serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
1648            SessionInfoUpdate {
1649                title: MaybeUndefined::Undefined,
1650                updated_at: MaybeUndefined::Undefined,
1651                meta: None
1652            }
1653        );
1654        assert_eq!(
1655            serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
1656                .unwrap(),
1657            SessionInfoUpdate {
1658                title: MaybeUndefined::Null,
1659                updated_at: MaybeUndefined::Null,
1660                meta: None
1661            }
1662        );
1663        assert_eq!(
1664            serde_json::from_value::<SessionInfoUpdate>(
1665                json!({"title": "title", "updatedAt": "timestamp"})
1666            )
1667            .unwrap(),
1668            SessionInfoUpdate {
1669                title: MaybeUndefined::Value("title".to_string()),
1670                updated_at: MaybeUndefined::Value("timestamp".to_string()),
1671                meta: None
1672            }
1673        );
1674
1675        assert_eq!(
1676            serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
1677            json!({})
1678        );
1679        assert_eq!(
1680            serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
1681            json!({"title": "title"})
1682        );
1683        assert_eq!(
1684            serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
1685            json!({"title": null})
1686        );
1687        assert_eq!(
1688            serde_json::to_value(
1689                SessionInfoUpdate::new()
1690                    .title("title")
1691                    .title(MaybeUndefined::Undefined)
1692            )
1693            .unwrap(),
1694            json!({})
1695        );
1696    }
1697
1698    #[test]
1699    fn test_content_chunk_message_id_serialization() {
1700        use serde_json::json;
1701
1702        assert_eq!(
1703            serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
1704                ContentBlock::Text(crate::v2::TextContent::new("Hello")),
1705                "msg_agent_c42b9",
1706            )))
1707            .unwrap(),
1708            json!({
1709                "sessionUpdate": "agent_message_chunk",
1710                "messageId": "msg_agent_c42b9",
1711                "content": {
1712                    "type": "text",
1713                    "text": "Hello"
1714                }
1715            })
1716        );
1717
1718        let err = serde_json::from_value::<ContentChunk>(json!({
1719            "content": {
1720                "type": "text",
1721                "text": "Hello"
1722            }
1723        }))
1724        .unwrap_err();
1725
1726        assert!(err.to_string().contains("messageId"), "{err}");
1727    }
1728
1729    #[test]
1730    fn test_tool_call_content_chunk_serialization() {
1731        use serde_json::json;
1732
1733        assert_eq!(
1734            serde_json::to_value(SessionUpdate::ToolCallContentChunk(
1735                ToolCallContentChunk::new(
1736                    "call_001",
1737                    crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
1738                )
1739            ))
1740            .unwrap(),
1741            json!({
1742                "sessionUpdate": "tool_call_content_chunk",
1743                "toolCallId": "call_001",
1744                "content": {
1745                    "type": "content",
1746                    "content": {
1747                        "type": "text",
1748                        "text": "partial output"
1749                    }
1750                }
1751            })
1752        );
1753
1754        let err = serde_json::from_value::<ToolCallContentChunk>(json!({
1755            "content": {
1756                "type": "content",
1757                "content": {
1758                    "type": "text",
1759                    "text": "partial output"
1760                }
1761            }
1762        }))
1763        .unwrap_err();
1764
1765        assert!(err.to_string().contains("toolCallId"), "{err}");
1766    }
1767
1768    #[test]
1769    fn test_full_message_serialization() {
1770        use serde_json::json;
1771
1772        assert_eq!(
1773            serde_json::to_value(SessionUpdate::UserMessage(
1774                UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
1775                    crate::v2::TextContent::new("Hello")
1776                )])
1777            ))
1778            .unwrap(),
1779            json!({
1780                "sessionUpdate": "user_message",
1781                "messageId": "msg_user_8f7a1",
1782                "content": [
1783                    {
1784                        "type": "text",
1785                        "text": "Hello"
1786                    }
1787                ]
1788            })
1789        );
1790
1791        assert_eq!(
1792            serde_json::to_value(SessionUpdate::AgentMessage(
1793                AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
1794                    crate::v2::TextContent::new("Hello")
1795                )])
1796            ))
1797            .unwrap(),
1798            json!({
1799                "sessionUpdate": "agent_message",
1800                "messageId": "msg_agent_c42b9",
1801                "content": [
1802                    {
1803                        "type": "text",
1804                        "text": "Hello"
1805                    }
1806                ]
1807            })
1808        );
1809
1810        assert_eq!(
1811            serde_json::to_value(SessionUpdate::AgentThought(
1812                AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
1813                    crate::v2::TextContent::new("Need to inspect the call sites first.")
1814                )])
1815            ))
1816            .unwrap(),
1817            json!({
1818                "sessionUpdate": "agent_thought",
1819                "messageId": "msg_thought_a12",
1820                "content": [
1821                    {
1822                        "type": "text",
1823                        "text": "Need to inspect the call sites first."
1824                    }
1825                ]
1826            })
1827        );
1828    }
1829
1830    #[test]
1831    fn test_message_upsert_serialization() {
1832        use serde_json::json;
1833
1834        assert_eq!(
1835            serde_json::to_value(SessionUpdate::UserMessage(
1836                UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
1837            ))
1838            .unwrap(),
1839            json!({
1840                "sessionUpdate": "user_message",
1841                "messageId": "msg_empty",
1842                "content": []
1843            })
1844        );
1845
1846        let empty = serde_json::from_value::<UserMessage>(json!({
1847            "messageId": "msg_empty",
1848            "content": []
1849        }))
1850        .unwrap();
1851        assert!(matches!(
1852            empty.content,
1853            MaybeUndefined::Value(ref content) if content.is_empty()
1854        ));
1855
1856        let patch = serde_json::from_value::<AgentMessage>(json!({
1857            "messageId": "msg_agent_c42b9"
1858        }))
1859        .unwrap();
1860        assert_eq!(patch.content, MaybeUndefined::Undefined);
1861        assert_eq!(patch.meta, MaybeUndefined::Undefined);
1862
1863        let patch = serde_json::from_value::<AgentThought>(json!({
1864            "messageId": "msg_thought_a12"
1865        }))
1866        .unwrap();
1867        assert_eq!(patch.content, MaybeUndefined::Undefined);
1868
1869        let clear = serde_json::from_value::<UserMessage>(json!({
1870            "messageId": "msg_user_8f7a1",
1871            "content": null
1872        }))
1873        .unwrap();
1874        assert_eq!(clear.content, MaybeUndefined::Null);
1875
1876        let mut meta = Meta::new();
1877        meta.insert("source".to_string(), json!("replay"));
1878
1879        assert_eq!(
1880            serde_json::to_value(SessionUpdate::UserMessage(
1881                UserMessage::new("msg_user_8f7a1").meta(meta)
1882            ))
1883            .unwrap(),
1884            json!({
1885                "sessionUpdate": "user_message",
1886                "messageId": "msg_user_8f7a1",
1887                "_meta": {
1888                    "source": "replay"
1889                }
1890            })
1891        );
1892
1893        assert_eq!(
1894            serde_json::to_value(SessionUpdate::UserMessage(
1895                UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
1896            ))
1897            .unwrap(),
1898            json!({
1899                "sessionUpdate": "user_message",
1900                "messageId": "msg_user_8f7a1",
1901                "_meta": null
1902            })
1903        );
1904    }
1905
1906    #[test]
1907    fn test_usage_update_serialization() {
1908        use serde_json::json;
1909
1910        assert_eq!(
1911            serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
1912                53_000, 200_000
1913            )))
1914            .unwrap(),
1915            json!({
1916                "sessionUpdate": "usage_update",
1917                "used": 53000,
1918                "size": 200_000
1919            })
1920        );
1921
1922        assert_eq!(
1923            serde_json::to_value(SessionUpdate::UsageUpdate(
1924                UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
1925            ))
1926            .unwrap(),
1927            json!({
1928                "sessionUpdate": "usage_update",
1929                "used": 53000,
1930                "size": 200_000,
1931                "cost": {
1932                    "amount": 0.045,
1933                    "currency": "USD"
1934                }
1935            })
1936        );
1937
1938        let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
1939            "sessionUpdate": "usage_update",
1940            "used": 53000,
1941            "size": 200_000,
1942            "cost": null
1943        }))
1944        .unwrap() else {
1945            panic!("expected usage update");
1946        };
1947
1948        assert_eq!(update.cost, None);
1949    }
1950
1951    #[test]
1952    fn session_update_preserves_unknown_variant() {
1953        use serde_json::json;
1954
1955        let update: SessionUpdate = serde_json::from_value(json!({
1956            "sessionUpdate": "_status_badge",
1957            "label": "Indexing",
1958            "progress": 0.5
1959        }))
1960        .unwrap();
1961
1962        let SessionUpdate::Other(unknown) = update else {
1963            panic!("expected unknown session update");
1964        };
1965
1966        assert_eq!(unknown.session_update, "_status_badge");
1967        assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
1968        assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
1969
1970        assert_eq!(
1971            serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
1972            json!({
1973                "sessionUpdate": "_status_badge",
1974                "label": "Indexing",
1975                "progress": 0.5
1976            })
1977        );
1978    }
1979
1980    #[test]
1981    fn test_plan_update_serialization() {
1982        use serde_json::json;
1983
1984        let plan_update =
1985            SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
1986                "plan-1",
1987                vec![crate::v2::PlanEntry::new(
1988                    "Step 1",
1989                    crate::v2::PlanEntryPriority::High,
1990                    crate::v2::PlanEntryStatus::Pending,
1991                )],
1992            )));
1993
1994        assert_eq!(
1995            serde_json::to_value(plan_update).unwrap(),
1996            json!({
1997                "sessionUpdate": "plan_update",
1998                "plan": {
1999                    "type": "items",
2000                    "id": "plan-1",
2001                    "entries": [
2002                        {
2003                            "content": "Step 1",
2004                            "priority": "high",
2005                            "status": "pending"
2006                        }
2007                    ]
2008                }
2009            })
2010        );
2011    }
2012
2013    #[cfg(feature = "unstable_plan_operations")]
2014    #[test]
2015    fn test_plan_removed_serialization() {
2016        use serde_json::json;
2017
2018        assert_eq!(
2019            serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
2020            json!({
2021                "sessionUpdate": "plan_removed",
2022                "id": "plan-1"
2023            })
2024        );
2025    }
2026
2027    #[test]
2028    fn available_command_input_preserves_unknown_typed_variant() {
2029        use serde_json::json;
2030
2031        let input: AvailableCommandInput = serde_json::from_value(json!({
2032            "type": "_choices",
2033            "hint": "Pick one",
2034            "options": ["fast", "careful"]
2035        }))
2036        .unwrap();
2037
2038        let AvailableCommandInput::Other(unknown) = input else {
2039            panic!("expected unknown command input");
2040        };
2041
2042        assert_eq!(unknown.type_, "_choices");
2043        assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
2044        assert_eq!(
2045            unknown.fields.get("options"),
2046            Some(&json!(["fast", "careful"]))
2047        );
2048        assert_eq!(
2049            serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
2050            json!({
2051                "type": "_choices",
2052                "hint": "Pick one",
2053                "options": ["fast", "careful"]
2054            })
2055        );
2056    }
2057
2058    #[test]
2059    fn available_command_input_unknown_does_not_hide_malformed_unstructured_variant() {
2060        use serde_json::json;
2061
2062        assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
2063        assert!(
2064            serde_json::from_value::<AvailableCommandInput>(json!({
2065                "type": 1,
2066                "hint": "Pick one"
2067            }))
2068            .is_err()
2069        );
2070    }
2071
2072    #[cfg(feature = "unstable_nes")]
2073    #[test]
2074    fn test_client_capabilities_position_encodings_serialization() {
2075        use serde_json::json;
2076
2077        let capabilities = ClientCapabilities::new().position_encodings(vec![
2078            PositionEncodingKind::Utf32,
2079            PositionEncodingKind::Utf16,
2080        ]);
2081        let json = serde_json::to_value(&capabilities).unwrap();
2082
2083        assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
2084    }
2085
2086    #[cfg(feature = "unstable_mcp_over_acp")]
2087    #[test]
2088    fn test_agent_mcp_request_method_names() {
2089        use serde_json::json;
2090
2091        let params: serde_json::Map<String, serde_json::Value> =
2092            [("cursor".to_string(), json!("abc"))].into_iter().collect();
2093
2094        assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
2095        assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
2096        assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
2097
2098        assert_eq!(
2099            AgentRequest::ConnectMcpRequest(ConnectMcpRequest::new("server-1")).method(),
2100            "mcp/connect"
2101        );
2102        assert_eq!(
2103            AgentRequest::MessageMcpRequest(MessageMcpRequest::new("conn-1", "tools/list"))
2104                .method(),
2105            "mcp/message"
2106        );
2107        assert_eq!(
2108            AgentRequest::DisconnectMcpRequest(DisconnectMcpRequest::new("conn-1")).method(),
2109            "mcp/disconnect"
2110        );
2111        assert_eq!(
2112            AgentNotification::MessageMcpNotification(MessageMcpNotification::new(
2113                "conn-1",
2114                "notifications/progress"
2115            ))
2116            .method(),
2117            "mcp/message"
2118        );
2119
2120        assert_eq!(
2121            serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
2122            json!({ "acpId": "server-1" })
2123        );
2124        assert_eq!(
2125            serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
2126            json!({ "connectionId": "conn-1" })
2127        );
2128        assert_eq!(
2129            serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
2130                .unwrap(),
2131            json!({
2132                "connectionId": "conn-1",
2133                "method": "tools/list",
2134                "params": { "cursor": "abc" }
2135            })
2136        );
2137        assert_eq!(
2138            serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
2139            json!({ "connectionId": "conn-1" })
2140        );
2141        assert_eq!(
2142            serde_json::to_value(MessageMcpNotification::new(
2143                "conn-1",
2144                "notifications/progress"
2145            ))
2146            .unwrap(),
2147            json!({
2148                "connectionId": "conn-1",
2149                "method": "notifications/progress"
2150            })
2151        );
2152
2153        let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
2154            "connectionId": "conn-1",
2155            "method": "tools/list",
2156            "params": null
2157        }))
2158        .unwrap();
2159        assert_eq!(request_with_null_params.params, None);
2160    }
2161
2162    #[cfg(feature = "unstable_auth_methods")]
2163    #[test]
2164    fn test_auth_capabilities_serialize_terminal_support_as_object() {
2165        use serde_json::json;
2166
2167        let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
2168
2169        assert_eq!(
2170            serde_json::to_value(&capabilities).unwrap(),
2171            json!({
2172                "terminal": {}
2173            })
2174        );
2175
2176        let deserialized: AuthCapabilities = serde_json::from_value(json!({
2177            "terminal": false
2178        }))
2179        .unwrap();
2180        assert!(deserialized.terminal.is_none());
2181    }
2182}