Skip to main content

agent_client_protocol_schema/v2/
elicitation.rs

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