1use std::{collections::BTreeMap, sync::Arc};
9
10use derive_more::{Display, From};
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use serde_with::{DefaultOnError, serde_as, skip_serializing_none};
14
15use super::{
16 ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, Meta, RequestId, SessionId,
17 ToolCallId,
18};
19use crate::IntoOption;
20
21#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
27#[serde(transparent)]
28#[from(Arc<str>, String, &'static str)]
29#[non_exhaustive]
30pub struct ElicitationId(pub Arc<str>);
31
32impl ElicitationId {
33 #[must_use]
34 pub fn new(id: impl Into<Arc<str>>) -> Self {
35 Self(id.into())
36 }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
41#[serde(rename_all = "kebab-case")]
42#[non_exhaustive]
43pub enum StringFormat {
44 Email,
46 Uri,
48 Date,
50 DateTime,
52 #[serde(untagged)]
57 Other(String),
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
62#[serde(rename_all = "snake_case")]
63#[non_exhaustive]
64pub enum ElicitationSchemaType {
65 #[default]
67 Object,
68}
69
70#[skip_serializing_none]
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73#[non_exhaustive]
74pub struct EnumOption {
75 #[serde(rename = "const")]
77 pub value: String,
78 pub title: String,
80 #[serde(rename = "_meta")]
86 pub meta: Option<Meta>,
87}
88
89impl EnumOption {
90 #[must_use]
92 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
93 Self {
94 value: value.into(),
95 title: title.into(),
96 meta: None,
97 }
98 }
99
100 #[must_use]
106 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
107 self.meta = meta.into_option();
108 self
109 }
110}
111
112#[skip_serializing_none]
117#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
118#[serde(rename_all = "camelCase")]
119#[non_exhaustive]
120pub struct StringPropertySchema {
121 pub title: Option<String>,
123 pub description: Option<String>,
125 pub min_length: Option<u32>,
127 pub max_length: Option<u32>,
129 pub pattern: Option<String>,
131 pub format: Option<StringFormat>,
133 pub default: Option<String>,
135 #[serde(rename = "enum")]
137 pub enum_values: Option<Vec<String>>,
138 #[serde(rename = "oneOf")]
140 pub one_of: Option<Vec<EnumOption>>,
141 #[serde(rename = "_meta")]
147 pub meta: Option<Meta>,
148}
149
150impl StringPropertySchema {
151 #[must_use]
153 pub fn new() -> Self {
154 Self::default()
155 }
156
157 #[must_use]
159 pub fn email() -> Self {
160 Self {
161 format: Some(StringFormat::Email),
162 ..Default::default()
163 }
164 }
165
166 #[must_use]
168 pub fn uri() -> Self {
169 Self {
170 format: Some(StringFormat::Uri),
171 ..Default::default()
172 }
173 }
174
175 #[must_use]
177 pub fn date() -> Self {
178 Self {
179 format: Some(StringFormat::Date),
180 ..Default::default()
181 }
182 }
183
184 #[must_use]
186 pub fn date_time() -> Self {
187 Self {
188 format: Some(StringFormat::DateTime),
189 ..Default::default()
190 }
191 }
192
193 #[must_use]
195 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
196 self.title = title.into_option();
197 self
198 }
199
200 #[must_use]
202 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
203 self.description = description.into_option();
204 self
205 }
206
207 #[must_use]
209 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
210 self.min_length = min_length.into_option();
211 self
212 }
213
214 #[must_use]
216 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
217 self.max_length = max_length.into_option();
218 self
219 }
220
221 #[must_use]
223 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
224 self.pattern = pattern.into_option();
225 self
226 }
227
228 #[must_use]
230 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
231 self.format = format.into_option();
232 self
233 }
234
235 #[must_use]
237 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
238 self.default = default.into_option();
239 self
240 }
241
242 #[must_use]
244 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
245 self.enum_values = enum_values.into_option();
246 self
247 }
248
249 #[must_use]
251 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
252 self.one_of = one_of.into_option();
253 self
254 }
255
256 #[must_use]
262 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
263 self.meta = meta.into_option();
264 self
265 }
266}
267
268#[skip_serializing_none]
270#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
271#[serde(rename_all = "camelCase")]
272#[non_exhaustive]
273pub struct NumberPropertySchema {
274 pub title: Option<String>,
276 pub description: Option<String>,
278 pub minimum: Option<f64>,
280 pub maximum: Option<f64>,
282 pub default: Option<f64>,
284 #[serde(rename = "_meta")]
290 pub meta: Option<Meta>,
291}
292
293impl NumberPropertySchema {
294 #[must_use]
296 pub fn new() -> Self {
297 Self::default()
298 }
299
300 #[must_use]
302 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
303 self.title = title.into_option();
304 self
305 }
306
307 #[must_use]
309 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
310 self.description = description.into_option();
311 self
312 }
313
314 #[must_use]
316 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
317 self.minimum = minimum.into_option();
318 self
319 }
320
321 #[must_use]
323 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
324 self.maximum = maximum.into_option();
325 self
326 }
327
328 #[must_use]
330 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
331 self.default = default.into_option();
332 self
333 }
334
335 #[must_use]
341 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
342 self.meta = meta.into_option();
343 self
344 }
345}
346
347#[skip_serializing_none]
349#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
350#[serde(rename_all = "camelCase")]
351#[non_exhaustive]
352pub struct IntegerPropertySchema {
353 pub title: Option<String>,
355 pub description: Option<String>,
357 pub minimum: Option<i64>,
359 pub maximum: Option<i64>,
361 pub default: Option<i64>,
363 #[serde(rename = "_meta")]
369 pub meta: Option<Meta>,
370}
371
372impl IntegerPropertySchema {
373 #[must_use]
375 pub fn new() -> Self {
376 Self::default()
377 }
378
379 #[must_use]
381 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
382 self.title = title.into_option();
383 self
384 }
385
386 #[must_use]
388 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
389 self.description = description.into_option();
390 self
391 }
392
393 #[must_use]
395 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
396 self.minimum = minimum.into_option();
397 self
398 }
399
400 #[must_use]
402 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
403 self.maximum = maximum.into_option();
404 self
405 }
406
407 #[must_use]
409 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
410 self.default = default.into_option();
411 self
412 }
413
414 #[must_use]
420 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
421 self.meta = meta.into_option();
422 self
423 }
424}
425
426#[skip_serializing_none]
428#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
429#[serde(rename_all = "camelCase")]
430#[non_exhaustive]
431pub struct BooleanPropertySchema {
432 pub title: Option<String>,
434 pub description: Option<String>,
436 pub default: Option<bool>,
438 #[serde(rename = "_meta")]
444 pub meta: Option<Meta>,
445}
446
447impl BooleanPropertySchema {
448 #[must_use]
450 pub fn new() -> Self {
451 Self::default()
452 }
453
454 #[must_use]
456 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
457 self.title = title.into_option();
458 self
459 }
460
461 #[must_use]
463 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
464 self.description = description.into_option();
465 self
466 }
467
468 #[must_use]
470 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
471 self.default = default.into_option();
472 self
473 }
474
475 #[must_use]
481 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
482 self.meta = meta.into_option();
483 self
484 }
485}
486
487#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
489#[serde(rename_all = "snake_case")]
490#[non_exhaustive]
491pub enum ElicitationStringType {
492 #[default]
494 String,
495}
496
497#[skip_serializing_none]
499#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
500#[non_exhaustive]
501pub struct UntitledMultiSelectItems {
502 #[serde(rename = "type")]
504 pub type_: ElicitationStringType,
505 #[serde(rename = "enum")]
507 pub values: Vec<String>,
508 #[serde(rename = "_meta")]
514 pub meta: Option<Meta>,
515}
516
517impl UntitledMultiSelectItems {
518 #[must_use]
524 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
525 self.meta = meta.into_option();
526 self
527 }
528}
529
530#[skip_serializing_none]
532#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
533#[non_exhaustive]
534pub struct TitledMultiSelectItems {
535 #[serde(rename = "anyOf")]
537 pub options: Vec<EnumOption>,
538 #[serde(rename = "_meta")]
544 pub meta: Option<Meta>,
545}
546
547impl TitledMultiSelectItems {
548 #[must_use]
550 pub fn new(options: Vec<EnumOption>) -> Self {
551 Self {
552 options,
553 meta: None,
554 }
555 }
556
557 #[must_use]
563 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
564 self.meta = meta.into_option();
565 self
566 }
567}
568
569#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
571#[serde(untagged)]
572#[non_exhaustive]
573pub enum MultiSelectItems {
574 Untitled(UntitledMultiSelectItems),
576 Titled(TitledMultiSelectItems),
578}
579
580#[skip_serializing_none]
582#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
583#[serde(rename_all = "camelCase")]
584#[non_exhaustive]
585pub struct MultiSelectPropertySchema {
586 pub title: Option<String>,
588 pub description: Option<String>,
590 pub min_items: Option<u64>,
592 pub max_items: Option<u64>,
594 pub items: MultiSelectItems,
596 pub default: Option<Vec<String>>,
598 #[serde(rename = "_meta")]
604 pub meta: Option<Meta>,
605}
606
607impl MultiSelectPropertySchema {
608 #[must_use]
610 pub fn new(values: Vec<String>) -> Self {
611 Self {
612 title: None,
613 description: None,
614 min_items: None,
615 max_items: None,
616 items: MultiSelectItems::Untitled(UntitledMultiSelectItems {
617 type_: ElicitationStringType::String,
618 values,
619 meta: None,
620 }),
621 default: None,
622 meta: None,
623 }
624 }
625
626 #[must_use]
628 pub fn titled(options: Vec<EnumOption>) -> Self {
629 Self {
630 title: None,
631 description: None,
632 min_items: None,
633 max_items: None,
634 items: MultiSelectItems::Titled(TitledMultiSelectItems {
635 options,
636 meta: None,
637 }),
638 default: None,
639 meta: None,
640 }
641 }
642
643 #[must_use]
645 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
646 self.title = title.into_option();
647 self
648 }
649
650 #[must_use]
652 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
653 self.description = description.into_option();
654 self
655 }
656
657 #[must_use]
659 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
660 self.min_items = min_items.into_option();
661 self
662 }
663
664 #[must_use]
666 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
667 self.max_items = max_items.into_option();
668 self
669 }
670
671 #[must_use]
673 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
674 self.default = default.into_option();
675 self
676 }
677
678 #[must_use]
684 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
685 self.meta = meta.into_option();
686 self
687 }
688}
689
690#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
696#[serde(tag = "type", rename_all = "snake_case")]
697#[schemars(extend("discriminator" = {"propertyName": "type"}))]
698#[non_exhaustive]
699pub enum ElicitationPropertySchema {
700 String(StringPropertySchema),
702 Number(NumberPropertySchema),
704 Integer(IntegerPropertySchema),
706 Boolean(BooleanPropertySchema),
708 Array(MultiSelectPropertySchema),
710}
711
712impl From<StringPropertySchema> for ElicitationPropertySchema {
713 fn from(schema: StringPropertySchema) -> Self {
714 Self::String(schema)
715 }
716}
717
718impl From<NumberPropertySchema> for ElicitationPropertySchema {
719 fn from(schema: NumberPropertySchema) -> Self {
720 Self::Number(schema)
721 }
722}
723
724impl From<IntegerPropertySchema> for ElicitationPropertySchema {
725 fn from(schema: IntegerPropertySchema) -> Self {
726 Self::Integer(schema)
727 }
728}
729
730impl From<BooleanPropertySchema> for ElicitationPropertySchema {
731 fn from(schema: BooleanPropertySchema) -> Self {
732 Self::Boolean(schema)
733 }
734}
735
736impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
737 fn from(schema: MultiSelectPropertySchema) -> Self {
738 Self::Array(schema)
739 }
740}
741
742fn default_object_type() -> ElicitationSchemaType {
743 ElicitationSchemaType::Object
744}
745
746#[skip_serializing_none]
751#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
752#[serde(rename_all = "camelCase")]
753#[non_exhaustive]
754pub struct ElicitationSchema {
755 #[serde(rename = "type", default = "default_object_type")]
757 pub type_: ElicitationSchemaType,
758 pub title: Option<String>,
760 #[serde(default)]
762 pub properties: BTreeMap<String, ElicitationPropertySchema>,
763 pub required: Option<Vec<String>>,
765 pub description: Option<String>,
767 #[serde(rename = "_meta")]
773 pub meta: Option<Meta>,
774}
775
776impl Default for ElicitationSchema {
777 fn default() -> Self {
778 Self {
779 type_: default_object_type(),
780 title: None,
781 properties: BTreeMap::new(),
782 required: None,
783 description: None,
784 meta: None,
785 }
786 }
787}
788
789impl ElicitationSchema {
790 #[must_use]
792 pub fn new() -> Self {
793 Self::default()
794 }
795
796 #[must_use]
798 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
799 self.title = title.into_option();
800 self
801 }
802
803 #[must_use]
805 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
806 self.description = description.into_option();
807 self
808 }
809
810 #[must_use]
816 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
817 self.meta = meta.into_option();
818 self
819 }
820
821 #[must_use]
823 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
824 where
825 S: Into<ElicitationPropertySchema>,
826 {
827 let name = name.into();
828 self.properties.insert(name.clone(), schema.into());
829
830 if required {
831 let required_fields = self.required.get_or_insert_with(Vec::new);
832 if !required_fields.contains(&name) {
833 required_fields.push(name);
834 }
835 } else if let Some(required_fields) = &mut self.required {
836 required_fields.retain(|field| field != &name);
837
838 if required_fields.is_empty() {
839 self.required = None;
840 }
841 }
842
843 self
844 }
845
846 #[must_use]
848 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
849 self.property(name, StringPropertySchema::new(), required)
850 }
851
852 #[must_use]
854 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
855 self.property(name, StringPropertySchema::email(), required)
856 }
857
858 #[must_use]
860 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
861 self.property(name, StringPropertySchema::uri(), required)
862 }
863
864 #[must_use]
866 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
867 self.property(name, StringPropertySchema::date(), required)
868 }
869
870 #[must_use]
872 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
873 self.property(name, StringPropertySchema::date_time(), required)
874 }
875
876 #[must_use]
878 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
879 self.property(
880 name,
881 NumberPropertySchema::new().minimum(min).maximum(max),
882 required,
883 )
884 }
885
886 #[must_use]
888 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
889 self.property(
890 name,
891 IntegerPropertySchema::new().minimum(min).maximum(max),
892 required,
893 )
894 }
895
896 #[must_use]
898 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
899 self.property(name, BooleanPropertySchema::new(), required)
900 }
901}
902
903#[serde_as]
909#[skip_serializing_none]
910#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
911#[serde(rename_all = "camelCase")]
912#[non_exhaustive]
913pub struct ElicitationCapabilities {
914 #[serde_as(deserialize_as = "DefaultOnError")]
916 #[schemars(extend("x-deserialize-default-on-error" = true))]
917 #[serde(default)]
918 pub form: Option<ElicitationFormCapabilities>,
919 #[serde_as(deserialize_as = "DefaultOnError")]
921 #[schemars(extend("x-deserialize-default-on-error" = true))]
922 #[serde(default)]
923 pub url: Option<ElicitationUrlCapabilities>,
924 #[serde(rename = "_meta")]
930 pub meta: Option<Meta>,
931}
932
933impl ElicitationCapabilities {
934 #[must_use]
935 pub fn new() -> Self {
936 Self::default()
937 }
938
939 #[must_use]
941 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
942 self.form = form.into_option();
943 self
944 }
945
946 #[must_use]
948 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
949 self.url = url.into_option();
950 self
951 }
952
953 #[must_use]
959 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
960 self.meta = meta.into_option();
961 self
962 }
963}
964
965#[skip_serializing_none]
971#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
972#[serde(rename_all = "camelCase")]
973#[non_exhaustive]
974pub struct ElicitationFormCapabilities {
975 #[serde(rename = "_meta")]
981 pub meta: Option<Meta>,
982}
983
984impl ElicitationFormCapabilities {
985 #[must_use]
986 pub fn new() -> Self {
987 Self::default()
988 }
989
990 #[must_use]
996 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
997 self.meta = meta.into_option();
998 self
999 }
1000}
1001
1002#[skip_serializing_none]
1008#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1009#[serde(rename_all = "camelCase")]
1010#[non_exhaustive]
1011pub struct ElicitationUrlCapabilities {
1012 #[serde(rename = "_meta")]
1018 pub meta: Option<Meta>,
1019}
1020
1021impl ElicitationUrlCapabilities {
1022 #[must_use]
1023 pub fn new() -> Self {
1024 Self::default()
1025 }
1026
1027 #[must_use]
1033 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1034 self.meta = meta.into_option();
1035 self
1036 }
1037}
1038
1039#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1045#[serde(untagged)]
1046#[non_exhaustive]
1047pub enum ElicitationScope {
1048 Session(ElicitationSessionScope),
1050 Request(ElicitationRequestScope),
1053}
1054
1055#[skip_serializing_none]
1065#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1066#[serde(rename_all = "camelCase")]
1067#[non_exhaustive]
1068pub struct ElicitationSessionScope {
1069 pub session_id: SessionId,
1071 pub tool_call_id: Option<ToolCallId>,
1073}
1074
1075impl ElicitationSessionScope {
1076 #[must_use]
1077 pub fn new(session_id: impl Into<SessionId>) -> Self {
1078 Self {
1079 session_id: session_id.into(),
1080 tool_call_id: None,
1081 }
1082 }
1083
1084 #[must_use]
1085 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1086 self.tool_call_id = tool_call_id.into_option();
1087 self
1088 }
1089}
1090
1091#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1098#[serde(rename_all = "camelCase")]
1099#[non_exhaustive]
1100pub struct ElicitationRequestScope {
1101 pub request_id: RequestId,
1103}
1104
1105impl ElicitationRequestScope {
1106 #[must_use]
1107 pub fn new(request_id: impl Into<RequestId>) -> Self {
1108 Self {
1109 request_id: request_id.into(),
1110 }
1111 }
1112}
1113
1114impl From<ElicitationSessionScope> for ElicitationScope {
1115 fn from(scope: ElicitationSessionScope) -> Self {
1116 Self::Session(scope)
1117 }
1118}
1119
1120impl From<ElicitationRequestScope> for ElicitationScope {
1121 fn from(scope: ElicitationRequestScope) -> Self {
1122 Self::Request(scope)
1123 }
1124}
1125
1126#[skip_serializing_none]
1136#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1137#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1138#[serde(rename_all = "camelCase")]
1139#[non_exhaustive]
1140pub struct CreateElicitationRequest {
1141 #[serde(flatten)]
1143 pub mode: ElicitationMode,
1144 pub message: String,
1146 #[serde(rename = "_meta")]
1152 pub meta: Option<Meta>,
1153}
1154
1155impl CreateElicitationRequest {
1156 #[must_use]
1157 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1158 Self {
1159 mode: mode.into(),
1160 message: message.into(),
1161 meta: None,
1162 }
1163 }
1164
1165 #[must_use]
1167 pub fn scope(&self) -> &ElicitationScope {
1168 self.mode.scope()
1169 }
1170
1171 #[must_use]
1177 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1178 self.meta = meta.into_option();
1179 self
1180 }
1181}
1182
1183#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1189#[serde(tag = "mode", rename_all = "snake_case")]
1190#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1191#[non_exhaustive]
1192pub enum ElicitationMode {
1193 Form(ElicitationFormMode),
1195 Url(ElicitationUrlMode),
1197}
1198
1199impl From<ElicitationFormMode> for ElicitationMode {
1200 fn from(mode: ElicitationFormMode) -> Self {
1201 Self::Form(mode)
1202 }
1203}
1204
1205impl From<ElicitationUrlMode> for ElicitationMode {
1206 fn from(mode: ElicitationUrlMode) -> Self {
1207 Self::Url(mode)
1208 }
1209}
1210
1211impl ElicitationMode {
1212 #[must_use]
1214 pub fn scope(&self) -> &ElicitationScope {
1215 match self {
1216 Self::Form(f) => &f.scope,
1217 Self::Url(u) => &u.scope,
1218 }
1219 }
1220}
1221
1222#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1228#[serde(rename_all = "camelCase")]
1229#[non_exhaustive]
1230pub struct ElicitationFormMode {
1231 #[serde(flatten)]
1233 pub scope: ElicitationScope,
1234 pub requested_schema: ElicitationSchema,
1236}
1237
1238impl ElicitationFormMode {
1239 #[must_use]
1240 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1241 Self {
1242 scope: scope.into(),
1243 requested_schema,
1244 }
1245 }
1246}
1247
1248#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1254#[serde(rename_all = "camelCase")]
1255#[non_exhaustive]
1256pub struct ElicitationUrlMode {
1257 #[serde(flatten)]
1259 pub scope: ElicitationScope,
1260 pub elicitation_id: ElicitationId,
1262 #[schemars(extend("format" = "uri"))]
1264 pub url: String,
1265}
1266
1267impl ElicitationUrlMode {
1268 #[must_use]
1269 pub fn new(
1270 scope: impl Into<ElicitationScope>,
1271 elicitation_id: impl Into<ElicitationId>,
1272 url: impl Into<String>,
1273 ) -> Self {
1274 Self {
1275 scope: scope.into(),
1276 elicitation_id: elicitation_id.into(),
1277 url: url.into(),
1278 }
1279 }
1280}
1281
1282#[skip_serializing_none]
1288#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1289#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1290#[serde(rename_all = "camelCase")]
1291#[non_exhaustive]
1292pub struct CreateElicitationResponse {
1293 #[serde(flatten)]
1295 pub action: ElicitationAction,
1296 #[serde(rename = "_meta")]
1302 pub meta: Option<Meta>,
1303}
1304
1305impl CreateElicitationResponse {
1306 #[must_use]
1307 pub fn new(action: ElicitationAction) -> Self {
1308 Self { action, meta: None }
1309 }
1310
1311 #[must_use]
1317 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1318 self.meta = meta.into_option();
1319 self
1320 }
1321}
1322
1323#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1329#[serde(tag = "action", rename_all = "snake_case")]
1330#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1331#[non_exhaustive]
1332pub enum ElicitationAction {
1333 Accept(ElicitationAcceptAction),
1335 Decline,
1337 Cancel,
1339}
1340
1341#[skip_serializing_none]
1347#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1348#[serde(rename_all = "camelCase")]
1349#[non_exhaustive]
1350pub struct ElicitationAcceptAction {
1351 #[serde(default)]
1353 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1354}
1355
1356impl ElicitationAcceptAction {
1357 #[must_use]
1358 pub fn new() -> Self {
1359 Self { content: None }
1360 }
1361
1362 #[must_use]
1364 pub fn content(
1365 mut self,
1366 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1367 ) -> Self {
1368 self.content = content.into_option();
1369 self
1370 }
1371}
1372
1373#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1374#[serde(untagged)]
1375#[non_exhaustive]
1376pub enum ElicitationContentValue {
1377 String(String),
1378 Integer(i64),
1379 Number(f64),
1380 Boolean(bool),
1381 StringArray(Vec<String>),
1382}
1383
1384impl From<String> for ElicitationContentValue {
1385 fn from(value: String) -> Self {
1386 Self::String(value)
1387 }
1388}
1389
1390impl From<&str> for ElicitationContentValue {
1391 fn from(value: &str) -> Self {
1392 Self::String(value.to_string())
1393 }
1394}
1395
1396impl From<i64> for ElicitationContentValue {
1397 fn from(value: i64) -> Self {
1398 Self::Integer(value)
1399 }
1400}
1401
1402impl From<i32> for ElicitationContentValue {
1403 fn from(value: i32) -> Self {
1404 Self::Integer(i64::from(value))
1405 }
1406}
1407
1408impl From<f64> for ElicitationContentValue {
1409 fn from(value: f64) -> Self {
1410 Self::Number(value)
1411 }
1412}
1413
1414impl From<bool> for ElicitationContentValue {
1415 fn from(value: bool) -> Self {
1416 Self::Boolean(value)
1417 }
1418}
1419
1420impl From<Vec<String>> for ElicitationContentValue {
1421 fn from(value: Vec<String>) -> Self {
1422 Self::StringArray(value)
1423 }
1424}
1425
1426impl From<Vec<&str>> for ElicitationContentValue {
1427 fn from(value: Vec<&str>) -> Self {
1428 Self::StringArray(value.into_iter().map(str::to_string).collect())
1429 }
1430}
1431
1432impl Default for ElicitationAcceptAction {
1433 fn default() -> Self {
1434 Self::new()
1435 }
1436}
1437
1438#[skip_serializing_none]
1444#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1445#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1446#[serde(rename_all = "camelCase")]
1447#[non_exhaustive]
1448pub struct CompleteElicitationNotification {
1449 pub elicitation_id: ElicitationId,
1451 #[serde(rename = "_meta")]
1457 pub meta: Option<Meta>,
1458}
1459
1460impl CompleteElicitationNotification {
1461 #[must_use]
1462 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
1463 Self {
1464 elicitation_id: elicitation_id.into(),
1465 meta: None,
1466 }
1467 }
1468
1469 #[must_use]
1475 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1476 self.meta = meta.into_option();
1477 self
1478 }
1479}
1480
1481#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1488#[serde(rename_all = "camelCase")]
1489#[non_exhaustive]
1490pub struct UrlElicitationRequiredData {
1491 pub elicitations: Vec<UrlElicitationRequiredItem>,
1493}
1494
1495impl UrlElicitationRequiredData {
1496 #[must_use]
1497 pub fn new(elicitations: Vec<UrlElicitationRequiredItem>) -> Self {
1498 Self { elicitations }
1499 }
1500}
1501
1502#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1508#[serde(rename_all = "camelCase")]
1509#[non_exhaustive]
1510pub struct UrlElicitationRequiredItem {
1511 pub mode: ElicitationUrlOnlyMode,
1513 pub elicitation_id: ElicitationId,
1515 #[schemars(extend("format" = "uri"))]
1517 pub url: String,
1518 pub message: String,
1520}
1521
1522#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
1524#[serde(rename_all = "snake_case")]
1525#[non_exhaustive]
1526pub enum ElicitationUrlOnlyMode {
1527 #[default]
1529 Url,
1530}
1531
1532impl UrlElicitationRequiredItem {
1533 #[must_use]
1534 pub fn new(
1535 elicitation_id: impl Into<ElicitationId>,
1536 url: impl Into<String>,
1537 message: impl Into<String>,
1538 ) -> Self {
1539 Self {
1540 mode: ElicitationUrlOnlyMode::Url,
1541 elicitation_id: elicitation_id.into(),
1542 url: url.into(),
1543 message: message.into(),
1544 }
1545 }
1546}
1547
1548#[cfg(test)]
1549mod tests {
1550 use super::*;
1551 use serde_json::json;
1552
1553 #[test]
1554 fn form_mode_request_serialization() {
1555 let schema = ElicitationSchema::new().string("name", true);
1556 let req = CreateElicitationRequest::new(
1557 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
1558 "Please enter your name",
1559 );
1560
1561 let json = serde_json::to_value(&req).unwrap();
1562 assert_eq!(json["sessionId"], "sess_1");
1563 assert!(json.get("toolCallId").is_none());
1564 assert_eq!(json["mode"], "form");
1565 assert_eq!(json["message"], "Please enter your name");
1566 assert!(json["requestedSchema"].is_object());
1567 assert_eq!(json["requestedSchema"]["type"], "object");
1568 assert_eq!(
1569 json["requestedSchema"]["properties"]["name"]["type"],
1570 "string"
1571 );
1572
1573 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1574 assert_eq!(
1575 *roundtripped.scope(),
1576 ElicitationSessionScope::new("sess_1").into()
1577 );
1578 assert_eq!(roundtripped.message, "Please enter your name");
1579 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
1580 }
1581
1582 #[test]
1583 fn url_mode_request_serialization() {
1584 let req = CreateElicitationRequest::new(
1585 ElicitationUrlMode::new(
1586 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
1587 "elic_1",
1588 "https://example.com/auth",
1589 ),
1590 "Please authenticate",
1591 );
1592
1593 let json = serde_json::to_value(&req).unwrap();
1594 assert_eq!(json["sessionId"], "sess_2");
1595 assert_eq!(json["toolCallId"], "tc_1");
1596 assert_eq!(json["mode"], "url");
1597 assert_eq!(json["elicitationId"], "elic_1");
1598 assert_eq!(json["url"], "https://example.com/auth");
1599 assert_eq!(json["message"], "Please authenticate");
1600
1601 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1602 assert_eq!(
1603 *roundtripped.scope(),
1604 ElicitationSessionScope::new("sess_2")
1605 .tool_call_id("tc_1")
1606 .into()
1607 );
1608 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1609 }
1610
1611 #[test]
1612 fn response_accept_serialization() {
1613 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
1614 ElicitationAcceptAction::new().content(BTreeMap::from([(
1615 "name".to_string(),
1616 ElicitationContentValue::from("Alice"),
1617 )])),
1618 ));
1619
1620 let json = serde_json::to_value(&resp).unwrap();
1621 assert_eq!(json["action"], "accept");
1622 assert_eq!(json["content"]["name"], "Alice");
1623
1624 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1625 assert!(matches!(
1626 roundtripped.action,
1627 ElicitationAction::Accept(ElicitationAcceptAction {
1628 content: Some(_),
1629 ..
1630 })
1631 ));
1632 }
1633
1634 #[test]
1635 fn response_decline_serialization() {
1636 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
1637
1638 let json = serde_json::to_value(&resp).unwrap();
1639 assert_eq!(json["action"], "decline");
1640
1641 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1642 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1643 }
1644
1645 #[test]
1646 fn response_cancel_serialization() {
1647 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
1648
1649 let json = serde_json::to_value(&resp).unwrap();
1650 assert_eq!(json["action"], "cancel");
1651
1652 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1653 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1654 }
1655
1656 #[test]
1657 fn url_mode_request_scope_serialization() {
1658 let req = CreateElicitationRequest::new(
1659 ElicitationUrlMode::new(
1660 ElicitationRequestScope::new(RequestId::Number(42)),
1661 "elic_2",
1662 "https://example.com/setup",
1663 ),
1664 "Please complete setup",
1665 );
1666
1667 let json = serde_json::to_value(&req).unwrap();
1668 assert_eq!(json["requestId"], 42);
1669 assert!(json.get("sessionId").is_none());
1670 assert_eq!(json["mode"], "url");
1671 assert_eq!(json["elicitationId"], "elic_2");
1672 assert_eq!(json["url"], "https://example.com/setup");
1673 assert_eq!(json["message"], "Please complete setup");
1674
1675 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1676 assert_eq!(
1677 *roundtripped.scope(),
1678 ElicitationRequestScope::new(RequestId::Number(42)).into()
1679 );
1680 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1681 }
1682
1683 #[test]
1684 fn request_scope_request_serialization() {
1685 let req = CreateElicitationRequest::new(
1686 ElicitationFormMode::new(
1687 ElicitationRequestScope::new(RequestId::Number(99)),
1688 ElicitationSchema::new().string("workspace", true),
1689 ),
1690 "Enter workspace name",
1691 );
1692
1693 let json = serde_json::to_value(&req).unwrap();
1694 assert_eq!(json["requestId"], 99);
1695 assert!(json.get("sessionId").is_none());
1696
1697 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1698 assert_eq!(
1699 *roundtripped.scope(),
1700 ElicitationRequestScope::new(RequestId::Number(99)).into()
1701 );
1702 }
1703
1704 #[test]
1708 fn client_response_serialization_accept() {
1709 use crate::v2::ClientResponse;
1710
1711 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1712 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
1713 "name".to_string(),
1714 ElicitationContentValue::from("Alice"),
1715 )]))),
1716 ));
1717 let json = serde_json::to_value(&resp).unwrap();
1718 assert_eq!(json["action"], "accept");
1719 assert_eq!(json["content"]["name"], "Alice");
1720
1721 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1723 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
1724 }
1725
1726 #[test]
1727 fn client_response_serialization_decline() {
1728 use crate::v2::ClientResponse;
1729
1730 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1731 ElicitationAction::Decline,
1732 ));
1733 let json = serde_json::to_value(&resp).unwrap();
1734 assert_eq!(json["action"], "decline");
1735
1736 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1737 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1738 }
1739
1740 #[test]
1741 fn client_response_serialization_cancel() {
1742 use crate::v2::ClientResponse;
1743
1744 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1745 ElicitationAction::Cancel,
1746 ));
1747 let json = serde_json::to_value(&resp).unwrap();
1748 assert_eq!(json["action"], "cancel");
1749
1750 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1751 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1752 }
1753
1754 #[test]
1757 fn request_tolerates_extra_fields() {
1758 let json = json!({
1759 "sessionId": "sess_1",
1760 "mode": "form",
1761 "message": "Enter your name",
1762 "requestedSchema": {
1763 "type": "object",
1764 "properties": {
1765 "name": { "type": "string", "title": "Name" }
1766 },
1767 "required": ["name"]
1768 },
1769 "unknownStringField": "hello",
1770 "unknownNumberField": 42
1771 });
1772
1773 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1774 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
1775 assert_eq!(req.message, "Enter your name");
1776 assert!(matches!(req.mode, ElicitationMode::Form(_)));
1777 }
1778
1779 #[test]
1780 fn completion_notification_serialization() {
1781 let notif = CompleteElicitationNotification::new("elic_1");
1782
1783 let json = serde_json::to_value(¬if).unwrap();
1784 assert_eq!(json["elicitationId"], "elic_1");
1785
1786 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
1787 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
1788 }
1789
1790 #[test]
1791 fn capabilities_form_only() {
1792 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
1793
1794 let json = serde_json::to_value(&caps).unwrap();
1795 assert!(json["form"].is_object());
1796 assert!(json.get("url").is_none());
1797
1798 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1799 assert!(roundtripped.form.is_some());
1800 assert!(roundtripped.url.is_none());
1801 }
1802
1803 #[test]
1804 fn capabilities_url_only() {
1805 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
1806
1807 let json = serde_json::to_value(&caps).unwrap();
1808 assert!(json.get("form").is_none());
1809 assert!(json["url"].is_object());
1810
1811 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1812 assert!(roundtripped.form.is_none());
1813 assert!(roundtripped.url.is_some());
1814 }
1815
1816 #[test]
1817 fn capabilities_both() {
1818 let caps = ElicitationCapabilities::new()
1819 .form(ElicitationFormCapabilities::new())
1820 .url(ElicitationUrlCapabilities::new());
1821
1822 let json = serde_json::to_value(&caps).unwrap();
1823 assert!(json["form"].is_object());
1824 assert!(json["url"].is_object());
1825
1826 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1827 assert!(roundtripped.form.is_some());
1828 assert!(roundtripped.url.is_some());
1829 }
1830
1831 #[test]
1832 fn url_elicitation_required_data_serialization() {
1833 let data = UrlElicitationRequiredData::new(vec![UrlElicitationRequiredItem::new(
1834 "elic_1",
1835 "https://example.com/auth",
1836 "Please authenticate",
1837 )]);
1838
1839 let json = serde_json::to_value(&data).unwrap();
1840 assert_eq!(json["elicitations"][0]["mode"], "url");
1841 assert_eq!(json["elicitations"][0]["elicitationId"], "elic_1");
1842 assert_eq!(json["elicitations"][0]["url"], "https://example.com/auth");
1843
1844 let roundtripped: UrlElicitationRequiredData = serde_json::from_value(json).unwrap();
1845 assert_eq!(roundtripped.elicitations.len(), 1);
1846 assert_eq!(
1847 roundtripped.elicitations[0].mode,
1848 ElicitationUrlOnlyMode::Url
1849 );
1850 }
1851
1852 #[test]
1853 fn schema_default_sets_object_type() {
1854 let schema = ElicitationSchema::default();
1855
1856 assert_eq!(schema.type_, ElicitationSchemaType::Object);
1857 assert!(schema.properties.is_empty());
1858
1859 let json = serde_json::to_value(&schema).unwrap();
1860 assert_eq!(json["type"], "object");
1861 }
1862
1863 #[test]
1864 fn schema_builder_serialization() {
1865 let schema = ElicitationSchema::new()
1866 .string("name", true)
1867 .email("email", true)
1868 .integer("age", 0, 150, true)
1869 .boolean("newsletter", false)
1870 .description("User registration");
1871
1872 let json = serde_json::to_value(&schema).unwrap();
1873 assert_eq!(json["type"], "object");
1874 assert_eq!(json["description"], "User registration");
1875 assert_eq!(json["properties"]["name"]["type"], "string");
1876 assert_eq!(json["properties"]["email"]["type"], "string");
1877 assert_eq!(json["properties"]["email"]["format"], "email");
1878 assert_eq!(json["properties"]["age"]["type"], "integer");
1879 assert_eq!(json["properties"]["age"]["minimum"], 0);
1880 assert_eq!(json["properties"]["age"]["maximum"], 150);
1881 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
1882
1883 let required = json["required"].as_array().unwrap();
1884 assert!(required.contains(&json!("name")));
1885 assert!(required.contains(&json!("email")));
1886 assert!(required.contains(&json!("age")));
1887 assert!(!required.contains(&json!("newsletter")));
1888
1889 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1890 assert_eq!(roundtripped.properties.len(), 4);
1891 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
1892 }
1893
1894 #[test]
1895 fn schema_string_enum_serialization() {
1896 let schema = ElicitationSchema::new().property(
1897 "color",
1898 StringPropertySchema::new().enum_values(vec![
1899 "red".into(),
1900 "green".into(),
1901 "blue".into(),
1902 ]),
1903 true,
1904 );
1905
1906 let json = serde_json::to_value(&schema).unwrap();
1907 assert_eq!(json["properties"]["color"]["type"], "string");
1908 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
1909 assert_eq!(enum_vals.len(), 3);
1910
1911 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1912 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
1913 {
1914 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
1915 } else {
1916 panic!("expected String variant");
1917 }
1918 }
1919
1920 #[test]
1921 fn schema_multi_select_serialization() {
1922 let schema = ElicitationSchema::new().property(
1923 "colors",
1924 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
1925 .min_items(1)
1926 .max_items(3),
1927 false,
1928 );
1929
1930 let json = serde_json::to_value(&schema).unwrap();
1931 assert_eq!(json["properties"]["colors"]["type"], "array");
1932 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
1933 assert_eq!(json["properties"]["colors"]["minItems"], 1);
1934 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
1935
1936 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1937 assert!(matches!(
1938 roundtripped.properties.get("colors").unwrap(),
1939 ElicitationPropertySchema::Array(_)
1940 ));
1941 }
1942
1943 #[test]
1944 fn schema_titled_enum_serialization() {
1945 let schema = ElicitationSchema::new().property(
1946 "country",
1947 StringPropertySchema::new().one_of(vec![
1948 EnumOption::new("us", "United States"),
1949 EnumOption::new("uk", "United Kingdom"),
1950 ]),
1951 true,
1952 );
1953
1954 let json = serde_json::to_value(&schema).unwrap();
1955 assert_eq!(json["properties"]["country"]["type"], "string");
1956 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
1957 assert_eq!(one_of.len(), 2);
1958 assert_eq!(one_of[0]["const"], "us");
1959 assert_eq!(one_of[0]["title"], "United States");
1960
1961 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1962 if let ElicitationPropertySchema::String(s) =
1963 roundtripped.properties.get("country").unwrap()
1964 {
1965 assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
1966 } else {
1967 panic!("expected String variant");
1968 }
1969 }
1970
1971 #[test]
1972 fn schema_number_property_serialization() {
1973 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
1974
1975 let json = serde_json::to_value(&schema).unwrap();
1976 assert_eq!(json["properties"]["rating"]["type"], "number");
1977 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
1978 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
1979
1980 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1981 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
1982 {
1983 assert_eq!(n.minimum, Some(0.0));
1984 assert_eq!(n.maximum, Some(5.0));
1985 } else {
1986 panic!("expected Number variant");
1987 }
1988 }
1989
1990 #[test]
1991 fn schema_string_format_serialization() {
1992 let schema = ElicitationSchema::new()
1993 .uri("website", true)
1994 .date("birthday", true)
1995 .date_time("updated_at", false);
1996
1997 let json = serde_json::to_value(&schema).unwrap();
1998 assert_eq!(json["properties"]["website"]["type"], "string");
1999 assert_eq!(json["properties"]["website"]["format"], "uri");
2000 assert_eq!(json["properties"]["birthday"]["type"], "string");
2001 assert_eq!(json["properties"]["birthday"]["format"], "date");
2002 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2003 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2004
2005 let required = json["required"].as_array().unwrap();
2006 assert!(required.contains(&json!("website")));
2007 assert!(required.contains(&json!("birthday")));
2008 assert!(!required.contains(&json!("updated_at")));
2009 }
2010
2011 #[test]
2012 fn schema_string_pattern_serialization() {
2013 let schema = ElicitationSchema::new().property(
2014 "name",
2015 StringPropertySchema::new()
2016 .min_length(1)
2017 .max_length(64)
2018 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2019 true,
2020 );
2021
2022 let json = serde_json::to_value(&schema).unwrap();
2023 assert_eq!(json["properties"]["name"]["type"], "string");
2024 assert_eq!(
2025 json["properties"]["name"]["pattern"],
2026 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2027 );
2028
2029 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2030 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2031 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2032 } else {
2033 panic!("expected String variant");
2034 }
2035 }
2036
2037 #[test]
2038 fn schema_property_updates_required_state() {
2039 let schema = ElicitationSchema::new()
2040 .string("name", true)
2041 .email("name", false);
2042
2043 let json = serde_json::to_value(&schema).unwrap();
2044 assert!(json.get("required").is_none());
2045 assert_eq!(json["properties"]["name"]["format"], "email");
2046 }
2047
2048 #[test]
2049 fn schema_rejects_invalid_object_type() {
2050 let err = serde_json::from_value::<ElicitationSchema>(json!({
2051 "type": "array",
2052 "properties": {
2053 "name": {
2054 "type": "string"
2055 }
2056 }
2057 }))
2058 .unwrap_err();
2059
2060 assert!(err.to_string().contains("unknown variant"));
2061 }
2062
2063 #[test]
2064 fn titled_multi_select_items_reject_one_of() {
2065 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2066 "oneOf": [
2067 {
2068 "const": "red",
2069 "title": "Red"
2070 }
2071 ]
2072 }))
2073 .unwrap_err();
2074
2075 assert!(err.to_string().contains("missing field `anyOf`"));
2076 }
2077
2078 #[test]
2079 fn response_accept_rejects_non_object_content() {
2080 let err = serde_json::from_value::<CreateElicitationResponse>(json!({
2081 "action": "accept",
2082 "content": "Alice"
2083 }))
2084 .unwrap_err();
2085
2086 assert!(err.to_string().contains("invalid type"));
2087 }
2088
2089 #[test]
2090 fn response_accept_rejects_nested_object_content() {
2091 let err = serde_json::from_value::<CreateElicitationResponse>(json!({
2092 "action": "accept",
2093 "content": {
2094 "profile": {
2095 "name": "Alice"
2096 }
2097 }
2098 }))
2099 .unwrap_err();
2100
2101 assert!(err.to_string().contains("data did not match any variant"));
2102 }
2103
2104 #[test]
2105 fn response_accept_allows_primitive_and_string_array_content() {
2106 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2107 ElicitationAcceptAction::new().content(BTreeMap::from([
2108 ("name".to_string(), ElicitationContentValue::from("Alice")),
2109 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2110 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2111 (
2112 "subscribed".to_string(),
2113 ElicitationContentValue::from(true),
2114 ),
2115 (
2116 "tags".to_string(),
2117 ElicitationContentValue::from(vec!["rust", "acp"]),
2118 ),
2119 ])),
2120 ));
2121
2122 let json = serde_json::to_value(&response).unwrap();
2123 assert_eq!(json["action"], "accept");
2124 assert_eq!(json["content"]["name"], "Alice");
2125 assert_eq!(json["content"]["age"], 30);
2126 assert_eq!(json["content"]["score"], 9.5);
2127 assert_eq!(json["content"]["subscribed"], true);
2128 assert_eq!(json["content"]["tags"][0], "rust");
2129 assert_eq!(json["content"]["tags"][1], "acp");
2130 }
2131}