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