1use 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#[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 #[must_use]
30 pub fn new(id: impl Into<Self>) -> Self {
31 id.into()
32 }
33}
34
35#[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,
43 Uri,
45 Date,
47 DateTime,
49 #[serde(untagged)]
54 Other(String),
55}
56
57#[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 #[default]
65 Object,
66}
67
68#[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 #[serde(rename = "const")]
77 pub value: String,
78 pub title: String,
80 #[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 #[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 #[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 #[must_use]
113 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
114 self.description = description.into_option();
115 self
116 }
117
118 #[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#[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 #[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 #[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 #[serde(default)]
161 pub min_length: Option<u32>,
162 #[serde(default)]
166 pub max_length: Option<u32>,
167 #[cfg_attr(feature = "schemars", schemars(extend("format" = "regex")))]
171 #[serde(default)]
172 pub pattern: Option<String>,
173 #[serde(default)]
177 pub format: Option<StringFormat>,
178 #[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 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
190 #[serde(default)]
191 #[serde(rename = "enum")]
192 pub enum_values: Option<Vec<String>>,
193 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
198 #[serde(default)]
199 #[serde(rename = "oneOf")]
200 pub one_of: Option<Vec<EnumOption>>,
201 #[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 #[must_use]
216 pub fn new() -> Self {
217 Self::default()
218 }
219
220 #[must_use]
222 pub fn email() -> Self {
223 Self {
224 format: Some(StringFormat::Email),
225 ..Default::default()
226 }
227 }
228
229 #[must_use]
231 pub fn uri() -> Self {
232 Self {
233 format: Some(StringFormat::Uri),
234 ..Default::default()
235 }
236 }
237
238 #[must_use]
240 pub fn date() -> Self {
241 Self {
242 format: Some(StringFormat::Date),
243 ..Default::default()
244 }
245 }
246
247 #[must_use]
249 pub fn date_time() -> Self {
250 Self {
251 format: Some(StringFormat::DateTime),
252 ..Default::default()
253 }
254 }
255
256 #[must_use]
258 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
259 self.title = title.into_option();
260 self
261 }
262
263 #[must_use]
265 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
266 self.description = description.into_option();
267 self
268 }
269
270 #[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 #[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 #[must_use]
286 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
287 self.pattern = pattern.into_option();
288 self
289 }
290
291 #[must_use]
293 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
294 self.format = format.into_option();
295 self
296 }
297
298 #[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 #[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 #[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 #[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#[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 #[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 #[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 #[serde(default)]
359 pub minimum: Option<f64>,
360 #[serde(default)]
364 pub maximum: Option<f64>,
365 #[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 #[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 #[must_use]
387 pub fn new() -> Self {
388 Self::default()
389 }
390
391 #[must_use]
393 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
394 self.title = title.into_option();
395 self
396 }
397
398 #[must_use]
400 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
401 self.description = description.into_option();
402 self
403 }
404
405 #[must_use]
407 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
408 self.minimum = minimum.into_option();
409 self
410 }
411
412 #[must_use]
414 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
415 self.maximum = maximum.into_option();
416 self
417 }
418
419 #[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 #[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#[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 #[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 #[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 #[serde(default)]
466 pub minimum: Option<i64>,
467 #[serde(default)]
471 pub maximum: Option<i64>,
472 #[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 #[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 #[must_use]
494 pub fn new() -> Self {
495 Self::default()
496 }
497
498 #[must_use]
500 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
501 self.title = title.into_option();
502 self
503 }
504
505 #[must_use]
507 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
508 self.description = description.into_option();
509 self
510 }
511
512 #[must_use]
514 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
515 self.minimum = minimum.into_option();
516 self
517 }
518
519 #[must_use]
521 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
522 self.maximum = maximum.into_option();
523 self
524 }
525
526 #[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 #[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#[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 #[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 #[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 #[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 #[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 #[must_use]
591 pub fn new() -> Self {
592 Self::default()
593 }
594
595 #[must_use]
597 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
598 self.title = title.into_option();
599 self
600 }
601
602 #[must_use]
604 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
605 self.description = description.into_option();
606 self
607 }
608
609 #[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 #[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#[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 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
639 #[serde(rename = "enum")]
640 pub values: Vec<String>,
641 #[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 #[must_use]
656 pub fn new(values: Vec<String>) -> Self {
657 Self { values, meta: None }
658 }
659
660 #[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#[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 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
683 #[serde(rename = "anyOf")]
684 pub options: Vec<EnumOption>,
685 #[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 #[must_use]
700 pub fn new(options: Vec<EnumOption>) -> Self {
701 Self {
702 options,
703 meta: None,
704 }
705 }
706
707 #[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#[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 #[serde(rename = "type")]
739 pub type_: String,
740 #[serde(flatten)]
742 pub fields: BTreeMap<String, serde_json::Value>,
743}
744
745impl OtherMultiSelectItems {
746 #[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#[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 String(StringMultiSelectItems),
803 #[serde(untagged)]
805 Other(OtherMultiSelectItems),
806 #[serde(untagged)]
810 Titled(TitledMultiSelectItems),
811}
812
813#[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 #[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 #[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 #[serde(default)]
839 pub min_items: Option<u64>,
840 #[serde(default)]
844 pub max_items: Option<u64>,
845 pub items: MultiSelectItems,
847 #[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 #[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 #[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 #[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 #[must_use]
900 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
901 self.title = title.into_option();
902 self
903 }
904
905 #[must_use]
907 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
908 self.description = description.into_option();
909 self
910 }
911
912 #[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 #[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 #[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 #[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#[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(StringPropertySchema),
959 Number(NumberPropertySchema),
961 Integer(IntegerPropertySchema),
963 Boolean(BooleanPropertySchema),
965 Array(MultiSelectPropertySchema),
967 #[serde(untagged)]
977 Other(OtherElicitationPropertySchema),
978}
979
980#[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 #[serde(rename = "type")]
998 pub type_: String,
999 #[serde(flatten)]
1001 pub fields: BTreeMap<String, serde_json::Value>,
1002}
1003
1004impl OtherElicitationPropertySchema {
1005 #[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#[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 #[serde(rename = "type")]
1102 pub type_: ElicitationSchemaType,
1103 #[serde_as(deserialize_as = "DefaultOnError")]
1107 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1108 #[serde(default)]
1109 pub title: Option<String>,
1110 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1112 #[serde(default)]
1116 pub required: Option<Vec<String>>,
1117 #[serde_as(deserialize_as = "DefaultOnError")]
1121 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1122 #[serde(default)]
1123 pub description: Option<String>,
1124 #[serde_as(deserialize_as = "DefaultOnError")]
1130 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1131 #[serde(default)]
1132 #[serde(rename = "_meta")]
1133 pub meta: Option<Meta>,
1134}
1135
1136impl Default for ElicitationSchema {
1137 fn default() -> Self {
1138 Self {
1139 type_: default_object_type(),
1140 title: None,
1141 properties: BTreeMap::new(),
1142 required: None,
1143 description: None,
1144 meta: None,
1145 }
1146 }
1147}
1148
1149impl ElicitationSchema {
1150 #[must_use]
1152 pub fn new() -> Self {
1153 Self::default()
1154 }
1155
1156 #[must_use]
1158 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1159 self.title = title.into_option();
1160 self
1161 }
1162
1163 #[must_use]
1165 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1166 self.description = description.into_option();
1167 self
1168 }
1169
1170 #[must_use]
1178 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1179 self.meta = meta.into_option();
1180 self
1181 }
1182
1183 #[must_use]
1185 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1186 where
1187 S: Into<ElicitationPropertySchema>,
1188 {
1189 let name = name.into();
1190 self.properties.insert(name.clone(), schema.into());
1191
1192 if required {
1193 let required_fields = self.required.get_or_insert_with(Vec::new);
1194 if !required_fields.contains(&name) {
1195 required_fields.push(name);
1196 }
1197 } else if let Some(required_fields) = &mut self.required {
1198 required_fields.retain(|field| field != &name);
1199
1200 if required_fields.is_empty() {
1201 self.required = None;
1202 }
1203 }
1204
1205 self
1206 }
1207
1208 #[must_use]
1210 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1211 self.property(name, StringPropertySchema::new(), required)
1212 }
1213
1214 #[must_use]
1216 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1217 self.property(name, StringPropertySchema::email(), required)
1218 }
1219
1220 #[must_use]
1222 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1223 self.property(name, StringPropertySchema::uri(), required)
1224 }
1225
1226 #[must_use]
1228 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1229 self.property(name, StringPropertySchema::date(), required)
1230 }
1231
1232 #[must_use]
1234 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1235 self.property(name, StringPropertySchema::date_time(), required)
1236 }
1237
1238 #[must_use]
1240 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1241 self.property(
1242 name,
1243 NumberPropertySchema::new().minimum(min).maximum(max),
1244 required,
1245 )
1246 }
1247
1248 #[must_use]
1250 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1251 self.property(
1252 name,
1253 IntegerPropertySchema::new().minimum(min).maximum(max),
1254 required,
1255 )
1256 }
1257
1258 #[must_use]
1260 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1261 self.property(name, BooleanPropertySchema::new(), required)
1262 }
1263}
1264
1265#[serde_as]
1267#[skip_serializing_none]
1268#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1269#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1270#[serde(rename_all = "camelCase")]
1271#[non_exhaustive]
1272pub struct ElicitationCapabilities {
1273 #[serde_as(deserialize_as = "DefaultOnError")]
1278 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1279 #[serde(default)]
1280 pub form: Option<ElicitationFormCapabilities>,
1281 #[serde_as(deserialize_as = "DefaultOnError")]
1286 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1287 #[serde(default)]
1288 pub url: Option<ElicitationUrlCapabilities>,
1289 #[serde_as(deserialize_as = "DefaultOnError")]
1295 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1296 #[serde(default)]
1297 #[serde(rename = "_meta")]
1298 pub meta: Option<Meta>,
1299}
1300
1301impl ElicitationCapabilities {
1302 #[must_use]
1307 pub fn new() -> Self {
1308 Self::default()
1309 }
1310
1311 #[must_use]
1314 pub fn supports_form(&self) -> bool {
1315 self.form.is_some()
1316 }
1317
1318 #[must_use]
1320 pub fn supports_url(&self) -> bool {
1321 self.url.is_some()
1322 }
1323
1324 #[must_use]
1329 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1330 self.form = form.into_option();
1331 self
1332 }
1333
1334 #[must_use]
1339 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1340 self.url = url.into_option();
1341 self
1342 }
1343
1344 #[must_use]
1352 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1353 self.meta = meta.into_option();
1354 self
1355 }
1356}
1357
1358#[serde_as]
1362#[skip_serializing_none]
1363#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1364#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1365#[serde(rename_all = "camelCase")]
1366#[non_exhaustive]
1367pub struct ElicitationFormCapabilities {
1368 #[serde_as(deserialize_as = "DefaultOnError")]
1374 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1375 #[serde(default)]
1376 #[serde(rename = "_meta")]
1377 pub meta: Option<Meta>,
1378}
1379
1380impl ElicitationFormCapabilities {
1381 #[must_use]
1383 pub fn new() -> Self {
1384 Self::default()
1385 }
1386
1387 #[must_use]
1395 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1396 self.meta = meta.into_option();
1397 self
1398 }
1399}
1400
1401#[serde_as]
1405#[skip_serializing_none]
1406#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1407#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1408#[serde(rename_all = "camelCase")]
1409#[non_exhaustive]
1410pub struct ElicitationUrlCapabilities {
1411 #[serde_as(deserialize_as = "DefaultOnError")]
1417 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1418 #[serde(default)]
1419 #[serde(rename = "_meta")]
1420 pub meta: Option<Meta>,
1421}
1422
1423impl ElicitationUrlCapabilities {
1424 #[must_use]
1426 pub fn new() -> Self {
1427 Self::default()
1428 }
1429
1430 #[must_use]
1438 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1439 self.meta = meta.into_option();
1440 self
1441 }
1442}
1443
1444#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1448#[cfg_attr(feature = "schemars", schemars(transform = elicitation_scope_schema))]
1449#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1450#[serde(untagged)]
1451#[non_exhaustive]
1452pub enum ElicitationScope {
1453 Session(ElicitationSessionScope),
1455 Request(ElicitationRequestScope),
1458}
1459
1460impl<'de> Deserialize<'de> for ElicitationScope {
1461 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1462 where
1463 D: serde::Deserializer<'de>,
1464 {
1465 let mut fields = serde_json::Map::deserialize(deserializer)?;
1466 let scope = match (fields.remove("sessionId"), fields.remove("requestId")) {
1467 (Some(session_id), None) => {
1468 let mut scope = serde_json::Map::new();
1469 scope.insert("sessionId".into(), session_id);
1470 if let Some(tool_call_id) = fields.remove("toolCallId") {
1471 scope.insert("toolCallId".into(), tool_call_id);
1472 }
1473 serde_json::from_value(scope.into()).map(Self::Session)
1474 }
1475 (None, Some(request_id)) => {
1476 serde_json::from_value(serde_json::json!({ "requestId": request_id }))
1477 .map(Self::Request)
1478 }
1479 _ => {
1480 return Err(serde::de::Error::custom(
1481 "elicitation scope must include exactly one of `sessionId` or `requestId`",
1482 ));
1483 }
1484 };
1485 scope.map_err(serde::de::Error::custom)
1486 }
1487}
1488
1489#[cfg(feature = "schemars")]
1490fn elicitation_scope_schema(schema: &mut Schema) {
1491 if let Some(variants) = schema.remove("anyOf") {
1492 schema.insert("oneOf".into(), variants);
1493 }
1494}
1495
1496#[serde_as]
1502#[skip_serializing_none]
1503#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1505#[serde(rename_all = "camelCase")]
1506#[non_exhaustive]
1507pub struct ElicitationSessionScope {
1508 pub session_id: SessionId,
1510 #[serde_as(deserialize_as = "DefaultOnError")]
1515 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1516 #[serde(default)]
1517 pub tool_call_id: Option<ToolCallId>,
1518}
1519
1520impl ElicitationSessionScope {
1521 #[must_use]
1523 pub fn new(session_id: impl Into<SessionId>) -> Self {
1524 Self {
1525 session_id: session_id.into(),
1526 tool_call_id: None,
1527 }
1528 }
1529
1530 #[must_use]
1532 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1533 self.tool_call_id = tool_call_id.into_option();
1534 self
1535 }
1536}
1537
1538#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1542#[serde(rename_all = "camelCase")]
1543#[non_exhaustive]
1544pub struct ElicitationRequestScope {
1545 pub request_id: RequestId,
1547}
1548
1549impl ElicitationRequestScope {
1550 #[must_use]
1552 pub fn new(request_id: impl Into<RequestId>) -> Self {
1553 Self {
1554 request_id: request_id.into(),
1555 }
1556 }
1557}
1558
1559impl From<ElicitationSessionScope> for ElicitationScope {
1560 fn from(scope: ElicitationSessionScope) -> Self {
1561 Self::Session(scope)
1562 }
1563}
1564
1565impl From<ElicitationRequestScope> for ElicitationScope {
1566 fn from(scope: ElicitationRequestScope) -> Self {
1567 Self::Request(scope)
1568 }
1569}
1570
1571#[serde_as]
1589#[skip_serializing_none]
1590#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1592#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1593#[serde(rename_all = "camelCase")]
1594#[non_exhaustive]
1595pub struct CreateElicitationRequest {
1596 #[serde(flatten)]
1598 pub mode: ElicitationMode,
1599 pub message: String,
1601 #[serde_as(deserialize_as = "DefaultOnError")]
1607 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1608 #[serde(default)]
1609 #[serde(rename = "_meta")]
1610 pub meta: Option<Meta>,
1611}
1612
1613impl CreateElicitationRequest {
1614 #[must_use]
1616 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1617 Self {
1618 mode: mode.into(),
1619 message: message.into(),
1620 meta: None,
1621 }
1622 }
1623
1624 #[must_use]
1626 pub fn scope(&self) -> &ElicitationScope {
1627 self.mode.scope()
1628 }
1629
1630 #[must_use]
1638 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1639 self.meta = meta.into_option();
1640 self
1641 }
1642}
1643
1644#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1646#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1647#[serde(tag = "mode", rename_all = "snake_case")]
1648#[non_exhaustive]
1649pub enum ElicitationMode {
1650 Form(ElicitationFormMode),
1652 Url(ElicitationUrlMode),
1654 #[serde(untagged)]
1664 Other(OtherElicitationMode),
1665}
1666
1667#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1673#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1674#[cfg_attr(feature = "schemars", schemars(inline))]
1675#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1676#[serde(rename_all = "camelCase")]
1677#[non_exhaustive]
1678pub struct OtherElicitationMode {
1679 pub mode: String,
1685 #[serde(flatten)]
1687 pub scope: ElicitationScope,
1688 #[serde(flatten)]
1690 pub fields: BTreeMap<String, serde_json::Value>,
1691}
1692
1693impl OtherElicitationMode {
1694 #[must_use]
1696 pub fn new(
1697 mode: impl Into<String>,
1698 scope: impl Into<ElicitationScope>,
1699 mut fields: BTreeMap<String, serde_json::Value>,
1700 ) -> Self {
1701 fields.remove("mode");
1702 remove_elicitation_scope_fields(&mut fields);
1703 Self {
1704 mode: mode.into(),
1705 scope: scope.into(),
1706 fields,
1707 }
1708 }
1709}
1710
1711impl<'de> Deserialize<'de> for OtherElicitationMode {
1712 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1713 where
1714 D: serde::Deserializer<'de>,
1715 {
1716 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1717 let mode = fields
1718 .remove("mode")
1719 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1720 let serde_json::Value::String(mode) = mode else {
1721 return Err(serde::de::Error::custom("`mode` must be a string"));
1722 };
1723
1724 if is_known_elicitation_mode(&mode) {
1725 return Err(serde::de::Error::custom(format!(
1726 "known elicitation mode `{mode}` did not match its schema"
1727 )));
1728 }
1729
1730 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1731 fields.clone().into_iter().collect(),
1732 ))
1733 .map_err(serde::de::Error::custom)?;
1734 remove_elicitation_scope_fields(&mut fields);
1735
1736 Ok(Self {
1737 mode,
1738 scope,
1739 fields,
1740 })
1741 }
1742}
1743
1744const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1745
1746fn is_known_elicitation_mode(mode: &str) -> bool {
1747 KNOWN_ELICITATION_MODES.contains(&mode)
1748}
1749
1750fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1751 fields.remove("sessionId");
1752 fields.remove("toolCallId");
1753 fields.remove("requestId");
1754}
1755
1756#[cfg(feature = "schemars")]
1757fn other_elicitation_mode_schema(schema: &mut Schema) {
1758 super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1759}
1760
1761impl From<ElicitationFormMode> for ElicitationMode {
1762 fn from(mode: ElicitationFormMode) -> Self {
1763 Self::Form(mode)
1764 }
1765}
1766
1767impl From<ElicitationUrlMode> for ElicitationMode {
1768 fn from(mode: ElicitationUrlMode) -> Self {
1769 Self::Url(mode)
1770 }
1771}
1772
1773impl From<OtherElicitationMode> for ElicitationMode {
1774 fn from(mode: OtherElicitationMode) -> Self {
1775 Self::Other(mode)
1776 }
1777}
1778
1779impl ElicitationMode {
1780 #[must_use]
1782 pub fn scope(&self) -> &ElicitationScope {
1783 match self {
1784 Self::Form(f) => &f.scope,
1785 Self::Url(u) => &u.scope,
1786 Self::Other(other) => &other.scope,
1787 }
1788 }
1789}
1790
1791#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1793#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1794#[serde(rename_all = "camelCase")]
1795#[non_exhaustive]
1796pub struct ElicitationFormMode {
1797 #[serde(flatten)]
1799 pub scope: ElicitationScope,
1800 pub requested_schema: ElicitationSchema,
1802}
1803
1804impl ElicitationFormMode {
1805 #[must_use]
1807 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1808 Self {
1809 scope: scope.into(),
1810 requested_schema,
1811 }
1812 }
1813}
1814
1815#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1817#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1818#[serde(rename_all = "camelCase")]
1819#[non_exhaustive]
1820pub struct ElicitationUrlMode {
1821 #[serde(flatten)]
1823 pub scope: ElicitationScope,
1824 pub elicitation_id: ElicitationId,
1826 #[cfg_attr(feature = "schemars", schemars(url))]
1828 pub url: String,
1829}
1830
1831impl ElicitationUrlMode {
1832 #[must_use]
1834 pub fn new(
1835 scope: impl Into<ElicitationScope>,
1836 elicitation_id: impl Into<ElicitationId>,
1837 url: impl Into<String>,
1838 ) -> Self {
1839 Self {
1840 scope: scope.into(),
1841 elicitation_id: elicitation_id.into(),
1842 url: url.into(),
1843 }
1844 }
1845}
1846
1847#[serde_as]
1849#[skip_serializing_none]
1850#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1851#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1852#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1853#[serde(rename_all = "camelCase")]
1854#[non_exhaustive]
1855pub struct CreateElicitationResponse {
1856 #[serde(flatten)]
1858 pub action: ElicitationAction,
1859 #[serde_as(deserialize_as = "DefaultOnError")]
1865 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1866 #[serde(default)]
1867 #[serde(rename = "_meta")]
1868 pub meta: Option<Meta>,
1869}
1870
1871impl CreateElicitationResponse {
1872 #[must_use]
1874 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1875 Self {
1876 action: action.into(),
1877 meta: None,
1878 }
1879 }
1880
1881 #[must_use]
1889 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1890 self.meta = meta.into_option();
1891 self
1892 }
1893}
1894
1895#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1897#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1898#[serde(tag = "action", rename_all = "snake_case")]
1899#[non_exhaustive]
1900pub enum ElicitationAction {
1901 Accept(ElicitationAcceptAction),
1903 Decline,
1905 Cancel,
1907 #[serde(untagged)]
1917 Other(OtherElicitationAction),
1918}
1919
1920#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1926#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1927#[cfg_attr(feature = "schemars", schemars(inline))]
1928#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1929#[serde(rename_all = "camelCase")]
1930#[non_exhaustive]
1931pub struct OtherElicitationAction {
1932 pub action: String,
1938 #[serde(flatten)]
1940 pub fields: BTreeMap<String, serde_json::Value>,
1941}
1942
1943impl OtherElicitationAction {
1944 #[must_use]
1946 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1947 fields.remove("action");
1948 Self {
1949 action: action.into(),
1950 fields,
1951 }
1952 }
1953}
1954
1955impl<'de> Deserialize<'de> for OtherElicitationAction {
1956 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1957 where
1958 D: serde::Deserializer<'de>,
1959 {
1960 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1961 let action = fields
1962 .remove("action")
1963 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1964 let serde_json::Value::String(action) = action else {
1965 return Err(serde::de::Error::custom("`action` must be a string"));
1966 };
1967
1968 if is_known_elicitation_action(&action) {
1969 return Err(serde::de::Error::custom(format!(
1970 "known elicitation action `{action}` did not match its schema"
1971 )));
1972 }
1973
1974 Ok(Self { action, fields })
1975 }
1976}
1977
1978const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1979
1980fn is_known_elicitation_action(action: &str) -> bool {
1981 KNOWN_ELICITATION_ACTIONS.contains(&action)
1982}
1983
1984#[cfg(feature = "schemars")]
1985fn other_elicitation_action_schema(schema: &mut Schema) {
1986 super::schema_util::reject_known_string_discriminators(
1987 schema,
1988 "action",
1989 KNOWN_ELICITATION_ACTIONS,
1990 );
1991}
1992
1993impl From<ElicitationAcceptAction> for ElicitationAction {
1994 fn from(action: ElicitationAcceptAction) -> Self {
1995 Self::Accept(action)
1996 }
1997}
1998
1999impl From<OtherElicitationAction> for ElicitationAction {
2000 fn from(action: OtherElicitationAction) -> Self {
2001 Self::Other(action)
2002 }
2003}
2004
2005#[serde_as]
2007#[skip_serializing_none]
2008#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2009#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2010#[serde(rename_all = "camelCase")]
2011#[non_exhaustive]
2012pub struct ElicitationAcceptAction {
2013 #[serde(default)]
2017 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
2018}
2019
2020impl ElicitationAcceptAction {
2021 #[must_use]
2023 pub fn new() -> Self {
2024 Self { content: None }
2025 }
2026
2027 #[must_use]
2029 pub fn content(
2030 mut self,
2031 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2032 ) -> Self {
2033 self.content = content.into_option();
2034 self
2035 }
2036}
2037
2038#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2040#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2041#[serde(untagged)]
2042#[non_exhaustive]
2043pub enum ElicitationContentValue {
2044 String(String),
2046 Integer(i64),
2048 Number(f64),
2050 Boolean(bool),
2052 StringArray(Vec<String>),
2054 Other(serde_json::Value),
2059}
2060
2061impl From<String> for ElicitationContentValue {
2062 fn from(value: String) -> Self {
2063 Self::String(value)
2064 }
2065}
2066
2067impl From<&str> for ElicitationContentValue {
2068 fn from(value: &str) -> Self {
2069 Self::String(value.to_string())
2070 }
2071}
2072
2073impl From<i64> for ElicitationContentValue {
2074 fn from(value: i64) -> Self {
2075 Self::Integer(value)
2076 }
2077}
2078
2079impl From<i32> for ElicitationContentValue {
2080 fn from(value: i32) -> Self {
2081 Self::Integer(i64::from(value))
2082 }
2083}
2084
2085impl From<f64> for ElicitationContentValue {
2086 fn from(value: f64) -> Self {
2087 Self::Number(value)
2088 }
2089}
2090
2091impl From<bool> for ElicitationContentValue {
2092 fn from(value: bool) -> Self {
2093 Self::Boolean(value)
2094 }
2095}
2096
2097impl From<Vec<String>> for ElicitationContentValue {
2098 fn from(value: Vec<String>) -> Self {
2099 Self::StringArray(value)
2100 }
2101}
2102
2103impl From<Vec<&str>> for ElicitationContentValue {
2104 fn from(value: Vec<&str>) -> Self {
2105 Self::StringArray(value.into_iter().map(str::to_string).collect())
2106 }
2107}
2108
2109impl Default for ElicitationAcceptAction {
2110 fn default() -> Self {
2111 Self::new()
2112 }
2113}
2114
2115#[serde_as]
2119#[skip_serializing_none]
2120#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2122#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2123#[serde(rename_all = "camelCase")]
2124#[non_exhaustive]
2125pub struct CompleteElicitationNotification {
2126 pub elicitation_id: ElicitationId,
2128 #[serde_as(deserialize_as = "DefaultOnError")]
2134 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2135 #[serde(default)]
2136 #[serde(rename = "_meta")]
2137 pub meta: Option<Meta>,
2138}
2139
2140impl CompleteElicitationNotification {
2141 #[must_use]
2143 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2144 Self {
2145 elicitation_id: elicitation_id.into(),
2146 meta: None,
2147 }
2148 }
2149
2150 #[must_use]
2158 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2159 self.meta = meta.into_option();
2160 self
2161 }
2162}
2163
2164#[cfg(test)]
2165mod tests {
2166 use super::*;
2167 use serde_json::json;
2168
2169 #[test]
2170 fn content_values_accept_other_shapes() {
2171 let wire = json!({
2172 "name": "Ada",
2173 "age": 36,
2174 "ratio": 0.5,
2175 "agree": true,
2176 "tags": ["a", "b"],
2177 "_custom": { "lat": 1.0, "lng": 2.0 },
2178 "mixed": ["a", 1]
2179 });
2180 let content: BTreeMap<String, ElicitationContentValue> =
2181 serde_json::from_value(wire.clone()).unwrap();
2182 assert_eq!(
2183 content["name"],
2184 ElicitationContentValue::String("Ada".into())
2185 );
2186 assert_eq!(content["age"], ElicitationContentValue::Integer(36));
2187 assert_eq!(
2188 content["tags"],
2189 ElicitationContentValue::StringArray(vec!["a".into(), "b".into()])
2190 );
2191 assert_eq!(
2192 content["_custom"],
2193 ElicitationContentValue::Other(json!({ "lat": 1.0, "lng": 2.0 }))
2194 );
2195 assert_eq!(
2196 content["mixed"],
2197 ElicitationContentValue::Other(json!(["a", 1]))
2198 );
2199 assert_eq!(serde_json::to_value(&content).unwrap(), wire);
2200 }
2201
2202 #[test]
2203 fn form_mode_request_serialization() {
2204 let schema = ElicitationSchema::new().string("name", true);
2205 let req = CreateElicitationRequest::new(
2206 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2207 "Please enter your name",
2208 );
2209
2210 let json = serde_json::to_value(&req).unwrap();
2211 assert_eq!(json["sessionId"], "sess_1");
2212 assert!(json.get("toolCallId").is_none());
2213 assert_eq!(json["mode"], "form");
2214 assert_eq!(json["message"], "Please enter your name");
2215 assert!(json["requestedSchema"].is_object());
2216 assert_eq!(json["requestedSchema"]["type"], "object");
2217 assert_eq!(
2218 json["requestedSchema"]["properties"]["name"]["type"],
2219 "string"
2220 );
2221
2222 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2223 assert_eq!(
2224 *roundtripped.scope(),
2225 ElicitationSessionScope::new("sess_1").into()
2226 );
2227 assert_eq!(roundtripped.message, "Please enter your name");
2228 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2229 }
2230
2231 #[test]
2232 fn url_mode_request_serialization() {
2233 let req = CreateElicitationRequest::new(
2234 ElicitationUrlMode::new(
2235 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2236 "elic_1",
2237 "https://example.com/auth",
2238 ),
2239 "Please authenticate",
2240 );
2241
2242 let json = serde_json::to_value(&req).unwrap();
2243 assert_eq!(json["sessionId"], "sess_2");
2244 assert_eq!(json["toolCallId"], "tc_1");
2245 assert_eq!(json["mode"], "url");
2246 assert_eq!(json["elicitationId"], "elic_1");
2247 assert_eq!(json["url"], "https://example.com/auth");
2248 assert_eq!(json["message"], "Please authenticate");
2249
2250 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2251 assert_eq!(
2252 *roundtripped.scope(),
2253 ElicitationSessionScope::new("sess_2")
2254 .tool_call_id("tc_1")
2255 .into()
2256 );
2257 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2258 }
2259
2260 #[test]
2261 fn response_accept_serialization() {
2262 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2263 ElicitationAcceptAction::new().content(BTreeMap::from([(
2264 "name".to_string(),
2265 ElicitationContentValue::from("Alice"),
2266 )])),
2267 ));
2268
2269 let json = serde_json::to_value(&resp).unwrap();
2270 assert_eq!(json["action"], "accept");
2271 assert_eq!(json["content"]["name"], "Alice");
2272
2273 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2274 assert!(matches!(
2275 roundtripped.action,
2276 ElicitationAction::Accept(ElicitationAcceptAction {
2277 content: Some(_),
2278 ..
2279 })
2280 ));
2281 }
2282
2283 #[test]
2284 fn response_decline_serialization() {
2285 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2286
2287 let json = serde_json::to_value(&resp).unwrap();
2288 assert_eq!(json["action"], "decline");
2289
2290 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2291 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2292 }
2293
2294 #[test]
2295 fn response_cancel_serialization() {
2296 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2297
2298 let json = serde_json::to_value(&resp).unwrap();
2299 assert_eq!(json["action"], "cancel");
2300
2301 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2302 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2303 }
2304
2305 #[test]
2306 fn unknown_action_response_serialization() {
2307 let json = json!({
2308 "action": "_defer",
2309 "reason": "waiting",
2310 "retryAfterMs": 1000
2311 });
2312
2313 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2314 let ElicitationAction::Other(other) = &resp.action else {
2315 panic!("expected unknown elicitation action");
2316 };
2317
2318 assert_eq!(other.action, "_defer");
2319 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2320 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2321 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2322 }
2323
2324 #[test]
2325 fn unknown_action_does_not_hide_known_action() {
2326 assert!(
2327 serde_json::from_value::<OtherElicitationAction>(json!({
2328 "action": "accept",
2329 "content": {}
2330 }))
2331 .is_err()
2332 );
2333 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2334 }
2335
2336 #[test]
2337 fn url_mode_request_scope_serialization() {
2338 let req = CreateElicitationRequest::new(
2339 ElicitationUrlMode::new(
2340 ElicitationRequestScope::new(RequestId::Number(42)),
2341 "elic_2",
2342 "https://example.com/setup",
2343 ),
2344 "Please complete setup",
2345 );
2346
2347 let json = serde_json::to_value(&req).unwrap();
2348 assert_eq!(json["requestId"], 42);
2349 assert!(json.get("sessionId").is_none());
2350 assert_eq!(json["mode"], "url");
2351 assert_eq!(json["elicitationId"], "elic_2");
2352 assert_eq!(json["url"], "https://example.com/setup");
2353 assert_eq!(json["message"], "Please complete setup");
2354
2355 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2356 assert_eq!(
2357 *roundtripped.scope(),
2358 ElicitationRequestScope::new(RequestId::Number(42)).into()
2359 );
2360 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2361 }
2362
2363 #[test]
2364 fn unknown_mode_request_serialization() {
2365 let json = json!({
2366 "requestId": 42,
2367 "mode": "_browser",
2368 "message": "Open a browser window",
2369 "target": "login"
2370 });
2371
2372 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2373 let ElicitationMode::Other(other) = &req.mode else {
2374 panic!("expected unknown elicitation mode");
2375 };
2376
2377 assert_eq!(other.mode, "_browser");
2378 assert_eq!(
2379 other.scope,
2380 ElicitationRequestScope::new(RequestId::Number(42)).into()
2381 );
2382 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2383 assert_eq!(
2384 *req.scope(),
2385 ElicitationRequestScope::new(RequestId::Number(42)).into()
2386 );
2387 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2388 }
2389
2390 #[test]
2391 fn unknown_mode_does_not_hide_malformed_known_mode() {
2392 let missing_requested_schema = json!({
2393 "requestId": 42,
2394 "mode": "form",
2395 "message": "Enter your name"
2396 });
2397
2398 assert!(
2399 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2400 );
2401 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2402 }
2403
2404 #[test]
2405 fn scope_requires_exactly_one_non_null_id() {
2406 let request = |scope: serde_json::Value| {
2407 let mut json = json!({
2408 "mode": "form",
2409 "message": "Enter your name",
2410 "requestedSchema": { "type": "object", "properties": {} }
2411 });
2412 json.as_object_mut()
2413 .unwrap()
2414 .extend(scope.as_object().unwrap().clone());
2415 serde_json::from_value::<CreateElicitationRequest>(json)
2416 };
2417
2418 assert!(request(json!({ "sessionId": "sess_1", "requestId": 42 })).is_err());
2419 assert!(request(json!({})).is_err());
2420 assert!(request(json!({ "sessionId": 42, "requestId": 42 })).is_err());
2421 assert!(request(json!({ "sessionId": 42 })).is_err());
2422 assert_eq!(
2423 *request(json!({ "sessionId": "sess_1", "toolCallId": "call_1" }))
2424 .unwrap()
2425 .scope(),
2426 ElicitationSessionScope::new("sess_1")
2427 .tool_call_id("call_1")
2428 .into()
2429 );
2430 assert_eq!(
2431 *request(json!({ "requestId": "req_1" })).unwrap().scope(),
2432 ElicitationRequestScope::new("req_1".to_string()).into()
2433 );
2434 }
2435
2436 #[test]
2437 fn request_scope_request_serialization() {
2438 let req = CreateElicitationRequest::new(
2439 ElicitationFormMode::new(
2440 ElicitationRequestScope::new(RequestId::Number(99)),
2441 ElicitationSchema::new().string("workspace", true),
2442 ),
2443 "Enter workspace name",
2444 );
2445
2446 let json = serde_json::to_value(&req).unwrap();
2447 assert_eq!(json["requestId"], 99);
2448 assert!(json.get("sessionId").is_none());
2449
2450 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2451 assert_eq!(
2452 *roundtripped.scope(),
2453 ElicitationRequestScope::new(RequestId::Number(99)).into()
2454 );
2455 }
2456
2457 #[test]
2461 fn client_response_serialization_accept() {
2462 use crate::v2::ClientResponse;
2463
2464 let resp =
2465 ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2466 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2467 [("name".to_string(), ElicitationContentValue::from("Alice"))],
2468 ))),
2469 )));
2470 let json = serde_json::to_value(&resp).unwrap();
2471 assert_eq!(json["action"], "accept");
2472 assert_eq!(json["content"]["name"], "Alice");
2473
2474 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2476 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2477 }
2478
2479 #[test]
2480 fn client_response_serialization_decline() {
2481 use crate::v2::ClientResponse;
2482
2483 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2484 CreateElicitationResponse::new(ElicitationAction::Decline),
2485 ));
2486 let json = serde_json::to_value(&resp).unwrap();
2487 assert_eq!(json["action"], "decline");
2488
2489 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2490 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2491 }
2492
2493 #[test]
2494 fn client_response_serialization_cancel() {
2495 use crate::v2::ClientResponse;
2496
2497 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2498 CreateElicitationResponse::new(ElicitationAction::Cancel),
2499 ));
2500 let json = serde_json::to_value(&resp).unwrap();
2501 assert_eq!(json["action"], "cancel");
2502
2503 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2504 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2505 }
2506
2507 #[test]
2510 fn request_tolerates_extra_fields() {
2511 let json = json!({
2512 "sessionId": "sess_1",
2513 "mode": "form",
2514 "message": "Enter your name",
2515 "requestedSchema": {
2516 "type": "object",
2517 "properties": {
2518 "name": { "type": "string", "title": "Name" }
2519 },
2520 "required": ["name"]
2521 },
2522 "unknownStringField": "hello",
2523 "unknownNumberField": 42
2524 });
2525
2526 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2527 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2528 assert_eq!(req.message, "Enter your name");
2529 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2530 }
2531
2532 #[test]
2533 fn completion_notification_serialization() {
2534 let notif = CompleteElicitationNotification::new("elic_1");
2535
2536 let json = serde_json::to_value(¬if).unwrap();
2537 assert_eq!(json["elicitationId"], "elic_1");
2538
2539 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2540 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2541 }
2542
2543 #[test]
2544 fn empty_capabilities_do_not_advertise_a_mode() {
2545 let caps = ElicitationCapabilities::new();
2546 assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2547 assert!(!caps.supports_form());
2548 assert!(!caps.supports_url());
2549
2550 for value in [
2551 json!({}),
2552 json!({ "form": null }),
2553 json!({ "url": null }),
2554 json!({ "form": null, "url": null }),
2555 ] {
2556 let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2557 assert!(!caps.supports_form());
2558 assert!(!caps.supports_url());
2559 }
2560 }
2561
2562 #[test]
2563 fn capabilities_form_only() {
2564 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2565
2566 let json = serde_json::to_value(&caps).unwrap();
2567 assert!(json["form"].is_object());
2568 assert!(json.get("url").is_none());
2569
2570 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2571 assert!(roundtripped.form.is_some());
2572 assert!(roundtripped.url.is_none());
2573 assert!(roundtripped.supports_form());
2574 assert!(!roundtripped.supports_url());
2575 }
2576
2577 #[test]
2578 fn capabilities_url_only() {
2579 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2580
2581 let json = serde_json::to_value(&caps).unwrap();
2582 assert!(json.get("form").is_none());
2583 assert!(json["url"].is_object());
2584
2585 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2586 assert!(roundtripped.form.is_none());
2587 assert!(roundtripped.url.is_some());
2588 assert!(!roundtripped.supports_form());
2589 assert!(roundtripped.supports_url());
2590 }
2591
2592 #[test]
2593 fn capabilities_both() {
2594 let caps = ElicitationCapabilities::new()
2595 .form(ElicitationFormCapabilities::new())
2596 .url(ElicitationUrlCapabilities::new());
2597
2598 let json = serde_json::to_value(&caps).unwrap();
2599 assert!(json["form"].is_object());
2600 assert!(json["url"].is_object());
2601
2602 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2603 assert!(roundtripped.form.is_some());
2604 assert!(roundtripped.url.is_some());
2605 assert!(roundtripped.supports_form());
2606 assert!(roundtripped.supports_url());
2607 }
2608
2609 #[test]
2610 fn schema_default_sets_object_type() {
2611 let schema = ElicitationSchema::default();
2612
2613 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2614 assert!(schema.properties.is_empty());
2615
2616 let json = serde_json::to_value(&schema).unwrap();
2617 assert_eq!(json["type"], "object");
2618 }
2619
2620 #[test]
2621 fn schema_root_requires_object_type_and_properties() {
2622 let schema: ElicitationSchema =
2623 serde_json::from_value(json!({ "type": "object", "properties": {} })).unwrap();
2624 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2625 for missing in [json!({ "properties": {} }), json!({ "type": "object" })] {
2626 assert!(
2627 serde_json::from_value::<ElicitationSchema>(missing.clone()).is_err(),
2628 "{missing}"
2629 );
2630 }
2631 for bad in [json!("array"), json!(42), json!(null)] {
2632 assert!(
2633 serde_json::from_value::<ElicitationSchema>(
2634 json!({ "type": bad, "properties": {} })
2635 )
2636 .is_err(),
2637 "{bad}"
2638 );
2639 }
2640 }
2641
2642 #[test]
2643 fn schema_builder_serialization() {
2644 let schema = ElicitationSchema::new()
2645 .string("name", true)
2646 .email("email", true)
2647 .integer("age", 0, 150, true)
2648 .boolean("newsletter", false)
2649 .description("User registration");
2650
2651 let json = serde_json::to_value(&schema).unwrap();
2652 assert_eq!(json["type"], "object");
2653 assert_eq!(json["description"], "User registration");
2654 assert_eq!(json["properties"]["name"]["type"], "string");
2655 assert_eq!(json["properties"]["email"]["type"], "string");
2656 assert_eq!(json["properties"]["email"]["format"], "email");
2657 assert_eq!(json["properties"]["age"]["type"], "integer");
2658 assert_eq!(json["properties"]["age"]["minimum"], 0);
2659 assert_eq!(json["properties"]["age"]["maximum"], 150);
2660 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2661
2662 let required = json["required"].as_array().unwrap();
2663 assert!(required.contains(&json!("name")));
2664 assert!(required.contains(&json!("email")));
2665 assert!(required.contains(&json!("age")));
2666 assert!(!required.contains(&json!("newsletter")));
2667
2668 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2669 assert_eq!(roundtripped.properties.len(), 4);
2670 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2671 }
2672
2673 #[test]
2674 fn schema_string_enum_serialization() {
2675 let schema = ElicitationSchema::new().property(
2676 "color",
2677 StringPropertySchema::new().enum_values(vec![
2678 "red".into(),
2679 "green".into(),
2680 "blue".into(),
2681 ]),
2682 true,
2683 );
2684
2685 let json = serde_json::to_value(&schema).unwrap();
2686 assert_eq!(json["properties"]["color"]["type"], "string");
2687 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2688 assert_eq!(enum_vals.len(), 3);
2689
2690 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2691 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2692 {
2693 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2694 } else {
2695 panic!("expected String variant");
2696 }
2697 }
2698
2699 #[test]
2700 fn schema_multi_select_serialization() {
2701 let schema = ElicitationSchema::new().property(
2702 "colors",
2703 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2704 .min_items(1)
2705 .max_items(3),
2706 false,
2707 );
2708
2709 let json = serde_json::to_value(&schema).unwrap();
2710 assert_eq!(json["properties"]["colors"]["type"], "array");
2711 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2712 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2713 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2714
2715 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2716 let ElicitationPropertySchema::Array(array) =
2717 roundtripped.properties.get("colors").unwrap()
2718 else {
2719 panic!("expected Array variant");
2720 };
2721 let MultiSelectItems::String(items) = &array.items else {
2722 panic!("expected String multi-select items");
2723 };
2724 assert_eq!(items.values.len(), 3);
2725 }
2726
2727 #[test]
2728 fn multi_select_titled_items_keep_mcp_shape() {
2729 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2730 "#ff0000", "Red",
2731 )]));
2732
2733 let json = serde_json::to_value(&items).unwrap();
2734 assert!(json.get("type").is_none());
2735 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2736 assert_eq!(json["anyOf"][0]["title"], "Red");
2737
2738 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2739 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2740 }
2741
2742 #[test]
2743 fn multi_select_items_preserve_unknown_type() {
2744 let json = json!({
2745 "type": "_token",
2746 "format": "workspace",
2747 "anyOf": [
2748 { "const": "repo", "title": "Repository" }
2749 ]
2750 });
2751
2752 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2753 let MultiSelectItems::Other(other) = &items else {
2754 panic!("expected unknown multi-select items");
2755 };
2756
2757 assert_eq!(other.type_, "_token");
2758 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2759 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2760 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2761 }
2762
2763 #[test]
2764 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2765 assert!(
2766 serde_json::from_value::<MultiSelectItems>(json!({
2767 "type": "string"
2768 }))
2769 .is_err()
2770 );
2771 assert!(
2772 serde_json::from_value::<OtherMultiSelectItems>(json!({
2773 "type": "string",
2774 "format": "workspace"
2775 }))
2776 .is_err()
2777 );
2778 }
2779
2780 #[test]
2781 fn property_schema_preserves_unknown_type() {
2782 let schema: ElicitationSchema = serde_json::from_value(json!({
2783 "type": "object",
2784 "properties": {
2785 "location": {
2786 "type": "_location",
2787 "title": "Location",
2788 "precision": "city"
2789 }
2790 }
2791 }))
2792 .unwrap();
2793
2794 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2795 else {
2796 panic!("expected unknown property schema");
2797 };
2798
2799 assert_eq!(unknown.type_, "_location");
2800 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2801 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2802 assert_eq!(
2803 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2804 json!({
2805 "type": "_location",
2806 "title": "Location",
2807 "precision": "city"
2808 })
2809 );
2810 }
2811
2812 #[test]
2813 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2814 assert!(
2815 serde_json::from_value::<ElicitationPropertySchema>(json!({
2816 "type": "array"
2817 }))
2818 .is_err()
2819 );
2820 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2821 }
2822
2823 #[test]
2824 fn schema_titled_enum_serialization() {
2825 let schema = ElicitationSchema::new().property(
2826 "country",
2827 StringPropertySchema::new().one_of(vec![
2828 EnumOption::new("us", "United States").description("Use US English spelling."),
2829 EnumOption::new("uk", "United Kingdom"),
2830 ]),
2831 true,
2832 );
2833
2834 let json = serde_json::to_value(&schema).unwrap();
2835 assert_eq!(json["properties"]["country"]["type"], "string");
2836 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2837 assert_eq!(one_of.len(), 2);
2838 assert_eq!(one_of[0]["const"], "us");
2839 assert_eq!(one_of[0]["title"], "United States");
2840 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2841 assert!(one_of[1].get("description").is_none());
2842
2843 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2844 if let ElicitationPropertySchema::String(s) =
2845 roundtripped.properties.get("country").unwrap()
2846 {
2847 let one_of = s.one_of.as_ref().unwrap();
2848 assert_eq!(one_of.len(), 2);
2849 assert_eq!(
2850 one_of[0].description.as_deref(),
2851 Some("Use US English spelling.")
2852 );
2853 assert!(one_of[1].description.is_none());
2854 } else {
2855 panic!("expected String variant");
2856 }
2857 }
2858
2859 #[test]
2860 fn schema_number_property_serialization() {
2861 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2862
2863 let json = serde_json::to_value(&schema).unwrap();
2864 assert_eq!(json["properties"]["rating"]["type"], "number");
2865 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2866 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2867
2868 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2869 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2870 {
2871 assert_eq!(n.minimum, Some(0.0));
2872 assert_eq!(n.maximum, Some(5.0));
2873 } else {
2874 panic!("expected Number variant");
2875 }
2876 }
2877
2878 #[test]
2879 fn schema_string_format_serialization() {
2880 let schema = ElicitationSchema::new()
2881 .uri("website", true)
2882 .date("birthday", true)
2883 .date_time("updated_at", false);
2884
2885 let json = serde_json::to_value(&schema).unwrap();
2886 assert_eq!(json["properties"]["website"]["type"], "string");
2887 assert_eq!(json["properties"]["website"]["format"], "uri");
2888 assert_eq!(json["properties"]["birthday"]["type"], "string");
2889 assert_eq!(json["properties"]["birthday"]["format"], "date");
2890 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2891 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2892
2893 let required = json["required"].as_array().unwrap();
2894 assert!(required.contains(&json!("website")));
2895 assert!(required.contains(&json!("birthday")));
2896 assert!(!required.contains(&json!("updated_at")));
2897 }
2898
2899 #[test]
2900 fn schema_string_pattern_serialization() {
2901 let schema = ElicitationSchema::new().property(
2902 "name",
2903 StringPropertySchema::new()
2904 .min_length(1)
2905 .max_length(64)
2906 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2907 true,
2908 );
2909
2910 let json = serde_json::to_value(&schema).unwrap();
2911 assert_eq!(json["properties"]["name"]["type"], "string");
2912 assert_eq!(
2913 json["properties"]["name"]["pattern"],
2914 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2915 );
2916
2917 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2918 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2919 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2920 } else {
2921 panic!("expected String variant");
2922 }
2923 }
2924
2925 #[test]
2926 fn schema_property_updates_required_state() {
2927 let schema = ElicitationSchema::new()
2928 .string("name", true)
2929 .email("name", false);
2930
2931 let json = serde_json::to_value(&schema).unwrap();
2932 assert!(json.get("required").is_none());
2933 assert_eq!(json["properties"]["name"]["format"], "email");
2934 }
2935
2936 #[test]
2937 fn titled_multi_select_items_reject_one_of() {
2938 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2939 "oneOf": [
2940 {
2941 "const": "red",
2942 "title": "Red"
2943 }
2944 ]
2945 }))
2946 .unwrap_err();
2947
2948 assert!(err.to_string().contains("missing field `anyOf`"));
2949 }
2950
2951 #[test]
2952 fn response_accept_rejects_non_object_content() {
2953 assert!(
2954 serde_json::from_value::<CreateElicitationResponse>(json!({
2955 "action": "accept",
2956 "content": "Alice"
2957 }))
2958 .is_err()
2959 );
2960 }
2961
2962 #[test]
2963 fn response_accept_treats_null_and_omitted_content_equally() {
2964 for value in [
2965 json!({ "action": "accept" }),
2966 json!({
2967 "action": "accept",
2968 "content": null
2969 }),
2970 ] {
2971 let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2972 let ElicitationAction::Accept(accept) = response.action else {
2973 panic!("expected accept action");
2974 };
2975 assert!(accept.content.is_none());
2976 }
2977 }
2978
2979 #[test]
2980 fn response_accept_preserves_nested_object_content() {
2981 let wire = json!({
2982 "action": "accept",
2983 "content": {
2984 "profile": {
2985 "name": "Alice"
2986 }
2987 }
2988 });
2989 let response: CreateElicitationResponse = serde_json::from_value(wire.clone()).unwrap();
2990 assert_eq!(serde_json::to_value(&response).unwrap(), wire);
2991 }
2992
2993 #[test]
2994 fn response_accept_allows_primitive_and_string_array_content() {
2995 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2996 ElicitationAcceptAction::new().content(BTreeMap::from([
2997 ("name".to_string(), ElicitationContentValue::from("Alice")),
2998 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2999 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
3000 (
3001 "subscribed".to_string(),
3002 ElicitationContentValue::from(true),
3003 ),
3004 (
3005 "tags".to_string(),
3006 ElicitationContentValue::from(vec!["rust", "acp"]),
3007 ),
3008 ])),
3009 ));
3010
3011 let json = serde_json::to_value(&response).unwrap();
3012 assert_eq!(json["action"], "accept");
3013 assert_eq!(json["content"]["name"], "Alice");
3014 assert_eq!(json["content"]["age"], 30);
3015 assert_eq!(json["content"]["score"], 9.5);
3016 assert_eq!(json["content"]["subscribed"], true);
3017 assert_eq!(json["content"]["tags"][0], "rust");
3018 assert_eq!(json["content"]["tags"][1], "acp");
3019 }
3020}