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