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")]
84 #[schemars(extend("x-deserialize-default-on-error" = true))]
85 #[serde(default)]
86 #[serde(rename = "_meta")]
87 pub meta: Option<Meta>,
88}
89
90impl EnumOption {
91 #[must_use]
93 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
94 Self {
95 value: value.into(),
96 title: title.into(),
97 meta: None,
98 }
99 }
100
101 #[must_use]
107 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
108 self.meta = meta.into_option();
109 self
110 }
111}
112
113#[serde_as]
118#[skip_serializing_none]
119#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
120#[serde(rename_all = "camelCase")]
121#[non_exhaustive]
122pub struct StringPropertySchema {
123 #[serde_as(deserialize_as = "DefaultOnError")]
125 #[schemars(extend("x-deserialize-default-on-error" = true))]
126 #[serde(default)]
127 pub title: Option<String>,
128 #[serde_as(deserialize_as = "DefaultOnError")]
130 #[schemars(extend("x-deserialize-default-on-error" = true))]
131 #[serde(default)]
132 pub description: Option<String>,
133 #[serde(default)]
135 pub min_length: Option<u32>,
136 #[serde(default)]
138 pub max_length: Option<u32>,
139 #[serde(default)]
141 pub pattern: Option<String>,
142 #[serde(default)]
144 pub format: Option<StringFormat>,
145 #[serde_as(deserialize_as = "DefaultOnError")]
147 #[schemars(extend("x-deserialize-default-on-error" = true))]
148 #[serde(default)]
149 pub default: Option<String>,
150 #[serde(default)]
152 #[serde(rename = "enum")]
153 pub enum_values: Option<Vec<String>>,
154 #[serde(default)]
156 #[serde(rename = "oneOf")]
157 pub one_of: Option<Vec<EnumOption>>,
158 #[serde_as(deserialize_as = "DefaultOnError")]
164 #[schemars(extend("x-deserialize-default-on-error" = true))]
165 #[serde(default)]
166 #[serde(rename = "_meta")]
167 pub meta: Option<Meta>,
168}
169
170impl StringPropertySchema {
171 #[must_use]
173 pub fn new() -> Self {
174 Self::default()
175 }
176
177 #[must_use]
179 pub fn email() -> Self {
180 Self {
181 format: Some(StringFormat::Email),
182 ..Default::default()
183 }
184 }
185
186 #[must_use]
188 pub fn uri() -> Self {
189 Self {
190 format: Some(StringFormat::Uri),
191 ..Default::default()
192 }
193 }
194
195 #[must_use]
197 pub fn date() -> Self {
198 Self {
199 format: Some(StringFormat::Date),
200 ..Default::default()
201 }
202 }
203
204 #[must_use]
206 pub fn date_time() -> Self {
207 Self {
208 format: Some(StringFormat::DateTime),
209 ..Default::default()
210 }
211 }
212
213 #[must_use]
215 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
216 self.title = title.into_option();
217 self
218 }
219
220 #[must_use]
222 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
223 self.description = description.into_option();
224 self
225 }
226
227 #[must_use]
229 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
230 self.min_length = min_length.into_option();
231 self
232 }
233
234 #[must_use]
236 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
237 self.max_length = max_length.into_option();
238 self
239 }
240
241 #[must_use]
243 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
244 self.pattern = pattern.into_option();
245 self
246 }
247
248 #[must_use]
250 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
251 self.format = format.into_option();
252 self
253 }
254
255 #[must_use]
257 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
258 self.default = default.into_option();
259 self
260 }
261
262 #[must_use]
264 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
265 self.enum_values = enum_values.into_option();
266 self
267 }
268
269 #[must_use]
271 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
272 self.one_of = one_of.into_option();
273 self
274 }
275
276 #[must_use]
282 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
283 self.meta = meta.into_option();
284 self
285 }
286}
287
288#[serde_as]
290#[skip_serializing_none]
291#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
292#[serde(rename_all = "camelCase")]
293#[non_exhaustive]
294pub struct NumberPropertySchema {
295 #[serde_as(deserialize_as = "DefaultOnError")]
297 #[schemars(extend("x-deserialize-default-on-error" = true))]
298 #[serde(default)]
299 pub title: Option<String>,
300 #[serde_as(deserialize_as = "DefaultOnError")]
302 #[schemars(extend("x-deserialize-default-on-error" = true))]
303 #[serde(default)]
304 pub description: Option<String>,
305 #[serde(default)]
307 pub minimum: Option<f64>,
308 #[serde(default)]
310 pub maximum: Option<f64>,
311 #[serde_as(deserialize_as = "DefaultOnError")]
313 #[schemars(extend("x-deserialize-default-on-error" = true))]
314 #[serde(default)]
315 pub default: Option<f64>,
316 #[serde_as(deserialize_as = "DefaultOnError")]
322 #[schemars(extend("x-deserialize-default-on-error" = true))]
323 #[serde(default)]
324 #[serde(rename = "_meta")]
325 pub meta: Option<Meta>,
326}
327
328impl NumberPropertySchema {
329 #[must_use]
331 pub fn new() -> Self {
332 Self::default()
333 }
334
335 #[must_use]
337 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
338 self.title = title.into_option();
339 self
340 }
341
342 #[must_use]
344 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
345 self.description = description.into_option();
346 self
347 }
348
349 #[must_use]
351 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
352 self.minimum = minimum.into_option();
353 self
354 }
355
356 #[must_use]
358 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
359 self.maximum = maximum.into_option();
360 self
361 }
362
363 #[must_use]
365 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
366 self.default = default.into_option();
367 self
368 }
369
370 #[must_use]
376 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
377 self.meta = meta.into_option();
378 self
379 }
380}
381
382#[serde_as]
384#[skip_serializing_none]
385#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
386#[serde(rename_all = "camelCase")]
387#[non_exhaustive]
388pub struct IntegerPropertySchema {
389 #[serde_as(deserialize_as = "DefaultOnError")]
391 #[schemars(extend("x-deserialize-default-on-error" = true))]
392 #[serde(default)]
393 pub title: Option<String>,
394 #[serde_as(deserialize_as = "DefaultOnError")]
396 #[schemars(extend("x-deserialize-default-on-error" = true))]
397 #[serde(default)]
398 pub description: Option<String>,
399 #[serde(default)]
401 pub minimum: Option<i64>,
402 #[serde(default)]
404 pub maximum: Option<i64>,
405 #[serde_as(deserialize_as = "DefaultOnError")]
407 #[schemars(extend("x-deserialize-default-on-error" = true))]
408 #[serde(default)]
409 pub default: Option<i64>,
410 #[serde_as(deserialize_as = "DefaultOnError")]
416 #[schemars(extend("x-deserialize-default-on-error" = true))]
417 #[serde(default)]
418 #[serde(rename = "_meta")]
419 pub meta: Option<Meta>,
420}
421
422impl IntegerPropertySchema {
423 #[must_use]
425 pub fn new() -> Self {
426 Self::default()
427 }
428
429 #[must_use]
431 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
432 self.title = title.into_option();
433 self
434 }
435
436 #[must_use]
438 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
439 self.description = description.into_option();
440 self
441 }
442
443 #[must_use]
445 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
446 self.minimum = minimum.into_option();
447 self
448 }
449
450 #[must_use]
452 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
453 self.maximum = maximum.into_option();
454 self
455 }
456
457 #[must_use]
459 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
460 self.default = default.into_option();
461 self
462 }
463
464 #[must_use]
470 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
471 self.meta = meta.into_option();
472 self
473 }
474}
475
476#[serde_as]
478#[skip_serializing_none]
479#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
480#[serde(rename_all = "camelCase")]
481#[non_exhaustive]
482pub struct BooleanPropertySchema {
483 #[serde_as(deserialize_as = "DefaultOnError")]
485 #[schemars(extend("x-deserialize-default-on-error" = true))]
486 #[serde(default)]
487 pub title: Option<String>,
488 #[serde_as(deserialize_as = "DefaultOnError")]
490 #[schemars(extend("x-deserialize-default-on-error" = true))]
491 #[serde(default)]
492 pub description: Option<String>,
493 #[serde_as(deserialize_as = "DefaultOnError")]
495 #[schemars(extend("x-deserialize-default-on-error" = true))]
496 #[serde(default)]
497 pub default: Option<bool>,
498 #[serde_as(deserialize_as = "DefaultOnError")]
504 #[schemars(extend("x-deserialize-default-on-error" = true))]
505 #[serde(default)]
506 #[serde(rename = "_meta")]
507 pub meta: Option<Meta>,
508}
509
510impl BooleanPropertySchema {
511 #[must_use]
513 pub fn new() -> Self {
514 Self::default()
515 }
516
517 #[must_use]
519 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
520 self.title = title.into_option();
521 self
522 }
523
524 #[must_use]
526 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
527 self.description = description.into_option();
528 self
529 }
530
531 #[must_use]
533 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
534 self.default = default.into_option();
535 self
536 }
537
538 #[must_use]
544 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
545 self.meta = meta.into_option();
546 self
547 }
548}
549
550#[serde_as]
552#[skip_serializing_none]
553#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
554#[non_exhaustive]
555pub struct StringMultiSelectItems {
556 #[serde(rename = "enum")]
558 pub values: Vec<String>,
559 #[serde_as(deserialize_as = "DefaultOnError")]
565 #[schemars(extend("x-deserialize-default-on-error" = true))]
566 #[serde(default)]
567 #[serde(rename = "_meta")]
568 pub meta: Option<Meta>,
569}
570
571impl StringMultiSelectItems {
572 #[must_use]
574 pub fn new(values: Vec<String>) -> Self {
575 Self { values, meta: None }
576 }
577
578 #[must_use]
584 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
585 self.meta = meta.into_option();
586 self
587 }
588}
589
590#[serde_as]
592#[skip_serializing_none]
593#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
594#[non_exhaustive]
595pub struct TitledMultiSelectItems {
596 #[serde(rename = "anyOf")]
598 pub options: Vec<EnumOption>,
599 #[serde_as(deserialize_as = "DefaultOnError")]
605 #[schemars(extend("x-deserialize-default-on-error" = true))]
606 #[serde(default)]
607 #[serde(rename = "_meta")]
608 pub meta: Option<Meta>,
609}
610
611impl TitledMultiSelectItems {
612 #[must_use]
614 pub fn new(options: Vec<EnumOption>) -> Self {
615 Self {
616 options,
617 meta: None,
618 }
619 }
620
621 #[must_use]
627 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
628 self.meta = meta.into_option();
629 self
630 }
631}
632
633#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
639#[schemars(inline)]
640#[schemars(transform = other_multi_select_items_schema)]
641#[serde(rename_all = "camelCase")]
642#[non_exhaustive]
643pub struct OtherMultiSelectItems {
644 #[serde(rename = "type")]
650 pub type_: String,
651 #[serde(flatten)]
653 pub fields: BTreeMap<String, serde_json::Value>,
654}
655
656impl OtherMultiSelectItems {
657 #[must_use]
659 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
660 fields.remove("type");
661 Self {
662 type_: type_.into(),
663 fields,
664 }
665 }
666}
667
668impl<'de> Deserialize<'de> for OtherMultiSelectItems {
669 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
670 where
671 D: serde::Deserializer<'de>,
672 {
673 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
674 let type_ = fields
675 .remove("type")
676 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
677 let serde_json::Value::String(type_) = type_ else {
678 return Err(serde::de::Error::custom("`type` must be a string"));
679 };
680
681 if is_known_multi_select_item_type(&type_) {
682 return Err(serde::de::Error::custom(format!(
683 "known multi-select item type `{type_}` did not match its schema"
684 )));
685 }
686
687 Ok(Self { type_, fields })
688 }
689}
690
691const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
692
693fn is_known_multi_select_item_type(type_: &str) -> bool {
694 KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
695}
696
697fn other_multi_select_items_schema(schema: &mut Schema) {
698 schema.insert(
699 "not".into(),
700 serde_json::json!({
701 "anyOf": [
702 {
703 "properties": {
704 "type": {
705 "const": "string",
706 "type": "string"
707 }
708 },
709 "required": ["type"],
710 "type": "object"
711 }
712 ]
713 }),
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::new(options)),
799 default: None,
800 meta: None,
801 }
802 }
803
804 #[must_use]
806 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
807 self.title = title.into_option();
808 self
809 }
810
811 #[must_use]
813 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
814 self.description = description.into_option();
815 self
816 }
817
818 #[must_use]
820 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
821 self.min_items = min_items.into_option();
822 self
823 }
824
825 #[must_use]
827 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
828 self.max_items = max_items.into_option();
829 self
830 }
831
832 #[must_use]
834 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
835 self.default = default.into_option();
836 self
837 }
838
839 #[must_use]
845 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
846 self.meta = meta.into_option();
847 self
848 }
849}
850
851#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
857#[serde(tag = "type", rename_all = "snake_case")]
858#[schemars(extend("discriminator" = {"propertyName": "type"}))]
859#[non_exhaustive]
860pub enum ElicitationPropertySchema {
861 String(StringPropertySchema),
863 Number(NumberPropertySchema),
865 Integer(IntegerPropertySchema),
867 Boolean(BooleanPropertySchema),
869 Array(MultiSelectPropertySchema),
871 #[serde(untagged)]
881 Other(OtherElicitationPropertySchema),
882}
883
884#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
890#[schemars(inline)]
891#[schemars(transform = other_elicitation_property_schema_schema)]
892#[serde(rename_all = "camelCase")]
893#[non_exhaustive]
894pub struct OtherElicitationPropertySchema {
895 #[serde(rename = "type")]
901 pub type_: String,
902 #[serde(flatten)]
904 pub fields: BTreeMap<String, serde_json::Value>,
905}
906
907impl OtherElicitationPropertySchema {
908 #[must_use]
910 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
911 fields.remove("type");
912 Self {
913 type_: type_.into(),
914 fields,
915 }
916 }
917}
918
919impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
920 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
921 where
922 D: serde::Deserializer<'de>,
923 {
924 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
925 let type_ = fields
926 .remove("type")
927 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
928 let serde_json::Value::String(type_) = type_ else {
929 return Err(serde::de::Error::custom("`type` must be a string"));
930 };
931
932 if is_known_elicitation_property_schema_type(&type_) {
933 return Err(serde::de::Error::custom(format!(
934 "known elicitation property schema type `{type_}` did not match its schema"
935 )));
936 }
937
938 Ok(Self { type_, fields })
939 }
940}
941
942const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
943 &["string", "number", "integer", "boolean", "array"];
944
945fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
946 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
947}
948
949fn other_elicitation_property_schema_schema(schema: &mut Schema) {
950 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES
951 .iter()
952 .map(|value| {
953 serde_json::json!({
954 "properties": {
955 "type": {
956 "const": value,
957 "type": "string"
958 }
959 },
960 "required": ["type"],
961 "type": "object"
962 })
963 })
964 .collect();
965
966 schema.insert(
967 "not".into(),
968 serde_json::json!({
969 "anyOf": known_value_schemas
970 }),
971 );
972}
973
974impl From<StringPropertySchema> for ElicitationPropertySchema {
975 fn from(schema: StringPropertySchema) -> Self {
976 Self::String(schema)
977 }
978}
979
980impl From<NumberPropertySchema> for ElicitationPropertySchema {
981 fn from(schema: NumberPropertySchema) -> Self {
982 Self::Number(schema)
983 }
984}
985
986impl From<IntegerPropertySchema> for ElicitationPropertySchema {
987 fn from(schema: IntegerPropertySchema) -> Self {
988 Self::Integer(schema)
989 }
990}
991
992impl From<BooleanPropertySchema> for ElicitationPropertySchema {
993 fn from(schema: BooleanPropertySchema) -> Self {
994 Self::Boolean(schema)
995 }
996}
997
998impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
999 fn from(schema: MultiSelectPropertySchema) -> Self {
1000 Self::Array(schema)
1001 }
1002}
1003
1004fn default_object_type() -> ElicitationSchemaType {
1005 ElicitationSchemaType::Object
1006}
1007
1008#[serde_as]
1013#[skip_serializing_none]
1014#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1015#[serde(rename_all = "camelCase")]
1016#[non_exhaustive]
1017pub struct ElicitationSchema {
1018 #[serde_as(deserialize_as = "DefaultOnError")]
1020 #[schemars(extend("x-deserialize-default-on-error" = true))]
1021 #[serde(rename = "type", default = "default_object_type")]
1022 pub type_: ElicitationSchemaType,
1023 #[serde_as(deserialize_as = "DefaultOnError")]
1025 #[schemars(extend("x-deserialize-default-on-error" = true))]
1026 #[serde(default)]
1027 pub title: Option<String>,
1028 #[serde(default)]
1030 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1031 #[serde(default)]
1033 pub required: Option<Vec<String>>,
1034 #[serde_as(deserialize_as = "DefaultOnError")]
1036 #[schemars(extend("x-deserialize-default-on-error" = true))]
1037 #[serde(default)]
1038 pub description: Option<String>,
1039 #[serde_as(deserialize_as = "DefaultOnError")]
1045 #[schemars(extend("x-deserialize-default-on-error" = true))]
1046 #[serde(default)]
1047 #[serde(rename = "_meta")]
1048 pub meta: Option<Meta>,
1049}
1050
1051impl Default for ElicitationSchema {
1052 fn default() -> Self {
1053 Self {
1054 type_: default_object_type(),
1055 title: None,
1056 properties: BTreeMap::new(),
1057 required: None,
1058 description: None,
1059 meta: None,
1060 }
1061 }
1062}
1063
1064impl ElicitationSchema {
1065 #[must_use]
1067 pub fn new() -> Self {
1068 Self::default()
1069 }
1070
1071 #[must_use]
1073 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1074 self.title = title.into_option();
1075 self
1076 }
1077
1078 #[must_use]
1080 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1081 self.description = description.into_option();
1082 self
1083 }
1084
1085 #[must_use]
1091 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1092 self.meta = meta.into_option();
1093 self
1094 }
1095
1096 #[must_use]
1098 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1099 where
1100 S: Into<ElicitationPropertySchema>,
1101 {
1102 let name = name.into();
1103 self.properties.insert(name.clone(), schema.into());
1104
1105 if required {
1106 let required_fields = self.required.get_or_insert_with(Vec::new);
1107 if !required_fields.contains(&name) {
1108 required_fields.push(name);
1109 }
1110 } else if let Some(required_fields) = &mut self.required {
1111 required_fields.retain(|field| field != &name);
1112
1113 if required_fields.is_empty() {
1114 self.required = None;
1115 }
1116 }
1117
1118 self
1119 }
1120
1121 #[must_use]
1123 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1124 self.property(name, StringPropertySchema::new(), required)
1125 }
1126
1127 #[must_use]
1129 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1130 self.property(name, StringPropertySchema::email(), required)
1131 }
1132
1133 #[must_use]
1135 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1136 self.property(name, StringPropertySchema::uri(), required)
1137 }
1138
1139 #[must_use]
1141 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1142 self.property(name, StringPropertySchema::date(), required)
1143 }
1144
1145 #[must_use]
1147 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1148 self.property(name, StringPropertySchema::date_time(), required)
1149 }
1150
1151 #[must_use]
1153 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1154 self.property(
1155 name,
1156 NumberPropertySchema::new().minimum(min).maximum(max),
1157 required,
1158 )
1159 }
1160
1161 #[must_use]
1163 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1164 self.property(
1165 name,
1166 IntegerPropertySchema::new().minimum(min).maximum(max),
1167 required,
1168 )
1169 }
1170
1171 #[must_use]
1173 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1174 self.property(name, BooleanPropertySchema::new(), required)
1175 }
1176}
1177
1178#[serde_as]
1184#[skip_serializing_none]
1185#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1186#[serde(rename_all = "camelCase")]
1187#[non_exhaustive]
1188pub struct ElicitationCapabilities {
1189 #[serde_as(deserialize_as = "DefaultOnError")]
1194 #[schemars(extend("x-deserialize-default-on-error" = true))]
1195 #[serde(default)]
1196 pub form: Option<ElicitationFormCapabilities>,
1197 #[serde_as(deserialize_as = "DefaultOnError")]
1202 #[schemars(extend("x-deserialize-default-on-error" = true))]
1203 #[serde(default)]
1204 pub url: Option<ElicitationUrlCapabilities>,
1205 #[serde_as(deserialize_as = "DefaultOnError")]
1211 #[schemars(extend("x-deserialize-default-on-error" = true))]
1212 #[serde(default)]
1213 #[serde(rename = "_meta")]
1214 pub meta: Option<Meta>,
1215}
1216
1217impl ElicitationCapabilities {
1218 #[must_use]
1220 pub fn new() -> Self {
1221 Self::default()
1222 }
1223
1224 #[must_use]
1229 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1230 self.form = form.into_option();
1231 self
1232 }
1233
1234 #[must_use]
1239 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1240 self.url = url.into_option();
1241 self
1242 }
1243
1244 #[must_use]
1250 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1251 self.meta = meta.into_option();
1252 self
1253 }
1254}
1255
1256#[serde_as]
1264#[skip_serializing_none]
1265#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1266#[serde(rename_all = "camelCase")]
1267#[non_exhaustive]
1268pub struct ElicitationFormCapabilities {
1269 #[serde_as(deserialize_as = "DefaultOnError")]
1275 #[schemars(extend("x-deserialize-default-on-error" = true))]
1276 #[serde(default)]
1277 #[serde(rename = "_meta")]
1278 pub meta: Option<Meta>,
1279}
1280
1281impl ElicitationFormCapabilities {
1282 #[must_use]
1284 pub fn new() -> Self {
1285 Self::default()
1286 }
1287
1288 #[must_use]
1294 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1295 self.meta = meta.into_option();
1296 self
1297 }
1298}
1299
1300#[serde_as]
1308#[skip_serializing_none]
1309#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1310#[serde(rename_all = "camelCase")]
1311#[non_exhaustive]
1312pub struct ElicitationUrlCapabilities {
1313 #[serde_as(deserialize_as = "DefaultOnError")]
1319 #[schemars(extend("x-deserialize-default-on-error" = true))]
1320 #[serde(default)]
1321 #[serde(rename = "_meta")]
1322 pub meta: Option<Meta>,
1323}
1324
1325impl ElicitationUrlCapabilities {
1326 #[must_use]
1328 pub fn new() -> Self {
1329 Self::default()
1330 }
1331
1332 #[must_use]
1338 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1339 self.meta = meta.into_option();
1340 self
1341 }
1342}
1343
1344#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1350#[serde(untagged)]
1351#[non_exhaustive]
1352pub enum ElicitationScope {
1353 Session(ElicitationSessionScope),
1355 Request(ElicitationRequestScope),
1358}
1359
1360#[serde_as]
1370#[skip_serializing_none]
1371#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1372#[serde(rename_all = "camelCase")]
1373#[non_exhaustive]
1374pub struct ElicitationSessionScope {
1375 pub session_id: SessionId,
1377 #[serde_as(deserialize_as = "DefaultOnError")]
1379 #[schemars(extend("x-deserialize-default-on-error" = true))]
1380 #[serde(default)]
1381 pub tool_call_id: Option<ToolCallId>,
1382}
1383
1384impl ElicitationSessionScope {
1385 #[must_use]
1387 pub fn new(session_id: impl Into<SessionId>) -> Self {
1388 Self {
1389 session_id: session_id.into(),
1390 tool_call_id: None,
1391 }
1392 }
1393
1394 #[must_use]
1396 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1397 self.tool_call_id = tool_call_id.into_option();
1398 self
1399 }
1400}
1401
1402#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1409#[serde(rename_all = "camelCase")]
1410#[non_exhaustive]
1411pub struct ElicitationRequestScope {
1412 pub request_id: RequestId,
1414}
1415
1416impl ElicitationRequestScope {
1417 #[must_use]
1419 pub fn new(request_id: impl Into<RequestId>) -> Self {
1420 Self {
1421 request_id: request_id.into(),
1422 }
1423 }
1424}
1425
1426impl From<ElicitationSessionScope> for ElicitationScope {
1427 fn from(scope: ElicitationSessionScope) -> Self {
1428 Self::Session(scope)
1429 }
1430}
1431
1432impl From<ElicitationRequestScope> for ElicitationScope {
1433 fn from(scope: ElicitationRequestScope) -> Self {
1434 Self::Request(scope)
1435 }
1436}
1437
1438#[serde_as]
1448#[skip_serializing_none]
1449#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1450#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1451#[serde(rename_all = "camelCase")]
1452#[non_exhaustive]
1453pub struct CreateElicitationRequest {
1454 #[serde(flatten)]
1456 pub mode: ElicitationMode,
1457 pub message: String,
1459 #[serde_as(deserialize_as = "DefaultOnError")]
1465 #[schemars(extend("x-deserialize-default-on-error" = true))]
1466 #[serde(default)]
1467 #[serde(rename = "_meta")]
1468 pub meta: Option<Meta>,
1469}
1470
1471impl CreateElicitationRequest {
1472 #[must_use]
1474 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1475 Self {
1476 mode: mode.into(),
1477 message: message.into(),
1478 meta: None,
1479 }
1480 }
1481
1482 #[must_use]
1484 pub fn scope(&self) -> &ElicitationScope {
1485 self.mode.scope()
1486 }
1487
1488 #[must_use]
1494 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1495 self.meta = meta.into_option();
1496 self
1497 }
1498}
1499
1500#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1506#[serde(tag = "mode", rename_all = "snake_case")]
1507#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1508#[non_exhaustive]
1509pub enum ElicitationMode {
1510 Form(ElicitationFormMode),
1512 Url(ElicitationUrlMode),
1514 #[serde(untagged)]
1524 Other(OtherElicitationMode),
1525}
1526
1527#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1533#[schemars(inline)]
1534#[schemars(transform = other_elicitation_mode_schema)]
1535#[serde(rename_all = "camelCase")]
1536#[non_exhaustive]
1537pub struct OtherElicitationMode {
1538 pub mode: String,
1544 #[serde(flatten)]
1546 pub scope: ElicitationScope,
1547 #[serde(flatten)]
1549 pub fields: BTreeMap<String, serde_json::Value>,
1550}
1551
1552impl OtherElicitationMode {
1553 #[must_use]
1555 pub fn new(
1556 mode: impl Into<String>,
1557 scope: impl Into<ElicitationScope>,
1558 mut fields: BTreeMap<String, serde_json::Value>,
1559 ) -> Self {
1560 fields.remove("mode");
1561 remove_elicitation_scope_fields(&mut fields);
1562 Self {
1563 mode: mode.into(),
1564 scope: scope.into(),
1565 fields,
1566 }
1567 }
1568}
1569
1570impl<'de> Deserialize<'de> for OtherElicitationMode {
1571 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1572 where
1573 D: serde::Deserializer<'de>,
1574 {
1575 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1576 let mode = fields
1577 .remove("mode")
1578 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1579 let serde_json::Value::String(mode) = mode else {
1580 return Err(serde::de::Error::custom("`mode` must be a string"));
1581 };
1582
1583 if is_known_elicitation_mode(&mode) {
1584 return Err(serde::de::Error::custom(format!(
1585 "known elicitation mode `{mode}` did not match its schema"
1586 )));
1587 }
1588
1589 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1590 fields.clone().into_iter().collect(),
1591 ))
1592 .map_err(serde::de::Error::custom)?;
1593 remove_elicitation_scope_fields(&mut fields);
1594
1595 Ok(Self {
1596 mode,
1597 scope,
1598 fields,
1599 })
1600 }
1601}
1602
1603const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1604
1605fn is_known_elicitation_mode(mode: &str) -> bool {
1606 KNOWN_ELICITATION_MODES.contains(&mode)
1607}
1608
1609fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1610 fields.remove("sessionId");
1611 fields.remove("toolCallId");
1612 fields.remove("requestId");
1613}
1614
1615fn other_elicitation_mode_schema(schema: &mut Schema) {
1616 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_MODES
1617 .iter()
1618 .map(|value| {
1619 serde_json::json!({
1620 "properties": {
1621 "mode": {
1622 "const": value,
1623 "type": "string"
1624 }
1625 },
1626 "required": ["mode"],
1627 "type": "object"
1628 })
1629 })
1630 .collect();
1631
1632 schema.insert(
1633 "not".into(),
1634 serde_json::json!({
1635 "anyOf": known_value_schemas
1636 }),
1637 );
1638}
1639
1640impl From<ElicitationFormMode> for ElicitationMode {
1641 fn from(mode: ElicitationFormMode) -> Self {
1642 Self::Form(mode)
1643 }
1644}
1645
1646impl From<ElicitationUrlMode> for ElicitationMode {
1647 fn from(mode: ElicitationUrlMode) -> Self {
1648 Self::Url(mode)
1649 }
1650}
1651
1652impl From<OtherElicitationMode> for ElicitationMode {
1653 fn from(mode: OtherElicitationMode) -> Self {
1654 Self::Other(mode)
1655 }
1656}
1657
1658impl ElicitationMode {
1659 #[must_use]
1661 pub fn scope(&self) -> &ElicitationScope {
1662 match self {
1663 Self::Form(f) => &f.scope,
1664 Self::Url(u) => &u.scope,
1665 Self::Other(other) => &other.scope,
1666 }
1667 }
1668}
1669
1670#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1676#[serde(rename_all = "camelCase")]
1677#[non_exhaustive]
1678pub struct ElicitationFormMode {
1679 #[serde(flatten)]
1681 pub scope: ElicitationScope,
1682 pub requested_schema: ElicitationSchema,
1684}
1685
1686impl ElicitationFormMode {
1687 #[must_use]
1689 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1690 Self {
1691 scope: scope.into(),
1692 requested_schema,
1693 }
1694 }
1695}
1696
1697#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1703#[serde(rename_all = "camelCase")]
1704#[non_exhaustive]
1705pub struct ElicitationUrlMode {
1706 #[serde(flatten)]
1708 pub scope: ElicitationScope,
1709 pub elicitation_id: ElicitationId,
1711 #[schemars(extend("format" = "uri"))]
1713 pub url: String,
1714}
1715
1716impl ElicitationUrlMode {
1717 #[must_use]
1719 pub fn new(
1720 scope: impl Into<ElicitationScope>,
1721 elicitation_id: impl Into<ElicitationId>,
1722 url: impl Into<String>,
1723 ) -> Self {
1724 Self {
1725 scope: scope.into(),
1726 elicitation_id: elicitation_id.into(),
1727 url: url.into(),
1728 }
1729 }
1730}
1731
1732#[serde_as]
1738#[skip_serializing_none]
1739#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1740#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1741#[serde(rename_all = "camelCase")]
1742#[non_exhaustive]
1743pub struct CreateElicitationResponse {
1744 #[serde(flatten)]
1746 pub action: ElicitationAction,
1747 #[serde_as(deserialize_as = "DefaultOnError")]
1753 #[schemars(extend("x-deserialize-default-on-error" = true))]
1754 #[serde(default)]
1755 #[serde(rename = "_meta")]
1756 pub meta: Option<Meta>,
1757}
1758
1759impl CreateElicitationResponse {
1760 #[must_use]
1762 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1763 Self {
1764 action: action.into(),
1765 meta: None,
1766 }
1767 }
1768
1769 #[must_use]
1775 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1776 self.meta = meta.into_option();
1777 self
1778 }
1779}
1780
1781#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1787#[serde(tag = "action", rename_all = "snake_case")]
1788#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1789#[non_exhaustive]
1790pub enum ElicitationAction {
1791 Accept(ElicitationAcceptAction),
1793 Decline,
1795 Cancel,
1797 #[serde(untagged)]
1807 Other(OtherElicitationAction),
1808}
1809
1810#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1816#[schemars(inline)]
1817#[schemars(transform = other_elicitation_action_schema)]
1818#[serde(rename_all = "camelCase")]
1819#[non_exhaustive]
1820pub struct OtherElicitationAction {
1821 pub action: String,
1827 #[serde(flatten)]
1829 pub fields: BTreeMap<String, serde_json::Value>,
1830}
1831
1832impl OtherElicitationAction {
1833 #[must_use]
1835 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1836 fields.remove("action");
1837 Self {
1838 action: action.into(),
1839 fields,
1840 }
1841 }
1842}
1843
1844impl<'de> Deserialize<'de> for OtherElicitationAction {
1845 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1846 where
1847 D: serde::Deserializer<'de>,
1848 {
1849 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1850 let action = fields
1851 .remove("action")
1852 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1853 let serde_json::Value::String(action) = action else {
1854 return Err(serde::de::Error::custom("`action` must be a string"));
1855 };
1856
1857 if is_known_elicitation_action(&action) {
1858 return Err(serde::de::Error::custom(format!(
1859 "known elicitation action `{action}` did not match its schema"
1860 )));
1861 }
1862
1863 Ok(Self { action, fields })
1864 }
1865}
1866
1867const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1868
1869fn is_known_elicitation_action(action: &str) -> bool {
1870 KNOWN_ELICITATION_ACTIONS.contains(&action)
1871}
1872
1873fn other_elicitation_action_schema(schema: &mut Schema) {
1874 let known_value_schemas: Vec<_> = KNOWN_ELICITATION_ACTIONS
1875 .iter()
1876 .map(|value| {
1877 serde_json::json!({
1878 "properties": {
1879 "action": {
1880 "const": value,
1881 "type": "string"
1882 }
1883 },
1884 "required": ["action"],
1885 "type": "object"
1886 })
1887 })
1888 .collect();
1889
1890 schema.insert(
1891 "not".into(),
1892 serde_json::json!({
1893 "anyOf": known_value_schemas
1894 }),
1895 );
1896}
1897
1898impl From<ElicitationAcceptAction> for ElicitationAction {
1899 fn from(action: ElicitationAcceptAction) -> Self {
1900 Self::Accept(action)
1901 }
1902}
1903
1904impl From<OtherElicitationAction> for ElicitationAction {
1905 fn from(action: OtherElicitationAction) -> Self {
1906 Self::Other(action)
1907 }
1908}
1909
1910#[serde_as]
1916#[skip_serializing_none]
1917#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1918#[serde(rename_all = "camelCase")]
1919#[non_exhaustive]
1920pub struct ElicitationAcceptAction {
1921 #[serde(default)]
1923 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1924}
1925
1926impl ElicitationAcceptAction {
1927 #[must_use]
1929 pub fn new() -> Self {
1930 Self { content: None }
1931 }
1932
1933 #[must_use]
1935 pub fn content(
1936 mut self,
1937 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1938 ) -> Self {
1939 self.content = content.into_option();
1940 self
1941 }
1942}
1943
1944#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1946#[serde(untagged)]
1947#[non_exhaustive]
1948pub enum ElicitationContentValue {
1949 String(String),
1951 Integer(i64),
1953 Number(f64),
1955 Boolean(bool),
1957 StringArray(Vec<String>),
1959}
1960
1961impl From<String> for ElicitationContentValue {
1962 fn from(value: String) -> Self {
1963 Self::String(value)
1964 }
1965}
1966
1967impl From<&str> for ElicitationContentValue {
1968 fn from(value: &str) -> Self {
1969 Self::String(value.to_string())
1970 }
1971}
1972
1973impl From<i64> for ElicitationContentValue {
1974 fn from(value: i64) -> Self {
1975 Self::Integer(value)
1976 }
1977}
1978
1979impl From<i32> for ElicitationContentValue {
1980 fn from(value: i32) -> Self {
1981 Self::Integer(i64::from(value))
1982 }
1983}
1984
1985impl From<f64> for ElicitationContentValue {
1986 fn from(value: f64) -> Self {
1987 Self::Number(value)
1988 }
1989}
1990
1991impl From<bool> for ElicitationContentValue {
1992 fn from(value: bool) -> Self {
1993 Self::Boolean(value)
1994 }
1995}
1996
1997impl From<Vec<String>> for ElicitationContentValue {
1998 fn from(value: Vec<String>) -> Self {
1999 Self::StringArray(value)
2000 }
2001}
2002
2003impl From<Vec<&str>> for ElicitationContentValue {
2004 fn from(value: Vec<&str>) -> Self {
2005 Self::StringArray(value.into_iter().map(str::to_string).collect())
2006 }
2007}
2008
2009impl Default for ElicitationAcceptAction {
2010 fn default() -> Self {
2011 Self::new()
2012 }
2013}
2014
2015#[serde_as]
2021#[skip_serializing_none]
2022#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2023#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
2024#[serde(rename_all = "camelCase")]
2025#[non_exhaustive]
2026pub struct CompleteElicitationNotification {
2027 pub elicitation_id: ElicitationId,
2029 #[serde_as(deserialize_as = "DefaultOnError")]
2035 #[schemars(extend("x-deserialize-default-on-error" = true))]
2036 #[serde(default)]
2037 #[serde(rename = "_meta")]
2038 pub meta: Option<Meta>,
2039}
2040
2041impl CompleteElicitationNotification {
2042 #[must_use]
2044 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2045 Self {
2046 elicitation_id: elicitation_id.into(),
2047 meta: None,
2048 }
2049 }
2050
2051 #[must_use]
2057 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2058 self.meta = meta.into_option();
2059 self
2060 }
2061}
2062
2063#[cfg(test)]
2064mod tests {
2065 use super::*;
2066 use serde_json::json;
2067
2068 #[test]
2069 fn form_mode_request_serialization() {
2070 let schema = ElicitationSchema::new().string("name", true);
2071 let req = CreateElicitationRequest::new(
2072 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2073 "Please enter your name",
2074 );
2075
2076 let json = serde_json::to_value(&req).unwrap();
2077 assert_eq!(json["sessionId"], "sess_1");
2078 assert!(json.get("toolCallId").is_none());
2079 assert_eq!(json["mode"], "form");
2080 assert_eq!(json["message"], "Please enter your name");
2081 assert!(json["requestedSchema"].is_object());
2082 assert_eq!(json["requestedSchema"]["type"], "object");
2083 assert_eq!(
2084 json["requestedSchema"]["properties"]["name"]["type"],
2085 "string"
2086 );
2087
2088 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2089 assert_eq!(
2090 *roundtripped.scope(),
2091 ElicitationSessionScope::new("sess_1").into()
2092 );
2093 assert_eq!(roundtripped.message, "Please enter your name");
2094 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2095 }
2096
2097 #[test]
2098 fn url_mode_request_serialization() {
2099 let req = CreateElicitationRequest::new(
2100 ElicitationUrlMode::new(
2101 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2102 "elic_1",
2103 "https://example.com/auth",
2104 ),
2105 "Please authenticate",
2106 );
2107
2108 let json = serde_json::to_value(&req).unwrap();
2109 assert_eq!(json["sessionId"], "sess_2");
2110 assert_eq!(json["toolCallId"], "tc_1");
2111 assert_eq!(json["mode"], "url");
2112 assert_eq!(json["elicitationId"], "elic_1");
2113 assert_eq!(json["url"], "https://example.com/auth");
2114 assert_eq!(json["message"], "Please authenticate");
2115
2116 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2117 assert_eq!(
2118 *roundtripped.scope(),
2119 ElicitationSessionScope::new("sess_2")
2120 .tool_call_id("tc_1")
2121 .into()
2122 );
2123 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2124 }
2125
2126 #[test]
2127 fn response_accept_serialization() {
2128 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2129 ElicitationAcceptAction::new().content(BTreeMap::from([(
2130 "name".to_string(),
2131 ElicitationContentValue::from("Alice"),
2132 )])),
2133 ));
2134
2135 let json = serde_json::to_value(&resp).unwrap();
2136 assert_eq!(json["action"], "accept");
2137 assert_eq!(json["content"]["name"], "Alice");
2138
2139 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2140 assert!(matches!(
2141 roundtripped.action,
2142 ElicitationAction::Accept(ElicitationAcceptAction {
2143 content: Some(_),
2144 ..
2145 })
2146 ));
2147 }
2148
2149 #[test]
2150 fn response_decline_serialization() {
2151 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2152
2153 let json = serde_json::to_value(&resp).unwrap();
2154 assert_eq!(json["action"], "decline");
2155
2156 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2157 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2158 }
2159
2160 #[test]
2161 fn response_cancel_serialization() {
2162 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2163
2164 let json = serde_json::to_value(&resp).unwrap();
2165 assert_eq!(json["action"], "cancel");
2166
2167 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2168 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2169 }
2170
2171 #[test]
2172 fn unknown_action_response_serialization() {
2173 let json = json!({
2174 "action": "_defer",
2175 "reason": "waiting",
2176 "retryAfterMs": 1000
2177 });
2178
2179 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2180 let ElicitationAction::Other(other) = &resp.action else {
2181 panic!("expected unknown elicitation action");
2182 };
2183
2184 assert_eq!(other.action, "_defer");
2185 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2186 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2187 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2188 }
2189
2190 #[test]
2191 fn unknown_action_does_not_hide_known_action() {
2192 assert!(
2193 serde_json::from_value::<OtherElicitationAction>(json!({
2194 "action": "accept",
2195 "content": {}
2196 }))
2197 .is_err()
2198 );
2199 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2200 }
2201
2202 #[test]
2203 fn url_mode_request_scope_serialization() {
2204 let req = CreateElicitationRequest::new(
2205 ElicitationUrlMode::new(
2206 ElicitationRequestScope::new(RequestId::Number(42)),
2207 "elic_2",
2208 "https://example.com/setup",
2209 ),
2210 "Please complete setup",
2211 );
2212
2213 let json = serde_json::to_value(&req).unwrap();
2214 assert_eq!(json["requestId"], 42);
2215 assert!(json.get("sessionId").is_none());
2216 assert_eq!(json["mode"], "url");
2217 assert_eq!(json["elicitationId"], "elic_2");
2218 assert_eq!(json["url"], "https://example.com/setup");
2219 assert_eq!(json["message"], "Please complete setup");
2220
2221 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2222 assert_eq!(
2223 *roundtripped.scope(),
2224 ElicitationRequestScope::new(RequestId::Number(42)).into()
2225 );
2226 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2227 }
2228
2229 #[test]
2230 fn unknown_mode_request_serialization() {
2231 let json = json!({
2232 "requestId": 42,
2233 "mode": "_browser",
2234 "message": "Open a browser window",
2235 "target": "login"
2236 });
2237
2238 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2239 let ElicitationMode::Other(other) = &req.mode else {
2240 panic!("expected unknown elicitation mode");
2241 };
2242
2243 assert_eq!(other.mode, "_browser");
2244 assert_eq!(
2245 other.scope,
2246 ElicitationRequestScope::new(RequestId::Number(42)).into()
2247 );
2248 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2249 assert_eq!(
2250 *req.scope(),
2251 ElicitationRequestScope::new(RequestId::Number(42)).into()
2252 );
2253 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2254 }
2255
2256 #[test]
2257 fn unknown_mode_does_not_hide_malformed_known_mode() {
2258 let missing_requested_schema = json!({
2259 "requestId": 42,
2260 "mode": "form",
2261 "message": "Enter your name"
2262 });
2263
2264 assert!(
2265 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2266 );
2267 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2268 }
2269
2270 #[test]
2271 fn request_scope_request_serialization() {
2272 let req = CreateElicitationRequest::new(
2273 ElicitationFormMode::new(
2274 ElicitationRequestScope::new(RequestId::Number(99)),
2275 ElicitationSchema::new().string("workspace", true),
2276 ),
2277 "Enter workspace name",
2278 );
2279
2280 let json = serde_json::to_value(&req).unwrap();
2281 assert_eq!(json["requestId"], 99);
2282 assert!(json.get("sessionId").is_none());
2283
2284 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2285 assert_eq!(
2286 *roundtripped.scope(),
2287 ElicitationRequestScope::new(RequestId::Number(99)).into()
2288 );
2289 }
2290
2291 #[test]
2298 fn client_response_serialization_accept() {
2299 use crate::v1::ClientResponse;
2300
2301 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2302 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
2303 "name".to_string(),
2304 ElicitationContentValue::from("Alice"),
2305 )]))),
2306 ));
2307 let json = serde_json::to_value(&resp).unwrap();
2308 assert_eq!(json["action"], "accept");
2309 assert_eq!(json["content"]["name"], "Alice");
2310
2311 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2313 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2314 }
2315
2316 #[test]
2317 fn client_response_serialization_decline() {
2318 use crate::v1::ClientResponse;
2319
2320 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2321 ElicitationAction::Decline,
2322 ));
2323 let json = serde_json::to_value(&resp).unwrap();
2324 assert_eq!(json["action"], "decline");
2325
2326 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2327 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2328 }
2329
2330 #[test]
2331 fn client_response_serialization_cancel() {
2332 use crate::v1::ClientResponse;
2333
2334 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
2335 ElicitationAction::Cancel,
2336 ));
2337 let json = serde_json::to_value(&resp).unwrap();
2338 assert_eq!(json["action"], "cancel");
2339
2340 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2341 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2342 }
2343
2344 #[test]
2347 fn request_tolerates_extra_fields() {
2348 let json = json!({
2349 "sessionId": "sess_1",
2350 "mode": "form",
2351 "message": "Enter your name",
2352 "requestedSchema": {
2353 "type": "object",
2354 "properties": {
2355 "name": { "type": "string", "title": "Name" }
2356 },
2357 "required": ["name"]
2358 },
2359 "unknownStringField": "hello",
2360 "unknownNumberField": 42
2361 });
2362
2363 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2364 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2365 assert_eq!(req.message, "Enter your name");
2366 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2367 }
2368
2369 #[test]
2370 fn completion_notification_serialization() {
2371 let notif = CompleteElicitationNotification::new("elic_1");
2372
2373 let json = serde_json::to_value(¬if).unwrap();
2374 assert_eq!(json["elicitationId"], "elic_1");
2375
2376 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2377 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2378 }
2379
2380 #[test]
2381 fn capabilities_form_only() {
2382 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2383
2384 let json = serde_json::to_value(&caps).unwrap();
2385 assert!(json["form"].is_object());
2386 assert!(json.get("url").is_none());
2387
2388 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2389 assert!(roundtripped.form.is_some());
2390 assert!(roundtripped.url.is_none());
2391 }
2392
2393 #[test]
2394 fn capabilities_url_only() {
2395 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2396
2397 let json = serde_json::to_value(&caps).unwrap();
2398 assert!(json.get("form").is_none());
2399 assert!(json["url"].is_object());
2400
2401 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2402 assert!(roundtripped.form.is_none());
2403 assert!(roundtripped.url.is_some());
2404 }
2405
2406 #[test]
2407 fn capabilities_both() {
2408 let caps = ElicitationCapabilities::new()
2409 .form(ElicitationFormCapabilities::new())
2410 .url(ElicitationUrlCapabilities::new());
2411
2412 let json = serde_json::to_value(&caps).unwrap();
2413 assert!(json["form"].is_object());
2414 assert!(json["url"].is_object());
2415
2416 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2417 assert!(roundtripped.form.is_some());
2418 assert!(roundtripped.url.is_some());
2419 }
2420
2421 #[test]
2422 fn schema_default_sets_object_type() {
2423 let schema = ElicitationSchema::default();
2424
2425 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2426 assert!(schema.properties.is_empty());
2427
2428 let json = serde_json::to_value(&schema).unwrap();
2429 assert_eq!(json["type"], "object");
2430 }
2431
2432 #[test]
2433 fn schema_builder_serialization() {
2434 let schema = ElicitationSchema::new()
2435 .string("name", true)
2436 .email("email", true)
2437 .integer("age", 0, 150, true)
2438 .boolean("newsletter", false)
2439 .description("User registration");
2440
2441 let json = serde_json::to_value(&schema).unwrap();
2442 assert_eq!(json["type"], "object");
2443 assert_eq!(json["description"], "User registration");
2444 assert_eq!(json["properties"]["name"]["type"], "string");
2445 assert_eq!(json["properties"]["email"]["type"], "string");
2446 assert_eq!(json["properties"]["email"]["format"], "email");
2447 assert_eq!(json["properties"]["age"]["type"], "integer");
2448 assert_eq!(json["properties"]["age"]["minimum"], 0);
2449 assert_eq!(json["properties"]["age"]["maximum"], 150);
2450 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2451
2452 let required = json["required"].as_array().unwrap();
2453 assert!(required.contains(&json!("name")));
2454 assert!(required.contains(&json!("email")));
2455 assert!(required.contains(&json!("age")));
2456 assert!(!required.contains(&json!("newsletter")));
2457
2458 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2459 assert_eq!(roundtripped.properties.len(), 4);
2460 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2461 }
2462
2463 #[test]
2464 fn schema_string_enum_serialization() {
2465 let schema = ElicitationSchema::new().property(
2466 "color",
2467 StringPropertySchema::new().enum_values(vec![
2468 "red".into(),
2469 "green".into(),
2470 "blue".into(),
2471 ]),
2472 true,
2473 );
2474
2475 let json = serde_json::to_value(&schema).unwrap();
2476 assert_eq!(json["properties"]["color"]["type"], "string");
2477 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2478 assert_eq!(enum_vals.len(), 3);
2479
2480 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2481 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2482 {
2483 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2484 } else {
2485 panic!("expected String variant");
2486 }
2487 }
2488
2489 #[test]
2490 fn schema_multi_select_serialization() {
2491 let schema = ElicitationSchema::new().property(
2492 "colors",
2493 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2494 .min_items(1)
2495 .max_items(3),
2496 false,
2497 );
2498
2499 let json = serde_json::to_value(&schema).unwrap();
2500 assert_eq!(json["properties"]["colors"]["type"], "array");
2501 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2502 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2503 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2504
2505 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2506 let ElicitationPropertySchema::Array(array) =
2507 roundtripped.properties.get("colors").unwrap()
2508 else {
2509 panic!("expected Array variant");
2510 };
2511 let MultiSelectItems::String(items) = &array.items else {
2512 panic!("expected String multi-select items");
2513 };
2514 assert_eq!(items.values.len(), 3);
2515 }
2516
2517 #[test]
2518 fn multi_select_titled_items_keep_mcp_shape() {
2519 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2520 "#ff0000", "Red",
2521 )]));
2522
2523 let json = serde_json::to_value(&items).unwrap();
2524 assert!(json.get("type").is_none());
2525 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2526 assert_eq!(json["anyOf"][0]["title"], "Red");
2527
2528 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2529 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2530 }
2531
2532 #[test]
2533 fn multi_select_items_preserve_unknown_type() {
2534 let json = json!({
2535 "type": "_token",
2536 "format": "workspace",
2537 "anyOf": [
2538 { "const": "repo", "title": "Repository" }
2539 ]
2540 });
2541
2542 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2543 let MultiSelectItems::Other(other) = &items else {
2544 panic!("expected unknown multi-select items");
2545 };
2546
2547 assert_eq!(other.type_, "_token");
2548 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2549 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2550 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2551 }
2552
2553 #[test]
2554 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2555 assert!(
2556 serde_json::from_value::<MultiSelectItems>(json!({
2557 "type": "string"
2558 }))
2559 .is_err()
2560 );
2561 assert!(
2562 serde_json::from_value::<OtherMultiSelectItems>(json!({
2563 "type": "string",
2564 "format": "workspace"
2565 }))
2566 .is_err()
2567 );
2568 }
2569
2570 #[test]
2571 fn property_schema_preserves_unknown_type() {
2572 let schema: ElicitationSchema = serde_json::from_value(json!({
2573 "type": "object",
2574 "properties": {
2575 "location": {
2576 "type": "_location",
2577 "title": "Location",
2578 "precision": "city"
2579 }
2580 }
2581 }))
2582 .unwrap();
2583
2584 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2585 else {
2586 panic!("expected unknown property schema");
2587 };
2588
2589 assert_eq!(unknown.type_, "_location");
2590 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2591 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2592 assert_eq!(
2593 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2594 json!({
2595 "type": "_location",
2596 "title": "Location",
2597 "precision": "city"
2598 })
2599 );
2600 }
2601
2602 #[test]
2603 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2604 assert!(
2605 serde_json::from_value::<ElicitationPropertySchema>(json!({
2606 "type": "array"
2607 }))
2608 .is_err()
2609 );
2610 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2611 }
2612
2613 #[test]
2614 fn schema_titled_enum_serialization() {
2615 let schema = ElicitationSchema::new().property(
2616 "country",
2617 StringPropertySchema::new().one_of(vec![
2618 EnumOption::new("us", "United States"),
2619 EnumOption::new("uk", "United Kingdom"),
2620 ]),
2621 true,
2622 );
2623
2624 let json = serde_json::to_value(&schema).unwrap();
2625 assert_eq!(json["properties"]["country"]["type"], "string");
2626 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2627 assert_eq!(one_of.len(), 2);
2628 assert_eq!(one_of[0]["const"], "us");
2629 assert_eq!(one_of[0]["title"], "United States");
2630
2631 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2632 if let ElicitationPropertySchema::String(s) =
2633 roundtripped.properties.get("country").unwrap()
2634 {
2635 assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
2636 } else {
2637 panic!("expected String variant");
2638 }
2639 }
2640
2641 #[test]
2642 fn schema_number_property_serialization() {
2643 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2644
2645 let json = serde_json::to_value(&schema).unwrap();
2646 assert_eq!(json["properties"]["rating"]["type"], "number");
2647 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2648 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2649
2650 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2651 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2652 {
2653 assert_eq!(n.minimum, Some(0.0));
2654 assert_eq!(n.maximum, Some(5.0));
2655 } else {
2656 panic!("expected Number variant");
2657 }
2658 }
2659
2660 #[test]
2661 fn schema_string_format_serialization() {
2662 let schema = ElicitationSchema::new()
2663 .uri("website", true)
2664 .date("birthday", true)
2665 .date_time("updated_at", false);
2666
2667 let json = serde_json::to_value(&schema).unwrap();
2668 assert_eq!(json["properties"]["website"]["type"], "string");
2669 assert_eq!(json["properties"]["website"]["format"], "uri");
2670 assert_eq!(json["properties"]["birthday"]["type"], "string");
2671 assert_eq!(json["properties"]["birthday"]["format"], "date");
2672 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2673 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2674
2675 let required = json["required"].as_array().unwrap();
2676 assert!(required.contains(&json!("website")));
2677 assert!(required.contains(&json!("birthday")));
2678 assert!(!required.contains(&json!("updated_at")));
2679 }
2680
2681 #[test]
2682 fn schema_string_pattern_serialization() {
2683 let schema = ElicitationSchema::new().property(
2684 "name",
2685 StringPropertySchema::new()
2686 .min_length(1)
2687 .max_length(64)
2688 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2689 true,
2690 );
2691
2692 let json = serde_json::to_value(&schema).unwrap();
2693 assert_eq!(json["properties"]["name"]["type"], "string");
2694 assert_eq!(
2695 json["properties"]["name"]["pattern"],
2696 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2697 );
2698
2699 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2700 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2701 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2702 } else {
2703 panic!("expected String variant");
2704 }
2705 }
2706
2707 #[test]
2708 fn schema_property_updates_required_state() {
2709 let schema = ElicitationSchema::new()
2710 .string("name", true)
2711 .email("name", false);
2712
2713 let json = serde_json::to_value(&schema).unwrap();
2714 assert!(json.get("required").is_none());
2715 assert_eq!(json["properties"]["name"]["format"], "email");
2716 }
2717
2718 #[test]
2719 fn schema_defaults_invalid_object_type() {
2720 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2721 "type": "array",
2722 "properties": {
2723 "name": {
2724 "type": "string"
2725 }
2726 }
2727 }))
2728 .unwrap();
2729
2730 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2731 assert!(schema.properties.contains_key("name"));
2732 }
2733
2734 #[test]
2735 fn titled_multi_select_items_reject_one_of() {
2736 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2737 "oneOf": [
2738 {
2739 "const": "red",
2740 "title": "Red"
2741 }
2742 ]
2743 }))
2744 .unwrap_err();
2745
2746 assert!(err.to_string().contains("missing field `anyOf`"));
2747 }
2748
2749 #[test]
2750 fn response_accept_rejects_non_object_content() {
2751 assert!(
2752 serde_json::from_value::<CreateElicitationResponse>(json!({
2753 "action": "accept",
2754 "content": "Alice"
2755 }))
2756 .is_err()
2757 );
2758 }
2759
2760 #[test]
2761 fn response_accept_rejects_nested_object_content() {
2762 assert!(
2763 serde_json::from_value::<CreateElicitationResponse>(json!({
2764 "action": "accept",
2765 "content": {
2766 "profile": {
2767 "name": "Alice"
2768 }
2769 }
2770 }))
2771 .is_err()
2772 );
2773 }
2774
2775 #[test]
2776 fn response_accept_allows_primitive_and_string_array_content() {
2777 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2778 ElicitationAcceptAction::new().content(BTreeMap::from([
2779 ("name".to_string(), ElicitationContentValue::from("Alice")),
2780 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2781 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2782 (
2783 "subscribed".to_string(),
2784 ElicitationContentValue::from(true),
2785 ),
2786 (
2787 "tags".to_string(),
2788 ElicitationContentValue::from(vec!["rust", "acp"]),
2789 ),
2790 ])),
2791 ));
2792
2793 let json = serde_json::to_value(&response).unwrap();
2794 assert_eq!(json["action"], "accept");
2795 assert_eq!(json["content"]["name"], "Alice");
2796 assert_eq!(json["content"]["age"], 30);
2797 assert_eq!(json["content"]["score"], 9.5);
2798 assert_eq!(json["content"]["subscribed"], true);
2799 assert_eq!(json["content"]["tags"][0], "rust");
2800 assert_eq!(json["content"]["tags"][1], "acp");
2801 }
2802}