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 super::{
16 ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, Meta, RequestId, SessionId,
17 ToolCallId,
18};
19use crate::IntoOption;
20use crate::SkipListener;
21
22#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
28#[serde(transparent)]
29#[from(Arc<str>, String, &'static str)]
30#[non_exhaustive]
31pub struct ElicitationId(pub Arc<str>);
32
33impl ElicitationId {
34 #[must_use]
36 pub fn new(id: impl Into<Arc<str>>) -> Self {
37 Self(id.into())
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
43#[serde(rename_all = "kebab-case")]
44#[non_exhaustive]
45pub enum StringFormat {
46 Email,
48 Uri,
50 Date,
52 DateTime,
54 #[serde(untagged)]
59 Other(String),
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
64#[serde(rename_all = "snake_case")]
65#[non_exhaustive]
66pub enum ElicitationSchemaType {
67 #[default]
69 Object,
70}
71
72#[serde_as]
74#[skip_serializing_none]
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
76#[non_exhaustive]
77pub struct EnumOption {
78 #[serde(rename = "const")]
80 pub value: String,
81 pub title: String,
83 #[serde_as(deserialize_as = "DefaultOnError")]
85 #[schemars(extend("x-deserialize-default-on-error" = true))]
86 #[serde(default)]
87 pub description: Option<String>,
88 #[serde_as(deserialize_as = "DefaultOnError")]
94 #[schemars(extend("x-deserialize-default-on-error" = true))]
95 #[serde(default)]
96 #[serde(rename = "_meta")]
97 pub meta: Option<Meta>,
98}
99
100impl EnumOption {
101 #[must_use]
103 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
104 Self {
105 value: value.into(),
106 title: title.into(),
107 description: None,
108 meta: None,
109 }
110 }
111
112 #[must_use]
114 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
115 self.description = description.into_option();
116 self
117 }
118
119 #[must_use]
125 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
126 self.meta = meta.into_option();
127 self
128 }
129}
130
131#[serde_as]
136#[skip_serializing_none]
137#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
138#[serde(rename_all = "camelCase")]
139#[non_exhaustive]
140pub struct StringPropertySchema {
141 #[serde_as(deserialize_as = "DefaultOnError")]
143 #[schemars(extend("x-deserialize-default-on-error" = true))]
144 #[serde(default)]
145 pub title: Option<String>,
146 #[serde_as(deserialize_as = "DefaultOnError")]
148 #[schemars(extend("x-deserialize-default-on-error" = true))]
149 #[serde(default)]
150 pub description: Option<String>,
151 #[serde(default)]
153 pub min_length: Option<u32>,
154 #[serde(default)]
156 pub max_length: Option<u32>,
157 #[schemars(extend("format" = "regex"))]
159 #[serde(default)]
160 pub pattern: Option<String>,
161 #[serde(default)]
163 pub format: Option<StringFormat>,
164 #[serde_as(deserialize_as = "DefaultOnError")]
166 #[schemars(extend("x-deserialize-default-on-error" = true))]
167 #[serde(default)]
168 pub default: Option<String>,
169 #[schemars(length(min = 1))]
172 #[serde(default)]
173 #[serde(rename = "enum")]
174 pub enum_values: Option<Vec<String>>,
175 #[schemars(length(min = 1))]
178 #[serde(default)]
179 #[serde(rename = "oneOf")]
180 pub one_of: Option<Vec<EnumOption>>,
181 #[serde_as(deserialize_as = "DefaultOnError")]
187 #[schemars(extend("x-deserialize-default-on-error" = true))]
188 #[serde(default)]
189 #[serde(rename = "_meta")]
190 pub meta: Option<Meta>,
191}
192
193impl StringPropertySchema {
194 #[must_use]
196 pub fn new() -> Self {
197 Self::default()
198 }
199
200 #[must_use]
202 pub fn email() -> Self {
203 Self {
204 format: Some(StringFormat::Email),
205 ..Default::default()
206 }
207 }
208
209 #[must_use]
211 pub fn uri() -> Self {
212 Self {
213 format: Some(StringFormat::Uri),
214 ..Default::default()
215 }
216 }
217
218 #[must_use]
220 pub fn date() -> Self {
221 Self {
222 format: Some(StringFormat::Date),
223 ..Default::default()
224 }
225 }
226
227 #[must_use]
229 pub fn date_time() -> Self {
230 Self {
231 format: Some(StringFormat::DateTime),
232 ..Default::default()
233 }
234 }
235
236 #[must_use]
238 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
239 self.title = title.into_option();
240 self
241 }
242
243 #[must_use]
245 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
246 self.description = description.into_option();
247 self
248 }
249
250 #[must_use]
252 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
253 self.min_length = min_length.into_option();
254 self
255 }
256
257 #[must_use]
259 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
260 self.max_length = max_length.into_option();
261 self
262 }
263
264 #[must_use]
266 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
267 self.pattern = pattern.into_option();
268 self
269 }
270
271 #[must_use]
273 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
274 self.format = format.into_option();
275 self
276 }
277
278 #[must_use]
280 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
281 self.default = default.into_option();
282 self
283 }
284
285 #[must_use]
287 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
288 self.enum_values = enum_values.into_option();
289 self
290 }
291
292 #[must_use]
294 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
295 self.one_of = one_of.into_option();
296 self
297 }
298
299 #[must_use]
305 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
306 self.meta = meta.into_option();
307 self
308 }
309}
310
311#[serde_as]
313#[skip_serializing_none]
314#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
315#[serde(rename_all = "camelCase")]
316#[non_exhaustive]
317pub struct NumberPropertySchema {
318 #[serde_as(deserialize_as = "DefaultOnError")]
320 #[schemars(extend("x-deserialize-default-on-error" = true))]
321 #[serde(default)]
322 pub title: Option<String>,
323 #[serde_as(deserialize_as = "DefaultOnError")]
325 #[schemars(extend("x-deserialize-default-on-error" = true))]
326 #[serde(default)]
327 pub description: Option<String>,
328 #[serde(default)]
330 pub minimum: Option<f64>,
331 #[serde(default)]
333 pub maximum: Option<f64>,
334 #[serde_as(deserialize_as = "DefaultOnError")]
336 #[schemars(extend("x-deserialize-default-on-error" = true))]
337 #[serde(default)]
338 pub default: Option<f64>,
339 #[serde_as(deserialize_as = "DefaultOnError")]
345 #[schemars(extend("x-deserialize-default-on-error" = true))]
346 #[serde(default)]
347 #[serde(rename = "_meta")]
348 pub meta: Option<Meta>,
349}
350
351impl NumberPropertySchema {
352 #[must_use]
354 pub fn new() -> Self {
355 Self::default()
356 }
357
358 #[must_use]
360 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
361 self.title = title.into_option();
362 self
363 }
364
365 #[must_use]
367 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
368 self.description = description.into_option();
369 self
370 }
371
372 #[must_use]
374 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
375 self.minimum = minimum.into_option();
376 self
377 }
378
379 #[must_use]
381 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
382 self.maximum = maximum.into_option();
383 self
384 }
385
386 #[must_use]
388 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
389 self.default = default.into_option();
390 self
391 }
392
393 #[must_use]
399 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
400 self.meta = meta.into_option();
401 self
402 }
403}
404
405#[serde_as]
407#[skip_serializing_none]
408#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
409#[serde(rename_all = "camelCase")]
410#[non_exhaustive]
411pub struct IntegerPropertySchema {
412 #[serde_as(deserialize_as = "DefaultOnError")]
414 #[schemars(extend("x-deserialize-default-on-error" = true))]
415 #[serde(default)]
416 pub title: Option<String>,
417 #[serde_as(deserialize_as = "DefaultOnError")]
419 #[schemars(extend("x-deserialize-default-on-error" = true))]
420 #[serde(default)]
421 pub description: Option<String>,
422 #[serde(default)]
424 pub minimum: Option<i64>,
425 #[serde(default)]
427 pub maximum: Option<i64>,
428 #[serde_as(deserialize_as = "DefaultOnError")]
430 #[schemars(extend("x-deserialize-default-on-error" = true))]
431 #[serde(default)]
432 pub default: Option<i64>,
433 #[serde_as(deserialize_as = "DefaultOnError")]
439 #[schemars(extend("x-deserialize-default-on-error" = true))]
440 #[serde(default)]
441 #[serde(rename = "_meta")]
442 pub meta: Option<Meta>,
443}
444
445impl IntegerPropertySchema {
446 #[must_use]
448 pub fn new() -> Self {
449 Self::default()
450 }
451
452 #[must_use]
454 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
455 self.title = title.into_option();
456 self
457 }
458
459 #[must_use]
461 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
462 self.description = description.into_option();
463 self
464 }
465
466 #[must_use]
468 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
469 self.minimum = minimum.into_option();
470 self
471 }
472
473 #[must_use]
475 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
476 self.maximum = maximum.into_option();
477 self
478 }
479
480 #[must_use]
482 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
483 self.default = default.into_option();
484 self
485 }
486
487 #[must_use]
493 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
494 self.meta = meta.into_option();
495 self
496 }
497}
498
499#[serde_as]
501#[skip_serializing_none]
502#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
503#[serde(rename_all = "camelCase")]
504#[non_exhaustive]
505pub struct BooleanPropertySchema {
506 #[serde_as(deserialize_as = "DefaultOnError")]
508 #[schemars(extend("x-deserialize-default-on-error" = true))]
509 #[serde(default)]
510 pub title: Option<String>,
511 #[serde_as(deserialize_as = "DefaultOnError")]
513 #[schemars(extend("x-deserialize-default-on-error" = true))]
514 #[serde(default)]
515 pub description: Option<String>,
516 #[serde_as(deserialize_as = "DefaultOnError")]
518 #[schemars(extend("x-deserialize-default-on-error" = true))]
519 #[serde(default)]
520 pub default: Option<bool>,
521 #[serde_as(deserialize_as = "DefaultOnError")]
527 #[schemars(extend("x-deserialize-default-on-error" = true))]
528 #[serde(default)]
529 #[serde(rename = "_meta")]
530 pub meta: Option<Meta>,
531}
532
533impl BooleanPropertySchema {
534 #[must_use]
536 pub fn new() -> Self {
537 Self::default()
538 }
539
540 #[must_use]
542 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
543 self.title = title.into_option();
544 self
545 }
546
547 #[must_use]
549 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
550 self.description = description.into_option();
551 self
552 }
553
554 #[must_use]
556 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
557 self.default = default.into_option();
558 self
559 }
560
561 #[must_use]
567 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
568 self.meta = meta.into_option();
569 self
570 }
571}
572
573#[serde_as]
575#[skip_serializing_none]
576#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
577#[non_exhaustive]
578pub struct StringMultiSelectItems {
579 #[schemars(length(min = 1))]
581 #[serde(rename = "enum")]
582 pub values: Vec<String>,
583 #[serde_as(deserialize_as = "DefaultOnError")]
589 #[schemars(extend("x-deserialize-default-on-error" = true))]
590 #[serde(default)]
591 #[serde(rename = "_meta")]
592 pub meta: Option<Meta>,
593}
594
595impl StringMultiSelectItems {
596 #[must_use]
598 pub fn new(values: Vec<String>) -> Self {
599 Self { values, meta: None }
600 }
601
602 #[must_use]
608 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
609 self.meta = meta.into_option();
610 self
611 }
612}
613
614#[serde_as]
616#[skip_serializing_none]
617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
618#[non_exhaustive]
619pub struct TitledMultiSelectItems {
620 #[schemars(length(min = 1))]
622 #[serde(rename = "anyOf")]
623 pub options: Vec<EnumOption>,
624 #[serde_as(deserialize_as = "DefaultOnError")]
630 #[schemars(extend("x-deserialize-default-on-error" = true))]
631 #[serde(default)]
632 #[serde(rename = "_meta")]
633 pub meta: Option<Meta>,
634}
635
636impl TitledMultiSelectItems {
637 #[must_use]
639 pub fn new(options: Vec<EnumOption>) -> Self {
640 Self {
641 options,
642 meta: None,
643 }
644 }
645
646 #[must_use]
652 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
653 self.meta = meta.into_option();
654 self
655 }
656}
657
658#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
664#[schemars(inline)]
665#[schemars(transform = other_multi_select_items_schema)]
666#[serde(rename_all = "camelCase")]
667#[non_exhaustive]
668pub struct OtherMultiSelectItems {
669 #[serde(rename = "type")]
675 pub type_: String,
676 #[serde(flatten)]
678 pub fields: BTreeMap<String, serde_json::Value>,
679}
680
681impl OtherMultiSelectItems {
682 #[must_use]
684 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
685 fields.remove("type");
686 Self {
687 type_: type_.into(),
688 fields,
689 }
690 }
691}
692
693impl<'de> Deserialize<'de> for OtherMultiSelectItems {
694 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
695 where
696 D: serde::Deserializer<'de>,
697 {
698 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
699 let type_ = fields
700 .remove("type")
701 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
702 let serde_json::Value::String(type_) = type_ else {
703 return Err(serde::de::Error::custom("`type` must be a string"));
704 };
705
706 if is_known_multi_select_item_type(&type_) {
707 return Err(serde::de::Error::custom(format!(
708 "known multi-select item type `{type_}` did not match its schema"
709 )));
710 }
711
712 Ok(Self { type_, fields })
713 }
714}
715
716const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
717
718fn is_known_multi_select_item_type(type_: &str) -> bool {
719 KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
720}
721
722fn other_multi_select_items_schema(schema: &mut Schema) {
723 super::schema_util::reject_known_string_discriminators(
724 schema,
725 "type",
726 KNOWN_MULTI_SELECT_ITEM_TYPES,
727 );
728}
729
730#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
732#[serde(tag = "type", rename_all = "snake_case")]
733#[schemars(extend("discriminator" = {"propertyName": "type"}))]
734#[non_exhaustive]
735pub enum MultiSelectItems {
736 String(StringMultiSelectItems),
738 #[serde(untagged)]
740 Other(OtherMultiSelectItems),
741 #[serde(untagged)]
743 Titled(TitledMultiSelectItems),
744}
745
746#[serde_as]
748#[skip_serializing_none]
749#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
750#[serde(rename_all = "camelCase")]
751#[non_exhaustive]
752pub struct MultiSelectPropertySchema {
753 #[serde_as(deserialize_as = "DefaultOnError")]
755 #[schemars(extend("x-deserialize-default-on-error" = true))]
756 #[serde(default)]
757 pub title: Option<String>,
758 #[serde_as(deserialize_as = "DefaultOnError")]
760 #[schemars(extend("x-deserialize-default-on-error" = true))]
761 #[serde(default)]
762 pub description: Option<String>,
763 #[serde(default)]
765 pub min_items: Option<u64>,
766 #[serde(default)]
768 pub max_items: Option<u64>,
769 pub items: MultiSelectItems,
771 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
773 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
774 #[serde(default)]
775 pub default: Option<Vec<String>>,
776 #[serde_as(deserialize_as = "DefaultOnError")]
782 #[schemars(extend("x-deserialize-default-on-error" = true))]
783 #[serde(default)]
784 #[serde(rename = "_meta")]
785 pub meta: Option<Meta>,
786}
787
788impl MultiSelectPropertySchema {
789 #[must_use]
791 pub fn new(values: Vec<String>) -> Self {
792 Self {
793 title: None,
794 description: None,
795 min_items: None,
796 max_items: None,
797 items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
798 default: None,
799 meta: None,
800 }
801 }
802
803 #[must_use]
805 pub fn titled(options: Vec<EnumOption>) -> Self {
806 Self {
807 title: None,
808 description: None,
809 min_items: None,
810 max_items: None,
811 items: MultiSelectItems::Titled(TitledMultiSelectItems {
812 options,
813 meta: None,
814 }),
815 default: None,
816 meta: None,
817 }
818 }
819
820 #[must_use]
822 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
823 self.title = title.into_option();
824 self
825 }
826
827 #[must_use]
829 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
830 self.description = description.into_option();
831 self
832 }
833
834 #[must_use]
836 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
837 self.min_items = min_items.into_option();
838 self
839 }
840
841 #[must_use]
843 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
844 self.max_items = max_items.into_option();
845 self
846 }
847
848 #[must_use]
850 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
851 self.default = default.into_option();
852 self
853 }
854
855 #[must_use]
861 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
862 self.meta = meta.into_option();
863 self
864 }
865}
866
867#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
873#[serde(tag = "type", rename_all = "snake_case")]
874#[schemars(extend("discriminator" = {"propertyName": "type"}))]
875#[non_exhaustive]
876pub enum ElicitationPropertySchema {
877 String(StringPropertySchema),
879 Number(NumberPropertySchema),
881 Integer(IntegerPropertySchema),
883 Boolean(BooleanPropertySchema),
885 Array(MultiSelectPropertySchema),
887 #[serde(untagged)]
897 Other(OtherElicitationPropertySchema),
898}
899
900#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
906#[schemars(inline)]
907#[schemars(transform = other_elicitation_property_schema_schema)]
908#[serde(rename_all = "camelCase")]
909#[non_exhaustive]
910pub struct OtherElicitationPropertySchema {
911 #[serde(rename = "type")]
917 pub type_: String,
918 #[serde(flatten)]
920 pub fields: BTreeMap<String, serde_json::Value>,
921}
922
923impl OtherElicitationPropertySchema {
924 #[must_use]
926 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
927 fields.remove("type");
928 Self {
929 type_: type_.into(),
930 fields,
931 }
932 }
933}
934
935impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
936 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
937 where
938 D: serde::Deserializer<'de>,
939 {
940 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
941 let type_ = fields
942 .remove("type")
943 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
944 let serde_json::Value::String(type_) = type_ else {
945 return Err(serde::de::Error::custom("`type` must be a string"));
946 };
947
948 if is_known_elicitation_property_schema_type(&type_) {
949 return Err(serde::de::Error::custom(format!(
950 "known elicitation property schema type `{type_}` did not match its schema"
951 )));
952 }
953
954 Ok(Self { type_, fields })
955 }
956}
957
958const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
959 &["string", "number", "integer", "boolean", "array"];
960
961fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
962 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
963}
964
965fn other_elicitation_property_schema_schema(schema: &mut Schema) {
966 super::schema_util::reject_known_string_discriminators(
967 schema,
968 "type",
969 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES,
970 );
971}
972
973impl From<StringPropertySchema> for ElicitationPropertySchema {
974 fn from(schema: StringPropertySchema) -> Self {
975 Self::String(schema)
976 }
977}
978
979impl From<NumberPropertySchema> for ElicitationPropertySchema {
980 fn from(schema: NumberPropertySchema) -> Self {
981 Self::Number(schema)
982 }
983}
984
985impl From<IntegerPropertySchema> for ElicitationPropertySchema {
986 fn from(schema: IntegerPropertySchema) -> Self {
987 Self::Integer(schema)
988 }
989}
990
991impl From<BooleanPropertySchema> for ElicitationPropertySchema {
992 fn from(schema: BooleanPropertySchema) -> Self {
993 Self::Boolean(schema)
994 }
995}
996
997impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
998 fn from(schema: MultiSelectPropertySchema) -> Self {
999 Self::Array(schema)
1000 }
1001}
1002
1003fn default_object_type() -> ElicitationSchemaType {
1004 ElicitationSchemaType::Object
1005}
1006
1007#[serde_as]
1012#[skip_serializing_none]
1013#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1014#[serde(rename_all = "camelCase")]
1015#[non_exhaustive]
1016pub struct ElicitationSchema {
1017 #[serde_as(deserialize_as = "DefaultOnError")]
1019 #[schemars(extend("x-deserialize-default-on-error" = true))]
1020 #[serde(rename = "type", default = "default_object_type")]
1021 pub type_: ElicitationSchemaType,
1022 #[serde_as(deserialize_as = "DefaultOnError")]
1024 #[schemars(extend("x-deserialize-default-on-error" = true))]
1025 #[serde(default)]
1026 pub title: Option<String>,
1027 #[serde(default)]
1029 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1030 #[serde(default)]
1032 pub required: Option<Vec<String>>,
1033 #[serde_as(deserialize_as = "DefaultOnError")]
1035 #[schemars(extend("x-deserialize-default-on-error" = true))]
1036 #[serde(default)]
1037 pub description: Option<String>,
1038 #[serde_as(deserialize_as = "DefaultOnError")]
1044 #[schemars(extend("x-deserialize-default-on-error" = true))]
1045 #[serde(default)]
1046 #[serde(rename = "_meta")]
1047 pub meta: Option<Meta>,
1048}
1049
1050impl Default for ElicitationSchema {
1051 fn default() -> Self {
1052 Self {
1053 type_: default_object_type(),
1054 title: None,
1055 properties: BTreeMap::new(),
1056 required: None,
1057 description: None,
1058 meta: None,
1059 }
1060 }
1061}
1062
1063impl ElicitationSchema {
1064 #[must_use]
1066 pub fn new() -> Self {
1067 Self::default()
1068 }
1069
1070 #[must_use]
1072 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1073 self.title = title.into_option();
1074 self
1075 }
1076
1077 #[must_use]
1079 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1080 self.description = description.into_option();
1081 self
1082 }
1083
1084 #[must_use]
1090 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1091 self.meta = meta.into_option();
1092 self
1093 }
1094
1095 #[must_use]
1097 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1098 where
1099 S: Into<ElicitationPropertySchema>,
1100 {
1101 let name = name.into();
1102 self.properties.insert(name.clone(), schema.into());
1103
1104 if required {
1105 let required_fields = self.required.get_or_insert_with(Vec::new);
1106 if !required_fields.contains(&name) {
1107 required_fields.push(name);
1108 }
1109 } else if let Some(required_fields) = &mut self.required {
1110 required_fields.retain(|field| field != &name);
1111
1112 if required_fields.is_empty() {
1113 self.required = None;
1114 }
1115 }
1116
1117 self
1118 }
1119
1120 #[must_use]
1122 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1123 self.property(name, StringPropertySchema::new(), required)
1124 }
1125
1126 #[must_use]
1128 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1129 self.property(name, StringPropertySchema::email(), required)
1130 }
1131
1132 #[must_use]
1134 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1135 self.property(name, StringPropertySchema::uri(), required)
1136 }
1137
1138 #[must_use]
1140 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1141 self.property(name, StringPropertySchema::date(), required)
1142 }
1143
1144 #[must_use]
1146 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1147 self.property(name, StringPropertySchema::date_time(), required)
1148 }
1149
1150 #[must_use]
1152 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1153 self.property(
1154 name,
1155 NumberPropertySchema::new().minimum(min).maximum(max),
1156 required,
1157 )
1158 }
1159
1160 #[must_use]
1162 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1163 self.property(
1164 name,
1165 IntegerPropertySchema::new().minimum(min).maximum(max),
1166 required,
1167 )
1168 }
1169
1170 #[must_use]
1172 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1173 self.property(name, BooleanPropertySchema::new(), required)
1174 }
1175}
1176
1177#[serde_as]
1183#[skip_serializing_none]
1184#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1185#[serde(rename_all = "camelCase")]
1186#[non_exhaustive]
1187pub struct ElicitationCapabilities {
1188 #[serde_as(deserialize_as = "DefaultOnError")]
1193 #[schemars(extend("x-deserialize-default-on-error" = true))]
1194 #[serde(default)]
1195 pub form: Option<ElicitationFormCapabilities>,
1196 #[serde_as(deserialize_as = "DefaultOnError")]
1201 #[schemars(extend("x-deserialize-default-on-error" = true))]
1202 #[serde(default)]
1203 pub url: Option<ElicitationUrlCapabilities>,
1204 #[serde_as(deserialize_as = "DefaultOnError")]
1210 #[schemars(extend("x-deserialize-default-on-error" = true))]
1211 #[serde(default)]
1212 #[serde(rename = "_meta")]
1213 pub meta: Option<Meta>,
1214}
1215
1216impl ElicitationCapabilities {
1217 #[must_use]
1219 pub fn new() -> Self {
1220 Self::default()
1221 }
1222
1223 #[must_use]
1228 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1229 self.form = form.into_option();
1230 self
1231 }
1232
1233 #[must_use]
1238 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1239 self.url = url.into_option();
1240 self
1241 }
1242
1243 #[must_use]
1249 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1250 self.meta = meta.into_option();
1251 self
1252 }
1253}
1254
1255#[serde_as]
1263#[skip_serializing_none]
1264#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1265#[serde(rename_all = "camelCase")]
1266#[non_exhaustive]
1267pub struct ElicitationFormCapabilities {
1268 #[serde_as(deserialize_as = "DefaultOnError")]
1274 #[schemars(extend("x-deserialize-default-on-error" = true))]
1275 #[serde(default)]
1276 #[serde(rename = "_meta")]
1277 pub meta: Option<Meta>,
1278}
1279
1280impl ElicitationFormCapabilities {
1281 #[must_use]
1283 pub fn new() -> Self {
1284 Self::default()
1285 }
1286
1287 #[must_use]
1293 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1294 self.meta = meta.into_option();
1295 self
1296 }
1297}
1298
1299#[serde_as]
1307#[skip_serializing_none]
1308#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1309#[serde(rename_all = "camelCase")]
1310#[non_exhaustive]
1311pub struct ElicitationUrlCapabilities {
1312 #[serde_as(deserialize_as = "DefaultOnError")]
1318 #[schemars(extend("x-deserialize-default-on-error" = true))]
1319 #[serde(default)]
1320 #[serde(rename = "_meta")]
1321 pub meta: Option<Meta>,
1322}
1323
1324impl ElicitationUrlCapabilities {
1325 #[must_use]
1327 pub fn new() -> Self {
1328 Self::default()
1329 }
1330
1331 #[must_use]
1337 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1338 self.meta = meta.into_option();
1339 self
1340 }
1341}
1342
1343#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1349#[serde(untagged)]
1350#[non_exhaustive]
1351pub enum ElicitationScope {
1352 Session(ElicitationSessionScope),
1354 Request(ElicitationRequestScope),
1357}
1358
1359#[serde_as]
1369#[skip_serializing_none]
1370#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1371#[serde(rename_all = "camelCase")]
1372#[non_exhaustive]
1373pub struct ElicitationSessionScope {
1374 pub session_id: SessionId,
1376 #[serde_as(deserialize_as = "DefaultOnError")]
1378 #[schemars(extend("x-deserialize-default-on-error" = true))]
1379 #[serde(default)]
1380 pub tool_call_id: Option<ToolCallId>,
1381}
1382
1383impl ElicitationSessionScope {
1384 #[must_use]
1386 pub fn new(session_id: impl Into<SessionId>) -> Self {
1387 Self {
1388 session_id: session_id.into(),
1389 tool_call_id: None,
1390 }
1391 }
1392
1393 #[must_use]
1395 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1396 self.tool_call_id = tool_call_id.into_option();
1397 self
1398 }
1399}
1400
1401#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1408#[serde(rename_all = "camelCase")]
1409#[non_exhaustive]
1410pub struct ElicitationRequestScope {
1411 pub request_id: RequestId,
1413}
1414
1415impl ElicitationRequestScope {
1416 #[must_use]
1418 pub fn new(request_id: impl Into<RequestId>) -> Self {
1419 Self {
1420 request_id: request_id.into(),
1421 }
1422 }
1423}
1424
1425impl From<ElicitationSessionScope> for ElicitationScope {
1426 fn from(scope: ElicitationSessionScope) -> Self {
1427 Self::Session(scope)
1428 }
1429}
1430
1431impl From<ElicitationRequestScope> for ElicitationScope {
1432 fn from(scope: ElicitationRequestScope) -> Self {
1433 Self::Request(scope)
1434 }
1435}
1436
1437#[serde_as]
1447#[skip_serializing_none]
1448#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1449#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1450#[serde(rename_all = "camelCase")]
1451#[non_exhaustive]
1452pub struct CreateElicitationRequest {
1453 #[serde(flatten)]
1455 pub mode: ElicitationMode,
1456 pub message: String,
1458 #[serde_as(deserialize_as = "DefaultOnError")]
1464 #[schemars(extend("x-deserialize-default-on-error" = true))]
1465 #[serde(default)]
1466 #[serde(rename = "_meta")]
1467 pub meta: Option<Meta>,
1468}
1469
1470impl CreateElicitationRequest {
1471 #[must_use]
1473 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1474 Self {
1475 mode: mode.into(),
1476 message: message.into(),
1477 meta: None,
1478 }
1479 }
1480
1481 #[must_use]
1483 pub fn scope(&self) -> &ElicitationScope {
1484 self.mode.scope()
1485 }
1486
1487 #[must_use]
1493 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1494 self.meta = meta.into_option();
1495 self
1496 }
1497}
1498
1499#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1505#[serde(tag = "mode", rename_all = "snake_case")]
1506#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1507#[non_exhaustive]
1508pub enum ElicitationMode {
1509 Form(ElicitationFormMode),
1511 Url(ElicitationUrlMode),
1513 #[serde(untagged)]
1523 Other(OtherElicitationMode),
1524}
1525
1526#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1532#[schemars(inline)]
1533#[schemars(transform = other_elicitation_mode_schema)]
1534#[serde(rename_all = "camelCase")]
1535#[non_exhaustive]
1536pub struct OtherElicitationMode {
1537 pub mode: String,
1543 #[serde(flatten)]
1545 pub scope: ElicitationScope,
1546 #[serde(flatten)]
1548 pub fields: BTreeMap<String, serde_json::Value>,
1549}
1550
1551impl OtherElicitationMode {
1552 #[must_use]
1554 pub fn new(
1555 mode: impl Into<String>,
1556 scope: impl Into<ElicitationScope>,
1557 mut fields: BTreeMap<String, serde_json::Value>,
1558 ) -> Self {
1559 fields.remove("mode");
1560 remove_elicitation_scope_fields(&mut fields);
1561 Self {
1562 mode: mode.into(),
1563 scope: scope.into(),
1564 fields,
1565 }
1566 }
1567}
1568
1569impl<'de> Deserialize<'de> for OtherElicitationMode {
1570 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1571 where
1572 D: serde::Deserializer<'de>,
1573 {
1574 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1575 let mode = fields
1576 .remove("mode")
1577 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1578 let serde_json::Value::String(mode) = mode else {
1579 return Err(serde::de::Error::custom("`mode` must be a string"));
1580 };
1581
1582 if is_known_elicitation_mode(&mode) {
1583 return Err(serde::de::Error::custom(format!(
1584 "known elicitation mode `{mode}` did not match its schema"
1585 )));
1586 }
1587
1588 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1589 fields.clone().into_iter().collect(),
1590 ))
1591 .map_err(serde::de::Error::custom)?;
1592 remove_elicitation_scope_fields(&mut fields);
1593
1594 Ok(Self {
1595 mode,
1596 scope,
1597 fields,
1598 })
1599 }
1600}
1601
1602const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1603
1604fn is_known_elicitation_mode(mode: &str) -> bool {
1605 KNOWN_ELICITATION_MODES.contains(&mode)
1606}
1607
1608fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1609 fields.remove("sessionId");
1610 fields.remove("toolCallId");
1611 fields.remove("requestId");
1612}
1613
1614fn other_elicitation_mode_schema(schema: &mut Schema) {
1615 super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1616}
1617
1618impl From<ElicitationFormMode> for ElicitationMode {
1619 fn from(mode: ElicitationFormMode) -> Self {
1620 Self::Form(mode)
1621 }
1622}
1623
1624impl From<ElicitationUrlMode> for ElicitationMode {
1625 fn from(mode: ElicitationUrlMode) -> Self {
1626 Self::Url(mode)
1627 }
1628}
1629
1630impl From<OtherElicitationMode> for ElicitationMode {
1631 fn from(mode: OtherElicitationMode) -> Self {
1632 Self::Other(mode)
1633 }
1634}
1635
1636impl ElicitationMode {
1637 #[must_use]
1639 pub fn scope(&self) -> &ElicitationScope {
1640 match self {
1641 Self::Form(f) => &f.scope,
1642 Self::Url(u) => &u.scope,
1643 Self::Other(other) => &other.scope,
1644 }
1645 }
1646}
1647
1648#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1654#[serde(rename_all = "camelCase")]
1655#[non_exhaustive]
1656pub struct ElicitationFormMode {
1657 #[serde(flatten)]
1659 pub scope: ElicitationScope,
1660 pub requested_schema: ElicitationSchema,
1662}
1663
1664impl ElicitationFormMode {
1665 #[must_use]
1667 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1668 Self {
1669 scope: scope.into(),
1670 requested_schema,
1671 }
1672 }
1673}
1674
1675#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1681#[serde(rename_all = "camelCase")]
1682#[non_exhaustive]
1683pub struct ElicitationUrlMode {
1684 #[serde(flatten)]
1686 pub scope: ElicitationScope,
1687 pub elicitation_id: ElicitationId,
1689 #[schemars(url)]
1691 pub url: String,
1692}
1693
1694impl ElicitationUrlMode {
1695 #[must_use]
1697 pub fn new(
1698 scope: impl Into<ElicitationScope>,
1699 elicitation_id: impl Into<ElicitationId>,
1700 url: impl Into<String>,
1701 ) -> Self {
1702 Self {
1703 scope: scope.into(),
1704 elicitation_id: elicitation_id.into(),
1705 url: url.into(),
1706 }
1707 }
1708}
1709
1710#[serde_as]
1716#[skip_serializing_none]
1717#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1718#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1719#[serde(rename_all = "camelCase")]
1720#[non_exhaustive]
1721pub struct CreateElicitationResponse {
1722 #[serde(flatten)]
1724 pub action: ElicitationAction,
1725 #[serde_as(deserialize_as = "DefaultOnError")]
1731 #[schemars(extend("x-deserialize-default-on-error" = true))]
1732 #[serde(default)]
1733 #[serde(rename = "_meta")]
1734 pub meta: Option<Meta>,
1735}
1736
1737impl CreateElicitationResponse {
1738 #[must_use]
1740 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1741 Self {
1742 action: action.into(),
1743 meta: None,
1744 }
1745 }
1746
1747 #[must_use]
1753 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1754 self.meta = meta.into_option();
1755 self
1756 }
1757}
1758
1759#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1765#[serde(tag = "action", rename_all = "snake_case")]
1766#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1767#[non_exhaustive]
1768pub enum ElicitationAction {
1769 Accept(ElicitationAcceptAction),
1771 Decline,
1773 Cancel,
1775 #[serde(untagged)]
1785 Other(OtherElicitationAction),
1786}
1787
1788#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1794#[schemars(inline)]
1795#[schemars(transform = other_elicitation_action_schema)]
1796#[serde(rename_all = "camelCase")]
1797#[non_exhaustive]
1798pub struct OtherElicitationAction {
1799 pub action: String,
1805 #[serde(flatten)]
1807 pub fields: BTreeMap<String, serde_json::Value>,
1808}
1809
1810impl OtherElicitationAction {
1811 #[must_use]
1813 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1814 fields.remove("action");
1815 Self {
1816 action: action.into(),
1817 fields,
1818 }
1819 }
1820}
1821
1822impl<'de> Deserialize<'de> for OtherElicitationAction {
1823 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1824 where
1825 D: serde::Deserializer<'de>,
1826 {
1827 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1828 let action = fields
1829 .remove("action")
1830 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1831 let serde_json::Value::String(action) = action else {
1832 return Err(serde::de::Error::custom("`action` must be a string"));
1833 };
1834
1835 if is_known_elicitation_action(&action) {
1836 return Err(serde::de::Error::custom(format!(
1837 "known elicitation action `{action}` did not match its schema"
1838 )));
1839 }
1840
1841 Ok(Self { action, fields })
1842 }
1843}
1844
1845const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1846
1847fn is_known_elicitation_action(action: &str) -> bool {
1848 KNOWN_ELICITATION_ACTIONS.contains(&action)
1849}
1850
1851fn other_elicitation_action_schema(schema: &mut Schema) {
1852 super::schema_util::reject_known_string_discriminators(
1853 schema,
1854 "action",
1855 KNOWN_ELICITATION_ACTIONS,
1856 );
1857}
1858
1859impl From<ElicitationAcceptAction> for ElicitationAction {
1860 fn from(action: ElicitationAcceptAction) -> Self {
1861 Self::Accept(action)
1862 }
1863}
1864
1865impl From<OtherElicitationAction> for ElicitationAction {
1866 fn from(action: OtherElicitationAction) -> Self {
1867 Self::Other(action)
1868 }
1869}
1870
1871#[serde_as]
1877#[skip_serializing_none]
1878#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1879#[serde(rename_all = "camelCase")]
1880#[non_exhaustive]
1881pub struct ElicitationAcceptAction {
1882 #[serde(default)]
1884 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1885}
1886
1887impl ElicitationAcceptAction {
1888 #[must_use]
1890 pub fn new() -> Self {
1891 Self { content: None }
1892 }
1893
1894 #[must_use]
1896 pub fn content(
1897 mut self,
1898 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1899 ) -> Self {
1900 self.content = content.into_option();
1901 self
1902 }
1903}
1904
1905#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1907#[serde(untagged)]
1908#[non_exhaustive]
1909pub enum ElicitationContentValue {
1910 String(String),
1912 Integer(i64),
1914 Number(f64),
1916 Boolean(bool),
1918 StringArray(Vec<String>),
1920}
1921
1922impl From<String> for ElicitationContentValue {
1923 fn from(value: String) -> Self {
1924 Self::String(value)
1925 }
1926}
1927
1928impl From<&str> for ElicitationContentValue {
1929 fn from(value: &str) -> Self {
1930 Self::String(value.to_string())
1931 }
1932}
1933
1934impl From<i64> for ElicitationContentValue {
1935 fn from(value: i64) -> Self {
1936 Self::Integer(value)
1937 }
1938}
1939
1940impl From<i32> for ElicitationContentValue {
1941 fn from(value: i32) -> Self {
1942 Self::Integer(i64::from(value))
1943 }
1944}
1945
1946impl From<f64> for ElicitationContentValue {
1947 fn from(value: f64) -> Self {
1948 Self::Number(value)
1949 }
1950}
1951
1952impl From<bool> for ElicitationContentValue {
1953 fn from(value: bool) -> Self {
1954 Self::Boolean(value)
1955 }
1956}
1957
1958impl From<Vec<String>> for ElicitationContentValue {
1959 fn from(value: Vec<String>) -> Self {
1960 Self::StringArray(value)
1961 }
1962}
1963
1964impl From<Vec<&str>> for ElicitationContentValue {
1965 fn from(value: Vec<&str>) -> Self {
1966 Self::StringArray(value.into_iter().map(str::to_string).collect())
1967 }
1968}
1969
1970impl Default for ElicitationAcceptAction {
1971 fn default() -> Self {
1972 Self::new()
1973 }
1974}
1975
1976#[serde_as]
1982#[skip_serializing_none]
1983#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1984#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1985#[serde(rename_all = "camelCase")]
1986#[non_exhaustive]
1987pub struct CompleteElicitationNotification {
1988 pub elicitation_id: ElicitationId,
1990 #[serde_as(deserialize_as = "DefaultOnError")]
1996 #[schemars(extend("x-deserialize-default-on-error" = true))]
1997 #[serde(default)]
1998 #[serde(rename = "_meta")]
1999 pub meta: Option<Meta>,
2000}
2001
2002impl CompleteElicitationNotification {
2003 #[must_use]
2005 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2006 Self {
2007 elicitation_id: elicitation_id.into(),
2008 meta: None,
2009 }
2010 }
2011
2012 #[must_use]
2018 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2019 self.meta = meta.into_option();
2020 self
2021 }
2022}
2023
2024#[cfg(test)]
2025mod tests {
2026 use super::*;
2027 use serde_json::json;
2028
2029 #[test]
2030 fn form_mode_request_serialization() {
2031 let schema = ElicitationSchema::new().string("name", true);
2032 let req = CreateElicitationRequest::new(
2033 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2034 "Please enter your name",
2035 );
2036
2037 let json = serde_json::to_value(&req).unwrap();
2038 assert_eq!(json["sessionId"], "sess_1");
2039 assert!(json.get("toolCallId").is_none());
2040 assert_eq!(json["mode"], "form");
2041 assert_eq!(json["message"], "Please enter your name");
2042 assert!(json["requestedSchema"].is_object());
2043 assert_eq!(json["requestedSchema"]["type"], "object");
2044 assert_eq!(
2045 json["requestedSchema"]["properties"]["name"]["type"],
2046 "string"
2047 );
2048
2049 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2050 assert_eq!(
2051 *roundtripped.scope(),
2052 ElicitationSessionScope::new("sess_1").into()
2053 );
2054 assert_eq!(roundtripped.message, "Please enter your name");
2055 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2056 }
2057
2058 #[test]
2059 fn url_mode_request_serialization() {
2060 let req = CreateElicitationRequest::new(
2061 ElicitationUrlMode::new(
2062 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2063 "elic_1",
2064 "https://example.com/auth",
2065 ),
2066 "Please authenticate",
2067 );
2068
2069 let json = serde_json::to_value(&req).unwrap();
2070 assert_eq!(json["sessionId"], "sess_2");
2071 assert_eq!(json["toolCallId"], "tc_1");
2072 assert_eq!(json["mode"], "url");
2073 assert_eq!(json["elicitationId"], "elic_1");
2074 assert_eq!(json["url"], "https://example.com/auth");
2075 assert_eq!(json["message"], "Please authenticate");
2076
2077 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2078 assert_eq!(
2079 *roundtripped.scope(),
2080 ElicitationSessionScope::new("sess_2")
2081 .tool_call_id("tc_1")
2082 .into()
2083 );
2084 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2085 }
2086
2087 #[test]
2088 fn response_accept_serialization() {
2089 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2090 ElicitationAcceptAction::new().content(BTreeMap::from([(
2091 "name".to_string(),
2092 ElicitationContentValue::from("Alice"),
2093 )])),
2094 ));
2095
2096 let json = serde_json::to_value(&resp).unwrap();
2097 assert_eq!(json["action"], "accept");
2098 assert_eq!(json["content"]["name"], "Alice");
2099
2100 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2101 assert!(matches!(
2102 roundtripped.action,
2103 ElicitationAction::Accept(ElicitationAcceptAction {
2104 content: Some(_),
2105 ..
2106 })
2107 ));
2108 }
2109
2110 #[test]
2111 fn response_decline_serialization() {
2112 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2113
2114 let json = serde_json::to_value(&resp).unwrap();
2115 assert_eq!(json["action"], "decline");
2116
2117 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2118 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2119 }
2120
2121 #[test]
2122 fn response_cancel_serialization() {
2123 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2124
2125 let json = serde_json::to_value(&resp).unwrap();
2126 assert_eq!(json["action"], "cancel");
2127
2128 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2129 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2130 }
2131
2132 #[test]
2133 fn unknown_action_response_serialization() {
2134 let json = json!({
2135 "action": "_defer",
2136 "reason": "waiting",
2137 "retryAfterMs": 1000
2138 });
2139
2140 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2141 let ElicitationAction::Other(other) = &resp.action else {
2142 panic!("expected unknown elicitation action");
2143 };
2144
2145 assert_eq!(other.action, "_defer");
2146 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2147 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2148 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2149 }
2150
2151 #[test]
2152 fn unknown_action_does_not_hide_known_action() {
2153 assert!(
2154 serde_json::from_value::<OtherElicitationAction>(json!({
2155 "action": "accept",
2156 "content": {}
2157 }))
2158 .is_err()
2159 );
2160 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2161 }
2162
2163 #[test]
2164 fn url_mode_request_scope_serialization() {
2165 let req = CreateElicitationRequest::new(
2166 ElicitationUrlMode::new(
2167 ElicitationRequestScope::new(RequestId::Number(42)),
2168 "elic_2",
2169 "https://example.com/setup",
2170 ),
2171 "Please complete setup",
2172 );
2173
2174 let json = serde_json::to_value(&req).unwrap();
2175 assert_eq!(json["requestId"], 42);
2176 assert!(json.get("sessionId").is_none());
2177 assert_eq!(json["mode"], "url");
2178 assert_eq!(json["elicitationId"], "elic_2");
2179 assert_eq!(json["url"], "https://example.com/setup");
2180 assert_eq!(json["message"], "Please complete setup");
2181
2182 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2183 assert_eq!(
2184 *roundtripped.scope(),
2185 ElicitationRequestScope::new(RequestId::Number(42)).into()
2186 );
2187 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2188 }
2189
2190 #[test]
2191 fn unknown_mode_request_serialization() {
2192 let json = json!({
2193 "requestId": 42,
2194 "mode": "_browser",
2195 "message": "Open a browser window",
2196 "target": "login"
2197 });
2198
2199 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2200 let ElicitationMode::Other(other) = &req.mode else {
2201 panic!("expected unknown elicitation mode");
2202 };
2203
2204 assert_eq!(other.mode, "_browser");
2205 assert_eq!(
2206 other.scope,
2207 ElicitationRequestScope::new(RequestId::Number(42)).into()
2208 );
2209 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2210 assert_eq!(
2211 *req.scope(),
2212 ElicitationRequestScope::new(RequestId::Number(42)).into()
2213 );
2214 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2215 }
2216
2217 #[test]
2218 fn unknown_mode_does_not_hide_malformed_known_mode() {
2219 let missing_requested_schema = json!({
2220 "requestId": 42,
2221 "mode": "form",
2222 "message": "Enter your name"
2223 });
2224
2225 assert!(
2226 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2227 );
2228 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2229 }
2230
2231 #[test]
2232 fn request_scope_request_serialization() {
2233 let req = CreateElicitationRequest::new(
2234 ElicitationFormMode::new(
2235 ElicitationRequestScope::new(RequestId::Number(99)),
2236 ElicitationSchema::new().string("workspace", true),
2237 ),
2238 "Enter workspace name",
2239 );
2240
2241 let json = serde_json::to_value(&req).unwrap();
2242 assert_eq!(json["requestId"], 99);
2243 assert!(json.get("sessionId").is_none());
2244
2245 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2246 assert_eq!(
2247 *roundtripped.scope(),
2248 ElicitationRequestScope::new(RequestId::Number(99)).into()
2249 );
2250 }
2251
2252 #[test]
2256 fn client_response_serialization_accept() {
2257 use crate::v2::ClientResponse;
2258
2259 let resp =
2260 ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2261 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2262 [("name".to_string(), ElicitationContentValue::from("Alice"))],
2263 ))),
2264 )));
2265 let json = serde_json::to_value(&resp).unwrap();
2266 assert_eq!(json["action"], "accept");
2267 assert_eq!(json["content"]["name"], "Alice");
2268
2269 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2271 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2272 }
2273
2274 #[test]
2275 fn client_response_serialization_decline() {
2276 use crate::v2::ClientResponse;
2277
2278 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2279 CreateElicitationResponse::new(ElicitationAction::Decline),
2280 ));
2281 let json = serde_json::to_value(&resp).unwrap();
2282 assert_eq!(json["action"], "decline");
2283
2284 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2285 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2286 }
2287
2288 #[test]
2289 fn client_response_serialization_cancel() {
2290 use crate::v2::ClientResponse;
2291
2292 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2293 CreateElicitationResponse::new(ElicitationAction::Cancel),
2294 ));
2295 let json = serde_json::to_value(&resp).unwrap();
2296 assert_eq!(json["action"], "cancel");
2297
2298 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2299 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2300 }
2301
2302 #[test]
2305 fn request_tolerates_extra_fields() {
2306 let json = json!({
2307 "sessionId": "sess_1",
2308 "mode": "form",
2309 "message": "Enter your name",
2310 "requestedSchema": {
2311 "type": "object",
2312 "properties": {
2313 "name": { "type": "string", "title": "Name" }
2314 },
2315 "required": ["name"]
2316 },
2317 "unknownStringField": "hello",
2318 "unknownNumberField": 42
2319 });
2320
2321 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2322 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2323 assert_eq!(req.message, "Enter your name");
2324 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2325 }
2326
2327 #[test]
2328 fn completion_notification_serialization() {
2329 let notif = CompleteElicitationNotification::new("elic_1");
2330
2331 let json = serde_json::to_value(¬if).unwrap();
2332 assert_eq!(json["elicitationId"], "elic_1");
2333
2334 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2335 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2336 }
2337
2338 #[test]
2339 fn capabilities_form_only() {
2340 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2341
2342 let json = serde_json::to_value(&caps).unwrap();
2343 assert!(json["form"].is_object());
2344 assert!(json.get("url").is_none());
2345
2346 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2347 assert!(roundtripped.form.is_some());
2348 assert!(roundtripped.url.is_none());
2349 }
2350
2351 #[test]
2352 fn capabilities_url_only() {
2353 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2354
2355 let json = serde_json::to_value(&caps).unwrap();
2356 assert!(json.get("form").is_none());
2357 assert!(json["url"].is_object());
2358
2359 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2360 assert!(roundtripped.form.is_none());
2361 assert!(roundtripped.url.is_some());
2362 }
2363
2364 #[test]
2365 fn capabilities_both() {
2366 let caps = ElicitationCapabilities::new()
2367 .form(ElicitationFormCapabilities::new())
2368 .url(ElicitationUrlCapabilities::new());
2369
2370 let json = serde_json::to_value(&caps).unwrap();
2371 assert!(json["form"].is_object());
2372 assert!(json["url"].is_object());
2373
2374 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2375 assert!(roundtripped.form.is_some());
2376 assert!(roundtripped.url.is_some());
2377 }
2378
2379 #[test]
2380 fn schema_default_sets_object_type() {
2381 let schema = ElicitationSchema::default();
2382
2383 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2384 assert!(schema.properties.is_empty());
2385
2386 let json = serde_json::to_value(&schema).unwrap();
2387 assert_eq!(json["type"], "object");
2388 }
2389
2390 #[test]
2391 fn schema_builder_serialization() {
2392 let schema = ElicitationSchema::new()
2393 .string("name", true)
2394 .email("email", true)
2395 .integer("age", 0, 150, true)
2396 .boolean("newsletter", false)
2397 .description("User registration");
2398
2399 let json = serde_json::to_value(&schema).unwrap();
2400 assert_eq!(json["type"], "object");
2401 assert_eq!(json["description"], "User registration");
2402 assert_eq!(json["properties"]["name"]["type"], "string");
2403 assert_eq!(json["properties"]["email"]["type"], "string");
2404 assert_eq!(json["properties"]["email"]["format"], "email");
2405 assert_eq!(json["properties"]["age"]["type"], "integer");
2406 assert_eq!(json["properties"]["age"]["minimum"], 0);
2407 assert_eq!(json["properties"]["age"]["maximum"], 150);
2408 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2409
2410 let required = json["required"].as_array().unwrap();
2411 assert!(required.contains(&json!("name")));
2412 assert!(required.contains(&json!("email")));
2413 assert!(required.contains(&json!("age")));
2414 assert!(!required.contains(&json!("newsletter")));
2415
2416 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2417 assert_eq!(roundtripped.properties.len(), 4);
2418 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2419 }
2420
2421 #[test]
2422 fn schema_string_enum_serialization() {
2423 let schema = ElicitationSchema::new().property(
2424 "color",
2425 StringPropertySchema::new().enum_values(vec![
2426 "red".into(),
2427 "green".into(),
2428 "blue".into(),
2429 ]),
2430 true,
2431 );
2432
2433 let json = serde_json::to_value(&schema).unwrap();
2434 assert_eq!(json["properties"]["color"]["type"], "string");
2435 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2436 assert_eq!(enum_vals.len(), 3);
2437
2438 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2439 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2440 {
2441 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2442 } else {
2443 panic!("expected String variant");
2444 }
2445 }
2446
2447 #[test]
2448 fn schema_multi_select_serialization() {
2449 let schema = ElicitationSchema::new().property(
2450 "colors",
2451 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2452 .min_items(1)
2453 .max_items(3),
2454 false,
2455 );
2456
2457 let json = serde_json::to_value(&schema).unwrap();
2458 assert_eq!(json["properties"]["colors"]["type"], "array");
2459 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2460 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2461 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2462
2463 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2464 let ElicitationPropertySchema::Array(array) =
2465 roundtripped.properties.get("colors").unwrap()
2466 else {
2467 panic!("expected Array variant");
2468 };
2469 let MultiSelectItems::String(items) = &array.items else {
2470 panic!("expected String multi-select items");
2471 };
2472 assert_eq!(items.values.len(), 3);
2473 }
2474
2475 #[test]
2476 fn multi_select_titled_items_keep_mcp_shape() {
2477 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2478 "#ff0000", "Red",
2479 )]));
2480
2481 let json = serde_json::to_value(&items).unwrap();
2482 assert!(json.get("type").is_none());
2483 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2484 assert_eq!(json["anyOf"][0]["title"], "Red");
2485
2486 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2487 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2488 }
2489
2490 #[test]
2491 fn multi_select_items_preserve_unknown_type() {
2492 let json = json!({
2493 "type": "_token",
2494 "format": "workspace",
2495 "anyOf": [
2496 { "const": "repo", "title": "Repository" }
2497 ]
2498 });
2499
2500 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2501 let MultiSelectItems::Other(other) = &items else {
2502 panic!("expected unknown multi-select items");
2503 };
2504
2505 assert_eq!(other.type_, "_token");
2506 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2507 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2508 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2509 }
2510
2511 #[test]
2512 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2513 assert!(
2514 serde_json::from_value::<MultiSelectItems>(json!({
2515 "type": "string"
2516 }))
2517 .is_err()
2518 );
2519 assert!(
2520 serde_json::from_value::<OtherMultiSelectItems>(json!({
2521 "type": "string",
2522 "format": "workspace"
2523 }))
2524 .is_err()
2525 );
2526 }
2527
2528 #[test]
2529 fn property_schema_preserves_unknown_type() {
2530 let schema: ElicitationSchema = serde_json::from_value(json!({
2531 "type": "object",
2532 "properties": {
2533 "location": {
2534 "type": "_location",
2535 "title": "Location",
2536 "precision": "city"
2537 }
2538 }
2539 }))
2540 .unwrap();
2541
2542 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2543 else {
2544 panic!("expected unknown property schema");
2545 };
2546
2547 assert_eq!(unknown.type_, "_location");
2548 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2549 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2550 assert_eq!(
2551 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2552 json!({
2553 "type": "_location",
2554 "title": "Location",
2555 "precision": "city"
2556 })
2557 );
2558 }
2559
2560 #[test]
2561 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2562 assert!(
2563 serde_json::from_value::<ElicitationPropertySchema>(json!({
2564 "type": "array"
2565 }))
2566 .is_err()
2567 );
2568 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2569 }
2570
2571 #[test]
2572 fn schema_titled_enum_serialization() {
2573 let schema = ElicitationSchema::new().property(
2574 "country",
2575 StringPropertySchema::new().one_of(vec![
2576 EnumOption::new("us", "United States").description("Use US English spelling."),
2577 EnumOption::new("uk", "United Kingdom"),
2578 ]),
2579 true,
2580 );
2581
2582 let json = serde_json::to_value(&schema).unwrap();
2583 assert_eq!(json["properties"]["country"]["type"], "string");
2584 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2585 assert_eq!(one_of.len(), 2);
2586 assert_eq!(one_of[0]["const"], "us");
2587 assert_eq!(one_of[0]["title"], "United States");
2588 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2589 assert!(one_of[1].get("description").is_none());
2590
2591 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2592 if let ElicitationPropertySchema::String(s) =
2593 roundtripped.properties.get("country").unwrap()
2594 {
2595 let one_of = s.one_of.as_ref().unwrap();
2596 assert_eq!(one_of.len(), 2);
2597 assert_eq!(
2598 one_of[0].description.as_deref(),
2599 Some("Use US English spelling.")
2600 );
2601 assert!(one_of[1].description.is_none());
2602 } else {
2603 panic!("expected String variant");
2604 }
2605 }
2606
2607 #[test]
2608 fn schema_number_property_serialization() {
2609 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2610
2611 let json = serde_json::to_value(&schema).unwrap();
2612 assert_eq!(json["properties"]["rating"]["type"], "number");
2613 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2614 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2615
2616 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2617 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2618 {
2619 assert_eq!(n.minimum, Some(0.0));
2620 assert_eq!(n.maximum, Some(5.0));
2621 } else {
2622 panic!("expected Number variant");
2623 }
2624 }
2625
2626 #[test]
2627 fn schema_string_format_serialization() {
2628 let schema = ElicitationSchema::new()
2629 .uri("website", true)
2630 .date("birthday", true)
2631 .date_time("updated_at", false);
2632
2633 let json = serde_json::to_value(&schema).unwrap();
2634 assert_eq!(json["properties"]["website"]["type"], "string");
2635 assert_eq!(json["properties"]["website"]["format"], "uri");
2636 assert_eq!(json["properties"]["birthday"]["type"], "string");
2637 assert_eq!(json["properties"]["birthday"]["format"], "date");
2638 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2639 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2640
2641 let required = json["required"].as_array().unwrap();
2642 assert!(required.contains(&json!("website")));
2643 assert!(required.contains(&json!("birthday")));
2644 assert!(!required.contains(&json!("updated_at")));
2645 }
2646
2647 #[test]
2648 fn schema_string_pattern_serialization() {
2649 let schema = ElicitationSchema::new().property(
2650 "name",
2651 StringPropertySchema::new()
2652 .min_length(1)
2653 .max_length(64)
2654 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2655 true,
2656 );
2657
2658 let json = serde_json::to_value(&schema).unwrap();
2659 assert_eq!(json["properties"]["name"]["type"], "string");
2660 assert_eq!(
2661 json["properties"]["name"]["pattern"],
2662 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2663 );
2664
2665 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2666 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2667 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2668 } else {
2669 panic!("expected String variant");
2670 }
2671 }
2672
2673 #[test]
2674 fn schema_property_updates_required_state() {
2675 let schema = ElicitationSchema::new()
2676 .string("name", true)
2677 .email("name", false);
2678
2679 let json = serde_json::to_value(&schema).unwrap();
2680 assert!(json.get("required").is_none());
2681 assert_eq!(json["properties"]["name"]["format"], "email");
2682 }
2683
2684 #[test]
2685 fn schema_defaults_invalid_object_type() {
2686 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2687 "type": "array",
2688 "properties": {
2689 "name": {
2690 "type": "string"
2691 }
2692 }
2693 }))
2694 .unwrap();
2695
2696 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2697 assert!(schema.properties.contains_key("name"));
2698 }
2699
2700 #[test]
2701 fn titled_multi_select_items_reject_one_of() {
2702 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2703 "oneOf": [
2704 {
2705 "const": "red",
2706 "title": "Red"
2707 }
2708 ]
2709 }))
2710 .unwrap_err();
2711
2712 assert!(err.to_string().contains("missing field `anyOf`"));
2713 }
2714
2715 #[test]
2716 fn response_accept_rejects_non_object_content() {
2717 assert!(
2718 serde_json::from_value::<CreateElicitationResponse>(json!({
2719 "action": "accept",
2720 "content": "Alice"
2721 }))
2722 .is_err()
2723 );
2724 }
2725
2726 #[test]
2727 fn response_accept_rejects_nested_object_content() {
2728 assert!(
2729 serde_json::from_value::<CreateElicitationResponse>(json!({
2730 "action": "accept",
2731 "content": {
2732 "profile": {
2733 "name": "Alice"
2734 }
2735 }
2736 }))
2737 .is_err()
2738 );
2739 }
2740
2741 #[test]
2742 fn response_accept_allows_primitive_and_string_array_content() {
2743 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2744 ElicitationAcceptAction::new().content(BTreeMap::from([
2745 ("name".to_string(), ElicitationContentValue::from("Alice")),
2746 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2747 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2748 (
2749 "subscribed".to_string(),
2750 ElicitationContentValue::from(true),
2751 ),
2752 (
2753 "tags".to_string(),
2754 ElicitationContentValue::from(vec!["rust", "acp"]),
2755 ),
2756 ])),
2757 ));
2758
2759 let json = serde_json::to_value(&response).unwrap();
2760 assert_eq!(json["action"], "accept");
2761 assert_eq!(json["content"]["name"], "Alice");
2762 assert_eq!(json["content"]["age"], 30);
2763 assert_eq!(json["content"]["score"], 9.5);
2764 assert_eq!(json["content"]["subscribed"], true);
2765 assert_eq!(json["content"]["tags"][0], "rust");
2766 assert_eq!(json["content"]["tags"][1], "acp");
2767 }
2768}