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, Schema};
12use serde::{Deserialize, Serialize};
13use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
14
15use crate::IntoOption;
16use crate::SkipListener;
17
18use super::{
19    ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, Meta, RequestId, SessionId,
20    ToolCallId,
21};
22
23/// **UNSTABLE**
24///
25/// This capability is not part of the spec yet, and may be removed or changed at any point.
26///
27/// Unique identifier for an elicitation.
28#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
29#[serde(transparent)]
30#[from(Arc<str>, String, &'static str)]
31#[non_exhaustive]
32pub struct ElicitationId(pub Arc<str>);
33
34impl ElicitationId {
35    /// Wraps a protocol string as a typed [`ElicitationId`].
36    #[must_use]
37    pub fn new(id: impl Into<Arc<str>>) -> Self {
38        Self(id.into())
39    }
40}
41
42/// String format types for string properties in elicitation schemas.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
44#[serde(rename_all = "kebab-case")]
45#[non_exhaustive]
46pub enum StringFormat {
47    /// Email address format.
48    Email,
49    /// URI format.
50    Uri,
51    /// Date format (YYYY-MM-DD).
52    Date,
53    /// Date-time format (ISO 8601).
54    DateTime,
55}
56
57/// Type discriminator for elicitation schemas.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
59#[serde(rename_all = "snake_case")]
60#[non_exhaustive]
61pub enum ElicitationSchemaType {
62    /// Object schema type.
63    #[default]
64    Object,
65}
66
67/// A titled enum option with a const value, human-readable title, and optional description.
68#[serde_as]
69#[skip_serializing_none]
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
71#[non_exhaustive]
72pub struct EnumOption {
73    /// The constant value for this option.
74    #[serde(rename = "const")]
75    pub value: String,
76    /// Human-readable title for this option.
77    pub title: String,
78    /// Human-readable description.
79    #[serde_as(deserialize_as = "DefaultOnError")]
80    #[schemars(extend("x-deserialize-default-on-error" = true))]
81    #[serde(default)]
82    pub description: Option<String>,
83    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
84    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
85    /// these keys.
86    ///
87    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
88    #[serde_as(deserialize_as = "DefaultOnError")]
89    #[schemars(extend("x-deserialize-default-on-error" = true))]
90    #[serde(default)]
91    #[serde(rename = "_meta")]
92    pub meta: Option<Meta>,
93}
94
95impl EnumOption {
96    /// Create a new enum option.
97    #[must_use]
98    pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
99        Self {
100            value: value.into(),
101            title: title.into(),
102            description: None,
103            meta: None,
104        }
105    }
106
107    /// Human-readable description.
108    #[must_use]
109    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
110        self.description = description.into_option();
111        self
112    }
113
114    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
115    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
116    /// these keys.
117    ///
118    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
119    #[must_use]
120    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
121        self.meta = meta.into_option();
122        self
123    }
124}
125
126/// Schema for string properties in an elicitation form.
127///
128/// When `enum` or `oneOf` is set, this represents a single-select enum
129/// with `"type": "string"`.
130#[serde_as]
131#[skip_serializing_none]
132#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
133#[serde(rename_all = "camelCase")]
134#[non_exhaustive]
135pub struct StringPropertySchema {
136    /// Optional title for the property.
137    #[serde_as(deserialize_as = "DefaultOnError")]
138    #[schemars(extend("x-deserialize-default-on-error" = true))]
139    #[serde(default)]
140    pub title: Option<String>,
141    /// Human-readable description.
142    #[serde_as(deserialize_as = "DefaultOnError")]
143    #[schemars(extend("x-deserialize-default-on-error" = true))]
144    #[serde(default)]
145    pub description: Option<String>,
146    /// Minimum string length.
147    #[serde(default)]
148    pub min_length: Option<u32>,
149    /// Maximum string length.
150    #[serde(default)]
151    pub max_length: Option<u32>,
152    /// Pattern the string must match.
153    #[serde(default)]
154    pub pattern: Option<String>,
155    /// String format.
156    #[serde(default)]
157    pub format: Option<StringFormat>,
158    /// Default value.
159    #[serde_as(deserialize_as = "DefaultOnError")]
160    #[schemars(extend("x-deserialize-default-on-error" = true))]
161    #[serde(default)]
162    pub default: Option<String>,
163    /// Enum values for untitled single-select enums.
164    #[serde(default)]
165    #[serde(rename = "enum")]
166    pub enum_values: Option<Vec<String>>,
167    /// Titled enum options for titled single-select enums.
168    #[serde(default)]
169    #[serde(rename = "oneOf")]
170    pub one_of: Option<Vec<EnumOption>>,
171    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
172    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
173    /// these keys.
174    ///
175    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
176    #[serde_as(deserialize_as = "DefaultOnError")]
177    #[schemars(extend("x-deserialize-default-on-error" = true))]
178    #[serde(default)]
179    #[serde(rename = "_meta")]
180    pub meta: Option<Meta>,
181}
182
183impl StringPropertySchema {
184    /// Create a new string property schema.
185    #[must_use]
186    pub fn new() -> Self {
187        Self::default()
188    }
189
190    /// Create an email string property schema.
191    #[must_use]
192    pub fn email() -> Self {
193        Self {
194            format: Some(StringFormat::Email),
195            ..Default::default()
196        }
197    }
198
199    /// Create a URI string property schema.
200    #[must_use]
201    pub fn uri() -> Self {
202        Self {
203            format: Some(StringFormat::Uri),
204            ..Default::default()
205        }
206    }
207
208    /// Create a date string property schema.
209    #[must_use]
210    pub fn date() -> Self {
211        Self {
212            format: Some(StringFormat::Date),
213            ..Default::default()
214        }
215    }
216
217    /// Create a date-time string property schema.
218    #[must_use]
219    pub fn date_time() -> Self {
220        Self {
221            format: Some(StringFormat::DateTime),
222            ..Default::default()
223        }
224    }
225
226    /// Optional title for the property.
227    #[must_use]
228    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
229        self.title = title.into_option();
230        self
231    }
232
233    /// Human-readable description.
234    #[must_use]
235    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
236        self.description = description.into_option();
237        self
238    }
239
240    /// Minimum string length.
241    #[must_use]
242    pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
243        self.min_length = min_length.into_option();
244        self
245    }
246
247    /// Maximum string length.
248    #[must_use]
249    pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
250        self.max_length = max_length.into_option();
251        self
252    }
253
254    /// Pattern the string must match.
255    #[must_use]
256    pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
257        self.pattern = pattern.into_option();
258        self
259    }
260
261    /// String format.
262    #[must_use]
263    pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
264        self.format = format.into_option();
265        self
266    }
267
268    /// Default value.
269    #[must_use]
270    pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
271        self.default = default.into_option();
272        self
273    }
274
275    /// Enum values for untitled single-select enums.
276    #[must_use]
277    pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
278        self.enum_values = enum_values.into_option();
279        self
280    }
281
282    /// Titled enum options for titled single-select enums.
283    #[must_use]
284    pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
285        self.one_of = one_of.into_option();
286        self
287    }
288
289    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
290    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
291    /// these keys.
292    ///
293    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
294    #[must_use]
295    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
296        self.meta = meta.into_option();
297        self
298    }
299}
300
301/// Schema for number (floating-point) properties in an elicitation form.
302#[serde_as]
303#[skip_serializing_none]
304#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
305#[serde(rename_all = "camelCase")]
306#[non_exhaustive]
307pub struct NumberPropertySchema {
308    /// Optional title for the property.
309    #[serde_as(deserialize_as = "DefaultOnError")]
310    #[schemars(extend("x-deserialize-default-on-error" = true))]
311    #[serde(default)]
312    pub title: Option<String>,
313    /// Human-readable description.
314    #[serde_as(deserialize_as = "DefaultOnError")]
315    #[schemars(extend("x-deserialize-default-on-error" = true))]
316    #[serde(default)]
317    pub description: Option<String>,
318    /// Minimum value (inclusive).
319    #[serde(default)]
320    pub minimum: Option<f64>,
321    /// Maximum value (inclusive).
322    #[serde(default)]
323    pub maximum: Option<f64>,
324    /// Default value.
325    #[serde_as(deserialize_as = "DefaultOnError")]
326    #[schemars(extend("x-deserialize-default-on-error" = true))]
327    #[serde(default)]
328    pub default: Option<f64>,
329    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
330    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
331    /// these keys.
332    ///
333    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
334    #[serde_as(deserialize_as = "DefaultOnError")]
335    #[schemars(extend("x-deserialize-default-on-error" = true))]
336    #[serde(default)]
337    #[serde(rename = "_meta")]
338    pub meta: Option<Meta>,
339}
340
341impl NumberPropertySchema {
342    /// Create a new number property schema.
343    #[must_use]
344    pub fn new() -> Self {
345        Self::default()
346    }
347
348    /// Optional title for the property.
349    #[must_use]
350    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
351        self.title = title.into_option();
352        self
353    }
354
355    /// Human-readable description.
356    #[must_use]
357    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
358        self.description = description.into_option();
359        self
360    }
361
362    /// Minimum value (inclusive).
363    #[must_use]
364    pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
365        self.minimum = minimum.into_option();
366        self
367    }
368
369    /// Maximum value (inclusive).
370    #[must_use]
371    pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
372        self.maximum = maximum.into_option();
373        self
374    }
375
376    /// Default value.
377    #[must_use]
378    pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
379        self.default = default.into_option();
380        self
381    }
382
383    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
384    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
385    /// these keys.
386    ///
387    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
388    #[must_use]
389    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
390        self.meta = meta.into_option();
391        self
392    }
393}
394
395/// Schema for integer properties in an elicitation form.
396#[serde_as]
397#[skip_serializing_none]
398#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
399#[serde(rename_all = "camelCase")]
400#[non_exhaustive]
401pub struct IntegerPropertySchema {
402    /// Optional title for the property.
403    #[serde_as(deserialize_as = "DefaultOnError")]
404    #[schemars(extend("x-deserialize-default-on-error" = true))]
405    #[serde(default)]
406    pub title: Option<String>,
407    /// Human-readable description.
408    #[serde_as(deserialize_as = "DefaultOnError")]
409    #[schemars(extend("x-deserialize-default-on-error" = true))]
410    #[serde(default)]
411    pub description: Option<String>,
412    /// Minimum value (inclusive).
413    #[serde(default)]
414    pub minimum: Option<i64>,
415    /// Maximum value (inclusive).
416    #[serde(default)]
417    pub maximum: Option<i64>,
418    /// Default value.
419    #[serde_as(deserialize_as = "DefaultOnError")]
420    #[schemars(extend("x-deserialize-default-on-error" = true))]
421    #[serde(default)]
422    pub default: Option<i64>,
423    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
424    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
425    /// these keys.
426    ///
427    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
428    #[serde_as(deserialize_as = "DefaultOnError")]
429    #[schemars(extend("x-deserialize-default-on-error" = true))]
430    #[serde(default)]
431    #[serde(rename = "_meta")]
432    pub meta: Option<Meta>,
433}
434
435impl IntegerPropertySchema {
436    /// Create a new integer property schema.
437    #[must_use]
438    pub fn new() -> Self {
439        Self::default()
440    }
441
442    /// Optional title for the property.
443    #[must_use]
444    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
445        self.title = title.into_option();
446        self
447    }
448
449    /// Human-readable description.
450    #[must_use]
451    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
452        self.description = description.into_option();
453        self
454    }
455
456    /// Minimum value (inclusive).
457    #[must_use]
458    pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
459        self.minimum = minimum.into_option();
460        self
461    }
462
463    /// Maximum value (inclusive).
464    #[must_use]
465    pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
466        self.maximum = maximum.into_option();
467        self
468    }
469
470    /// Default value.
471    #[must_use]
472    pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
473        self.default = default.into_option();
474        self
475    }
476
477    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
478    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
479    /// these keys.
480    ///
481    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
482    #[must_use]
483    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
484        self.meta = meta.into_option();
485        self
486    }
487}
488
489/// Schema for boolean properties in an elicitation form.
490#[serde_as]
491#[skip_serializing_none]
492#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
493#[serde(rename_all = "camelCase")]
494#[non_exhaustive]
495pub struct BooleanPropertySchema {
496    /// Optional title for the property.
497    #[serde_as(deserialize_as = "DefaultOnError")]
498    #[schemars(extend("x-deserialize-default-on-error" = true))]
499    #[serde(default)]
500    pub title: Option<String>,
501    /// Human-readable description.
502    #[serde_as(deserialize_as = "DefaultOnError")]
503    #[schemars(extend("x-deserialize-default-on-error" = true))]
504    #[serde(default)]
505    pub description: Option<String>,
506    /// Default value.
507    #[serde_as(deserialize_as = "DefaultOnError")]
508    #[schemars(extend("x-deserialize-default-on-error" = true))]
509    #[serde(default)]
510    pub default: Option<bool>,
511    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
512    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
513    /// these keys.
514    ///
515    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
516    #[serde_as(deserialize_as = "DefaultOnError")]
517    #[schemars(extend("x-deserialize-default-on-error" = true))]
518    #[serde(default)]
519    #[serde(rename = "_meta")]
520    pub meta: Option<Meta>,
521}
522
523impl BooleanPropertySchema {
524    /// Create a new boolean property schema.
525    #[must_use]
526    pub fn new() -> Self {
527        Self::default()
528    }
529
530    /// Optional title for the property.
531    #[must_use]
532    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
533        self.title = title.into_option();
534        self
535    }
536
537    /// Human-readable description.
538    #[must_use]
539    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
540        self.description = description.into_option();
541        self
542    }
543
544    /// Default value.
545    #[must_use]
546    pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
547        self.default = default.into_option();
548        self
549    }
550
551    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
552    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
553    /// these keys.
554    ///
555    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
556    #[must_use]
557    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
558        self.meta = meta.into_option();
559        self
560    }
561}
562
563/// String item schema for multi-select enum properties.
564#[serde_as]
565#[skip_serializing_none]
566#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
567#[non_exhaustive]
568pub struct StringMultiSelectItems {
569    /// Allowed enum values.
570    #[serde(rename = "enum")]
571    pub values: Vec<String>,
572    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
573    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
574    /// these keys.
575    ///
576    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
577    #[serde_as(deserialize_as = "DefaultOnError")]
578    #[schemars(extend("x-deserialize-default-on-error" = true))]
579    #[serde(default)]
580    #[serde(rename = "_meta")]
581    pub meta: Option<Meta>,
582}
583
584impl StringMultiSelectItems {
585    /// Create new string multi-select items.
586    #[must_use]
587    pub fn new(values: Vec<String>) -> Self {
588        Self { values, meta: None }
589    }
590
591    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
592    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
593    /// these keys.
594    ///
595    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
596    #[must_use]
597    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
598        self.meta = meta.into_option();
599        self
600    }
601}
602
603/// Items definition for titled multi-select enum properties.
604#[serde_as]
605#[skip_serializing_none]
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
607#[non_exhaustive]
608pub struct TitledMultiSelectItems {
609    /// Titled enum options.
610    #[serde(rename = "anyOf")]
611    pub options: Vec<EnumOption>,
612    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
613    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
614    /// these keys.
615    ///
616    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
617    #[serde_as(deserialize_as = "DefaultOnError")]
618    #[schemars(extend("x-deserialize-default-on-error" = true))]
619    #[serde(default)]
620    #[serde(rename = "_meta")]
621    pub meta: Option<Meta>,
622}
623
624impl TitledMultiSelectItems {
625    /// Create new titled multi-select items.
626    #[must_use]
627    pub fn new(options: Vec<EnumOption>) -> Self {
628        Self {
629            options,
630            meta: None,
631        }
632    }
633
634    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
635    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
636    /// these keys.
637    ///
638    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
639    #[must_use]
640    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
641        self.meta = meta.into_option();
642        self
643    }
644}
645
646/// Custom or future typed item schema for multi-select properties.
647///
648/// This preserves unknown item `type` values and the rest of the `items`
649/// payload for clients that store, replay, proxy, or forward elicitation
650/// requests.
651#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
652#[schemars(inline)]
653#[schemars(transform = other_multi_select_items_schema)]
654#[serde(rename_all = "camelCase")]
655#[non_exhaustive]
656pub struct OtherMultiSelectItems {
657    /// Custom or future multi-select item type.
658    ///
659    /// Values beginning with `_` are reserved for implementation-specific
660    /// extensions. Unknown values that do not begin with `_` are reserved for
661    /// future ACP variants.
662    #[serde(rename = "type")]
663    pub type_: String,
664    /// Additional fields from the unknown item schema payload.
665    #[serde(flatten)]
666    pub fields: BTreeMap<String, serde_json::Value>,
667}
668
669impl OtherMultiSelectItems {
670    /// Builds [`OtherMultiSelectItems`] from an unknown discriminator and preserves the remaining extension fields.
671    #[must_use]
672    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
673        fields.remove("type");
674        Self {
675            type_: type_.into(),
676            fields,
677        }
678    }
679}
680
681impl<'de> Deserialize<'de> for OtherMultiSelectItems {
682    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
683    where
684        D: serde::Deserializer<'de>,
685    {
686        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
687        let type_ = fields
688            .remove("type")
689            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
690        let serde_json::Value::String(type_) = type_ else {
691            return Err(serde::de::Error::custom("`type` must be a string"));
692        };
693
694        if is_known_multi_select_item_type(&type_) {
695            return Err(serde::de::Error::custom(format!(
696                "known multi-select item type `{type_}` did not match its schema"
697            )));
698        }
699
700        Ok(Self { type_, fields })
701    }
702}
703
704const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
705
706fn is_known_multi_select_item_type(type_: &str) -> bool {
707    KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
708}
709
710fn other_multi_select_items_schema(schema: &mut Schema) {
711    schema.insert(
712        "not".into(),
713        serde_json::json!({
714            "anyOf": [
715                {
716                    "properties": {
717                        "type": {
718                            "const": "string",
719                            "type": "string"
720                        }
721                    },
722                    "required": ["type"],
723                    "type": "object"
724                }
725            ]
726        }),
727    );
728}
729
730/// Items for a multi-select (array) property schema.
731#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
732#[serde(tag = "type", rename_all = "snake_case")]
733#[schemars(extend("discriminator" = {"propertyName": "type"}))]
734#[non_exhaustive]
735pub enum MultiSelectItems {
736    /// Multi-select string items with plain string values.
737    String(StringMultiSelectItems),
738    /// Custom or future typed multi-select items.
739    #[serde(untagged)]
740    Other(OtherMultiSelectItems),
741    /// Titled multi-select items with human-readable labels.
742    #[serde(untagged)]
743    Titled(TitledMultiSelectItems),
744}
745
746/// Schema for multi-select (array) properties in an elicitation form.
747#[serde_as]
748#[skip_serializing_none]
749#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
750#[serde(rename_all = "camelCase")]
751#[non_exhaustive]
752pub struct MultiSelectPropertySchema {
753    /// Optional title for the property.
754    #[serde_as(deserialize_as = "DefaultOnError")]
755    #[schemars(extend("x-deserialize-default-on-error" = true))]
756    #[serde(default)]
757    pub title: Option<String>,
758    /// Human-readable description.
759    #[serde_as(deserialize_as = "DefaultOnError")]
760    #[schemars(extend("x-deserialize-default-on-error" = true))]
761    #[serde(default)]
762    pub description: Option<String>,
763    /// Minimum number of items to select.
764    #[serde(default)]
765    pub min_items: Option<u64>,
766    /// Maximum number of items to select.
767    #[serde(default)]
768    pub max_items: Option<u64>,
769    /// The items definition describing allowed values.
770    pub items: MultiSelectItems,
771    /// Default selected values.
772    #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
773    #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
774    #[serde(default)]
775    pub default: Option<Vec<String>>,
776    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
777    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
778    /// these keys.
779    ///
780    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
781    #[serde_as(deserialize_as = "DefaultOnError")]
782    #[schemars(extend("x-deserialize-default-on-error" = true))]
783    #[serde(default)]
784    #[serde(rename = "_meta")]
785    pub meta: Option<Meta>,
786}
787
788impl MultiSelectPropertySchema {
789    /// Create a new untitled multi-select property schema.
790    #[must_use]
791    pub fn new(values: Vec<String>) -> Self {
792        Self {
793            title: None,
794            description: None,
795            min_items: None,
796            max_items: None,
797            items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
798            default: None,
799            meta: None,
800        }
801    }
802
803    /// Create a new titled multi-select property schema.
804    #[must_use]
805    pub fn titled(options: Vec<EnumOption>) -> Self {
806        Self {
807            title: None,
808            description: None,
809            min_items: None,
810            max_items: None,
811            items: MultiSelectItems::Titled(TitledMultiSelectItems::new(options)),
812            default: None,
813            meta: None,
814        }
815    }
816
817    /// Optional title for the property.
818    #[must_use]
819    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
820        self.title = title.into_option();
821        self
822    }
823
824    /// Human-readable description.
825    #[must_use]
826    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
827        self.description = description.into_option();
828        self
829    }
830
831    /// Minimum number of items to select.
832    #[must_use]
833    pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
834        self.min_items = min_items.into_option();
835        self
836    }
837
838    /// Maximum number of items to select.
839    #[must_use]
840    pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
841        self.max_items = max_items.into_option();
842        self
843    }
844
845    /// Default selected values.
846    #[must_use]
847    pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
848        self.default = default.into_option();
849        self
850    }
851
852    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
853    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
854    /// these keys.
855    ///
856    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
857    #[must_use]
858    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
859        self.meta = meta.into_option();
860        self
861    }
862}
863
864/// Property schema for elicitation form fields.
865///
866/// Each variant corresponds to a JSON Schema `"type"` value.
867/// Single-select enums use the `String` variant with `enum` or `oneOf` set.
868/// Multi-select enums use the `Array` variant.
869#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
870#[serde(tag = "type", rename_all = "snake_case")]
871#[schemars(extend("discriminator" = {"propertyName": "type"}))]
872#[non_exhaustive]
873pub enum ElicitationPropertySchema {
874    /// String property (or single-select enum when `enum`/`oneOf` is set).
875    String(StringPropertySchema),
876    /// Number (floating-point) property.
877    Number(NumberPropertySchema),
878    /// Integer property.
879    Integer(IntegerPropertySchema),
880    /// Boolean property.
881    Boolean(BooleanPropertySchema),
882    /// Multi-select array property.
883    Array(MultiSelectPropertySchema),
884    /// Custom or future elicitation property schema.
885    ///
886    /// Values beginning with `_` are reserved for implementation-specific
887    /// extensions. Unknown values that do not begin with `_` are reserved for
888    /// future ACP variants.
889    ///
890    /// Clients that do not understand this property schema type should preserve
891    /// the raw schema when storing, replaying, proxying, or forwarding
892    /// elicitation requests. They MUST NOT render it as a known input control.
893    #[serde(untagged)]
894    Other(OtherElicitationPropertySchema),
895}
896
897/// Custom or future elicitation property schema payload.
898///
899/// This preserves the unknown `type` discriminator and the rest of the property
900/// schema object for clients that store, replay, proxy, or forward elicitation
901/// requests.
902#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
903#[schemars(inline)]
904#[schemars(transform = other_elicitation_property_schema_schema)]
905#[serde(rename_all = "camelCase")]
906#[non_exhaustive]
907pub struct OtherElicitationPropertySchema {
908    /// Custom or future elicitation property schema type.
909    ///
910    /// Values beginning with `_` are reserved for implementation-specific
911    /// extensions. Unknown values that do not begin with `_` are reserved for
912    /// future ACP variants.
913    #[serde(rename = "type")]
914    pub type_: String,
915    /// Additional fields from the unknown property schema payload.
916    #[serde(flatten)]
917    pub fields: BTreeMap<String, serde_json::Value>,
918}
919
920impl OtherElicitationPropertySchema {
921    /// Builds [`OtherElicitationPropertySchema`] from an unknown discriminator and preserves the remaining extension fields.
922    #[must_use]
923    pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
924        fields.remove("type");
925        Self {
926            type_: type_.into(),
927            fields,
928        }
929    }
930}
931
932impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
933    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
934    where
935        D: serde::Deserializer<'de>,
936    {
937        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
938        let type_ = fields
939            .remove("type")
940            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
941        let serde_json::Value::String(type_) = type_ else {
942            return Err(serde::de::Error::custom("`type` must be a string"));
943        };
944
945        if is_known_elicitation_property_schema_type(&type_) {
946            return Err(serde::de::Error::custom(format!(
947                "known elicitation property schema type `{type_}` did not match its schema"
948            )));
949        }
950
951        Ok(Self { type_, fields })
952    }
953}
954
955const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
956    &["string", "number", "integer", "boolean", "array"];
957
958fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
959    KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
960}
961
962fn other_elicitation_property_schema_schema(schema: &mut Schema) {
963    let known_value_schemas: Vec<_> = KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES
964        .iter()
965        .map(|value| {
966            serde_json::json!({
967                "properties": {
968                    "type": {
969                        "const": value,
970                        "type": "string"
971                    }
972                },
973                "required": ["type"],
974                "type": "object"
975            })
976        })
977        .collect();
978
979    schema.insert(
980        "not".into(),
981        serde_json::json!({
982            "anyOf": known_value_schemas
983        }),
984    );
985}
986
987impl From<StringPropertySchema> for ElicitationPropertySchema {
988    fn from(schema: StringPropertySchema) -> Self {
989        Self::String(schema)
990    }
991}
992
993impl From<NumberPropertySchema> for ElicitationPropertySchema {
994    fn from(schema: NumberPropertySchema) -> Self {
995        Self::Number(schema)
996    }
997}
998
999impl From<IntegerPropertySchema> for ElicitationPropertySchema {
1000    fn from(schema: IntegerPropertySchema) -> Self {
1001        Self::Integer(schema)
1002    }
1003}
1004
1005impl From<BooleanPropertySchema> for ElicitationPropertySchema {
1006    fn from(schema: BooleanPropertySchema) -> Self {
1007        Self::Boolean(schema)
1008    }
1009}
1010
1011impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
1012    fn from(schema: MultiSelectPropertySchema) -> Self {
1013        Self::Array(schema)
1014    }
1015}
1016
1017fn default_object_type() -> ElicitationSchemaType {
1018    ElicitationSchemaType::Object
1019}
1020
1021/// Type-safe elicitation schema for requesting structured user input.
1022///
1023/// This represents a JSON Schema object with primitive-typed properties,
1024/// as required by the elicitation specification.
1025#[serde_as]
1026#[skip_serializing_none]
1027#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1028#[serde(rename_all = "camelCase")]
1029#[non_exhaustive]
1030pub struct ElicitationSchema {
1031    /// Type discriminator. Always `"object"`.
1032    #[serde_as(deserialize_as = "DefaultOnError")]
1033    #[schemars(extend("x-deserialize-default-on-error" = true))]
1034    #[serde(rename = "type", default = "default_object_type")]
1035    pub type_: ElicitationSchemaType,
1036    /// Optional title for the schema.
1037    #[serde_as(deserialize_as = "DefaultOnError")]
1038    #[schemars(extend("x-deserialize-default-on-error" = true))]
1039    #[serde(default)]
1040    pub title: Option<String>,
1041    /// Property definitions (must be primitive types).
1042    #[serde(default)]
1043    pub properties: BTreeMap<String, ElicitationPropertySchema>,
1044    /// List of required property names.
1045    #[serde(default)]
1046    pub required: Option<Vec<String>>,
1047    /// Optional description of what this schema represents.
1048    #[serde_as(deserialize_as = "DefaultOnError")]
1049    #[schemars(extend("x-deserialize-default-on-error" = true))]
1050    #[serde(default)]
1051    pub description: Option<String>,
1052    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1053    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1054    /// these keys.
1055    ///
1056    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1057    #[serde_as(deserialize_as = "DefaultOnError")]
1058    #[schemars(extend("x-deserialize-default-on-error" = true))]
1059    #[serde(default)]
1060    #[serde(rename = "_meta")]
1061    pub meta: Option<Meta>,
1062}
1063
1064impl Default for ElicitationSchema {
1065    fn default() -> Self {
1066        Self {
1067            type_: default_object_type(),
1068            title: None,
1069            properties: BTreeMap::new(),
1070            required: None,
1071            description: None,
1072            meta: None,
1073        }
1074    }
1075}
1076
1077impl ElicitationSchema {
1078    /// Create a new empty elicitation schema.
1079    #[must_use]
1080    pub fn new() -> Self {
1081        Self::default()
1082    }
1083
1084    /// Optional title for the schema.
1085    #[must_use]
1086    pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1087        self.title = title.into_option();
1088        self
1089    }
1090
1091    /// Optional description of what this schema represents.
1092    #[must_use]
1093    pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1094        self.description = description.into_option();
1095        self
1096    }
1097
1098    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1099    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1100    /// these keys.
1101    ///
1102    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1103    #[must_use]
1104    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1105        self.meta = meta.into_option();
1106        self
1107    }
1108
1109    /// Add a property to the schema.
1110    #[must_use]
1111    pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1112    where
1113        S: Into<ElicitationPropertySchema>,
1114    {
1115        let name = name.into();
1116        self.properties.insert(name.clone(), schema.into());
1117
1118        if required {
1119            let required_fields = self.required.get_or_insert_with(Vec::new);
1120            if !required_fields.contains(&name) {
1121                required_fields.push(name);
1122            }
1123        } else if let Some(required_fields) = &mut self.required {
1124            required_fields.retain(|field| field != &name);
1125
1126            if required_fields.is_empty() {
1127                self.required = None;
1128            }
1129        }
1130
1131        self
1132    }
1133
1134    /// Add a string property.
1135    #[must_use]
1136    pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1137        self.property(name, StringPropertySchema::new(), required)
1138    }
1139
1140    /// Add an email property.
1141    #[must_use]
1142    pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1143        self.property(name, StringPropertySchema::email(), required)
1144    }
1145
1146    /// Add a URI property.
1147    #[must_use]
1148    pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1149        self.property(name, StringPropertySchema::uri(), required)
1150    }
1151
1152    /// Add a date property.
1153    #[must_use]
1154    pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1155        self.property(name, StringPropertySchema::date(), required)
1156    }
1157
1158    /// Add a date-time property.
1159    #[must_use]
1160    pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1161        self.property(name, StringPropertySchema::date_time(), required)
1162    }
1163
1164    /// Add a number property with range.
1165    #[must_use]
1166    pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1167        self.property(
1168            name,
1169            NumberPropertySchema::new().minimum(min).maximum(max),
1170            required,
1171        )
1172    }
1173
1174    /// Add an integer property with range.
1175    #[must_use]
1176    pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1177        self.property(
1178            name,
1179            IntegerPropertySchema::new().minimum(min).maximum(max),
1180            required,
1181        )
1182    }
1183
1184    /// Add a boolean property.
1185    #[must_use]
1186    pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1187        self.property(name, BooleanPropertySchema::new(), required)
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/// Elicitation capabilities supported by the client.
1196#[serde_as]
1197#[skip_serializing_none]
1198#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1199#[serde(rename_all = "camelCase")]
1200#[non_exhaustive]
1201pub struct ElicitationCapabilities {
1202    /// Whether the client supports form-based elicitation.
1203    ///
1204    /// Optional. Omitted or `null` both mean the client does not advertise support.
1205    /// Supplying `{}` means the client supports form-based elicitation.
1206    #[serde_as(deserialize_as = "DefaultOnError")]
1207    #[schemars(extend("x-deserialize-default-on-error" = true))]
1208    #[serde(default)]
1209    pub form: Option<ElicitationFormCapabilities>,
1210    /// Whether the client supports URL-based elicitation.
1211    ///
1212    /// Optional. Omitted or `null` both mean the client does not advertise support.
1213    /// Supplying `{}` means the client supports URL-based elicitation.
1214    #[serde_as(deserialize_as = "DefaultOnError")]
1215    #[schemars(extend("x-deserialize-default-on-error" = true))]
1216    #[serde(default)]
1217    pub url: Option<ElicitationUrlCapabilities>,
1218    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1219    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1220    /// these keys.
1221    ///
1222    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1223    #[serde_as(deserialize_as = "DefaultOnError")]
1224    #[schemars(extend("x-deserialize-default-on-error" = true))]
1225    #[serde(default)]
1226    #[serde(rename = "_meta")]
1227    pub meta: Option<Meta>,
1228}
1229
1230impl ElicitationCapabilities {
1231    /// Builds an empty [`ElicitationCapabilities`]; use builder methods to advertise supported sub-capabilities.
1232    #[must_use]
1233    pub fn new() -> Self {
1234        Self::default()
1235    }
1236
1237    /// Whether the client supports form-based elicitation.
1238    ///
1239    /// Omitted or `null` both mean the client does not advertise support.
1240    /// Supplying `{}` means the client supports form-based elicitation.
1241    #[must_use]
1242    pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1243        self.form = form.into_option();
1244        self
1245    }
1246
1247    /// Whether the client supports URL-based elicitation.
1248    ///
1249    /// Omitted or `null` both mean the client does not advertise support.
1250    /// Supplying `{}` means the client supports URL-based elicitation.
1251    #[must_use]
1252    pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1253        self.url = url.into_option();
1254        self
1255    }
1256
1257    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1258    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1259    /// these keys.
1260    ///
1261    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1262    #[must_use]
1263    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1264        self.meta = meta.into_option();
1265        self
1266    }
1267}
1268
1269/// **UNSTABLE**
1270///
1271/// This capability is not part of the spec yet, and may be removed or changed at any point.
1272///
1273/// Form-based elicitation capabilities.
1274///
1275/// Supplying `{}` means the client supports form-based elicitation.
1276#[serde_as]
1277#[skip_serializing_none]
1278#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1279#[serde(rename_all = "camelCase")]
1280#[non_exhaustive]
1281pub struct ElicitationFormCapabilities {
1282    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1283    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1284    /// these keys.
1285    ///
1286    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1287    #[serde_as(deserialize_as = "DefaultOnError")]
1288    #[schemars(extend("x-deserialize-default-on-error" = true))]
1289    #[serde(default)]
1290    #[serde(rename = "_meta")]
1291    pub meta: Option<Meta>,
1292}
1293
1294impl ElicitationFormCapabilities {
1295    /// Builds an empty [`ElicitationFormCapabilities`]; use builder methods to advertise supported sub-capabilities.
1296    #[must_use]
1297    pub fn new() -> Self {
1298        Self::default()
1299    }
1300
1301    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1302    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1303    /// these keys.
1304    ///
1305    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1306    #[must_use]
1307    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1308        self.meta = meta.into_option();
1309        self
1310    }
1311}
1312
1313/// **UNSTABLE**
1314///
1315/// This capability is not part of the spec yet, and may be removed or changed at any point.
1316///
1317/// URL-based elicitation capabilities.
1318///
1319/// Supplying `{}` means the client supports URL-based elicitation.
1320#[serde_as]
1321#[skip_serializing_none]
1322#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1323#[serde(rename_all = "camelCase")]
1324#[non_exhaustive]
1325pub struct ElicitationUrlCapabilities {
1326    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1327    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1328    /// these keys.
1329    ///
1330    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1331    #[serde_as(deserialize_as = "DefaultOnError")]
1332    #[schemars(extend("x-deserialize-default-on-error" = true))]
1333    #[serde(default)]
1334    #[serde(rename = "_meta")]
1335    pub meta: Option<Meta>,
1336}
1337
1338impl ElicitationUrlCapabilities {
1339    /// Builds an empty [`ElicitationUrlCapabilities`]; use builder methods to advertise supported sub-capabilities.
1340    #[must_use]
1341    pub fn new() -> Self {
1342        Self::default()
1343    }
1344
1345    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1346    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1347    /// these keys.
1348    ///
1349    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1350    #[must_use]
1351    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1352        self.meta = meta.into_option();
1353        self
1354    }
1355}
1356
1357/// **UNSTABLE**
1358///
1359/// This capability is not part of the spec yet, and may be removed or changed at any point.
1360///
1361/// The scope of an elicitation request, determining what context it's tied to.
1362#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1363#[serde(untagged)]
1364#[non_exhaustive]
1365pub enum ElicitationScope {
1366    /// Tied to a session, optionally to a specific tool call within that session.
1367    Session(ElicitationSessionScope),
1368    /// Tied to a specific JSON-RPC request outside of a session
1369    /// (e.g., during auth/configuration phases before any session is started).
1370    Request(ElicitationRequestScope),
1371}
1372
1373/// **UNSTABLE**
1374///
1375/// This capability is not part of the spec yet, and may be removed or changed at any point.
1376///
1377/// Session-scoped elicitation, optionally tied to a specific tool call.
1378///
1379/// When `tool_call_id` is set, the elicitation is tied to a specific tool call.
1380/// This is useful when an agent receives an elicitation from an MCP server
1381/// during a tool call and needs to redirect it to the user.
1382#[serde_as]
1383#[skip_serializing_none]
1384#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1385#[serde(rename_all = "camelCase")]
1386#[non_exhaustive]
1387pub struct ElicitationSessionScope {
1388    /// The session this elicitation is tied to.
1389    pub session_id: SessionId,
1390    /// Optional tool call within the session.
1391    #[serde_as(deserialize_as = "DefaultOnError")]
1392    #[schemars(extend("x-deserialize-default-on-error" = true))]
1393    #[serde(default)]
1394    pub tool_call_id: Option<ToolCallId>,
1395}
1396
1397impl ElicitationSessionScope {
1398    /// Builds [`ElicitationSessionScope`] with the required fields set; optional fields start unset or empty.
1399    #[must_use]
1400    pub fn new(session_id: impl Into<SessionId>) -> Self {
1401        Self {
1402            session_id: session_id.into(),
1403            tool_call_id: None,
1404        }
1405    }
1406
1407    /// Sets or clears the optional `toolCallId` field.
1408    #[must_use]
1409    pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1410        self.tool_call_id = tool_call_id.into_option();
1411        self
1412    }
1413}
1414
1415/// **UNSTABLE**
1416///
1417/// This capability is not part of the spec yet, and may be removed or changed at any point.
1418///
1419/// Request-scoped elicitation, tied to a specific JSON-RPC request outside of a session
1420/// (e.g., during auth/configuration phases before any session is started).
1421#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1422#[serde(rename_all = "camelCase")]
1423#[non_exhaustive]
1424pub struct ElicitationRequestScope {
1425    /// The request this elicitation is tied to.
1426    pub request_id: RequestId,
1427}
1428
1429impl ElicitationRequestScope {
1430    /// Builds [`ElicitationRequestScope`] with the required fields set; optional fields start unset or empty.
1431    #[must_use]
1432    pub fn new(request_id: impl Into<RequestId>) -> Self {
1433        Self {
1434            request_id: request_id.into(),
1435        }
1436    }
1437}
1438
1439impl From<ElicitationSessionScope> for ElicitationScope {
1440    fn from(scope: ElicitationSessionScope) -> Self {
1441        Self::Session(scope)
1442    }
1443}
1444
1445impl From<ElicitationRequestScope> for ElicitationScope {
1446    fn from(scope: ElicitationRequestScope) -> Self {
1447        Self::Request(scope)
1448    }
1449}
1450
1451/// **UNSTABLE**
1452///
1453/// This capability is not part of the spec yet, and may be removed or changed at any point.
1454///
1455/// Request from the agent to elicit structured user input.
1456///
1457/// The agent sends this to the client to request information from the user,
1458/// either via a form or by directing them to a URL.
1459/// Elicitations are tied to a session (optionally a tool call) or a request.
1460#[serde_as]
1461#[skip_serializing_none]
1462#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1463#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1464#[serde(rename_all = "camelCase")]
1465#[non_exhaustive]
1466pub struct CreateElicitationRequest {
1467    /// The elicitation mode and its mode-specific fields.
1468    #[serde(flatten)]
1469    pub mode: ElicitationMode,
1470    /// A human-readable message describing what input is needed.
1471    pub message: String,
1472    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1473    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1474    /// these keys.
1475    ///
1476    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1477    #[serde_as(deserialize_as = "DefaultOnError")]
1478    #[schemars(extend("x-deserialize-default-on-error" = true))]
1479    #[serde(default)]
1480    #[serde(rename = "_meta")]
1481    pub meta: Option<Meta>,
1482}
1483
1484impl CreateElicitationRequest {
1485    /// Builds [`CreateElicitationRequest`] with the required request fields set; optional fields start unset or empty.
1486    #[must_use]
1487    pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1488        Self {
1489            mode: mode.into(),
1490            message: message.into(),
1491            meta: None,
1492        }
1493    }
1494
1495    /// Returns the scope this elicitation is tied to.
1496    #[must_use]
1497    pub fn scope(&self) -> &ElicitationScope {
1498        self.mode.scope()
1499    }
1500
1501    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1502    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1503    /// these keys.
1504    ///
1505    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1506    #[must_use]
1507    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1508        self.meta = meta.into_option();
1509        self
1510    }
1511}
1512
1513/// **UNSTABLE**
1514///
1515/// This capability is not part of the spec yet, and may be removed or changed at any point.
1516///
1517/// The mode of elicitation, determining how user input is collected.
1518#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1519#[serde(tag = "mode", rename_all = "snake_case")]
1520#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1521#[non_exhaustive]
1522pub enum ElicitationMode {
1523    /// Form-based elicitation where the client renders a form from the provided schema.
1524    Form(ElicitationFormMode),
1525    /// URL-based elicitation where the client directs the user to a URL.
1526    Url(ElicitationUrlMode),
1527    /// Custom or future elicitation mode.
1528    ///
1529    /// Values beginning with `_` are reserved for implementation-specific
1530    /// extensions. Unknown values that do not begin with `_` are reserved for
1531    /// future ACP variants.
1532    ///
1533    /// Clients that do not understand this mode should preserve the raw payload
1534    /// when storing, replaying, proxying, or forwarding elicitation requests.
1535    /// They MUST NOT render it as a known elicitation mode.
1536    #[serde(untagged)]
1537    Other(OtherElicitationMode),
1538}
1539
1540/// Custom or future elicitation mode payload.
1541///
1542/// This preserves the unknown `mode` discriminator and the rest of the mode
1543/// object for clients that store, replay, proxy, or forward elicitation
1544/// requests.
1545#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1546#[schemars(inline)]
1547#[schemars(transform = other_elicitation_mode_schema)]
1548#[serde(rename_all = "camelCase")]
1549#[non_exhaustive]
1550pub struct OtherElicitationMode {
1551    /// Custom or future elicitation mode.
1552    ///
1553    /// Values beginning with `_` are reserved for implementation-specific
1554    /// extensions. Unknown values that do not begin with `_` are reserved for
1555    /// future ACP variants.
1556    pub mode: String,
1557    /// The scope this elicitation is tied to.
1558    #[serde(flatten)]
1559    pub scope: ElicitationScope,
1560    /// Additional fields from the unknown elicitation mode payload.
1561    #[serde(flatten)]
1562    pub fields: BTreeMap<String, serde_json::Value>,
1563}
1564
1565impl OtherElicitationMode {
1566    /// Builds [`OtherElicitationMode`] from an unknown discriminator and preserves the remaining extension fields.
1567    #[must_use]
1568    pub fn new(
1569        mode: impl Into<String>,
1570        scope: impl Into<ElicitationScope>,
1571        mut fields: BTreeMap<String, serde_json::Value>,
1572    ) -> Self {
1573        fields.remove("mode");
1574        remove_elicitation_scope_fields(&mut fields);
1575        Self {
1576            mode: mode.into(),
1577            scope: scope.into(),
1578            fields,
1579        }
1580    }
1581}
1582
1583impl<'de> Deserialize<'de> for OtherElicitationMode {
1584    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1585    where
1586        D: serde::Deserializer<'de>,
1587    {
1588        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1589        let mode = fields
1590            .remove("mode")
1591            .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1592        let serde_json::Value::String(mode) = mode else {
1593            return Err(serde::de::Error::custom("`mode` must be a string"));
1594        };
1595
1596        if is_known_elicitation_mode(&mode) {
1597            return Err(serde::de::Error::custom(format!(
1598                "known elicitation mode `{mode}` did not match its schema"
1599            )));
1600        }
1601
1602        let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1603            fields.clone().into_iter().collect(),
1604        ))
1605        .map_err(serde::de::Error::custom)?;
1606        remove_elicitation_scope_fields(&mut fields);
1607
1608        Ok(Self {
1609            mode,
1610            scope,
1611            fields,
1612        })
1613    }
1614}
1615
1616const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1617
1618fn is_known_elicitation_mode(mode: &str) -> bool {
1619    KNOWN_ELICITATION_MODES.contains(&mode)
1620}
1621
1622fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1623    fields.remove("sessionId");
1624    fields.remove("toolCallId");
1625    fields.remove("requestId");
1626}
1627
1628fn other_elicitation_mode_schema(schema: &mut Schema) {
1629    let known_value_schemas: Vec<_> = KNOWN_ELICITATION_MODES
1630        .iter()
1631        .map(|value| {
1632            serde_json::json!({
1633                "properties": {
1634                    "mode": {
1635                        "const": value,
1636                        "type": "string"
1637                    }
1638                },
1639                "required": ["mode"],
1640                "type": "object"
1641            })
1642        })
1643        .collect();
1644
1645    schema.insert(
1646        "not".into(),
1647        serde_json::json!({
1648            "anyOf": known_value_schemas
1649        }),
1650    );
1651}
1652
1653impl From<ElicitationFormMode> for ElicitationMode {
1654    fn from(mode: ElicitationFormMode) -> Self {
1655        Self::Form(mode)
1656    }
1657}
1658
1659impl From<ElicitationUrlMode> for ElicitationMode {
1660    fn from(mode: ElicitationUrlMode) -> Self {
1661        Self::Url(mode)
1662    }
1663}
1664
1665impl From<OtherElicitationMode> for ElicitationMode {
1666    fn from(mode: OtherElicitationMode) -> Self {
1667        Self::Other(mode)
1668    }
1669}
1670
1671impl ElicitationMode {
1672    /// Returns the scope this elicitation mode is tied to.
1673    #[must_use]
1674    pub fn scope(&self) -> &ElicitationScope {
1675        match self {
1676            Self::Form(f) => &f.scope,
1677            Self::Url(u) => &u.scope,
1678            Self::Other(other) => &other.scope,
1679        }
1680    }
1681}
1682
1683/// **UNSTABLE**
1684///
1685/// This capability is not part of the spec yet, and may be removed or changed at any point.
1686///
1687/// Form-based elicitation mode where the client renders a form from the provided schema.
1688#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1689#[serde(rename_all = "camelCase")]
1690#[non_exhaustive]
1691pub struct ElicitationFormMode {
1692    /// The scope this elicitation is tied to.
1693    #[serde(flatten)]
1694    pub scope: ElicitationScope,
1695    /// A JSON Schema describing the form fields to present to the user.
1696    pub requested_schema: ElicitationSchema,
1697}
1698
1699impl ElicitationFormMode {
1700    /// Builds [`ElicitationFormMode`] with the required fields set; optional fields start unset or empty.
1701    #[must_use]
1702    pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1703        Self {
1704            scope: scope.into(),
1705            requested_schema,
1706        }
1707    }
1708}
1709
1710/// **UNSTABLE**
1711///
1712/// This capability is not part of the spec yet, and may be removed or changed at any point.
1713///
1714/// URL-based elicitation mode where the client directs the user to a URL.
1715#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1716#[serde(rename_all = "camelCase")]
1717#[non_exhaustive]
1718pub struct ElicitationUrlMode {
1719    /// The scope this elicitation is tied to.
1720    #[serde(flatten)]
1721    pub scope: ElicitationScope,
1722    /// The unique identifier for this elicitation.
1723    pub elicitation_id: ElicitationId,
1724    /// The URL to direct the user to.
1725    #[schemars(extend("format" = "uri"))]
1726    pub url: String,
1727}
1728
1729impl ElicitationUrlMode {
1730    /// Builds [`ElicitationUrlMode`] with the required fields set; optional fields start unset or empty.
1731    #[must_use]
1732    pub fn new(
1733        scope: impl Into<ElicitationScope>,
1734        elicitation_id: impl Into<ElicitationId>,
1735        url: impl Into<String>,
1736    ) -> Self {
1737        Self {
1738            scope: scope.into(),
1739            elicitation_id: elicitation_id.into(),
1740            url: url.into(),
1741        }
1742    }
1743}
1744
1745/// **UNSTABLE**
1746///
1747/// This capability is not part of the spec yet, and may be removed or changed at any point.
1748///
1749/// Response from the client to an elicitation request.
1750#[serde_as]
1751#[skip_serializing_none]
1752#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1753#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1754#[serde(rename_all = "camelCase")]
1755#[non_exhaustive]
1756pub struct CreateElicitationResponse {
1757    /// The user's action in response to the elicitation.
1758    #[serde(flatten)]
1759    pub action: ElicitationAction,
1760    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1761    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1762    /// these keys.
1763    ///
1764    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1765    #[serde_as(deserialize_as = "DefaultOnError")]
1766    #[schemars(extend("x-deserialize-default-on-error" = true))]
1767    #[serde(default)]
1768    #[serde(rename = "_meta")]
1769    pub meta: Option<Meta>,
1770}
1771
1772impl CreateElicitationResponse {
1773    /// Builds [`CreateElicitationResponse`] with the required response fields set; optional fields start unset or empty.
1774    #[must_use]
1775    pub fn new(action: impl Into<ElicitationAction>) -> Self {
1776        Self {
1777            action: action.into(),
1778            meta: None,
1779        }
1780    }
1781
1782    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
1783    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
1784    /// these keys.
1785    ///
1786    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
1787    #[must_use]
1788    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1789        self.meta = meta.into_option();
1790        self
1791    }
1792}
1793
1794/// **UNSTABLE**
1795///
1796/// This capability is not part of the spec yet, and may be removed or changed at any point.
1797///
1798/// The user's action in response to an elicitation.
1799#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1800#[serde(tag = "action", rename_all = "snake_case")]
1801#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1802#[non_exhaustive]
1803pub enum ElicitationAction {
1804    /// The user accepted and provided content.
1805    Accept(ElicitationAcceptAction),
1806    /// The user declined the elicitation.
1807    Decline,
1808    /// The elicitation was cancelled.
1809    Cancel,
1810    /// Custom or future elicitation action.
1811    ///
1812    /// Values beginning with `_` are reserved for implementation-specific
1813    /// extensions. Unknown values that do not begin with `_` are reserved for
1814    /// future ACP variants.
1815    ///
1816    /// Agents that do not understand this action should preserve the raw
1817    /// payload when storing, replaying, proxying, or forwarding elicitation
1818    /// responses. They MUST NOT treat it as a known elicitation action.
1819    #[serde(untagged)]
1820    Other(OtherElicitationAction),
1821}
1822
1823/// Custom or future elicitation action payload.
1824///
1825/// This preserves the unknown `action` discriminator and the rest of the
1826/// response object for agents that store, replay, proxy, or forward elicitation
1827/// responses.
1828#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1829#[schemars(inline)]
1830#[schemars(transform = other_elicitation_action_schema)]
1831#[serde(rename_all = "camelCase")]
1832#[non_exhaustive]
1833pub struct OtherElicitationAction {
1834    /// Custom or future elicitation action.
1835    ///
1836    /// Values beginning with `_` are reserved for implementation-specific
1837    /// extensions. Unknown values that do not begin with `_` are reserved for
1838    /// future ACP variants.
1839    pub action: String,
1840    /// Additional fields from the unknown elicitation action payload.
1841    #[serde(flatten)]
1842    pub fields: BTreeMap<String, serde_json::Value>,
1843}
1844
1845impl OtherElicitationAction {
1846    /// Builds [`OtherElicitationAction`] from an unknown discriminator and preserves the remaining extension fields.
1847    #[must_use]
1848    pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1849        fields.remove("action");
1850        Self {
1851            action: action.into(),
1852            fields,
1853        }
1854    }
1855}
1856
1857impl<'de> Deserialize<'de> for OtherElicitationAction {
1858    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1859    where
1860        D: serde::Deserializer<'de>,
1861    {
1862        let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1863        let action = fields
1864            .remove("action")
1865            .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1866        let serde_json::Value::String(action) = action else {
1867            return Err(serde::de::Error::custom("`action` must be a string"));
1868        };
1869
1870        if is_known_elicitation_action(&action) {
1871            return Err(serde::de::Error::custom(format!(
1872                "known elicitation action `{action}` did not match its schema"
1873            )));
1874        }
1875
1876        Ok(Self { action, fields })
1877    }
1878}
1879
1880const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1881
1882fn is_known_elicitation_action(action: &str) -> bool {
1883    KNOWN_ELICITATION_ACTIONS.contains(&action)
1884}
1885
1886fn other_elicitation_action_schema(schema: &mut Schema) {
1887    let known_value_schemas: Vec<_> = KNOWN_ELICITATION_ACTIONS
1888        .iter()
1889        .map(|value| {
1890            serde_json::json!({
1891                "properties": {
1892                    "action": {
1893                        "const": value,
1894                        "type": "string"
1895                    }
1896                },
1897                "required": ["action"],
1898                "type": "object"
1899            })
1900        })
1901        .collect();
1902
1903    schema.insert(
1904        "not".into(),
1905        serde_json::json!({
1906            "anyOf": known_value_schemas
1907        }),
1908    );
1909}
1910
1911impl From<ElicitationAcceptAction> for ElicitationAction {
1912    fn from(action: ElicitationAcceptAction) -> Self {
1913        Self::Accept(action)
1914    }
1915}
1916
1917impl From<OtherElicitationAction> for ElicitationAction {
1918    fn from(action: OtherElicitationAction) -> Self {
1919        Self::Other(action)
1920    }
1921}
1922
1923/// **UNSTABLE**
1924///
1925/// This capability is not part of the spec yet, and may be removed or changed at any point.
1926///
1927/// The user accepted the elicitation and provided content.
1928#[serde_as]
1929#[skip_serializing_none]
1930#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1931#[serde(rename_all = "camelCase")]
1932#[non_exhaustive]
1933pub struct ElicitationAcceptAction {
1934    /// The user-provided content, if any, as an object matching the requested schema.
1935    #[serde(default)]
1936    pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1937}
1938
1939impl ElicitationAcceptAction {
1940    /// Builds [`ElicitationAcceptAction`] with the required fields set; optional fields start unset or empty.
1941    #[must_use]
1942    pub fn new() -> Self {
1943        Self { content: None }
1944    }
1945
1946    /// The user-provided content as an object matching the requested schema.
1947    #[must_use]
1948    pub fn content(
1949        mut self,
1950        content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1951    ) -> Self {
1952        self.content = content.into_option();
1953        self
1954    }
1955}
1956
1957/// Allowed wire representations for [`ElicitationContentValue`].
1958#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1959#[serde(untagged)]
1960#[non_exhaustive]
1961pub enum ElicitationContentValue {
1962    /// String value accepted in elicitation response content.
1963    String(String),
1964    /// Integer value accepted in elicitation response content.
1965    Integer(i64),
1966    /// Number value accepted in elicitation response content.
1967    Number(f64),
1968    /// Boolean value accepted in elicitation response content.
1969    Boolean(bool),
1970    /// String array value accepted in elicitation response content.
1971    StringArray(Vec<String>),
1972}
1973
1974impl From<String> for ElicitationContentValue {
1975    fn from(value: String) -> Self {
1976        Self::String(value)
1977    }
1978}
1979
1980impl From<&str> for ElicitationContentValue {
1981    fn from(value: &str) -> Self {
1982        Self::String(value.to_string())
1983    }
1984}
1985
1986impl From<i64> for ElicitationContentValue {
1987    fn from(value: i64) -> Self {
1988        Self::Integer(value)
1989    }
1990}
1991
1992impl From<i32> for ElicitationContentValue {
1993    fn from(value: i32) -> Self {
1994        Self::Integer(i64::from(value))
1995    }
1996}
1997
1998impl From<f64> for ElicitationContentValue {
1999    fn from(value: f64) -> Self {
2000        Self::Number(value)
2001    }
2002}
2003
2004impl From<bool> for ElicitationContentValue {
2005    fn from(value: bool) -> Self {
2006        Self::Boolean(value)
2007    }
2008}
2009
2010impl From<Vec<String>> for ElicitationContentValue {
2011    fn from(value: Vec<String>) -> Self {
2012        Self::StringArray(value)
2013    }
2014}
2015
2016impl From<Vec<&str>> for ElicitationContentValue {
2017    fn from(value: Vec<&str>) -> Self {
2018        Self::StringArray(value.into_iter().map(str::to_string).collect())
2019    }
2020}
2021
2022impl Default for ElicitationAcceptAction {
2023    fn default() -> Self {
2024        Self::new()
2025    }
2026}
2027
2028/// **UNSTABLE**
2029///
2030/// This capability is not part of the spec yet, and may be removed or changed at any point.
2031///
2032/// Notification sent by the agent when a URL-based elicitation is complete.
2033#[serde_as]
2034#[skip_serializing_none]
2035#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2036#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
2037#[serde(rename_all = "camelCase")]
2038#[non_exhaustive]
2039pub struct CompleteElicitationNotification {
2040    /// The ID of the elicitation that completed.
2041    pub elicitation_id: ElicitationId,
2042    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2043    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2044    /// these keys.
2045    ///
2046    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2047    #[serde_as(deserialize_as = "DefaultOnError")]
2048    #[schemars(extend("x-deserialize-default-on-error" = true))]
2049    #[serde(default)]
2050    #[serde(rename = "_meta")]
2051    pub meta: Option<Meta>,
2052}
2053
2054impl CompleteElicitationNotification {
2055    /// Builds [`CompleteElicitationNotification`] with the required notification fields set; optional fields start unset or empty.
2056    #[must_use]
2057    pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2058        Self {
2059            elicitation_id: elicitation_id.into(),
2060            meta: None,
2061        }
2062    }
2063
2064    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
2065    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
2066    /// these keys.
2067    ///
2068    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2069    #[must_use]
2070    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2071        self.meta = meta.into_option();
2072        self
2073    }
2074}
2075
2076#[cfg(test)]
2077mod tests {
2078    use super::*;
2079    use serde_json::json;
2080
2081    #[test]
2082    fn form_mode_request_serialization() {
2083        let schema = ElicitationSchema::new().string("name", true);
2084        let req = CreateElicitationRequest::new(
2085            ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2086            "Please enter your name",
2087        );
2088
2089        let json = serde_json::to_value(&req).unwrap();
2090        assert_eq!(json["sessionId"], "sess_1");
2091        assert!(json.get("toolCallId").is_none());
2092        assert_eq!(json["mode"], "form");
2093        assert_eq!(json["message"], "Please enter your name");
2094        assert!(json["requestedSchema"].is_object());
2095        assert_eq!(json["requestedSchema"]["type"], "object");
2096        assert_eq!(
2097            json["requestedSchema"]["properties"]["name"]["type"],
2098            "string"
2099        );
2100
2101        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2102        assert_eq!(
2103            *roundtripped.scope(),
2104            ElicitationSessionScope::new("sess_1").into()
2105        );
2106        assert_eq!(roundtripped.message, "Please enter your name");
2107        assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2108    }
2109
2110    #[test]
2111    fn url_mode_request_serialization() {
2112        let req = CreateElicitationRequest::new(
2113            ElicitationUrlMode::new(
2114                ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2115                "elic_1",
2116                "https://example.com/auth",
2117            ),
2118            "Please authenticate",
2119        );
2120
2121        let json = serde_json::to_value(&req).unwrap();
2122        assert_eq!(json["sessionId"], "sess_2");
2123        assert_eq!(json["toolCallId"], "tc_1");
2124        assert_eq!(json["mode"], "url");
2125        assert_eq!(json["elicitationId"], "elic_1");
2126        assert_eq!(json["url"], "https://example.com/auth");
2127        assert_eq!(json["message"], "Please authenticate");
2128
2129        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2130        assert_eq!(
2131            *roundtripped.scope(),
2132            ElicitationSessionScope::new("sess_2")
2133                .tool_call_id("tc_1")
2134                .into()
2135        );
2136        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2137    }
2138
2139    #[test]
2140    fn response_accept_serialization() {
2141        let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2142            ElicitationAcceptAction::new().content(BTreeMap::from([(
2143                "name".to_string(),
2144                ElicitationContentValue::from("Alice"),
2145            )])),
2146        ));
2147
2148        let json = serde_json::to_value(&resp).unwrap();
2149        assert_eq!(json["action"], "accept");
2150        assert_eq!(json["content"]["name"], "Alice");
2151
2152        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2153        assert!(matches!(
2154            roundtripped.action,
2155            ElicitationAction::Accept(ElicitationAcceptAction {
2156                content: Some(_),
2157                ..
2158            })
2159        ));
2160    }
2161
2162    #[test]
2163    fn response_decline_serialization() {
2164        let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2165
2166        let json = serde_json::to_value(&resp).unwrap();
2167        assert_eq!(json["action"], "decline");
2168
2169        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2170        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2171    }
2172
2173    #[test]
2174    fn response_cancel_serialization() {
2175        let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2176
2177        let json = serde_json::to_value(&resp).unwrap();
2178        assert_eq!(json["action"], "cancel");
2179
2180        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2181        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2182    }
2183
2184    #[test]
2185    fn unknown_action_response_serialization() {
2186        let json = json!({
2187            "action": "_defer",
2188            "reason": "waiting",
2189            "retryAfterMs": 1000
2190        });
2191
2192        let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2193        let ElicitationAction::Other(other) = &resp.action else {
2194            panic!("expected unknown elicitation action");
2195        };
2196
2197        assert_eq!(other.action, "_defer");
2198        assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2199        assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2200        assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2201    }
2202
2203    #[test]
2204    fn unknown_action_does_not_hide_known_action() {
2205        assert!(
2206            serde_json::from_value::<OtherElicitationAction>(json!({
2207                "action": "accept",
2208                "content": {}
2209            }))
2210            .is_err()
2211        );
2212        assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2213    }
2214
2215    #[test]
2216    fn url_mode_request_scope_serialization() {
2217        let req = CreateElicitationRequest::new(
2218            ElicitationUrlMode::new(
2219                ElicitationRequestScope::new(RequestId::Number(42)),
2220                "elic_2",
2221                "https://example.com/setup",
2222            ),
2223            "Please complete setup",
2224        );
2225
2226        let json = serde_json::to_value(&req).unwrap();
2227        assert_eq!(json["requestId"], 42);
2228        assert!(json.get("sessionId").is_none());
2229        assert_eq!(json["mode"], "url");
2230        assert_eq!(json["elicitationId"], "elic_2");
2231        assert_eq!(json["url"], "https://example.com/setup");
2232        assert_eq!(json["message"], "Please complete setup");
2233
2234        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2235        assert_eq!(
2236            *roundtripped.scope(),
2237            ElicitationRequestScope::new(RequestId::Number(42)).into()
2238        );
2239        assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2240    }
2241
2242    #[test]
2243    fn unknown_mode_request_serialization() {
2244        let json = json!({
2245            "requestId": 42,
2246            "mode": "_browser",
2247            "message": "Open a browser window",
2248            "target": "login"
2249        });
2250
2251        let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2252        let ElicitationMode::Other(other) = &req.mode else {
2253            panic!("expected unknown elicitation mode");
2254        };
2255
2256        assert_eq!(other.mode, "_browser");
2257        assert_eq!(
2258            other.scope,
2259            ElicitationRequestScope::new(RequestId::Number(42)).into()
2260        );
2261        assert_eq!(other.fields.get("target"), Some(&json!("login")));
2262        assert_eq!(
2263            *req.scope(),
2264            ElicitationRequestScope::new(RequestId::Number(42)).into()
2265        );
2266        assert_eq!(serde_json::to_value(&req).unwrap(), json);
2267    }
2268
2269    #[test]
2270    fn unknown_mode_does_not_hide_malformed_known_mode() {
2271        let missing_requested_schema = json!({
2272            "requestId": 42,
2273            "mode": "form",
2274            "message": "Enter your name"
2275        });
2276
2277        assert!(
2278            serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2279        );
2280        assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2281    }
2282
2283    #[test]
2284    fn request_scope_request_serialization() {
2285        let req = CreateElicitationRequest::new(
2286            ElicitationFormMode::new(
2287                ElicitationRequestScope::new(RequestId::Number(99)),
2288                ElicitationSchema::new().string("workspace", true),
2289            ),
2290            "Enter workspace name",
2291        );
2292
2293        let json = serde_json::to_value(&req).unwrap();
2294        assert_eq!(json["requestId"], 99);
2295        assert!(json.get("sessionId").is_none());
2296
2297        let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2298        assert_eq!(
2299            *roundtripped.scope(),
2300            ElicitationRequestScope::new(RequestId::Number(99)).into()
2301        );
2302    }
2303
2304    /// `ClientResponse` is `#[serde(untagged)]` with `WriteTextFileResponse` (which has
2305    /// `#[serde(default)]`) listed first, so standalone deserialization is ambiguous.
2306    /// In practice, the RPC layer selects the correct variant based on the originating
2307    /// request method. These tests verify that serialization through `ClientResponse`
2308    /// produces the correct flattened wire format and round-trips back via the
2309    /// concrete `CreateElicitationResponse` type.
2310    #[test]
2311    fn client_response_serialization_accept() {
2312        use crate::v1::ClientResponse;
2313
2314        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2315            ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
2316                "name".to_string(),
2317                ElicitationContentValue::from("Alice"),
2318            )]))),
2319        ));
2320        let json = serde_json::to_value(&resp).unwrap();
2321        assert_eq!(json["action"], "accept");
2322        assert_eq!(json["content"]["name"], "Alice");
2323
2324        // Round-trip back through the concrete type
2325        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2326        assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2327    }
2328
2329    #[test]
2330    fn client_response_serialization_decline() {
2331        use crate::v1::ClientResponse;
2332
2333        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2334            ElicitationAction::Decline,
2335        ));
2336        let json = serde_json::to_value(&resp).unwrap();
2337        assert_eq!(json["action"], "decline");
2338
2339        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2340        assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2341    }
2342
2343    #[test]
2344    fn client_response_serialization_cancel() {
2345        use crate::v1::ClientResponse;
2346
2347        let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2348            ElicitationAction::Cancel,
2349        ));
2350        let json = serde_json::to_value(&resp).unwrap();
2351        assert_eq!(json["action"], "cancel");
2352
2353        let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2354        assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2355    }
2356
2357    /// Guard against serde regressions with the `flatten` + internally-tagged combination.
2358    /// Extra fields in the JSON must not cause deserialization failures.
2359    #[test]
2360    fn request_tolerates_extra_fields() {
2361        let json = json!({
2362            "sessionId": "sess_1",
2363            "mode": "form",
2364            "message": "Enter your name",
2365            "requestedSchema": {
2366                "type": "object",
2367                "properties": {
2368                    "name": { "type": "string", "title": "Name" }
2369                },
2370                "required": ["name"]
2371            },
2372            "unknownStringField": "hello",
2373            "unknownNumberField": 42
2374        });
2375
2376        let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2377        assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2378        assert_eq!(req.message, "Enter your name");
2379        assert!(matches!(req.mode, ElicitationMode::Form(_)));
2380    }
2381
2382    #[test]
2383    fn completion_notification_serialization() {
2384        let notif = CompleteElicitationNotification::new("elic_1");
2385
2386        let json = serde_json::to_value(&notif).unwrap();
2387        assert_eq!(json["elicitationId"], "elic_1");
2388
2389        let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2390        assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2391    }
2392
2393    #[test]
2394    fn capabilities_form_only() {
2395        let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2396
2397        let json = serde_json::to_value(&caps).unwrap();
2398        assert!(json["form"].is_object());
2399        assert!(json.get("url").is_none());
2400
2401        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2402        assert!(roundtripped.form.is_some());
2403        assert!(roundtripped.url.is_none());
2404    }
2405
2406    #[test]
2407    fn capabilities_url_only() {
2408        let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2409
2410        let json = serde_json::to_value(&caps).unwrap();
2411        assert!(json.get("form").is_none());
2412        assert!(json["url"].is_object());
2413
2414        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2415        assert!(roundtripped.form.is_none());
2416        assert!(roundtripped.url.is_some());
2417    }
2418
2419    #[test]
2420    fn capabilities_both() {
2421        let caps = ElicitationCapabilities::new()
2422            .form(ElicitationFormCapabilities::new())
2423            .url(ElicitationUrlCapabilities::new());
2424
2425        let json = serde_json::to_value(&caps).unwrap();
2426        assert!(json["form"].is_object());
2427        assert!(json["url"].is_object());
2428
2429        let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2430        assert!(roundtripped.form.is_some());
2431        assert!(roundtripped.url.is_some());
2432    }
2433
2434    #[test]
2435    fn schema_default_sets_object_type() {
2436        let schema = ElicitationSchema::default();
2437
2438        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2439        assert!(schema.properties.is_empty());
2440
2441        let json = serde_json::to_value(&schema).unwrap();
2442        assert_eq!(json["type"], "object");
2443    }
2444
2445    #[test]
2446    fn schema_builder_serialization() {
2447        let schema = ElicitationSchema::new()
2448            .string("name", true)
2449            .email("email", true)
2450            .integer("age", 0, 150, true)
2451            .boolean("newsletter", false)
2452            .description("User registration");
2453
2454        let json = serde_json::to_value(&schema).unwrap();
2455        assert_eq!(json["type"], "object");
2456        assert_eq!(json["description"], "User registration");
2457        assert_eq!(json["properties"]["name"]["type"], "string");
2458        assert_eq!(json["properties"]["email"]["type"], "string");
2459        assert_eq!(json["properties"]["email"]["format"], "email");
2460        assert_eq!(json["properties"]["age"]["type"], "integer");
2461        assert_eq!(json["properties"]["age"]["minimum"], 0);
2462        assert_eq!(json["properties"]["age"]["maximum"], 150);
2463        assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2464
2465        let required = json["required"].as_array().unwrap();
2466        assert!(required.contains(&json!("name")));
2467        assert!(required.contains(&json!("email")));
2468        assert!(required.contains(&json!("age")));
2469        assert!(!required.contains(&json!("newsletter")));
2470
2471        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2472        assert_eq!(roundtripped.properties.len(), 4);
2473        assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2474    }
2475
2476    #[test]
2477    fn schema_string_enum_serialization() {
2478        let schema = ElicitationSchema::new().property(
2479            "color",
2480            StringPropertySchema::new().enum_values(vec![
2481                "red".into(),
2482                "green".into(),
2483                "blue".into(),
2484            ]),
2485            true,
2486        );
2487
2488        let json = serde_json::to_value(&schema).unwrap();
2489        assert_eq!(json["properties"]["color"]["type"], "string");
2490        let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2491        assert_eq!(enum_vals.len(), 3);
2492
2493        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2494        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2495        {
2496            assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2497        } else {
2498            panic!("expected String variant");
2499        }
2500    }
2501
2502    #[test]
2503    fn schema_multi_select_serialization() {
2504        let schema = ElicitationSchema::new().property(
2505            "colors",
2506            MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2507                .min_items(1)
2508                .max_items(3),
2509            false,
2510        );
2511
2512        let json = serde_json::to_value(&schema).unwrap();
2513        assert_eq!(json["properties"]["colors"]["type"], "array");
2514        assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2515        assert_eq!(json["properties"]["colors"]["minItems"], 1);
2516        assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2517
2518        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2519        let ElicitationPropertySchema::Array(array) =
2520            roundtripped.properties.get("colors").unwrap()
2521        else {
2522            panic!("expected Array variant");
2523        };
2524        let MultiSelectItems::String(items) = &array.items else {
2525            panic!("expected String multi-select items");
2526        };
2527        assert_eq!(items.values.len(), 3);
2528    }
2529
2530    #[test]
2531    fn multi_select_titled_items_keep_mcp_shape() {
2532        let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2533            "#ff0000", "Red",
2534        )]));
2535
2536        let json = serde_json::to_value(&items).unwrap();
2537        assert!(json.get("type").is_none());
2538        assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2539        assert_eq!(json["anyOf"][0]["title"], "Red");
2540
2541        let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2542        assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2543    }
2544
2545    #[test]
2546    fn multi_select_items_preserve_unknown_type() {
2547        let json = json!({
2548            "type": "_token",
2549            "format": "workspace",
2550            "anyOf": [
2551                { "const": "repo", "title": "Repository" }
2552            ]
2553        });
2554
2555        let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2556        let MultiSelectItems::Other(other) = &items else {
2557            panic!("expected unknown multi-select items");
2558        };
2559
2560        assert_eq!(other.type_, "_token");
2561        assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2562        assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2563        assert_eq!(serde_json::to_value(&items).unwrap(), json);
2564    }
2565
2566    #[test]
2567    fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2568        assert!(
2569            serde_json::from_value::<MultiSelectItems>(json!({
2570                "type": "string"
2571            }))
2572            .is_err()
2573        );
2574        assert!(
2575            serde_json::from_value::<OtherMultiSelectItems>(json!({
2576                "type": "string",
2577                "format": "workspace"
2578            }))
2579            .is_err()
2580        );
2581    }
2582
2583    #[test]
2584    fn property_schema_preserves_unknown_type() {
2585        let schema: ElicitationSchema = serde_json::from_value(json!({
2586            "type": "object",
2587            "properties": {
2588                "location": {
2589                    "type": "_location",
2590                    "title": "Location",
2591                    "precision": "city"
2592                }
2593            }
2594        }))
2595        .unwrap();
2596
2597        let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2598        else {
2599            panic!("expected unknown property schema");
2600        };
2601
2602        assert_eq!(unknown.type_, "_location");
2603        assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2604        assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2605        assert_eq!(
2606            serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2607            json!({
2608                "type": "_location",
2609                "title": "Location",
2610                "precision": "city"
2611            })
2612        );
2613    }
2614
2615    #[test]
2616    fn property_schema_unknown_does_not_hide_malformed_known_type() {
2617        assert!(
2618            serde_json::from_value::<ElicitationPropertySchema>(json!({
2619                "type": "array"
2620            }))
2621            .is_err()
2622        );
2623        assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2624    }
2625
2626    #[test]
2627    fn schema_titled_enum_serialization() {
2628        let schema = ElicitationSchema::new().property(
2629            "country",
2630            StringPropertySchema::new().one_of(vec![
2631                EnumOption::new("us", "United States").description("Use US English spelling."),
2632                EnumOption::new("uk", "United Kingdom"),
2633            ]),
2634            true,
2635        );
2636
2637        let json = serde_json::to_value(&schema).unwrap();
2638        assert_eq!(json["properties"]["country"]["type"], "string");
2639        let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2640        assert_eq!(one_of.len(), 2);
2641        assert_eq!(one_of[0]["const"], "us");
2642        assert_eq!(one_of[0]["title"], "United States");
2643        assert_eq!(one_of[0]["description"], "Use US English spelling.");
2644        assert!(one_of[1].get("description").is_none());
2645
2646        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2647        if let ElicitationPropertySchema::String(s) =
2648            roundtripped.properties.get("country").unwrap()
2649        {
2650            let one_of = s.one_of.as_ref().unwrap();
2651            assert_eq!(one_of.len(), 2);
2652            assert_eq!(
2653                one_of[0].description.as_deref(),
2654                Some("Use US English spelling.")
2655            );
2656            assert!(one_of[1].description.is_none());
2657        } else {
2658            panic!("expected String variant");
2659        }
2660    }
2661
2662    #[test]
2663    fn schema_number_property_serialization() {
2664        let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2665
2666        let json = serde_json::to_value(&schema).unwrap();
2667        assert_eq!(json["properties"]["rating"]["type"], "number");
2668        assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2669        assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2670
2671        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2672        if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2673        {
2674            assert_eq!(n.minimum, Some(0.0));
2675            assert_eq!(n.maximum, Some(5.0));
2676        } else {
2677            panic!("expected Number variant");
2678        }
2679    }
2680
2681    #[test]
2682    fn schema_string_format_serialization() {
2683        let schema = ElicitationSchema::new()
2684            .uri("website", true)
2685            .date("birthday", true)
2686            .date_time("updated_at", false);
2687
2688        let json = serde_json::to_value(&schema).unwrap();
2689        assert_eq!(json["properties"]["website"]["type"], "string");
2690        assert_eq!(json["properties"]["website"]["format"], "uri");
2691        assert_eq!(json["properties"]["birthday"]["type"], "string");
2692        assert_eq!(json["properties"]["birthday"]["format"], "date");
2693        assert_eq!(json["properties"]["updated_at"]["type"], "string");
2694        assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2695
2696        let required = json["required"].as_array().unwrap();
2697        assert!(required.contains(&json!("website")));
2698        assert!(required.contains(&json!("birthday")));
2699        assert!(!required.contains(&json!("updated_at")));
2700    }
2701
2702    #[test]
2703    fn schema_string_pattern_serialization() {
2704        let schema = ElicitationSchema::new().property(
2705            "name",
2706            StringPropertySchema::new()
2707                .min_length(1)
2708                .max_length(64)
2709                .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2710            true,
2711        );
2712
2713        let json = serde_json::to_value(&schema).unwrap();
2714        assert_eq!(json["properties"]["name"]["type"], "string");
2715        assert_eq!(
2716            json["properties"]["name"]["pattern"],
2717            "^[a-zA-Z_][a-zA-Z0-9_]*$"
2718        );
2719
2720        let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2721        if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2722            assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2723        } else {
2724            panic!("expected String variant");
2725        }
2726    }
2727
2728    #[test]
2729    fn schema_property_updates_required_state() {
2730        let schema = ElicitationSchema::new()
2731            .string("name", true)
2732            .email("name", false);
2733
2734        let json = serde_json::to_value(&schema).unwrap();
2735        assert!(json.get("required").is_none());
2736        assert_eq!(json["properties"]["name"]["format"], "email");
2737    }
2738
2739    #[test]
2740    fn schema_defaults_invalid_object_type() {
2741        let schema = serde_json::from_value::<ElicitationSchema>(json!({
2742            "type": "array",
2743            "properties": {
2744                "name": {
2745                    "type": "string"
2746                }
2747            }
2748        }))
2749        .unwrap();
2750
2751        assert_eq!(schema.type_, ElicitationSchemaType::Object);
2752        assert!(schema.properties.contains_key("name"));
2753    }
2754
2755    #[test]
2756    fn titled_multi_select_items_reject_one_of() {
2757        let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2758            "oneOf": [
2759                {
2760                    "const": "red",
2761                    "title": "Red"
2762                }
2763            ]
2764        }))
2765        .unwrap_err();
2766
2767        assert!(err.to_string().contains("missing field `anyOf`"));
2768    }
2769
2770    #[test]
2771    fn response_accept_rejects_non_object_content() {
2772        assert!(
2773            serde_json::from_value::<CreateElicitationResponse>(json!({
2774                "action": "accept",
2775                "content": "Alice"
2776            }))
2777            .is_err()
2778        );
2779    }
2780
2781    #[test]
2782    fn response_accept_rejects_nested_object_content() {
2783        assert!(
2784            serde_json::from_value::<CreateElicitationResponse>(json!({
2785                "action": "accept",
2786                "content": {
2787                    "profile": {
2788                        "name": "Alice"
2789                    }
2790                }
2791            }))
2792            .is_err()
2793        );
2794    }
2795
2796    #[test]
2797    fn response_accept_allows_primitive_and_string_array_content() {
2798        let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2799            ElicitationAcceptAction::new().content(BTreeMap::from([
2800                ("name".to_string(), ElicitationContentValue::from("Alice")),
2801                ("age".to_string(), ElicitationContentValue::from(30_i32)),
2802                ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2803                (
2804                    "subscribed".to_string(),
2805                    ElicitationContentValue::from(true),
2806                ),
2807                (
2808                    "tags".to_string(),
2809                    ElicitationContentValue::from(vec!["rust", "acp"]),
2810                ),
2811            ])),
2812        ));
2813
2814        let json = serde_json::to_value(&response).unwrap();
2815        assert_eq!(json["action"], "accept");
2816        assert_eq!(json["content"]["name"], "Alice");
2817        assert_eq!(json["content"]["age"], 30);
2818        assert_eq!(json["content"]["score"], 9.5);
2819        assert_eq!(json["content"]["subscribed"], true);
2820        assert_eq!(json["content"]["tags"][0], "rust");
2821        assert_eq!(json["content"]["tags"][1], "acp");
2822    }
2823}