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