1use std::{collections::BTreeMap, sync::Arc};
9
10use derive_more::{Display, From};
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
14
15use crate::IntoOption;
16use crate::SkipListener;
17
18use super::{
19 ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, Meta, RequestId, SessionId,
20 ToolCallId,
21};
22
23#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
29#[serde(transparent)]
30#[from(Arc<str>, String, &'static str)]
31#[non_exhaustive]
32pub struct ElicitationId(pub Arc<str>);
33
34impl ElicitationId {
35 #[must_use]
37 pub fn new(id: impl Into<Arc<str>>) -> Self {
38 Self(id.into())
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
44#[serde(rename_all = "kebab-case")]
45#[non_exhaustive]
46pub enum StringFormat {
47 Email,
49 Uri,
51 Date,
53 DateTime,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
59#[serde(rename_all = "snake_case")]
60#[non_exhaustive]
61pub enum ElicitationSchemaType {
62 #[default]
64 Object,
65}
66
67#[serde_as]
69#[skip_serializing_none]
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
71#[non_exhaustive]
72pub struct EnumOption {
73 #[serde(rename = "const")]
75 pub value: String,
76 pub title: String,
78 #[serde_as(deserialize_as = "DefaultOnError")]
84 #[schemars(extend("x-deserialize-default-on-error" = true))]
85 #[serde(default)]
86 #[serde(rename = "_meta")]
87 pub meta: Option<Meta>,
88}
89
90impl EnumOption {
91 #[must_use]
93 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
94 Self {
95 value: value.into(),
96 title: title.into(),
97 meta: None,
98 }
99 }
100
101 #[must_use]
107 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
108 self.meta = meta.into_option();
109 self
110 }
111}
112
113#[serde_as]
118#[skip_serializing_none]
119#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
120#[serde(rename_all = "camelCase")]
121#[non_exhaustive]
122pub struct StringPropertySchema {
123 #[serde_as(deserialize_as = "DefaultOnError")]
125 #[schemars(extend("x-deserialize-default-on-error" = true))]
126 #[serde(default)]
127 pub title: Option<String>,
128 #[serde_as(deserialize_as = "DefaultOnError")]
130 #[schemars(extend("x-deserialize-default-on-error" = true))]
131 #[serde(default)]
132 pub description: Option<String>,
133 #[serde_as(deserialize_as = "DefaultOnError")]
135 #[schemars(extend("x-deserialize-default-on-error" = true))]
136 #[serde(default)]
137 pub min_length: Option<u32>,
138 #[serde_as(deserialize_as = "DefaultOnError")]
140 #[schemars(extend("x-deserialize-default-on-error" = true))]
141 #[serde(default)]
142 pub max_length: Option<u32>,
143 #[serde_as(deserialize_as = "DefaultOnError")]
145 #[schemars(extend("x-deserialize-default-on-error" = true))]
146 #[serde(default)]
147 pub pattern: Option<String>,
148 #[serde_as(deserialize_as = "DefaultOnError")]
150 #[schemars(extend("x-deserialize-default-on-error" = true))]
151 #[serde(default)]
152 pub format: Option<StringFormat>,
153 #[serde_as(deserialize_as = "DefaultOnError")]
155 #[schemars(extend("x-deserialize-default-on-error" = true))]
156 #[serde(default)]
157 pub default: Option<String>,
158 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
160 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
161 #[serde(default)]
162 #[serde(rename = "enum")]
163 pub enum_values: Option<Vec<String>>,
164 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
166 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
167 #[serde(default)]
168 #[serde(rename = "oneOf")]
169 pub one_of: Option<Vec<EnumOption>>,
170 #[serde_as(deserialize_as = "DefaultOnError")]
176 #[schemars(extend("x-deserialize-default-on-error" = true))]
177 #[serde(default)]
178 #[serde(rename = "_meta")]
179 pub meta: Option<Meta>,
180}
181
182impl StringPropertySchema {
183 #[must_use]
185 pub fn new() -> Self {
186 Self::default()
187 }
188
189 #[must_use]
191 pub fn email() -> Self {
192 Self {
193 format: Some(StringFormat::Email),
194 ..Default::default()
195 }
196 }
197
198 #[must_use]
200 pub fn uri() -> Self {
201 Self {
202 format: Some(StringFormat::Uri),
203 ..Default::default()
204 }
205 }
206
207 #[must_use]
209 pub fn date() -> Self {
210 Self {
211 format: Some(StringFormat::Date),
212 ..Default::default()
213 }
214 }
215
216 #[must_use]
218 pub fn date_time() -> Self {
219 Self {
220 format: Some(StringFormat::DateTime),
221 ..Default::default()
222 }
223 }
224
225 #[must_use]
227 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
228 self.title = title.into_option();
229 self
230 }
231
232 #[must_use]
234 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
235 self.description = description.into_option();
236 self
237 }
238
239 #[must_use]
241 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
242 self.min_length = min_length.into_option();
243 self
244 }
245
246 #[must_use]
248 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
249 self.max_length = max_length.into_option();
250 self
251 }
252
253 #[must_use]
255 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
256 self.pattern = pattern.into_option();
257 self
258 }
259
260 #[must_use]
262 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
263 self.format = format.into_option();
264 self
265 }
266
267 #[must_use]
269 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
270 self.default = default.into_option();
271 self
272 }
273
274 #[must_use]
276 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
277 self.enum_values = enum_values.into_option();
278 self
279 }
280
281 #[must_use]
283 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
284 self.one_of = one_of.into_option();
285 self
286 }
287
288 #[must_use]
294 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
295 self.meta = meta.into_option();
296 self
297 }
298}
299
300#[serde_as]
302#[skip_serializing_none]
303#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
304#[serde(rename_all = "camelCase")]
305#[non_exhaustive]
306pub struct NumberPropertySchema {
307 #[serde_as(deserialize_as = "DefaultOnError")]
309 #[schemars(extend("x-deserialize-default-on-error" = true))]
310 #[serde(default)]
311 pub title: Option<String>,
312 #[serde_as(deserialize_as = "DefaultOnError")]
314 #[schemars(extend("x-deserialize-default-on-error" = true))]
315 #[serde(default)]
316 pub description: Option<String>,
317 #[serde_as(deserialize_as = "DefaultOnError")]
319 #[schemars(extend("x-deserialize-default-on-error" = true))]
320 #[serde(default)]
321 pub minimum: Option<f64>,
322 #[serde_as(deserialize_as = "DefaultOnError")]
324 #[schemars(extend("x-deserialize-default-on-error" = true))]
325 #[serde(default)]
326 pub maximum: Option<f64>,
327 #[serde_as(deserialize_as = "DefaultOnError")]
329 #[schemars(extend("x-deserialize-default-on-error" = true))]
330 #[serde(default)]
331 pub default: Option<f64>,
332 #[serde_as(deserialize_as = "DefaultOnError")]
338 #[schemars(extend("x-deserialize-default-on-error" = true))]
339 #[serde(default)]
340 #[serde(rename = "_meta")]
341 pub meta: Option<Meta>,
342}
343
344impl NumberPropertySchema {
345 #[must_use]
347 pub fn new() -> Self {
348 Self::default()
349 }
350
351 #[must_use]
353 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
354 self.title = title.into_option();
355 self
356 }
357
358 #[must_use]
360 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
361 self.description = description.into_option();
362 self
363 }
364
365 #[must_use]
367 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
368 self.minimum = minimum.into_option();
369 self
370 }
371
372 #[must_use]
374 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
375 self.maximum = maximum.into_option();
376 self
377 }
378
379 #[must_use]
381 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
382 self.default = default.into_option();
383 self
384 }
385
386 #[must_use]
392 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
393 self.meta = meta.into_option();
394 self
395 }
396}
397
398#[serde_as]
400#[skip_serializing_none]
401#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
402#[serde(rename_all = "camelCase")]
403#[non_exhaustive]
404pub struct IntegerPropertySchema {
405 #[serde_as(deserialize_as = "DefaultOnError")]
407 #[schemars(extend("x-deserialize-default-on-error" = true))]
408 #[serde(default)]
409 pub title: Option<String>,
410 #[serde_as(deserialize_as = "DefaultOnError")]
412 #[schemars(extend("x-deserialize-default-on-error" = true))]
413 #[serde(default)]
414 pub description: Option<String>,
415 #[serde_as(deserialize_as = "DefaultOnError")]
417 #[schemars(extend("x-deserialize-default-on-error" = true))]
418 #[serde(default)]
419 pub minimum: Option<i64>,
420 #[serde_as(deserialize_as = "DefaultOnError")]
422 #[schemars(extend("x-deserialize-default-on-error" = true))]
423 #[serde(default)]
424 pub maximum: Option<i64>,
425 #[serde_as(deserialize_as = "DefaultOnError")]
427 #[schemars(extend("x-deserialize-default-on-error" = true))]
428 #[serde(default)]
429 pub default: Option<i64>,
430 #[serde_as(deserialize_as = "DefaultOnError")]
436 #[schemars(extend("x-deserialize-default-on-error" = true))]
437 #[serde(default)]
438 #[serde(rename = "_meta")]
439 pub meta: Option<Meta>,
440}
441
442impl IntegerPropertySchema {
443 #[must_use]
445 pub fn new() -> Self {
446 Self::default()
447 }
448
449 #[must_use]
451 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
452 self.title = title.into_option();
453 self
454 }
455
456 #[must_use]
458 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
459 self.description = description.into_option();
460 self
461 }
462
463 #[must_use]
465 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
466 self.minimum = minimum.into_option();
467 self
468 }
469
470 #[must_use]
472 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
473 self.maximum = maximum.into_option();
474 self
475 }
476
477 #[must_use]
479 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
480 self.default = default.into_option();
481 self
482 }
483
484 #[must_use]
490 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
491 self.meta = meta.into_option();
492 self
493 }
494}
495
496#[serde_as]
498#[skip_serializing_none]
499#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
500#[serde(rename_all = "camelCase")]
501#[non_exhaustive]
502pub struct BooleanPropertySchema {
503 #[serde_as(deserialize_as = "DefaultOnError")]
505 #[schemars(extend("x-deserialize-default-on-error" = true))]
506 #[serde(default)]
507 pub title: Option<String>,
508 #[serde_as(deserialize_as = "DefaultOnError")]
510 #[schemars(extend("x-deserialize-default-on-error" = true))]
511 #[serde(default)]
512 pub description: Option<String>,
513 #[serde_as(deserialize_as = "DefaultOnError")]
515 #[schemars(extend("x-deserialize-default-on-error" = true))]
516 #[serde(default)]
517 pub default: Option<bool>,
518 #[serde_as(deserialize_as = "DefaultOnError")]
524 #[schemars(extend("x-deserialize-default-on-error" = true))]
525 #[serde(default)]
526 #[serde(rename = "_meta")]
527 pub meta: Option<Meta>,
528}
529
530impl BooleanPropertySchema {
531 #[must_use]
533 pub fn new() -> Self {
534 Self::default()
535 }
536
537 #[must_use]
539 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
540 self.title = title.into_option();
541 self
542 }
543
544 #[must_use]
546 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
547 self.description = description.into_option();
548 self
549 }
550
551 #[must_use]
553 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
554 self.default = default.into_option();
555 self
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, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
572#[serde(rename_all = "snake_case")]
573#[non_exhaustive]
574pub enum ElicitationStringType {
575 #[default]
577 String,
578}
579
580#[serde_as]
582#[skip_serializing_none]
583#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
584#[non_exhaustive]
585pub struct UntitledMultiSelectItems {
586 #[serde(rename = "type")]
588 pub type_: ElicitationStringType,
589 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
591 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
592 #[serde(rename = "enum")]
593 pub values: Vec<String>,
594 #[serde_as(deserialize_as = "DefaultOnError")]
600 #[schemars(extend("x-deserialize-default-on-error" = true))]
601 #[serde(default)]
602 #[serde(rename = "_meta")]
603 pub meta: Option<Meta>,
604}
605
606impl UntitledMultiSelectItems {
607 #[must_use]
609 pub fn new(type_: ElicitationStringType, values: Vec<String>) -> Self {
610 Self {
611 type_,
612 values,
613 meta: None,
614 }
615 }
616
617 #[must_use]
623 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
624 self.meta = meta.into_option();
625 self
626 }
627}
628
629#[serde_as]
631#[skip_serializing_none]
632#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
633#[non_exhaustive]
634pub struct TitledMultiSelectItems {
635 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
637 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
638 #[serde(rename = "anyOf")]
639 pub options: Vec<EnumOption>,
640 #[serde_as(deserialize_as = "DefaultOnError")]
646 #[schemars(extend("x-deserialize-default-on-error" = true))]
647 #[serde(default)]
648 #[serde(rename = "_meta")]
649 pub meta: Option<Meta>,
650}
651
652impl TitledMultiSelectItems {
653 #[must_use]
655 pub fn new(options: Vec<EnumOption>) -> Self {
656 Self {
657 options,
658 meta: None,
659 }
660 }
661
662 #[must_use]
668 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
669 self.meta = meta.into_option();
670 self
671 }
672}
673
674#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
676#[serde(untagged)]
677#[non_exhaustive]
678pub enum MultiSelectItems {
679 Untitled(UntitledMultiSelectItems),
681 Titled(TitledMultiSelectItems),
683}
684
685#[serde_as]
687#[skip_serializing_none]
688#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
689#[serde(rename_all = "camelCase")]
690#[non_exhaustive]
691pub struct MultiSelectPropertySchema {
692 #[serde_as(deserialize_as = "DefaultOnError")]
694 #[schemars(extend("x-deserialize-default-on-error" = true))]
695 #[serde(default)]
696 pub title: Option<String>,
697 #[serde_as(deserialize_as = "DefaultOnError")]
699 #[schemars(extend("x-deserialize-default-on-error" = true))]
700 #[serde(default)]
701 pub description: Option<String>,
702 #[serde_as(deserialize_as = "DefaultOnError")]
704 #[schemars(extend("x-deserialize-default-on-error" = true))]
705 #[serde(default)]
706 pub min_items: Option<u64>,
707 #[serde_as(deserialize_as = "DefaultOnError")]
709 #[schemars(extend("x-deserialize-default-on-error" = true))]
710 #[serde(default)]
711 pub max_items: Option<u64>,
712 pub items: MultiSelectItems,
714 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
716 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
717 #[serde(default)]
718 pub default: Option<Vec<String>>,
719 #[serde_as(deserialize_as = "DefaultOnError")]
725 #[schemars(extend("x-deserialize-default-on-error" = true))]
726 #[serde(default)]
727 #[serde(rename = "_meta")]
728 pub meta: Option<Meta>,
729}
730
731impl MultiSelectPropertySchema {
732 #[must_use]
734 pub fn new(values: Vec<String>) -> Self {
735 Self {
736 title: None,
737 description: None,
738 min_items: None,
739 max_items: None,
740 items: MultiSelectItems::Untitled(UntitledMultiSelectItems::new(
741 ElicitationStringType::String,
742 values,
743 )),
744 default: None,
745 meta: None,
746 }
747 }
748
749 #[must_use]
751 pub fn titled(options: Vec<EnumOption>) -> Self {
752 Self {
753 title: None,
754 description: None,
755 min_items: None,
756 max_items: None,
757 items: MultiSelectItems::Titled(TitledMultiSelectItems::new(options)),
758 default: None,
759 meta: None,
760 }
761 }
762
763 #[must_use]
765 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
766 self.title = title.into_option();
767 self
768 }
769
770 #[must_use]
772 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
773 self.description = description.into_option();
774 self
775 }
776
777 #[must_use]
779 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
780 self.min_items = min_items.into_option();
781 self
782 }
783
784 #[must_use]
786 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
787 self.max_items = max_items.into_option();
788 self
789 }
790
791 #[must_use]
793 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
794 self.default = default.into_option();
795 self
796 }
797
798 #[must_use]
804 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
805 self.meta = meta.into_option();
806 self
807 }
808}
809
810#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
816#[serde(tag = "type", rename_all = "snake_case")]
817#[schemars(extend("discriminator" = {"propertyName": "type"}))]
818#[non_exhaustive]
819pub enum ElicitationPropertySchema {
820 String(StringPropertySchema),
822 Number(NumberPropertySchema),
824 Integer(IntegerPropertySchema),
826 Boolean(BooleanPropertySchema),
828 Array(MultiSelectPropertySchema),
830}
831
832impl From<StringPropertySchema> for ElicitationPropertySchema {
833 fn from(schema: StringPropertySchema) -> Self {
834 Self::String(schema)
835 }
836}
837
838impl From<NumberPropertySchema> for ElicitationPropertySchema {
839 fn from(schema: NumberPropertySchema) -> Self {
840 Self::Number(schema)
841 }
842}
843
844impl From<IntegerPropertySchema> for ElicitationPropertySchema {
845 fn from(schema: IntegerPropertySchema) -> Self {
846 Self::Integer(schema)
847 }
848}
849
850impl From<BooleanPropertySchema> for ElicitationPropertySchema {
851 fn from(schema: BooleanPropertySchema) -> Self {
852 Self::Boolean(schema)
853 }
854}
855
856impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
857 fn from(schema: MultiSelectPropertySchema) -> Self {
858 Self::Array(schema)
859 }
860}
861
862fn default_object_type() -> ElicitationSchemaType {
863 ElicitationSchemaType::Object
864}
865
866#[serde_as]
871#[skip_serializing_none]
872#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
873#[serde(rename_all = "camelCase")]
874#[non_exhaustive]
875pub struct ElicitationSchema {
876 #[serde_as(deserialize_as = "DefaultOnError")]
878 #[schemars(extend("x-deserialize-default-on-error" = true))]
879 #[serde(rename = "type", default = "default_object_type")]
880 pub type_: ElicitationSchemaType,
881 #[serde_as(deserialize_as = "DefaultOnError")]
883 #[schemars(extend("x-deserialize-default-on-error" = true))]
884 #[serde(default)]
885 pub title: Option<String>,
886 #[serde_as(deserialize_as = "DefaultOnError")]
888 #[schemars(extend("x-deserialize-default-on-error" = true))]
889 #[serde(default)]
890 pub properties: BTreeMap<String, ElicitationPropertySchema>,
891 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
893 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
894 #[serde(default)]
895 pub required: Option<Vec<String>>,
896 #[serde_as(deserialize_as = "DefaultOnError")]
898 #[schemars(extend("x-deserialize-default-on-error" = true))]
899 #[serde(default)]
900 pub description: Option<String>,
901 #[serde_as(deserialize_as = "DefaultOnError")]
907 #[schemars(extend("x-deserialize-default-on-error" = true))]
908 #[serde(default)]
909 #[serde(rename = "_meta")]
910 pub meta: Option<Meta>,
911}
912
913impl Default for ElicitationSchema {
914 fn default() -> Self {
915 Self {
916 type_: default_object_type(),
917 title: None,
918 properties: BTreeMap::new(),
919 required: None,
920 description: None,
921 meta: None,
922 }
923 }
924}
925
926impl ElicitationSchema {
927 #[must_use]
929 pub fn new() -> Self {
930 Self::default()
931 }
932
933 #[must_use]
935 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
936 self.title = title.into_option();
937 self
938 }
939
940 #[must_use]
942 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
943 self.description = description.into_option();
944 self
945 }
946
947 #[must_use]
953 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
954 self.meta = meta.into_option();
955 self
956 }
957
958 #[must_use]
960 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
961 where
962 S: Into<ElicitationPropertySchema>,
963 {
964 let name = name.into();
965 self.properties.insert(name.clone(), schema.into());
966
967 if required {
968 let required_fields = self.required.get_or_insert_with(Vec::new);
969 if !required_fields.contains(&name) {
970 required_fields.push(name);
971 }
972 } else if let Some(required_fields) = &mut self.required {
973 required_fields.retain(|field| field != &name);
974
975 if required_fields.is_empty() {
976 self.required = None;
977 }
978 }
979
980 self
981 }
982
983 #[must_use]
985 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
986 self.property(name, StringPropertySchema::new(), required)
987 }
988
989 #[must_use]
991 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
992 self.property(name, StringPropertySchema::email(), required)
993 }
994
995 #[must_use]
997 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
998 self.property(name, StringPropertySchema::uri(), required)
999 }
1000
1001 #[must_use]
1003 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1004 self.property(name, StringPropertySchema::date(), required)
1005 }
1006
1007 #[must_use]
1009 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1010 self.property(name, StringPropertySchema::date_time(), required)
1011 }
1012
1013 #[must_use]
1015 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1016 self.property(
1017 name,
1018 NumberPropertySchema::new().minimum(min).maximum(max),
1019 required,
1020 )
1021 }
1022
1023 #[must_use]
1025 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1026 self.property(
1027 name,
1028 IntegerPropertySchema::new().minimum(min).maximum(max),
1029 required,
1030 )
1031 }
1032
1033 #[must_use]
1035 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1036 self.property(name, BooleanPropertySchema::new(), required)
1037 }
1038}
1039
1040#[serde_as]
1046#[skip_serializing_none]
1047#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1048#[serde(rename_all = "camelCase")]
1049#[non_exhaustive]
1050pub struct ElicitationCapabilities {
1051 #[serde_as(deserialize_as = "DefaultOnError")]
1053 #[schemars(extend("x-deserialize-default-on-error" = true))]
1054 #[serde(default)]
1055 pub form: Option<ElicitationFormCapabilities>,
1056 #[serde_as(deserialize_as = "DefaultOnError")]
1058 #[schemars(extend("x-deserialize-default-on-error" = true))]
1059 #[serde(default)]
1060 pub url: Option<ElicitationUrlCapabilities>,
1061 #[serde_as(deserialize_as = "DefaultOnError")]
1067 #[schemars(extend("x-deserialize-default-on-error" = true))]
1068 #[serde(default)]
1069 #[serde(rename = "_meta")]
1070 pub meta: Option<Meta>,
1071}
1072
1073impl ElicitationCapabilities {
1074 #[must_use]
1076 pub fn new() -> Self {
1077 Self::default()
1078 }
1079
1080 #[must_use]
1082 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1083 self.form = form.into_option();
1084 self
1085 }
1086
1087 #[must_use]
1089 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1090 self.url = url.into_option();
1091 self
1092 }
1093
1094 #[must_use]
1100 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1101 self.meta = meta.into_option();
1102 self
1103 }
1104}
1105
1106#[serde_as]
1112#[skip_serializing_none]
1113#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1114#[serde(rename_all = "camelCase")]
1115#[non_exhaustive]
1116pub struct ElicitationFormCapabilities {
1117 #[serde_as(deserialize_as = "DefaultOnError")]
1123 #[schemars(extend("x-deserialize-default-on-error" = true))]
1124 #[serde(default)]
1125 #[serde(rename = "_meta")]
1126 pub meta: Option<Meta>,
1127}
1128
1129impl ElicitationFormCapabilities {
1130 #[must_use]
1132 pub fn new() -> Self {
1133 Self::default()
1134 }
1135
1136 #[must_use]
1142 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1143 self.meta = meta.into_option();
1144 self
1145 }
1146}
1147
1148#[serde_as]
1154#[skip_serializing_none]
1155#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1156#[serde(rename_all = "camelCase")]
1157#[non_exhaustive]
1158pub struct ElicitationUrlCapabilities {
1159 #[serde_as(deserialize_as = "DefaultOnError")]
1165 #[schemars(extend("x-deserialize-default-on-error" = true))]
1166 #[serde(default)]
1167 #[serde(rename = "_meta")]
1168 pub meta: Option<Meta>,
1169}
1170
1171impl ElicitationUrlCapabilities {
1172 #[must_use]
1174 pub fn new() -> Self {
1175 Self::default()
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, Eq)]
1196#[serde(untagged)]
1197#[non_exhaustive]
1198pub enum ElicitationScope {
1199 Session(ElicitationSessionScope),
1201 Request(ElicitationRequestScope),
1204}
1205
1206#[serde_as]
1216#[skip_serializing_none]
1217#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1218#[serde(rename_all = "camelCase")]
1219#[non_exhaustive]
1220pub struct ElicitationSessionScope {
1221 pub session_id: SessionId,
1223 #[serde_as(deserialize_as = "DefaultOnError")]
1225 #[schemars(extend("x-deserialize-default-on-error" = true))]
1226 #[serde(default)]
1227 pub tool_call_id: Option<ToolCallId>,
1228}
1229
1230impl ElicitationSessionScope {
1231 #[must_use]
1233 pub fn new(session_id: impl Into<SessionId>) -> Self {
1234 Self {
1235 session_id: session_id.into(),
1236 tool_call_id: None,
1237 }
1238 }
1239
1240 #[must_use]
1242 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1243 self.tool_call_id = tool_call_id.into_option();
1244 self
1245 }
1246}
1247
1248#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1255#[serde(rename_all = "camelCase")]
1256#[non_exhaustive]
1257pub struct ElicitationRequestScope {
1258 pub request_id: RequestId,
1260}
1261
1262impl ElicitationRequestScope {
1263 #[must_use]
1265 pub fn new(request_id: impl Into<RequestId>) -> Self {
1266 Self {
1267 request_id: request_id.into(),
1268 }
1269 }
1270}
1271
1272impl From<ElicitationSessionScope> for ElicitationScope {
1273 fn from(scope: ElicitationSessionScope) -> Self {
1274 Self::Session(scope)
1275 }
1276}
1277
1278impl From<ElicitationRequestScope> for ElicitationScope {
1279 fn from(scope: ElicitationRequestScope) -> Self {
1280 Self::Request(scope)
1281 }
1282}
1283
1284#[serde_as]
1294#[skip_serializing_none]
1295#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1296#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1297#[serde(rename_all = "camelCase")]
1298#[non_exhaustive]
1299pub struct CreateElicitationRequest {
1300 #[serde(flatten)]
1302 pub mode: ElicitationMode,
1303 pub message: String,
1305 #[serde_as(deserialize_as = "DefaultOnError")]
1311 #[schemars(extend("x-deserialize-default-on-error" = true))]
1312 #[serde(default)]
1313 #[serde(rename = "_meta")]
1314 pub meta: Option<Meta>,
1315}
1316
1317impl CreateElicitationRequest {
1318 #[must_use]
1320 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1321 Self {
1322 mode: mode.into(),
1323 message: message.into(),
1324 meta: None,
1325 }
1326 }
1327
1328 #[must_use]
1330 pub fn scope(&self) -> &ElicitationScope {
1331 self.mode.scope()
1332 }
1333
1334 #[must_use]
1340 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1341 self.meta = meta.into_option();
1342 self
1343 }
1344}
1345
1346#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1352#[serde(tag = "mode", rename_all = "snake_case")]
1353#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1354#[non_exhaustive]
1355pub enum ElicitationMode {
1356 Form(ElicitationFormMode),
1358 Url(ElicitationUrlMode),
1360}
1361
1362impl From<ElicitationFormMode> for ElicitationMode {
1363 fn from(mode: ElicitationFormMode) -> Self {
1364 Self::Form(mode)
1365 }
1366}
1367
1368impl From<ElicitationUrlMode> for ElicitationMode {
1369 fn from(mode: ElicitationUrlMode) -> Self {
1370 Self::Url(mode)
1371 }
1372}
1373
1374impl ElicitationMode {
1375 #[must_use]
1377 pub fn scope(&self) -> &ElicitationScope {
1378 match self {
1379 Self::Form(f) => &f.scope,
1380 Self::Url(u) => &u.scope,
1381 }
1382 }
1383}
1384
1385#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1391#[serde(rename_all = "camelCase")]
1392#[non_exhaustive]
1393pub struct ElicitationFormMode {
1394 #[serde(flatten)]
1396 pub scope: ElicitationScope,
1397 pub requested_schema: ElicitationSchema,
1399}
1400
1401impl ElicitationFormMode {
1402 #[must_use]
1404 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1405 Self {
1406 scope: scope.into(),
1407 requested_schema,
1408 }
1409 }
1410}
1411
1412#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1418#[serde(rename_all = "camelCase")]
1419#[non_exhaustive]
1420pub struct ElicitationUrlMode {
1421 #[serde(flatten)]
1423 pub scope: ElicitationScope,
1424 pub elicitation_id: ElicitationId,
1426 #[schemars(extend("format" = "uri"))]
1428 pub url: String,
1429}
1430
1431impl ElicitationUrlMode {
1432 #[must_use]
1434 pub fn new(
1435 scope: impl Into<ElicitationScope>,
1436 elicitation_id: impl Into<ElicitationId>,
1437 url: impl Into<String>,
1438 ) -> Self {
1439 Self {
1440 scope: scope.into(),
1441 elicitation_id: elicitation_id.into(),
1442 url: url.into(),
1443 }
1444 }
1445}
1446
1447#[serde_as]
1453#[skip_serializing_none]
1454#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1455#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1456#[serde(rename_all = "camelCase")]
1457#[non_exhaustive]
1458pub struct CreateElicitationResponse {
1459 #[serde(flatten)]
1461 pub action: ElicitationAction,
1462 #[serde_as(deserialize_as = "DefaultOnError")]
1468 #[schemars(extend("x-deserialize-default-on-error" = true))]
1469 #[serde(default)]
1470 #[serde(rename = "_meta")]
1471 pub meta: Option<Meta>,
1472}
1473
1474impl CreateElicitationResponse {
1475 #[must_use]
1477 pub fn new(action: ElicitationAction) -> Self {
1478 Self { action, meta: None }
1479 }
1480
1481 #[must_use]
1487 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1488 self.meta = meta.into_option();
1489 self
1490 }
1491}
1492
1493#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1499#[serde(tag = "action", rename_all = "snake_case")]
1500#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1501#[non_exhaustive]
1502pub enum ElicitationAction {
1503 Accept(ElicitationAcceptAction),
1505 Decline,
1507 Cancel,
1509}
1510
1511#[serde_as]
1517#[skip_serializing_none]
1518#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1519#[serde(rename_all = "camelCase")]
1520#[non_exhaustive]
1521pub struct ElicitationAcceptAction {
1522 #[serde_as(deserialize_as = "DefaultOnError")]
1524 #[schemars(extend("x-deserialize-default-on-error" = true))]
1525 #[serde(default)]
1526 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1527}
1528
1529impl ElicitationAcceptAction {
1530 #[must_use]
1532 pub fn new() -> Self {
1533 Self { content: None }
1534 }
1535
1536 #[must_use]
1538 pub fn content(
1539 mut self,
1540 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1541 ) -> Self {
1542 self.content = content.into_option();
1543 self
1544 }
1545}
1546
1547#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1549#[serde(untagged)]
1550#[non_exhaustive]
1551pub enum ElicitationContentValue {
1552 String(String),
1554 Integer(i64),
1556 Number(f64),
1558 Boolean(bool),
1560 StringArray(Vec<String>),
1562}
1563
1564impl From<String> for ElicitationContentValue {
1565 fn from(value: String) -> Self {
1566 Self::String(value)
1567 }
1568}
1569
1570impl From<&str> for ElicitationContentValue {
1571 fn from(value: &str) -> Self {
1572 Self::String(value.to_string())
1573 }
1574}
1575
1576impl From<i64> for ElicitationContentValue {
1577 fn from(value: i64) -> Self {
1578 Self::Integer(value)
1579 }
1580}
1581
1582impl From<i32> for ElicitationContentValue {
1583 fn from(value: i32) -> Self {
1584 Self::Integer(i64::from(value))
1585 }
1586}
1587
1588impl From<f64> for ElicitationContentValue {
1589 fn from(value: f64) -> Self {
1590 Self::Number(value)
1591 }
1592}
1593
1594impl From<bool> for ElicitationContentValue {
1595 fn from(value: bool) -> Self {
1596 Self::Boolean(value)
1597 }
1598}
1599
1600impl From<Vec<String>> for ElicitationContentValue {
1601 fn from(value: Vec<String>) -> Self {
1602 Self::StringArray(value)
1603 }
1604}
1605
1606impl From<Vec<&str>> for ElicitationContentValue {
1607 fn from(value: Vec<&str>) -> Self {
1608 Self::StringArray(value.into_iter().map(str::to_string).collect())
1609 }
1610}
1611
1612impl Default for ElicitationAcceptAction {
1613 fn default() -> Self {
1614 Self::new()
1615 }
1616}
1617
1618#[serde_as]
1624#[skip_serializing_none]
1625#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1626#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1627#[serde(rename_all = "camelCase")]
1628#[non_exhaustive]
1629pub struct CompleteElicitationNotification {
1630 pub elicitation_id: ElicitationId,
1632 #[serde_as(deserialize_as = "DefaultOnError")]
1638 #[schemars(extend("x-deserialize-default-on-error" = true))]
1639 #[serde(default)]
1640 #[serde(rename = "_meta")]
1641 pub meta: Option<Meta>,
1642}
1643
1644impl CompleteElicitationNotification {
1645 #[must_use]
1647 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
1648 Self {
1649 elicitation_id: elicitation_id.into(),
1650 meta: None,
1651 }
1652 }
1653
1654 #[must_use]
1660 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1661 self.meta = meta.into_option();
1662 self
1663 }
1664}
1665
1666#[serde_as]
1673#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1674#[serde(rename_all = "camelCase")]
1675#[non_exhaustive]
1676pub struct UrlElicitationRequiredData {
1677 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1679 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1680 pub elicitations: Vec<UrlElicitationRequiredItem>,
1681}
1682
1683impl UrlElicitationRequiredData {
1684 #[must_use]
1686 pub fn new(elicitations: Vec<UrlElicitationRequiredItem>) -> Self {
1687 Self { elicitations }
1688 }
1689}
1690
1691#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1697#[serde(rename_all = "camelCase")]
1698#[non_exhaustive]
1699pub struct UrlElicitationRequiredItem {
1700 pub mode: ElicitationUrlOnlyMode,
1702 pub elicitation_id: ElicitationId,
1704 #[schemars(extend("format" = "uri"))]
1706 pub url: String,
1707 pub message: String,
1709}
1710
1711#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
1713#[serde(rename_all = "snake_case")]
1714#[non_exhaustive]
1715pub enum ElicitationUrlOnlyMode {
1716 #[default]
1718 Url,
1719}
1720
1721impl UrlElicitationRequiredItem {
1722 #[must_use]
1724 pub fn new(
1725 elicitation_id: impl Into<ElicitationId>,
1726 url: impl Into<String>,
1727 message: impl Into<String>,
1728 ) -> Self {
1729 Self {
1730 mode: ElicitationUrlOnlyMode::Url,
1731 elicitation_id: elicitation_id.into(),
1732 url: url.into(),
1733 message: message.into(),
1734 }
1735 }
1736}
1737
1738#[cfg(test)]
1739mod tests {
1740 use super::*;
1741 use serde_json::json;
1742
1743 #[test]
1744 fn form_mode_request_serialization() {
1745 let schema = ElicitationSchema::new().string("name", true);
1746 let req = CreateElicitationRequest::new(
1747 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
1748 "Please enter your name",
1749 );
1750
1751 let json = serde_json::to_value(&req).unwrap();
1752 assert_eq!(json["sessionId"], "sess_1");
1753 assert!(json.get("toolCallId").is_none());
1754 assert_eq!(json["mode"], "form");
1755 assert_eq!(json["message"], "Please enter your name");
1756 assert!(json["requestedSchema"].is_object());
1757 assert_eq!(json["requestedSchema"]["type"], "object");
1758 assert_eq!(
1759 json["requestedSchema"]["properties"]["name"]["type"],
1760 "string"
1761 );
1762
1763 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1764 assert_eq!(
1765 *roundtripped.scope(),
1766 ElicitationSessionScope::new("sess_1").into()
1767 );
1768 assert_eq!(roundtripped.message, "Please enter your name");
1769 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
1770 }
1771
1772 #[test]
1773 fn url_mode_request_serialization() {
1774 let req = CreateElicitationRequest::new(
1775 ElicitationUrlMode::new(
1776 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
1777 "elic_1",
1778 "https://example.com/auth",
1779 ),
1780 "Please authenticate",
1781 );
1782
1783 let json = serde_json::to_value(&req).unwrap();
1784 assert_eq!(json["sessionId"], "sess_2");
1785 assert_eq!(json["toolCallId"], "tc_1");
1786 assert_eq!(json["mode"], "url");
1787 assert_eq!(json["elicitationId"], "elic_1");
1788 assert_eq!(json["url"], "https://example.com/auth");
1789 assert_eq!(json["message"], "Please authenticate");
1790
1791 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1792 assert_eq!(
1793 *roundtripped.scope(),
1794 ElicitationSessionScope::new("sess_2")
1795 .tool_call_id("tc_1")
1796 .into()
1797 );
1798 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1799 }
1800
1801 #[test]
1802 fn response_accept_serialization() {
1803 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
1804 ElicitationAcceptAction::new().content(BTreeMap::from([(
1805 "name".to_string(),
1806 ElicitationContentValue::from("Alice"),
1807 )])),
1808 ));
1809
1810 let json = serde_json::to_value(&resp).unwrap();
1811 assert_eq!(json["action"], "accept");
1812 assert_eq!(json["content"]["name"], "Alice");
1813
1814 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1815 assert!(matches!(
1816 roundtripped.action,
1817 ElicitationAction::Accept(ElicitationAcceptAction {
1818 content: Some(_),
1819 ..
1820 })
1821 ));
1822 }
1823
1824 #[test]
1825 fn response_decline_serialization() {
1826 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
1827
1828 let json = serde_json::to_value(&resp).unwrap();
1829 assert_eq!(json["action"], "decline");
1830
1831 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1832 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1833 }
1834
1835 #[test]
1836 fn response_cancel_serialization() {
1837 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
1838
1839 let json = serde_json::to_value(&resp).unwrap();
1840 assert_eq!(json["action"], "cancel");
1841
1842 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1843 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1844 }
1845
1846 #[test]
1847 fn url_mode_request_scope_serialization() {
1848 let req = CreateElicitationRequest::new(
1849 ElicitationUrlMode::new(
1850 ElicitationRequestScope::new(RequestId::Number(42)),
1851 "elic_2",
1852 "https://example.com/setup",
1853 ),
1854 "Please complete setup",
1855 );
1856
1857 let json = serde_json::to_value(&req).unwrap();
1858 assert_eq!(json["requestId"], 42);
1859 assert!(json.get("sessionId").is_none());
1860 assert_eq!(json["mode"], "url");
1861 assert_eq!(json["elicitationId"], "elic_2");
1862 assert_eq!(json["url"], "https://example.com/setup");
1863 assert_eq!(json["message"], "Please complete setup");
1864
1865 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1866 assert_eq!(
1867 *roundtripped.scope(),
1868 ElicitationRequestScope::new(RequestId::Number(42)).into()
1869 );
1870 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1871 }
1872
1873 #[test]
1874 fn request_scope_request_serialization() {
1875 let req = CreateElicitationRequest::new(
1876 ElicitationFormMode::new(
1877 ElicitationRequestScope::new(RequestId::Number(99)),
1878 ElicitationSchema::new().string("workspace", true),
1879 ),
1880 "Enter workspace name",
1881 );
1882
1883 let json = serde_json::to_value(&req).unwrap();
1884 assert_eq!(json["requestId"], 99);
1885 assert!(json.get("sessionId").is_none());
1886
1887 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1888 assert_eq!(
1889 *roundtripped.scope(),
1890 ElicitationRequestScope::new(RequestId::Number(99)).into()
1891 );
1892 }
1893
1894 #[test]
1901 fn client_response_serialization_accept() {
1902 use crate::v1::ClientResponse;
1903
1904 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1905 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
1906 "name".to_string(),
1907 ElicitationContentValue::from("Alice"),
1908 )]))),
1909 ));
1910 let json = serde_json::to_value(&resp).unwrap();
1911 assert_eq!(json["action"], "accept");
1912 assert_eq!(json["content"]["name"], "Alice");
1913
1914 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1916 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
1917 }
1918
1919 #[test]
1920 fn client_response_serialization_decline() {
1921 use crate::v1::ClientResponse;
1922
1923 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1924 ElicitationAction::Decline,
1925 ));
1926 let json = serde_json::to_value(&resp).unwrap();
1927 assert_eq!(json["action"], "decline");
1928
1929 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1930 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1931 }
1932
1933 #[test]
1934 fn client_response_serialization_cancel() {
1935 use crate::v1::ClientResponse;
1936
1937 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1938 ElicitationAction::Cancel,
1939 ));
1940 let json = serde_json::to_value(&resp).unwrap();
1941 assert_eq!(json["action"], "cancel");
1942
1943 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1944 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1945 }
1946
1947 #[test]
1950 fn request_tolerates_extra_fields() {
1951 let json = json!({
1952 "sessionId": "sess_1",
1953 "mode": "form",
1954 "message": "Enter your name",
1955 "requestedSchema": {
1956 "type": "object",
1957 "properties": {
1958 "name": { "type": "string", "title": "Name" }
1959 },
1960 "required": ["name"]
1961 },
1962 "unknownStringField": "hello",
1963 "unknownNumberField": 42
1964 });
1965
1966 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1967 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
1968 assert_eq!(req.message, "Enter your name");
1969 assert!(matches!(req.mode, ElicitationMode::Form(_)));
1970 }
1971
1972 #[test]
1973 fn completion_notification_serialization() {
1974 let notif = CompleteElicitationNotification::new("elic_1");
1975
1976 let json = serde_json::to_value(¬if).unwrap();
1977 assert_eq!(json["elicitationId"], "elic_1");
1978
1979 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
1980 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
1981 }
1982
1983 #[test]
1984 fn capabilities_form_only() {
1985 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
1986
1987 let json = serde_json::to_value(&caps).unwrap();
1988 assert!(json["form"].is_object());
1989 assert!(json.get("url").is_none());
1990
1991 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1992 assert!(roundtripped.form.is_some());
1993 assert!(roundtripped.url.is_none());
1994 }
1995
1996 #[test]
1997 fn capabilities_url_only() {
1998 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
1999
2000 let json = serde_json::to_value(&caps).unwrap();
2001 assert!(json.get("form").is_none());
2002 assert!(json["url"].is_object());
2003
2004 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2005 assert!(roundtripped.form.is_none());
2006 assert!(roundtripped.url.is_some());
2007 }
2008
2009 #[test]
2010 fn capabilities_both() {
2011 let caps = ElicitationCapabilities::new()
2012 .form(ElicitationFormCapabilities::new())
2013 .url(ElicitationUrlCapabilities::new());
2014
2015 let json = serde_json::to_value(&caps).unwrap();
2016 assert!(json["form"].is_object());
2017 assert!(json["url"].is_object());
2018
2019 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2020 assert!(roundtripped.form.is_some());
2021 assert!(roundtripped.url.is_some());
2022 }
2023
2024 #[test]
2025 fn url_elicitation_required_data_serialization() {
2026 let data = UrlElicitationRequiredData::new(vec![UrlElicitationRequiredItem::new(
2027 "elic_1",
2028 "https://example.com/auth",
2029 "Please authenticate",
2030 )]);
2031
2032 let json = serde_json::to_value(&data).unwrap();
2033 assert_eq!(json["elicitations"][0]["mode"], "url");
2034 assert_eq!(json["elicitations"][0]["elicitationId"], "elic_1");
2035 assert_eq!(json["elicitations"][0]["url"], "https://example.com/auth");
2036
2037 let roundtripped: UrlElicitationRequiredData = serde_json::from_value(json).unwrap();
2038 assert_eq!(roundtripped.elicitations.len(), 1);
2039 assert_eq!(
2040 roundtripped.elicitations[0].mode,
2041 ElicitationUrlOnlyMode::Url
2042 );
2043 }
2044
2045 #[test]
2046 fn schema_default_sets_object_type() {
2047 let schema = ElicitationSchema::default();
2048
2049 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2050 assert!(schema.properties.is_empty());
2051
2052 let json = serde_json::to_value(&schema).unwrap();
2053 assert_eq!(json["type"], "object");
2054 }
2055
2056 #[test]
2057 fn schema_builder_serialization() {
2058 let schema = ElicitationSchema::new()
2059 .string("name", true)
2060 .email("email", true)
2061 .integer("age", 0, 150, true)
2062 .boolean("newsletter", false)
2063 .description("User registration");
2064
2065 let json = serde_json::to_value(&schema).unwrap();
2066 assert_eq!(json["type"], "object");
2067 assert_eq!(json["description"], "User registration");
2068 assert_eq!(json["properties"]["name"]["type"], "string");
2069 assert_eq!(json["properties"]["email"]["type"], "string");
2070 assert_eq!(json["properties"]["email"]["format"], "email");
2071 assert_eq!(json["properties"]["age"]["type"], "integer");
2072 assert_eq!(json["properties"]["age"]["minimum"], 0);
2073 assert_eq!(json["properties"]["age"]["maximum"], 150);
2074 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2075
2076 let required = json["required"].as_array().unwrap();
2077 assert!(required.contains(&json!("name")));
2078 assert!(required.contains(&json!("email")));
2079 assert!(required.contains(&json!("age")));
2080 assert!(!required.contains(&json!("newsletter")));
2081
2082 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2083 assert_eq!(roundtripped.properties.len(), 4);
2084 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2085 }
2086
2087 #[test]
2088 fn schema_string_enum_serialization() {
2089 let schema = ElicitationSchema::new().property(
2090 "color",
2091 StringPropertySchema::new().enum_values(vec![
2092 "red".into(),
2093 "green".into(),
2094 "blue".into(),
2095 ]),
2096 true,
2097 );
2098
2099 let json = serde_json::to_value(&schema).unwrap();
2100 assert_eq!(json["properties"]["color"]["type"], "string");
2101 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2102 assert_eq!(enum_vals.len(), 3);
2103
2104 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2105 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2106 {
2107 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2108 } else {
2109 panic!("expected String variant");
2110 }
2111 }
2112
2113 #[test]
2114 fn schema_multi_select_serialization() {
2115 let schema = ElicitationSchema::new().property(
2116 "colors",
2117 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2118 .min_items(1)
2119 .max_items(3),
2120 false,
2121 );
2122
2123 let json = serde_json::to_value(&schema).unwrap();
2124 assert_eq!(json["properties"]["colors"]["type"], "array");
2125 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2126 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2127 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2128
2129 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2130 assert!(matches!(
2131 roundtripped.properties.get("colors").unwrap(),
2132 ElicitationPropertySchema::Array(_)
2133 ));
2134 }
2135
2136 #[test]
2137 fn schema_titled_enum_serialization() {
2138 let schema = ElicitationSchema::new().property(
2139 "country",
2140 StringPropertySchema::new().one_of(vec![
2141 EnumOption::new("us", "United States"),
2142 EnumOption::new("uk", "United Kingdom"),
2143 ]),
2144 true,
2145 );
2146
2147 let json = serde_json::to_value(&schema).unwrap();
2148 assert_eq!(json["properties"]["country"]["type"], "string");
2149 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2150 assert_eq!(one_of.len(), 2);
2151 assert_eq!(one_of[0]["const"], "us");
2152 assert_eq!(one_of[0]["title"], "United States");
2153
2154 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2155 if let ElicitationPropertySchema::String(s) =
2156 roundtripped.properties.get("country").unwrap()
2157 {
2158 assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
2159 } else {
2160 panic!("expected String variant");
2161 }
2162 }
2163
2164 #[test]
2165 fn schema_number_property_serialization() {
2166 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2167
2168 let json = serde_json::to_value(&schema).unwrap();
2169 assert_eq!(json["properties"]["rating"]["type"], "number");
2170 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2171 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2172
2173 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2174 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2175 {
2176 assert_eq!(n.minimum, Some(0.0));
2177 assert_eq!(n.maximum, Some(5.0));
2178 } else {
2179 panic!("expected Number variant");
2180 }
2181 }
2182
2183 #[test]
2184 fn schema_string_format_serialization() {
2185 let schema = ElicitationSchema::new()
2186 .uri("website", true)
2187 .date("birthday", true)
2188 .date_time("updated_at", false);
2189
2190 let json = serde_json::to_value(&schema).unwrap();
2191 assert_eq!(json["properties"]["website"]["type"], "string");
2192 assert_eq!(json["properties"]["website"]["format"], "uri");
2193 assert_eq!(json["properties"]["birthday"]["type"], "string");
2194 assert_eq!(json["properties"]["birthday"]["format"], "date");
2195 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2196 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2197
2198 let required = json["required"].as_array().unwrap();
2199 assert!(required.contains(&json!("website")));
2200 assert!(required.contains(&json!("birthday")));
2201 assert!(!required.contains(&json!("updated_at")));
2202 }
2203
2204 #[test]
2205 fn schema_string_pattern_serialization() {
2206 let schema = ElicitationSchema::new().property(
2207 "name",
2208 StringPropertySchema::new()
2209 .min_length(1)
2210 .max_length(64)
2211 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2212 true,
2213 );
2214
2215 let json = serde_json::to_value(&schema).unwrap();
2216 assert_eq!(json["properties"]["name"]["type"], "string");
2217 assert_eq!(
2218 json["properties"]["name"]["pattern"],
2219 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2220 );
2221
2222 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2223 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2224 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2225 } else {
2226 panic!("expected String variant");
2227 }
2228 }
2229
2230 #[test]
2231 fn schema_property_updates_required_state() {
2232 let schema = ElicitationSchema::new()
2233 .string("name", true)
2234 .email("name", false);
2235
2236 let json = serde_json::to_value(&schema).unwrap();
2237 assert!(json.get("required").is_none());
2238 assert_eq!(json["properties"]["name"]["format"], "email");
2239 }
2240
2241 #[test]
2242 fn schema_defaults_invalid_object_type() {
2243 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2244 "type": "array",
2245 "properties": {
2246 "name": {
2247 "type": "string"
2248 }
2249 }
2250 }))
2251 .unwrap();
2252
2253 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2254 assert!(schema.properties.contains_key("name"));
2255 }
2256
2257 #[test]
2258 fn titled_multi_select_items_reject_one_of() {
2259 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2260 "oneOf": [
2261 {
2262 "const": "red",
2263 "title": "Red"
2264 }
2265 ]
2266 }))
2267 .unwrap_err();
2268
2269 assert!(err.to_string().contains("missing field `anyOf`"));
2270 }
2271
2272 #[test]
2273 fn response_accept_defaults_non_object_content() {
2274 let response = serde_json::from_value::<CreateElicitationResponse>(json!({
2275 "action": "accept",
2276 "content": "Alice"
2277 }))
2278 .unwrap();
2279
2280 assert_eq!(
2281 response.action,
2282 ElicitationAction::Accept(ElicitationAcceptAction::new())
2283 );
2284 }
2285
2286 #[test]
2287 fn response_accept_defaults_nested_object_content() {
2288 let response = serde_json::from_value::<CreateElicitationResponse>(json!({
2289 "action": "accept",
2290 "content": {
2291 "profile": {
2292 "name": "Alice"
2293 }
2294 }
2295 }))
2296 .unwrap();
2297
2298 assert_eq!(
2299 response.action,
2300 ElicitationAction::Accept(ElicitationAcceptAction::new())
2301 );
2302 }
2303
2304 #[test]
2305 fn response_accept_allows_primitive_and_string_array_content() {
2306 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2307 ElicitationAcceptAction::new().content(BTreeMap::from([
2308 ("name".to_string(), ElicitationContentValue::from("Alice")),
2309 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2310 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2311 (
2312 "subscribed".to_string(),
2313 ElicitationContentValue::from(true),
2314 ),
2315 (
2316 "tags".to_string(),
2317 ElicitationContentValue::from(vec!["rust", "acp"]),
2318 ),
2319 ])),
2320 ));
2321
2322 let json = serde_json::to_value(&response).unwrap();
2323 assert_eq!(json["action"], "accept");
2324 assert_eq!(json["content"]["name"], "Alice");
2325 assert_eq!(json["content"]["age"], 30);
2326 assert_eq!(json["content"]["score"], 9.5);
2327 assert_eq!(json["content"]["subscribed"], true);
2328 assert_eq!(json["content"]["tags"][0], "rust");
2329 assert_eq!(json["content"]["tags"][1], "acp");
2330 }
2331}