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