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/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/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, 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/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/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/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/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/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/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/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/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/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/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/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/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    #[serde(untagged)]
822    Titled(TitledMultiSelectItems),
823}
824
825/// Schema for multi-select (array) properties in an elicitation form.
826#[serde_as]
827#[skip_serializing_none]
828#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
829#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
830#[serde(rename_all = "camelCase")]
831#[non_exhaustive]
832pub struct MultiSelectPropertySchema {
833    /// Optional title for the property.
834    ///
835    /// Optional. Omitted and `null` are equivalent and mean no title is provided.
836    #[serde_as(deserialize_as = "DefaultOnError")]
837    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
838    #[serde(default)]
839    pub title: Option<String>,
840    /// Human-readable description.
841    ///
842    /// Optional. Omitted and `null` are equivalent and mean no description is provided.
843    #[serde_as(deserialize_as = "DefaultOnError")]
844    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
845    #[serde(default)]
846    pub description: Option<String>,
847    /// Minimum number of items to select.
848    ///
849    /// Optional. Omitted and `null` are equivalent and mean there is no minimum selection count.
850    #[serde(default)]
851    pub min_items: Option<u64>,
852    /// Maximum number of items to select.
853    ///
854    /// Optional. Omitted and `null` are equivalent and mean there is no maximum selection count.
855    #[serde(default)]
856    pub max_items: Option<u64>,
857    /// The items definition describing allowed values.
858    pub items: MultiSelectItems,
859    /// Default selected values.
860    ///
861    /// Optional. Omitted and `null` are equivalent and mean no default selections are provided.
862    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
863    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
864    #[serde(default)]
865    pub default: Option<Vec<String>>,
866    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
867    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
868    /// these keys.
869    ///
870    /// Optional. Omitted and `null` are equivalent and mean no metadata.
871    ///
872    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
873    #[serde_as(deserialize_as = "DefaultOnError")]
874    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
875    #[serde(default)]
876    #[serde(rename = "_meta")]
877    pub meta: Option<Meta>,
878}
879
880impl MultiSelectPropertySchema {
881    /// Create a new untitled multi-select property schema.
882    #[must_use]
883    pub fn new(values: Vec<String>) -> Self {
884        Self {
885            title: None,
886            description: None,
887            min_items: None,
888            max_items: None,
889            items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
890            default: None,
891            meta: None,
892        }
893    }
894
895    /// Create a new titled multi-select property schema.
896    #[must_use]
897    pub fn titled(options: Vec<EnumOption>) -> Self {
898        Self {
899            title: None,
900            description: None,
901            min_items: None,
902            max_items: None,
903            items: MultiSelectItems::Titled(TitledMultiSelectItems::new(options)),
904            default: None,
905            meta: None,
906        }
907    }
908
909    /// Optional title for the property.
910    #[must_use]
911    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
912        self.title = title.into_option();
913        self
914    }
915
916    /// Human-readable description.
917    #[must_use]
918    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
919        self.description = description.into_option();
920        self
921    }
922
923    /// Minimum number of items to select.
924    #[must_use]
925    pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
926        self.min_items = min_items.into_option();
927        self
928    }
929
930    /// Maximum number of items to select.
931    #[must_use]
932    pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
933        self.max_items = max_items.into_option();
934        self
935    }
936
937    /// Default selected values.
938    #[must_use]
939    pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
940        self.default = default.into_option();
941        self
942    }
943
944    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
945    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
946    /// these keys.
947    ///
948    /// Optional. Omitted and `null` are equivalent and mean no metadata.
949    ///
950    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
951    #[must_use]
952    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
953        self.meta = meta.into_option();
954        self
955    }
956}
957
958/// Property schema for elicitation form fields.
959///
960/// Each variant corresponds to a JSON Schema `"type"` value.
961/// Single-select enums use the `String` variant with `enum` or `oneOf` set.
962/// Multi-select enums use the `Array` variant.
963#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
964#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
965#[serde(tag = "type", rename_all = "snake_case")]
966#[non_exhaustive]
967pub enum ElicitationPropertySchema {
968    /// String property (or single-select enum when `enum`/`oneOf` is set).
969    String(StringPropertySchema),
970    /// Number (floating-point) property.
971    Number(NumberPropertySchema),
972    /// Integer property.
973    Integer(IntegerPropertySchema),
974    /// Boolean property.
975    Boolean(BooleanPropertySchema),
976    /// Multi-select array property.
977    Array(MultiSelectPropertySchema),
978    /// Custom or future elicitation property schema.
979    ///
980    /// Values beginning with `_` are reserved for implementation-specific
981    /// extensions. Unknown values that do not begin with `_` are reserved for
982    /// future ACP variants.
983    ///
984    /// Clients that do not understand this property schema type should preserve
985    /// the raw schema when storing, replaying, proxying, or forwarding
986    /// elicitation requests. They MUST NOT render it as a known input control.
987    #[serde(untagged)]
988    Other(OtherElicitationPropertySchema),
989}
990
991/// Custom or future elicitation property schema payload.
992///
993/// This preserves the unknown `type` discriminator and the rest of the property
994/// schema object for clients that store, replay, proxy, or forward elicitation
995/// requests.
996#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
997#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
998#[cfg_attr(feature = "schemars", schemars(inline))]
999#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_property_schema_schema))]
1000#[serde(rename_all = "camelCase")]
1001#[non_exhaustive]
1002pub struct OtherElicitationPropertySchema {
1003    /// Custom or future elicitation property schema type.
1004    ///
1005    /// Values beginning with `_` are reserved for implementation-specific
1006    /// extensions. Unknown values that do not begin with `_` are reserved for
1007    /// future ACP variants.
1008    #[serde(rename = "type")]
1009    pub type_: String,
1010    /// Additional fields from the unknown property schema payload.
1011    #[serde(flatten)]
1012    pub fields: BTreeMap<String, serde_json::Value>,
1013}
1014
1015impl OtherElicitationPropertySchema {
1016    /// Builds [`OtherElicitationPropertySchema`] from an unknown discriminator and preserves the remaining extension fields.
1017    #[must_use]
1018    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1019        fields.remove("type");
1020        Self {
1021            type_: type_.into(),
1022            fields,
1023        }
1024    }
1025}
1026
1027impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
1028    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1029    where
1030        D: serde::Deserializer<'de>,
1031    {
1032        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1033        let type_ = fields
1034            .remove("type")
1035            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1036        let serde_json::Value::String(type_) = type_ else {
1037            return Err(serde::de::Error::custom("`type` must be a string"));
1038        };
1039
1040        if is_known_elicitation_property_schema_type(&type_) {
1041            return Err(serde::de::Error::custom(format!(
1042                "known elicitation property schema type `{type_}` did not match its schema"
1043            )));
1044        }
1045
1046        Ok(Self { type_, fields })
1047    }
1048}
1049
1050const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
1051    &["string", "number", "integer", "boolean", "array"];
1052
1053fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
1054    KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
1055}
1056
1057#[cfg(feature = "schemars")]
1058fn other_elicitation_property_schema_schema(schema: &mut Schema) {
1059    let known_value_schemas: Vec<_> = KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES
1060        .iter()
1061        .map(|value| {
1062            serde_json::json!({
1063                "properties": {
1064                    "type": {
1065                        "const": value,
1066                        "type": "string"
1067                    }
1068                },
1069                "required": ["type"],
1070                "type": "object"
1071            })
1072        })
1073        .collect();
1074
1075    schema.insert(
1076        "not".into(),
1077        serde_json::json!({
1078            "anyOf": known_value_schemas
1079        }),
1080    );
1081}
1082
1083impl From<StringPropertySchema> for ElicitationPropertySchema {
1084    fn from(schema: StringPropertySchema) -> Self {
1085        Self::String(schema)
1086    }
1087}
1088
1089impl From<NumberPropertySchema> for ElicitationPropertySchema {
1090    fn from(schema: NumberPropertySchema) -> Self {
1091        Self::Number(schema)
1092    }
1093}
1094
1095impl From<IntegerPropertySchema> for ElicitationPropertySchema {
1096    fn from(schema: IntegerPropertySchema) -> Self {
1097        Self::Integer(schema)
1098    }
1099}
1100
1101impl From<BooleanPropertySchema> for ElicitationPropertySchema {
1102    fn from(schema: BooleanPropertySchema) -> Self {
1103        Self::Boolean(schema)
1104    }
1105}
1106
1107impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
1108    fn from(schema: MultiSelectPropertySchema) -> Self {
1109        Self::Array(schema)
1110    }
1111}
1112
1113fn default_object_type() -> ElicitationSchemaType {
1114    ElicitationSchemaType::Object
1115}
1116
1117/// Type-safe elicitation schema for requesting structured user input.
1118///
1119/// This represents a JSON Schema object with primitive-typed properties,
1120/// as required by the elicitation specification.
1121#[serde_as]
1122#[skip_serializing_none]
1123#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1125#[serde(rename_all = "camelCase")]
1126#[non_exhaustive]
1127pub struct ElicitationSchema {
1128    /// Type discriminator. Always `"object"`.
1129    #[serde_as(deserialize_as = "DefaultOnError")]
1130    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1131    #[serde(rename = "type", default = "default_object_type")]
1132    pub type_: ElicitationSchemaType,
1133    /// Optional title for the schema.
1134    ///
1135    /// Optional. Omitted and `null` are equivalent and mean no title is provided.
1136    #[serde_as(deserialize_as = "DefaultOnError")]
1137    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1138    #[serde(default)]
1139    pub title: Option<String>,
1140    /// Property definitions (must be primitive types).
1141    #[serde(default)]
1142    pub properties: BTreeMap<String, ElicitationPropertySchema>,
1143    /// List of required property names.
1144    ///
1145    /// Optional. Omitted and `null` are equivalent and mean no property names are required.
1146    #[serde(default)]
1147    pub required: Option<Vec<String>>,
1148    /// Optional description of what this schema represents.
1149    ///
1150    /// Optional. Omitted and `null` are equivalent and mean no schema description is provided.
1151    #[serde_as(deserialize_as = "DefaultOnError")]
1152    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1153    #[serde(default)]
1154    pub description: Option<String>,
1155    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1156    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1157    /// these keys.
1158    ///
1159    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1160    ///
1161    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1162    #[serde_as(deserialize_as = "DefaultOnError")]
1163    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1164    #[serde(default)]
1165    #[serde(rename = "_meta")]
1166    pub meta: Option<Meta>,
1167}
1168
1169impl Default for ElicitationSchema {
1170    fn default() -> Self {
1171        Self {
1172            type_: default_object_type(),
1173            title: None,
1174            properties: BTreeMap::new(),
1175            required: None,
1176            description: None,
1177            meta: None,
1178        }
1179    }
1180}
1181
1182impl ElicitationSchema {
1183    /// Create a new empty elicitation schema.
1184    #[must_use]
1185    pub fn new() -> Self {
1186        Self::default()
1187    }
1188
1189    /// Optional title for the schema.
1190    #[must_use]
1191    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1192        self.title = title.into_option();
1193        self
1194    }
1195
1196    /// Optional description of what this schema represents.
1197    #[must_use]
1198    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1199        self.description = description.into_option();
1200        self
1201    }
1202
1203    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1204    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1205    /// these keys.
1206    ///
1207    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1208    ///
1209    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1210    #[must_use]
1211    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1212        self.meta = meta.into_option();
1213        self
1214    }
1215
1216    /// Add a property to the schema.
1217    #[must_use]
1218    pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1219    where
1220        S: Into<ElicitationPropertySchema>,
1221    {
1222        let name = name.into();
1223        self.properties.insert(name.clone(), schema.into());
1224
1225        if required {
1226            let required_fields = self.required.get_or_insert_with(Vec::new);
1227            if !required_fields.contains(&name) {
1228                required_fields.push(name);
1229            }
1230        } else if let Some(required_fields) = &mut self.required {
1231            required_fields.retain(|field| field != &name);
1232
1233            if required_fields.is_empty() {
1234                self.required = None;
1235            }
1236        }
1237
1238        self
1239    }
1240
1241    /// Add a string property.
1242    #[must_use]
1243    pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1244        self.property(name, StringPropertySchema::new(), required)
1245    }
1246
1247    /// Add an email property.
1248    #[must_use]
1249    pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1250        self.property(name, StringPropertySchema::email(), required)
1251    }
1252
1253    /// Add a URI property.
1254    #[must_use]
1255    pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1256        self.property(name, StringPropertySchema::uri(), required)
1257    }
1258
1259    /// Add a date property.
1260    #[must_use]
1261    pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1262        self.property(name, StringPropertySchema::date(), required)
1263    }
1264
1265    /// Add a date-time property.
1266    #[must_use]
1267    pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1268        self.property(name, StringPropertySchema::date_time(), required)
1269    }
1270
1271    /// Add a number property with range.
1272    #[must_use]
1273    pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1274        self.property(
1275            name,
1276            NumberPropertySchema::new().minimum(min).maximum(max),
1277            required,
1278        )
1279    }
1280
1281    /// Add an integer property with range.
1282    #[must_use]
1283    pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1284        self.property(
1285            name,
1286            IntegerPropertySchema::new().minimum(min).maximum(max),
1287            required,
1288        )
1289    }
1290
1291    /// Add a boolean property.
1292    #[must_use]
1293    pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1294        self.property(name, BooleanPropertySchema::new(), required)
1295    }
1296}
1297
1298/// Elicitation capabilities supported by the client.
1299#[serde_as]
1300#[skip_serializing_none]
1301#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1302#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1303#[serde(rename_all = "camelCase")]
1304#[non_exhaustive]
1305pub struct ElicitationCapabilities {
1306    /// Whether the client supports form-based elicitation.
1307    ///
1308    /// Optional. Omitted and `null` are equivalent and mean form support is not advertised.
1309    /// Supplying `{}` explicitly advertises form support.
1310    #[serde_as(deserialize_as = "DefaultOnError")]
1311    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1312    #[serde(default)]
1313    pub form: Option<ElicitationFormCapabilities>,
1314    /// Whether the client supports URL-based elicitation.
1315    ///
1316    /// Optional. Omitted or `null` both mean the client does not advertise support.
1317    /// Supplying `{}` means the client supports URL-based elicitation.
1318    #[serde_as(deserialize_as = "DefaultOnError")]
1319    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1320    #[serde(default)]
1321    pub url: Option<ElicitationUrlCapabilities>,
1322    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1323    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1324    /// these keys.
1325    ///
1326    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1327    ///
1328    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1329    #[serde_as(deserialize_as = "DefaultOnError")]
1330    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1331    #[serde(default)]
1332    #[serde(rename = "_meta")]
1333    pub meta: Option<Meta>,
1334}
1335
1336impl ElicitationCapabilities {
1337    /// Builds empty elicitation capabilities.
1338    ///
1339    /// Use the builder methods to advertise supported modes. An empty capability object does not
1340    /// advertise form or URL support.
1341    #[must_use]
1342    pub fn new() -> Self {
1343        Self::default()
1344    }
1345
1346    /// Returns whether form-based elicitation is supported.
1347    ///
1348    #[must_use]
1349    pub fn supports_form(&self) -> bool {
1350        self.form.is_some()
1351    }
1352
1353    /// Returns whether URL-based elicitation is supported.
1354    #[must_use]
1355    pub fn supports_url(&self) -> bool {
1356        self.url.is_some()
1357    }
1358
1359    /// Whether the client supports form-based elicitation.
1360    ///
1361    /// Omitted and `null` are equivalent and mean form support is not advertised.
1362    /// Supplying `{}` explicitly advertises form-based elicitation.
1363    #[must_use]
1364    pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1365        self.form = form.into_option();
1366        self
1367    }
1368
1369    /// Whether the client supports URL-based elicitation.
1370    ///
1371    /// Omitted or `null` both mean the client does not advertise support.
1372    /// Supplying `{}` means the client supports URL-based elicitation.
1373    #[must_use]
1374    pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1375        self.url = url.into_option();
1376        self
1377    }
1378
1379    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1380    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1381    /// these keys.
1382    ///
1383    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1384    ///
1385    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1386    #[must_use]
1387    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1388        self.meta = meta.into_option();
1389        self
1390    }
1391}
1392
1393/// Form-based elicitation capabilities.
1394///
1395/// Supplying `{}` means the client supports form-based elicitation.
1396#[serde_as]
1397#[skip_serializing_none]
1398#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1399#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1400#[serde(rename_all = "camelCase")]
1401#[non_exhaustive]
1402pub struct ElicitationFormCapabilities {
1403    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1404    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1405    /// these keys.
1406    ///
1407    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1408    ///
1409    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1410    #[serde_as(deserialize_as = "DefaultOnError")]
1411    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1412    #[serde(default)]
1413    #[serde(rename = "_meta")]
1414    pub meta: Option<Meta>,
1415}
1416
1417impl ElicitationFormCapabilities {
1418    /// Builds an empty [`ElicitationFormCapabilities`]; use builder methods to advertise supported sub-capabilities.
1419    #[must_use]
1420    pub fn new() -> Self {
1421        Self::default()
1422    }
1423
1424    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1425    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1426    /// these keys.
1427    ///
1428    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1429    ///
1430    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1431    #[must_use]
1432    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1433        self.meta = meta.into_option();
1434        self
1435    }
1436}
1437
1438/// URL-based elicitation capabilities.
1439///
1440/// Supplying `{}` means the client supports URL-based elicitation.
1441#[serde_as]
1442#[skip_serializing_none]
1443#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1444#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1445#[serde(rename_all = "camelCase")]
1446#[non_exhaustive]
1447pub struct ElicitationUrlCapabilities {
1448    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1449    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1450    /// these keys.
1451    ///
1452    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1453    ///
1454    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1455    #[serde_as(deserialize_as = "DefaultOnError")]
1456    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1457    #[serde(default)]
1458    #[serde(rename = "_meta")]
1459    pub meta: Option<Meta>,
1460}
1461
1462impl ElicitationUrlCapabilities {
1463    /// Builds an empty [`ElicitationUrlCapabilities`]; use builder methods to advertise supported sub-capabilities.
1464    #[must_use]
1465    pub fn new() -> Self {
1466        Self::default()
1467    }
1468
1469    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1470    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1471    /// these keys.
1472    ///
1473    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1474    ///
1475    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1476    #[must_use]
1477    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1478        self.meta = meta.into_option();
1479        self
1480    }
1481}
1482
1483/// The scope of an elicitation request, determining what context it's tied to.
1484#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1485#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1486#[serde(untagged)]
1487#[non_exhaustive]
1488pub enum ElicitationScope {
1489    /// Tied to a session, optionally to a specific tool call within that session.
1490    Session(ElicitationSessionScope),
1491    /// Tied to a specific JSON-RPC request outside of a session
1492    /// (e.g., during auth/configuration phases before any session is started).
1493    Request(ElicitationRequestScope),
1494}
1495
1496/// Session-scoped elicitation, optionally tied to a specific tool call.
1497///
1498/// When `tool_call_id` is set, the elicitation is tied to a specific tool call.
1499/// This is useful when an agent receives an elicitation from an MCP server
1500/// during a tool call and needs to redirect it to the user.
1501#[serde_as]
1502#[skip_serializing_none]
1503#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1505#[serde(rename_all = "camelCase")]
1506#[non_exhaustive]
1507pub struct ElicitationSessionScope {
1508    /// The session this elicitation is tied to.
1509    pub session_id: SessionId,
1510    /// Optional tool call within the session.
1511    ///
1512    /// Optional. Omitted and `null` are equivalent and mean the elicitation is scoped to the
1513    /// session without a specific tool call.
1514    #[serde_as(deserialize_as = "DefaultOnError")]
1515    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1516    #[serde(default)]
1517    pub tool_call_id: Option<ToolCallId>,
1518}
1519
1520impl ElicitationSessionScope {
1521    /// Builds [`ElicitationSessionScope`] with the required fields set; optional fields start unset or empty.
1522    #[must_use]
1523    pub fn new(session_id: impl Into<SessionId>) -> Self {
1524        Self {
1525            session_id: session_id.into(),
1526            tool_call_id: None,
1527        }
1528    }
1529
1530    /// Sets or clears the optional `toolCallId` field.
1531    #[must_use]
1532    pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1533        self.tool_call_id = tool_call_id.into_option();
1534        self
1535    }
1536}
1537
1538/// Request-scoped elicitation, tied to a specific JSON-RPC request outside of a session
1539/// (e.g., during auth/configuration phases before any session is started).
1540#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1542#[serde(rename_all = "camelCase")]
1543#[non_exhaustive]
1544pub struct ElicitationRequestScope {
1545    /// The request this elicitation is tied to.
1546    pub request_id: RequestId,
1547}
1548
1549impl ElicitationRequestScope {
1550    /// Builds [`ElicitationRequestScope`] with the required fields set; optional fields start unset or empty.
1551    #[must_use]
1552    pub fn new(request_id: impl Into<RequestId>) -> Self {
1553        Self {
1554            request_id: request_id.into(),
1555        }
1556    }
1557}
1558
1559impl From<ElicitationSessionScope> for ElicitationScope {
1560    fn from(scope: ElicitationSessionScope) -> Self {
1561        Self::Session(scope)
1562    }
1563}
1564
1565impl From<ElicitationRequestScope> for ElicitationScope {
1566    fn from(scope: ElicitationRequestScope) -> Self {
1567        Self::Request(scope)
1568    }
1569}
1570
1571/// Request from the agent to elicit structured user input.
1572///
1573/// The agent sends this to the client to request information from the user,
1574/// either via a form or by directing them to a URL.
1575/// Elicitations are tied to a session (optionally a tool call) or a request.
1576#[serde_as]
1577#[skip_serializing_none]
1578#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1579#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1580#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1581#[serde(rename_all = "camelCase")]
1582#[non_exhaustive]
1583pub struct CreateElicitationRequest {
1584    /// The elicitation mode and its mode-specific fields.
1585    #[serde(flatten)]
1586    pub mode: ElicitationMode,
1587    /// A human-readable message describing what input is needed.
1588    pub message: String,
1589    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1590    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1591    /// these keys.
1592    ///
1593    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1594    ///
1595    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1596    #[serde_as(deserialize_as = "DefaultOnError")]
1597    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1598    #[serde(default)]
1599    #[serde(rename = "_meta")]
1600    pub meta: Option<Meta>,
1601}
1602
1603impl CreateElicitationRequest {
1604    /// Builds [`CreateElicitationRequest`] with the required request fields set; optional fields start unset or empty.
1605    #[must_use]
1606    pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1607        Self {
1608            mode: mode.into(),
1609            message: message.into(),
1610            meta: None,
1611        }
1612    }
1613
1614    /// Returns the scope this elicitation is tied to.
1615    #[must_use]
1616    pub fn scope(&self) -> &ElicitationScope {
1617        self.mode.scope()
1618    }
1619
1620    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1621    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1622    /// these keys.
1623    ///
1624    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1625    ///
1626    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1627    #[must_use]
1628    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1629        self.meta = meta.into_option();
1630        self
1631    }
1632}
1633
1634/// The mode of elicitation, determining how user input is collected.
1635#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1636#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1637#[serde(tag = "mode", rename_all = "snake_case")]
1638#[non_exhaustive]
1639pub enum ElicitationMode {
1640    /// Form-based elicitation where the client renders a form from the provided schema.
1641    Form(ElicitationFormMode),
1642    /// URL-based elicitation where the client directs the user to a URL.
1643    Url(ElicitationUrlMode),
1644    /// Custom or future elicitation mode.
1645    ///
1646    /// Values beginning with `_` are reserved for implementation-specific
1647    /// extensions. Unknown values that do not begin with `_` are reserved for
1648    /// future ACP variants.
1649    ///
1650    /// Clients that do not understand this mode should preserve the raw payload
1651    /// when storing, replaying, proxying, or forwarding elicitation requests.
1652    /// They MUST NOT render it as a known elicitation mode.
1653    #[serde(untagged)]
1654    Other(OtherElicitationMode),
1655}
1656
1657/// Custom or future elicitation mode payload.
1658///
1659/// This preserves the unknown `mode` discriminator and the rest of the mode
1660/// object for clients that store, replay, proxy, or forward elicitation
1661/// requests.
1662#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1663#[derive(Debug, Clone, Serialize, PartialEq)]
1664#[cfg_attr(feature = "schemars", schemars(inline))]
1665#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1666#[serde(rename_all = "camelCase")]
1667#[non_exhaustive]
1668pub struct OtherElicitationMode {
1669    /// Custom or future elicitation mode.
1670    ///
1671    /// Values beginning with `_` are reserved for implementation-specific
1672    /// extensions. Unknown values that do not begin with `_` are reserved for
1673    /// future ACP variants.
1674    pub mode: String,
1675    /// The scope this elicitation is tied to.
1676    #[serde(flatten)]
1677    pub scope: ElicitationScope,
1678    /// Additional fields from the unknown elicitation mode payload.
1679    #[serde(flatten)]
1680    pub fields: BTreeMap<String, serde_json::Value>,
1681}
1682
1683impl OtherElicitationMode {
1684    /// Builds [`OtherElicitationMode`] from an unknown discriminator and preserves the remaining extension fields.
1685    #[must_use]
1686    pub fn new(
1687        mode: impl Into<String>,
1688        scope: impl Into<ElicitationScope>,
1689        mut fields: BTreeMap<String, serde_json::Value>,
1690    ) -> Self {
1691        fields.remove("mode");
1692        remove_elicitation_scope_fields(&mut fields);
1693        Self {
1694            mode: mode.into(),
1695            scope: scope.into(),
1696            fields,
1697        }
1698    }
1699}
1700
1701impl<'de> Deserialize<'de> for OtherElicitationMode {
1702    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1703    where
1704        D: serde::Deserializer<'de>,
1705    {
1706        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1707        let mode = fields
1708            .remove("mode")
1709            .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1710        let serde_json::Value::String(mode) = mode else {
1711            return Err(serde::de::Error::custom("`mode` must be a string"));
1712        };
1713
1714        if is_known_elicitation_mode(&mode) {
1715            return Err(serde::de::Error::custom(format!(
1716                "known elicitation mode `{mode}` did not match its schema"
1717            )));
1718        }
1719
1720        let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1721            fields.clone().into_iter().collect(),
1722        ))
1723        .map_err(serde::de::Error::custom)?;
1724        remove_elicitation_scope_fields(&mut fields);
1725
1726        Ok(Self {
1727            mode,
1728            scope,
1729            fields,
1730        })
1731    }
1732}
1733
1734const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1735
1736fn is_known_elicitation_mode(mode: &str) -> bool {
1737    KNOWN_ELICITATION_MODES.contains(&mode)
1738}
1739
1740fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1741    fields.remove("sessionId");
1742    fields.remove("toolCallId");
1743    fields.remove("requestId");
1744}
1745
1746#[cfg(feature = "schemars")]
1747fn other_elicitation_mode_schema(schema: &mut Schema) {
1748    let known_value_schemas: Vec<_> = KNOWN_ELICITATION_MODES
1749        .iter()
1750        .map(|value| {
1751            serde_json::json!({
1752                "properties": {
1753                    "mode": {
1754                        "const": value,
1755                        "type": "string"
1756                    }
1757                },
1758                "required": ["mode"],
1759                "type": "object"
1760            })
1761        })
1762        .collect();
1763
1764    schema.insert(
1765        "not".into(),
1766        serde_json::json!({
1767            "anyOf": known_value_schemas
1768        }),
1769    );
1770}
1771
1772impl From<ElicitationFormMode> for ElicitationMode {
1773    fn from(mode: ElicitationFormMode) -> Self {
1774        Self::Form(mode)
1775    }
1776}
1777
1778impl From<ElicitationUrlMode> for ElicitationMode {
1779    fn from(mode: ElicitationUrlMode) -> Self {
1780        Self::Url(mode)
1781    }
1782}
1783
1784impl From<OtherElicitationMode> for ElicitationMode {
1785    fn from(mode: OtherElicitationMode) -> Self {
1786        Self::Other(mode)
1787    }
1788}
1789
1790impl ElicitationMode {
1791    /// Returns the scope this elicitation mode is tied to.
1792    #[must_use]
1793    pub fn scope(&self) -> &ElicitationScope {
1794        match self {
1795            Self::Form(f) => &f.scope,
1796            Self::Url(u) => &u.scope,
1797            Self::Other(other) => &other.scope,
1798        }
1799    }
1800}
1801
1802/// Form-based elicitation mode where the client renders a form from the provided schema.
1803#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1804#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1805#[serde(rename_all = "camelCase")]
1806#[non_exhaustive]
1807pub struct ElicitationFormMode {
1808    /// The scope this elicitation is tied to.
1809    #[serde(flatten)]
1810    pub scope: ElicitationScope,
1811    /// A JSON Schema describing the form fields to present to the user.
1812    pub requested_schema: ElicitationSchema,
1813}
1814
1815impl ElicitationFormMode {
1816    /// Builds [`ElicitationFormMode`] with the required fields set; optional fields start unset or empty.
1817    #[must_use]
1818    pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1819        Self {
1820            scope: scope.into(),
1821            requested_schema,
1822        }
1823    }
1824}
1825
1826/// URL-based elicitation mode where the client directs the user to a URL.
1827#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1828#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1829#[serde(rename_all = "camelCase")]
1830#[non_exhaustive]
1831pub struct ElicitationUrlMode {
1832    /// The scope this elicitation is tied to.
1833    #[serde(flatten)]
1834    pub scope: ElicitationScope,
1835    /// The unique identifier for this elicitation.
1836    pub elicitation_id: ElicitationId,
1837    /// The URL to direct the user to.
1838    #[cfg_attr(feature = "schemars", schemars(extend("format" = "uri")))]
1839    pub url: String,
1840}
1841
1842impl ElicitationUrlMode {
1843    /// Builds [`ElicitationUrlMode`] with the required fields set; optional fields start unset or empty.
1844    #[must_use]
1845    pub fn new(
1846        scope: impl Into<ElicitationScope>,
1847        elicitation_id: impl Into<ElicitationId>,
1848        url: impl Into<String>,
1849    ) -> Self {
1850        Self {
1851            scope: scope.into(),
1852            elicitation_id: elicitation_id.into(),
1853            url: url.into(),
1854        }
1855    }
1856}
1857
1858/// Response from the client to an elicitation request.
1859#[serde_as]
1860#[skip_serializing_none]
1861#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1862#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1863#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1864#[serde(rename_all = "camelCase")]
1865#[non_exhaustive]
1866pub struct CreateElicitationResponse {
1867    /// The user's action in response to the elicitation.
1868    #[serde(flatten)]
1869    pub action: ElicitationAction,
1870    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1871    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1872    /// these keys.
1873    ///
1874    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1875    ///
1876    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1877    #[serde_as(deserialize_as = "DefaultOnError")]
1878    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1879    #[serde(default)]
1880    #[serde(rename = "_meta")]
1881    pub meta: Option<Meta>,
1882}
1883
1884impl CreateElicitationResponse {
1885    /// Builds [`CreateElicitationResponse`] with the required response fields set; optional fields start unset or empty.
1886    #[must_use]
1887    pub fn new(action: impl Into<ElicitationAction>) -> Self {
1888        Self {
1889            action: action.into(),
1890            meta: None,
1891        }
1892    }
1893
1894    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1895    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1896    /// these keys.
1897    ///
1898    /// Optional. Omitted and `null` are equivalent and mean no metadata.
1899    ///
1900    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1901    #[must_use]
1902    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1903        self.meta = meta.into_option();
1904        self
1905    }
1906}
1907
1908/// The user's action in response to an elicitation.
1909#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1910#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1911#[serde(tag = "action", rename_all = "snake_case")]
1912#[non_exhaustive]
1913pub enum ElicitationAction {
1914    /// The user accepted and provided content.
1915    Accept(ElicitationAcceptAction),
1916    /// The user declined the elicitation.
1917    Decline,
1918    /// The elicitation was cancelled.
1919    Cancel,
1920    /// Custom or future elicitation action.
1921    ///
1922    /// Values beginning with `_` are reserved for implementation-specific
1923    /// extensions. Unknown values that do not begin with `_` are reserved for
1924    /// future ACP variants.
1925    ///
1926    /// Agents that do not understand this action should preserve the raw
1927    /// payload when storing, replaying, proxying, or forwarding elicitation
1928    /// responses. They MUST NOT treat it as a known elicitation action.
1929    #[serde(untagged)]
1930    Other(OtherElicitationAction),
1931}
1932
1933/// Custom or future elicitation action payload.
1934///
1935/// This preserves the unknown `action` discriminator and the rest of the
1936/// response object for agents that store, replay, proxy, or forward elicitation
1937/// responses.
1938#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1939#[derive(Debug, Clone, Serialize, PartialEq)]
1940#[cfg_attr(feature = "schemars", schemars(inline))]
1941#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1942#[serde(rename_all = "camelCase")]
1943#[non_exhaustive]
1944pub struct OtherElicitationAction {
1945    /// Custom or future elicitation action.
1946    ///
1947    /// Values beginning with `_` are reserved for implementation-specific
1948    /// extensions. Unknown values that do not begin with `_` are reserved for
1949    /// future ACP variants.
1950    pub action: String,
1951    /// Additional fields from the unknown elicitation action payload.
1952    #[serde(flatten)]
1953    pub fields: BTreeMap<String, serde_json::Value>,
1954}
1955
1956impl OtherElicitationAction {
1957    /// Builds [`OtherElicitationAction`] from an unknown discriminator and preserves the remaining extension fields.
1958    #[must_use]
1959    pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1960        fields.remove("action");
1961        Self {
1962            action: action.into(),
1963            fields,
1964        }
1965    }
1966}
1967
1968impl<'de> Deserialize<'de> for OtherElicitationAction {
1969    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1970    where
1971        D: serde::Deserializer<'de>,
1972    {
1973        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1974        let action = fields
1975            .remove("action")
1976            .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1977        let serde_json::Value::String(action) = action else {
1978            return Err(serde::de::Error::custom("`action` must be a string"));
1979        };
1980
1981        if is_known_elicitation_action(&action) {
1982            return Err(serde::de::Error::custom(format!(
1983                "known elicitation action `{action}` did not match its schema"
1984            )));
1985        }
1986
1987        Ok(Self { action, fields })
1988    }
1989}
1990
1991const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1992
1993fn is_known_elicitation_action(action: &str) -> bool {
1994    KNOWN_ELICITATION_ACTIONS.contains(&action)
1995}
1996
1997#[cfg(feature = "schemars")]
1998fn other_elicitation_action_schema(schema: &mut Schema) {
1999    let known_value_schemas: Vec<_> = KNOWN_ELICITATION_ACTIONS
2000        .iter()
2001        .map(|value| {
2002            serde_json::json!({
2003                "properties": {
2004                    "action": {
2005                        "const": value,
2006                        "type": "string"
2007                    }
2008                },
2009                "required": ["action"],
2010                "type": "object"
2011            })
2012        })
2013        .collect();
2014
2015    schema.insert(
2016        "not".into(),
2017        serde_json::json!({
2018            "anyOf": known_value_schemas
2019        }),
2020    );
2021}
2022
2023impl From<ElicitationAcceptAction> for ElicitationAction {
2024    fn from(action: ElicitationAcceptAction) -> Self {
2025        Self::Accept(action)
2026    }
2027}
2028
2029impl From<OtherElicitationAction> for ElicitationAction {
2030    fn from(action: OtherElicitationAction) -> Self {
2031        Self::Other(action)
2032    }
2033}
2034
2035/// The user accepted the elicitation and provided content.
2036#[serde_as]
2037#[skip_serializing_none]
2038#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2039#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2040#[serde(rename_all = "camelCase")]
2041#[non_exhaustive]
2042pub struct ElicitationAcceptAction {
2043    /// The user-provided content, if any, as an object matching the requested schema.
2044    #[serde(default)]
2045    pub content: Option<BTreeMap<String, ElicitationContentValue>>,
2046}
2047
2048impl ElicitationAcceptAction {
2049    /// Builds [`ElicitationAcceptAction`] with the required fields set; optional fields start unset or empty.
2050    #[must_use]
2051    pub fn new() -> Self {
2052        Self { content: None }
2053    }
2054
2055    /// The user-provided content as an object matching the requested schema.
2056    #[must_use]
2057    pub fn content(
2058        mut self,
2059        content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2060    ) -> Self {
2061        self.content = content.into_option();
2062        self
2063    }
2064}
2065
2066/// Allowed wire representations for [`ElicitationContentValue`].
2067#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2068#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2069#[serde(untagged)]
2070#[non_exhaustive]
2071pub enum ElicitationContentValue {
2072    /// String value accepted in elicitation response content.
2073    String(String),
2074    /// Integer value accepted in elicitation response content.
2075    Integer(i64),
2076    /// Number value accepted in elicitation response content.
2077    Number(f64),
2078    /// Boolean value accepted in elicitation response content.
2079    Boolean(bool),
2080    /// String array value accepted in elicitation response content.
2081    StringArray(Vec<String>),
2082}
2083
2084impl From<String> for ElicitationContentValue {
2085    fn from(value: String) -> Self {
2086        Self::String(value)
2087    }
2088}
2089
2090impl From<&str> for ElicitationContentValue {
2091    fn from(value: &str) -> Self {
2092        Self::String(value.to_string())
2093    }
2094}
2095
2096impl From<i64> for ElicitationContentValue {
2097    fn from(value: i64) -> Self {
2098        Self::Integer(value)
2099    }
2100}
2101
2102impl From<i32> for ElicitationContentValue {
2103    fn from(value: i32) -> Self {
2104        Self::Integer(i64::from(value))
2105    }
2106}
2107
2108impl From<f64> for ElicitationContentValue {
2109    fn from(value: f64) -> Self {
2110        Self::Number(value)
2111    }
2112}
2113
2114impl From<bool> for ElicitationContentValue {
2115    fn from(value: bool) -> Self {
2116        Self::Boolean(value)
2117    }
2118}
2119
2120impl From<Vec<String>> for ElicitationContentValue {
2121    fn from(value: Vec<String>) -> Self {
2122        Self::StringArray(value)
2123    }
2124}
2125
2126impl From<Vec<&str>> for ElicitationContentValue {
2127    fn from(value: Vec<&str>) -> Self {
2128        Self::StringArray(value.into_iter().map(str::to_string).collect())
2129    }
2130}
2131
2132impl Default for ElicitationAcceptAction {
2133    fn default() -> Self {
2134        Self::new()
2135    }
2136}
2137
2138/// Notification sent by the agent when a URL-based elicitation is complete.
2139#[serde_as]
2140#[skip_serializing_none]
2141#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2143#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2144#[serde(rename_all = "camelCase")]
2145#[non_exhaustive]
2146pub struct CompleteElicitationNotification {
2147    /// The ID of the elicitation that completed.
2148    pub elicitation_id: ElicitationId,
2149    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2150    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2151    /// these keys.
2152    ///
2153    /// Optional. Omitted and `null` are equivalent and mean no metadata.
2154    ///
2155    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2156    #[serde_as(deserialize_as = "DefaultOnError")]
2157    #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2158    #[serde(default)]
2159    #[serde(rename = "_meta")]
2160    pub meta: Option<Meta>,
2161}
2162
2163impl CompleteElicitationNotification {
2164    /// Builds [`CompleteElicitationNotification`] with the required notification fields set; optional fields start unset or empty.
2165    #[must_use]
2166    pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2167        Self {
2168            elicitation_id: elicitation_id.into(),
2169            meta: None,
2170        }
2171    }
2172
2173    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2174    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2175    /// these keys.
2176    ///
2177    /// Optional. Omitted and `null` are equivalent and mean no metadata.
2178    ///
2179    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2180    #[must_use]
2181    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2182        self.meta = meta.into_option();
2183        self
2184    }
2185}
2186
2187#[cfg(test)]
2188mod tests {
2189    use super::*;
2190    use serde_json::json;
2191
2192    #[test]
2193    fn form_mode_request_serialization() {
2194        let schema = ElicitationSchema::new().string("name", true);
2195        let req = CreateElicitationRequest::new(
2196            ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2197            "Please enter your name",
2198        );
2199
2200        let json = serde_json::to_value(&req).unwrap();
2201        assert_eq!(json["sessionId"], "sess_1");
2202        assert!(json.get("toolCallId").is_none());
2203        assert_eq!(json["mode"], "form");
2204        assert_eq!(json["message"], "Please enter your name");
2205        assert!(json["requestedSchema"].is_object());
2206        assert_eq!(json["requestedSchema"]["type"], "object");
2207        assert_eq!(
2208            json["requestedSchema"]["properties"]["name"]["type"],
2209            "string"
2210        );
2211
2212        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2213        assert_eq!(
2214            *roundtripped.scope(),
2215            ElicitationSessionScope::new("sess_1").into()
2216        );
2217        assert_eq!(roundtripped.message, "Please enter your name");
2218        assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2219    }
2220
2221    #[test]
2222    fn url_mode_request_serialization() {
2223        let req = CreateElicitationRequest::new(
2224            ElicitationUrlMode::new(
2225                ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2226                "elic_1",
2227                "https://example.com/auth",
2228            ),
2229            "Please authenticate",
2230        );
2231
2232        let json = serde_json::to_value(&req).unwrap();
2233        assert_eq!(json["sessionId"], "sess_2");
2234        assert_eq!(json["toolCallId"], "tc_1");
2235        assert_eq!(json["mode"], "url");
2236        assert_eq!(json["elicitationId"], "elic_1");
2237        assert_eq!(json["url"], "https://example.com/auth");
2238        assert_eq!(json["message"], "Please authenticate");
2239
2240        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2241        assert_eq!(
2242            *roundtripped.scope(),
2243            ElicitationSessionScope::new("sess_2")
2244                .tool_call_id("tc_1")
2245                .into()
2246        );
2247        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2248    }
2249
2250    #[test]
2251    fn response_accept_serialization() {
2252        let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2253            ElicitationAcceptAction::new().content(BTreeMap::from([(
2254                "name".to_string(),
2255                ElicitationContentValue::from("Alice"),
2256            )])),
2257        ));
2258
2259        let json = serde_json::to_value(&resp).unwrap();
2260        assert_eq!(json["action"], "accept");
2261        assert_eq!(json["content"]["name"], "Alice");
2262
2263        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2264        assert!(matches!(
2265            roundtripped.action,
2266            ElicitationAction::Accept(ElicitationAcceptAction {
2267                content: Some(_),
2268                ..
2269            })
2270        ));
2271    }
2272
2273    #[test]
2274    fn response_decline_serialization() {
2275        let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2276
2277        let json = serde_json::to_value(&resp).unwrap();
2278        assert_eq!(json["action"], "decline");
2279
2280        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2281        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2282    }
2283
2284    #[test]
2285    fn response_cancel_serialization() {
2286        let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2287
2288        let json = serde_json::to_value(&resp).unwrap();
2289        assert_eq!(json["action"], "cancel");
2290
2291        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2292        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2293    }
2294
2295    #[test]
2296    fn unknown_action_response_serialization() {
2297        let json = json!({
2298            "action": "_defer",
2299            "reason": "waiting",
2300            "retryAfterMs": 1000
2301        });
2302
2303        let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2304        let ElicitationAction::Other(other) = &resp.action else {
2305            panic!("expected unknown elicitation action");
2306        };
2307
2308        assert_eq!(other.action, "_defer");
2309        assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2310        assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2311        assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2312    }
2313
2314    #[test]
2315    fn unknown_action_does_not_hide_known_action() {
2316        assert!(
2317            serde_json::from_value::<OtherElicitationAction>(json!({
2318                "action": "accept",
2319                "content": {}
2320            }))
2321            .is_err()
2322        );
2323        assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2324    }
2325
2326    #[test]
2327    fn url_mode_request_scope_serialization() {
2328        let req = CreateElicitationRequest::new(
2329            ElicitationUrlMode::new(
2330                ElicitationRequestScope::new(RequestId::Number(42)),
2331                "elic_2",
2332                "https://example.com/setup",
2333            ),
2334            "Please complete setup",
2335        );
2336
2337        let json = serde_json::to_value(&req).unwrap();
2338        assert_eq!(json["requestId"], 42);
2339        assert!(json.get("sessionId").is_none());
2340        assert_eq!(json["mode"], "url");
2341        assert_eq!(json["elicitationId"], "elic_2");
2342        assert_eq!(json["url"], "https://example.com/setup");
2343        assert_eq!(json["message"], "Please complete setup");
2344
2345        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2346        assert_eq!(
2347            *roundtripped.scope(),
2348            ElicitationRequestScope::new(RequestId::Number(42)).into()
2349        );
2350        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2351    }
2352
2353    #[test]
2354    fn unknown_mode_request_serialization() {
2355        let json = json!({
2356            "requestId": 42,
2357            "mode": "_browser",
2358            "message": "Open a browser window",
2359            "target": "login"
2360        });
2361
2362        let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2363        let ElicitationMode::Other(other) = &req.mode else {
2364            panic!("expected unknown elicitation mode");
2365        };
2366
2367        assert_eq!(other.mode, "_browser");
2368        assert_eq!(
2369            other.scope,
2370            ElicitationRequestScope::new(RequestId::Number(42)).into()
2371        );
2372        assert_eq!(other.fields.get("target"), Some(&json!("login")));
2373        assert_eq!(
2374            *req.scope(),
2375            ElicitationRequestScope::new(RequestId::Number(42)).into()
2376        );
2377        assert_eq!(serde_json::to_value(&req).unwrap(), json);
2378    }
2379
2380    #[test]
2381    fn unknown_mode_does_not_hide_malformed_known_mode() {
2382        let missing_requested_schema = json!({
2383            "requestId": 42,
2384            "mode": "form",
2385            "message": "Enter your name"
2386        });
2387
2388        assert!(
2389            serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2390        );
2391        assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2392    }
2393
2394    #[test]
2395    fn request_scope_request_serialization() {
2396        let req = CreateElicitationRequest::new(
2397            ElicitationFormMode::new(
2398                ElicitationRequestScope::new(RequestId::Number(99)),
2399                ElicitationSchema::new().string("workspace", true),
2400            ),
2401            "Enter workspace name",
2402        );
2403
2404        let json = serde_json::to_value(&req).unwrap();
2405        assert_eq!(json["requestId"], 99);
2406        assert!(json.get("sessionId").is_none());
2407
2408        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2409        assert_eq!(
2410            *roundtripped.scope(),
2411            ElicitationRequestScope::new(RequestId::Number(99)).into()
2412        );
2413    }
2414
2415    /// `ClientResponse` is `#[serde(untagged)]` with `WriteTextFileResponse` (which has
2416    /// `#[serde(default)]`) listed first, so standalone deserialization is ambiguous.
2417    /// In practice, the RPC layer selects the correct variant based on the originating
2418    /// request method. These tests verify that serialization through `ClientResponse`
2419    /// produces the correct flattened wire format and round-trips back via the
2420    /// concrete `CreateElicitationResponse` type.
2421    #[test]
2422    fn client_response_serialization_accept() {
2423        use crate::v1::ClientResponse;
2424
2425        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2426            ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
2427                "name".to_string(),
2428                ElicitationContentValue::from("Alice"),
2429            )]))),
2430        ));
2431        let json = serde_json::to_value(&resp).unwrap();
2432        assert_eq!(json["action"], "accept");
2433        assert_eq!(json["content"]["name"], "Alice");
2434
2435        // Round-trip back through the concrete type
2436        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2437        assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2438    }
2439
2440    #[test]
2441    fn client_response_serialization_decline() {
2442        use crate::v1::ClientResponse;
2443
2444        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2445            ElicitationAction::Decline,
2446        ));
2447        let json = serde_json::to_value(&resp).unwrap();
2448        assert_eq!(json["action"], "decline");
2449
2450        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2451        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2452    }
2453
2454    #[test]
2455    fn client_response_serialization_cancel() {
2456        use crate::v1::ClientResponse;
2457
2458        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2459            ElicitationAction::Cancel,
2460        ));
2461        let json = serde_json::to_value(&resp).unwrap();
2462        assert_eq!(json["action"], "cancel");
2463
2464        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2465        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2466    }
2467
2468    /// Guard against serde regressions with the `flatten` + internally-tagged combination.
2469    /// Extra fields in the JSON must not cause deserialization failures.
2470    #[test]
2471    fn request_tolerates_extra_fields() {
2472        let json = json!({
2473            "sessionId": "sess_1",
2474            "mode": "form",
2475            "message": "Enter your name",
2476            "requestedSchema": {
2477                "type": "object",
2478                "properties": {
2479                    "name": { "type": "string", "title": "Name" }
2480                },
2481                "required": ["name"]
2482            },
2483            "unknownStringField": "hello",
2484            "unknownNumberField": 42
2485        });
2486
2487        let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2488        assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2489        assert_eq!(req.message, "Enter your name");
2490        assert!(matches!(req.mode, ElicitationMode::Form(_)));
2491    }
2492
2493    #[test]
2494    fn completion_notification_serialization() {
2495        let notif = CompleteElicitationNotification::new("elic_1");
2496
2497        let json = serde_json::to_value(&notif).unwrap();
2498        assert_eq!(json["elicitationId"], "elic_1");
2499
2500        let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2501        assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2502    }
2503
2504    #[test]
2505    fn empty_capabilities_do_not_advertise_a_mode() {
2506        let caps = ElicitationCapabilities::new();
2507        assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2508        assert!(!caps.supports_form());
2509        assert!(!caps.supports_url());
2510
2511        for value in [
2512            json!({}),
2513            json!({ "form": null }),
2514            json!({ "url": null }),
2515            json!({ "form": null, "url": null }),
2516        ] {
2517            let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2518            assert!(!caps.supports_form());
2519            assert!(!caps.supports_url());
2520        }
2521    }
2522
2523    #[test]
2524    fn capabilities_form_only() {
2525        let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2526
2527        let json = serde_json::to_value(&caps).unwrap();
2528        assert!(json["form"].is_object());
2529        assert!(json.get("url").is_none());
2530
2531        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2532        assert!(roundtripped.form.is_some());
2533        assert!(roundtripped.url.is_none());
2534        assert!(roundtripped.supports_form());
2535        assert!(!roundtripped.supports_url());
2536    }
2537
2538    #[test]
2539    fn capabilities_url_only() {
2540        let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2541
2542        let json = serde_json::to_value(&caps).unwrap();
2543        assert!(json.get("form").is_none());
2544        assert!(json["url"].is_object());
2545
2546        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2547        assert!(roundtripped.form.is_none());
2548        assert!(roundtripped.url.is_some());
2549        assert!(!roundtripped.supports_form());
2550        assert!(roundtripped.supports_url());
2551    }
2552
2553    #[test]
2554    fn capabilities_both() {
2555        let caps = ElicitationCapabilities::new()
2556            .form(ElicitationFormCapabilities::new())
2557            .url(ElicitationUrlCapabilities::new());
2558
2559        let json = serde_json::to_value(&caps).unwrap();
2560        assert!(json["form"].is_object());
2561        assert!(json["url"].is_object());
2562
2563        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2564        assert!(roundtripped.form.is_some());
2565        assert!(roundtripped.url.is_some());
2566        assert!(roundtripped.supports_form());
2567        assert!(roundtripped.supports_url());
2568    }
2569
2570    #[test]
2571    fn schema_default_sets_object_type() {
2572        let schema = ElicitationSchema::default();
2573
2574        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2575        assert!(schema.properties.is_empty());
2576
2577        let json = serde_json::to_value(&schema).unwrap();
2578        assert_eq!(json["type"], "object");
2579    }
2580
2581    #[test]
2582    fn schema_builder_serialization() {
2583        let schema = ElicitationSchema::new()
2584            .string("name", true)
2585            .email("email", true)
2586            .integer("age", 0, 150, true)
2587            .boolean("newsletter", false)
2588            .description("User registration");
2589
2590        let json = serde_json::to_value(&schema).unwrap();
2591        assert_eq!(json["type"], "object");
2592        assert_eq!(json["description"], "User registration");
2593        assert_eq!(json["properties"]["name"]["type"], "string");
2594        assert_eq!(json["properties"]["email"]["type"], "string");
2595        assert_eq!(json["properties"]["email"]["format"], "email");
2596        assert_eq!(json["properties"]["age"]["type"], "integer");
2597        assert_eq!(json["properties"]["age"]["minimum"], 0);
2598        assert_eq!(json["properties"]["age"]["maximum"], 150);
2599        assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2600
2601        let required = json["required"].as_array().unwrap();
2602        assert!(required.contains(&json!("name")));
2603        assert!(required.contains(&json!("email")));
2604        assert!(required.contains(&json!("age")));
2605        assert!(!required.contains(&json!("newsletter")));
2606
2607        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2608        assert_eq!(roundtripped.properties.len(), 4);
2609        assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2610    }
2611
2612    #[test]
2613    fn schema_string_enum_serialization() {
2614        let schema = ElicitationSchema::new().property(
2615            "color",
2616            StringPropertySchema::new().enum_values(vec![
2617                "red".into(),
2618                "green".into(),
2619                "blue".into(),
2620            ]),
2621            true,
2622        );
2623
2624        let json = serde_json::to_value(&schema).unwrap();
2625        assert_eq!(json["properties"]["color"]["type"], "string");
2626        let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2627        assert_eq!(enum_vals.len(), 3);
2628
2629        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2630        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2631        {
2632            assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2633        } else {
2634            panic!("expected String variant");
2635        }
2636    }
2637
2638    #[test]
2639    fn schema_multi_select_serialization() {
2640        let schema = ElicitationSchema::new().property(
2641            "colors",
2642            MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2643                .min_items(1)
2644                .max_items(3),
2645            false,
2646        );
2647
2648        let json = serde_json::to_value(&schema).unwrap();
2649        assert_eq!(json["properties"]["colors"]["type"], "array");
2650        assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2651        assert_eq!(json["properties"]["colors"]["minItems"], 1);
2652        assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2653
2654        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2655        let ElicitationPropertySchema::Array(array) =
2656            roundtripped.properties.get("colors").unwrap()
2657        else {
2658            panic!("expected Array variant");
2659        };
2660        let MultiSelectItems::String(items) = &array.items else {
2661            panic!("expected String multi-select items");
2662        };
2663        assert_eq!(items.values.len(), 3);
2664    }
2665
2666    #[test]
2667    fn multi_select_titled_items_keep_mcp_shape() {
2668        let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2669            "#ff0000", "Red",
2670        )]));
2671
2672        let json = serde_json::to_value(&items).unwrap();
2673        assert!(json.get("type").is_none());
2674        assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2675        assert_eq!(json["anyOf"][0]["title"], "Red");
2676
2677        let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2678        assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2679    }
2680
2681    #[test]
2682    fn multi_select_items_preserve_unknown_type() {
2683        let json = json!({
2684            "type": "_token",
2685            "format": "workspace",
2686            "anyOf": [
2687                { "const": "repo", "title": "Repository" }
2688            ]
2689        });
2690
2691        let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2692        let MultiSelectItems::Other(other) = &items else {
2693            panic!("expected unknown multi-select items");
2694        };
2695
2696        assert_eq!(other.type_, "_token");
2697        assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2698        assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2699        assert_eq!(serde_json::to_value(&items).unwrap(), json);
2700    }
2701
2702    #[test]
2703    fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2704        assert!(
2705            serde_json::from_value::<MultiSelectItems>(json!({
2706                "type": "string"
2707            }))
2708            .is_err()
2709        );
2710        assert!(
2711            serde_json::from_value::<OtherMultiSelectItems>(json!({
2712                "type": "string",
2713                "format": "workspace"
2714            }))
2715            .is_err()
2716        );
2717    }
2718
2719    #[test]
2720    fn property_schema_preserves_unknown_type() {
2721        let schema: ElicitationSchema = serde_json::from_value(json!({
2722            "type": "object",
2723            "properties": {
2724                "location": {
2725                    "type": "_location",
2726                    "title": "Location",
2727                    "precision": "city"
2728                }
2729            }
2730        }))
2731        .unwrap();
2732
2733        let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2734        else {
2735            panic!("expected unknown property schema");
2736        };
2737
2738        assert_eq!(unknown.type_, "_location");
2739        assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2740        assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2741        assert_eq!(
2742            serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2743            json!({
2744                "type": "_location",
2745                "title": "Location",
2746                "precision": "city"
2747            })
2748        );
2749    }
2750
2751    #[test]
2752    fn property_schema_unknown_does_not_hide_malformed_known_type() {
2753        assert!(
2754            serde_json::from_value::<ElicitationPropertySchema>(json!({
2755                "type": "array"
2756            }))
2757            .is_err()
2758        );
2759        assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2760    }
2761
2762    #[test]
2763    fn schema_titled_enum_serialization() {
2764        let schema = ElicitationSchema::new().property(
2765            "country",
2766            StringPropertySchema::new().one_of(vec![
2767                EnumOption::new("us", "United States").description("Use US English spelling."),
2768                EnumOption::new("uk", "United Kingdom"),
2769            ]),
2770            true,
2771        );
2772
2773        let json = serde_json::to_value(&schema).unwrap();
2774        assert_eq!(json["properties"]["country"]["type"], "string");
2775        let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2776        assert_eq!(one_of.len(), 2);
2777        assert_eq!(one_of[0]["const"], "us");
2778        assert_eq!(one_of[0]["title"], "United States");
2779        assert_eq!(one_of[0]["description"], "Use US English spelling.");
2780        assert!(one_of[1].get("description").is_none());
2781
2782        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2783        if let ElicitationPropertySchema::String(s) =
2784            roundtripped.properties.get("country").unwrap()
2785        {
2786            let one_of = s.one_of.as_ref().unwrap();
2787            assert_eq!(one_of.len(), 2);
2788            assert_eq!(
2789                one_of[0].description.as_deref(),
2790                Some("Use US English spelling.")
2791            );
2792            assert!(one_of[1].description.is_none());
2793        } else {
2794            panic!("expected String variant");
2795        }
2796    }
2797
2798    #[test]
2799    fn schema_number_property_serialization() {
2800        let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2801
2802        let json = serde_json::to_value(&schema).unwrap();
2803        assert_eq!(json["properties"]["rating"]["type"], "number");
2804        assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2805        assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2806
2807        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2808        if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2809        {
2810            assert_eq!(n.minimum, Some(0.0));
2811            assert_eq!(n.maximum, Some(5.0));
2812        } else {
2813            panic!("expected Number variant");
2814        }
2815    }
2816
2817    #[test]
2818    fn schema_string_format_serialization() {
2819        let schema = ElicitationSchema::new()
2820            .uri("website", true)
2821            .date("birthday", true)
2822            .date_time("updated_at", false);
2823
2824        let json = serde_json::to_value(&schema).unwrap();
2825        assert_eq!(json["properties"]["website"]["type"], "string");
2826        assert_eq!(json["properties"]["website"]["format"], "uri");
2827        assert_eq!(json["properties"]["birthday"]["type"], "string");
2828        assert_eq!(json["properties"]["birthday"]["format"], "date");
2829        assert_eq!(json["properties"]["updated_at"]["type"], "string");
2830        assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2831
2832        let required = json["required"].as_array().unwrap();
2833        assert!(required.contains(&json!("website")));
2834        assert!(required.contains(&json!("birthday")));
2835        assert!(!required.contains(&json!("updated_at")));
2836    }
2837
2838    #[test]
2839    fn schema_string_pattern_serialization() {
2840        let schema = ElicitationSchema::new().property(
2841            "name",
2842            StringPropertySchema::new()
2843                .min_length(1)
2844                .max_length(64)
2845                .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2846            true,
2847        );
2848
2849        let json = serde_json::to_value(&schema).unwrap();
2850        assert_eq!(json["properties"]["name"]["type"], "string");
2851        assert_eq!(
2852            json["properties"]["name"]["pattern"],
2853            "^[a-zA-Z_][a-zA-Z0-9_]*$"
2854        );
2855
2856        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2857        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2858            assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2859        } else {
2860            panic!("expected String variant");
2861        }
2862    }
2863
2864    #[test]
2865    fn schema_property_updates_required_state() {
2866        let schema = ElicitationSchema::new()
2867            .string("name", true)
2868            .email("name", false);
2869
2870        let json = serde_json::to_value(&schema).unwrap();
2871        assert!(json.get("required").is_none());
2872        assert_eq!(json["properties"]["name"]["format"], "email");
2873    }
2874
2875    #[test]
2876    fn schema_defaults_invalid_object_type() {
2877        let schema = serde_json::from_value::<ElicitationSchema>(json!({
2878            "type": "array",
2879            "properties": {
2880                "name": {
2881                    "type": "string"
2882                }
2883            }
2884        }))
2885        .unwrap();
2886
2887        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2888        assert!(schema.properties.contains_key("name"));
2889    }
2890
2891    #[test]
2892    fn titled_multi_select_items_reject_one_of() {
2893        let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2894            "oneOf": [
2895                {
2896                    "const": "red",
2897                    "title": "Red"
2898                }
2899            ]
2900        }))
2901        .unwrap_err();
2902
2903        assert!(err.to_string().contains("missing field `anyOf`"));
2904    }
2905
2906    #[test]
2907    fn response_accept_rejects_non_object_content() {
2908        assert!(
2909            serde_json::from_value::<CreateElicitationResponse>(json!({
2910                "action": "accept",
2911                "content": "Alice"
2912            }))
2913            .is_err()
2914        );
2915    }
2916
2917    #[test]
2918    fn response_accept_treats_null_and_omitted_content_equally() {
2919        for value in [
2920            json!({ "action": "accept" }),
2921            json!({
2922                "action": "accept",
2923                "content": null
2924            }),
2925        ] {
2926            let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2927            let ElicitationAction::Accept(accept) = response.action else {
2928                panic!("expected accept action");
2929            };
2930            assert!(accept.content.is_none());
2931        }
2932    }
2933
2934    #[test]
2935    fn response_accept_rejects_nested_object_content() {
2936        assert!(
2937            serde_json::from_value::<CreateElicitationResponse>(json!({
2938                "action": "accept",
2939                "content": {
2940                    "profile": {
2941                        "name": "Alice"
2942                    }
2943                }
2944            }))
2945            .is_err()
2946        );
2947    }
2948
2949    #[test]
2950    fn response_accept_allows_primitive_and_string_array_content() {
2951        let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2952            ElicitationAcceptAction::new().content(BTreeMap::from([
2953                ("name".to_string(), ElicitationContentValue::from("Alice")),
2954                ("age".to_string(), ElicitationContentValue::from(30_i32)),
2955                ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2956                (
2957                    "subscribed".to_string(),
2958                    ElicitationContentValue::from(true),
2959                ),
2960                (
2961                    "tags".to_string(),
2962                    ElicitationContentValue::from(vec!["rust", "acp"]),
2963                ),
2964            ])),
2965        ));
2966
2967        let json = serde_json::to_value(&response).unwrap();
2968        assert_eq!(json["action"], "accept");
2969        assert_eq!(json["content"]["name"], "Alice");
2970        assert_eq!(json["content"]["age"], 30);
2971        assert_eq!(json["content"]["score"], 9.5);
2972        assert_eq!(json["content"]["subscribed"], true);
2973        assert_eq!(json["content"]["tags"][0], "rust");
2974        assert_eq!(json["content"]["tags"][1], "acp");
2975    }
2976}