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