Skip to main content

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