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 crate::{
16 ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, IntoOption, Meta, RequestId,
17 SessionId, ToolCallId,
18};
19
20#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
26#[serde(transparent)]
27#[from(Arc<str>, String, &'static str)]
28#[non_exhaustive]
29pub struct ElicitationId(pub Arc<str>);
30
31impl ElicitationId {
32 #[must_use]
34 pub fn new(id: impl Into<Arc<str>>) -> Self {
35 Self(id.into())
36 }
37}
38
39#[derive(Debug, Clone, Copy, 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}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
56#[serde(rename_all = "snake_case")]
57#[non_exhaustive]
58pub enum ElicitationSchemaType {
59 #[default]
61 Object,
62}
63
64#[skip_serializing_none]
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
67#[non_exhaustive]
68pub struct EnumOption {
69 #[serde(rename = "const")]
71 pub value: String,
72 pub title: String,
74 #[serde(rename = "_meta")]
80 pub meta: Option<Meta>,
81}
82
83impl EnumOption {
84 #[must_use]
86 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
87 Self {
88 value: value.into(),
89 title: title.into(),
90 meta: None,
91 }
92 }
93
94 #[must_use]
100 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
101 self.meta = meta.into_option();
102 self
103 }
104}
105
106#[skip_serializing_none]
111#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
112#[serde(rename_all = "camelCase")]
113#[non_exhaustive]
114pub struct StringPropertySchema {
115 pub title: Option<String>,
117 pub description: Option<String>,
119 pub min_length: Option<u32>,
121 pub max_length: Option<u32>,
123 pub pattern: Option<String>,
125 pub format: Option<StringFormat>,
127 pub default: Option<String>,
129 #[serde(rename = "enum")]
131 pub enum_values: Option<Vec<String>>,
132 #[serde(rename = "oneOf")]
134 pub one_of: Option<Vec<EnumOption>>,
135 #[serde(rename = "_meta")]
141 pub meta: Option<Meta>,
142}
143
144impl StringPropertySchema {
145 #[must_use]
147 pub fn new() -> Self {
148 Self::default()
149 }
150
151 #[must_use]
153 pub fn email() -> Self {
154 Self {
155 format: Some(StringFormat::Email),
156 ..Default::default()
157 }
158 }
159
160 #[must_use]
162 pub fn uri() -> Self {
163 Self {
164 format: Some(StringFormat::Uri),
165 ..Default::default()
166 }
167 }
168
169 #[must_use]
171 pub fn date() -> Self {
172 Self {
173 format: Some(StringFormat::Date),
174 ..Default::default()
175 }
176 }
177
178 #[must_use]
180 pub fn date_time() -> Self {
181 Self {
182 format: Some(StringFormat::DateTime),
183 ..Default::default()
184 }
185 }
186
187 #[must_use]
189 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
190 self.title = title.into_option();
191 self
192 }
193
194 #[must_use]
196 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
197 self.description = description.into_option();
198 self
199 }
200
201 #[must_use]
203 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
204 self.min_length = min_length.into_option();
205 self
206 }
207
208 #[must_use]
210 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
211 self.max_length = max_length.into_option();
212 self
213 }
214
215 #[must_use]
217 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
218 self.pattern = pattern.into_option();
219 self
220 }
221
222 #[must_use]
224 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
225 self.format = format.into_option();
226 self
227 }
228
229 #[must_use]
231 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
232 self.default = default.into_option();
233 self
234 }
235
236 #[must_use]
238 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
239 self.enum_values = enum_values.into_option();
240 self
241 }
242
243 #[must_use]
245 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
246 self.one_of = one_of.into_option();
247 self
248 }
249
250 #[must_use]
256 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
257 self.meta = meta.into_option();
258 self
259 }
260}
261
262#[skip_serializing_none]
264#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
265#[serde(rename_all = "camelCase")]
266#[non_exhaustive]
267pub struct NumberPropertySchema {
268 pub title: Option<String>,
270 pub description: Option<String>,
272 pub minimum: Option<f64>,
274 pub maximum: Option<f64>,
276 pub default: Option<f64>,
278 #[serde(rename = "_meta")]
284 pub meta: Option<Meta>,
285}
286
287impl NumberPropertySchema {
288 #[must_use]
290 pub fn new() -> Self {
291 Self::default()
292 }
293
294 #[must_use]
296 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
297 self.title = title.into_option();
298 self
299 }
300
301 #[must_use]
303 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
304 self.description = description.into_option();
305 self
306 }
307
308 #[must_use]
310 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
311 self.minimum = minimum.into_option();
312 self
313 }
314
315 #[must_use]
317 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
318 self.maximum = maximum.into_option();
319 self
320 }
321
322 #[must_use]
324 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
325 self.default = default.into_option();
326 self
327 }
328
329 #[must_use]
335 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
336 self.meta = meta.into_option();
337 self
338 }
339}
340
341#[skip_serializing_none]
343#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
344#[serde(rename_all = "camelCase")]
345#[non_exhaustive]
346pub struct IntegerPropertySchema {
347 pub title: Option<String>,
349 pub description: Option<String>,
351 pub minimum: Option<i64>,
353 pub maximum: Option<i64>,
355 pub default: Option<i64>,
357 #[serde(rename = "_meta")]
363 pub meta: Option<Meta>,
364}
365
366impl IntegerPropertySchema {
367 #[must_use]
369 pub fn new() -> Self {
370 Self::default()
371 }
372
373 #[must_use]
375 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
376 self.title = title.into_option();
377 self
378 }
379
380 #[must_use]
382 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
383 self.description = description.into_option();
384 self
385 }
386
387 #[must_use]
389 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
390 self.minimum = minimum.into_option();
391 self
392 }
393
394 #[must_use]
396 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
397 self.maximum = maximum.into_option();
398 self
399 }
400
401 #[must_use]
403 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
404 self.default = default.into_option();
405 self
406 }
407
408 #[must_use]
414 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
415 self.meta = meta.into_option();
416 self
417 }
418}
419
420#[skip_serializing_none]
422#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
423#[serde(rename_all = "camelCase")]
424#[non_exhaustive]
425pub struct BooleanPropertySchema {
426 pub title: Option<String>,
428 pub description: Option<String>,
430 pub default: Option<bool>,
432 #[serde(rename = "_meta")]
438 pub meta: Option<Meta>,
439}
440
441impl BooleanPropertySchema {
442 #[must_use]
444 pub fn new() -> Self {
445 Self::default()
446 }
447
448 #[must_use]
450 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
451 self.title = title.into_option();
452 self
453 }
454
455 #[must_use]
457 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
458 self.description = description.into_option();
459 self
460 }
461
462 #[must_use]
464 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
465 self.default = default.into_option();
466 self
467 }
468
469 #[must_use]
475 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
476 self.meta = meta.into_option();
477 self
478 }
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
483#[serde(rename_all = "snake_case")]
484#[non_exhaustive]
485pub enum ElicitationStringType {
486 #[default]
488 String,
489}
490
491#[skip_serializing_none]
493#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
494#[non_exhaustive]
495pub struct UntitledMultiSelectItems {
496 #[serde(rename = "type")]
498 pub type_: ElicitationStringType,
499 #[serde(rename = "enum")]
501 pub values: Vec<String>,
502 #[serde(rename = "_meta")]
508 pub meta: Option<Meta>,
509}
510
511impl UntitledMultiSelectItems {
512 #[must_use]
514 pub fn new(type_: ElicitationStringType, values: Vec<String>) -> Self {
515 Self {
516 type_,
517 values,
518 meta: None,
519 }
520 }
521
522 #[must_use]
528 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
529 self.meta = meta.into_option();
530 self
531 }
532}
533
534#[skip_serializing_none]
536#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
537#[non_exhaustive]
538pub struct TitledMultiSelectItems {
539 #[serde(rename = "anyOf")]
541 pub options: Vec<EnumOption>,
542 #[serde(rename = "_meta")]
548 pub meta: Option<Meta>,
549}
550
551impl TitledMultiSelectItems {
552 #[must_use]
554 pub fn new(options: Vec<EnumOption>) -> Self {
555 Self {
556 options,
557 meta: None,
558 }
559 }
560
561 #[must_use]
567 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
568 self.meta = meta.into_option();
569 self
570 }
571}
572
573#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
575#[serde(untagged)]
576#[non_exhaustive]
577pub enum MultiSelectItems {
578 Untitled(UntitledMultiSelectItems),
580 Titled(TitledMultiSelectItems),
582}
583
584#[skip_serializing_none]
586#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
587#[serde(rename_all = "camelCase")]
588#[non_exhaustive]
589pub struct MultiSelectPropertySchema {
590 pub title: Option<String>,
592 pub description: Option<String>,
594 pub min_items: Option<u64>,
596 pub max_items: Option<u64>,
598 pub items: MultiSelectItems,
600 pub default: Option<Vec<String>>,
602 #[serde(rename = "_meta")]
608 pub meta: Option<Meta>,
609}
610
611impl MultiSelectPropertySchema {
612 #[must_use]
614 pub fn new(values: Vec<String>) -> Self {
615 Self {
616 title: None,
617 description: None,
618 min_items: None,
619 max_items: None,
620 items: MultiSelectItems::Untitled(UntitledMultiSelectItems::new(
621 ElicitationStringType::String,
622 values,
623 )),
624 default: None,
625 meta: None,
626 }
627 }
628
629 #[must_use]
631 pub fn titled(options: Vec<EnumOption>) -> Self {
632 Self {
633 title: None,
634 description: None,
635 min_items: None,
636 max_items: None,
637 items: MultiSelectItems::Titled(TitledMultiSelectItems::new(options)),
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]
936 pub fn new() -> Self {
937 Self::default()
938 }
939
940 #[must_use]
942 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
943 self.form = form.into_option();
944 self
945 }
946
947 #[must_use]
949 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
950 self.url = url.into_option();
951 self
952 }
953
954 #[must_use]
960 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
961 self.meta = meta.into_option();
962 self
963 }
964}
965
966#[skip_serializing_none]
972#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
973#[serde(rename_all = "camelCase")]
974#[non_exhaustive]
975pub struct ElicitationFormCapabilities {
976 #[serde(rename = "_meta")]
982 pub meta: Option<Meta>,
983}
984
985impl ElicitationFormCapabilities {
986 #[must_use]
988 pub fn new() -> Self {
989 Self::default()
990 }
991
992 #[must_use]
998 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
999 self.meta = meta.into_option();
1000 self
1001 }
1002}
1003
1004#[skip_serializing_none]
1010#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1011#[serde(rename_all = "camelCase")]
1012#[non_exhaustive]
1013pub struct ElicitationUrlCapabilities {
1014 #[serde(rename = "_meta")]
1020 pub meta: Option<Meta>,
1021}
1022
1023impl ElicitationUrlCapabilities {
1024 #[must_use]
1026 pub fn new() -> Self {
1027 Self::default()
1028 }
1029
1030 #[must_use]
1036 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1037 self.meta = meta.into_option();
1038 self
1039 }
1040}
1041
1042#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1048#[serde(untagged)]
1049#[non_exhaustive]
1050pub enum ElicitationScope {
1051 Session(ElicitationSessionScope),
1053 Request(ElicitationRequestScope),
1056}
1057
1058#[skip_serializing_none]
1068#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1069#[serde(rename_all = "camelCase")]
1070#[non_exhaustive]
1071pub struct ElicitationSessionScope {
1072 pub session_id: SessionId,
1074 pub tool_call_id: Option<ToolCallId>,
1076}
1077
1078impl ElicitationSessionScope {
1079 #[must_use]
1081 pub fn new(session_id: impl Into<SessionId>) -> Self {
1082 Self {
1083 session_id: session_id.into(),
1084 tool_call_id: None,
1085 }
1086 }
1087
1088 #[must_use]
1090 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1091 self.tool_call_id = tool_call_id.into_option();
1092 self
1093 }
1094}
1095
1096#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1103#[serde(rename_all = "camelCase")]
1104#[non_exhaustive]
1105pub struct ElicitationRequestScope {
1106 pub request_id: RequestId,
1108}
1109
1110impl ElicitationRequestScope {
1111 #[must_use]
1113 pub fn new(request_id: impl Into<RequestId>) -> Self {
1114 Self {
1115 request_id: request_id.into(),
1116 }
1117 }
1118}
1119
1120impl From<ElicitationSessionScope> for ElicitationScope {
1121 fn from(scope: ElicitationSessionScope) -> Self {
1122 Self::Session(scope)
1123 }
1124}
1125
1126impl From<ElicitationRequestScope> for ElicitationScope {
1127 fn from(scope: ElicitationRequestScope) -> Self {
1128 Self::Request(scope)
1129 }
1130}
1131
1132#[skip_serializing_none]
1142#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1143#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1144#[serde(rename_all = "camelCase")]
1145#[non_exhaustive]
1146pub struct CreateElicitationRequest {
1147 #[serde(flatten)]
1149 pub mode: ElicitationMode,
1150 pub message: String,
1152 #[serde(rename = "_meta")]
1158 pub meta: Option<Meta>,
1159}
1160
1161impl CreateElicitationRequest {
1162 #[must_use]
1164 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1165 Self {
1166 mode: mode.into(),
1167 message: message.into(),
1168 meta: None,
1169 }
1170 }
1171
1172 #[must_use]
1174 pub fn scope(&self) -> &ElicitationScope {
1175 self.mode.scope()
1176 }
1177
1178 #[must_use]
1184 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1185 self.meta = meta.into_option();
1186 self
1187 }
1188}
1189
1190#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1196#[serde(tag = "mode", rename_all = "snake_case")]
1197#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1198#[non_exhaustive]
1199pub enum ElicitationMode {
1200 Form(ElicitationFormMode),
1202 Url(ElicitationUrlMode),
1204}
1205
1206impl From<ElicitationFormMode> for ElicitationMode {
1207 fn from(mode: ElicitationFormMode) -> Self {
1208 Self::Form(mode)
1209 }
1210}
1211
1212impl From<ElicitationUrlMode> for ElicitationMode {
1213 fn from(mode: ElicitationUrlMode) -> Self {
1214 Self::Url(mode)
1215 }
1216}
1217
1218impl ElicitationMode {
1219 #[must_use]
1221 pub fn scope(&self) -> &ElicitationScope {
1222 match self {
1223 Self::Form(f) => &f.scope,
1224 Self::Url(u) => &u.scope,
1225 }
1226 }
1227}
1228
1229#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1235#[serde(rename_all = "camelCase")]
1236#[non_exhaustive]
1237pub struct ElicitationFormMode {
1238 #[serde(flatten)]
1240 pub scope: ElicitationScope,
1241 pub requested_schema: ElicitationSchema,
1243}
1244
1245impl ElicitationFormMode {
1246 #[must_use]
1248 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1249 Self {
1250 scope: scope.into(),
1251 requested_schema,
1252 }
1253 }
1254}
1255
1256#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1262#[serde(rename_all = "camelCase")]
1263#[non_exhaustive]
1264pub struct ElicitationUrlMode {
1265 #[serde(flatten)]
1267 pub scope: ElicitationScope,
1268 pub elicitation_id: ElicitationId,
1270 #[schemars(extend("format" = "uri"))]
1272 pub url: String,
1273}
1274
1275impl ElicitationUrlMode {
1276 #[must_use]
1278 pub fn new(
1279 scope: impl Into<ElicitationScope>,
1280 elicitation_id: impl Into<ElicitationId>,
1281 url: impl Into<String>,
1282 ) -> Self {
1283 Self {
1284 scope: scope.into(),
1285 elicitation_id: elicitation_id.into(),
1286 url: url.into(),
1287 }
1288 }
1289}
1290
1291#[skip_serializing_none]
1297#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1298#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1299#[serde(rename_all = "camelCase")]
1300#[non_exhaustive]
1301pub struct CreateElicitationResponse {
1302 #[serde(flatten)]
1304 pub action: ElicitationAction,
1305 #[serde(rename = "_meta")]
1311 pub meta: Option<Meta>,
1312}
1313
1314impl CreateElicitationResponse {
1315 #[must_use]
1317 pub fn new(action: ElicitationAction) -> Self {
1318 Self { action, meta: None }
1319 }
1320
1321 #[must_use]
1327 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1328 self.meta = meta.into_option();
1329 self
1330 }
1331}
1332
1333#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1339#[serde(tag = "action", rename_all = "snake_case")]
1340#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1341#[non_exhaustive]
1342pub enum ElicitationAction {
1343 Accept(ElicitationAcceptAction),
1345 Decline,
1347 Cancel,
1349}
1350
1351#[skip_serializing_none]
1357#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1358#[serde(rename_all = "camelCase")]
1359#[non_exhaustive]
1360pub struct ElicitationAcceptAction {
1361 #[serde(default)]
1363 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1364}
1365
1366impl ElicitationAcceptAction {
1367 #[must_use]
1369 pub fn new() -> Self {
1370 Self { content: None }
1371 }
1372
1373 #[must_use]
1375 pub fn content(
1376 mut self,
1377 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1378 ) -> Self {
1379 self.content = content.into_option();
1380 self
1381 }
1382}
1383
1384#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1386#[serde(untagged)]
1387#[non_exhaustive]
1388pub enum ElicitationContentValue {
1389 String(String),
1391 Integer(i64),
1393 Number(f64),
1395 Boolean(bool),
1397 StringArray(Vec<String>),
1399}
1400
1401impl From<String> for ElicitationContentValue {
1402 fn from(value: String) -> Self {
1403 Self::String(value)
1404 }
1405}
1406
1407impl From<&str> for ElicitationContentValue {
1408 fn from(value: &str) -> Self {
1409 Self::String(value.to_string())
1410 }
1411}
1412
1413impl From<i64> for ElicitationContentValue {
1414 fn from(value: i64) -> Self {
1415 Self::Integer(value)
1416 }
1417}
1418
1419impl From<i32> for ElicitationContentValue {
1420 fn from(value: i32) -> Self {
1421 Self::Integer(i64::from(value))
1422 }
1423}
1424
1425impl From<f64> for ElicitationContentValue {
1426 fn from(value: f64) -> Self {
1427 Self::Number(value)
1428 }
1429}
1430
1431impl From<bool> for ElicitationContentValue {
1432 fn from(value: bool) -> Self {
1433 Self::Boolean(value)
1434 }
1435}
1436
1437impl From<Vec<String>> for ElicitationContentValue {
1438 fn from(value: Vec<String>) -> Self {
1439 Self::StringArray(value)
1440 }
1441}
1442
1443impl From<Vec<&str>> for ElicitationContentValue {
1444 fn from(value: Vec<&str>) -> Self {
1445 Self::StringArray(value.into_iter().map(str::to_string).collect())
1446 }
1447}
1448
1449impl Default for ElicitationAcceptAction {
1450 fn default() -> Self {
1451 Self::new()
1452 }
1453}
1454
1455#[skip_serializing_none]
1461#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1462#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1463#[serde(rename_all = "camelCase")]
1464#[non_exhaustive]
1465pub struct CompleteElicitationNotification {
1466 pub elicitation_id: ElicitationId,
1468 #[serde(rename = "_meta")]
1474 pub meta: Option<Meta>,
1475}
1476
1477impl CompleteElicitationNotification {
1478 #[must_use]
1480 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
1481 Self {
1482 elicitation_id: elicitation_id.into(),
1483 meta: None,
1484 }
1485 }
1486
1487 #[must_use]
1493 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1494 self.meta = meta.into_option();
1495 self
1496 }
1497}
1498
1499#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1506#[serde(rename_all = "camelCase")]
1507#[non_exhaustive]
1508pub struct UrlElicitationRequiredData {
1509 pub elicitations: Vec<UrlElicitationRequiredItem>,
1511}
1512
1513impl UrlElicitationRequiredData {
1514 #[must_use]
1516 pub fn new(elicitations: Vec<UrlElicitationRequiredItem>) -> Self {
1517 Self { elicitations }
1518 }
1519}
1520
1521#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1527#[serde(rename_all = "camelCase")]
1528#[non_exhaustive]
1529pub struct UrlElicitationRequiredItem {
1530 pub mode: ElicitationUrlOnlyMode,
1532 pub elicitation_id: ElicitationId,
1534 #[schemars(extend("format" = "uri"))]
1536 pub url: String,
1537 pub message: String,
1539}
1540
1541#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
1543#[serde(rename_all = "snake_case")]
1544#[non_exhaustive]
1545pub enum ElicitationUrlOnlyMode {
1546 #[default]
1548 Url,
1549}
1550
1551impl UrlElicitationRequiredItem {
1552 #[must_use]
1554 pub fn new(
1555 elicitation_id: impl Into<ElicitationId>,
1556 url: impl Into<String>,
1557 message: impl Into<String>,
1558 ) -> Self {
1559 Self {
1560 mode: ElicitationUrlOnlyMode::Url,
1561 elicitation_id: elicitation_id.into(),
1562 url: url.into(),
1563 message: message.into(),
1564 }
1565 }
1566}
1567
1568#[cfg(test)]
1569mod tests {
1570 use super::*;
1571 use serde_json::json;
1572
1573 #[test]
1574 fn form_mode_request_serialization() {
1575 let schema = ElicitationSchema::new().string("name", true);
1576 let req = CreateElicitationRequest::new(
1577 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
1578 "Please enter your name",
1579 );
1580
1581 let json = serde_json::to_value(&req).unwrap();
1582 assert_eq!(json["sessionId"], "sess_1");
1583 assert!(json.get("toolCallId").is_none());
1584 assert_eq!(json["mode"], "form");
1585 assert_eq!(json["message"], "Please enter your name");
1586 assert!(json["requestedSchema"].is_object());
1587 assert_eq!(json["requestedSchema"]["type"], "object");
1588 assert_eq!(
1589 json["requestedSchema"]["properties"]["name"]["type"],
1590 "string"
1591 );
1592
1593 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1594 assert_eq!(
1595 *roundtripped.scope(),
1596 ElicitationSessionScope::new("sess_1").into()
1597 );
1598 assert_eq!(roundtripped.message, "Please enter your name");
1599 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
1600 }
1601
1602 #[test]
1603 fn url_mode_request_serialization() {
1604 let req = CreateElicitationRequest::new(
1605 ElicitationUrlMode::new(
1606 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
1607 "elic_1",
1608 "https://example.com/auth",
1609 ),
1610 "Please authenticate",
1611 );
1612
1613 let json = serde_json::to_value(&req).unwrap();
1614 assert_eq!(json["sessionId"], "sess_2");
1615 assert_eq!(json["toolCallId"], "tc_1");
1616 assert_eq!(json["mode"], "url");
1617 assert_eq!(json["elicitationId"], "elic_1");
1618 assert_eq!(json["url"], "https://example.com/auth");
1619 assert_eq!(json["message"], "Please authenticate");
1620
1621 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1622 assert_eq!(
1623 *roundtripped.scope(),
1624 ElicitationSessionScope::new("sess_2")
1625 .tool_call_id("tc_1")
1626 .into()
1627 );
1628 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1629 }
1630
1631 #[test]
1632 fn response_accept_serialization() {
1633 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
1634 ElicitationAcceptAction::new().content(BTreeMap::from([(
1635 "name".to_string(),
1636 ElicitationContentValue::from("Alice"),
1637 )])),
1638 ));
1639
1640 let json = serde_json::to_value(&resp).unwrap();
1641 assert_eq!(json["action"], "accept");
1642 assert_eq!(json["content"]["name"], "Alice");
1643
1644 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1645 assert!(matches!(
1646 roundtripped.action,
1647 ElicitationAction::Accept(ElicitationAcceptAction {
1648 content: Some(_),
1649 ..
1650 })
1651 ));
1652 }
1653
1654 #[test]
1655 fn response_decline_serialization() {
1656 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
1657
1658 let json = serde_json::to_value(&resp).unwrap();
1659 assert_eq!(json["action"], "decline");
1660
1661 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1662 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1663 }
1664
1665 #[test]
1666 fn response_cancel_serialization() {
1667 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
1668
1669 let json = serde_json::to_value(&resp).unwrap();
1670 assert_eq!(json["action"], "cancel");
1671
1672 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1673 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1674 }
1675
1676 #[test]
1677 fn url_mode_request_scope_serialization() {
1678 let req = CreateElicitationRequest::new(
1679 ElicitationUrlMode::new(
1680 ElicitationRequestScope::new(RequestId::Number(42)),
1681 "elic_2",
1682 "https://example.com/setup",
1683 ),
1684 "Please complete setup",
1685 );
1686
1687 let json = serde_json::to_value(&req).unwrap();
1688 assert_eq!(json["requestId"], 42);
1689 assert!(json.get("sessionId").is_none());
1690 assert_eq!(json["mode"], "url");
1691 assert_eq!(json["elicitationId"], "elic_2");
1692 assert_eq!(json["url"], "https://example.com/setup");
1693 assert_eq!(json["message"], "Please complete setup");
1694
1695 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1696 assert_eq!(
1697 *roundtripped.scope(),
1698 ElicitationRequestScope::new(RequestId::Number(42)).into()
1699 );
1700 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1701 }
1702
1703 #[test]
1704 fn request_scope_request_serialization() {
1705 let req = CreateElicitationRequest::new(
1706 ElicitationFormMode::new(
1707 ElicitationRequestScope::new(RequestId::Number(99)),
1708 ElicitationSchema::new().string("workspace", true),
1709 ),
1710 "Enter workspace name",
1711 );
1712
1713 let json = serde_json::to_value(&req).unwrap();
1714 assert_eq!(json["requestId"], 99);
1715 assert!(json.get("sessionId").is_none());
1716
1717 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1718 assert_eq!(
1719 *roundtripped.scope(),
1720 ElicitationRequestScope::new(RequestId::Number(99)).into()
1721 );
1722 }
1723
1724 #[test]
1731 fn client_response_serialization_accept() {
1732 use crate::ClientResponse;
1733
1734 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1735 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
1736 "name".to_string(),
1737 ElicitationContentValue::from("Alice"),
1738 )]))),
1739 ));
1740 let json = serde_json::to_value(&resp).unwrap();
1741 assert_eq!(json["action"], "accept");
1742 assert_eq!(json["content"]["name"], "Alice");
1743
1744 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1746 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
1747 }
1748
1749 #[test]
1750 fn client_response_serialization_decline() {
1751 use crate::ClientResponse;
1752
1753 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1754 ElicitationAction::Decline,
1755 ));
1756 let json = serde_json::to_value(&resp).unwrap();
1757 assert_eq!(json["action"], "decline");
1758
1759 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1760 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1761 }
1762
1763 #[test]
1764 fn client_response_serialization_cancel() {
1765 use crate::ClientResponse;
1766
1767 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1768 ElicitationAction::Cancel,
1769 ));
1770 let json = serde_json::to_value(&resp).unwrap();
1771 assert_eq!(json["action"], "cancel");
1772
1773 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1774 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1775 }
1776
1777 #[test]
1780 fn request_tolerates_extra_fields() {
1781 let json = json!({
1782 "sessionId": "sess_1",
1783 "mode": "form",
1784 "message": "Enter your name",
1785 "requestedSchema": {
1786 "type": "object",
1787 "properties": {
1788 "name": { "type": "string", "title": "Name" }
1789 },
1790 "required": ["name"]
1791 },
1792 "unknownStringField": "hello",
1793 "unknownNumberField": 42
1794 });
1795
1796 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1797 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
1798 assert_eq!(req.message, "Enter your name");
1799 assert!(matches!(req.mode, ElicitationMode::Form(_)));
1800 }
1801
1802 #[test]
1803 fn completion_notification_serialization() {
1804 let notif = CompleteElicitationNotification::new("elic_1");
1805
1806 let json = serde_json::to_value(¬if).unwrap();
1807 assert_eq!(json["elicitationId"], "elic_1");
1808
1809 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
1810 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
1811 }
1812
1813 #[test]
1814 fn capabilities_form_only() {
1815 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
1816
1817 let json = serde_json::to_value(&caps).unwrap();
1818 assert!(json["form"].is_object());
1819 assert!(json.get("url").is_none());
1820
1821 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1822 assert!(roundtripped.form.is_some());
1823 assert!(roundtripped.url.is_none());
1824 }
1825
1826 #[test]
1827 fn capabilities_url_only() {
1828 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
1829
1830 let json = serde_json::to_value(&caps).unwrap();
1831 assert!(json.get("form").is_none());
1832 assert!(json["url"].is_object());
1833
1834 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1835 assert!(roundtripped.form.is_none());
1836 assert!(roundtripped.url.is_some());
1837 }
1838
1839 #[test]
1840 fn capabilities_both() {
1841 let caps = ElicitationCapabilities::new()
1842 .form(ElicitationFormCapabilities::new())
1843 .url(ElicitationUrlCapabilities::new());
1844
1845 let json = serde_json::to_value(&caps).unwrap();
1846 assert!(json["form"].is_object());
1847 assert!(json["url"].is_object());
1848
1849 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1850 assert!(roundtripped.form.is_some());
1851 assert!(roundtripped.url.is_some());
1852 }
1853
1854 #[test]
1855 fn url_elicitation_required_data_serialization() {
1856 let data = UrlElicitationRequiredData::new(vec![UrlElicitationRequiredItem::new(
1857 "elic_1",
1858 "https://example.com/auth",
1859 "Please authenticate",
1860 )]);
1861
1862 let json = serde_json::to_value(&data).unwrap();
1863 assert_eq!(json["elicitations"][0]["mode"], "url");
1864 assert_eq!(json["elicitations"][0]["elicitationId"], "elic_1");
1865 assert_eq!(json["elicitations"][0]["url"], "https://example.com/auth");
1866
1867 let roundtripped: UrlElicitationRequiredData = serde_json::from_value(json).unwrap();
1868 assert_eq!(roundtripped.elicitations.len(), 1);
1869 assert_eq!(
1870 roundtripped.elicitations[0].mode,
1871 ElicitationUrlOnlyMode::Url
1872 );
1873 }
1874
1875 #[test]
1876 fn schema_default_sets_object_type() {
1877 let schema = ElicitationSchema::default();
1878
1879 assert_eq!(schema.type_, ElicitationSchemaType::Object);
1880 assert!(schema.properties.is_empty());
1881
1882 let json = serde_json::to_value(&schema).unwrap();
1883 assert_eq!(json["type"], "object");
1884 }
1885
1886 #[test]
1887 fn schema_builder_serialization() {
1888 let schema = ElicitationSchema::new()
1889 .string("name", true)
1890 .email("email", true)
1891 .integer("age", 0, 150, true)
1892 .boolean("newsletter", false)
1893 .description("User registration");
1894
1895 let json = serde_json::to_value(&schema).unwrap();
1896 assert_eq!(json["type"], "object");
1897 assert_eq!(json["description"], "User registration");
1898 assert_eq!(json["properties"]["name"]["type"], "string");
1899 assert_eq!(json["properties"]["email"]["type"], "string");
1900 assert_eq!(json["properties"]["email"]["format"], "email");
1901 assert_eq!(json["properties"]["age"]["type"], "integer");
1902 assert_eq!(json["properties"]["age"]["minimum"], 0);
1903 assert_eq!(json["properties"]["age"]["maximum"], 150);
1904 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
1905
1906 let required = json["required"].as_array().unwrap();
1907 assert!(required.contains(&json!("name")));
1908 assert!(required.contains(&json!("email")));
1909 assert!(required.contains(&json!("age")));
1910 assert!(!required.contains(&json!("newsletter")));
1911
1912 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1913 assert_eq!(roundtripped.properties.len(), 4);
1914 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
1915 }
1916
1917 #[test]
1918 fn schema_string_enum_serialization() {
1919 let schema = ElicitationSchema::new().property(
1920 "color",
1921 StringPropertySchema::new().enum_values(vec![
1922 "red".into(),
1923 "green".into(),
1924 "blue".into(),
1925 ]),
1926 true,
1927 );
1928
1929 let json = serde_json::to_value(&schema).unwrap();
1930 assert_eq!(json["properties"]["color"]["type"], "string");
1931 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
1932 assert_eq!(enum_vals.len(), 3);
1933
1934 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1935 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
1936 {
1937 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
1938 } else {
1939 panic!("expected String variant");
1940 }
1941 }
1942
1943 #[test]
1944 fn schema_multi_select_serialization() {
1945 let schema = ElicitationSchema::new().property(
1946 "colors",
1947 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
1948 .min_items(1)
1949 .max_items(3),
1950 false,
1951 );
1952
1953 let json = serde_json::to_value(&schema).unwrap();
1954 assert_eq!(json["properties"]["colors"]["type"], "array");
1955 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
1956 assert_eq!(json["properties"]["colors"]["minItems"], 1);
1957 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
1958
1959 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1960 assert!(matches!(
1961 roundtripped.properties.get("colors").unwrap(),
1962 ElicitationPropertySchema::Array(_)
1963 ));
1964 }
1965
1966 #[test]
1967 fn schema_titled_enum_serialization() {
1968 let schema = ElicitationSchema::new().property(
1969 "country",
1970 StringPropertySchema::new().one_of(vec![
1971 EnumOption::new("us", "United States"),
1972 EnumOption::new("uk", "United Kingdom"),
1973 ]),
1974 true,
1975 );
1976
1977 let json = serde_json::to_value(&schema).unwrap();
1978 assert_eq!(json["properties"]["country"]["type"], "string");
1979 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
1980 assert_eq!(one_of.len(), 2);
1981 assert_eq!(one_of[0]["const"], "us");
1982 assert_eq!(one_of[0]["title"], "United States");
1983
1984 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1985 if let ElicitationPropertySchema::String(s) =
1986 roundtripped.properties.get("country").unwrap()
1987 {
1988 assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
1989 } else {
1990 panic!("expected String variant");
1991 }
1992 }
1993
1994 #[test]
1995 fn schema_number_property_serialization() {
1996 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
1997
1998 let json = serde_json::to_value(&schema).unwrap();
1999 assert_eq!(json["properties"]["rating"]["type"], "number");
2000 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2001 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2002
2003 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2004 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2005 {
2006 assert_eq!(n.minimum, Some(0.0));
2007 assert_eq!(n.maximum, Some(5.0));
2008 } else {
2009 panic!("expected Number variant");
2010 }
2011 }
2012
2013 #[test]
2014 fn schema_string_format_serialization() {
2015 let schema = ElicitationSchema::new()
2016 .uri("website", true)
2017 .date("birthday", true)
2018 .date_time("updated_at", false);
2019
2020 let json = serde_json::to_value(&schema).unwrap();
2021 assert_eq!(json["properties"]["website"]["type"], "string");
2022 assert_eq!(json["properties"]["website"]["format"], "uri");
2023 assert_eq!(json["properties"]["birthday"]["type"], "string");
2024 assert_eq!(json["properties"]["birthday"]["format"], "date");
2025 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2026 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2027
2028 let required = json["required"].as_array().unwrap();
2029 assert!(required.contains(&json!("website")));
2030 assert!(required.contains(&json!("birthday")));
2031 assert!(!required.contains(&json!("updated_at")));
2032 }
2033
2034 #[test]
2035 fn schema_string_pattern_serialization() {
2036 let schema = ElicitationSchema::new().property(
2037 "name",
2038 StringPropertySchema::new()
2039 .min_length(1)
2040 .max_length(64)
2041 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2042 true,
2043 );
2044
2045 let json = serde_json::to_value(&schema).unwrap();
2046 assert_eq!(json["properties"]["name"]["type"], "string");
2047 assert_eq!(
2048 json["properties"]["name"]["pattern"],
2049 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2050 );
2051
2052 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2053 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2054 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2055 } else {
2056 panic!("expected String variant");
2057 }
2058 }
2059
2060 #[test]
2061 fn schema_property_updates_required_state() {
2062 let schema = ElicitationSchema::new()
2063 .string("name", true)
2064 .email("name", false);
2065
2066 let json = serde_json::to_value(&schema).unwrap();
2067 assert!(json.get("required").is_none());
2068 assert_eq!(json["properties"]["name"]["format"], "email");
2069 }
2070
2071 #[test]
2072 fn schema_rejects_invalid_object_type() {
2073 let err = serde_json::from_value::<ElicitationSchema>(json!({
2074 "type": "array",
2075 "properties": {
2076 "name": {
2077 "type": "string"
2078 }
2079 }
2080 }))
2081 .unwrap_err();
2082
2083 assert!(err.to_string().contains("unknown variant"));
2084 }
2085
2086 #[test]
2087 fn titled_multi_select_items_reject_one_of() {
2088 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2089 "oneOf": [
2090 {
2091 "const": "red",
2092 "title": "Red"
2093 }
2094 ]
2095 }))
2096 .unwrap_err();
2097
2098 assert!(err.to_string().contains("missing field `anyOf`"));
2099 }
2100
2101 #[test]
2102 fn response_accept_rejects_non_object_content() {
2103 let err = serde_json::from_value::<CreateElicitationResponse>(json!({
2104 "action": "accept",
2105 "content": "Alice"
2106 }))
2107 .unwrap_err();
2108
2109 assert!(err.to_string().contains("invalid type"));
2110 }
2111
2112 #[test]
2113 fn response_accept_rejects_nested_object_content() {
2114 let err = serde_json::from_value::<CreateElicitationResponse>(json!({
2115 "action": "accept",
2116 "content": {
2117 "profile": {
2118 "name": "Alice"
2119 }
2120 }
2121 }))
2122 .unwrap_err();
2123
2124 assert!(err.to_string().contains("data did not match any variant"));
2125 }
2126
2127 #[test]
2128 fn response_accept_allows_primitive_and_string_array_content() {
2129 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2130 ElicitationAcceptAction::new().content(BTreeMap::from([
2131 ("name".to_string(), ElicitationContentValue::from("Alice")),
2132 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2133 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2134 (
2135 "subscribed".to_string(),
2136 ElicitationContentValue::from(true),
2137 ),
2138 (
2139 "tags".to_string(),
2140 ElicitationContentValue::from(vec!["rust", "acp"]),
2141 ),
2142 ])),
2143 ));
2144
2145 let json = serde_json::to_value(&response).unwrap();
2146 assert_eq!(json["action"], "accept");
2147 assert_eq!(json["content"]["name"], "Alice");
2148 assert_eq!(json["content"]["age"], 30);
2149 assert_eq!(json["content"]["score"], 9.5);
2150 assert_eq!(json["content"]["subscribed"], true);
2151 assert_eq!(json["content"]["tags"][0], "rust");
2152 assert_eq!(json["content"]["tags"][1], "acp");
2153 }
2154}