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