1use std::{collections::BTreeMap, sync::Arc};
7
8use derive_more::{Display, From};
9#[cfg(feature = "schemars")]
10use schemars::Schema;
11use serde::{Deserialize, Serialize};
12use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
13
14#[cfg(feature = "schemars")]
15use super::{ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME};
16use super::{Meta, RequestId, SessionId, ToolCallId};
17use crate::IntoOption;
18
19#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
22#[serde(transparent)]
23#[from(forward)]
24#[non_exhaustive]
25pub struct ElicitationId(pub Arc<str>);
26
27impl ElicitationId {
28 #[must_use]
30 pub fn new(id: impl Into<Self>) -> Self {
31 id.into()
32 }
33}
34
35#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "kebab-case")]
39#[non_exhaustive]
40pub enum StringFormat {
41 Email,
43 Uri,
45 Date,
47 DateTime,
49 #[serde(untagged)]
54 Other(String),
55}
56
57#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
60#[serde(rename_all = "snake_case")]
61#[non_exhaustive]
62pub enum ElicitationSchemaType {
63 #[default]
65 Object,
66}
67
68#[serde_as]
70#[skip_serializing_none]
71#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[non_exhaustive]
74pub struct EnumOption {
75 #[serde(rename = "const")]
77 pub value: String,
78 pub title: String,
80 #[serde_as(deserialize_as = "DefaultOnError")]
84 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
85 #[serde(default)]
86 pub description: Option<String>,
87 #[serde_as(deserialize_as = "DefaultOnError")]
93 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
94 #[serde(default)]
95 #[serde(rename = "_meta")]
96 pub meta: Option<Meta>,
97}
98
99impl EnumOption {
100 #[must_use]
102 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
103 Self {
104 value: value.into(),
105 title: title.into(),
106 description: None,
107 meta: None,
108 }
109 }
110
111 #[must_use]
113 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
114 self.description = description.into_option();
115 self
116 }
117
118 #[must_use]
126 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
127 self.meta = meta.into_option();
128 self
129 }
130}
131
132#[serde_as]
137#[skip_serializing_none]
138#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
139#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141#[non_exhaustive]
142pub struct StringPropertySchema {
143 #[serde_as(deserialize_as = "DefaultOnError")]
147 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
148 #[serde(default)]
149 pub title: Option<String>,
150 #[serde_as(deserialize_as = "DefaultOnError")]
154 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
155 #[serde(default)]
156 pub description: Option<String>,
157 #[serde(default)]
161 pub min_length: Option<u32>,
162 #[serde(default)]
166 pub max_length: Option<u32>,
167 #[cfg_attr(feature = "schemars", schemars(extend("format" = "regex")))]
171 #[serde(default)]
172 pub pattern: Option<String>,
173 #[serde(default)]
177 pub format: Option<StringFormat>,
178 #[serde_as(deserialize_as = "DefaultOnError")]
182 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
183 #[serde(default)]
184 pub default: Option<String>,
185 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
190 #[serde(default)]
191 #[serde(rename = "enum")]
192 pub enum_values: Option<Vec<String>>,
193 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
198 #[serde(default)]
199 #[serde(rename = "oneOf")]
200 pub one_of: Option<Vec<EnumOption>>,
201 #[serde_as(deserialize_as = "DefaultOnError")]
207 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
208 #[serde(default)]
209 #[serde(rename = "_meta")]
210 pub meta: Option<Meta>,
211}
212
213impl StringPropertySchema {
214 #[must_use]
216 pub fn new() -> Self {
217 Self::default()
218 }
219
220 #[must_use]
222 pub fn email() -> Self {
223 Self {
224 format: Some(StringFormat::Email),
225 ..Default::default()
226 }
227 }
228
229 #[must_use]
231 pub fn uri() -> Self {
232 Self {
233 format: Some(StringFormat::Uri),
234 ..Default::default()
235 }
236 }
237
238 #[must_use]
240 pub fn date() -> Self {
241 Self {
242 format: Some(StringFormat::Date),
243 ..Default::default()
244 }
245 }
246
247 #[must_use]
249 pub fn date_time() -> Self {
250 Self {
251 format: Some(StringFormat::DateTime),
252 ..Default::default()
253 }
254 }
255
256 #[must_use]
258 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
259 self.title = title.into_option();
260 self
261 }
262
263 #[must_use]
265 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
266 self.description = description.into_option();
267 self
268 }
269
270 #[must_use]
272 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
273 self.min_length = min_length.into_option();
274 self
275 }
276
277 #[must_use]
279 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
280 self.max_length = max_length.into_option();
281 self
282 }
283
284 #[must_use]
286 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
287 self.pattern = pattern.into_option();
288 self
289 }
290
291 #[must_use]
293 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
294 self.format = format.into_option();
295 self
296 }
297
298 #[must_use]
300 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
301 self.default = default.into_option();
302 self
303 }
304
305 #[must_use]
307 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
308 self.enum_values = enum_values.into_option();
309 self
310 }
311
312 #[must_use]
314 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
315 self.one_of = one_of.into_option();
316 self
317 }
318
319 #[must_use]
327 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
328 self.meta = meta.into_option();
329 self
330 }
331}
332
333#[serde_as]
335#[skip_serializing_none]
336#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
337#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
338#[serde(rename_all = "camelCase")]
339#[non_exhaustive]
340pub struct NumberPropertySchema {
341 #[serde_as(deserialize_as = "DefaultOnError")]
345 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
346 #[serde(default)]
347 pub title: Option<String>,
348 #[serde_as(deserialize_as = "DefaultOnError")]
352 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
353 #[serde(default)]
354 pub description: Option<String>,
355 #[serde(default)]
359 pub minimum: Option<f64>,
360 #[serde(default)]
364 pub maximum: Option<f64>,
365 #[serde_as(deserialize_as = "DefaultOnError")]
369 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
370 #[serde(default)]
371 pub default: Option<f64>,
372 #[serde_as(deserialize_as = "DefaultOnError")]
378 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
379 #[serde(default)]
380 #[serde(rename = "_meta")]
381 pub meta: Option<Meta>,
382}
383
384impl NumberPropertySchema {
385 #[must_use]
387 pub fn new() -> Self {
388 Self::default()
389 }
390
391 #[must_use]
393 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
394 self.title = title.into_option();
395 self
396 }
397
398 #[must_use]
400 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
401 self.description = description.into_option();
402 self
403 }
404
405 #[must_use]
407 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
408 self.minimum = minimum.into_option();
409 self
410 }
411
412 #[must_use]
414 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
415 self.maximum = maximum.into_option();
416 self
417 }
418
419 #[must_use]
421 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
422 self.default = default.into_option();
423 self
424 }
425
426 #[must_use]
434 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
435 self.meta = meta.into_option();
436 self
437 }
438}
439
440#[serde_as]
442#[skip_serializing_none]
443#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
444#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446#[non_exhaustive]
447pub struct IntegerPropertySchema {
448 #[serde_as(deserialize_as = "DefaultOnError")]
452 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
453 #[serde(default)]
454 pub title: Option<String>,
455 #[serde_as(deserialize_as = "DefaultOnError")]
459 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
460 #[serde(default)]
461 pub description: Option<String>,
462 #[serde(default)]
466 pub minimum: Option<i64>,
467 #[serde(default)]
471 pub maximum: Option<i64>,
472 #[serde_as(deserialize_as = "DefaultOnError")]
476 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
477 #[serde(default)]
478 pub default: Option<i64>,
479 #[serde_as(deserialize_as = "DefaultOnError")]
485 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
486 #[serde(default)]
487 #[serde(rename = "_meta")]
488 pub meta: Option<Meta>,
489}
490
491impl IntegerPropertySchema {
492 #[must_use]
494 pub fn new() -> Self {
495 Self::default()
496 }
497
498 #[must_use]
500 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
501 self.title = title.into_option();
502 self
503 }
504
505 #[must_use]
507 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
508 self.description = description.into_option();
509 self
510 }
511
512 #[must_use]
514 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
515 self.minimum = minimum.into_option();
516 self
517 }
518
519 #[must_use]
521 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
522 self.maximum = maximum.into_option();
523 self
524 }
525
526 #[must_use]
528 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
529 self.default = default.into_option();
530 self
531 }
532
533 #[must_use]
541 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
542 self.meta = meta.into_option();
543 self
544 }
545}
546
547#[serde_as]
549#[skip_serializing_none]
550#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
551#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
552#[serde(rename_all = "camelCase")]
553#[non_exhaustive]
554pub struct BooleanPropertySchema {
555 #[serde_as(deserialize_as = "DefaultOnError")]
559 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
560 #[serde(default)]
561 pub title: Option<String>,
562 #[serde_as(deserialize_as = "DefaultOnError")]
566 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
567 #[serde(default)]
568 pub description: Option<String>,
569 #[serde_as(deserialize_as = "DefaultOnError")]
573 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
574 #[serde(default)]
575 pub default: Option<bool>,
576 #[serde_as(deserialize_as = "DefaultOnError")]
582 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
583 #[serde(default)]
584 #[serde(rename = "_meta")]
585 pub meta: Option<Meta>,
586}
587
588impl BooleanPropertySchema {
589 #[must_use]
591 pub fn new() -> Self {
592 Self::default()
593 }
594
595 #[must_use]
597 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
598 self.title = title.into_option();
599 self
600 }
601
602 #[must_use]
604 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
605 self.description = description.into_option();
606 self
607 }
608
609 #[must_use]
611 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
612 self.default = default.into_option();
613 self
614 }
615
616 #[must_use]
624 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
625 self.meta = meta.into_option();
626 self
627 }
628}
629
630#[serde_as]
632#[skip_serializing_none]
633#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
634#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
635#[non_exhaustive]
636pub struct StringMultiSelectItems {
637 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
639 #[serde(rename = "enum")]
640 pub values: Vec<String>,
641 #[serde_as(deserialize_as = "DefaultOnError")]
647 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
648 #[serde(default)]
649 #[serde(rename = "_meta")]
650 pub meta: Option<Meta>,
651}
652
653impl StringMultiSelectItems {
654 #[must_use]
656 pub fn new(values: Vec<String>) -> Self {
657 Self { values, meta: None }
658 }
659
660 #[must_use]
668 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
669 self.meta = meta.into_option();
670 self
671 }
672}
673
674#[serde_as]
676#[skip_serializing_none]
677#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
678#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
679#[non_exhaustive]
680pub struct TitledMultiSelectItems {
681 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
683 #[serde(rename = "anyOf")]
684 pub options: Vec<EnumOption>,
685 #[serde_as(deserialize_as = "DefaultOnError")]
691 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
692 #[serde(default)]
693 #[serde(rename = "_meta")]
694 pub meta: Option<Meta>,
695}
696
697impl TitledMultiSelectItems {
698 #[must_use]
700 pub fn new(options: Vec<EnumOption>) -> Self {
701 Self {
702 options,
703 meta: None,
704 }
705 }
706
707 #[must_use]
715 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
716 self.meta = meta.into_option();
717 self
718 }
719}
720
721#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
727#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
728#[cfg_attr(feature = "schemars", schemars(inline))]
729#[cfg_attr(feature = "schemars", schemars(transform = other_multi_select_items_schema))]
730#[serde(rename_all = "camelCase")]
731#[non_exhaustive]
732pub struct OtherMultiSelectItems {
733 #[serde(rename = "type")]
739 pub type_: String,
740 #[serde(flatten)]
742 pub fields: BTreeMap<String, serde_json::Value>,
743}
744
745impl OtherMultiSelectItems {
746 #[must_use]
748 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
749 fields.remove("type");
750 Self {
751 type_: type_.into(),
752 fields,
753 }
754 }
755}
756
757impl<'de> Deserialize<'de> for OtherMultiSelectItems {
758 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
759 where
760 D: serde::Deserializer<'de>,
761 {
762 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
763 let type_ = fields
764 .remove("type")
765 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
766 let serde_json::Value::String(type_) = type_ else {
767 return Err(serde::de::Error::custom("`type` must be a string"));
768 };
769
770 if is_known_multi_select_item_type(&type_) {
771 return Err(serde::de::Error::custom(format!(
772 "known multi-select item type `{type_}` did not match its schema"
773 )));
774 }
775
776 Ok(Self { type_, fields })
777 }
778}
779
780const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
781
782fn is_known_multi_select_item_type(type_: &str) -> bool {
783 KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
784}
785
786#[cfg(feature = "schemars")]
787fn other_multi_select_items_schema(schema: &mut Schema) {
788 super::schema_util::reject_known_string_discriminators(
789 schema,
790 "type",
791 KNOWN_MULTI_SELECT_ITEM_TYPES,
792 );
793}
794
795#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
797#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
798#[serde(tag = "type", rename_all = "snake_case")]
799#[non_exhaustive]
800pub enum MultiSelectItems {
801 String(StringMultiSelectItems),
803 #[serde(untagged)]
805 Other(OtherMultiSelectItems),
806 #[serde(untagged)]
810 Titled(TitledMultiSelectItems),
811}
812
813#[serde_as]
815#[skip_serializing_none]
816#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
817#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
818#[serde(rename_all = "camelCase")]
819#[non_exhaustive]
820pub struct MultiSelectPropertySchema {
821 #[serde_as(deserialize_as = "DefaultOnError")]
825 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
826 #[serde(default)]
827 pub title: Option<String>,
828 #[serde_as(deserialize_as = "DefaultOnError")]
832 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
833 #[serde(default)]
834 pub description: Option<String>,
835 #[serde(default)]
839 pub min_items: Option<u64>,
840 #[serde(default)]
844 pub max_items: Option<u64>,
845 pub items: MultiSelectItems,
847 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
851 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
852 #[serde(default)]
853 pub default: Option<Vec<String>>,
854 #[serde_as(deserialize_as = "DefaultOnError")]
860 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
861 #[serde(default)]
862 #[serde(rename = "_meta")]
863 pub meta: Option<Meta>,
864}
865
866impl MultiSelectPropertySchema {
867 #[must_use]
869 pub fn new(values: Vec<String>) -> Self {
870 Self {
871 title: None,
872 description: None,
873 min_items: None,
874 max_items: None,
875 items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
876 default: None,
877 meta: None,
878 }
879 }
880
881 #[must_use]
883 pub fn titled(options: Vec<EnumOption>) -> Self {
884 Self {
885 title: None,
886 description: None,
887 min_items: None,
888 max_items: None,
889 items: MultiSelectItems::Titled(TitledMultiSelectItems {
890 options,
891 meta: None,
892 }),
893 default: None,
894 meta: None,
895 }
896 }
897
898 #[must_use]
900 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
901 self.title = title.into_option();
902 self
903 }
904
905 #[must_use]
907 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
908 self.description = description.into_option();
909 self
910 }
911
912 #[must_use]
914 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
915 self.min_items = min_items.into_option();
916 self
917 }
918
919 #[must_use]
921 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
922 self.max_items = max_items.into_option();
923 self
924 }
925
926 #[must_use]
928 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
929 self.default = default.into_option();
930 self
931 }
932
933 #[must_use]
941 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
942 self.meta = meta.into_option();
943 self
944 }
945}
946
947#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
953#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
954#[serde(tag = "type", rename_all = "snake_case")]
955#[non_exhaustive]
956pub enum ElicitationPropertySchema {
957 String(StringPropertySchema),
959 Number(NumberPropertySchema),
961 Integer(IntegerPropertySchema),
963 Boolean(BooleanPropertySchema),
965 Array(MultiSelectPropertySchema),
967 #[serde(untagged)]
977 Other(OtherElicitationPropertySchema),
978}
979
980#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
986#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
987#[cfg_attr(feature = "schemars", schemars(inline))]
988#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_property_schema_schema))]
989#[serde(rename_all = "camelCase")]
990#[non_exhaustive]
991pub struct OtherElicitationPropertySchema {
992 #[serde(rename = "type")]
998 pub type_: String,
999 #[serde(flatten)]
1001 pub fields: BTreeMap<String, serde_json::Value>,
1002}
1003
1004impl OtherElicitationPropertySchema {
1005 #[must_use]
1007 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1008 fields.remove("type");
1009 Self {
1010 type_: type_.into(),
1011 fields,
1012 }
1013 }
1014}
1015
1016impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
1017 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1018 where
1019 D: serde::Deserializer<'de>,
1020 {
1021 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1022 let type_ = fields
1023 .remove("type")
1024 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1025 let serde_json::Value::String(type_) = type_ else {
1026 return Err(serde::de::Error::custom("`type` must be a string"));
1027 };
1028
1029 if is_known_elicitation_property_schema_type(&type_) {
1030 return Err(serde::de::Error::custom(format!(
1031 "known elicitation property schema type `{type_}` did not match its schema"
1032 )));
1033 }
1034
1035 Ok(Self { type_, fields })
1036 }
1037}
1038
1039const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
1040 &["string", "number", "integer", "boolean", "array"];
1041
1042fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
1043 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
1044}
1045
1046#[cfg(feature = "schemars")]
1047fn other_elicitation_property_schema_schema(schema: &mut Schema) {
1048 super::schema_util::reject_known_string_discriminators(
1049 schema,
1050 "type",
1051 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES,
1052 );
1053}
1054
1055impl From<StringPropertySchema> for ElicitationPropertySchema {
1056 fn from(schema: StringPropertySchema) -> Self {
1057 Self::String(schema)
1058 }
1059}
1060
1061impl From<NumberPropertySchema> for ElicitationPropertySchema {
1062 fn from(schema: NumberPropertySchema) -> Self {
1063 Self::Number(schema)
1064 }
1065}
1066
1067impl From<IntegerPropertySchema> for ElicitationPropertySchema {
1068 fn from(schema: IntegerPropertySchema) -> Self {
1069 Self::Integer(schema)
1070 }
1071}
1072
1073impl From<BooleanPropertySchema> for ElicitationPropertySchema {
1074 fn from(schema: BooleanPropertySchema) -> Self {
1075 Self::Boolean(schema)
1076 }
1077}
1078
1079impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
1080 fn from(schema: MultiSelectPropertySchema) -> Self {
1081 Self::Array(schema)
1082 }
1083}
1084
1085fn default_object_type() -> ElicitationSchemaType {
1086 ElicitationSchemaType::Object
1087}
1088
1089#[serde_as]
1094#[skip_serializing_none]
1095#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1096#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1097#[serde(rename_all = "camelCase")]
1098#[non_exhaustive]
1099pub struct ElicitationSchema {
1100 #[serde(rename = "type", default = "default_object_type")]
1103 pub type_: ElicitationSchemaType,
1104 #[serde_as(deserialize_as = "DefaultOnError")]
1108 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1109 #[serde(default)]
1110 pub title: Option<String>,
1111 #[serde(default)]
1113 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1114 #[serde(default)]
1118 pub required: Option<Vec<String>>,
1119 #[serde_as(deserialize_as = "DefaultOnError")]
1123 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1124 #[serde(default)]
1125 pub description: Option<String>,
1126 #[serde_as(deserialize_as = "DefaultOnError")]
1132 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1133 #[serde(default)]
1134 #[serde(rename = "_meta")]
1135 pub meta: Option<Meta>,
1136}
1137
1138impl Default for ElicitationSchema {
1139 fn default() -> Self {
1140 Self {
1141 type_: default_object_type(),
1142 title: None,
1143 properties: BTreeMap::new(),
1144 required: None,
1145 description: None,
1146 meta: None,
1147 }
1148 }
1149}
1150
1151impl ElicitationSchema {
1152 #[must_use]
1154 pub fn new() -> Self {
1155 Self::default()
1156 }
1157
1158 #[must_use]
1160 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1161 self.title = title.into_option();
1162 self
1163 }
1164
1165 #[must_use]
1167 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1168 self.description = description.into_option();
1169 self
1170 }
1171
1172 #[must_use]
1180 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1181 self.meta = meta.into_option();
1182 self
1183 }
1184
1185 #[must_use]
1187 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1188 where
1189 S: Into<ElicitationPropertySchema>,
1190 {
1191 let name = name.into();
1192 self.properties.insert(name.clone(), schema.into());
1193
1194 if required {
1195 let required_fields = self.required.get_or_insert_with(Vec::new);
1196 if !required_fields.contains(&name) {
1197 required_fields.push(name);
1198 }
1199 } else if let Some(required_fields) = &mut self.required {
1200 required_fields.retain(|field| field != &name);
1201
1202 if required_fields.is_empty() {
1203 self.required = None;
1204 }
1205 }
1206
1207 self
1208 }
1209
1210 #[must_use]
1212 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1213 self.property(name, StringPropertySchema::new(), required)
1214 }
1215
1216 #[must_use]
1218 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1219 self.property(name, StringPropertySchema::email(), required)
1220 }
1221
1222 #[must_use]
1224 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1225 self.property(name, StringPropertySchema::uri(), required)
1226 }
1227
1228 #[must_use]
1230 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1231 self.property(name, StringPropertySchema::date(), required)
1232 }
1233
1234 #[must_use]
1236 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1237 self.property(name, StringPropertySchema::date_time(), required)
1238 }
1239
1240 #[must_use]
1242 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1243 self.property(
1244 name,
1245 NumberPropertySchema::new().minimum(min).maximum(max),
1246 required,
1247 )
1248 }
1249
1250 #[must_use]
1252 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1253 self.property(
1254 name,
1255 IntegerPropertySchema::new().minimum(min).maximum(max),
1256 required,
1257 )
1258 }
1259
1260 #[must_use]
1262 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1263 self.property(name, BooleanPropertySchema::new(), required)
1264 }
1265}
1266
1267#[serde_as]
1269#[skip_serializing_none]
1270#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1271#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1272#[serde(rename_all = "camelCase")]
1273#[non_exhaustive]
1274pub struct ElicitationCapabilities {
1275 #[serde_as(deserialize_as = "DefaultOnError")]
1280 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1281 #[serde(default)]
1282 pub form: Option<ElicitationFormCapabilities>,
1283 #[serde_as(deserialize_as = "DefaultOnError")]
1288 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1289 #[serde(default)]
1290 pub url: Option<ElicitationUrlCapabilities>,
1291 #[serde_as(deserialize_as = "DefaultOnError")]
1297 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1298 #[serde(default)]
1299 #[serde(rename = "_meta")]
1300 pub meta: Option<Meta>,
1301}
1302
1303impl ElicitationCapabilities {
1304 #[must_use]
1309 pub fn new() -> Self {
1310 Self::default()
1311 }
1312
1313 #[must_use]
1316 pub fn supports_form(&self) -> bool {
1317 self.form.is_some()
1318 }
1319
1320 #[must_use]
1322 pub fn supports_url(&self) -> bool {
1323 self.url.is_some()
1324 }
1325
1326 #[must_use]
1331 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1332 self.form = form.into_option();
1333 self
1334 }
1335
1336 #[must_use]
1341 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1342 self.url = url.into_option();
1343 self
1344 }
1345
1346 #[must_use]
1354 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1355 self.meta = meta.into_option();
1356 self
1357 }
1358}
1359
1360#[serde_as]
1364#[skip_serializing_none]
1365#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1366#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1367#[serde(rename_all = "camelCase")]
1368#[non_exhaustive]
1369pub struct ElicitationFormCapabilities {
1370 #[serde_as(deserialize_as = "DefaultOnError")]
1376 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1377 #[serde(default)]
1378 #[serde(rename = "_meta")]
1379 pub meta: Option<Meta>,
1380}
1381
1382impl ElicitationFormCapabilities {
1383 #[must_use]
1385 pub fn new() -> Self {
1386 Self::default()
1387 }
1388
1389 #[must_use]
1397 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1398 self.meta = meta.into_option();
1399 self
1400 }
1401}
1402
1403#[serde_as]
1407#[skip_serializing_none]
1408#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1409#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1410#[serde(rename_all = "camelCase")]
1411#[non_exhaustive]
1412pub struct ElicitationUrlCapabilities {
1413 #[serde_as(deserialize_as = "DefaultOnError")]
1419 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1420 #[serde(default)]
1421 #[serde(rename = "_meta")]
1422 pub meta: Option<Meta>,
1423}
1424
1425impl ElicitationUrlCapabilities {
1426 #[must_use]
1428 pub fn new() -> Self {
1429 Self::default()
1430 }
1431
1432 #[must_use]
1440 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1441 self.meta = meta.into_option();
1442 self
1443 }
1444}
1445
1446#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1450#[cfg_attr(feature = "schemars", schemars(transform = elicitation_scope_schema))]
1451#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1452#[serde(untagged)]
1453#[non_exhaustive]
1454pub enum ElicitationScope {
1455 Session(ElicitationSessionScope),
1457 Request(ElicitationRequestScope),
1460}
1461
1462impl<'de> Deserialize<'de> for ElicitationScope {
1463 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1464 where
1465 D: serde::Deserializer<'de>,
1466 {
1467 let mut fields = serde_json::Map::deserialize(deserializer)?;
1468 let scope = match (fields.remove("sessionId"), fields.remove("requestId")) {
1469 (Some(session_id), None) => {
1470 let mut scope = serde_json::Map::new();
1471 scope.insert("sessionId".into(), session_id);
1472 if let Some(tool_call_id) = fields.remove("toolCallId") {
1473 scope.insert("toolCallId".into(), tool_call_id);
1474 }
1475 serde_json::from_value(scope.into()).map(Self::Session)
1476 }
1477 (None, Some(request_id)) => {
1478 serde_json::from_value(serde_json::json!({ "requestId": request_id }))
1479 .map(Self::Request)
1480 }
1481 _ => {
1482 return Err(serde::de::Error::custom(
1483 "elicitation scope must include exactly one of `sessionId` or `requestId`",
1484 ));
1485 }
1486 };
1487 scope.map_err(serde::de::Error::custom)
1488 }
1489}
1490
1491#[cfg(feature = "schemars")]
1492fn elicitation_scope_schema(schema: &mut Schema) {
1493 if let Some(variants) = schema.remove("anyOf") {
1494 schema.insert("oneOf".into(), variants);
1495 }
1496}
1497
1498#[serde_as]
1504#[skip_serializing_none]
1505#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1506#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1507#[serde(rename_all = "camelCase")]
1508#[non_exhaustive]
1509pub struct ElicitationSessionScope {
1510 pub session_id: SessionId,
1512 #[serde_as(deserialize_as = "DefaultOnError")]
1517 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1518 #[serde(default)]
1519 pub tool_call_id: Option<ToolCallId>,
1520}
1521
1522impl ElicitationSessionScope {
1523 #[must_use]
1525 pub fn new(session_id: impl Into<SessionId>) -> Self {
1526 Self {
1527 session_id: session_id.into(),
1528 tool_call_id: None,
1529 }
1530 }
1531
1532 #[must_use]
1534 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1535 self.tool_call_id = tool_call_id.into_option();
1536 self
1537 }
1538}
1539
1540#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1543#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1544#[serde(rename_all = "camelCase")]
1545#[non_exhaustive]
1546pub struct ElicitationRequestScope {
1547 pub request_id: RequestId,
1549}
1550
1551impl ElicitationRequestScope {
1552 #[must_use]
1554 pub fn new(request_id: impl Into<RequestId>) -> Self {
1555 Self {
1556 request_id: request_id.into(),
1557 }
1558 }
1559}
1560
1561impl From<ElicitationSessionScope> for ElicitationScope {
1562 fn from(scope: ElicitationSessionScope) -> Self {
1563 Self::Session(scope)
1564 }
1565}
1566
1567impl From<ElicitationRequestScope> for ElicitationScope {
1568 fn from(scope: ElicitationRequestScope) -> Self {
1569 Self::Request(scope)
1570 }
1571}
1572
1573#[serde_as]
1591#[skip_serializing_none]
1592#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1593#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1594#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1595#[serde(rename_all = "camelCase")]
1596#[non_exhaustive]
1597pub struct CreateElicitationRequest {
1598 #[serde(flatten)]
1600 pub mode: ElicitationMode,
1601 pub message: String,
1603 #[serde_as(deserialize_as = "DefaultOnError")]
1609 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1610 #[serde(default)]
1611 #[serde(rename = "_meta")]
1612 pub meta: Option<Meta>,
1613}
1614
1615impl CreateElicitationRequest {
1616 #[must_use]
1618 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1619 Self {
1620 mode: mode.into(),
1621 message: message.into(),
1622 meta: None,
1623 }
1624 }
1625
1626 #[must_use]
1628 pub fn scope(&self) -> &ElicitationScope {
1629 self.mode.scope()
1630 }
1631
1632 #[must_use]
1640 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1641 self.meta = meta.into_option();
1642 self
1643 }
1644}
1645
1646#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1648#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1649#[serde(tag = "mode", rename_all = "snake_case")]
1650#[non_exhaustive]
1651pub enum ElicitationMode {
1652 Form(ElicitationFormMode),
1654 Url(ElicitationUrlMode),
1656 #[serde(untagged)]
1666 Other(OtherElicitationMode),
1667}
1668
1669#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1675#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1676#[cfg_attr(feature = "schemars", schemars(inline))]
1677#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1678#[serde(rename_all = "camelCase")]
1679#[non_exhaustive]
1680pub struct OtherElicitationMode {
1681 pub mode: String,
1687 #[serde(flatten)]
1689 pub scope: ElicitationScope,
1690 #[serde(flatten)]
1692 pub fields: BTreeMap<String, serde_json::Value>,
1693}
1694
1695impl OtherElicitationMode {
1696 #[must_use]
1698 pub fn new(
1699 mode: impl Into<String>,
1700 scope: impl Into<ElicitationScope>,
1701 mut fields: BTreeMap<String, serde_json::Value>,
1702 ) -> Self {
1703 fields.remove("mode");
1704 remove_elicitation_scope_fields(&mut fields);
1705 Self {
1706 mode: mode.into(),
1707 scope: scope.into(),
1708 fields,
1709 }
1710 }
1711}
1712
1713impl<'de> Deserialize<'de> for OtherElicitationMode {
1714 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1715 where
1716 D: serde::Deserializer<'de>,
1717 {
1718 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1719 let mode = fields
1720 .remove("mode")
1721 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1722 let serde_json::Value::String(mode) = mode else {
1723 return Err(serde::de::Error::custom("`mode` must be a string"));
1724 };
1725
1726 if is_known_elicitation_mode(&mode) {
1727 return Err(serde::de::Error::custom(format!(
1728 "known elicitation mode `{mode}` did not match its schema"
1729 )));
1730 }
1731
1732 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1733 fields.clone().into_iter().collect(),
1734 ))
1735 .map_err(serde::de::Error::custom)?;
1736 remove_elicitation_scope_fields(&mut fields);
1737
1738 Ok(Self {
1739 mode,
1740 scope,
1741 fields,
1742 })
1743 }
1744}
1745
1746const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1747
1748fn is_known_elicitation_mode(mode: &str) -> bool {
1749 KNOWN_ELICITATION_MODES.contains(&mode)
1750}
1751
1752fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1753 fields.remove("sessionId");
1754 fields.remove("toolCallId");
1755 fields.remove("requestId");
1756}
1757
1758#[cfg(feature = "schemars")]
1759fn other_elicitation_mode_schema(schema: &mut Schema) {
1760 super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1761}
1762
1763impl From<ElicitationFormMode> for ElicitationMode {
1764 fn from(mode: ElicitationFormMode) -> Self {
1765 Self::Form(mode)
1766 }
1767}
1768
1769impl From<ElicitationUrlMode> for ElicitationMode {
1770 fn from(mode: ElicitationUrlMode) -> Self {
1771 Self::Url(mode)
1772 }
1773}
1774
1775impl From<OtherElicitationMode> for ElicitationMode {
1776 fn from(mode: OtherElicitationMode) -> Self {
1777 Self::Other(mode)
1778 }
1779}
1780
1781impl ElicitationMode {
1782 #[must_use]
1784 pub fn scope(&self) -> &ElicitationScope {
1785 match self {
1786 Self::Form(f) => &f.scope,
1787 Self::Url(u) => &u.scope,
1788 Self::Other(other) => &other.scope,
1789 }
1790 }
1791}
1792
1793#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1795#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1796#[serde(rename_all = "camelCase")]
1797#[non_exhaustive]
1798pub struct ElicitationFormMode {
1799 #[serde(flatten)]
1801 pub scope: ElicitationScope,
1802 pub requested_schema: ElicitationSchema,
1804}
1805
1806impl ElicitationFormMode {
1807 #[must_use]
1809 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1810 Self {
1811 scope: scope.into(),
1812 requested_schema,
1813 }
1814 }
1815}
1816
1817#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1819#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1820#[serde(rename_all = "camelCase")]
1821#[non_exhaustive]
1822pub struct ElicitationUrlMode {
1823 #[serde(flatten)]
1825 pub scope: ElicitationScope,
1826 pub elicitation_id: ElicitationId,
1828 #[cfg_attr(feature = "schemars", schemars(url))]
1830 pub url: String,
1831}
1832
1833impl ElicitationUrlMode {
1834 #[must_use]
1836 pub fn new(
1837 scope: impl Into<ElicitationScope>,
1838 elicitation_id: impl Into<ElicitationId>,
1839 url: impl Into<String>,
1840 ) -> Self {
1841 Self {
1842 scope: scope.into(),
1843 elicitation_id: elicitation_id.into(),
1844 url: url.into(),
1845 }
1846 }
1847}
1848
1849#[serde_as]
1851#[skip_serializing_none]
1852#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1853#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1854#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1855#[serde(rename_all = "camelCase")]
1856#[non_exhaustive]
1857pub struct CreateElicitationResponse {
1858 #[serde(flatten)]
1860 pub action: ElicitationAction,
1861 #[serde_as(deserialize_as = "DefaultOnError")]
1867 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1868 #[serde(default)]
1869 #[serde(rename = "_meta")]
1870 pub meta: Option<Meta>,
1871}
1872
1873impl CreateElicitationResponse {
1874 #[must_use]
1876 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1877 Self {
1878 action: action.into(),
1879 meta: None,
1880 }
1881 }
1882
1883 #[must_use]
1891 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1892 self.meta = meta.into_option();
1893 self
1894 }
1895}
1896
1897#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1899#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1900#[serde(tag = "action", rename_all = "snake_case")]
1901#[non_exhaustive]
1902pub enum ElicitationAction {
1903 Accept(ElicitationAcceptAction),
1905 Decline,
1907 Cancel,
1909 #[serde(untagged)]
1919 Other(OtherElicitationAction),
1920}
1921
1922#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1928#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1929#[cfg_attr(feature = "schemars", schemars(inline))]
1930#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1931#[serde(rename_all = "camelCase")]
1932#[non_exhaustive]
1933pub struct OtherElicitationAction {
1934 pub action: String,
1940 #[serde(flatten)]
1942 pub fields: BTreeMap<String, serde_json::Value>,
1943}
1944
1945impl OtherElicitationAction {
1946 #[must_use]
1948 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1949 fields.remove("action");
1950 Self {
1951 action: action.into(),
1952 fields,
1953 }
1954 }
1955}
1956
1957impl<'de> Deserialize<'de> for OtherElicitationAction {
1958 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1959 where
1960 D: serde::Deserializer<'de>,
1961 {
1962 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1963 let action = fields
1964 .remove("action")
1965 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1966 let serde_json::Value::String(action) = action else {
1967 return Err(serde::de::Error::custom("`action` must be a string"));
1968 };
1969
1970 if is_known_elicitation_action(&action) {
1971 return Err(serde::de::Error::custom(format!(
1972 "known elicitation action `{action}` did not match its schema"
1973 )));
1974 }
1975
1976 Ok(Self { action, fields })
1977 }
1978}
1979
1980const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1981
1982fn is_known_elicitation_action(action: &str) -> bool {
1983 KNOWN_ELICITATION_ACTIONS.contains(&action)
1984}
1985
1986#[cfg(feature = "schemars")]
1987fn other_elicitation_action_schema(schema: &mut Schema) {
1988 super::schema_util::reject_known_string_discriminators(
1989 schema,
1990 "action",
1991 KNOWN_ELICITATION_ACTIONS,
1992 );
1993}
1994
1995impl From<ElicitationAcceptAction> for ElicitationAction {
1996 fn from(action: ElicitationAcceptAction) -> Self {
1997 Self::Accept(action)
1998 }
1999}
2000
2001impl From<OtherElicitationAction> for ElicitationAction {
2002 fn from(action: OtherElicitationAction) -> Self {
2003 Self::Other(action)
2004 }
2005}
2006
2007#[serde_as]
2009#[skip_serializing_none]
2010#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2011#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2012#[serde(rename_all = "camelCase")]
2013#[non_exhaustive]
2014pub struct ElicitationAcceptAction {
2015 #[serde(default)]
2019 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
2020}
2021
2022impl ElicitationAcceptAction {
2023 #[must_use]
2025 pub fn new() -> Self {
2026 Self { content: None }
2027 }
2028
2029 #[must_use]
2031 pub fn content(
2032 mut self,
2033 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2034 ) -> Self {
2035 self.content = content.into_option();
2036 self
2037 }
2038}
2039
2040#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2042#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2043#[serde(untagged)]
2044#[non_exhaustive]
2045pub enum ElicitationContentValue {
2046 String(String),
2048 Integer(i64),
2050 Number(f64),
2052 Boolean(bool),
2054 StringArray(Vec<String>),
2056}
2057
2058impl From<String> for ElicitationContentValue {
2059 fn from(value: String) -> Self {
2060 Self::String(value)
2061 }
2062}
2063
2064impl From<&str> for ElicitationContentValue {
2065 fn from(value: &str) -> Self {
2066 Self::String(value.to_string())
2067 }
2068}
2069
2070impl From<i64> for ElicitationContentValue {
2071 fn from(value: i64) -> Self {
2072 Self::Integer(value)
2073 }
2074}
2075
2076impl From<i32> for ElicitationContentValue {
2077 fn from(value: i32) -> Self {
2078 Self::Integer(i64::from(value))
2079 }
2080}
2081
2082impl From<f64> for ElicitationContentValue {
2083 fn from(value: f64) -> Self {
2084 Self::Number(value)
2085 }
2086}
2087
2088impl From<bool> for ElicitationContentValue {
2089 fn from(value: bool) -> Self {
2090 Self::Boolean(value)
2091 }
2092}
2093
2094impl From<Vec<String>> for ElicitationContentValue {
2095 fn from(value: Vec<String>) -> Self {
2096 Self::StringArray(value)
2097 }
2098}
2099
2100impl From<Vec<&str>> for ElicitationContentValue {
2101 fn from(value: Vec<&str>) -> Self {
2102 Self::StringArray(value.into_iter().map(str::to_string).collect())
2103 }
2104}
2105
2106impl Default for ElicitationAcceptAction {
2107 fn default() -> Self {
2108 Self::new()
2109 }
2110}
2111
2112#[serde_as]
2116#[skip_serializing_none]
2117#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2119#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2120#[serde(rename_all = "camelCase")]
2121#[non_exhaustive]
2122pub struct CompleteElicitationNotification {
2123 pub elicitation_id: ElicitationId,
2125 #[serde_as(deserialize_as = "DefaultOnError")]
2131 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2132 #[serde(default)]
2133 #[serde(rename = "_meta")]
2134 pub meta: Option<Meta>,
2135}
2136
2137impl CompleteElicitationNotification {
2138 #[must_use]
2140 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2141 Self {
2142 elicitation_id: elicitation_id.into(),
2143 meta: None,
2144 }
2145 }
2146
2147 #[must_use]
2155 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2156 self.meta = meta.into_option();
2157 self
2158 }
2159}
2160
2161#[cfg(test)]
2162mod tests {
2163 use super::*;
2164 use serde_json::json;
2165
2166 #[test]
2167 fn form_mode_request_serialization() {
2168 let schema = ElicitationSchema::new().string("name", true);
2169 let req = CreateElicitationRequest::new(
2170 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2171 "Please enter your name",
2172 );
2173
2174 let json = serde_json::to_value(&req).unwrap();
2175 assert_eq!(json["sessionId"], "sess_1");
2176 assert!(json.get("toolCallId").is_none());
2177 assert_eq!(json["mode"], "form");
2178 assert_eq!(json["message"], "Please enter your name");
2179 assert!(json["requestedSchema"].is_object());
2180 assert_eq!(json["requestedSchema"]["type"], "object");
2181 assert_eq!(
2182 json["requestedSchema"]["properties"]["name"]["type"],
2183 "string"
2184 );
2185
2186 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2187 assert_eq!(
2188 *roundtripped.scope(),
2189 ElicitationSessionScope::new("sess_1").into()
2190 );
2191 assert_eq!(roundtripped.message, "Please enter your name");
2192 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2193 }
2194
2195 #[test]
2196 fn url_mode_request_serialization() {
2197 let req = CreateElicitationRequest::new(
2198 ElicitationUrlMode::new(
2199 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2200 "elic_1",
2201 "https://example.com/auth",
2202 ),
2203 "Please authenticate",
2204 );
2205
2206 let json = serde_json::to_value(&req).unwrap();
2207 assert_eq!(json["sessionId"], "sess_2");
2208 assert_eq!(json["toolCallId"], "tc_1");
2209 assert_eq!(json["mode"], "url");
2210 assert_eq!(json["elicitationId"], "elic_1");
2211 assert_eq!(json["url"], "https://example.com/auth");
2212 assert_eq!(json["message"], "Please authenticate");
2213
2214 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2215 assert_eq!(
2216 *roundtripped.scope(),
2217 ElicitationSessionScope::new("sess_2")
2218 .tool_call_id("tc_1")
2219 .into()
2220 );
2221 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2222 }
2223
2224 #[test]
2225 fn response_accept_serialization() {
2226 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2227 ElicitationAcceptAction::new().content(BTreeMap::from([(
2228 "name".to_string(),
2229 ElicitationContentValue::from("Alice"),
2230 )])),
2231 ));
2232
2233 let json = serde_json::to_value(&resp).unwrap();
2234 assert_eq!(json["action"], "accept");
2235 assert_eq!(json["content"]["name"], "Alice");
2236
2237 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2238 assert!(matches!(
2239 roundtripped.action,
2240 ElicitationAction::Accept(ElicitationAcceptAction {
2241 content: Some(_),
2242 ..
2243 })
2244 ));
2245 }
2246
2247 #[test]
2248 fn response_decline_serialization() {
2249 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2250
2251 let json = serde_json::to_value(&resp).unwrap();
2252 assert_eq!(json["action"], "decline");
2253
2254 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2255 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2256 }
2257
2258 #[test]
2259 fn response_cancel_serialization() {
2260 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2261
2262 let json = serde_json::to_value(&resp).unwrap();
2263 assert_eq!(json["action"], "cancel");
2264
2265 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2266 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2267 }
2268
2269 #[test]
2270 fn unknown_action_response_serialization() {
2271 let json = json!({
2272 "action": "_defer",
2273 "reason": "waiting",
2274 "retryAfterMs": 1000
2275 });
2276
2277 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2278 let ElicitationAction::Other(other) = &resp.action else {
2279 panic!("expected unknown elicitation action");
2280 };
2281
2282 assert_eq!(other.action, "_defer");
2283 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2284 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2285 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2286 }
2287
2288 #[test]
2289 fn unknown_action_does_not_hide_known_action() {
2290 assert!(
2291 serde_json::from_value::<OtherElicitationAction>(json!({
2292 "action": "accept",
2293 "content": {}
2294 }))
2295 .is_err()
2296 );
2297 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2298 }
2299
2300 #[test]
2301 fn url_mode_request_scope_serialization() {
2302 let req = CreateElicitationRequest::new(
2303 ElicitationUrlMode::new(
2304 ElicitationRequestScope::new(RequestId::Number(42)),
2305 "elic_2",
2306 "https://example.com/setup",
2307 ),
2308 "Please complete setup",
2309 );
2310
2311 let json = serde_json::to_value(&req).unwrap();
2312 assert_eq!(json["requestId"], 42);
2313 assert!(json.get("sessionId").is_none());
2314 assert_eq!(json["mode"], "url");
2315 assert_eq!(json["elicitationId"], "elic_2");
2316 assert_eq!(json["url"], "https://example.com/setup");
2317 assert_eq!(json["message"], "Please complete setup");
2318
2319 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2320 assert_eq!(
2321 *roundtripped.scope(),
2322 ElicitationRequestScope::new(RequestId::Number(42)).into()
2323 );
2324 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2325 }
2326
2327 #[test]
2328 fn unknown_mode_request_serialization() {
2329 let json = json!({
2330 "requestId": 42,
2331 "mode": "_browser",
2332 "message": "Open a browser window",
2333 "target": "login"
2334 });
2335
2336 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2337 let ElicitationMode::Other(other) = &req.mode else {
2338 panic!("expected unknown elicitation mode");
2339 };
2340
2341 assert_eq!(other.mode, "_browser");
2342 assert_eq!(
2343 other.scope,
2344 ElicitationRequestScope::new(RequestId::Number(42)).into()
2345 );
2346 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2347 assert_eq!(
2348 *req.scope(),
2349 ElicitationRequestScope::new(RequestId::Number(42)).into()
2350 );
2351 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2352 }
2353
2354 #[test]
2355 fn unknown_mode_does_not_hide_malformed_known_mode() {
2356 let missing_requested_schema = json!({
2357 "requestId": 42,
2358 "mode": "form",
2359 "message": "Enter your name"
2360 });
2361
2362 assert!(
2363 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2364 );
2365 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2366 }
2367
2368 #[test]
2369 fn scope_requires_exactly_one_non_null_id() {
2370 let request = |scope: serde_json::Value| {
2371 let mut json = json!({
2372 "mode": "form",
2373 "message": "Enter your name",
2374 "requestedSchema": { "type": "object", "properties": {} }
2375 });
2376 json.as_object_mut()
2377 .unwrap()
2378 .extend(scope.as_object().unwrap().clone());
2379 serde_json::from_value::<CreateElicitationRequest>(json)
2380 };
2381
2382 assert!(request(json!({ "sessionId": "sess_1", "requestId": 42 })).is_err());
2383 assert!(request(json!({})).is_err());
2384 assert!(request(json!({ "sessionId": 42, "requestId": 42 })).is_err());
2385 assert!(request(json!({ "sessionId": 42 })).is_err());
2386 assert_eq!(
2387 *request(json!({ "sessionId": "sess_1", "toolCallId": "call_1" }))
2388 .unwrap()
2389 .scope(),
2390 ElicitationSessionScope::new("sess_1")
2391 .tool_call_id("call_1")
2392 .into()
2393 );
2394 assert_eq!(
2395 *request(json!({ "requestId": "req_1" })).unwrap().scope(),
2396 ElicitationRequestScope::new("req_1".to_string()).into()
2397 );
2398 }
2399
2400 #[test]
2401 fn request_scope_request_serialization() {
2402 let req = CreateElicitationRequest::new(
2403 ElicitationFormMode::new(
2404 ElicitationRequestScope::new(RequestId::Number(99)),
2405 ElicitationSchema::new().string("workspace", true),
2406 ),
2407 "Enter workspace name",
2408 );
2409
2410 let json = serde_json::to_value(&req).unwrap();
2411 assert_eq!(json["requestId"], 99);
2412 assert!(json.get("sessionId").is_none());
2413
2414 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2415 assert_eq!(
2416 *roundtripped.scope(),
2417 ElicitationRequestScope::new(RequestId::Number(99)).into()
2418 );
2419 }
2420
2421 #[test]
2425 fn client_response_serialization_accept() {
2426 use crate::v2::ClientResponse;
2427
2428 let resp =
2429 ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2430 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2431 [("name".to_string(), ElicitationContentValue::from("Alice"))],
2432 ))),
2433 )));
2434 let json = serde_json::to_value(&resp).unwrap();
2435 assert_eq!(json["action"], "accept");
2436 assert_eq!(json["content"]["name"], "Alice");
2437
2438 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2440 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2441 }
2442
2443 #[test]
2444 fn client_response_serialization_decline() {
2445 use crate::v2::ClientResponse;
2446
2447 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2448 CreateElicitationResponse::new(ElicitationAction::Decline),
2449 ));
2450 let json = serde_json::to_value(&resp).unwrap();
2451 assert_eq!(json["action"], "decline");
2452
2453 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2454 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2455 }
2456
2457 #[test]
2458 fn client_response_serialization_cancel() {
2459 use crate::v2::ClientResponse;
2460
2461 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2462 CreateElicitationResponse::new(ElicitationAction::Cancel),
2463 ));
2464 let json = serde_json::to_value(&resp).unwrap();
2465 assert_eq!(json["action"], "cancel");
2466
2467 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2468 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2469 }
2470
2471 #[test]
2474 fn request_tolerates_extra_fields() {
2475 let json = json!({
2476 "sessionId": "sess_1",
2477 "mode": "form",
2478 "message": "Enter your name",
2479 "requestedSchema": {
2480 "type": "object",
2481 "properties": {
2482 "name": { "type": "string", "title": "Name" }
2483 },
2484 "required": ["name"]
2485 },
2486 "unknownStringField": "hello",
2487 "unknownNumberField": 42
2488 });
2489
2490 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2491 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2492 assert_eq!(req.message, "Enter your name");
2493 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2494 }
2495
2496 #[test]
2497 fn completion_notification_serialization() {
2498 let notif = CompleteElicitationNotification::new("elic_1");
2499
2500 let json = serde_json::to_value(¬if).unwrap();
2501 assert_eq!(json["elicitationId"], "elic_1");
2502
2503 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2504 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2505 }
2506
2507 #[test]
2508 fn empty_capabilities_do_not_advertise_a_mode() {
2509 let caps = ElicitationCapabilities::new();
2510 assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2511 assert!(!caps.supports_form());
2512 assert!(!caps.supports_url());
2513
2514 for value in [
2515 json!({}),
2516 json!({ "form": null }),
2517 json!({ "url": null }),
2518 json!({ "form": null, "url": null }),
2519 ] {
2520 let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2521 assert!(!caps.supports_form());
2522 assert!(!caps.supports_url());
2523 }
2524 }
2525
2526 #[test]
2527 fn capabilities_form_only() {
2528 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2529
2530 let json = serde_json::to_value(&caps).unwrap();
2531 assert!(json["form"].is_object());
2532 assert!(json.get("url").is_none());
2533
2534 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2535 assert!(roundtripped.form.is_some());
2536 assert!(roundtripped.url.is_none());
2537 assert!(roundtripped.supports_form());
2538 assert!(!roundtripped.supports_url());
2539 }
2540
2541 #[test]
2542 fn capabilities_url_only() {
2543 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2544
2545 let json = serde_json::to_value(&caps).unwrap();
2546 assert!(json.get("form").is_none());
2547 assert!(json["url"].is_object());
2548
2549 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2550 assert!(roundtripped.form.is_none());
2551 assert!(roundtripped.url.is_some());
2552 assert!(!roundtripped.supports_form());
2553 assert!(roundtripped.supports_url());
2554 }
2555
2556 #[test]
2557 fn capabilities_both() {
2558 let caps = ElicitationCapabilities::new()
2559 .form(ElicitationFormCapabilities::new())
2560 .url(ElicitationUrlCapabilities::new());
2561
2562 let json = serde_json::to_value(&caps).unwrap();
2563 assert!(json["form"].is_object());
2564 assert!(json["url"].is_object());
2565
2566 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2567 assert!(roundtripped.form.is_some());
2568 assert!(roundtripped.url.is_some());
2569 assert!(roundtripped.supports_form());
2570 assert!(roundtripped.supports_url());
2571 }
2572
2573 #[test]
2574 fn schema_default_sets_object_type() {
2575 let schema = ElicitationSchema::default();
2576
2577 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2578 assert!(schema.properties.is_empty());
2579
2580 let json = serde_json::to_value(&schema).unwrap();
2581 assert_eq!(json["type"], "object");
2582 }
2583
2584 #[test]
2585 fn schema_root_type_defaults_when_omitted_and_rejects_other_values() {
2586 let omitted: ElicitationSchema =
2587 serde_json::from_value(json!({ "properties": {} })).unwrap();
2588 assert_eq!(omitted.type_, ElicitationSchemaType::Object);
2589 for bad in [json!("array"), json!(42), json!(null)] {
2590 assert!(
2591 serde_json::from_value::<ElicitationSchema>(
2592 json!({ "type": bad, "properties": {} })
2593 )
2594 .is_err(),
2595 "{bad}"
2596 );
2597 }
2598 }
2599
2600 #[test]
2601 fn schema_builder_serialization() {
2602 let schema = ElicitationSchema::new()
2603 .string("name", true)
2604 .email("email", true)
2605 .integer("age", 0, 150, true)
2606 .boolean("newsletter", false)
2607 .description("User registration");
2608
2609 let json = serde_json::to_value(&schema).unwrap();
2610 assert_eq!(json["type"], "object");
2611 assert_eq!(json["description"], "User registration");
2612 assert_eq!(json["properties"]["name"]["type"], "string");
2613 assert_eq!(json["properties"]["email"]["type"], "string");
2614 assert_eq!(json["properties"]["email"]["format"], "email");
2615 assert_eq!(json["properties"]["age"]["type"], "integer");
2616 assert_eq!(json["properties"]["age"]["minimum"], 0);
2617 assert_eq!(json["properties"]["age"]["maximum"], 150);
2618 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2619
2620 let required = json["required"].as_array().unwrap();
2621 assert!(required.contains(&json!("name")));
2622 assert!(required.contains(&json!("email")));
2623 assert!(required.contains(&json!("age")));
2624 assert!(!required.contains(&json!("newsletter")));
2625
2626 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2627 assert_eq!(roundtripped.properties.len(), 4);
2628 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2629 }
2630
2631 #[test]
2632 fn schema_string_enum_serialization() {
2633 let schema = ElicitationSchema::new().property(
2634 "color",
2635 StringPropertySchema::new().enum_values(vec![
2636 "red".into(),
2637 "green".into(),
2638 "blue".into(),
2639 ]),
2640 true,
2641 );
2642
2643 let json = serde_json::to_value(&schema).unwrap();
2644 assert_eq!(json["properties"]["color"]["type"], "string");
2645 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2646 assert_eq!(enum_vals.len(), 3);
2647
2648 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2649 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2650 {
2651 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2652 } else {
2653 panic!("expected String variant");
2654 }
2655 }
2656
2657 #[test]
2658 fn schema_multi_select_serialization() {
2659 let schema = ElicitationSchema::new().property(
2660 "colors",
2661 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2662 .min_items(1)
2663 .max_items(3),
2664 false,
2665 );
2666
2667 let json = serde_json::to_value(&schema).unwrap();
2668 assert_eq!(json["properties"]["colors"]["type"], "array");
2669 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2670 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2671 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2672
2673 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2674 let ElicitationPropertySchema::Array(array) =
2675 roundtripped.properties.get("colors").unwrap()
2676 else {
2677 panic!("expected Array variant");
2678 };
2679 let MultiSelectItems::String(items) = &array.items else {
2680 panic!("expected String multi-select items");
2681 };
2682 assert_eq!(items.values.len(), 3);
2683 }
2684
2685 #[test]
2686 fn multi_select_titled_items_keep_mcp_shape() {
2687 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2688 "#ff0000", "Red",
2689 )]));
2690
2691 let json = serde_json::to_value(&items).unwrap();
2692 assert!(json.get("type").is_none());
2693 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2694 assert_eq!(json["anyOf"][0]["title"], "Red");
2695
2696 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2697 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2698 }
2699
2700 #[test]
2701 fn multi_select_items_preserve_unknown_type() {
2702 let json = json!({
2703 "type": "_token",
2704 "format": "workspace",
2705 "anyOf": [
2706 { "const": "repo", "title": "Repository" }
2707 ]
2708 });
2709
2710 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2711 let MultiSelectItems::Other(other) = &items else {
2712 panic!("expected unknown multi-select items");
2713 };
2714
2715 assert_eq!(other.type_, "_token");
2716 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2717 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2718 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2719 }
2720
2721 #[test]
2722 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2723 assert!(
2724 serde_json::from_value::<MultiSelectItems>(json!({
2725 "type": "string"
2726 }))
2727 .is_err()
2728 );
2729 assert!(
2730 serde_json::from_value::<OtherMultiSelectItems>(json!({
2731 "type": "string",
2732 "format": "workspace"
2733 }))
2734 .is_err()
2735 );
2736 }
2737
2738 #[test]
2739 fn property_schema_preserves_unknown_type() {
2740 let schema: ElicitationSchema = serde_json::from_value(json!({
2741 "type": "object",
2742 "properties": {
2743 "location": {
2744 "type": "_location",
2745 "title": "Location",
2746 "precision": "city"
2747 }
2748 }
2749 }))
2750 .unwrap();
2751
2752 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2753 else {
2754 panic!("expected unknown property schema");
2755 };
2756
2757 assert_eq!(unknown.type_, "_location");
2758 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2759 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2760 assert_eq!(
2761 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2762 json!({
2763 "type": "_location",
2764 "title": "Location",
2765 "precision": "city"
2766 })
2767 );
2768 }
2769
2770 #[test]
2771 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2772 assert!(
2773 serde_json::from_value::<ElicitationPropertySchema>(json!({
2774 "type": "array"
2775 }))
2776 .is_err()
2777 );
2778 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2779 }
2780
2781 #[test]
2782 fn schema_titled_enum_serialization() {
2783 let schema = ElicitationSchema::new().property(
2784 "country",
2785 StringPropertySchema::new().one_of(vec![
2786 EnumOption::new("us", "United States").description("Use US English spelling."),
2787 EnumOption::new("uk", "United Kingdom"),
2788 ]),
2789 true,
2790 );
2791
2792 let json = serde_json::to_value(&schema).unwrap();
2793 assert_eq!(json["properties"]["country"]["type"], "string");
2794 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2795 assert_eq!(one_of.len(), 2);
2796 assert_eq!(one_of[0]["const"], "us");
2797 assert_eq!(one_of[0]["title"], "United States");
2798 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2799 assert!(one_of[1].get("description").is_none());
2800
2801 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2802 if let ElicitationPropertySchema::String(s) =
2803 roundtripped.properties.get("country").unwrap()
2804 {
2805 let one_of = s.one_of.as_ref().unwrap();
2806 assert_eq!(one_of.len(), 2);
2807 assert_eq!(
2808 one_of[0].description.as_deref(),
2809 Some("Use US English spelling.")
2810 );
2811 assert!(one_of[1].description.is_none());
2812 } else {
2813 panic!("expected String variant");
2814 }
2815 }
2816
2817 #[test]
2818 fn schema_number_property_serialization() {
2819 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2820
2821 let json = serde_json::to_value(&schema).unwrap();
2822 assert_eq!(json["properties"]["rating"]["type"], "number");
2823 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2824 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2825
2826 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2827 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2828 {
2829 assert_eq!(n.minimum, Some(0.0));
2830 assert_eq!(n.maximum, Some(5.0));
2831 } else {
2832 panic!("expected Number variant");
2833 }
2834 }
2835
2836 #[test]
2837 fn schema_string_format_serialization() {
2838 let schema = ElicitationSchema::new()
2839 .uri("website", true)
2840 .date("birthday", true)
2841 .date_time("updated_at", false);
2842
2843 let json = serde_json::to_value(&schema).unwrap();
2844 assert_eq!(json["properties"]["website"]["type"], "string");
2845 assert_eq!(json["properties"]["website"]["format"], "uri");
2846 assert_eq!(json["properties"]["birthday"]["type"], "string");
2847 assert_eq!(json["properties"]["birthday"]["format"], "date");
2848 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2849 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2850
2851 let required = json["required"].as_array().unwrap();
2852 assert!(required.contains(&json!("website")));
2853 assert!(required.contains(&json!("birthday")));
2854 assert!(!required.contains(&json!("updated_at")));
2855 }
2856
2857 #[test]
2858 fn schema_string_pattern_serialization() {
2859 let schema = ElicitationSchema::new().property(
2860 "name",
2861 StringPropertySchema::new()
2862 .min_length(1)
2863 .max_length(64)
2864 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2865 true,
2866 );
2867
2868 let json = serde_json::to_value(&schema).unwrap();
2869 assert_eq!(json["properties"]["name"]["type"], "string");
2870 assert_eq!(
2871 json["properties"]["name"]["pattern"],
2872 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2873 );
2874
2875 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2876 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2877 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2878 } else {
2879 panic!("expected String variant");
2880 }
2881 }
2882
2883 #[test]
2884 fn schema_property_updates_required_state() {
2885 let schema = ElicitationSchema::new()
2886 .string("name", true)
2887 .email("name", false);
2888
2889 let json = serde_json::to_value(&schema).unwrap();
2890 assert!(json.get("required").is_none());
2891 assert_eq!(json["properties"]["name"]["format"], "email");
2892 }
2893
2894 #[test]
2895 fn titled_multi_select_items_reject_one_of() {
2896 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2897 "oneOf": [
2898 {
2899 "const": "red",
2900 "title": "Red"
2901 }
2902 ]
2903 }))
2904 .unwrap_err();
2905
2906 assert!(err.to_string().contains("missing field `anyOf`"));
2907 }
2908
2909 #[test]
2910 fn response_accept_rejects_non_object_content() {
2911 assert!(
2912 serde_json::from_value::<CreateElicitationResponse>(json!({
2913 "action": "accept",
2914 "content": "Alice"
2915 }))
2916 .is_err()
2917 );
2918 }
2919
2920 #[test]
2921 fn response_accept_treats_null_and_omitted_content_equally() {
2922 for value in [
2923 json!({ "action": "accept" }),
2924 json!({
2925 "action": "accept",
2926 "content": null
2927 }),
2928 ] {
2929 let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2930 let ElicitationAction::Accept(accept) = response.action else {
2931 panic!("expected accept action");
2932 };
2933 assert!(accept.content.is_none());
2934 }
2935 }
2936
2937 #[test]
2938 fn response_accept_rejects_nested_object_content() {
2939 assert!(
2940 serde_json::from_value::<CreateElicitationResponse>(json!({
2941 "action": "accept",
2942 "content": {
2943 "profile": {
2944 "name": "Alice"
2945 }
2946 }
2947 }))
2948 .is_err()
2949 );
2950 }
2951
2952 #[test]
2953 fn response_accept_allows_primitive_and_string_array_content() {
2954 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2955 ElicitationAcceptAction::new().content(BTreeMap::from([
2956 ("name".to_string(), ElicitationContentValue::from("Alice")),
2957 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2958 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2959 (
2960 "subscribed".to_string(),
2961 ElicitationContentValue::from(true),
2962 ),
2963 (
2964 "tags".to_string(),
2965 ElicitationContentValue::from(vec!["rust", "acp"]),
2966 ),
2967 ])),
2968 ));
2969
2970 let json = serde_json::to_value(&response).unwrap();
2971 assert_eq!(json["action"], "accept");
2972 assert_eq!(json["content"]["name"], "Alice");
2973 assert_eq!(json["content"]["age"], 30);
2974 assert_eq!(json["content"]["score"], 9.5);
2975 assert_eq!(json["content"]["subscribed"], true);
2976 assert_eq!(json["content"]["tags"][0], "rust");
2977 assert_eq!(json["content"]["tags"][1], "acp");
2978 }
2979}