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
14use crate::IntoOption;
15use crate::SkipListener;
16
17#[cfg(feature = "schemars")]
18use super::{ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME};
19use super::{Meta, RequestId, SessionId, ToolCallId};
20
21#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
24#[serde(transparent)]
25#[from(Arc<str>, String, &'static str)]
26#[non_exhaustive]
27pub struct ElicitationId(pub Arc<str>);
28
29impl ElicitationId {
30 #[must_use]
32 pub fn new(id: impl Into<Arc<str>>) -> Self {
33 Self(id.into())
34 }
35}
36
37#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "kebab-case")]
41#[non_exhaustive]
42pub enum StringFormat {
43 Email,
45 Uri,
47 Date,
49 DateTime,
51}
52
53#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
56#[serde(rename_all = "snake_case")]
57#[non_exhaustive]
58pub enum ElicitationSchemaType {
59 #[default]
61 Object,
62}
63
64#[serde_as]
66#[skip_serializing_none]
67#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[non_exhaustive]
70pub struct EnumOption {
71 #[serde(rename = "const")]
73 pub value: String,
74 pub title: String,
76 #[serde_as(deserialize_as = "DefaultOnError")]
80 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
81 #[serde(default)]
82 pub description: Option<String>,
83 #[serde_as(deserialize_as = "DefaultOnError")]
91 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
92 #[serde(default)]
93 #[serde(rename = "_meta")]
94 pub meta: Option<Meta>,
95}
96
97impl EnumOption {
98 #[must_use]
100 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
101 Self {
102 value: value.into(),
103 title: title.into(),
104 description: None,
105 meta: None,
106 }
107 }
108
109 #[must_use]
111 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
112 self.description = description.into_option();
113 self
114 }
115
116 #[must_use]
124 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
125 self.meta = meta.into_option();
126 self
127 }
128}
129
130#[serde_as]
135#[skip_serializing_none]
136#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
137#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139#[non_exhaustive]
140pub struct StringPropertySchema {
141 #[serde_as(deserialize_as = "DefaultOnError")]
145 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
146 #[serde(default)]
147 pub title: Option<String>,
148 #[serde_as(deserialize_as = "DefaultOnError")]
152 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
153 #[serde(default)]
154 pub description: Option<String>,
155 #[serde(default)]
159 pub min_length: Option<u32>,
160 #[serde(default)]
164 pub max_length: Option<u32>,
165 #[serde(default)]
169 pub pattern: Option<String>,
170 #[serde(default)]
174 pub format: Option<StringFormat>,
175 #[serde_as(deserialize_as = "DefaultOnError")]
179 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
180 #[serde(default)]
181 pub default: Option<String>,
182 #[serde(default)]
186 #[serde(rename = "enum")]
187 pub enum_values: Option<Vec<String>>,
188 #[serde(default)]
192 #[serde(rename = "oneOf")]
193 pub one_of: Option<Vec<EnumOption>>,
194 #[serde_as(deserialize_as = "DefaultOnError")]
202 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
203 #[serde(default)]
204 #[serde(rename = "_meta")]
205 pub meta: Option<Meta>,
206}
207
208impl StringPropertySchema {
209 #[must_use]
211 pub fn new() -> Self {
212 Self::default()
213 }
214
215 #[must_use]
217 pub fn email() -> Self {
218 Self {
219 format: Some(StringFormat::Email),
220 ..Default::default()
221 }
222 }
223
224 #[must_use]
226 pub fn uri() -> Self {
227 Self {
228 format: Some(StringFormat::Uri),
229 ..Default::default()
230 }
231 }
232
233 #[must_use]
235 pub fn date() -> Self {
236 Self {
237 format: Some(StringFormat::Date),
238 ..Default::default()
239 }
240 }
241
242 #[must_use]
244 pub fn date_time() -> Self {
245 Self {
246 format: Some(StringFormat::DateTime),
247 ..Default::default()
248 }
249 }
250
251 #[must_use]
253 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
254 self.title = title.into_option();
255 self
256 }
257
258 #[must_use]
260 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
261 self.description = description.into_option();
262 self
263 }
264
265 #[must_use]
267 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
268 self.min_length = min_length.into_option();
269 self
270 }
271
272 #[must_use]
274 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
275 self.max_length = max_length.into_option();
276 self
277 }
278
279 #[must_use]
281 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
282 self.pattern = pattern.into_option();
283 self
284 }
285
286 #[must_use]
288 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
289 self.format = format.into_option();
290 self
291 }
292
293 #[must_use]
295 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
296 self.default = default.into_option();
297 self
298 }
299
300 #[must_use]
302 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
303 self.enum_values = enum_values.into_option();
304 self
305 }
306
307 #[must_use]
309 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
310 self.one_of = one_of.into_option();
311 self
312 }
313
314 #[must_use]
322 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
323 self.meta = meta.into_option();
324 self
325 }
326}
327
328#[serde_as]
330#[skip_serializing_none]
331#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
332#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
333#[serde(rename_all = "camelCase")]
334#[non_exhaustive]
335pub struct NumberPropertySchema {
336 #[serde_as(deserialize_as = "DefaultOnError")]
340 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
341 #[serde(default)]
342 pub title: Option<String>,
343 #[serde_as(deserialize_as = "DefaultOnError")]
347 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
348 #[serde(default)]
349 pub description: Option<String>,
350 #[serde(default)]
354 pub minimum: Option<f64>,
355 #[serde(default)]
359 pub maximum: Option<f64>,
360 #[serde_as(deserialize_as = "DefaultOnError")]
364 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
365 #[serde(default)]
366 pub default: Option<f64>,
367 #[serde_as(deserialize_as = "DefaultOnError")]
375 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
376 #[serde(default)]
377 #[serde(rename = "_meta")]
378 pub meta: Option<Meta>,
379}
380
381impl NumberPropertySchema {
382 #[must_use]
384 pub fn new() -> Self {
385 Self::default()
386 }
387
388 #[must_use]
390 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
391 self.title = title.into_option();
392 self
393 }
394
395 #[must_use]
397 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
398 self.description = description.into_option();
399 self
400 }
401
402 #[must_use]
404 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
405 self.minimum = minimum.into_option();
406 self
407 }
408
409 #[must_use]
411 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
412 self.maximum = maximum.into_option();
413 self
414 }
415
416 #[must_use]
418 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
419 self.default = default.into_option();
420 self
421 }
422
423 #[must_use]
431 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
432 self.meta = meta.into_option();
433 self
434 }
435}
436
437#[serde_as]
439#[skip_serializing_none]
440#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
441#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
442#[serde(rename_all = "camelCase")]
443#[non_exhaustive]
444pub struct IntegerPropertySchema {
445 #[serde_as(deserialize_as = "DefaultOnError")]
449 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
450 #[serde(default)]
451 pub title: Option<String>,
452 #[serde_as(deserialize_as = "DefaultOnError")]
456 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
457 #[serde(default)]
458 pub description: Option<String>,
459 #[serde(default)]
463 pub minimum: Option<i64>,
464 #[serde(default)]
468 pub maximum: Option<i64>,
469 #[serde_as(deserialize_as = "DefaultOnError")]
473 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
474 #[serde(default)]
475 pub default: Option<i64>,
476 #[serde_as(deserialize_as = "DefaultOnError")]
484 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
485 #[serde(default)]
486 #[serde(rename = "_meta")]
487 pub meta: Option<Meta>,
488}
489
490impl IntegerPropertySchema {
491 #[must_use]
493 pub fn new() -> Self {
494 Self::default()
495 }
496
497 #[must_use]
499 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
500 self.title = title.into_option();
501 self
502 }
503
504 #[must_use]
506 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
507 self.description = description.into_option();
508 self
509 }
510
511 #[must_use]
513 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
514 self.minimum = minimum.into_option();
515 self
516 }
517
518 #[must_use]
520 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
521 self.maximum = maximum.into_option();
522 self
523 }
524
525 #[must_use]
527 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
528 self.default = default.into_option();
529 self
530 }
531
532 #[must_use]
540 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
541 self.meta = meta.into_option();
542 self
543 }
544}
545
546#[serde_as]
548#[skip_serializing_none]
549#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
550#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
551#[serde(rename_all = "camelCase")]
552#[non_exhaustive]
553pub struct BooleanPropertySchema {
554 #[serde_as(deserialize_as = "DefaultOnError")]
558 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
559 #[serde(default)]
560 pub title: Option<String>,
561 #[serde_as(deserialize_as = "DefaultOnError")]
565 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
566 #[serde(default)]
567 pub description: Option<String>,
568 #[serde_as(deserialize_as = "DefaultOnError")]
572 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
573 #[serde(default)]
574 pub default: Option<bool>,
575 #[serde_as(deserialize_as = "DefaultOnError")]
583 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
584 #[serde(default)]
585 #[serde(rename = "_meta")]
586 pub meta: Option<Meta>,
587}
588
589impl BooleanPropertySchema {
590 #[must_use]
592 pub fn new() -> Self {
593 Self::default()
594 }
595
596 #[must_use]
598 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
599 self.title = title.into_option();
600 self
601 }
602
603 #[must_use]
605 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
606 self.description = description.into_option();
607 self
608 }
609
610 #[must_use]
612 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
613 self.default = default.into_option();
614 self
615 }
616
617 #[must_use]
625 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
626 self.meta = meta.into_option();
627 self
628 }
629}
630
631#[serde_as]
633#[skip_serializing_none]
634#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
635#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
636#[non_exhaustive]
637pub struct StringMultiSelectItems {
638 #[serde(rename = "enum")]
640 pub values: Vec<String>,
641 #[serde_as(deserialize_as = "DefaultOnError")]
649 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
650 #[serde(default)]
651 #[serde(rename = "_meta")]
652 pub meta: Option<Meta>,
653}
654
655impl StringMultiSelectItems {
656 #[must_use]
658 pub fn new(values: Vec<String>) -> Self {
659 Self { values, meta: None }
660 }
661
662 #[must_use]
670 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
671 self.meta = meta.into_option();
672 self
673 }
674}
675
676#[serde_as]
678#[skip_serializing_none]
679#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
680#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
681#[non_exhaustive]
682pub struct TitledMultiSelectItems {
683 #[serde(rename = "anyOf")]
685 pub options: Vec<EnumOption>,
686 #[serde_as(deserialize_as = "DefaultOnError")]
694 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
695 #[serde(default)]
696 #[serde(rename = "_meta")]
697 pub meta: Option<Meta>,
698}
699
700impl TitledMultiSelectItems {
701 #[must_use]
703 pub fn new(options: Vec<EnumOption>) -> Self {
704 Self {
705 options,
706 meta: None,
707 }
708 }
709
710 #[must_use]
718 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
719 self.meta = meta.into_option();
720 self
721 }
722}
723
724#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
730#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
731#[cfg_attr(feature = "schemars", schemars(inline))]
732#[cfg_attr(feature = "schemars", schemars(transform = other_multi_select_items_schema))]
733#[serde(rename_all = "camelCase")]
734#[non_exhaustive]
735pub struct OtherMultiSelectItems {
736 #[serde(rename = "type")]
742 pub type_: String,
743 #[serde(flatten)]
745 pub fields: BTreeMap<String, serde_json::Value>,
746}
747
748impl OtherMultiSelectItems {
749 #[must_use]
751 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
752 fields.remove("type");
753 Self {
754 type_: type_.into(),
755 fields,
756 }
757 }
758}
759
760impl<'de> Deserialize<'de> for OtherMultiSelectItems {
761 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
762 where
763 D: serde::Deserializer<'de>,
764 {
765 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
766 let type_ = fields
767 .remove("type")
768 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
769 let serde_json::Value::String(type_) = type_ else {
770 return Err(serde::de::Error::custom("`type` must be a string"));
771 };
772
773 if is_known_multi_select_item_type(&type_) {
774 return Err(serde::de::Error::custom(format!(
775 "known multi-select item type `{type_}` did not match its schema"
776 )));
777 }
778
779 Ok(Self { type_, fields })
780 }
781}
782
783const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
784
785fn is_known_multi_select_item_type(type_: &str) -> bool {
786 KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
787}
788
789#[cfg(feature = "schemars")]
790fn other_multi_select_items_schema(schema: &mut Schema) {
791 schema.insert(
792 "not".into(),
793 serde_json::json!({
794 "anyOf": [
795 {
796 "properties": {
797 "type": {
798 "const": "string",
799 "type": "string"
800 }
801 },
802 "required": ["type"],
803 "type": "object"
804 }
805 ]
806 }),
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::new(options)),
905 default: None,
906 meta: None,
907 }
908 }
909
910 #[must_use]
912 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
913 self.title = title.into_option();
914 self
915 }
916
917 #[must_use]
919 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
920 self.description = description.into_option();
921 self
922 }
923
924 #[must_use]
926 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
927 self.min_items = min_items.into_option();
928 self
929 }
930
931 #[must_use]
933 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
934 self.max_items = max_items.into_option();
935 self
936 }
937
938 #[must_use]
940 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
941 self.default = default.into_option();
942 self
943 }
944
945 #[must_use]
953 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
954 self.meta = meta.into_option();
955 self
956 }
957}
958
959#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
965#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
966#[serde(tag = "type", rename_all = "snake_case")]
967#[non_exhaustive]
968pub enum ElicitationPropertySchema {
969 String(StringPropertySchema),
971 Number(NumberPropertySchema),
973 Integer(IntegerPropertySchema),
975 Boolean(BooleanPropertySchema),
977 Array(MultiSelectPropertySchema),
979 #[serde(untagged)]
989 Other(OtherElicitationPropertySchema),
990}
991
992#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
998#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
999#[cfg_attr(feature = "schemars", schemars(inline))]
1000#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_property_schema_schema))]
1001#[serde(rename_all = "camelCase")]
1002#[non_exhaustive]
1003pub struct OtherElicitationPropertySchema {
1004 #[serde(rename = "type")]
1010 pub type_: String,
1011 #[serde(flatten)]
1013 pub fields: BTreeMap<String, serde_json::Value>,
1014}
1015
1016impl OtherElicitationPropertySchema {
1017 #[must_use]
1019 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1020 fields.remove("type");
1021 Self {
1022 type_: type_.into(),
1023 fields,
1024 }
1025 }
1026}
1027
1028impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
1029 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1030 where
1031 D: serde::Deserializer<'de>,
1032 {
1033 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1034 let type_ = fields
1035 .remove("type")
1036 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1037 let serde_json::Value::String(type_) = type_ else {
1038 return Err(serde::de::Error::custom("`type` must be a string"));
1039 };
1040
1041 if is_known_elicitation_property_schema_type(&type_) {
1042 return Err(serde::de::Error::custom(format!(
1043 "known elicitation property schema type `{type_}` did not match its schema"
1044 )));
1045 }
1046
1047 Ok(Self { type_, fields })
1048 }
1049}
1050
1051const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
1052 &["string", "number", "integer", "boolean", "array"];
1053
1054fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
1055 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
1056}
1057
1058#[cfg(feature = "schemars")]
1059fn other_elicitation_property_schema_schema(schema: &mut Schema) {
1060 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES
1061 .iter()
1062 .map(|value| {
1063 serde_json::json!({
1064 "properties": {
1065 "type": {
1066 "const": value,
1067 "type": "string"
1068 }
1069 },
1070 "required": ["type"],
1071 "type": "object"
1072 })
1073 })
1074 .collect();
1075
1076 schema.insert(
1077 "not".into(),
1078 serde_json::json!({
1079 "anyOf": known_value_schemas
1080 }),
1081 );
1082}
1083
1084impl From<StringPropertySchema> for ElicitationPropertySchema {
1085 fn from(schema: StringPropertySchema) -> Self {
1086 Self::String(schema)
1087 }
1088}
1089
1090impl From<NumberPropertySchema> for ElicitationPropertySchema {
1091 fn from(schema: NumberPropertySchema) -> Self {
1092 Self::Number(schema)
1093 }
1094}
1095
1096impl From<IntegerPropertySchema> for ElicitationPropertySchema {
1097 fn from(schema: IntegerPropertySchema) -> Self {
1098 Self::Integer(schema)
1099 }
1100}
1101
1102impl From<BooleanPropertySchema> for ElicitationPropertySchema {
1103 fn from(schema: BooleanPropertySchema) -> Self {
1104 Self::Boolean(schema)
1105 }
1106}
1107
1108impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
1109 fn from(schema: MultiSelectPropertySchema) -> Self {
1110 Self::Array(schema)
1111 }
1112}
1113
1114fn default_object_type() -> ElicitationSchemaType {
1115 ElicitationSchemaType::Object
1116}
1117
1118#[serde_as]
1123#[skip_serializing_none]
1124#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1126#[serde(rename_all = "camelCase")]
1127#[non_exhaustive]
1128pub struct ElicitationSchema {
1129 #[serde_as(deserialize_as = "DefaultOnError")]
1131 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1132 #[serde(rename = "type", default = "default_object_type")]
1133 pub type_: ElicitationSchemaType,
1134 #[serde_as(deserialize_as = "DefaultOnError")]
1138 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1139 #[serde(default)]
1140 pub title: Option<String>,
1141 #[serde(default)]
1143 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1144 #[serde(default)]
1148 pub required: Option<Vec<String>>,
1149 #[serde_as(deserialize_as = "DefaultOnError")]
1153 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1154 #[serde(default)]
1155 pub description: Option<String>,
1156 #[serde_as(deserialize_as = "DefaultOnError")]
1164 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1165 #[serde(default)]
1166 #[serde(rename = "_meta")]
1167 pub meta: Option<Meta>,
1168}
1169
1170impl Default for ElicitationSchema {
1171 fn default() -> Self {
1172 Self {
1173 type_: default_object_type(),
1174 title: None,
1175 properties: BTreeMap::new(),
1176 required: None,
1177 description: None,
1178 meta: None,
1179 }
1180 }
1181}
1182
1183impl ElicitationSchema {
1184 #[must_use]
1186 pub fn new() -> Self {
1187 Self::default()
1188 }
1189
1190 #[must_use]
1192 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1193 self.title = title.into_option();
1194 self
1195 }
1196
1197 #[must_use]
1199 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1200 self.description = description.into_option();
1201 self
1202 }
1203
1204 #[must_use]
1212 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1213 self.meta = meta.into_option();
1214 self
1215 }
1216
1217 #[must_use]
1219 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1220 where
1221 S: Into<ElicitationPropertySchema>,
1222 {
1223 let name = name.into();
1224 self.properties.insert(name.clone(), schema.into());
1225
1226 if required {
1227 let required_fields = self.required.get_or_insert_with(Vec::new);
1228 if !required_fields.contains(&name) {
1229 required_fields.push(name);
1230 }
1231 } else if let Some(required_fields) = &mut self.required {
1232 required_fields.retain(|field| field != &name);
1233
1234 if required_fields.is_empty() {
1235 self.required = None;
1236 }
1237 }
1238
1239 self
1240 }
1241
1242 #[must_use]
1244 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1245 self.property(name, StringPropertySchema::new(), required)
1246 }
1247
1248 #[must_use]
1250 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1251 self.property(name, StringPropertySchema::email(), required)
1252 }
1253
1254 #[must_use]
1256 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1257 self.property(name, StringPropertySchema::uri(), required)
1258 }
1259
1260 #[must_use]
1262 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1263 self.property(name, StringPropertySchema::date(), required)
1264 }
1265
1266 #[must_use]
1268 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1269 self.property(name, StringPropertySchema::date_time(), required)
1270 }
1271
1272 #[must_use]
1274 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1275 self.property(
1276 name,
1277 NumberPropertySchema::new().minimum(min).maximum(max),
1278 required,
1279 )
1280 }
1281
1282 #[must_use]
1284 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1285 self.property(
1286 name,
1287 IntegerPropertySchema::new().minimum(min).maximum(max),
1288 required,
1289 )
1290 }
1291
1292 #[must_use]
1294 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1295 self.property(name, BooleanPropertySchema::new(), required)
1296 }
1297}
1298
1299#[serde_as]
1301#[skip_serializing_none]
1302#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1303#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1304#[serde(rename_all = "camelCase")]
1305#[non_exhaustive]
1306pub struct ElicitationCapabilities {
1307 #[serde_as(deserialize_as = "DefaultOnError")]
1312 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1313 #[serde(default)]
1314 pub form: Option<ElicitationFormCapabilities>,
1315 #[serde_as(deserialize_as = "DefaultOnError")]
1320 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1321 #[serde(default)]
1322 pub url: Option<ElicitationUrlCapabilities>,
1323 #[serde_as(deserialize_as = "DefaultOnError")]
1331 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1332 #[serde(default)]
1333 #[serde(rename = "_meta")]
1334 pub meta: Option<Meta>,
1335}
1336
1337impl ElicitationCapabilities {
1338 #[must_use]
1343 pub fn new() -> Self {
1344 Self::default()
1345 }
1346
1347 #[must_use]
1350 pub fn supports_form(&self) -> bool {
1351 self.form.is_some()
1352 }
1353
1354 #[must_use]
1356 pub fn supports_url(&self) -> bool {
1357 self.url.is_some()
1358 }
1359
1360 #[must_use]
1365 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1366 self.form = form.into_option();
1367 self
1368 }
1369
1370 #[must_use]
1375 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1376 self.url = url.into_option();
1377 self
1378 }
1379
1380 #[must_use]
1388 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1389 self.meta = meta.into_option();
1390 self
1391 }
1392}
1393
1394#[serde_as]
1398#[skip_serializing_none]
1399#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1400#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1401#[serde(rename_all = "camelCase")]
1402#[non_exhaustive]
1403pub struct ElicitationFormCapabilities {
1404 #[serde_as(deserialize_as = "DefaultOnError")]
1412 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1413 #[serde(default)]
1414 #[serde(rename = "_meta")]
1415 pub meta: Option<Meta>,
1416}
1417
1418impl ElicitationFormCapabilities {
1419 #[must_use]
1421 pub fn new() -> Self {
1422 Self::default()
1423 }
1424
1425 #[must_use]
1433 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1434 self.meta = meta.into_option();
1435 self
1436 }
1437}
1438
1439#[serde_as]
1443#[skip_serializing_none]
1444#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1445#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1446#[serde(rename_all = "camelCase")]
1447#[non_exhaustive]
1448pub struct ElicitationUrlCapabilities {
1449 #[serde_as(deserialize_as = "DefaultOnError")]
1457 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1458 #[serde(default)]
1459 #[serde(rename = "_meta")]
1460 pub meta: Option<Meta>,
1461}
1462
1463impl ElicitationUrlCapabilities {
1464 #[must_use]
1466 pub fn new() -> Self {
1467 Self::default()
1468 }
1469
1470 #[must_use]
1478 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1479 self.meta = meta.into_option();
1480 self
1481 }
1482}
1483
1484#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1486#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1487#[serde(untagged)]
1488#[non_exhaustive]
1489pub enum ElicitationScope {
1490 Session(ElicitationSessionScope),
1492 Request(ElicitationRequestScope),
1495}
1496
1497#[serde_as]
1503#[skip_serializing_none]
1504#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1505#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1506#[serde(rename_all = "camelCase")]
1507#[non_exhaustive]
1508pub struct ElicitationSessionScope {
1509 pub session_id: SessionId,
1511 #[serde_as(deserialize_as = "DefaultOnError")]
1516 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1517 #[serde(default)]
1518 pub tool_call_id: Option<ToolCallId>,
1519}
1520
1521impl ElicitationSessionScope {
1522 #[must_use]
1524 pub fn new(session_id: impl Into<SessionId>) -> Self {
1525 Self {
1526 session_id: session_id.into(),
1527 tool_call_id: None,
1528 }
1529 }
1530
1531 #[must_use]
1533 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1534 self.tool_call_id = tool_call_id.into_option();
1535 self
1536 }
1537}
1538
1539#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1542#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1543#[serde(rename_all = "camelCase")]
1544#[non_exhaustive]
1545pub struct ElicitationRequestScope {
1546 pub request_id: RequestId,
1548}
1549
1550impl ElicitationRequestScope {
1551 #[must_use]
1553 pub fn new(request_id: impl Into<RequestId>) -> Self {
1554 Self {
1555 request_id: request_id.into(),
1556 }
1557 }
1558}
1559
1560impl From<ElicitationSessionScope> for ElicitationScope {
1561 fn from(scope: ElicitationSessionScope) -> Self {
1562 Self::Session(scope)
1563 }
1564}
1565
1566impl From<ElicitationRequestScope> for ElicitationScope {
1567 fn from(scope: ElicitationRequestScope) -> Self {
1568 Self::Request(scope)
1569 }
1570}
1571
1572#[serde_as]
1578#[skip_serializing_none]
1579#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1580#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1581#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1582#[serde(rename_all = "camelCase")]
1583#[non_exhaustive]
1584pub struct CreateElicitationRequest {
1585 #[serde(flatten)]
1587 pub mode: ElicitationMode,
1588 pub message: String,
1590 #[serde_as(deserialize_as = "DefaultOnError")]
1598 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1599 #[serde(default)]
1600 #[serde(rename = "_meta")]
1601 pub meta: Option<Meta>,
1602}
1603
1604impl CreateElicitationRequest {
1605 #[must_use]
1607 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1608 Self {
1609 mode: mode.into(),
1610 message: message.into(),
1611 meta: None,
1612 }
1613 }
1614
1615 #[must_use]
1617 pub fn scope(&self) -> &ElicitationScope {
1618 self.mode.scope()
1619 }
1620
1621 #[must_use]
1629 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1630 self.meta = meta.into_option();
1631 self
1632 }
1633}
1634
1635#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1637#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1638#[serde(tag = "mode", rename_all = "snake_case")]
1639#[non_exhaustive]
1640pub enum ElicitationMode {
1641 Form(ElicitationFormMode),
1643 Url(ElicitationUrlMode),
1645 #[serde(untagged)]
1655 Other(OtherElicitationMode),
1656}
1657
1658#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1664#[derive(Debug, Clone, Serialize, PartialEq)]
1665#[cfg_attr(feature = "schemars", schemars(inline))]
1666#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1667#[serde(rename_all = "camelCase")]
1668#[non_exhaustive]
1669pub struct OtherElicitationMode {
1670 pub mode: String,
1676 #[serde(flatten)]
1678 pub scope: ElicitationScope,
1679 #[serde(flatten)]
1681 pub fields: BTreeMap<String, serde_json::Value>,
1682}
1683
1684impl OtherElicitationMode {
1685 #[must_use]
1687 pub fn new(
1688 mode: impl Into<String>,
1689 scope: impl Into<ElicitationScope>,
1690 mut fields: BTreeMap<String, serde_json::Value>,
1691 ) -> Self {
1692 fields.remove("mode");
1693 remove_elicitation_scope_fields(&mut fields);
1694 Self {
1695 mode: mode.into(),
1696 scope: scope.into(),
1697 fields,
1698 }
1699 }
1700}
1701
1702impl<'de> Deserialize<'de> for OtherElicitationMode {
1703 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1704 where
1705 D: serde::Deserializer<'de>,
1706 {
1707 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1708 let mode = fields
1709 .remove("mode")
1710 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1711 let serde_json::Value::String(mode) = mode else {
1712 return Err(serde::de::Error::custom("`mode` must be a string"));
1713 };
1714
1715 if is_known_elicitation_mode(&mode) {
1716 return Err(serde::de::Error::custom(format!(
1717 "known elicitation mode `{mode}` did not match its schema"
1718 )));
1719 }
1720
1721 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1722 fields.clone().into_iter().collect(),
1723 ))
1724 .map_err(serde::de::Error::custom)?;
1725 remove_elicitation_scope_fields(&mut fields);
1726
1727 Ok(Self {
1728 mode,
1729 scope,
1730 fields,
1731 })
1732 }
1733}
1734
1735const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1736
1737fn is_known_elicitation_mode(mode: &str) -> bool {
1738 KNOWN_ELICITATION_MODES.contains(&mode)
1739}
1740
1741fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1742 fields.remove("sessionId");
1743 fields.remove("toolCallId");
1744 fields.remove("requestId");
1745}
1746
1747#[cfg(feature = "schemars")]
1748fn other_elicitation_mode_schema(schema: &mut Schema) {
1749 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_MODES
1750 .iter()
1751 .map(|value| {
1752 serde_json::json!({
1753 "properties": {
1754 "mode": {
1755 "const": value,
1756 "type": "string"
1757 }
1758 },
1759 "required": ["mode"],
1760 "type": "object"
1761 })
1762 })
1763 .collect();
1764
1765 schema.insert(
1766 "not".into(),
1767 serde_json::json!({
1768 "anyOf": known_value_schemas
1769 }),
1770 );
1771}
1772
1773impl From<ElicitationFormMode> for ElicitationMode {
1774 fn from(mode: ElicitationFormMode) -> Self {
1775 Self::Form(mode)
1776 }
1777}
1778
1779impl From<ElicitationUrlMode> for ElicitationMode {
1780 fn from(mode: ElicitationUrlMode) -> Self {
1781 Self::Url(mode)
1782 }
1783}
1784
1785impl From<OtherElicitationMode> for ElicitationMode {
1786 fn from(mode: OtherElicitationMode) -> Self {
1787 Self::Other(mode)
1788 }
1789}
1790
1791impl ElicitationMode {
1792 #[must_use]
1794 pub fn scope(&self) -> &ElicitationScope {
1795 match self {
1796 Self::Form(f) => &f.scope,
1797 Self::Url(u) => &u.scope,
1798 Self::Other(other) => &other.scope,
1799 }
1800 }
1801}
1802
1803#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1805#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1806#[serde(rename_all = "camelCase")]
1807#[non_exhaustive]
1808pub struct ElicitationFormMode {
1809 #[serde(flatten)]
1811 pub scope: ElicitationScope,
1812 pub requested_schema: ElicitationSchema,
1814}
1815
1816impl ElicitationFormMode {
1817 #[must_use]
1819 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1820 Self {
1821 scope: scope.into(),
1822 requested_schema,
1823 }
1824 }
1825}
1826
1827#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1829#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1830#[serde(rename_all = "camelCase")]
1831#[non_exhaustive]
1832pub struct ElicitationUrlMode {
1833 #[serde(flatten)]
1835 pub scope: ElicitationScope,
1836 pub elicitation_id: ElicitationId,
1838 #[cfg_attr(feature = "schemars", schemars(extend("format" = "uri")))]
1840 pub url: String,
1841}
1842
1843impl ElicitationUrlMode {
1844 #[must_use]
1846 pub fn new(
1847 scope: impl Into<ElicitationScope>,
1848 elicitation_id: impl Into<ElicitationId>,
1849 url: impl Into<String>,
1850 ) -> Self {
1851 Self {
1852 scope: scope.into(),
1853 elicitation_id: elicitation_id.into(),
1854 url: url.into(),
1855 }
1856 }
1857}
1858
1859#[serde_as]
1861#[skip_serializing_none]
1862#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1863#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1864#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1865#[serde(rename_all = "camelCase")]
1866#[non_exhaustive]
1867pub struct CreateElicitationResponse {
1868 #[serde(flatten)]
1870 pub action: ElicitationAction,
1871 #[serde_as(deserialize_as = "DefaultOnError")]
1879 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1880 #[serde(default)]
1881 #[serde(rename = "_meta")]
1882 pub meta: Option<Meta>,
1883}
1884
1885impl CreateElicitationResponse {
1886 #[must_use]
1888 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1889 Self {
1890 action: action.into(),
1891 meta: None,
1892 }
1893 }
1894
1895 #[must_use]
1903 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1904 self.meta = meta.into_option();
1905 self
1906 }
1907}
1908
1909#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1911#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1912#[serde(tag = "action", rename_all = "snake_case")]
1913#[non_exhaustive]
1914pub enum ElicitationAction {
1915 Accept(ElicitationAcceptAction),
1917 Decline,
1919 Cancel,
1921 #[serde(untagged)]
1931 Other(OtherElicitationAction),
1932}
1933
1934#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1940#[derive(Debug, Clone, Serialize, PartialEq)]
1941#[cfg_attr(feature = "schemars", schemars(inline))]
1942#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1943#[serde(rename_all = "camelCase")]
1944#[non_exhaustive]
1945pub struct OtherElicitationAction {
1946 pub action: String,
1952 #[serde(flatten)]
1954 pub fields: BTreeMap<String, serde_json::Value>,
1955}
1956
1957impl OtherElicitationAction {
1958 #[must_use]
1960 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1961 fields.remove("action");
1962 Self {
1963 action: action.into(),
1964 fields,
1965 }
1966 }
1967}
1968
1969impl<'de> Deserialize<'de> for OtherElicitationAction {
1970 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1971 where
1972 D: serde::Deserializer<'de>,
1973 {
1974 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1975 let action = fields
1976 .remove("action")
1977 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1978 let serde_json::Value::String(action) = action else {
1979 return Err(serde::de::Error::custom("`action` must be a string"));
1980 };
1981
1982 if is_known_elicitation_action(&action) {
1983 return Err(serde::de::Error::custom(format!(
1984 "known elicitation action `{action}` did not match its schema"
1985 )));
1986 }
1987
1988 Ok(Self { action, fields })
1989 }
1990}
1991
1992const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1993
1994fn is_known_elicitation_action(action: &str) -> bool {
1995 KNOWN_ELICITATION_ACTIONS.contains(&action)
1996}
1997
1998#[cfg(feature = "schemars")]
1999fn other_elicitation_action_schema(schema: &mut Schema) {
2000 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_ACTIONS
2001 .iter()
2002 .map(|value| {
2003 serde_json::json!({
2004 "properties": {
2005 "action": {
2006 "const": value,
2007 "type": "string"
2008 }
2009 },
2010 "required": ["action"],
2011 "type": "object"
2012 })
2013 })
2014 .collect();
2015
2016 schema.insert(
2017 "not".into(),
2018 serde_json::json!({
2019 "anyOf": known_value_schemas
2020 }),
2021 );
2022}
2023
2024impl From<ElicitationAcceptAction> for ElicitationAction {
2025 fn from(action: ElicitationAcceptAction) -> Self {
2026 Self::Accept(action)
2027 }
2028}
2029
2030impl From<OtherElicitationAction> for ElicitationAction {
2031 fn from(action: OtherElicitationAction) -> Self {
2032 Self::Other(action)
2033 }
2034}
2035
2036#[serde_as]
2038#[skip_serializing_none]
2039#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2040#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2041#[serde(rename_all = "camelCase")]
2042#[non_exhaustive]
2043pub struct ElicitationAcceptAction {
2044 #[serde(default)]
2046 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
2047}
2048
2049impl ElicitationAcceptAction {
2050 #[must_use]
2052 pub fn new() -> Self {
2053 Self { content: None }
2054 }
2055
2056 #[must_use]
2058 pub fn content(
2059 mut self,
2060 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2061 ) -> Self {
2062 self.content = content.into_option();
2063 self
2064 }
2065}
2066
2067#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2069#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2070#[serde(untagged)]
2071#[non_exhaustive]
2072pub enum ElicitationContentValue {
2073 String(String),
2075 Integer(i64),
2077 Number(f64),
2079 Boolean(bool),
2081 StringArray(Vec<String>),
2083}
2084
2085impl From<String> for ElicitationContentValue {
2086 fn from(value: String) -> Self {
2087 Self::String(value)
2088 }
2089}
2090
2091impl From<&str> for ElicitationContentValue {
2092 fn from(value: &str) -> Self {
2093 Self::String(value.to_string())
2094 }
2095}
2096
2097impl From<i64> for ElicitationContentValue {
2098 fn from(value: i64) -> Self {
2099 Self::Integer(value)
2100 }
2101}
2102
2103impl From<i32> for ElicitationContentValue {
2104 fn from(value: i32) -> Self {
2105 Self::Integer(i64::from(value))
2106 }
2107}
2108
2109impl From<f64> for ElicitationContentValue {
2110 fn from(value: f64) -> Self {
2111 Self::Number(value)
2112 }
2113}
2114
2115impl From<bool> for ElicitationContentValue {
2116 fn from(value: bool) -> Self {
2117 Self::Boolean(value)
2118 }
2119}
2120
2121impl From<Vec<String>> for ElicitationContentValue {
2122 fn from(value: Vec<String>) -> Self {
2123 Self::StringArray(value)
2124 }
2125}
2126
2127impl From<Vec<&str>> for ElicitationContentValue {
2128 fn from(value: Vec<&str>) -> Self {
2129 Self::StringArray(value.into_iter().map(str::to_string).collect())
2130 }
2131}
2132
2133impl Default for ElicitationAcceptAction {
2134 fn default() -> Self {
2135 Self::new()
2136 }
2137}
2138
2139#[serde_as]
2141#[skip_serializing_none]
2142#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2144#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2145#[serde(rename_all = "camelCase")]
2146#[non_exhaustive]
2147pub struct CompleteElicitationNotification {
2148 pub elicitation_id: ElicitationId,
2150 #[serde_as(deserialize_as = "DefaultOnError")]
2158 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2159 #[serde(default)]
2160 #[serde(rename = "_meta")]
2161 pub meta: Option<Meta>,
2162}
2163
2164impl CompleteElicitationNotification {
2165 #[must_use]
2167 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2168 Self {
2169 elicitation_id: elicitation_id.into(),
2170 meta: None,
2171 }
2172 }
2173
2174 #[must_use]
2182 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2183 self.meta = meta.into_option();
2184 self
2185 }
2186}
2187
2188#[cfg(test)]
2189mod tests {
2190 use super::*;
2191 use serde_json::json;
2192
2193 #[test]
2194 fn form_mode_request_serialization() {
2195 let schema = ElicitationSchema::new().string("name", true);
2196 let req = CreateElicitationRequest::new(
2197 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2198 "Please enter your name",
2199 );
2200
2201 let json = serde_json::to_value(&req).unwrap();
2202 assert_eq!(json["sessionId"], "sess_1");
2203 assert!(json.get("toolCallId").is_none());
2204 assert_eq!(json["mode"], "form");
2205 assert_eq!(json["message"], "Please enter your name");
2206 assert!(json["requestedSchema"].is_object());
2207 assert_eq!(json["requestedSchema"]["type"], "object");
2208 assert_eq!(
2209 json["requestedSchema"]["properties"]["name"]["type"],
2210 "string"
2211 );
2212
2213 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2214 assert_eq!(
2215 *roundtripped.scope(),
2216 ElicitationSessionScope::new("sess_1").into()
2217 );
2218 assert_eq!(roundtripped.message, "Please enter your name");
2219 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2220 }
2221
2222 #[test]
2223 fn url_mode_request_serialization() {
2224 let req = CreateElicitationRequest::new(
2225 ElicitationUrlMode::new(
2226 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2227 "elic_1",
2228 "https://example.com/auth",
2229 ),
2230 "Please authenticate",
2231 );
2232
2233 let json = serde_json::to_value(&req).unwrap();
2234 assert_eq!(json["sessionId"], "sess_2");
2235 assert_eq!(json["toolCallId"], "tc_1");
2236 assert_eq!(json["mode"], "url");
2237 assert_eq!(json["elicitationId"], "elic_1");
2238 assert_eq!(json["url"], "https://example.com/auth");
2239 assert_eq!(json["message"], "Please authenticate");
2240
2241 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2242 assert_eq!(
2243 *roundtripped.scope(),
2244 ElicitationSessionScope::new("sess_2")
2245 .tool_call_id("tc_1")
2246 .into()
2247 );
2248 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2249 }
2250
2251 #[test]
2252 fn response_accept_serialization() {
2253 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2254 ElicitationAcceptAction::new().content(BTreeMap::from([(
2255 "name".to_string(),
2256 ElicitationContentValue::from("Alice"),
2257 )])),
2258 ));
2259
2260 let json = serde_json::to_value(&resp).unwrap();
2261 assert_eq!(json["action"], "accept");
2262 assert_eq!(json["content"]["name"], "Alice");
2263
2264 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2265 assert!(matches!(
2266 roundtripped.action,
2267 ElicitationAction::Accept(ElicitationAcceptAction {
2268 content: Some(_),
2269 ..
2270 })
2271 ));
2272 }
2273
2274 #[test]
2275 fn response_decline_serialization() {
2276 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2277
2278 let json = serde_json::to_value(&resp).unwrap();
2279 assert_eq!(json["action"], "decline");
2280
2281 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2282 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2283 }
2284
2285 #[test]
2286 fn response_cancel_serialization() {
2287 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2288
2289 let json = serde_json::to_value(&resp).unwrap();
2290 assert_eq!(json["action"], "cancel");
2291
2292 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2293 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2294 }
2295
2296 #[test]
2297 fn unknown_action_response_serialization() {
2298 let json = json!({
2299 "action": "_defer",
2300 "reason": "waiting",
2301 "retryAfterMs": 1000
2302 });
2303
2304 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2305 let ElicitationAction::Other(other) = &resp.action else {
2306 panic!("expected unknown elicitation action");
2307 };
2308
2309 assert_eq!(other.action, "_defer");
2310 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2311 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2312 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2313 }
2314
2315 #[test]
2316 fn unknown_action_does_not_hide_known_action() {
2317 assert!(
2318 serde_json::from_value::<OtherElicitationAction>(json!({
2319 "action": "accept",
2320 "content": {}
2321 }))
2322 .is_err()
2323 );
2324 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2325 }
2326
2327 #[test]
2328 fn url_mode_request_scope_serialization() {
2329 let req = CreateElicitationRequest::new(
2330 ElicitationUrlMode::new(
2331 ElicitationRequestScope::new(RequestId::Number(42)),
2332 "elic_2",
2333 "https://example.com/setup",
2334 ),
2335 "Please complete setup",
2336 );
2337
2338 let json = serde_json::to_value(&req).unwrap();
2339 assert_eq!(json["requestId"], 42);
2340 assert!(json.get("sessionId").is_none());
2341 assert_eq!(json["mode"], "url");
2342 assert_eq!(json["elicitationId"], "elic_2");
2343 assert_eq!(json["url"], "https://example.com/setup");
2344 assert_eq!(json["message"], "Please complete setup");
2345
2346 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2347 assert_eq!(
2348 *roundtripped.scope(),
2349 ElicitationRequestScope::new(RequestId::Number(42)).into()
2350 );
2351 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2352 }
2353
2354 #[test]
2355 fn unknown_mode_request_serialization() {
2356 let json = json!({
2357 "requestId": 42,
2358 "mode": "_browser",
2359 "message": "Open a browser window",
2360 "target": "login"
2361 });
2362
2363 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2364 let ElicitationMode::Other(other) = &req.mode else {
2365 panic!("expected unknown elicitation mode");
2366 };
2367
2368 assert_eq!(other.mode, "_browser");
2369 assert_eq!(
2370 other.scope,
2371 ElicitationRequestScope::new(RequestId::Number(42)).into()
2372 );
2373 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2374 assert_eq!(
2375 *req.scope(),
2376 ElicitationRequestScope::new(RequestId::Number(42)).into()
2377 );
2378 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2379 }
2380
2381 #[test]
2382 fn unknown_mode_does_not_hide_malformed_known_mode() {
2383 let missing_requested_schema = json!({
2384 "requestId": 42,
2385 "mode": "form",
2386 "message": "Enter your name"
2387 });
2388
2389 assert!(
2390 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2391 );
2392 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2393 }
2394
2395 #[test]
2396 fn request_scope_request_serialization() {
2397 let req = CreateElicitationRequest::new(
2398 ElicitationFormMode::new(
2399 ElicitationRequestScope::new(RequestId::Number(99)),
2400 ElicitationSchema::new().string("workspace", true),
2401 ),
2402 "Enter workspace name",
2403 );
2404
2405 let json = serde_json::to_value(&req).unwrap();
2406 assert_eq!(json["requestId"], 99);
2407 assert!(json.get("sessionId").is_none());
2408
2409 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2410 assert_eq!(
2411 *roundtripped.scope(),
2412 ElicitationRequestScope::new(RequestId::Number(99)).into()
2413 );
2414 }
2415
2416 #[test]
2423 fn client_response_serialization_accept() {
2424 use crate::v1::ClientResponse;
2425
2426 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2427 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
2428 "name".to_string(),
2429 ElicitationContentValue::from("Alice"),
2430 )]))),
2431 ));
2432 let json = serde_json::to_value(&resp).unwrap();
2433 assert_eq!(json["action"], "accept");
2434 assert_eq!(json["content"]["name"], "Alice");
2435
2436 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2438 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2439 }
2440
2441 #[test]
2442 fn client_response_serialization_decline() {
2443 use crate::v1::ClientResponse;
2444
2445 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2446 ElicitationAction::Decline,
2447 ));
2448 let json = serde_json::to_value(&resp).unwrap();
2449 assert_eq!(json["action"], "decline");
2450
2451 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2452 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2453 }
2454
2455 #[test]
2456 fn client_response_serialization_cancel() {
2457 use crate::v1::ClientResponse;
2458
2459 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2460 ElicitationAction::Cancel,
2461 ));
2462 let json = serde_json::to_value(&resp).unwrap();
2463 assert_eq!(json["action"], "cancel");
2464
2465 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2466 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2467 }
2468
2469 #[test]
2472 fn request_tolerates_extra_fields() {
2473 let json = json!({
2474 "sessionId": "sess_1",
2475 "mode": "form",
2476 "message": "Enter your name",
2477 "requestedSchema": {
2478 "type": "object",
2479 "properties": {
2480 "name": { "type": "string", "title": "Name" }
2481 },
2482 "required": ["name"]
2483 },
2484 "unknownStringField": "hello",
2485 "unknownNumberField": 42
2486 });
2487
2488 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2489 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2490 assert_eq!(req.message, "Enter your name");
2491 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2492 }
2493
2494 #[test]
2495 fn completion_notification_serialization() {
2496 let notif = CompleteElicitationNotification::new("elic_1");
2497
2498 let json = serde_json::to_value(¬if).unwrap();
2499 assert_eq!(json["elicitationId"], "elic_1");
2500
2501 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2502 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2503 }
2504
2505 #[test]
2506 fn empty_capabilities_do_not_advertise_a_mode() {
2507 let caps = ElicitationCapabilities::new();
2508 assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2509 assert!(!caps.supports_form());
2510 assert!(!caps.supports_url());
2511
2512 for value in [
2513 json!({}),
2514 json!({ "form": null }),
2515 json!({ "url": null }),
2516 json!({ "form": null, "url": null }),
2517 ] {
2518 let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2519 assert!(!caps.supports_form());
2520 assert!(!caps.supports_url());
2521 }
2522 }
2523
2524 #[test]
2525 fn capabilities_form_only() {
2526 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2527
2528 let json = serde_json::to_value(&caps).unwrap();
2529 assert!(json["form"].is_object());
2530 assert!(json.get("url").is_none());
2531
2532 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2533 assert!(roundtripped.form.is_some());
2534 assert!(roundtripped.url.is_none());
2535 assert!(roundtripped.supports_form());
2536 assert!(!roundtripped.supports_url());
2537 }
2538
2539 #[test]
2540 fn capabilities_url_only() {
2541 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2542
2543 let json = serde_json::to_value(&caps).unwrap();
2544 assert!(json.get("form").is_none());
2545 assert!(json["url"].is_object());
2546
2547 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2548 assert!(roundtripped.form.is_none());
2549 assert!(roundtripped.url.is_some());
2550 assert!(!roundtripped.supports_form());
2551 assert!(roundtripped.supports_url());
2552 }
2553
2554 #[test]
2555 fn capabilities_both() {
2556 let caps = ElicitationCapabilities::new()
2557 .form(ElicitationFormCapabilities::new())
2558 .url(ElicitationUrlCapabilities::new());
2559
2560 let json = serde_json::to_value(&caps).unwrap();
2561 assert!(json["form"].is_object());
2562 assert!(json["url"].is_object());
2563
2564 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2565 assert!(roundtripped.form.is_some());
2566 assert!(roundtripped.url.is_some());
2567 assert!(roundtripped.supports_form());
2568 assert!(roundtripped.supports_url());
2569 }
2570
2571 #[test]
2572 fn schema_default_sets_object_type() {
2573 let schema = ElicitationSchema::default();
2574
2575 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2576 assert!(schema.properties.is_empty());
2577
2578 let json = serde_json::to_value(&schema).unwrap();
2579 assert_eq!(json["type"], "object");
2580 }
2581
2582 #[test]
2583 fn schema_builder_serialization() {
2584 let schema = ElicitationSchema::new()
2585 .string("name", true)
2586 .email("email", true)
2587 .integer("age", 0, 150, true)
2588 .boolean("newsletter", false)
2589 .description("User registration");
2590
2591 let json = serde_json::to_value(&schema).unwrap();
2592 assert_eq!(json["type"], "object");
2593 assert_eq!(json["description"], "User registration");
2594 assert_eq!(json["properties"]["name"]["type"], "string");
2595 assert_eq!(json["properties"]["email"]["type"], "string");
2596 assert_eq!(json["properties"]["email"]["format"], "email");
2597 assert_eq!(json["properties"]["age"]["type"], "integer");
2598 assert_eq!(json["properties"]["age"]["minimum"], 0);
2599 assert_eq!(json["properties"]["age"]["maximum"], 150);
2600 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2601
2602 let required = json["required"].as_array().unwrap();
2603 assert!(required.contains(&json!("name")));
2604 assert!(required.contains(&json!("email")));
2605 assert!(required.contains(&json!("age")));
2606 assert!(!required.contains(&json!("newsletter")));
2607
2608 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2609 assert_eq!(roundtripped.properties.len(), 4);
2610 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2611 }
2612
2613 #[test]
2614 fn schema_string_enum_serialization() {
2615 let schema = ElicitationSchema::new().property(
2616 "color",
2617 StringPropertySchema::new().enum_values(vec![
2618 "red".into(),
2619 "green".into(),
2620 "blue".into(),
2621 ]),
2622 true,
2623 );
2624
2625 let json = serde_json::to_value(&schema).unwrap();
2626 assert_eq!(json["properties"]["color"]["type"], "string");
2627 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2628 assert_eq!(enum_vals.len(), 3);
2629
2630 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2631 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2632 {
2633 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2634 } else {
2635 panic!("expected String variant");
2636 }
2637 }
2638
2639 #[test]
2640 fn schema_multi_select_serialization() {
2641 let schema = ElicitationSchema::new().property(
2642 "colors",
2643 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2644 .min_items(1)
2645 .max_items(3),
2646 false,
2647 );
2648
2649 let json = serde_json::to_value(&schema).unwrap();
2650 assert_eq!(json["properties"]["colors"]["type"], "array");
2651 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2652 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2653 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2654
2655 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2656 let ElicitationPropertySchema::Array(array) =
2657 roundtripped.properties.get("colors").unwrap()
2658 else {
2659 panic!("expected Array variant");
2660 };
2661 let MultiSelectItems::String(items) = &array.items else {
2662 panic!("expected String multi-select items");
2663 };
2664 assert_eq!(items.values.len(), 3);
2665 }
2666
2667 #[test]
2668 fn multi_select_titled_items_keep_mcp_shape() {
2669 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2670 "#ff0000", "Red",
2671 )]));
2672
2673 let json = serde_json::to_value(&items).unwrap();
2674 assert!(json.get("type").is_none());
2675 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2676 assert_eq!(json["anyOf"][0]["title"], "Red");
2677
2678 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2679 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2680 }
2681
2682 #[test]
2683 fn multi_select_items_preserve_unknown_type() {
2684 let json = json!({
2685 "type": "_token",
2686 "format": "workspace",
2687 "anyOf": [
2688 { "const": "repo", "title": "Repository" }
2689 ]
2690 });
2691
2692 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2693 let MultiSelectItems::Other(other) = &items else {
2694 panic!("expected unknown multi-select items");
2695 };
2696
2697 assert_eq!(other.type_, "_token");
2698 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2699 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2700 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2701 }
2702
2703 #[test]
2704 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2705 assert!(
2706 serde_json::from_value::<MultiSelectItems>(json!({
2707 "type": "string"
2708 }))
2709 .is_err()
2710 );
2711 assert!(
2712 serde_json::from_value::<OtherMultiSelectItems>(json!({
2713 "type": "string",
2714 "format": "workspace"
2715 }))
2716 .is_err()
2717 );
2718 }
2719
2720 #[test]
2721 fn property_schema_preserves_unknown_type() {
2722 let schema: ElicitationSchema = serde_json::from_value(json!({
2723 "type": "object",
2724 "properties": {
2725 "location": {
2726 "type": "_location",
2727 "title": "Location",
2728 "precision": "city"
2729 }
2730 }
2731 }))
2732 .unwrap();
2733
2734 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2735 else {
2736 panic!("expected unknown property schema");
2737 };
2738
2739 assert_eq!(unknown.type_, "_location");
2740 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2741 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2742 assert_eq!(
2743 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2744 json!({
2745 "type": "_location",
2746 "title": "Location",
2747 "precision": "city"
2748 })
2749 );
2750 }
2751
2752 #[test]
2753 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2754 assert!(
2755 serde_json::from_value::<ElicitationPropertySchema>(json!({
2756 "type": "array"
2757 }))
2758 .is_err()
2759 );
2760 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2761 }
2762
2763 #[test]
2764 fn schema_titled_enum_serialization() {
2765 let schema = ElicitationSchema::new().property(
2766 "country",
2767 StringPropertySchema::new().one_of(vec![
2768 EnumOption::new("us", "United States").description("Use US English spelling."),
2769 EnumOption::new("uk", "United Kingdom"),
2770 ]),
2771 true,
2772 );
2773
2774 let json = serde_json::to_value(&schema).unwrap();
2775 assert_eq!(json["properties"]["country"]["type"], "string");
2776 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2777 assert_eq!(one_of.len(), 2);
2778 assert_eq!(one_of[0]["const"], "us");
2779 assert_eq!(one_of[0]["title"], "United States");
2780 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2781 assert!(one_of[1].get("description").is_none());
2782
2783 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2784 if let ElicitationPropertySchema::String(s) =
2785 roundtripped.properties.get("country").unwrap()
2786 {
2787 let one_of = s.one_of.as_ref().unwrap();
2788 assert_eq!(one_of.len(), 2);
2789 assert_eq!(
2790 one_of[0].description.as_deref(),
2791 Some("Use US English spelling.")
2792 );
2793 assert!(one_of[1].description.is_none());
2794 } else {
2795 panic!("expected String variant");
2796 }
2797 }
2798
2799 #[test]
2800 fn schema_number_property_serialization() {
2801 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2802
2803 let json = serde_json::to_value(&schema).unwrap();
2804 assert_eq!(json["properties"]["rating"]["type"], "number");
2805 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2806 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2807
2808 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2809 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2810 {
2811 assert_eq!(n.minimum, Some(0.0));
2812 assert_eq!(n.maximum, Some(5.0));
2813 } else {
2814 panic!("expected Number variant");
2815 }
2816 }
2817
2818 #[test]
2819 fn schema_string_format_serialization() {
2820 let schema = ElicitationSchema::new()
2821 .uri("website", true)
2822 .date("birthday", true)
2823 .date_time("updated_at", false);
2824
2825 let json = serde_json::to_value(&schema).unwrap();
2826 assert_eq!(json["properties"]["website"]["type"], "string");
2827 assert_eq!(json["properties"]["website"]["format"], "uri");
2828 assert_eq!(json["properties"]["birthday"]["type"], "string");
2829 assert_eq!(json["properties"]["birthday"]["format"], "date");
2830 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2831 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2832
2833 let required = json["required"].as_array().unwrap();
2834 assert!(required.contains(&json!("website")));
2835 assert!(required.contains(&json!("birthday")));
2836 assert!(!required.contains(&json!("updated_at")));
2837 }
2838
2839 #[test]
2840 fn schema_string_pattern_serialization() {
2841 let schema = ElicitationSchema::new().property(
2842 "name",
2843 StringPropertySchema::new()
2844 .min_length(1)
2845 .max_length(64)
2846 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2847 true,
2848 );
2849
2850 let json = serde_json::to_value(&schema).unwrap();
2851 assert_eq!(json["properties"]["name"]["type"], "string");
2852 assert_eq!(
2853 json["properties"]["name"]["pattern"],
2854 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2855 );
2856
2857 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2858 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2859 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2860 } else {
2861 panic!("expected String variant");
2862 }
2863 }
2864
2865 #[test]
2866 fn schema_property_updates_required_state() {
2867 let schema = ElicitationSchema::new()
2868 .string("name", true)
2869 .email("name", false);
2870
2871 let json = serde_json::to_value(&schema).unwrap();
2872 assert!(json.get("required").is_none());
2873 assert_eq!(json["properties"]["name"]["format"], "email");
2874 }
2875
2876 #[test]
2877 fn schema_defaults_invalid_object_type() {
2878 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2879 "type": "array",
2880 "properties": {
2881 "name": {
2882 "type": "string"
2883 }
2884 }
2885 }))
2886 .unwrap();
2887
2888 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2889 assert!(schema.properties.contains_key("name"));
2890 }
2891
2892 #[test]
2893 fn titled_multi_select_items_reject_one_of() {
2894 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2895 "oneOf": [
2896 {
2897 "const": "red",
2898 "title": "Red"
2899 }
2900 ]
2901 }))
2902 .unwrap_err();
2903
2904 assert!(err.to_string().contains("missing field `anyOf`"));
2905 }
2906
2907 #[test]
2908 fn response_accept_rejects_non_object_content() {
2909 assert!(
2910 serde_json::from_value::<CreateElicitationResponse>(json!({
2911 "action": "accept",
2912 "content": "Alice"
2913 }))
2914 .is_err()
2915 );
2916 }
2917
2918 #[test]
2919 fn response_accept_treats_null_and_omitted_content_equally() {
2920 for value in [
2921 json!({ "action": "accept" }),
2922 json!({
2923 "action": "accept",
2924 "content": null
2925 }),
2926 ] {
2927 let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2928 let ElicitationAction::Accept(accept) = response.action else {
2929 panic!("expected accept action");
2930 };
2931 assert!(accept.content.is_none());
2932 }
2933 }
2934
2935 #[test]
2936 fn response_accept_rejects_nested_object_content() {
2937 assert!(
2938 serde_json::from_value::<CreateElicitationResponse>(json!({
2939 "action": "accept",
2940 "content": {
2941 "profile": {
2942 "name": "Alice"
2943 }
2944 }
2945 }))
2946 .is_err()
2947 );
2948 }
2949
2950 #[test]
2951 fn response_accept_allows_primitive_and_string_array_content() {
2952 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2953 ElicitationAcceptAction::new().content(BTreeMap::from([
2954 ("name".to_string(), ElicitationContentValue::from("Alice")),
2955 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2956 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2957 (
2958 "subscribed".to_string(),
2959 ElicitationContentValue::from(true),
2960 ),
2961 (
2962 "tags".to_string(),
2963 ElicitationContentValue::from(vec!["rust", "acp"]),
2964 ),
2965 ])),
2966 ));
2967
2968 let json = serde_json::to_value(&response).unwrap();
2969 assert_eq!(json["action"], "accept");
2970 assert_eq!(json["content"]["name"], "Alice");
2971 assert_eq!(json["content"]["age"], 30);
2972 assert_eq!(json["content"]["score"], 9.5);
2973 assert_eq!(json["content"]["subscribed"], true);
2974 assert_eq!(json["content"]["tags"][0], "rust");
2975 assert_eq!(json["content"]["tags"][1], "acp");
2976 }
2977}