Skip to main content

agent_client_protocol_schema/v2/
elicitation.rs

1//! Elicitation types for structured user input.
2//!
3//! This module defines the types used for agent-initiated elicitation,
4//! where the agent requests structured input from the user via forms or URLs.
5
6use std::{collections::BTreeMap, sync::Arc};
7
8use derive_more::{Display, From};
9#[cfg(feature = "schemars")]
10use schemars::Schema;
11use serde::{Deserialize, Serialize};
12use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
13
14#[cfg(feature = "schemars")]
15use super::{ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME};
16use super::{Meta, RequestId, SessionId, ToolCallId};
17use crate::IntoOption;
18
19/// Unique identifier for an elicitation.
20#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
22#[serde(transparent)]
23#[from(forward)]
24#[non_exhaustive]
25pub struct ElicitationId(pub Arc<str>);
26
27impl ElicitationId {
28    /// Wraps a protocol string as a typed [`ElicitationId`].
29    #[must_use]
30    pub fn new(id: impl Into<Self>) -> Self {
31        id.into()
32    }
33}
34
35/// String format types for string properties in elicitation schemas.
36#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "kebab-case")]
39#[non_exhaustive]
40pub enum StringFormat {
41    /// Email address format.
42    Email,
43    /// URI format.
44    Uri,
45    /// Date format (YYYY-MM-DD).
46    Date,
47    /// Date-time format (RFC 3339).
48    DateTime,
49    /// Custom or future string format.
50    ///
51    /// Unknown formats are preserved. Implementations that do not understand a
52    /// format should treat it as an annotation rather than rejecting the schema.
53    #[serde(untagged)]
54    Other(String),
55}
56
57/// Type discriminator for elicitation schemas.
58#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
60#[serde(rename_all = "snake_case")]
61#[non_exhaustive]
62pub enum ElicitationSchemaType {
63    /// Object schema type.
64    #[default]
65    Object,
66}
67
68/// A titled enum option with a const value, human-readable title, and optional description.
69#[serde_as]
70#[skip_serializing_none]
71#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[non_exhaustive]
74pub struct EnumOption {
75    /// The constant value for this option.
76    #[serde(rename = "const")]
77    pub value: String,
78    /// Human-readable title for this option.
79    pub title: String,
80    /// Human-readable description.
81    ///
82    /// Optional. Omitted and `null` are equivalent and mean no description is provided.
83    #[serde_as(deserialize_as = "DefaultOnError")]
84    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
85    #[serde(default)]
86    pub description: Option<String>,
87    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
88    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
89    /// Implementations MUST NOT assign meaning to keys they do not recognize.
90    ///
91    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
92    #[serde_as(deserialize_as = "DefaultOnError")]
93    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
94    #[serde(default)]
95    #[serde(rename = "_meta")]
96    pub meta: Option<Meta>,
97}
98
99impl EnumOption {
100    /// Create a new enum option.
101    #[must_use]
102    pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
103        Self {
104            value: value.into(),
105            title: title.into(),
106            description: None,
107            meta: None,
108        }
109    }
110
111    /// Human-readable description.
112    #[must_use]
113    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
114        self.description = description.into_option();
115        self
116    }
117
118    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
119    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
120    /// do not recognize.
121    ///
122    /// Optional. Omitted and `null` are equivalent and mean no metadata.
123    ///
124    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
125    #[must_use]
126    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
127        self.meta = meta.into_option();
128        self
129    }
130}
131
132/// Schema for string properties in an elicitation form.
133///
134/// When `enum` or `oneOf` is set, this represents a single-select enum
135/// with `"type": "string"`.
136#[serde_as]
137#[skip_serializing_none]
138#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
139#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141#[non_exhaustive]
142pub struct StringPropertySchema {
143    /// Optional title for the property.
144    ///
145    /// Optional. Omitted and `null` are equivalent and mean no title is provided.
146    #[serde_as(deserialize_as = "DefaultOnError")]
147    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
148    #[serde(default)]
149    pub title: Option<String>,
150    /// Human-readable description.
151    ///
152    /// Optional. Omitted and `null` are equivalent and mean no description is provided.
153    #[serde_as(deserialize_as = "DefaultOnError")]
154    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
155    #[serde(default)]
156    pub description: Option<String>,
157    /// Minimum string length.
158    ///
159    /// Optional. Omitted and `null` are equivalent and mean there is no minimum length constraint.
160    #[serde(default)]
161    pub min_length: Option<u32>,
162    /// Maximum string length.
163    ///
164    /// Optional. Omitted and `null` are equivalent and mean there is no maximum length constraint.
165    #[serde(default)]
166    pub max_length: Option<u32>,
167    /// Pattern the string must match, in ECMA-262 regular expression syntax.
168    ///
169    /// Optional. Omitted and `null` are equivalent and mean there is no pattern constraint.
170    #[cfg_attr(feature = "schemars", schemars(extend("format" = "regex")))]
171    #[serde(default)]
172    pub pattern: Option<String>,
173    /// String format.
174    ///
175    /// Optional. Omitted and `null` are equivalent and mean there is no format constraint.
176    #[serde(default)]
177    pub format: Option<StringFormat>,
178    /// Default value.
179    ///
180    /// Optional. Omitted and `null` are equivalent and mean no default value is provided.
181    #[serde_as(deserialize_as = "DefaultOnError")]
182    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
183    #[serde(default)]
184    pub default: Option<String>,
185    /// Enum values for untitled single-select enums.
186    /// Must contain at least one value when present.
187    /// Optional. Omitted and `null` are equivalent and mean no untitled single-select choices are
188    /// declared by `enum`.
189    #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
190    #[serde(default)]
191    #[serde(rename = "enum")]
192    pub enum_values: Option<Vec<String>>,
193    /// Titled enum options for titled single-select enums.
194    /// Must contain at least one option when present.
195    /// Optional. Omitted and `null` are equivalent and mean no titled single-select choices are
196    /// declared by `oneOf`.
197    #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
198    #[serde(default)]
199    #[serde(rename = "oneOf")]
200    pub one_of: Option<Vec<EnumOption>>,
201    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
202    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
203    /// Implementations MUST NOT assign meaning to keys they do not recognize.
204    ///
205    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
206    #[serde_as(deserialize_as = "DefaultOnError")]
207    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
208    #[serde(default)]
209    #[serde(rename = "_meta")]
210    pub meta: Option<Meta>,
211}
212
213impl StringPropertySchema {
214    /// Create a new string property schema.
215    #[must_use]
216    pub fn new() -> Self {
217        Self::default()
218    }
219
220    /// Create an email string property schema.
221    #[must_use]
222    pub fn email() -> Self {
223        Self {
224            format: Some(StringFormat::Email),
225            ..Default::default()
226        }
227    }
228
229    /// Create a URI string property schema.
230    #[must_use]
231    pub fn uri() -> Self {
232        Self {
233            format: Some(StringFormat::Uri),
234            ..Default::default()
235        }
236    }
237
238    /// Create a date string property schema.
239    #[must_use]
240    pub fn date() -> Self {
241        Self {
242            format: Some(StringFormat::Date),
243            ..Default::default()
244        }
245    }
246
247    /// Create a date-time string property schema.
248    #[must_use]
249    pub fn date_time() -> Self {
250        Self {
251            format: Some(StringFormat::DateTime),
252            ..Default::default()
253        }
254    }
255
256    /// Optional title for the property.
257    #[must_use]
258    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
259        self.title = title.into_option();
260        self
261    }
262
263    /// Human-readable description.
264    #[must_use]
265    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
266        self.description = description.into_option();
267        self
268    }
269
270    /// Minimum string length.
271    #[must_use]
272    pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
273        self.min_length = min_length.into_option();
274        self
275    }
276
277    /// Maximum string length.
278    #[must_use]
279    pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
280        self.max_length = max_length.into_option();
281        self
282    }
283
284    /// Pattern the string must match, in ECMA-262 regular expression syntax.
285    #[must_use]
286    pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
287        self.pattern = pattern.into_option();
288        self
289    }
290
291    /// String format.
292    #[must_use]
293    pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
294        self.format = format.into_option();
295        self
296    }
297
298    /// Default value.
299    #[must_use]
300    pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
301        self.default = default.into_option();
302        self
303    }
304
305    /// Enum values for untitled single-select enums.
306    #[must_use]
307    pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
308        self.enum_values = enum_values.into_option();
309        self
310    }
311
312    /// Titled enum options for titled single-select enums.
313    #[must_use]
314    pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
315        self.one_of = one_of.into_option();
316        self
317    }
318
319    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
320    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
321    /// do not recognize.
322    ///
323    /// Optional. Omitted and `null` are equivalent and mean no metadata.
324    ///
325    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
326    #[must_use]
327    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
328        self.meta = meta.into_option();
329        self
330    }
331}
332
333/// Schema for number (floating-point) properties in an elicitation form.
334#[serde_as]
335#[skip_serializing_none]
336#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
337#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
338#[serde(rename_all = "camelCase")]
339#[non_exhaustive]
340pub struct NumberPropertySchema {
341    /// Optional title for the property.
342    ///
343    /// Optional. Omitted and `null` are equivalent and mean no title is provided.
344    #[serde_as(deserialize_as = "DefaultOnError")]
345    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
346    #[serde(default)]
347    pub title: Option<String>,
348    /// Human-readable description.
349    ///
350    /// Optional. Omitted and `null` are equivalent and mean no description is provided.
351    #[serde_as(deserialize_as = "DefaultOnError")]
352    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
353    #[serde(default)]
354    pub description: Option<String>,
355    /// Minimum value (inclusive).
356    ///
357    /// Optional. Omitted and `null` are equivalent and mean there is no inclusive lower bound.
358    #[serde(default)]
359    pub minimum: Option<f64>,
360    /// Maximum value (inclusive).
361    ///
362    /// Optional. Omitted and `null` are equivalent and mean there is no inclusive upper bound.
363    #[serde(default)]
364    pub maximum: Option<f64>,
365    /// Default value.
366    ///
367    /// Optional. Omitted and `null` are equivalent and mean no default value is provided.
368    #[serde_as(deserialize_as = "DefaultOnError")]
369    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
370    #[serde(default)]
371    pub default: Option<f64>,
372    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
373    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
374    /// Implementations MUST NOT assign meaning to keys they do not recognize.
375    ///
376    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
377    #[serde_as(deserialize_as = "DefaultOnError")]
378    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
379    #[serde(default)]
380    #[serde(rename = "_meta")]
381    pub meta: Option<Meta>,
382}
383
384impl NumberPropertySchema {
385    /// Create a new number property schema.
386    #[must_use]
387    pub fn new() -> Self {
388        Self::default()
389    }
390
391    /// Optional title for the property.
392    #[must_use]
393    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
394        self.title = title.into_option();
395        self
396    }
397
398    /// Human-readable description.
399    #[must_use]
400    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
401        self.description = description.into_option();
402        self
403    }
404
405    /// Minimum value (inclusive).
406    #[must_use]
407    pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
408        self.minimum = minimum.into_option();
409        self
410    }
411
412    /// Maximum value (inclusive).
413    #[must_use]
414    pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
415        self.maximum = maximum.into_option();
416        self
417    }
418
419    /// Default value.
420    #[must_use]
421    pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
422        self.default = default.into_option();
423        self
424    }
425
426    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
427    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
428    /// do not recognize.
429    ///
430    /// Optional. Omitted and `null` are equivalent and mean no metadata.
431    ///
432    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
433    #[must_use]
434    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
435        self.meta = meta.into_option();
436        self
437    }
438}
439
440/// Schema for integer properties in an elicitation form.
441#[serde_as]
442#[skip_serializing_none]
443#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
444#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446#[non_exhaustive]
447pub struct IntegerPropertySchema {
448    /// Optional title for the property.
449    ///
450    /// Optional. Omitted and `null` are equivalent and mean no title is provided.
451    #[serde_as(deserialize_as = "DefaultOnError")]
452    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
453    #[serde(default)]
454    pub title: Option<String>,
455    /// Human-readable description.
456    ///
457    /// Optional. Omitted and `null` are equivalent and mean no description is provided.
458    #[serde_as(deserialize_as = "DefaultOnError")]
459    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
460    #[serde(default)]
461    pub description: Option<String>,
462    /// Minimum value (inclusive).
463    ///
464    /// Optional. Omitted and `null` are equivalent and mean there is no inclusive lower bound.
465    #[serde(default)]
466    pub minimum: Option<i64>,
467    /// Maximum value (inclusive).
468    ///
469    /// Optional. Omitted and `null` are equivalent and mean there is no inclusive upper bound.
470    #[serde(default)]
471    pub maximum: Option<i64>,
472    /// Default value.
473    ///
474    /// Optional. Omitted and `null` are equivalent and mean no default value is provided.
475    #[serde_as(deserialize_as = "DefaultOnError")]
476    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
477    #[serde(default)]
478    pub default: Option<i64>,
479    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
480    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
481    /// Implementations MUST NOT assign meaning to keys they do not recognize.
482    ///
483    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
484    #[serde_as(deserialize_as = "DefaultOnError")]
485    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
486    #[serde(default)]
487    #[serde(rename = "_meta")]
488    pub meta: Option<Meta>,
489}
490
491impl IntegerPropertySchema {
492    /// Create a new integer property schema.
493    #[must_use]
494    pub fn new() -> Self {
495        Self::default()
496    }
497
498    /// Optional title for the property.
499    #[must_use]
500    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
501        self.title = title.into_option();
502        self
503    }
504
505    /// Human-readable description.
506    #[must_use]
507    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
508        self.description = description.into_option();
509        self
510    }
511
512    /// Minimum value (inclusive).
513    #[must_use]
514    pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
515        self.minimum = minimum.into_option();
516        self
517    }
518
519    /// Maximum value (inclusive).
520    #[must_use]
521    pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
522        self.maximum = maximum.into_option();
523        self
524    }
525
526    /// Default value.
527    #[must_use]
528    pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
529        self.default = default.into_option();
530        self
531    }
532
533    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
534    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
535    /// do not recognize.
536    ///
537    /// Optional. Omitted and `null` are equivalent and mean no metadata.
538    ///
539    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
540    #[must_use]
541    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
542        self.meta = meta.into_option();
543        self
544    }
545}
546
547/// Schema for boolean properties in an elicitation form.
548#[serde_as]
549#[skip_serializing_none]
550#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
551#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
552#[serde(rename_all = "camelCase")]
553#[non_exhaustive]
554pub struct BooleanPropertySchema {
555    /// Optional title for the property.
556    ///
557    /// Optional. Omitted and `null` are equivalent and mean no title is provided.
558    #[serde_as(deserialize_as = "DefaultOnError")]
559    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
560    #[serde(default)]
561    pub title: Option<String>,
562    /// Human-readable description.
563    ///
564    /// Optional. Omitted and `null` are equivalent and mean no description is provided.
565    #[serde_as(deserialize_as = "DefaultOnError")]
566    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
567    #[serde(default)]
568    pub description: Option<String>,
569    /// Default value.
570    ///
571    /// Optional. Omitted and `null` are equivalent and mean no default value is provided.
572    #[serde_as(deserialize_as = "DefaultOnError")]
573    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
574    #[serde(default)]
575    pub default: Option<bool>,
576    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
577    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
578    /// Implementations MUST NOT assign meaning to keys they do not recognize.
579    ///
580    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
581    #[serde_as(deserialize_as = "DefaultOnError")]
582    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
583    #[serde(default)]
584    #[serde(rename = "_meta")]
585    pub meta: Option<Meta>,
586}
587
588impl BooleanPropertySchema {
589    /// Create a new boolean property schema.
590    #[must_use]
591    pub fn new() -> Self {
592        Self::default()
593    }
594
595    /// Optional title for the property.
596    #[must_use]
597    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
598        self.title = title.into_option();
599        self
600    }
601
602    /// Human-readable description.
603    #[must_use]
604    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
605        self.description = description.into_option();
606        self
607    }
608
609    /// Default value.
610    #[must_use]
611    pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
612        self.default = default.into_option();
613        self
614    }
615
616    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
617    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
618    /// do not recognize.
619    ///
620    /// Optional. Omitted and `null` are equivalent and mean no metadata.
621    ///
622    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
623    #[must_use]
624    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
625        self.meta = meta.into_option();
626        self
627    }
628}
629
630/// String item schema for multi-select enum properties.
631#[serde_as]
632#[skip_serializing_none]
633#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
634#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
635#[non_exhaustive]
636pub struct StringMultiSelectItems {
637    /// Allowed enum values. Must contain at least one value.
638    #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
639    #[serde(rename = "enum")]
640    pub values: Vec<String>,
641    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
642    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
643    /// Implementations MUST NOT assign meaning to keys they do not recognize.
644    ///
645    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
646    #[serde_as(deserialize_as = "DefaultOnError")]
647    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
648    #[serde(default)]
649    #[serde(rename = "_meta")]
650    pub meta: Option<Meta>,
651}
652
653impl StringMultiSelectItems {
654    /// Create new string multi-select items.
655    #[must_use]
656    pub fn new(values: Vec<String>) -> Self {
657        Self { values, meta: None }
658    }
659
660    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
661    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
662    /// do not recognize.
663    ///
664    /// Optional. Omitted and `null` are equivalent and mean no metadata.
665    ///
666    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
667    #[must_use]
668    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
669        self.meta = meta.into_option();
670        self
671    }
672}
673
674/// Items definition for titled multi-select enum properties.
675#[serde_as]
676#[skip_serializing_none]
677#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
678#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
679#[non_exhaustive]
680pub struct TitledMultiSelectItems {
681    /// Titled enum options. Must contain at least one option.
682    #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
683    #[serde(rename = "anyOf")]
684    pub options: Vec<EnumOption>,
685    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
686    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
687    /// Implementations MUST NOT assign meaning to keys they do not recognize.
688    ///
689    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
690    #[serde_as(deserialize_as = "DefaultOnError")]
691    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
692    #[serde(default)]
693    #[serde(rename = "_meta")]
694    pub meta: Option<Meta>,
695}
696
697impl TitledMultiSelectItems {
698    /// Create new titled multi-select items.
699    #[must_use]
700    pub fn new(options: Vec<EnumOption>) -> Self {
701        Self {
702            options,
703            meta: None,
704        }
705    }
706
707    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
708    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
709    /// do not recognize.
710    ///
711    /// Optional. Omitted and `null` are equivalent and mean no metadata.
712    ///
713    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
714    #[must_use]
715    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
716        self.meta = meta.into_option();
717        self
718    }
719}
720
721/// Custom or future typed item schema for multi-select properties.
722///
723/// This preserves unknown item `type` values and the rest of the `items`
724/// payload for clients that store, replay, proxy, or forward elicitation
725/// requests.
726#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
727#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
728#[cfg_attr(feature = "schemars", schemars(inline))]
729#[cfg_attr(feature = "schemars", schemars(transform = other_multi_select_items_schema))]
730#[serde(rename_all = "camelCase")]
731#[non_exhaustive]
732pub struct OtherMultiSelectItems {
733    /// Custom or future multi-select item type.
734    ///
735    /// Values beginning with `_` are reserved for implementation-specific
736    /// extensions. Unknown values that do not begin with `_` are reserved for
737    /// future ACP variants.
738    #[serde(rename = "type")]
739    pub type_: String,
740    /// Additional fields from the unknown item schema payload.
741    #[serde(flatten)]
742    pub fields: BTreeMap<String, serde_json::Value>,
743}
744
745impl OtherMultiSelectItems {
746    /// Builds [`OtherMultiSelectItems`] from an unknown discriminator and preserves the remaining extension fields.
747    #[must_use]
748    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
749        fields.remove("type");
750        Self {
751            type_: type_.into(),
752            fields,
753        }
754    }
755}
756
757impl<'de> Deserialize<'de> for OtherMultiSelectItems {
758    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
759    where
760        D: serde::Deserializer<'de>,
761    {
762        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
763        let type_ = fields
764            .remove("type")
765            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
766        let serde_json::Value::String(type_) = type_ else {
767            return Err(serde::de::Error::custom("`type` must be a string"));
768        };
769
770        if is_known_multi_select_item_type(&type_) {
771            return Err(serde::de::Error::custom(format!(
772                "known multi-select item type `{type_}` did not match its schema"
773            )));
774        }
775
776        Ok(Self { type_, fields })
777    }
778}
779
780const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
781
782fn is_known_multi_select_item_type(type_: &str) -> bool {
783    KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
784}
785
786#[cfg(feature = "schemars")]
787fn other_multi_select_items_schema(schema: &mut Schema) {
788    super::schema_util::reject_known_string_discriminators(
789        schema,
790        "type",
791        KNOWN_MULTI_SELECT_ITEM_TYPES,
792    );
793}
794
795/// Items for a multi-select (array) property schema.
796#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
797#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
798#[serde(tag = "type", rename_all = "snake_case")]
799#[non_exhaustive]
800pub enum MultiSelectItems {
801    /// Multi-select string items with plain string values.
802    String(StringMultiSelectItems),
803    /// Custom or future typed multi-select items.
804    #[serde(untagged)]
805    Other(OtherMultiSelectItems),
806    /// Titled multi-select items with human-readable labels.
807    ///
808    /// As in MCP, titled items omit `type` and are identified by `anyOf`.
809    #[serde(untagged)]
810    Titled(TitledMultiSelectItems),
811}
812
813/// Schema for multi-select (array) properties in an elicitation form.
814#[serde_as]
815#[skip_serializing_none]
816#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
817#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
818#[serde(rename_all = "camelCase")]
819#[non_exhaustive]
820pub struct MultiSelectPropertySchema {
821    /// Optional title for the property.
822    ///
823    /// Optional. Omitted and `null` are equivalent and mean no title is provided.
824    #[serde_as(deserialize_as = "DefaultOnError")]
825    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
826    #[serde(default)]
827    pub title: Option<String>,
828    /// Human-readable description.
829    ///
830    /// Optional. Omitted and `null` are equivalent and mean no description is provided.
831    #[serde_as(deserialize_as = "DefaultOnError")]
832    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
833    #[serde(default)]
834    pub description: Option<String>,
835    /// Minimum number of items to select.
836    ///
837    /// Optional. Omitted and `null` are equivalent and mean there is no minimum selection count.
838    #[serde(default)]
839    pub min_items: Option<u64>,
840    /// Maximum number of items to select.
841    ///
842    /// Optional. Omitted and `null` are equivalent and mean there is no maximum selection count.
843    #[serde(default)]
844    pub max_items: Option<u64>,
845    /// The items definition describing allowed values.
846    pub items: MultiSelectItems,
847    /// Default selected values.
848    ///
849    /// Optional. Omitted and `null` are equivalent and mean no default selections are provided.
850    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
851    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
852    #[serde(default)]
853    pub default: Option<Vec<String>>,
854    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
855    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
856    /// Implementations MUST NOT assign meaning to keys they do not recognize.
857    ///
858    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
859    #[serde_as(deserialize_as = "DefaultOnError")]
860    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
861    #[serde(default)]
862    #[serde(rename = "_meta")]
863    pub meta: Option<Meta>,
864}
865
866impl MultiSelectPropertySchema {
867    /// Create a new untitled multi-select property schema.
868    #[must_use]
869    pub fn new(values: Vec<String>) -> Self {
870        Self {
871            title: None,
872            description: None,
873            min_items: None,
874            max_items: None,
875            items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
876            default: None,
877            meta: None,
878        }
879    }
880
881    /// Create a new titled multi-select property schema.
882    #[must_use]
883    pub fn titled(options: Vec<EnumOption>) -> Self {
884        Self {
885            title: None,
886            description: None,
887            min_items: None,
888            max_items: None,
889            items: MultiSelectItems::Titled(TitledMultiSelectItems {
890                options,
891                meta: None,
892            }),
893            default: None,
894            meta: None,
895        }
896    }
897
898    /// Optional title for the property.
899    #[must_use]
900    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
901        self.title = title.into_option();
902        self
903    }
904
905    /// Human-readable description.
906    #[must_use]
907    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
908        self.description = description.into_option();
909        self
910    }
911
912    /// Minimum number of items to select.
913    #[must_use]
914    pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
915        self.min_items = min_items.into_option();
916        self
917    }
918
919    /// Maximum number of items to select.
920    #[must_use]
921    pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
922        self.max_items = max_items.into_option();
923        self
924    }
925
926    /// Default selected values.
927    #[must_use]
928    pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
929        self.default = default.into_option();
930        self
931    }
932
933    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
934    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
935    /// do not recognize.
936    ///
937    /// Optional. Omitted and `null` are equivalent and mean no metadata.
938    ///
939    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
940    #[must_use]
941    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
942        self.meta = meta.into_option();
943        self
944    }
945}
946
947/// Property schema for elicitation form fields.
948///
949/// Each variant corresponds to a JSON Schema `"type"` value.
950/// Single-select enums use `type: "string"` with `enum` or `oneOf` set.
951/// Multi-select enums use `type: "array"`.
952#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
953#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
954#[serde(tag = "type", rename_all = "snake_case")]
955#[non_exhaustive]
956pub enum ElicitationPropertySchema {
957    /// String property (or single-select enum when `enum`/`oneOf` is set).
958    String(StringPropertySchema),
959    /// Number (floating-point) property.
960    Number(NumberPropertySchema),
961    /// Integer property.
962    Integer(IntegerPropertySchema),
963    /// Boolean property.
964    Boolean(BooleanPropertySchema),
965    /// Multi-select array property.
966    Array(MultiSelectPropertySchema),
967    /// Custom or future elicitation property schema.
968    ///
969    /// Values beginning with `_` are reserved for implementation-specific
970    /// extensions. Unknown values that do not begin with `_` are reserved for
971    /// future ACP variants.
972    ///
973    /// Clients that do not understand this property schema type should preserve
974    /// the raw schema when storing, replaying, proxying, or forwarding
975    /// elicitation requests. They MUST NOT render it as a known input control.
976    #[serde(untagged)]
977    Other(OtherElicitationPropertySchema),
978}
979
980/// Custom or future elicitation property schema payload.
981///
982/// This preserves the unknown `type` discriminator and the rest of the property
983/// schema object for clients that store, replay, proxy, or forward elicitation
984/// requests.
985#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
986#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
987#[cfg_attr(feature = "schemars", schemars(inline))]
988#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_property_schema_schema))]
989#[serde(rename_all = "camelCase")]
990#[non_exhaustive]
991pub struct OtherElicitationPropertySchema {
992    /// Custom or future elicitation property schema type.
993    ///
994    /// Values beginning with `_` are reserved for implementation-specific
995    /// extensions. Unknown values that do not begin with `_` are reserved for
996    /// future ACP variants.
997    #[serde(rename = "type")]
998    pub type_: String,
999    /// Additional fields from the unknown property schema payload.
1000    #[serde(flatten)]
1001    pub fields: BTreeMap<String, serde_json::Value>,
1002}
1003
1004impl OtherElicitationPropertySchema {
1005    /// Builds [`OtherElicitationPropertySchema`] from an unknown discriminator and preserves the remaining extension fields.
1006    #[must_use]
1007    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1008        fields.remove("type");
1009        Self {
1010            type_: type_.into(),
1011            fields,
1012        }
1013    }
1014}
1015
1016impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
1017    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1018    where
1019        D: serde::Deserializer<'de>,
1020    {
1021        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1022        let type_ = fields
1023            .remove("type")
1024            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1025        let serde_json::Value::String(type_) = type_ else {
1026            return Err(serde::de::Error::custom("`type` must be a string"));
1027        };
1028
1029        if is_known_elicitation_property_schema_type(&type_) {
1030            return Err(serde::de::Error::custom(format!(
1031                "known elicitation property schema type `{type_}` did not match its schema"
1032            )));
1033        }
1034
1035        Ok(Self { type_, fields })
1036    }
1037}
1038
1039const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
1040    &["string", "number", "integer", "boolean", "array"];
1041
1042fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
1043    KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
1044}
1045
1046#[cfg(feature = "schemars")]
1047fn other_elicitation_property_schema_schema(schema: &mut Schema) {
1048    super::schema_util::reject_known_string_discriminators(
1049        schema,
1050        "type",
1051        KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES,
1052    );
1053}
1054
1055impl From<StringPropertySchema> for ElicitationPropertySchema {
1056    fn from(schema: StringPropertySchema) -> Self {
1057        Self::String(schema)
1058    }
1059}
1060
1061impl From<NumberPropertySchema> for ElicitationPropertySchema {
1062    fn from(schema: NumberPropertySchema) -> Self {
1063        Self::Number(schema)
1064    }
1065}
1066
1067impl From<IntegerPropertySchema> for ElicitationPropertySchema {
1068    fn from(schema: IntegerPropertySchema) -> Self {
1069        Self::Integer(schema)
1070    }
1071}
1072
1073impl From<BooleanPropertySchema> for ElicitationPropertySchema {
1074    fn from(schema: BooleanPropertySchema) -> Self {
1075        Self::Boolean(schema)
1076    }
1077}
1078
1079impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
1080    fn from(schema: MultiSelectPropertySchema) -> Self {
1081        Self::Array(schema)
1082    }
1083}
1084
1085fn default_object_type() -> ElicitationSchemaType {
1086    ElicitationSchemaType::Object
1087}
1088
1089/// Type-safe elicitation schema for requesting structured user input.
1090///
1091/// This represents a JSON Schema object with primitive-typed properties,
1092/// as required by the elicitation specification.
1093#[serde_as]
1094#[skip_serializing_none]
1095#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1096#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1097#[serde(rename_all = "camelCase")]
1098#[non_exhaustive]
1099pub struct ElicitationSchema {
1100    /// Type discriminator. Always `"object"`; any other value is rejected.
1101    #[serde(rename = "type")]
1102    pub type_: ElicitationSchemaType,
1103    /// Optional title for the schema.
1104    ///
1105    /// Optional. Omitted and `null` are equivalent and mean no title is provided.
1106    #[serde_as(deserialize_as = "DefaultOnError")]
1107    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1108    #[serde(default)]
1109    pub title: Option<String>,
1110    /// Property definitions (must be primitive types).
1111    pub properties: BTreeMap<String, ElicitationPropertySchema>,
1112    /// List of required property names.
1113    ///
1114    /// Optional. Omitted and `null` are equivalent and mean no property names are required.
1115    #[serde(default)]
1116    pub required: Option<Vec<String>>,
1117    /// Optional description of what this schema represents.
1118    ///
1119    /// Optional. Omitted and `null` are equivalent and mean no schema description is provided.
1120    #[serde_as(deserialize_as = "DefaultOnError")]
1121    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1122    #[serde(default)]
1123    pub description: Option<String>,
1124    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1125    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
1126    /// Implementations MUST NOT assign meaning to keys they do not recognize.
1127    ///
1128    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1129    #[serde_as(deserialize_as = "DefaultOnError")]
1130    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1131    #[serde(default)]
1132    #[serde(rename = "_meta")]
1133    pub meta: Option<Meta>,
1134}
1135
1136impl Default for ElicitationSchema {
1137    fn default() -> Self {
1138        Self {
1139            type_: default_object_type(),
1140            title: None,
1141            properties: BTreeMap::new(),
1142            required: None,
1143            description: None,
1144            meta: None,
1145        }
1146    }
1147}
1148
1149impl ElicitationSchema {
1150    /// Create a new empty elicitation schema.
1151    #[must_use]
1152    pub fn new() -> Self {
1153        Self::default()
1154    }
1155
1156    /// Optional title for the schema.
1157    #[must_use]
1158    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1159        self.title = title.into_option();
1160        self
1161    }
1162
1163    /// Optional description of what this schema represents.
1164    #[must_use]
1165    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1166        self.description = description.into_option();
1167        self
1168    }
1169
1170    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1171    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
1172    /// do not recognize.
1173    ///
1174    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1175    ///
1176    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1177    #[must_use]
1178    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1179        self.meta = meta.into_option();
1180        self
1181    }
1182
1183    /// Add a property to the schema.
1184    #[must_use]
1185    pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1186    where
1187        S: Into<ElicitationPropertySchema>,
1188    {
1189        let name = name.into();
1190        self.properties.insert(name.clone(), schema.into());
1191
1192        if required {
1193            let required_fields = self.required.get_or_insert_with(Vec::new);
1194            if !required_fields.contains(&name) {
1195                required_fields.push(name);
1196            }
1197        } else if let Some(required_fields) = &mut self.required {
1198            required_fields.retain(|field| field != &name);
1199
1200            if required_fields.is_empty() {
1201                self.required = None;
1202            }
1203        }
1204
1205        self
1206    }
1207
1208    /// Add a string property.
1209    #[must_use]
1210    pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1211        self.property(name, StringPropertySchema::new(), required)
1212    }
1213
1214    /// Add an email property.
1215    #[must_use]
1216    pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1217        self.property(name, StringPropertySchema::email(), required)
1218    }
1219
1220    /// Add a URI property.
1221    #[must_use]
1222    pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1223        self.property(name, StringPropertySchema::uri(), required)
1224    }
1225
1226    /// Add a date property.
1227    #[must_use]
1228    pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1229        self.property(name, StringPropertySchema::date(), required)
1230    }
1231
1232    /// Add a date-time property.
1233    #[must_use]
1234    pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1235        self.property(name, StringPropertySchema::date_time(), required)
1236    }
1237
1238    /// Add a number property with range.
1239    #[must_use]
1240    pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1241        self.property(
1242            name,
1243            NumberPropertySchema::new().minimum(min).maximum(max),
1244            required,
1245        )
1246    }
1247
1248    /// Add an integer property with range.
1249    #[must_use]
1250    pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1251        self.property(
1252            name,
1253            IntegerPropertySchema::new().minimum(min).maximum(max),
1254            required,
1255        )
1256    }
1257
1258    /// Add a boolean property.
1259    #[must_use]
1260    pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1261        self.property(name, BooleanPropertySchema::new(), required)
1262    }
1263}
1264
1265/// Elicitation capabilities supported by the client.
1266#[serde_as]
1267#[skip_serializing_none]
1268#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1269#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1270#[serde(rename_all = "camelCase")]
1271#[non_exhaustive]
1272pub struct ElicitationCapabilities {
1273    /// Whether the client supports form-based elicitation.
1274    ///
1275    /// Optional. Omitted and `null` are equivalent and mean form support is not advertised.
1276    /// Supplying `{}` explicitly advertises form support.
1277    #[serde_as(deserialize_as = "DefaultOnError")]
1278    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1279    #[serde(default)]
1280    pub form: Option<ElicitationFormCapabilities>,
1281    /// Whether the client supports URL-based elicitation.
1282    ///
1283    /// Optional. Omitted and `null` are equivalent and mean the client does not advertise support.
1284    /// Supplying `{}` means the client supports URL-based elicitation.
1285    #[serde_as(deserialize_as = "DefaultOnError")]
1286    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1287    #[serde(default)]
1288    pub url: Option<ElicitationUrlCapabilities>,
1289    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1290    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
1291    /// Implementations MUST NOT assign meaning to keys they do not recognize.
1292    ///
1293    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1294    #[serde_as(deserialize_as = "DefaultOnError")]
1295    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1296    #[serde(default)]
1297    #[serde(rename = "_meta")]
1298    pub meta: Option<Meta>,
1299}
1300
1301impl ElicitationCapabilities {
1302    /// Builds empty elicitation capabilities.
1303    ///
1304    /// Use the builder methods to advertise supported modes. An empty capability object does not
1305    /// advertise form or URL support.
1306    #[must_use]
1307    pub fn new() -> Self {
1308        Self::default()
1309    }
1310
1311    /// Returns whether form-based elicitation is supported.
1312    ///
1313    #[must_use]
1314    pub fn supports_form(&self) -> bool {
1315        self.form.is_some()
1316    }
1317
1318    /// Returns whether URL-based elicitation is supported.
1319    #[must_use]
1320    pub fn supports_url(&self) -> bool {
1321        self.url.is_some()
1322    }
1323
1324    /// Whether the client supports form-based elicitation.
1325    ///
1326    /// Omitted and `null` are equivalent and mean form support is not advertised.
1327    /// Supplying `{}` explicitly advertises form-based elicitation.
1328    #[must_use]
1329    pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1330        self.form = form.into_option();
1331        self
1332    }
1333
1334    /// Whether the client supports URL-based elicitation.
1335    ///
1336    /// Omitted and `null` are equivalent and mean the client does not advertise support.
1337    /// Supplying `{}` means the client supports URL-based elicitation.
1338    #[must_use]
1339    pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1340        self.url = url.into_option();
1341        self
1342    }
1343
1344    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1345    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
1346    /// do not recognize.
1347    ///
1348    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1349    ///
1350    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1351    #[must_use]
1352    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1353        self.meta = meta.into_option();
1354        self
1355    }
1356}
1357
1358/// Form-based elicitation capabilities.
1359///
1360/// Supplying `{}` means the client supports form-based elicitation.
1361#[serde_as]
1362#[skip_serializing_none]
1363#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1364#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1365#[serde(rename_all = "camelCase")]
1366#[non_exhaustive]
1367pub struct ElicitationFormCapabilities {
1368    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1369    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
1370    /// Implementations MUST NOT assign meaning to keys they do not recognize.
1371    ///
1372    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1373    #[serde_as(deserialize_as = "DefaultOnError")]
1374    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1375    #[serde(default)]
1376    #[serde(rename = "_meta")]
1377    pub meta: Option<Meta>,
1378}
1379
1380impl ElicitationFormCapabilities {
1381    /// Builds an empty [`ElicitationFormCapabilities`]; use builder methods to advertise supported sub-capabilities.
1382    #[must_use]
1383    pub fn new() -> Self {
1384        Self::default()
1385    }
1386
1387    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1388    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
1389    /// do not recognize.
1390    ///
1391    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1392    ///
1393    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1394    #[must_use]
1395    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1396        self.meta = meta.into_option();
1397        self
1398    }
1399}
1400
1401/// URL-based elicitation capabilities.
1402///
1403/// Supplying `{}` means the client supports URL-based elicitation.
1404#[serde_as]
1405#[skip_serializing_none]
1406#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1407#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1408#[serde(rename_all = "camelCase")]
1409#[non_exhaustive]
1410pub struct ElicitationUrlCapabilities {
1411    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1412    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
1413    /// Implementations MUST NOT assign meaning to keys they do not recognize.
1414    ///
1415    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1416    #[serde_as(deserialize_as = "DefaultOnError")]
1417    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1418    #[serde(default)]
1419    #[serde(rename = "_meta")]
1420    pub meta: Option<Meta>,
1421}
1422
1423impl ElicitationUrlCapabilities {
1424    /// Builds an empty [`ElicitationUrlCapabilities`]; use builder methods to advertise supported sub-capabilities.
1425    #[must_use]
1426    pub fn new() -> Self {
1427        Self::default()
1428    }
1429
1430    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1431    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
1432    /// do not recognize.
1433    ///
1434    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1435    ///
1436    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1437    #[must_use]
1438    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1439        self.meta = meta.into_option();
1440        self
1441    }
1442}
1443
1444/// The scope of an elicitation request, determining what context it's tied to.
1445///
1446/// Exactly one of `sessionId` or `requestId` MUST be present.
1447#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1448#[cfg_attr(feature = "schemars", schemars(transform = elicitation_scope_schema))]
1449#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1450#[serde(untagged)]
1451#[non_exhaustive]
1452pub enum ElicitationScope {
1453    /// Tied to a session, optionally to a specific tool call within that session.
1454    Session(ElicitationSessionScope),
1455    /// Tied to a specific JSON-RPC request outside of a session
1456    /// (e.g., during auth/configuration phases before any session is started).
1457    Request(ElicitationRequestScope),
1458}
1459
1460impl<'de> Deserialize<'de> for ElicitationScope {
1461    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1462    where
1463        D: serde::Deserializer<'de>,
1464    {
1465        let mut fields = serde_json::Map::deserialize(deserializer)?;
1466        let scope = match (fields.remove("sessionId"), fields.remove("requestId")) {
1467            (Some(session_id), None) => {
1468                let mut scope = serde_json::Map::new();
1469                scope.insert("sessionId".into(), session_id);
1470                if let Some(tool_call_id) = fields.remove("toolCallId") {
1471                    scope.insert("toolCallId".into(), tool_call_id);
1472                }
1473                serde_json::from_value(scope.into()).map(Self::Session)
1474            }
1475            (None, Some(request_id)) => {
1476                serde_json::from_value(serde_json::json!({ "requestId": request_id }))
1477                    .map(Self::Request)
1478            }
1479            _ => {
1480                return Err(serde::de::Error::custom(
1481                    "elicitation scope must include exactly one of `sessionId` or `requestId`",
1482                ));
1483            }
1484        };
1485        scope.map_err(serde::de::Error::custom)
1486    }
1487}
1488
1489#[cfg(feature = "schemars")]
1490fn elicitation_scope_schema(schema: &mut Schema) {
1491    if let Some(variants) = schema.remove("anyOf") {
1492        schema.insert("oneOf".into(), variants);
1493    }
1494}
1495
1496/// Session-scoped elicitation, optionally tied to a specific tool call.
1497///
1498/// When `toolCallId` is set, the elicitation is tied to a specific tool call.
1499/// This is useful when an agent receives an elicitation from an MCP server
1500/// during a tool call and needs to redirect it to the user.
1501#[serde_as]
1502#[skip_serializing_none]
1503#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1505#[serde(rename_all = "camelCase")]
1506#[non_exhaustive]
1507pub struct ElicitationSessionScope {
1508    /// The session this elicitation is tied to.
1509    pub session_id: SessionId,
1510    /// Optional tool call within the session.
1511    ///
1512    /// Optional. Omitted and `null` are equivalent and mean the elicitation is scoped to the
1513    /// session without a specific tool call.
1514    #[serde_as(deserialize_as = "DefaultOnError")]
1515    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1516    #[serde(default)]
1517    pub tool_call_id: Option<ToolCallId>,
1518}
1519
1520impl ElicitationSessionScope {
1521    /// Builds [`ElicitationSessionScope`] with the required fields set; optional fields start unset or empty.
1522    #[must_use]
1523    pub fn new(session_id: impl Into<SessionId>) -> Self {
1524        Self {
1525            session_id: session_id.into(),
1526            tool_call_id: None,
1527        }
1528    }
1529
1530    /// Sets or clears the optional `toolCallId` field.
1531    #[must_use]
1532    pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1533        self.tool_call_id = tool_call_id.into_option();
1534        self
1535    }
1536}
1537
1538/// Request-scoped elicitation, tied to a specific JSON-RPC request outside of a session
1539/// (e.g., during auth/configuration phases before any session is started).
1540#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1542#[serde(rename_all = "camelCase")]
1543#[non_exhaustive]
1544pub struct ElicitationRequestScope {
1545    /// The pending Client-to-Agent request this elicitation is tied to.
1546    pub request_id: RequestId,
1547}
1548
1549impl ElicitationRequestScope {
1550    /// Builds [`ElicitationRequestScope`] with the required fields set; optional fields start unset or empty.
1551    #[must_use]
1552    pub fn new(request_id: impl Into<RequestId>) -> Self {
1553        Self {
1554            request_id: request_id.into(),
1555        }
1556    }
1557}
1558
1559impl From<ElicitationSessionScope> for ElicitationScope {
1560    fn from(scope: ElicitationSessionScope) -> Self {
1561        Self::Session(scope)
1562    }
1563}
1564
1565impl From<ElicitationRequestScope> for ElicitationScope {
1566    fn from(scope: ElicitationRequestScope) -> Self {
1567        Self::Request(scope)
1568    }
1569}
1570
1571/// Request from the agent to elicit structured user input.
1572///
1573/// The agent sends this to the client to request information from the user,
1574/// either via a form or by directing them to a URL.
1575/// Elicitations are tied to a session (optionally a tool call) or a request.
1576///
1577/// A session-scoped request follows the aggregate foreground-state contract:
1578/// agents SHOULD report `requires_action` when the whole foreground workflow is
1579/// blocked on user action. Once reported, they MUST report `running` when progress
1580/// resumes, or `idle` if work ends without resuming. Independent background requests
1581/// and request-scoped elicitations do not themselves change session state.
1582/// Accepting a URL does not mean the external interaction completed.
1583///
1584/// Agents SHOULD cancel pending elicitations no longer needed by cancelled work.
1585/// The request's scope alone does not establish that dependency.
1586///
1587/// See protocol docs: [Elicitation](https://agentclientprotocol.com/protocol/v2/elicitation)
1588#[serde_as]
1589#[skip_serializing_none]
1590#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1592#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1593#[serde(rename_all = "camelCase")]
1594#[non_exhaustive]
1595pub struct CreateElicitationRequest {
1596    /// The elicitation mode and its mode-specific fields.
1597    #[serde(flatten)]
1598    pub mode: ElicitationMode,
1599    /// A human-readable message describing what input is needed.
1600    pub message: String,
1601    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1602    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
1603    /// Implementations MUST NOT assign meaning to keys they do not recognize.
1604    ///
1605    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1606    #[serde_as(deserialize_as = "DefaultOnError")]
1607    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1608    #[serde(default)]
1609    #[serde(rename = "_meta")]
1610    pub meta: Option<Meta>,
1611}
1612
1613impl CreateElicitationRequest {
1614    /// Builds [`CreateElicitationRequest`] with the required request fields set; optional fields start unset or empty.
1615    #[must_use]
1616    pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1617        Self {
1618            mode: mode.into(),
1619            message: message.into(),
1620            meta: None,
1621        }
1622    }
1623
1624    /// Returns the scope this elicitation is tied to.
1625    #[must_use]
1626    pub fn scope(&self) -> &ElicitationScope {
1627        self.mode.scope()
1628    }
1629
1630    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1631    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
1632    /// do not recognize.
1633    ///
1634    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1635    ///
1636    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1637    #[must_use]
1638    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1639        self.meta = meta.into_option();
1640        self
1641    }
1642}
1643
1644/// The mode of elicitation, determining how user input is collected.
1645#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1646#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1647#[serde(tag = "mode", rename_all = "snake_case")]
1648#[non_exhaustive]
1649pub enum ElicitationMode {
1650    /// Form-based elicitation where the client renders a form from the provided schema.
1651    Form(ElicitationFormMode),
1652    /// URL-based elicitation where the client directs the user to a URL.
1653    Url(ElicitationUrlMode),
1654    /// Custom or future elicitation mode.
1655    ///
1656    /// Values beginning with `_` are reserved for implementation-specific
1657    /// extensions. Unknown values that do not begin with `_` are reserved for
1658    /// future ACP variants.
1659    ///
1660    /// Clients that do not understand this mode should preserve the raw payload
1661    /// when storing, replaying, proxying, or forwarding elicitation requests.
1662    /// They MUST NOT render it as a known elicitation mode.
1663    #[serde(untagged)]
1664    Other(OtherElicitationMode),
1665}
1666
1667/// Custom or future elicitation mode payload.
1668///
1669/// This preserves the unknown `mode` discriminator and the rest of the mode
1670/// object for clients that store, replay, proxy, or forward elicitation
1671/// requests.
1672#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1673#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1674#[cfg_attr(feature = "schemars", schemars(inline))]
1675#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1676#[serde(rename_all = "camelCase")]
1677#[non_exhaustive]
1678pub struct OtherElicitationMode {
1679    /// Custom or future elicitation mode.
1680    ///
1681    /// Values beginning with `_` are reserved for implementation-specific
1682    /// extensions. Unknown values that do not begin with `_` are reserved for
1683    /// future ACP variants.
1684    pub mode: String,
1685    /// The scope this elicitation is tied to.
1686    #[serde(flatten)]
1687    pub scope: ElicitationScope,
1688    /// Additional fields from the unknown elicitation mode payload.
1689    #[serde(flatten)]
1690    pub fields: BTreeMap<String, serde_json::Value>,
1691}
1692
1693impl OtherElicitationMode {
1694    /// Builds [`OtherElicitationMode`] from an unknown discriminator and preserves the remaining extension fields.
1695    #[must_use]
1696    pub fn new(
1697        mode: impl Into<String>,
1698        scope: impl Into<ElicitationScope>,
1699        mut fields: BTreeMap<String, serde_json::Value>,
1700    ) -> Self {
1701        fields.remove("mode");
1702        remove_elicitation_scope_fields(&mut fields);
1703        Self {
1704            mode: mode.into(),
1705            scope: scope.into(),
1706            fields,
1707        }
1708    }
1709}
1710
1711impl<'de> Deserialize<'de> for OtherElicitationMode {
1712    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1713    where
1714        D: serde::Deserializer<'de>,
1715    {
1716        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1717        let mode = fields
1718            .remove("mode")
1719            .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1720        let serde_json::Value::String(mode) = mode else {
1721            return Err(serde::de::Error::custom("`mode` must be a string"));
1722        };
1723
1724        if is_known_elicitation_mode(&mode) {
1725            return Err(serde::de::Error::custom(format!(
1726                "known elicitation mode `{mode}` did not match its schema"
1727            )));
1728        }
1729
1730        let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1731            fields.clone().into_iter().collect(),
1732        ))
1733        .map_err(serde::de::Error::custom)?;
1734        remove_elicitation_scope_fields(&mut fields);
1735
1736        Ok(Self {
1737            mode,
1738            scope,
1739            fields,
1740        })
1741    }
1742}
1743
1744const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1745
1746fn is_known_elicitation_mode(mode: &str) -> bool {
1747    KNOWN_ELICITATION_MODES.contains(&mode)
1748}
1749
1750fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1751    fields.remove("sessionId");
1752    fields.remove("toolCallId");
1753    fields.remove("requestId");
1754}
1755
1756#[cfg(feature = "schemars")]
1757fn other_elicitation_mode_schema(schema: &mut Schema) {
1758    super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1759}
1760
1761impl From<ElicitationFormMode> for ElicitationMode {
1762    fn from(mode: ElicitationFormMode) -> Self {
1763        Self::Form(mode)
1764    }
1765}
1766
1767impl From<ElicitationUrlMode> for ElicitationMode {
1768    fn from(mode: ElicitationUrlMode) -> Self {
1769        Self::Url(mode)
1770    }
1771}
1772
1773impl From<OtherElicitationMode> for ElicitationMode {
1774    fn from(mode: OtherElicitationMode) -> Self {
1775        Self::Other(mode)
1776    }
1777}
1778
1779impl ElicitationMode {
1780    /// Returns the scope this elicitation mode is tied to.
1781    #[must_use]
1782    pub fn scope(&self) -> &ElicitationScope {
1783        match self {
1784            Self::Form(f) => &f.scope,
1785            Self::Url(u) => &u.scope,
1786            Self::Other(other) => &other.scope,
1787        }
1788    }
1789}
1790
1791/// Form-based elicitation mode where the client renders a form from the provided schema.
1792#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1793#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1794#[serde(rename_all = "camelCase")]
1795#[non_exhaustive]
1796pub struct ElicitationFormMode {
1797    /// The scope this elicitation is tied to.
1798    #[serde(flatten)]
1799    pub scope: ElicitationScope,
1800    /// A JSON Schema describing the form fields to present to the user.
1801    pub requested_schema: ElicitationSchema,
1802}
1803
1804impl ElicitationFormMode {
1805    /// Builds [`ElicitationFormMode`] with the required fields set; optional fields start unset or empty.
1806    #[must_use]
1807    pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1808        Self {
1809            scope: scope.into(),
1810            requested_schema,
1811        }
1812    }
1813}
1814
1815/// URL-based elicitation mode where the client directs the user to a URL.
1816#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1817#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1818#[serde(rename_all = "camelCase")]
1819#[non_exhaustive]
1820pub struct ElicitationUrlMode {
1821    /// The scope this elicitation is tied to.
1822    #[serde(flatten)]
1823    pub scope: ElicitationScope,
1824    /// The unique identifier for this elicitation.
1825    pub elicitation_id: ElicitationId,
1826    /// The URL to direct the user to.
1827    #[cfg_attr(feature = "schemars", schemars(url))]
1828    pub url: String,
1829}
1830
1831impl ElicitationUrlMode {
1832    /// Builds [`ElicitationUrlMode`] with the required fields set; optional fields start unset or empty.
1833    #[must_use]
1834    pub fn new(
1835        scope: impl Into<ElicitationScope>,
1836        elicitation_id: impl Into<ElicitationId>,
1837        url: impl Into<String>,
1838    ) -> Self {
1839        Self {
1840            scope: scope.into(),
1841            elicitation_id: elicitation_id.into(),
1842            url: url.into(),
1843        }
1844    }
1845}
1846
1847/// Response from the client to an elicitation request.
1848#[serde_as]
1849#[skip_serializing_none]
1850#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1851#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1852#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1853#[serde(rename_all = "camelCase")]
1854#[non_exhaustive]
1855pub struct CreateElicitationResponse {
1856    /// The user's action in response to the elicitation.
1857    #[serde(flatten)]
1858    pub action: ElicitationAction,
1859    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1860    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
1861    /// Implementations MUST NOT assign meaning to keys they do not recognize.
1862    ///
1863    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1864    #[serde_as(deserialize_as = "DefaultOnError")]
1865    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1866    #[serde(default)]
1867    #[serde(rename = "_meta")]
1868    pub meta: Option<Meta>,
1869}
1870
1871impl CreateElicitationResponse {
1872    /// Builds [`CreateElicitationResponse`] with the required response fields set; optional fields start unset or empty.
1873    #[must_use]
1874    pub fn new(action: impl Into<ElicitationAction>) -> Self {
1875        Self {
1876            action: action.into(),
1877            meta: None,
1878        }
1879    }
1880
1881    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1882    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
1883    /// do not recognize.
1884    ///
1885    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1886    ///
1887    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
1888    #[must_use]
1889    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1890        self.meta = meta.into_option();
1891        self
1892    }
1893}
1894
1895/// The user's action in response to an elicitation.
1896#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1897#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1898#[serde(tag = "action", rename_all = "snake_case")]
1899#[non_exhaustive]
1900pub enum ElicitationAction {
1901    /// The user accepted and provided content.
1902    Accept(ElicitationAcceptAction),
1903    /// The user declined the elicitation.
1904    Decline,
1905    /// The elicitation was cancelled.
1906    Cancel,
1907    /// Custom or future elicitation action.
1908    ///
1909    /// Values beginning with `_` are reserved for implementation-specific
1910    /// extensions. Unknown values that do not begin with `_` are reserved for
1911    /// future ACP variants.
1912    ///
1913    /// Agents that do not understand this action should preserve the raw
1914    /// payload when storing, replaying, proxying, or forwarding elicitation
1915    /// responses, and otherwise MUST handle it as `cancel`.
1916    #[serde(untagged)]
1917    Other(OtherElicitationAction),
1918}
1919
1920/// Custom or future elicitation action payload.
1921///
1922/// This preserves the unknown `action` discriminator and the rest of the
1923/// response object for agents that store, replay, proxy, or forward elicitation
1924/// responses.
1925#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1926#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1927#[cfg_attr(feature = "schemars", schemars(inline))]
1928#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1929#[serde(rename_all = "camelCase")]
1930#[non_exhaustive]
1931pub struct OtherElicitationAction {
1932    /// Custom or future elicitation action.
1933    ///
1934    /// Values beginning with `_` are reserved for implementation-specific
1935    /// extensions. Unknown values that do not begin with `_` are reserved for
1936    /// future ACP variants.
1937    pub action: String,
1938    /// Additional fields from the unknown elicitation action payload.
1939    #[serde(flatten)]
1940    pub fields: BTreeMap<String, serde_json::Value>,
1941}
1942
1943impl OtherElicitationAction {
1944    /// Builds [`OtherElicitationAction`] from an unknown discriminator and preserves the remaining extension fields.
1945    #[must_use]
1946    pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1947        fields.remove("action");
1948        Self {
1949            action: action.into(),
1950            fields,
1951        }
1952    }
1953}
1954
1955impl<'de> Deserialize<'de> for OtherElicitationAction {
1956    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1957    where
1958        D: serde::Deserializer<'de>,
1959    {
1960        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1961        let action = fields
1962            .remove("action")
1963            .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1964        let serde_json::Value::String(action) = action else {
1965            return Err(serde::de::Error::custom("`action` must be a string"));
1966        };
1967
1968        if is_known_elicitation_action(&action) {
1969            return Err(serde::de::Error::custom(format!(
1970                "known elicitation action `{action}` did not match its schema"
1971            )));
1972        }
1973
1974        Ok(Self { action, fields })
1975    }
1976}
1977
1978const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1979
1980fn is_known_elicitation_action(action: &str) -> bool {
1981    KNOWN_ELICITATION_ACTIONS.contains(&action)
1982}
1983
1984#[cfg(feature = "schemars")]
1985fn other_elicitation_action_schema(schema: &mut Schema) {
1986    super::schema_util::reject_known_string_discriminators(
1987        schema,
1988        "action",
1989        KNOWN_ELICITATION_ACTIONS,
1990    );
1991}
1992
1993impl From<ElicitationAcceptAction> for ElicitationAction {
1994    fn from(action: ElicitationAcceptAction) -> Self {
1995        Self::Accept(action)
1996    }
1997}
1998
1999impl From<OtherElicitationAction> for ElicitationAction {
2000    fn from(action: OtherElicitationAction) -> Self {
2001        Self::Other(action)
2002    }
2003}
2004
2005/// The user accepted the elicitation and provided content.
2006#[serde_as]
2007#[skip_serializing_none]
2008#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2009#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2010#[serde(rename_all = "camelCase")]
2011#[non_exhaustive]
2012pub struct ElicitationAcceptAction {
2013    /// The user-provided content, if any, as an object matching the requested schema.
2014    ///
2015    /// Omitted and `null` are equivalent and mean no content is provided.
2016    #[serde(default)]
2017    pub content: Option<BTreeMap<String, ElicitationContentValue>>,
2018}
2019
2020impl ElicitationAcceptAction {
2021    /// Builds [`ElicitationAcceptAction`] with the required fields set; optional fields start unset or empty.
2022    #[must_use]
2023    pub fn new() -> Self {
2024        Self { content: None }
2025    }
2026
2027    /// The user-provided content as an object matching the requested schema.
2028    #[must_use]
2029    pub fn content(
2030        mut self,
2031        content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2032    ) -> Self {
2033        self.content = content.into_option();
2034        self
2035    }
2036}
2037
2038/// Allowed wire representations for `ElicitationContentValue`.
2039#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2040#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2041#[serde(untagged)]
2042#[non_exhaustive]
2043pub enum ElicitationContentValue {
2044    /// String value accepted in elicitation response content.
2045    String(String),
2046    /// Integer value accepted in elicitation response content.
2047    Integer(i64),
2048    /// Number value accepted in elicitation response content.
2049    Number(f64),
2050    /// Boolean value accepted in elicitation response content.
2051    Boolean(bool),
2052    /// String array value accepted in elicitation response content.
2053    StringArray(Vec<String>),
2054    /// Any other JSON value, such as the value of a custom or future property type.
2055    ///
2056    /// Preserved as sent. Agents validate it against the requested schema like any
2057    /// other value.
2058    Other(serde_json::Value),
2059}
2060
2061impl From<String> for ElicitationContentValue {
2062    fn from(value: String) -> Self {
2063        Self::String(value)
2064    }
2065}
2066
2067impl From<&str> for ElicitationContentValue {
2068    fn from(value: &str) -> Self {
2069        Self::String(value.to_string())
2070    }
2071}
2072
2073impl From<i64> for ElicitationContentValue {
2074    fn from(value: i64) -> Self {
2075        Self::Integer(value)
2076    }
2077}
2078
2079impl From<i32> for ElicitationContentValue {
2080    fn from(value: i32) -> Self {
2081        Self::Integer(i64::from(value))
2082    }
2083}
2084
2085impl From<f64> for ElicitationContentValue {
2086    fn from(value: f64) -> Self {
2087        Self::Number(value)
2088    }
2089}
2090
2091impl From<bool> for ElicitationContentValue {
2092    fn from(value: bool) -> Self {
2093        Self::Boolean(value)
2094    }
2095}
2096
2097impl From<Vec<String>> for ElicitationContentValue {
2098    fn from(value: Vec<String>) -> Self {
2099        Self::StringArray(value)
2100    }
2101}
2102
2103impl From<Vec<&str>> for ElicitationContentValue {
2104    fn from(value: Vec<&str>) -> Self {
2105        Self::StringArray(value.into_iter().map(str::to_string).collect())
2106    }
2107}
2108
2109impl Default for ElicitationAcceptAction {
2110    fn default() -> Self {
2111        Self::new()
2112    }
2113}
2114
2115/// Notification sent by the agent when a URL-based elicitation is complete.
2116///
2117/// This notification is not a foreground state update.
2118#[serde_as]
2119#[skip_serializing_none]
2120#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2122#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2123#[serde(rename_all = "camelCase")]
2124#[non_exhaustive]
2125pub struct CompleteElicitationNotification {
2126    /// The ID of the elicitation that completed.
2127    pub elicitation_id: ElicitationId,
2128    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2129    /// metadata to their interactions. Omitted and `null` are equivalent and mean no metadata.
2130    /// Implementations MUST NOT assign meaning to keys they do not recognize.
2131    ///
2132    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
2133    #[serde_as(deserialize_as = "DefaultOnError")]
2134    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2135    #[serde(default)]
2136    #[serde(rename = "_meta")]
2137    pub meta: Option<Meta>,
2138}
2139
2140impl CompleteElicitationNotification {
2141    /// Builds [`CompleteElicitationNotification`] with the required notification fields set; optional fields start unset or empty.
2142    #[must_use]
2143    pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2144        Self {
2145            elicitation_id: elicitation_id.into(),
2146            meta: None,
2147        }
2148    }
2149
2150    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2151    /// metadata to their interactions. Implementations MUST NOT assign meaning to keys they
2152    /// do not recognize.
2153    ///
2154    /// Optional. Omitted and `null` are equivalent and mean no metadata.
2155    ///
2156    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)
2157    #[must_use]
2158    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2159        self.meta = meta.into_option();
2160        self
2161    }
2162}
2163
2164#[cfg(test)]
2165mod tests {
2166    use super::*;
2167    use serde_json::json;
2168
2169    #[test]
2170    fn content_values_accept_other_shapes() {
2171        let wire = json!({
2172            "name": "Ada",
2173            "age": 36,
2174            "ratio": 0.5,
2175            "agree": true,
2176            "tags": ["a", "b"],
2177            "_custom": { "lat": 1.0, "lng": 2.0 },
2178            "mixed": ["a", 1]
2179        });
2180        let content: BTreeMap<String, ElicitationContentValue> =
2181            serde_json::from_value(wire.clone()).unwrap();
2182        assert_eq!(
2183            content["name"],
2184            ElicitationContentValue::String("Ada".into())
2185        );
2186        assert_eq!(content["age"], ElicitationContentValue::Integer(36));
2187        assert_eq!(
2188            content["tags"],
2189            ElicitationContentValue::StringArray(vec!["a".into(), "b".into()])
2190        );
2191        assert_eq!(
2192            content["_custom"],
2193            ElicitationContentValue::Other(json!({ "lat": 1.0, "lng": 2.0 }))
2194        );
2195        assert_eq!(
2196            content["mixed"],
2197            ElicitationContentValue::Other(json!(["a", 1]))
2198        );
2199        assert_eq!(serde_json::to_value(&content).unwrap(), wire);
2200    }
2201
2202    #[test]
2203    fn form_mode_request_serialization() {
2204        let schema = ElicitationSchema::new().string("name", true);
2205        let req = CreateElicitationRequest::new(
2206            ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2207            "Please enter your name",
2208        );
2209
2210        let json = serde_json::to_value(&req).unwrap();
2211        assert_eq!(json["sessionId"], "sess_1");
2212        assert!(json.get("toolCallId").is_none());
2213        assert_eq!(json["mode"], "form");
2214        assert_eq!(json["message"], "Please enter your name");
2215        assert!(json["requestedSchema"].is_object());
2216        assert_eq!(json["requestedSchema"]["type"], "object");
2217        assert_eq!(
2218            json["requestedSchema"]["properties"]["name"]["type"],
2219            "string"
2220        );
2221
2222        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2223        assert_eq!(
2224            *roundtripped.scope(),
2225            ElicitationSessionScope::new("sess_1").into()
2226        );
2227        assert_eq!(roundtripped.message, "Please enter your name");
2228        assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2229    }
2230
2231    #[test]
2232    fn url_mode_request_serialization() {
2233        let req = CreateElicitationRequest::new(
2234            ElicitationUrlMode::new(
2235                ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2236                "elic_1",
2237                "https://example.com/auth",
2238            ),
2239            "Please authenticate",
2240        );
2241
2242        let json = serde_json::to_value(&req).unwrap();
2243        assert_eq!(json["sessionId"], "sess_2");
2244        assert_eq!(json["toolCallId"], "tc_1");
2245        assert_eq!(json["mode"], "url");
2246        assert_eq!(json["elicitationId"], "elic_1");
2247        assert_eq!(json["url"], "https://example.com/auth");
2248        assert_eq!(json["message"], "Please authenticate");
2249
2250        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2251        assert_eq!(
2252            *roundtripped.scope(),
2253            ElicitationSessionScope::new("sess_2")
2254                .tool_call_id("tc_1")
2255                .into()
2256        );
2257        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2258    }
2259
2260    #[test]
2261    fn response_accept_serialization() {
2262        let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2263            ElicitationAcceptAction::new().content(BTreeMap::from([(
2264                "name".to_string(),
2265                ElicitationContentValue::from("Alice"),
2266            )])),
2267        ));
2268
2269        let json = serde_json::to_value(&resp).unwrap();
2270        assert_eq!(json["action"], "accept");
2271        assert_eq!(json["content"]["name"], "Alice");
2272
2273        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2274        assert!(matches!(
2275            roundtripped.action,
2276            ElicitationAction::Accept(ElicitationAcceptAction {
2277                content: Some(_),
2278                ..
2279            })
2280        ));
2281    }
2282
2283    #[test]
2284    fn response_decline_serialization() {
2285        let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2286
2287        let json = serde_json::to_value(&resp).unwrap();
2288        assert_eq!(json["action"], "decline");
2289
2290        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2291        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2292    }
2293
2294    #[test]
2295    fn response_cancel_serialization() {
2296        let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2297
2298        let json = serde_json::to_value(&resp).unwrap();
2299        assert_eq!(json["action"], "cancel");
2300
2301        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2302        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2303    }
2304
2305    #[test]
2306    fn unknown_action_response_serialization() {
2307        let json = json!({
2308            "action": "_defer",
2309            "reason": "waiting",
2310            "retryAfterMs": 1000
2311        });
2312
2313        let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2314        let ElicitationAction::Other(other) = &resp.action else {
2315            panic!("expected unknown elicitation action");
2316        };
2317
2318        assert_eq!(other.action, "_defer");
2319        assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2320        assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2321        assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2322    }
2323
2324    #[test]
2325    fn unknown_action_does_not_hide_known_action() {
2326        assert!(
2327            serde_json::from_value::<OtherElicitationAction>(json!({
2328                "action": "accept",
2329                "content": {}
2330            }))
2331            .is_err()
2332        );
2333        assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2334    }
2335
2336    #[test]
2337    fn url_mode_request_scope_serialization() {
2338        let req = CreateElicitationRequest::new(
2339            ElicitationUrlMode::new(
2340                ElicitationRequestScope::new(RequestId::Number(42)),
2341                "elic_2",
2342                "https://example.com/setup",
2343            ),
2344            "Please complete setup",
2345        );
2346
2347        let json = serde_json::to_value(&req).unwrap();
2348        assert_eq!(json["requestId"], 42);
2349        assert!(json.get("sessionId").is_none());
2350        assert_eq!(json["mode"], "url");
2351        assert_eq!(json["elicitationId"], "elic_2");
2352        assert_eq!(json["url"], "https://example.com/setup");
2353        assert_eq!(json["message"], "Please complete setup");
2354
2355        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2356        assert_eq!(
2357            *roundtripped.scope(),
2358            ElicitationRequestScope::new(RequestId::Number(42)).into()
2359        );
2360        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2361    }
2362
2363    #[test]
2364    fn unknown_mode_request_serialization() {
2365        let json = json!({
2366            "requestId": 42,
2367            "mode": "_browser",
2368            "message": "Open a browser window",
2369            "target": "login"
2370        });
2371
2372        let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2373        let ElicitationMode::Other(other) = &req.mode else {
2374            panic!("expected unknown elicitation mode");
2375        };
2376
2377        assert_eq!(other.mode, "_browser");
2378        assert_eq!(
2379            other.scope,
2380            ElicitationRequestScope::new(RequestId::Number(42)).into()
2381        );
2382        assert_eq!(other.fields.get("target"), Some(&json!("login")));
2383        assert_eq!(
2384            *req.scope(),
2385            ElicitationRequestScope::new(RequestId::Number(42)).into()
2386        );
2387        assert_eq!(serde_json::to_value(&req).unwrap(), json);
2388    }
2389
2390    #[test]
2391    fn unknown_mode_does_not_hide_malformed_known_mode() {
2392        let missing_requested_schema = json!({
2393            "requestId": 42,
2394            "mode": "form",
2395            "message": "Enter your name"
2396        });
2397
2398        assert!(
2399            serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2400        );
2401        assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2402    }
2403
2404    #[test]
2405    fn scope_requires_exactly_one_non_null_id() {
2406        let request = |scope: serde_json::Value| {
2407            let mut json = json!({
2408                "mode": "form",
2409                "message": "Enter your name",
2410                "requestedSchema": { "type": "object", "properties": {} }
2411            });
2412            json.as_object_mut()
2413                .unwrap()
2414                .extend(scope.as_object().unwrap().clone());
2415            serde_json::from_value::<CreateElicitationRequest>(json)
2416        };
2417
2418        assert!(request(json!({ "sessionId": "sess_1", "requestId": 42 })).is_err());
2419        assert!(request(json!({})).is_err());
2420        assert!(request(json!({ "sessionId": 42, "requestId": 42 })).is_err());
2421        assert!(request(json!({ "sessionId": 42 })).is_err());
2422        assert_eq!(
2423            *request(json!({ "sessionId": "sess_1", "toolCallId": "call_1" }))
2424                .unwrap()
2425                .scope(),
2426            ElicitationSessionScope::new("sess_1")
2427                .tool_call_id("call_1")
2428                .into()
2429        );
2430        assert_eq!(
2431            *request(json!({ "requestId": "req_1" })).unwrap().scope(),
2432            ElicitationRequestScope::new("req_1".to_string()).into()
2433        );
2434    }
2435
2436    #[test]
2437    fn request_scope_request_serialization() {
2438        let req = CreateElicitationRequest::new(
2439            ElicitationFormMode::new(
2440                ElicitationRequestScope::new(RequestId::Number(99)),
2441                ElicitationSchema::new().string("workspace", true),
2442            ),
2443            "Enter workspace name",
2444        );
2445
2446        let json = serde_json::to_value(&req).unwrap();
2447        assert_eq!(json["requestId"], 99);
2448        assert!(json.get("sessionId").is_none());
2449
2450        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2451        assert_eq!(
2452            *roundtripped.scope(),
2453            ElicitationRequestScope::new(RequestId::Number(99)).into()
2454        );
2455    }
2456
2457    /// These tests verify that serialization through `ClientResponse` produces the
2458    /// correct flattened wire format and round-trips back via the concrete
2459    /// `CreateElicitationResponse` type.
2460    #[test]
2461    fn client_response_serialization_accept() {
2462        use crate::v2::ClientResponse;
2463
2464        let resp =
2465            ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2466                ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2467                    [("name".to_string(), ElicitationContentValue::from("Alice"))],
2468                ))),
2469            )));
2470        let json = serde_json::to_value(&resp).unwrap();
2471        assert_eq!(json["action"], "accept");
2472        assert_eq!(json["content"]["name"], "Alice");
2473
2474        // Round-trip back through the concrete type
2475        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2476        assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2477    }
2478
2479    #[test]
2480    fn client_response_serialization_decline() {
2481        use crate::v2::ClientResponse;
2482
2483        let resp = ClientResponse::CreateElicitationResponse(Box::new(
2484            CreateElicitationResponse::new(ElicitationAction::Decline),
2485        ));
2486        let json = serde_json::to_value(&resp).unwrap();
2487        assert_eq!(json["action"], "decline");
2488
2489        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2490        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2491    }
2492
2493    #[test]
2494    fn client_response_serialization_cancel() {
2495        use crate::v2::ClientResponse;
2496
2497        let resp = ClientResponse::CreateElicitationResponse(Box::new(
2498            CreateElicitationResponse::new(ElicitationAction::Cancel),
2499        ));
2500        let json = serde_json::to_value(&resp).unwrap();
2501        assert_eq!(json["action"], "cancel");
2502
2503        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2504        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2505    }
2506
2507    /// Guard against serde regressions with the `flatten` + internally-tagged combination.
2508    /// Extra fields in the JSON must not cause deserialization failures.
2509    #[test]
2510    fn request_tolerates_extra_fields() {
2511        let json = json!({
2512            "sessionId": "sess_1",
2513            "mode": "form",
2514            "message": "Enter your name",
2515            "requestedSchema": {
2516                "type": "object",
2517                "properties": {
2518                    "name": { "type": "string", "title": "Name" }
2519                },
2520                "required": ["name"]
2521            },
2522            "unknownStringField": "hello",
2523            "unknownNumberField": 42
2524        });
2525
2526        let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2527        assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2528        assert_eq!(req.message, "Enter your name");
2529        assert!(matches!(req.mode, ElicitationMode::Form(_)));
2530    }
2531
2532    #[test]
2533    fn completion_notification_serialization() {
2534        let notif = CompleteElicitationNotification::new("elic_1");
2535
2536        let json = serde_json::to_value(&notif).unwrap();
2537        assert_eq!(json["elicitationId"], "elic_1");
2538
2539        let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2540        assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2541    }
2542
2543    #[test]
2544    fn empty_capabilities_do_not_advertise_a_mode() {
2545        let caps = ElicitationCapabilities::new();
2546        assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2547        assert!(!caps.supports_form());
2548        assert!(!caps.supports_url());
2549
2550        for value in [
2551            json!({}),
2552            json!({ "form": null }),
2553            json!({ "url": null }),
2554            json!({ "form": null, "url": null }),
2555        ] {
2556            let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2557            assert!(!caps.supports_form());
2558            assert!(!caps.supports_url());
2559        }
2560    }
2561
2562    #[test]
2563    fn capabilities_form_only() {
2564        let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2565
2566        let json = serde_json::to_value(&caps).unwrap();
2567        assert!(json["form"].is_object());
2568        assert!(json.get("url").is_none());
2569
2570        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2571        assert!(roundtripped.form.is_some());
2572        assert!(roundtripped.url.is_none());
2573        assert!(roundtripped.supports_form());
2574        assert!(!roundtripped.supports_url());
2575    }
2576
2577    #[test]
2578    fn capabilities_url_only() {
2579        let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2580
2581        let json = serde_json::to_value(&caps).unwrap();
2582        assert!(json.get("form").is_none());
2583        assert!(json["url"].is_object());
2584
2585        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2586        assert!(roundtripped.form.is_none());
2587        assert!(roundtripped.url.is_some());
2588        assert!(!roundtripped.supports_form());
2589        assert!(roundtripped.supports_url());
2590    }
2591
2592    #[test]
2593    fn capabilities_both() {
2594        let caps = ElicitationCapabilities::new()
2595            .form(ElicitationFormCapabilities::new())
2596            .url(ElicitationUrlCapabilities::new());
2597
2598        let json = serde_json::to_value(&caps).unwrap();
2599        assert!(json["form"].is_object());
2600        assert!(json["url"].is_object());
2601
2602        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2603        assert!(roundtripped.form.is_some());
2604        assert!(roundtripped.url.is_some());
2605        assert!(roundtripped.supports_form());
2606        assert!(roundtripped.supports_url());
2607    }
2608
2609    #[test]
2610    fn schema_default_sets_object_type() {
2611        let schema = ElicitationSchema::default();
2612
2613        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2614        assert!(schema.properties.is_empty());
2615
2616        let json = serde_json::to_value(&schema).unwrap();
2617        assert_eq!(json["type"], "object");
2618    }
2619
2620    #[test]
2621    fn schema_root_requires_object_type_and_properties() {
2622        let schema: ElicitationSchema =
2623            serde_json::from_value(json!({ "type": "object", "properties": {} })).unwrap();
2624        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2625        for missing in [json!({ "properties": {} }), json!({ "type": "object" })] {
2626            assert!(
2627                serde_json::from_value::<ElicitationSchema>(missing.clone()).is_err(),
2628                "{missing}"
2629            );
2630        }
2631        for bad in [json!("array"), json!(42), json!(null)] {
2632            assert!(
2633                serde_json::from_value::<ElicitationSchema>(
2634                    json!({ "type": bad, "properties": {} })
2635                )
2636                .is_err(),
2637                "{bad}"
2638            );
2639        }
2640    }
2641
2642    #[test]
2643    fn schema_builder_serialization() {
2644        let schema = ElicitationSchema::new()
2645            .string("name", true)
2646            .email("email", true)
2647            .integer("age", 0, 150, true)
2648            .boolean("newsletter", false)
2649            .description("User registration");
2650
2651        let json = serde_json::to_value(&schema).unwrap();
2652        assert_eq!(json["type"], "object");
2653        assert_eq!(json["description"], "User registration");
2654        assert_eq!(json["properties"]["name"]["type"], "string");
2655        assert_eq!(json["properties"]["email"]["type"], "string");
2656        assert_eq!(json["properties"]["email"]["format"], "email");
2657        assert_eq!(json["properties"]["age"]["type"], "integer");
2658        assert_eq!(json["properties"]["age"]["minimum"], 0);
2659        assert_eq!(json["properties"]["age"]["maximum"], 150);
2660        assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2661
2662        let required = json["required"].as_array().unwrap();
2663        assert!(required.contains(&json!("name")));
2664        assert!(required.contains(&json!("email")));
2665        assert!(required.contains(&json!("age")));
2666        assert!(!required.contains(&json!("newsletter")));
2667
2668        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2669        assert_eq!(roundtripped.properties.len(), 4);
2670        assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2671    }
2672
2673    #[test]
2674    fn schema_string_enum_serialization() {
2675        let schema = ElicitationSchema::new().property(
2676            "color",
2677            StringPropertySchema::new().enum_values(vec![
2678                "red".into(),
2679                "green".into(),
2680                "blue".into(),
2681            ]),
2682            true,
2683        );
2684
2685        let json = serde_json::to_value(&schema).unwrap();
2686        assert_eq!(json["properties"]["color"]["type"], "string");
2687        let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2688        assert_eq!(enum_vals.len(), 3);
2689
2690        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2691        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2692        {
2693            assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2694        } else {
2695            panic!("expected String variant");
2696        }
2697    }
2698
2699    #[test]
2700    fn schema_multi_select_serialization() {
2701        let schema = ElicitationSchema::new().property(
2702            "colors",
2703            MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2704                .min_items(1)
2705                .max_items(3),
2706            false,
2707        );
2708
2709        let json = serde_json::to_value(&schema).unwrap();
2710        assert_eq!(json["properties"]["colors"]["type"], "array");
2711        assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2712        assert_eq!(json["properties"]["colors"]["minItems"], 1);
2713        assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2714
2715        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2716        let ElicitationPropertySchema::Array(array) =
2717            roundtripped.properties.get("colors").unwrap()
2718        else {
2719            panic!("expected Array variant");
2720        };
2721        let MultiSelectItems::String(items) = &array.items else {
2722            panic!("expected String multi-select items");
2723        };
2724        assert_eq!(items.values.len(), 3);
2725    }
2726
2727    #[test]
2728    fn multi_select_titled_items_keep_mcp_shape() {
2729        let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2730            "#ff0000", "Red",
2731        )]));
2732
2733        let json = serde_json::to_value(&items).unwrap();
2734        assert!(json.get("type").is_none());
2735        assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2736        assert_eq!(json["anyOf"][0]["title"], "Red");
2737
2738        let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2739        assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2740    }
2741
2742    #[test]
2743    fn multi_select_items_preserve_unknown_type() {
2744        let json = json!({
2745            "type": "_token",
2746            "format": "workspace",
2747            "anyOf": [
2748                { "const": "repo", "title": "Repository" }
2749            ]
2750        });
2751
2752        let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2753        let MultiSelectItems::Other(other) = &items else {
2754            panic!("expected unknown multi-select items");
2755        };
2756
2757        assert_eq!(other.type_, "_token");
2758        assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2759        assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2760        assert_eq!(serde_json::to_value(&items).unwrap(), json);
2761    }
2762
2763    #[test]
2764    fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2765        assert!(
2766            serde_json::from_value::<MultiSelectItems>(json!({
2767                "type": "string"
2768            }))
2769            .is_err()
2770        );
2771        assert!(
2772            serde_json::from_value::<OtherMultiSelectItems>(json!({
2773                "type": "string",
2774                "format": "workspace"
2775            }))
2776            .is_err()
2777        );
2778    }
2779
2780    #[test]
2781    fn property_schema_preserves_unknown_type() {
2782        let schema: ElicitationSchema = serde_json::from_value(json!({
2783            "type": "object",
2784            "properties": {
2785                "location": {
2786                    "type": "_location",
2787                    "title": "Location",
2788                    "precision": "city"
2789                }
2790            }
2791        }))
2792        .unwrap();
2793
2794        let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2795        else {
2796            panic!("expected unknown property schema");
2797        };
2798
2799        assert_eq!(unknown.type_, "_location");
2800        assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2801        assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2802        assert_eq!(
2803            serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2804            json!({
2805                "type": "_location",
2806                "title": "Location",
2807                "precision": "city"
2808            })
2809        );
2810    }
2811
2812    #[test]
2813    fn property_schema_unknown_does_not_hide_malformed_known_type() {
2814        assert!(
2815            serde_json::from_value::<ElicitationPropertySchema>(json!({
2816                "type": "array"
2817            }))
2818            .is_err()
2819        );
2820        assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2821    }
2822
2823    #[test]
2824    fn schema_titled_enum_serialization() {
2825        let schema = ElicitationSchema::new().property(
2826            "country",
2827            StringPropertySchema::new().one_of(vec![
2828                EnumOption::new("us", "United States").description("Use US English spelling."),
2829                EnumOption::new("uk", "United Kingdom"),
2830            ]),
2831            true,
2832        );
2833
2834        let json = serde_json::to_value(&schema).unwrap();
2835        assert_eq!(json["properties"]["country"]["type"], "string");
2836        let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2837        assert_eq!(one_of.len(), 2);
2838        assert_eq!(one_of[0]["const"], "us");
2839        assert_eq!(one_of[0]["title"], "United States");
2840        assert_eq!(one_of[0]["description"], "Use US English spelling.");
2841        assert!(one_of[1].get("description").is_none());
2842
2843        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2844        if let ElicitationPropertySchema::String(s) =
2845            roundtripped.properties.get("country").unwrap()
2846        {
2847            let one_of = s.one_of.as_ref().unwrap();
2848            assert_eq!(one_of.len(), 2);
2849            assert_eq!(
2850                one_of[0].description.as_deref(),
2851                Some("Use US English spelling.")
2852            );
2853            assert!(one_of[1].description.is_none());
2854        } else {
2855            panic!("expected String variant");
2856        }
2857    }
2858
2859    #[test]
2860    fn schema_number_property_serialization() {
2861        let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2862
2863        let json = serde_json::to_value(&schema).unwrap();
2864        assert_eq!(json["properties"]["rating"]["type"], "number");
2865        assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2866        assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2867
2868        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2869        if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2870        {
2871            assert_eq!(n.minimum, Some(0.0));
2872            assert_eq!(n.maximum, Some(5.0));
2873        } else {
2874            panic!("expected Number variant");
2875        }
2876    }
2877
2878    #[test]
2879    fn schema_string_format_serialization() {
2880        let schema = ElicitationSchema::new()
2881            .uri("website", true)
2882            .date("birthday", true)
2883            .date_time("updated_at", false);
2884
2885        let json = serde_json::to_value(&schema).unwrap();
2886        assert_eq!(json["properties"]["website"]["type"], "string");
2887        assert_eq!(json["properties"]["website"]["format"], "uri");
2888        assert_eq!(json["properties"]["birthday"]["type"], "string");
2889        assert_eq!(json["properties"]["birthday"]["format"], "date");
2890        assert_eq!(json["properties"]["updated_at"]["type"], "string");
2891        assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2892
2893        let required = json["required"].as_array().unwrap();
2894        assert!(required.contains(&json!("website")));
2895        assert!(required.contains(&json!("birthday")));
2896        assert!(!required.contains(&json!("updated_at")));
2897    }
2898
2899    #[test]
2900    fn schema_string_pattern_serialization() {
2901        let schema = ElicitationSchema::new().property(
2902            "name",
2903            StringPropertySchema::new()
2904                .min_length(1)
2905                .max_length(64)
2906                .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2907            true,
2908        );
2909
2910        let json = serde_json::to_value(&schema).unwrap();
2911        assert_eq!(json["properties"]["name"]["type"], "string");
2912        assert_eq!(
2913            json["properties"]["name"]["pattern"],
2914            "^[a-zA-Z_][a-zA-Z0-9_]*$"
2915        );
2916
2917        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2918        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2919            assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2920        } else {
2921            panic!("expected String variant");
2922        }
2923    }
2924
2925    #[test]
2926    fn schema_property_updates_required_state() {
2927        let schema = ElicitationSchema::new()
2928            .string("name", true)
2929            .email("name", false);
2930
2931        let json = serde_json::to_value(&schema).unwrap();
2932        assert!(json.get("required").is_none());
2933        assert_eq!(json["properties"]["name"]["format"], "email");
2934    }
2935
2936    #[test]
2937    fn titled_multi_select_items_reject_one_of() {
2938        let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2939            "oneOf": [
2940                {
2941                    "const": "red",
2942                    "title": "Red"
2943                }
2944            ]
2945        }))
2946        .unwrap_err();
2947
2948        assert!(err.to_string().contains("missing field `anyOf`"));
2949    }
2950
2951    #[test]
2952    fn response_accept_rejects_non_object_content() {
2953        assert!(
2954            serde_json::from_value::<CreateElicitationResponse>(json!({
2955                "action": "accept",
2956                "content": "Alice"
2957            }))
2958            .is_err()
2959        );
2960    }
2961
2962    #[test]
2963    fn response_accept_treats_null_and_omitted_content_equally() {
2964        for value in [
2965            json!({ "action": "accept" }),
2966            json!({
2967                "action": "accept",
2968                "content": null
2969            }),
2970        ] {
2971            let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2972            let ElicitationAction::Accept(accept) = response.action else {
2973                panic!("expected accept action");
2974            };
2975            assert!(accept.content.is_none());
2976        }
2977    }
2978
2979    #[test]
2980    fn response_accept_preserves_nested_object_content() {
2981        let wire = json!({
2982            "action": "accept",
2983            "content": {
2984                "profile": {
2985                    "name": "Alice"
2986                }
2987            }
2988        });
2989        let response: CreateElicitationResponse = serde_json::from_value(wire.clone()).unwrap();
2990        assert_eq!(serde_json::to_value(&response).unwrap(), wire);
2991    }
2992
2993    #[test]
2994    fn response_accept_allows_primitive_and_string_array_content() {
2995        let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2996            ElicitationAcceptAction::new().content(BTreeMap::from([
2997                ("name".to_string(), ElicitationContentValue::from("Alice")),
2998                ("age".to_string(), ElicitationContentValue::from(30_i32)),
2999                ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
3000                (
3001                    "subscribed".to_string(),
3002                    ElicitationContentValue::from(true),
3003                ),
3004                (
3005                    "tags".to_string(),
3006                    ElicitationContentValue::from(vec!["rust", "acp"]),
3007                ),
3008            ])),
3009        ));
3010
3011        let json = serde_json::to_value(&response).unwrap();
3012        assert_eq!(json["action"], "accept");
3013        assert_eq!(json["content"]["name"], "Alice");
3014        assert_eq!(json["content"]["age"], 30);
3015        assert_eq!(json["content"]["score"], 9.5);
3016        assert_eq!(json["content"]["subscribed"], true);
3017        assert_eq!(json["content"]["tags"][0], "rust");
3018        assert_eq!(json["content"]["tags"][1], "acp");
3019    }
3020}