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")]
95 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
96 #[serde(default)]
97 #[serde(rename = "_meta")]
98 pub meta: Option<Meta>,
99}
100
101impl EnumOption {
102 #[must_use]
104 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
105 Self {
106 value: value.into(),
107 title: title.into(),
108 description: None,
109 meta: None,
110 }
111 }
112
113 #[must_use]
115 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
116 self.description = description.into_option();
117 self
118 }
119
120 #[must_use]
128 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
129 self.meta = meta.into_option();
130 self
131 }
132}
133
134#[serde_as]
139#[skip_serializing_none]
140#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
141#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143#[non_exhaustive]
144pub struct StringPropertySchema {
145 #[serde_as(deserialize_as = "DefaultOnError")]
149 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
150 #[serde(default)]
151 pub title: Option<String>,
152 #[serde_as(deserialize_as = "DefaultOnError")]
156 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
157 #[serde(default)]
158 pub description: Option<String>,
159 #[serde(default)]
163 pub min_length: Option<u32>,
164 #[serde(default)]
168 pub max_length: Option<u32>,
169 #[cfg_attr(feature = "schemars", schemars(extend("format" = "regex")))]
173 #[serde(default)]
174 pub pattern: Option<String>,
175 #[serde(default)]
179 pub format: Option<StringFormat>,
180 #[serde_as(deserialize_as = "DefaultOnError")]
184 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
185 #[serde(default)]
186 pub default: Option<String>,
187 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
192 #[serde(default)]
193 #[serde(rename = "enum")]
194 pub enum_values: Option<Vec<String>>,
195 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
200 #[serde(default)]
201 #[serde(rename = "oneOf")]
202 pub one_of: Option<Vec<EnumOption>>,
203 #[serde_as(deserialize_as = "DefaultOnError")]
211 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
212 #[serde(default)]
213 #[serde(rename = "_meta")]
214 pub meta: Option<Meta>,
215}
216
217impl StringPropertySchema {
218 #[must_use]
220 pub fn new() -> Self {
221 Self::default()
222 }
223
224 #[must_use]
226 pub fn email() -> Self {
227 Self {
228 format: Some(StringFormat::Email),
229 ..Default::default()
230 }
231 }
232
233 #[must_use]
235 pub fn uri() -> Self {
236 Self {
237 format: Some(StringFormat::Uri),
238 ..Default::default()
239 }
240 }
241
242 #[must_use]
244 pub fn date() -> Self {
245 Self {
246 format: Some(StringFormat::Date),
247 ..Default::default()
248 }
249 }
250
251 #[must_use]
253 pub fn date_time() -> Self {
254 Self {
255 format: Some(StringFormat::DateTime),
256 ..Default::default()
257 }
258 }
259
260 #[must_use]
262 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
263 self.title = title.into_option();
264 self
265 }
266
267 #[must_use]
269 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
270 self.description = description.into_option();
271 self
272 }
273
274 #[must_use]
276 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
277 self.min_length = min_length.into_option();
278 self
279 }
280
281 #[must_use]
283 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
284 self.max_length = max_length.into_option();
285 self
286 }
287
288 #[must_use]
290 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
291 self.pattern = pattern.into_option();
292 self
293 }
294
295 #[must_use]
297 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
298 self.format = format.into_option();
299 self
300 }
301
302 #[must_use]
304 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
305 self.default = default.into_option();
306 self
307 }
308
309 #[must_use]
311 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
312 self.enum_values = enum_values.into_option();
313 self
314 }
315
316 #[must_use]
318 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
319 self.one_of = one_of.into_option();
320 self
321 }
322
323 #[must_use]
331 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
332 self.meta = meta.into_option();
333 self
334 }
335}
336
337#[serde_as]
339#[skip_serializing_none]
340#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
341#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
342#[serde(rename_all = "camelCase")]
343#[non_exhaustive]
344pub struct NumberPropertySchema {
345 #[serde_as(deserialize_as = "DefaultOnError")]
349 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
350 #[serde(default)]
351 pub title: Option<String>,
352 #[serde_as(deserialize_as = "DefaultOnError")]
356 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
357 #[serde(default)]
358 pub description: Option<String>,
359 #[serde(default)]
363 pub minimum: Option<f64>,
364 #[serde(default)]
368 pub maximum: Option<f64>,
369 #[serde_as(deserialize_as = "DefaultOnError")]
373 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
374 #[serde(default)]
375 pub default: Option<f64>,
376 #[serde_as(deserialize_as = "DefaultOnError")]
384 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
385 #[serde(default)]
386 #[serde(rename = "_meta")]
387 pub meta: Option<Meta>,
388}
389
390impl NumberPropertySchema {
391 #[must_use]
393 pub fn new() -> Self {
394 Self::default()
395 }
396
397 #[must_use]
399 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
400 self.title = title.into_option();
401 self
402 }
403
404 #[must_use]
406 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
407 self.description = description.into_option();
408 self
409 }
410
411 #[must_use]
413 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
414 self.minimum = minimum.into_option();
415 self
416 }
417
418 #[must_use]
420 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
421 self.maximum = maximum.into_option();
422 self
423 }
424
425 #[must_use]
427 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
428 self.default = default.into_option();
429 self
430 }
431
432 #[must_use]
440 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
441 self.meta = meta.into_option();
442 self
443 }
444}
445
446#[serde_as]
448#[skip_serializing_none]
449#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
450#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
451#[serde(rename_all = "camelCase")]
452#[non_exhaustive]
453pub struct IntegerPropertySchema {
454 #[serde_as(deserialize_as = "DefaultOnError")]
458 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
459 #[serde(default)]
460 pub title: Option<String>,
461 #[serde_as(deserialize_as = "DefaultOnError")]
465 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
466 #[serde(default)]
467 pub description: Option<String>,
468 #[serde(default)]
472 pub minimum: Option<i64>,
473 #[serde(default)]
477 pub maximum: Option<i64>,
478 #[serde_as(deserialize_as = "DefaultOnError")]
482 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
483 #[serde(default)]
484 pub default: Option<i64>,
485 #[serde_as(deserialize_as = "DefaultOnError")]
493 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
494 #[serde(default)]
495 #[serde(rename = "_meta")]
496 pub meta: Option<Meta>,
497}
498
499impl IntegerPropertySchema {
500 #[must_use]
502 pub fn new() -> Self {
503 Self::default()
504 }
505
506 #[must_use]
508 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
509 self.title = title.into_option();
510 self
511 }
512
513 #[must_use]
515 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
516 self.description = description.into_option();
517 self
518 }
519
520 #[must_use]
522 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
523 self.minimum = minimum.into_option();
524 self
525 }
526
527 #[must_use]
529 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
530 self.maximum = maximum.into_option();
531 self
532 }
533
534 #[must_use]
536 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
537 self.default = default.into_option();
538 self
539 }
540
541 #[must_use]
549 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
550 self.meta = meta.into_option();
551 self
552 }
553}
554
555#[serde_as]
557#[skip_serializing_none]
558#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
559#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
560#[serde(rename_all = "camelCase")]
561#[non_exhaustive]
562pub struct BooleanPropertySchema {
563 #[serde_as(deserialize_as = "DefaultOnError")]
567 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
568 #[serde(default)]
569 pub title: Option<String>,
570 #[serde_as(deserialize_as = "DefaultOnError")]
574 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
575 #[serde(default)]
576 pub description: Option<String>,
577 #[serde_as(deserialize_as = "DefaultOnError")]
581 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
582 #[serde(default)]
583 pub default: Option<bool>,
584 #[serde_as(deserialize_as = "DefaultOnError")]
592 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
593 #[serde(default)]
594 #[serde(rename = "_meta")]
595 pub meta: Option<Meta>,
596}
597
598impl BooleanPropertySchema {
599 #[must_use]
601 pub fn new() -> Self {
602 Self::default()
603 }
604
605 #[must_use]
607 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
608 self.title = title.into_option();
609 self
610 }
611
612 #[must_use]
614 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
615 self.description = description.into_option();
616 self
617 }
618
619 #[must_use]
621 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
622 self.default = default.into_option();
623 self
624 }
625
626 #[must_use]
634 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
635 self.meta = meta.into_option();
636 self
637 }
638}
639
640#[serde_as]
642#[skip_serializing_none]
643#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
644#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
645#[non_exhaustive]
646pub struct StringMultiSelectItems {
647 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
649 #[serde(rename = "enum")]
650 pub values: Vec<String>,
651 #[serde_as(deserialize_as = "DefaultOnError")]
659 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
660 #[serde(default)]
661 #[serde(rename = "_meta")]
662 pub meta: Option<Meta>,
663}
664
665impl StringMultiSelectItems {
666 #[must_use]
668 pub fn new(values: Vec<String>) -> Self {
669 Self { values, meta: None }
670 }
671
672 #[must_use]
680 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
681 self.meta = meta.into_option();
682 self
683 }
684}
685
686#[serde_as]
688#[skip_serializing_none]
689#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
690#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
691#[non_exhaustive]
692pub struct TitledMultiSelectItems {
693 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
695 #[serde(rename = "anyOf")]
696 pub options: Vec<EnumOption>,
697 #[serde_as(deserialize_as = "DefaultOnError")]
705 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
706 #[serde(default)]
707 #[serde(rename = "_meta")]
708 pub meta: Option<Meta>,
709}
710
711impl TitledMultiSelectItems {
712 #[must_use]
714 pub fn new(options: Vec<EnumOption>) -> Self {
715 Self {
716 options,
717 meta: None,
718 }
719 }
720
721 #[must_use]
729 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
730 self.meta = meta.into_option();
731 self
732 }
733}
734
735#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
741#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
742#[cfg_attr(feature = "schemars", schemars(inline))]
743#[cfg_attr(feature = "schemars", schemars(transform = other_multi_select_items_schema))]
744#[serde(rename_all = "camelCase")]
745#[non_exhaustive]
746pub struct OtherMultiSelectItems {
747 #[serde(rename = "type")]
753 pub type_: String,
754 #[serde(flatten)]
756 pub fields: BTreeMap<String, serde_json::Value>,
757}
758
759impl OtherMultiSelectItems {
760 #[must_use]
762 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
763 fields.remove("type");
764 Self {
765 type_: type_.into(),
766 fields,
767 }
768 }
769}
770
771impl<'de> Deserialize<'de> for OtherMultiSelectItems {
772 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
773 where
774 D: serde::Deserializer<'de>,
775 {
776 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
777 let type_ = fields
778 .remove("type")
779 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
780 let serde_json::Value::String(type_) = type_ else {
781 return Err(serde::de::Error::custom("`type` must be a string"));
782 };
783
784 if is_known_multi_select_item_type(&type_) {
785 return Err(serde::de::Error::custom(format!(
786 "known multi-select item type `{type_}` did not match its schema"
787 )));
788 }
789
790 Ok(Self { type_, fields })
791 }
792}
793
794const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
795
796fn is_known_multi_select_item_type(type_: &str) -> bool {
797 KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
798}
799
800#[cfg(feature = "schemars")]
801fn other_multi_select_items_schema(schema: &mut Schema) {
802 super::schema_util::reject_known_string_discriminators(
803 schema,
804 "type",
805 KNOWN_MULTI_SELECT_ITEM_TYPES,
806 );
807}
808
809#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
811#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
812#[serde(tag = "type", rename_all = "snake_case")]
813#[non_exhaustive]
814pub enum MultiSelectItems {
815 String(StringMultiSelectItems),
817 #[serde(untagged)]
819 Other(OtherMultiSelectItems),
820 #[serde(untagged)]
822 Titled(TitledMultiSelectItems),
823}
824
825#[serde_as]
827#[skip_serializing_none]
828#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
829#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
830#[serde(rename_all = "camelCase")]
831#[non_exhaustive]
832pub struct MultiSelectPropertySchema {
833 #[serde_as(deserialize_as = "DefaultOnError")]
837 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
838 #[serde(default)]
839 pub title: Option<String>,
840 #[serde_as(deserialize_as = "DefaultOnError")]
844 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
845 #[serde(default)]
846 pub description: Option<String>,
847 #[serde(default)]
851 pub min_items: Option<u64>,
852 #[serde(default)]
856 pub max_items: Option<u64>,
857 pub items: MultiSelectItems,
859 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
863 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
864 #[serde(default)]
865 pub default: Option<Vec<String>>,
866 #[serde_as(deserialize_as = "DefaultOnError")]
874 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
875 #[serde(default)]
876 #[serde(rename = "_meta")]
877 pub meta: Option<Meta>,
878}
879
880impl MultiSelectPropertySchema {
881 #[must_use]
883 pub fn new(values: Vec<String>) -> Self {
884 Self {
885 title: None,
886 description: None,
887 min_items: None,
888 max_items: None,
889 items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
890 default: None,
891 meta: None,
892 }
893 }
894
895 #[must_use]
897 pub fn titled(options: Vec<EnumOption>) -> Self {
898 Self {
899 title: None,
900 description: None,
901 min_items: None,
902 max_items: None,
903 items: MultiSelectItems::Titled(TitledMultiSelectItems {
904 options,
905 meta: None,
906 }),
907 default: None,
908 meta: None,
909 }
910 }
911
912 #[must_use]
914 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
915 self.title = title.into_option();
916 self
917 }
918
919 #[must_use]
921 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
922 self.description = description.into_option();
923 self
924 }
925
926 #[must_use]
928 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
929 self.min_items = min_items.into_option();
930 self
931 }
932
933 #[must_use]
935 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
936 self.max_items = max_items.into_option();
937 self
938 }
939
940 #[must_use]
942 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
943 self.default = default.into_option();
944 self
945 }
946
947 #[must_use]
955 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
956 self.meta = meta.into_option();
957 self
958 }
959}
960
961#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
967#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
968#[serde(tag = "type", rename_all = "snake_case")]
969#[non_exhaustive]
970pub enum ElicitationPropertySchema {
971 String(StringPropertySchema),
973 Number(NumberPropertySchema),
975 Integer(IntegerPropertySchema),
977 Boolean(BooleanPropertySchema),
979 Array(MultiSelectPropertySchema),
981 #[serde(untagged)]
991 Other(OtherElicitationPropertySchema),
992}
993
994#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1000#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1001#[cfg_attr(feature = "schemars", schemars(inline))]
1002#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_property_schema_schema))]
1003#[serde(rename_all = "camelCase")]
1004#[non_exhaustive]
1005pub struct OtherElicitationPropertySchema {
1006 #[serde(rename = "type")]
1012 pub type_: String,
1013 #[serde(flatten)]
1015 pub fields: BTreeMap<String, serde_json::Value>,
1016}
1017
1018impl OtherElicitationPropertySchema {
1019 #[must_use]
1021 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1022 fields.remove("type");
1023 Self {
1024 type_: type_.into(),
1025 fields,
1026 }
1027 }
1028}
1029
1030impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
1031 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1032 where
1033 D: serde::Deserializer<'de>,
1034 {
1035 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1036 let type_ = fields
1037 .remove("type")
1038 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1039 let serde_json::Value::String(type_) = type_ else {
1040 return Err(serde::de::Error::custom("`type` must be a string"));
1041 };
1042
1043 if is_known_elicitation_property_schema_type(&type_) {
1044 return Err(serde::de::Error::custom(format!(
1045 "known elicitation property schema type `{type_}` did not match its schema"
1046 )));
1047 }
1048
1049 Ok(Self { type_, fields })
1050 }
1051}
1052
1053const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
1054 &["string", "number", "integer", "boolean", "array"];
1055
1056fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
1057 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
1058}
1059
1060#[cfg(feature = "schemars")]
1061fn other_elicitation_property_schema_schema(schema: &mut Schema) {
1062 super::schema_util::reject_known_string_discriminators(
1063 schema,
1064 "type",
1065 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES,
1066 );
1067}
1068
1069impl From<StringPropertySchema> for ElicitationPropertySchema {
1070 fn from(schema: StringPropertySchema) -> Self {
1071 Self::String(schema)
1072 }
1073}
1074
1075impl From<NumberPropertySchema> for ElicitationPropertySchema {
1076 fn from(schema: NumberPropertySchema) -> Self {
1077 Self::Number(schema)
1078 }
1079}
1080
1081impl From<IntegerPropertySchema> for ElicitationPropertySchema {
1082 fn from(schema: IntegerPropertySchema) -> Self {
1083 Self::Integer(schema)
1084 }
1085}
1086
1087impl From<BooleanPropertySchema> for ElicitationPropertySchema {
1088 fn from(schema: BooleanPropertySchema) -> Self {
1089 Self::Boolean(schema)
1090 }
1091}
1092
1093impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
1094 fn from(schema: MultiSelectPropertySchema) -> Self {
1095 Self::Array(schema)
1096 }
1097}
1098
1099fn default_object_type() -> ElicitationSchemaType {
1100 ElicitationSchemaType::Object
1101}
1102
1103#[serde_as]
1108#[skip_serializing_none]
1109#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1111#[serde(rename_all = "camelCase")]
1112#[non_exhaustive]
1113pub struct ElicitationSchema {
1114 #[serde_as(deserialize_as = "DefaultOnError")]
1116 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1117 #[serde(rename = "type", default = "default_object_type")]
1118 pub type_: ElicitationSchemaType,
1119 #[serde_as(deserialize_as = "DefaultOnError")]
1123 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1124 #[serde(default)]
1125 pub title: Option<String>,
1126 #[serde(default)]
1128 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1129 #[serde(default)]
1133 pub required: Option<Vec<String>>,
1134 #[serde_as(deserialize_as = "DefaultOnError")]
1138 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1139 #[serde(default)]
1140 pub description: Option<String>,
1141 #[serde_as(deserialize_as = "DefaultOnError")]
1149 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1150 #[serde(default)]
1151 #[serde(rename = "_meta")]
1152 pub meta: Option<Meta>,
1153}
1154
1155impl Default for ElicitationSchema {
1156 fn default() -> Self {
1157 Self {
1158 type_: default_object_type(),
1159 title: None,
1160 properties: BTreeMap::new(),
1161 required: None,
1162 description: None,
1163 meta: None,
1164 }
1165 }
1166}
1167
1168impl ElicitationSchema {
1169 #[must_use]
1171 pub fn new() -> Self {
1172 Self::default()
1173 }
1174
1175 #[must_use]
1177 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1178 self.title = title.into_option();
1179 self
1180 }
1181
1182 #[must_use]
1184 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1185 self.description = description.into_option();
1186 self
1187 }
1188
1189 #[must_use]
1197 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1198 self.meta = meta.into_option();
1199 self
1200 }
1201
1202 #[must_use]
1204 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1205 where
1206 S: Into<ElicitationPropertySchema>,
1207 {
1208 let name = name.into();
1209 self.properties.insert(name.clone(), schema.into());
1210
1211 if required {
1212 let required_fields = self.required.get_or_insert_with(Vec::new);
1213 if !required_fields.contains(&name) {
1214 required_fields.push(name);
1215 }
1216 } else if let Some(required_fields) = &mut self.required {
1217 required_fields.retain(|field| field != &name);
1218
1219 if required_fields.is_empty() {
1220 self.required = None;
1221 }
1222 }
1223
1224 self
1225 }
1226
1227 #[must_use]
1229 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1230 self.property(name, StringPropertySchema::new(), required)
1231 }
1232
1233 #[must_use]
1235 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1236 self.property(name, StringPropertySchema::email(), required)
1237 }
1238
1239 #[must_use]
1241 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1242 self.property(name, StringPropertySchema::uri(), required)
1243 }
1244
1245 #[must_use]
1247 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1248 self.property(name, StringPropertySchema::date(), required)
1249 }
1250
1251 #[must_use]
1253 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1254 self.property(name, StringPropertySchema::date_time(), required)
1255 }
1256
1257 #[must_use]
1259 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1260 self.property(
1261 name,
1262 NumberPropertySchema::new().minimum(min).maximum(max),
1263 required,
1264 )
1265 }
1266
1267 #[must_use]
1269 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1270 self.property(
1271 name,
1272 IntegerPropertySchema::new().minimum(min).maximum(max),
1273 required,
1274 )
1275 }
1276
1277 #[must_use]
1279 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1280 self.property(name, BooleanPropertySchema::new(), required)
1281 }
1282}
1283
1284#[serde_as]
1286#[skip_serializing_none]
1287#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1288#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1289#[serde(rename_all = "camelCase")]
1290#[non_exhaustive]
1291pub struct ElicitationCapabilities {
1292 #[serde_as(deserialize_as = "DefaultOnError")]
1297 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1298 #[serde(default)]
1299 pub form: Option<ElicitationFormCapabilities>,
1300 #[serde_as(deserialize_as = "DefaultOnError")]
1305 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1306 #[serde(default)]
1307 pub url: Option<ElicitationUrlCapabilities>,
1308 #[serde_as(deserialize_as = "DefaultOnError")]
1316 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1317 #[serde(default)]
1318 #[serde(rename = "_meta")]
1319 pub meta: Option<Meta>,
1320}
1321
1322impl ElicitationCapabilities {
1323 #[must_use]
1328 pub fn new() -> Self {
1329 Self::default()
1330 }
1331
1332 #[must_use]
1335 pub fn supports_form(&self) -> bool {
1336 self.form.is_some()
1337 }
1338
1339 #[must_use]
1341 pub fn supports_url(&self) -> bool {
1342 self.url.is_some()
1343 }
1344
1345 #[must_use]
1350 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1351 self.form = form.into_option();
1352 self
1353 }
1354
1355 #[must_use]
1360 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1361 self.url = url.into_option();
1362 self
1363 }
1364
1365 #[must_use]
1373 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1374 self.meta = meta.into_option();
1375 self
1376 }
1377}
1378
1379#[serde_as]
1383#[skip_serializing_none]
1384#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1385#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1386#[serde(rename_all = "camelCase")]
1387#[non_exhaustive]
1388pub struct ElicitationFormCapabilities {
1389 #[serde_as(deserialize_as = "DefaultOnError")]
1397 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1398 #[serde(default)]
1399 #[serde(rename = "_meta")]
1400 pub meta: Option<Meta>,
1401}
1402
1403impl ElicitationFormCapabilities {
1404 #[must_use]
1406 pub fn new() -> Self {
1407 Self::default()
1408 }
1409
1410 #[must_use]
1418 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1419 self.meta = meta.into_option();
1420 self
1421 }
1422}
1423
1424#[serde_as]
1428#[skip_serializing_none]
1429#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1430#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1431#[serde(rename_all = "camelCase")]
1432#[non_exhaustive]
1433pub struct ElicitationUrlCapabilities {
1434 #[serde_as(deserialize_as = "DefaultOnError")]
1442 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1443 #[serde(default)]
1444 #[serde(rename = "_meta")]
1445 pub meta: Option<Meta>,
1446}
1447
1448impl ElicitationUrlCapabilities {
1449 #[must_use]
1451 pub fn new() -> Self {
1452 Self::default()
1453 }
1454
1455 #[must_use]
1463 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1464 self.meta = meta.into_option();
1465 self
1466 }
1467}
1468
1469#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1471#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1472#[serde(untagged)]
1473#[non_exhaustive]
1474pub enum ElicitationScope {
1475 Session(ElicitationSessionScope),
1477 Request(ElicitationRequestScope),
1480}
1481
1482#[serde_as]
1488#[skip_serializing_none]
1489#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1490#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1491#[serde(rename_all = "camelCase")]
1492#[non_exhaustive]
1493pub struct ElicitationSessionScope {
1494 pub session_id: SessionId,
1496 #[serde_as(deserialize_as = "DefaultOnError")]
1501 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1502 #[serde(default)]
1503 pub tool_call_id: Option<ToolCallId>,
1504}
1505
1506impl ElicitationSessionScope {
1507 #[must_use]
1509 pub fn new(session_id: impl Into<SessionId>) -> Self {
1510 Self {
1511 session_id: session_id.into(),
1512 tool_call_id: None,
1513 }
1514 }
1515
1516 #[must_use]
1518 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1519 self.tool_call_id = tool_call_id.into_option();
1520 self
1521 }
1522}
1523
1524#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1527#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1528#[serde(rename_all = "camelCase")]
1529#[non_exhaustive]
1530pub struct ElicitationRequestScope {
1531 pub request_id: RequestId,
1533}
1534
1535impl ElicitationRequestScope {
1536 #[must_use]
1538 pub fn new(request_id: impl Into<RequestId>) -> Self {
1539 Self {
1540 request_id: request_id.into(),
1541 }
1542 }
1543}
1544
1545impl From<ElicitationSessionScope> for ElicitationScope {
1546 fn from(scope: ElicitationSessionScope) -> Self {
1547 Self::Session(scope)
1548 }
1549}
1550
1551impl From<ElicitationRequestScope> for ElicitationScope {
1552 fn from(scope: ElicitationRequestScope) -> Self {
1553 Self::Request(scope)
1554 }
1555}
1556
1557#[serde_as]
1563#[skip_serializing_none]
1564#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1565#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1566#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1567#[serde(rename_all = "camelCase")]
1568#[non_exhaustive]
1569pub struct CreateElicitationRequest {
1570 #[serde(flatten)]
1572 pub mode: ElicitationMode,
1573 pub message: String,
1575 #[serde_as(deserialize_as = "DefaultOnError")]
1583 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1584 #[serde(default)]
1585 #[serde(rename = "_meta")]
1586 pub meta: Option<Meta>,
1587}
1588
1589impl CreateElicitationRequest {
1590 #[must_use]
1592 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1593 Self {
1594 mode: mode.into(),
1595 message: message.into(),
1596 meta: None,
1597 }
1598 }
1599
1600 #[must_use]
1602 pub fn scope(&self) -> &ElicitationScope {
1603 self.mode.scope()
1604 }
1605
1606 #[must_use]
1614 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1615 self.meta = meta.into_option();
1616 self
1617 }
1618}
1619
1620#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1622#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1623#[serde(tag = "mode", rename_all = "snake_case")]
1624#[non_exhaustive]
1625pub enum ElicitationMode {
1626 Form(ElicitationFormMode),
1628 Url(ElicitationUrlMode),
1630 #[serde(untagged)]
1640 Other(OtherElicitationMode),
1641}
1642
1643#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1649#[derive(Debug, Clone, Serialize, PartialEq)]
1650#[cfg_attr(feature = "schemars", schemars(inline))]
1651#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1652#[serde(rename_all = "camelCase")]
1653#[non_exhaustive]
1654pub struct OtherElicitationMode {
1655 pub mode: String,
1661 #[serde(flatten)]
1663 pub scope: ElicitationScope,
1664 #[serde(flatten)]
1666 pub fields: BTreeMap<String, serde_json::Value>,
1667}
1668
1669impl OtherElicitationMode {
1670 #[must_use]
1672 pub fn new(
1673 mode: impl Into<String>,
1674 scope: impl Into<ElicitationScope>,
1675 mut fields: BTreeMap<String, serde_json::Value>,
1676 ) -> Self {
1677 fields.remove("mode");
1678 remove_elicitation_scope_fields(&mut fields);
1679 Self {
1680 mode: mode.into(),
1681 scope: scope.into(),
1682 fields,
1683 }
1684 }
1685}
1686
1687impl<'de> Deserialize<'de> for OtherElicitationMode {
1688 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1689 where
1690 D: serde::Deserializer<'de>,
1691 {
1692 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1693 let mode = fields
1694 .remove("mode")
1695 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1696 let serde_json::Value::String(mode) = mode else {
1697 return Err(serde::de::Error::custom("`mode` must be a string"));
1698 };
1699
1700 if is_known_elicitation_mode(&mode) {
1701 return Err(serde::de::Error::custom(format!(
1702 "known elicitation mode `{mode}` did not match its schema"
1703 )));
1704 }
1705
1706 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1707 fields.clone().into_iter().collect(),
1708 ))
1709 .map_err(serde::de::Error::custom)?;
1710 remove_elicitation_scope_fields(&mut fields);
1711
1712 Ok(Self {
1713 mode,
1714 scope,
1715 fields,
1716 })
1717 }
1718}
1719
1720const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1721
1722fn is_known_elicitation_mode(mode: &str) -> bool {
1723 KNOWN_ELICITATION_MODES.contains(&mode)
1724}
1725
1726fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1727 fields.remove("sessionId");
1728 fields.remove("toolCallId");
1729 fields.remove("requestId");
1730}
1731
1732#[cfg(feature = "schemars")]
1733fn other_elicitation_mode_schema(schema: &mut Schema) {
1734 super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1735}
1736
1737impl From<ElicitationFormMode> for ElicitationMode {
1738 fn from(mode: ElicitationFormMode) -> Self {
1739 Self::Form(mode)
1740 }
1741}
1742
1743impl From<ElicitationUrlMode> for ElicitationMode {
1744 fn from(mode: ElicitationUrlMode) -> Self {
1745 Self::Url(mode)
1746 }
1747}
1748
1749impl From<OtherElicitationMode> for ElicitationMode {
1750 fn from(mode: OtherElicitationMode) -> Self {
1751 Self::Other(mode)
1752 }
1753}
1754
1755impl ElicitationMode {
1756 #[must_use]
1758 pub fn scope(&self) -> &ElicitationScope {
1759 match self {
1760 Self::Form(f) => &f.scope,
1761 Self::Url(u) => &u.scope,
1762 Self::Other(other) => &other.scope,
1763 }
1764 }
1765}
1766
1767#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1769#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1770#[serde(rename_all = "camelCase")]
1771#[non_exhaustive]
1772pub struct ElicitationFormMode {
1773 #[serde(flatten)]
1775 pub scope: ElicitationScope,
1776 pub requested_schema: ElicitationSchema,
1778}
1779
1780impl ElicitationFormMode {
1781 #[must_use]
1783 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1784 Self {
1785 scope: scope.into(),
1786 requested_schema,
1787 }
1788 }
1789}
1790
1791#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1793#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1794#[serde(rename_all = "camelCase")]
1795#[non_exhaustive]
1796pub struct ElicitationUrlMode {
1797 #[serde(flatten)]
1799 pub scope: ElicitationScope,
1800 pub elicitation_id: ElicitationId,
1802 #[cfg_attr(feature = "schemars", schemars(url))]
1804 pub url: String,
1805}
1806
1807impl ElicitationUrlMode {
1808 #[must_use]
1810 pub fn new(
1811 scope: impl Into<ElicitationScope>,
1812 elicitation_id: impl Into<ElicitationId>,
1813 url: impl Into<String>,
1814 ) -> Self {
1815 Self {
1816 scope: scope.into(),
1817 elicitation_id: elicitation_id.into(),
1818 url: url.into(),
1819 }
1820 }
1821}
1822
1823#[serde_as]
1825#[skip_serializing_none]
1826#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1827#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1828#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1829#[serde(rename_all = "camelCase")]
1830#[non_exhaustive]
1831pub struct CreateElicitationResponse {
1832 #[serde(flatten)]
1834 pub action: ElicitationAction,
1835 #[serde_as(deserialize_as = "DefaultOnError")]
1843 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1844 #[serde(default)]
1845 #[serde(rename = "_meta")]
1846 pub meta: Option<Meta>,
1847}
1848
1849impl CreateElicitationResponse {
1850 #[must_use]
1852 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1853 Self {
1854 action: action.into(),
1855 meta: None,
1856 }
1857 }
1858
1859 #[must_use]
1867 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1868 self.meta = meta.into_option();
1869 self
1870 }
1871}
1872
1873#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1875#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1876#[serde(tag = "action", rename_all = "snake_case")]
1877#[non_exhaustive]
1878pub enum ElicitationAction {
1879 Accept(ElicitationAcceptAction),
1881 Decline,
1883 Cancel,
1885 #[serde(untagged)]
1895 Other(OtherElicitationAction),
1896}
1897
1898#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1904#[derive(Debug, Clone, Serialize, PartialEq)]
1905#[cfg_attr(feature = "schemars", schemars(inline))]
1906#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1907#[serde(rename_all = "camelCase")]
1908#[non_exhaustive]
1909pub struct OtherElicitationAction {
1910 pub action: String,
1916 #[serde(flatten)]
1918 pub fields: BTreeMap<String, serde_json::Value>,
1919}
1920
1921impl OtherElicitationAction {
1922 #[must_use]
1924 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1925 fields.remove("action");
1926 Self {
1927 action: action.into(),
1928 fields,
1929 }
1930 }
1931}
1932
1933impl<'de> Deserialize<'de> for OtherElicitationAction {
1934 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1935 where
1936 D: serde::Deserializer<'de>,
1937 {
1938 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1939 let action = fields
1940 .remove("action")
1941 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1942 let serde_json::Value::String(action) = action else {
1943 return Err(serde::de::Error::custom("`action` must be a string"));
1944 };
1945
1946 if is_known_elicitation_action(&action) {
1947 return Err(serde::de::Error::custom(format!(
1948 "known elicitation action `{action}` did not match its schema"
1949 )));
1950 }
1951
1952 Ok(Self { action, fields })
1953 }
1954}
1955
1956const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1957
1958fn is_known_elicitation_action(action: &str) -> bool {
1959 KNOWN_ELICITATION_ACTIONS.contains(&action)
1960}
1961
1962#[cfg(feature = "schemars")]
1963fn other_elicitation_action_schema(schema: &mut Schema) {
1964 super::schema_util::reject_known_string_discriminators(
1965 schema,
1966 "action",
1967 KNOWN_ELICITATION_ACTIONS,
1968 );
1969}
1970
1971impl From<ElicitationAcceptAction> for ElicitationAction {
1972 fn from(action: ElicitationAcceptAction) -> Self {
1973 Self::Accept(action)
1974 }
1975}
1976
1977impl From<OtherElicitationAction> for ElicitationAction {
1978 fn from(action: OtherElicitationAction) -> Self {
1979 Self::Other(action)
1980 }
1981}
1982
1983#[serde_as]
1985#[skip_serializing_none]
1986#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1987#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1988#[serde(rename_all = "camelCase")]
1989#[non_exhaustive]
1990pub struct ElicitationAcceptAction {
1991 #[serde(default)]
1993 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1994}
1995
1996impl ElicitationAcceptAction {
1997 #[must_use]
1999 pub fn new() -> Self {
2000 Self { content: None }
2001 }
2002
2003 #[must_use]
2005 pub fn content(
2006 mut self,
2007 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2008 ) -> Self {
2009 self.content = content.into_option();
2010 self
2011 }
2012}
2013
2014#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2016#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2017#[serde(untagged)]
2018#[non_exhaustive]
2019pub enum ElicitationContentValue {
2020 String(String),
2022 Integer(i64),
2024 Number(f64),
2026 Boolean(bool),
2028 StringArray(Vec<String>),
2030}
2031
2032impl From<String> for ElicitationContentValue {
2033 fn from(value: String) -> Self {
2034 Self::String(value)
2035 }
2036}
2037
2038impl From<&str> for ElicitationContentValue {
2039 fn from(value: &str) -> Self {
2040 Self::String(value.to_string())
2041 }
2042}
2043
2044impl From<i64> for ElicitationContentValue {
2045 fn from(value: i64) -> Self {
2046 Self::Integer(value)
2047 }
2048}
2049
2050impl From<i32> for ElicitationContentValue {
2051 fn from(value: i32) -> Self {
2052 Self::Integer(i64::from(value))
2053 }
2054}
2055
2056impl From<f64> for ElicitationContentValue {
2057 fn from(value: f64) -> Self {
2058 Self::Number(value)
2059 }
2060}
2061
2062impl From<bool> for ElicitationContentValue {
2063 fn from(value: bool) -> Self {
2064 Self::Boolean(value)
2065 }
2066}
2067
2068impl From<Vec<String>> for ElicitationContentValue {
2069 fn from(value: Vec<String>) -> Self {
2070 Self::StringArray(value)
2071 }
2072}
2073
2074impl From<Vec<&str>> for ElicitationContentValue {
2075 fn from(value: Vec<&str>) -> Self {
2076 Self::StringArray(value.into_iter().map(str::to_string).collect())
2077 }
2078}
2079
2080impl Default for ElicitationAcceptAction {
2081 fn default() -> Self {
2082 Self::new()
2083 }
2084}
2085
2086#[serde_as]
2088#[skip_serializing_none]
2089#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2090#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2091#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2092#[serde(rename_all = "camelCase")]
2093#[non_exhaustive]
2094pub struct CompleteElicitationNotification {
2095 pub elicitation_id: ElicitationId,
2097 #[serde_as(deserialize_as = "DefaultOnError")]
2105 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2106 #[serde(default)]
2107 #[serde(rename = "_meta")]
2108 pub meta: Option<Meta>,
2109}
2110
2111impl CompleteElicitationNotification {
2112 #[must_use]
2114 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2115 Self {
2116 elicitation_id: elicitation_id.into(),
2117 meta: None,
2118 }
2119 }
2120
2121 #[must_use]
2129 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2130 self.meta = meta.into_option();
2131 self
2132 }
2133}
2134
2135#[cfg(test)]
2136mod tests {
2137 use super::*;
2138 use serde_json::json;
2139
2140 #[test]
2141 fn form_mode_request_serialization() {
2142 let schema = ElicitationSchema::new().string("name", true);
2143 let req = CreateElicitationRequest::new(
2144 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2145 "Please enter your name",
2146 );
2147
2148 let json = serde_json::to_value(&req).unwrap();
2149 assert_eq!(json["sessionId"], "sess_1");
2150 assert!(json.get("toolCallId").is_none());
2151 assert_eq!(json["mode"], "form");
2152 assert_eq!(json["message"], "Please enter your name");
2153 assert!(json["requestedSchema"].is_object());
2154 assert_eq!(json["requestedSchema"]["type"], "object");
2155 assert_eq!(
2156 json["requestedSchema"]["properties"]["name"]["type"],
2157 "string"
2158 );
2159
2160 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2161 assert_eq!(
2162 *roundtripped.scope(),
2163 ElicitationSessionScope::new("sess_1").into()
2164 );
2165 assert_eq!(roundtripped.message, "Please enter your name");
2166 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2167 }
2168
2169 #[test]
2170 fn url_mode_request_serialization() {
2171 let req = CreateElicitationRequest::new(
2172 ElicitationUrlMode::new(
2173 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2174 "elic_1",
2175 "https://example.com/auth",
2176 ),
2177 "Please authenticate",
2178 );
2179
2180 let json = serde_json::to_value(&req).unwrap();
2181 assert_eq!(json["sessionId"], "sess_2");
2182 assert_eq!(json["toolCallId"], "tc_1");
2183 assert_eq!(json["mode"], "url");
2184 assert_eq!(json["elicitationId"], "elic_1");
2185 assert_eq!(json["url"], "https://example.com/auth");
2186 assert_eq!(json["message"], "Please authenticate");
2187
2188 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2189 assert_eq!(
2190 *roundtripped.scope(),
2191 ElicitationSessionScope::new("sess_2")
2192 .tool_call_id("tc_1")
2193 .into()
2194 );
2195 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2196 }
2197
2198 #[test]
2199 fn response_accept_serialization() {
2200 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2201 ElicitationAcceptAction::new().content(BTreeMap::from([(
2202 "name".to_string(),
2203 ElicitationContentValue::from("Alice"),
2204 )])),
2205 ));
2206
2207 let json = serde_json::to_value(&resp).unwrap();
2208 assert_eq!(json["action"], "accept");
2209 assert_eq!(json["content"]["name"], "Alice");
2210
2211 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2212 assert!(matches!(
2213 roundtripped.action,
2214 ElicitationAction::Accept(ElicitationAcceptAction {
2215 content: Some(_),
2216 ..
2217 })
2218 ));
2219 }
2220
2221 #[test]
2222 fn response_decline_serialization() {
2223 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2224
2225 let json = serde_json::to_value(&resp).unwrap();
2226 assert_eq!(json["action"], "decline");
2227
2228 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2229 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2230 }
2231
2232 #[test]
2233 fn response_cancel_serialization() {
2234 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2235
2236 let json = serde_json::to_value(&resp).unwrap();
2237 assert_eq!(json["action"], "cancel");
2238
2239 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2240 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2241 }
2242
2243 #[test]
2244 fn unknown_action_response_serialization() {
2245 let json = json!({
2246 "action": "_defer",
2247 "reason": "waiting",
2248 "retryAfterMs": 1000
2249 });
2250
2251 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2252 let ElicitationAction::Other(other) = &resp.action else {
2253 panic!("expected unknown elicitation action");
2254 };
2255
2256 assert_eq!(other.action, "_defer");
2257 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2258 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2259 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2260 }
2261
2262 #[test]
2263 fn unknown_action_does_not_hide_known_action() {
2264 assert!(
2265 serde_json::from_value::<OtherElicitationAction>(json!({
2266 "action": "accept",
2267 "content": {}
2268 }))
2269 .is_err()
2270 );
2271 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2272 }
2273
2274 #[test]
2275 fn url_mode_request_scope_serialization() {
2276 let req = CreateElicitationRequest::new(
2277 ElicitationUrlMode::new(
2278 ElicitationRequestScope::new(RequestId::Number(42)),
2279 "elic_2",
2280 "https://example.com/setup",
2281 ),
2282 "Please complete setup",
2283 );
2284
2285 let json = serde_json::to_value(&req).unwrap();
2286 assert_eq!(json["requestId"], 42);
2287 assert!(json.get("sessionId").is_none());
2288 assert_eq!(json["mode"], "url");
2289 assert_eq!(json["elicitationId"], "elic_2");
2290 assert_eq!(json["url"], "https://example.com/setup");
2291 assert_eq!(json["message"], "Please complete setup");
2292
2293 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2294 assert_eq!(
2295 *roundtripped.scope(),
2296 ElicitationRequestScope::new(RequestId::Number(42)).into()
2297 );
2298 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2299 }
2300
2301 #[test]
2302 fn unknown_mode_request_serialization() {
2303 let json = json!({
2304 "requestId": 42,
2305 "mode": "_browser",
2306 "message": "Open a browser window",
2307 "target": "login"
2308 });
2309
2310 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2311 let ElicitationMode::Other(other) = &req.mode else {
2312 panic!("expected unknown elicitation mode");
2313 };
2314
2315 assert_eq!(other.mode, "_browser");
2316 assert_eq!(
2317 other.scope,
2318 ElicitationRequestScope::new(RequestId::Number(42)).into()
2319 );
2320 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2321 assert_eq!(
2322 *req.scope(),
2323 ElicitationRequestScope::new(RequestId::Number(42)).into()
2324 );
2325 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2326 }
2327
2328 #[test]
2329 fn unknown_mode_does_not_hide_malformed_known_mode() {
2330 let missing_requested_schema = json!({
2331 "requestId": 42,
2332 "mode": "form",
2333 "message": "Enter your name"
2334 });
2335
2336 assert!(
2337 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2338 );
2339 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2340 }
2341
2342 #[test]
2343 fn request_scope_request_serialization() {
2344 let req = CreateElicitationRequest::new(
2345 ElicitationFormMode::new(
2346 ElicitationRequestScope::new(RequestId::Number(99)),
2347 ElicitationSchema::new().string("workspace", true),
2348 ),
2349 "Enter workspace name",
2350 );
2351
2352 let json = serde_json::to_value(&req).unwrap();
2353 assert_eq!(json["requestId"], 99);
2354 assert!(json.get("sessionId").is_none());
2355
2356 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2357 assert_eq!(
2358 *roundtripped.scope(),
2359 ElicitationRequestScope::new(RequestId::Number(99)).into()
2360 );
2361 }
2362
2363 #[test]
2367 fn client_response_serialization_accept() {
2368 use crate::v2::ClientResponse;
2369
2370 let resp =
2371 ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2372 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2373 [("name".to_string(), ElicitationContentValue::from("Alice"))],
2374 ))),
2375 )));
2376 let json = serde_json::to_value(&resp).unwrap();
2377 assert_eq!(json["action"], "accept");
2378 assert_eq!(json["content"]["name"], "Alice");
2379
2380 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2382 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2383 }
2384
2385 #[test]
2386 fn client_response_serialization_decline() {
2387 use crate::v2::ClientResponse;
2388
2389 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2390 CreateElicitationResponse::new(ElicitationAction::Decline),
2391 ));
2392 let json = serde_json::to_value(&resp).unwrap();
2393 assert_eq!(json["action"], "decline");
2394
2395 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2396 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2397 }
2398
2399 #[test]
2400 fn client_response_serialization_cancel() {
2401 use crate::v2::ClientResponse;
2402
2403 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2404 CreateElicitationResponse::new(ElicitationAction::Cancel),
2405 ));
2406 let json = serde_json::to_value(&resp).unwrap();
2407 assert_eq!(json["action"], "cancel");
2408
2409 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2410 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2411 }
2412
2413 #[test]
2416 fn request_tolerates_extra_fields() {
2417 let json = json!({
2418 "sessionId": "sess_1",
2419 "mode": "form",
2420 "message": "Enter your name",
2421 "requestedSchema": {
2422 "type": "object",
2423 "properties": {
2424 "name": { "type": "string", "title": "Name" }
2425 },
2426 "required": ["name"]
2427 },
2428 "unknownStringField": "hello",
2429 "unknownNumberField": 42
2430 });
2431
2432 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2433 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2434 assert_eq!(req.message, "Enter your name");
2435 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2436 }
2437
2438 #[test]
2439 fn completion_notification_serialization() {
2440 let notif = CompleteElicitationNotification::new("elic_1");
2441
2442 let json = serde_json::to_value(¬if).unwrap();
2443 assert_eq!(json["elicitationId"], "elic_1");
2444
2445 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2446 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2447 }
2448
2449 #[test]
2450 fn empty_capabilities_do_not_advertise_a_mode() {
2451 let caps = ElicitationCapabilities::new();
2452 assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2453 assert!(!caps.supports_form());
2454 assert!(!caps.supports_url());
2455
2456 for value in [
2457 json!({}),
2458 json!({ "form": null }),
2459 json!({ "url": null }),
2460 json!({ "form": null, "url": null }),
2461 ] {
2462 let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2463 assert!(!caps.supports_form());
2464 assert!(!caps.supports_url());
2465 }
2466 }
2467
2468 #[test]
2469 fn capabilities_form_only() {
2470 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2471
2472 let json = serde_json::to_value(&caps).unwrap();
2473 assert!(json["form"].is_object());
2474 assert!(json.get("url").is_none());
2475
2476 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2477 assert!(roundtripped.form.is_some());
2478 assert!(roundtripped.url.is_none());
2479 assert!(roundtripped.supports_form());
2480 assert!(!roundtripped.supports_url());
2481 }
2482
2483 #[test]
2484 fn capabilities_url_only() {
2485 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2486
2487 let json = serde_json::to_value(&caps).unwrap();
2488 assert!(json.get("form").is_none());
2489 assert!(json["url"].is_object());
2490
2491 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2492 assert!(roundtripped.form.is_none());
2493 assert!(roundtripped.url.is_some());
2494 assert!(!roundtripped.supports_form());
2495 assert!(roundtripped.supports_url());
2496 }
2497
2498 #[test]
2499 fn capabilities_both() {
2500 let caps = ElicitationCapabilities::new()
2501 .form(ElicitationFormCapabilities::new())
2502 .url(ElicitationUrlCapabilities::new());
2503
2504 let json = serde_json::to_value(&caps).unwrap();
2505 assert!(json["form"].is_object());
2506 assert!(json["url"].is_object());
2507
2508 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2509 assert!(roundtripped.form.is_some());
2510 assert!(roundtripped.url.is_some());
2511 assert!(roundtripped.supports_form());
2512 assert!(roundtripped.supports_url());
2513 }
2514
2515 #[test]
2516 fn schema_default_sets_object_type() {
2517 let schema = ElicitationSchema::default();
2518
2519 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2520 assert!(schema.properties.is_empty());
2521
2522 let json = serde_json::to_value(&schema).unwrap();
2523 assert_eq!(json["type"], "object");
2524 }
2525
2526 #[test]
2527 fn schema_builder_serialization() {
2528 let schema = ElicitationSchema::new()
2529 .string("name", true)
2530 .email("email", true)
2531 .integer("age", 0, 150, true)
2532 .boolean("newsletter", false)
2533 .description("User registration");
2534
2535 let json = serde_json::to_value(&schema).unwrap();
2536 assert_eq!(json["type"], "object");
2537 assert_eq!(json["description"], "User registration");
2538 assert_eq!(json["properties"]["name"]["type"], "string");
2539 assert_eq!(json["properties"]["email"]["type"], "string");
2540 assert_eq!(json["properties"]["email"]["format"], "email");
2541 assert_eq!(json["properties"]["age"]["type"], "integer");
2542 assert_eq!(json["properties"]["age"]["minimum"], 0);
2543 assert_eq!(json["properties"]["age"]["maximum"], 150);
2544 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2545
2546 let required = json["required"].as_array().unwrap();
2547 assert!(required.contains(&json!("name")));
2548 assert!(required.contains(&json!("email")));
2549 assert!(required.contains(&json!("age")));
2550 assert!(!required.contains(&json!("newsletter")));
2551
2552 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2553 assert_eq!(roundtripped.properties.len(), 4);
2554 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2555 }
2556
2557 #[test]
2558 fn schema_string_enum_serialization() {
2559 let schema = ElicitationSchema::new().property(
2560 "color",
2561 StringPropertySchema::new().enum_values(vec![
2562 "red".into(),
2563 "green".into(),
2564 "blue".into(),
2565 ]),
2566 true,
2567 );
2568
2569 let json = serde_json::to_value(&schema).unwrap();
2570 assert_eq!(json["properties"]["color"]["type"], "string");
2571 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2572 assert_eq!(enum_vals.len(), 3);
2573
2574 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2575 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2576 {
2577 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2578 } else {
2579 panic!("expected String variant");
2580 }
2581 }
2582
2583 #[test]
2584 fn schema_multi_select_serialization() {
2585 let schema = ElicitationSchema::new().property(
2586 "colors",
2587 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2588 .min_items(1)
2589 .max_items(3),
2590 false,
2591 );
2592
2593 let json = serde_json::to_value(&schema).unwrap();
2594 assert_eq!(json["properties"]["colors"]["type"], "array");
2595 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2596 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2597 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2598
2599 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2600 let ElicitationPropertySchema::Array(array) =
2601 roundtripped.properties.get("colors").unwrap()
2602 else {
2603 panic!("expected Array variant");
2604 };
2605 let MultiSelectItems::String(items) = &array.items else {
2606 panic!("expected String multi-select items");
2607 };
2608 assert_eq!(items.values.len(), 3);
2609 }
2610
2611 #[test]
2612 fn multi_select_titled_items_keep_mcp_shape() {
2613 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2614 "#ff0000", "Red",
2615 )]));
2616
2617 let json = serde_json::to_value(&items).unwrap();
2618 assert!(json.get("type").is_none());
2619 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2620 assert_eq!(json["anyOf"][0]["title"], "Red");
2621
2622 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2623 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2624 }
2625
2626 #[test]
2627 fn multi_select_items_preserve_unknown_type() {
2628 let json = json!({
2629 "type": "_token",
2630 "format": "workspace",
2631 "anyOf": [
2632 { "const": "repo", "title": "Repository" }
2633 ]
2634 });
2635
2636 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2637 let MultiSelectItems::Other(other) = &items else {
2638 panic!("expected unknown multi-select items");
2639 };
2640
2641 assert_eq!(other.type_, "_token");
2642 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2643 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2644 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2645 }
2646
2647 #[test]
2648 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2649 assert!(
2650 serde_json::from_value::<MultiSelectItems>(json!({
2651 "type": "string"
2652 }))
2653 .is_err()
2654 );
2655 assert!(
2656 serde_json::from_value::<OtherMultiSelectItems>(json!({
2657 "type": "string",
2658 "format": "workspace"
2659 }))
2660 .is_err()
2661 );
2662 }
2663
2664 #[test]
2665 fn property_schema_preserves_unknown_type() {
2666 let schema: ElicitationSchema = serde_json::from_value(json!({
2667 "type": "object",
2668 "properties": {
2669 "location": {
2670 "type": "_location",
2671 "title": "Location",
2672 "precision": "city"
2673 }
2674 }
2675 }))
2676 .unwrap();
2677
2678 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2679 else {
2680 panic!("expected unknown property schema");
2681 };
2682
2683 assert_eq!(unknown.type_, "_location");
2684 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2685 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2686 assert_eq!(
2687 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2688 json!({
2689 "type": "_location",
2690 "title": "Location",
2691 "precision": "city"
2692 })
2693 );
2694 }
2695
2696 #[test]
2697 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2698 assert!(
2699 serde_json::from_value::<ElicitationPropertySchema>(json!({
2700 "type": "array"
2701 }))
2702 .is_err()
2703 );
2704 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2705 }
2706
2707 #[test]
2708 fn schema_titled_enum_serialization() {
2709 let schema = ElicitationSchema::new().property(
2710 "country",
2711 StringPropertySchema::new().one_of(vec![
2712 EnumOption::new("us", "United States").description("Use US English spelling."),
2713 EnumOption::new("uk", "United Kingdom"),
2714 ]),
2715 true,
2716 );
2717
2718 let json = serde_json::to_value(&schema).unwrap();
2719 assert_eq!(json["properties"]["country"]["type"], "string");
2720 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2721 assert_eq!(one_of.len(), 2);
2722 assert_eq!(one_of[0]["const"], "us");
2723 assert_eq!(one_of[0]["title"], "United States");
2724 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2725 assert!(one_of[1].get("description").is_none());
2726
2727 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2728 if let ElicitationPropertySchema::String(s) =
2729 roundtripped.properties.get("country").unwrap()
2730 {
2731 let one_of = s.one_of.as_ref().unwrap();
2732 assert_eq!(one_of.len(), 2);
2733 assert_eq!(
2734 one_of[0].description.as_deref(),
2735 Some("Use US English spelling.")
2736 );
2737 assert!(one_of[1].description.is_none());
2738 } else {
2739 panic!("expected String variant");
2740 }
2741 }
2742
2743 #[test]
2744 fn schema_number_property_serialization() {
2745 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2746
2747 let json = serde_json::to_value(&schema).unwrap();
2748 assert_eq!(json["properties"]["rating"]["type"], "number");
2749 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2750 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2751
2752 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2753 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2754 {
2755 assert_eq!(n.minimum, Some(0.0));
2756 assert_eq!(n.maximum, Some(5.0));
2757 } else {
2758 panic!("expected Number variant");
2759 }
2760 }
2761
2762 #[test]
2763 fn schema_string_format_serialization() {
2764 let schema = ElicitationSchema::new()
2765 .uri("website", true)
2766 .date("birthday", true)
2767 .date_time("updated_at", false);
2768
2769 let json = serde_json::to_value(&schema).unwrap();
2770 assert_eq!(json["properties"]["website"]["type"], "string");
2771 assert_eq!(json["properties"]["website"]["format"], "uri");
2772 assert_eq!(json["properties"]["birthday"]["type"], "string");
2773 assert_eq!(json["properties"]["birthday"]["format"], "date");
2774 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2775 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2776
2777 let required = json["required"].as_array().unwrap();
2778 assert!(required.contains(&json!("website")));
2779 assert!(required.contains(&json!("birthday")));
2780 assert!(!required.contains(&json!("updated_at")));
2781 }
2782
2783 #[test]
2784 fn schema_string_pattern_serialization() {
2785 let schema = ElicitationSchema::new().property(
2786 "name",
2787 StringPropertySchema::new()
2788 .min_length(1)
2789 .max_length(64)
2790 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2791 true,
2792 );
2793
2794 let json = serde_json::to_value(&schema).unwrap();
2795 assert_eq!(json["properties"]["name"]["type"], "string");
2796 assert_eq!(
2797 json["properties"]["name"]["pattern"],
2798 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2799 );
2800
2801 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2802 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2803 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2804 } else {
2805 panic!("expected String variant");
2806 }
2807 }
2808
2809 #[test]
2810 fn schema_property_updates_required_state() {
2811 let schema = ElicitationSchema::new()
2812 .string("name", true)
2813 .email("name", false);
2814
2815 let json = serde_json::to_value(&schema).unwrap();
2816 assert!(json.get("required").is_none());
2817 assert_eq!(json["properties"]["name"]["format"], "email");
2818 }
2819
2820 #[test]
2821 fn schema_defaults_invalid_object_type() {
2822 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2823 "type": "array",
2824 "properties": {
2825 "name": {
2826 "type": "string"
2827 }
2828 }
2829 }))
2830 .unwrap();
2831
2832 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2833 assert!(schema.properties.contains_key("name"));
2834 }
2835
2836 #[test]
2837 fn titled_multi_select_items_reject_one_of() {
2838 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2839 "oneOf": [
2840 {
2841 "const": "red",
2842 "title": "Red"
2843 }
2844 ]
2845 }))
2846 .unwrap_err();
2847
2848 assert!(err.to_string().contains("missing field `anyOf`"));
2849 }
2850
2851 #[test]
2852 fn response_accept_rejects_non_object_content() {
2853 assert!(
2854 serde_json::from_value::<CreateElicitationResponse>(json!({
2855 "action": "accept",
2856 "content": "Alice"
2857 }))
2858 .is_err()
2859 );
2860 }
2861
2862 #[test]
2863 fn response_accept_treats_null_and_omitted_content_equally() {
2864 for value in [
2865 json!({ "action": "accept" }),
2866 json!({
2867 "action": "accept",
2868 "content": null
2869 }),
2870 ] {
2871 let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2872 let ElicitationAction::Accept(accept) = response.action else {
2873 panic!("expected accept action");
2874 };
2875 assert!(accept.content.is_none());
2876 }
2877 }
2878
2879 #[test]
2880 fn response_accept_rejects_nested_object_content() {
2881 assert!(
2882 serde_json::from_value::<CreateElicitationResponse>(json!({
2883 "action": "accept",
2884 "content": {
2885 "profile": {
2886 "name": "Alice"
2887 }
2888 }
2889 }))
2890 .is_err()
2891 );
2892 }
2893
2894 #[test]
2895 fn response_accept_allows_primitive_and_string_array_content() {
2896 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2897 ElicitationAcceptAction::new().content(BTreeMap::from([
2898 ("name".to_string(), ElicitationContentValue::from("Alice")),
2899 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2900 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2901 (
2902 "subscribed".to_string(),
2903 ElicitationContentValue::from(true),
2904 ),
2905 (
2906 "tags".to_string(),
2907 ElicitationContentValue::from(vec!["rust", "acp"]),
2908 ),
2909 ])),
2910 ));
2911
2912 let json = serde_json::to_value(&response).unwrap();
2913 assert_eq!(json["action"], "accept");
2914 assert_eq!(json["content"]["name"], "Alice");
2915 assert_eq!(json["content"]["age"], 30);
2916 assert_eq!(json["content"]["score"], 9.5);
2917 assert_eq!(json["content"]["subscribed"], true);
2918 assert_eq!(json["content"]["tags"][0], "rust");
2919 assert_eq!(json["content"]["tags"][1], "acp");
2920 }
2921}