Skip to main content

agent_client_protocol_schema/v1/
elicitation.rs

1//! Elicitation types for structured user input.
2//!
3//! **UNSTABLE**: This module is not part of the spec yet, and may be removed or changed at any point.
4//!
5//! This module defines the types used for agent-initiated elicitation,
6//! where the agent requests structured input from the user via forms or URLs.
7
8use std::{collections::BTreeMap, sync::Arc};
9
10use derive_more::{Display, From};
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
14
15use crate::IntoOption;
16use crate::SkipListener;
17
18use super::{
19    ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, Meta, RequestId, SessionId,
20    ToolCallId,
21};
22
23/// **UNSTABLE**
24///
25/// This capability is not part of the spec yet, and may be removed or changed at any point.
26///
27/// Unique identifier for an elicitation.
28#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
29#[serde(transparent)]
30#[from(Arc<str>, String, &'static str)]
31#[non_exhaustive]
32pub struct ElicitationId(pub Arc<str>);
33
34impl ElicitationId {
35    /// Wraps a protocol string as a typed [`ElicitationId`].
36    #[must_use]
37    pub fn new(id: impl Into<Arc<str>>) -> Self {
38        Self(id.into())
39    }
40}
41
42/// String format types for string properties in elicitation schemas.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
44#[serde(rename_all = "kebab-case")]
45#[non_exhaustive]
46pub enum StringFormat {
47    /// Email address format.
48    Email,
49    /// URI format.
50    Uri,
51    /// Date format (YYYY-MM-DD).
52    Date,
53    /// Date-time format (ISO 8601).
54    DateTime,
55}
56
57/// Type discriminator for elicitation schemas.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
59#[serde(rename_all = "snake_case")]
60#[non_exhaustive]
61pub enum ElicitationSchemaType {
62    /// Object schema type.
63    #[default]
64    Object,
65}
66
67/// A titled enum option with a const value and human-readable title.
68#[serde_as]
69#[skip_serializing_none]
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
71#[non_exhaustive]
72pub struct EnumOption {
73    /// The constant value for this option.
74    #[serde(rename = "const")]
75    pub value: String,
76    /// Human-readable title for this option.
77    pub title: String,
78    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
79    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
80    /// these keys.
81    ///
82    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
83    #[serde_as(deserialize_as = "DefaultOnError")]
84    #[schemars(extend("x-deserialize-default-on-error" = true))]
85    #[serde(default)]
86    #[serde(rename = "_meta")]
87    pub meta: Option<Meta>,
88}
89
90impl EnumOption {
91    /// Create a new enum option.
92    #[must_use]
93    pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
94        Self {
95            value: value.into(),
96            title: title.into(),
97            meta: None,
98        }
99    }
100
101    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
102    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
103    /// these keys.
104    ///
105    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
106    #[must_use]
107    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
108        self.meta = meta.into_option();
109        self
110    }
111}
112
113/// Schema for string properties in an elicitation form.
114///
115/// When `enum` or `oneOf` is set, this represents a single-select enum
116/// with `"type": "string"`.
117#[serde_as]
118#[skip_serializing_none]
119#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
120#[serde(rename_all = "camelCase")]
121#[non_exhaustive]
122pub struct StringPropertySchema {
123    /// Optional title for the property.
124    #[serde_as(deserialize_as = "DefaultOnError")]
125    #[schemars(extend("x-deserialize-default-on-error" = true))]
126    #[serde(default)]
127    pub title: Option<String>,
128    /// Human-readable description.
129    #[serde_as(deserialize_as = "DefaultOnError")]
130    #[schemars(extend("x-deserialize-default-on-error" = true))]
131    #[serde(default)]
132    pub description: Option<String>,
133    /// Minimum string length.
134    #[serde_as(deserialize_as = "DefaultOnError")]
135    #[schemars(extend("x-deserialize-default-on-error" = true))]
136    #[serde(default)]
137    pub min_length: Option<u32>,
138    /// Maximum string length.
139    #[serde_as(deserialize_as = "DefaultOnError")]
140    #[schemars(extend("x-deserialize-default-on-error" = true))]
141    #[serde(default)]
142    pub max_length: Option<u32>,
143    /// Pattern the string must match.
144    #[serde_as(deserialize_as = "DefaultOnError")]
145    #[schemars(extend("x-deserialize-default-on-error" = true))]
146    #[serde(default)]
147    pub pattern: Option<String>,
148    /// String format.
149    #[serde_as(deserialize_as = "DefaultOnError")]
150    #[schemars(extend("x-deserialize-default-on-error" = true))]
151    #[serde(default)]
152    pub format: Option<StringFormat>,
153    /// Default value.
154    #[serde_as(deserialize_as = "DefaultOnError")]
155    #[schemars(extend("x-deserialize-default-on-error" = true))]
156    #[serde(default)]
157    pub default: Option<String>,
158    /// Enum values for untitled single-select enums.
159    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
160    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
161    #[serde(default)]
162    #[serde(rename = "enum")]
163    pub enum_values: Option<Vec<String>>,
164    /// Titled enum options for titled single-select enums.
165    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
166    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
167    #[serde(default)]
168    #[serde(rename = "oneOf")]
169    pub one_of: Option<Vec<EnumOption>>,
170    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
171    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
172    /// these keys.
173    ///
174    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
175    #[serde_as(deserialize_as = "DefaultOnError")]
176    #[schemars(extend("x-deserialize-default-on-error" = true))]
177    #[serde(default)]
178    #[serde(rename = "_meta")]
179    pub meta: Option<Meta>,
180}
181
182impl StringPropertySchema {
183    /// Create a new string property schema.
184    #[must_use]
185    pub fn new() -> Self {
186        Self::default()
187    }
188
189    /// Create an email string property schema.
190    #[must_use]
191    pub fn email() -> Self {
192        Self {
193            format: Some(StringFormat::Email),
194            ..Default::default()
195        }
196    }
197
198    /// Create a URI string property schema.
199    #[must_use]
200    pub fn uri() -> Self {
201        Self {
202            format: Some(StringFormat::Uri),
203            ..Default::default()
204        }
205    }
206
207    /// Create a date string property schema.
208    #[must_use]
209    pub fn date() -> Self {
210        Self {
211            format: Some(StringFormat::Date),
212            ..Default::default()
213        }
214    }
215
216    /// Create a date-time string property schema.
217    #[must_use]
218    pub fn date_time() -> Self {
219        Self {
220            format: Some(StringFormat::DateTime),
221            ..Default::default()
222        }
223    }
224
225    /// Optional title for the property.
226    #[must_use]
227    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
228        self.title = title.into_option();
229        self
230    }
231
232    /// Human-readable description.
233    #[must_use]
234    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
235        self.description = description.into_option();
236        self
237    }
238
239    /// Minimum string length.
240    #[must_use]
241    pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
242        self.min_length = min_length.into_option();
243        self
244    }
245
246    /// Maximum string length.
247    #[must_use]
248    pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
249        self.max_length = max_length.into_option();
250        self
251    }
252
253    /// Pattern the string must match.
254    #[must_use]
255    pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
256        self.pattern = pattern.into_option();
257        self
258    }
259
260    /// String format.
261    #[must_use]
262    pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
263        self.format = format.into_option();
264        self
265    }
266
267    /// Default value.
268    #[must_use]
269    pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
270        self.default = default.into_option();
271        self
272    }
273
274    /// Enum values for untitled single-select enums.
275    #[must_use]
276    pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
277        self.enum_values = enum_values.into_option();
278        self
279    }
280
281    /// Titled enum options for titled single-select enums.
282    #[must_use]
283    pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
284        self.one_of = one_of.into_option();
285        self
286    }
287
288    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
289    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
290    /// these keys.
291    ///
292    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
293    #[must_use]
294    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
295        self.meta = meta.into_option();
296        self
297    }
298}
299
300/// Schema for number (floating-point) properties in an elicitation form.
301#[serde_as]
302#[skip_serializing_none]
303#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
304#[serde(rename_all = "camelCase")]
305#[non_exhaustive]
306pub struct NumberPropertySchema {
307    /// Optional title for the property.
308    #[serde_as(deserialize_as = "DefaultOnError")]
309    #[schemars(extend("x-deserialize-default-on-error" = true))]
310    #[serde(default)]
311    pub title: Option<String>,
312    /// Human-readable description.
313    #[serde_as(deserialize_as = "DefaultOnError")]
314    #[schemars(extend("x-deserialize-default-on-error" = true))]
315    #[serde(default)]
316    pub description: Option<String>,
317    /// Minimum value (inclusive).
318    #[serde_as(deserialize_as = "DefaultOnError")]
319    #[schemars(extend("x-deserialize-default-on-error" = true))]
320    #[serde(default)]
321    pub minimum: Option<f64>,
322    /// Maximum value (inclusive).
323    #[serde_as(deserialize_as = "DefaultOnError")]
324    #[schemars(extend("x-deserialize-default-on-error" = true))]
325    #[serde(default)]
326    pub maximum: Option<f64>,
327    /// Default value.
328    #[serde_as(deserialize_as = "DefaultOnError")]
329    #[schemars(extend("x-deserialize-default-on-error" = true))]
330    #[serde(default)]
331    pub default: Option<f64>,
332    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
333    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
334    /// these keys.
335    ///
336    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
337    #[serde_as(deserialize_as = "DefaultOnError")]
338    #[schemars(extend("x-deserialize-default-on-error" = true))]
339    #[serde(default)]
340    #[serde(rename = "_meta")]
341    pub meta: Option<Meta>,
342}
343
344impl NumberPropertySchema {
345    /// Create a new number property schema.
346    #[must_use]
347    pub fn new() -> Self {
348        Self::default()
349    }
350
351    /// Optional title for the property.
352    #[must_use]
353    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
354        self.title = title.into_option();
355        self
356    }
357
358    /// Human-readable description.
359    #[must_use]
360    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
361        self.description = description.into_option();
362        self
363    }
364
365    /// Minimum value (inclusive).
366    #[must_use]
367    pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
368        self.minimum = minimum.into_option();
369        self
370    }
371
372    /// Maximum value (inclusive).
373    #[must_use]
374    pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
375        self.maximum = maximum.into_option();
376        self
377    }
378
379    /// Default value.
380    #[must_use]
381    pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
382        self.default = default.into_option();
383        self
384    }
385
386    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
387    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
388    /// these keys.
389    ///
390    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
391    #[must_use]
392    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
393        self.meta = meta.into_option();
394        self
395    }
396}
397
398/// Schema for integer properties in an elicitation form.
399#[serde_as]
400#[skip_serializing_none]
401#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
402#[serde(rename_all = "camelCase")]
403#[non_exhaustive]
404pub struct IntegerPropertySchema {
405    /// Optional title for the property.
406    #[serde_as(deserialize_as = "DefaultOnError")]
407    #[schemars(extend("x-deserialize-default-on-error" = true))]
408    #[serde(default)]
409    pub title: Option<String>,
410    /// Human-readable description.
411    #[serde_as(deserialize_as = "DefaultOnError")]
412    #[schemars(extend("x-deserialize-default-on-error" = true))]
413    #[serde(default)]
414    pub description: Option<String>,
415    /// Minimum value (inclusive).
416    #[serde_as(deserialize_as = "DefaultOnError")]
417    #[schemars(extend("x-deserialize-default-on-error" = true))]
418    #[serde(default)]
419    pub minimum: Option<i64>,
420    /// Maximum value (inclusive).
421    #[serde_as(deserialize_as = "DefaultOnError")]
422    #[schemars(extend("x-deserialize-default-on-error" = true))]
423    #[serde(default)]
424    pub maximum: Option<i64>,
425    /// Default value.
426    #[serde_as(deserialize_as = "DefaultOnError")]
427    #[schemars(extend("x-deserialize-default-on-error" = true))]
428    #[serde(default)]
429    pub default: Option<i64>,
430    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
431    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
432    /// these keys.
433    ///
434    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
435    #[serde_as(deserialize_as = "DefaultOnError")]
436    #[schemars(extend("x-deserialize-default-on-error" = true))]
437    #[serde(default)]
438    #[serde(rename = "_meta")]
439    pub meta: Option<Meta>,
440}
441
442impl IntegerPropertySchema {
443    /// Create a new integer property schema.
444    #[must_use]
445    pub fn new() -> Self {
446        Self::default()
447    }
448
449    /// Optional title for the property.
450    #[must_use]
451    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
452        self.title = title.into_option();
453        self
454    }
455
456    /// Human-readable description.
457    #[must_use]
458    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
459        self.description = description.into_option();
460        self
461    }
462
463    /// Minimum value (inclusive).
464    #[must_use]
465    pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
466        self.minimum = minimum.into_option();
467        self
468    }
469
470    /// Maximum value (inclusive).
471    #[must_use]
472    pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
473        self.maximum = maximum.into_option();
474        self
475    }
476
477    /// Default value.
478    #[must_use]
479    pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
480        self.default = default.into_option();
481        self
482    }
483
484    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
485    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
486    /// these keys.
487    ///
488    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
489    #[must_use]
490    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
491        self.meta = meta.into_option();
492        self
493    }
494}
495
496/// Schema for boolean properties in an elicitation form.
497#[serde_as]
498#[skip_serializing_none]
499#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
500#[serde(rename_all = "camelCase")]
501#[non_exhaustive]
502pub struct BooleanPropertySchema {
503    /// Optional title for the property.
504    #[serde_as(deserialize_as = "DefaultOnError")]
505    #[schemars(extend("x-deserialize-default-on-error" = true))]
506    #[serde(default)]
507    pub title: Option<String>,
508    /// Human-readable description.
509    #[serde_as(deserialize_as = "DefaultOnError")]
510    #[schemars(extend("x-deserialize-default-on-error" = true))]
511    #[serde(default)]
512    pub description: Option<String>,
513    /// Default value.
514    #[serde_as(deserialize_as = "DefaultOnError")]
515    #[schemars(extend("x-deserialize-default-on-error" = true))]
516    #[serde(default)]
517    pub default: Option<bool>,
518    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
519    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
520    /// these keys.
521    ///
522    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
523    #[serde_as(deserialize_as = "DefaultOnError")]
524    #[schemars(extend("x-deserialize-default-on-error" = true))]
525    #[serde(default)]
526    #[serde(rename = "_meta")]
527    pub meta: Option<Meta>,
528}
529
530impl BooleanPropertySchema {
531    /// Create a new boolean property schema.
532    #[must_use]
533    pub fn new() -> Self {
534        Self::default()
535    }
536
537    /// Optional title for the property.
538    #[must_use]
539    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
540        self.title = title.into_option();
541        self
542    }
543
544    /// Human-readable description.
545    #[must_use]
546    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
547        self.description = description.into_option();
548        self
549    }
550
551    /// Default value.
552    #[must_use]
553    pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
554        self.default = default.into_option();
555        self
556    }
557
558    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
559    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
560    /// these keys.
561    ///
562    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
563    #[must_use]
564    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
565        self.meta = meta.into_option();
566        self
567    }
568}
569
570/// Items definition for untitled multi-select enum properties.
571#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
572#[serde(rename_all = "snake_case")]
573#[non_exhaustive]
574pub enum ElicitationStringType {
575    /// String schema type.
576    #[default]
577    String,
578}
579
580/// Items definition for untitled multi-select enum properties.
581#[serde_as]
582#[skip_serializing_none]
583#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
584#[non_exhaustive]
585pub struct UntitledMultiSelectItems {
586    /// Item type discriminator. Must be `"string"`.
587    #[serde(rename = "type")]
588    pub type_: ElicitationStringType,
589    /// Allowed enum values.
590    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
591    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
592    #[serde(rename = "enum")]
593    pub values: Vec<String>,
594    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
595    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
596    /// these keys.
597    ///
598    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
599    #[serde_as(deserialize_as = "DefaultOnError")]
600    #[schemars(extend("x-deserialize-default-on-error" = true))]
601    #[serde(default)]
602    #[serde(rename = "_meta")]
603    pub meta: Option<Meta>,
604}
605
606impl UntitledMultiSelectItems {
607    /// Create new titled multi-select items.
608    #[must_use]
609    pub fn new(type_: ElicitationStringType, values: Vec<String>) -> Self {
610        Self {
611            type_,
612            values,
613            meta: None,
614        }
615    }
616
617    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
618    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
619    /// these keys.
620    ///
621    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
622    #[must_use]
623    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
624        self.meta = meta.into_option();
625        self
626    }
627}
628
629/// Items definition for titled multi-select enum properties.
630#[serde_as]
631#[skip_serializing_none]
632#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
633#[non_exhaustive]
634pub struct TitledMultiSelectItems {
635    /// Titled enum options.
636    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
637    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
638    #[serde(rename = "anyOf")]
639    pub options: Vec<EnumOption>,
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    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
645    #[serde_as(deserialize_as = "DefaultOnError")]
646    #[schemars(extend("x-deserialize-default-on-error" = true))]
647    #[serde(default)]
648    #[serde(rename = "_meta")]
649    pub meta: Option<Meta>,
650}
651
652impl TitledMultiSelectItems {
653    /// Create new titled multi-select items.
654    #[must_use]
655    pub fn new(options: Vec<EnumOption>) -> Self {
656        Self {
657            options,
658            meta: None,
659        }
660    }
661
662    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
663    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
664    /// these keys.
665    ///
666    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
667    #[must_use]
668    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
669        self.meta = meta.into_option();
670        self
671    }
672}
673
674/// Items for a multi-select (array) property schema.
675#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
676#[serde(untagged)]
677#[non_exhaustive]
678pub enum MultiSelectItems {
679    /// Untitled multi-select items with plain string values.
680    Untitled(UntitledMultiSelectItems),
681    /// Titled multi-select items with human-readable labels.
682    Titled(TitledMultiSelectItems),
683}
684
685/// Schema for multi-select (array) properties in an elicitation form.
686#[serde_as]
687#[skip_serializing_none]
688#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
689#[serde(rename_all = "camelCase")]
690#[non_exhaustive]
691pub struct MultiSelectPropertySchema {
692    /// Optional title for the property.
693    #[serde_as(deserialize_as = "DefaultOnError")]
694    #[schemars(extend("x-deserialize-default-on-error" = true))]
695    #[serde(default)]
696    pub title: Option<String>,
697    /// Human-readable description.
698    #[serde_as(deserialize_as = "DefaultOnError")]
699    #[schemars(extend("x-deserialize-default-on-error" = true))]
700    #[serde(default)]
701    pub description: Option<String>,
702    /// Minimum number of items to select.
703    #[serde_as(deserialize_as = "DefaultOnError")]
704    #[schemars(extend("x-deserialize-default-on-error" = true))]
705    #[serde(default)]
706    pub min_items: Option<u64>,
707    /// Maximum number of items to select.
708    #[serde_as(deserialize_as = "DefaultOnError")]
709    #[schemars(extend("x-deserialize-default-on-error" = true))]
710    #[serde(default)]
711    pub max_items: Option<u64>,
712    /// The items definition describing allowed values.
713    pub items: MultiSelectItems,
714    /// Default selected values.
715    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
716    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
717    #[serde(default)]
718    pub default: Option<Vec<String>>,
719    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
720    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
721    /// these keys.
722    ///
723    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
724    #[serde_as(deserialize_as = "DefaultOnError")]
725    #[schemars(extend("x-deserialize-default-on-error" = true))]
726    #[serde(default)]
727    #[serde(rename = "_meta")]
728    pub meta: Option<Meta>,
729}
730
731impl MultiSelectPropertySchema {
732    /// Create a new untitled multi-select property schema.
733    #[must_use]
734    pub fn new(values: Vec<String>) -> Self {
735        Self {
736            title: None,
737            description: None,
738            min_items: None,
739            max_items: None,
740            items: MultiSelectItems::Untitled(UntitledMultiSelectItems::new(
741                ElicitationStringType::String,
742                values,
743            )),
744            default: None,
745            meta: None,
746        }
747    }
748
749    /// Create a new titled multi-select property schema.
750    #[must_use]
751    pub fn titled(options: Vec<EnumOption>) -> Self {
752        Self {
753            title: None,
754            description: None,
755            min_items: None,
756            max_items: None,
757            items: MultiSelectItems::Titled(TitledMultiSelectItems::new(options)),
758            default: None,
759            meta: None,
760        }
761    }
762
763    /// Optional title for the property.
764    #[must_use]
765    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
766        self.title = title.into_option();
767        self
768    }
769
770    /// Human-readable description.
771    #[must_use]
772    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
773        self.description = description.into_option();
774        self
775    }
776
777    /// Minimum number of items to select.
778    #[must_use]
779    pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
780        self.min_items = min_items.into_option();
781        self
782    }
783
784    /// Maximum number of items to select.
785    #[must_use]
786    pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
787        self.max_items = max_items.into_option();
788        self
789    }
790
791    /// Default selected values.
792    #[must_use]
793    pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
794        self.default = default.into_option();
795        self
796    }
797
798    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
799    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
800    /// these keys.
801    ///
802    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
803    #[must_use]
804    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
805        self.meta = meta.into_option();
806        self
807    }
808}
809
810/// Property schema for elicitation form fields.
811///
812/// Each variant corresponds to a JSON Schema `"type"` value.
813/// Single-select enums use the `String` variant with `enum` or `oneOf` set.
814/// Multi-select enums use the `Array` variant.
815#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
816#[serde(tag = "type", rename_all = "snake_case")]
817#[schemars(extend("discriminator" = {"propertyName": "type"}))]
818#[non_exhaustive]
819pub enum ElicitationPropertySchema {
820    /// String property (or single-select enum when `enum`/`oneOf` is set).
821    String(StringPropertySchema),
822    /// Number (floating-point) property.
823    Number(NumberPropertySchema),
824    /// Integer property.
825    Integer(IntegerPropertySchema),
826    /// Boolean property.
827    Boolean(BooleanPropertySchema),
828    /// Multi-select array property.
829    Array(MultiSelectPropertySchema),
830}
831
832impl From<StringPropertySchema> for ElicitationPropertySchema {
833    fn from(schema: StringPropertySchema) -> Self {
834        Self::String(schema)
835    }
836}
837
838impl From<NumberPropertySchema> for ElicitationPropertySchema {
839    fn from(schema: NumberPropertySchema) -> Self {
840        Self::Number(schema)
841    }
842}
843
844impl From<IntegerPropertySchema> for ElicitationPropertySchema {
845    fn from(schema: IntegerPropertySchema) -> Self {
846        Self::Integer(schema)
847    }
848}
849
850impl From<BooleanPropertySchema> for ElicitationPropertySchema {
851    fn from(schema: BooleanPropertySchema) -> Self {
852        Self::Boolean(schema)
853    }
854}
855
856impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
857    fn from(schema: MultiSelectPropertySchema) -> Self {
858        Self::Array(schema)
859    }
860}
861
862fn default_object_type() -> ElicitationSchemaType {
863    ElicitationSchemaType::Object
864}
865
866/// Type-safe elicitation schema for requesting structured user input.
867///
868/// This represents a JSON Schema object with primitive-typed properties,
869/// as required by the elicitation specification.
870#[serde_as]
871#[skip_serializing_none]
872#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
873#[serde(rename_all = "camelCase")]
874#[non_exhaustive]
875pub struct ElicitationSchema {
876    /// Type discriminator. Always `"object"`.
877    #[serde_as(deserialize_as = "DefaultOnError")]
878    #[schemars(extend("x-deserialize-default-on-error" = true))]
879    #[serde(rename = "type", default = "default_object_type")]
880    pub type_: ElicitationSchemaType,
881    /// Optional title for the schema.
882    #[serde_as(deserialize_as = "DefaultOnError")]
883    #[schemars(extend("x-deserialize-default-on-error" = true))]
884    #[serde(default)]
885    pub title: Option<String>,
886    /// Property definitions (must be primitive types).
887    #[serde_as(deserialize_as = "DefaultOnError")]
888    #[schemars(extend("x-deserialize-default-on-error" = true))]
889    #[serde(default)]
890    pub properties: BTreeMap<String, ElicitationPropertySchema>,
891    /// List of required property names.
892    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
893    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
894    #[serde(default)]
895    pub required: Option<Vec<String>>,
896    /// Optional description of what this schema represents.
897    #[serde_as(deserialize_as = "DefaultOnError")]
898    #[schemars(extend("x-deserialize-default-on-error" = true))]
899    #[serde(default)]
900    pub description: Option<String>,
901    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
902    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
903    /// these keys.
904    ///
905    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
906    #[serde_as(deserialize_as = "DefaultOnError")]
907    #[schemars(extend("x-deserialize-default-on-error" = true))]
908    #[serde(default)]
909    #[serde(rename = "_meta")]
910    pub meta: Option<Meta>,
911}
912
913impl Default for ElicitationSchema {
914    fn default() -> Self {
915        Self {
916            type_: default_object_type(),
917            title: None,
918            properties: BTreeMap::new(),
919            required: None,
920            description: None,
921            meta: None,
922        }
923    }
924}
925
926impl ElicitationSchema {
927    /// Create a new empty elicitation schema.
928    #[must_use]
929    pub fn new() -> Self {
930        Self::default()
931    }
932
933    /// Optional title for the schema.
934    #[must_use]
935    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
936        self.title = title.into_option();
937        self
938    }
939
940    /// Optional description of what this schema represents.
941    #[must_use]
942    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
943        self.description = description.into_option();
944        self
945    }
946
947    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
948    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
949    /// these keys.
950    ///
951    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
952    #[must_use]
953    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
954        self.meta = meta.into_option();
955        self
956    }
957
958    /// Add a property to the schema.
959    #[must_use]
960    pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
961    where
962        S: Into<ElicitationPropertySchema>,
963    {
964        let name = name.into();
965        self.properties.insert(name.clone(), schema.into());
966
967        if required {
968            let required_fields = self.required.get_or_insert_with(Vec::new);
969            if !required_fields.contains(&name) {
970                required_fields.push(name);
971            }
972        } else if let Some(required_fields) = &mut self.required {
973            required_fields.retain(|field| field != &name);
974
975            if required_fields.is_empty() {
976                self.required = None;
977            }
978        }
979
980        self
981    }
982
983    /// Add a string property.
984    #[must_use]
985    pub fn string(self, name: impl Into<String>, required: bool) -> Self {
986        self.property(name, StringPropertySchema::new(), required)
987    }
988
989    /// Add an email property.
990    #[must_use]
991    pub fn email(self, name: impl Into<String>, required: bool) -> Self {
992        self.property(name, StringPropertySchema::email(), required)
993    }
994
995    /// Add a URI property.
996    #[must_use]
997    pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
998        self.property(name, StringPropertySchema::uri(), required)
999    }
1000
1001    /// Add a date property.
1002    #[must_use]
1003    pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1004        self.property(name, StringPropertySchema::date(), required)
1005    }
1006
1007    /// Add a date-time property.
1008    #[must_use]
1009    pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1010        self.property(name, StringPropertySchema::date_time(), required)
1011    }
1012
1013    /// Add a number property with range.
1014    #[must_use]
1015    pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1016        self.property(
1017            name,
1018            NumberPropertySchema::new().minimum(min).maximum(max),
1019            required,
1020        )
1021    }
1022
1023    /// Add an integer property with range.
1024    #[must_use]
1025    pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1026        self.property(
1027            name,
1028            IntegerPropertySchema::new().minimum(min).maximum(max),
1029            required,
1030        )
1031    }
1032
1033    /// Add a boolean property.
1034    #[must_use]
1035    pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1036        self.property(name, BooleanPropertySchema::new(), required)
1037    }
1038}
1039
1040/// **UNSTABLE**
1041///
1042/// This capability is not part of the spec yet, and may be removed or changed at any point.
1043///
1044/// Elicitation capabilities supported by the client.
1045#[serde_as]
1046#[skip_serializing_none]
1047#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1048#[serde(rename_all = "camelCase")]
1049#[non_exhaustive]
1050pub struct ElicitationCapabilities {
1051    /// Whether the client supports form-based elicitation.
1052    #[serde_as(deserialize_as = "DefaultOnError")]
1053    #[schemars(extend("x-deserialize-default-on-error" = true))]
1054    #[serde(default)]
1055    pub form: Option<ElicitationFormCapabilities>,
1056    /// Whether the client supports URL-based elicitation.
1057    #[serde_as(deserialize_as = "DefaultOnError")]
1058    #[schemars(extend("x-deserialize-default-on-error" = true))]
1059    #[serde(default)]
1060    pub url: Option<ElicitationUrlCapabilities>,
1061    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1062    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1063    /// these keys.
1064    ///
1065    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1066    #[serde_as(deserialize_as = "DefaultOnError")]
1067    #[schemars(extend("x-deserialize-default-on-error" = true))]
1068    #[serde(default)]
1069    #[serde(rename = "_meta")]
1070    pub meta: Option<Meta>,
1071}
1072
1073impl ElicitationCapabilities {
1074    /// Builds an empty [`ElicitationCapabilities`]; use builder methods to advertise supported sub-capabilities.
1075    #[must_use]
1076    pub fn new() -> Self {
1077        Self::default()
1078    }
1079
1080    /// Whether the client supports form-based elicitation.
1081    #[must_use]
1082    pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1083        self.form = form.into_option();
1084        self
1085    }
1086
1087    /// Whether the client supports URL-based elicitation.
1088    #[must_use]
1089    pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1090        self.url = url.into_option();
1091        self
1092    }
1093
1094    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1095    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1096    /// these keys.
1097    ///
1098    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1099    #[must_use]
1100    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1101        self.meta = meta.into_option();
1102        self
1103    }
1104}
1105
1106/// **UNSTABLE**
1107///
1108/// This capability is not part of the spec yet, and may be removed or changed at any point.
1109///
1110/// Form-based elicitation capabilities.
1111#[serde_as]
1112#[skip_serializing_none]
1113#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1114#[serde(rename_all = "camelCase")]
1115#[non_exhaustive]
1116pub struct ElicitationFormCapabilities {
1117    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1118    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1119    /// these keys.
1120    ///
1121    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1122    #[serde_as(deserialize_as = "DefaultOnError")]
1123    #[schemars(extend("x-deserialize-default-on-error" = true))]
1124    #[serde(default)]
1125    #[serde(rename = "_meta")]
1126    pub meta: Option<Meta>,
1127}
1128
1129impl ElicitationFormCapabilities {
1130    /// Builds an empty [`ElicitationFormCapabilities`]; use builder methods to advertise supported sub-capabilities.
1131    #[must_use]
1132    pub fn new() -> Self {
1133        Self::default()
1134    }
1135
1136    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1137    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1138    /// these keys.
1139    ///
1140    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1141    #[must_use]
1142    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1143        self.meta = meta.into_option();
1144        self
1145    }
1146}
1147
1148/// **UNSTABLE**
1149///
1150/// This capability is not part of the spec yet, and may be removed or changed at any point.
1151///
1152/// URL-based elicitation capabilities.
1153#[serde_as]
1154#[skip_serializing_none]
1155#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1156#[serde(rename_all = "camelCase")]
1157#[non_exhaustive]
1158pub struct ElicitationUrlCapabilities {
1159    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1160    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1161    /// these keys.
1162    ///
1163    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1164    #[serde_as(deserialize_as = "DefaultOnError")]
1165    #[schemars(extend("x-deserialize-default-on-error" = true))]
1166    #[serde(default)]
1167    #[serde(rename = "_meta")]
1168    pub meta: Option<Meta>,
1169}
1170
1171impl ElicitationUrlCapabilities {
1172    /// Builds an empty [`ElicitationUrlCapabilities`]; use builder methods to advertise supported sub-capabilities.
1173    #[must_use]
1174    pub fn new() -> Self {
1175        Self::default()
1176    }
1177
1178    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1179    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1180    /// these keys.
1181    ///
1182    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1183    #[must_use]
1184    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1185        self.meta = meta.into_option();
1186        self
1187    }
1188}
1189
1190/// **UNSTABLE**
1191///
1192/// This capability is not part of the spec yet, and may be removed or changed at any point.
1193///
1194/// The scope of an elicitation request, determining what context it's tied to.
1195#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1196#[serde(untagged)]
1197#[non_exhaustive]
1198pub enum ElicitationScope {
1199    /// Tied to a session, optionally to a specific tool call within that session.
1200    Session(ElicitationSessionScope),
1201    /// Tied to a specific JSON-RPC request outside of a session
1202    /// (e.g., during auth/configuration phases before any session is started).
1203    Request(ElicitationRequestScope),
1204}
1205
1206/// **UNSTABLE**
1207///
1208/// This capability is not part of the spec yet, and may be removed or changed at any point.
1209///
1210/// Session-scoped elicitation, optionally tied to a specific tool call.
1211///
1212/// When `tool_call_id` is set, the elicitation is tied to a specific tool call.
1213/// This is useful when an agent receives an elicitation from an MCP server
1214/// during a tool call and needs to redirect it to the user.
1215#[serde_as]
1216#[skip_serializing_none]
1217#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1218#[serde(rename_all = "camelCase")]
1219#[non_exhaustive]
1220pub struct ElicitationSessionScope {
1221    /// The session this elicitation is tied to.
1222    pub session_id: SessionId,
1223    /// Optional tool call within the session.
1224    #[serde_as(deserialize_as = "DefaultOnError")]
1225    #[schemars(extend("x-deserialize-default-on-error" = true))]
1226    #[serde(default)]
1227    pub tool_call_id: Option<ToolCallId>,
1228}
1229
1230impl ElicitationSessionScope {
1231    /// Builds [`ElicitationSessionScope`] with the required fields set; optional fields start unset or empty.
1232    #[must_use]
1233    pub fn new(session_id: impl Into<SessionId>) -> Self {
1234        Self {
1235            session_id: session_id.into(),
1236            tool_call_id: None,
1237        }
1238    }
1239
1240    /// Sets or clears the optional `toolCallId` field.
1241    #[must_use]
1242    pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1243        self.tool_call_id = tool_call_id.into_option();
1244        self
1245    }
1246}
1247
1248/// **UNSTABLE**
1249///
1250/// This capability is not part of the spec yet, and may be removed or changed at any point.
1251///
1252/// Request-scoped elicitation, tied to a specific JSON-RPC request outside of a session
1253/// (e.g., during auth/configuration phases before any session is started).
1254#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1255#[serde(rename_all = "camelCase")]
1256#[non_exhaustive]
1257pub struct ElicitationRequestScope {
1258    /// The request this elicitation is tied to.
1259    pub request_id: RequestId,
1260}
1261
1262impl ElicitationRequestScope {
1263    /// Builds [`ElicitationRequestScope`] with the required fields set; optional fields start unset or empty.
1264    #[must_use]
1265    pub fn new(request_id: impl Into<RequestId>) -> Self {
1266        Self {
1267            request_id: request_id.into(),
1268        }
1269    }
1270}
1271
1272impl From<ElicitationSessionScope> for ElicitationScope {
1273    fn from(scope: ElicitationSessionScope) -> Self {
1274        Self::Session(scope)
1275    }
1276}
1277
1278impl From<ElicitationRequestScope> for ElicitationScope {
1279    fn from(scope: ElicitationRequestScope) -> Self {
1280        Self::Request(scope)
1281    }
1282}
1283
1284/// **UNSTABLE**
1285///
1286/// This capability is not part of the spec yet, and may be removed or changed at any point.
1287///
1288/// Request from the agent to elicit structured user input.
1289///
1290/// The agent sends this to the client to request information from the user,
1291/// either via a form or by directing them to a URL.
1292/// Elicitations are tied to a session (optionally a tool call) or a request.
1293#[serde_as]
1294#[skip_serializing_none]
1295#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1296#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1297#[serde(rename_all = "camelCase")]
1298#[non_exhaustive]
1299pub struct CreateElicitationRequest {
1300    /// The elicitation mode and its mode-specific fields.
1301    #[serde(flatten)]
1302    pub mode: ElicitationMode,
1303    /// A human-readable message describing what input is needed.
1304    pub message: String,
1305    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1306    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1307    /// these keys.
1308    ///
1309    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1310    #[serde_as(deserialize_as = "DefaultOnError")]
1311    #[schemars(extend("x-deserialize-default-on-error" = true))]
1312    #[serde(default)]
1313    #[serde(rename = "_meta")]
1314    pub meta: Option<Meta>,
1315}
1316
1317impl CreateElicitationRequest {
1318    /// Builds [`CreateElicitationRequest`] with the required request fields set; optional fields start unset or empty.
1319    #[must_use]
1320    pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1321        Self {
1322            mode: mode.into(),
1323            message: message.into(),
1324            meta: None,
1325        }
1326    }
1327
1328    /// Returns the scope this elicitation is tied to.
1329    #[must_use]
1330    pub fn scope(&self) -> &ElicitationScope {
1331        self.mode.scope()
1332    }
1333
1334    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1335    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1336    /// these keys.
1337    ///
1338    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1339    #[must_use]
1340    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1341        self.meta = meta.into_option();
1342        self
1343    }
1344}
1345
1346/// **UNSTABLE**
1347///
1348/// This capability is not part of the spec yet, and may be removed or changed at any point.
1349///
1350/// The mode of elicitation, determining how user input is collected.
1351#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1352#[serde(tag = "mode", rename_all = "snake_case")]
1353#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1354#[non_exhaustive]
1355pub enum ElicitationMode {
1356    /// Form-based elicitation where the client renders a form from the provided schema.
1357    Form(ElicitationFormMode),
1358    /// URL-based elicitation where the client directs the user to a URL.
1359    Url(ElicitationUrlMode),
1360}
1361
1362impl From<ElicitationFormMode> for ElicitationMode {
1363    fn from(mode: ElicitationFormMode) -> Self {
1364        Self::Form(mode)
1365    }
1366}
1367
1368impl From<ElicitationUrlMode> for ElicitationMode {
1369    fn from(mode: ElicitationUrlMode) -> Self {
1370        Self::Url(mode)
1371    }
1372}
1373
1374impl ElicitationMode {
1375    /// Returns the scope this elicitation mode is tied to.
1376    #[must_use]
1377    pub fn scope(&self) -> &ElicitationScope {
1378        match self {
1379            Self::Form(f) => &f.scope,
1380            Self::Url(u) => &u.scope,
1381        }
1382    }
1383}
1384
1385/// **UNSTABLE**
1386///
1387/// This capability is not part of the spec yet, and may be removed or changed at any point.
1388///
1389/// Form-based elicitation mode where the client renders a form from the provided schema.
1390#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1391#[serde(rename_all = "camelCase")]
1392#[non_exhaustive]
1393pub struct ElicitationFormMode {
1394    /// The scope this elicitation is tied to.
1395    #[serde(flatten)]
1396    pub scope: ElicitationScope,
1397    /// A JSON Schema describing the form fields to present to the user.
1398    pub requested_schema: ElicitationSchema,
1399}
1400
1401impl ElicitationFormMode {
1402    /// Builds [`ElicitationFormMode`] with the required fields set; optional fields start unset or empty.
1403    #[must_use]
1404    pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1405        Self {
1406            scope: scope.into(),
1407            requested_schema,
1408        }
1409    }
1410}
1411
1412/// **UNSTABLE**
1413///
1414/// This capability is not part of the spec yet, and may be removed or changed at any point.
1415///
1416/// URL-based elicitation mode where the client directs the user to a URL.
1417#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1418#[serde(rename_all = "camelCase")]
1419#[non_exhaustive]
1420pub struct ElicitationUrlMode {
1421    /// The scope this elicitation is tied to.
1422    #[serde(flatten)]
1423    pub scope: ElicitationScope,
1424    /// The unique identifier for this elicitation.
1425    pub elicitation_id: ElicitationId,
1426    /// The URL to direct the user to.
1427    #[schemars(extend("format" = "uri"))]
1428    pub url: String,
1429}
1430
1431impl ElicitationUrlMode {
1432    /// Builds [`ElicitationUrlMode`] with the required fields set; optional fields start unset or empty.
1433    #[must_use]
1434    pub fn new(
1435        scope: impl Into<ElicitationScope>,
1436        elicitation_id: impl Into<ElicitationId>,
1437        url: impl Into<String>,
1438    ) -> Self {
1439        Self {
1440            scope: scope.into(),
1441            elicitation_id: elicitation_id.into(),
1442            url: url.into(),
1443        }
1444    }
1445}
1446
1447/// **UNSTABLE**
1448///
1449/// This capability is not part of the spec yet, and may be removed or changed at any point.
1450///
1451/// Response from the client to an elicitation request.
1452#[serde_as]
1453#[skip_serializing_none]
1454#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1455#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1456#[serde(rename_all = "camelCase")]
1457#[non_exhaustive]
1458pub struct CreateElicitationResponse {
1459    /// The user's action in response to the elicitation.
1460    #[serde(flatten)]
1461    pub action: ElicitationAction,
1462    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1463    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1464    /// these keys.
1465    ///
1466    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1467    #[serde_as(deserialize_as = "DefaultOnError")]
1468    #[schemars(extend("x-deserialize-default-on-error" = true))]
1469    #[serde(default)]
1470    #[serde(rename = "_meta")]
1471    pub meta: Option<Meta>,
1472}
1473
1474impl CreateElicitationResponse {
1475    /// Builds [`CreateElicitationResponse`] with the required response fields set; optional fields start unset or empty.
1476    #[must_use]
1477    pub fn new(action: ElicitationAction) -> Self {
1478        Self { action, meta: None }
1479    }
1480
1481    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1482    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1483    /// these keys.
1484    ///
1485    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1486    #[must_use]
1487    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1488        self.meta = meta.into_option();
1489        self
1490    }
1491}
1492
1493/// **UNSTABLE**
1494///
1495/// This capability is not part of the spec yet, and may be removed or changed at any point.
1496///
1497/// The user's action in response to an elicitation.
1498#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1499#[serde(tag = "action", rename_all = "snake_case")]
1500#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1501#[non_exhaustive]
1502pub enum ElicitationAction {
1503    /// The user accepted and provided content.
1504    Accept(ElicitationAcceptAction),
1505    /// The user declined the elicitation.
1506    Decline,
1507    /// The elicitation was cancelled.
1508    Cancel,
1509}
1510
1511/// **UNSTABLE**
1512///
1513/// This capability is not part of the spec yet, and may be removed or changed at any point.
1514///
1515/// The user accepted the elicitation and provided content.
1516#[serde_as]
1517#[skip_serializing_none]
1518#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1519#[serde(rename_all = "camelCase")]
1520#[non_exhaustive]
1521pub struct ElicitationAcceptAction {
1522    /// The user-provided content, if any, as an object matching the requested schema.
1523    #[serde_as(deserialize_as = "DefaultOnError")]
1524    #[schemars(extend("x-deserialize-default-on-error" = true))]
1525    #[serde(default)]
1526    pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1527}
1528
1529impl ElicitationAcceptAction {
1530    /// Builds [`ElicitationAcceptAction`] with the required fields set; optional fields start unset or empty.
1531    #[must_use]
1532    pub fn new() -> Self {
1533        Self { content: None }
1534    }
1535
1536    /// The user-provided content as an object matching the requested schema.
1537    #[must_use]
1538    pub fn content(
1539        mut self,
1540        content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1541    ) -> Self {
1542        self.content = content.into_option();
1543        self
1544    }
1545}
1546
1547/// Allowed wire representations for [`ElicitationContentValue`].
1548#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1549#[serde(untagged)]
1550#[non_exhaustive]
1551pub enum ElicitationContentValue {
1552    /// String value accepted in elicitation response content.
1553    String(String),
1554    /// Integer value accepted in elicitation response content.
1555    Integer(i64),
1556    /// Number value accepted in elicitation response content.
1557    Number(f64),
1558    /// Boolean value accepted in elicitation response content.
1559    Boolean(bool),
1560    /// String array value accepted in elicitation response content.
1561    StringArray(Vec<String>),
1562}
1563
1564impl From<String> for ElicitationContentValue {
1565    fn from(value: String) -> Self {
1566        Self::String(value)
1567    }
1568}
1569
1570impl From<&str> for ElicitationContentValue {
1571    fn from(value: &str) -> Self {
1572        Self::String(value.to_string())
1573    }
1574}
1575
1576impl From<i64> for ElicitationContentValue {
1577    fn from(value: i64) -> Self {
1578        Self::Integer(value)
1579    }
1580}
1581
1582impl From<i32> for ElicitationContentValue {
1583    fn from(value: i32) -> Self {
1584        Self::Integer(i64::from(value))
1585    }
1586}
1587
1588impl From<f64> for ElicitationContentValue {
1589    fn from(value: f64) -> Self {
1590        Self::Number(value)
1591    }
1592}
1593
1594impl From<bool> for ElicitationContentValue {
1595    fn from(value: bool) -> Self {
1596        Self::Boolean(value)
1597    }
1598}
1599
1600impl From<Vec<String>> for ElicitationContentValue {
1601    fn from(value: Vec<String>) -> Self {
1602        Self::StringArray(value)
1603    }
1604}
1605
1606impl From<Vec<&str>> for ElicitationContentValue {
1607    fn from(value: Vec<&str>) -> Self {
1608        Self::StringArray(value.into_iter().map(str::to_string).collect())
1609    }
1610}
1611
1612impl Default for ElicitationAcceptAction {
1613    fn default() -> Self {
1614        Self::new()
1615    }
1616}
1617
1618/// **UNSTABLE**
1619///
1620/// This capability is not part of the spec yet, and may be removed or changed at any point.
1621///
1622/// Notification sent by the agent when a URL-based elicitation is complete.
1623#[serde_as]
1624#[skip_serializing_none]
1625#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1626#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1627#[serde(rename_all = "camelCase")]
1628#[non_exhaustive]
1629pub struct CompleteElicitationNotification {
1630    /// The ID of the elicitation that completed.
1631    pub elicitation_id: ElicitationId,
1632    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1633    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1634    /// these keys.
1635    ///
1636    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1637    #[serde_as(deserialize_as = "DefaultOnError")]
1638    #[schemars(extend("x-deserialize-default-on-error" = true))]
1639    #[serde(default)]
1640    #[serde(rename = "_meta")]
1641    pub meta: Option<Meta>,
1642}
1643
1644impl CompleteElicitationNotification {
1645    /// Builds [`CompleteElicitationNotification`] with the required notification fields set; optional fields start unset or empty.
1646    #[must_use]
1647    pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
1648        Self {
1649            elicitation_id: elicitation_id.into(),
1650            meta: None,
1651        }
1652    }
1653
1654    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1655    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1656    /// these keys.
1657    ///
1658    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1659    #[must_use]
1660    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1661        self.meta = meta.into_option();
1662        self
1663    }
1664}
1665
1666/// **UNSTABLE**
1667///
1668/// This capability is not part of the spec yet, and may be removed or changed at any point.
1669///
1670/// Data payload for the `UrlElicitationRequired` error, describing the URL elicitations
1671/// the user must complete.
1672#[serde_as]
1673#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1674#[serde(rename_all = "camelCase")]
1675#[non_exhaustive]
1676pub struct UrlElicitationRequiredData {
1677    /// The URL elicitations the user must complete.
1678    #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1679    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1680    pub elicitations: Vec<UrlElicitationRequiredItem>,
1681}
1682
1683impl UrlElicitationRequiredData {
1684    /// Builds [`UrlElicitationRequiredData`] with the required fields set; optional fields start unset or empty.
1685    #[must_use]
1686    pub fn new(elicitations: Vec<UrlElicitationRequiredItem>) -> Self {
1687        Self { elicitations }
1688    }
1689}
1690
1691/// **UNSTABLE**
1692///
1693/// This capability is not part of the spec yet, and may be removed or changed at any point.
1694///
1695/// A single URL elicitation item within the `UrlElicitationRequired` error data.
1696#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1697#[serde(rename_all = "camelCase")]
1698#[non_exhaustive]
1699pub struct UrlElicitationRequiredItem {
1700    /// The elicitation mode (always `"url"` for this item type).
1701    pub mode: ElicitationUrlOnlyMode,
1702    /// The unique identifier for this elicitation.
1703    pub elicitation_id: ElicitationId,
1704    /// The URL the user should be directed to.
1705    #[schemars(extend("format" = "uri"))]
1706    pub url: String,
1707    /// A human-readable message describing what input is needed.
1708    pub message: String,
1709}
1710
1711/// Type discriminator for URL-only elicitation error items.
1712#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
1713#[serde(rename_all = "snake_case")]
1714#[non_exhaustive]
1715pub enum ElicitationUrlOnlyMode {
1716    /// URL elicitation mode.
1717    #[default]
1718    Url,
1719}
1720
1721impl UrlElicitationRequiredItem {
1722    /// Builds [`UrlElicitationRequiredItem`] with the required fields set; optional fields start unset or empty.
1723    #[must_use]
1724    pub fn new(
1725        elicitation_id: impl Into<ElicitationId>,
1726        url: impl Into<String>,
1727        message: impl Into<String>,
1728    ) -> Self {
1729        Self {
1730            mode: ElicitationUrlOnlyMode::Url,
1731            elicitation_id: elicitation_id.into(),
1732            url: url.into(),
1733            message: message.into(),
1734        }
1735    }
1736}
1737
1738#[cfg(test)]
1739mod tests {
1740    use super::*;
1741    use serde_json::json;
1742
1743    #[test]
1744    fn form_mode_request_serialization() {
1745        let schema = ElicitationSchema::new().string("name", true);
1746        let req = CreateElicitationRequest::new(
1747            ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
1748            "Please enter your name",
1749        );
1750
1751        let json = serde_json::to_value(&req).unwrap();
1752        assert_eq!(json["sessionId"], "sess_1");
1753        assert!(json.get("toolCallId").is_none());
1754        assert_eq!(json["mode"], "form");
1755        assert_eq!(json["message"], "Please enter your name");
1756        assert!(json["requestedSchema"].is_object());
1757        assert_eq!(json["requestedSchema"]["type"], "object");
1758        assert_eq!(
1759            json["requestedSchema"]["properties"]["name"]["type"],
1760            "string"
1761        );
1762
1763        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1764        assert_eq!(
1765            *roundtripped.scope(),
1766            ElicitationSessionScope::new("sess_1").into()
1767        );
1768        assert_eq!(roundtripped.message, "Please enter your name");
1769        assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
1770    }
1771
1772    #[test]
1773    fn url_mode_request_serialization() {
1774        let req = CreateElicitationRequest::new(
1775            ElicitationUrlMode::new(
1776                ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
1777                "elic_1",
1778                "https://example.com/auth",
1779            ),
1780            "Please authenticate",
1781        );
1782
1783        let json = serde_json::to_value(&req).unwrap();
1784        assert_eq!(json["sessionId"], "sess_2");
1785        assert_eq!(json["toolCallId"], "tc_1");
1786        assert_eq!(json["mode"], "url");
1787        assert_eq!(json["elicitationId"], "elic_1");
1788        assert_eq!(json["url"], "https://example.com/auth");
1789        assert_eq!(json["message"], "Please authenticate");
1790
1791        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1792        assert_eq!(
1793            *roundtripped.scope(),
1794            ElicitationSessionScope::new("sess_2")
1795                .tool_call_id("tc_1")
1796                .into()
1797        );
1798        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1799    }
1800
1801    #[test]
1802    fn response_accept_serialization() {
1803        let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
1804            ElicitationAcceptAction::new().content(BTreeMap::from([(
1805                "name".to_string(),
1806                ElicitationContentValue::from("Alice"),
1807            )])),
1808        ));
1809
1810        let json = serde_json::to_value(&resp).unwrap();
1811        assert_eq!(json["action"], "accept");
1812        assert_eq!(json["content"]["name"], "Alice");
1813
1814        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1815        assert!(matches!(
1816            roundtripped.action,
1817            ElicitationAction::Accept(ElicitationAcceptAction {
1818                content: Some(_),
1819                ..
1820            })
1821        ));
1822    }
1823
1824    #[test]
1825    fn response_decline_serialization() {
1826        let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
1827
1828        let json = serde_json::to_value(&resp).unwrap();
1829        assert_eq!(json["action"], "decline");
1830
1831        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1832        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1833    }
1834
1835    #[test]
1836    fn response_cancel_serialization() {
1837        let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
1838
1839        let json = serde_json::to_value(&resp).unwrap();
1840        assert_eq!(json["action"], "cancel");
1841
1842        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1843        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1844    }
1845
1846    #[test]
1847    fn url_mode_request_scope_serialization() {
1848        let req = CreateElicitationRequest::new(
1849            ElicitationUrlMode::new(
1850                ElicitationRequestScope::new(RequestId::Number(42)),
1851                "elic_2",
1852                "https://example.com/setup",
1853            ),
1854            "Please complete setup",
1855        );
1856
1857        let json = serde_json::to_value(&req).unwrap();
1858        assert_eq!(json["requestId"], 42);
1859        assert!(json.get("sessionId").is_none());
1860        assert_eq!(json["mode"], "url");
1861        assert_eq!(json["elicitationId"], "elic_2");
1862        assert_eq!(json["url"], "https://example.com/setup");
1863        assert_eq!(json["message"], "Please complete setup");
1864
1865        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1866        assert_eq!(
1867            *roundtripped.scope(),
1868            ElicitationRequestScope::new(RequestId::Number(42)).into()
1869        );
1870        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1871    }
1872
1873    #[test]
1874    fn request_scope_request_serialization() {
1875        let req = CreateElicitationRequest::new(
1876            ElicitationFormMode::new(
1877                ElicitationRequestScope::new(RequestId::Number(99)),
1878                ElicitationSchema::new().string("workspace", true),
1879            ),
1880            "Enter workspace name",
1881        );
1882
1883        let json = serde_json::to_value(&req).unwrap();
1884        assert_eq!(json["requestId"], 99);
1885        assert!(json.get("sessionId").is_none());
1886
1887        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1888        assert_eq!(
1889            *roundtripped.scope(),
1890            ElicitationRequestScope::new(RequestId::Number(99)).into()
1891        );
1892    }
1893
1894    /// `ClientResponse` is `#[serde(untagged)]` with `WriteTextFileResponse` (which has
1895    /// `#[serde(default)]`) listed first, so standalone deserialization is ambiguous.
1896    /// In practice, the RPC layer selects the correct variant based on the originating
1897    /// request method. These tests verify that serialization through `ClientResponse`
1898    /// produces the correct flattened wire format and round-trips back via the
1899    /// concrete `CreateElicitationResponse` type.
1900    #[test]
1901    fn client_response_serialization_accept() {
1902        use crate::v1::ClientResponse;
1903
1904        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1905            ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
1906                "name".to_string(),
1907                ElicitationContentValue::from("Alice"),
1908            )]))),
1909        ));
1910        let json = serde_json::to_value(&resp).unwrap();
1911        assert_eq!(json["action"], "accept");
1912        assert_eq!(json["content"]["name"], "Alice");
1913
1914        // Round-trip back through the concrete type
1915        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1916        assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
1917    }
1918
1919    #[test]
1920    fn client_response_serialization_decline() {
1921        use crate::v1::ClientResponse;
1922
1923        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1924            ElicitationAction::Decline,
1925        ));
1926        let json = serde_json::to_value(&resp).unwrap();
1927        assert_eq!(json["action"], "decline");
1928
1929        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1930        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1931    }
1932
1933    #[test]
1934    fn client_response_serialization_cancel() {
1935        use crate::v1::ClientResponse;
1936
1937        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1938            ElicitationAction::Cancel,
1939        ));
1940        let json = serde_json::to_value(&resp).unwrap();
1941        assert_eq!(json["action"], "cancel");
1942
1943        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1944        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1945    }
1946
1947    /// Guard against serde regressions with the `flatten` + internally-tagged combination.
1948    /// Extra fields in the JSON must not cause deserialization failures.
1949    #[test]
1950    fn request_tolerates_extra_fields() {
1951        let json = json!({
1952            "sessionId": "sess_1",
1953            "mode": "form",
1954            "message": "Enter your name",
1955            "requestedSchema": {
1956                "type": "object",
1957                "properties": {
1958                    "name": { "type": "string", "title": "Name" }
1959                },
1960                "required": ["name"]
1961            },
1962            "unknownStringField": "hello",
1963            "unknownNumberField": 42
1964        });
1965
1966        let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1967        assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
1968        assert_eq!(req.message, "Enter your name");
1969        assert!(matches!(req.mode, ElicitationMode::Form(_)));
1970    }
1971
1972    #[test]
1973    fn completion_notification_serialization() {
1974        let notif = CompleteElicitationNotification::new("elic_1");
1975
1976        let json = serde_json::to_value(&notif).unwrap();
1977        assert_eq!(json["elicitationId"], "elic_1");
1978
1979        let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
1980        assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
1981    }
1982
1983    #[test]
1984    fn capabilities_form_only() {
1985        let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
1986
1987        let json = serde_json::to_value(&caps).unwrap();
1988        assert!(json["form"].is_object());
1989        assert!(json.get("url").is_none());
1990
1991        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1992        assert!(roundtripped.form.is_some());
1993        assert!(roundtripped.url.is_none());
1994    }
1995
1996    #[test]
1997    fn capabilities_url_only() {
1998        let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
1999
2000        let json = serde_json::to_value(&caps).unwrap();
2001        assert!(json.get("form").is_none());
2002        assert!(json["url"].is_object());
2003
2004        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2005        assert!(roundtripped.form.is_none());
2006        assert!(roundtripped.url.is_some());
2007    }
2008
2009    #[test]
2010    fn capabilities_both() {
2011        let caps = ElicitationCapabilities::new()
2012            .form(ElicitationFormCapabilities::new())
2013            .url(ElicitationUrlCapabilities::new());
2014
2015        let json = serde_json::to_value(&caps).unwrap();
2016        assert!(json["form"].is_object());
2017        assert!(json["url"].is_object());
2018
2019        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2020        assert!(roundtripped.form.is_some());
2021        assert!(roundtripped.url.is_some());
2022    }
2023
2024    #[test]
2025    fn url_elicitation_required_data_serialization() {
2026        let data = UrlElicitationRequiredData::new(vec![UrlElicitationRequiredItem::new(
2027            "elic_1",
2028            "https://example.com/auth",
2029            "Please authenticate",
2030        )]);
2031
2032        let json = serde_json::to_value(&data).unwrap();
2033        assert_eq!(json["elicitations"][0]["mode"], "url");
2034        assert_eq!(json["elicitations"][0]["elicitationId"], "elic_1");
2035        assert_eq!(json["elicitations"][0]["url"], "https://example.com/auth");
2036
2037        let roundtripped: UrlElicitationRequiredData = serde_json::from_value(json).unwrap();
2038        assert_eq!(roundtripped.elicitations.len(), 1);
2039        assert_eq!(
2040            roundtripped.elicitations[0].mode,
2041            ElicitationUrlOnlyMode::Url
2042        );
2043    }
2044
2045    #[test]
2046    fn schema_default_sets_object_type() {
2047        let schema = ElicitationSchema::default();
2048
2049        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2050        assert!(schema.properties.is_empty());
2051
2052        let json = serde_json::to_value(&schema).unwrap();
2053        assert_eq!(json["type"], "object");
2054    }
2055
2056    #[test]
2057    fn schema_builder_serialization() {
2058        let schema = ElicitationSchema::new()
2059            .string("name", true)
2060            .email("email", true)
2061            .integer("age", 0, 150, true)
2062            .boolean("newsletter", false)
2063            .description("User registration");
2064
2065        let json = serde_json::to_value(&schema).unwrap();
2066        assert_eq!(json["type"], "object");
2067        assert_eq!(json["description"], "User registration");
2068        assert_eq!(json["properties"]["name"]["type"], "string");
2069        assert_eq!(json["properties"]["email"]["type"], "string");
2070        assert_eq!(json["properties"]["email"]["format"], "email");
2071        assert_eq!(json["properties"]["age"]["type"], "integer");
2072        assert_eq!(json["properties"]["age"]["minimum"], 0);
2073        assert_eq!(json["properties"]["age"]["maximum"], 150);
2074        assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2075
2076        let required = json["required"].as_array().unwrap();
2077        assert!(required.contains(&json!("name")));
2078        assert!(required.contains(&json!("email")));
2079        assert!(required.contains(&json!("age")));
2080        assert!(!required.contains(&json!("newsletter")));
2081
2082        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2083        assert_eq!(roundtripped.properties.len(), 4);
2084        assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2085    }
2086
2087    #[test]
2088    fn schema_string_enum_serialization() {
2089        let schema = ElicitationSchema::new().property(
2090            "color",
2091            StringPropertySchema::new().enum_values(vec![
2092                "red".into(),
2093                "green".into(),
2094                "blue".into(),
2095            ]),
2096            true,
2097        );
2098
2099        let json = serde_json::to_value(&schema).unwrap();
2100        assert_eq!(json["properties"]["color"]["type"], "string");
2101        let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2102        assert_eq!(enum_vals.len(), 3);
2103
2104        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2105        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2106        {
2107            assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2108        } else {
2109            panic!("expected String variant");
2110        }
2111    }
2112
2113    #[test]
2114    fn schema_multi_select_serialization() {
2115        let schema = ElicitationSchema::new().property(
2116            "colors",
2117            MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2118                .min_items(1)
2119                .max_items(3),
2120            false,
2121        );
2122
2123        let json = serde_json::to_value(&schema).unwrap();
2124        assert_eq!(json["properties"]["colors"]["type"], "array");
2125        assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2126        assert_eq!(json["properties"]["colors"]["minItems"], 1);
2127        assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2128
2129        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2130        assert!(matches!(
2131            roundtripped.properties.get("colors").unwrap(),
2132            ElicitationPropertySchema::Array(_)
2133        ));
2134    }
2135
2136    #[test]
2137    fn schema_titled_enum_serialization() {
2138        let schema = ElicitationSchema::new().property(
2139            "country",
2140            StringPropertySchema::new().one_of(vec![
2141                EnumOption::new("us", "United States"),
2142                EnumOption::new("uk", "United Kingdom"),
2143            ]),
2144            true,
2145        );
2146
2147        let json = serde_json::to_value(&schema).unwrap();
2148        assert_eq!(json["properties"]["country"]["type"], "string");
2149        let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2150        assert_eq!(one_of.len(), 2);
2151        assert_eq!(one_of[0]["const"], "us");
2152        assert_eq!(one_of[0]["title"], "United States");
2153
2154        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2155        if let ElicitationPropertySchema::String(s) =
2156            roundtripped.properties.get("country").unwrap()
2157        {
2158            assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
2159        } else {
2160            panic!("expected String variant");
2161        }
2162    }
2163
2164    #[test]
2165    fn schema_number_property_serialization() {
2166        let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2167
2168        let json = serde_json::to_value(&schema).unwrap();
2169        assert_eq!(json["properties"]["rating"]["type"], "number");
2170        assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2171        assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2172
2173        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2174        if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2175        {
2176            assert_eq!(n.minimum, Some(0.0));
2177            assert_eq!(n.maximum, Some(5.0));
2178        } else {
2179            panic!("expected Number variant");
2180        }
2181    }
2182
2183    #[test]
2184    fn schema_string_format_serialization() {
2185        let schema = ElicitationSchema::new()
2186            .uri("website", true)
2187            .date("birthday", true)
2188            .date_time("updated_at", false);
2189
2190        let json = serde_json::to_value(&schema).unwrap();
2191        assert_eq!(json["properties"]["website"]["type"], "string");
2192        assert_eq!(json["properties"]["website"]["format"], "uri");
2193        assert_eq!(json["properties"]["birthday"]["type"], "string");
2194        assert_eq!(json["properties"]["birthday"]["format"], "date");
2195        assert_eq!(json["properties"]["updated_at"]["type"], "string");
2196        assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2197
2198        let required = json["required"].as_array().unwrap();
2199        assert!(required.contains(&json!("website")));
2200        assert!(required.contains(&json!("birthday")));
2201        assert!(!required.contains(&json!("updated_at")));
2202    }
2203
2204    #[test]
2205    fn schema_string_pattern_serialization() {
2206        let schema = ElicitationSchema::new().property(
2207            "name",
2208            StringPropertySchema::new()
2209                .min_length(1)
2210                .max_length(64)
2211                .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2212            true,
2213        );
2214
2215        let json = serde_json::to_value(&schema).unwrap();
2216        assert_eq!(json["properties"]["name"]["type"], "string");
2217        assert_eq!(
2218            json["properties"]["name"]["pattern"],
2219            "^[a-zA-Z_][a-zA-Z0-9_]*$"
2220        );
2221
2222        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2223        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2224            assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2225        } else {
2226            panic!("expected String variant");
2227        }
2228    }
2229
2230    #[test]
2231    fn schema_property_updates_required_state() {
2232        let schema = ElicitationSchema::new()
2233            .string("name", true)
2234            .email("name", false);
2235
2236        let json = serde_json::to_value(&schema).unwrap();
2237        assert!(json.get("required").is_none());
2238        assert_eq!(json["properties"]["name"]["format"], "email");
2239    }
2240
2241    #[test]
2242    fn schema_defaults_invalid_object_type() {
2243        let schema = serde_json::from_value::<ElicitationSchema>(json!({
2244            "type": "array",
2245            "properties": {
2246                "name": {
2247                    "type": "string"
2248                }
2249            }
2250        }))
2251        .unwrap();
2252
2253        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2254        assert!(schema.properties.contains_key("name"));
2255    }
2256
2257    #[test]
2258    fn titled_multi_select_items_reject_one_of() {
2259        let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2260            "oneOf": [
2261                {
2262                    "const": "red",
2263                    "title": "Red"
2264                }
2265            ]
2266        }))
2267        .unwrap_err();
2268
2269        assert!(err.to_string().contains("missing field `anyOf`"));
2270    }
2271
2272    #[test]
2273    fn response_accept_defaults_non_object_content() {
2274        let response = serde_json::from_value::<CreateElicitationResponse>(json!({
2275            "action": "accept",
2276            "content": "Alice"
2277        }))
2278        .unwrap();
2279
2280        assert_eq!(
2281            response.action,
2282            ElicitationAction::Accept(ElicitationAcceptAction::new())
2283        );
2284    }
2285
2286    #[test]
2287    fn response_accept_defaults_nested_object_content() {
2288        let response = serde_json::from_value::<CreateElicitationResponse>(json!({
2289            "action": "accept",
2290            "content": {
2291                "profile": {
2292                    "name": "Alice"
2293                }
2294            }
2295        }))
2296        .unwrap();
2297
2298        assert_eq!(
2299            response.action,
2300            ElicitationAction::Accept(ElicitationAcceptAction::new())
2301        );
2302    }
2303
2304    #[test]
2305    fn response_accept_allows_primitive_and_string_array_content() {
2306        let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2307            ElicitationAcceptAction::new().content(BTreeMap::from([
2308                ("name".to_string(), ElicitationContentValue::from("Alice")),
2309                ("age".to_string(), ElicitationContentValue::from(30_i32)),
2310                ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2311                (
2312                    "subscribed".to_string(),
2313                    ElicitationContentValue::from(true),
2314                ),
2315                (
2316                    "tags".to_string(),
2317                    ElicitationContentValue::from(vec!["rust", "acp"]),
2318                ),
2319            ])),
2320        ));
2321
2322        let json = serde_json::to_value(&response).unwrap();
2323        assert_eq!(json["action"], "accept");
2324        assert_eq!(json["content"]["name"], "Alice");
2325        assert_eq!(json["content"]["age"], 30);
2326        assert_eq!(json["content"]["score"], 9.5);
2327        assert_eq!(json["content"]["subscribed"], true);
2328        assert_eq!(json["content"]["tags"][0], "rust");
2329        assert_eq!(json["content"]["tags"][1], "acp");
2330    }
2331}