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]
35 pub fn new(id: impl Into<Arc<str>>) -> Self {
36 Self(id.into())
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
42#[serde(rename_all = "kebab-case")]
43#[non_exhaustive]
44pub enum StringFormat {
45 Email,
47 Uri,
49 Date,
51 DateTime,
53 #[serde(untagged)]
58 Other(String),
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
63#[serde(rename_all = "snake_case")]
64#[non_exhaustive]
65pub enum ElicitationSchemaType {
66 #[default]
68 Object,
69}
70
71#[skip_serializing_none]
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
74#[non_exhaustive]
75pub struct EnumOption {
76 #[serde(rename = "const")]
78 pub value: String,
79 pub title: String,
81 #[serde(rename = "_meta")]
87 pub meta: Option<Meta>,
88}
89
90impl EnumOption {
91 #[must_use]
93 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
94 Self {
95 value: value.into(),
96 title: title.into(),
97 meta: None,
98 }
99 }
100
101 #[must_use]
107 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
108 self.meta = meta.into_option();
109 self
110 }
111}
112
113#[skip_serializing_none]
118#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
119#[serde(rename_all = "camelCase")]
120#[non_exhaustive]
121pub struct StringPropertySchema {
122 pub title: Option<String>,
124 pub description: Option<String>,
126 pub min_length: Option<u32>,
128 pub max_length: Option<u32>,
130 pub pattern: Option<String>,
132 pub format: Option<StringFormat>,
134 pub default: Option<String>,
136 #[serde(rename = "enum")]
138 pub enum_values: Option<Vec<String>>,
139 #[serde(rename = "oneOf")]
141 pub one_of: Option<Vec<EnumOption>>,
142 #[serde(rename = "_meta")]
148 pub meta: Option<Meta>,
149}
150
151impl StringPropertySchema {
152 #[must_use]
154 pub fn new() -> Self {
155 Self::default()
156 }
157
158 #[must_use]
160 pub fn email() -> Self {
161 Self {
162 format: Some(StringFormat::Email),
163 ..Default::default()
164 }
165 }
166
167 #[must_use]
169 pub fn uri() -> Self {
170 Self {
171 format: Some(StringFormat::Uri),
172 ..Default::default()
173 }
174 }
175
176 #[must_use]
178 pub fn date() -> Self {
179 Self {
180 format: Some(StringFormat::Date),
181 ..Default::default()
182 }
183 }
184
185 #[must_use]
187 pub fn date_time() -> Self {
188 Self {
189 format: Some(StringFormat::DateTime),
190 ..Default::default()
191 }
192 }
193
194 #[must_use]
196 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
197 self.title = title.into_option();
198 self
199 }
200
201 #[must_use]
203 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
204 self.description = description.into_option();
205 self
206 }
207
208 #[must_use]
210 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
211 self.min_length = min_length.into_option();
212 self
213 }
214
215 #[must_use]
217 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
218 self.max_length = max_length.into_option();
219 self
220 }
221
222 #[must_use]
224 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
225 self.pattern = pattern.into_option();
226 self
227 }
228
229 #[must_use]
231 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
232 self.format = format.into_option();
233 self
234 }
235
236 #[must_use]
238 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
239 self.default = default.into_option();
240 self
241 }
242
243 #[must_use]
245 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
246 self.enum_values = enum_values.into_option();
247 self
248 }
249
250 #[must_use]
252 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
253 self.one_of = one_of.into_option();
254 self
255 }
256
257 #[must_use]
263 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
264 self.meta = meta.into_option();
265 self
266 }
267}
268
269#[skip_serializing_none]
271#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
272#[serde(rename_all = "camelCase")]
273#[non_exhaustive]
274pub struct NumberPropertySchema {
275 pub title: Option<String>,
277 pub description: Option<String>,
279 pub minimum: Option<f64>,
281 pub maximum: Option<f64>,
283 pub default: Option<f64>,
285 #[serde(rename = "_meta")]
291 pub meta: Option<Meta>,
292}
293
294impl NumberPropertySchema {
295 #[must_use]
297 pub fn new() -> Self {
298 Self::default()
299 }
300
301 #[must_use]
303 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
304 self.title = title.into_option();
305 self
306 }
307
308 #[must_use]
310 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
311 self.description = description.into_option();
312 self
313 }
314
315 #[must_use]
317 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
318 self.minimum = minimum.into_option();
319 self
320 }
321
322 #[must_use]
324 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
325 self.maximum = maximum.into_option();
326 self
327 }
328
329 #[must_use]
331 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
332 self.default = default.into_option();
333 self
334 }
335
336 #[must_use]
342 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
343 self.meta = meta.into_option();
344 self
345 }
346}
347
348#[skip_serializing_none]
350#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
351#[serde(rename_all = "camelCase")]
352#[non_exhaustive]
353pub struct IntegerPropertySchema {
354 pub title: Option<String>,
356 pub description: Option<String>,
358 pub minimum: Option<i64>,
360 pub maximum: Option<i64>,
362 pub default: Option<i64>,
364 #[serde(rename = "_meta")]
370 pub meta: Option<Meta>,
371}
372
373impl IntegerPropertySchema {
374 #[must_use]
376 pub fn new() -> Self {
377 Self::default()
378 }
379
380 #[must_use]
382 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
383 self.title = title.into_option();
384 self
385 }
386
387 #[must_use]
389 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
390 self.description = description.into_option();
391 self
392 }
393
394 #[must_use]
396 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
397 self.minimum = minimum.into_option();
398 self
399 }
400
401 #[must_use]
403 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
404 self.maximum = maximum.into_option();
405 self
406 }
407
408 #[must_use]
410 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
411 self.default = default.into_option();
412 self
413 }
414
415 #[must_use]
421 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
422 self.meta = meta.into_option();
423 self
424 }
425}
426
427#[skip_serializing_none]
429#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
430#[serde(rename_all = "camelCase")]
431#[non_exhaustive]
432pub struct BooleanPropertySchema {
433 pub title: Option<String>,
435 pub description: Option<String>,
437 pub default: Option<bool>,
439 #[serde(rename = "_meta")]
445 pub meta: Option<Meta>,
446}
447
448impl BooleanPropertySchema {
449 #[must_use]
451 pub fn new() -> Self {
452 Self::default()
453 }
454
455 #[must_use]
457 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
458 self.title = title.into_option();
459 self
460 }
461
462 #[must_use]
464 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
465 self.description = description.into_option();
466 self
467 }
468
469 #[must_use]
471 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
472 self.default = default.into_option();
473 self
474 }
475
476 #[must_use]
482 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
483 self.meta = meta.into_option();
484 self
485 }
486}
487
488#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
490#[serde(rename_all = "snake_case")]
491#[non_exhaustive]
492pub enum ElicitationStringType {
493 #[default]
495 String,
496}
497
498#[skip_serializing_none]
500#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
501#[non_exhaustive]
502pub struct UntitledMultiSelectItems {
503 #[serde(rename = "type")]
505 pub type_: ElicitationStringType,
506 #[serde(rename = "enum")]
508 pub values: Vec<String>,
509 #[serde(rename = "_meta")]
515 pub meta: Option<Meta>,
516}
517
518impl UntitledMultiSelectItems {
519 #[must_use]
525 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
526 self.meta = meta.into_option();
527 self
528 }
529}
530
531#[skip_serializing_none]
533#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
534#[non_exhaustive]
535pub struct TitledMultiSelectItems {
536 #[serde(rename = "anyOf")]
538 pub options: Vec<EnumOption>,
539 #[serde(rename = "_meta")]
545 pub meta: Option<Meta>,
546}
547
548impl TitledMultiSelectItems {
549 #[must_use]
551 pub fn new(options: Vec<EnumOption>) -> Self {
552 Self {
553 options,
554 meta: None,
555 }
556 }
557
558 #[must_use]
564 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
565 self.meta = meta.into_option();
566 self
567 }
568}
569
570#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
572#[serde(untagged)]
573#[non_exhaustive]
574pub enum MultiSelectItems {
575 Untitled(UntitledMultiSelectItems),
577 Titled(TitledMultiSelectItems),
579}
580
581#[skip_serializing_none]
583#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
584#[serde(rename_all = "camelCase")]
585#[non_exhaustive]
586pub struct MultiSelectPropertySchema {
587 pub title: Option<String>,
589 pub description: Option<String>,
591 pub min_items: Option<u64>,
593 pub max_items: Option<u64>,
595 pub items: MultiSelectItems,
597 pub default: Option<Vec<String>>,
599 #[serde(rename = "_meta")]
605 pub meta: Option<Meta>,
606}
607
608impl MultiSelectPropertySchema {
609 #[must_use]
611 pub fn new(values: Vec<String>) -> Self {
612 Self {
613 title: None,
614 description: None,
615 min_items: None,
616 max_items: None,
617 items: MultiSelectItems::Untitled(UntitledMultiSelectItems {
618 type_: ElicitationStringType::String,
619 values,
620 meta: None,
621 }),
622 default: None,
623 meta: None,
624 }
625 }
626
627 #[must_use]
629 pub fn titled(options: Vec<EnumOption>) -> Self {
630 Self {
631 title: None,
632 description: None,
633 min_items: None,
634 max_items: None,
635 items: MultiSelectItems::Titled(TitledMultiSelectItems {
636 options,
637 meta: None,
638 }),
639 default: None,
640 meta: None,
641 }
642 }
643
644 #[must_use]
646 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
647 self.title = title.into_option();
648 self
649 }
650
651 #[must_use]
653 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
654 self.description = description.into_option();
655 self
656 }
657
658 #[must_use]
660 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
661 self.min_items = min_items.into_option();
662 self
663 }
664
665 #[must_use]
667 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
668 self.max_items = max_items.into_option();
669 self
670 }
671
672 #[must_use]
674 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
675 self.default = default.into_option();
676 self
677 }
678
679 #[must_use]
685 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
686 self.meta = meta.into_option();
687 self
688 }
689}
690
691#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
697#[serde(tag = "type", rename_all = "snake_case")]
698#[schemars(extend("discriminator" = {"propertyName": "type"}))]
699#[non_exhaustive]
700pub enum ElicitationPropertySchema {
701 String(StringPropertySchema),
703 Number(NumberPropertySchema),
705 Integer(IntegerPropertySchema),
707 Boolean(BooleanPropertySchema),
709 Array(MultiSelectPropertySchema),
711}
712
713impl From<StringPropertySchema> for ElicitationPropertySchema {
714 fn from(schema: StringPropertySchema) -> Self {
715 Self::String(schema)
716 }
717}
718
719impl From<NumberPropertySchema> for ElicitationPropertySchema {
720 fn from(schema: NumberPropertySchema) -> Self {
721 Self::Number(schema)
722 }
723}
724
725impl From<IntegerPropertySchema> for ElicitationPropertySchema {
726 fn from(schema: IntegerPropertySchema) -> Self {
727 Self::Integer(schema)
728 }
729}
730
731impl From<BooleanPropertySchema> for ElicitationPropertySchema {
732 fn from(schema: BooleanPropertySchema) -> Self {
733 Self::Boolean(schema)
734 }
735}
736
737impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
738 fn from(schema: MultiSelectPropertySchema) -> Self {
739 Self::Array(schema)
740 }
741}
742
743fn default_object_type() -> ElicitationSchemaType {
744 ElicitationSchemaType::Object
745}
746
747#[skip_serializing_none]
752#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
753#[serde(rename_all = "camelCase")]
754#[non_exhaustive]
755pub struct ElicitationSchema {
756 #[serde(rename = "type", default = "default_object_type")]
758 pub type_: ElicitationSchemaType,
759 pub title: Option<String>,
761 #[serde(default)]
763 pub properties: BTreeMap<String, ElicitationPropertySchema>,
764 pub required: Option<Vec<String>>,
766 pub description: Option<String>,
768 #[serde(rename = "_meta")]
774 pub meta: Option<Meta>,
775}
776
777impl Default for ElicitationSchema {
778 fn default() -> Self {
779 Self {
780 type_: default_object_type(),
781 title: None,
782 properties: BTreeMap::new(),
783 required: None,
784 description: None,
785 meta: None,
786 }
787 }
788}
789
790impl ElicitationSchema {
791 #[must_use]
793 pub fn new() -> Self {
794 Self::default()
795 }
796
797 #[must_use]
799 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
800 self.title = title.into_option();
801 self
802 }
803
804 #[must_use]
806 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
807 self.description = description.into_option();
808 self
809 }
810
811 #[must_use]
817 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
818 self.meta = meta.into_option();
819 self
820 }
821
822 #[must_use]
824 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
825 where
826 S: Into<ElicitationPropertySchema>,
827 {
828 let name = name.into();
829 self.properties.insert(name.clone(), schema.into());
830
831 if required {
832 let required_fields = self.required.get_or_insert_with(Vec::new);
833 if !required_fields.contains(&name) {
834 required_fields.push(name);
835 }
836 } else if let Some(required_fields) = &mut self.required {
837 required_fields.retain(|field| field != &name);
838
839 if required_fields.is_empty() {
840 self.required = None;
841 }
842 }
843
844 self
845 }
846
847 #[must_use]
849 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
850 self.property(name, StringPropertySchema::new(), required)
851 }
852
853 #[must_use]
855 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
856 self.property(name, StringPropertySchema::email(), required)
857 }
858
859 #[must_use]
861 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
862 self.property(name, StringPropertySchema::uri(), required)
863 }
864
865 #[must_use]
867 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
868 self.property(name, StringPropertySchema::date(), required)
869 }
870
871 #[must_use]
873 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
874 self.property(name, StringPropertySchema::date_time(), required)
875 }
876
877 #[must_use]
879 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
880 self.property(
881 name,
882 NumberPropertySchema::new().minimum(min).maximum(max),
883 required,
884 )
885 }
886
887 #[must_use]
889 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
890 self.property(
891 name,
892 IntegerPropertySchema::new().minimum(min).maximum(max),
893 required,
894 )
895 }
896
897 #[must_use]
899 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
900 self.property(name, BooleanPropertySchema::new(), required)
901 }
902}
903
904#[serde_as]
910#[skip_serializing_none]
911#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
912#[serde(rename_all = "camelCase")]
913#[non_exhaustive]
914pub struct ElicitationCapabilities {
915 #[serde_as(deserialize_as = "DefaultOnError")]
917 #[schemars(extend("x-deserialize-default-on-error" = true))]
918 #[serde(default)]
919 pub form: Option<ElicitationFormCapabilities>,
920 #[serde_as(deserialize_as = "DefaultOnError")]
922 #[schemars(extend("x-deserialize-default-on-error" = true))]
923 #[serde(default)]
924 pub url: Option<ElicitationUrlCapabilities>,
925 #[serde(rename = "_meta")]
931 pub meta: Option<Meta>,
932}
933
934impl ElicitationCapabilities {
935 #[must_use]
937 pub fn new() -> Self {
938 Self::default()
939 }
940
941 #[must_use]
943 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
944 self.form = form.into_option();
945 self
946 }
947
948 #[must_use]
950 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
951 self.url = url.into_option();
952 self
953 }
954
955 #[must_use]
961 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
962 self.meta = meta.into_option();
963 self
964 }
965}
966
967#[skip_serializing_none]
973#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
974#[serde(rename_all = "camelCase")]
975#[non_exhaustive]
976pub struct ElicitationFormCapabilities {
977 #[serde(rename = "_meta")]
983 pub meta: Option<Meta>,
984}
985
986impl ElicitationFormCapabilities {
987 #[must_use]
989 pub fn new() -> Self {
990 Self::default()
991 }
992
993 #[must_use]
999 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1000 self.meta = meta.into_option();
1001 self
1002 }
1003}
1004
1005#[skip_serializing_none]
1011#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1012#[serde(rename_all = "camelCase")]
1013#[non_exhaustive]
1014pub struct ElicitationUrlCapabilities {
1015 #[serde(rename = "_meta")]
1021 pub meta: Option<Meta>,
1022}
1023
1024impl ElicitationUrlCapabilities {
1025 #[must_use]
1027 pub fn new() -> Self {
1028 Self::default()
1029 }
1030
1031 #[must_use]
1037 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1038 self.meta = meta.into_option();
1039 self
1040 }
1041}
1042
1043#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1049#[serde(untagged)]
1050#[non_exhaustive]
1051pub enum ElicitationScope {
1052 Session(ElicitationSessionScope),
1054 Request(ElicitationRequestScope),
1057}
1058
1059#[skip_serializing_none]
1069#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1070#[serde(rename_all = "camelCase")]
1071#[non_exhaustive]
1072pub struct ElicitationSessionScope {
1073 pub session_id: SessionId,
1075 pub tool_call_id: Option<ToolCallId>,
1077}
1078
1079impl ElicitationSessionScope {
1080 #[must_use]
1082 pub fn new(session_id: impl Into<SessionId>) -> Self {
1083 Self {
1084 session_id: session_id.into(),
1085 tool_call_id: None,
1086 }
1087 }
1088
1089 #[must_use]
1091 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1092 self.tool_call_id = tool_call_id.into_option();
1093 self
1094 }
1095}
1096
1097#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1104#[serde(rename_all = "camelCase")]
1105#[non_exhaustive]
1106pub struct ElicitationRequestScope {
1107 pub request_id: RequestId,
1109}
1110
1111impl ElicitationRequestScope {
1112 #[must_use]
1114 pub fn new(request_id: impl Into<RequestId>) -> Self {
1115 Self {
1116 request_id: request_id.into(),
1117 }
1118 }
1119}
1120
1121impl From<ElicitationSessionScope> for ElicitationScope {
1122 fn from(scope: ElicitationSessionScope) -> Self {
1123 Self::Session(scope)
1124 }
1125}
1126
1127impl From<ElicitationRequestScope> for ElicitationScope {
1128 fn from(scope: ElicitationRequestScope) -> Self {
1129 Self::Request(scope)
1130 }
1131}
1132
1133#[skip_serializing_none]
1143#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1144#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1145#[serde(rename_all = "camelCase")]
1146#[non_exhaustive]
1147pub struct CreateElicitationRequest {
1148 #[serde(flatten)]
1150 pub mode: ElicitationMode,
1151 pub message: String,
1153 #[serde(rename = "_meta")]
1159 pub meta: Option<Meta>,
1160}
1161
1162impl CreateElicitationRequest {
1163 #[must_use]
1165 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1166 Self {
1167 mode: mode.into(),
1168 message: message.into(),
1169 meta: None,
1170 }
1171 }
1172
1173 #[must_use]
1175 pub fn scope(&self) -> &ElicitationScope {
1176 self.mode.scope()
1177 }
1178
1179 #[must_use]
1185 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1186 self.meta = meta.into_option();
1187 self
1188 }
1189}
1190
1191#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1197#[serde(tag = "mode", rename_all = "snake_case")]
1198#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1199#[non_exhaustive]
1200pub enum ElicitationMode {
1201 Form(ElicitationFormMode),
1203 Url(ElicitationUrlMode),
1205}
1206
1207impl From<ElicitationFormMode> for ElicitationMode {
1208 fn from(mode: ElicitationFormMode) -> Self {
1209 Self::Form(mode)
1210 }
1211}
1212
1213impl From<ElicitationUrlMode> for ElicitationMode {
1214 fn from(mode: ElicitationUrlMode) -> Self {
1215 Self::Url(mode)
1216 }
1217}
1218
1219impl ElicitationMode {
1220 #[must_use]
1222 pub fn scope(&self) -> &ElicitationScope {
1223 match self {
1224 Self::Form(f) => &f.scope,
1225 Self::Url(u) => &u.scope,
1226 }
1227 }
1228}
1229
1230#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1236#[serde(rename_all = "camelCase")]
1237#[non_exhaustive]
1238pub struct ElicitationFormMode {
1239 #[serde(flatten)]
1241 pub scope: ElicitationScope,
1242 pub requested_schema: ElicitationSchema,
1244}
1245
1246impl ElicitationFormMode {
1247 #[must_use]
1249 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1250 Self {
1251 scope: scope.into(),
1252 requested_schema,
1253 }
1254 }
1255}
1256
1257#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1263#[serde(rename_all = "camelCase")]
1264#[non_exhaustive]
1265pub struct ElicitationUrlMode {
1266 #[serde(flatten)]
1268 pub scope: ElicitationScope,
1269 pub elicitation_id: ElicitationId,
1271 #[schemars(extend("format" = "uri"))]
1273 pub url: String,
1274}
1275
1276impl ElicitationUrlMode {
1277 #[must_use]
1279 pub fn new(
1280 scope: impl Into<ElicitationScope>,
1281 elicitation_id: impl Into<ElicitationId>,
1282 url: impl Into<String>,
1283 ) -> Self {
1284 Self {
1285 scope: scope.into(),
1286 elicitation_id: elicitation_id.into(),
1287 url: url.into(),
1288 }
1289 }
1290}
1291
1292#[skip_serializing_none]
1298#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1299#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1300#[serde(rename_all = "camelCase")]
1301#[non_exhaustive]
1302pub struct CreateElicitationResponse {
1303 #[serde(flatten)]
1305 pub action: ElicitationAction,
1306 #[serde(rename = "_meta")]
1312 pub meta: Option<Meta>,
1313}
1314
1315impl CreateElicitationResponse {
1316 #[must_use]
1318 pub fn new(action: ElicitationAction) -> Self {
1319 Self { action, meta: None }
1320 }
1321
1322 #[must_use]
1328 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1329 self.meta = meta.into_option();
1330 self
1331 }
1332}
1333
1334#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1340#[serde(tag = "action", rename_all = "snake_case")]
1341#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1342#[non_exhaustive]
1343pub enum ElicitationAction {
1344 Accept(ElicitationAcceptAction),
1346 Decline,
1348 Cancel,
1350}
1351
1352#[skip_serializing_none]
1358#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1359#[serde(rename_all = "camelCase")]
1360#[non_exhaustive]
1361pub struct ElicitationAcceptAction {
1362 #[serde(default)]
1364 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1365}
1366
1367impl ElicitationAcceptAction {
1368 #[must_use]
1370 pub fn new() -> Self {
1371 Self { content: None }
1372 }
1373
1374 #[must_use]
1376 pub fn content(
1377 mut self,
1378 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1379 ) -> Self {
1380 self.content = content.into_option();
1381 self
1382 }
1383}
1384
1385#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1387#[serde(untagged)]
1388#[non_exhaustive]
1389pub enum ElicitationContentValue {
1390 String(String),
1392 Integer(i64),
1394 Number(f64),
1396 Boolean(bool),
1398 StringArray(Vec<String>),
1400}
1401
1402impl From<String> for ElicitationContentValue {
1403 fn from(value: String) -> Self {
1404 Self::String(value)
1405 }
1406}
1407
1408impl From<&str> for ElicitationContentValue {
1409 fn from(value: &str) -> Self {
1410 Self::String(value.to_string())
1411 }
1412}
1413
1414impl From<i64> for ElicitationContentValue {
1415 fn from(value: i64) -> Self {
1416 Self::Integer(value)
1417 }
1418}
1419
1420impl From<i32> for ElicitationContentValue {
1421 fn from(value: i32) -> Self {
1422 Self::Integer(i64::from(value))
1423 }
1424}
1425
1426impl From<f64> for ElicitationContentValue {
1427 fn from(value: f64) -> Self {
1428 Self::Number(value)
1429 }
1430}
1431
1432impl From<bool> for ElicitationContentValue {
1433 fn from(value: bool) -> Self {
1434 Self::Boolean(value)
1435 }
1436}
1437
1438impl From<Vec<String>> for ElicitationContentValue {
1439 fn from(value: Vec<String>) -> Self {
1440 Self::StringArray(value)
1441 }
1442}
1443
1444impl From<Vec<&str>> for ElicitationContentValue {
1445 fn from(value: Vec<&str>) -> Self {
1446 Self::StringArray(value.into_iter().map(str::to_string).collect())
1447 }
1448}
1449
1450impl Default for ElicitationAcceptAction {
1451 fn default() -> Self {
1452 Self::new()
1453 }
1454}
1455
1456#[skip_serializing_none]
1462#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1463#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1464#[serde(rename_all = "camelCase")]
1465#[non_exhaustive]
1466pub struct CompleteElicitationNotification {
1467 pub elicitation_id: ElicitationId,
1469 #[serde(rename = "_meta")]
1475 pub meta: Option<Meta>,
1476}
1477
1478impl CompleteElicitationNotification {
1479 #[must_use]
1481 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
1482 Self {
1483 elicitation_id: elicitation_id.into(),
1484 meta: None,
1485 }
1486 }
1487
1488 #[must_use]
1494 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1495 self.meta = meta.into_option();
1496 self
1497 }
1498}
1499
1500#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1507#[serde(rename_all = "camelCase")]
1508#[non_exhaustive]
1509pub struct UrlElicitationRequiredData {
1510 pub elicitations: Vec<UrlElicitationRequiredItem>,
1512}
1513
1514impl UrlElicitationRequiredData {
1515 #[must_use]
1517 pub fn new(elicitations: Vec<UrlElicitationRequiredItem>) -> Self {
1518 Self { elicitations }
1519 }
1520}
1521
1522#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1528#[serde(rename_all = "camelCase")]
1529#[non_exhaustive]
1530pub struct UrlElicitationRequiredItem {
1531 pub mode: ElicitationUrlOnlyMode,
1533 pub elicitation_id: ElicitationId,
1535 #[schemars(extend("format" = "uri"))]
1537 pub url: String,
1538 pub message: String,
1540}
1541
1542#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
1544#[serde(rename_all = "snake_case")]
1545#[non_exhaustive]
1546pub enum ElicitationUrlOnlyMode {
1547 #[default]
1549 Url,
1550}
1551
1552impl UrlElicitationRequiredItem {
1553 #[must_use]
1555 pub fn new(
1556 elicitation_id: impl Into<ElicitationId>,
1557 url: impl Into<String>,
1558 message: impl Into<String>,
1559 ) -> Self {
1560 Self {
1561 mode: ElicitationUrlOnlyMode::Url,
1562 elicitation_id: elicitation_id.into(),
1563 url: url.into(),
1564 message: message.into(),
1565 }
1566 }
1567}
1568
1569#[cfg(test)]
1570mod tests {
1571 use super::*;
1572 use serde_json::json;
1573
1574 #[test]
1575 fn form_mode_request_serialization() {
1576 let schema = ElicitationSchema::new().string("name", true);
1577 let req = CreateElicitationRequest::new(
1578 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
1579 "Please enter your name",
1580 );
1581
1582 let json = serde_json::to_value(&req).unwrap();
1583 assert_eq!(json["sessionId"], "sess_1");
1584 assert!(json.get("toolCallId").is_none());
1585 assert_eq!(json["mode"], "form");
1586 assert_eq!(json["message"], "Please enter your name");
1587 assert!(json["requestedSchema"].is_object());
1588 assert_eq!(json["requestedSchema"]["type"], "object");
1589 assert_eq!(
1590 json["requestedSchema"]["properties"]["name"]["type"],
1591 "string"
1592 );
1593
1594 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1595 assert_eq!(
1596 *roundtripped.scope(),
1597 ElicitationSessionScope::new("sess_1").into()
1598 );
1599 assert_eq!(roundtripped.message, "Please enter your name");
1600 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
1601 }
1602
1603 #[test]
1604 fn url_mode_request_serialization() {
1605 let req = CreateElicitationRequest::new(
1606 ElicitationUrlMode::new(
1607 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
1608 "elic_1",
1609 "https://example.com/auth",
1610 ),
1611 "Please authenticate",
1612 );
1613
1614 let json = serde_json::to_value(&req).unwrap();
1615 assert_eq!(json["sessionId"], "sess_2");
1616 assert_eq!(json["toolCallId"], "tc_1");
1617 assert_eq!(json["mode"], "url");
1618 assert_eq!(json["elicitationId"], "elic_1");
1619 assert_eq!(json["url"], "https://example.com/auth");
1620 assert_eq!(json["message"], "Please authenticate");
1621
1622 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1623 assert_eq!(
1624 *roundtripped.scope(),
1625 ElicitationSessionScope::new("sess_2")
1626 .tool_call_id("tc_1")
1627 .into()
1628 );
1629 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1630 }
1631
1632 #[test]
1633 fn response_accept_serialization() {
1634 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
1635 ElicitationAcceptAction::new().content(BTreeMap::from([(
1636 "name".to_string(),
1637 ElicitationContentValue::from("Alice"),
1638 )])),
1639 ));
1640
1641 let json = serde_json::to_value(&resp).unwrap();
1642 assert_eq!(json["action"], "accept");
1643 assert_eq!(json["content"]["name"], "Alice");
1644
1645 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1646 assert!(matches!(
1647 roundtripped.action,
1648 ElicitationAction::Accept(ElicitationAcceptAction {
1649 content: Some(_),
1650 ..
1651 })
1652 ));
1653 }
1654
1655 #[test]
1656 fn response_decline_serialization() {
1657 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
1658
1659 let json = serde_json::to_value(&resp).unwrap();
1660 assert_eq!(json["action"], "decline");
1661
1662 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1663 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1664 }
1665
1666 #[test]
1667 fn response_cancel_serialization() {
1668 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
1669
1670 let json = serde_json::to_value(&resp).unwrap();
1671 assert_eq!(json["action"], "cancel");
1672
1673 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1674 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1675 }
1676
1677 #[test]
1678 fn url_mode_request_scope_serialization() {
1679 let req = CreateElicitationRequest::new(
1680 ElicitationUrlMode::new(
1681 ElicitationRequestScope::new(RequestId::Number(42)),
1682 "elic_2",
1683 "https://example.com/setup",
1684 ),
1685 "Please complete setup",
1686 );
1687
1688 let json = serde_json::to_value(&req).unwrap();
1689 assert_eq!(json["requestId"], 42);
1690 assert!(json.get("sessionId").is_none());
1691 assert_eq!(json["mode"], "url");
1692 assert_eq!(json["elicitationId"], "elic_2");
1693 assert_eq!(json["url"], "https://example.com/setup");
1694 assert_eq!(json["message"], "Please complete setup");
1695
1696 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1697 assert_eq!(
1698 *roundtripped.scope(),
1699 ElicitationRequestScope::new(RequestId::Number(42)).into()
1700 );
1701 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1702 }
1703
1704 #[test]
1705 fn request_scope_request_serialization() {
1706 let req = CreateElicitationRequest::new(
1707 ElicitationFormMode::new(
1708 ElicitationRequestScope::new(RequestId::Number(99)),
1709 ElicitationSchema::new().string("workspace", true),
1710 ),
1711 "Enter workspace name",
1712 );
1713
1714 let json = serde_json::to_value(&req).unwrap();
1715 assert_eq!(json["requestId"], 99);
1716 assert!(json.get("sessionId").is_none());
1717
1718 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1719 assert_eq!(
1720 *roundtripped.scope(),
1721 ElicitationRequestScope::new(RequestId::Number(99)).into()
1722 );
1723 }
1724
1725 #[test]
1729 fn client_response_serialization_accept() {
1730 use crate::v2::ClientResponse;
1731
1732 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1733 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
1734 "name".to_string(),
1735 ElicitationContentValue::from("Alice"),
1736 )]))),
1737 ));
1738 let json = serde_json::to_value(&resp).unwrap();
1739 assert_eq!(json["action"], "accept");
1740 assert_eq!(json["content"]["name"], "Alice");
1741
1742 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1744 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
1745 }
1746
1747 #[test]
1748 fn client_response_serialization_decline() {
1749 use crate::v2::ClientResponse;
1750
1751 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1752 ElicitationAction::Decline,
1753 ));
1754 let json = serde_json::to_value(&resp).unwrap();
1755 assert_eq!(json["action"], "decline");
1756
1757 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1758 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1759 }
1760
1761 #[test]
1762 fn client_response_serialization_cancel() {
1763 use crate::v2::ClientResponse;
1764
1765 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1766 ElicitationAction::Cancel,
1767 ));
1768 let json = serde_json::to_value(&resp).unwrap();
1769 assert_eq!(json["action"], "cancel");
1770
1771 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1772 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1773 }
1774
1775 #[test]
1778 fn request_tolerates_extra_fields() {
1779 let json = json!({
1780 "sessionId": "sess_1",
1781 "mode": "form",
1782 "message": "Enter your name",
1783 "requestedSchema": {
1784 "type": "object",
1785 "properties": {
1786 "name": { "type": "string", "title": "Name" }
1787 },
1788 "required": ["name"]
1789 },
1790 "unknownStringField": "hello",
1791 "unknownNumberField": 42
1792 });
1793
1794 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1795 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
1796 assert_eq!(req.message, "Enter your name");
1797 assert!(matches!(req.mode, ElicitationMode::Form(_)));
1798 }
1799
1800 #[test]
1801 fn completion_notification_serialization() {
1802 let notif = CompleteElicitationNotification::new("elic_1");
1803
1804 let json = serde_json::to_value(¬if).unwrap();
1805 assert_eq!(json["elicitationId"], "elic_1");
1806
1807 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
1808 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
1809 }
1810
1811 #[test]
1812 fn capabilities_form_only() {
1813 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
1814
1815 let json = serde_json::to_value(&caps).unwrap();
1816 assert!(json["form"].is_object());
1817 assert!(json.get("url").is_none());
1818
1819 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1820 assert!(roundtripped.form.is_some());
1821 assert!(roundtripped.url.is_none());
1822 }
1823
1824 #[test]
1825 fn capabilities_url_only() {
1826 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
1827
1828 let json = serde_json::to_value(&caps).unwrap();
1829 assert!(json.get("form").is_none());
1830 assert!(json["url"].is_object());
1831
1832 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1833 assert!(roundtripped.form.is_none());
1834 assert!(roundtripped.url.is_some());
1835 }
1836
1837 #[test]
1838 fn capabilities_both() {
1839 let caps = ElicitationCapabilities::new()
1840 .form(ElicitationFormCapabilities::new())
1841 .url(ElicitationUrlCapabilities::new());
1842
1843 let json = serde_json::to_value(&caps).unwrap();
1844 assert!(json["form"].is_object());
1845 assert!(json["url"].is_object());
1846
1847 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1848 assert!(roundtripped.form.is_some());
1849 assert!(roundtripped.url.is_some());
1850 }
1851
1852 #[test]
1853 fn url_elicitation_required_data_serialization() {
1854 let data = UrlElicitationRequiredData::new(vec![UrlElicitationRequiredItem::new(
1855 "elic_1",
1856 "https://example.com/auth",
1857 "Please authenticate",
1858 )]);
1859
1860 let json = serde_json::to_value(&data).unwrap();
1861 assert_eq!(json["elicitations"][0]["mode"], "url");
1862 assert_eq!(json["elicitations"][0]["elicitationId"], "elic_1");
1863 assert_eq!(json["elicitations"][0]["url"], "https://example.com/auth");
1864
1865 let roundtripped: UrlElicitationRequiredData = serde_json::from_value(json).unwrap();
1866 assert_eq!(roundtripped.elicitations.len(), 1);
1867 assert_eq!(
1868 roundtripped.elicitations[0].mode,
1869 ElicitationUrlOnlyMode::Url
1870 );
1871 }
1872
1873 #[test]
1874 fn schema_default_sets_object_type() {
1875 let schema = ElicitationSchema::default();
1876
1877 assert_eq!(schema.type_, ElicitationSchemaType::Object);
1878 assert!(schema.properties.is_empty());
1879
1880 let json = serde_json::to_value(&schema).unwrap();
1881 assert_eq!(json["type"], "object");
1882 }
1883
1884 #[test]
1885 fn schema_builder_serialization() {
1886 let schema = ElicitationSchema::new()
1887 .string("name", true)
1888 .email("email", true)
1889 .integer("age", 0, 150, true)
1890 .boolean("newsletter", false)
1891 .description("User registration");
1892
1893 let json = serde_json::to_value(&schema).unwrap();
1894 assert_eq!(json["type"], "object");
1895 assert_eq!(json["description"], "User registration");
1896 assert_eq!(json["properties"]["name"]["type"], "string");
1897 assert_eq!(json["properties"]["email"]["type"], "string");
1898 assert_eq!(json["properties"]["email"]["format"], "email");
1899 assert_eq!(json["properties"]["age"]["type"], "integer");
1900 assert_eq!(json["properties"]["age"]["minimum"], 0);
1901 assert_eq!(json["properties"]["age"]["maximum"], 150);
1902 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
1903
1904 let required = json["required"].as_array().unwrap();
1905 assert!(required.contains(&json!("name")));
1906 assert!(required.contains(&json!("email")));
1907 assert!(required.contains(&json!("age")));
1908 assert!(!required.contains(&json!("newsletter")));
1909
1910 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1911 assert_eq!(roundtripped.properties.len(), 4);
1912 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
1913 }
1914
1915 #[test]
1916 fn schema_string_enum_serialization() {
1917 let schema = ElicitationSchema::new().property(
1918 "color",
1919 StringPropertySchema::new().enum_values(vec![
1920 "red".into(),
1921 "green".into(),
1922 "blue".into(),
1923 ]),
1924 true,
1925 );
1926
1927 let json = serde_json::to_value(&schema).unwrap();
1928 assert_eq!(json["properties"]["color"]["type"], "string");
1929 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
1930 assert_eq!(enum_vals.len(), 3);
1931
1932 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1933 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
1934 {
1935 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
1936 } else {
1937 panic!("expected String variant");
1938 }
1939 }
1940
1941 #[test]
1942 fn schema_multi_select_serialization() {
1943 let schema = ElicitationSchema::new().property(
1944 "colors",
1945 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
1946 .min_items(1)
1947 .max_items(3),
1948 false,
1949 );
1950
1951 let json = serde_json::to_value(&schema).unwrap();
1952 assert_eq!(json["properties"]["colors"]["type"], "array");
1953 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
1954 assert_eq!(json["properties"]["colors"]["minItems"], 1);
1955 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
1956
1957 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1958 assert!(matches!(
1959 roundtripped.properties.get("colors").unwrap(),
1960 ElicitationPropertySchema::Array(_)
1961 ));
1962 }
1963
1964 #[test]
1965 fn schema_titled_enum_serialization() {
1966 let schema = ElicitationSchema::new().property(
1967 "country",
1968 StringPropertySchema::new().one_of(vec![
1969 EnumOption::new("us", "United States"),
1970 EnumOption::new("uk", "United Kingdom"),
1971 ]),
1972 true,
1973 );
1974
1975 let json = serde_json::to_value(&schema).unwrap();
1976 assert_eq!(json["properties"]["country"]["type"], "string");
1977 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
1978 assert_eq!(one_of.len(), 2);
1979 assert_eq!(one_of[0]["const"], "us");
1980 assert_eq!(one_of[0]["title"], "United States");
1981
1982 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1983 if let ElicitationPropertySchema::String(s) =
1984 roundtripped.properties.get("country").unwrap()
1985 {
1986 assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
1987 } else {
1988 panic!("expected String variant");
1989 }
1990 }
1991
1992 #[test]
1993 fn schema_number_property_serialization() {
1994 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
1995
1996 let json = serde_json::to_value(&schema).unwrap();
1997 assert_eq!(json["properties"]["rating"]["type"], "number");
1998 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
1999 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2000
2001 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2002 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2003 {
2004 assert_eq!(n.minimum, Some(0.0));
2005 assert_eq!(n.maximum, Some(5.0));
2006 } else {
2007 panic!("expected Number variant");
2008 }
2009 }
2010
2011 #[test]
2012 fn schema_string_format_serialization() {
2013 let schema = ElicitationSchema::new()
2014 .uri("website", true)
2015 .date("birthday", true)
2016 .date_time("updated_at", false);
2017
2018 let json = serde_json::to_value(&schema).unwrap();
2019 assert_eq!(json["properties"]["website"]["type"], "string");
2020 assert_eq!(json["properties"]["website"]["format"], "uri");
2021 assert_eq!(json["properties"]["birthday"]["type"], "string");
2022 assert_eq!(json["properties"]["birthday"]["format"], "date");
2023 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2024 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2025
2026 let required = json["required"].as_array().unwrap();
2027 assert!(required.contains(&json!("website")));
2028 assert!(required.contains(&json!("birthday")));
2029 assert!(!required.contains(&json!("updated_at")));
2030 }
2031
2032 #[test]
2033 fn schema_string_pattern_serialization() {
2034 let schema = ElicitationSchema::new().property(
2035 "name",
2036 StringPropertySchema::new()
2037 .min_length(1)
2038 .max_length(64)
2039 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2040 true,
2041 );
2042
2043 let json = serde_json::to_value(&schema).unwrap();
2044 assert_eq!(json["properties"]["name"]["type"], "string");
2045 assert_eq!(
2046 json["properties"]["name"]["pattern"],
2047 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2048 );
2049
2050 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2051 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2052 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2053 } else {
2054 panic!("expected String variant");
2055 }
2056 }
2057
2058 #[test]
2059 fn schema_property_updates_required_state() {
2060 let schema = ElicitationSchema::new()
2061 .string("name", true)
2062 .email("name", false);
2063
2064 let json = serde_json::to_value(&schema).unwrap();
2065 assert!(json.get("required").is_none());
2066 assert_eq!(json["properties"]["name"]["format"], "email");
2067 }
2068
2069 #[test]
2070 fn schema_rejects_invalid_object_type() {
2071 let err = serde_json::from_value::<ElicitationSchema>(json!({
2072 "type": "array",
2073 "properties": {
2074 "name": {
2075 "type": "string"
2076 }
2077 }
2078 }))
2079 .unwrap_err();
2080
2081 assert!(err.to_string().contains("unknown variant"));
2082 }
2083
2084 #[test]
2085 fn titled_multi_select_items_reject_one_of() {
2086 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2087 "oneOf": [
2088 {
2089 "const": "red",
2090 "title": "Red"
2091 }
2092 ]
2093 }))
2094 .unwrap_err();
2095
2096 assert!(err.to_string().contains("missing field `anyOf`"));
2097 }
2098
2099 #[test]
2100 fn response_accept_rejects_non_object_content() {
2101 let err = serde_json::from_value::<CreateElicitationResponse>(json!({
2102 "action": "accept",
2103 "content": "Alice"
2104 }))
2105 .unwrap_err();
2106
2107 assert!(err.to_string().contains("invalid type"));
2108 }
2109
2110 #[test]
2111 fn response_accept_rejects_nested_object_content() {
2112 let err = serde_json::from_value::<CreateElicitationResponse>(json!({
2113 "action": "accept",
2114 "content": {
2115 "profile": {
2116 "name": "Alice"
2117 }
2118 }
2119 }))
2120 .unwrap_err();
2121
2122 assert!(err.to_string().contains("data did not match any variant"));
2123 }
2124
2125 #[test]
2126 fn response_accept_allows_primitive_and_string_array_content() {
2127 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2128 ElicitationAcceptAction::new().content(BTreeMap::from([
2129 ("name".to_string(), ElicitationContentValue::from("Alice")),
2130 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2131 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2132 (
2133 "subscribed".to_string(),
2134 ElicitationContentValue::from(true),
2135 ),
2136 (
2137 "tags".to_string(),
2138 ElicitationContentValue::from(vec!["rust", "acp"]),
2139 ),
2140 ])),
2141 ));
2142
2143 let json = serde_json::to_value(&response).unwrap();
2144 assert_eq!(json["action"], "accept");
2145 assert_eq!(json["content"]["name"], "Alice");
2146 assert_eq!(json["content"]["age"], 30);
2147 assert_eq!(json["content"]["score"], 9.5);
2148 assert_eq!(json["content"]["subscribed"], true);
2149 assert_eq!(json["content"]["tags"][0], "rust");
2150 assert_eq!(json["content"]["tags"][1], "acp");
2151 }
2152}