1use std::{collections::BTreeMap, sync::Arc};
9
10use derive_more::{Display, From};
11use schemars::{JsonSchema, Schema};
12use serde::{Deserialize, Serialize};
13use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
14
15use crate::IntoOption;
16use crate::SkipListener;
17
18use super::{
19 ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, Meta, RequestId, SessionId,
20 ToolCallId,
21};
22
23#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
29#[serde(transparent)]
30#[from(Arc<str>, String, &'static str)]
31#[non_exhaustive]
32pub struct ElicitationId(pub Arc<str>);
33
34impl ElicitationId {
35 #[must_use]
37 pub fn new(id: impl Into<Arc<str>>) -> Self {
38 Self(id.into())
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
44#[serde(rename_all = "kebab-case")]
45#[non_exhaustive]
46pub enum StringFormat {
47 Email,
49 Uri,
51 Date,
53 DateTime,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
59#[serde(rename_all = "snake_case")]
60#[non_exhaustive]
61pub enum ElicitationSchemaType {
62 #[default]
64 Object,
65}
66
67#[serde_as]
69#[skip_serializing_none]
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
71#[non_exhaustive]
72pub struct EnumOption {
73 #[serde(rename = "const")]
75 pub value: String,
76 pub title: String,
78 #[serde_as(deserialize_as = "DefaultOnError")]
80 #[schemars(extend("x-deserialize-default-on-error" = true))]
81 #[serde(default)]
82 pub description: Option<String>,
83 #[serde_as(deserialize_as = "DefaultOnError")]
89 #[schemars(extend("x-deserialize-default-on-error" = true))]
90 #[serde(default)]
91 #[serde(rename = "_meta")]
92 pub meta: Option<Meta>,
93}
94
95impl EnumOption {
96 #[must_use]
98 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
99 Self {
100 value: value.into(),
101 title: title.into(),
102 description: None,
103 meta: None,
104 }
105 }
106
107 #[must_use]
109 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
110 self.description = description.into_option();
111 self
112 }
113
114 #[must_use]
120 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
121 self.meta = meta.into_option();
122 self
123 }
124}
125
126#[serde_as]
131#[skip_serializing_none]
132#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
133#[serde(rename_all = "camelCase")]
134#[non_exhaustive]
135pub struct StringPropertySchema {
136 #[serde_as(deserialize_as = "DefaultOnError")]
138 #[schemars(extend("x-deserialize-default-on-error" = true))]
139 #[serde(default)]
140 pub title: Option<String>,
141 #[serde_as(deserialize_as = "DefaultOnError")]
143 #[schemars(extend("x-deserialize-default-on-error" = true))]
144 #[serde(default)]
145 pub description: Option<String>,
146 #[serde(default)]
148 pub min_length: Option<u32>,
149 #[serde(default)]
151 pub max_length: Option<u32>,
152 #[serde(default)]
154 pub pattern: Option<String>,
155 #[serde(default)]
157 pub format: Option<StringFormat>,
158 #[serde_as(deserialize_as = "DefaultOnError")]
160 #[schemars(extend("x-deserialize-default-on-error" = true))]
161 #[serde(default)]
162 pub default: Option<String>,
163 #[serde(default)]
165 #[serde(rename = "enum")]
166 pub enum_values: Option<Vec<String>>,
167 #[serde(default)]
169 #[serde(rename = "oneOf")]
170 pub one_of: Option<Vec<EnumOption>>,
171 #[serde_as(deserialize_as = "DefaultOnError")]
177 #[schemars(extend("x-deserialize-default-on-error" = true))]
178 #[serde(default)]
179 #[serde(rename = "_meta")]
180 pub meta: Option<Meta>,
181}
182
183impl StringPropertySchema {
184 #[must_use]
186 pub fn new() -> Self {
187 Self::default()
188 }
189
190 #[must_use]
192 pub fn email() -> Self {
193 Self {
194 format: Some(StringFormat::Email),
195 ..Default::default()
196 }
197 }
198
199 #[must_use]
201 pub fn uri() -> Self {
202 Self {
203 format: Some(StringFormat::Uri),
204 ..Default::default()
205 }
206 }
207
208 #[must_use]
210 pub fn date() -> Self {
211 Self {
212 format: Some(StringFormat::Date),
213 ..Default::default()
214 }
215 }
216
217 #[must_use]
219 pub fn date_time() -> Self {
220 Self {
221 format: Some(StringFormat::DateTime),
222 ..Default::default()
223 }
224 }
225
226 #[must_use]
228 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
229 self.title = title.into_option();
230 self
231 }
232
233 #[must_use]
235 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
236 self.description = description.into_option();
237 self
238 }
239
240 #[must_use]
242 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
243 self.min_length = min_length.into_option();
244 self
245 }
246
247 #[must_use]
249 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
250 self.max_length = max_length.into_option();
251 self
252 }
253
254 #[must_use]
256 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
257 self.pattern = pattern.into_option();
258 self
259 }
260
261 #[must_use]
263 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
264 self.format = format.into_option();
265 self
266 }
267
268 #[must_use]
270 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
271 self.default = default.into_option();
272 self
273 }
274
275 #[must_use]
277 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
278 self.enum_values = enum_values.into_option();
279 self
280 }
281
282 #[must_use]
284 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
285 self.one_of = one_of.into_option();
286 self
287 }
288
289 #[must_use]
295 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
296 self.meta = meta.into_option();
297 self
298 }
299}
300
301#[serde_as]
303#[skip_serializing_none]
304#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
305#[serde(rename_all = "camelCase")]
306#[non_exhaustive]
307pub struct NumberPropertySchema {
308 #[serde_as(deserialize_as = "DefaultOnError")]
310 #[schemars(extend("x-deserialize-default-on-error" = true))]
311 #[serde(default)]
312 pub title: Option<String>,
313 #[serde_as(deserialize_as = "DefaultOnError")]
315 #[schemars(extend("x-deserialize-default-on-error" = true))]
316 #[serde(default)]
317 pub description: Option<String>,
318 #[serde(default)]
320 pub minimum: Option<f64>,
321 #[serde(default)]
323 pub maximum: Option<f64>,
324 #[serde_as(deserialize_as = "DefaultOnError")]
326 #[schemars(extend("x-deserialize-default-on-error" = true))]
327 #[serde(default)]
328 pub default: Option<f64>,
329 #[serde_as(deserialize_as = "DefaultOnError")]
335 #[schemars(extend("x-deserialize-default-on-error" = true))]
336 #[serde(default)]
337 #[serde(rename = "_meta")]
338 pub meta: Option<Meta>,
339}
340
341impl NumberPropertySchema {
342 #[must_use]
344 pub fn new() -> Self {
345 Self::default()
346 }
347
348 #[must_use]
350 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
351 self.title = title.into_option();
352 self
353 }
354
355 #[must_use]
357 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
358 self.description = description.into_option();
359 self
360 }
361
362 #[must_use]
364 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
365 self.minimum = minimum.into_option();
366 self
367 }
368
369 #[must_use]
371 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
372 self.maximum = maximum.into_option();
373 self
374 }
375
376 #[must_use]
378 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
379 self.default = default.into_option();
380 self
381 }
382
383 #[must_use]
389 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
390 self.meta = meta.into_option();
391 self
392 }
393}
394
395#[serde_as]
397#[skip_serializing_none]
398#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
399#[serde(rename_all = "camelCase")]
400#[non_exhaustive]
401pub struct IntegerPropertySchema {
402 #[serde_as(deserialize_as = "DefaultOnError")]
404 #[schemars(extend("x-deserialize-default-on-error" = true))]
405 #[serde(default)]
406 pub title: Option<String>,
407 #[serde_as(deserialize_as = "DefaultOnError")]
409 #[schemars(extend("x-deserialize-default-on-error" = true))]
410 #[serde(default)]
411 pub description: Option<String>,
412 #[serde(default)]
414 pub minimum: Option<i64>,
415 #[serde(default)]
417 pub maximum: Option<i64>,
418 #[serde_as(deserialize_as = "DefaultOnError")]
420 #[schemars(extend("x-deserialize-default-on-error" = true))]
421 #[serde(default)]
422 pub default: Option<i64>,
423 #[serde_as(deserialize_as = "DefaultOnError")]
429 #[schemars(extend("x-deserialize-default-on-error" = true))]
430 #[serde(default)]
431 #[serde(rename = "_meta")]
432 pub meta: Option<Meta>,
433}
434
435impl IntegerPropertySchema {
436 #[must_use]
438 pub fn new() -> Self {
439 Self::default()
440 }
441
442 #[must_use]
444 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
445 self.title = title.into_option();
446 self
447 }
448
449 #[must_use]
451 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
452 self.description = description.into_option();
453 self
454 }
455
456 #[must_use]
458 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
459 self.minimum = minimum.into_option();
460 self
461 }
462
463 #[must_use]
465 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
466 self.maximum = maximum.into_option();
467 self
468 }
469
470 #[must_use]
472 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
473 self.default = default.into_option();
474 self
475 }
476
477 #[must_use]
483 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
484 self.meta = meta.into_option();
485 self
486 }
487}
488
489#[serde_as]
491#[skip_serializing_none]
492#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
493#[serde(rename_all = "camelCase")]
494#[non_exhaustive]
495pub struct BooleanPropertySchema {
496 #[serde_as(deserialize_as = "DefaultOnError")]
498 #[schemars(extend("x-deserialize-default-on-error" = true))]
499 #[serde(default)]
500 pub title: Option<String>,
501 #[serde_as(deserialize_as = "DefaultOnError")]
503 #[schemars(extend("x-deserialize-default-on-error" = true))]
504 #[serde(default)]
505 pub description: Option<String>,
506 #[serde_as(deserialize_as = "DefaultOnError")]
508 #[schemars(extend("x-deserialize-default-on-error" = true))]
509 #[serde(default)]
510 pub default: Option<bool>,
511 #[serde_as(deserialize_as = "DefaultOnError")]
517 #[schemars(extend("x-deserialize-default-on-error" = true))]
518 #[serde(default)]
519 #[serde(rename = "_meta")]
520 pub meta: Option<Meta>,
521}
522
523impl BooleanPropertySchema {
524 #[must_use]
526 pub fn new() -> Self {
527 Self::default()
528 }
529
530 #[must_use]
532 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
533 self.title = title.into_option();
534 self
535 }
536
537 #[must_use]
539 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
540 self.description = description.into_option();
541 self
542 }
543
544 #[must_use]
546 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
547 self.default = default.into_option();
548 self
549 }
550
551 #[must_use]
557 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
558 self.meta = meta.into_option();
559 self
560 }
561}
562
563#[serde_as]
565#[skip_serializing_none]
566#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
567#[non_exhaustive]
568pub struct StringMultiSelectItems {
569 #[serde(rename = "enum")]
571 pub values: Vec<String>,
572 #[serde_as(deserialize_as = "DefaultOnError")]
578 #[schemars(extend("x-deserialize-default-on-error" = true))]
579 #[serde(default)]
580 #[serde(rename = "_meta")]
581 pub meta: Option<Meta>,
582}
583
584impl StringMultiSelectItems {
585 #[must_use]
587 pub fn new(values: Vec<String>) -> Self {
588 Self { values, meta: None }
589 }
590
591 #[must_use]
597 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
598 self.meta = meta.into_option();
599 self
600 }
601}
602
603#[serde_as]
605#[skip_serializing_none]
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
607#[non_exhaustive]
608pub struct TitledMultiSelectItems {
609 #[serde(rename = "anyOf")]
611 pub options: Vec<EnumOption>,
612 #[serde_as(deserialize_as = "DefaultOnError")]
618 #[schemars(extend("x-deserialize-default-on-error" = true))]
619 #[serde(default)]
620 #[serde(rename = "_meta")]
621 pub meta: Option<Meta>,
622}
623
624impl TitledMultiSelectItems {
625 #[must_use]
627 pub fn new(options: Vec<EnumOption>) -> Self {
628 Self {
629 options,
630 meta: None,
631 }
632 }
633
634 #[must_use]
640 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
641 self.meta = meta.into_option();
642 self
643 }
644}
645
646#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
652#[schemars(inline)]
653#[schemars(transform = other_multi_select_items_schema)]
654#[serde(rename_all = "camelCase")]
655#[non_exhaustive]
656pub struct OtherMultiSelectItems {
657 #[serde(rename = "type")]
663 pub type_: String,
664 #[serde(flatten)]
666 pub fields: BTreeMap<String, serde_json::Value>,
667}
668
669impl OtherMultiSelectItems {
670 #[must_use]
672 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
673 fields.remove("type");
674 Self {
675 type_: type_.into(),
676 fields,
677 }
678 }
679}
680
681impl<'de> Deserialize<'de> for OtherMultiSelectItems {
682 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
683 where
684 D: serde::Deserializer<'de>,
685 {
686 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
687 let type_ = fields
688 .remove("type")
689 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
690 let serde_json::Value::String(type_) = type_ else {
691 return Err(serde::de::Error::custom("`type` must be a string"));
692 };
693
694 if is_known_multi_select_item_type(&type_) {
695 return Err(serde::de::Error::custom(format!(
696 "known multi-select item type `{type_}` did not match its schema"
697 )));
698 }
699
700 Ok(Self { type_, fields })
701 }
702}
703
704const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
705
706fn is_known_multi_select_item_type(type_: &str) -> bool {
707 KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
708}
709
710fn other_multi_select_items_schema(schema: &mut Schema) {
711 schema.insert(
712 "not".into(),
713 serde_json::json!({
714 "anyOf": [
715 {
716 "properties": {
717 "type": {
718 "const": "string",
719 "type": "string"
720 }
721 },
722 "required": ["type"],
723 "type": "object"
724 }
725 ]
726 }),
727 );
728}
729
730#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
732#[serde(tag = "type", rename_all = "snake_case")]
733#[non_exhaustive]
734pub enum MultiSelectItems {
735 String(StringMultiSelectItems),
737 #[serde(untagged)]
739 Other(OtherMultiSelectItems),
740 #[serde(untagged)]
742 Titled(TitledMultiSelectItems),
743}
744
745#[serde_as]
747#[skip_serializing_none]
748#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
749#[serde(rename_all = "camelCase")]
750#[non_exhaustive]
751pub struct MultiSelectPropertySchema {
752 #[serde_as(deserialize_as = "DefaultOnError")]
754 #[schemars(extend("x-deserialize-default-on-error" = true))]
755 #[serde(default)]
756 pub title: Option<String>,
757 #[serde_as(deserialize_as = "DefaultOnError")]
759 #[schemars(extend("x-deserialize-default-on-error" = true))]
760 #[serde(default)]
761 pub description: Option<String>,
762 #[serde(default)]
764 pub min_items: Option<u64>,
765 #[serde(default)]
767 pub max_items: Option<u64>,
768 pub items: MultiSelectItems,
770 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
772 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
773 #[serde(default)]
774 pub default: Option<Vec<String>>,
775 #[serde_as(deserialize_as = "DefaultOnError")]
781 #[schemars(extend("x-deserialize-default-on-error" = true))]
782 #[serde(default)]
783 #[serde(rename = "_meta")]
784 pub meta: Option<Meta>,
785}
786
787impl MultiSelectPropertySchema {
788 #[must_use]
790 pub fn new(values: Vec<String>) -> Self {
791 Self {
792 title: None,
793 description: None,
794 min_items: None,
795 max_items: None,
796 items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
797 default: None,
798 meta: None,
799 }
800 }
801
802 #[must_use]
804 pub fn titled(options: Vec<EnumOption>) -> Self {
805 Self {
806 title: None,
807 description: None,
808 min_items: None,
809 max_items: None,
810 items: MultiSelectItems::Titled(TitledMultiSelectItems::new(options)),
811 default: None,
812 meta: None,
813 }
814 }
815
816 #[must_use]
818 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
819 self.title = title.into_option();
820 self
821 }
822
823 #[must_use]
825 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
826 self.description = description.into_option();
827 self
828 }
829
830 #[must_use]
832 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
833 self.min_items = min_items.into_option();
834 self
835 }
836
837 #[must_use]
839 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
840 self.max_items = max_items.into_option();
841 self
842 }
843
844 #[must_use]
846 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
847 self.default = default.into_option();
848 self
849 }
850
851 #[must_use]
857 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
858 self.meta = meta.into_option();
859 self
860 }
861}
862
863#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
869#[serde(tag = "type", rename_all = "snake_case")]
870#[non_exhaustive]
871pub enum ElicitationPropertySchema {
872 String(StringPropertySchema),
874 Number(NumberPropertySchema),
876 Integer(IntegerPropertySchema),
878 Boolean(BooleanPropertySchema),
880 Array(MultiSelectPropertySchema),
882 #[serde(untagged)]
892 Other(OtherElicitationPropertySchema),
893}
894
895#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
901#[schemars(inline)]
902#[schemars(transform = other_elicitation_property_schema_schema)]
903#[serde(rename_all = "camelCase")]
904#[non_exhaustive]
905pub struct OtherElicitationPropertySchema {
906 #[serde(rename = "type")]
912 pub type_: String,
913 #[serde(flatten)]
915 pub fields: BTreeMap<String, serde_json::Value>,
916}
917
918impl OtherElicitationPropertySchema {
919 #[must_use]
921 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
922 fields.remove("type");
923 Self {
924 type_: type_.into(),
925 fields,
926 }
927 }
928}
929
930impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
931 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
932 where
933 D: serde::Deserializer<'de>,
934 {
935 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
936 let type_ = fields
937 .remove("type")
938 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
939 let serde_json::Value::String(type_) = type_ else {
940 return Err(serde::de::Error::custom("`type` must be a string"));
941 };
942
943 if is_known_elicitation_property_schema_type(&type_) {
944 return Err(serde::de::Error::custom(format!(
945 "known elicitation property schema type `{type_}` did not match its schema"
946 )));
947 }
948
949 Ok(Self { type_, fields })
950 }
951}
952
953const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
954 &["string", "number", "integer", "boolean", "array"];
955
956fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
957 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
958}
959
960fn other_elicitation_property_schema_schema(schema: &mut Schema) {
961 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES
962 .iter()
963 .map(|value| {
964 serde_json::json!({
965 "properties": {
966 "type": {
967 "const": value,
968 "type": "string"
969 }
970 },
971 "required": ["type"],
972 "type": "object"
973 })
974 })
975 .collect();
976
977 schema.insert(
978 "not".into(),
979 serde_json::json!({
980 "anyOf": known_value_schemas
981 }),
982 );
983}
984
985impl From<StringPropertySchema> for ElicitationPropertySchema {
986 fn from(schema: StringPropertySchema) -> Self {
987 Self::String(schema)
988 }
989}
990
991impl From<NumberPropertySchema> for ElicitationPropertySchema {
992 fn from(schema: NumberPropertySchema) -> Self {
993 Self::Number(schema)
994 }
995}
996
997impl From<IntegerPropertySchema> for ElicitationPropertySchema {
998 fn from(schema: IntegerPropertySchema) -> Self {
999 Self::Integer(schema)
1000 }
1001}
1002
1003impl From<BooleanPropertySchema> for ElicitationPropertySchema {
1004 fn from(schema: BooleanPropertySchema) -> Self {
1005 Self::Boolean(schema)
1006 }
1007}
1008
1009impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
1010 fn from(schema: MultiSelectPropertySchema) -> Self {
1011 Self::Array(schema)
1012 }
1013}
1014
1015fn default_object_type() -> ElicitationSchemaType {
1016 ElicitationSchemaType::Object
1017}
1018
1019#[serde_as]
1024#[skip_serializing_none]
1025#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1026#[serde(rename_all = "camelCase")]
1027#[non_exhaustive]
1028pub struct ElicitationSchema {
1029 #[serde_as(deserialize_as = "DefaultOnError")]
1031 #[schemars(extend("x-deserialize-default-on-error" = true))]
1032 #[serde(rename = "type", default = "default_object_type")]
1033 pub type_: ElicitationSchemaType,
1034 #[serde_as(deserialize_as = "DefaultOnError")]
1036 #[schemars(extend("x-deserialize-default-on-error" = true))]
1037 #[serde(default)]
1038 pub title: Option<String>,
1039 #[serde(default)]
1041 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1042 #[serde(default)]
1044 pub required: Option<Vec<String>>,
1045 #[serde_as(deserialize_as = "DefaultOnError")]
1047 #[schemars(extend("x-deserialize-default-on-error" = true))]
1048 #[serde(default)]
1049 pub description: Option<String>,
1050 #[serde_as(deserialize_as = "DefaultOnError")]
1056 #[schemars(extend("x-deserialize-default-on-error" = true))]
1057 #[serde(default)]
1058 #[serde(rename = "_meta")]
1059 pub meta: Option<Meta>,
1060}
1061
1062impl Default for ElicitationSchema {
1063 fn default() -> Self {
1064 Self {
1065 type_: default_object_type(),
1066 title: None,
1067 properties: BTreeMap::new(),
1068 required: None,
1069 description: None,
1070 meta: None,
1071 }
1072 }
1073}
1074
1075impl ElicitationSchema {
1076 #[must_use]
1078 pub fn new() -> Self {
1079 Self::default()
1080 }
1081
1082 #[must_use]
1084 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1085 self.title = title.into_option();
1086 self
1087 }
1088
1089 #[must_use]
1091 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1092 self.description = description.into_option();
1093 self
1094 }
1095
1096 #[must_use]
1102 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1103 self.meta = meta.into_option();
1104 self
1105 }
1106
1107 #[must_use]
1109 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1110 where
1111 S: Into<ElicitationPropertySchema>,
1112 {
1113 let name = name.into();
1114 self.properties.insert(name.clone(), schema.into());
1115
1116 if required {
1117 let required_fields = self.required.get_or_insert_with(Vec::new);
1118 if !required_fields.contains(&name) {
1119 required_fields.push(name);
1120 }
1121 } else if let Some(required_fields) = &mut self.required {
1122 required_fields.retain(|field| field != &name);
1123
1124 if required_fields.is_empty() {
1125 self.required = None;
1126 }
1127 }
1128
1129 self
1130 }
1131
1132 #[must_use]
1134 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1135 self.property(name, StringPropertySchema::new(), required)
1136 }
1137
1138 #[must_use]
1140 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1141 self.property(name, StringPropertySchema::email(), required)
1142 }
1143
1144 #[must_use]
1146 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1147 self.property(name, StringPropertySchema::uri(), required)
1148 }
1149
1150 #[must_use]
1152 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1153 self.property(name, StringPropertySchema::date(), required)
1154 }
1155
1156 #[must_use]
1158 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1159 self.property(name, StringPropertySchema::date_time(), required)
1160 }
1161
1162 #[must_use]
1164 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1165 self.property(
1166 name,
1167 NumberPropertySchema::new().minimum(min).maximum(max),
1168 required,
1169 )
1170 }
1171
1172 #[must_use]
1174 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1175 self.property(
1176 name,
1177 IntegerPropertySchema::new().minimum(min).maximum(max),
1178 required,
1179 )
1180 }
1181
1182 #[must_use]
1184 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1185 self.property(name, BooleanPropertySchema::new(), required)
1186 }
1187}
1188
1189#[serde_as]
1195#[skip_serializing_none]
1196#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1197#[serde(rename_all = "camelCase")]
1198#[non_exhaustive]
1199pub struct ElicitationCapabilities {
1200 #[serde_as(deserialize_as = "DefaultOnError")]
1205 #[schemars(extend("x-deserialize-default-on-error" = true))]
1206 #[serde(default)]
1207 pub form: Option<ElicitationFormCapabilities>,
1208 #[serde_as(deserialize_as = "DefaultOnError")]
1213 #[schemars(extend("x-deserialize-default-on-error" = true))]
1214 #[serde(default)]
1215 pub url: Option<ElicitationUrlCapabilities>,
1216 #[serde_as(deserialize_as = "DefaultOnError")]
1222 #[schemars(extend("x-deserialize-default-on-error" = true))]
1223 #[serde(default)]
1224 #[serde(rename = "_meta")]
1225 pub meta: Option<Meta>,
1226}
1227
1228impl ElicitationCapabilities {
1229 #[must_use]
1231 pub fn new() -> Self {
1232 Self::default()
1233 }
1234
1235 #[must_use]
1240 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1241 self.form = form.into_option();
1242 self
1243 }
1244
1245 #[must_use]
1250 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1251 self.url = url.into_option();
1252 self
1253 }
1254
1255 #[must_use]
1261 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1262 self.meta = meta.into_option();
1263 self
1264 }
1265}
1266
1267#[serde_as]
1275#[skip_serializing_none]
1276#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1277#[serde(rename_all = "camelCase")]
1278#[non_exhaustive]
1279pub struct ElicitationFormCapabilities {
1280 #[serde_as(deserialize_as = "DefaultOnError")]
1286 #[schemars(extend("x-deserialize-default-on-error" = true))]
1287 #[serde(default)]
1288 #[serde(rename = "_meta")]
1289 pub meta: Option<Meta>,
1290}
1291
1292impl ElicitationFormCapabilities {
1293 #[must_use]
1295 pub fn new() -> Self {
1296 Self::default()
1297 }
1298
1299 #[must_use]
1305 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1306 self.meta = meta.into_option();
1307 self
1308 }
1309}
1310
1311#[serde_as]
1319#[skip_serializing_none]
1320#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1321#[serde(rename_all = "camelCase")]
1322#[non_exhaustive]
1323pub struct ElicitationUrlCapabilities {
1324 #[serde_as(deserialize_as = "DefaultOnError")]
1330 #[schemars(extend("x-deserialize-default-on-error" = true))]
1331 #[serde(default)]
1332 #[serde(rename = "_meta")]
1333 pub meta: Option<Meta>,
1334}
1335
1336impl ElicitationUrlCapabilities {
1337 #[must_use]
1339 pub fn new() -> Self {
1340 Self::default()
1341 }
1342
1343 #[must_use]
1349 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1350 self.meta = meta.into_option();
1351 self
1352 }
1353}
1354
1355#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1361#[serde(untagged)]
1362#[non_exhaustive]
1363pub enum ElicitationScope {
1364 Session(ElicitationSessionScope),
1366 Request(ElicitationRequestScope),
1369}
1370
1371#[serde_as]
1381#[skip_serializing_none]
1382#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1383#[serde(rename_all = "camelCase")]
1384#[non_exhaustive]
1385pub struct ElicitationSessionScope {
1386 pub session_id: SessionId,
1388 #[serde_as(deserialize_as = "DefaultOnError")]
1390 #[schemars(extend("x-deserialize-default-on-error" = true))]
1391 #[serde(default)]
1392 pub tool_call_id: Option<ToolCallId>,
1393}
1394
1395impl ElicitationSessionScope {
1396 #[must_use]
1398 pub fn new(session_id: impl Into<SessionId>) -> Self {
1399 Self {
1400 session_id: session_id.into(),
1401 tool_call_id: None,
1402 }
1403 }
1404
1405 #[must_use]
1407 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1408 self.tool_call_id = tool_call_id.into_option();
1409 self
1410 }
1411}
1412
1413#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1420#[serde(rename_all = "camelCase")]
1421#[non_exhaustive]
1422pub struct ElicitationRequestScope {
1423 pub request_id: RequestId,
1425}
1426
1427impl ElicitationRequestScope {
1428 #[must_use]
1430 pub fn new(request_id: impl Into<RequestId>) -> Self {
1431 Self {
1432 request_id: request_id.into(),
1433 }
1434 }
1435}
1436
1437impl From<ElicitationSessionScope> for ElicitationScope {
1438 fn from(scope: ElicitationSessionScope) -> Self {
1439 Self::Session(scope)
1440 }
1441}
1442
1443impl From<ElicitationRequestScope> for ElicitationScope {
1444 fn from(scope: ElicitationRequestScope) -> Self {
1445 Self::Request(scope)
1446 }
1447}
1448
1449#[serde_as]
1459#[skip_serializing_none]
1460#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1461#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1462#[serde(rename_all = "camelCase")]
1463#[non_exhaustive]
1464pub struct CreateElicitationRequest {
1465 #[serde(flatten)]
1467 pub mode: ElicitationMode,
1468 pub message: String,
1470 #[serde_as(deserialize_as = "DefaultOnError")]
1476 #[schemars(extend("x-deserialize-default-on-error" = true))]
1477 #[serde(default)]
1478 #[serde(rename = "_meta")]
1479 pub meta: Option<Meta>,
1480}
1481
1482impl CreateElicitationRequest {
1483 #[must_use]
1485 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1486 Self {
1487 mode: mode.into(),
1488 message: message.into(),
1489 meta: None,
1490 }
1491 }
1492
1493 #[must_use]
1495 pub fn scope(&self) -> &ElicitationScope {
1496 self.mode.scope()
1497 }
1498
1499 #[must_use]
1505 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1506 self.meta = meta.into_option();
1507 self
1508 }
1509}
1510
1511#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1517#[serde(tag = "mode", rename_all = "snake_case")]
1518#[non_exhaustive]
1519pub enum ElicitationMode {
1520 Form(ElicitationFormMode),
1522 Url(ElicitationUrlMode),
1524 #[serde(untagged)]
1534 Other(OtherElicitationMode),
1535}
1536
1537#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1543#[schemars(inline)]
1544#[schemars(transform = other_elicitation_mode_schema)]
1545#[serde(rename_all = "camelCase")]
1546#[non_exhaustive]
1547pub struct OtherElicitationMode {
1548 pub mode: String,
1554 #[serde(flatten)]
1556 pub scope: ElicitationScope,
1557 #[serde(flatten)]
1559 pub fields: BTreeMap<String, serde_json::Value>,
1560}
1561
1562impl OtherElicitationMode {
1563 #[must_use]
1565 pub fn new(
1566 mode: impl Into<String>,
1567 scope: impl Into<ElicitationScope>,
1568 mut fields: BTreeMap<String, serde_json::Value>,
1569 ) -> Self {
1570 fields.remove("mode");
1571 remove_elicitation_scope_fields(&mut fields);
1572 Self {
1573 mode: mode.into(),
1574 scope: scope.into(),
1575 fields,
1576 }
1577 }
1578}
1579
1580impl<'de> Deserialize<'de> for OtherElicitationMode {
1581 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1582 where
1583 D: serde::Deserializer<'de>,
1584 {
1585 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1586 let mode = fields
1587 .remove("mode")
1588 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1589 let serde_json::Value::String(mode) = mode else {
1590 return Err(serde::de::Error::custom("`mode` must be a string"));
1591 };
1592
1593 if is_known_elicitation_mode(&mode) {
1594 return Err(serde::de::Error::custom(format!(
1595 "known elicitation mode `{mode}` did not match its schema"
1596 )));
1597 }
1598
1599 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1600 fields.clone().into_iter().collect(),
1601 ))
1602 .map_err(serde::de::Error::custom)?;
1603 remove_elicitation_scope_fields(&mut fields);
1604
1605 Ok(Self {
1606 mode,
1607 scope,
1608 fields,
1609 })
1610 }
1611}
1612
1613const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1614
1615fn is_known_elicitation_mode(mode: &str) -> bool {
1616 KNOWN_ELICITATION_MODES.contains(&mode)
1617}
1618
1619fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1620 fields.remove("sessionId");
1621 fields.remove("toolCallId");
1622 fields.remove("requestId");
1623}
1624
1625fn other_elicitation_mode_schema(schema: &mut Schema) {
1626 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_MODES
1627 .iter()
1628 .map(|value| {
1629 serde_json::json!({
1630 "properties": {
1631 "mode": {
1632 "const": value,
1633 "type": "string"
1634 }
1635 },
1636 "required": ["mode"],
1637 "type": "object"
1638 })
1639 })
1640 .collect();
1641
1642 schema.insert(
1643 "not".into(),
1644 serde_json::json!({
1645 "anyOf": known_value_schemas
1646 }),
1647 );
1648}
1649
1650impl From<ElicitationFormMode> for ElicitationMode {
1651 fn from(mode: ElicitationFormMode) -> Self {
1652 Self::Form(mode)
1653 }
1654}
1655
1656impl From<ElicitationUrlMode> for ElicitationMode {
1657 fn from(mode: ElicitationUrlMode) -> Self {
1658 Self::Url(mode)
1659 }
1660}
1661
1662impl From<OtherElicitationMode> for ElicitationMode {
1663 fn from(mode: OtherElicitationMode) -> Self {
1664 Self::Other(mode)
1665 }
1666}
1667
1668impl ElicitationMode {
1669 #[must_use]
1671 pub fn scope(&self) -> &ElicitationScope {
1672 match self {
1673 Self::Form(f) => &f.scope,
1674 Self::Url(u) => &u.scope,
1675 Self::Other(other) => &other.scope,
1676 }
1677 }
1678}
1679
1680#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1686#[serde(rename_all = "camelCase")]
1687#[non_exhaustive]
1688pub struct ElicitationFormMode {
1689 #[serde(flatten)]
1691 pub scope: ElicitationScope,
1692 pub requested_schema: ElicitationSchema,
1694}
1695
1696impl ElicitationFormMode {
1697 #[must_use]
1699 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1700 Self {
1701 scope: scope.into(),
1702 requested_schema,
1703 }
1704 }
1705}
1706
1707#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1713#[serde(rename_all = "camelCase")]
1714#[non_exhaustive]
1715pub struct ElicitationUrlMode {
1716 #[serde(flatten)]
1718 pub scope: ElicitationScope,
1719 pub elicitation_id: ElicitationId,
1721 #[schemars(extend("format" = "uri"))]
1723 pub url: String,
1724}
1725
1726impl ElicitationUrlMode {
1727 #[must_use]
1729 pub fn new(
1730 scope: impl Into<ElicitationScope>,
1731 elicitation_id: impl Into<ElicitationId>,
1732 url: impl Into<String>,
1733 ) -> Self {
1734 Self {
1735 scope: scope.into(),
1736 elicitation_id: elicitation_id.into(),
1737 url: url.into(),
1738 }
1739 }
1740}
1741
1742#[serde_as]
1748#[skip_serializing_none]
1749#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1750#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1751#[serde(rename_all = "camelCase")]
1752#[non_exhaustive]
1753pub struct CreateElicitationResponse {
1754 #[serde(flatten)]
1756 pub action: ElicitationAction,
1757 #[serde_as(deserialize_as = "DefaultOnError")]
1763 #[schemars(extend("x-deserialize-default-on-error" = true))]
1764 #[serde(default)]
1765 #[serde(rename = "_meta")]
1766 pub meta: Option<Meta>,
1767}
1768
1769impl CreateElicitationResponse {
1770 #[must_use]
1772 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1773 Self {
1774 action: action.into(),
1775 meta: None,
1776 }
1777 }
1778
1779 #[must_use]
1785 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1786 self.meta = meta.into_option();
1787 self
1788 }
1789}
1790
1791#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1797#[serde(tag = "action", rename_all = "snake_case")]
1798#[non_exhaustive]
1799pub enum ElicitationAction {
1800 Accept(ElicitationAcceptAction),
1802 Decline,
1804 Cancel,
1806 #[serde(untagged)]
1816 Other(OtherElicitationAction),
1817}
1818
1819#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1825#[schemars(inline)]
1826#[schemars(transform = other_elicitation_action_schema)]
1827#[serde(rename_all = "camelCase")]
1828#[non_exhaustive]
1829pub struct OtherElicitationAction {
1830 pub action: String,
1836 #[serde(flatten)]
1838 pub fields: BTreeMap<String, serde_json::Value>,
1839}
1840
1841impl OtherElicitationAction {
1842 #[must_use]
1844 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1845 fields.remove("action");
1846 Self {
1847 action: action.into(),
1848 fields,
1849 }
1850 }
1851}
1852
1853impl<'de> Deserialize<'de> for OtherElicitationAction {
1854 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1855 where
1856 D: serde::Deserializer<'de>,
1857 {
1858 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1859 let action = fields
1860 .remove("action")
1861 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1862 let serde_json::Value::String(action) = action else {
1863 return Err(serde::de::Error::custom("`action` must be a string"));
1864 };
1865
1866 if is_known_elicitation_action(&action) {
1867 return Err(serde::de::Error::custom(format!(
1868 "known elicitation action `{action}` did not match its schema"
1869 )));
1870 }
1871
1872 Ok(Self { action, fields })
1873 }
1874}
1875
1876const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1877
1878fn is_known_elicitation_action(action: &str) -> bool {
1879 KNOWN_ELICITATION_ACTIONS.contains(&action)
1880}
1881
1882fn other_elicitation_action_schema(schema: &mut Schema) {
1883 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_ACTIONS
1884 .iter()
1885 .map(|value| {
1886 serde_json::json!({
1887 "properties": {
1888 "action": {
1889 "const": value,
1890 "type": "string"
1891 }
1892 },
1893 "required": ["action"],
1894 "type": "object"
1895 })
1896 })
1897 .collect();
1898
1899 schema.insert(
1900 "not".into(),
1901 serde_json::json!({
1902 "anyOf": known_value_schemas
1903 }),
1904 );
1905}
1906
1907impl From<ElicitationAcceptAction> for ElicitationAction {
1908 fn from(action: ElicitationAcceptAction) -> Self {
1909 Self::Accept(action)
1910 }
1911}
1912
1913impl From<OtherElicitationAction> for ElicitationAction {
1914 fn from(action: OtherElicitationAction) -> Self {
1915 Self::Other(action)
1916 }
1917}
1918
1919#[serde_as]
1925#[skip_serializing_none]
1926#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1927#[serde(rename_all = "camelCase")]
1928#[non_exhaustive]
1929pub struct ElicitationAcceptAction {
1930 #[serde(default)]
1932 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1933}
1934
1935impl ElicitationAcceptAction {
1936 #[must_use]
1938 pub fn new() -> Self {
1939 Self { content: None }
1940 }
1941
1942 #[must_use]
1944 pub fn content(
1945 mut self,
1946 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1947 ) -> Self {
1948 self.content = content.into_option();
1949 self
1950 }
1951}
1952
1953#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1955#[serde(untagged)]
1956#[non_exhaustive]
1957pub enum ElicitationContentValue {
1958 String(String),
1960 Integer(i64),
1962 Number(f64),
1964 Boolean(bool),
1966 StringArray(Vec<String>),
1968}
1969
1970impl From<String> for ElicitationContentValue {
1971 fn from(value: String) -> Self {
1972 Self::String(value)
1973 }
1974}
1975
1976impl From<&str> for ElicitationContentValue {
1977 fn from(value: &str) -> Self {
1978 Self::String(value.to_string())
1979 }
1980}
1981
1982impl From<i64> for ElicitationContentValue {
1983 fn from(value: i64) -> Self {
1984 Self::Integer(value)
1985 }
1986}
1987
1988impl From<i32> for ElicitationContentValue {
1989 fn from(value: i32) -> Self {
1990 Self::Integer(i64::from(value))
1991 }
1992}
1993
1994impl From<f64> for ElicitationContentValue {
1995 fn from(value: f64) -> Self {
1996 Self::Number(value)
1997 }
1998}
1999
2000impl From<bool> for ElicitationContentValue {
2001 fn from(value: bool) -> Self {
2002 Self::Boolean(value)
2003 }
2004}
2005
2006impl From<Vec<String>> for ElicitationContentValue {
2007 fn from(value: Vec<String>) -> Self {
2008 Self::StringArray(value)
2009 }
2010}
2011
2012impl From<Vec<&str>> for ElicitationContentValue {
2013 fn from(value: Vec<&str>) -> Self {
2014 Self::StringArray(value.into_iter().map(str::to_string).collect())
2015 }
2016}
2017
2018impl Default for ElicitationAcceptAction {
2019 fn default() -> Self {
2020 Self::new()
2021 }
2022}
2023
2024#[serde_as]
2030#[skip_serializing_none]
2031#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2032#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
2033#[serde(rename_all = "camelCase")]
2034#[non_exhaustive]
2035pub struct CompleteElicitationNotification {
2036 pub elicitation_id: ElicitationId,
2038 #[serde_as(deserialize_as = "DefaultOnError")]
2044 #[schemars(extend("x-deserialize-default-on-error" = true))]
2045 #[serde(default)]
2046 #[serde(rename = "_meta")]
2047 pub meta: Option<Meta>,
2048}
2049
2050impl CompleteElicitationNotification {
2051 #[must_use]
2053 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2054 Self {
2055 elicitation_id: elicitation_id.into(),
2056 meta: None,
2057 }
2058 }
2059
2060 #[must_use]
2066 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2067 self.meta = meta.into_option();
2068 self
2069 }
2070}
2071
2072#[cfg(test)]
2073mod tests {
2074 use super::*;
2075 use serde_json::json;
2076
2077 #[test]
2078 fn form_mode_request_serialization() {
2079 let schema = ElicitationSchema::new().string("name", true);
2080 let req = CreateElicitationRequest::new(
2081 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2082 "Please enter your name",
2083 );
2084
2085 let json = serde_json::to_value(&req).unwrap();
2086 assert_eq!(json["sessionId"], "sess_1");
2087 assert!(json.get("toolCallId").is_none());
2088 assert_eq!(json["mode"], "form");
2089 assert_eq!(json["message"], "Please enter your name");
2090 assert!(json["requestedSchema"].is_object());
2091 assert_eq!(json["requestedSchema"]["type"], "object");
2092 assert_eq!(
2093 json["requestedSchema"]["properties"]["name"]["type"],
2094 "string"
2095 );
2096
2097 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2098 assert_eq!(
2099 *roundtripped.scope(),
2100 ElicitationSessionScope::new("sess_1").into()
2101 );
2102 assert_eq!(roundtripped.message, "Please enter your name");
2103 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2104 }
2105
2106 #[test]
2107 fn url_mode_request_serialization() {
2108 let req = CreateElicitationRequest::new(
2109 ElicitationUrlMode::new(
2110 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2111 "elic_1",
2112 "https://example.com/auth",
2113 ),
2114 "Please authenticate",
2115 );
2116
2117 let json = serde_json::to_value(&req).unwrap();
2118 assert_eq!(json["sessionId"], "sess_2");
2119 assert_eq!(json["toolCallId"], "tc_1");
2120 assert_eq!(json["mode"], "url");
2121 assert_eq!(json["elicitationId"], "elic_1");
2122 assert_eq!(json["url"], "https://example.com/auth");
2123 assert_eq!(json["message"], "Please authenticate");
2124
2125 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2126 assert_eq!(
2127 *roundtripped.scope(),
2128 ElicitationSessionScope::new("sess_2")
2129 .tool_call_id("tc_1")
2130 .into()
2131 );
2132 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2133 }
2134
2135 #[test]
2136 fn response_accept_serialization() {
2137 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2138 ElicitationAcceptAction::new().content(BTreeMap::from([(
2139 "name".to_string(),
2140 ElicitationContentValue::from("Alice"),
2141 )])),
2142 ));
2143
2144 let json = serde_json::to_value(&resp).unwrap();
2145 assert_eq!(json["action"], "accept");
2146 assert_eq!(json["content"]["name"], "Alice");
2147
2148 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2149 assert!(matches!(
2150 roundtripped.action,
2151 ElicitationAction::Accept(ElicitationAcceptAction {
2152 content: Some(_),
2153 ..
2154 })
2155 ));
2156 }
2157
2158 #[test]
2159 fn response_decline_serialization() {
2160 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2161
2162 let json = serde_json::to_value(&resp).unwrap();
2163 assert_eq!(json["action"], "decline");
2164
2165 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2166 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2167 }
2168
2169 #[test]
2170 fn response_cancel_serialization() {
2171 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2172
2173 let json = serde_json::to_value(&resp).unwrap();
2174 assert_eq!(json["action"], "cancel");
2175
2176 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2177 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2178 }
2179
2180 #[test]
2181 fn unknown_action_response_serialization() {
2182 let json = json!({
2183 "action": "_defer",
2184 "reason": "waiting",
2185 "retryAfterMs": 1000
2186 });
2187
2188 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2189 let ElicitationAction::Other(other) = &resp.action else {
2190 panic!("expected unknown elicitation action");
2191 };
2192
2193 assert_eq!(other.action, "_defer");
2194 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2195 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2196 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2197 }
2198
2199 #[test]
2200 fn unknown_action_does_not_hide_known_action() {
2201 assert!(
2202 serde_json::from_value::<OtherElicitationAction>(json!({
2203 "action": "accept",
2204 "content": {}
2205 }))
2206 .is_err()
2207 );
2208 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2209 }
2210
2211 #[test]
2212 fn url_mode_request_scope_serialization() {
2213 let req = CreateElicitationRequest::new(
2214 ElicitationUrlMode::new(
2215 ElicitationRequestScope::new(RequestId::Number(42)),
2216 "elic_2",
2217 "https://example.com/setup",
2218 ),
2219 "Please complete setup",
2220 );
2221
2222 let json = serde_json::to_value(&req).unwrap();
2223 assert_eq!(json["requestId"], 42);
2224 assert!(json.get("sessionId").is_none());
2225 assert_eq!(json["mode"], "url");
2226 assert_eq!(json["elicitationId"], "elic_2");
2227 assert_eq!(json["url"], "https://example.com/setup");
2228 assert_eq!(json["message"], "Please complete setup");
2229
2230 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2231 assert_eq!(
2232 *roundtripped.scope(),
2233 ElicitationRequestScope::new(RequestId::Number(42)).into()
2234 );
2235 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2236 }
2237
2238 #[test]
2239 fn unknown_mode_request_serialization() {
2240 let json = json!({
2241 "requestId": 42,
2242 "mode": "_browser",
2243 "message": "Open a browser window",
2244 "target": "login"
2245 });
2246
2247 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2248 let ElicitationMode::Other(other) = &req.mode else {
2249 panic!("expected unknown elicitation mode");
2250 };
2251
2252 assert_eq!(other.mode, "_browser");
2253 assert_eq!(
2254 other.scope,
2255 ElicitationRequestScope::new(RequestId::Number(42)).into()
2256 );
2257 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2258 assert_eq!(
2259 *req.scope(),
2260 ElicitationRequestScope::new(RequestId::Number(42)).into()
2261 );
2262 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2263 }
2264
2265 #[test]
2266 fn unknown_mode_does_not_hide_malformed_known_mode() {
2267 let missing_requested_schema = json!({
2268 "requestId": 42,
2269 "mode": "form",
2270 "message": "Enter your name"
2271 });
2272
2273 assert!(
2274 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2275 );
2276 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2277 }
2278
2279 #[test]
2280 fn request_scope_request_serialization() {
2281 let req = CreateElicitationRequest::new(
2282 ElicitationFormMode::new(
2283 ElicitationRequestScope::new(RequestId::Number(99)),
2284 ElicitationSchema::new().string("workspace", true),
2285 ),
2286 "Enter workspace name",
2287 );
2288
2289 let json = serde_json::to_value(&req).unwrap();
2290 assert_eq!(json["requestId"], 99);
2291 assert!(json.get("sessionId").is_none());
2292
2293 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2294 assert_eq!(
2295 *roundtripped.scope(),
2296 ElicitationRequestScope::new(RequestId::Number(99)).into()
2297 );
2298 }
2299
2300 #[test]
2307 fn client_response_serialization_accept() {
2308 use crate::v1::ClientResponse;
2309
2310 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2311 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
2312 "name".to_string(),
2313 ElicitationContentValue::from("Alice"),
2314 )]))),
2315 ));
2316 let json = serde_json::to_value(&resp).unwrap();
2317 assert_eq!(json["action"], "accept");
2318 assert_eq!(json["content"]["name"], "Alice");
2319
2320 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2322 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2323 }
2324
2325 #[test]
2326 fn client_response_serialization_decline() {
2327 use crate::v1::ClientResponse;
2328
2329 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2330 ElicitationAction::Decline,
2331 ));
2332 let json = serde_json::to_value(&resp).unwrap();
2333 assert_eq!(json["action"], "decline");
2334
2335 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2336 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2337 }
2338
2339 #[test]
2340 fn client_response_serialization_cancel() {
2341 use crate::v1::ClientResponse;
2342
2343 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2344 ElicitationAction::Cancel,
2345 ));
2346 let json = serde_json::to_value(&resp).unwrap();
2347 assert_eq!(json["action"], "cancel");
2348
2349 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2350 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2351 }
2352
2353 #[test]
2356 fn request_tolerates_extra_fields() {
2357 let json = json!({
2358 "sessionId": "sess_1",
2359 "mode": "form",
2360 "message": "Enter your name",
2361 "requestedSchema": {
2362 "type": "object",
2363 "properties": {
2364 "name": { "type": "string", "title": "Name" }
2365 },
2366 "required": ["name"]
2367 },
2368 "unknownStringField": "hello",
2369 "unknownNumberField": 42
2370 });
2371
2372 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2373 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2374 assert_eq!(req.message, "Enter your name");
2375 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2376 }
2377
2378 #[test]
2379 fn completion_notification_serialization() {
2380 let notif = CompleteElicitationNotification::new("elic_1");
2381
2382 let json = serde_json::to_value(¬if).unwrap();
2383 assert_eq!(json["elicitationId"], "elic_1");
2384
2385 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2386 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2387 }
2388
2389 #[test]
2390 fn capabilities_form_only() {
2391 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2392
2393 let json = serde_json::to_value(&caps).unwrap();
2394 assert!(json["form"].is_object());
2395 assert!(json.get("url").is_none());
2396
2397 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2398 assert!(roundtripped.form.is_some());
2399 assert!(roundtripped.url.is_none());
2400 }
2401
2402 #[test]
2403 fn capabilities_url_only() {
2404 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2405
2406 let json = serde_json::to_value(&caps).unwrap();
2407 assert!(json.get("form").is_none());
2408 assert!(json["url"].is_object());
2409
2410 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2411 assert!(roundtripped.form.is_none());
2412 assert!(roundtripped.url.is_some());
2413 }
2414
2415 #[test]
2416 fn capabilities_both() {
2417 let caps = ElicitationCapabilities::new()
2418 .form(ElicitationFormCapabilities::new())
2419 .url(ElicitationUrlCapabilities::new());
2420
2421 let json = serde_json::to_value(&caps).unwrap();
2422 assert!(json["form"].is_object());
2423 assert!(json["url"].is_object());
2424
2425 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2426 assert!(roundtripped.form.is_some());
2427 assert!(roundtripped.url.is_some());
2428 }
2429
2430 #[test]
2431 fn schema_default_sets_object_type() {
2432 let schema = ElicitationSchema::default();
2433
2434 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2435 assert!(schema.properties.is_empty());
2436
2437 let json = serde_json::to_value(&schema).unwrap();
2438 assert_eq!(json["type"], "object");
2439 }
2440
2441 #[test]
2442 fn schema_builder_serialization() {
2443 let schema = ElicitationSchema::new()
2444 .string("name", true)
2445 .email("email", true)
2446 .integer("age", 0, 150, true)
2447 .boolean("newsletter", false)
2448 .description("User registration");
2449
2450 let json = serde_json::to_value(&schema).unwrap();
2451 assert_eq!(json["type"], "object");
2452 assert_eq!(json["description"], "User registration");
2453 assert_eq!(json["properties"]["name"]["type"], "string");
2454 assert_eq!(json["properties"]["email"]["type"], "string");
2455 assert_eq!(json["properties"]["email"]["format"], "email");
2456 assert_eq!(json["properties"]["age"]["type"], "integer");
2457 assert_eq!(json["properties"]["age"]["minimum"], 0);
2458 assert_eq!(json["properties"]["age"]["maximum"], 150);
2459 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2460
2461 let required = json["required"].as_array().unwrap();
2462 assert!(required.contains(&json!("name")));
2463 assert!(required.contains(&json!("email")));
2464 assert!(required.contains(&json!("age")));
2465 assert!(!required.contains(&json!("newsletter")));
2466
2467 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2468 assert_eq!(roundtripped.properties.len(), 4);
2469 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2470 }
2471
2472 #[test]
2473 fn schema_string_enum_serialization() {
2474 let schema = ElicitationSchema::new().property(
2475 "color",
2476 StringPropertySchema::new().enum_values(vec![
2477 "red".into(),
2478 "green".into(),
2479 "blue".into(),
2480 ]),
2481 true,
2482 );
2483
2484 let json = serde_json::to_value(&schema).unwrap();
2485 assert_eq!(json["properties"]["color"]["type"], "string");
2486 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2487 assert_eq!(enum_vals.len(), 3);
2488
2489 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2490 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2491 {
2492 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2493 } else {
2494 panic!("expected String variant");
2495 }
2496 }
2497
2498 #[test]
2499 fn schema_multi_select_serialization() {
2500 let schema = ElicitationSchema::new().property(
2501 "colors",
2502 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2503 .min_items(1)
2504 .max_items(3),
2505 false,
2506 );
2507
2508 let json = serde_json::to_value(&schema).unwrap();
2509 assert_eq!(json["properties"]["colors"]["type"], "array");
2510 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2511 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2512 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2513
2514 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2515 let ElicitationPropertySchema::Array(array) =
2516 roundtripped.properties.get("colors").unwrap()
2517 else {
2518 panic!("expected Array variant");
2519 };
2520 let MultiSelectItems::String(items) = &array.items else {
2521 panic!("expected String multi-select items");
2522 };
2523 assert_eq!(items.values.len(), 3);
2524 }
2525
2526 #[test]
2527 fn multi_select_titled_items_keep_mcp_shape() {
2528 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2529 "#ff0000", "Red",
2530 )]));
2531
2532 let json = serde_json::to_value(&items).unwrap();
2533 assert!(json.get("type").is_none());
2534 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2535 assert_eq!(json["anyOf"][0]["title"], "Red");
2536
2537 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2538 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2539 }
2540
2541 #[test]
2542 fn multi_select_items_preserve_unknown_type() {
2543 let json = json!({
2544 "type": "_token",
2545 "format": "workspace",
2546 "anyOf": [
2547 { "const": "repo", "title": "Repository" }
2548 ]
2549 });
2550
2551 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2552 let MultiSelectItems::Other(other) = &items else {
2553 panic!("expected unknown multi-select items");
2554 };
2555
2556 assert_eq!(other.type_, "_token");
2557 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2558 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2559 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2560 }
2561
2562 #[test]
2563 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2564 assert!(
2565 serde_json::from_value::<MultiSelectItems>(json!({
2566 "type": "string"
2567 }))
2568 .is_err()
2569 );
2570 assert!(
2571 serde_json::from_value::<OtherMultiSelectItems>(json!({
2572 "type": "string",
2573 "format": "workspace"
2574 }))
2575 .is_err()
2576 );
2577 }
2578
2579 #[test]
2580 fn property_schema_preserves_unknown_type() {
2581 let schema: ElicitationSchema = serde_json::from_value(json!({
2582 "type": "object",
2583 "properties": {
2584 "location": {
2585 "type": "_location",
2586 "title": "Location",
2587 "precision": "city"
2588 }
2589 }
2590 }))
2591 .unwrap();
2592
2593 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2594 else {
2595 panic!("expected unknown property schema");
2596 };
2597
2598 assert_eq!(unknown.type_, "_location");
2599 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2600 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2601 assert_eq!(
2602 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2603 json!({
2604 "type": "_location",
2605 "title": "Location",
2606 "precision": "city"
2607 })
2608 );
2609 }
2610
2611 #[test]
2612 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2613 assert!(
2614 serde_json::from_value::<ElicitationPropertySchema>(json!({
2615 "type": "array"
2616 }))
2617 .is_err()
2618 );
2619 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2620 }
2621
2622 #[test]
2623 fn schema_titled_enum_serialization() {
2624 let schema = ElicitationSchema::new().property(
2625 "country",
2626 StringPropertySchema::new().one_of(vec![
2627 EnumOption::new("us", "United States").description("Use US English spelling."),
2628 EnumOption::new("uk", "United Kingdom"),
2629 ]),
2630 true,
2631 );
2632
2633 let json = serde_json::to_value(&schema).unwrap();
2634 assert_eq!(json["properties"]["country"]["type"], "string");
2635 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2636 assert_eq!(one_of.len(), 2);
2637 assert_eq!(one_of[0]["const"], "us");
2638 assert_eq!(one_of[0]["title"], "United States");
2639 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2640 assert!(one_of[1].get("description").is_none());
2641
2642 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2643 if let ElicitationPropertySchema::String(s) =
2644 roundtripped.properties.get("country").unwrap()
2645 {
2646 let one_of = s.one_of.as_ref().unwrap();
2647 assert_eq!(one_of.len(), 2);
2648 assert_eq!(
2649 one_of[0].description.as_deref(),
2650 Some("Use US English spelling.")
2651 );
2652 assert!(one_of[1].description.is_none());
2653 } else {
2654 panic!("expected String variant");
2655 }
2656 }
2657
2658 #[test]
2659 fn schema_number_property_serialization() {
2660 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2661
2662 let json = serde_json::to_value(&schema).unwrap();
2663 assert_eq!(json["properties"]["rating"]["type"], "number");
2664 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2665 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2666
2667 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2668 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2669 {
2670 assert_eq!(n.minimum, Some(0.0));
2671 assert_eq!(n.maximum, Some(5.0));
2672 } else {
2673 panic!("expected Number variant");
2674 }
2675 }
2676
2677 #[test]
2678 fn schema_string_format_serialization() {
2679 let schema = ElicitationSchema::new()
2680 .uri("website", true)
2681 .date("birthday", true)
2682 .date_time("updated_at", false);
2683
2684 let json = serde_json::to_value(&schema).unwrap();
2685 assert_eq!(json["properties"]["website"]["type"], "string");
2686 assert_eq!(json["properties"]["website"]["format"], "uri");
2687 assert_eq!(json["properties"]["birthday"]["type"], "string");
2688 assert_eq!(json["properties"]["birthday"]["format"], "date");
2689 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2690 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2691
2692 let required = json["required"].as_array().unwrap();
2693 assert!(required.contains(&json!("website")));
2694 assert!(required.contains(&json!("birthday")));
2695 assert!(!required.contains(&json!("updated_at")));
2696 }
2697
2698 #[test]
2699 fn schema_string_pattern_serialization() {
2700 let schema = ElicitationSchema::new().property(
2701 "name",
2702 StringPropertySchema::new()
2703 .min_length(1)
2704 .max_length(64)
2705 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2706 true,
2707 );
2708
2709 let json = serde_json::to_value(&schema).unwrap();
2710 assert_eq!(json["properties"]["name"]["type"], "string");
2711 assert_eq!(
2712 json["properties"]["name"]["pattern"],
2713 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2714 );
2715
2716 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2717 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2718 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2719 } else {
2720 panic!("expected String variant");
2721 }
2722 }
2723
2724 #[test]
2725 fn schema_property_updates_required_state() {
2726 let schema = ElicitationSchema::new()
2727 .string("name", true)
2728 .email("name", false);
2729
2730 let json = serde_json::to_value(&schema).unwrap();
2731 assert!(json.get("required").is_none());
2732 assert_eq!(json["properties"]["name"]["format"], "email");
2733 }
2734
2735 #[test]
2736 fn schema_defaults_invalid_object_type() {
2737 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2738 "type": "array",
2739 "properties": {
2740 "name": {
2741 "type": "string"
2742 }
2743 }
2744 }))
2745 .unwrap();
2746
2747 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2748 assert!(schema.properties.contains_key("name"));
2749 }
2750
2751 #[test]
2752 fn titled_multi_select_items_reject_one_of() {
2753 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2754 "oneOf": [
2755 {
2756 "const": "red",
2757 "title": "Red"
2758 }
2759 ]
2760 }))
2761 .unwrap_err();
2762
2763 assert!(err.to_string().contains("missing field `anyOf`"));
2764 }
2765
2766 #[test]
2767 fn response_accept_rejects_non_object_content() {
2768 assert!(
2769 serde_json::from_value::<CreateElicitationResponse>(json!({
2770 "action": "accept",
2771 "content": "Alice"
2772 }))
2773 .is_err()
2774 );
2775 }
2776
2777 #[test]
2778 fn response_accept_rejects_nested_object_content() {
2779 assert!(
2780 serde_json::from_value::<CreateElicitationResponse>(json!({
2781 "action": "accept",
2782 "content": {
2783 "profile": {
2784 "name": "Alice"
2785 }
2786 }
2787 }))
2788 .is_err()
2789 );
2790 }
2791
2792 #[test]
2793 fn response_accept_allows_primitive_and_string_array_content() {
2794 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2795 ElicitationAcceptAction::new().content(BTreeMap::from([
2796 ("name".to_string(), ElicitationContentValue::from("Alice")),
2797 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2798 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2799 (
2800 "subscribed".to_string(),
2801 ElicitationContentValue::from(true),
2802 ),
2803 (
2804 "tags".to_string(),
2805 ElicitationContentValue::from(vec!["rust", "acp"]),
2806 ),
2807 ])),
2808 ));
2809
2810 let json = serde_json::to_value(&response).unwrap();
2811 assert_eq!(json["action"], "accept");
2812 assert_eq!(json["content"]["name"], "Alice");
2813 assert_eq!(json["content"]["age"], 30);
2814 assert_eq!(json["content"]["score"], 9.5);
2815 assert_eq!(json["content"]["subscribed"], true);
2816 assert_eq!(json["content"]["tags"][0], "rust");
2817 assert_eq!(json["content"]["tags"][1], "acp");
2818 }
2819}