Skip to main content

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