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