Skip to main content

agent_client_protocol_schema/v1/
client.rs

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