Skip to main content

agent_client_protocol_schema/v1/
elicitation.rs

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