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