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