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_end_turn_token_usage")]
16use super::Usage;
17use super::{
18    AbsolutePath, ContentBlock, ExtNotification, ExtRequest, ExtResponse, Meta, PlanUpdate,
19    SessionConfigOption, SessionId, StopReason, TerminalId, TerminalOutputChunk, TerminalUpdate,
20    ToolCallContentChunk, ToolCallId, ToolCallUpdate,
21};
22#[cfg(feature = "unstable_elicitation")]
23use super::{
24    CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
25    ElicitationCapabilities,
26};
27use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined, SkipListener};
28
29#[cfg(feature = "unstable_mcp_over_acp")]
30use super::mcp::{
31    ConnectMcpRequest, ConnectMcpResponse, DisconnectMcpRequest, DisconnectMcpResponse,
32    MCP_CONNECT_METHOD_NAME, MCP_DISCONNECT_METHOD_NAME, MCP_MESSAGE_METHOD_NAME,
33    MessageMcpNotification, MessageMcpRequest, MessageMcpResponse,
34};
35
36#[cfg(feature = "unstable_nes")]
37use super::{ClientNesCapabilities, PositionEncodingKind};
38
39// Session updates
40
41/// Notification containing a session update from the agent.
42///
43/// Agents can send session updates at any point while the session exists.
44///
45/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-lifecycle#3-agent-reports-output)
46#[serde_as]
47#[skip_serializing_none]
48#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
49#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
50#[serde(rename_all = "camelCase")]
51#[non_exhaustive]
52pub struct UpdateSessionNotification {
53    /// The ID of the session this update pertains to.
54    pub session_id: SessionId,
55    /// The actual update content.
56    pub update: SessionUpdate,
57    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
58    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
59    /// these keys.
60    ///
61    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
62    #[serde_as(deserialize_as = "DefaultOnError")]
63    #[schemars(extend("x-deserialize-default-on-error" = true))]
64    #[serde(default)]
65    #[serde(rename = "_meta")]
66    pub meta: Option<Meta>,
67}
68
69impl UpdateSessionNotification {
70    /// Builds [`UpdateSessionNotification`] with the required notification fields set; optional fields start unset or empty.
71    #[must_use]
72    pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
73        Self {
74            session_id: session_id.into(),
75            update,
76            meta: None,
77        }
78    }
79
80    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
81    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
82    /// these keys.
83    ///
84    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
85    #[must_use]
86    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
87        self.meta = meta.into_option();
88        self
89    }
90}
91
92/// Different types of updates that can be sent while a session exists.
93///
94/// These updates report messages, progress, and other session activity.
95///
96/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-lifecycle#3-agent-reports-output)
97#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
98#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
99#[non_exhaustive]
100pub enum SessionUpdate {
101    /// A chunk of the user's message being streamed.
102    UserMessageChunk(ContentChunk),
103    /// A user message has been created or updated.
104    ///
105    /// Agents can send this when they accept or replay a user message. When a
106    /// client receives another `user_message` update with the same `messageId`,
107    /// fields in the new update patch the previous fields for that message.
108    UserMessage(UserMessage),
109    /// A chunk of the agent's response being streamed.
110    AgentMessageChunk(ContentChunk),
111    /// An agent message has been created or updated.
112    ///
113    /// Agents can send this in addition to streamed chunks. When a client
114    /// receives another `agent_message` update with the same `messageId`,
115    /// fields in the new update patch the previous fields for that message.
116    AgentMessage(AgentMessage),
117    /// A chunk of the agent's internal reasoning being streamed.
118    AgentThoughtChunk(ContentChunk),
119    /// An agent thought or reasoning message has been created or updated.
120    ///
121    /// Agents can send this in addition to streamed chunks. When a client
122    /// receives another `agent_thought` update with the same `messageId`,
123    /// fields in the new update patch the previous fields for that message.
124    AgentThought(AgentThought),
125    /// The state of the agent's foreground work has changed.
126    StateUpdate(StateUpdate),
127    /// A chunk of tool-call content being streamed.
128    ToolCallContentChunk(ToolCallContentChunk),
129    /// A tool call has been created or updated.
130    ToolCallUpdate(ToolCallUpdate),
131    /// An agent-owned terminal has been created or updated.
132    TerminalUpdate(TerminalUpdate),
133    /// A chunk of bytes appended to an agent-owned terminal's output.
134    TerminalOutputChunk(TerminalOutputChunk),
135    /// A content update for a plan identified by ID.
136    /// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
137    PlanUpdate(PlanUpdate),
138    /// **UNSTABLE**
139    ///
140    /// This capability is not part of the spec yet, and may be removed or changed at any point.
141    ///
142    /// Removal notice for a plan identified by ID.
143    #[cfg(feature = "unstable_plan_operations")]
144    PlanRemoved(PlanRemoved),
145    /// Available commands are ready or have changed
146    AvailableCommandsUpdate(AvailableCommandsUpdate),
147    /// Session configuration options have been updated.
148    ConfigOptionUpdate(ConfigOptionUpdate),
149    /// Session metadata has been updated (title, timestamps, custom metadata)
150    SessionInfoUpdate(SessionInfoUpdate),
151    /// Context window and cost update for the session.
152    UsageUpdate(UsageUpdate),
153    /// Custom or future session update.
154    ///
155    /// Values beginning with `_` are reserved for implementation-specific
156    /// extensions. Unknown values that do not begin with `_` are reserved for
157    /// future ACP variants.
158    ///
159    /// Receivers that do not understand this update type should preserve the
160    /// raw payload when storing, replaying, proxying, or forwarding session
161    /// history, and otherwise ignore it or display it generically.
162    #[serde(untagged)]
163    Other(OtherSessionUpdate),
164}
165
166/// Custom or future session update payload.
167///
168/// This preserves the unknown `sessionUpdate` discriminator and the rest of the
169/// update object for clients that store, replay, proxy, or forward session
170/// history.
171#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
172#[schemars(inline)]
173#[schemars(transform = other_session_update_schema)]
174#[serde(rename_all = "camelCase")]
175#[non_exhaustive]
176pub struct OtherSessionUpdate {
177    /// Custom or future session update type.
178    ///
179    /// Values beginning with `_` are reserved for implementation-specific
180    /// extensions. Unknown values that do not begin with `_` are reserved for
181    /// future ACP variants.
182    #[serde(rename = "sessionUpdate")]
183    pub session_update: String,
184    /// Additional fields from the unknown update payload.
185    #[serde(flatten)]
186    pub fields: BTreeMap<String, serde_json::Value>,
187}
188
189impl OtherSessionUpdate {
190    /// Builds [`OtherSessionUpdate`] from an unknown discriminator and preserves the remaining extension fields.
191    #[must_use]
192    pub fn new(
193        session_update: impl Into<String>,
194        mut fields: BTreeMap<String, serde_json::Value>,
195    ) -> Self {
196        fields.remove("sessionUpdate");
197        Self {
198            session_update: session_update.into(),
199            fields,
200        }
201    }
202}
203
204impl<'de> Deserialize<'de> for OtherSessionUpdate {
205    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206    where
207        D: serde::Deserializer<'de>,
208    {
209        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
210        let session_update = fields
211            .remove("sessionUpdate")
212            .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
213        let serde_json::Value::String(session_update) = session_update else {
214            return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
215        };
216
217        if is_known_session_update(&session_update) {
218            return Err(serde::de::Error::custom(format!(
219                "known session update `{session_update}` did not match its schema"
220            )));
221        }
222
223        Ok(Self {
224            session_update,
225            fields,
226        })
227    }
228}
229
230fn is_known_session_update(session_update: &str) -> bool {
231    #[cfg(feature = "unstable_plan_operations")]
232    if session_update == "plan_removed" {
233        return true;
234    }
235    matches!(
236        session_update,
237        "user_message_chunk"
238            | "user_message"
239            | "agent_message_chunk"
240            | "agent_message"
241            | "agent_thought_chunk"
242            | "agent_thought"
243            | "state_update"
244            | "tool_call_content_chunk"
245            | "tool_call_update"
246            | "terminal_update"
247            | "terminal_output_chunk"
248            | "plan_update"
249            | "available_commands_update"
250            | "config_option_update"
251            | "session_info_update"
252            | "usage_update"
253    )
254}
255
256fn other_session_update_schema(schema: &mut Schema) {
257    super::schema_util::reject_known_string_discriminators(
258        schema,
259        "sessionUpdate",
260        &[
261            "user_message_chunk",
262            "user_message",
263            "agent_message_chunk",
264            "agent_message",
265            "agent_thought_chunk",
266            "agent_thought",
267            "state_update",
268            "tool_call_content_chunk",
269            "tool_call_update",
270            "terminal_update",
271            "terminal_output_chunk",
272            "plan_update",
273            "available_commands_update",
274            "config_option_update",
275            "session_info_update",
276            #[cfg(feature = "unstable_plan_operations")]
277            "plan_removed",
278            "usage_update",
279        ],
280    );
281}
282
283/// Session configuration options have been updated.
284#[serde_as]
285#[skip_serializing_none]
286#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
287#[serde(rename_all = "camelCase")]
288#[non_exhaustive]
289pub struct ConfigOptionUpdate {
290    /// The full set of configuration options and their current values.
291    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
292    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
293    pub config_options: Vec<SessionConfigOption>,
294    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
295    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
296    /// these keys.
297    ///
298    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
299    #[serde_as(deserialize_as = "DefaultOnError")]
300    #[schemars(extend("x-deserialize-default-on-error" = true))]
301    #[serde(default)]
302    #[serde(rename = "_meta")]
303    pub meta: Option<Meta>,
304}
305
306impl ConfigOptionUpdate {
307    /// Builds [`ConfigOptionUpdate`] with the required fields set; optional fields start unset or empty.
308    #[must_use]
309    pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
310        Self {
311            config_options,
312            meta: None,
313        }
314    }
315
316    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
317    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
318    /// these keys.
319    ///
320    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
321    #[must_use]
322    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
323        self.meta = meta.into_option();
324        self
325    }
326}
327
328/// Update to session metadata. All fields are optional to support partial updates.
329///
330/// Agents send this notification to update session information like title or custom metadata.
331/// This allows clients to display dynamic session names and track session state changes.
332///
333/// Omitted fields leave the existing session info unchanged. `null` clears the
334/// corresponding value.
335#[serde_as]
336#[skip_serializing_none]
337#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
338#[serde(rename_all = "camelCase")]
339#[non_exhaustive]
340pub struct SessionInfoUpdate {
341    /// Human-readable title for the session. Set to null to clear.
342    #[serde_as(deserialize_as = "DefaultOnError")]
343    #[schemars(extend("x-deserialize-default-on-error" = true))]
344    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
345    pub title: MaybeUndefined<String>,
346    /// RFC 3339 timestamp of last activity. Set to null to clear.
347    #[serde_as(deserialize_as = "DefaultOnError")]
348    #[schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time"))]
349    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
350    pub updated_at: MaybeUndefined<String>,
351    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
352    /// metadata to their interactions. Omitted means no metadata update; `null` is an
353    /// explicit clear signal. Implementations MUST NOT make assumptions about values at these keys.
354    ///
355    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
356    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
357    #[schemars(extend("x-deserialize-default-on-error" = true))]
358    #[serde(
359        rename = "_meta",
360        default,
361        skip_serializing_if = "MaybeUndefined::is_undefined"
362    )]
363    pub meta: MaybeUndefined<Meta>,
364}
365
366impl SessionInfoUpdate {
367    /// Builds [`SessionInfoUpdate`] with the required fields set; optional fields start unset or empty.
368    #[must_use]
369    pub fn new() -> Self {
370        Self::default()
371    }
372
373    /// Human-readable title for the session. Set to null to clear.
374    #[must_use]
375    pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
376        self.title = title.into_maybe_undefined();
377        self
378    }
379
380    /// RFC 3339 timestamp of last activity. Set to null to clear.
381    #[must_use]
382    pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
383        self.updated_at = updated_at.into_maybe_undefined();
384        self
385    }
386
387    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
388    /// metadata to their interactions. Omitted means no metadata update; `null` is an
389    /// explicit clear signal. Implementations MUST NOT make assumptions about values at these keys.
390    ///
391    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
392    #[must_use]
393    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
394        self.meta = meta.into_maybe_undefined();
395        self
396    }
397}
398
399/// Context window and cost update for a session.
400#[serde_as]
401#[skip_serializing_none]
402#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
403#[serde(rename_all = "camelCase")]
404#[non_exhaustive]
405pub struct UsageUpdate {
406    /// Tokens currently in context.
407    pub used: u64,
408    /// Total context window size in tokens.
409    pub size: u64,
410    /// Cumulative session cost (optional).
411    #[serde_as(deserialize_as = "DefaultOnError")]
412    #[schemars(extend("x-deserialize-default-on-error" = true))]
413    #[serde(default)]
414    pub cost: Option<Cost>,
415    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
416    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
417    /// these keys.
418    ///
419    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
420    #[serde_as(deserialize_as = "DefaultOnError")]
421    #[schemars(extend("x-deserialize-default-on-error" = true))]
422    #[serde(default)]
423    #[serde(rename = "_meta")]
424    pub meta: Option<Meta>,
425}
426
427impl UsageUpdate {
428    /// Builds [`UsageUpdate`] with the required fields set; optional fields start unset or empty.
429    #[must_use]
430    pub fn new(used: u64, size: u64) -> Self {
431        Self {
432            used,
433            size,
434            cost: None,
435            meta: None,
436        }
437    }
438
439    /// Cumulative session cost (optional).
440    #[must_use]
441    pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
442        self.cost = cost.into_option();
443        self
444    }
445
446    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
447    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
448    /// these keys.
449    ///
450    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
451    #[must_use]
452    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
453        self.meta = meta.into_option();
454        self
455    }
456}
457
458/// The state of the agent's foreground work has changed.
459///
460/// Background activity can continue and emit other `session/update` notifications
461/// while `idle`. Those notifications do not change this state.
462#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
463#[serde(tag = "state", rename_all = "snake_case")]
464#[non_exhaustive]
465pub enum StateUpdate {
466    /// Foreground work is in progress.
467    Running(RunningStateUpdate),
468    /// The agent is ready to process a new prompt.
469    Idle(IdleStateUpdate),
470    /// Foreground work is blocked on user action.
471    RequiresAction(RequiresActionStateUpdate),
472    /// Custom or future session state.
473    ///
474    /// Values beginning with `_` are reserved for implementation-specific
475    /// extensions. Unknown values that do not begin with `_` are reserved for
476    /// future ACP variants.
477    #[serde(untagged)]
478    Other(OtherStateUpdate),
479}
480
481/// Foreground work is in progress.
482#[serde_as]
483#[skip_serializing_none]
484#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
485#[serde(rename_all = "camelCase")]
486#[non_exhaustive]
487pub struct RunningStateUpdate {
488    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
489    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
490    /// these keys.
491    ///
492    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
493    #[serde_as(deserialize_as = "DefaultOnError")]
494    #[schemars(extend("x-deserialize-default-on-error" = true))]
495    #[serde(default)]
496    #[serde(rename = "_meta")]
497    pub meta: Option<Meta>,
498}
499
500impl RunningStateUpdate {
501    /// Builds [`RunningStateUpdate`] with the required fields set; optional fields start unset or empty.
502    #[must_use]
503    pub fn new() -> Self {
504        Self::default()
505    }
506
507    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
508    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
509    /// these keys.
510    ///
511    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
512    #[must_use]
513    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
514        self.meta = meta.into_option();
515        self
516    }
517}
518
519/// The agent is ready to process a new prompt.
520#[serde_as]
521#[skip_serializing_none]
522#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
523#[serde(rename_all = "camelCase")]
524#[non_exhaustive]
525pub struct IdleStateUpdate {
526    /// Indicates why foreground work stopped.
527    ///
528    /// Optional. Omitted or `null` both mean the agent is not reporting a stop reason.
529    /// Agents SHOULD include this when the idle transition ends foreground work.
530    #[serde_as(deserialize_as = "DefaultOnError")]
531    #[schemars(extend("x-deserialize-default-on-error" = true))]
532    #[serde(default)]
533    pub stop_reason: Option<StopReason>,
534    /// **UNSTABLE**
535    ///
536    /// This capability is not part of the spec yet, and may be removed or changed at any point.
537    ///
538    /// Token usage for completed foreground work.
539    ///
540    /// Optional. Omitted or `null` both mean the agent is not reporting token
541    /// usage for this state update.
542    #[cfg(feature = "unstable_end_turn_token_usage")]
543    #[serde_as(deserialize_as = "DefaultOnError")]
544    #[schemars(extend("x-deserialize-default-on-error" = true))]
545    #[serde(default)]
546    pub usage: Option<Usage>,
547    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
548    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
549    /// these keys.
550    ///
551    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
552    #[serde_as(deserialize_as = "DefaultOnError")]
553    #[schemars(extend("x-deserialize-default-on-error" = true))]
554    #[serde(default)]
555    #[serde(rename = "_meta")]
556    pub meta: Option<Meta>,
557}
558
559impl IdleStateUpdate {
560    /// Builds [`IdleStateUpdate`] with the required fields set; optional fields start unset or empty.
561    #[must_use]
562    pub fn new() -> Self {
563        Self::default()
564    }
565
566    /// Indicates why foreground work stopped.
567    #[must_use]
568    pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
569        self.stop_reason = stop_reason.into_option();
570        self
571    }
572
573    /// **UNSTABLE**
574    ///
575    /// This capability is not part of the spec yet, and may be removed or changed at any point.
576    ///
577    /// Token usage for completed foreground work.
578    #[cfg(feature = "unstable_end_turn_token_usage")]
579    #[must_use]
580    pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
581        self.usage = usage.into_option();
582        self
583    }
584
585    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
586    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
587    /// these keys.
588    ///
589    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
590    #[must_use]
591    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
592        self.meta = meta.into_option();
593        self
594    }
595}
596
597/// Foreground work is blocked on user action.
598#[serde_as]
599#[skip_serializing_none]
600#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
601#[serde(rename_all = "camelCase")]
602#[non_exhaustive]
603pub struct RequiresActionStateUpdate {
604    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
605    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
606    /// these keys.
607    ///
608    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
609    #[serde_as(deserialize_as = "DefaultOnError")]
610    #[schemars(extend("x-deserialize-default-on-error" = true))]
611    #[serde(default)]
612    #[serde(rename = "_meta")]
613    pub meta: Option<Meta>,
614}
615
616impl RequiresActionStateUpdate {
617    /// Builds [`RequiresActionStateUpdate`] with the required fields set; optional fields start unset or empty.
618    #[must_use]
619    pub fn new() -> Self {
620        Self::default()
621    }
622
623    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
624    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
625    /// these keys.
626    ///
627    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
628    #[must_use]
629    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
630        self.meta = meta.into_option();
631        self
632    }
633}
634
635/// Custom or future session state payload.
636///
637/// This preserves the unknown `state` discriminator and the rest of the state
638/// object for clients that store, replay, proxy, or forward session history.
639#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
640#[schemars(inline)]
641#[schemars(transform = other_state_update_schema)]
642#[serde(rename_all = "camelCase")]
643#[non_exhaustive]
644pub struct OtherStateUpdate {
645    /// Custom or future session state.
646    ///
647    /// Values beginning with `_` are reserved for implementation-specific
648    /// extensions. Unknown values that do not begin with `_` are reserved for
649    /// future ACP variants.
650    #[serde(rename = "state")]
651    pub state: String,
652    /// Additional fields from the unknown state payload.
653    #[serde(flatten)]
654    pub fields: BTreeMap<String, serde_json::Value>,
655}
656
657impl OtherStateUpdate {
658    /// Builds [`OtherStateUpdate`] from an unknown discriminator and preserves the remaining extension fields.
659    #[must_use]
660    pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
661        fields.remove("state");
662        Self {
663            state: state.into(),
664            fields,
665        }
666    }
667}
668
669impl<'de> Deserialize<'de> for OtherStateUpdate {
670    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
671    where
672        D: serde::Deserializer<'de>,
673    {
674        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
675        let state = fields
676            .remove("state")
677            .ok_or_else(|| serde::de::Error::missing_field("state"))?;
678        let serde_json::Value::String(state) = state else {
679            return Err(serde::de::Error::custom("`state` must be a string"));
680        };
681
682        if is_known_state_update(&state) {
683            return Err(serde::de::Error::custom(format!(
684                "known state update `{state}` did not match its schema"
685            )));
686        }
687
688        Ok(Self { state, fields })
689    }
690}
691
692fn is_known_state_update(state: &str) -> bool {
693    matches!(state, "running" | "idle" | "requires_action")
694}
695
696fn other_state_update_schema(schema: &mut Schema) {
697    super::schema_util::reject_known_string_discriminators(
698        schema,
699        "state",
700        &["running", "idle", "requires_action"],
701    );
702}
703
704/// Cost information for a session.
705#[serde_as]
706#[skip_serializing_none]
707#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
708#[serde(rename_all = "camelCase")]
709#[non_exhaustive]
710pub struct Cost {
711    /// Total cumulative cost for session.
712    pub amount: f64,
713    /// ISO 4217 currency code (e.g., "USD", "EUR").
714    #[schemars(pattern(r"^[A-Z]{3}$"))]
715    pub currency: String,
716    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
717    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
718    /// these keys.
719    ///
720    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
721    #[serde_as(deserialize_as = "DefaultOnError")]
722    #[schemars(extend("x-deserialize-default-on-error" = true))]
723    #[serde(default)]
724    #[serde(rename = "_meta")]
725    pub meta: Option<Meta>,
726}
727
728impl Cost {
729    /// Builds [`Cost`] with the required fields set; optional fields start unset or empty.
730    #[must_use]
731    pub fn new(amount: f64, currency: impl Into<String>) -> Self {
732        Self {
733            amount,
734            currency: currency.into(),
735            meta: None,
736        }
737    }
738
739    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
740    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
741    /// these keys.
742    ///
743    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
744    #[must_use]
745    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
746        self.meta = meta.into_option();
747        self
748    }
749}
750
751/// A streamed item of message content.
752#[serde_as]
753#[skip_serializing_none]
754#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
755#[serde(rename_all = "camelCase")]
756#[non_exhaustive]
757pub struct ContentChunk {
758    /// A unique identifier for the message this chunk belongs to.
759    ///
760    /// All chunks belonging to the same message share the same `messageId`.
761    /// A change in `messageId` indicates a new message has started.
762    pub message_id: MessageId,
763    /// A single item of content
764    pub content: ContentBlock,
765    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
766    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
767    /// these keys. This field is chunk-scoped.
768    ///
769    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
770    #[serde_as(deserialize_as = "DefaultOnError")]
771    #[schemars(extend("x-deserialize-default-on-error" = true))]
772    #[serde(default)]
773    #[serde(rename = "_meta")]
774    pub meta: Option<Meta>,
775}
776
777impl ContentChunk {
778    /// Builds [`ContentChunk`] with the required fields set; optional fields start unset or empty.
779    #[must_use]
780    pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
781        Self {
782            content,
783            message_id: message_id.into(),
784            meta: None,
785        }
786    }
787
788    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
789    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
790    /// these keys. This field is chunk-scoped.
791    ///
792    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
793    #[must_use]
794    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
795        self.meta = meta.into_option();
796        self
797    }
798}
799
800/// A user message upsert.
801///
802/// Only [`UserMessage::message_id`] is required. `content` has patch semantics:
803/// an omitted field leaves existing message content unchanged, `null` clears the
804/// value, and a concrete array replaces the previous value. For a new
805/// `messageId`, omitted fields use client defaults. `content` is replaced as a
806/// whole array; send `[]` or `null` to clear it.
807///
808/// Message updates and chunks are applied in the order they are received. When
809/// a `user_message` update includes `content`, that array replaces any content
810/// previously accumulated for the message, including content from earlier
811/// chunks. Later chunks with the same `messageId` append to the current
812/// content.
813#[serde_as]
814#[skip_serializing_none]
815#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
816#[serde(rename_all = "camelCase")]
817#[non_exhaustive]
818pub struct UserMessage {
819    /// A unique identifier for the message.
820    pub message_id: MessageId,
821    /// Complete replacement content for this message.
822    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
823    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
824    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
825    pub content: MaybeUndefined<Vec<ContentBlock>>,
826    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
827    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
828    /// these keys. Omitted means no metadata update; `null` is an explicit clear signal.
829    ///
830    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
831    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
832    #[schemars(extend("x-deserialize-default-on-error" = true))]
833    #[serde(
834        rename = "_meta",
835        default,
836        skip_serializing_if = "MaybeUndefined::is_undefined"
837    )]
838    pub meta: MaybeUndefined<Meta>,
839}
840
841impl UserMessage {
842    /// Builds [`UserMessage`] with the required fields set; optional fields start unset or empty.
843    #[must_use]
844    pub fn new(message_id: impl Into<MessageId>) -> Self {
845        Self {
846            message_id: message_id.into(),
847            content: MaybeUndefined::Undefined,
848            meta: MaybeUndefined::Undefined,
849        }
850    }
851
852    /// Complete replacement content for this message.
853    #[must_use]
854    pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
855        self.content = content.into_maybe_undefined();
856        self
857    }
858
859    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
860    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
861    /// these keys.
862    ///
863    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
864    #[must_use]
865    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
866        self.meta = meta.into_maybe_undefined();
867        self
868    }
869}
870
871/// An agent message upsert.
872///
873/// Only [`AgentMessage::message_id`] is required. `content` has patch semantics:
874/// an omitted field leaves existing message content unchanged, `null` clears the
875/// value, and a concrete array replaces the previous value. For a new
876/// `messageId`, omitted fields use client defaults. `content` is replaced as a
877/// whole array; send `[]` or `null` to clear it.
878///
879/// Message updates and chunks are applied in the order they are received. When
880/// an `agent_message` update includes `content`, that array replaces any
881/// content previously accumulated for the message, including content from
882/// earlier chunks. Later chunks with the same `messageId` append to the current
883/// content.
884#[serde_as]
885#[skip_serializing_none]
886#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
887#[serde(rename_all = "camelCase")]
888#[non_exhaustive]
889pub struct AgentMessage {
890    /// A unique identifier for the message.
891    pub message_id: MessageId,
892    /// Complete replacement content for this message.
893    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
894    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
895    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
896    pub content: MaybeUndefined<Vec<ContentBlock>>,
897    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
898    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
899    /// these keys. Omitted means no metadata update; `null` is an explicit clear signal.
900    ///
901    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
902    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
903    #[schemars(extend("x-deserialize-default-on-error" = true))]
904    #[serde(
905        rename = "_meta",
906        default,
907        skip_serializing_if = "MaybeUndefined::is_undefined"
908    )]
909    pub meta: MaybeUndefined<Meta>,
910}
911
912impl AgentMessage {
913    /// Builds [`AgentMessage`] with the required fields set; optional fields start unset or empty.
914    #[must_use]
915    pub fn new(message_id: impl Into<MessageId>) -> Self {
916        Self {
917            message_id: message_id.into(),
918            content: MaybeUndefined::Undefined,
919            meta: MaybeUndefined::Undefined,
920        }
921    }
922
923    /// Complete replacement content for this message.
924    #[must_use]
925    pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
926        self.content = content.into_maybe_undefined();
927        self
928    }
929
930    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
931    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
932    /// these keys.
933    ///
934    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
935    #[must_use]
936    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
937        self.meta = meta.into_maybe_undefined();
938        self
939    }
940}
941
942/// An agent thought or reasoning message upsert.
943///
944/// Only [`AgentThought::message_id`] is required. `content` has patch semantics:
945/// an omitted field leaves existing thought content unchanged, `null` clears the
946/// value, and a concrete array replaces the previous value. For a new
947/// `messageId`, omitted fields use client defaults. `content` is replaced as a
948/// whole array; send `[]` or `null` to clear it.
949///
950/// Message updates and chunks are applied in the order they are received. When
951/// an `agent_thought` update includes `content`, that array replaces any
952/// content previously accumulated for the thought, including content from
953/// earlier chunks. Later chunks with the same `messageId` append to the current
954/// content.
955#[serde_as]
956#[skip_serializing_none]
957#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
958#[serde(rename_all = "camelCase")]
959#[non_exhaustive]
960pub struct AgentThought {
961    /// A unique identifier for the thought message.
962    pub message_id: MessageId,
963    /// Complete replacement content for this thought message.
964    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
965    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
966    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
967    pub content: MaybeUndefined<Vec<ContentBlock>>,
968    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
969    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
970    /// these keys. Omitted means no metadata update; `null` is an explicit clear signal.
971    ///
972    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
973    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
974    #[schemars(extend("x-deserialize-default-on-error" = true))]
975    #[serde(
976        rename = "_meta",
977        default,
978        skip_serializing_if = "MaybeUndefined::is_undefined"
979    )]
980    pub meta: MaybeUndefined<Meta>,
981}
982
983impl AgentThought {
984    /// Builds [`AgentThought`] with the required fields set; optional fields start unset or empty.
985    #[must_use]
986    pub fn new(message_id: impl Into<MessageId>) -> Self {
987        Self {
988            message_id: message_id.into(),
989            content: MaybeUndefined::Undefined,
990            meta: MaybeUndefined::Undefined,
991        }
992    }
993
994    /// Complete replacement content for this thought message.
995    #[must_use]
996    pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
997        self.content = content.into_maybe_undefined();
998        self
999    }
1000
1001    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1002    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1003    /// these keys.
1004    ///
1005    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1006    #[must_use]
1007    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1008        self.meta = meta.into_maybe_undefined();
1009        self
1010    }
1011}
1012
1013/// Unique identifier for a message within a session.
1014#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1015#[serde(transparent)]
1016#[from(forward)]
1017#[non_exhaustive]
1018pub struct MessageId(pub Arc<str>);
1019
1020impl MessageId {
1021    /// Wraps a protocol string as a typed [`MessageId`].
1022    #[must_use]
1023    pub fn new(id: impl Into<Self>) -> Self {
1024        id.into()
1025    }
1026}
1027
1028/// Available commands are ready or have changed
1029#[serde_as]
1030#[skip_serializing_none]
1031#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1032#[serde(rename_all = "camelCase")]
1033#[non_exhaustive]
1034pub struct AvailableCommandsUpdate {
1035    /// Commands the agent can execute.
1036    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1037    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1038    pub available_commands: Vec<AvailableCommand>,
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    #[serde_as(deserialize_as = "DefaultOnError")]
1045    #[schemars(extend("x-deserialize-default-on-error" = true))]
1046    #[serde(default)]
1047    #[serde(rename = "_meta")]
1048    pub meta: Option<Meta>,
1049}
1050
1051impl AvailableCommandsUpdate {
1052    /// Builds [`AvailableCommandsUpdate`] with the required fields set; optional fields start unset or empty.
1053    #[must_use]
1054    pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
1055        Self {
1056            available_commands,
1057            meta: None,
1058        }
1059    }
1060
1061    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1062    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1063    /// these keys.
1064    ///
1065    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1066    #[must_use]
1067    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1068        self.meta = meta.into_option();
1069        self
1070    }
1071}
1072
1073/// Information about a command.
1074#[serde_as]
1075#[skip_serializing_none]
1076#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1077#[serde(rename_all = "camelCase")]
1078#[non_exhaustive]
1079pub struct AvailableCommand {
1080    /// Command name (e.g., `create_plan`, `research_codebase`).
1081    pub name: String,
1082    /// Human-readable description of what the command does.
1083    pub description: String,
1084    /// Input for the command if required
1085    #[serde_as(deserialize_as = "DefaultOnError")]
1086    #[schemars(extend("x-deserialize-default-on-error" = true))]
1087    #[serde(default)]
1088    pub input: Option<AvailableCommandInput>,
1089    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1090    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1091    /// these keys.
1092    ///
1093    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1094    #[serde_as(deserialize_as = "DefaultOnError")]
1095    #[schemars(extend("x-deserialize-default-on-error" = true))]
1096    #[serde(default)]
1097    #[serde(rename = "_meta")]
1098    pub meta: Option<Meta>,
1099}
1100
1101impl AvailableCommand {
1102    /// Builds [`AvailableCommand`] with the required fields set; optional fields start unset or empty.
1103    #[must_use]
1104    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1105        Self {
1106            name: name.into(),
1107            description: description.into(),
1108            input: None,
1109            meta: None,
1110        }
1111    }
1112
1113    /// Input for the command if required
1114    #[must_use]
1115    pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
1116        self.input = input.into_option();
1117        self
1118    }
1119
1120    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1121    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1122    /// these keys.
1123    ///
1124    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1125    #[must_use]
1126    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1127        self.meta = meta.into_option();
1128        self
1129    }
1130}
1131
1132/// The input specification for a command.
1133#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1134#[serde(tag = "type", rename_all = "snake_case")]
1135#[non_exhaustive]
1136pub enum AvailableCommandInput {
1137    /// All text that was typed after the command name is provided as input.
1138    #[serde(rename = "text")]
1139    Text(TextCommandInput),
1140    /// Custom or future command input specification.
1141    ///
1142    /// Values beginning with `_` are reserved for implementation-specific
1143    /// extensions. Unknown values that do not begin with `_` are reserved for
1144    /// future ACP variants.
1145    ///
1146    /// Clients that do not understand this input type should preserve the raw
1147    /// payload when storing, replaying, proxying, or forwarding command
1148    /// metadata, and otherwise ignore the input specification or display the
1149    /// command without structured input.
1150    #[serde(untagged)]
1151    Other(OtherAvailableCommandInput),
1152}
1153
1154/// Custom or future command input specification.
1155#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1156#[schemars(inline)]
1157#[schemars(transform = other_available_command_input_schema)]
1158#[serde(rename_all = "camelCase")]
1159#[non_exhaustive]
1160pub struct OtherAvailableCommandInput {
1161    /// Custom or future command input type.
1162    ///
1163    /// Values beginning with `_` are reserved for implementation-specific
1164    /// extensions. Unknown values that do not begin with `_` are reserved for
1165    /// future ACP variants.
1166    #[serde(rename = "type")]
1167    pub type_: String,
1168    /// Additional fields from the unknown command input payload.
1169    #[serde(flatten)]
1170    pub fields: BTreeMap<String, serde_json::Value>,
1171}
1172
1173impl OtherAvailableCommandInput {
1174    /// Builds [`OtherAvailableCommandInput`] from an unknown discriminator and preserves the remaining extension fields.
1175    #[must_use]
1176    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1177        fields.remove("type");
1178        Self {
1179            type_: type_.into(),
1180            fields,
1181        }
1182    }
1183}
1184
1185impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
1186    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1187    where
1188        D: serde::Deserializer<'de>,
1189    {
1190        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1191        let type_ = fields
1192            .remove("type")
1193            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1194        let serde_json::Value::String(type_) = type_ else {
1195            return Err(serde::de::Error::custom("`type` must be a string"));
1196        };
1197
1198        if is_known_available_command_input_type(&type_) {
1199            return Err(serde::de::Error::custom(format!(
1200                "known available command input type `{type_}` did not match its schema"
1201            )));
1202        }
1203
1204        Ok(Self { type_, fields })
1205    }
1206}
1207
1208const KNOWN_AVAILABLE_COMMAND_INPUT_TYPES: &[&str] = &["text"];
1209
1210fn is_known_available_command_input_type(type_: &str) -> bool {
1211    KNOWN_AVAILABLE_COMMAND_INPUT_TYPES.contains(&type_)
1212}
1213
1214fn other_available_command_input_schema(schema: &mut Schema) {
1215    super::schema_util::reject_known_string_discriminators(
1216        schema,
1217        "type",
1218        KNOWN_AVAILABLE_COMMAND_INPUT_TYPES,
1219    );
1220}
1221
1222/// All text that was typed after the command name is provided as input.
1223#[serde_as]
1224#[skip_serializing_none]
1225#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1226#[serde(rename_all = "camelCase")]
1227#[non_exhaustive]
1228pub struct TextCommandInput {
1229    /// A hint to display when the input hasn't been provided yet
1230    pub hint: String,
1231    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1232    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1233    /// these keys.
1234    ///
1235    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1236    #[serde_as(deserialize_as = "DefaultOnError")]
1237    #[schemars(extend("x-deserialize-default-on-error" = true))]
1238    #[serde(default)]
1239    #[serde(rename = "_meta")]
1240    pub meta: Option<Meta>,
1241}
1242
1243impl TextCommandInput {
1244    /// Builds [`TextCommandInput`] with the required fields set; optional fields start unset or empty.
1245    #[must_use]
1246    pub fn new(hint: impl Into<String>) -> Self {
1247        Self {
1248            hint: hint.into(),
1249            meta: None,
1250        }
1251    }
1252
1253    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1254    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1255    /// these keys.
1256    ///
1257    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1258    #[must_use]
1259    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1260        self.meta = meta.into_option();
1261        self
1262    }
1263}
1264
1265// Permission
1266
1267/// Request for user permission to proceed with an operation.
1268///
1269/// Sent when the agent needs authorization before performing a sensitive operation.
1270///
1271/// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
1272#[serde_as]
1273#[skip_serializing_none]
1274#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1275#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1276#[serde(rename_all = "camelCase")]
1277#[non_exhaustive]
1278pub struct RequestPermissionRequest {
1279    /// The session ID for this request.
1280    pub session_id: SessionId,
1281    /// Human-readable title for the permission prompt.
1282    ///
1283    /// This title is specific to the permission prompt and does not update any
1284    /// subject's displayed title.
1285    pub title: String,
1286    /// Optional human-readable explanation of why permission is needed.
1287    ///
1288    /// This text is specific to the permission prompt and does not update any
1289    /// subject's displayed content. Omitted or `null` both mean no separate
1290    /// permission description was provided.
1291    #[serde_as(deserialize_as = "DefaultOnError")]
1292    #[schemars(extend("x-deserialize-default-on-error" = true))]
1293    #[serde(default)]
1294    pub description: Option<String>,
1295    /// Optional structured context about the operation requiring permission.
1296    ///
1297    /// Omitted or `null` both mean no structured subject was provided.
1298    #[serde(default)]
1299    pub subject: Option<RequestPermissionSubject>,
1300    /// Available permission options for the user to choose from.
1301    /// Must contain at least one option.
1302    #[schemars(length(min = 1))]
1303    pub options: Vec<PermissionOption>,
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    #[serde_as(deserialize_as = "DefaultOnError")]
1310    #[schemars(extend("x-deserialize-default-on-error" = true))]
1311    #[serde(default)]
1312    #[serde(rename = "_meta")]
1313    pub meta: Option<Meta>,
1314}
1315
1316impl RequestPermissionRequest {
1317    /// Builds [`RequestPermissionRequest`] with the required request fields set; optional fields start unset or empty.
1318    #[must_use]
1319    pub fn new(
1320        session_id: impl Into<SessionId>,
1321        title: impl Into<String>,
1322        options: Vec<PermissionOption>,
1323    ) -> Self {
1324        Self {
1325            session_id: session_id.into(),
1326            title: title.into(),
1327            description: None,
1328            subject: None,
1329            options,
1330            meta: None,
1331        }
1332    }
1333
1334    /// Sets or clears the optional `description` field.
1335    #[must_use]
1336    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1337        self.description = description.into_option();
1338        self
1339    }
1340
1341    /// Sets or clears the optional `subject` field.
1342    #[must_use]
1343    pub fn subject(mut self, subject: impl IntoOption<RequestPermissionSubject>) -> Self {
1344        self.subject = subject.into_option();
1345        self
1346    }
1347
1348    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1349    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1350    /// these keys.
1351    ///
1352    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1353    #[must_use]
1354    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1355        self.meta = meta.into_option();
1356        self
1357    }
1358}
1359
1360/// The operation requiring permission.
1361#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1362#[serde(tag = "type", rename_all = "snake_case")]
1363#[non_exhaustive]
1364pub enum RequestPermissionSubject {
1365    /// Permission is requested before executing a tool call.
1366    ToolCall(Box<ToolCallPermissionSubject>),
1367    /// Permission is requested before running a command.
1368    Command(CommandPermissionSubject),
1369    /// Custom or future permission subject.
1370    ///
1371    /// Values beginning with `_` are reserved for implementation-specific
1372    /// extensions. Unknown values that do not begin with `_` are reserved for
1373    /// future ACP variants.
1374    ///
1375    /// Clients that do not understand this subject type should preserve the raw
1376    /// payload when storing, replaying, proxying, or forwarding permission
1377    /// requests, and otherwise display a generic permission prompt or decline it
1378    /// according to policy.
1379    #[serde(untagged)]
1380    Other(OtherRequestPermissionSubject),
1381}
1382
1383impl From<ToolCallPermissionSubject> for RequestPermissionSubject {
1384    fn from(subject: ToolCallPermissionSubject) -> Self {
1385        Self::ToolCall(Box::new(subject))
1386    }
1387}
1388
1389impl From<ToolCallUpdate> for RequestPermissionSubject {
1390    fn from(tool_call: ToolCallUpdate) -> Self {
1391        ToolCallPermissionSubject::new(tool_call).into()
1392    }
1393}
1394
1395impl From<CommandPermissionSubject> for RequestPermissionSubject {
1396    fn from(subject: CommandPermissionSubject) -> Self {
1397        Self::Command(subject)
1398    }
1399}
1400
1401/// Permission request details for a tool call.
1402#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1403#[serde(rename_all = "camelCase")]
1404#[non_exhaustive]
1405pub struct ToolCallPermissionSubject {
1406    /// Details about the tool call requiring permission.
1407    pub tool_call: ToolCallUpdate,
1408}
1409
1410impl ToolCallPermissionSubject {
1411    /// Builds [`ToolCallPermissionSubject`] with the required fields set.
1412    #[must_use]
1413    pub fn new(tool_call: ToolCallUpdate) -> Self {
1414        Self { tool_call }
1415    }
1416}
1417
1418/// Permission request details for a command.
1419#[serde_as]
1420#[skip_serializing_none]
1421#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1422#[serde(rename_all = "camelCase")]
1423#[non_exhaustive]
1424pub struct CommandPermissionSubject {
1425    /// The command that would be run if permission is granted.
1426    pub command: String,
1427    /// The absolute working directory for the command.
1428    pub cwd: AbsolutePath,
1429    /// The associated tool call, when known. Omitted and `null` are equivalent.
1430    #[serde_as(deserialize_as = "DefaultOnError")]
1431    #[schemars(extend("x-deserialize-default-on-error" = true))]
1432    #[serde(default)]
1433    pub tool_call_id: Option<ToolCallId>,
1434    /// The associated terminal, when already known. Omitted and `null` are equivalent.
1435    #[serde_as(deserialize_as = "DefaultOnError")]
1436    #[schemars(extend("x-deserialize-default-on-error" = true))]
1437    #[serde(default)]
1438    pub terminal_id: Option<TerminalId>,
1439    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1440    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1441    /// these keys. Omitted and `null` are equivalent and mean no subject metadata was provided.
1442    ///
1443    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1444    #[serde_as(deserialize_as = "DefaultOnError")]
1445    #[schemars(extend("x-deserialize-default-on-error" = true))]
1446    #[serde(default)]
1447    #[serde(rename = "_meta")]
1448    pub meta: Option<Meta>,
1449}
1450
1451impl CommandPermissionSubject {
1452    /// Builds command permission details with the required command and working directory.
1453    #[must_use]
1454    pub fn new(command: impl Into<String>, cwd: impl Into<AbsolutePath>) -> Self {
1455        Self {
1456            command: command.into(),
1457            cwd: cwd.into(),
1458            tool_call_id: None,
1459            terminal_id: None,
1460            meta: None,
1461        }
1462    }
1463
1464    /// Sets or clears the associated tool-call ID.
1465    #[must_use]
1466    pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1467        self.tool_call_id = tool_call_id.into_option();
1468        self
1469    }
1470
1471    /// Sets or clears the associated terminal ID.
1472    #[must_use]
1473    pub fn terminal_id(mut self, terminal_id: impl IntoOption<TerminalId>) -> Self {
1474        self.terminal_id = terminal_id.into_option();
1475        self
1476    }
1477
1478    /// Sets or clears subject-scoped metadata.
1479    #[must_use]
1480    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1481        self.meta = meta.into_option();
1482        self
1483    }
1484}
1485
1486/// Custom or future permission subject payload.
1487#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1488#[schemars(inline)]
1489#[schemars(transform = other_request_permission_subject_schema)]
1490#[serde(rename_all = "camelCase")]
1491#[non_exhaustive]
1492pub struct OtherRequestPermissionSubject {
1493    /// Custom or future permission subject type.
1494    ///
1495    /// Values beginning with `_` are reserved for implementation-specific
1496    /// extensions. Unknown values that do not begin with `_` are reserved for
1497    /// future ACP variants.
1498    #[serde(rename = "type")]
1499    pub type_: String,
1500    /// Additional fields from the unknown permission subject payload.
1501    #[serde(flatten)]
1502    pub fields: BTreeMap<String, serde_json::Value>,
1503}
1504
1505impl OtherRequestPermissionSubject {
1506    /// Builds [`OtherRequestPermissionSubject`] from an unknown discriminator and preserves the remaining extension fields.
1507    #[must_use]
1508    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1509        fields.remove("type");
1510        Self {
1511            type_: type_.into(),
1512            fields,
1513        }
1514    }
1515}
1516
1517impl<'de> Deserialize<'de> for OtherRequestPermissionSubject {
1518    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1519    where
1520        D: serde::Deserializer<'de>,
1521    {
1522        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1523        let type_ = fields
1524            .remove("type")
1525            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1526        let serde_json::Value::String(type_) = type_ else {
1527            return Err(serde::de::Error::custom("`type` must be a string"));
1528        };
1529
1530        if is_known_request_permission_subject_type(&type_) {
1531            return Err(serde::de::Error::custom(format!(
1532                "known request permission subject `{type_}` did not match its schema"
1533            )));
1534        }
1535
1536        Ok(Self { type_, fields })
1537    }
1538}
1539
1540fn is_known_request_permission_subject_type(type_: &str) -> bool {
1541    matches!(type_, "tool_call" | "command")
1542}
1543
1544fn other_request_permission_subject_schema(schema: &mut Schema) {
1545    super::schema_util::reject_known_string_discriminators(
1546        schema,
1547        "type",
1548        &["tool_call", "command"],
1549    );
1550}
1551
1552/// An option presented to the user when requesting permission.
1553#[serde_as]
1554#[skip_serializing_none]
1555#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1556#[serde(rename_all = "camelCase")]
1557#[non_exhaustive]
1558pub struct PermissionOption {
1559    /// Unique identifier for this permission option.
1560    pub option_id: PermissionOptionId,
1561    /// Human-readable label to display to the user.
1562    pub name: String,
1563    /// Hint about the nature of this permission option.
1564    pub kind: PermissionOptionKind,
1565    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1566    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1567    /// these keys.
1568    ///
1569    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1570    #[serde_as(deserialize_as = "DefaultOnError")]
1571    #[schemars(extend("x-deserialize-default-on-error" = true))]
1572    #[serde(default)]
1573    #[serde(rename = "_meta")]
1574    pub meta: Option<Meta>,
1575}
1576
1577impl PermissionOption {
1578    /// Builds [`PermissionOption`] with the required fields set; optional fields start unset or empty.
1579    #[must_use]
1580    pub fn new(
1581        option_id: impl Into<PermissionOptionId>,
1582        name: impl Into<String>,
1583        kind: PermissionOptionKind,
1584    ) -> Self {
1585        Self {
1586            option_id: option_id.into(),
1587            name: name.into(),
1588            kind,
1589            meta: None,
1590        }
1591    }
1592
1593    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1594    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1595    /// these keys.
1596    ///
1597    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1598    #[must_use]
1599    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1600        self.meta = meta.into_option();
1601        self
1602    }
1603}
1604
1605/// Unique identifier for a permission option.
1606#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1607#[serde(transparent)]
1608#[from(forward)]
1609#[non_exhaustive]
1610pub struct PermissionOptionId(pub Arc<str>);
1611
1612impl PermissionOptionId {
1613    /// Wraps a protocol string as a typed [`PermissionOptionId`].
1614    #[must_use]
1615    pub fn new(id: impl Into<Self>) -> Self {
1616        id.into()
1617    }
1618}
1619
1620/// The type of permission option being presented to the user.
1621///
1622/// Helps clients choose appropriate icons and UI treatment.
1623#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1624#[serde(rename_all = "snake_case")]
1625#[non_exhaustive]
1626pub enum PermissionOptionKind {
1627    /// Allow this operation only this time.
1628    AllowOnce,
1629    /// Allow this operation and remember the choice.
1630    AllowAlways,
1631    /// Reject this operation only this time.
1632    RejectOnce,
1633    /// Reject this operation and remember the choice.
1634    RejectAlways,
1635    /// Custom or future permission option kind.
1636    ///
1637    /// Values beginning with `_` are reserved for implementation-specific
1638    /// extensions. Unknown values that do not begin with `_` are reserved for
1639    /// future ACP variants.
1640    #[serde(untagged)]
1641    Other(String),
1642}
1643
1644/// Response to a permission request.
1645#[serde_as]
1646#[skip_serializing_none]
1647#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1648#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1649#[serde(rename_all = "camelCase")]
1650#[non_exhaustive]
1651pub struct RequestPermissionResponse {
1652    /// The user's decision on the permission request.
1653    pub outcome: RequestPermissionOutcome,
1654    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1655    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1656    /// these keys.
1657    ///
1658    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1659    #[serde_as(deserialize_as = "DefaultOnError")]
1660    #[schemars(extend("x-deserialize-default-on-error" = true))]
1661    #[serde(default)]
1662    #[serde(rename = "_meta")]
1663    pub meta: Option<Meta>,
1664}
1665
1666impl RequestPermissionResponse {
1667    /// Builds [`RequestPermissionResponse`] with the required response fields set; optional fields start unset or empty.
1668    #[must_use]
1669    pub fn new(outcome: RequestPermissionOutcome) -> Self {
1670        Self {
1671            outcome,
1672            meta: None,
1673        }
1674    }
1675
1676    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1677    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1678    /// these keys.
1679    ///
1680    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1681    #[must_use]
1682    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1683        self.meta = meta.into_option();
1684        self
1685    }
1686}
1687
1688/// The outcome of a permission request.
1689#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1690#[serde(tag = "outcome", rename_all = "snake_case")]
1691#[non_exhaustive]
1692pub enum RequestPermissionOutcome {
1693    /// Active session work was cancelled before the user responded.
1694    ///
1695    /// When a client sends a `session/cancel` notification to cancel active
1696    /// session work, it MUST respond to all pending `session/request_permission`
1697    /// requests with this `Cancelled` outcome.
1698    ///
1699    /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-lifecycle#cancellation)
1700    Cancelled,
1701    /// The user selected one of the provided options.
1702    #[serde(rename_all = "camelCase")]
1703    Selected(SelectedPermissionOutcome),
1704    /// Custom or future permission outcome.
1705    ///
1706    /// Values beginning with `_` are reserved for implementation-specific
1707    /// extensions. Unknown values that do not begin with `_` are reserved for
1708    /// future ACP variants.
1709    ///
1710    /// Agents that do not understand this outcome MUST NOT treat it as approval.
1711    /// They should preserve the raw payload when storing, replaying, proxying, or
1712    /// forwarding permission responses, and otherwise fail or decline the
1713    /// permission request according to policy.
1714    #[serde(untagged)]
1715    Other(OtherRequestPermissionOutcome),
1716}
1717
1718/// Custom or future permission outcome payload.
1719///
1720/// This preserves the unknown `outcome` discriminator and the rest of the
1721/// outcome object for agents that store, replay, proxy, or forward permission
1722/// responses.
1723#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1724#[schemars(inline)]
1725#[schemars(transform = other_request_permission_outcome_schema)]
1726#[serde(rename_all = "camelCase")]
1727#[non_exhaustive]
1728pub struct OtherRequestPermissionOutcome {
1729    /// Custom or future permission outcome.
1730    ///
1731    /// Values beginning with `_` are reserved for implementation-specific
1732    /// extensions. Unknown values that do not begin with `_` are reserved for
1733    /// future ACP variants.
1734    pub outcome: String,
1735    /// Additional fields from the unknown permission outcome payload.
1736    #[serde(flatten)]
1737    pub fields: BTreeMap<String, serde_json::Value>,
1738}
1739
1740impl OtherRequestPermissionOutcome {
1741    /// Builds [`OtherRequestPermissionOutcome`] from an unknown discriminator and preserves the remaining extension fields.
1742    #[must_use]
1743    pub fn new(
1744        outcome: impl Into<String>,
1745        mut fields: BTreeMap<String, serde_json::Value>,
1746    ) -> Self {
1747        fields.remove("outcome");
1748        Self {
1749            outcome: outcome.into(),
1750            fields,
1751        }
1752    }
1753}
1754
1755impl<'de> Deserialize<'de> for OtherRequestPermissionOutcome {
1756    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1757    where
1758        D: serde::Deserializer<'de>,
1759    {
1760        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1761        let outcome = fields
1762            .remove("outcome")
1763            .ok_or_else(|| serde::de::Error::missing_field("outcome"))?;
1764        let serde_json::Value::String(outcome) = outcome else {
1765            return Err(serde::de::Error::custom("`outcome` must be a string"));
1766        };
1767
1768        if is_known_request_permission_outcome(&outcome) {
1769            return Err(serde::de::Error::custom(format!(
1770                "known request permission outcome `{outcome}` did not match its schema"
1771            )));
1772        }
1773
1774        Ok(Self { outcome, fields })
1775    }
1776}
1777
1778fn is_known_request_permission_outcome(outcome: &str) -> bool {
1779    matches!(outcome, "cancelled" | "selected")
1780}
1781
1782fn other_request_permission_outcome_schema(schema: &mut Schema) {
1783    super::schema_util::reject_known_string_discriminators(
1784        schema,
1785        "outcome",
1786        &["cancelled", "selected"],
1787    );
1788}
1789
1790/// The user selected one of the provided options.
1791#[serde_as]
1792#[skip_serializing_none]
1793#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1794#[serde(rename_all = "camelCase")]
1795#[non_exhaustive]
1796pub struct SelectedPermissionOutcome {
1797    /// The ID of the option the user selected.
1798    pub option_id: PermissionOptionId,
1799    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1800    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1801    /// these keys.
1802    ///
1803    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1804    #[serde_as(deserialize_as = "DefaultOnError")]
1805    #[schemars(extend("x-deserialize-default-on-error" = true))]
1806    #[serde(default)]
1807    #[serde(rename = "_meta")]
1808    pub meta: Option<Meta>,
1809}
1810
1811impl SelectedPermissionOutcome {
1812    /// Builds [`SelectedPermissionOutcome`] with the required fields set; optional fields start unset or empty.
1813    #[must_use]
1814    pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
1815        Self {
1816            option_id: option_id.into(),
1817            meta: None,
1818        }
1819    }
1820
1821    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1822    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1823    /// these keys.
1824    ///
1825    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1826    #[must_use]
1827    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1828        self.meta = meta.into_option();
1829        self
1830    }
1831}
1832
1833// Capabilities
1834
1835/// Capabilities supported by the client.
1836///
1837/// Advertised during initialization to inform the agent about
1838/// available features and methods.
1839///
1840/// See protocol docs: [Client Capabilities](https://agentclientprotocol.com/protocol/initialization#client-capabilities)
1841#[serde_as]
1842#[skip_serializing_none]
1843#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1844#[serde(rename_all = "camelCase")]
1845#[non_exhaustive]
1846pub struct ClientCapabilities {
1847    /// **UNSTABLE**
1848    ///
1849    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1850    ///
1851    /// Authentication capabilities supported by the client.
1852    /// Determines which authentication method types the agent may include
1853    /// in its `InitializeResponse`.
1854    ///
1855    /// Optional. Omitted or `null` both mean the client does not advertise any
1856    /// authentication-method extensions.
1857    #[cfg(feature = "unstable_auth_methods")]
1858    #[serde_as(deserialize_as = "DefaultOnError")]
1859    #[schemars(extend("x-deserialize-default-on-error" = true))]
1860    #[serde(default)]
1861    pub auth: Option<AuthCapabilities>,
1862    /// **UNSTABLE**
1863    ///
1864    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1865    ///
1866    /// Elicitation capabilities supported by the client.
1867    /// Determines which elicitation modes the agent may use.
1868    ///
1869    /// Optional. Omitted or `null` both mean the client does not advertise
1870    /// elicitation support.
1871    #[cfg(feature = "unstable_elicitation")]
1872    #[serde_as(deserialize_as = "DefaultOnError")]
1873    #[schemars(extend("x-deserialize-default-on-error" = true))]
1874    #[serde(default)]
1875    pub elicitation: Option<ElicitationCapabilities>,
1876    /// **UNSTABLE**
1877    ///
1878    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1879    ///
1880    /// NES (Next Edit Suggestions) capabilities supported by the client.
1881    ///
1882    /// Optional. Omitted or `null` both mean the client does not advertise any
1883    /// NES suggestion-kind extensions.
1884    #[cfg(feature = "unstable_nes")]
1885    #[serde_as(deserialize_as = "DefaultOnError")]
1886    #[schemars(extend("x-deserialize-default-on-error" = true))]
1887    #[serde(default)]
1888    pub nes: Option<ClientNesCapabilities>,
1889    /// **UNSTABLE**
1890    ///
1891    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1892    ///
1893    /// The position encodings supported by the client, in order of preference.
1894    #[cfg(feature = "unstable_nes")]
1895    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1896    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1897    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1898    pub position_encodings: Vec<PositionEncodingKind>,
1899
1900    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1901    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1902    /// these keys.
1903    ///
1904    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1905    #[serde_as(deserialize_as = "DefaultOnError")]
1906    #[schemars(extend("x-deserialize-default-on-error" = true))]
1907    #[serde(default)]
1908    #[serde(rename = "_meta")]
1909    pub meta: Option<Meta>,
1910}
1911
1912impl ClientCapabilities {
1913    /// Builds an empty [`ClientCapabilities`]; use builder methods to advertise supported sub-capabilities.
1914    #[must_use]
1915    pub fn new() -> Self {
1916        Self::default()
1917    }
1918
1919    /// **UNSTABLE**
1920    ///
1921    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1922    ///
1923    /// Authentication capabilities supported by the client.
1924    /// Determines which authentication method types the agent may include
1925    /// in its `InitializeResponse`.
1926    #[cfg(feature = "unstable_auth_methods")]
1927    #[must_use]
1928    pub fn auth(mut self, auth: impl IntoOption<AuthCapabilities>) -> Self {
1929        self.auth = auth.into_option();
1930        self
1931    }
1932
1933    /// **UNSTABLE**
1934    ///
1935    /// This capability is not part of the spec yet, and may be removed or changed at any point.
1936    ///
1937    /// Elicitation capabilities supported by the client.
1938    /// Determines which elicitation modes the agent may use.
1939    #[cfg(feature = "unstable_elicitation")]
1940    #[must_use]
1941    pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1942        self.elicitation = elicitation.into_option();
1943        self
1944    }
1945
1946    /// **UNSTABLE**
1947    ///
1948    /// NES (Next Edit Suggestions) capabilities supported by the client.
1949    #[cfg(feature = "unstable_nes")]
1950    #[must_use]
1951    pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1952        self.nes = nes.into_option();
1953        self
1954    }
1955
1956    /// **UNSTABLE**
1957    ///
1958    /// The position encodings supported by the client, in order of preference.
1959    #[cfg(feature = "unstable_nes")]
1960    #[must_use]
1961    pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1962        self.position_encodings = position_encodings;
1963        self
1964    }
1965
1966    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1967    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1968    /// these keys.
1969    ///
1970    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1971    #[must_use]
1972    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1973        self.meta = meta.into_option();
1974        self
1975    }
1976}
1977
1978/// **UNSTABLE**
1979///
1980/// This capability is not part of the spec yet, and may be removed or changed at any point.
1981///
1982/// Authentication capabilities supported by the client.
1983///
1984/// Advertised during initialization to inform the agent which authentication
1985/// method types the client can handle. This governs opt-in types that require
1986/// additional client-side support.
1987#[cfg(feature = "unstable_auth_methods")]
1988#[serde_as]
1989#[skip_serializing_none]
1990#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1991#[serde(rename_all = "camelCase")]
1992#[non_exhaustive]
1993pub struct AuthCapabilities {
1994    /// Whether the client supports `terminal` authentication methods.
1995    ///
1996    /// Optional. Omitted or `null` both mean the client does not advertise support.
1997    /// Supplying `{}` means the agent may include `terminal` entries in its authentication methods.
1998    #[serde_as(deserialize_as = "DefaultOnError")]
1999    #[schemars(extend("x-deserialize-default-on-error" = true))]
2000    #[serde(default)]
2001    pub terminal: Option<TerminalAuthCapabilities>,
2002    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2003    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2004    /// these keys.
2005    ///
2006    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2007    #[serde_as(deserialize_as = "DefaultOnError")]
2008    #[schemars(extend("x-deserialize-default-on-error" = true))]
2009    #[serde(default)]
2010    #[serde(rename = "_meta")]
2011    pub meta: Option<Meta>,
2012}
2013
2014#[cfg(feature = "unstable_auth_methods")]
2015impl AuthCapabilities {
2016    /// Builds an empty [`AuthCapabilities`]; use builder methods to advertise supported sub-capabilities.
2017    #[must_use]
2018    pub fn new() -> Self {
2019        Self::default()
2020    }
2021
2022    /// Whether the client supports `terminal` authentication methods.
2023    ///
2024    /// Omitted or `null` both mean the client does not advertise support.
2025    /// Supplying `{}` means the agent may include `AuthMethod::Terminal` entries in its authentication methods.
2026    #[must_use]
2027    pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
2028        self.terminal = terminal.into_option();
2029        self
2030    }
2031
2032    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2033    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2034    /// these keys.
2035    ///
2036    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2037    #[must_use]
2038    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2039        self.meta = meta.into_option();
2040        self
2041    }
2042}
2043
2044/// **UNSTABLE**
2045///
2046/// This capability is not part of the spec yet, and may be removed or changed at any point.
2047///
2048/// Capabilities for terminal authentication methods.
2049///
2050/// Supplying `{}` means the client supports terminal authentication methods.
2051#[cfg(feature = "unstable_auth_methods")]
2052#[serde_as]
2053#[skip_serializing_none]
2054#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2055#[non_exhaustive]
2056pub struct TerminalAuthCapabilities {
2057    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2058    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2059    /// these keys.
2060    ///
2061    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2062    #[serde_as(deserialize_as = "DefaultOnError")]
2063    #[schemars(extend("x-deserialize-default-on-error" = true))]
2064    #[serde(default)]
2065    #[serde(rename = "_meta")]
2066    pub meta: Option<Meta>,
2067}
2068
2069#[cfg(feature = "unstable_auth_methods")]
2070impl TerminalAuthCapabilities {
2071    /// Builds an empty [`TerminalAuthCapabilities`]; use builder methods to advertise supported sub-capabilities.
2072    #[must_use]
2073    pub fn new() -> Self {
2074        Self::default()
2075    }
2076
2077    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2078    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2079    /// these keys.
2080    ///
2081    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2082    #[must_use]
2083    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2084        self.meta = meta.into_option();
2085        self
2086    }
2087}
2088
2089// Method schema
2090
2091/// Names of all methods that clients handle.
2092///
2093/// Provides a centralized definition of method names used in the protocol.
2094#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2095#[non_exhaustive]
2096pub struct ClientMethodNames {
2097    /// Method for requesting permission from the user.
2098    pub session_request_permission: &'static str,
2099    /// Notification for session updates.
2100    pub session_update: &'static str,
2101    /// Method for opening an MCP-over-ACP connection.
2102    #[cfg(feature = "unstable_mcp_over_acp")]
2103    pub mcp_connect: &'static str,
2104    /// Method for exchanging MCP-over-ACP messages.
2105    #[cfg(feature = "unstable_mcp_over_acp")]
2106    pub mcp_message: &'static str,
2107    /// Method for closing an MCP-over-ACP connection.
2108    #[cfg(feature = "unstable_mcp_over_acp")]
2109    pub mcp_disconnect: &'static str,
2110    /// Method for elicitation.
2111    #[cfg(feature = "unstable_elicitation")]
2112    pub elicitation_create: &'static str,
2113    /// Notification for elicitation completion.
2114    #[cfg(feature = "unstable_elicitation")]
2115    pub elicitation_complete: &'static str,
2116}
2117
2118/// Constant containing all client method names.
2119pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
2120    session_update: SESSION_UPDATE_NOTIFICATION,
2121    session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
2122    #[cfg(feature = "unstable_mcp_over_acp")]
2123    mcp_connect: MCP_CONNECT_METHOD_NAME,
2124    #[cfg(feature = "unstable_mcp_over_acp")]
2125    mcp_message: MCP_MESSAGE_METHOD_NAME,
2126    #[cfg(feature = "unstable_mcp_over_acp")]
2127    mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
2128    #[cfg(feature = "unstable_elicitation")]
2129    elicitation_create: ELICITATION_CREATE_METHOD_NAME,
2130    #[cfg(feature = "unstable_elicitation")]
2131    elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
2132};
2133
2134/// Notification name for session updates.
2135pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
2136/// Method name for requesting user permission.
2137pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
2138/// Method name for elicitation.
2139#[cfg(feature = "unstable_elicitation")]
2140pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
2141/// Notification name for elicitation completion.
2142#[cfg(feature = "unstable_elicitation")]
2143pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
2144
2145/// All possible requests that an agent can send to a client.
2146///
2147/// This enum is used internally for routing RPC requests. You typically won't need
2148/// to use this directly.
2149///
2150/// This enum encompasses all method calls from agent to client.
2151#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2152#[serde(untagged)]
2153#[schemars(inline)]
2154#[non_exhaustive]
2155pub enum AgentRequest {
2156    /// Requests permission from the user for an operation.
2157    ///
2158    /// Called by the agent when it needs user authorization before executing
2159    /// a potentially sensitive operation. The client should present the options
2160    /// to the user and return their decision.
2161    ///
2162    /// If the client cancels active session work via `session/cancel`, it MUST
2163    /// respond to this request with `RequestPermissionOutcome::Cancelled`.
2164    ///
2165    /// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)
2166    RequestPermissionRequest(Box<RequestPermissionRequest>),
2167    /// **UNSTABLE**
2168    ///
2169    /// This capability is not part of the spec yet, and may be removed or changed at any point.
2170    ///
2171    /// Requests structured user input via a form or URL.
2172    #[cfg(feature = "unstable_elicitation")]
2173    CreateElicitationRequest(Box<CreateElicitationRequest>),
2174    /// **UNSTABLE**
2175    ///
2176    /// This capability is not part of the spec yet, and may be removed or changed at any point.
2177    ///
2178    /// Opens an MCP-over-ACP connection.
2179    #[cfg(feature = "unstable_mcp_over_acp")]
2180    ConnectMcpRequest(Box<ConnectMcpRequest>),
2181    /// **UNSTABLE**
2182    ///
2183    /// This capability is not part of the spec yet, and may be removed or changed at any point.
2184    ///
2185    /// Exchanges an MCP-over-ACP message.
2186    #[cfg(feature = "unstable_mcp_over_acp")]
2187    MessageMcpRequest(Box<MessageMcpRequest>),
2188    /// **UNSTABLE**
2189    ///
2190    /// This capability is not part of the spec yet, and may be removed or changed at any point.
2191    ///
2192    /// Closes an MCP-over-ACP connection.
2193    #[cfg(feature = "unstable_mcp_over_acp")]
2194    DisconnectMcpRequest(Box<DisconnectMcpRequest>),
2195    /// Handles extension method requests from the agent.
2196    ///
2197    /// Allows the Agent to send an arbitrary request that is not part of the ACP spec.
2198    /// Extension methods provide a way to add custom functionality while maintaining
2199    /// protocol compatibility.
2200    ///
2201    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2202    ExtMethodRequest(Box<ExtRequest>),
2203}
2204
2205impl AgentRequest {
2206    /// Returns the corresponding method name of the request.
2207    #[must_use]
2208    pub fn method(&self) -> &str {
2209        match self {
2210            Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
2211            #[cfg(feature = "unstable_elicitation")]
2212            Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
2213            #[cfg(feature = "unstable_mcp_over_acp")]
2214            Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
2215            #[cfg(feature = "unstable_mcp_over_acp")]
2216            Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
2217            #[cfg(feature = "unstable_mcp_over_acp")]
2218            Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
2219            Self::ExtMethodRequest(ext_request) => &ext_request.method,
2220        }
2221    }
2222}
2223
2224/// All possible responses that a client can send to an agent.
2225///
2226/// This enum is used internally for routing RPC responses. You typically won't need
2227/// to use this directly - the responses are handled automatically by the connection.
2228///
2229/// These are responses to the corresponding `AgentRequest` variants.
2230#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2231#[serde(untagged)]
2232#[schemars(inline)]
2233#[non_exhaustive]
2234pub enum ClientResponse {
2235    /// Successful result returned for a `session/request_permission` request.
2236    RequestPermissionResponse(Box<RequestPermissionResponse>),
2237    /// Successful result returned for a `elicitation/create` request.
2238    #[cfg(feature = "unstable_elicitation")]
2239    CreateElicitationResponse(Box<CreateElicitationResponse>),
2240    /// Successful result returned for a `mcp/connect` request.
2241    #[cfg(feature = "unstable_mcp_over_acp")]
2242    ConnectMcpResponse(Box<ConnectMcpResponse>),
2243    /// Successful result returned for a `mcp/disconnect` request.
2244    #[cfg(feature = "unstable_mcp_over_acp")]
2245    DisconnectMcpResponse(#[serde(default)] Box<DisconnectMcpResponse>),
2246    /// Successful result returned by an MCP-over-ACP `mcp/message` request.
2247    #[cfg(feature = "unstable_mcp_over_acp")]
2248    MessageMcpResponse(Box<MessageMcpResponse>),
2249    /// Successful result returned by an extension method outside the core ACP method set.
2250    ExtMethodResponse(Box<ExtResponse>),
2251}
2252
2253/// All possible notifications that an agent can send to a client.
2254///
2255/// This enum is used internally for routing RPC notifications. You typically won't need
2256/// to use this directly.
2257///
2258/// Notifications do not expect a response.
2259#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2260#[serde(untagged)]
2261#[schemars(inline)]
2262#[non_exhaustive]
2263pub enum AgentNotification {
2264    /// Handles session update notifications from the agent.
2265    ///
2266    /// This is a notification endpoint (no response expected) that receives
2267    /// updates about session activity, including message updates, message chunks,
2268    /// tool calls, and execution plans.
2269    ///
2270    /// Note: Clients SHOULD continue accepting tool call updates even after
2271    /// sending a `session/cancel` notification, as the agent may send final
2272    /// updates before reporting an idle `state_update` with the cancelled
2273    /// stop reason.
2274    ///
2275    /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-lifecycle#3-agent-reports-output)
2276    UpdateSessionNotification(Box<UpdateSessionNotification>),
2277    /// **UNSTABLE**
2278    ///
2279    /// This capability is not part of the spec yet, and may be removed or changed at any point.
2280    ///
2281    /// Notification that a URL-based elicitation has completed.
2282    #[cfg(feature = "unstable_elicitation")]
2283    CompleteElicitationNotification(Box<CompleteElicitationNotification>),
2284    /// **UNSTABLE**
2285    ///
2286    /// This capability is not part of the spec yet, and may be removed or changed at any point.
2287    ///
2288    /// Receives an MCP-over-ACP notification.
2289    #[cfg(feature = "unstable_mcp_over_acp")]
2290    MessageMcpNotification(Box<MessageMcpNotification>),
2291    /// Handles extension notifications from the agent.
2292    ///
2293    /// Allows the Agent to send an arbitrary notification that is not part of the ACP spec.
2294    /// Extension notifications provide a way to send one-way messages for custom functionality
2295    /// while maintaining protocol compatibility.
2296    ///
2297    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2298    ExtNotification(Box<ExtNotification>),
2299}
2300
2301impl AgentNotification {
2302    /// Returns the corresponding method name of the notification.
2303    #[must_use]
2304    pub fn method(&self) -> &str {
2305        match self {
2306            Self::UpdateSessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
2307            #[cfg(feature = "unstable_elicitation")]
2308            Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
2309            #[cfg(feature = "unstable_mcp_over_acp")]
2310            Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
2311            Self::ExtNotification(ext_notification) => &ext_notification.method,
2312        }
2313    }
2314}
2315
2316#[cfg(test)]
2317mod tests {
2318    use super::*;
2319
2320    #[cfg(feature = "unstable_auth_methods")]
2321    #[test]
2322    fn test_client_capabilities_auth_defaults_on_malformed_value() {
2323        use serde_json::json;
2324
2325        let capabilities: ClientCapabilities = serde_json::from_value(json!({
2326            "auth": false
2327        }))
2328        .unwrap();
2329
2330        assert_eq!(capabilities.auth, None);
2331    }
2332
2333    #[test]
2334    fn test_serialization_behavior() {
2335        use serde_json::json;
2336
2337        assert_eq!(
2338            serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
2339            SessionInfoUpdate {
2340                title: MaybeUndefined::Undefined,
2341                updated_at: MaybeUndefined::Undefined,
2342                meta: MaybeUndefined::Undefined
2343            }
2344        );
2345        assert_eq!(
2346            serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
2347                .unwrap(),
2348            SessionInfoUpdate {
2349                title: MaybeUndefined::Null,
2350                updated_at: MaybeUndefined::Null,
2351                meta: MaybeUndefined::Undefined
2352            }
2353        );
2354        assert_eq!(
2355            serde_json::from_value::<SessionInfoUpdate>(
2356                json!({"title": "title", "updatedAt": "timestamp"})
2357            )
2358            .unwrap(),
2359            SessionInfoUpdate {
2360                title: MaybeUndefined::Value("title".to_string()),
2361                updated_at: MaybeUndefined::Value("timestamp".to_string()),
2362                meta: MaybeUndefined::Undefined
2363            }
2364        );
2365
2366        let clear_meta =
2367            serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": null})).unwrap();
2368        assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2369
2370        let mut meta = Meta::new();
2371        meta.insert("source".to_string(), json!("session-info"));
2372
2373        assert_eq!(
2374            serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": {
2375                "source": "session-info"
2376            }}))
2377            .unwrap()
2378            .meta,
2379            MaybeUndefined::Value(meta.clone())
2380        );
2381
2382        assert_eq!(
2383            serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
2384            json!({})
2385        );
2386
2387        assert_eq!(
2388            serde_json::to_value(SessionInfoUpdate::new().meta(None::<Meta>)).unwrap(),
2389            json!({"_meta": null})
2390        );
2391
2392        assert_eq!(
2393            serde_json::to_value(SessionInfoUpdate::new().meta(meta)).unwrap(),
2394            json!({"_meta": {
2395                "source": "session-info"
2396            }})
2397        );
2398        assert_eq!(
2399            serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
2400            json!({"title": "title"})
2401        );
2402        assert_eq!(
2403            serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
2404            json!({"title": null})
2405        );
2406        assert_eq!(
2407            serde_json::to_value(
2408                SessionInfoUpdate::new()
2409                    .title("title")
2410                    .title(MaybeUndefined::Undefined)
2411            )
2412            .unwrap(),
2413            json!({})
2414        );
2415    }
2416
2417    #[test]
2418    fn test_content_chunk_message_id_serialization() {
2419        use serde_json::json;
2420
2421        assert_eq!(
2422            serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
2423                ContentBlock::Text(crate::v2::TextContent::new("Hello")),
2424                "msg_agent_c42b9",
2425            )))
2426            .unwrap(),
2427            json!({
2428                "sessionUpdate": "agent_message_chunk",
2429                "messageId": "msg_agent_c42b9",
2430                "content": {
2431                    "type": "text",
2432                    "text": "Hello"
2433                }
2434            })
2435        );
2436
2437        let err = serde_json::from_value::<ContentChunk>(json!({
2438            "content": {
2439                "type": "text",
2440                "text": "Hello"
2441            }
2442        }))
2443        .unwrap_err();
2444
2445        assert!(err.to_string().contains("messageId"), "{err}");
2446    }
2447
2448    #[test]
2449    fn test_tool_call_content_chunk_serialization() {
2450        use serde_json::json;
2451
2452        assert_eq!(
2453            serde_json::to_value(SessionUpdate::ToolCallContentChunk(
2454                ToolCallContentChunk::new(
2455                    "call_001",
2456                    crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
2457                )
2458            ))
2459            .unwrap(),
2460            json!({
2461                "sessionUpdate": "tool_call_content_chunk",
2462                "toolCallId": "call_001",
2463                "content": {
2464                    "type": "content",
2465                    "content": {
2466                        "type": "text",
2467                        "text": "partial output"
2468                    }
2469                }
2470            })
2471        );
2472
2473        let err = serde_json::from_value::<ToolCallContentChunk>(json!({
2474            "content": {
2475                "type": "content",
2476                "content": {
2477                    "type": "text",
2478                    "text": "partial output"
2479                }
2480            }
2481        }))
2482        .unwrap_err();
2483
2484        assert!(err.to_string().contains("toolCallId"), "{err}");
2485    }
2486
2487    #[test]
2488    fn test_full_message_serialization() {
2489        use serde_json::json;
2490
2491        assert_eq!(
2492            serde_json::to_value(SessionUpdate::UserMessage(
2493                UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
2494                    crate::v2::TextContent::new("Hello")
2495                )])
2496            ))
2497            .unwrap(),
2498            json!({
2499                "sessionUpdate": "user_message",
2500                "messageId": "msg_user_8f7a1",
2501                "content": [
2502                    {
2503                        "type": "text",
2504                        "text": "Hello"
2505                    }
2506                ]
2507            })
2508        );
2509
2510        assert_eq!(
2511            serde_json::to_value(SessionUpdate::AgentMessage(
2512                AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
2513                    crate::v2::TextContent::new("Hello")
2514                )])
2515            ))
2516            .unwrap(),
2517            json!({
2518                "sessionUpdate": "agent_message",
2519                "messageId": "msg_agent_c42b9",
2520                "content": [
2521                    {
2522                        "type": "text",
2523                        "text": "Hello"
2524                    }
2525                ]
2526            })
2527        );
2528
2529        assert_eq!(
2530            serde_json::to_value(SessionUpdate::AgentThought(
2531                AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
2532                    crate::v2::TextContent::new("Need to inspect the call sites first.")
2533                )])
2534            ))
2535            .unwrap(),
2536            json!({
2537                "sessionUpdate": "agent_thought",
2538                "messageId": "msg_thought_a12",
2539                "content": [
2540                    {
2541                        "type": "text",
2542                        "text": "Need to inspect the call sites first."
2543                    }
2544                ]
2545            })
2546        );
2547    }
2548
2549    #[test]
2550    fn test_message_upsert_serialization() {
2551        use serde_json::json;
2552
2553        assert_eq!(
2554            serde_json::to_value(SessionUpdate::UserMessage(
2555                UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
2556            ))
2557            .unwrap(),
2558            json!({
2559                "sessionUpdate": "user_message",
2560                "messageId": "msg_empty",
2561                "content": []
2562            })
2563        );
2564
2565        let empty = serde_json::from_value::<UserMessage>(json!({
2566            "messageId": "msg_empty",
2567            "content": []
2568        }))
2569        .unwrap();
2570        assert!(matches!(
2571            empty.content,
2572            MaybeUndefined::Value(ref content) if content.is_empty()
2573        ));
2574
2575        let patch = serde_json::from_value::<AgentMessage>(json!({
2576            "messageId": "msg_agent_c42b9"
2577        }))
2578        .unwrap();
2579        assert_eq!(patch.content, MaybeUndefined::Undefined);
2580        assert_eq!(patch.meta, MaybeUndefined::Undefined);
2581
2582        let malformed_meta = serde_json::from_value::<AgentMessage>(json!({
2583            "messageId": "msg_agent_c42b9",
2584            "_meta": false
2585        }))
2586        .unwrap();
2587        assert_eq!(malformed_meta.meta, MaybeUndefined::Undefined);
2588
2589        let patch = serde_json::from_value::<AgentThought>(json!({
2590            "messageId": "msg_thought_a12"
2591        }))
2592        .unwrap();
2593        assert_eq!(patch.content, MaybeUndefined::Undefined);
2594
2595        let clear = serde_json::from_value::<UserMessage>(json!({
2596            "messageId": "msg_user_8f7a1",
2597            "content": null
2598        }))
2599        .unwrap();
2600        assert_eq!(clear.content, MaybeUndefined::Null);
2601
2602        let clear_meta = serde_json::from_value::<UserMessage>(json!({
2603            "messageId": "msg_user_8f7a1",
2604            "_meta": null
2605        }))
2606        .unwrap();
2607        assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2608
2609        let mut meta = Meta::new();
2610        meta.insert("source".to_string(), json!("replay"));
2611
2612        assert_eq!(
2613            serde_json::to_value(SessionUpdate::UserMessage(
2614                UserMessage::new("msg_user_8f7a1").meta(meta)
2615            ))
2616            .unwrap(),
2617            json!({
2618                "sessionUpdate": "user_message",
2619                "messageId": "msg_user_8f7a1",
2620                "_meta": {
2621                    "source": "replay"
2622                }
2623            })
2624        );
2625
2626        assert_eq!(
2627            serde_json::to_value(SessionUpdate::UserMessage(
2628                UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
2629            ))
2630            .unwrap(),
2631            json!({
2632                "sessionUpdate": "user_message",
2633                "messageId": "msg_user_8f7a1",
2634                "_meta": null
2635            })
2636        );
2637    }
2638
2639    #[test]
2640    fn test_usage_update_serialization() {
2641        use serde_json::json;
2642
2643        assert_eq!(
2644            serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
2645                53_000, 200_000
2646            )))
2647            .unwrap(),
2648            json!({
2649                "sessionUpdate": "usage_update",
2650                "used": 53000,
2651                "size": 200_000
2652            })
2653        );
2654
2655        assert_eq!(
2656            serde_json::to_value(SessionUpdate::UsageUpdate(
2657                UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
2658            ))
2659            .unwrap(),
2660            json!({
2661                "sessionUpdate": "usage_update",
2662                "used": 53000,
2663                "size": 200_000,
2664                "cost": {
2665                    "amount": 0.045,
2666                    "currency": "USD"
2667                }
2668            })
2669        );
2670
2671        let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
2672            "sessionUpdate": "usage_update",
2673            "used": 53000,
2674            "size": 200_000,
2675            "cost": null
2676        }))
2677        .unwrap() else {
2678            panic!("expected usage update");
2679        };
2680
2681        assert_eq!(update.cost, None);
2682    }
2683
2684    #[test]
2685    fn test_state_update_serialization() {
2686        use serde_json::json;
2687
2688        assert_eq!(
2689            serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Running(
2690                RunningStateUpdate::new()
2691            )))
2692            .unwrap(),
2693            json!({
2694                "sessionUpdate": "state_update",
2695                "state": "running"
2696            })
2697        );
2698
2699        assert_eq!(
2700            serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Idle(
2701                IdleStateUpdate::new().stop_reason(StopReason::EndTurn)
2702            )))
2703            .unwrap(),
2704            json!({
2705                "sessionUpdate": "state_update",
2706                "state": "idle",
2707                "stopReason": "end_turn"
2708            })
2709        );
2710
2711        let SessionUpdate::StateUpdate(update) = serde_json::from_value(json!({
2712            "sessionUpdate": "state_update",
2713            "state": "requires_action"
2714        }))
2715        .unwrap() else {
2716            panic!("expected state update");
2717        };
2718
2719        assert!(matches!(update, StateUpdate::RequiresAction(_)));
2720
2721        let SessionUpdate::StateUpdate(StateUpdate::Idle(update)) = serde_json::from_value(json!({
2722            "sessionUpdate": "state_update",
2723            "state": "idle",
2724            "stopReason": null
2725        }))
2726        .unwrap() else {
2727            panic!("expected idle state update");
2728        };
2729
2730        assert_eq!(update.stop_reason, None);
2731
2732        let SessionUpdate::StateUpdate(StateUpdate::Other(update)) =
2733            serde_json::from_value(json!({
2734                "sessionUpdate": "state_update",
2735                "state": "_paused",
2736                "label": "Paused"
2737            }))
2738            .unwrap()
2739        else {
2740            panic!("expected unknown state update");
2741        };
2742
2743        assert_eq!(update.state, "_paused");
2744        assert_eq!(update.fields["label"], json!("Paused"));
2745    }
2746
2747    #[test]
2748    fn session_update_preserves_unknown_variant() {
2749        use serde_json::json;
2750
2751        let update: SessionUpdate = serde_json::from_value(json!({
2752            "sessionUpdate": "_status_badge",
2753            "label": "Indexing",
2754            "progress": 0.5
2755        }))
2756        .unwrap();
2757
2758        let SessionUpdate::Other(unknown) = update else {
2759            panic!("expected unknown session update");
2760        };
2761
2762        assert_eq!(unknown.session_update, "_status_badge");
2763        assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
2764        assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
2765
2766        assert_eq!(
2767            serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
2768            json!({
2769                "sessionUpdate": "_status_badge",
2770                "label": "Indexing",
2771                "progress": 0.5
2772            })
2773        );
2774    }
2775
2776    #[test]
2777    fn terminal_session_updates_use_known_discriminators() {
2778        use serde_json::json;
2779
2780        assert_eq!(
2781            serde_json::to_value(SessionUpdate::TerminalUpdate(
2782                TerminalUpdate::new("term_1").command("cargo test")
2783            ))
2784            .unwrap(),
2785            json!({
2786                "sessionUpdate": "terminal_update",
2787                "terminalId": "term_1",
2788                "command": "cargo test"
2789            })
2790        );
2791        assert_eq!(
2792            serde_json::to_value(SessionUpdate::TerminalOutputChunk(
2793                TerminalOutputChunk::new("term_1", "dGVzdAo=")
2794            ))
2795            .unwrap(),
2796            json!({
2797                "sessionUpdate": "terminal_output_chunk",
2798                "terminalId": "term_1",
2799                "data": "dGVzdAo="
2800            })
2801        );
2802    }
2803
2804    #[test]
2805    fn session_update_does_not_hide_malformed_known_terminal_variants() {
2806        use serde_json::json;
2807
2808        assert!(
2809            serde_json::from_value::<SessionUpdate>(json!({
2810                "sessionUpdate": "terminal_update"
2811            }))
2812            .is_err()
2813        );
2814        assert!(
2815            serde_json::from_value::<SessionUpdate>(json!({
2816                "sessionUpdate": "terminal_output_chunk",
2817                "terminalId": "term_1"
2818            }))
2819            .is_err()
2820        );
2821    }
2822
2823    #[test]
2824    fn test_plan_update_serialization() {
2825        use serde_json::json;
2826
2827        let plan_update =
2828            SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
2829                "plan-1",
2830                vec![crate::v2::PlanEntry::new(
2831                    "Step 1",
2832                    crate::v2::PlanEntryPriority::High,
2833                    crate::v2::PlanEntryStatus::Pending,
2834                )],
2835            )));
2836
2837        assert_eq!(
2838            serde_json::to_value(plan_update).unwrap(),
2839            json!({
2840                "sessionUpdate": "plan_update",
2841                "plan": {
2842                    "type": "items",
2843                    "planId": "plan-1",
2844                    "entries": [
2845                        {
2846                            "content": "Step 1",
2847                            "priority": "high",
2848                            "status": "pending"
2849                        }
2850                    ]
2851                }
2852            })
2853        );
2854    }
2855
2856    #[cfg(feature = "unstable_plan_operations")]
2857    #[test]
2858    fn test_plan_removed_serialization() {
2859        use serde_json::json;
2860
2861        assert_eq!(
2862            serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
2863            json!({
2864                "sessionUpdate": "plan_removed",
2865                "planId": "plan-1"
2866            })
2867        );
2868    }
2869
2870    #[test]
2871    fn available_command_input_preserves_unknown_typed_variant() {
2872        use serde_json::json;
2873
2874        let input: AvailableCommandInput = serde_json::from_value(json!({
2875            "type": "_choices",
2876            "hint": "Pick one",
2877            "options": ["fast", "careful"]
2878        }))
2879        .unwrap();
2880
2881        let AvailableCommandInput::Other(unknown) = input else {
2882            panic!("expected unknown command input");
2883        };
2884
2885        assert_eq!(unknown.type_, "_choices");
2886        assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
2887        assert_eq!(
2888            unknown.fields.get("options"),
2889            Some(&json!(["fast", "careful"]))
2890        );
2891        assert_eq!(
2892            serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
2893            json!({
2894                "type": "_choices",
2895                "hint": "Pick one",
2896                "options": ["fast", "careful"]
2897            })
2898        );
2899    }
2900
2901    #[test]
2902    fn available_command_input_text_uses_type_discriminator() {
2903        use serde_json::json;
2904
2905        let input = AvailableCommandInput::Text(TextCommandInput::new("Describe changes"));
2906
2907        let json = serde_json::to_value(&input).unwrap();
2908        assert_eq!(
2909            json,
2910            json!({
2911                "type": "text",
2912                "hint": "Describe changes"
2913            })
2914        );
2915
2916        let roundtripped: AvailableCommandInput = serde_json::from_value(json).unwrap();
2917        assert!(matches!(roundtripped, AvailableCommandInput::Text(_)));
2918    }
2919
2920    #[test]
2921    fn request_permission_subject_tool_call_uses_type_discriminator() {
2922        use serde_json::json;
2923
2924        let subject = RequestPermissionSubject::from(ToolCallUpdate::new("call_001"));
2925
2926        let json = serde_json::to_value(&subject).unwrap();
2927        assert_eq!(
2928            json,
2929            json!({
2930                "type": "tool_call",
2931                "toolCall": {
2932                    "toolCallId": "call_001"
2933                }
2934            })
2935        );
2936
2937        let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
2938        assert!(matches!(
2939            roundtripped,
2940            RequestPermissionSubject::ToolCall(_)
2941        ));
2942    }
2943
2944    #[test]
2945    fn request_permission_subject_command_uses_type_discriminator() {
2946        use serde_json::json;
2947
2948        let mut meta = Meta::new();
2949        meta.insert("source".to_string(), json!("shell"));
2950        let subject = RequestPermissionSubject::from(
2951            CommandPermissionSubject::new("cargo test", "/workspace/project")
2952                .tool_call_id("call_001")
2953                .terminal_id("term_1")
2954                .meta(meta),
2955        );
2956
2957        let json = serde_json::to_value(&subject).unwrap();
2958        assert_eq!(
2959            json,
2960            json!({
2961                "type": "command",
2962                "command": "cargo test",
2963                "cwd": "/workspace/project",
2964                "toolCallId": "call_001",
2965                "terminalId": "term_1",
2966                "_meta": {
2967                    "source": "shell"
2968                }
2969            })
2970        );
2971
2972        let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
2973        assert!(matches!(roundtripped, RequestPermissionSubject::Command(_)));
2974    }
2975
2976    #[test]
2977    fn command_permission_subject_treats_optional_association_nulls_as_omitted() {
2978        use serde_json::json;
2979
2980        let subject: RequestPermissionSubject = serde_json::from_value(json!({
2981            "type": "command",
2982            "command": "cargo test",
2983            "cwd": "/workspace/project",
2984            "toolCallId": null,
2985            "terminalId": null,
2986            "_meta": null
2987        }))
2988        .unwrap();
2989
2990        let RequestPermissionSubject::Command(subject) = subject else {
2991            panic!("expected command permission subject");
2992        };
2993        assert_eq!(subject.cwd, AbsolutePath::new("/workspace/project"));
2994        assert_eq!(subject.tool_call_id, None);
2995        assert_eq!(subject.terminal_id, None);
2996        assert_eq!(subject.meta, None);
2997        assert_eq!(
2998            serde_json::to_value(RequestPermissionSubject::Command(subject)).unwrap(),
2999            json!({
3000                "type": "command",
3001                "command": "cargo test",
3002                "cwd": "/workspace/project"
3003            })
3004        );
3005    }
3006
3007    #[test]
3008    fn request_permission_subject_preserves_unknown_variant() {
3009        use serde_json::json;
3010
3011        let subject: RequestPermissionSubject = serde_json::from_value(json!({
3012            "type": "_review",
3013            "reason": "needs-review",
3014            "retryAfterSeconds": 30
3015        }))
3016        .unwrap();
3017
3018        let RequestPermissionSubject::Other(unknown) = subject else {
3019            panic!("expected unknown permission subject");
3020        };
3021
3022        assert_eq!(unknown.type_, "_review");
3023        assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
3024        assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
3025        assert_eq!(
3026            serde_json::to_value(RequestPermissionSubject::Other(unknown)).unwrap(),
3027            json!({
3028                "type": "_review",
3029                "reason": "needs-review",
3030                "retryAfterSeconds": 30
3031            })
3032        );
3033    }
3034
3035    #[test]
3036    fn request_permission_subject_unknown_does_not_hide_malformed_known_variant() {
3037        use serde_json::json;
3038
3039        assert!(
3040            serde_json::from_value::<RequestPermissionSubject>(json!({
3041                "type": "tool_call"
3042            }))
3043            .is_err()
3044        );
3045        assert!(
3046            serde_json::from_value::<RequestPermissionSubject>(json!({
3047                "type": 1
3048            }))
3049            .is_err()
3050        );
3051        assert!(
3052            serde_json::from_value::<RequestPermissionSubject>(json!({
3053                "type": "command",
3054                "cwd": "/workspace/project"
3055            }))
3056            .is_err()
3057        );
3058        assert!(
3059            serde_json::from_value::<RequestPermissionSubject>(json!({
3060                "type": "command",
3061                "command": "cargo test"
3062            }))
3063            .is_err()
3064        );
3065        assert!(
3066            serde_json::from_value::<RequestPermissionSubject>(json!({
3067                "type": "command",
3068                "command": "cargo test",
3069                "cwd": null
3070            }))
3071            .is_err()
3072        );
3073    }
3074
3075    #[test]
3076    fn request_permission_title_and_description_are_separate_from_tool_call_content() {
3077        use serde_json::json;
3078
3079        let request =
3080            RequestPermissionRequest::new("sess_abc123def456", "Approve file edit?", Vec::new())
3081                .description("Allow this tool to edit src/main.rs?")
3082                .subject(RequestPermissionSubject::from(ToolCallUpdate::new(
3083                    "call_001",
3084                )));
3085
3086        assert_eq!(
3087            serde_json::to_value(request).unwrap(),
3088            json!({
3089                "sessionId": "sess_abc123def456",
3090                "title": "Approve file edit?",
3091                "description": "Allow this tool to edit src/main.rs?",
3092                "subject": {
3093                    "type": "tool_call",
3094                    "toolCall": {
3095                        "toolCallId": "call_001"
3096                    }
3097                },
3098                "options": []
3099            })
3100        );
3101    }
3102
3103    #[test]
3104    fn request_permission_requires_title_and_allows_missing_subject() {
3105        use serde_json::json;
3106
3107        let request = RequestPermissionRequest::new(
3108            "sess_abc123def456",
3109            "Approve elevated permissions?",
3110            Vec::new(),
3111        );
3112
3113        assert_eq!(
3114            serde_json::to_value(request).unwrap(),
3115            json!({
3116                "sessionId": "sess_abc123def456",
3117                "title": "Approve elevated permissions?",
3118                "options": []
3119            })
3120        );
3121
3122        let missing_subject: RequestPermissionRequest = serde_json::from_value(json!({
3123            "sessionId": "sess_abc123def456",
3124            "title": "Approve elevated permissions?",
3125            "options": []
3126        }))
3127        .unwrap();
3128        assert!(missing_subject.subject.is_none());
3129
3130        let null_subject: RequestPermissionRequest = serde_json::from_value(json!({
3131            "sessionId": "sess_abc123def456",
3132            "title": "Approve elevated permissions?",
3133            "subject": null,
3134            "options": []
3135        }))
3136        .unwrap();
3137        assert!(null_subject.subject.is_none());
3138
3139        assert!(
3140            serde_json::from_value::<RequestPermissionRequest>(json!({
3141                "sessionId": "sess_abc123def456",
3142                "options": []
3143            }))
3144            .is_err()
3145        );
3146    }
3147
3148    #[test]
3149    fn request_permission_outcome_preserves_unknown_variant() {
3150        use serde_json::json;
3151
3152        let outcome: RequestPermissionOutcome = serde_json::from_value(json!({
3153            "outcome": "_defer",
3154            "reason": "needs-review",
3155            "retryAfterSeconds": 30
3156        }))
3157        .unwrap();
3158
3159        let RequestPermissionOutcome::Other(unknown) = outcome else {
3160            panic!("expected unknown permission outcome");
3161        };
3162
3163        assert_eq!(unknown.outcome, "_defer");
3164        assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
3165        assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
3166        assert_eq!(
3167            serde_json::to_value(RequestPermissionOutcome::Other(unknown)).unwrap(),
3168            json!({
3169                "outcome": "_defer",
3170                "reason": "needs-review",
3171                "retryAfterSeconds": 30
3172            })
3173        );
3174    }
3175
3176    #[test]
3177    fn request_permission_outcome_unknown_does_not_hide_malformed_known_variant() {
3178        use serde_json::json;
3179
3180        assert!(
3181            serde_json::from_value::<RequestPermissionOutcome>(json!({
3182                "outcome": "selected"
3183            }))
3184            .is_err()
3185        );
3186        assert!(
3187            serde_json::from_value::<RequestPermissionOutcome>(json!({
3188                "outcome": 1
3189            }))
3190            .is_err()
3191        );
3192    }
3193
3194    #[test]
3195    fn available_command_input_unknown_does_not_hide_malformed_text_variant() {
3196        use serde_json::json;
3197
3198        assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
3199        assert!(
3200            serde_json::from_value::<AvailableCommandInput>(json!({
3201                "hint": "Pick one"
3202            }))
3203            .is_err()
3204        );
3205        assert!(
3206            serde_json::from_value::<AvailableCommandInput>(json!({
3207                "type": 1,
3208                "hint": "Pick one"
3209            }))
3210            .is_err()
3211        );
3212        assert!(
3213            serde_json::from_value::<OtherAvailableCommandInput>(json!({
3214                "type": "text",
3215                "hint": "Pick one"
3216            }))
3217            .is_err()
3218        );
3219    }
3220
3221    #[cfg(feature = "unstable_nes")]
3222    #[test]
3223    fn test_client_capabilities_position_encodings_serialization() {
3224        use serde_json::json;
3225
3226        let capabilities = ClientCapabilities::new().position_encodings(vec![
3227            PositionEncodingKind::Utf32,
3228            PositionEncodingKind::Utf16,
3229        ]);
3230        let json = serde_json::to_value(&capabilities).unwrap();
3231
3232        assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
3233    }
3234
3235    #[cfg(feature = "unstable_mcp_over_acp")]
3236    #[test]
3237    fn test_agent_mcp_request_method_names() {
3238        use serde_json::json;
3239
3240        let params: serde_json::Map<String, serde_json::Value> =
3241            [("cursor".to_string(), json!("abc"))].into_iter().collect();
3242
3243        assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
3244        assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
3245        assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
3246
3247        assert_eq!(
3248            AgentRequest::ConnectMcpRequest(Box::new(ConnectMcpRequest::new("server-1"))).method(),
3249            "mcp/connect"
3250        );
3251        assert_eq!(
3252            AgentRequest::MessageMcpRequest(Box::new(MessageMcpRequest::new(
3253                "conn-1",
3254                "tools/list"
3255            )))
3256            .method(),
3257            "mcp/message"
3258        );
3259        assert_eq!(
3260            AgentRequest::DisconnectMcpRequest(Box::new(DisconnectMcpRequest::new("conn-1")))
3261                .method(),
3262            "mcp/disconnect"
3263        );
3264        assert_eq!(
3265            AgentNotification::MessageMcpNotification(Box::new(MessageMcpNotification::new(
3266                "conn-1",
3267                "notifications/progress"
3268            )))
3269            .method(),
3270            "mcp/message"
3271        );
3272
3273        assert_eq!(
3274            serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
3275            json!({ "serverId": "server-1" })
3276        );
3277        assert_eq!(
3278            serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
3279            json!({ "connectionId": "conn-1" })
3280        );
3281        assert_eq!(
3282            serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
3283                .unwrap(),
3284            json!({
3285                "connectionId": "conn-1",
3286                "method": "tools/list",
3287                "params": { "cursor": "abc" }
3288            })
3289        );
3290        assert_eq!(
3291            serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
3292            json!({ "connectionId": "conn-1" })
3293        );
3294        assert_eq!(
3295            serde_json::to_value(MessageMcpNotification::new(
3296                "conn-1",
3297                "notifications/progress"
3298            ))
3299            .unwrap(),
3300            json!({
3301                "connectionId": "conn-1",
3302                "method": "notifications/progress"
3303            })
3304        );
3305
3306        let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
3307            "connectionId": "conn-1",
3308            "method": "tools/list",
3309            "params": null
3310        }))
3311        .unwrap();
3312        assert_eq!(request_with_null_params.params, None);
3313    }
3314
3315    #[cfg(feature = "unstable_auth_methods")]
3316    #[test]
3317    fn test_auth_capabilities_serialize_terminal_support_as_object() {
3318        use serde_json::json;
3319
3320        let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
3321
3322        assert_eq!(
3323            serde_json::to_value(&capabilities).unwrap(),
3324            json!({
3325                "terminal": {}
3326            })
3327        );
3328
3329        let deserialized: AuthCapabilities = serde_json::from_value(json!({
3330            "terminal": false
3331        }))
3332        .unwrap();
3333        assert!(deserialized.terminal.is_none());
3334    }
3335
3336    #[test]
3337    fn request_permission_request_rejects_malformed_options() {
3338        use serde_json::json;
3339
3340        assert!(
3341            serde_json::from_value::<RequestPermissionRequest>(json!({
3342                "sessionId": "sess-1",
3343                "title": "Run tool?",
3344                "options": "not-an-array"
3345            }))
3346            .is_err()
3347        );
3348        assert!(
3349            serde_json::from_value::<RequestPermissionRequest>(json!({
3350                "sessionId": "sess-1",
3351                "title": "Run tool?",
3352                "options": [{"optionId": "allow"}]
3353            }))
3354            .is_err()
3355        );
3356    }
3357
3358    #[cfg(feature = "unstable_plan_operations")]
3359    #[test]
3360    fn malformed_plan_removed_is_not_hidden_as_unknown_update() {
3361        use serde_json::json;
3362
3363        assert!(
3364            serde_json::from_value::<SessionUpdate>(json!({
3365                "sessionUpdate": "plan_removed"
3366            }))
3367            .is_err()
3368        );
3369    }
3370}