Skip to main content

agent_client_protocol_schema/v2/
elicitation.rs

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