Skip to main content

agent_client_protocol_schema/v1/
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::{path::PathBuf, sync::Arc};
7
8use derive_more::{Display, From};
9#[cfg(all(feature = "schemars", feature = "unstable_subagents"))]
10use schemars::Schema;
11use serde::{Deserialize, Serialize};
12use serde_with::{DefaultOnError, DefaultOnNull, VecSkipError, serde_as, skip_serializing_none};
13#[cfg(feature = "unstable_subagents")]
14use std::collections::BTreeMap;
15
16#[cfg(feature = "unstable_subagents")]
17use super::Error;
18#[cfg(all(
19    feature = "unstable_subagents",
20    feature = "unstable_end_turn_token_usage"
21))]
22use super::Usage;
23use super::{
24    CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
25    ElicitationCapabilities,
26};
27use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined};
28
29use super::{
30    ContentBlock, EnvVariable, ExtNotification, ExtRequest, ExtResponse, Meta, Plan,
31    SessionConfigOption, SessionId, SessionModeId, ToolCall, ToolCallUpdate,
32};
33#[cfg(feature = "unstable_plan_operations")]
34use super::{PlanCapabilities, PlanRemoved, PlanUpdate};
35
36#[cfg(feature = "unstable_mcp_over_acp")]
37use super::mcp::{MCP_MESSAGE_METHOD_NAME, MessageMcpRequest, MessageMcpResponse};
38
39// Session updates
40
41/// Notification containing a session update from the agent.
42///
43/// Used to stream real-time progress and results during prompt processing.
44///
45/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/v1/prompt-turn#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 SessionNotification {
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/v1/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 SessionNotification {
71    /// Builds [`SessionNotification`] 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/v1/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 during session processing.
94///
95/// These updates provide real-time feedback about the agent's progress.
96///
97/// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/v1/prompt-turn#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#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "sessionUpdate"})))]
102#[non_exhaustive]
103pub enum SessionUpdate {
104    /// A chunk of the user's message being streamed.
105    UserMessageChunk(ContentChunk),
106    /// A chunk of the agent's response being streamed.
107    AgentMessageChunk(ContentChunk),
108    /// A chunk of the agent's internal reasoning being streamed.
109    AgentThoughtChunk(ContentChunk),
110    /// Notification that a new tool call has been initiated.
111    ToolCall(ToolCall),
112    /// Update on the status or results of a tool call.
113    ToolCallUpdate(ToolCallUpdate),
114    /// The agent's execution plan for complex tasks.
115    /// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/v1/agent-plan)
116    Plan(Plan),
117    /// **UNSTABLE**
118    ///
119    /// This capability is not part of the spec yet, and may be removed or changed at any point.
120    ///
121    /// A content update for a plan identified by ID.
122    #[cfg(feature = "unstable_plan_operations")]
123    PlanUpdate(PlanUpdate),
124    /// **UNSTABLE**
125    ///
126    /// This capability is not part of the spec yet, and may be removed or changed at any point.
127    ///
128    /// Removal notice for a plan identified by ID.
129    #[cfg(feature = "unstable_plan_operations")]
130    PlanRemoved(PlanRemoved),
131    /// Available commands are ready or have changed
132    AvailableCommandsUpdate(AvailableCommandsUpdate),
133    /// The current mode of the session has changed
134    ///
135    /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/v1/session-modes)
136    CurrentModeUpdate(CurrentModeUpdate),
137    /// Session configuration options have been updated.
138    ConfigOptionUpdate(ConfigOptionUpdate),
139    /// Session metadata has been updated (title, timestamps, custom metadata)
140    SessionInfoUpdate(SessionInfoUpdate),
141    /// Context window and cost update for the session.
142    UsageUpdate(UsageUpdate),
143    /// Information for the user that is not part of session history.
144    ///
145    /// Agents MUST only send this update when the Client advertised
146    /// the `session.notices` Client capability.
147    Notice(Notice),
148    /// A context compaction has been created or updated.
149    ///
150    /// Agents MUST only send this update when the Client advertised
151    /// the `session.compaction` Client capability.
152    CompactionUpdate(CompactionUpdate),
153    /// A content block appended to a context compaction's retained summary.
154    ///
155    /// Agents MUST only send this update when the Client advertised
156    /// the `session.compaction` Client capability.
157    CompactionSummaryChunk(CompactionSummaryChunk),
158    /// **UNSTABLE**
159    ///
160    /// This capability is not part of the spec yet, and may be removed or changed at any point.
161    ///
162    /// Announces a child session created and owned by this session, or updates
163    /// that ownership association's metadata.
164    #[cfg(feature = "unstable_subagents")]
165    SubagentUpdate(SubagentUpdate),
166    /// **UNSTABLE**
167    ///
168    /// This capability is not part of the spec yet, and may be removed or changed at any point.
169    ///
170    /// A message upsert observed in this session's transcript, sent to or
171    /// received from another session.
172    #[cfg(feature = "unstable_subagents")]
173    SessionMessage(SessionMessage),
174    /// **UNSTABLE**
175    ///
176    /// This capability is not part of the spec yet, and may be removed or changed at any point.
177    ///
178    /// One content block appended to a sent or received session message.
179    #[cfg(feature = "unstable_subagents")]
180    SessionMessageChunk(SessionMessageChunk),
181}
182
183/// **UNSTABLE**
184///
185/// This capability is not part of the spec yet, and may be removed or changed at any point.
186///
187/// A streamed content block of an inter-session message.
188///
189/// A chunk carries only content. The message's participants and references come from
190/// `session_message` upserts, which can arrive before, between, or after its chunks.
191#[cfg(feature = "unstable_subagents")]
192#[serde_as]
193#[skip_serializing_none]
194#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196#[serde(rename_all = "camelCase")]
197#[non_exhaustive]
198pub struct SessionMessageChunk {
199    /// Identifier of this message within the enclosing session's transcript.
200    pub message_id: MessageId,
201    /// A single content block appended to the message.
202    pub content: ContentBlock,
203    /// Optional and nullable chunk-scoped metadata; omitted or `null` means none.
204    ///
205    /// Implementations MUST NOT make assumptions about values in `_meta`.
206    #[serde_as(deserialize_as = "DefaultOnError")]
207    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
208    #[serde(default, rename = "_meta")]
209    pub meta: Option<Meta>,
210}
211
212#[cfg(feature = "unstable_subagents")]
213impl SessionMessageChunk {
214    /// Builds a single streamed content block without chunk metadata.
215    #[must_use]
216    pub fn new(message_id: impl Into<MessageId>, content: ContentBlock) -> Self {
217        Self {
218            message_id: message_id.into(),
219            content,
220            meta: None,
221        }
222    }
223
224    /// Sets optional chunk-scoped metadata.
225    #[must_use]
226    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
227        self.meta = meta.into_option();
228        self
229    }
230}
231
232/// **UNSTABLE**
233///
234/// This capability is not part of the spec yet, and may be removed or changed at any point.
235///
236/// An upsert for an inter-session message.
237#[cfg(feature = "unstable_subagents")]
238#[serde_as]
239#[skip_serializing_none]
240#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
241#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
242#[serde(rename_all = "camelCase")]
243#[non_exhaustive]
244pub struct SessionMessage {
245    /// Identifier of this message within the enclosing session's transcript.
246    pub message_id: MessageId,
247    /// The sending session.
248    ///
249    /// Omitted leaves the stored value unchanged; `null` clears it; a value replaces it.
250    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
251    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
252    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
253    pub sender_session_id: MaybeUndefined<SessionId>,
254    /// The receiving session.
255    ///
256    /// Omitted leaves the stored value unchanged; `null` clears it; a value replaces it.
257    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
258    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
259    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
260    pub recipient_session_id: MaybeUndefined<SessionId>,
261    /// On an incoming view, the `messageId` of the same message's outgoing view in the
262    /// sender's transcript.
263    ///
264    /// Omitted leaves the stored value unchanged; `null` clears it; a value replaces it. It can arrive
265    /// after the message starts streaming.
266    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
267    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
268    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
269    pub sender_message_id: MaybeUndefined<MessageId>,
270    /// On an outgoing view, the `messageId` of the same message's incoming view in the
271    /// recipient's transcript.
272    ///
273    /// Omitted leaves the stored value unchanged; `null` clears it; a value replaces it. It can arrive
274    /// after the message starts streaming.
275    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
276    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
277    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
278    pub recipient_message_id: MaybeUndefined<MessageId>,
279    /// Omitted leaves content unchanged; `null` or `[]` clears it.
280    /// A non-empty array replaces all content.
281    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
282    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
283    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
284    pub content: MaybeUndefined<Vec<ContentBlock>>,
285    /// Omitted leaves metadata unchanged; `null` removes it.
286    ///
287    /// Implementations MUST NOT make assumptions about values in `_meta`.
288    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
289    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
290    #[serde(
291        default,
292        rename = "_meta",
293        skip_serializing_if = "MaybeUndefined::is_undefined"
294    )]
295    pub meta: MaybeUndefined<Meta>,
296}
297
298#[cfg(feature = "unstable_subagents")]
299impl SessionMessage {
300    /// Builds a message upsert with its transcript-local ID.
301    #[must_use]
302    pub fn new(message_id: impl Into<MessageId>) -> Self {
303        Self {
304            message_id: message_id.into(),
305            sender_session_id: MaybeUndefined::Undefined,
306            recipient_session_id: MaybeUndefined::Undefined,
307            sender_message_id: MaybeUndefined::Undefined,
308            recipient_message_id: MaybeUndefined::Undefined,
309            content: MaybeUndefined::Undefined,
310            meta: MaybeUndefined::Undefined,
311        }
312    }
313
314    /// Sets, clears, or leaves unchanged the sending session.
315    #[must_use]
316    pub fn sender_session_id(
317        mut self,
318        sender_session_id: impl IntoMaybeUndefined<SessionId>,
319    ) -> Self {
320        self.sender_session_id = sender_session_id.into_maybe_undefined();
321        self
322    }
323
324    /// Sets, clears, or leaves unchanged the receiving session.
325    #[must_use]
326    pub fn recipient_session_id(
327        mut self,
328        recipient_session_id: impl IntoMaybeUndefined<SessionId>,
329    ) -> Self {
330        self.recipient_session_id = recipient_session_id.into_maybe_undefined();
331        self
332    }
333
334    /// Links an incoming view to the outgoing view's message ID in the sender's transcript.
335    #[must_use]
336    pub fn sender_message_id(
337        mut self,
338        sender_message_id: impl IntoMaybeUndefined<MessageId>,
339    ) -> Self {
340        self.sender_message_id = sender_message_id.into_maybe_undefined();
341        self
342    }
343
344    /// Links an outgoing view to the incoming view's message ID in the recipient's transcript.
345    #[must_use]
346    pub fn recipient_message_id(
347        mut self,
348        recipient_message_id: impl IntoMaybeUndefined<MessageId>,
349    ) -> Self {
350        self.recipient_message_id = recipient_message_id.into_maybe_undefined();
351        self
352    }
353
354    /// Replaces, clears, or omits the complete content patch.
355    #[must_use]
356    pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
357        self.content = content.into_maybe_undefined();
358        self
359    }
360
361    /// Sets, clears, or omits the metadata patch.
362    #[must_use]
363    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
364        self.meta = meta.into_maybe_undefined();
365        self
366    }
367}
368
369#[cfg(all(test, feature = "unstable_subagents"))]
370mod session_message_tests {
371    use super::*;
372    use serde_json::{Value, json};
373
374    #[test]
375    fn envelopes_preserve_participants_and_multimodal_content() {
376        let content = json!([
377            {"type": "text", "text": "Please inspect this"},
378            {"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"}
379        ]);
380        for (transcript, sender, recipient, id) in [
381            ("parent", "parent", "child", "sent-1"),
382            ("child", "parent", "child", "received-9"),
383            ("child", "child", "parent", "sent-2"),
384        ] {
385            let wire = json!({"sessionId": transcript, "update": {
386                "sessionUpdate": "session_message", "messageId": id,
387                "senderSessionId": sender, "recipientSessionId": recipient, "content": content
388            }});
389            let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
390            assert_eq!(serde_json::to_value(&decoded).unwrap(), wire);
391            assert!(matches!(decoded.update, SessionUpdate::SessionMessage(_)));
392            let message = SessionMessage::new(id)
393                .sender_session_id(SessionId::new(sender))
394                .recipient_session_id(SessionId::new(recipient))
395                .content(vec![]);
396            assert_eq!(serde_json::to_value(message).unwrap()["content"], json!([]));
397        }
398    }
399
400    #[test]
401    fn message_upserts_validate_ids_and_patch_fields() {
402        let base = json!({"sessionUpdate": "session_message", "messageId": "m1",
403            "senderSessionId": "parent", "recipientSessionId": "child", "content": []});
404        {
405            let key = "messageId";
406            let mut missing = base.clone();
407            missing.as_object_mut().unwrap().remove(key);
408            assert!(
409                serde_json::from_value::<SessionUpdate>(missing).is_err(),
410                "{key}"
411            );
412            let mut null = base.clone();
413            null[key] = Value::Null;
414            assert!(
415                serde_json::from_value::<SessionUpdate>(null).is_err(),
416                "{key}"
417            );
418            let mut non_string = base.clone();
419            non_string[key] = json!(42);
420            assert!(
421                serde_json::from_value::<SessionUpdate>(non_string).is_err(),
422                "{key}"
423            );
424        }
425        for content in [None, Some(json!({}))] {
426            let mut wire = base.clone();
427            wire.as_object_mut().unwrap().remove("content");
428            if let Some(content) = content {
429                wire["content"] = content;
430            }
431            let decoded: SessionUpdate = serde_json::from_value(wire).unwrap();
432            let encoded = serde_json::to_value(decoded).unwrap();
433            let mut unchanged = base.clone();
434            unchanged.as_object_mut().unwrap().remove("content");
435            assert_eq!(encoded, unchanged);
436        }
437        let clear = SessionMessage::new("m1")
438            .sender_session_id(SessionId::new("parent"))
439            .recipient_session_id(SessionId::new("child"))
440            .content(None)
441            .meta(None);
442        let mut clear_wire = base.clone();
443        clear_wire["content"] = Value::Null;
444        clear_wire["_meta"] = Value::Null;
445        assert_eq!(
446            serde_json::to_value(&clear).unwrap()["content"],
447            Value::Null
448        );
449        let clear_update: SessionUpdate = serde_json::from_value(clear_wire.clone()).unwrap();
450        assert_eq!(serde_json::to_value(clear_update).unwrap(), clear_wire);
451        assert!(clear.content.is_null());
452        assert!(clear.meta.is_null());
453        for meta in [None, Some(Value::Null), Some(json!({"tag": "value"}))] {
454            let mut wire = base.clone();
455            if let Some(meta) = meta {
456                wire["_meta"] = meta;
457            }
458            let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
459            let encoded = serde_json::to_value(decoded).unwrap();
460            assert_eq!(encoded, wire);
461        }
462        let metadata_only = json!({"sessionUpdate": "session_message",
463            "messageId": "m1", "senderSessionId": "parent",
464            "recipientSessionId": "child", "_meta": {"tag": "value"}});
465        let decoded: SessionUpdate = serde_json::from_value(metadata_only.clone()).unwrap();
466        assert_eq!(serde_json::to_value(decoded).unwrap(), metadata_only);
467        let unset = SessionMessage::new("m1");
468        assert_eq!(
469            serde_json::to_value(&unset).unwrap(),
470            json!({"messageId": "m1"})
471        );
472        assert!(unset.content.is_undefined());
473        assert!(unset.meta.is_undefined());
474        let reset = SessionMessage::new("m1").content(vec![]);
475        assert_eq!(serde_json::to_value(reset).unwrap()["content"], json!([]));
476        let malformed: SessionMessage = serde_json::from_value(json!({
477            "messageId": "m1", "senderSessionId": "parent", "recipientSessionId": "child",
478            "content": false, "_meta": false
479        }))
480        .unwrap();
481        assert!(malformed.content.is_undefined());
482        assert!(malformed.meta.is_undefined());
483    }
484
485    #[test]
486    fn first_chunk_and_upsert_share_transcript_local_identity() {
487        for (transcript, id) in [("parent", "sent-1"), ("child", "received-9")] {
488            let wire = json!({"sessionId": transcript, "update": {
489                "sessionUpdate": "session_message_chunk", "messageId": id,
490                "content": {"type": "text", "text": "first"}
491            }});
492            let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
493            assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
494            let upsert = SessionMessage::new(id)
495                .sender_session_id(SessionId::new("parent"))
496                .recipient_session_id(SessionId::new("child"));
497            let chunk = SessionMessageChunk::new(
498                id,
499                ContentBlock::Text(crate::v1::TextContent::new("first")),
500            );
501            assert_eq!(chunk.message_id, upsert.message_id);
502            assert_eq!(
503                serde_json::to_value(SessionUpdate::SessionMessageChunk(chunk)).unwrap(),
504                wire["update"]
505            );
506        }
507        let wire = json!({"sessionId": "child", "update": {
508            "sessionUpdate": "session_message_chunk", "messageId": "received-9",
509            "content": {"type": "text", "text": "first"}
510        }});
511        let base = wire["update"].clone();
512        for key in ["messageId", "content"] {
513            let mut missing = base.clone();
514            missing.as_object_mut().unwrap().remove(key);
515            assert!(
516                serde_json::from_value::<SessionUpdate>(missing).is_err(),
517                "{key}"
518            );
519            let mut null = base.clone();
520            null[key] = Value::Null;
521            assert!(
522                serde_json::from_value::<SessionUpdate>(null).is_err(),
523                "{key}"
524            );
525            let mut non_string = base.clone();
526            non_string[key] = json!(42);
527            assert!(
528                serde_json::from_value::<SessionUpdate>(non_string).is_err(),
529                "{key}"
530            );
531        }
532        for meta in [None, Some(Value::Null), Some(json!({"chunk": true}))] {
533            let mut value = base.clone();
534            if let Some(meta) = meta {
535                value["_meta"] = meta;
536            }
537            let decoded: SessionUpdate = serde_json::from_value(value.clone()).unwrap();
538            let encoded = serde_json::to_value(decoded).unwrap();
539            if value["_meta"].is_null() {
540                assert_eq!(encoded, base);
541            } else {
542                assert_eq!(encoded, value);
543            }
544        }
545        // A chunk carries only content, so identity fields on one are not part of it.
546        let with_identity = json!({"sessionUpdate": "session_message_chunk", "messageId": "m1",
547            "senderSessionId": "parent", "recipientSessionId": "child",
548            "senderMessageId": "sent-1", "recipientMessageId": "received-1",
549            "content": {"type": "text", "text": "hello"}});
550        let decoded: SessionUpdate = serde_json::from_value(with_identity).unwrap();
551        assert_eq!(
552            serde_json::to_value(decoded).unwrap(),
553            json!({"sessionUpdate": "session_message_chunk", "messageId": "m1",
554                "content": {"type": "text", "text": "hello"}})
555        );
556    }
557
558    #[cfg(feature = "schemars")]
559    #[test]
560    fn schema_requires_ids_and_chunk_content() {
561        let schema = serde_json::to_value(schemars::schema_for!(SessionMessage)).unwrap();
562        let required = schema["required"].as_array().unwrap();
563        assert_eq!(required, &vec![json!("messageId")]);
564        let chunk = serde_json::to_value(schemars::schema_for!(SessionMessageChunk)).unwrap();
565        let required = chunk["required"].as_array().unwrap();
566        assert_eq!(required.len(), 2);
567        assert!(required.contains(&json!("messageId")));
568        assert!(required.contains(&json!("content")));
569    }
570
571    #[test]
572    fn endpoints_can_arrive_late_or_be_omitted_from_later_events() {
573        let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
574        let minimal = SessionMessage::new("m1");
575        let first = SessionMessageChunk::new("m1", block.clone());
576        assert_eq!(
577            serde_json::to_value(&minimal).unwrap(),
578            json!({"messageId": "m1"})
579        );
580        assert_eq!(
581            serde_json::to_value(&first).unwrap(),
582            json!({"messageId": "m1", "content": {"type": "text", "text": "hello"}})
583        );
584        let enriched = SessionMessage::new("m1")
585            .sender_session_id(SessionId::new("parent"))
586            .recipient_session_id(SessionId::new("child"));
587        assert_eq!(
588            enriched.sender_session_id,
589            MaybeUndefined::Value(SessionId::new("parent"))
590        );
591        assert_eq!(
592            enriched.recipient_session_id,
593            MaybeUndefined::Value(SessionId::new("child"))
594        );
595        let later = SessionMessage::new("m1").sender_session_id(SessionId::new("parent"));
596        assert_eq!(
597            later.sender_session_id,
598            MaybeUndefined::Value(SessionId::new("parent"))
599        );
600        assert_eq!(later.recipient_session_id, MaybeUndefined::Undefined);
601        let more = SessionMessageChunk::new("m1", block);
602        for update in [
603            SessionUpdate::SessionMessage(minimal),
604            SessionUpdate::SessionMessageChunk(first),
605            SessionUpdate::SessionMessage(enriched),
606            SessionUpdate::SessionMessage(later),
607            SessionUpdate::SessionMessageChunk(more),
608        ] {
609            let wire = serde_json::to_value(&update).unwrap();
610            let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
611            assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
612        }
613    }
614
615    #[test]
616    fn null_endpoints_clear_invalid_are_absent_but_message_id_is_required() {
617        let mut base = json!({"sessionUpdate": "session_message", "messageId": "m1",
618            "senderSessionId": null, "recipientSessionId": 42});
619        let decoded: SessionUpdate = serde_json::from_value(base.clone()).unwrap();
620        let encoded = serde_json::to_value(decoded).unwrap();
621        base.as_object_mut().unwrap().remove("recipientSessionId");
622        assert_eq!(encoded, base);
623        for bad in [Value::Null, json!(42)] {
624            let mut invalid = base.clone();
625            invalid["messageId"] = bad;
626            assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
627        }
628    }
629
630    #[test]
631    fn message_references_link_views_and_can_arrive_late() {
632        let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
633        let incoming = SessionMessage::new("received-1")
634            .sender_session_id(SessionId::new("parent"))
635            .recipient_session_id(SessionId::new("child"))
636            .sender_message_id(MessageId::new("sent-1"))
637            .content(vec![block]);
638        // The outgoing view gets its reference after it started, with a metadata-only upsert.
639        let outgoing =
640            SessionMessage::new("sent-1").recipient_message_id(MessageId::new("received-1"));
641        for (update, wire) in [
642            (
643                SessionUpdate::SessionMessage(incoming),
644                json!({"sessionUpdate": "session_message", "messageId": "received-1",
645                    "senderSessionId": "parent", "recipientSessionId": "child",
646                    "senderMessageId": "sent-1", "content": [{"type": "text", "text": "hello"}]}),
647            ),
648            (
649                SessionUpdate::SessionMessage(outgoing),
650                json!({"sessionUpdate": "session_message", "messageId": "sent-1",
651                    "recipientMessageId": "received-1"}),
652            ),
653        ] {
654            assert_eq!(serde_json::to_value(&update).unwrap(), wire);
655            let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
656            assert_eq!(decoded, update);
657        }
658
659        let mut wire = json!({"sessionUpdate": "session_message", "messageId": "m1",
660            "senderMessageId": null, "recipientMessageId": 42});
661        let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
662        wire.as_object_mut().unwrap().remove("recipientMessageId");
663        assert_eq!(
664            serde_json::to_value(decoded).unwrap(),
665            wire,
666            "null clears a reference and a malformed one is not supplied"
667        );
668    }
669}
670
671#[cfg(all(test, not(feature = "unstable_subagents")))]
672mod disabled_session_message_tests {
673    use super::*;
674    use serde_json::json;
675
676    #[test]
677    fn message_updates_require_subagents_gate() {
678        for (kind, content) in [
679            ("session_message", json!([])),
680            (
681                "session_message_chunk",
682                json!({"type": "text", "text": "hello"}),
683            ),
684        ] {
685            let wire = json!({"sessionUpdate": kind, "messageId": "m1", "content": content});
686            assert!(serde_json::from_value::<SessionUpdate>(wire).is_err());
687        }
688    }
689}
690
691/// Severity hint for a session notice.
692#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
693#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
694#[serde(rename_all = "snake_case")]
695#[non_exhaustive]
696pub enum NoticeSeverity {
697    /// Informational notice.
698    Info,
699    /// Warning notice.
700    Warning,
701    /// Error notice.
702    Error,
703    /// Custom or future notice severity.
704    ///
705    /// Values beginning with `_` are reserved for implementation-specific
706    /// extensions. Other unknown values are reserved for future ACP severities.
707    #[serde(untagged)]
708    Other(String),
709}
710
711/// Fire-and-forget information for the user.
712///
713/// Notices are live events rather than session history. Agents must not rely on
714/// a notice being received, displayed, or seen by the user.
715/// Agents MUST only send notices when the Client advertised
716/// the `session.notices` Client capability. Otherwise, Agents may use an agent
717/// message when the information should still be surfaced to the user.
718///
719/// See RFD: [Session Notices](https://agentclientprotocol.com/rfds/session-notices)
720#[serde_as]
721#[skip_serializing_none]
722#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
723#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
724#[serde(rename_all = "camelCase")]
725#[non_exhaustive]
726pub struct Notice {
727    /// Presentation severity hint.
728    pub severity: NoticeSeverity,
729    /// Required non-empty plain-text title that can stand alone.
730    #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
731    pub title: String,
732    /// Optional plain-text detail or guidance.
733    ///
734    /// Omitted and `null` are equivalent and mean no description was supplied.
735    #[serde_as(deserialize_as = "DefaultOnError")]
736    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
737    #[serde(default)]
738    pub description: Option<String>,
739    /// Metadata scoped to this notice.
740    ///
741    /// Omitted and `null` are equivalent and mean no metadata was supplied.
742    #[serde_as(deserialize_as = "DefaultOnError")]
743    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
744    #[serde(default, rename = "_meta")]
745    pub meta: Option<Meta>,
746}
747
748impl Notice {
749    /// Builds a notice with the required fields set and optional fields omitted.
750    #[must_use]
751    pub fn new(severity: NoticeSeverity, title: impl Into<String>) -> Self {
752        Self {
753            severity,
754            title: title.into(),
755            description: None,
756            meta: None,
757        }
758    }
759
760    /// Sets or clears the optional description.
761    #[must_use]
762    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
763        self.description = description.into_option();
764        self
765    }
766
767    /// Sets or clears notice-scoped metadata.
768    #[must_use]
769    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
770        self.meta = meta.into_option();
771        self
772    }
773}
774
775/// Unique identifier for a context compaction within a session.
776#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
777#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
778#[serde(transparent)]
779#[from(Arc<str>, String, &'static str)]
780#[non_exhaustive]
781pub struct CompactionId(pub Arc<str>);
782
783impl CompactionId {
784    /// Wraps a protocol string as a typed [`CompactionId`].
785    #[must_use]
786    pub fn new(id: impl Into<Arc<str>>) -> Self {
787        Self(id.into())
788    }
789}
790
791/// Lifecycle state of a context compaction.
792#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
793#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
794#[serde(rename_all = "snake_case")]
795#[non_exhaustive]
796pub enum CompactionStatus {
797    /// Compaction has started and has not finished.
798    #[default]
799    InProgress,
800    /// Compaction finished successfully.
801    Completed,
802    /// Compaction finished unsuccessfully.
803    Failed,
804    /// Compaction was cancelled before it finished.
805    Cancelled,
806    /// Custom or future compaction status.
807    ///
808    /// Values beginning with `_` are reserved for implementation-specific
809    /// extensions. Other unknown values are reserved for future ACP statuses.
810    #[serde(untagged)]
811    Other(String),
812}
813
814/// A context compaction upsert. The first notification fixes the compaction's
815/// timeline position. Later updates with the same ID patch that entity in place.
816/// Agents MUST only send this update when the Client advertised
817/// the `session.compaction` Client capability.
818///
819/// `summary`, `error`, and `_meta` have patch semantics: omission leaves the
820/// stored value unchanged, `null` clears it, and a concrete value replaces it.
821/// `summary: []` also clears the summary.
822#[serde_as]
823#[skip_serializing_none]
824#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
825#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
826#[serde(rename_all = "camelCase")]
827#[non_exhaustive]
828pub struct CompactionUpdate {
829    /// The Agent-owned ID of this compaction, unique within the session.
830    pub compaction_id: CompactionId,
831    /// Current lifecycle status.
832    pub status: CompactionStatus,
833    /// Complete replacement user-displayable summary content for the compaction.
834    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
835    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
836    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
837    pub summary: MaybeUndefined<Vec<ContentBlock>>,
838    /// Human-readable error details for the compaction.
839    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
840    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
841    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
842    pub error: MaybeUndefined<String>,
843    /// Extensible metadata patch for this compaction.
844    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
845    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
846    #[serde(
847        rename = "_meta",
848        default,
849        skip_serializing_if = "MaybeUndefined::is_undefined"
850    )]
851    pub meta: MaybeUndefined<Meta>,
852}
853
854impl CompactionUpdate {
855    /// Builds a compaction update with optional patch fields omitted.
856    #[must_use]
857    pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
858        Self {
859            compaction_id: compaction_id.into(),
860            status,
861            summary: MaybeUndefined::Undefined,
862            error: MaybeUndefined::Undefined,
863            meta: MaybeUndefined::Undefined,
864        }
865    }
866
867    /// Sets, clears, or omits the complete retained summary patch.
868    #[must_use]
869    pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
870        self.summary = summary.into_maybe_undefined();
871        self
872    }
873
874    /// Sets, clears, or omits the error details patch.
875    #[must_use]
876    pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
877        self.error = error.into_maybe_undefined();
878        self
879    }
880
881    /// Sets, clears, or omits the metadata patch.
882    #[must_use]
883    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
884        self.meta = meta.into_maybe_undefined();
885        self
886    }
887}
888
889/// A content block appended to a compaction's summary. A first-seen ID creates
890/// an in-progress compaction. Chunks append in receive order.
891/// Agents MUST only send this update when the Client advertised
892/// the `session.compaction` Client capability.
893#[serde_as]
894#[skip_serializing_none]
895#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
896#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
897#[serde(rename_all = "camelCase")]
898#[non_exhaustive]
899pub struct CompactionSummaryChunk {
900    /// ID of the compaction whose summary receives this content.
901    pub compaction_id: CompactionId,
902    /// One content block to append.
903    pub content: ContentBlock,
904    /// Metadata scoped to this chunk. Omission and `null` both mean absent.
905    #[serde_as(deserialize_as = "DefaultOnError")]
906    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
907    #[serde(default, rename = "_meta")]
908    pub meta: Option<Meta>,
909}
910
911impl CompactionSummaryChunk {
912    /// Builds a summary chunk without metadata.
913    #[must_use]
914    pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
915        Self {
916            compaction_id: compaction_id.into(),
917            content,
918            meta: None,
919        }
920    }
921
922    /// Sets or clears chunk-scoped metadata.
923    #[must_use]
924    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
925        self.meta = meta.into_option();
926        self
927    }
928}
929
930/// **UNSTABLE**
931///
932/// This capability is not part of the spec yet, and may be removed or changed at any point.
933///
934/// Notification that the enclosing parent session created and owns a child session.
935///
936/// Later updates modify the existing association's metadata, not its ownership.
937///
938/// Sent on the immediate parent session. The first update for an unknown
939/// `sessionId` announces the child and MUST be sent
940/// before any live child traffic or live message naming the child as sender
941/// or recipient. Parents may message and reuse an announced child across
942/// multiple operations.
943/// Child events are delivered automatically on the same connection; no child
944/// load, resume, or subscription is needed.
945///
946/// Only the subagent session ID is required. Omitted patch fields keep their
947/// previous values; `null` clears them. Clearing capabilities disables child
948/// mutations. Clearing state leaves current activity unset/unconfirmed: it does
949/// not imply idle, stop work, or create an `unknown` state snapshot. A concrete
950/// state replaces the entire previous state object, not the session.
951/// The title and description provide the parent's display metadata for the
952/// child. They do not replace the content of individual messages or operations.
953/// `cwd` and `additionalDirectories` report where the Agent placed the child;
954/// while unset, each inherits the immediate parent's effective value.
955#[cfg(feature = "unstable_subagents")]
956#[serde_as]
957#[skip_serializing_none]
958#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
959#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
960#[serde(rename_all = "camelCase")]
961#[non_exhaustive]
962pub struct SubagentUpdate {
963    /// The opaque session ID identifying the child in all ACP messages.
964    pub session_id: SessionId,
965    /// The parent's human-readable display title for this child. It need not be unique.
966    ///
967    /// Omitted means unchanged; `null` clears it. If unset, the Client chooses
968    /// a fallback presentation.
969    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
970    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
971    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
972    pub title: MaybeUndefined<String>,
973    /// The parent's human-readable description of the child's role or purpose.
974    ///
975    /// Omitted means unchanged; `null` clears it. If unset, the Client chooses
976    /// a fallback presentation. This is current display metadata, not the
977    /// history of instructions sent to the child.
978    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
979    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
980    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
981    pub description: MaybeUndefined<String>,
982    /// Client-initiated session mutations permitted for this subagent session.
983    ///
984    /// Omitted means unchanged; `null` clears the capability set and disables
985    /// child mutations. If never supplied, no session mutations are permitted.
986    /// Read-only operations retain their normal protocol semantics and capability
987    /// requirements. A concrete object replaces the whole capability set.
988    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
989    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
990    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
991    pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
992    /// Current state snapshot for the child session.
993    ///
994    /// Omitted means unchanged; `null` clears the current activity without
995    /// asserting idle or sending an `unknown` snapshot. A concrete state
996    /// replaces the previous state object wholesale. If never supplied, the
997    /// current activity is unset/unconfirmed.
998    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
999    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1000    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1001    pub state: MaybeUndefined<StateUpdate>,
1002    /// The child's working directory. Must be an absolute path.
1003    ///
1004    /// Optional and nullable. Omitted means unchanged; `null` clears the
1005    /// reported value. While unset, the child inherits its immediate parent's
1006    /// effective working directory. Clients use this value for the child's
1007    /// root set and as the default terminal working directory.
1008    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1009    pub cwd: MaybeUndefined<PathBuf>,
1010    /// Additional workspace roots for the child. Each path must be absolute.
1011    ///
1012    /// Optional and nullable. Omitted means unchanged; `null` clears the
1013    /// reported value. While unset, the child inherits its immediate parent's
1014    /// effective additional directories, independently of `cwd`. A concrete
1015    /// array replaces the whole list; `[]` reports no additional directories.
1016    /// Clients use these values for the child's root set.
1017    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1018    pub additional_directories: MaybeUndefined<Vec<PathBuf>>,
1019    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1020    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1021    /// these keys.
1022    ///
1023    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1024    /// Omitted means unchanged; `null` removes the metadata.
1025    #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1026    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1027    #[serde(
1028        default,
1029        rename = "_meta",
1030        skip_serializing_if = "MaybeUndefined::is_undefined"
1031    )]
1032    pub meta: MaybeUndefined<Meta>,
1033}
1034
1035#[cfg(feature = "unstable_subagents")]
1036impl SubagentUpdate {
1037    /// Builds a subagent upsert with only its required session ID set.
1038    #[must_use]
1039    pub fn new(session_id: impl Into<SessionId>) -> Self {
1040        Self {
1041            session_id: session_id.into(),
1042            title: MaybeUndefined::Undefined,
1043            description: MaybeUndefined::Undefined,
1044            capabilities: MaybeUndefined::Undefined,
1045            state: MaybeUndefined::Undefined,
1046            cwd: MaybeUndefined::Undefined,
1047            additional_directories: MaybeUndefined::Undefined,
1048            meta: MaybeUndefined::Undefined,
1049        }
1050    }
1051
1052    /// Sets, clears, or omits the parent's display title patch.
1053    #[must_use]
1054    pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1055        self.title = title.into_maybe_undefined();
1056        self
1057    }
1058
1059    /// Sets, clears, or omits the parent's description patch.
1060    #[must_use]
1061    pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
1062        self.description = description.into_maybe_undefined();
1063        self
1064    }
1065
1066    /// Replaces, clears, or omits the permitted client-initiated mutations patch.
1067    #[must_use]
1068    pub fn capabilities(
1069        mut self,
1070        capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
1071    ) -> Self {
1072        self.capabilities = capabilities.into_maybe_undefined();
1073        self
1074    }
1075
1076    /// Replaces, clears, or omits the current activity patch.
1077    #[must_use]
1078    pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
1079        self.state = state.into_maybe_undefined();
1080        self
1081    }
1082
1083    /// Sets, clears, or leaves unchanged the child's working directory.
1084    ///
1085    /// While unset, the child inherits its parent's working directory.
1086    #[must_use]
1087    pub fn cwd(mut self, cwd: impl IntoMaybeUndefined<PathBuf>) -> Self {
1088        self.cwd = cwd.into_maybe_undefined();
1089        self
1090    }
1091
1092    /// Replaces, clears, or leaves unchanged the child's additional directories.
1093    ///
1094    /// While unset, the child inherits its parent's additional directories.
1095    #[must_use]
1096    pub fn additional_directories(
1097        mut self,
1098        additional_directories: impl IntoMaybeUndefined<Vec<PathBuf>>,
1099    ) -> Self {
1100        self.additional_directories = additional_directories.into_maybe_undefined();
1101        self
1102    }
1103
1104    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1105    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1106    /// these keys. Sets, clears, or omits this metadata patch.
1107    #[must_use]
1108    pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1109        self.meta = meta.into_maybe_undefined();
1110        self
1111    }
1112}
1113
1114/// **UNSTABLE**
1115///
1116/// This capability is not part of the spec yet, and may be removed or changed at any point.
1117///
1118/// Client-initiated session mutations permitted for a specific subagent session.
1119///
1120/// A mutation requires an explicit per-child capability; support for the method
1121/// on ordinary sessions does not grant support on a child.
1122#[cfg(feature = "unstable_subagents")]
1123#[serde_as]
1124#[skip_serializing_none]
1125#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1126#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1127#[serde(rename_all = "camelCase")]
1128#[non_exhaustive]
1129pub struct SubagentSessionCapabilities {
1130    /// Permits the client to cancel this child's current work without ending
1131    /// the session. Omitted or `null` means unsupported; an object (including
1132    /// `{}`) means supported.
1133    #[serde_as(deserialize_as = "DefaultOnError")]
1134    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1135    #[serde(default)]
1136    pub cancel: Option<SessionCancelCapabilities>,
1137    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1138    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1139    /// these keys.
1140    #[serde_as(deserialize_as = "DefaultOnError")]
1141    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1142    #[serde(default)]
1143    #[serde(rename = "_meta")]
1144    pub meta: Option<Meta>,
1145}
1146
1147#[cfg(feature = "unstable_subagents")]
1148impl SubagentSessionCapabilities {
1149    /// Builds an empty capability set; cancellation is disabled.
1150    #[must_use]
1151    pub fn new() -> Self {
1152        Self::default()
1153    }
1154
1155    /// Sets or removes permission to cancel this child's current work.
1156    #[must_use]
1157    pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1158        self.cancel = cancel.into_option();
1159        self
1160    }
1161
1162    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1163    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1164    /// these keys.
1165    #[must_use]
1166    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1167        self.meta = meta.into_option();
1168        self
1169    }
1170}
1171
1172/// **UNSTABLE**
1173///
1174/// This capability is not part of the spec yet, and may be removed or changed at any point.
1175///
1176/// Capability to cancel work in a subagent session without ending that session.
1177///
1178/// Supplying `{}` advertises support; an omitted or `null` `cancel` does not.
1179#[cfg(feature = "unstable_subagents")]
1180#[serde_as]
1181#[skip_serializing_none]
1182#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1183#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1184#[non_exhaustive]
1185pub struct SessionCancelCapabilities {
1186    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1187    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1188    /// these keys.
1189    ///
1190    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1191    #[serde_as(deserialize_as = "DefaultOnError")]
1192    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1193    #[serde(default)]
1194    #[serde(rename = "_meta")]
1195    pub meta: Option<Meta>,
1196}
1197
1198#[cfg(feature = "unstable_subagents")]
1199impl SessionCancelCapabilities {
1200    /// Builds an empty capability object advertising cancellation support.
1201    #[must_use]
1202    pub fn new() -> Self {
1203        Self::default()
1204    }
1205
1206    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1207    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1208    /// these keys.
1209    ///
1210    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1211    #[must_use]
1212    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1213        self.meta = meta.into_option();
1214        self
1215    }
1216}
1217
1218/// **UNSTABLE**
1219///
1220/// This capability is not part of the spec yet, and may be removed or changed at any point.
1221///
1222/// Current foreground-work state of a reusable child session.
1223///
1224/// Each update is a whole-object snapshot. Idle does not terminate the child;
1225/// the parent can message it again, transitioning it back to running.
1226/// Background activity may still emit other session updates while idle.
1227#[cfg(feature = "unstable_subagents")]
1228#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1230#[serde(tag = "state", rename_all = "snake_case")]
1231#[non_exhaustive]
1232pub enum StateUpdate {
1233    /// Foreground work is in progress.
1234    Running(RunningStateUpdate),
1235    /// The child is ready to process another prompt.
1236    Idle(IdleStateUpdate),
1237    /// Foreground work is blocked on user action.
1238    RequiresAction(RequiresActionStateUpdate),
1239    /// The Agent cannot currently determine foreground activity.
1240    ///
1241    /// This replaces previously confirmed activity without ending the work or session.
1242    Unknown(UnknownStateUpdate),
1243    /// Custom or future state.
1244    ///
1245    /// Values beginning with `_` are reserved for implementation-specific
1246    /// extensions. Other unknown values are reserved for future ACP variants.
1247    #[serde(untagged)]
1248    Other(OtherStateUpdate),
1249}
1250
1251/// Foreground work is in progress.
1252#[cfg(feature = "unstable_subagents")]
1253#[serde_as]
1254#[skip_serializing_none]
1255#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1256#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1257#[serde(rename_all = "camelCase")]
1258#[non_exhaustive]
1259pub struct RunningStateUpdate {
1260    /// The _meta property is reserved by ACP for additional metadata.
1261    /// Implementations MUST NOT make assumptions about values at these keys.
1262    /// Optional; omitted and `null` mean no metadata for this state snapshot.
1263    #[serde_as(deserialize_as = "DefaultOnError")]
1264    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1265    #[serde(default, rename = "_meta")]
1266    pub meta: Option<Meta>,
1267}
1268
1269#[cfg(feature = "unstable_subagents")]
1270impl RunningStateUpdate {
1271    /// Builds an empty running state.
1272    #[must_use]
1273    pub fn new() -> Self {
1274        Self::default()
1275    }
1276
1277    /// Reserved metadata for extensions.
1278    #[must_use]
1279    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1280        self.meta = meta.into_option();
1281        self
1282    }
1283}
1284
1285/// The child is ready to process another prompt.
1286///
1287/// An omitted, `null`, or malformed `stopReason` means not reported.
1288#[cfg(feature = "unstable_subagents")]
1289#[serde_as]
1290#[skip_serializing_none]
1291#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1292#[cfg_attr(feature = "schemars", schemars(transform = idle_state_update_schema))]
1293#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1294#[serde(rename_all = "camelCase")]
1295#[non_exhaustive]
1296pub struct IdleStateUpdate {
1297    /// Reason foreground work stopped, with any fields of that stop reason.
1298    #[serde(flatten)]
1299    pub stop_reason: Option<IdleStopReason>,
1300    /// **UNSTABLE** Token usage for completed foreground work.
1301    ///
1302    /// Optional; omitted or `null` means not reported.
1303    #[cfg(feature = "unstable_end_turn_token_usage")]
1304    #[serde_as(deserialize_as = "DefaultOnError")]
1305    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1306    #[serde(default)]
1307    pub usage: Option<Box<Usage>>,
1308    /// The _meta property is reserved by ACP for additional metadata.
1309    /// Implementations MUST NOT make assumptions about values at these keys.
1310    /// Optional; omitted and `null` mean no metadata for this state snapshot.
1311    #[serde_as(deserialize_as = "DefaultOnError")]
1312    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1313    #[serde(default, rename = "_meta")]
1314    pub meta: Option<Meta>,
1315}
1316
1317#[cfg(feature = "unstable_subagents")]
1318impl IdleStateUpdate {
1319    /// Builds an empty idle state.
1320    #[must_use]
1321    pub fn new() -> Self {
1322        Self::default()
1323    }
1324
1325    /// Reason foreground work stopped.
1326    #[must_use]
1327    pub fn stop_reason(mut self, stop_reason: impl IntoOption<IdleStopReason>) -> Self {
1328        self.stop_reason = stop_reason.into_option();
1329        self
1330    }
1331
1332    /// Token usage for completed foreground work.
1333    #[cfg(feature = "unstable_end_turn_token_usage")]
1334    #[must_use]
1335    pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1336        self.usage = usage.into_option().map(Box::new);
1337        self
1338    }
1339
1340    /// Reserved metadata for extensions.
1341    #[must_use]
1342    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1343        self.meta = meta.into_option();
1344        self
1345    }
1346}
1347
1348/// Makes the schema check stop reasons and keeps their default-on-error hint.
1349///
1350/// Flattening `Option<IdleStopReason>` adds an empty `{}` branch to the `anyOf`. It
1351/// matches any value, so the stop reason branches would check nothing; this
1352/// replaces it with a branch for an omitted or `null` `stopReason`. Flattening
1353/// also removes the `stopReason` property that carried
1354/// `x-deserialize-default-on-error`, which SDKs generated from the schema rely on
1355/// to tolerate a malformed value, so this adds it back as a shared property.
1356#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1357fn idle_state_update_schema(schema: &mut Schema) {
1358    if let Some(properties) = schema
1359        .get_mut("properties")
1360        .and_then(serde_json::Value::as_object_mut)
1361    {
1362        properties.insert(
1363            "stopReason".into(),
1364            serde_json::json!({
1365                "description": "Why foreground work stopped. The value selects one of this type's variants, which may add fields of their own.\n\nOptional. Omitted or `null` both mean the agent is not reporting a stop reason; a malformed value is treated the same way.\n\nSee protocol docs: [Current work state](https://agentclientprotocol.com/rfds/subagents#current-work-state)",
1366                "type": ["string", "null"],
1367                "x-deserialize-default-on-error": true
1368            }),
1369        );
1370    }
1371    let Some(variants) = schema
1372        .get_mut("anyOf")
1373        .and_then(serde_json::Value::as_array_mut)
1374    else {
1375        return;
1376    };
1377    for variant in variants {
1378        if variant.as_object().is_some_and(serde_json::Map::is_empty) {
1379            *variant = serde_json::json!({
1380                "title": "none",
1381                "description": "No stop reason: `stopReason` is omitted or `null`.",
1382                "type": "object",
1383                "properties": {
1384                    "stopReason": { "type": "null" }
1385                }
1386            });
1387        }
1388    }
1389}
1390
1391/// **UNSTABLE**
1392///
1393/// This capability is not part of the spec yet, and may be removed or changed at any point.
1394///
1395/// Reason a child's foreground work stopped, reported on its idle state snapshot.
1396///
1397/// Tagged by `stopReason` and flattened into [`IdleStateUpdate`], so the snapshot
1398/// matches v2's idle `state_update`, including its handling of unknown values.
1399/// The known values are those of [`StopReason`](super::StopReason) plus `error`.
1400/// A child has no prompt response of its own, so this snapshot is the only place
1401/// to report that its work failed. The prompt response keeps
1402/// [`StopReason`](super::StopReason) and reports failures as JSON-RPC errors.
1403#[cfg(feature = "unstable_subagents")]
1404#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1405#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1406#[serde(tag = "stopReason", rename_all = "snake_case")]
1407#[non_exhaustive]
1408pub enum IdleStopReason {
1409    /// The work ended successfully.
1410    EndTurn,
1411    /// The work ended because the agent reached the maximum number of tokens.
1412    MaxTokens,
1413    /// The work ended because the agent reached the maximum number of allowed
1414    /// agent requests.
1415    MaxTurnRequests,
1416    /// The work ended because the agent refused to continue.
1417    Refusal,
1418    /// The work was cancelled.
1419    Cancelled,
1420    /// The work ended because something failed.
1421    Error(ErrorStopReason),
1422    /// Custom or future stop reason.
1423    ///
1424    /// Values beginning with `_` are reserved for implementation-specific
1425    /// extensions. Other unknown values are reserved for future ACP variants.
1426    #[serde(untagged)]
1427    Other(OtherStopReason),
1428}
1429
1430#[cfg(feature = "unstable_subagents")]
1431impl From<ErrorStopReason> for IdleStopReason {
1432    fn from(value: ErrorStopReason) -> Self {
1433        Self::Error(value)
1434    }
1435}
1436
1437/// **UNSTABLE**
1438///
1439/// This capability is not part of the spec yet, and may be removed or changed at any point.
1440///
1441/// Details of a failure that ended a child's foreground work.
1442#[cfg(feature = "unstable_subagents")]
1443#[serde_as]
1444#[skip_serializing_none]
1445#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1446#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1447#[serde(rename_all = "camelCase")]
1448#[non_exhaustive]
1449pub struct ErrorStopReason {
1450    /// The failure, as a JSON-RPC error object.
1451    ///
1452    /// Optional. Omitted or `null` both mean the agent is not reporting failure details.
1453    /// Agents SHOULD include it.
1454    #[serde_as(deserialize_as = "DefaultOnError")]
1455    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1456    #[serde(default)]
1457    pub error: Option<Box<Error>>,
1458}
1459
1460#[cfg(feature = "unstable_subagents")]
1461impl ErrorStopReason {
1462    /// Builds [`ErrorStopReason`] with the required fields set; optional fields start unset or empty.
1463    #[must_use]
1464    pub fn new() -> Self {
1465        Self::default()
1466    }
1467
1468    /// The failure, as a JSON-RPC error object.
1469    #[must_use]
1470    pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
1471        self.error = error.into_option().map(Box::new);
1472        self
1473    }
1474}
1475
1476/// **UNSTABLE**
1477///
1478/// This capability is not part of the spec yet, and may be removed or changed at any point.
1479///
1480/// Custom or future stop reason payload, preserving its discriminator and fields.
1481#[cfg(feature = "unstable_subagents")]
1482#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1483#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1484#[cfg_attr(feature = "schemars", schemars(inline))]
1485#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
1486#[serde(rename_all = "camelCase")]
1487#[non_exhaustive]
1488pub struct OtherStopReason {
1489    /// Unrecognized stop reason.
1490    pub stop_reason: String,
1491    /// Remaining fields of the unknown stop reason.
1492    #[serde(flatten)]
1493    pub fields: BTreeMap<String, serde_json::Value>,
1494}
1495
1496#[cfg(feature = "unstable_subagents")]
1497impl OtherStopReason {
1498    /// Builds a custom stop reason, preserving its extension fields.
1499    #[must_use]
1500    pub fn new(
1501        stop_reason: impl Into<String>,
1502        mut fields: BTreeMap<String, serde_json::Value>,
1503    ) -> Self {
1504        remove_idle_state_update_fields(&mut fields);
1505        Self {
1506            stop_reason: stop_reason.into(),
1507            fields,
1508        }
1509    }
1510}
1511
1512#[cfg(feature = "unstable_subagents")]
1513impl<'de> Deserialize<'de> for OtherStopReason {
1514    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1515    where
1516        D: serde::Deserializer<'de>,
1517    {
1518        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1519        let stop_reason = fields
1520            .remove("stopReason")
1521            .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
1522        let serde_json::Value::String(stop_reason) = stop_reason else {
1523            return Err(serde::de::Error::custom("`stopReason` must be a string"));
1524        };
1525        if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
1526            return Err(serde::de::Error::custom(format!(
1527                "known stop reason `{stop_reason}` did not match its schema"
1528            )));
1529        }
1530        remove_idle_state_update_fields(&mut fields);
1531        Ok(Self {
1532            stop_reason,
1533            fields,
1534        })
1535    }
1536}
1537
1538#[cfg(feature = "unstable_subagents")]
1539const KNOWN_STOP_REASONS: &[&str] = &[
1540    "end_turn",
1541    "max_tokens",
1542    "max_turn_requests",
1543    "refusal",
1544    "cancelled",
1545    "error",
1546];
1547
1548/// Keeps the idle snapshot's own fields out of an unknown stop reason's payload.
1549#[cfg(feature = "unstable_subagents")]
1550fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1551    fields.remove("stopReason");
1552    fields.remove("usage");
1553    fields.remove("_meta");
1554}
1555
1556#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1557fn other_stop_reason_schema(schema: &mut Schema) {
1558    let known = KNOWN_STOP_REASONS
1559        .iter()
1560        .map(|stop_reason| {
1561            serde_json::json!({
1562                "properties": { "stopReason": { "const": stop_reason, "type": "string" } },
1563                "required": ["stopReason"],
1564                "type": "object"
1565            })
1566        })
1567        .collect::<Vec<_>>();
1568    schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1569}
1570
1571/// Foreground work is blocked on user action.
1572#[cfg(feature = "unstable_subagents")]
1573#[serde_as]
1574#[skip_serializing_none]
1575#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1576#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1577#[serde(rename_all = "camelCase")]
1578#[non_exhaustive]
1579pub struct RequiresActionStateUpdate {
1580    /// The _meta property is reserved by ACP for additional metadata.
1581    /// Implementations MUST NOT make assumptions about values at these keys.
1582    /// Optional; omitted and `null` mean no metadata for this state snapshot.
1583    #[serde_as(deserialize_as = "DefaultOnError")]
1584    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1585    #[serde(default, rename = "_meta")]
1586    pub meta: Option<Meta>,
1587}
1588
1589#[cfg(feature = "unstable_subagents")]
1590impl RequiresActionStateUpdate {
1591    /// Builds an empty requires-action state.
1592    #[must_use]
1593    pub fn new() -> Self {
1594        Self::default()
1595    }
1596
1597    /// Reserved metadata for extensions.
1598    #[must_use]
1599    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1600        self.meta = meta.into_option();
1601        self
1602    }
1603}
1604
1605/// **UNSTABLE**
1606///
1607/// This capability is not part of the spec yet, and may be removed or changed at any point.
1608///
1609/// The Agent cannot currently determine foreground activity.
1610///
1611/// Report this when activity becomes unobservable, not merely because the child
1612/// has been quiet. The Client MUST stop presenting the previous state as confirmed
1613/// current activity, but may retain it as last known. A later state replaces this
1614/// snapshot normally.
1615///
1616/// This is not a task outcome or session closure. It does not cancel work, resolve
1617/// pending requests, or revoke capabilities; capabilities are updated separately.
1618#[cfg(feature = "unstable_subagents")]
1619#[serde_as]
1620#[skip_serializing_none]
1621#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1622#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1623#[serde(rename_all = "camelCase")]
1624#[non_exhaustive]
1625pub struct UnknownStateUpdate {
1626    /// The _meta property is reserved by ACP for additional metadata.
1627    /// Implementations MUST NOT make assumptions about values at these keys.
1628    /// Optional; omitted and `null` mean no metadata for this state snapshot.
1629    #[serde_as(deserialize_as = "DefaultOnError")]
1630    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1631    #[serde(default, rename = "_meta")]
1632    pub meta: Option<Meta>,
1633}
1634
1635#[cfg(feature = "unstable_subagents")]
1636impl UnknownStateUpdate {
1637    /// Builds an unknown-activity state.
1638    #[must_use]
1639    pub fn new() -> Self {
1640        Self::default()
1641    }
1642
1643    /// Sets optional metadata for this state snapshot.
1644    #[must_use]
1645    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1646        self.meta = meta.into_option();
1647        self
1648    }
1649}
1650
1651/// Custom or future state payload, preserving its discriminator and fields.
1652#[cfg(feature = "unstable_subagents")]
1653#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1654#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1655#[cfg_attr(feature = "schemars", schemars(inline))]
1656#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1657#[serde(rename_all = "camelCase")]
1658#[non_exhaustive]
1659pub struct OtherStateUpdate {
1660    /// Unrecognized state discriminator.
1661    pub state: String,
1662    /// Remaining fields in the state object.
1663    #[serde(flatten)]
1664    pub fields: BTreeMap<String, serde_json::Value>,
1665}
1666
1667#[cfg(feature = "unstable_subagents")]
1668impl OtherStateUpdate {
1669    /// Builds a custom state, preserving its extension fields.
1670    #[must_use]
1671    pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1672        fields.remove("state");
1673        Self {
1674            state: state.into(),
1675            fields,
1676        }
1677    }
1678}
1679
1680#[cfg(feature = "unstable_subagents")]
1681impl<'de> Deserialize<'de> for OtherStateUpdate {
1682    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1683    where
1684        D: serde::Deserializer<'de>,
1685    {
1686        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1687        let state = fields
1688            .remove("state")
1689            .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1690        let serde_json::Value::String(state) = state else {
1691            return Err(serde::de::Error::custom("`state` must be a string"));
1692        };
1693        if is_known_state_update(&state) {
1694            return Err(serde::de::Error::custom(format!(
1695                "known state update `{state}` did not match its schema"
1696            )));
1697        }
1698        Ok(Self { state, fields })
1699    }
1700}
1701
1702#[cfg(feature = "unstable_subagents")]
1703const KNOWN_STATE_UPDATE_STATES: &[&str] = &["running", "idle", "requires_action", "unknown"];
1704
1705#[cfg(feature = "unstable_subagents")]
1706fn is_known_state_update(state: &str) -> bool {
1707    KNOWN_STATE_UPDATE_STATES.contains(&state)
1708}
1709
1710#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1711fn other_state_update_schema(schema: &mut Schema) {
1712    let known = KNOWN_STATE_UPDATE_STATES
1713        .iter()
1714        .map(|state| {
1715            serde_json::json!({
1716                "properties": { "state": { "const": state, "type": "string" } },
1717                "required": ["state"],
1718                "type": "object"
1719            })
1720        })
1721        .collect::<Vec<_>>();
1722    schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1723}
1724
1725/// The current mode of the session has changed
1726///
1727/// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/v1/session-modes)
1728#[serde_as]
1729#[skip_serializing_none]
1730#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1731#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1732#[serde(rename_all = "camelCase")]
1733#[non_exhaustive]
1734pub struct CurrentModeUpdate {
1735    /// The ID of the current mode
1736    pub current_mode_id: SessionModeId,
1737    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1738    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1739    /// these keys.
1740    ///
1741    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1742    #[serde_as(deserialize_as = "DefaultOnError")]
1743    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1744    #[serde(default)]
1745    #[serde(rename = "_meta")]
1746    pub meta: Option<Meta>,
1747}
1748
1749impl CurrentModeUpdate {
1750    /// Builds [`CurrentModeUpdate`] with the required fields set; optional fields start unset or empty.
1751    #[must_use]
1752    pub fn new(current_mode_id: impl Into<SessionModeId>) -> Self {
1753        Self {
1754            current_mode_id: current_mode_id.into(),
1755            meta: None,
1756        }
1757    }
1758
1759    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1760    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1761    /// these keys.
1762    ///
1763    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1764    #[must_use]
1765    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1766        self.meta = meta.into_option();
1767        self
1768    }
1769}
1770
1771/// Session configuration options have been updated.
1772#[serde_as]
1773#[skip_serializing_none]
1774#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1776#[serde(rename_all = "camelCase")]
1777#[non_exhaustive]
1778pub struct ConfigOptionUpdate {
1779    /// The full set of configuration options and their current values.
1780    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1781    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1782    pub config_options: Vec<SessionConfigOption>,
1783    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1784    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1785    /// these keys.
1786    ///
1787    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1788    #[serde_as(deserialize_as = "DefaultOnError")]
1789    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1790    #[serde(default)]
1791    #[serde(rename = "_meta")]
1792    pub meta: Option<Meta>,
1793}
1794
1795impl ConfigOptionUpdate {
1796    /// Builds [`ConfigOptionUpdate`] with the required fields set; optional fields start unset or empty.
1797    #[must_use]
1798    pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1799        Self {
1800            config_options,
1801            meta: None,
1802        }
1803    }
1804
1805    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1806    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1807    /// these keys.
1808    ///
1809    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1810    #[must_use]
1811    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1812        self.meta = meta.into_option();
1813        self
1814    }
1815}
1816
1817/// Update to session metadata. All fields are optional to support partial updates.
1818///
1819/// Agents send this notification to update session information like title or custom metadata.
1820/// This allows clients to display dynamic session names and track session state changes.
1821#[serde_as]
1822#[skip_serializing_none]
1823#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1824#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1825#[serde(rename_all = "camelCase")]
1826#[non_exhaustive]
1827pub struct SessionInfoUpdate {
1828    /// Human-readable title for the session. Set to null to clear.
1829    #[serde_as(deserialize_as = "DefaultOnError")]
1830    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1831    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1832    pub title: MaybeUndefined<String>,
1833    /// ISO 8601 timestamp of last activity. Set to null to clear.
1834    #[serde_as(deserialize_as = "DefaultOnError")]
1835    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1836    #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1837    pub updated_at: MaybeUndefined<String>,
1838    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1839    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1840    /// these keys.
1841    ///
1842    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1843    #[serde_as(deserialize_as = "DefaultOnError")]
1844    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1845    #[serde(default)]
1846    #[serde(rename = "_meta")]
1847    pub meta: Option<Meta>,
1848}
1849
1850impl SessionInfoUpdate {
1851    /// Builds [`SessionInfoUpdate`] with the required fields set; optional fields start unset or empty.
1852    #[must_use]
1853    pub fn new() -> Self {
1854        Self::default()
1855    }
1856
1857    /// Human-readable title for the session. Set to null to clear.
1858    #[must_use]
1859    pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1860        self.title = title.into_maybe_undefined();
1861        self
1862    }
1863
1864    /// ISO 8601 timestamp of last activity. Set to null to clear.
1865    #[must_use]
1866    pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1867        self.updated_at = updated_at.into_maybe_undefined();
1868        self
1869    }
1870
1871    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1872    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1873    /// these keys.
1874    ///
1875    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1876    #[must_use]
1877    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1878        self.meta = meta.into_option();
1879        self
1880    }
1881}
1882
1883/// Context window and cost update for a session.
1884#[serde_as]
1885#[skip_serializing_none]
1886#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1887#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1888#[serde(rename_all = "camelCase")]
1889#[non_exhaustive]
1890pub struct UsageUpdate {
1891    /// Tokens currently in context.
1892    pub used: u64,
1893    /// Total context window size in tokens.
1894    pub size: u64,
1895    /// Cumulative session cost (optional).
1896    #[serde_as(deserialize_as = "DefaultOnError")]
1897    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1898    #[serde(default)]
1899    pub cost: Option<Cost>,
1900    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1901    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1902    /// these keys.
1903    ///
1904    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1905    #[serde_as(deserialize_as = "DefaultOnError")]
1906    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1907    #[serde(default)]
1908    #[serde(rename = "_meta")]
1909    pub meta: Option<Meta>,
1910}
1911
1912impl UsageUpdate {
1913    /// Builds [`UsageUpdate`] with the required fields set; optional fields start unset or empty.
1914    #[must_use]
1915    pub fn new(used: u64, size: u64) -> Self {
1916        Self {
1917            used,
1918            size,
1919            cost: None,
1920            meta: None,
1921        }
1922    }
1923
1924    /// Cumulative session cost (optional).
1925    #[must_use]
1926    pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1927        self.cost = cost.into_option();
1928        self
1929    }
1930
1931    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1932    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1933    /// these keys.
1934    ///
1935    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1936    #[must_use]
1937    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1938        self.meta = meta.into_option();
1939        self
1940    }
1941}
1942
1943/// Cost information for a session.
1944#[serde_as]
1945#[skip_serializing_none]
1946#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1947#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1948#[serde(rename_all = "camelCase")]
1949#[non_exhaustive]
1950pub struct Cost {
1951    /// Total cumulative cost for session.
1952    pub amount: f64,
1953    /// Unit of `amount`: an ISO 4217 currency code (e.g., "USD", "EUR") for money, or another
1954    /// unit such as "credits".
1955    pub currency: String,
1956    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1957    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1958    /// these keys.
1959    ///
1960    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1961    #[serde_as(deserialize_as = "DefaultOnError")]
1962    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1963    #[serde(default)]
1964    #[serde(rename = "_meta")]
1965    pub meta: Option<Meta>,
1966}
1967
1968impl Cost {
1969    /// Builds [`Cost`] with the required fields set; optional fields start unset or empty.
1970    #[must_use]
1971    pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1972        Self {
1973            amount,
1974            currency: currency.into(),
1975            meta: None,
1976        }
1977    }
1978
1979    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1980    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1981    /// these keys.
1982    ///
1983    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
1984    #[must_use]
1985    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1986        self.meta = meta.into_option();
1987        self
1988    }
1989}
1990
1991/// A streamed item of content
1992#[serde_as]
1993#[skip_serializing_none]
1994#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1995#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1996#[serde(rename_all = "camelCase")]
1997#[non_exhaustive]
1998pub struct ContentChunk {
1999    /// A single item of content
2000    pub content: ContentBlock,
2001    /// A unique identifier for the message this chunk belongs to.
2002    ///
2003    /// All chunks belonging to the same message share the same `messageId`.
2004    /// A change in `messageId` indicates a new message has started.
2005    #[serde_as(deserialize_as = "DefaultOnError")]
2006    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2007    #[serde(default)]
2008    pub message_id: Option<MessageId>,
2009    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2010    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2011    /// these keys.
2012    ///
2013    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2014    #[serde_as(deserialize_as = "DefaultOnError")]
2015    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2016    #[serde(default)]
2017    #[serde(rename = "_meta")]
2018    pub meta: Option<Meta>,
2019}
2020
2021impl ContentChunk {
2022    /// Builds [`ContentChunk`] with the required fields set; optional fields start unset or empty.
2023    #[must_use]
2024    pub fn new(content: ContentBlock) -> Self {
2025        Self {
2026            content,
2027            message_id: None,
2028            meta: None,
2029        }
2030    }
2031
2032    /// A unique identifier for the message this chunk belongs to.
2033    ///
2034    /// All chunks belonging to the same message share the same `messageId`.
2035    /// A change in `messageId` indicates a new message has started.
2036    #[must_use]
2037    pub fn message_id(mut self, message_id: impl IntoOption<MessageId>) -> Self {
2038        self.message_id = message_id.into_option();
2039        self
2040    }
2041
2042    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2043    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2044    /// these keys.
2045    ///
2046    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2047    #[must_use]
2048    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2049        self.meta = meta.into_option();
2050        self
2051    }
2052}
2053
2054/// Unique identifier for a message within a session.
2055#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2056#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2057#[serde(transparent)]
2058#[from(Arc<str>, String, &'static str)]
2059#[non_exhaustive]
2060pub struct MessageId(pub Arc<str>);
2061
2062impl MessageId {
2063    /// Wraps a protocol string as a typed [`MessageId`].
2064    #[must_use]
2065    pub fn new(id: impl Into<Arc<str>>) -> Self {
2066        Self(id.into())
2067    }
2068}
2069
2070impl IntoOption<MessageId> for &str {
2071    fn into_option(self) -> Option<MessageId> {
2072        Some(MessageId::new(self))
2073    }
2074}
2075
2076/// Available commands are ready or have changed
2077#[serde_as]
2078#[skip_serializing_none]
2079#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2080#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2081#[serde(rename_all = "camelCase")]
2082#[non_exhaustive]
2083pub struct AvailableCommandsUpdate {
2084    /// Commands the agent can execute
2085    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2086    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2087    pub available_commands: Vec<AvailableCommand>,
2088    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2089    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2090    /// these keys.
2091    ///
2092    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2093    #[serde_as(deserialize_as = "DefaultOnError")]
2094    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2095    #[serde(default)]
2096    #[serde(rename = "_meta")]
2097    pub meta: Option<Meta>,
2098}
2099
2100impl AvailableCommandsUpdate {
2101    /// Builds [`AvailableCommandsUpdate`] with the required fields set; optional fields start unset or empty.
2102    #[must_use]
2103    pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
2104        Self {
2105            available_commands,
2106            meta: None,
2107        }
2108    }
2109
2110    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2111    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2112    /// these keys.
2113    ///
2114    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2115    #[must_use]
2116    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2117        self.meta = meta.into_option();
2118        self
2119    }
2120}
2121
2122/// Information about a command.
2123#[serde_as]
2124#[skip_serializing_none]
2125#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2127#[serde(rename_all = "camelCase")]
2128#[non_exhaustive]
2129pub struct AvailableCommand {
2130    /// Command name (e.g., `create_plan`, `research_codebase`).
2131    pub name: String,
2132    /// Human-readable description of what the command does.
2133    pub description: String,
2134    /// Input for the command if required
2135    #[serde_as(deserialize_as = "DefaultOnError")]
2136    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2137    #[serde(default)]
2138    pub input: Option<AvailableCommandInput>,
2139    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2140    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2141    /// these keys.
2142    ///
2143    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2144    #[serde_as(deserialize_as = "DefaultOnError")]
2145    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2146    #[serde(default)]
2147    #[serde(rename = "_meta")]
2148    pub meta: Option<Meta>,
2149}
2150
2151impl AvailableCommand {
2152    /// Builds [`AvailableCommand`] with the required fields set; optional fields start unset or empty.
2153    #[must_use]
2154    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
2155        Self {
2156            name: name.into(),
2157            description: description.into(),
2158            input: None,
2159            meta: None,
2160        }
2161    }
2162
2163    /// Input for the command if required
2164    #[must_use]
2165    pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
2166        self.input = input.into_option();
2167        self
2168    }
2169
2170    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2171    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2172    /// these keys.
2173    ///
2174    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2175    #[must_use]
2176    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2177        self.meta = meta.into_option();
2178        self
2179    }
2180}
2181
2182/// The input specification for a command.
2183#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2185#[serde(untagged, rename_all = "camelCase")]
2186#[non_exhaustive]
2187pub enum AvailableCommandInput {
2188    /// All text that was typed after the command name is provided as input.
2189    Unstructured(UnstructuredCommandInput),
2190}
2191
2192/// All text that was typed after the command name is provided as input.
2193#[serde_as]
2194#[skip_serializing_none]
2195#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2197#[serde(rename_all = "camelCase")]
2198#[non_exhaustive]
2199pub struct UnstructuredCommandInput {
2200    /// A hint to display when the input hasn't been provided yet
2201    pub hint: String,
2202    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2203    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2204    /// these keys.
2205    ///
2206    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2207    #[serde_as(deserialize_as = "DefaultOnError")]
2208    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2209    #[serde(default)]
2210    #[serde(rename = "_meta")]
2211    pub meta: Option<Meta>,
2212}
2213
2214impl UnstructuredCommandInput {
2215    /// Builds [`UnstructuredCommandInput`] with the required fields set; optional fields start unset or empty.
2216    #[must_use]
2217    pub fn new(hint: impl Into<String>) -> Self {
2218        Self {
2219            hint: hint.into(),
2220            meta: None,
2221        }
2222    }
2223
2224    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2225    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2226    /// these keys.
2227    ///
2228    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2229    #[must_use]
2230    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2231        self.meta = meta.into_option();
2232        self
2233    }
2234}
2235
2236// Permission
2237
2238/// Request for user permission to execute a tool call.
2239///
2240/// Sent when the agent needs authorization before performing a sensitive operation.
2241///
2242/// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/v1/tool-calls#requesting-permission)
2243#[serde_as]
2244#[skip_serializing_none]
2245#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2247#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2248#[serde(rename_all = "camelCase")]
2249#[non_exhaustive]
2250pub struct RequestPermissionRequest {
2251    /// The session ID for this request.
2252    pub session_id: SessionId,
2253    /// Details about the tool call requiring permission.
2254    pub tool_call: ToolCallUpdate,
2255    /// Available permission options for the user to choose from.
2256    pub options: Vec<PermissionOption>,
2257    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2258    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2259    /// these keys.
2260    ///
2261    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2262    #[serde_as(deserialize_as = "DefaultOnError")]
2263    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2264    #[serde(default)]
2265    #[serde(rename = "_meta")]
2266    pub meta: Option<Meta>,
2267}
2268
2269impl RequestPermissionRequest {
2270    /// Builds [`RequestPermissionRequest`] with the required request fields set; optional fields start unset or empty.
2271    #[must_use]
2272    pub fn new(
2273        session_id: impl Into<SessionId>,
2274        tool_call: ToolCallUpdate,
2275        options: Vec<PermissionOption>,
2276    ) -> Self {
2277        Self {
2278            session_id: session_id.into(),
2279            tool_call,
2280            options,
2281            meta: None,
2282        }
2283    }
2284
2285    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2286    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2287    /// these keys.
2288    ///
2289    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2290    #[must_use]
2291    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2292        self.meta = meta.into_option();
2293        self
2294    }
2295}
2296
2297/// An option presented to the user when requesting permission.
2298#[serde_as]
2299#[skip_serializing_none]
2300#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2302#[serde(rename_all = "camelCase")]
2303#[non_exhaustive]
2304pub struct PermissionOption {
2305    /// Unique identifier for this permission option.
2306    pub option_id: PermissionOptionId,
2307    /// Human-readable label to display to the user.
2308    pub name: String,
2309    /// Hint about the nature of this permission option.
2310    pub kind: PermissionOptionKind,
2311    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2312    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2313    /// these keys.
2314    ///
2315    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2316    #[serde_as(deserialize_as = "DefaultOnError")]
2317    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2318    #[serde(default)]
2319    #[serde(rename = "_meta")]
2320    pub meta: Option<Meta>,
2321}
2322
2323impl PermissionOption {
2324    /// Builds [`PermissionOption`] with the required fields set; optional fields start unset or empty.
2325    #[must_use]
2326    pub fn new(
2327        option_id: impl Into<PermissionOptionId>,
2328        name: impl Into<String>,
2329        kind: PermissionOptionKind,
2330    ) -> Self {
2331        Self {
2332            option_id: option_id.into(),
2333            name: name.into(),
2334            kind,
2335            meta: None,
2336        }
2337    }
2338
2339    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2340    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2341    /// these keys.
2342    ///
2343    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2344    #[must_use]
2345    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2346        self.meta = meta.into_option();
2347        self
2348    }
2349}
2350
2351/// Unique identifier for a permission option.
2352#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2353#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2354#[serde(transparent)]
2355#[from(Arc<str>, String, &'static str)]
2356#[non_exhaustive]
2357pub struct PermissionOptionId(pub Arc<str>);
2358
2359impl PermissionOptionId {
2360    /// Wraps a protocol string as a typed [`PermissionOptionId`].
2361    #[must_use]
2362    pub fn new(id: impl Into<Arc<str>>) -> Self {
2363        Self(id.into())
2364    }
2365}
2366
2367/// The type of permission option being presented to the user.
2368///
2369/// Helps clients choose appropriate icons and UI treatment.
2370#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2371#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2372#[serde(rename_all = "snake_case")]
2373#[non_exhaustive]
2374pub enum PermissionOptionKind {
2375    /// Allow this operation only this time.
2376    AllowOnce,
2377    /// Allow this operation and remember the choice.
2378    AllowAlways,
2379    /// Reject this operation only this time.
2380    RejectOnce,
2381    /// Reject this operation and remember the choice.
2382    RejectAlways,
2383}
2384
2385/// Response to a permission request.
2386#[serde_as]
2387#[skip_serializing_none]
2388#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2389#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2390#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2391#[serde(rename_all = "camelCase")]
2392#[non_exhaustive]
2393pub struct RequestPermissionResponse {
2394    /// The user's decision on the permission request.
2395    // This extra-level is unfortunately needed because the output must be an object
2396    pub outcome: RequestPermissionOutcome,
2397    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2398    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2399    /// these keys.
2400    ///
2401    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2402    #[serde_as(deserialize_as = "DefaultOnError")]
2403    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2404    #[serde(default)]
2405    #[serde(rename = "_meta")]
2406    pub meta: Option<Meta>,
2407}
2408
2409impl RequestPermissionResponse {
2410    /// Builds [`RequestPermissionResponse`] with the required response fields set; optional fields start unset or empty.
2411    #[must_use]
2412    pub fn new(outcome: RequestPermissionOutcome) -> Self {
2413        Self {
2414            outcome,
2415            meta: None,
2416        }
2417    }
2418
2419    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2420    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2421    /// these keys.
2422    ///
2423    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2424    #[must_use]
2425    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2426        self.meta = meta.into_option();
2427        self
2428    }
2429}
2430
2431/// The outcome of a permission request.
2432#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2433#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2434#[serde(tag = "outcome", rename_all = "snake_case")]
2435#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "outcome"})))]
2436#[non_exhaustive]
2437pub enum RequestPermissionOutcome {
2438    /// The prompt turn was cancelled before the user responded.
2439    ///
2440    /// When a client sends a `session/cancel` notification to cancel an ongoing
2441    /// prompt turn, it MUST respond to all pending `session/request_permission`
2442    /// requests with this `cancelled` outcome.
2443    ///
2444    /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/v1/prompt-turn#cancellation)
2445    Cancelled,
2446    /// The user selected one of the provided options.
2447    #[serde(rename_all = "camelCase")]
2448    Selected(SelectedPermissionOutcome),
2449}
2450
2451/// The user selected one of the provided options.
2452#[serde_as]
2453#[skip_serializing_none]
2454#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2455#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2456#[serde(rename_all = "camelCase")]
2457#[non_exhaustive]
2458pub struct SelectedPermissionOutcome {
2459    /// The ID of the option the user selected.
2460    pub option_id: PermissionOptionId,
2461    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2462    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2463    /// these keys.
2464    ///
2465    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2466    #[serde_as(deserialize_as = "DefaultOnError")]
2467    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2468    #[serde(default)]
2469    #[serde(rename = "_meta")]
2470    pub meta: Option<Meta>,
2471}
2472
2473impl SelectedPermissionOutcome {
2474    /// Builds [`SelectedPermissionOutcome`] with the required fields set; optional fields start unset or empty.
2475    #[must_use]
2476    pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2477        Self {
2478            option_id: option_id.into(),
2479            meta: None,
2480        }
2481    }
2482
2483    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2484    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2485    /// these keys.
2486    ///
2487    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2488    #[must_use]
2489    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2490        self.meta = meta.into_option();
2491        self
2492    }
2493}
2494
2495// Write text file
2496
2497/// Request to write content to a text file.
2498///
2499/// Only available if the client supports the `fs.writeTextFile` capability.
2500#[serde_as]
2501#[skip_serializing_none]
2502#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2503#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2504#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2505#[serde(rename_all = "camelCase")]
2506#[non_exhaustive]
2507pub struct WriteTextFileRequest {
2508    /// The session ID for this request.
2509    pub session_id: SessionId,
2510    /// Absolute path to the file to write.
2511    pub path: PathBuf,
2512    /// The text content to write to the file.
2513    pub content: String,
2514    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2515    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2516    /// these keys.
2517    ///
2518    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2519    #[serde_as(deserialize_as = "DefaultOnError")]
2520    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2521    #[serde(default)]
2522    #[serde(rename = "_meta")]
2523    pub meta: Option<Meta>,
2524}
2525
2526impl WriteTextFileRequest {
2527    /// Builds [`WriteTextFileRequest`] with the required request fields set; optional fields start unset or empty.
2528    #[must_use]
2529    pub fn new(
2530        session_id: impl Into<SessionId>,
2531        path: impl Into<PathBuf>,
2532        content: impl Into<String>,
2533    ) -> Self {
2534        Self {
2535            session_id: session_id.into(),
2536            path: path.into(),
2537            content: content.into(),
2538            meta: None,
2539        }
2540    }
2541
2542    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2543    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2544    /// these keys.
2545    ///
2546    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2547    #[must_use]
2548    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2549        self.meta = meta.into_option();
2550        self
2551    }
2552}
2553
2554crate::serde_util::default_on_null! {
2555    /// Response to `fs/write_text_file`
2556    #[serde_as]
2557    #[skip_serializing_none]
2558    #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2559    #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2560    #[serde(rename_all = "camelCase")]
2561    #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2562    #[non_exhaustive]
2563    pub struct WriteTextFileResponse {
2564        /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2565        /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2566        /// these keys.
2567        ///
2568        /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2569        #[serde_as(deserialize_as = "DefaultOnError")]
2570        #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2571        #[serde(default)]
2572        #[serde(rename = "_meta")]
2573        pub meta: Option<Meta>,
2574    }
2575}
2576
2577impl WriteTextFileResponse {
2578    /// Builds [`WriteTextFileResponse`] with the required response fields set; optional fields start unset or empty.
2579    #[must_use]
2580    pub fn new() -> Self {
2581        Self::default()
2582    }
2583
2584    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2585    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2586    /// these keys.
2587    ///
2588    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2589    #[must_use]
2590    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2591        self.meta = meta.into_option();
2592        self
2593    }
2594}
2595
2596// Read text file
2597
2598/// Request to read content from a text file.
2599///
2600/// Only available if the client supports the `fs.readTextFile` capability.
2601#[serde_as]
2602#[skip_serializing_none]
2603#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2604#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2605#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2606#[serde(rename_all = "camelCase")]
2607#[non_exhaustive]
2608pub struct ReadTextFileRequest {
2609    /// The session ID for this request.
2610    pub session_id: SessionId,
2611    /// Absolute path to the file to read.
2612    pub path: PathBuf,
2613    /// Line number to start reading from (1-based).
2614    #[serde(default)]
2615    pub line: Option<u32>,
2616    /// Maximum number of lines to read.
2617    #[serde(default)]
2618    pub limit: Option<u32>,
2619    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2620    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2621    /// these keys.
2622    ///
2623    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2624    #[serde_as(deserialize_as = "DefaultOnError")]
2625    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2626    #[serde(default)]
2627    #[serde(rename = "_meta")]
2628    pub meta: Option<Meta>,
2629}
2630
2631impl ReadTextFileRequest {
2632    /// Builds [`ReadTextFileRequest`] with the required request fields set; optional fields start unset or empty.
2633    #[must_use]
2634    pub fn new(session_id: impl Into<SessionId>, path: impl Into<PathBuf>) -> Self {
2635        Self {
2636            session_id: session_id.into(),
2637            path: path.into(),
2638            line: None,
2639            limit: None,
2640            meta: None,
2641        }
2642    }
2643
2644    /// Line number to start reading from (1-based).
2645    #[must_use]
2646    pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
2647        self.line = line.into_option();
2648        self
2649    }
2650
2651    /// Maximum number of lines to read.
2652    #[must_use]
2653    pub fn limit(mut self, limit: impl IntoOption<u32>) -> Self {
2654        self.limit = limit.into_option();
2655        self
2656    }
2657
2658    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2659    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2660    /// these keys.
2661    ///
2662    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2663    #[must_use]
2664    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2665        self.meta = meta.into_option();
2666        self
2667    }
2668}
2669
2670/// Response containing the contents of a text file.
2671#[serde_as]
2672#[skip_serializing_none]
2673#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2674#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2675#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2676#[serde(rename_all = "camelCase")]
2677#[non_exhaustive]
2678pub struct ReadTextFileResponse {
2679    /// Content payload returned by this response.
2680    pub content: String,
2681    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2682    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2683    /// these keys.
2684    ///
2685    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2686    #[serde_as(deserialize_as = "DefaultOnError")]
2687    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2688    #[serde(default)]
2689    #[serde(rename = "_meta")]
2690    pub meta: Option<Meta>,
2691}
2692
2693impl ReadTextFileResponse {
2694    /// Builds [`ReadTextFileResponse`] with the required response fields set; optional fields start unset or empty.
2695    #[must_use]
2696    pub fn new(content: impl Into<String>) -> Self {
2697        Self {
2698            content: content.into(),
2699            meta: None,
2700        }
2701    }
2702
2703    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2704    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2705    /// these keys.
2706    ///
2707    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2708    #[must_use]
2709    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2710        self.meta = meta.into_option();
2711        self
2712    }
2713}
2714
2715// Terminals
2716
2717/// Typed identifier used for terminal values on the wire.
2718#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2719#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2720#[serde(transparent)]
2721#[from(Arc<str>, String, &'static str)]
2722#[non_exhaustive]
2723pub struct TerminalId(pub Arc<str>);
2724
2725impl TerminalId {
2726    /// Wraps a protocol string as a typed [`TerminalId`].
2727    #[must_use]
2728    pub fn new(id: impl Into<Arc<str>>) -> Self {
2729        Self(id.into())
2730    }
2731}
2732
2733/// Request to create a new terminal and execute a command.
2734#[serde_as]
2735#[skip_serializing_none]
2736#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2737#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2738#[serde(rename_all = "camelCase")]
2739#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2740#[non_exhaustive]
2741pub struct CreateTerminalRequest {
2742    /// The session ID for this request.
2743    pub session_id: SessionId,
2744    /// The command to execute.
2745    pub command: String,
2746    /// Array of command arguments.
2747    #[serde_as(deserialize_as = "DefaultOnNull")]
2748    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2749    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2750    pub args: Vec<String>,
2751    /// Environment variables for the command.
2752    #[serde_as(deserialize_as = "DefaultOnNull")]
2753    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2754    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2755    pub env: Vec<EnvVariable>,
2756    /// Working directory for the command. Must be an absolute path.
2757    #[serde(default)]
2758    pub cwd: Option<PathBuf>,
2759    /// Maximum number of output bytes to retain.
2760    ///
2761    /// When the limit is exceeded, the Client truncates from the beginning of the output
2762    /// to stay within the limit.
2763    ///
2764    /// The Client MUST ensure truncation happens at a character boundary to maintain valid
2765    /// string output, even if this means the retained output is slightly less than the
2766    /// specified limit.
2767    #[serde(default)]
2768    pub output_byte_limit: Option<u64>,
2769    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2770    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2771    /// these keys.
2772    ///
2773    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2774    #[serde_as(deserialize_as = "DefaultOnError")]
2775    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2776    #[serde(default)]
2777    #[serde(rename = "_meta")]
2778    pub meta: Option<Meta>,
2779}
2780
2781impl CreateTerminalRequest {
2782    /// Builds [`CreateTerminalRequest`] with the required request fields set; optional fields start unset or empty.
2783    #[must_use]
2784    pub fn new(session_id: impl Into<SessionId>, command: impl Into<String>) -> Self {
2785        Self {
2786            session_id: session_id.into(),
2787            command: command.into(),
2788            args: Vec::new(),
2789            env: Vec::new(),
2790            cwd: None,
2791            output_byte_limit: None,
2792            meta: None,
2793        }
2794    }
2795
2796    /// Array of command arguments.
2797    #[must_use]
2798    pub fn args(mut self, args: Vec<String>) -> Self {
2799        self.args = args;
2800        self
2801    }
2802
2803    /// Environment variables for the command.
2804    #[must_use]
2805    pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
2806        self.env = env;
2807        self
2808    }
2809
2810    /// Working directory for the command. Must be an absolute path.
2811    #[must_use]
2812    pub fn cwd(mut self, cwd: impl IntoOption<PathBuf>) -> Self {
2813        self.cwd = cwd.into_option();
2814        self
2815    }
2816
2817    /// Maximum number of output bytes to retain.
2818    ///
2819    /// When the limit is exceeded, the Client truncates from the beginning of the output
2820    /// to stay within the limit.
2821    ///
2822    /// The Client MUST ensure truncation happens at a character boundary to maintain valid
2823    /// string output, even if this means the retained output is slightly less than the
2824    /// specified limit.
2825    #[must_use]
2826    pub fn output_byte_limit(mut self, output_byte_limit: impl IntoOption<u64>) -> Self {
2827        self.output_byte_limit = output_byte_limit.into_option();
2828        self
2829    }
2830
2831    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2832    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2833    /// these keys.
2834    ///
2835    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2836    #[must_use]
2837    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2838        self.meta = meta.into_option();
2839        self
2840    }
2841}
2842
2843/// Response containing the ID of the created terminal.
2844#[serde_as]
2845#[skip_serializing_none]
2846#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2847#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2848#[serde(rename_all = "camelCase")]
2849#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2850#[non_exhaustive]
2851pub struct CreateTerminalResponse {
2852    /// The unique identifier for the created terminal.
2853    pub terminal_id: TerminalId,
2854    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2855    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2856    /// these keys.
2857    ///
2858    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2859    #[serde_as(deserialize_as = "DefaultOnError")]
2860    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2861    #[serde(default)]
2862    #[serde(rename = "_meta")]
2863    pub meta: Option<Meta>,
2864}
2865
2866impl CreateTerminalResponse {
2867    /// Builds [`CreateTerminalResponse`] with the required response fields set; optional fields start unset or empty.
2868    #[must_use]
2869    pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
2870        Self {
2871            terminal_id: terminal_id.into(),
2872            meta: None,
2873        }
2874    }
2875
2876    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2877    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2878    /// these keys.
2879    ///
2880    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2881    #[must_use]
2882    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2883        self.meta = meta.into_option();
2884        self
2885    }
2886}
2887
2888/// Request to get the current output and status of a terminal.
2889#[serde_as]
2890#[skip_serializing_none]
2891#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2892#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2893#[serde(rename_all = "camelCase")]
2894#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2895#[non_exhaustive]
2896pub struct TerminalOutputRequest {
2897    /// The session ID for this request.
2898    pub session_id: SessionId,
2899    /// The ID of the terminal to get output from.
2900    pub terminal_id: TerminalId,
2901    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2902    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2903    /// these keys.
2904    ///
2905    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2906    #[serde_as(deserialize_as = "DefaultOnError")]
2907    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2908    #[serde(default)]
2909    #[serde(rename = "_meta")]
2910    pub meta: Option<Meta>,
2911}
2912
2913impl TerminalOutputRequest {
2914    /// Builds [`TerminalOutputRequest`] with the required request fields set; optional fields start unset or empty.
2915    #[must_use]
2916    pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2917        Self {
2918            session_id: session_id.into(),
2919            terminal_id: terminal_id.into(),
2920            meta: None,
2921        }
2922    }
2923
2924    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2925    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2926    /// these keys.
2927    ///
2928    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2929    #[must_use]
2930    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2931        self.meta = meta.into_option();
2932        self
2933    }
2934}
2935
2936/// Response containing the terminal output and exit status.
2937#[serde_as]
2938#[skip_serializing_none]
2939#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2940#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2941#[serde(rename_all = "camelCase")]
2942#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2943#[non_exhaustive]
2944pub struct TerminalOutputResponse {
2945    /// The terminal output captured so far.
2946    pub output: String,
2947    /// Whether the output was truncated due to byte limits.
2948    pub truncated: bool,
2949    /// Exit status if the command has completed.
2950    #[serde_as(deserialize_as = "DefaultOnError")]
2951    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2952    #[serde(default)]
2953    pub exit_status: Option<TerminalExitStatus>,
2954    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2955    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2956    /// these keys.
2957    ///
2958    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2959    #[serde_as(deserialize_as = "DefaultOnError")]
2960    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2961    #[serde(default)]
2962    #[serde(rename = "_meta")]
2963    pub meta: Option<Meta>,
2964}
2965
2966impl TerminalOutputResponse {
2967    /// Builds [`TerminalOutputResponse`] with the required response fields set; optional fields start unset or empty.
2968    #[must_use]
2969    pub fn new(output: impl Into<String>, truncated: bool) -> Self {
2970        Self {
2971            output: output.into(),
2972            truncated,
2973            exit_status: None,
2974            meta: None,
2975        }
2976    }
2977
2978    /// Exit status if the command has completed.
2979    #[must_use]
2980    pub fn exit_status(mut self, exit_status: impl IntoOption<TerminalExitStatus>) -> Self {
2981        self.exit_status = exit_status.into_option();
2982        self
2983    }
2984
2985    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2986    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2987    /// these keys.
2988    ///
2989    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
2990    #[must_use]
2991    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2992        self.meta = meta.into_option();
2993        self
2994    }
2995}
2996
2997/// Request to release a terminal and free its resources.
2998#[serde_as]
2999#[skip_serializing_none]
3000#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3001#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3002#[serde(rename_all = "camelCase")]
3003#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
3004#[non_exhaustive]
3005pub struct ReleaseTerminalRequest {
3006    /// The session ID for this request.
3007    pub session_id: SessionId,
3008    /// The ID of the terminal to release.
3009    pub terminal_id: TerminalId,
3010    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3011    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3012    /// these keys.
3013    ///
3014    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3015    #[serde_as(deserialize_as = "DefaultOnError")]
3016    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3017    #[serde(default)]
3018    #[serde(rename = "_meta")]
3019    pub meta: Option<Meta>,
3020}
3021
3022impl ReleaseTerminalRequest {
3023    /// Builds [`ReleaseTerminalRequest`] with the required request fields set; optional fields start unset or empty.
3024    #[must_use]
3025    pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3026        Self {
3027            session_id: session_id.into(),
3028            terminal_id: terminal_id.into(),
3029            meta: None,
3030        }
3031    }
3032
3033    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3034    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3035    /// these keys.
3036    ///
3037    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3038    #[must_use]
3039    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3040        self.meta = meta.into_option();
3041        self
3042    }
3043}
3044
3045crate::serde_util::default_on_null! {
3046    /// Response to terminal/release method
3047    #[serde_as]
3048    #[skip_serializing_none]
3049    #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3050    #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3051    #[serde(rename_all = "camelCase")]
3052    #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
3053    #[non_exhaustive]
3054    pub struct ReleaseTerminalResponse {
3055        /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3056        /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3057        /// these keys.
3058        ///
3059        /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3060        #[serde_as(deserialize_as = "DefaultOnError")]
3061        #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3062        #[serde(default)]
3063        #[serde(rename = "_meta")]
3064        pub meta: Option<Meta>,
3065    }
3066}
3067
3068impl ReleaseTerminalResponse {
3069    /// Builds [`ReleaseTerminalResponse`] with the required response fields set; optional fields start unset or empty.
3070    #[must_use]
3071    pub fn new() -> Self {
3072        Self::default()
3073    }
3074
3075    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3076    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3077    /// these keys.
3078    ///
3079    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3080    #[must_use]
3081    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3082        self.meta = meta.into_option();
3083        self
3084    }
3085}
3086
3087/// Request to kill a terminal without releasing it.
3088#[serde_as]
3089#[skip_serializing_none]
3090#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3091#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3092#[serde(rename_all = "camelCase")]
3093#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3094#[non_exhaustive]
3095pub struct KillTerminalRequest {
3096    /// The session ID for this request.
3097    pub session_id: SessionId,
3098    /// The ID of the terminal to kill.
3099    pub terminal_id: TerminalId,
3100    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3101    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3102    /// these keys.
3103    ///
3104    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3105    #[serde_as(deserialize_as = "DefaultOnError")]
3106    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3107    #[serde(default)]
3108    #[serde(rename = "_meta")]
3109    pub meta: Option<Meta>,
3110}
3111
3112impl KillTerminalRequest {
3113    /// Builds [`KillTerminalRequest`] with the required request fields set; optional fields start unset or empty.
3114    #[must_use]
3115    pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3116        Self {
3117            session_id: session_id.into(),
3118            terminal_id: terminal_id.into(),
3119            meta: None,
3120        }
3121    }
3122
3123    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3124    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3125    /// these keys.
3126    ///
3127    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3128    #[must_use]
3129    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3130        self.meta = meta.into_option();
3131        self
3132    }
3133}
3134
3135crate::serde_util::default_on_null! {
3136    /// Response to `terminal/kill` method
3137    #[serde_as]
3138    #[skip_serializing_none]
3139    #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3140    #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3141    #[serde(rename_all = "camelCase")]
3142    #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3143    #[non_exhaustive]
3144    pub struct KillTerminalResponse {
3145        /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3146        /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3147        /// these keys.
3148        ///
3149        /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3150        #[serde_as(deserialize_as = "DefaultOnError")]
3151        #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3152        #[serde(default)]
3153        #[serde(rename = "_meta")]
3154        pub meta: Option<Meta>,
3155    }
3156}
3157
3158impl KillTerminalResponse {
3159    /// Builds [`KillTerminalResponse`] with the required response fields set; optional fields start unset or empty.
3160    #[must_use]
3161    pub fn new() -> Self {
3162        Self::default()
3163    }
3164
3165    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3166    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3167    /// these keys.
3168    ///
3169    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3170    #[must_use]
3171    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3172        self.meta = meta.into_option();
3173        self
3174    }
3175}
3176
3177/// Request to wait for a terminal command to exit.
3178#[serde_as]
3179#[skip_serializing_none]
3180#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3182#[serde(rename_all = "camelCase")]
3183#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3184#[non_exhaustive]
3185pub struct WaitForTerminalExitRequest {
3186    /// The session ID for this request.
3187    pub session_id: SessionId,
3188    /// The ID of the terminal to wait for.
3189    pub terminal_id: TerminalId,
3190    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3191    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3192    /// these keys.
3193    ///
3194    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3195    #[serde_as(deserialize_as = "DefaultOnError")]
3196    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3197    #[serde(default)]
3198    #[serde(rename = "_meta")]
3199    pub meta: Option<Meta>,
3200}
3201
3202impl WaitForTerminalExitRequest {
3203    /// Builds [`WaitForTerminalExitRequest`] with the required request fields set; optional fields start unset or empty.
3204    #[must_use]
3205    pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3206        Self {
3207            session_id: session_id.into(),
3208            terminal_id: terminal_id.into(),
3209            meta: None,
3210        }
3211    }
3212
3213    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3214    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3215    /// these keys.
3216    ///
3217    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3218    #[must_use]
3219    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3220        self.meta = meta.into_option();
3221        self
3222    }
3223}
3224
3225crate::serde_util::default_on_null! {
3226    /// Response containing the exit status of a terminal command.
3227    #[serde_as]
3228    #[skip_serializing_none]
3229    #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3230    #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3231    #[serde(rename_all = "camelCase")]
3232    #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3233    #[non_exhaustive]
3234    pub struct WaitForTerminalExitResponse {
3235        /// The exit status of the terminal command.
3236        #[serde(flatten)]
3237        pub exit_status: TerminalExitStatus,
3238        /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3239        /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3240        /// these keys.
3241        ///
3242        /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3243        #[serde_as(deserialize_as = "DefaultOnError")]
3244        #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3245        #[serde(default)]
3246        #[serde(rename = "_meta")]
3247        pub meta: Option<Meta>,
3248    }
3249}
3250
3251impl WaitForTerminalExitResponse {
3252    /// Builds [`WaitForTerminalExitResponse`] with the required response fields set; optional fields start unset or empty.
3253    #[must_use]
3254    pub fn new(exit_status: TerminalExitStatus) -> Self {
3255        Self {
3256            exit_status,
3257            meta: None,
3258        }
3259    }
3260
3261    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3262    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3263    /// these keys.
3264    ///
3265    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3266    #[must_use]
3267    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3268        self.meta = meta.into_option();
3269        self
3270    }
3271}
3272
3273/// Exit status of a terminal command.
3274#[serde_as]
3275#[skip_serializing_none]
3276#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3277#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3278#[serde(rename_all = "camelCase")]
3279#[non_exhaustive]
3280pub struct TerminalExitStatus {
3281    /// The process exit code (may be null if terminated by signal).
3282    #[serde_as(deserialize_as = "DefaultOnError")]
3283    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3284    #[serde(default)]
3285    pub exit_code: Option<u32>,
3286    /// The signal that terminated the process (may be null if exited normally).
3287    #[serde_as(deserialize_as = "DefaultOnError")]
3288    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3289    #[serde(default)]
3290    pub signal: Option<String>,
3291    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3292    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3293    /// these keys.
3294    ///
3295    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3296    #[serde_as(deserialize_as = "DefaultOnError")]
3297    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3298    #[serde(default)]
3299    #[serde(rename = "_meta")]
3300    pub meta: Option<Meta>,
3301}
3302
3303impl TerminalExitStatus {
3304    /// Builds [`TerminalExitStatus`] with the required fields set; optional fields start unset or empty.
3305    #[must_use]
3306    pub fn new() -> Self {
3307        Self::default()
3308    }
3309
3310    /// The process exit code (may be null if terminated by signal).
3311    #[must_use]
3312    pub fn exit_code(mut self, exit_code: impl IntoOption<u32>) -> Self {
3313        self.exit_code = exit_code.into_option();
3314        self
3315    }
3316
3317    /// The signal that terminated the process (may be null if exited normally).
3318    #[must_use]
3319    pub fn signal(mut self, signal: impl IntoOption<String>) -> Self {
3320        self.signal = signal.into_option();
3321        self
3322    }
3323
3324    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3325    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3326    /// these keys.
3327    ///
3328    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3329    #[must_use]
3330    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3331        self.meta = meta.into_option();
3332        self
3333    }
3334}
3335
3336// Capabilities
3337
3338/// Capabilities supported by the client.
3339///
3340/// Advertised during initialization to inform the agent about
3341/// available features and methods.
3342///
3343/// See protocol docs: [Client Capabilities](https://agentclientprotocol.com/protocol/v1/initialization#client-capabilities)
3344#[serde_as]
3345#[skip_serializing_none]
3346#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3347#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3348#[serde(rename_all = "camelCase")]
3349#[non_exhaustive]
3350pub struct ClientCapabilities {
3351    /// File system capabilities supported by the client.
3352    /// Determines which file operations the agent can request.
3353    #[serde_as(deserialize_as = "DefaultOnError")]
3354    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3355    #[serde(default)]
3356    pub fs: FileSystemCapabilities,
3357    /// Whether the Client support all `terminal/*` methods.
3358    #[serde_as(deserialize_as = "DefaultOnError")]
3359    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3360    #[serde(default)]
3361    pub terminal: bool,
3362    /// Session-related capabilities supported by the client.
3363    ///
3364    /// Optional. Omitted or `null` both mean the client does not advertise any
3365    /// session-related extensions.
3366    #[serde_as(deserialize_as = "DefaultOnError")]
3367    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3368    #[serde(default)]
3369    pub session: Option<ClientSessionCapabilities>,
3370    /// **UNSTABLE**
3371    ///
3372    /// This capability is not part of the spec yet, and may be removed or changed at any point.
3373    ///
3374    /// Whether the client understands exposed subagent sessions.
3375    ///
3376    /// Optional and nullable. Omitted or `null` both mean the client does not
3377    /// advertise support.
3378    /// Supplying `{}` means the client understands child associations, work-state
3379    /// snapshots, session-directed messages, and restricted-session semantics.
3380    #[cfg(feature = "unstable_subagents")]
3381    #[serde_as(deserialize_as = "DefaultOnError")]
3382    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3383    #[serde(default)]
3384    pub subagents: Option<SubagentCapabilities>,
3385    /// **UNSTABLE**
3386    ///
3387    /// This capability is not part of the spec yet, and may be removed or changed at any point.
3388    ///
3389    /// Whether the client supports `plan_update` and `plan_removed` session updates.
3390    ///
3391    /// Optional. Omitted or `null` both mean the client does not advertise support.
3392    /// Supplying `{}` means the client can receive both update types.
3393    #[cfg(feature = "unstable_plan_operations")]
3394    #[serde_as(deserialize_as = "DefaultOnError")]
3395    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3396    #[serde(default)]
3397    pub plan: Option<PlanCapabilities>,
3398    /// Authentication capabilities supported by the client.
3399    /// Determines which authentication method types the agent may include
3400    /// in its `InitializeResponse`.
3401    #[serde_as(deserialize_as = "DefaultOnError")]
3402    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3403    #[serde(default)]
3404    pub auth: AuthCapabilities,
3405    /// Elicitation capabilities supported by the client.
3406    /// Determines which elicitation modes the agent may use.
3407    ///
3408    /// Optional. Omitted or `null` both mean the client does not advertise
3409    /// elicitation support.
3410    #[serde_as(deserialize_as = "DefaultOnError")]
3411    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3412    #[serde(default)]
3413    pub elicitation: Option<ElicitationCapabilities>,
3414    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3415    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3416    /// these keys.
3417    ///
3418    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3419    #[serde_as(deserialize_as = "DefaultOnError")]
3420    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3421    #[serde(default)]
3422    #[serde(rename = "_meta")]
3423    pub meta: Option<Meta>,
3424}
3425
3426impl ClientCapabilities {
3427    /// Builds an empty [`ClientCapabilities`]; use builder methods to advertise supported sub-capabilities.
3428    #[must_use]
3429    pub fn new() -> Self {
3430        Self::default()
3431    }
3432
3433    /// File system capabilities supported by the client.
3434    /// Determines which file operations the agent can request.
3435    #[must_use]
3436    pub fn fs(mut self, fs: FileSystemCapabilities) -> Self {
3437        self.fs = fs;
3438        self
3439    }
3440
3441    /// Whether the Client support all `terminal/*` methods.
3442    #[must_use]
3443    pub fn terminal(mut self, terminal: bool) -> Self {
3444        self.terminal = terminal;
3445        self
3446    }
3447
3448    /// Session-related capabilities supported by the client.
3449    #[must_use]
3450    pub fn session(mut self, session: impl IntoOption<ClientSessionCapabilities>) -> Self {
3451        self.session = session.into_option();
3452        self
3453    }
3454
3455    /// **UNSTABLE**
3456    ///
3457    /// This capability is not part of the spec yet, and may be removed or changed at any point.
3458    ///
3459    /// Whether the client understands exposed subagent sessions.
3460    #[cfg(feature = "unstable_subagents")]
3461    #[must_use]
3462    pub fn subagents(mut self, subagents: impl IntoOption<SubagentCapabilities>) -> Self {
3463        self.subagents = subagents.into_option();
3464        self
3465    }
3466
3467    /// **UNSTABLE**
3468    ///
3469    /// This capability is not part of the spec yet, and may be removed or changed at any point.
3470    ///
3471    /// Whether the client supports `plan_update` and `plan_removed` session updates.
3472    ///
3473    /// Omitted or `null` both mean the client does not advertise support.
3474    /// Supplying `{}` means the client can receive both update types.
3475    #[cfg(feature = "unstable_plan_operations")]
3476    #[must_use]
3477    pub fn plan(mut self, plan: impl IntoOption<PlanCapabilities>) -> Self {
3478        self.plan = plan.into_option();
3479        self
3480    }
3481
3482    /// Authentication capabilities supported by the client.
3483    /// Determines which authentication method types the agent may include
3484    /// in its `InitializeResponse`.
3485    #[must_use]
3486    pub fn auth(mut self, auth: AuthCapabilities) -> Self {
3487        self.auth = auth;
3488        self
3489    }
3490
3491    /// Elicitation capabilities supported by the client.
3492    /// Determines which elicitation modes the agent may use.
3493    #[must_use]
3494    pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3495        self.elicitation = elicitation.into_option();
3496        self
3497    }
3498
3499    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3500    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3501    /// these keys.
3502    ///
3503    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3504    #[must_use]
3505    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3506        self.meta = meta.into_option();
3507        self
3508    }
3509}
3510
3511/// **UNSTABLE**
3512///
3513/// This capability is not part of the spec yet, and may be removed or changed at any point.
3514///
3515/// Capability marker for exposing reusable child sessions as restricted ACP sessions.
3516///
3517/// Supplying `{}` advertises support for child association and state updates,
3518/// session-directed messages, and restricted-session semantics. The client
3519/// must advertise this capability before the agent sends subagent updates or
3520/// session-directed messages.
3521#[cfg(feature = "unstable_subagents")]
3522#[serde_as]
3523#[skip_serializing_none]
3524#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3525#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3526#[non_exhaustive]
3527pub struct SubagentCapabilities {
3528    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3529    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3530    /// these keys.
3531    #[serde_as(deserialize_as = "DefaultOnError")]
3532    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3533    #[serde(default)]
3534    #[serde(rename = "_meta")]
3535    pub meta: Option<Meta>,
3536}
3537
3538#[cfg(feature = "unstable_subagents")]
3539impl SubagentCapabilities {
3540    /// Builds an empty capability marker.
3541    #[must_use]
3542    pub fn new() -> Self {
3543        Self::default()
3544    }
3545
3546    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3547    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3548    /// these keys.
3549    #[must_use]
3550    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3551        self.meta = meta.into_option();
3552        self
3553    }
3554}
3555
3556/// Session-related capabilities supported by the client.
3557#[serde_as]
3558#[skip_serializing_none]
3559#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3560#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3561#[serde(rename_all = "camelCase")]
3562#[non_exhaustive]
3563pub struct ClientSessionCapabilities {
3564    /// Support for ID-addressed context compaction updates. Omitted or `null`
3565    /// means unsupported; `{}` advertises the complete compaction contract.
3566    #[serde_as(deserialize_as = "DefaultOnError")]
3567    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3568    #[serde(default)]
3569    pub compaction: Option<CompactionCapabilities>,
3570    /// Config option capabilities supported by the client.
3571    ///
3572    /// Omitted or `null` both mean the client does not advertise support for any
3573    /// config option extensions.
3574    #[serde_as(deserialize_as = "DefaultOnError")]
3575    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3576    #[serde(default)]
3577    pub config_options: Option<SessionConfigOptionsCapabilities>,
3578    /// Support for live user-facing `notice` session updates.
3579    ///
3580    /// Optional. Omitted or `null` both mean the client does not advertise support.
3581    /// Supplying `{}` means the client can present notices to the user.
3582    #[serde_as(deserialize_as = "DefaultOnError")]
3583    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3584    #[serde(default)]
3585    pub notices: Option<NoticeCapabilities>,
3586    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3587    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3588    /// these keys.
3589    ///
3590    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3591    #[serde_as(deserialize_as = "DefaultOnError")]
3592    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3593    #[serde(default)]
3594    #[serde(rename = "_meta")]
3595    pub meta: Option<Meta>,
3596}
3597
3598impl ClientSessionCapabilities {
3599    /// Builds an empty [`ClientSessionCapabilities`]; use builder methods to advertise supported sub-capabilities.
3600    #[must_use]
3601    pub fn new() -> Self {
3602        Self::default()
3603    }
3604
3605    /// Advertises support for ID-addressed context compaction updates.
3606    #[must_use]
3607    pub fn compaction(mut self, compaction: impl IntoOption<CompactionCapabilities>) -> Self {
3608        self.compaction = compaction.into_option();
3609        self
3610    }
3611
3612    /// Config option capabilities supported by the client.
3613    ///
3614    /// Omitted or `null` both mean the client does not advertise support for any
3615    /// config option extensions.
3616    #[must_use]
3617    pub fn config_options(
3618        mut self,
3619        config_options: impl IntoOption<SessionConfigOptionsCapabilities>,
3620    ) -> Self {
3621        self.config_options = config_options.into_option();
3622        self
3623    }
3624
3625    /// Advertises support for presenting live notices to the user.
3626    #[must_use]
3627    pub fn notices(mut self, notices: impl IntoOption<NoticeCapabilities>) -> Self {
3628        self.notices = notices.into_option();
3629        self
3630    }
3631
3632    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3633    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3634    /// these keys.
3635    ///
3636    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3637    #[must_use]
3638    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3639        self.meta = meta.into_option();
3640        self
3641    }
3642}
3643
3644/// Client support for ID-addressed context compaction updates.
3645#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3646#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3647#[serde(rename_all = "camelCase")]
3648#[non_exhaustive]
3649pub struct CompactionCapabilities {}
3650
3651impl CompactionCapabilities {
3652    /// Advertises the complete compaction update contract.
3653    #[must_use]
3654    pub fn new() -> Self {
3655        Self {}
3656    }
3657}
3658
3659/// Client support for presenting live notices to the user.
3660#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3661#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3662#[serde(rename_all = "camelCase")]
3663#[non_exhaustive]
3664pub struct NoticeCapabilities {}
3665
3666impl NoticeCapabilities {
3667    /// Advertises support for presenting live notices to the user.
3668    #[must_use]
3669    pub fn new() -> Self {
3670        Self {}
3671    }
3672}
3673
3674/// Session configuration option capabilities supported by the client.
3675#[serde_as]
3676#[skip_serializing_none]
3677#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3678#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3679#[serde(rename_all = "camelCase")]
3680#[non_exhaustive]
3681pub struct SessionConfigOptionsCapabilities {
3682    /// Whether the client supports boolean session configuration options.
3683    ///
3684    /// Optional. Omitted or `null` both mean the client does not advertise support.
3685    /// Supplying `{}` means agents may include `type: "boolean"` entries in
3686    /// `configOptions`, and the client may send `session/set_config_option`
3687    /// requests with `type: "boolean"` and a boolean `value`.
3688    #[serde_as(deserialize_as = "DefaultOnError")]
3689    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3690    #[serde(default)]
3691    pub boolean: Option<BooleanConfigOptionCapabilities>,
3692    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3693    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3694    /// these keys.
3695    ///
3696    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3697    #[serde_as(deserialize_as = "DefaultOnError")]
3698    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3699    #[serde(default)]
3700    #[serde(rename = "_meta")]
3701    pub meta: Option<Meta>,
3702}
3703
3704impl SessionConfigOptionsCapabilities {
3705    /// Builds an empty [`SessionConfigOptionsCapabilities`]; use builder methods to advertise supported sub-capabilities.
3706    #[must_use]
3707    pub fn new() -> Self {
3708        Self::default()
3709    }
3710
3711    /// Whether the client supports boolean session configuration options.
3712    ///
3713    /// Omitted or `null` both mean the client does not advertise support.
3714    /// Supplying `{}` means agents may include `type: "boolean"` entries in
3715    /// `configOptions`, and the client may send `session/set_config_option`
3716    /// requests with `type: "boolean"` and a boolean `value`.
3717    #[must_use]
3718    pub fn boolean(mut self, boolean: impl IntoOption<BooleanConfigOptionCapabilities>) -> Self {
3719        self.boolean = boolean.into_option();
3720        self
3721    }
3722
3723    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3724    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3725    /// these keys.
3726    ///
3727    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3728    #[must_use]
3729    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3730        self.meta = meta.into_option();
3731        self
3732    }
3733}
3734
3735/// Capabilities for boolean session configuration options.
3736///
3737/// Supplying `{}` means the client supports boolean session configuration options.
3738#[serde_as]
3739#[skip_serializing_none]
3740#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3741#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3742#[non_exhaustive]
3743pub struct BooleanConfigOptionCapabilities {
3744    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3745    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3746    /// these keys.
3747    ///
3748    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3749    #[serde_as(deserialize_as = "DefaultOnError")]
3750    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3751    #[serde(default)]
3752    #[serde(rename = "_meta")]
3753    pub meta: Option<Meta>,
3754}
3755
3756impl BooleanConfigOptionCapabilities {
3757    /// Builds an empty [`BooleanConfigOptionCapabilities`]; use builder methods to advertise supported sub-capabilities.
3758    #[must_use]
3759    pub fn new() -> Self {
3760        Self::default()
3761    }
3762
3763    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3764    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3765    /// these keys.
3766    ///
3767    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3768    #[must_use]
3769    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3770        self.meta = meta.into_option();
3771        self
3772    }
3773}
3774
3775/// Authentication capabilities supported by the client.
3776///
3777/// Advertised during initialization to inform the agent which authentication
3778/// method types the client can handle. This governs opt-in types that require
3779/// additional client-side support.
3780#[serde_as]
3781#[skip_serializing_none]
3782#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3783#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3784#[serde(rename_all = "camelCase")]
3785#[non_exhaustive]
3786pub struct AuthCapabilities {
3787    /// Whether the client supports `terminal` authentication methods.
3788    ///
3789    /// The client should set this to `true` only when it can reproduce the
3790    /// configured agent invocation in an interactive terminal. When `true`, the
3791    /// agent may include `terminal` entries in its authentication methods.
3792    #[serde_as(deserialize_as = "DefaultOnError")]
3793    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3794    #[serde(default)]
3795    pub terminal: bool,
3796    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3797    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3798    /// these keys.
3799    ///
3800    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3801    #[serde_as(deserialize_as = "DefaultOnError")]
3802    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3803    #[serde(default)]
3804    #[serde(rename = "_meta")]
3805    pub meta: Option<Meta>,
3806}
3807
3808impl AuthCapabilities {
3809    /// Builds an empty [`AuthCapabilities`]; use builder methods to advertise supported sub-capabilities.
3810    #[must_use]
3811    pub fn new() -> Self {
3812        Self::default()
3813    }
3814
3815    /// Whether the client supports `terminal` authentication methods.
3816    ///
3817    /// The client should set this to `true` only when it can reproduce the
3818    /// configured agent invocation in an interactive terminal. When `true`, the
3819    /// agent may include `AuthMethod::Terminal` entries in its authentication
3820    /// methods.
3821    #[must_use]
3822    pub fn terminal(mut self, terminal: bool) -> Self {
3823        self.terminal = terminal;
3824        self
3825    }
3826
3827    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3828    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3829    /// these keys.
3830    ///
3831    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3832    #[must_use]
3833    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3834        self.meta = meta.into_option();
3835        self
3836    }
3837}
3838
3839/// File system capabilities that a client may support.
3840///
3841/// See protocol docs: [FileSystem](https://agentclientprotocol.com/protocol/v1/initialization#filesystem)
3842#[serde_as]
3843#[skip_serializing_none]
3844#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3845#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3846#[serde(rename_all = "camelCase")]
3847#[non_exhaustive]
3848pub struct FileSystemCapabilities {
3849    /// Whether the Client supports `fs/read_text_file` requests.
3850    #[serde_as(deserialize_as = "DefaultOnError")]
3851    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3852    #[serde(default)]
3853    pub read_text_file: bool,
3854    /// Whether the Client supports `fs/write_text_file` requests.
3855    #[serde_as(deserialize_as = "DefaultOnError")]
3856    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3857    #[serde(default)]
3858    pub write_text_file: bool,
3859    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3860    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3861    /// these keys.
3862    ///
3863    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3864    #[serde_as(deserialize_as = "DefaultOnError")]
3865    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3866    #[serde(default)]
3867    #[serde(rename = "_meta")]
3868    pub meta: Option<Meta>,
3869}
3870
3871impl FileSystemCapabilities {
3872    /// Builds an empty [`FileSystemCapabilities`]; use builder methods to advertise supported sub-capabilities.
3873    #[must_use]
3874    pub fn new() -> Self {
3875        Self::default()
3876    }
3877
3878    /// Whether the Client supports `fs/read_text_file` requests.
3879    #[must_use]
3880    pub fn read_text_file(mut self, read_text_file: bool) -> Self {
3881        self.read_text_file = read_text_file;
3882        self
3883    }
3884
3885    /// Whether the Client supports `fs/write_text_file` requests.
3886    #[must_use]
3887    pub fn write_text_file(mut self, write_text_file: bool) -> Self {
3888        self.write_text_file = write_text_file;
3889        self
3890    }
3891
3892    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
3893    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
3894    /// these keys.
3895    ///
3896    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
3897    #[must_use]
3898    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3899        self.meta = meta.into_option();
3900        self
3901    }
3902}
3903
3904// Method schema
3905
3906/// Names of all methods that clients handle.
3907///
3908/// Provides a centralized definition of method names used in the protocol.
3909#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3910#[non_exhaustive]
3911pub struct ClientMethodNames {
3912    /// Method for requesting permission from the user.
3913    pub session_request_permission: &'static str,
3914    /// Notification for session updates.
3915    pub session_update: &'static str,
3916    /// Method for writing text files.
3917    pub fs_write_text_file: &'static str,
3918    /// Method for reading text files.
3919    pub fs_read_text_file: &'static str,
3920    /// Method for creating new terminals.
3921    pub terminal_create: &'static str,
3922    /// Method for getting terminals output.
3923    pub terminal_output: &'static str,
3924    /// Method for releasing a terminal.
3925    pub terminal_release: &'static str,
3926    /// Method for waiting for a terminal to finish.
3927    pub terminal_wait_for_exit: &'static str,
3928    /// Method for killing a terminal.
3929    pub terminal_kill: &'static str,
3930    /// Method for exchanging MCP-over-ACP messages.
3931    #[cfg(feature = "unstable_mcp_over_acp")]
3932    pub mcp_message: &'static str,
3933    /// Method for elicitation.
3934    pub elicitation_create: &'static str,
3935    /// Notification for elicitation completion.
3936    pub elicitation_complete: &'static str,
3937}
3938
3939/// Constant containing all client method names.
3940pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3941    session_update: SESSION_UPDATE_NOTIFICATION,
3942    session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3943    fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
3944    fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
3945    terminal_create: TERMINAL_CREATE_METHOD_NAME,
3946    terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
3947    terminal_release: TERMINAL_RELEASE_METHOD_NAME,
3948    terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
3949    terminal_kill: TERMINAL_KILL_METHOD_NAME,
3950    #[cfg(feature = "unstable_mcp_over_acp")]
3951    mcp_message: MCP_MESSAGE_METHOD_NAME,
3952    elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3953    elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3954};
3955
3956/// Notification name for session updates.
3957pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3958/// Method name for requesting user permission.
3959pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3960/// Method name for writing text files.
3961pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
3962/// Method name for reading text files.
3963pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
3964/// Method name for creating a new terminal.
3965pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
3966/// Method for getting terminals output.
3967pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
3968/// Method for releasing a terminal.
3969pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
3970/// Method for waiting for a terminal to finish.
3971pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
3972/// Method for killing a terminal.
3973pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
3974/// Method name for elicitation.
3975pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3976/// Notification name for elicitation completion.
3977pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3978
3979/// All possible requests that an agent can send to a client.
3980///
3981/// This enum is used internally for routing RPC requests. You typically won't need
3982/// to use this directly.
3983///
3984/// This enum encompasses all method calls from agent to client.
3985#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3986#[derive(Clone, Debug, Serialize, Deserialize)]
3987#[serde(untagged)]
3988#[cfg_attr(feature = "schemars", schemars(inline))]
3989#[non_exhaustive]
3990#[allow(clippy::large_enum_variant)]
3991pub enum AgentRequest {
3992    /// Writes content to a text file in the client's file system.
3993    ///
3994    /// Only available if the client advertises the `fs.writeTextFile` capability.
3995    /// Allows the agent to create or modify files within the client's environment.
3996    ///
3997    /// See protocol docs: [Client](https://agentclientprotocol.com/protocol/v1/overview#client)
3998    WriteTextFileRequest(WriteTextFileRequest),
3999    /// Reads content from a text file in the client's file system.
4000    ///
4001    /// Only available if the client advertises the `fs.readTextFile` capability.
4002    /// Allows the agent to access file contents within the client's environment.
4003    ///
4004    /// See protocol docs: [Client](https://agentclientprotocol.com/protocol/v1/overview#client)
4005    ReadTextFileRequest(ReadTextFileRequest),
4006    /// Requests permission from the user for a tool call operation.
4007    ///
4008    /// Called by the agent when it needs user authorization before executing
4009    /// a potentially sensitive operation. The client should present the options
4010    /// to the user and return their decision.
4011    ///
4012    /// If the client cancels the prompt turn via `session/cancel`, it MUST
4013    /// respond to this request with the `cancelled` outcome.
4014    ///
4015    /// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/v1/tool-calls#requesting-permission)
4016    RequestPermissionRequest(RequestPermissionRequest),
4017    /// Executes a command in a new terminal
4018    ///
4019    /// Only available if the `terminal` Client capability is set to `true`.
4020    ///
4021    /// Returns a `TerminalId` that can be used with other terminal methods
4022    /// to get the current output, wait for exit, and kill the command.
4023    ///
4024    /// The `TerminalId` can also be used to embed the terminal in a tool call
4025    /// by using `terminal` tool call content.
4026    ///
4027    /// The Agent is responsible for releasing the terminal by using the `terminal/release`
4028    /// method.
4029    ///
4030    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/v1/terminals)
4031    CreateTerminalRequest(CreateTerminalRequest),
4032    /// Gets the terminal output and exit status
4033    ///
4034    /// Returns the current content in the terminal without waiting for the command to exit.
4035    /// If the command has already exited, the exit status is included.
4036    ///
4037    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/v1/terminals)
4038    TerminalOutputRequest(TerminalOutputRequest),
4039    /// Releases a terminal
4040    ///
4041    /// The command is killed if it hasn't exited yet. Use `terminal/wait_for_exit`
4042    /// to wait for the command to exit before releasing the terminal.
4043    ///
4044    /// After release, the `TerminalId` can no longer be used with other `terminal/*` methods,
4045    /// but tool calls that already contain it, continue to display its output.
4046    ///
4047    /// The `terminal/kill` method can be used to terminate the command without releasing
4048    /// the terminal, allowing the Agent to call `terminal/output` and other methods.
4049    ///
4050    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/v1/terminals)
4051    ReleaseTerminalRequest(ReleaseTerminalRequest),
4052    /// Waits for the terminal command to exit and return its exit status
4053    ///
4054    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/v1/terminals)
4055    WaitForTerminalExitRequest(WaitForTerminalExitRequest),
4056    /// Kills the terminal command without releasing the terminal
4057    ///
4058    /// While `terminal/release` will also kill the command, this method will keep
4059    /// the `TerminalId` valid so it can be used with other methods.
4060    ///
4061    /// This method can be helpful when implementing command timeouts which terminate
4062    /// the command as soon as elapsed, and then get the final output so it can be sent
4063    /// to the model.
4064    ///
4065    /// Note: Call `terminal/release` when `TerminalId` is no longer needed.
4066    ///
4067    /// See protocol docs: [Terminals](https://agentclientprotocol.com/protocol/v1/terminals)
4068    KillTerminalRequest(KillTerminalRequest),
4069    /// Requests structured user input via a form or URL.
4070    ///
4071    /// See protocol docs: [Elicitation](https://agentclientprotocol.com/protocol/v1/elicitation)
4072    CreateElicitationRequest(CreateElicitationRequest),
4073    /// **UNSTABLE**
4074    ///
4075    /// This capability is not part of the spec yet, and may be removed or changed at any point.
4076    ///
4077    /// Exchanges an MCP-over-ACP message.
4078    #[cfg(feature = "unstable_mcp_over_acp")]
4079    MessageMcpRequest(MessageMcpRequest),
4080    /// Handles extension method requests from the agent.
4081    ///
4082    /// Allows the Agent to send an arbitrary request that is not part of the ACP spec.
4083    /// Extension methods provide a way to add custom functionality while maintaining
4084    /// protocol compatibility.
4085    ///
4086    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
4087    ExtMethodRequest(ExtRequest),
4088}
4089
4090impl AgentRequest {
4091    /// Returns the corresponding method name of the request.
4092    #[must_use]
4093    pub fn method(&self) -> &str {
4094        match self {
4095            Self::WriteTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_write_text_file,
4096            Self::ReadTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_read_text_file,
4097            Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
4098            Self::CreateTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_create,
4099            Self::TerminalOutputRequest(_) => CLIENT_METHOD_NAMES.terminal_output,
4100            Self::ReleaseTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_release,
4101            Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
4102            Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
4103            Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
4104            #[cfg(feature = "unstable_mcp_over_acp")]
4105            Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
4106            Self::ExtMethodRequest(ext_request) => &ext_request.method,
4107        }
4108    }
4109}
4110
4111/// All possible responses that a client can send to an agent.
4112///
4113/// This enum is used internally for routing RPC responses. You typically won't need
4114/// to use this directly - the responses are handled automatically by the connection.
4115///
4116/// These are responses to the corresponding `AgentRequest` variants.
4117#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4118#[derive(Clone, Debug, Serialize, Deserialize)]
4119#[serde(untagged)]
4120#[cfg_attr(feature = "schemars", schemars(inline))]
4121#[non_exhaustive]
4122pub enum ClientResponse {
4123    /// Successful result returned for a `fs/write_text_file` request.
4124    WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
4125    /// Successful result returned for a `fs/read_text_file` request.
4126    ReadTextFileResponse(ReadTextFileResponse),
4127    /// Successful result returned for a `session/request_permission` request.
4128    RequestPermissionResponse(RequestPermissionResponse),
4129    /// Successful result returned for a `terminal/create` request.
4130    CreateTerminalResponse(CreateTerminalResponse),
4131    /// Successful result returned for a `terminal/output` request.
4132    TerminalOutputResponse(TerminalOutputResponse),
4133    /// Successful result returned for a `terminal/release` request.
4134    ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
4135    /// Successful result returned for a `terminal/wait_for_exit` request.
4136    WaitForTerminalExitResponse(WaitForTerminalExitResponse),
4137    /// Successful result returned for a `terminal/kill` request.
4138    KillTerminalResponse(#[serde(default)] KillTerminalResponse),
4139    /// Successful result returned for a `elicitation/create` request.
4140    CreateElicitationResponse(CreateElicitationResponse),
4141    /// Successful result returned by an MCP-over-ACP `mcp/message` request.
4142    #[cfg(feature = "unstable_mcp_over_acp")]
4143    MessageMcpResponse(MessageMcpResponse),
4144    /// Successful result returned by an extension method outside the core ACP method set.
4145    ExtMethodResponse(ExtResponse),
4146}
4147
4148/// All possible notifications that an agent can send to a client.
4149///
4150/// This enum is used internally for routing RPC notifications. You typically won't need
4151/// to use this directly.
4152///
4153/// Notifications do not expect a response.
4154#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4155#[derive(Clone, Debug, Serialize, Deserialize)]
4156#[serde(untagged)]
4157#[expect(clippy::large_enum_variant)]
4158#[cfg_attr(feature = "schemars", schemars(inline))]
4159#[non_exhaustive]
4160pub enum AgentNotification {
4161    /// Handles session update notifications from the agent.
4162    ///
4163    /// This is a notification endpoint (no response expected) that receives
4164    /// real-time updates about session progress, including message chunks,
4165    /// tool calls, and execution plans.
4166    ///
4167    /// Note: Clients SHOULD continue accepting tool call updates even after
4168    /// sending a `session/cancel` notification, as the agent may send final
4169    /// updates before responding with the cancelled stop reason.
4170    ///
4171    /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/v1/prompt-turn#3-agent-reports-output)
4172    SessionNotification(SessionNotification),
4173    /// Notification that a URL-based elicitation has completed.
4174    ///
4175    /// See protocol docs: [Elicitation](https://agentclientprotocol.com/protocol/v1/elicitation#url-completion)
4176    CompleteElicitationNotification(CompleteElicitationNotification),
4177    /// Handles extension notifications from the agent.
4178    ///
4179    /// Allows the Agent to send an arbitrary notification that is not part of the ACP spec.
4180    /// Extension notifications provide a way to send one-way messages for custom functionality
4181    /// while maintaining protocol compatibility.
4182    ///
4183    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/extensibility)
4184    ExtNotification(ExtNotification),
4185}
4186
4187impl AgentNotification {
4188    /// Returns the corresponding method name of the notification.
4189    #[must_use]
4190    pub fn method(&self) -> &str {
4191        match self {
4192            Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
4193            Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
4194            Self::ExtNotification(ext_notification) => &ext_notification.method,
4195        }
4196    }
4197}
4198
4199#[cfg(test)]
4200mod tests {
4201    use super::*;
4202
4203    #[test]
4204    fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
4205        use serde_json::json;
4206
4207        let mut meta = Meta::new();
4208        meta.insert("source".into(), json!("fallback"));
4209        assert_eq!(
4210            serde_json::to_value(SessionUpdate::Notice(
4211                Notice::new(NoticeSeverity::Warning, "MCP server unavailable")
4212                    .description("Continuing without it.")
4213                    .meta(meta),
4214            ))
4215            .unwrap(),
4216            json!({
4217                "sessionUpdate": "notice",
4218                "severity": "warning",
4219                "title": "MCP server unavailable",
4220                "description": "Continuing without it.",
4221                "_meta": { "source": "fallback" }
4222            })
4223        );
4224
4225        assert_eq!(
4226            serde_json::to_value(SessionUpdate::Notice(Notice::new(
4227                NoticeSeverity::Info,
4228                "Indexing workspace",
4229            )))
4230            .unwrap(),
4231            json!({
4232                "sessionUpdate": "notice",
4233                "severity": "info",
4234                "title": "Indexing workspace"
4235            })
4236        );
4237
4238        let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
4239            "sessionUpdate": "notice",
4240            "severity": "critical",
4241            "title": "Provider degraded",
4242            "description": null,
4243            "_meta": null
4244        }))
4245        .unwrap() else {
4246            panic!("expected notice");
4247        };
4248
4249        assert_eq!(
4250            notice.severity,
4251            NoticeSeverity::Other("critical".to_string())
4252        );
4253        assert_eq!(notice.description, None);
4254        assert_eq!(notice.meta, None);
4255        assert_eq!(
4256            serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
4257            json!({
4258                "sessionUpdate": "notice",
4259                "severity": "critical",
4260                "title": "Provider degraded"
4261            })
4262        );
4263    }
4264
4265    #[test]
4266    fn notice_requires_string_severity_and_title() {
4267        use serde_json::json;
4268
4269        for malformed in [
4270            json!({
4271                "sessionUpdate": "notice",
4272                "severity": "warning",
4273                "title": 42
4274            }),
4275            json!({
4276                "sessionUpdate": "notice",
4277                "severity": 42,
4278                "title": "MCP server unavailable"
4279            }),
4280            json!({
4281                "sessionUpdate": "notice",
4282                "severity": "warning"
4283            }),
4284            json!({
4285                "sessionUpdate": "notice",
4286                "severity": "warning",
4287                "title": null
4288            }),
4289            json!({
4290                "sessionUpdate": "notice",
4291                "title": "MCP server unavailable"
4292            }),
4293            json!({
4294                "sessionUpdate": "notice",
4295                "severity": null,
4296                "title": "MCP server unavailable"
4297            }),
4298        ] {
4299            assert!(serde_json::from_str::<SessionUpdate>(&malformed.to_string()).is_err());
4300            assert!(serde_json::from_value::<SessionUpdate>(malformed).is_err());
4301        }
4302    }
4303
4304    #[test]
4305    fn notice_tolerates_invalid_optional_display_fields_and_custom_severity() {
4306        use serde_json::json;
4307
4308        let wire = json!({
4309            "sessionUpdate": "notice",
4310            "severity": "_custom",
4311            "title": "Provider degraded",
4312            "description": 42,
4313            "_meta": false
4314        });
4315        let SessionUpdate::Notice(notice) = serde_json::from_value(wire.clone()).unwrap() else {
4316            panic!("expected notice");
4317        };
4318        // Streaming deserialization must enforce the same contract as from_value.
4319        assert_eq!(
4320            serde_json::from_str::<SessionUpdate>(&wire.to_string()).unwrap(),
4321            SessionUpdate::Notice(notice.clone())
4322        );
4323        assert_eq!(notice.severity, NoticeSeverity::Other("_custom".into()));
4324        assert_eq!(notice.description, None);
4325        assert_eq!(notice.meta, None);
4326        assert_eq!(
4327            serde_json::to_value(notice).unwrap(),
4328            json!({"severity": "_custom", "title": "Provider degraded"})
4329        );
4330    }
4331
4332    #[cfg(feature = "schemars")]
4333    #[test]
4334    fn notice_schema_preserves_required_and_nullable_fields() {
4335        use serde_json::json;
4336
4337        let schema = serde_json::to_value(schemars::schema_for!(Notice)).unwrap();
4338        assert_eq!(schema["required"], json!(["severity", "title"]));
4339        assert_eq!(schema["properties"]["title"]["minLength"], 1);
4340        for field in ["description", "_meta"] {
4341            assert!(
4342                schema["properties"][field]["type"]
4343                    .as_array()
4344                    .unwrap()
4345                    .contains(&json!("null"))
4346            );
4347        }
4348        let capabilities =
4349            serde_json::to_value(schemars::schema_for!(ClientSessionCapabilities)).unwrap();
4350        assert!(
4351            capabilities["properties"]["notices"]["anyOf"]
4352                .as_array()
4353                .unwrap()
4354                .contains(&json!({"type": "null"}))
4355        );
4356        assert!(
4357            !capabilities["required"]
4358                .as_array()
4359                .is_some_and(|required| required.contains(&json!("notices")))
4360        );
4361    }
4362
4363    #[test]
4364    fn notice_capability_advertises_support_only_when_present() {
4365        use serde_json::json;
4366
4367        let capabilities = ClientCapabilities::new()
4368            .session(ClientSessionCapabilities::new().notices(NoticeCapabilities::new()));
4369        let value = serde_json::to_value(&capabilities).unwrap();
4370        assert_eq!(value["session"], json!({ "notices": {} }));
4371        assert_eq!(
4372            serde_json::from_value::<ClientCapabilities>(value).unwrap(),
4373            capabilities
4374        );
4375
4376        for unsupported in [
4377            json!({}),
4378            json!({ "session": null }),
4379            json!({ "session": {} }),
4380            json!({ "session": { "notices": null } }),
4381            json!({ "session": { "notices": false } }),
4382            json!({ "session": { "notices": true } }),
4383            json!({ "session": { "notices": "supported" } }),
4384        ] {
4385            let capabilities: ClientCapabilities = serde_json::from_value(unsupported).unwrap();
4386            assert!(
4387                capabilities
4388                    .session
4389                    .and_then(|session| session.notices)
4390                    .is_none()
4391            );
4392        }
4393
4394        assert_eq!(
4395            serde_json::to_value(
4396                ClientSessionCapabilities::new()
4397                    .notices(NoticeCapabilities::new())
4398                    .notices(None)
4399            )
4400            .unwrap(),
4401            json!({})
4402        );
4403    }
4404
4405    #[test]
4406    fn compaction_updates_preserve_patch_and_open_status_semantics() {
4407        use serde_json::json;
4408
4409        assert_eq!(
4410            serde_json::to_value(SessionUpdate::CompactionUpdate(CompactionUpdate::new(
4411                "cmp_001",
4412                CompactionStatus::InProgress,
4413            )))
4414            .unwrap(),
4415            json!({
4416                "sessionUpdate": "compaction_update",
4417                "compactionId": "cmp_001",
4418                "status": "in_progress"
4419            })
4420        );
4421
4422        let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
4423            "sessionUpdate": "compaction_update",
4424            "compactionId": "cmp_001",
4425            "status": "paused",
4426            "summary": null,
4427            "error": null
4428        }))
4429        .unwrap() else {
4430            panic!("expected compaction update");
4431        };
4432        assert_eq!(update.status, CompactionStatus::Other("paused".into()));
4433        assert!(update.summary.is_null());
4434        assert!(update.error.is_null());
4435        assert!(update.meta.is_undefined());
4436    }
4437
4438    #[test]
4439    fn compaction_chunk_and_v1_capability_serialize() {
4440        use serde_json::json;
4441
4442        assert_eq!(
4443            serde_json::to_value(SessionUpdate::CompactionSummaryChunk(
4444                CompactionSummaryChunk::new(
4445                    "cmp_001",
4446                    ContentBlock::Text(crate::v1::TextContent::new("retained")),
4447                ),
4448            ))
4449            .unwrap(),
4450            json!({
4451                "sessionUpdate": "compaction_summary_chunk",
4452                "compactionId": "cmp_001",
4453                "content": { "type": "text", "text": "retained" }
4454            })
4455        );
4456        assert_eq!(
4457            serde_json::to_value(
4458                ClientSessionCapabilities::new().compaction(CompactionCapabilities::new())
4459            )
4460            .unwrap(),
4461            json!({ "compaction": {} })
4462        );
4463        let absent: ClientSessionCapabilities = serde_json::from_value(json!({})).unwrap();
4464        let null: ClientSessionCapabilities =
4465            serde_json::from_value(json!({ "compaction": null })).unwrap();
4466        assert!(absent.compaction.is_none());
4467        assert!(null.compaction.is_none());
4468    }
4469
4470    #[cfg(feature = "unstable_subagents")]
4471    #[test]
4472    fn test_subagent_updates_serialization() {
4473        use serde_json::json;
4474
4475        let announced = SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1"));
4476        assert_eq!(
4477            serde_json::to_value(&announced).unwrap(),
4478            json!({
4479                "sessionUpdate": "subagent_update",
4480                "sessionId": "sess_child_1"
4481            })
4482        );
4483        assert_eq!(
4484            serde_json::from_value::<SessionUpdate>(serde_json::to_value(&announced).unwrap())
4485                .unwrap(),
4486            announced
4487        );
4488        let capable = SubagentUpdate::new("sess_child_1").capabilities(
4489            SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
4490        );
4491        assert_eq!(
4492            serde_json::to_value(capable).unwrap(),
4493            json!({
4494                "sessionId": "sess_child_1",
4495                "capabilities": {"cancel": {}}
4496            })
4497        );
4498        let minimal: SubagentUpdate =
4499            serde_json::from_value(json!({ "sessionId": "sess_child_3" })).unwrap();
4500        assert!(minimal.capabilities.is_undefined());
4501        assert!(minimal.state.is_undefined());
4502
4503        let nulls: SubagentUpdate = serde_json::from_value(json!({
4504            "sessionId": "sess_child_3",
4505            "capabilities": null,
4506            "state": null
4507        }))
4508        .unwrap();
4509        assert!(nulls.capabilities.is_null());
4510        assert!(nulls.state.is_null());
4511        assert_eq!(
4512            serde_json::to_value(nulls).unwrap(),
4513            json!({"sessionId": "sess_child_3", "capabilities": null, "state": null})
4514        );
4515    }
4516
4517    #[cfg(feature = "unstable_subagents")]
4518    #[test]
4519    fn test_subagent_display_metadata() {
4520        use serde_json::json;
4521
4522        let update = SubagentUpdate::new("child")
4523            .title("Test investigator".to_string())
4524            .description("Investigates platform-specific test failures.".to_string());
4525        let wire = json!({
4526            "sessionId": "child",
4527            "title": "Test investigator",
4528            "description": "Investigates platform-specific test failures."
4529        });
4530        assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4531        assert_eq!(
4532            serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4533            update
4534        );
4535
4536        let minimal = SubagentUpdate::new("child");
4537        assert_eq!(
4538            serde_json::to_value(&minimal).unwrap(),
4539            json!({"sessionId": "child"})
4540        );
4541        let cleared: SubagentUpdate = serde_json::from_value(json!({
4542            "sessionId": "child", "title": null, "description": null
4543        }))
4544        .unwrap();
4545        assert!(cleared.title.is_null());
4546        assert!(cleared.description.is_null());
4547        assert_eq!(
4548            serde_json::to_value(cleared).unwrap(),
4549            json!({"sessionId": "child", "title": null, "description": null})
4550        );
4551        let invalid: SubagentUpdate = serde_json::from_value(json!({
4552            "sessionId": "child", "title": false, "description": false
4553        }))
4554        .unwrap();
4555        assert_eq!(invalid, minimal);
4556
4557        let title_only: SubagentUpdate = serde_json::from_value(json!({
4558            "sessionId": "child", "title": "Updated title"
4559        }))
4560        .unwrap();
4561        assert_eq!(
4562            title_only.title.value().map(String::as_str),
4563            Some("Updated title")
4564        );
4565        assert!(title_only.description.is_undefined());
4566    }
4567
4568    #[cfg(feature = "unstable_subagents")]
4569    #[test]
4570    fn subagent_directories_use_strict_patch_semantics() {
4571        use serde_json::json;
4572
4573        let update = SubagentUpdate::new("child")
4574            .cwd("/home/user/project/.worktrees/investigator")
4575            .additional_directories(vec![]);
4576        let wire = json!({
4577            "sessionId": "child",
4578            "cwd": "/home/user/project/.worktrees/investigator",
4579            "additionalDirectories": []
4580        });
4581        assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4582        assert_eq!(
4583            serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4584            update
4585        );
4586
4587        let inherited: SubagentUpdate =
4588            serde_json::from_value(json!({"sessionId": "child"})).unwrap();
4589        assert!(inherited.cwd.is_undefined());
4590        assert!(inherited.additional_directories.is_undefined());
4591
4592        let cleared_wire = json!({
4593            "sessionId": "child", "cwd": null, "additionalDirectories": null
4594        });
4595        let cleared: SubagentUpdate = serde_json::from_value(cleared_wire.clone()).unwrap();
4596        assert!(cleared.cwd.is_null());
4597        assert!(cleared.additional_directories.is_null());
4598        assert_eq!(serde_json::to_value(cleared).unwrap(), cleared_wire);
4599
4600        for malformed in [
4601            json!({"sessionId": "child", "cwd": false}),
4602            json!({"sessionId": "child", "additionalDirectories": "/tmp/wt"}),
4603            json!({"sessionId": "child", "additionalDirectories": ["/tmp/wt", false]}),
4604        ] {
4605            assert!(
4606                serde_json::from_value::<SubagentUpdate>(malformed.clone()).is_err(),
4607                "{malformed}"
4608            );
4609        }
4610    }
4611
4612    #[cfg(feature = "unstable_subagents")]
4613    #[test]
4614    fn subagent_patch_fields_preserve_omitted_null_and_concrete() {
4615        use serde_json::json;
4616
4617        let omitted = SubagentUpdate::new("child");
4618        for field in ["title", "description", "capabilities", "state", "_meta"] {
4619            let wire = json!({"sessionId": "child", field: null});
4620            let decoded: SubagentUpdate = serde_json::from_value(wire.clone()).unwrap();
4621            assert_eq!(serde_json::to_value(decoded).unwrap(), wire, "{field}");
4622            let malformed = json!({"sessionId": "child", field: false});
4623            let decoded: SubagentUpdate = serde_json::from_value(malformed).unwrap();
4624            assert_eq!(decoded, omitted, "{field}");
4625        }
4626        let concrete = json!({
4627            "sessionId": "child", "title": "Investigator",
4628            "description": "Tests", "capabilities": {"cancel": {}},
4629            "state": {"state": "running"}, "_meta": {"source": "parent"}
4630        });
4631        let decoded: SubagentUpdate = serde_json::from_value(concrete.clone()).unwrap();
4632        assert!(decoded.title.value().is_some());
4633        assert!(decoded.description.value().is_some());
4634        assert!(decoded.capabilities.value().is_some());
4635        assert!(decoded.state.value().is_some());
4636        assert!(decoded.meta.value().is_some());
4637        assert_eq!(serde_json::to_value(decoded).unwrap(), concrete);
4638        let cleared = SubagentUpdate::new("child")
4639            .title(None)
4640            .description(None)
4641            .capabilities(None)
4642            .state(None)
4643            .meta(None);
4644        assert_eq!(
4645            serde_json::to_value(cleared).unwrap(),
4646            json!({
4647                "sessionId": "child", "title": null, "description": null,
4648                "capabilities": null, "state": null, "_meta": null
4649            })
4650        );
4651    }
4652
4653    #[cfg(feature = "unstable_subagents")]
4654    #[test]
4655    fn test_subagent_notification_keeps_parent_and_child_ids_nested() {
4656        use serde_json::json;
4657
4658        let notification = SessionNotification::new(
4659            "parent",
4660            SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
4661        );
4662        let wire = json!({
4663            "sessionId": "parent",
4664            "update": {
4665                "sessionUpdate": "subagent_update",
4666                "sessionId": "child"
4667            }
4668        });
4669        assert_eq!(serde_json::to_value(&notification).unwrap(), wire);
4670        assert_eq!(
4671            serde_json::from_value::<SessionNotification>(wire).unwrap(),
4672            notification
4673        );
4674        // Neither ID can stand in for the other: both nesting levels require one.
4675        for malformed in [
4676            json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
4677            json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
4678        ] {
4679            assert!(serde_json::from_value::<SessionNotification>(malformed).is_err());
4680        }
4681    }
4682
4683    #[cfg(feature = "unstable_subagents")]
4684    #[test]
4685    fn test_subagent_states_round_trip() {
4686        use serde_json::json;
4687
4688        for (wire_state, expected) in [
4689            (
4690                json!({"state": "running"}),
4691                StateUpdate::Running(RunningStateUpdate::new()),
4692            ),
4693            (
4694                json!({"state": "idle", "stopReason": "end_turn"}),
4695                StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4696            ),
4697            (
4698                json!({"state": "requires_action"}),
4699                StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4700            ),
4701            (
4702                json!({"state": "unknown"}),
4703                StateUpdate::Unknown(UnknownStateUpdate::new()),
4704            ),
4705            (
4706                json!({"state": "_waiting", "detail": {"ticket": 7}}),
4707                StateUpdate::Other(OtherStateUpdate::new(
4708                    "_waiting",
4709                    [("detail".into(), json!({"ticket": 7}))].into(),
4710                )),
4711            ),
4712            (
4713                json!({"state": "paused", "retryAt": 42}),
4714                StateUpdate::Other(OtherStateUpdate::new(
4715                    "paused",
4716                    [("retryAt".into(), json!(42))].into(),
4717                )),
4718            ),
4719        ] {
4720            let wire = json!({
4721                "sessionUpdate": "subagent_update",
4722                "sessionId": "sess_child",
4723                "state": wire_state
4724            });
4725            let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
4726            let SessionUpdate::SubagentUpdate(update) = &parsed else {
4727                panic!("expected subagent update");
4728            };
4729            assert_eq!(update.state.value(), Some(&expected));
4730            assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
4731        }
4732
4733        for reason in [
4734            "end_turn",
4735            "max_tokens",
4736            "max_turn_requests",
4737            "refusal",
4738            "cancelled",
4739        ] {
4740            let state: StateUpdate = serde_json::from_value(json!({
4741                "state": "idle", "stopReason": reason
4742            }))
4743            .unwrap();
4744            assert_eq!(serde_json::to_value(state).unwrap()["stopReason"], reason);
4745        }
4746
4747        // Unknown activity is a replaceable snapshot, not a session outcome.
4748        // Later snapshots still address the same child without stale stop reasons.
4749        for state in [
4750            StateUpdate::Running(RunningStateUpdate::new()),
4751            StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4752            StateUpdate::Unknown(UnknownStateUpdate::new()),
4753            StateUpdate::Running(RunningStateUpdate::new()),
4754            StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4755            StateUpdate::Unknown(UnknownStateUpdate::new()),
4756            StateUpdate::Running(RunningStateUpdate::new()),
4757        ] {
4758            let update = SubagentUpdate::new("sess_child").state(state.clone());
4759            let wire = serde_json::to_value(&update).unwrap();
4760            assert_eq!(wire["sessionId"], "sess_child");
4761            assert_eq!(
4762                serde_json::from_value::<SubagentUpdate>(wire)
4763                    .unwrap()
4764                    .state,
4765                MaybeUndefined::Value(state)
4766            );
4767        }
4768        assert_eq!(
4769            serde_json::to_value(
4770                SubagentUpdate::new("sess_child").state(StateUpdate::Idle(IdleStateUpdate::new()))
4771            )
4772            .unwrap()["state"],
4773            json!({"state": "idle"})
4774        );
4775    }
4776
4777    #[cfg(feature = "unstable_subagents")]
4778    #[test]
4779    fn subagent_idle_snapshot_reports_child_failure() {
4780        use serde_json::json;
4781
4782        let update = SubagentUpdate::new("sess_child").state(StateUpdate::Idle(
4783            IdleStateUpdate::new().stop_reason(IdleStopReason::from(
4784                ErrorStopReason::new().error(Error::auth_required()),
4785            )),
4786        ));
4787        let wire = json!({
4788            "sessionId": "sess_child",
4789            "state": {
4790                "state": "idle",
4791                "stopReason": "error",
4792                "error": { "code": -32000, "message": "Authentication required" }
4793            }
4794        });
4795        assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4796        assert_eq!(
4797            serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4798            update
4799        );
4800
4801        // Missing, null, and malformed details still end the work as a failure.
4802        let without_details = StateUpdate::Idle(
4803            IdleStateUpdate::new().stop_reason(IdleStopReason::from(ErrorStopReason::new())),
4804        );
4805        for error in [None, Some(json!(null)), Some(json!("failed"))] {
4806            let mut wire = json!({ "state": "idle", "stopReason": "error" });
4807            if let Some(error) = error {
4808                wire["error"] = error;
4809            }
4810            assert_eq!(
4811                serde_json::from_value::<StateUpdate>(wire).unwrap(),
4812                without_details
4813            );
4814        }
4815
4816        // `error` belongs to the `error` stop reason; beside another one it is
4817        // an unknown field.
4818        let parsed: StateUpdate = serde_json::from_value(json!({
4819            "state": "idle",
4820            "stopReason": "end_turn",
4821            "error": { "code": -32603, "message": "Internal error" }
4822        }))
4823        .unwrap();
4824        assert_eq!(
4825            parsed,
4826            StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn))
4827        );
4828
4829        // Omitted, null, and malformed stop reasons mean none was reported,
4830        // and the rest of the snapshot survives.
4831        let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4832        for stop_reason in [None, Some(json!(null)), Some(json!(true))] {
4833            let mut wire = json!({ "state": "idle", "_meta": { "source": "worker" } });
4834            if let Some(stop_reason) = stop_reason {
4835                wire["stopReason"] = stop_reason;
4836            }
4837            assert_eq!(
4838                serde_json::from_value::<StateUpdate>(wire).unwrap(),
4839                StateUpdate::Idle(IdleStateUpdate::new().meta(meta.clone()))
4840            );
4841        }
4842
4843        // Unknown stop reasons keep their own fields, as in v2, while the
4844        // snapshot's fields stay on the snapshot.
4845        for stop_reason in ["_paused", "not_a_reason"] {
4846            let wire = json!({
4847                "state": "idle",
4848                "stopReason": stop_reason,
4849                "resumeAfter": 30,
4850                "_meta": { "source": "worker" }
4851            });
4852            let parsed: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4853            assert_eq!(
4854                parsed,
4855                StateUpdate::Idle(
4856                    IdleStateUpdate::new()
4857                        .stop_reason(IdleStopReason::Other(OtherStopReason::new(
4858                            stop_reason,
4859                            [("resumeAfter".into(), json!(30))].into_iter().collect(),
4860                        )))
4861                        .meta(meta.clone())
4862                )
4863            );
4864            assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
4865        }
4866    }
4867
4868    #[cfg(feature = "unstable_subagents")]
4869    #[test]
4870    fn subagent_known_stop_reasons_are_never_read_as_other() {
4871        use serde_json::json;
4872
4873        let known = [
4874            IdleStopReason::EndTurn,
4875            IdleStopReason::MaxTokens,
4876            IdleStopReason::MaxTurnRequests,
4877            IdleStopReason::Refusal,
4878            IdleStopReason::Cancelled,
4879            IdleStopReason::Error(ErrorStopReason::new()),
4880        ];
4881        // Adding a variant breaks this match, so `known` stays complete.
4882        for stop_reason in &known {
4883            match stop_reason {
4884                IdleStopReason::EndTurn
4885                | IdleStopReason::MaxTokens
4886                | IdleStopReason::MaxTurnRequests
4887                | IdleStopReason::Refusal
4888                | IdleStopReason::Cancelled
4889                | IdleStopReason::Error(_)
4890                | IdleStopReason::Other(_) => {}
4891            }
4892        }
4893
4894        for stop_reason in known {
4895            let state = StateUpdate::Idle(IdleStateUpdate::new().stop_reason(stop_reason));
4896            let wire = serde_json::to_value(&state).unwrap();
4897            assert_eq!(
4898                serde_json::from_value::<StateUpdate>(wire.clone()).unwrap(),
4899                state
4900            );
4901            assert!(
4902                serde_json::from_value::<OtherStopReason>(
4903                    json!({ "stopReason": wire["stopReason"] })
4904                )
4905                .is_err(),
4906                "{} must not be accepted as a custom stop reason",
4907                wire["stopReason"]
4908            );
4909        }
4910    }
4911
4912    #[cfg(feature = "unstable_subagents")]
4913    #[test]
4914    fn subagent_unknown_stop_reason_leaves_usage_on_the_snapshot() {
4915        use serde_json::json;
4916
4917        let usage = json!({ "totalTokens": 10, "inputTokens": 6, "outputTokens": 4 });
4918        let wire = json!({ "state": "idle", "stopReason": "_paused", "usage": usage });
4919        let StateUpdate::Idle(idle) = serde_json::from_value(wire).unwrap() else {
4920            panic!("expected idle state snapshot");
4921        };
4922        let Some(IdleStopReason::Other(other)) = &idle.stop_reason else {
4923            panic!("expected a custom stop reason");
4924        };
4925        assert!(other.fields.is_empty());
4926
4927        #[cfg(feature = "unstable_end_turn_token_usage")]
4928        {
4929            assert!(idle.usage.is_some());
4930            assert_eq!(serde_json::to_value(&idle).unwrap()["usage"], usage);
4931        }
4932        // Without the feature, `usage` is not a field of the snapshot, and it
4933        // must not be smuggled through as a field of the stop reason either.
4934        #[cfg(not(feature = "unstable_end_turn_token_usage"))]
4935        assert!(serde_json::to_value(&idle).unwrap().get("usage").is_none());
4936    }
4937
4938    #[cfg(feature = "unstable_subagents")]
4939    #[test]
4940    fn test_subagent_unknown_activity_metadata_and_capabilities() {
4941        use serde_json::json;
4942
4943        let update = SubagentUpdate::new("child").state(StateUpdate::Unknown(
4944            UnknownStateUpdate::new().meta(
4945                [("source".into(), json!("worker"))]
4946                    .into_iter()
4947                    .collect::<Meta>(),
4948            ),
4949        ));
4950        let wire = json!({
4951            "sessionId": "child",
4952            "state": {
4953                "state": "unknown",
4954                "_meta": { "source": "worker" }
4955            }
4956        });
4957        assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4958        assert_eq!(
4959            serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4960            update
4961        );
4962        // Reporting unknown activity does not send a capability revocation.
4963        assert!(update.capabilities.is_undefined());
4964
4965        for meta in [json!(null), json!(false)] {
4966            let state: StateUpdate = serde_json::from_value(json!({
4967                "state": "unknown",
4968                "_meta": meta
4969            }))
4970            .unwrap();
4971            assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
4972            assert_eq!(
4973                serde_json::to_value(state).unwrap(),
4974                json!({"state": "unknown"})
4975            );
4976        }
4977        // The standard unknown-activity report is recognized, not an extension fallback.
4978        assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
4979    }
4980
4981    #[cfg(feature = "unstable_subagents")]
4982    #[test]
4983    fn test_subagent_malformed_optional_state() {
4984        use serde_json::json;
4985
4986        for malformed in [json!("running"), json!({}), json!({"state": 4})] {
4987            assert!(serde_json::from_value::<StateUpdate>(malformed.clone()).is_err());
4988            let update: SubagentUpdate = serde_json::from_value(json!({
4989                "sessionId": "child", "state": malformed
4990            }))
4991            .unwrap();
4992            assert!(update.state.is_undefined());
4993        }
4994        let bad_reason: StateUpdate =
4995            serde_json::from_value(json!({"state": "idle", "stopReason": false})).unwrap();
4996        assert_eq!(bad_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4997        let null_reason: StateUpdate =
4998            serde_json::from_value(json!({"state": "idle", "stopReason": null})).unwrap();
4999        assert_eq!(null_reason, StateUpdate::Idle(IdleStateUpdate::new()));
5000    }
5001
5002    #[cfg(all(feature = "unstable_subagents", feature = "unstable_protocol_v2"))]
5003    #[test]
5004    fn test_subagent_state_v2_wire_parity() {
5005        use serde_json::json;
5006
5007        for wire in [
5008            json!({"state": "running"}),
5009            json!({"state": "idle", "stopReason": "cancelled"}),
5010            json!({"state": "requires_action"}),
5011            json!({"state": "unknown"}),
5012            json!({"state": "unknown", "_meta": {"source": "worker"}}),
5013            json!({"state": "_waiting", "detail": {"ticket": 7}}),
5014        ] {
5015            let v1: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
5016            let v2: crate::v2::StateUpdate = serde_json::from_value(wire.clone()).unwrap();
5017            assert_eq!(serde_json::to_value(v1).unwrap(), wire);
5018            assert_eq!(serde_json::to_value(v2).unwrap(), wire);
5019        }
5020    }
5021
5022    #[cfg(feature = "unstable_subagents")]
5023    #[test]
5024    fn test_subagent_capability_semantics() {
5025        use serde_json::json;
5026
5027        let capabilities =
5028            serde_json::to_value(ClientCapabilities::new().subagents(SubagentCapabilities::new()))
5029                .unwrap();
5030        assert_eq!(capabilities["subagents"], json!({}));
5031
5032        let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5033        assert!(omitted.subagents.is_none());
5034
5035        let null: ClientCapabilities =
5036            serde_json::from_value(json!({ "subagents": null })).unwrap();
5037        assert!(null.subagents.is_none());
5038        assert_eq!(null, omitted);
5039        let serialized = serde_json::to_value(null).unwrap();
5040        assert_eq!(serialized, serde_json::to_value(omitted).unwrap());
5041        assert!(serialized.get("subagents").is_none());
5042
5043        for wire in [
5044            json!({}),
5045            json!({"cancel": null}),
5046            json!({"cancel": true}),
5047            json!({"cancel": false}),
5048        ] {
5049            let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
5050            assert!(child.cancel.is_none());
5051            assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
5052        }
5053        let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
5054        assert_eq!(
5055            serde_json::to_value(&enabled).unwrap(),
5056            json!({"cancel": {}})
5057        );
5058        assert_eq!(
5059            serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
5060            enabled
5061        );
5062        let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
5063        let with_meta =
5064            SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
5065        let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
5066        assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
5067        assert_eq!(
5068            serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
5069            with_meta
5070        );
5071        // Each supplied child capability set replaces the previous set.
5072        let removed = SubagentSessionCapabilities::new();
5073        assert_eq!(serde_json::to_value(&removed).unwrap(), json!({}));
5074        assert!(removed.cancel.is_none());
5075        assert!(enabled.cancel.is_some());
5076        assert!(enabled.cancel(None).cancel.is_none());
5077        let replacement = SubagentUpdate::new("child").capabilities(removed);
5078        assert_eq!(
5079            serde_json::to_value(replacement).unwrap(),
5080            json!({"sessionId": "child", "capabilities": {}})
5081        );
5082    }
5083
5084    #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
5085    #[test]
5086    fn test_subagent_capability_schema_is_optional_and_nullable() {
5087        use serde_json::json;
5088
5089        let schema = serde_json::to_value(schemars::schema_for!(ClientCapabilities)).unwrap();
5090        let variants = schema["properties"]["subagents"]["anyOf"]
5091            .as_array()
5092            .expect("subagents must allow the capability object or null");
5093        assert!(variants.contains(&json!({"type": "null"})));
5094        assert!(
5095            !schema["required"]
5096                .as_array()
5097                .is_some_and(|required| required.contains(&json!("subagents")))
5098        );
5099        let child =
5100            serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
5101        let variants = child["properties"]["cancel"]["anyOf"]
5102            .as_array()
5103            .expect("cancel must allow the capability object or null");
5104        assert!(variants.contains(&json!({"type": "null"})));
5105        assert!(
5106            variants
5107                .iter()
5108                .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
5109        );
5110        assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
5111        let cancel =
5112            serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
5113        assert_eq!(cancel["type"], "object");
5114        assert!(
5115            !child["required"]
5116                .as_array()
5117                .is_some_and(|required| required.contains(&json!("cancel")))
5118        );
5119    }
5120
5121    #[test]
5122    fn test_elicitation_capability_semantics() {
5123        use serde_json::json;
5124
5125        let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5126        assert!(unsupported.elicitation.is_none());
5127
5128        let null: ClientCapabilities =
5129            serde_json::from_value(json!({ "elicitation": null })).unwrap();
5130        assert!(null.elicitation.is_none());
5131
5132        let malformed: ClientCapabilities =
5133            serde_json::from_value(json!({ "elicitation": false })).unwrap();
5134        assert!(malformed.elicitation.is_none());
5135
5136        let empty: ClientCapabilities =
5137            serde_json::from_value(json!({ "elicitation": {} })).unwrap();
5138        let empty = empty.elicitation.expect("present capability");
5139        assert!(!empty.supports_form());
5140        assert!(!empty.supports_url());
5141
5142        let form_only: ClientCapabilities = serde_json::from_value(json!({
5143            "elicitation": { "form": {} }
5144        }))
5145        .unwrap();
5146        let form_only = form_only.elicitation.expect("advertised capability");
5147        assert!(form_only.supports_form());
5148        assert!(!form_only.supports_url());
5149
5150        let url_only: ClientCapabilities = serde_json::from_value(json!({
5151            "elicitation": { "url": {} }
5152        }))
5153        .unwrap();
5154        let url_only = url_only.elicitation.expect("advertised capability");
5155        assert!(!url_only.supports_form());
5156        assert!(url_only.supports_url());
5157
5158        let both: ClientCapabilities = serde_json::from_value(json!({
5159            "elicitation": { "form": {}, "url": {} }
5160        }))
5161        .unwrap();
5162        let both = both.elicitation.expect("advertised capability");
5163        assert!(both.supports_form());
5164        assert!(both.supports_url());
5165    }
5166
5167    #[test]
5168    fn test_elicitation_method_routing_and_envelopes() {
5169        use serde_json::json;
5170
5171        assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
5172        assert_eq!(
5173            CLIENT_METHOD_NAMES.elicitation_complete,
5174            "elicitation/complete"
5175        );
5176
5177        let request = AgentRequest::CreateElicitationRequest(CreateElicitationRequest::new(
5178            crate::v1::ElicitationFormMode::new(
5179                crate::v1::ElicitationSessionScope::new("sess_1"),
5180                crate::v1::ElicitationSchema::new(),
5181            ),
5182            "Choose a value",
5183        ));
5184        assert_eq!(request.method(), "elicitation/create");
5185        let method = Arc::from(request.method());
5186        let request = crate::v1::JsonRpcMessage::wrap(crate::v1::Request {
5187            id: crate::v1::RequestId::Number(7),
5188            method,
5189            params: Some(request),
5190        });
5191        assert_eq!(
5192            serde_json::to_value(request).unwrap(),
5193            json!({
5194                "jsonrpc": "2.0",
5195                "id": 7,
5196                "method": "elicitation/create",
5197                "params": {
5198                    "mode": "form",
5199                    "sessionId": "sess_1",
5200                    "message": "Choose a value",
5201                    "requestedSchema": { "type": "object", "properties": {} }
5202                }
5203            })
5204        );
5205
5206        let notification = AgentNotification::CompleteElicitationNotification(
5207            CompleteElicitationNotification::new("elic_1"),
5208        );
5209        assert_eq!(notification.method(), "elicitation/complete");
5210        let method = Arc::from(notification.method());
5211        let notification = crate::v1::JsonRpcMessage::wrap(crate::v1::Notification {
5212            method,
5213            params: Some(notification),
5214        });
5215        assert_eq!(
5216            serde_json::to_value(notification).unwrap(),
5217            json!({
5218                "jsonrpc": "2.0",
5219                "method": "elicitation/complete",
5220                "params": { "elicitationId": "elic_1" }
5221            })
5222        );
5223    }
5224
5225    #[test]
5226    fn test_client_capabilities_default_on_malformed_values() {
5227        use serde_json::json;
5228
5229        let capabilities: ClientCapabilities = serde_json::from_value(json!({
5230            "fs": {
5231                "readTextFile": "yes",
5232                "writeTextFile": true
5233            },
5234            "terminal": {}
5235        }))
5236        .unwrap();
5237
5238        assert!(!capabilities.fs.read_text_file);
5239        assert!(capabilities.fs.write_text_file);
5240        assert!(!capabilities.terminal);
5241
5242        let capabilities: ClientCapabilities = serde_json::from_value(json!({
5243            "fs": false
5244        }))
5245        .unwrap();
5246        assert_eq!(capabilities.fs, FileSystemCapabilities::default());
5247
5248        {
5249            let capabilities: ClientCapabilities = serde_json::from_value(json!({
5250                "auth": false
5251            }))
5252            .unwrap();
5253            assert_eq!(capabilities.auth, AuthCapabilities::default());
5254
5255            let capabilities: AuthCapabilities = serde_json::from_value(json!({
5256                "terminal": {}
5257            }))
5258            .unwrap();
5259            assert!(!capabilities.terminal);
5260        }
5261    }
5262
5263    #[test]
5264    fn test_serialization_behavior() {
5265        use serde_json::json;
5266
5267        assert_eq!(
5268            serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
5269            SessionInfoUpdate {
5270                title: MaybeUndefined::Undefined,
5271                updated_at: MaybeUndefined::Undefined,
5272                meta: None
5273            }
5274        );
5275        assert_eq!(
5276            serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
5277                .unwrap(),
5278            SessionInfoUpdate {
5279                title: MaybeUndefined::Null,
5280                updated_at: MaybeUndefined::Null,
5281                meta: None
5282            }
5283        );
5284        assert_eq!(
5285            serde_json::from_value::<SessionInfoUpdate>(
5286                json!({"title": "title", "updatedAt": "timestamp"})
5287            )
5288            .unwrap(),
5289            SessionInfoUpdate {
5290                title: MaybeUndefined::Value("title".to_string()),
5291                updated_at: MaybeUndefined::Value("timestamp".to_string()),
5292                meta: None
5293            }
5294        );
5295
5296        assert_eq!(
5297            serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
5298            json!({})
5299        );
5300        assert_eq!(
5301            serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
5302            json!({"title": "title"})
5303        );
5304        assert_eq!(
5305            serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
5306            json!({"title": null})
5307        );
5308        assert_eq!(
5309            serde_json::to_value(
5310                SessionInfoUpdate::new()
5311                    .title("title")
5312                    .title(MaybeUndefined::Undefined)
5313            )
5314            .unwrap(),
5315            json!({})
5316        );
5317    }
5318
5319    #[test]
5320    fn test_content_chunk_message_id_serialization() {
5321        use serde_json::json;
5322
5323        assert_eq!(
5324            serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
5325                ContentBlock::Text(crate::v1::TextContent::new("Hello"))
5326            )))
5327            .unwrap(),
5328            json!({
5329                "sessionUpdate": "agent_message_chunk",
5330                "content": {
5331                    "type": "text",
5332                    "text": "Hello"
5333                }
5334            })
5335        );
5336
5337        assert_eq!(
5338            serde_json::to_value(SessionUpdate::AgentMessageChunk(
5339                ContentChunk::new(ContentBlock::Text(crate::v1::TextContent::new("Hello")))
5340                    .message_id("msg_agent_c42b9")
5341            ))
5342            .unwrap(),
5343            json!({
5344                "sessionUpdate": "agent_message_chunk",
5345                "messageId": "msg_agent_c42b9",
5346                "content": {
5347                    "type": "text",
5348                    "text": "Hello"
5349                }
5350            })
5351        );
5352
5353        let SessionUpdate::AgentMessageChunk(chunk) = serde_json::from_value(json!({
5354            "sessionUpdate": "agent_message_chunk",
5355            "messageId": null,
5356            "content": {
5357                "type": "text",
5358                "text": "Hello"
5359            }
5360        }))
5361        .unwrap() else {
5362            panic!("expected agent message chunk");
5363        };
5364
5365        assert_eq!(chunk.message_id, None);
5366    }
5367
5368    #[test]
5369    fn test_usage_update_serialization() {
5370        use serde_json::json;
5371
5372        assert_eq!(
5373            serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
5374                53_000, 200_000
5375            )))
5376            .unwrap(),
5377            json!({
5378                "sessionUpdate": "usage_update",
5379                "used": 53000,
5380                "size": 200_000
5381            })
5382        );
5383
5384        assert_eq!(
5385            serde_json::to_value(SessionUpdate::UsageUpdate(
5386                UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
5387            ))
5388            .unwrap(),
5389            json!({
5390                "sessionUpdate": "usage_update",
5391                "used": 53000,
5392                "size": 200_000,
5393                "cost": {
5394                    "amount": 0.045,
5395                    "currency": "USD"
5396                }
5397            })
5398        );
5399
5400        let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
5401            "sessionUpdate": "usage_update",
5402            "used": 53000,
5403            "size": 200_000,
5404            "cost": null
5405        }))
5406        .unwrap() else {
5407            panic!("expected usage update");
5408        };
5409
5410        assert_eq!(update.cost, None);
5411    }
5412
5413    #[test]
5414    fn test_client_capabilities_boolean_config_options_serialization() {
5415        use serde_json::json;
5416
5417        let capabilities = ClientCapabilities::new().session(
5418            ClientSessionCapabilities::new().config_options(
5419                SessionConfigOptionsCapabilities::new()
5420                    .boolean(BooleanConfigOptionCapabilities::new()),
5421            ),
5422        );
5423        let json = serde_json::to_value(&capabilities).unwrap();
5424
5425        assert_eq!(json["session"]["configOptions"]["boolean"], json!({}));
5426
5427        let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5428        assert!(omitted.session.is_none());
5429
5430        let null_session: ClientCapabilities = serde_json::from_value(json!({
5431            "session": null
5432        }))
5433        .unwrap();
5434        assert!(null_session.session.is_none());
5435
5436        let null_config_options: ClientCapabilities = serde_json::from_value(json!({
5437            "session": {
5438                "configOptions": null
5439            }
5440        }))
5441        .unwrap();
5442        assert!(
5443            null_config_options
5444                .session
5445                .and_then(|session| session.config_options)
5446                .is_none()
5447        );
5448
5449        let null_boolean: ClientCapabilities = serde_json::from_value(json!({
5450            "session": {
5451                "configOptions": {
5452                    "boolean": null
5453                }
5454            }
5455        }))
5456        .unwrap();
5457        assert!(
5458            null_boolean
5459                .session
5460                .and_then(|session| session.config_options)
5461                .and_then(|config_options| config_options.boolean)
5462                .is_none()
5463        );
5464    }
5465
5466    #[cfg(feature = "unstable_plan_operations")]
5467    #[test]
5468    fn test_plan_operations_serialization() {
5469        use serde_json::json;
5470
5471        use crate::v1::{PlanEntry, PlanEntryPriority, PlanEntryStatus, PlanUpdateContent};
5472
5473        let plan_update = SessionUpdate::PlanUpdate(PlanUpdate::new(PlanUpdateContent::items(
5474            "plan-1",
5475            vec![PlanEntry::new(
5476                "Step 1",
5477                PlanEntryPriority::High,
5478                PlanEntryStatus::Pending,
5479            )],
5480        )));
5481
5482        assert_eq!(
5483            serde_json::to_value(plan_update).unwrap(),
5484            json!({
5485                "sessionUpdate": "plan_update",
5486                "plan": {
5487                    "type": "items",
5488                    "planId": "plan-1",
5489                    "entries": [
5490                        {
5491                            "content": "Step 1",
5492                            "priority": "high",
5493                            "status": "pending"
5494                        }
5495                    ]
5496                }
5497            })
5498        );
5499
5500        assert_eq!(
5501            serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
5502            json!({
5503                "sessionUpdate": "plan_removed",
5504                "planId": "plan-1"
5505            })
5506        );
5507
5508        let capabilities = ClientCapabilities::new().plan(PlanCapabilities::new());
5509        let json = serde_json::to_value(&capabilities).unwrap();
5510        assert_eq!(json["plan"], json!({}));
5511
5512        assert_eq!(
5513            serde_json::from_value::<ClientCapabilities>(json!({ "plan": null }))
5514                .unwrap()
5515                .plan,
5516            None
5517        );
5518    }
5519
5520    #[cfg(feature = "unstable_mcp_over_acp")]
5521    #[test]
5522    fn test_agent_mcp_request_method_names() {
5523        use serde_json::json;
5524
5525        let params: serde_json::Map<String, serde_json::Value> =
5526            [("cursor".to_string(), json!("abc"))].into_iter().collect();
5527
5528        assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
5529        assert_eq!(
5530            AgentRequest::MessageMcpRequest(MessageMcpRequest::new(
5531                "server-1",
5532                "req-1",
5533                "tools/list"
5534            ))
5535            .method(),
5536            "mcp/message"
5537        );
5538        assert_eq!(
5539            serde_json::to_value(
5540                MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
5541            )
5542            .unwrap(),
5543            json!({
5544                "serverId": "server-1",
5545                "requestId": "req-1",
5546                "method": "tools/list",
5547                "params": { "cursor": "abc" }
5548            })
5549        );
5550
5551        let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
5552            "serverId": "server-1",
5553            "requestId": "req-1",
5554            "method": "tools/list",
5555            "params": null,
5556            "_meta": null
5557        }))
5558        .unwrap();
5559        assert_eq!(request_with_null_params.params, None);
5560        assert_eq!(request_with_null_params.meta, None);
5561        for key in ["serverId", "requestId", "method"] {
5562            let mut value =
5563                json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5564            value.as_object_mut().unwrap().remove(key);
5565            assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5566        }
5567        for key in ["serverId", "requestId", "method"] {
5568            let mut value =
5569                json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5570            value[key] = serde_json::Value::Null;
5571            assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5572        }
5573    }
5574
5575    #[test]
5576    fn request_permission_request_rejects_malformed_options() {
5577        use serde_json::json;
5578
5579        assert!(
5580            serde_json::from_value::<RequestPermissionRequest>(json!({
5581                "sessionId": "sess-1",
5582                "toolCall": {"toolCallId": "tc-1"},
5583                "options": "not-an-array"
5584            }))
5585            .is_err()
5586        );
5587        assert!(
5588            serde_json::from_value::<RequestPermissionRequest>(json!({
5589                "sessionId": "sess-1",
5590                "toolCall": {"toolCallId": "tc-1"},
5591                "options": [{"optionId": "allow"}]
5592            }))
5593            .is_err()
5594        );
5595    }
5596
5597    #[test]
5598    fn create_terminal_request_rejects_malformed_fields() {
5599        use serde_json::json;
5600
5601        let request = |field: &str, value: serde_json::Value| {
5602            let mut request = json!({"sessionId": "sess-1", "command": "npx"});
5603            request[field] = value;
5604            serde_json::from_value::<CreateTerminalRequest>(request)
5605        };
5606
5607        // Defaulting any of these would run a different command than requested.
5608        for (field, value) in [
5609            ("args", json!(["serve", "--port", 3000])),
5610            ("args", json!("serve --port 3000")),
5611            ("env", json!([{"name": "PORT", "value": 3000}])),
5612            ("env", json!({"PORT": "3000"})),
5613            ("cwd", json!({"path": "/repo"})),
5614            ("outputByteLimit", json!("1048576")),
5615        ] {
5616            assert!(request(field, value.clone()).is_err(), "{field}: {value}");
5617        }
5618
5619        for field in ["args", "env", "cwd", "outputByteLimit"] {
5620            assert_eq!(
5621                request(field, serde_json::Value::Null).unwrap(),
5622                CreateTerminalRequest::new("sess-1", "npx"),
5623                "{field}"
5624            );
5625        }
5626    }
5627
5628    #[test]
5629    fn read_text_file_request_rejects_malformed_range() {
5630        use serde_json::json;
5631
5632        let request = |field: &str, value: serde_json::Value| {
5633            let mut request = json!({"sessionId": "sess-1", "path": "/repo/src/main.rs"});
5634            request[field] = value;
5635            serde_json::from_value::<ReadTextFileRequest>(request)
5636        };
5637
5638        // Defaulting either bound would return a different part of the file.
5639        for field in ["line", "limit"] {
5640            assert!(request(field, json!("120")).is_err(), "{field}");
5641            assert!(request(field, json!(-1)).is_err(), "{field}");
5642            assert_eq!(
5643                request(field, serde_json::Value::Null).unwrap(),
5644                ReadTextFileRequest::new("sess-1", "/repo/src/main.rs"),
5645                "{field}"
5646            );
5647        }
5648    }
5649}