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, 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)]
822 Titled(TitledMultiSelectItems),
823}
824
825#[serde_as]
827#[skip_serializing_none]
828#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
829#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
830#[serde(rename_all = "camelCase")]
831#[non_exhaustive]
832pub struct MultiSelectPropertySchema {
833 #[serde_as(deserialize_as = "DefaultOnError")]
837 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
838 #[serde(default)]
839 pub title: Option<String>,
840 #[serde_as(deserialize_as = "DefaultOnError")]
844 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
845 #[serde(default)]
846 pub description: Option<String>,
847 #[serde(default)]
851 pub min_items: Option<u64>,
852 #[serde(default)]
856 pub max_items: Option<u64>,
857 pub items: MultiSelectItems,
859 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_>>>")]
863 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
864 #[serde(default)]
865 pub default: Option<Vec<String>>,
866 #[serde_as(deserialize_as = "DefaultOnError")]
874 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
875 #[serde(default)]
876 #[serde(rename = "_meta")]
877 pub meta: Option<Meta>,
878}
879
880impl MultiSelectPropertySchema {
881 #[must_use]
883 pub fn new(values: Vec<String>) -> Self {
884 Self {
885 title: None,
886 description: None,
887 min_items: None,
888 max_items: None,
889 items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
890 default: None,
891 meta: None,
892 }
893 }
894
895 #[must_use]
897 pub fn titled(options: Vec<EnumOption>) -> Self {
898 Self {
899 title: None,
900 description: None,
901 min_items: None,
902 max_items: None,
903 items: MultiSelectItems::Titled(TitledMultiSelectItems::new(options)),
904 default: None,
905 meta: None,
906 }
907 }
908
909 #[must_use]
911 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
912 self.title = title.into_option();
913 self
914 }
915
916 #[must_use]
918 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
919 self.description = description.into_option();
920 self
921 }
922
923 #[must_use]
925 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
926 self.min_items = min_items.into_option();
927 self
928 }
929
930 #[must_use]
932 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
933 self.max_items = max_items.into_option();
934 self
935 }
936
937 #[must_use]
939 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
940 self.default = default.into_option();
941 self
942 }
943
944 #[must_use]
952 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
953 self.meta = meta.into_option();
954 self
955 }
956}
957
958#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
964#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
965#[serde(tag = "type", rename_all = "snake_case")]
966#[non_exhaustive]
967pub enum ElicitationPropertySchema {
968 String(StringPropertySchema),
970 Number(NumberPropertySchema),
972 Integer(IntegerPropertySchema),
974 Boolean(BooleanPropertySchema),
976 Array(MultiSelectPropertySchema),
978 #[serde(untagged)]
988 Other(OtherElicitationPropertySchema),
989}
990
991#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
997#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
998#[cfg_attr(feature = "schemars", schemars(inline))]
999#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_property_schema_schema))]
1000#[serde(rename_all = "camelCase")]
1001#[non_exhaustive]
1002pub struct OtherElicitationPropertySchema {
1003 #[serde(rename = "type")]
1009 pub type_: String,
1010 #[serde(flatten)]
1012 pub fields: BTreeMap<String, serde_json::Value>,
1013}
1014
1015impl OtherElicitationPropertySchema {
1016 #[must_use]
1018 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1019 fields.remove("type");
1020 Self {
1021 type_: type_.into(),
1022 fields,
1023 }
1024 }
1025}
1026
1027impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
1028 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1029 where
1030 D: serde::Deserializer<'de>,
1031 {
1032 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1033 let type_ = fields
1034 .remove("type")
1035 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1036 let serde_json::Value::String(type_) = type_ else {
1037 return Err(serde::de::Error::custom("`type` must be a string"));
1038 };
1039
1040 if is_known_elicitation_property_schema_type(&type_) {
1041 return Err(serde::de::Error::custom(format!(
1042 "known elicitation property schema type `{type_}` did not match its schema"
1043 )));
1044 }
1045
1046 Ok(Self { type_, fields })
1047 }
1048}
1049
1050const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
1051 &["string", "number", "integer", "boolean", "array"];
1052
1053fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
1054 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
1055}
1056
1057#[cfg(feature = "schemars")]
1058fn other_elicitation_property_schema_schema(schema: &mut Schema) {
1059 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES
1060 .iter()
1061 .map(|value| {
1062 serde_json::json!({
1063 "properties": {
1064 "type": {
1065 "const": value,
1066 "type": "string"
1067 }
1068 },
1069 "required": ["type"],
1070 "type": "object"
1071 })
1072 })
1073 .collect();
1074
1075 schema.insert(
1076 "not".into(),
1077 serde_json::json!({
1078 "anyOf": known_value_schemas
1079 }),
1080 );
1081}
1082
1083impl From<StringPropertySchema> for ElicitationPropertySchema {
1084 fn from(schema: StringPropertySchema) -> Self {
1085 Self::String(schema)
1086 }
1087}
1088
1089impl From<NumberPropertySchema> for ElicitationPropertySchema {
1090 fn from(schema: NumberPropertySchema) -> Self {
1091 Self::Number(schema)
1092 }
1093}
1094
1095impl From<IntegerPropertySchema> for ElicitationPropertySchema {
1096 fn from(schema: IntegerPropertySchema) -> Self {
1097 Self::Integer(schema)
1098 }
1099}
1100
1101impl From<BooleanPropertySchema> for ElicitationPropertySchema {
1102 fn from(schema: BooleanPropertySchema) -> Self {
1103 Self::Boolean(schema)
1104 }
1105}
1106
1107impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
1108 fn from(schema: MultiSelectPropertySchema) -> Self {
1109 Self::Array(schema)
1110 }
1111}
1112
1113fn default_object_type() -> ElicitationSchemaType {
1114 ElicitationSchemaType::Object
1115}
1116
1117#[serde_as]
1122#[skip_serializing_none]
1123#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1125#[serde(rename_all = "camelCase")]
1126#[non_exhaustive]
1127pub struct ElicitationSchema {
1128 #[serde_as(deserialize_as = "DefaultOnError")]
1130 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1131 #[serde(rename = "type", default = "default_object_type")]
1132 pub type_: ElicitationSchemaType,
1133 #[serde_as(deserialize_as = "DefaultOnError")]
1137 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1138 #[serde(default)]
1139 pub title: Option<String>,
1140 #[serde(default)]
1142 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1143 #[serde(default)]
1147 pub required: Option<Vec<String>>,
1148 #[serde_as(deserialize_as = "DefaultOnError")]
1152 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1153 #[serde(default)]
1154 pub description: Option<String>,
1155 #[serde_as(deserialize_as = "DefaultOnError")]
1163 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1164 #[serde(default)]
1165 #[serde(rename = "_meta")]
1166 pub meta: Option<Meta>,
1167}
1168
1169impl Default for ElicitationSchema {
1170 fn default() -> Self {
1171 Self {
1172 type_: default_object_type(),
1173 title: None,
1174 properties: BTreeMap::new(),
1175 required: None,
1176 description: None,
1177 meta: None,
1178 }
1179 }
1180}
1181
1182impl ElicitationSchema {
1183 #[must_use]
1185 pub fn new() -> Self {
1186 Self::default()
1187 }
1188
1189 #[must_use]
1191 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1192 self.title = title.into_option();
1193 self
1194 }
1195
1196 #[must_use]
1198 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1199 self.description = description.into_option();
1200 self
1201 }
1202
1203 #[must_use]
1211 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1212 self.meta = meta.into_option();
1213 self
1214 }
1215
1216 #[must_use]
1218 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1219 where
1220 S: Into<ElicitationPropertySchema>,
1221 {
1222 let name = name.into();
1223 self.properties.insert(name.clone(), schema.into());
1224
1225 if required {
1226 let required_fields = self.required.get_or_insert_with(Vec::new);
1227 if !required_fields.contains(&name) {
1228 required_fields.push(name);
1229 }
1230 } else if let Some(required_fields) = &mut self.required {
1231 required_fields.retain(|field| field != &name);
1232
1233 if required_fields.is_empty() {
1234 self.required = None;
1235 }
1236 }
1237
1238 self
1239 }
1240
1241 #[must_use]
1243 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1244 self.property(name, StringPropertySchema::new(), required)
1245 }
1246
1247 #[must_use]
1249 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1250 self.property(name, StringPropertySchema::email(), required)
1251 }
1252
1253 #[must_use]
1255 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1256 self.property(name, StringPropertySchema::uri(), required)
1257 }
1258
1259 #[must_use]
1261 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1262 self.property(name, StringPropertySchema::date(), required)
1263 }
1264
1265 #[must_use]
1267 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1268 self.property(name, StringPropertySchema::date_time(), required)
1269 }
1270
1271 #[must_use]
1273 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1274 self.property(
1275 name,
1276 NumberPropertySchema::new().minimum(min).maximum(max),
1277 required,
1278 )
1279 }
1280
1281 #[must_use]
1283 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1284 self.property(
1285 name,
1286 IntegerPropertySchema::new().minimum(min).maximum(max),
1287 required,
1288 )
1289 }
1290
1291 #[must_use]
1293 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1294 self.property(name, BooleanPropertySchema::new(), required)
1295 }
1296}
1297
1298#[serde_as]
1300#[skip_serializing_none]
1301#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1302#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1303#[serde(rename_all = "camelCase")]
1304#[non_exhaustive]
1305pub struct ElicitationCapabilities {
1306 #[serde_as(deserialize_as = "DefaultOnError")]
1311 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1312 #[serde(default)]
1313 pub form: Option<ElicitationFormCapabilities>,
1314 #[serde_as(deserialize_as = "DefaultOnError")]
1319 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1320 #[serde(default)]
1321 pub url: Option<ElicitationUrlCapabilities>,
1322 #[serde_as(deserialize_as = "DefaultOnError")]
1330 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1331 #[serde(default)]
1332 #[serde(rename = "_meta")]
1333 pub meta: Option<Meta>,
1334}
1335
1336impl ElicitationCapabilities {
1337 #[must_use]
1342 pub fn new() -> Self {
1343 Self::default()
1344 }
1345
1346 #[must_use]
1349 pub fn supports_form(&self) -> bool {
1350 self.form.is_some()
1351 }
1352
1353 #[must_use]
1355 pub fn supports_url(&self) -> bool {
1356 self.url.is_some()
1357 }
1358
1359 #[must_use]
1364 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1365 self.form = form.into_option();
1366 self
1367 }
1368
1369 #[must_use]
1374 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1375 self.url = url.into_option();
1376 self
1377 }
1378
1379 #[must_use]
1387 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1388 self.meta = meta.into_option();
1389 self
1390 }
1391}
1392
1393#[serde_as]
1397#[skip_serializing_none]
1398#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1399#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1400#[serde(rename_all = "camelCase")]
1401#[non_exhaustive]
1402pub struct ElicitationFormCapabilities {
1403 #[serde_as(deserialize_as = "DefaultOnError")]
1411 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1412 #[serde(default)]
1413 #[serde(rename = "_meta")]
1414 pub meta: Option<Meta>,
1415}
1416
1417impl ElicitationFormCapabilities {
1418 #[must_use]
1420 pub fn new() -> Self {
1421 Self::default()
1422 }
1423
1424 #[must_use]
1432 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1433 self.meta = meta.into_option();
1434 self
1435 }
1436}
1437
1438#[serde_as]
1442#[skip_serializing_none]
1443#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1444#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1445#[serde(rename_all = "camelCase")]
1446#[non_exhaustive]
1447pub struct ElicitationUrlCapabilities {
1448 #[serde_as(deserialize_as = "DefaultOnError")]
1456 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1457 #[serde(default)]
1458 #[serde(rename = "_meta")]
1459 pub meta: Option<Meta>,
1460}
1461
1462impl ElicitationUrlCapabilities {
1463 #[must_use]
1465 pub fn new() -> Self {
1466 Self::default()
1467 }
1468
1469 #[must_use]
1477 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1478 self.meta = meta.into_option();
1479 self
1480 }
1481}
1482
1483#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1485#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1486#[serde(untagged)]
1487#[non_exhaustive]
1488pub enum ElicitationScope {
1489 Session(ElicitationSessionScope),
1491 Request(ElicitationRequestScope),
1494}
1495
1496#[serde_as]
1502#[skip_serializing_none]
1503#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1505#[serde(rename_all = "camelCase")]
1506#[non_exhaustive]
1507pub struct ElicitationSessionScope {
1508 pub session_id: SessionId,
1510 #[serde_as(deserialize_as = "DefaultOnError")]
1515 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1516 #[serde(default)]
1517 pub tool_call_id: Option<ToolCallId>,
1518}
1519
1520impl ElicitationSessionScope {
1521 #[must_use]
1523 pub fn new(session_id: impl Into<SessionId>) -> Self {
1524 Self {
1525 session_id: session_id.into(),
1526 tool_call_id: None,
1527 }
1528 }
1529
1530 #[must_use]
1532 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1533 self.tool_call_id = tool_call_id.into_option();
1534 self
1535 }
1536}
1537
1538#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1542#[serde(rename_all = "camelCase")]
1543#[non_exhaustive]
1544pub struct ElicitationRequestScope {
1545 pub request_id: RequestId,
1547}
1548
1549impl ElicitationRequestScope {
1550 #[must_use]
1552 pub fn new(request_id: impl Into<RequestId>) -> Self {
1553 Self {
1554 request_id: request_id.into(),
1555 }
1556 }
1557}
1558
1559impl From<ElicitationSessionScope> for ElicitationScope {
1560 fn from(scope: ElicitationSessionScope) -> Self {
1561 Self::Session(scope)
1562 }
1563}
1564
1565impl From<ElicitationRequestScope> for ElicitationScope {
1566 fn from(scope: ElicitationRequestScope) -> Self {
1567 Self::Request(scope)
1568 }
1569}
1570
1571#[serde_as]
1577#[skip_serializing_none]
1578#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1579#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1580#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1581#[serde(rename_all = "camelCase")]
1582#[non_exhaustive]
1583pub struct CreateElicitationRequest {
1584 #[serde(flatten)]
1586 pub mode: ElicitationMode,
1587 pub message: String,
1589 #[serde_as(deserialize_as = "DefaultOnError")]
1597 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1598 #[serde(default)]
1599 #[serde(rename = "_meta")]
1600 pub meta: Option<Meta>,
1601}
1602
1603impl CreateElicitationRequest {
1604 #[must_use]
1606 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1607 Self {
1608 mode: mode.into(),
1609 message: message.into(),
1610 meta: None,
1611 }
1612 }
1613
1614 #[must_use]
1616 pub fn scope(&self) -> &ElicitationScope {
1617 self.mode.scope()
1618 }
1619
1620 #[must_use]
1628 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1629 self.meta = meta.into_option();
1630 self
1631 }
1632}
1633
1634#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1636#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1637#[serde(tag = "mode", rename_all = "snake_case")]
1638#[non_exhaustive]
1639pub enum ElicitationMode {
1640 Form(ElicitationFormMode),
1642 Url(ElicitationUrlMode),
1644 #[serde(untagged)]
1654 Other(OtherElicitationMode),
1655}
1656
1657#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1663#[derive(Debug, Clone, Serialize, PartialEq)]
1664#[cfg_attr(feature = "schemars", schemars(inline))]
1665#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_mode_schema))]
1666#[serde(rename_all = "camelCase")]
1667#[non_exhaustive]
1668pub struct OtherElicitationMode {
1669 pub mode: String,
1675 #[serde(flatten)]
1677 pub scope: ElicitationScope,
1678 #[serde(flatten)]
1680 pub fields: BTreeMap<String, serde_json::Value>,
1681}
1682
1683impl OtherElicitationMode {
1684 #[must_use]
1686 pub fn new(
1687 mode: impl Into<String>,
1688 scope: impl Into<ElicitationScope>,
1689 mut fields: BTreeMap<String, serde_json::Value>,
1690 ) -> Self {
1691 fields.remove("mode");
1692 remove_elicitation_scope_fields(&mut fields);
1693 Self {
1694 mode: mode.into(),
1695 scope: scope.into(),
1696 fields,
1697 }
1698 }
1699}
1700
1701impl<'de> Deserialize<'de> for OtherElicitationMode {
1702 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1703 where
1704 D: serde::Deserializer<'de>,
1705 {
1706 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1707 let mode = fields
1708 .remove("mode")
1709 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1710 let serde_json::Value::String(mode) = mode else {
1711 return Err(serde::de::Error::custom("`mode` must be a string"));
1712 };
1713
1714 if is_known_elicitation_mode(&mode) {
1715 return Err(serde::de::Error::custom(format!(
1716 "known elicitation mode `{mode}` did not match its schema"
1717 )));
1718 }
1719
1720 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1721 fields.clone().into_iter().collect(),
1722 ))
1723 .map_err(serde::de::Error::custom)?;
1724 remove_elicitation_scope_fields(&mut fields);
1725
1726 Ok(Self {
1727 mode,
1728 scope,
1729 fields,
1730 })
1731 }
1732}
1733
1734const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1735
1736fn is_known_elicitation_mode(mode: &str) -> bool {
1737 KNOWN_ELICITATION_MODES.contains(&mode)
1738}
1739
1740fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1741 fields.remove("sessionId");
1742 fields.remove("toolCallId");
1743 fields.remove("requestId");
1744}
1745
1746#[cfg(feature = "schemars")]
1747fn other_elicitation_mode_schema(schema: &mut Schema) {
1748 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_MODES
1749 .iter()
1750 .map(|value| {
1751 serde_json::json!({
1752 "properties": {
1753 "mode": {
1754 "const": value,
1755 "type": "string"
1756 }
1757 },
1758 "required": ["mode"],
1759 "type": "object"
1760 })
1761 })
1762 .collect();
1763
1764 schema.insert(
1765 "not".into(),
1766 serde_json::json!({
1767 "anyOf": known_value_schemas
1768 }),
1769 );
1770}
1771
1772impl From<ElicitationFormMode> for ElicitationMode {
1773 fn from(mode: ElicitationFormMode) -> Self {
1774 Self::Form(mode)
1775 }
1776}
1777
1778impl From<ElicitationUrlMode> for ElicitationMode {
1779 fn from(mode: ElicitationUrlMode) -> Self {
1780 Self::Url(mode)
1781 }
1782}
1783
1784impl From<OtherElicitationMode> for ElicitationMode {
1785 fn from(mode: OtherElicitationMode) -> Self {
1786 Self::Other(mode)
1787 }
1788}
1789
1790impl ElicitationMode {
1791 #[must_use]
1793 pub fn scope(&self) -> &ElicitationScope {
1794 match self {
1795 Self::Form(f) => &f.scope,
1796 Self::Url(u) => &u.scope,
1797 Self::Other(other) => &other.scope,
1798 }
1799 }
1800}
1801
1802#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1804#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1805#[serde(rename_all = "camelCase")]
1806#[non_exhaustive]
1807pub struct ElicitationFormMode {
1808 #[serde(flatten)]
1810 pub scope: ElicitationScope,
1811 pub requested_schema: ElicitationSchema,
1813}
1814
1815impl ElicitationFormMode {
1816 #[must_use]
1818 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1819 Self {
1820 scope: scope.into(),
1821 requested_schema,
1822 }
1823 }
1824}
1825
1826#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1828#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1829#[serde(rename_all = "camelCase")]
1830#[non_exhaustive]
1831pub struct ElicitationUrlMode {
1832 #[serde(flatten)]
1834 pub scope: ElicitationScope,
1835 pub elicitation_id: ElicitationId,
1837 #[cfg_attr(feature = "schemars", schemars(extend("format" = "uri")))]
1839 pub url: String,
1840}
1841
1842impl ElicitationUrlMode {
1843 #[must_use]
1845 pub fn new(
1846 scope: impl Into<ElicitationScope>,
1847 elicitation_id: impl Into<ElicitationId>,
1848 url: impl Into<String>,
1849 ) -> Self {
1850 Self {
1851 scope: scope.into(),
1852 elicitation_id: elicitation_id.into(),
1853 url: url.into(),
1854 }
1855 }
1856}
1857
1858#[serde_as]
1860#[skip_serializing_none]
1861#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1862#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1863#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME)))]
1864#[serde(rename_all = "camelCase")]
1865#[non_exhaustive]
1866pub struct CreateElicitationResponse {
1867 #[serde(flatten)]
1869 pub action: ElicitationAction,
1870 #[serde_as(deserialize_as = "DefaultOnError")]
1878 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1879 #[serde(default)]
1880 #[serde(rename = "_meta")]
1881 pub meta: Option<Meta>,
1882}
1883
1884impl CreateElicitationResponse {
1885 #[must_use]
1887 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1888 Self {
1889 action: action.into(),
1890 meta: None,
1891 }
1892 }
1893
1894 #[must_use]
1902 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1903 self.meta = meta.into_option();
1904 self
1905 }
1906}
1907
1908#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1910#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1911#[serde(tag = "action", rename_all = "snake_case")]
1912#[non_exhaustive]
1913pub enum ElicitationAction {
1914 Accept(ElicitationAcceptAction),
1916 Decline,
1918 Cancel,
1920 #[serde(untagged)]
1930 Other(OtherElicitationAction),
1931}
1932
1933#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1939#[derive(Debug, Clone, Serialize, PartialEq)]
1940#[cfg_attr(feature = "schemars", schemars(inline))]
1941#[cfg_attr(feature = "schemars", schemars(transform = other_elicitation_action_schema))]
1942#[serde(rename_all = "camelCase")]
1943#[non_exhaustive]
1944pub struct OtherElicitationAction {
1945 pub action: String,
1951 #[serde(flatten)]
1953 pub fields: BTreeMap<String, serde_json::Value>,
1954}
1955
1956impl OtherElicitationAction {
1957 #[must_use]
1959 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1960 fields.remove("action");
1961 Self {
1962 action: action.into(),
1963 fields,
1964 }
1965 }
1966}
1967
1968impl<'de> Deserialize<'de> for OtherElicitationAction {
1969 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1970 where
1971 D: serde::Deserializer<'de>,
1972 {
1973 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1974 let action = fields
1975 .remove("action")
1976 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1977 let serde_json::Value::String(action) = action else {
1978 return Err(serde::de::Error::custom("`action` must be a string"));
1979 };
1980
1981 if is_known_elicitation_action(&action) {
1982 return Err(serde::de::Error::custom(format!(
1983 "known elicitation action `{action}` did not match its schema"
1984 )));
1985 }
1986
1987 Ok(Self { action, fields })
1988 }
1989}
1990
1991const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1992
1993fn is_known_elicitation_action(action: &str) -> bool {
1994 KNOWN_ELICITATION_ACTIONS.contains(&action)
1995}
1996
1997#[cfg(feature = "schemars")]
1998fn other_elicitation_action_schema(schema: &mut Schema) {
1999 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_ACTIONS
2000 .iter()
2001 .map(|value| {
2002 serde_json::json!({
2003 "properties": {
2004 "action": {
2005 "const": value,
2006 "type": "string"
2007 }
2008 },
2009 "required": ["action"],
2010 "type": "object"
2011 })
2012 })
2013 .collect();
2014
2015 schema.insert(
2016 "not".into(),
2017 serde_json::json!({
2018 "anyOf": known_value_schemas
2019 }),
2020 );
2021}
2022
2023impl From<ElicitationAcceptAction> for ElicitationAction {
2024 fn from(action: ElicitationAcceptAction) -> Self {
2025 Self::Accept(action)
2026 }
2027}
2028
2029impl From<OtherElicitationAction> for ElicitationAction {
2030 fn from(action: OtherElicitationAction) -> Self {
2031 Self::Other(action)
2032 }
2033}
2034
2035#[serde_as]
2037#[skip_serializing_none]
2038#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2039#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2040#[serde(rename_all = "camelCase")]
2041#[non_exhaustive]
2042pub struct ElicitationAcceptAction {
2043 #[serde(default)]
2045 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
2046}
2047
2048impl ElicitationAcceptAction {
2049 #[must_use]
2051 pub fn new() -> Self {
2052 Self { content: None }
2053 }
2054
2055 #[must_use]
2057 pub fn content(
2058 mut self,
2059 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
2060 ) -> Self {
2061 self.content = content.into_option();
2062 self
2063 }
2064}
2065
2066#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2068#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2069#[serde(untagged)]
2070#[non_exhaustive]
2071pub enum ElicitationContentValue {
2072 String(String),
2074 Integer(i64),
2076 Number(f64),
2078 Boolean(bool),
2080 StringArray(Vec<String>),
2082}
2083
2084impl From<String> for ElicitationContentValue {
2085 fn from(value: String) -> Self {
2086 Self::String(value)
2087 }
2088}
2089
2090impl From<&str> for ElicitationContentValue {
2091 fn from(value: &str) -> Self {
2092 Self::String(value.to_string())
2093 }
2094}
2095
2096impl From<i64> for ElicitationContentValue {
2097 fn from(value: i64) -> Self {
2098 Self::Integer(value)
2099 }
2100}
2101
2102impl From<i32> for ElicitationContentValue {
2103 fn from(value: i32) -> Self {
2104 Self::Integer(i64::from(value))
2105 }
2106}
2107
2108impl From<f64> for ElicitationContentValue {
2109 fn from(value: f64) -> Self {
2110 Self::Number(value)
2111 }
2112}
2113
2114impl From<bool> for ElicitationContentValue {
2115 fn from(value: bool) -> Self {
2116 Self::Boolean(value)
2117 }
2118}
2119
2120impl From<Vec<String>> for ElicitationContentValue {
2121 fn from(value: Vec<String>) -> Self {
2122 Self::StringArray(value)
2123 }
2124}
2125
2126impl From<Vec<&str>> for ElicitationContentValue {
2127 fn from(value: Vec<&str>) -> Self {
2128 Self::StringArray(value.into_iter().map(str::to_string).collect())
2129 }
2130}
2131
2132impl Default for ElicitationAcceptAction {
2133 fn default() -> Self {
2134 Self::new()
2135 }
2136}
2137
2138#[serde_as]
2140#[skip_serializing_none]
2141#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2143#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION)))]
2144#[serde(rename_all = "camelCase")]
2145#[non_exhaustive]
2146pub struct CompleteElicitationNotification {
2147 pub elicitation_id: ElicitationId,
2149 #[serde_as(deserialize_as = "DefaultOnError")]
2157 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2158 #[serde(default)]
2159 #[serde(rename = "_meta")]
2160 pub meta: Option<Meta>,
2161}
2162
2163impl CompleteElicitationNotification {
2164 #[must_use]
2166 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2167 Self {
2168 elicitation_id: elicitation_id.into(),
2169 meta: None,
2170 }
2171 }
2172
2173 #[must_use]
2181 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2182 self.meta = meta.into_option();
2183 self
2184 }
2185}
2186
2187#[cfg(test)]
2188mod tests {
2189 use super::*;
2190 use serde_json::json;
2191
2192 #[test]
2193 fn form_mode_request_serialization() {
2194 let schema = ElicitationSchema::new().string("name", true);
2195 let req = CreateElicitationRequest::new(
2196 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2197 "Please enter your name",
2198 );
2199
2200 let json = serde_json::to_value(&req).unwrap();
2201 assert_eq!(json["sessionId"], "sess_1");
2202 assert!(json.get("toolCallId").is_none());
2203 assert_eq!(json["mode"], "form");
2204 assert_eq!(json["message"], "Please enter your name");
2205 assert!(json["requestedSchema"].is_object());
2206 assert_eq!(json["requestedSchema"]["type"], "object");
2207 assert_eq!(
2208 json["requestedSchema"]["properties"]["name"]["type"],
2209 "string"
2210 );
2211
2212 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2213 assert_eq!(
2214 *roundtripped.scope(),
2215 ElicitationSessionScope::new("sess_1").into()
2216 );
2217 assert_eq!(roundtripped.message, "Please enter your name");
2218 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2219 }
2220
2221 #[test]
2222 fn url_mode_request_serialization() {
2223 let req = CreateElicitationRequest::new(
2224 ElicitationUrlMode::new(
2225 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2226 "elic_1",
2227 "https://example.com/auth",
2228 ),
2229 "Please authenticate",
2230 );
2231
2232 let json = serde_json::to_value(&req).unwrap();
2233 assert_eq!(json["sessionId"], "sess_2");
2234 assert_eq!(json["toolCallId"], "tc_1");
2235 assert_eq!(json["mode"], "url");
2236 assert_eq!(json["elicitationId"], "elic_1");
2237 assert_eq!(json["url"], "https://example.com/auth");
2238 assert_eq!(json["message"], "Please authenticate");
2239
2240 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2241 assert_eq!(
2242 *roundtripped.scope(),
2243 ElicitationSessionScope::new("sess_2")
2244 .tool_call_id("tc_1")
2245 .into()
2246 );
2247 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2248 }
2249
2250 #[test]
2251 fn response_accept_serialization() {
2252 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2253 ElicitationAcceptAction::new().content(BTreeMap::from([(
2254 "name".to_string(),
2255 ElicitationContentValue::from("Alice"),
2256 )])),
2257 ));
2258
2259 let json = serde_json::to_value(&resp).unwrap();
2260 assert_eq!(json["action"], "accept");
2261 assert_eq!(json["content"]["name"], "Alice");
2262
2263 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2264 assert!(matches!(
2265 roundtripped.action,
2266 ElicitationAction::Accept(ElicitationAcceptAction {
2267 content: Some(_),
2268 ..
2269 })
2270 ));
2271 }
2272
2273 #[test]
2274 fn response_decline_serialization() {
2275 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2276
2277 let json = serde_json::to_value(&resp).unwrap();
2278 assert_eq!(json["action"], "decline");
2279
2280 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2281 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2282 }
2283
2284 #[test]
2285 fn response_cancel_serialization() {
2286 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2287
2288 let json = serde_json::to_value(&resp).unwrap();
2289 assert_eq!(json["action"], "cancel");
2290
2291 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2292 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2293 }
2294
2295 #[test]
2296 fn unknown_action_response_serialization() {
2297 let json = json!({
2298 "action": "_defer",
2299 "reason": "waiting",
2300 "retryAfterMs": 1000
2301 });
2302
2303 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2304 let ElicitationAction::Other(other) = &resp.action else {
2305 panic!("expected unknown elicitation action");
2306 };
2307
2308 assert_eq!(other.action, "_defer");
2309 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2310 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2311 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2312 }
2313
2314 #[test]
2315 fn unknown_action_does_not_hide_known_action() {
2316 assert!(
2317 serde_json::from_value::<OtherElicitationAction>(json!({
2318 "action": "accept",
2319 "content": {}
2320 }))
2321 .is_err()
2322 );
2323 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2324 }
2325
2326 #[test]
2327 fn url_mode_request_scope_serialization() {
2328 let req = CreateElicitationRequest::new(
2329 ElicitationUrlMode::new(
2330 ElicitationRequestScope::new(RequestId::Number(42)),
2331 "elic_2",
2332 "https://example.com/setup",
2333 ),
2334 "Please complete setup",
2335 );
2336
2337 let json = serde_json::to_value(&req).unwrap();
2338 assert_eq!(json["requestId"], 42);
2339 assert!(json.get("sessionId").is_none());
2340 assert_eq!(json["mode"], "url");
2341 assert_eq!(json["elicitationId"], "elic_2");
2342 assert_eq!(json["url"], "https://example.com/setup");
2343 assert_eq!(json["message"], "Please complete setup");
2344
2345 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2346 assert_eq!(
2347 *roundtripped.scope(),
2348 ElicitationRequestScope::new(RequestId::Number(42)).into()
2349 );
2350 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2351 }
2352
2353 #[test]
2354 fn unknown_mode_request_serialization() {
2355 let json = json!({
2356 "requestId": 42,
2357 "mode": "_browser",
2358 "message": "Open a browser window",
2359 "target": "login"
2360 });
2361
2362 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2363 let ElicitationMode::Other(other) = &req.mode else {
2364 panic!("expected unknown elicitation mode");
2365 };
2366
2367 assert_eq!(other.mode, "_browser");
2368 assert_eq!(
2369 other.scope,
2370 ElicitationRequestScope::new(RequestId::Number(42)).into()
2371 );
2372 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2373 assert_eq!(
2374 *req.scope(),
2375 ElicitationRequestScope::new(RequestId::Number(42)).into()
2376 );
2377 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2378 }
2379
2380 #[test]
2381 fn unknown_mode_does_not_hide_malformed_known_mode() {
2382 let missing_requested_schema = json!({
2383 "requestId": 42,
2384 "mode": "form",
2385 "message": "Enter your name"
2386 });
2387
2388 assert!(
2389 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2390 );
2391 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2392 }
2393
2394 #[test]
2395 fn request_scope_request_serialization() {
2396 let req = CreateElicitationRequest::new(
2397 ElicitationFormMode::new(
2398 ElicitationRequestScope::new(RequestId::Number(99)),
2399 ElicitationSchema::new().string("workspace", true),
2400 ),
2401 "Enter workspace name",
2402 );
2403
2404 let json = serde_json::to_value(&req).unwrap();
2405 assert_eq!(json["requestId"], 99);
2406 assert!(json.get("sessionId").is_none());
2407
2408 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2409 assert_eq!(
2410 *roundtripped.scope(),
2411 ElicitationRequestScope::new(RequestId::Number(99)).into()
2412 );
2413 }
2414
2415 #[test]
2422 fn client_response_serialization_accept() {
2423 use crate::v1::ClientResponse;
2424
2425 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2426 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
2427 "name".to_string(),
2428 ElicitationContentValue::from("Alice"),
2429 )]))),
2430 ));
2431 let json = serde_json::to_value(&resp).unwrap();
2432 assert_eq!(json["action"], "accept");
2433 assert_eq!(json["content"]["name"], "Alice");
2434
2435 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2437 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2438 }
2439
2440 #[test]
2441 fn client_response_serialization_decline() {
2442 use crate::v1::ClientResponse;
2443
2444 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2445 ElicitationAction::Decline,
2446 ));
2447 let json = serde_json::to_value(&resp).unwrap();
2448 assert_eq!(json["action"], "decline");
2449
2450 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2451 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2452 }
2453
2454 #[test]
2455 fn client_response_serialization_cancel() {
2456 use crate::v1::ClientResponse;
2457
2458 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2459 ElicitationAction::Cancel,
2460 ));
2461 let json = serde_json::to_value(&resp).unwrap();
2462 assert_eq!(json["action"], "cancel");
2463
2464 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2465 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2466 }
2467
2468 #[test]
2471 fn request_tolerates_extra_fields() {
2472 let json = json!({
2473 "sessionId": "sess_1",
2474 "mode": "form",
2475 "message": "Enter your name",
2476 "requestedSchema": {
2477 "type": "object",
2478 "properties": {
2479 "name": { "type": "string", "title": "Name" }
2480 },
2481 "required": ["name"]
2482 },
2483 "unknownStringField": "hello",
2484 "unknownNumberField": 42
2485 });
2486
2487 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2488 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2489 assert_eq!(req.message, "Enter your name");
2490 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2491 }
2492
2493 #[test]
2494 fn completion_notification_serialization() {
2495 let notif = CompleteElicitationNotification::new("elic_1");
2496
2497 let json = serde_json::to_value(¬if).unwrap();
2498 assert_eq!(json["elicitationId"], "elic_1");
2499
2500 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2501 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2502 }
2503
2504 #[test]
2505 fn empty_capabilities_do_not_advertise_a_mode() {
2506 let caps = ElicitationCapabilities::new();
2507 assert_eq!(serde_json::to_value(&caps).unwrap(), json!({}));
2508 assert!(!caps.supports_form());
2509 assert!(!caps.supports_url());
2510
2511 for value in [
2512 json!({}),
2513 json!({ "form": null }),
2514 json!({ "url": null }),
2515 json!({ "form": null, "url": null }),
2516 ] {
2517 let caps: ElicitationCapabilities = serde_json::from_value(value).unwrap();
2518 assert!(!caps.supports_form());
2519 assert!(!caps.supports_url());
2520 }
2521 }
2522
2523 #[test]
2524 fn capabilities_form_only() {
2525 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2526
2527 let json = serde_json::to_value(&caps).unwrap();
2528 assert!(json["form"].is_object());
2529 assert!(json.get("url").is_none());
2530
2531 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2532 assert!(roundtripped.form.is_some());
2533 assert!(roundtripped.url.is_none());
2534 assert!(roundtripped.supports_form());
2535 assert!(!roundtripped.supports_url());
2536 }
2537
2538 #[test]
2539 fn capabilities_url_only() {
2540 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2541
2542 let json = serde_json::to_value(&caps).unwrap();
2543 assert!(json.get("form").is_none());
2544 assert!(json["url"].is_object());
2545
2546 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2547 assert!(roundtripped.form.is_none());
2548 assert!(roundtripped.url.is_some());
2549 assert!(!roundtripped.supports_form());
2550 assert!(roundtripped.supports_url());
2551 }
2552
2553 #[test]
2554 fn capabilities_both() {
2555 let caps = ElicitationCapabilities::new()
2556 .form(ElicitationFormCapabilities::new())
2557 .url(ElicitationUrlCapabilities::new());
2558
2559 let json = serde_json::to_value(&caps).unwrap();
2560 assert!(json["form"].is_object());
2561 assert!(json["url"].is_object());
2562
2563 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2564 assert!(roundtripped.form.is_some());
2565 assert!(roundtripped.url.is_some());
2566 assert!(roundtripped.supports_form());
2567 assert!(roundtripped.supports_url());
2568 }
2569
2570 #[test]
2571 fn schema_default_sets_object_type() {
2572 let schema = ElicitationSchema::default();
2573
2574 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2575 assert!(schema.properties.is_empty());
2576
2577 let json = serde_json::to_value(&schema).unwrap();
2578 assert_eq!(json["type"], "object");
2579 }
2580
2581 #[test]
2582 fn schema_builder_serialization() {
2583 let schema = ElicitationSchema::new()
2584 .string("name", true)
2585 .email("email", true)
2586 .integer("age", 0, 150, true)
2587 .boolean("newsletter", false)
2588 .description("User registration");
2589
2590 let json = serde_json::to_value(&schema).unwrap();
2591 assert_eq!(json["type"], "object");
2592 assert_eq!(json["description"], "User registration");
2593 assert_eq!(json["properties"]["name"]["type"], "string");
2594 assert_eq!(json["properties"]["email"]["type"], "string");
2595 assert_eq!(json["properties"]["email"]["format"], "email");
2596 assert_eq!(json["properties"]["age"]["type"], "integer");
2597 assert_eq!(json["properties"]["age"]["minimum"], 0);
2598 assert_eq!(json["properties"]["age"]["maximum"], 150);
2599 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2600
2601 let required = json["required"].as_array().unwrap();
2602 assert!(required.contains(&json!("name")));
2603 assert!(required.contains(&json!("email")));
2604 assert!(required.contains(&json!("age")));
2605 assert!(!required.contains(&json!("newsletter")));
2606
2607 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2608 assert_eq!(roundtripped.properties.len(), 4);
2609 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2610 }
2611
2612 #[test]
2613 fn schema_string_enum_serialization() {
2614 let schema = ElicitationSchema::new().property(
2615 "color",
2616 StringPropertySchema::new().enum_values(vec![
2617 "red".into(),
2618 "green".into(),
2619 "blue".into(),
2620 ]),
2621 true,
2622 );
2623
2624 let json = serde_json::to_value(&schema).unwrap();
2625 assert_eq!(json["properties"]["color"]["type"], "string");
2626 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2627 assert_eq!(enum_vals.len(), 3);
2628
2629 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2630 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2631 {
2632 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2633 } else {
2634 panic!("expected String variant");
2635 }
2636 }
2637
2638 #[test]
2639 fn schema_multi_select_serialization() {
2640 let schema = ElicitationSchema::new().property(
2641 "colors",
2642 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2643 .min_items(1)
2644 .max_items(3),
2645 false,
2646 );
2647
2648 let json = serde_json::to_value(&schema).unwrap();
2649 assert_eq!(json["properties"]["colors"]["type"], "array");
2650 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2651 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2652 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2653
2654 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2655 let ElicitationPropertySchema::Array(array) =
2656 roundtripped.properties.get("colors").unwrap()
2657 else {
2658 panic!("expected Array variant");
2659 };
2660 let MultiSelectItems::String(items) = &array.items else {
2661 panic!("expected String multi-select items");
2662 };
2663 assert_eq!(items.values.len(), 3);
2664 }
2665
2666 #[test]
2667 fn multi_select_titled_items_keep_mcp_shape() {
2668 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2669 "#ff0000", "Red",
2670 )]));
2671
2672 let json = serde_json::to_value(&items).unwrap();
2673 assert!(json.get("type").is_none());
2674 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2675 assert_eq!(json["anyOf"][0]["title"], "Red");
2676
2677 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2678 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2679 }
2680
2681 #[test]
2682 fn multi_select_items_preserve_unknown_type() {
2683 let json = json!({
2684 "type": "_token",
2685 "format": "workspace",
2686 "anyOf": [
2687 { "const": "repo", "title": "Repository" }
2688 ]
2689 });
2690
2691 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2692 let MultiSelectItems::Other(other) = &items else {
2693 panic!("expected unknown multi-select items");
2694 };
2695
2696 assert_eq!(other.type_, "_token");
2697 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2698 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2699 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2700 }
2701
2702 #[test]
2703 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2704 assert!(
2705 serde_json::from_value::<MultiSelectItems>(json!({
2706 "type": "string"
2707 }))
2708 .is_err()
2709 );
2710 assert!(
2711 serde_json::from_value::<OtherMultiSelectItems>(json!({
2712 "type": "string",
2713 "format": "workspace"
2714 }))
2715 .is_err()
2716 );
2717 }
2718
2719 #[test]
2720 fn property_schema_preserves_unknown_type() {
2721 let schema: ElicitationSchema = serde_json::from_value(json!({
2722 "type": "object",
2723 "properties": {
2724 "location": {
2725 "type": "_location",
2726 "title": "Location",
2727 "precision": "city"
2728 }
2729 }
2730 }))
2731 .unwrap();
2732
2733 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2734 else {
2735 panic!("expected unknown property schema");
2736 };
2737
2738 assert_eq!(unknown.type_, "_location");
2739 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2740 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2741 assert_eq!(
2742 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2743 json!({
2744 "type": "_location",
2745 "title": "Location",
2746 "precision": "city"
2747 })
2748 );
2749 }
2750
2751 #[test]
2752 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2753 assert!(
2754 serde_json::from_value::<ElicitationPropertySchema>(json!({
2755 "type": "array"
2756 }))
2757 .is_err()
2758 );
2759 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2760 }
2761
2762 #[test]
2763 fn schema_titled_enum_serialization() {
2764 let schema = ElicitationSchema::new().property(
2765 "country",
2766 StringPropertySchema::new().one_of(vec![
2767 EnumOption::new("us", "United States").description("Use US English spelling."),
2768 EnumOption::new("uk", "United Kingdom"),
2769 ]),
2770 true,
2771 );
2772
2773 let json = serde_json::to_value(&schema).unwrap();
2774 assert_eq!(json["properties"]["country"]["type"], "string");
2775 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2776 assert_eq!(one_of.len(), 2);
2777 assert_eq!(one_of[0]["const"], "us");
2778 assert_eq!(one_of[0]["title"], "United States");
2779 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2780 assert!(one_of[1].get("description").is_none());
2781
2782 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2783 if let ElicitationPropertySchema::String(s) =
2784 roundtripped.properties.get("country").unwrap()
2785 {
2786 let one_of = s.one_of.as_ref().unwrap();
2787 assert_eq!(one_of.len(), 2);
2788 assert_eq!(
2789 one_of[0].description.as_deref(),
2790 Some("Use US English spelling.")
2791 );
2792 assert!(one_of[1].description.is_none());
2793 } else {
2794 panic!("expected String variant");
2795 }
2796 }
2797
2798 #[test]
2799 fn schema_number_property_serialization() {
2800 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2801
2802 let json = serde_json::to_value(&schema).unwrap();
2803 assert_eq!(json["properties"]["rating"]["type"], "number");
2804 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2805 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2806
2807 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2808 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2809 {
2810 assert_eq!(n.minimum, Some(0.0));
2811 assert_eq!(n.maximum, Some(5.0));
2812 } else {
2813 panic!("expected Number variant");
2814 }
2815 }
2816
2817 #[test]
2818 fn schema_string_format_serialization() {
2819 let schema = ElicitationSchema::new()
2820 .uri("website", true)
2821 .date("birthday", true)
2822 .date_time("updated_at", false);
2823
2824 let json = serde_json::to_value(&schema).unwrap();
2825 assert_eq!(json["properties"]["website"]["type"], "string");
2826 assert_eq!(json["properties"]["website"]["format"], "uri");
2827 assert_eq!(json["properties"]["birthday"]["type"], "string");
2828 assert_eq!(json["properties"]["birthday"]["format"], "date");
2829 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2830 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2831
2832 let required = json["required"].as_array().unwrap();
2833 assert!(required.contains(&json!("website")));
2834 assert!(required.contains(&json!("birthday")));
2835 assert!(!required.contains(&json!("updated_at")));
2836 }
2837
2838 #[test]
2839 fn schema_string_pattern_serialization() {
2840 let schema = ElicitationSchema::new().property(
2841 "name",
2842 StringPropertySchema::new()
2843 .min_length(1)
2844 .max_length(64)
2845 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2846 true,
2847 );
2848
2849 let json = serde_json::to_value(&schema).unwrap();
2850 assert_eq!(json["properties"]["name"]["type"], "string");
2851 assert_eq!(
2852 json["properties"]["name"]["pattern"],
2853 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2854 );
2855
2856 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2857 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2858 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2859 } else {
2860 panic!("expected String variant");
2861 }
2862 }
2863
2864 #[test]
2865 fn schema_property_updates_required_state() {
2866 let schema = ElicitationSchema::new()
2867 .string("name", true)
2868 .email("name", false);
2869
2870 let json = serde_json::to_value(&schema).unwrap();
2871 assert!(json.get("required").is_none());
2872 assert_eq!(json["properties"]["name"]["format"], "email");
2873 }
2874
2875 #[test]
2876 fn schema_defaults_invalid_object_type() {
2877 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2878 "type": "array",
2879 "properties": {
2880 "name": {
2881 "type": "string"
2882 }
2883 }
2884 }))
2885 .unwrap();
2886
2887 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2888 assert!(schema.properties.contains_key("name"));
2889 }
2890
2891 #[test]
2892 fn titled_multi_select_items_reject_one_of() {
2893 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2894 "oneOf": [
2895 {
2896 "const": "red",
2897 "title": "Red"
2898 }
2899 ]
2900 }))
2901 .unwrap_err();
2902
2903 assert!(err.to_string().contains("missing field `anyOf`"));
2904 }
2905
2906 #[test]
2907 fn response_accept_rejects_non_object_content() {
2908 assert!(
2909 serde_json::from_value::<CreateElicitationResponse>(json!({
2910 "action": "accept",
2911 "content": "Alice"
2912 }))
2913 .is_err()
2914 );
2915 }
2916
2917 #[test]
2918 fn response_accept_treats_null_and_omitted_content_equally() {
2919 for value in [
2920 json!({ "action": "accept" }),
2921 json!({
2922 "action": "accept",
2923 "content": null
2924 }),
2925 ] {
2926 let response: CreateElicitationResponse = serde_json::from_value(value).unwrap();
2927 let ElicitationAction::Accept(accept) = response.action else {
2928 panic!("expected accept action");
2929 };
2930 assert!(accept.content.is_none());
2931 }
2932 }
2933
2934 #[test]
2935 fn response_accept_rejects_nested_object_content() {
2936 assert!(
2937 serde_json::from_value::<CreateElicitationResponse>(json!({
2938 "action": "accept",
2939 "content": {
2940 "profile": {
2941 "name": "Alice"
2942 }
2943 }
2944 }))
2945 .is_err()
2946 );
2947 }
2948
2949 #[test]
2950 fn response_accept_allows_primitive_and_string_array_content() {
2951 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2952 ElicitationAcceptAction::new().content(BTreeMap::from([
2953 ("name".to_string(), ElicitationContentValue::from("Alice")),
2954 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2955 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2956 (
2957 "subscribed".to_string(),
2958 ElicitationContentValue::from(true),
2959 ),
2960 (
2961 "tags".to_string(),
2962 ElicitationContentValue::from(vec!["rust", "acp"]),
2963 ),
2964 ])),
2965 ));
2966
2967 let json = serde_json::to_value(&response).unwrap();
2968 assert_eq!(json["action"], "accept");
2969 assert_eq!(json["content"]["name"], "Alice");
2970 assert_eq!(json["content"]["age"], 30);
2971 assert_eq!(json["content"]["score"], 9.5);
2972 assert_eq!(json["content"]["subscribed"], true);
2973 assert_eq!(json["content"]["tags"][0], "rust");
2974 assert_eq!(json["content"]["tags"][1], "acp");
2975 }
2976}