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")]
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 meta: None,
103 }
104 }
105
106 #[must_use]
112 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
113 self.meta = meta.into_option();
114 self
115 }
116}
117
118#[serde_as]
123#[skip_serializing_none]
124#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
125#[serde(rename_all = "camelCase")]
126#[non_exhaustive]
127pub struct StringPropertySchema {
128 #[serde_as(deserialize_as = "DefaultOnError")]
130 #[schemars(extend("x-deserialize-default-on-error" = true))]
131 #[serde(default)]
132 pub title: Option<String>,
133 #[serde_as(deserialize_as = "DefaultOnError")]
135 #[schemars(extend("x-deserialize-default-on-error" = true))]
136 #[serde(default)]
137 pub description: Option<String>,
138 #[serde(default)]
140 pub min_length: Option<u32>,
141 #[serde(default)]
143 pub max_length: Option<u32>,
144 #[schemars(extend("format" = "regex"))]
146 #[serde(default)]
147 pub pattern: Option<String>,
148 #[serde(default)]
150 pub format: Option<StringFormat>,
151 #[serde_as(deserialize_as = "DefaultOnError")]
153 #[schemars(extend("x-deserialize-default-on-error" = true))]
154 #[serde(default)]
155 pub default: Option<String>,
156 #[schemars(length(min = 1))]
159 #[serde(default)]
160 #[serde(rename = "enum")]
161 pub enum_values: Option<Vec<String>>,
162 #[schemars(length(min = 1))]
165 #[serde(default)]
166 #[serde(rename = "oneOf")]
167 pub one_of: Option<Vec<EnumOption>>,
168 #[serde_as(deserialize_as = "DefaultOnError")]
174 #[schemars(extend("x-deserialize-default-on-error" = true))]
175 #[serde(default)]
176 #[serde(rename = "_meta")]
177 pub meta: Option<Meta>,
178}
179
180impl StringPropertySchema {
181 #[must_use]
183 pub fn new() -> Self {
184 Self::default()
185 }
186
187 #[must_use]
189 pub fn email() -> Self {
190 Self {
191 format: Some(StringFormat::Email),
192 ..Default::default()
193 }
194 }
195
196 #[must_use]
198 pub fn uri() -> Self {
199 Self {
200 format: Some(StringFormat::Uri),
201 ..Default::default()
202 }
203 }
204
205 #[must_use]
207 pub fn date() -> Self {
208 Self {
209 format: Some(StringFormat::Date),
210 ..Default::default()
211 }
212 }
213
214 #[must_use]
216 pub fn date_time() -> Self {
217 Self {
218 format: Some(StringFormat::DateTime),
219 ..Default::default()
220 }
221 }
222
223 #[must_use]
225 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
226 self.title = title.into_option();
227 self
228 }
229
230 #[must_use]
232 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
233 self.description = description.into_option();
234 self
235 }
236
237 #[must_use]
239 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
240 self.min_length = min_length.into_option();
241 self
242 }
243
244 #[must_use]
246 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
247 self.max_length = max_length.into_option();
248 self
249 }
250
251 #[must_use]
253 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
254 self.pattern = pattern.into_option();
255 self
256 }
257
258 #[must_use]
260 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
261 self.format = format.into_option();
262 self
263 }
264
265 #[must_use]
267 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
268 self.default = default.into_option();
269 self
270 }
271
272 #[must_use]
274 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
275 self.enum_values = enum_values.into_option();
276 self
277 }
278
279 #[must_use]
281 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
282 self.one_of = one_of.into_option();
283 self
284 }
285
286 #[must_use]
292 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
293 self.meta = meta.into_option();
294 self
295 }
296}
297
298#[serde_as]
300#[skip_serializing_none]
301#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
302#[serde(rename_all = "camelCase")]
303#[non_exhaustive]
304pub struct NumberPropertySchema {
305 #[serde_as(deserialize_as = "DefaultOnError")]
307 #[schemars(extend("x-deserialize-default-on-error" = true))]
308 #[serde(default)]
309 pub title: Option<String>,
310 #[serde_as(deserialize_as = "DefaultOnError")]
312 #[schemars(extend("x-deserialize-default-on-error" = true))]
313 #[serde(default)]
314 pub description: Option<String>,
315 #[serde(default)]
317 pub minimum: Option<f64>,
318 #[serde(default)]
320 pub maximum: Option<f64>,
321 #[serde_as(deserialize_as = "DefaultOnError")]
323 #[schemars(extend("x-deserialize-default-on-error" = true))]
324 #[serde(default)]
325 pub default: Option<f64>,
326 #[serde_as(deserialize_as = "DefaultOnError")]
332 #[schemars(extend("x-deserialize-default-on-error" = true))]
333 #[serde(default)]
334 #[serde(rename = "_meta")]
335 pub meta: Option<Meta>,
336}
337
338impl NumberPropertySchema {
339 #[must_use]
341 pub fn new() -> Self {
342 Self::default()
343 }
344
345 #[must_use]
347 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
348 self.title = title.into_option();
349 self
350 }
351
352 #[must_use]
354 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
355 self.description = description.into_option();
356 self
357 }
358
359 #[must_use]
361 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
362 self.minimum = minimum.into_option();
363 self
364 }
365
366 #[must_use]
368 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
369 self.maximum = maximum.into_option();
370 self
371 }
372
373 #[must_use]
375 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
376 self.default = default.into_option();
377 self
378 }
379
380 #[must_use]
386 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
387 self.meta = meta.into_option();
388 self
389 }
390}
391
392#[serde_as]
394#[skip_serializing_none]
395#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
396#[serde(rename_all = "camelCase")]
397#[non_exhaustive]
398pub struct IntegerPropertySchema {
399 #[serde_as(deserialize_as = "DefaultOnError")]
401 #[schemars(extend("x-deserialize-default-on-error" = true))]
402 #[serde(default)]
403 pub title: Option<String>,
404 #[serde_as(deserialize_as = "DefaultOnError")]
406 #[schemars(extend("x-deserialize-default-on-error" = true))]
407 #[serde(default)]
408 pub description: Option<String>,
409 #[serde(default)]
411 pub minimum: Option<i64>,
412 #[serde(default)]
414 pub maximum: Option<i64>,
415 #[serde_as(deserialize_as = "DefaultOnError")]
417 #[schemars(extend("x-deserialize-default-on-error" = true))]
418 #[serde(default)]
419 pub default: Option<i64>,
420 #[serde_as(deserialize_as = "DefaultOnError")]
426 #[schemars(extend("x-deserialize-default-on-error" = true))]
427 #[serde(default)]
428 #[serde(rename = "_meta")]
429 pub meta: Option<Meta>,
430}
431
432impl IntegerPropertySchema {
433 #[must_use]
435 pub fn new() -> Self {
436 Self::default()
437 }
438
439 #[must_use]
441 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
442 self.title = title.into_option();
443 self
444 }
445
446 #[must_use]
448 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
449 self.description = description.into_option();
450 self
451 }
452
453 #[must_use]
455 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
456 self.minimum = minimum.into_option();
457 self
458 }
459
460 #[must_use]
462 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
463 self.maximum = maximum.into_option();
464 self
465 }
466
467 #[must_use]
469 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
470 self.default = default.into_option();
471 self
472 }
473
474 #[must_use]
480 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
481 self.meta = meta.into_option();
482 self
483 }
484}
485
486#[serde_as]
488#[skip_serializing_none]
489#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
490#[serde(rename_all = "camelCase")]
491#[non_exhaustive]
492pub struct BooleanPropertySchema {
493 #[serde_as(deserialize_as = "DefaultOnError")]
495 #[schemars(extend("x-deserialize-default-on-error" = true))]
496 #[serde(default)]
497 pub title: Option<String>,
498 #[serde_as(deserialize_as = "DefaultOnError")]
500 #[schemars(extend("x-deserialize-default-on-error" = true))]
501 #[serde(default)]
502 pub description: Option<String>,
503 #[serde_as(deserialize_as = "DefaultOnError")]
505 #[schemars(extend("x-deserialize-default-on-error" = true))]
506 #[serde(default)]
507 pub default: Option<bool>,
508 #[serde_as(deserialize_as = "DefaultOnError")]
514 #[schemars(extend("x-deserialize-default-on-error" = true))]
515 #[serde(default)]
516 #[serde(rename = "_meta")]
517 pub meta: Option<Meta>,
518}
519
520impl BooleanPropertySchema {
521 #[must_use]
523 pub fn new() -> Self {
524 Self::default()
525 }
526
527 #[must_use]
529 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
530 self.title = title.into_option();
531 self
532 }
533
534 #[must_use]
536 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
537 self.description = description.into_option();
538 self
539 }
540
541 #[must_use]
543 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
544 self.default = default.into_option();
545 self
546 }
547
548 #[must_use]
554 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
555 self.meta = meta.into_option();
556 self
557 }
558}
559
560#[serde_as]
562#[skip_serializing_none]
563#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
564#[non_exhaustive]
565pub struct StringMultiSelectItems {
566 #[schemars(length(min = 1))]
568 #[serde(rename = "enum")]
569 pub values: Vec<String>,
570 #[serde_as(deserialize_as = "DefaultOnError")]
576 #[schemars(extend("x-deserialize-default-on-error" = true))]
577 #[serde(default)]
578 #[serde(rename = "_meta")]
579 pub meta: Option<Meta>,
580}
581
582impl StringMultiSelectItems {
583 #[must_use]
585 pub fn new(values: Vec<String>) -> Self {
586 Self { values, meta: None }
587 }
588
589 #[must_use]
595 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
596 self.meta = meta.into_option();
597 self
598 }
599}
600
601#[serde_as]
603#[skip_serializing_none]
604#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
605#[non_exhaustive]
606pub struct TitledMultiSelectItems {
607 #[schemars(length(min = 1))]
609 #[serde(rename = "anyOf")]
610 pub options: Vec<EnumOption>,
611 #[serde_as(deserialize_as = "DefaultOnError")]
617 #[schemars(extend("x-deserialize-default-on-error" = true))]
618 #[serde(default)]
619 #[serde(rename = "_meta")]
620 pub meta: Option<Meta>,
621}
622
623impl TitledMultiSelectItems {
624 #[must_use]
626 pub fn new(options: Vec<EnumOption>) -> Self {
627 Self {
628 options,
629 meta: None,
630 }
631 }
632
633 #[must_use]
639 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
640 self.meta = meta.into_option();
641 self
642 }
643}
644
645#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
651#[schemars(inline)]
652#[schemars(transform = other_multi_select_items_schema)]
653#[serde(rename_all = "camelCase")]
654#[non_exhaustive]
655pub struct OtherMultiSelectItems {
656 #[serde(rename = "type")]
662 pub type_: String,
663 #[serde(flatten)]
665 pub fields: BTreeMap<String, serde_json::Value>,
666}
667
668impl OtherMultiSelectItems {
669 #[must_use]
671 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
672 fields.remove("type");
673 Self {
674 type_: type_.into(),
675 fields,
676 }
677 }
678}
679
680impl<'de> Deserialize<'de> for OtherMultiSelectItems {
681 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
682 where
683 D: serde::Deserializer<'de>,
684 {
685 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
686 let type_ = fields
687 .remove("type")
688 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
689 let serde_json::Value::String(type_) = type_ else {
690 return Err(serde::de::Error::custom("`type` must be a string"));
691 };
692
693 if is_known_multi_select_item_type(&type_) {
694 return Err(serde::de::Error::custom(format!(
695 "known multi-select item type `{type_}` did not match its schema"
696 )));
697 }
698
699 Ok(Self { type_, fields })
700 }
701}
702
703const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
704
705fn is_known_multi_select_item_type(type_: &str) -> bool {
706 KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
707}
708
709fn other_multi_select_items_schema(schema: &mut Schema) {
710 super::schema_util::reject_known_string_discriminators(
711 schema,
712 "type",
713 KNOWN_MULTI_SELECT_ITEM_TYPES,
714 );
715}
716
717#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
719#[serde(tag = "type", rename_all = "snake_case")]
720#[schemars(extend("discriminator" = {"propertyName": "type"}))]
721#[non_exhaustive]
722pub enum MultiSelectItems {
723 String(StringMultiSelectItems),
725 #[serde(untagged)]
727 Other(OtherMultiSelectItems),
728 #[serde(untagged)]
730 Titled(TitledMultiSelectItems),
731}
732
733#[serde_as]
735#[skip_serializing_none]
736#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
737#[serde(rename_all = "camelCase")]
738#[non_exhaustive]
739pub struct MultiSelectPropertySchema {
740 #[serde_as(deserialize_as = "DefaultOnError")]
742 #[schemars(extend("x-deserialize-default-on-error" = true))]
743 #[serde(default)]
744 pub title: Option<String>,
745 #[serde_as(deserialize_as = "DefaultOnError")]
747 #[schemars(extend("x-deserialize-default-on-error" = true))]
748 #[serde(default)]
749 pub description: Option<String>,
750 #[serde(default)]
752 pub min_items: Option<u64>,
753 #[serde(default)]
755 pub max_items: Option<u64>,
756 pub items: MultiSelectItems,
758 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
760 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
761 #[serde(default)]
762 pub default: Option<Vec<String>>,
763 #[serde_as(deserialize_as = "DefaultOnError")]
769 #[schemars(extend("x-deserialize-default-on-error" = true))]
770 #[serde(default)]
771 #[serde(rename = "_meta")]
772 pub meta: Option<Meta>,
773}
774
775impl MultiSelectPropertySchema {
776 #[must_use]
778 pub fn new(values: Vec<String>) -> Self {
779 Self {
780 title: None,
781 description: None,
782 min_items: None,
783 max_items: None,
784 items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
785 default: None,
786 meta: None,
787 }
788 }
789
790 #[must_use]
792 pub fn titled(options: Vec<EnumOption>) -> Self {
793 Self {
794 title: None,
795 description: None,
796 min_items: None,
797 max_items: None,
798 items: MultiSelectItems::Titled(TitledMultiSelectItems {
799 options,
800 meta: None,
801 }),
802 default: None,
803 meta: None,
804 }
805 }
806
807 #[must_use]
809 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
810 self.title = title.into_option();
811 self
812 }
813
814 #[must_use]
816 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
817 self.description = description.into_option();
818 self
819 }
820
821 #[must_use]
823 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
824 self.min_items = min_items.into_option();
825 self
826 }
827
828 #[must_use]
830 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
831 self.max_items = max_items.into_option();
832 self
833 }
834
835 #[must_use]
837 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
838 self.default = default.into_option();
839 self
840 }
841
842 #[must_use]
848 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
849 self.meta = meta.into_option();
850 self
851 }
852}
853
854#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
860#[serde(tag = "type", rename_all = "snake_case")]
861#[schemars(extend("discriminator" = {"propertyName": "type"}))]
862#[non_exhaustive]
863pub enum ElicitationPropertySchema {
864 String(StringPropertySchema),
866 Number(NumberPropertySchema),
868 Integer(IntegerPropertySchema),
870 Boolean(BooleanPropertySchema),
872 Array(MultiSelectPropertySchema),
874 #[serde(untagged)]
884 Other(OtherElicitationPropertySchema),
885}
886
887#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
893#[schemars(inline)]
894#[schemars(transform = other_elicitation_property_schema_schema)]
895#[serde(rename_all = "camelCase")]
896#[non_exhaustive]
897pub struct OtherElicitationPropertySchema {
898 #[serde(rename = "type")]
904 pub type_: String,
905 #[serde(flatten)]
907 pub fields: BTreeMap<String, serde_json::Value>,
908}
909
910impl OtherElicitationPropertySchema {
911 #[must_use]
913 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
914 fields.remove("type");
915 Self {
916 type_: type_.into(),
917 fields,
918 }
919 }
920}
921
922impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
923 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
924 where
925 D: serde::Deserializer<'de>,
926 {
927 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
928 let type_ = fields
929 .remove("type")
930 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
931 let serde_json::Value::String(type_) = type_ else {
932 return Err(serde::de::Error::custom("`type` must be a string"));
933 };
934
935 if is_known_elicitation_property_schema_type(&type_) {
936 return Err(serde::de::Error::custom(format!(
937 "known elicitation property schema type `{type_}` did not match its schema"
938 )));
939 }
940
941 Ok(Self { type_, fields })
942 }
943}
944
945const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
946 &["string", "number", "integer", "boolean", "array"];
947
948fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
949 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
950}
951
952fn other_elicitation_property_schema_schema(schema: &mut Schema) {
953 super::schema_util::reject_known_string_discriminators(
954 schema,
955 "type",
956 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES,
957 );
958}
959
960impl From<StringPropertySchema> for ElicitationPropertySchema {
961 fn from(schema: StringPropertySchema) -> Self {
962 Self::String(schema)
963 }
964}
965
966impl From<NumberPropertySchema> for ElicitationPropertySchema {
967 fn from(schema: NumberPropertySchema) -> Self {
968 Self::Number(schema)
969 }
970}
971
972impl From<IntegerPropertySchema> for ElicitationPropertySchema {
973 fn from(schema: IntegerPropertySchema) -> Self {
974 Self::Integer(schema)
975 }
976}
977
978impl From<BooleanPropertySchema> for ElicitationPropertySchema {
979 fn from(schema: BooleanPropertySchema) -> Self {
980 Self::Boolean(schema)
981 }
982}
983
984impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
985 fn from(schema: MultiSelectPropertySchema) -> Self {
986 Self::Array(schema)
987 }
988}
989
990fn default_object_type() -> ElicitationSchemaType {
991 ElicitationSchemaType::Object
992}
993
994#[serde_as]
999#[skip_serializing_none]
1000#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1001#[serde(rename_all = "camelCase")]
1002#[non_exhaustive]
1003pub struct ElicitationSchema {
1004 #[serde_as(deserialize_as = "DefaultOnError")]
1006 #[schemars(extend("x-deserialize-default-on-error" = true))]
1007 #[serde(rename = "type", default = "default_object_type")]
1008 pub type_: ElicitationSchemaType,
1009 #[serde_as(deserialize_as = "DefaultOnError")]
1011 #[schemars(extend("x-deserialize-default-on-error" = true))]
1012 #[serde(default)]
1013 pub title: Option<String>,
1014 #[serde(default)]
1016 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1017 #[serde(default)]
1019 pub required: Option<Vec<String>>,
1020 #[serde_as(deserialize_as = "DefaultOnError")]
1022 #[schemars(extend("x-deserialize-default-on-error" = true))]
1023 #[serde(default)]
1024 pub description: Option<String>,
1025 #[serde_as(deserialize_as = "DefaultOnError")]
1031 #[schemars(extend("x-deserialize-default-on-error" = true))]
1032 #[serde(default)]
1033 #[serde(rename = "_meta")]
1034 pub meta: Option<Meta>,
1035}
1036
1037impl Default for ElicitationSchema {
1038 fn default() -> Self {
1039 Self {
1040 type_: default_object_type(),
1041 title: None,
1042 properties: BTreeMap::new(),
1043 required: None,
1044 description: None,
1045 meta: None,
1046 }
1047 }
1048}
1049
1050impl ElicitationSchema {
1051 #[must_use]
1053 pub fn new() -> Self {
1054 Self::default()
1055 }
1056
1057 #[must_use]
1059 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1060 self.title = title.into_option();
1061 self
1062 }
1063
1064 #[must_use]
1066 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1067 self.description = description.into_option();
1068 self
1069 }
1070
1071 #[must_use]
1077 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1078 self.meta = meta.into_option();
1079 self
1080 }
1081
1082 #[must_use]
1084 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1085 where
1086 S: Into<ElicitationPropertySchema>,
1087 {
1088 let name = name.into();
1089 self.properties.insert(name.clone(), schema.into());
1090
1091 if required {
1092 let required_fields = self.required.get_or_insert_with(Vec::new);
1093 if !required_fields.contains(&name) {
1094 required_fields.push(name);
1095 }
1096 } else if let Some(required_fields) = &mut self.required {
1097 required_fields.retain(|field| field != &name);
1098
1099 if required_fields.is_empty() {
1100 self.required = None;
1101 }
1102 }
1103
1104 self
1105 }
1106
1107 #[must_use]
1109 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1110 self.property(name, StringPropertySchema::new(), required)
1111 }
1112
1113 #[must_use]
1115 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1116 self.property(name, StringPropertySchema::email(), required)
1117 }
1118
1119 #[must_use]
1121 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1122 self.property(name, StringPropertySchema::uri(), required)
1123 }
1124
1125 #[must_use]
1127 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1128 self.property(name, StringPropertySchema::date(), required)
1129 }
1130
1131 #[must_use]
1133 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1134 self.property(name, StringPropertySchema::date_time(), required)
1135 }
1136
1137 #[must_use]
1139 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1140 self.property(
1141 name,
1142 NumberPropertySchema::new().minimum(min).maximum(max),
1143 required,
1144 )
1145 }
1146
1147 #[must_use]
1149 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1150 self.property(
1151 name,
1152 IntegerPropertySchema::new().minimum(min).maximum(max),
1153 required,
1154 )
1155 }
1156
1157 #[must_use]
1159 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1160 self.property(name, BooleanPropertySchema::new(), required)
1161 }
1162}
1163
1164#[serde_as]
1170#[skip_serializing_none]
1171#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1172#[serde(rename_all = "camelCase")]
1173#[non_exhaustive]
1174pub struct ElicitationCapabilities {
1175 #[serde_as(deserialize_as = "DefaultOnError")]
1180 #[schemars(extend("x-deserialize-default-on-error" = true))]
1181 #[serde(default)]
1182 pub form: Option<ElicitationFormCapabilities>,
1183 #[serde_as(deserialize_as = "DefaultOnError")]
1188 #[schemars(extend("x-deserialize-default-on-error" = true))]
1189 #[serde(default)]
1190 pub url: Option<ElicitationUrlCapabilities>,
1191 #[serde_as(deserialize_as = "DefaultOnError")]
1197 #[schemars(extend("x-deserialize-default-on-error" = true))]
1198 #[serde(default)]
1199 #[serde(rename = "_meta")]
1200 pub meta: Option<Meta>,
1201}
1202
1203impl ElicitationCapabilities {
1204 #[must_use]
1206 pub fn new() -> Self {
1207 Self::default()
1208 }
1209
1210 #[must_use]
1215 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1216 self.form = form.into_option();
1217 self
1218 }
1219
1220 #[must_use]
1225 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1226 self.url = url.into_option();
1227 self
1228 }
1229
1230 #[must_use]
1236 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1237 self.meta = meta.into_option();
1238 self
1239 }
1240}
1241
1242#[serde_as]
1250#[skip_serializing_none]
1251#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1252#[serde(rename_all = "camelCase")]
1253#[non_exhaustive]
1254pub struct ElicitationFormCapabilities {
1255 #[serde_as(deserialize_as = "DefaultOnError")]
1261 #[schemars(extend("x-deserialize-default-on-error" = true))]
1262 #[serde(default)]
1263 #[serde(rename = "_meta")]
1264 pub meta: Option<Meta>,
1265}
1266
1267impl ElicitationFormCapabilities {
1268 #[must_use]
1270 pub fn new() -> Self {
1271 Self::default()
1272 }
1273
1274 #[must_use]
1280 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1281 self.meta = meta.into_option();
1282 self
1283 }
1284}
1285
1286#[serde_as]
1294#[skip_serializing_none]
1295#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1296#[serde(rename_all = "camelCase")]
1297#[non_exhaustive]
1298pub struct ElicitationUrlCapabilities {
1299 #[serde_as(deserialize_as = "DefaultOnError")]
1305 #[schemars(extend("x-deserialize-default-on-error" = true))]
1306 #[serde(default)]
1307 #[serde(rename = "_meta")]
1308 pub meta: Option<Meta>,
1309}
1310
1311impl ElicitationUrlCapabilities {
1312 #[must_use]
1314 pub fn new() -> Self {
1315 Self::default()
1316 }
1317
1318 #[must_use]
1324 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1325 self.meta = meta.into_option();
1326 self
1327 }
1328}
1329
1330#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1336#[serde(untagged)]
1337#[non_exhaustive]
1338pub enum ElicitationScope {
1339 Session(ElicitationSessionScope),
1341 Request(ElicitationRequestScope),
1344}
1345
1346#[serde_as]
1356#[skip_serializing_none]
1357#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1358#[serde(rename_all = "camelCase")]
1359#[non_exhaustive]
1360pub struct ElicitationSessionScope {
1361 pub session_id: SessionId,
1363 #[serde_as(deserialize_as = "DefaultOnError")]
1365 #[schemars(extend("x-deserialize-default-on-error" = true))]
1366 #[serde(default)]
1367 pub tool_call_id: Option<ToolCallId>,
1368}
1369
1370impl ElicitationSessionScope {
1371 #[must_use]
1373 pub fn new(session_id: impl Into<SessionId>) -> Self {
1374 Self {
1375 session_id: session_id.into(),
1376 tool_call_id: None,
1377 }
1378 }
1379
1380 #[must_use]
1382 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1383 self.tool_call_id = tool_call_id.into_option();
1384 self
1385 }
1386}
1387
1388#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1395#[serde(rename_all = "camelCase")]
1396#[non_exhaustive]
1397pub struct ElicitationRequestScope {
1398 pub request_id: RequestId,
1400}
1401
1402impl ElicitationRequestScope {
1403 #[must_use]
1405 pub fn new(request_id: impl Into<RequestId>) -> Self {
1406 Self {
1407 request_id: request_id.into(),
1408 }
1409 }
1410}
1411
1412impl From<ElicitationSessionScope> for ElicitationScope {
1413 fn from(scope: ElicitationSessionScope) -> Self {
1414 Self::Session(scope)
1415 }
1416}
1417
1418impl From<ElicitationRequestScope> for ElicitationScope {
1419 fn from(scope: ElicitationRequestScope) -> Self {
1420 Self::Request(scope)
1421 }
1422}
1423
1424#[serde_as]
1434#[skip_serializing_none]
1435#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1436#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1437#[serde(rename_all = "camelCase")]
1438#[non_exhaustive]
1439pub struct CreateElicitationRequest {
1440 #[serde(flatten)]
1442 pub mode: ElicitationMode,
1443 pub message: String,
1445 #[serde_as(deserialize_as = "DefaultOnError")]
1451 #[schemars(extend("x-deserialize-default-on-error" = true))]
1452 #[serde(default)]
1453 #[serde(rename = "_meta")]
1454 pub meta: Option<Meta>,
1455}
1456
1457impl CreateElicitationRequest {
1458 #[must_use]
1460 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1461 Self {
1462 mode: mode.into(),
1463 message: message.into(),
1464 meta: None,
1465 }
1466 }
1467
1468 #[must_use]
1470 pub fn scope(&self) -> &ElicitationScope {
1471 self.mode.scope()
1472 }
1473
1474 #[must_use]
1480 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1481 self.meta = meta.into_option();
1482 self
1483 }
1484}
1485
1486#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1492#[serde(tag = "mode", rename_all = "snake_case")]
1493#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1494#[non_exhaustive]
1495pub enum ElicitationMode {
1496 Form(ElicitationFormMode),
1498 Url(ElicitationUrlMode),
1500 #[serde(untagged)]
1510 Other(OtherElicitationMode),
1511}
1512
1513#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1519#[schemars(inline)]
1520#[schemars(transform = other_elicitation_mode_schema)]
1521#[serde(rename_all = "camelCase")]
1522#[non_exhaustive]
1523pub struct OtherElicitationMode {
1524 pub mode: String,
1530 #[serde(flatten)]
1532 pub scope: ElicitationScope,
1533 #[serde(flatten)]
1535 pub fields: BTreeMap<String, serde_json::Value>,
1536}
1537
1538impl OtherElicitationMode {
1539 #[must_use]
1541 pub fn new(
1542 mode: impl Into<String>,
1543 scope: impl Into<ElicitationScope>,
1544 mut fields: BTreeMap<String, serde_json::Value>,
1545 ) -> Self {
1546 fields.remove("mode");
1547 remove_elicitation_scope_fields(&mut fields);
1548 Self {
1549 mode: mode.into(),
1550 scope: scope.into(),
1551 fields,
1552 }
1553 }
1554}
1555
1556impl<'de> Deserialize<'de> for OtherElicitationMode {
1557 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1558 where
1559 D: serde::Deserializer<'de>,
1560 {
1561 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1562 let mode = fields
1563 .remove("mode")
1564 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1565 let serde_json::Value::String(mode) = mode else {
1566 return Err(serde::de::Error::custom("`mode` must be a string"));
1567 };
1568
1569 if is_known_elicitation_mode(&mode) {
1570 return Err(serde::de::Error::custom(format!(
1571 "known elicitation mode `{mode}` did not match its schema"
1572 )));
1573 }
1574
1575 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1576 fields.clone().into_iter().collect(),
1577 ))
1578 .map_err(serde::de::Error::custom)?;
1579 remove_elicitation_scope_fields(&mut fields);
1580
1581 Ok(Self {
1582 mode,
1583 scope,
1584 fields,
1585 })
1586 }
1587}
1588
1589const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1590
1591fn is_known_elicitation_mode(mode: &str) -> bool {
1592 KNOWN_ELICITATION_MODES.contains(&mode)
1593}
1594
1595fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1596 fields.remove("sessionId");
1597 fields.remove("toolCallId");
1598 fields.remove("requestId");
1599}
1600
1601fn other_elicitation_mode_schema(schema: &mut Schema) {
1602 super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1603}
1604
1605impl From<ElicitationFormMode> for ElicitationMode {
1606 fn from(mode: ElicitationFormMode) -> Self {
1607 Self::Form(mode)
1608 }
1609}
1610
1611impl From<ElicitationUrlMode> for ElicitationMode {
1612 fn from(mode: ElicitationUrlMode) -> Self {
1613 Self::Url(mode)
1614 }
1615}
1616
1617impl From<OtherElicitationMode> for ElicitationMode {
1618 fn from(mode: OtherElicitationMode) -> Self {
1619 Self::Other(mode)
1620 }
1621}
1622
1623impl ElicitationMode {
1624 #[must_use]
1626 pub fn scope(&self) -> &ElicitationScope {
1627 match self {
1628 Self::Form(f) => &f.scope,
1629 Self::Url(u) => &u.scope,
1630 Self::Other(other) => &other.scope,
1631 }
1632 }
1633}
1634
1635#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1641#[serde(rename_all = "camelCase")]
1642#[non_exhaustive]
1643pub struct ElicitationFormMode {
1644 #[serde(flatten)]
1646 pub scope: ElicitationScope,
1647 pub requested_schema: ElicitationSchema,
1649}
1650
1651impl ElicitationFormMode {
1652 #[must_use]
1654 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1655 Self {
1656 scope: scope.into(),
1657 requested_schema,
1658 }
1659 }
1660}
1661
1662#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1668#[serde(rename_all = "camelCase")]
1669#[non_exhaustive]
1670pub struct ElicitationUrlMode {
1671 #[serde(flatten)]
1673 pub scope: ElicitationScope,
1674 pub elicitation_id: ElicitationId,
1676 #[schemars(url)]
1678 pub url: String,
1679}
1680
1681impl ElicitationUrlMode {
1682 #[must_use]
1684 pub fn new(
1685 scope: impl Into<ElicitationScope>,
1686 elicitation_id: impl Into<ElicitationId>,
1687 url: impl Into<String>,
1688 ) -> Self {
1689 Self {
1690 scope: scope.into(),
1691 elicitation_id: elicitation_id.into(),
1692 url: url.into(),
1693 }
1694 }
1695}
1696
1697#[serde_as]
1703#[skip_serializing_none]
1704#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1705#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1706#[serde(rename_all = "camelCase")]
1707#[non_exhaustive]
1708pub struct CreateElicitationResponse {
1709 #[serde(flatten)]
1711 pub action: ElicitationAction,
1712 #[serde_as(deserialize_as = "DefaultOnError")]
1718 #[schemars(extend("x-deserialize-default-on-error" = true))]
1719 #[serde(default)]
1720 #[serde(rename = "_meta")]
1721 pub meta: Option<Meta>,
1722}
1723
1724impl CreateElicitationResponse {
1725 #[must_use]
1727 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1728 Self {
1729 action: action.into(),
1730 meta: None,
1731 }
1732 }
1733
1734 #[must_use]
1740 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1741 self.meta = meta.into_option();
1742 self
1743 }
1744}
1745
1746#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1752#[serde(tag = "action", rename_all = "snake_case")]
1753#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1754#[non_exhaustive]
1755pub enum ElicitationAction {
1756 Accept(ElicitationAcceptAction),
1758 Decline,
1760 Cancel,
1762 #[serde(untagged)]
1772 Other(OtherElicitationAction),
1773}
1774
1775#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1781#[schemars(inline)]
1782#[schemars(transform = other_elicitation_action_schema)]
1783#[serde(rename_all = "camelCase")]
1784#[non_exhaustive]
1785pub struct OtherElicitationAction {
1786 pub action: String,
1792 #[serde(flatten)]
1794 pub fields: BTreeMap<String, serde_json::Value>,
1795}
1796
1797impl OtherElicitationAction {
1798 #[must_use]
1800 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1801 fields.remove("action");
1802 Self {
1803 action: action.into(),
1804 fields,
1805 }
1806 }
1807}
1808
1809impl<'de> Deserialize<'de> for OtherElicitationAction {
1810 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1811 where
1812 D: serde::Deserializer<'de>,
1813 {
1814 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1815 let action = fields
1816 .remove("action")
1817 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1818 let serde_json::Value::String(action) = action else {
1819 return Err(serde::de::Error::custom("`action` must be a string"));
1820 };
1821
1822 if is_known_elicitation_action(&action) {
1823 return Err(serde::de::Error::custom(format!(
1824 "known elicitation action `{action}` did not match its schema"
1825 )));
1826 }
1827
1828 Ok(Self { action, fields })
1829 }
1830}
1831
1832const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1833
1834fn is_known_elicitation_action(action: &str) -> bool {
1835 KNOWN_ELICITATION_ACTIONS.contains(&action)
1836}
1837
1838fn other_elicitation_action_schema(schema: &mut Schema) {
1839 super::schema_util::reject_known_string_discriminators(
1840 schema,
1841 "action",
1842 KNOWN_ELICITATION_ACTIONS,
1843 );
1844}
1845
1846impl From<ElicitationAcceptAction> for ElicitationAction {
1847 fn from(action: ElicitationAcceptAction) -> Self {
1848 Self::Accept(action)
1849 }
1850}
1851
1852impl From<OtherElicitationAction> for ElicitationAction {
1853 fn from(action: OtherElicitationAction) -> Self {
1854 Self::Other(action)
1855 }
1856}
1857
1858#[serde_as]
1864#[skip_serializing_none]
1865#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1866#[serde(rename_all = "camelCase")]
1867#[non_exhaustive]
1868pub struct ElicitationAcceptAction {
1869 #[serde(default)]
1871 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1872}
1873
1874impl ElicitationAcceptAction {
1875 #[must_use]
1877 pub fn new() -> Self {
1878 Self { content: None }
1879 }
1880
1881 #[must_use]
1883 pub fn content(
1884 mut self,
1885 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1886 ) -> Self {
1887 self.content = content.into_option();
1888 self
1889 }
1890}
1891
1892#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1894#[serde(untagged)]
1895#[non_exhaustive]
1896pub enum ElicitationContentValue {
1897 String(String),
1899 Integer(i64),
1901 Number(f64),
1903 Boolean(bool),
1905 StringArray(Vec<String>),
1907}
1908
1909impl From<String> for ElicitationContentValue {
1910 fn from(value: String) -> Self {
1911 Self::String(value)
1912 }
1913}
1914
1915impl From<&str> for ElicitationContentValue {
1916 fn from(value: &str) -> Self {
1917 Self::String(value.to_string())
1918 }
1919}
1920
1921impl From<i64> for ElicitationContentValue {
1922 fn from(value: i64) -> Self {
1923 Self::Integer(value)
1924 }
1925}
1926
1927impl From<i32> for ElicitationContentValue {
1928 fn from(value: i32) -> Self {
1929 Self::Integer(i64::from(value))
1930 }
1931}
1932
1933impl From<f64> for ElicitationContentValue {
1934 fn from(value: f64) -> Self {
1935 Self::Number(value)
1936 }
1937}
1938
1939impl From<bool> for ElicitationContentValue {
1940 fn from(value: bool) -> Self {
1941 Self::Boolean(value)
1942 }
1943}
1944
1945impl From<Vec<String>> for ElicitationContentValue {
1946 fn from(value: Vec<String>) -> Self {
1947 Self::StringArray(value)
1948 }
1949}
1950
1951impl From<Vec<&str>> for ElicitationContentValue {
1952 fn from(value: Vec<&str>) -> Self {
1953 Self::StringArray(value.into_iter().map(str::to_string).collect())
1954 }
1955}
1956
1957impl Default for ElicitationAcceptAction {
1958 fn default() -> Self {
1959 Self::new()
1960 }
1961}
1962
1963#[serde_as]
1969#[skip_serializing_none]
1970#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1971#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1972#[serde(rename_all = "camelCase")]
1973#[non_exhaustive]
1974pub struct CompleteElicitationNotification {
1975 pub elicitation_id: ElicitationId,
1977 #[serde_as(deserialize_as = "DefaultOnError")]
1983 #[schemars(extend("x-deserialize-default-on-error" = true))]
1984 #[serde(default)]
1985 #[serde(rename = "_meta")]
1986 pub meta: Option<Meta>,
1987}
1988
1989impl CompleteElicitationNotification {
1990 #[must_use]
1992 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
1993 Self {
1994 elicitation_id: elicitation_id.into(),
1995 meta: None,
1996 }
1997 }
1998
1999 #[must_use]
2005 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2006 self.meta = meta.into_option();
2007 self
2008 }
2009}
2010
2011#[cfg(test)]
2012mod tests {
2013 use super::*;
2014 use serde_json::json;
2015
2016 #[test]
2017 fn form_mode_request_serialization() {
2018 let schema = ElicitationSchema::new().string("name", true);
2019 let req = CreateElicitationRequest::new(
2020 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2021 "Please enter your name",
2022 );
2023
2024 let json = serde_json::to_value(&req).unwrap();
2025 assert_eq!(json["sessionId"], "sess_1");
2026 assert!(json.get("toolCallId").is_none());
2027 assert_eq!(json["mode"], "form");
2028 assert_eq!(json["message"], "Please enter your name");
2029 assert!(json["requestedSchema"].is_object());
2030 assert_eq!(json["requestedSchema"]["type"], "object");
2031 assert_eq!(
2032 json["requestedSchema"]["properties"]["name"]["type"],
2033 "string"
2034 );
2035
2036 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2037 assert_eq!(
2038 *roundtripped.scope(),
2039 ElicitationSessionScope::new("sess_1").into()
2040 );
2041 assert_eq!(roundtripped.message, "Please enter your name");
2042 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2043 }
2044
2045 #[test]
2046 fn url_mode_request_serialization() {
2047 let req = CreateElicitationRequest::new(
2048 ElicitationUrlMode::new(
2049 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2050 "elic_1",
2051 "https://example.com/auth",
2052 ),
2053 "Please authenticate",
2054 );
2055
2056 let json = serde_json::to_value(&req).unwrap();
2057 assert_eq!(json["sessionId"], "sess_2");
2058 assert_eq!(json["toolCallId"], "tc_1");
2059 assert_eq!(json["mode"], "url");
2060 assert_eq!(json["elicitationId"], "elic_1");
2061 assert_eq!(json["url"], "https://example.com/auth");
2062 assert_eq!(json["message"], "Please authenticate");
2063
2064 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2065 assert_eq!(
2066 *roundtripped.scope(),
2067 ElicitationSessionScope::new("sess_2")
2068 .tool_call_id("tc_1")
2069 .into()
2070 );
2071 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2072 }
2073
2074 #[test]
2075 fn response_accept_serialization() {
2076 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2077 ElicitationAcceptAction::new().content(BTreeMap::from([(
2078 "name".to_string(),
2079 ElicitationContentValue::from("Alice"),
2080 )])),
2081 ));
2082
2083 let json = serde_json::to_value(&resp).unwrap();
2084 assert_eq!(json["action"], "accept");
2085 assert_eq!(json["content"]["name"], "Alice");
2086
2087 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2088 assert!(matches!(
2089 roundtripped.action,
2090 ElicitationAction::Accept(ElicitationAcceptAction {
2091 content: Some(_),
2092 ..
2093 })
2094 ));
2095 }
2096
2097 #[test]
2098 fn response_decline_serialization() {
2099 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2100
2101 let json = serde_json::to_value(&resp).unwrap();
2102 assert_eq!(json["action"], "decline");
2103
2104 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2105 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2106 }
2107
2108 #[test]
2109 fn response_cancel_serialization() {
2110 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2111
2112 let json = serde_json::to_value(&resp).unwrap();
2113 assert_eq!(json["action"], "cancel");
2114
2115 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2116 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2117 }
2118
2119 #[test]
2120 fn unknown_action_response_serialization() {
2121 let json = json!({
2122 "action": "_defer",
2123 "reason": "waiting",
2124 "retryAfterMs": 1000
2125 });
2126
2127 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2128 let ElicitationAction::Other(other) = &resp.action else {
2129 panic!("expected unknown elicitation action");
2130 };
2131
2132 assert_eq!(other.action, "_defer");
2133 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2134 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2135 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2136 }
2137
2138 #[test]
2139 fn unknown_action_does_not_hide_known_action() {
2140 assert!(
2141 serde_json::from_value::<OtherElicitationAction>(json!({
2142 "action": "accept",
2143 "content": {}
2144 }))
2145 .is_err()
2146 );
2147 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2148 }
2149
2150 #[test]
2151 fn url_mode_request_scope_serialization() {
2152 let req = CreateElicitationRequest::new(
2153 ElicitationUrlMode::new(
2154 ElicitationRequestScope::new(RequestId::Number(42)),
2155 "elic_2",
2156 "https://example.com/setup",
2157 ),
2158 "Please complete setup",
2159 );
2160
2161 let json = serde_json::to_value(&req).unwrap();
2162 assert_eq!(json["requestId"], 42);
2163 assert!(json.get("sessionId").is_none());
2164 assert_eq!(json["mode"], "url");
2165 assert_eq!(json["elicitationId"], "elic_2");
2166 assert_eq!(json["url"], "https://example.com/setup");
2167 assert_eq!(json["message"], "Please complete setup");
2168
2169 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2170 assert_eq!(
2171 *roundtripped.scope(),
2172 ElicitationRequestScope::new(RequestId::Number(42)).into()
2173 );
2174 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2175 }
2176
2177 #[test]
2178 fn unknown_mode_request_serialization() {
2179 let json = json!({
2180 "requestId": 42,
2181 "mode": "_browser",
2182 "message": "Open a browser window",
2183 "target": "login"
2184 });
2185
2186 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2187 let ElicitationMode::Other(other) = &req.mode else {
2188 panic!("expected unknown elicitation mode");
2189 };
2190
2191 assert_eq!(other.mode, "_browser");
2192 assert_eq!(
2193 other.scope,
2194 ElicitationRequestScope::new(RequestId::Number(42)).into()
2195 );
2196 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2197 assert_eq!(
2198 *req.scope(),
2199 ElicitationRequestScope::new(RequestId::Number(42)).into()
2200 );
2201 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2202 }
2203
2204 #[test]
2205 fn unknown_mode_does_not_hide_malformed_known_mode() {
2206 let missing_requested_schema = json!({
2207 "requestId": 42,
2208 "mode": "form",
2209 "message": "Enter your name"
2210 });
2211
2212 assert!(
2213 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2214 );
2215 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2216 }
2217
2218 #[test]
2219 fn request_scope_request_serialization() {
2220 let req = CreateElicitationRequest::new(
2221 ElicitationFormMode::new(
2222 ElicitationRequestScope::new(RequestId::Number(99)),
2223 ElicitationSchema::new().string("workspace", true),
2224 ),
2225 "Enter workspace name",
2226 );
2227
2228 let json = serde_json::to_value(&req).unwrap();
2229 assert_eq!(json["requestId"], 99);
2230 assert!(json.get("sessionId").is_none());
2231
2232 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2233 assert_eq!(
2234 *roundtripped.scope(),
2235 ElicitationRequestScope::new(RequestId::Number(99)).into()
2236 );
2237 }
2238
2239 #[test]
2243 fn client_response_serialization_accept() {
2244 use crate::v2::ClientResponse;
2245
2246 let resp =
2247 ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2248 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2249 [("name".to_string(), ElicitationContentValue::from("Alice"))],
2250 ))),
2251 )));
2252 let json = serde_json::to_value(&resp).unwrap();
2253 assert_eq!(json["action"], "accept");
2254 assert_eq!(json["content"]["name"], "Alice");
2255
2256 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2258 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2259 }
2260
2261 #[test]
2262 fn client_response_serialization_decline() {
2263 use crate::v2::ClientResponse;
2264
2265 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2266 CreateElicitationResponse::new(ElicitationAction::Decline),
2267 ));
2268 let json = serde_json::to_value(&resp).unwrap();
2269 assert_eq!(json["action"], "decline");
2270
2271 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2272 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2273 }
2274
2275 #[test]
2276 fn client_response_serialization_cancel() {
2277 use crate::v2::ClientResponse;
2278
2279 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2280 CreateElicitationResponse::new(ElicitationAction::Cancel),
2281 ));
2282 let json = serde_json::to_value(&resp).unwrap();
2283 assert_eq!(json["action"], "cancel");
2284
2285 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2286 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2287 }
2288
2289 #[test]
2292 fn request_tolerates_extra_fields() {
2293 let json = json!({
2294 "sessionId": "sess_1",
2295 "mode": "form",
2296 "message": "Enter your name",
2297 "requestedSchema": {
2298 "type": "object",
2299 "properties": {
2300 "name": { "type": "string", "title": "Name" }
2301 },
2302 "required": ["name"]
2303 },
2304 "unknownStringField": "hello",
2305 "unknownNumberField": 42
2306 });
2307
2308 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2309 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2310 assert_eq!(req.message, "Enter your name");
2311 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2312 }
2313
2314 #[test]
2315 fn completion_notification_serialization() {
2316 let notif = CompleteElicitationNotification::new("elic_1");
2317
2318 let json = serde_json::to_value(¬if).unwrap();
2319 assert_eq!(json["elicitationId"], "elic_1");
2320
2321 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2322 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2323 }
2324
2325 #[test]
2326 fn capabilities_form_only() {
2327 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2328
2329 let json = serde_json::to_value(&caps).unwrap();
2330 assert!(json["form"].is_object());
2331 assert!(json.get("url").is_none());
2332
2333 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2334 assert!(roundtripped.form.is_some());
2335 assert!(roundtripped.url.is_none());
2336 }
2337
2338 #[test]
2339 fn capabilities_url_only() {
2340 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2341
2342 let json = serde_json::to_value(&caps).unwrap();
2343 assert!(json.get("form").is_none());
2344 assert!(json["url"].is_object());
2345
2346 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2347 assert!(roundtripped.form.is_none());
2348 assert!(roundtripped.url.is_some());
2349 }
2350
2351 #[test]
2352 fn capabilities_both() {
2353 let caps = ElicitationCapabilities::new()
2354 .form(ElicitationFormCapabilities::new())
2355 .url(ElicitationUrlCapabilities::new());
2356
2357 let json = serde_json::to_value(&caps).unwrap();
2358 assert!(json["form"].is_object());
2359 assert!(json["url"].is_object());
2360
2361 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2362 assert!(roundtripped.form.is_some());
2363 assert!(roundtripped.url.is_some());
2364 }
2365
2366 #[test]
2367 fn schema_default_sets_object_type() {
2368 let schema = ElicitationSchema::default();
2369
2370 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2371 assert!(schema.properties.is_empty());
2372
2373 let json = serde_json::to_value(&schema).unwrap();
2374 assert_eq!(json["type"], "object");
2375 }
2376
2377 #[test]
2378 fn schema_builder_serialization() {
2379 let schema = ElicitationSchema::new()
2380 .string("name", true)
2381 .email("email", true)
2382 .integer("age", 0, 150, true)
2383 .boolean("newsletter", false)
2384 .description("User registration");
2385
2386 let json = serde_json::to_value(&schema).unwrap();
2387 assert_eq!(json["type"], "object");
2388 assert_eq!(json["description"], "User registration");
2389 assert_eq!(json["properties"]["name"]["type"], "string");
2390 assert_eq!(json["properties"]["email"]["type"], "string");
2391 assert_eq!(json["properties"]["email"]["format"], "email");
2392 assert_eq!(json["properties"]["age"]["type"], "integer");
2393 assert_eq!(json["properties"]["age"]["minimum"], 0);
2394 assert_eq!(json["properties"]["age"]["maximum"], 150);
2395 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2396
2397 let required = json["required"].as_array().unwrap();
2398 assert!(required.contains(&json!("name")));
2399 assert!(required.contains(&json!("email")));
2400 assert!(required.contains(&json!("age")));
2401 assert!(!required.contains(&json!("newsletter")));
2402
2403 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2404 assert_eq!(roundtripped.properties.len(), 4);
2405 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2406 }
2407
2408 #[test]
2409 fn schema_string_enum_serialization() {
2410 let schema = ElicitationSchema::new().property(
2411 "color",
2412 StringPropertySchema::new().enum_values(vec![
2413 "red".into(),
2414 "green".into(),
2415 "blue".into(),
2416 ]),
2417 true,
2418 );
2419
2420 let json = serde_json::to_value(&schema).unwrap();
2421 assert_eq!(json["properties"]["color"]["type"], "string");
2422 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2423 assert_eq!(enum_vals.len(), 3);
2424
2425 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2426 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2427 {
2428 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2429 } else {
2430 panic!("expected String variant");
2431 }
2432 }
2433
2434 #[test]
2435 fn schema_multi_select_serialization() {
2436 let schema = ElicitationSchema::new().property(
2437 "colors",
2438 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2439 .min_items(1)
2440 .max_items(3),
2441 false,
2442 );
2443
2444 let json = serde_json::to_value(&schema).unwrap();
2445 assert_eq!(json["properties"]["colors"]["type"], "array");
2446 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2447 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2448 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2449
2450 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2451 let ElicitationPropertySchema::Array(array) =
2452 roundtripped.properties.get("colors").unwrap()
2453 else {
2454 panic!("expected Array variant");
2455 };
2456 let MultiSelectItems::String(items) = &array.items else {
2457 panic!("expected String multi-select items");
2458 };
2459 assert_eq!(items.values.len(), 3);
2460 }
2461
2462 #[test]
2463 fn multi_select_titled_items_keep_mcp_shape() {
2464 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2465 "#ff0000", "Red",
2466 )]));
2467
2468 let json = serde_json::to_value(&items).unwrap();
2469 assert!(json.get("type").is_none());
2470 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2471 assert_eq!(json["anyOf"][0]["title"], "Red");
2472
2473 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2474 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2475 }
2476
2477 #[test]
2478 fn multi_select_items_preserve_unknown_type() {
2479 let json = json!({
2480 "type": "_token",
2481 "format": "workspace",
2482 "anyOf": [
2483 { "const": "repo", "title": "Repository" }
2484 ]
2485 });
2486
2487 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2488 let MultiSelectItems::Other(other) = &items else {
2489 panic!("expected unknown multi-select items");
2490 };
2491
2492 assert_eq!(other.type_, "_token");
2493 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2494 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2495 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2496 }
2497
2498 #[test]
2499 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2500 assert!(
2501 serde_json::from_value::<MultiSelectItems>(json!({
2502 "type": "string"
2503 }))
2504 .is_err()
2505 );
2506 assert!(
2507 serde_json::from_value::<OtherMultiSelectItems>(json!({
2508 "type": "string",
2509 "format": "workspace"
2510 }))
2511 .is_err()
2512 );
2513 }
2514
2515 #[test]
2516 fn property_schema_preserves_unknown_type() {
2517 let schema: ElicitationSchema = serde_json::from_value(json!({
2518 "type": "object",
2519 "properties": {
2520 "location": {
2521 "type": "_location",
2522 "title": "Location",
2523 "precision": "city"
2524 }
2525 }
2526 }))
2527 .unwrap();
2528
2529 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2530 else {
2531 panic!("expected unknown property schema");
2532 };
2533
2534 assert_eq!(unknown.type_, "_location");
2535 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2536 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2537 assert_eq!(
2538 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2539 json!({
2540 "type": "_location",
2541 "title": "Location",
2542 "precision": "city"
2543 })
2544 );
2545 }
2546
2547 #[test]
2548 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2549 assert!(
2550 serde_json::from_value::<ElicitationPropertySchema>(json!({
2551 "type": "array"
2552 }))
2553 .is_err()
2554 );
2555 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2556 }
2557
2558 #[test]
2559 fn schema_titled_enum_serialization() {
2560 let schema = ElicitationSchema::new().property(
2561 "country",
2562 StringPropertySchema::new().one_of(vec![
2563 EnumOption::new("us", "United States"),
2564 EnumOption::new("uk", "United Kingdom"),
2565 ]),
2566 true,
2567 );
2568
2569 let json = serde_json::to_value(&schema).unwrap();
2570 assert_eq!(json["properties"]["country"]["type"], "string");
2571 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2572 assert_eq!(one_of.len(), 2);
2573 assert_eq!(one_of[0]["const"], "us");
2574 assert_eq!(one_of[0]["title"], "United States");
2575
2576 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2577 if let ElicitationPropertySchema::String(s) =
2578 roundtripped.properties.get("country").unwrap()
2579 {
2580 assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
2581 } else {
2582 panic!("expected String variant");
2583 }
2584 }
2585
2586 #[test]
2587 fn schema_number_property_serialization() {
2588 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2589
2590 let json = serde_json::to_value(&schema).unwrap();
2591 assert_eq!(json["properties"]["rating"]["type"], "number");
2592 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2593 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2594
2595 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2596 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2597 {
2598 assert_eq!(n.minimum, Some(0.0));
2599 assert_eq!(n.maximum, Some(5.0));
2600 } else {
2601 panic!("expected Number variant");
2602 }
2603 }
2604
2605 #[test]
2606 fn schema_string_format_serialization() {
2607 let schema = ElicitationSchema::new()
2608 .uri("website", true)
2609 .date("birthday", true)
2610 .date_time("updated_at", false);
2611
2612 let json = serde_json::to_value(&schema).unwrap();
2613 assert_eq!(json["properties"]["website"]["type"], "string");
2614 assert_eq!(json["properties"]["website"]["format"], "uri");
2615 assert_eq!(json["properties"]["birthday"]["type"], "string");
2616 assert_eq!(json["properties"]["birthday"]["format"], "date");
2617 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2618 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2619
2620 let required = json["required"].as_array().unwrap();
2621 assert!(required.contains(&json!("website")));
2622 assert!(required.contains(&json!("birthday")));
2623 assert!(!required.contains(&json!("updated_at")));
2624 }
2625
2626 #[test]
2627 fn schema_string_pattern_serialization() {
2628 let schema = ElicitationSchema::new().property(
2629 "name",
2630 StringPropertySchema::new()
2631 .min_length(1)
2632 .max_length(64)
2633 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2634 true,
2635 );
2636
2637 let json = serde_json::to_value(&schema).unwrap();
2638 assert_eq!(json["properties"]["name"]["type"], "string");
2639 assert_eq!(
2640 json["properties"]["name"]["pattern"],
2641 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2642 );
2643
2644 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2645 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2646 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2647 } else {
2648 panic!("expected String variant");
2649 }
2650 }
2651
2652 #[test]
2653 fn schema_property_updates_required_state() {
2654 let schema = ElicitationSchema::new()
2655 .string("name", true)
2656 .email("name", false);
2657
2658 let json = serde_json::to_value(&schema).unwrap();
2659 assert!(json.get("required").is_none());
2660 assert_eq!(json["properties"]["name"]["format"], "email");
2661 }
2662
2663 #[test]
2664 fn schema_defaults_invalid_object_type() {
2665 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2666 "type": "array",
2667 "properties": {
2668 "name": {
2669 "type": "string"
2670 }
2671 }
2672 }))
2673 .unwrap();
2674
2675 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2676 assert!(schema.properties.contains_key("name"));
2677 }
2678
2679 #[test]
2680 fn titled_multi_select_items_reject_one_of() {
2681 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2682 "oneOf": [
2683 {
2684 "const": "red",
2685 "title": "Red"
2686 }
2687 ]
2688 }))
2689 .unwrap_err();
2690
2691 assert!(err.to_string().contains("missing field `anyOf`"));
2692 }
2693
2694 #[test]
2695 fn response_accept_rejects_non_object_content() {
2696 assert!(
2697 serde_json::from_value::<CreateElicitationResponse>(json!({
2698 "action": "accept",
2699 "content": "Alice"
2700 }))
2701 .is_err()
2702 );
2703 }
2704
2705 #[test]
2706 fn response_accept_rejects_nested_object_content() {
2707 assert!(
2708 serde_json::from_value::<CreateElicitationResponse>(json!({
2709 "action": "accept",
2710 "content": {
2711 "profile": {
2712 "name": "Alice"
2713 }
2714 }
2715 }))
2716 .is_err()
2717 );
2718 }
2719
2720 #[test]
2721 fn response_accept_allows_primitive_and_string_array_content() {
2722 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2723 ElicitationAcceptAction::new().content(BTreeMap::from([
2724 ("name".to_string(), ElicitationContentValue::from("Alice")),
2725 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2726 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2727 (
2728 "subscribed".to_string(),
2729 ElicitationContentValue::from(true),
2730 ),
2731 (
2732 "tags".to_string(),
2733 ElicitationContentValue::from(vec!["rust", "acp"]),
2734 ),
2735 ])),
2736 ));
2737
2738 let json = serde_json::to_value(&response).unwrap();
2739 assert_eq!(json["action"], "accept");
2740 assert_eq!(json["content"]["name"], "Alice");
2741 assert_eq!(json["content"]["age"], 30);
2742 assert_eq!(json["content"]["score"], 9.5);
2743 assert_eq!(json["content"]["subscribed"], true);
2744 assert_eq!(json["content"]["tags"][0], "rust");
2745 assert_eq!(json["content"]["tags"][1], "acp");
2746 }
2747}