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