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, Eq, 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]
1575#[skip_serializing_none]
1576#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1577#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1578#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1579#[serde(rename_all = "camelCase")]
1580#[non_exhaustive]
1581pub struct CreateElicitationRequest {
1582 #[serde(flatten)]
1584 pub mode: ElicitationMode,
1585 pub message: String,
1587 #[serde_as(deserialize_as = "DefaultOnError")]
1595 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1596 #[serde(default)]
1597 #[serde(rename = "_meta")]
1598 pub meta: Option<Meta>,
1599}
1600
1601impl CreateElicitationRequest {
1602 #[must_use]
1604 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1605 Self {
1606 mode: mode.into(),
1607 message: message.into(),
1608 meta: None,
1609 }
1610 }
1611
1612 #[must_use]
1614 pub fn scope(&self) -> &ElicitationScope {
1615 self.mode.scope()
1616 }
1617
1618 #[must_use]
1626 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1627 self.meta = meta.into_option();
1628 self
1629 }
1630}
1631
1632#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1634#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1635#[serde(tag = "mode", rename_all = "snake_case")]
1636#[non_exhaustive]
1637pub enum ElicitationMode {
1638 Form(ElicitationFormMode),
1640 Url(ElicitationUrlMode),
1642 #[serde(untagged)]
1652 Other(OtherElicitationMode),
1653}
1654
1655#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1661#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1662#[cfg_attr(feature = "schemars", schemars(inline))]
1663#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1664#[serde(rename_all = "camelCase")]
1665#[non_exhaustive]
1666pub struct OtherElicitationMode {
1667 pub mode: String,
1673 #[serde(flatten)]
1675 pub scope: ElicitationScope,
1676 #[serde(flatten)]
1678 pub fields: BTreeMap<String, serde_json::Value>,
1679}
1680
1681impl OtherElicitationMode {
1682 #[must_use]
1684 pub fn new(
1685 mode: impl Into<String>,
1686 scope: impl Into<ElicitationScope>,
1687 mut fields: BTreeMap<String, serde_json::Value>,
1688 ) -> Self {
1689 fields.remove("mode");
1690 remove_elicitation_scope_fields(&mut fields);
1691 Self {
1692 mode: mode.into(),
1693 scope: scope.into(),
1694 fields,
1695 }
1696 }
1697}
1698
1699impl<'de> Deserialize<'de> for OtherElicitationMode {
1700 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1701 where
1702 D: serde::Deserializer<'de>,
1703 {
1704 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1705 let mode = fields
1706 .remove("mode")
1707 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1708 let serde_json::Value::String(mode) = mode else {
1709 return Err(serde::de::Error::custom("`mode` must be a string"));
1710 };
1711
1712 if is_known_elicitation_mode(&mode) {
1713 return Err(serde::de::Error::custom(format!(
1714 "known elicitation mode `{mode}` did not match its schema"
1715 )));
1716 }
1717
1718 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1719 fields.clone().into_iter().collect(),
1720 ))
1721 .map_err(serde::de::Error::custom)?;
1722 remove_elicitation_scope_fields(&mut fields);
1723
1724 Ok(Self {
1725 mode,
1726 scope,
1727 fields,
1728 })
1729 }
1730}
1731
1732const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1733
1734fn is_known_elicitation_mode(mode: &str) -> bool {
1735 KNOWN_ELICITATION_MODES.contains(&mode)
1736}
1737
1738fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1739 fields.remove("sessionId");
1740 fields.remove("toolCallId");
1741 fields.remove("requestId");
1742}
1743
1744#[cfg(feature = "schemars")]
1745fn other_elicitation_mode_schema(schema: &mut Schema) {
1746 super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1747}
1748
1749impl From<ElicitationFormMode> for ElicitationMode {
1750 fn from(mode: ElicitationFormMode) -> Self {
1751 Self::Form(mode)
1752 }
1753}
1754
1755impl From<ElicitationUrlMode> for ElicitationMode {
1756 fn from(mode: ElicitationUrlMode) -> Self {
1757 Self::Url(mode)
1758 }
1759}
1760
1761impl From<OtherElicitationMode> for ElicitationMode {
1762 fn from(mode: OtherElicitationMode) -> Self {
1763 Self::Other(mode)
1764 }
1765}
1766
1767impl ElicitationMode {
1768 #[must_use]
1770 pub fn scope(&self) -> &ElicitationScope {
1771 match self {
1772 Self::Form(f) => &f.scope,
1773 Self::Url(u) => &u.scope,
1774 Self::Other(other) => &other.scope,
1775 }
1776 }
1777}
1778
1779#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1781#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1782#[serde(rename_all = "camelCase")]
1783#[non_exhaustive]
1784pub struct ElicitationFormMode {
1785 #[serde(flatten)]
1787 pub scope: ElicitationScope,
1788 pub requested_schema: ElicitationSchema,
1790}
1791
1792impl ElicitationFormMode {
1793 #[must_use]
1795 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1796 Self {
1797 scope: scope.into(),
1798 requested_schema,
1799 }
1800 }
1801}
1802
1803#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1805#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1806#[serde(rename_all = "camelCase")]
1807#[non_exhaustive]
1808pub struct ElicitationUrlMode {
1809 #[serde(flatten)]
1811 pub scope: ElicitationScope,
1812 pub elicitation_id: ElicitationId,
1814 #[cfg_attr(feature = "schemars", schemars(url))]
1816 pub url: String,
1817}
1818
1819impl ElicitationUrlMode {
1820 #[must_use]
1822 pub fn new(
1823 scope: impl Into<ElicitationScope>,
1824 elicitation_id: impl Into<ElicitationId>,
1825 url: impl Into<String>,
1826 ) -> Self {
1827 Self {
1828 scope: scope.into(),
1829 elicitation_id: elicitation_id.into(),
1830 url: url.into(),
1831 }
1832 }
1833}
1834
1835#[serde_as]
1837#[skip_serializing_none]
1838#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1839#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1840#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1841#[serde(rename_all = "camelCase")]
1842#[non_exhaustive]
1843pub struct CreateElicitationResponse {
1844 #[serde(flatten)]
1846 pub action: ElicitationAction,
1847 #[serde_as(deserialize_as = "DefaultOnError")]
1855 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1856 #[serde(default)]
1857 #[serde(rename = "_meta")]
1858 pub meta: Option<Meta>,
1859}
1860
1861impl CreateElicitationResponse {
1862 #[must_use]
1864 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1865 Self {
1866 action: action.into(),
1867 meta: None,
1868 }
1869 }
1870
1871 #[must_use]
1879 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1880 self.meta = meta.into_option();
1881 self
1882 }
1883}
1884
1885#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1887#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1888#[serde(tag = "action", rename_all = "snake_case")]
1889#[non_exhaustive]
1890pub enum ElicitationAction {
1891 Accept(ElicitationAcceptAction),
1893 Decline,
1895 Cancel,
1897 #[serde(untagged)]
1907 Other(OtherElicitationAction),
1908}
1909
1910#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1916#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1917#[cfg_attr(feature = "schemars", schemars(inline))]
1918#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1919#[serde(rename_all = "camelCase")]
1920#[non_exhaustive]
1921pub struct OtherElicitationAction {
1922 pub action: String,
1928 #[serde(flatten)]
1930 pub fields: BTreeMap<String, serde_json::Value>,
1931}
1932
1933impl OtherElicitationAction {
1934 #[must_use]
1936 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1937 fields.remove("action");
1938 Self {
1939 action: action.into(),
1940 fields,
1941 }
1942 }
1943}
1944
1945impl<'de> Deserialize<'de> for OtherElicitationAction {
1946 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1947 where
1948 D: serde::Deserializer<'de>,
1949 {
1950 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1951 let action = fields
1952 .remove("action")
1953 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1954 let serde_json::Value::String(action) = action else {
1955 return Err(serde::de::Error::custom("`action` must be a string"));
1956 };
1957
1958 if is_known_elicitation_action(&action) {
1959 return Err(serde::de::Error::custom(format!(
1960 "known elicitation action `{action}` did not match its schema"
1961 )));
1962 }
1963
1964 Ok(Self { action, fields })
1965 }
1966}
1967
1968const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1969
1970fn is_known_elicitation_action(action: &str) -> bool {
1971 KNOWN_ELICITATION_ACTIONS.contains(&action)
1972}
1973
1974#[cfg(feature = "schemars")]
1975fn other_elicitation_action_schema(schema: &mut Schema) {
1976 super::schema_util::reject_known_string_discriminators(
1977 schema,
1978 "action",
1979 KNOWN_ELICITATION_ACTIONS,
1980 );
1981}
1982
1983impl From<ElicitationAcceptAction> for ElicitationAction {
1984 fn from(action: ElicitationAcceptAction) -> Self {
1985 Self::Accept(action)
1986 }
1987}
1988
1989impl From<OtherElicitationAction> for ElicitationAction {
1990 fn from(action: OtherElicitationAction) -> Self {
1991 Self::Other(action)
1992 }
1993}
1994
1995#[serde_as]
1997#[skip_serializing_none]
1998#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1999#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2000#[serde(rename_all = "camelCase")]
2001#[non_exhaustive]
2002pub struct ElicitationAcceptAction {
2003 #[serde(default)]
2005 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
2006}
2007
2008impl ElicitationAcceptAction {
2009 #[must_use]
2011 pub fn new() -> Self {
2012 Self { content: None }
2013 }
2014
2015 #[must_use]
2017 pub fn content(
2018 mut self,
2019 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2020 ) -> Self {
2021 self.content = content.into_option();
2022 self
2023 }
2024}
2025
2026#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2028#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2029#[serde(untagged)]
2030#[non_exhaustive]
2031pub enum ElicitationContentValue {
2032 String(String),
2034 Integer(i64),
2036 Number(f64),
2038 Boolean(bool),
2040 StringArray(Vec<String>),
2042}
2043
2044impl From<String> for ElicitationContentValue {
2045 fn from(value: String) -> Self {
2046 Self::String(value)
2047 }
2048}
2049
2050impl From<&str> for ElicitationContentValue {
2051 fn from(value: &str) -> Self {
2052 Self::String(value.to_string())
2053 }
2054}
2055
2056impl From<i64> for ElicitationContentValue {
2057 fn from(value: i64) -> Self {
2058 Self::Integer(value)
2059 }
2060}
2061
2062impl From<i32> for ElicitationContentValue {
2063 fn from(value: i32) -> Self {
2064 Self::Integer(i64::from(value))
2065 }
2066}
2067
2068impl From<f64> for ElicitationContentValue {
2069 fn from(value: f64) -> Self {
2070 Self::Number(value)
2071 }
2072}
2073
2074impl From<bool> for ElicitationContentValue {
2075 fn from(value: bool) -> Self {
2076 Self::Boolean(value)
2077 }
2078}
2079
2080impl From<Vec<String>> for ElicitationContentValue {
2081 fn from(value: Vec<String>) -> Self {
2082 Self::StringArray(value)
2083 }
2084}
2085
2086impl From<Vec<&str>> for ElicitationContentValue {
2087 fn from(value: Vec<&str>) -> Self {
2088 Self::StringArray(value.into_iter().map(str::to_string).collect())
2089 }
2090}
2091
2092impl Default for ElicitationAcceptAction {
2093 fn default() -> Self {
2094 Self::new()
2095 }
2096}
2097
2098#[serde_as]
2102#[skip_serializing_none]
2103#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2105#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2106#[serde(rename_all = "camelCase")]
2107#[non_exhaustive]
2108pub struct CompleteElicitationNotification {
2109 pub elicitation_id: ElicitationId,
2111 #[serde_as(deserialize_as = "DefaultOnError")]
2119 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2120 #[serde(default)]
2121 #[serde(rename = "_meta")]
2122 pub meta: Option<Meta>,
2123}
2124
2125impl CompleteElicitationNotification {
2126 #[must_use]
2128 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2129 Self {
2130 elicitation_id: elicitation_id.into(),
2131 meta: None,
2132 }
2133 }
2134
2135 #[must_use]
2143 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2144 self.meta = meta.into_option();
2145 self
2146 }
2147}
2148
2149#[cfg(test)]
2150mod tests {
2151 use super::*;
2152 use serde_json::json;
2153
2154 #[test]
2155 fn form_mode_request_serialization() {
2156 let schema = ElicitationSchema::new().string("name", true);
2157 let req = CreateElicitationRequest::new(
2158 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2159 "Please enter your name",
2160 );
2161
2162 let json = serde_json::to_value(&req).unwrap();
2163 assert_eq!(json["sessionId"], "sess_1");
2164 assert!(json.get("toolCallId").is_none());
2165 assert_eq!(json["mode"], "form");
2166 assert_eq!(json["message"], "Please enter your name");
2167 assert!(json["requestedSchema"].is_object());
2168 assert_eq!(json["requestedSchema"]["type"], "object");
2169 assert_eq!(
2170 json["requestedSchema"]["properties"]["name"]["type"],
2171 "string"
2172 );
2173
2174 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2175 assert_eq!(
2176 *roundtripped.scope(),
2177 ElicitationSessionScope::new("sess_1").into()
2178 );
2179 assert_eq!(roundtripped.message, "Please enter your name");
2180 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2181 }
2182
2183 #[test]
2184 fn url_mode_request_serialization() {
2185 let req = CreateElicitationRequest::new(
2186 ElicitationUrlMode::new(
2187 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2188 "elic_1",
2189 "https://example.com/auth",
2190 ),
2191 "Please authenticate",
2192 );
2193
2194 let json = serde_json::to_value(&req).unwrap();
2195 assert_eq!(json["sessionId"], "sess_2");
2196 assert_eq!(json["toolCallId"], "tc_1");
2197 assert_eq!(json["mode"], "url");
2198 assert_eq!(json["elicitationId"], "elic_1");
2199 assert_eq!(json["url"], "https://example.com/auth");
2200 assert_eq!(json["message"], "Please authenticate");
2201
2202 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2203 assert_eq!(
2204 *roundtripped.scope(),
2205 ElicitationSessionScope::new("sess_2")
2206 .tool_call_id("tc_1")
2207 .into()
2208 );
2209 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2210 }
2211
2212 #[test]
2213 fn response_accept_serialization() {
2214 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2215 ElicitationAcceptAction::new().content(BTreeMap::from([(
2216 "name".to_string(),
2217 ElicitationContentValue::from("Alice"),
2218 )])),
2219 ));
2220
2221 let json = serde_json::to_value(&resp).unwrap();
2222 assert_eq!(json["action"], "accept");
2223 assert_eq!(json["content"]["name"], "Alice");
2224
2225 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2226 assert!(matches!(
2227 roundtripped.action,
2228 ElicitationAction::Accept(ElicitationAcceptAction {
2229 content: Some(_),
2230 ..
2231 })
2232 ));
2233 }
2234
2235 #[test]
2236 fn response_decline_serialization() {
2237 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2238
2239 let json = serde_json::to_value(&resp).unwrap();
2240 assert_eq!(json["action"], "decline");
2241
2242 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2243 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2244 }
2245
2246 #[test]
2247 fn response_cancel_serialization() {
2248 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2249
2250 let json = serde_json::to_value(&resp).unwrap();
2251 assert_eq!(json["action"], "cancel");
2252
2253 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2254 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2255 }
2256
2257 #[test]
2258 fn unknown_action_response_serialization() {
2259 let json = json!({
2260 "action": "_defer",
2261 "reason": "waiting",
2262 "retryAfterMs": 1000
2263 });
2264
2265 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2266 let ElicitationAction::Other(other) = &resp.action else {
2267 panic!("expected unknown elicitation action");
2268 };
2269
2270 assert_eq!(other.action, "_defer");
2271 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2272 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2273 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2274 }
2275
2276 #[test]
2277 fn unknown_action_does_not_hide_known_action() {
2278 assert!(
2279 serde_json::from_value::<OtherElicitationAction>(json!({
2280 "action": "accept",
2281 "content": {}
2282 }))
2283 .is_err()
2284 );
2285 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2286 }
2287
2288 #[test]
2289 fn url_mode_request_scope_serialization() {
2290 let req = CreateElicitationRequest::new(
2291 ElicitationUrlMode::new(
2292 ElicitationRequestScope::new(RequestId::Number(42)),
2293 "elic_2",
2294 "https://example.com/setup",
2295 ),
2296 "Please complete setup",
2297 );
2298
2299 let json = serde_json::to_value(&req).unwrap();
2300 assert_eq!(json["requestId"], 42);
2301 assert!(json.get("sessionId").is_none());
2302 assert_eq!(json["mode"], "url");
2303 assert_eq!(json["elicitationId"], "elic_2");
2304 assert_eq!(json["url"], "https://example.com/setup");
2305 assert_eq!(json["message"], "Please complete setup");
2306
2307 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2308 assert_eq!(
2309 *roundtripped.scope(),
2310 ElicitationRequestScope::new(RequestId::Number(42)).into()
2311 );
2312 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2313 }
2314
2315 #[test]
2316 fn unknown_mode_request_serialization() {
2317 let json = json!({
2318 "requestId": 42,
2319 "mode": "_browser",
2320 "message": "Open a browser window",
2321 "target": "login"
2322 });
2323
2324 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2325 let ElicitationMode::Other(other) = &req.mode else {
2326 panic!("expected unknown elicitation mode");
2327 };
2328
2329 assert_eq!(other.mode, "_browser");
2330 assert_eq!(
2331 other.scope,
2332 ElicitationRequestScope::new(RequestId::Number(42)).into()
2333 );
2334 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2335 assert_eq!(
2336 *req.scope(),
2337 ElicitationRequestScope::new(RequestId::Number(42)).into()
2338 );
2339 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2340 }
2341
2342 #[test]
2343 fn unknown_mode_does_not_hide_malformed_known_mode() {
2344 let missing_requested_schema = json!({
2345 "requestId": 42,
2346 "mode": "form",
2347 "message": "Enter your name"
2348 });
2349
2350 assert!(
2351 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2352 );
2353 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2354 }
2355
2356 #[test]
2357 fn request_scope_request_serialization() {
2358 let req = CreateElicitationRequest::new(
2359 ElicitationFormMode::new(
2360 ElicitationRequestScope::new(RequestId::Number(99)),
2361 ElicitationSchema::new().string("workspace", true),
2362 ),
2363 "Enter workspace name",
2364 );
2365
2366 let json = serde_json::to_value(&req).unwrap();
2367 assert_eq!(json["requestId"], 99);
2368 assert!(json.get("sessionId").is_none());
2369
2370 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2371 assert_eq!(
2372 *roundtripped.scope(),
2373 ElicitationRequestScope::new(RequestId::Number(99)).into()
2374 );
2375 }
2376
2377 #[test]
2381 fn client_response_serialization_accept() {
2382 use crate::v2::ClientResponse;
2383
2384 let resp =
2385 ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2386 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2387 [("name".to_string(), ElicitationContentValue::from("Alice"))],
2388 ))),
2389 )));
2390 let json = serde_json::to_value(&resp).unwrap();
2391 assert_eq!(json["action"], "accept");
2392 assert_eq!(json["content"]["name"], "Alice");
2393
2394 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2396 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2397 }
2398
2399 #[test]
2400 fn client_response_serialization_decline() {
2401 use crate::v2::ClientResponse;
2402
2403 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2404 CreateElicitationResponse::new(ElicitationAction::Decline),
2405 ));
2406 let json = serde_json::to_value(&resp).unwrap();
2407 assert_eq!(json["action"], "decline");
2408
2409 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2410 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2411 }
2412
2413 #[test]
2414 fn client_response_serialization_cancel() {
2415 use crate::v2::ClientResponse;
2416
2417 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2418 CreateElicitationResponse::new(ElicitationAction::Cancel),
2419 ));
2420 let json = serde_json::to_value(&resp).unwrap();
2421 assert_eq!(json["action"], "cancel");
2422
2423 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2424 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2425 }
2426
2427 #[test]
2430 fn request_tolerates_extra_fields() {
2431 let json = json!({
2432 "sessionId": "sess_1",
2433 "mode": "form",
2434 "message": "Enter your name",
2435 "requestedSchema": {
2436 "type": "object",
2437 "properties": {
2438 "name": { "type": "string", "title": "Name" }
2439 },
2440 "required": ["name"]
2441 },
2442 "unknownStringField": "hello",
2443 "unknownNumberField": 42
2444 });
2445
2446 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2447 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2448 assert_eq!(req.message, "Enter your name");
2449 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2450 }
2451
2452 #[test]
2453 fn completion_notification_serialization() {
2454 let notif = CompleteElicitationNotification::new("elic_1");
2455
2456 let json = serde_json::to_value(¬if).unwrap();
2457 assert_eq!(json["elicitationId"], "elic_1");
2458
2459 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2460 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2461 }
2462
2463 #[test]
2464 fn empty_capabilities_do_not_advertise_a_mode() {
2465 let caps = ElicitationCapabilities::new();
2466 assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2467 assert!(!caps.supports_form());
2468 assert!(!caps.supports_url());
2469
2470 for value in [
2471 json!({}),
2472 json!({ "form": null }),
2473 json!({ "url": null }),
2474 json!({ "form": null, "url": null }),
2475 ] {
2476 let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2477 assert!(!caps.supports_form());
2478 assert!(!caps.supports_url());
2479 }
2480 }
2481
2482 #[test]
2483 fn capabilities_form_only() {
2484 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2485
2486 let json = serde_json::to_value(&caps).unwrap();
2487 assert!(json["form"].is_object());
2488 assert!(json.get("url").is_none());
2489
2490 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2491 assert!(roundtripped.form.is_some());
2492 assert!(roundtripped.url.is_none());
2493 assert!(roundtripped.supports_form());
2494 assert!(!roundtripped.supports_url());
2495 }
2496
2497 #[test]
2498 fn capabilities_url_only() {
2499 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2500
2501 let json = serde_json::to_value(&caps).unwrap();
2502 assert!(json.get("form").is_none());
2503 assert!(json["url"].is_object());
2504
2505 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2506 assert!(roundtripped.form.is_none());
2507 assert!(roundtripped.url.is_some());
2508 assert!(!roundtripped.supports_form());
2509 assert!(roundtripped.supports_url());
2510 }
2511
2512 #[test]
2513 fn capabilities_both() {
2514 let caps = ElicitationCapabilities::new()
2515 .form(ElicitationFormCapabilities::new())
2516 .url(ElicitationUrlCapabilities::new());
2517
2518 let json = serde_json::to_value(&caps).unwrap();
2519 assert!(json["form"].is_object());
2520 assert!(json["url"].is_object());
2521
2522 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2523 assert!(roundtripped.form.is_some());
2524 assert!(roundtripped.url.is_some());
2525 assert!(roundtripped.supports_form());
2526 assert!(roundtripped.supports_url());
2527 }
2528
2529 #[test]
2530 fn schema_default_sets_object_type() {
2531 let schema = ElicitationSchema::default();
2532
2533 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2534 assert!(schema.properties.is_empty());
2535
2536 let json = serde_json::to_value(&schema).unwrap();
2537 assert_eq!(json["type"], "object");
2538 }
2539
2540 #[test]
2541 fn schema_builder_serialization() {
2542 let schema = ElicitationSchema::new()
2543 .string("name", true)
2544 .email("email", true)
2545 .integer("age", 0, 150, true)
2546 .boolean("newsletter", false)
2547 .description("User registration");
2548
2549 let json = serde_json::to_value(&schema).unwrap();
2550 assert_eq!(json["type"], "object");
2551 assert_eq!(json["description"], "User registration");
2552 assert_eq!(json["properties"]["name"]["type"], "string");
2553 assert_eq!(json["properties"]["email"]["type"], "string");
2554 assert_eq!(json["properties"]["email"]["format"], "email");
2555 assert_eq!(json["properties"]["age"]["type"], "integer");
2556 assert_eq!(json["properties"]["age"]["minimum"], 0);
2557 assert_eq!(json["properties"]["age"]["maximum"], 150);
2558 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2559
2560 let required = json["required"].as_array().unwrap();
2561 assert!(required.contains(&json!("name")));
2562 assert!(required.contains(&json!("email")));
2563 assert!(required.contains(&json!("age")));
2564 assert!(!required.contains(&json!("newsletter")));
2565
2566 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2567 assert_eq!(roundtripped.properties.len(), 4);
2568 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2569 }
2570
2571 #[test]
2572 fn schema_string_enum_serialization() {
2573 let schema = ElicitationSchema::new().property(
2574 "color",
2575 StringPropertySchema::new().enum_values(vec![
2576 "red".into(),
2577 "green".into(),
2578 "blue".into(),
2579 ]),
2580 true,
2581 );
2582
2583 let json = serde_json::to_value(&schema).unwrap();
2584 assert_eq!(json["properties"]["color"]["type"], "string");
2585 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2586 assert_eq!(enum_vals.len(), 3);
2587
2588 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2589 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2590 {
2591 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2592 } else {
2593 panic!("expected String variant");
2594 }
2595 }
2596
2597 #[test]
2598 fn schema_multi_select_serialization() {
2599 let schema = ElicitationSchema::new().property(
2600 "colors",
2601 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2602 .min_items(1)
2603 .max_items(3),
2604 false,
2605 );
2606
2607 let json = serde_json::to_value(&schema).unwrap();
2608 assert_eq!(json["properties"]["colors"]["type"], "array");
2609 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2610 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2611 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2612
2613 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2614 let ElicitationPropertySchema::Array(array) =
2615 roundtripped.properties.get("colors").unwrap()
2616 else {
2617 panic!("expected Array variant");
2618 };
2619 let MultiSelectItems::String(items) = &array.items else {
2620 panic!("expected String multi-select items");
2621 };
2622 assert_eq!(items.values.len(), 3);
2623 }
2624
2625 #[test]
2626 fn multi_select_titled_items_keep_mcp_shape() {
2627 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2628 "#ff0000", "Red",
2629 )]));
2630
2631 let json = serde_json::to_value(&items).unwrap();
2632 assert!(json.get("type").is_none());
2633 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2634 assert_eq!(json["anyOf"][0]["title"], "Red");
2635
2636 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2637 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2638 }
2639
2640 #[test]
2641 fn multi_select_items_preserve_unknown_type() {
2642 let json = json!({
2643 "type": "_token",
2644 "format": "workspace",
2645 "anyOf": [
2646 { "const": "repo", "title": "Repository" }
2647 ]
2648 });
2649
2650 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2651 let MultiSelectItems::Other(other) = &items else {
2652 panic!("expected unknown multi-select items");
2653 };
2654
2655 assert_eq!(other.type_, "_token");
2656 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2657 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2658 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2659 }
2660
2661 #[test]
2662 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2663 assert!(
2664 serde_json::from_value::<MultiSelectItems>(json!({
2665 "type": "string"
2666 }))
2667 .is_err()
2668 );
2669 assert!(
2670 serde_json::from_value::<OtherMultiSelectItems>(json!({
2671 "type": "string",
2672 "format": "workspace"
2673 }))
2674 .is_err()
2675 );
2676 }
2677
2678 #[test]
2679 fn property_schema_preserves_unknown_type() {
2680 let schema: ElicitationSchema = serde_json::from_value(json!({
2681 "type": "object",
2682 "properties": {
2683 "location": {
2684 "type": "_location",
2685 "title": "Location",
2686 "precision": "city"
2687 }
2688 }
2689 }))
2690 .unwrap();
2691
2692 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2693 else {
2694 panic!("expected unknown property schema");
2695 };
2696
2697 assert_eq!(unknown.type_, "_location");
2698 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2699 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2700 assert_eq!(
2701 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2702 json!({
2703 "type": "_location",
2704 "title": "Location",
2705 "precision": "city"
2706 })
2707 );
2708 }
2709
2710 #[test]
2711 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2712 assert!(
2713 serde_json::from_value::<ElicitationPropertySchema>(json!({
2714 "type": "array"
2715 }))
2716 .is_err()
2717 );
2718 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2719 }
2720
2721 #[test]
2722 fn schema_titled_enum_serialization() {
2723 let schema = ElicitationSchema::new().property(
2724 "country",
2725 StringPropertySchema::new().one_of(vec![
2726 EnumOption::new("us", "United States").description("Use US English spelling."),
2727 EnumOption::new("uk", "United Kingdom"),
2728 ]),
2729 true,
2730 );
2731
2732 let json = serde_json::to_value(&schema).unwrap();
2733 assert_eq!(json["properties"]["country"]["type"], "string");
2734 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2735 assert_eq!(one_of.len(), 2);
2736 assert_eq!(one_of[0]["const"], "us");
2737 assert_eq!(one_of[0]["title"], "United States");
2738 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2739 assert!(one_of[1].get("description").is_none());
2740
2741 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2742 if let ElicitationPropertySchema::String(s) =
2743 roundtripped.properties.get("country").unwrap()
2744 {
2745 let one_of = s.one_of.as_ref().unwrap();
2746 assert_eq!(one_of.len(), 2);
2747 assert_eq!(
2748 one_of[0].description.as_deref(),
2749 Some("Use US English spelling.")
2750 );
2751 assert!(one_of[1].description.is_none());
2752 } else {
2753 panic!("expected String variant");
2754 }
2755 }
2756
2757 #[test]
2758 fn schema_number_property_serialization() {
2759 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2760
2761 let json = serde_json::to_value(&schema).unwrap();
2762 assert_eq!(json["properties"]["rating"]["type"], "number");
2763 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2764 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2765
2766 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2767 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2768 {
2769 assert_eq!(n.minimum, Some(0.0));
2770 assert_eq!(n.maximum, Some(5.0));
2771 } else {
2772 panic!("expected Number variant");
2773 }
2774 }
2775
2776 #[test]
2777 fn schema_string_format_serialization() {
2778 let schema = ElicitationSchema::new()
2779 .uri("website", true)
2780 .date("birthday", true)
2781 .date_time("updated_at", false);
2782
2783 let json = serde_json::to_value(&schema).unwrap();
2784 assert_eq!(json["properties"]["website"]["type"], "string");
2785 assert_eq!(json["properties"]["website"]["format"], "uri");
2786 assert_eq!(json["properties"]["birthday"]["type"], "string");
2787 assert_eq!(json["properties"]["birthday"]["format"], "date");
2788 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2789 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2790
2791 let required = json["required"].as_array().unwrap();
2792 assert!(required.contains(&json!("website")));
2793 assert!(required.contains(&json!("birthday")));
2794 assert!(!required.contains(&json!("updated_at")));
2795 }
2796
2797 #[test]
2798 fn schema_string_pattern_serialization() {
2799 let schema = ElicitationSchema::new().property(
2800 "name",
2801 StringPropertySchema::new()
2802 .min_length(1)
2803 .max_length(64)
2804 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2805 true,
2806 );
2807
2808 let json = serde_json::to_value(&schema).unwrap();
2809 assert_eq!(json["properties"]["name"]["type"], "string");
2810 assert_eq!(
2811 json["properties"]["name"]["pattern"],
2812 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2813 );
2814
2815 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2816 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2817 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2818 } else {
2819 panic!("expected String variant");
2820 }
2821 }
2822
2823 #[test]
2824 fn schema_property_updates_required_state() {
2825 let schema = ElicitationSchema::new()
2826 .string("name", true)
2827 .email("name", false);
2828
2829 let json = serde_json::to_value(&schema).unwrap();
2830 assert!(json.get("required").is_none());
2831 assert_eq!(json["properties"]["name"]["format"], "email");
2832 }
2833
2834 #[test]
2835 fn schema_defaults_invalid_object_type() {
2836 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2837 "type": "array",
2838 "properties": {
2839 "name": {
2840 "type": "string"
2841 }
2842 }
2843 }))
2844 .unwrap();
2845
2846 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2847 assert!(schema.properties.contains_key("name"));
2848 }
2849
2850 #[test]
2851 fn titled_multi_select_items_reject_one_of() {
2852 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2853 "oneOf": [
2854 {
2855 "const": "red",
2856 "title": "Red"
2857 }
2858 ]
2859 }))
2860 .unwrap_err();
2861
2862 assert!(err.to_string().contains("missing field `anyOf`"));
2863 }
2864
2865 #[test]
2866 fn response_accept_rejects_non_object_content() {
2867 assert!(
2868 serde_json::from_value::<CreateElicitationResponse>(json!({
2869 "action": "accept",
2870 "content": "Alice"
2871 }))
2872 .is_err()
2873 );
2874 }
2875
2876 #[test]
2877 fn response_accept_treats_null_and_omitted_content_equally() {
2878 for value in [
2879 json!({ "action": "accept" }),
2880 json!({
2881 "action": "accept",
2882 "content": null
2883 }),
2884 ] {
2885 let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2886 let ElicitationAction::Accept(accept) = response.action else {
2887 panic!("expected accept action");
2888 };
2889 assert!(accept.content.is_none());
2890 }
2891 }
2892
2893 #[test]
2894 fn response_accept_rejects_nested_object_content() {
2895 assert!(
2896 serde_json::from_value::<CreateElicitationResponse>(json!({
2897 "action": "accept",
2898 "content": {
2899 "profile": {
2900 "name": "Alice"
2901 }
2902 }
2903 }))
2904 .is_err()
2905 );
2906 }
2907
2908 #[test]
2909 fn response_accept_allows_primitive_and_string_array_content() {
2910 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2911 ElicitationAcceptAction::new().content(BTreeMap::from([
2912 ("name".to_string(), ElicitationContentValue::from("Alice")),
2913 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2914 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2915 (
2916 "subscribed".to_string(),
2917 ElicitationContentValue::from(true),
2918 ),
2919 (
2920 "tags".to_string(),
2921 ElicitationContentValue::from(vec!["rust", "acp"]),
2922 ),
2923 ])),
2924 ));
2925
2926 let json = serde_json::to_value(&response).unwrap();
2927 assert_eq!(json["action"], "accept");
2928 assert_eq!(json["content"]["name"], "Alice");
2929 assert_eq!(json["content"]["age"], 30);
2930 assert_eq!(json["content"]["score"], 9.5);
2931 assert_eq!(json["content"]["subscribed"], true);
2932 assert_eq!(json["content"]["tags"][0], "rust");
2933 assert_eq!(json["content"]["tags"][1], "acp");
2934 }
2935}