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