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