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