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