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, 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#[serde_as]
1563#[skip_serializing_none]
1564#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1565#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1566#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1567#[serde(rename_all = "camelCase")]
1568#[non_exhaustive]
1569pub struct CreateElicitationRequest {
1570    /// The elicitation mode and its mode-specific fields.
1571    #[serde(flatten)]
1572    pub mode: ElicitationMode,
1573    /// A human-readable message describing what input is needed.
1574    pub message: String,
1575    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1576    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1577    /// these keys.
1578    ///
1579    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1580    ///
1581    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1582    #[serde_as(deserialize_as = "DefaultOnError")]
1583    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1584    #[serde(default)]
1585    #[serde(rename = "_meta")]
1586    pub meta: Option<Meta>,
1587}
1588
1589impl CreateElicitationRequest {
1590    /// Builds [`CreateElicitationRequest`] with the required request fields set; optional fields start unset or empty.
1591    #[must_use]
1592    pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1593        Self {
1594            mode: mode.into(),
1595            message: message.into(),
1596            meta: None,
1597        }
1598    }
1599
1600    /// Returns the scope this elicitation is tied to.
1601    #[must_use]
1602    pub fn scope(&self) -> &ElicitationScope {
1603        self.mode.scope()
1604    }
1605
1606    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1607    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1608    /// these keys.
1609    ///
1610    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1611    ///
1612    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1613    #[must_use]
1614    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1615        self.meta = meta.into_option();
1616        self
1617    }
1618}
1619
1620/// The mode of elicitation, determining how user input is collected.
1621#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1622#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1623#[serde(tag = "mode", rename_all = "snake_case")]
1624#[non_exhaustive]
1625pub enum ElicitationMode {
1626    /// Form-based elicitation where the client renders a form from the provided schema.
1627    Form(ElicitationFormMode),
1628    /// URL-based elicitation where the client directs the user to a URL.
1629    Url(ElicitationUrlMode),
1630    /// Custom or future elicitation mode.
1631    ///
1632    /// Values beginning with `_` are reserved for implementation-specific
1633    /// extensions. Unknown values that do not begin with `_` are reserved for
1634    /// future ACP variants.
1635    ///
1636    /// Clients that do not understand this mode should preserve the raw payload
1637    /// when storing, replaying, proxying, or forwarding elicitation requests.
1638    /// They MUST NOT render it as a known elicitation mode.
1639    #[serde(untagged)]
1640    Other(OtherElicitationMode),
1641}
1642
1643/// Custom or future elicitation mode payload.
1644///
1645/// This preserves the unknown `mode` discriminator and the rest of the mode
1646/// object for clients that store, replay, proxy, or forward elicitation
1647/// requests.
1648#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1649#[derive(Debug, Clone, Serialize, PartialEq)]
1650#[cfg_attr(feature = "schemars", schemars(inline))]
1651#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1652#[serde(rename_all = "camelCase")]
1653#[non_exhaustive]
1654pub struct OtherElicitationMode {
1655    /// Custom or future elicitation mode.
1656    ///
1657    /// Values beginning with `_` are reserved for implementation-specific
1658    /// extensions. Unknown values that do not begin with `_` are reserved for
1659    /// future ACP variants.
1660    pub mode: String,
1661    /// The scope this elicitation is tied to.
1662    #[serde(flatten)]
1663    pub scope: ElicitationScope,
1664    /// Additional fields from the unknown elicitation mode payload.
1665    #[serde(flatten)]
1666    pub fields: BTreeMap<String, serde_json::Value>,
1667}
1668
1669impl OtherElicitationMode {
1670    /// Builds [`OtherElicitationMode`] from an unknown discriminator and preserves the remaining extension fields.
1671    #[must_use]
1672    pub fn new(
1673        mode: impl Into<String>,
1674        scope: impl Into<ElicitationScope>,
1675        mut fields: BTreeMap<String, serde_json::Value>,
1676    ) -> Self {
1677        fields.remove("mode");
1678        remove_elicitation_scope_fields(&mut fields);
1679        Self {
1680            mode: mode.into(),
1681            scope: scope.into(),
1682            fields,
1683        }
1684    }
1685}
1686
1687impl<'de> Deserialize<'de> for OtherElicitationMode {
1688    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1689    where
1690        D: serde::Deserializer<'de>,
1691    {
1692        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1693        let mode = fields
1694            .remove("mode")
1695            .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1696        let serde_json::Value::String(mode) = mode else {
1697            return Err(serde::de::Error::custom("`mode` must be a string"));
1698        };
1699
1700        if is_known_elicitation_mode(&mode) {
1701            return Err(serde::de::Error::custom(format!(
1702                "known elicitation mode `{mode}` did not match its schema"
1703            )));
1704        }
1705
1706        let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1707            fields.clone().into_iter().collect(),
1708        ))
1709        .map_err(serde::de::Error::custom)?;
1710        remove_elicitation_scope_fields(&mut fields);
1711
1712        Ok(Self {
1713            mode,
1714            scope,
1715            fields,
1716        })
1717    }
1718}
1719
1720const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1721
1722fn is_known_elicitation_mode(mode: &str) -> bool {
1723    KNOWN_ELICITATION_MODES.contains(&mode)
1724}
1725
1726fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1727    fields.remove("sessionId");
1728    fields.remove("toolCallId");
1729    fields.remove("requestId");
1730}
1731
1732#[cfg(feature = "schemars")]
1733fn other_elicitation_mode_schema(schema: &mut Schema) {
1734    super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1735}
1736
1737impl From<ElicitationFormMode> for ElicitationMode {
1738    fn from(mode: ElicitationFormMode) -> Self {
1739        Self::Form(mode)
1740    }
1741}
1742
1743impl From<ElicitationUrlMode> for ElicitationMode {
1744    fn from(mode: ElicitationUrlMode) -> Self {
1745        Self::Url(mode)
1746    }
1747}
1748
1749impl From<OtherElicitationMode> for ElicitationMode {
1750    fn from(mode: OtherElicitationMode) -> Self {
1751        Self::Other(mode)
1752    }
1753}
1754
1755impl ElicitationMode {
1756    /// Returns the scope this elicitation mode is tied to.
1757    #[must_use]
1758    pub fn scope(&self) -> &ElicitationScope {
1759        match self {
1760            Self::Form(f) => &f.scope,
1761            Self::Url(u) => &u.scope,
1762            Self::Other(other) => &other.scope,
1763        }
1764    }
1765}
1766
1767/// Form-based elicitation mode where the client renders a form from the provided schema.
1768#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1769#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1770#[serde(rename_all = "camelCase")]
1771#[non_exhaustive]
1772pub struct ElicitationFormMode {
1773    /// The scope this elicitation is tied to.
1774    #[serde(flatten)]
1775    pub scope: ElicitationScope,
1776    /// A JSON Schema describing the form fields to present to the user.
1777    pub requested_schema: ElicitationSchema,
1778}
1779
1780impl ElicitationFormMode {
1781    /// Builds [`ElicitationFormMode`] with the required fields set; optional fields start unset or empty.
1782    #[must_use]
1783    pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1784        Self {
1785            scope: scope.into(),
1786            requested_schema,
1787        }
1788    }
1789}
1790
1791/// URL-based elicitation mode where the client directs the user to a URL.
1792#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1793#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1794#[serde(rename_all = "camelCase")]
1795#[non_exhaustive]
1796pub struct ElicitationUrlMode {
1797    /// The scope this elicitation is tied to.
1798    #[serde(flatten)]
1799    pub scope: ElicitationScope,
1800    /// The unique identifier for this elicitation.
1801    pub elicitation_id: ElicitationId,
1802    /// The URL to direct the user to.
1803    #[cfg_attr(feature = "schemars", schemars(url))]
1804    pub url: String,
1805}
1806
1807impl ElicitationUrlMode {
1808    /// Builds [`ElicitationUrlMode`] with the required fields set; optional fields start unset or empty.
1809    #[must_use]
1810    pub fn new(
1811        scope: impl Into<ElicitationScope>,
1812        elicitation_id: impl Into<ElicitationId>,
1813        url: impl Into<String>,
1814    ) -> Self {
1815        Self {
1816            scope: scope.into(),
1817            elicitation_id: elicitation_id.into(),
1818            url: url.into(),
1819        }
1820    }
1821}
1822
1823/// Response from the client to an elicitation request.
1824#[serde_as]
1825#[skip_serializing_none]
1826#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1827#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1828#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1829#[serde(rename_all = "camelCase")]
1830#[non_exhaustive]
1831pub struct CreateElicitationResponse {
1832    /// The user's action in response to the elicitation.
1833    #[serde(flatten)]
1834    pub action: ElicitationAction,
1835    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1836    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1837    /// these keys.
1838    ///
1839    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1840    ///
1841    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1842    #[serde_as(deserialize_as = "DefaultOnError")]
1843    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1844    #[serde(default)]
1845    #[serde(rename = "_meta")]
1846    pub meta: Option<Meta>,
1847}
1848
1849impl CreateElicitationResponse {
1850    /// Builds [`CreateElicitationResponse`] with the required response fields set; optional fields start unset or empty.
1851    #[must_use]
1852    pub fn new(action: impl Into<ElicitationAction>) -> Self {
1853        Self {
1854            action: action.into(),
1855            meta: None,
1856        }
1857    }
1858
1859    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1860    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1861    /// these keys.
1862    ///
1863    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1864    ///
1865    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1866    #[must_use]
1867    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1868        self.meta = meta.into_option();
1869        self
1870    }
1871}
1872
1873/// The user's action in response to an elicitation.
1874#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1875#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1876#[serde(tag = "action", rename_all = "snake_case")]
1877#[non_exhaustive]
1878pub enum ElicitationAction {
1879    /// The user accepted and provided content.
1880    Accept(ElicitationAcceptAction),
1881    /// The user declined the elicitation.
1882    Decline,
1883    /// The elicitation was cancelled.
1884    Cancel,
1885    /// Custom or future elicitation action.
1886    ///
1887    /// Values beginning with `_` are reserved for implementation-specific
1888    /// extensions. Unknown values that do not begin with `_` are reserved for
1889    /// future ACP variants.
1890    ///
1891    /// Agents that do not understand this action should preserve the raw
1892    /// payload when storing, replaying, proxying, or forwarding elicitation
1893    /// responses. They MUST NOT treat it as a known elicitation action.
1894    #[serde(untagged)]
1895    Other(OtherElicitationAction),
1896}
1897
1898/// Custom or future elicitation action payload.
1899///
1900/// This preserves the unknown `action` discriminator and the rest of the
1901/// response object for agents that store, replay, proxy, or forward elicitation
1902/// responses.
1903#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1904#[derive(Debug, Clone, Serialize, PartialEq)]
1905#[cfg_attr(feature = "schemars", schemars(inline))]
1906#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1907#[serde(rename_all = "camelCase")]
1908#[non_exhaustive]
1909pub struct OtherElicitationAction {
1910    /// Custom or future elicitation action.
1911    ///
1912    /// Values beginning with `_` are reserved for implementation-specific
1913    /// extensions. Unknown values that do not begin with `_` are reserved for
1914    /// future ACP variants.
1915    pub action: String,
1916    /// Additional fields from the unknown elicitation action payload.
1917    #[serde(flatten)]
1918    pub fields: BTreeMap<String, serde_json::Value>,
1919}
1920
1921impl OtherElicitationAction {
1922    /// Builds [`OtherElicitationAction`] from an unknown discriminator and preserves the remaining extension fields.
1923    #[must_use]
1924    pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1925        fields.remove("action");
1926        Self {
1927            action: action.into(),
1928            fields,
1929        }
1930    }
1931}
1932
1933impl<'de> Deserialize<'de> for OtherElicitationAction {
1934    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1935    where
1936        D: serde::Deserializer<'de>,
1937    {
1938        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1939        let action = fields
1940            .remove("action")
1941            .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1942        let serde_json::Value::String(action) = action else {
1943            return Err(serde::de::Error::custom("`action` must be a string"));
1944        };
1945
1946        if is_known_elicitation_action(&action) {
1947            return Err(serde::de::Error::custom(format!(
1948                "known elicitation action `{action}` did not match its schema"
1949            )));
1950        }
1951
1952        Ok(Self { action, fields })
1953    }
1954}
1955
1956const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1957
1958fn is_known_elicitation_action(action: &str) -> bool {
1959    KNOWN_ELICITATION_ACTIONS.contains(&action)
1960}
1961
1962#[cfg(feature = "schemars")]
1963fn other_elicitation_action_schema(schema: &mut Schema) {
1964    super::schema_util::reject_known_string_discriminators(
1965        schema,
1966        "action",
1967        KNOWN_ELICITATION_ACTIONS,
1968    );
1969}
1970
1971impl From<ElicitationAcceptAction> for ElicitationAction {
1972    fn from(action: ElicitationAcceptAction) -> Self {
1973        Self::Accept(action)
1974    }
1975}
1976
1977impl From<OtherElicitationAction> for ElicitationAction {
1978    fn from(action: OtherElicitationAction) -> Self {
1979        Self::Other(action)
1980    }
1981}
1982
1983/// The user accepted the elicitation and provided content.
1984#[serde_as]
1985#[skip_serializing_none]
1986#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1987#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1988#[serde(rename_all = "camelCase")]
1989#[non_exhaustive]
1990pub struct ElicitationAcceptAction {
1991    /// The user-provided content, if any, as an object matching the requested schema.
1992    #[serde(default)]
1993    pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1994}
1995
1996impl ElicitationAcceptAction {
1997    /// Builds [`ElicitationAcceptAction`] with the required fields set; optional fields start unset or empty.
1998    #[must_use]
1999    pub fn new() -> Self {
2000        Self { content: None }
2001    }
2002
2003    /// The user-provided content as an object matching the requested schema.
2004    #[must_use]
2005    pub fn content(
2006        mut self,
2007        content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2008    ) -> Self {
2009        self.content = content.into_option();
2010        self
2011    }
2012}
2013
2014/// Allowed wire representations for [`ElicitationContentValue`].
2015#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2016#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2017#[serde(untagged)]
2018#[non_exhaustive]
2019pub enum ElicitationContentValue {
2020    /// String value accepted in elicitation response content.
2021    String(String),
2022    /// Integer value accepted in elicitation response content.
2023    Integer(i64),
2024    /// Number value accepted in elicitation response content.
2025    Number(f64),
2026    /// Boolean value accepted in elicitation response content.
2027    Boolean(bool),
2028    /// String array value accepted in elicitation response content.
2029    StringArray(Vec<String>),
2030}
2031
2032impl From<String> for ElicitationContentValue {
2033    fn from(value: String) -> Self {
2034        Self::String(value)
2035    }
2036}
2037
2038impl From<&str> for ElicitationContentValue {
2039    fn from(value: &str) -> Self {
2040        Self::String(value.to_string())
2041    }
2042}
2043
2044impl From<i64> for ElicitationContentValue {
2045    fn from(value: i64) -> Self {
2046        Self::Integer(value)
2047    }
2048}
2049
2050impl From<i32> for ElicitationContentValue {
2051    fn from(value: i32) -> Self {
2052        Self::Integer(i64::from(value))
2053    }
2054}
2055
2056impl From<f64> for ElicitationContentValue {
2057    fn from(value: f64) -> Self {
2058        Self::Number(value)
2059    }
2060}
2061
2062impl From<bool> for ElicitationContentValue {
2063    fn from(value: bool) -> Self {
2064        Self::Boolean(value)
2065    }
2066}
2067
2068impl From<Vec<String>> for ElicitationContentValue {
2069    fn from(value: Vec<String>) -> Self {
2070        Self::StringArray(value)
2071    }
2072}
2073
2074impl From<Vec<&str>> for ElicitationContentValue {
2075    fn from(value: Vec<&str>) -> Self {
2076        Self::StringArray(value.into_iter().map(str::to_string).collect())
2077    }
2078}
2079
2080impl Default for ElicitationAcceptAction {
2081    fn default() -> Self {
2082        Self::new()
2083    }
2084}
2085
2086/// Notification sent by the agent when a URL-based elicitation is complete.
2087#[serde_as]
2088#[skip_serializing_none]
2089#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2090#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2091#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2092#[serde(rename_all = "camelCase")]
2093#[non_exhaustive]
2094pub struct CompleteElicitationNotification {
2095    /// The ID of the elicitation that completed.
2096    pub elicitation_id: ElicitationId,
2097    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2098    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2099    /// these keys.
2100    ///
2101    /// Optional. Omitted and `null` are equivalent and mean no metadata.
2102    ///
2103    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2104    #[serde_as(deserialize_as = "DefaultOnError")]
2105    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2106    #[serde(default)]
2107    #[serde(rename = "_meta")]
2108    pub meta: Option<Meta>,
2109}
2110
2111impl CompleteElicitationNotification {
2112    /// Builds [`CompleteElicitationNotification`] with the required notification fields set; optional fields start unset or empty.
2113    #[must_use]
2114    pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2115        Self {
2116            elicitation_id: elicitation_id.into(),
2117            meta: None,
2118        }
2119    }
2120
2121    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2122    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2123    /// these keys.
2124    ///
2125    /// Optional. Omitted and `null` are equivalent and mean no metadata.
2126    ///
2127    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2128    #[must_use]
2129    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2130        self.meta = meta.into_option();
2131        self
2132    }
2133}
2134
2135#[cfg(test)]
2136mod tests {
2137    use super::*;
2138    use serde_json::json;
2139
2140    #[test]
2141    fn form_mode_request_serialization() {
2142        let schema = ElicitationSchema::new().string("name", true);
2143        let req = CreateElicitationRequest::new(
2144            ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2145            "Please enter your name",
2146        );
2147
2148        let json = serde_json::to_value(&req).unwrap();
2149        assert_eq!(json["sessionId"], "sess_1");
2150        assert!(json.get("toolCallId").is_none());
2151        assert_eq!(json["mode"], "form");
2152        assert_eq!(json["message"], "Please enter your name");
2153        assert!(json["requestedSchema"].is_object());
2154        assert_eq!(json["requestedSchema"]["type"], "object");
2155        assert_eq!(
2156            json["requestedSchema"]["properties"]["name"]["type"],
2157            "string"
2158        );
2159
2160        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2161        assert_eq!(
2162            *roundtripped.scope(),
2163            ElicitationSessionScope::new("sess_1").into()
2164        );
2165        assert_eq!(roundtripped.message, "Please enter your name");
2166        assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2167    }
2168
2169    #[test]
2170    fn url_mode_request_serialization() {
2171        let req = CreateElicitationRequest::new(
2172            ElicitationUrlMode::new(
2173                ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2174                "elic_1",
2175                "https://example.com/auth",
2176            ),
2177            "Please authenticate",
2178        );
2179
2180        let json = serde_json::to_value(&req).unwrap();
2181        assert_eq!(json["sessionId"], "sess_2");
2182        assert_eq!(json["toolCallId"], "tc_1");
2183        assert_eq!(json["mode"], "url");
2184        assert_eq!(json["elicitationId"], "elic_1");
2185        assert_eq!(json["url"], "https://example.com/auth");
2186        assert_eq!(json["message"], "Please authenticate");
2187
2188        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2189        assert_eq!(
2190            *roundtripped.scope(),
2191            ElicitationSessionScope::new("sess_2")
2192                .tool_call_id("tc_1")
2193                .into()
2194        );
2195        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2196    }
2197
2198    #[test]
2199    fn response_accept_serialization() {
2200        let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2201            ElicitationAcceptAction::new().content(BTreeMap::from([(
2202                "name".to_string(),
2203                ElicitationContentValue::from("Alice"),
2204            )])),
2205        ));
2206
2207        let json = serde_json::to_value(&resp).unwrap();
2208        assert_eq!(json["action"], "accept");
2209        assert_eq!(json["content"]["name"], "Alice");
2210
2211        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2212        assert!(matches!(
2213            roundtripped.action,
2214            ElicitationAction::Accept(ElicitationAcceptAction {
2215                content: Some(_),
2216                ..
2217            })
2218        ));
2219    }
2220
2221    #[test]
2222    fn response_decline_serialization() {
2223        let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2224
2225        let json = serde_json::to_value(&resp).unwrap();
2226        assert_eq!(json["action"], "decline");
2227
2228        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2229        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2230    }
2231
2232    #[test]
2233    fn response_cancel_serialization() {
2234        let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2235
2236        let json = serde_json::to_value(&resp).unwrap();
2237        assert_eq!(json["action"], "cancel");
2238
2239        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2240        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2241    }
2242
2243    #[test]
2244    fn unknown_action_response_serialization() {
2245        let json = json!({
2246            "action": "_defer",
2247            "reason": "waiting",
2248            "retryAfterMs": 1000
2249        });
2250
2251        let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2252        let ElicitationAction::Other(other) = &resp.action else {
2253            panic!("expected unknown elicitation action");
2254        };
2255
2256        assert_eq!(other.action, "_defer");
2257        assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2258        assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2259        assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2260    }
2261
2262    #[test]
2263    fn unknown_action_does_not_hide_known_action() {
2264        assert!(
2265            serde_json::from_value::<OtherElicitationAction>(json!({
2266                "action": "accept",
2267                "content": {}
2268            }))
2269            .is_err()
2270        );
2271        assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2272    }
2273
2274    #[test]
2275    fn url_mode_request_scope_serialization() {
2276        let req = CreateElicitationRequest::new(
2277            ElicitationUrlMode::new(
2278                ElicitationRequestScope::new(RequestId::Number(42)),
2279                "elic_2",
2280                "https://example.com/setup",
2281            ),
2282            "Please complete setup",
2283        );
2284
2285        let json = serde_json::to_value(&req).unwrap();
2286        assert_eq!(json["requestId"], 42);
2287        assert!(json.get("sessionId").is_none());
2288        assert_eq!(json["mode"], "url");
2289        assert_eq!(json["elicitationId"], "elic_2");
2290        assert_eq!(json["url"], "https://example.com/setup");
2291        assert_eq!(json["message"], "Please complete setup");
2292
2293        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2294        assert_eq!(
2295            *roundtripped.scope(),
2296            ElicitationRequestScope::new(RequestId::Number(42)).into()
2297        );
2298        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2299    }
2300
2301    #[test]
2302    fn unknown_mode_request_serialization() {
2303        let json = json!({
2304            "requestId": 42,
2305            "mode": "_browser",
2306            "message": "Open a browser window",
2307            "target": "login"
2308        });
2309
2310        let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2311        let ElicitationMode::Other(other) = &req.mode else {
2312            panic!("expected unknown elicitation mode");
2313        };
2314
2315        assert_eq!(other.mode, "_browser");
2316        assert_eq!(
2317            other.scope,
2318            ElicitationRequestScope::new(RequestId::Number(42)).into()
2319        );
2320        assert_eq!(other.fields.get("target"), Some(&json!("login")));
2321        assert_eq!(
2322            *req.scope(),
2323            ElicitationRequestScope::new(RequestId::Number(42)).into()
2324        );
2325        assert_eq!(serde_json::to_value(&req).unwrap(), json);
2326    }
2327
2328    #[test]
2329    fn unknown_mode_does_not_hide_malformed_known_mode() {
2330        let missing_requested_schema = json!({
2331            "requestId": 42,
2332            "mode": "form",
2333            "message": "Enter your name"
2334        });
2335
2336        assert!(
2337            serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2338        );
2339        assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2340    }
2341
2342    #[test]
2343    fn request_scope_request_serialization() {
2344        let req = CreateElicitationRequest::new(
2345            ElicitationFormMode::new(
2346                ElicitationRequestScope::new(RequestId::Number(99)),
2347                ElicitationSchema::new().string("workspace", true),
2348            ),
2349            "Enter workspace name",
2350        );
2351
2352        let json = serde_json::to_value(&req).unwrap();
2353        assert_eq!(json["requestId"], 99);
2354        assert!(json.get("sessionId").is_none());
2355
2356        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2357        assert_eq!(
2358            *roundtripped.scope(),
2359            ElicitationRequestScope::new(RequestId::Number(99)).into()
2360        );
2361    }
2362
2363    /// These tests verify that serialization through `ClientResponse` produces the
2364    /// correct flattened wire format and round-trips back via the concrete
2365    /// `CreateElicitationResponse` type.
2366    #[test]
2367    fn client_response_serialization_accept() {
2368        use crate::v2::ClientResponse;
2369
2370        let resp =
2371            ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2372                ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2373                    [("name".to_string(), ElicitationContentValue::from("Alice"))],
2374                ))),
2375            )));
2376        let json = serde_json::to_value(&resp).unwrap();
2377        assert_eq!(json["action"], "accept");
2378        assert_eq!(json["content"]["name"], "Alice");
2379
2380        // Round-trip back through the concrete type
2381        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2382        assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2383    }
2384
2385    #[test]
2386    fn client_response_serialization_decline() {
2387        use crate::v2::ClientResponse;
2388
2389        let resp = ClientResponse::CreateElicitationResponse(Box::new(
2390            CreateElicitationResponse::new(ElicitationAction::Decline),
2391        ));
2392        let json = serde_json::to_value(&resp).unwrap();
2393        assert_eq!(json["action"], "decline");
2394
2395        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2396        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2397    }
2398
2399    #[test]
2400    fn client_response_serialization_cancel() {
2401        use crate::v2::ClientResponse;
2402
2403        let resp = ClientResponse::CreateElicitationResponse(Box::new(
2404            CreateElicitationResponse::new(ElicitationAction::Cancel),
2405        ));
2406        let json = serde_json::to_value(&resp).unwrap();
2407        assert_eq!(json["action"], "cancel");
2408
2409        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2410        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2411    }
2412
2413    /// Guard against serde regressions with the `flatten` + internally-tagged combination.
2414    /// Extra fields in the JSON must not cause deserialization failures.
2415    #[test]
2416    fn request_tolerates_extra_fields() {
2417        let json = json!({
2418            "sessionId": "sess_1",
2419            "mode": "form",
2420            "message": "Enter your name",
2421            "requestedSchema": {
2422                "type": "object",
2423                "properties": {
2424                    "name": { "type": "string", "title": "Name" }
2425                },
2426                "required": ["name"]
2427            },
2428            "unknownStringField": "hello",
2429            "unknownNumberField": 42
2430        });
2431
2432        let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2433        assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2434        assert_eq!(req.message, "Enter your name");
2435        assert!(matches!(req.mode, ElicitationMode::Form(_)));
2436    }
2437
2438    #[test]
2439    fn completion_notification_serialization() {
2440        let notif = CompleteElicitationNotification::new("elic_1");
2441
2442        let json = serde_json::to_value(&notif).unwrap();
2443        assert_eq!(json["elicitationId"], "elic_1");
2444
2445        let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2446        assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2447    }
2448
2449    #[test]
2450    fn empty_capabilities_do_not_advertise_a_mode() {
2451        let caps = ElicitationCapabilities::new();
2452        assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2453        assert!(!caps.supports_form());
2454        assert!(!caps.supports_url());
2455
2456        for value in [
2457            json!({}),
2458            json!({ "form": null }),
2459            json!({ "url": null }),
2460            json!({ "form": null, "url": null }),
2461        ] {
2462            let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2463            assert!(!caps.supports_form());
2464            assert!(!caps.supports_url());
2465        }
2466    }
2467
2468    #[test]
2469    fn capabilities_form_only() {
2470        let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2471
2472        let json = serde_json::to_value(&caps).unwrap();
2473        assert!(json["form"].is_object());
2474        assert!(json.get("url").is_none());
2475
2476        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2477        assert!(roundtripped.form.is_some());
2478        assert!(roundtripped.url.is_none());
2479        assert!(roundtripped.supports_form());
2480        assert!(!roundtripped.supports_url());
2481    }
2482
2483    #[test]
2484    fn capabilities_url_only() {
2485        let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2486
2487        let json = serde_json::to_value(&caps).unwrap();
2488        assert!(json.get("form").is_none());
2489        assert!(json["url"].is_object());
2490
2491        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2492        assert!(roundtripped.form.is_none());
2493        assert!(roundtripped.url.is_some());
2494        assert!(!roundtripped.supports_form());
2495        assert!(roundtripped.supports_url());
2496    }
2497
2498    #[test]
2499    fn capabilities_both() {
2500        let caps = ElicitationCapabilities::new()
2501            .form(ElicitationFormCapabilities::new())
2502            .url(ElicitationUrlCapabilities::new());
2503
2504        let json = serde_json::to_value(&caps).unwrap();
2505        assert!(json["form"].is_object());
2506        assert!(json["url"].is_object());
2507
2508        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2509        assert!(roundtripped.form.is_some());
2510        assert!(roundtripped.url.is_some());
2511        assert!(roundtripped.supports_form());
2512        assert!(roundtripped.supports_url());
2513    }
2514
2515    #[test]
2516    fn schema_default_sets_object_type() {
2517        let schema = ElicitationSchema::default();
2518
2519        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2520        assert!(schema.properties.is_empty());
2521
2522        let json = serde_json::to_value(&schema).unwrap();
2523        assert_eq!(json["type"], "object");
2524    }
2525
2526    #[test]
2527    fn schema_builder_serialization() {
2528        let schema = ElicitationSchema::new()
2529            .string("name", true)
2530            .email("email", true)
2531            .integer("age", 0, 150, true)
2532            .boolean("newsletter", false)
2533            .description("User registration");
2534
2535        let json = serde_json::to_value(&schema).unwrap();
2536        assert_eq!(json["type"], "object");
2537        assert_eq!(json["description"], "User registration");
2538        assert_eq!(json["properties"]["name"]["type"], "string");
2539        assert_eq!(json["properties"]["email"]["type"], "string");
2540        assert_eq!(json["properties"]["email"]["format"], "email");
2541        assert_eq!(json["properties"]["age"]["type"], "integer");
2542        assert_eq!(json["properties"]["age"]["minimum"], 0);
2543        assert_eq!(json["properties"]["age"]["maximum"], 150);
2544        assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2545
2546        let required = json["required"].as_array().unwrap();
2547        assert!(required.contains(&json!("name")));
2548        assert!(required.contains(&json!("email")));
2549        assert!(required.contains(&json!("age")));
2550        assert!(!required.contains(&json!("newsletter")));
2551
2552        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2553        assert_eq!(roundtripped.properties.len(), 4);
2554        assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2555    }
2556
2557    #[test]
2558    fn schema_string_enum_serialization() {
2559        let schema = ElicitationSchema::new().property(
2560            "color",
2561            StringPropertySchema::new().enum_values(vec![
2562                "red".into(),
2563                "green".into(),
2564                "blue".into(),
2565            ]),
2566            true,
2567        );
2568
2569        let json = serde_json::to_value(&schema).unwrap();
2570        assert_eq!(json["properties"]["color"]["type"], "string");
2571        let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2572        assert_eq!(enum_vals.len(), 3);
2573
2574        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2575        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2576        {
2577            assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2578        } else {
2579            panic!("expected String variant");
2580        }
2581    }
2582
2583    #[test]
2584    fn schema_multi_select_serialization() {
2585        let schema = ElicitationSchema::new().property(
2586            "colors",
2587            MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2588                .min_items(1)
2589                .max_items(3),
2590            false,
2591        );
2592
2593        let json = serde_json::to_value(&schema).unwrap();
2594        assert_eq!(json["properties"]["colors"]["type"], "array");
2595        assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2596        assert_eq!(json["properties"]["colors"]["minItems"], 1);
2597        assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2598
2599        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2600        let ElicitationPropertySchema::Array(array) =
2601            roundtripped.properties.get("colors").unwrap()
2602        else {
2603            panic!("expected Array variant");
2604        };
2605        let MultiSelectItems::String(items) = &array.items else {
2606            panic!("expected String multi-select items");
2607        };
2608        assert_eq!(items.values.len(), 3);
2609    }
2610
2611    #[test]
2612    fn multi_select_titled_items_keep_mcp_shape() {
2613        let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2614            "#ff0000", "Red",
2615        )]));
2616
2617        let json = serde_json::to_value(&items).unwrap();
2618        assert!(json.get("type").is_none());
2619        assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2620        assert_eq!(json["anyOf"][0]["title"], "Red");
2621
2622        let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2623        assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2624    }
2625
2626    #[test]
2627    fn multi_select_items_preserve_unknown_type() {
2628        let json = json!({
2629            "type": "_token",
2630            "format": "workspace",
2631            "anyOf": [
2632                { "const": "repo", "title": "Repository" }
2633            ]
2634        });
2635
2636        let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2637        let MultiSelectItems::Other(other) = &items else {
2638            panic!("expected unknown multi-select items");
2639        };
2640
2641        assert_eq!(other.type_, "_token");
2642        assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2643        assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2644        assert_eq!(serde_json::to_value(&items).unwrap(), json);
2645    }
2646
2647    #[test]
2648    fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2649        assert!(
2650            serde_json::from_value::<MultiSelectItems>(json!({
2651                "type": "string"
2652            }))
2653            .is_err()
2654        );
2655        assert!(
2656            serde_json::from_value::<OtherMultiSelectItems>(json!({
2657                "type": "string",
2658                "format": "workspace"
2659            }))
2660            .is_err()
2661        );
2662    }
2663
2664    #[test]
2665    fn property_schema_preserves_unknown_type() {
2666        let schema: ElicitationSchema = serde_json::from_value(json!({
2667            "type": "object",
2668            "properties": {
2669                "location": {
2670                    "type": "_location",
2671                    "title": "Location",
2672                    "precision": "city"
2673                }
2674            }
2675        }))
2676        .unwrap();
2677
2678        let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2679        else {
2680            panic!("expected unknown property schema");
2681        };
2682
2683        assert_eq!(unknown.type_, "_location");
2684        assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2685        assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2686        assert_eq!(
2687            serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2688            json!({
2689                "type": "_location",
2690                "title": "Location",
2691                "precision": "city"
2692            })
2693        );
2694    }
2695
2696    #[test]
2697    fn property_schema_unknown_does_not_hide_malformed_known_type() {
2698        assert!(
2699            serde_json::from_value::<ElicitationPropertySchema>(json!({
2700                "type": "array"
2701            }))
2702            .is_err()
2703        );
2704        assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2705    }
2706
2707    #[test]
2708    fn schema_titled_enum_serialization() {
2709        let schema = ElicitationSchema::new().property(
2710            "country",
2711            StringPropertySchema::new().one_of(vec![
2712                EnumOption::new("us", "United States").description("Use US English spelling."),
2713                EnumOption::new("uk", "United Kingdom"),
2714            ]),
2715            true,
2716        );
2717
2718        let json = serde_json::to_value(&schema).unwrap();
2719        assert_eq!(json["properties"]["country"]["type"], "string");
2720        let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2721        assert_eq!(one_of.len(), 2);
2722        assert_eq!(one_of[0]["const"], "us");
2723        assert_eq!(one_of[0]["title"], "United States");
2724        assert_eq!(one_of[0]["description"], "Use US English spelling.");
2725        assert!(one_of[1].get("description").is_none());
2726
2727        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2728        if let ElicitationPropertySchema::String(s) =
2729            roundtripped.properties.get("country").unwrap()
2730        {
2731            let one_of = s.one_of.as_ref().unwrap();
2732            assert_eq!(one_of.len(), 2);
2733            assert_eq!(
2734                one_of[0].description.as_deref(),
2735                Some("Use US English spelling.")
2736            );
2737            assert!(one_of[1].description.is_none());
2738        } else {
2739            panic!("expected String variant");
2740        }
2741    }
2742
2743    #[test]
2744    fn schema_number_property_serialization() {
2745        let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2746
2747        let json = serde_json::to_value(&schema).unwrap();
2748        assert_eq!(json["properties"]["rating"]["type"], "number");
2749        assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2750        assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2751
2752        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2753        if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2754        {
2755            assert_eq!(n.minimum, Some(0.0));
2756            assert_eq!(n.maximum, Some(5.0));
2757        } else {
2758            panic!("expected Number variant");
2759        }
2760    }
2761
2762    #[test]
2763    fn schema_string_format_serialization() {
2764        let schema = ElicitationSchema::new()
2765            .uri("website", true)
2766            .date("birthday", true)
2767            .date_time("updated_at", false);
2768
2769        let json = serde_json::to_value(&schema).unwrap();
2770        assert_eq!(json["properties"]["website"]["type"], "string");
2771        assert_eq!(json["properties"]["website"]["format"], "uri");
2772        assert_eq!(json["properties"]["birthday"]["type"], "string");
2773        assert_eq!(json["properties"]["birthday"]["format"], "date");
2774        assert_eq!(json["properties"]["updated_at"]["type"], "string");
2775        assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2776
2777        let required = json["required"].as_array().unwrap();
2778        assert!(required.contains(&json!("website")));
2779        assert!(required.contains(&json!("birthday")));
2780        assert!(!required.contains(&json!("updated_at")));
2781    }
2782
2783    #[test]
2784    fn schema_string_pattern_serialization() {
2785        let schema = ElicitationSchema::new().property(
2786            "name",
2787            StringPropertySchema::new()
2788                .min_length(1)
2789                .max_length(64)
2790                .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2791            true,
2792        );
2793
2794        let json = serde_json::to_value(&schema).unwrap();
2795        assert_eq!(json["properties"]["name"]["type"], "string");
2796        assert_eq!(
2797            json["properties"]["name"]["pattern"],
2798            "^[a-zA-Z_][a-zA-Z0-9_]*$"
2799        );
2800
2801        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2802        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2803            assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2804        } else {
2805            panic!("expected String variant");
2806        }
2807    }
2808
2809    #[test]
2810    fn schema_property_updates_required_state() {
2811        let schema = ElicitationSchema::new()
2812            .string("name", true)
2813            .email("name", false);
2814
2815        let json = serde_json::to_value(&schema).unwrap();
2816        assert!(json.get("required").is_none());
2817        assert_eq!(json["properties"]["name"]["format"], "email");
2818    }
2819
2820    #[test]
2821    fn schema_defaults_invalid_object_type() {
2822        let schema = serde_json::from_value::<ElicitationSchema>(json!({
2823            "type": "array",
2824            "properties": {
2825                "name": {
2826                    "type": "string"
2827                }
2828            }
2829        }))
2830        .unwrap();
2831
2832        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2833        assert!(schema.properties.contains_key("name"));
2834    }
2835
2836    #[test]
2837    fn titled_multi_select_items_reject_one_of() {
2838        let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2839            "oneOf": [
2840                {
2841                    "const": "red",
2842                    "title": "Red"
2843                }
2844            ]
2845        }))
2846        .unwrap_err();
2847
2848        assert!(err.to_string().contains("missing field `anyOf`"));
2849    }
2850
2851    #[test]
2852    fn response_accept_rejects_non_object_content() {
2853        assert!(
2854            serde_json::from_value::<CreateElicitationResponse>(json!({
2855                "action": "accept",
2856                "content": "Alice"
2857            }))
2858            .is_err()
2859        );
2860    }
2861
2862    #[test]
2863    fn response_accept_treats_null_and_omitted_content_equally() {
2864        for value in [
2865            json!({ "action": "accept" }),
2866            json!({
2867                "action": "accept",
2868                "content": null
2869            }),
2870        ] {
2871            let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2872            let ElicitationAction::Accept(accept) = response.action else {
2873                panic!("expected accept action");
2874            };
2875            assert!(accept.content.is_none());
2876        }
2877    }
2878
2879    #[test]
2880    fn response_accept_rejects_nested_object_content() {
2881        assert!(
2882            serde_json::from_value::<CreateElicitationResponse>(json!({
2883                "action": "accept",
2884                "content": {
2885                    "profile": {
2886                        "name": "Alice"
2887                    }
2888                }
2889            }))
2890            .is_err()
2891        );
2892    }
2893
2894    #[test]
2895    fn response_accept_allows_primitive_and_string_array_content() {
2896        let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2897            ElicitationAcceptAction::new().content(BTreeMap::from([
2898                ("name".to_string(), ElicitationContentValue::from("Alice")),
2899                ("age".to_string(), ElicitationContentValue::from(30_i32)),
2900                ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2901                (
2902                    "subscribed".to_string(),
2903                    ElicitationContentValue::from(true),
2904                ),
2905                (
2906                    "tags".to_string(),
2907                    ElicitationContentValue::from(vec!["rust", "acp"]),
2908                ),
2909            ])),
2910        ));
2911
2912        let json = serde_json::to_value(&response).unwrap();
2913        assert_eq!(json["action"], "accept");
2914        assert_eq!(json["content"]["name"], "Alice");
2915        assert_eq!(json["content"]["age"], 30);
2916        assert_eq!(json["content"]["score"], 9.5);
2917        assert_eq!(json["content"]["subscribed"], true);
2918        assert_eq!(json["content"]["tags"][0], "rust");
2919        assert_eq!(json["content"]["tags"][1], "acp");
2920    }
2921}