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 super::{
16 ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, Meta, RequestId, SessionId,
17 ToolCallId,
18};
19use crate::IntoOption;
20use crate::SkipListener;
21
22#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
28#[serde(transparent)]
29#[from(Arc<str>, String, &'static str)]
30#[non_exhaustive]
31pub struct ElicitationId(pub Arc<str>);
32
33impl ElicitationId {
34 #[must_use]
36 pub fn new(id: impl Into<Arc<str>>) -> Self {
37 Self(id.into())
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
43#[serde(rename_all = "kebab-case")]
44#[non_exhaustive]
45pub enum StringFormat {
46 Email,
48 Uri,
50 Date,
52 DateTime,
54 #[serde(untagged)]
59 Other(String),
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
64#[serde(rename_all = "snake_case")]
65#[non_exhaustive]
66pub enum ElicitationSchemaType {
67 #[default]
69 Object,
70}
71
72#[serde_as]
74#[skip_serializing_none]
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
76#[non_exhaustive]
77pub struct EnumOption {
78 #[serde(rename = "const")]
80 pub value: String,
81 pub title: String,
83 #[serde_as(deserialize_as = "DefaultOnError")]
89 #[schemars(extend("x-deserialize-default-on-error" = true))]
90 #[serde(default)]
91 #[serde(rename = "_meta")]
92 pub meta: Option<Meta>,
93}
94
95impl EnumOption {
96 #[must_use]
98 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
99 Self {
100 value: value.into(),
101 title: title.into(),
102 meta: None,
103 }
104 }
105
106 #[must_use]
112 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
113 self.meta = meta.into_option();
114 self
115 }
116}
117
118#[serde_as]
123#[skip_serializing_none]
124#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
125#[serde(rename_all = "camelCase")]
126#[non_exhaustive]
127pub struct StringPropertySchema {
128 #[serde_as(deserialize_as = "DefaultOnError")]
130 #[schemars(extend("x-deserialize-default-on-error" = true))]
131 #[serde(default)]
132 pub title: Option<String>,
133 #[serde_as(deserialize_as = "DefaultOnError")]
135 #[schemars(extend("x-deserialize-default-on-error" = true))]
136 #[serde(default)]
137 pub description: Option<String>,
138 #[serde_as(deserialize_as = "DefaultOnError")]
140 #[schemars(extend("x-deserialize-default-on-error" = true))]
141 #[serde(default)]
142 pub min_length: Option<u32>,
143 #[serde_as(deserialize_as = "DefaultOnError")]
145 #[schemars(extend("x-deserialize-default-on-error" = true))]
146 #[serde(default)]
147 pub max_length: Option<u32>,
148 #[serde_as(deserialize_as = "DefaultOnError")]
150 #[schemars(extend("x-deserialize-default-on-error" = true))]
151 #[serde(default)]
152 pub pattern: Option<String>,
153 #[serde_as(deserialize_as = "DefaultOnError")]
155 #[schemars(extend("x-deserialize-default-on-error" = true))]
156 #[serde(default)]
157 pub format: Option<StringFormat>,
158 #[serde_as(deserialize_as = "DefaultOnError")]
160 #[schemars(extend("x-deserialize-default-on-error" = true))]
161 #[serde(default)]
162 pub default: Option<String>,
163 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
165 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
166 #[serde(default)]
167 #[serde(rename = "enum")]
168 pub enum_values: Option<Vec<String>>,
169 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
171 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
172 #[serde(default)]
173 #[serde(rename = "oneOf")]
174 pub one_of: Option<Vec<EnumOption>>,
175 #[serde_as(deserialize_as = "DefaultOnError")]
181 #[schemars(extend("x-deserialize-default-on-error" = true))]
182 #[serde(default)]
183 #[serde(rename = "_meta")]
184 pub meta: Option<Meta>,
185}
186
187impl StringPropertySchema {
188 #[must_use]
190 pub fn new() -> Self {
191 Self::default()
192 }
193
194 #[must_use]
196 pub fn email() -> Self {
197 Self {
198 format: Some(StringFormat::Email),
199 ..Default::default()
200 }
201 }
202
203 #[must_use]
205 pub fn uri() -> Self {
206 Self {
207 format: Some(StringFormat::Uri),
208 ..Default::default()
209 }
210 }
211
212 #[must_use]
214 pub fn date() -> Self {
215 Self {
216 format: Some(StringFormat::Date),
217 ..Default::default()
218 }
219 }
220
221 #[must_use]
223 pub fn date_time() -> Self {
224 Self {
225 format: Some(StringFormat::DateTime),
226 ..Default::default()
227 }
228 }
229
230 #[must_use]
232 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
233 self.title = title.into_option();
234 self
235 }
236
237 #[must_use]
239 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
240 self.description = description.into_option();
241 self
242 }
243
244 #[must_use]
246 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
247 self.min_length = min_length.into_option();
248 self
249 }
250
251 #[must_use]
253 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
254 self.max_length = max_length.into_option();
255 self
256 }
257
258 #[must_use]
260 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
261 self.pattern = pattern.into_option();
262 self
263 }
264
265 #[must_use]
267 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
268 self.format = format.into_option();
269 self
270 }
271
272 #[must_use]
274 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
275 self.default = default.into_option();
276 self
277 }
278
279 #[must_use]
281 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
282 self.enum_values = enum_values.into_option();
283 self
284 }
285
286 #[must_use]
288 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
289 self.one_of = one_of.into_option();
290 self
291 }
292
293 #[must_use]
299 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
300 self.meta = meta.into_option();
301 self
302 }
303}
304
305#[serde_as]
307#[skip_serializing_none]
308#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
309#[serde(rename_all = "camelCase")]
310#[non_exhaustive]
311pub struct NumberPropertySchema {
312 #[serde_as(deserialize_as = "DefaultOnError")]
314 #[schemars(extend("x-deserialize-default-on-error" = true))]
315 #[serde(default)]
316 pub title: Option<String>,
317 #[serde_as(deserialize_as = "DefaultOnError")]
319 #[schemars(extend("x-deserialize-default-on-error" = true))]
320 #[serde(default)]
321 pub description: Option<String>,
322 #[serde_as(deserialize_as = "DefaultOnError")]
324 #[schemars(extend("x-deserialize-default-on-error" = true))]
325 #[serde(default)]
326 pub minimum: Option<f64>,
327 #[serde_as(deserialize_as = "DefaultOnError")]
329 #[schemars(extend("x-deserialize-default-on-error" = true))]
330 #[serde(default)]
331 pub maximum: Option<f64>,
332 #[serde_as(deserialize_as = "DefaultOnError")]
334 #[schemars(extend("x-deserialize-default-on-error" = true))]
335 #[serde(default)]
336 pub default: Option<f64>,
337 #[serde_as(deserialize_as = "DefaultOnError")]
343 #[schemars(extend("x-deserialize-default-on-error" = true))]
344 #[serde(default)]
345 #[serde(rename = "_meta")]
346 pub meta: Option<Meta>,
347}
348
349impl NumberPropertySchema {
350 #[must_use]
352 pub fn new() -> Self {
353 Self::default()
354 }
355
356 #[must_use]
358 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
359 self.title = title.into_option();
360 self
361 }
362
363 #[must_use]
365 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
366 self.description = description.into_option();
367 self
368 }
369
370 #[must_use]
372 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
373 self.minimum = minimum.into_option();
374 self
375 }
376
377 #[must_use]
379 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
380 self.maximum = maximum.into_option();
381 self
382 }
383
384 #[must_use]
386 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
387 self.default = default.into_option();
388 self
389 }
390
391 #[must_use]
397 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
398 self.meta = meta.into_option();
399 self
400 }
401}
402
403#[serde_as]
405#[skip_serializing_none]
406#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
407#[serde(rename_all = "camelCase")]
408#[non_exhaustive]
409pub struct IntegerPropertySchema {
410 #[serde_as(deserialize_as = "DefaultOnError")]
412 #[schemars(extend("x-deserialize-default-on-error" = true))]
413 #[serde(default)]
414 pub title: Option<String>,
415 #[serde_as(deserialize_as = "DefaultOnError")]
417 #[schemars(extend("x-deserialize-default-on-error" = true))]
418 #[serde(default)]
419 pub description: Option<String>,
420 #[serde_as(deserialize_as = "DefaultOnError")]
422 #[schemars(extend("x-deserialize-default-on-error" = true))]
423 #[serde(default)]
424 pub minimum: Option<i64>,
425 #[serde_as(deserialize_as = "DefaultOnError")]
427 #[schemars(extend("x-deserialize-default-on-error" = true))]
428 #[serde(default)]
429 pub maximum: Option<i64>,
430 #[serde_as(deserialize_as = "DefaultOnError")]
432 #[schemars(extend("x-deserialize-default-on-error" = true))]
433 #[serde(default)]
434 pub default: Option<i64>,
435 #[serde_as(deserialize_as = "DefaultOnError")]
441 #[schemars(extend("x-deserialize-default-on-error" = true))]
442 #[serde(default)]
443 #[serde(rename = "_meta")]
444 pub meta: Option<Meta>,
445}
446
447impl IntegerPropertySchema {
448 #[must_use]
450 pub fn new() -> Self {
451 Self::default()
452 }
453
454 #[must_use]
456 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
457 self.title = title.into_option();
458 self
459 }
460
461 #[must_use]
463 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
464 self.description = description.into_option();
465 self
466 }
467
468 #[must_use]
470 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
471 self.minimum = minimum.into_option();
472 self
473 }
474
475 #[must_use]
477 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
478 self.maximum = maximum.into_option();
479 self
480 }
481
482 #[must_use]
484 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
485 self.default = default.into_option();
486 self
487 }
488
489 #[must_use]
495 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
496 self.meta = meta.into_option();
497 self
498 }
499}
500
501#[serde_as]
503#[skip_serializing_none]
504#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
505#[serde(rename_all = "camelCase")]
506#[non_exhaustive]
507pub struct BooleanPropertySchema {
508 #[serde_as(deserialize_as = "DefaultOnError")]
510 #[schemars(extend("x-deserialize-default-on-error" = true))]
511 #[serde(default)]
512 pub title: Option<String>,
513 #[serde_as(deserialize_as = "DefaultOnError")]
515 #[schemars(extend("x-deserialize-default-on-error" = true))]
516 #[serde(default)]
517 pub description: Option<String>,
518 #[serde_as(deserialize_as = "DefaultOnError")]
520 #[schemars(extend("x-deserialize-default-on-error" = true))]
521 #[serde(default)]
522 pub default: Option<bool>,
523 #[serde_as(deserialize_as = "DefaultOnError")]
529 #[schemars(extend("x-deserialize-default-on-error" = true))]
530 #[serde(default)]
531 #[serde(rename = "_meta")]
532 pub meta: Option<Meta>,
533}
534
535impl BooleanPropertySchema {
536 #[must_use]
538 pub fn new() -> Self {
539 Self::default()
540 }
541
542 #[must_use]
544 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
545 self.title = title.into_option();
546 self
547 }
548
549 #[must_use]
551 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
552 self.description = description.into_option();
553 self
554 }
555
556 #[must_use]
558 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
559 self.default = default.into_option();
560 self
561 }
562
563 #[must_use]
569 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
570 self.meta = meta.into_option();
571 self
572 }
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
577#[serde(rename_all = "snake_case")]
578#[non_exhaustive]
579pub enum ElicitationStringType {
580 #[default]
582 String,
583}
584
585#[serde_as]
587#[skip_serializing_none]
588#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
589#[non_exhaustive]
590pub struct UntitledMultiSelectItems {
591 #[serde(rename = "type")]
593 pub type_: ElicitationStringType,
594 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
596 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
597 #[serde(rename = "enum")]
598 pub values: Vec<String>,
599 #[serde_as(deserialize_as = "DefaultOnError")]
605 #[schemars(extend("x-deserialize-default-on-error" = true))]
606 #[serde(default)]
607 #[serde(rename = "_meta")]
608 pub meta: Option<Meta>,
609}
610
611impl UntitledMultiSelectItems {
612 #[must_use]
618 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
619 self.meta = meta.into_option();
620 self
621 }
622}
623
624#[serde_as]
626#[skip_serializing_none]
627#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
628#[non_exhaustive]
629pub struct TitledMultiSelectItems {
630 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
632 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
633 #[serde(rename = "anyOf")]
634 pub options: Vec<EnumOption>,
635 #[serde_as(deserialize_as = "DefaultOnError")]
641 #[schemars(extend("x-deserialize-default-on-error" = true))]
642 #[serde(default)]
643 #[serde(rename = "_meta")]
644 pub meta: Option<Meta>,
645}
646
647impl TitledMultiSelectItems {
648 #[must_use]
650 pub fn new(options: Vec<EnumOption>) -> Self {
651 Self {
652 options,
653 meta: None,
654 }
655 }
656
657 #[must_use]
663 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
664 self.meta = meta.into_option();
665 self
666 }
667}
668
669#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
671#[serde(untagged)]
672#[non_exhaustive]
673pub enum MultiSelectItems {
674 Untitled(UntitledMultiSelectItems),
676 Titled(TitledMultiSelectItems),
678}
679
680#[serde_as]
682#[skip_serializing_none]
683#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
684#[serde(rename_all = "camelCase")]
685#[non_exhaustive]
686pub struct MultiSelectPropertySchema {
687 #[serde_as(deserialize_as = "DefaultOnError")]
689 #[schemars(extend("x-deserialize-default-on-error" = true))]
690 #[serde(default)]
691 pub title: Option<String>,
692 #[serde_as(deserialize_as = "DefaultOnError")]
694 #[schemars(extend("x-deserialize-default-on-error" = true))]
695 #[serde(default)]
696 pub description: Option<String>,
697 #[serde_as(deserialize_as = "DefaultOnError")]
699 #[schemars(extend("x-deserialize-default-on-error" = true))]
700 #[serde(default)]
701 pub min_items: Option<u64>,
702 #[serde_as(deserialize_as = "DefaultOnError")]
704 #[schemars(extend("x-deserialize-default-on-error" = true))]
705 #[serde(default)]
706 pub max_items: Option<u64>,
707 pub items: MultiSelectItems,
709 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
711 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
712 #[serde(default)]
713 pub default: Option<Vec<String>>,
714 #[serde_as(deserialize_as = "DefaultOnError")]
720 #[schemars(extend("x-deserialize-default-on-error" = true))]
721 #[serde(default)]
722 #[serde(rename = "_meta")]
723 pub meta: Option<Meta>,
724}
725
726impl MultiSelectPropertySchema {
727 #[must_use]
729 pub fn new(values: Vec<String>) -> Self {
730 Self {
731 title: None,
732 description: None,
733 min_items: None,
734 max_items: None,
735 items: MultiSelectItems::Untitled(UntitledMultiSelectItems {
736 type_: ElicitationStringType::String,
737 values,
738 meta: None,
739 }),
740 default: None,
741 meta: None,
742 }
743 }
744
745 #[must_use]
747 pub fn titled(options: Vec<EnumOption>) -> Self {
748 Self {
749 title: None,
750 description: None,
751 min_items: None,
752 max_items: None,
753 items: MultiSelectItems::Titled(TitledMultiSelectItems {
754 options,
755 meta: None,
756 }),
757 default: None,
758 meta: None,
759 }
760 }
761
762 #[must_use]
764 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
765 self.title = title.into_option();
766 self
767 }
768
769 #[must_use]
771 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
772 self.description = description.into_option();
773 self
774 }
775
776 #[must_use]
778 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
779 self.min_items = min_items.into_option();
780 self
781 }
782
783 #[must_use]
785 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
786 self.max_items = max_items.into_option();
787 self
788 }
789
790 #[must_use]
792 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
793 self.default = default.into_option();
794 self
795 }
796
797 #[must_use]
803 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
804 self.meta = meta.into_option();
805 self
806 }
807}
808
809#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
815#[serde(tag = "type", rename_all = "snake_case")]
816#[schemars(extend("discriminator" = {"propertyName": "type"}))]
817#[non_exhaustive]
818pub enum ElicitationPropertySchema {
819 String(StringPropertySchema),
821 Number(NumberPropertySchema),
823 Integer(IntegerPropertySchema),
825 Boolean(BooleanPropertySchema),
827 Array(MultiSelectPropertySchema),
829}
830
831impl From<StringPropertySchema> for ElicitationPropertySchema {
832 fn from(schema: StringPropertySchema) -> Self {
833 Self::String(schema)
834 }
835}
836
837impl From<NumberPropertySchema> for ElicitationPropertySchema {
838 fn from(schema: NumberPropertySchema) -> Self {
839 Self::Number(schema)
840 }
841}
842
843impl From<IntegerPropertySchema> for ElicitationPropertySchema {
844 fn from(schema: IntegerPropertySchema) -> Self {
845 Self::Integer(schema)
846 }
847}
848
849impl From<BooleanPropertySchema> for ElicitationPropertySchema {
850 fn from(schema: BooleanPropertySchema) -> Self {
851 Self::Boolean(schema)
852 }
853}
854
855impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
856 fn from(schema: MultiSelectPropertySchema) -> Self {
857 Self::Array(schema)
858 }
859}
860
861fn default_object_type() -> ElicitationSchemaType {
862 ElicitationSchemaType::Object
863}
864
865#[serde_as]
870#[skip_serializing_none]
871#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
872#[serde(rename_all = "camelCase")]
873#[non_exhaustive]
874pub struct ElicitationSchema {
875 #[serde_as(deserialize_as = "DefaultOnError")]
877 #[schemars(extend("x-deserialize-default-on-error" = true))]
878 #[serde(rename = "type", default = "default_object_type")]
879 pub type_: ElicitationSchemaType,
880 #[serde_as(deserialize_as = "DefaultOnError")]
882 #[schemars(extend("x-deserialize-default-on-error" = true))]
883 #[serde(default)]
884 pub title: Option<String>,
885 #[serde_as(deserialize_as = "DefaultOnError")]
887 #[schemars(extend("x-deserialize-default-on-error" = true))]
888 #[serde(default)]
889 pub properties: BTreeMap<String, ElicitationPropertySchema>,
890 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
892 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
893 #[serde(default)]
894 pub required: Option<Vec<String>>,
895 #[serde_as(deserialize_as = "DefaultOnError")]
897 #[schemars(extend("x-deserialize-default-on-error" = true))]
898 #[serde(default)]
899 pub description: Option<String>,
900 #[serde_as(deserialize_as = "DefaultOnError")]
906 #[schemars(extend("x-deserialize-default-on-error" = true))]
907 #[serde(default)]
908 #[serde(rename = "_meta")]
909 pub meta: Option<Meta>,
910}
911
912impl Default for ElicitationSchema {
913 fn default() -> Self {
914 Self {
915 type_: default_object_type(),
916 title: None,
917 properties: BTreeMap::new(),
918 required: None,
919 description: None,
920 meta: None,
921 }
922 }
923}
924
925impl ElicitationSchema {
926 #[must_use]
928 pub fn new() -> Self {
929 Self::default()
930 }
931
932 #[must_use]
934 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
935 self.title = title.into_option();
936 self
937 }
938
939 #[must_use]
941 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
942 self.description = description.into_option();
943 self
944 }
945
946 #[must_use]
952 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
953 self.meta = meta.into_option();
954 self
955 }
956
957 #[must_use]
959 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
960 where
961 S: Into<ElicitationPropertySchema>,
962 {
963 let name = name.into();
964 self.properties.insert(name.clone(), schema.into());
965
966 if required {
967 let required_fields = self.required.get_or_insert_with(Vec::new);
968 if !required_fields.contains(&name) {
969 required_fields.push(name);
970 }
971 } else if let Some(required_fields) = &mut self.required {
972 required_fields.retain(|field| field != &name);
973
974 if required_fields.is_empty() {
975 self.required = None;
976 }
977 }
978
979 self
980 }
981
982 #[must_use]
984 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
985 self.property(name, StringPropertySchema::new(), required)
986 }
987
988 #[must_use]
990 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
991 self.property(name, StringPropertySchema::email(), required)
992 }
993
994 #[must_use]
996 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
997 self.property(name, StringPropertySchema::uri(), required)
998 }
999
1000 #[must_use]
1002 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1003 self.property(name, StringPropertySchema::date(), required)
1004 }
1005
1006 #[must_use]
1008 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1009 self.property(name, StringPropertySchema::date_time(), required)
1010 }
1011
1012 #[must_use]
1014 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1015 self.property(
1016 name,
1017 NumberPropertySchema::new().minimum(min).maximum(max),
1018 required,
1019 )
1020 }
1021
1022 #[must_use]
1024 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1025 self.property(
1026 name,
1027 IntegerPropertySchema::new().minimum(min).maximum(max),
1028 required,
1029 )
1030 }
1031
1032 #[must_use]
1034 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1035 self.property(name, BooleanPropertySchema::new(), required)
1036 }
1037}
1038
1039#[serde_as]
1045#[skip_serializing_none]
1046#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1047#[serde(rename_all = "camelCase")]
1048#[non_exhaustive]
1049pub struct ElicitationCapabilities {
1050 #[serde_as(deserialize_as = "DefaultOnError")]
1052 #[schemars(extend("x-deserialize-default-on-error" = true))]
1053 #[serde(default)]
1054 pub form: Option<ElicitationFormCapabilities>,
1055 #[serde_as(deserialize_as = "DefaultOnError")]
1057 #[schemars(extend("x-deserialize-default-on-error" = true))]
1058 #[serde(default)]
1059 pub url: Option<ElicitationUrlCapabilities>,
1060 #[serde_as(deserialize_as = "DefaultOnError")]
1066 #[schemars(extend("x-deserialize-default-on-error" = true))]
1067 #[serde(default)]
1068 #[serde(rename = "_meta")]
1069 pub meta: Option<Meta>,
1070}
1071
1072impl ElicitationCapabilities {
1073 #[must_use]
1075 pub fn new() -> Self {
1076 Self::default()
1077 }
1078
1079 #[must_use]
1081 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1082 self.form = form.into_option();
1083 self
1084 }
1085
1086 #[must_use]
1088 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1089 self.url = url.into_option();
1090 self
1091 }
1092
1093 #[must_use]
1099 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1100 self.meta = meta.into_option();
1101 self
1102 }
1103}
1104
1105#[serde_as]
1111#[skip_serializing_none]
1112#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1113#[serde(rename_all = "camelCase")]
1114#[non_exhaustive]
1115pub struct ElicitationFormCapabilities {
1116 #[serde_as(deserialize_as = "DefaultOnError")]
1122 #[schemars(extend("x-deserialize-default-on-error" = true))]
1123 #[serde(default)]
1124 #[serde(rename = "_meta")]
1125 pub meta: Option<Meta>,
1126}
1127
1128impl ElicitationFormCapabilities {
1129 #[must_use]
1131 pub fn new() -> Self {
1132 Self::default()
1133 }
1134
1135 #[must_use]
1141 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1142 self.meta = meta.into_option();
1143 self
1144 }
1145}
1146
1147#[serde_as]
1153#[skip_serializing_none]
1154#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1155#[serde(rename_all = "camelCase")]
1156#[non_exhaustive]
1157pub struct ElicitationUrlCapabilities {
1158 #[serde_as(deserialize_as = "DefaultOnError")]
1164 #[schemars(extend("x-deserialize-default-on-error" = true))]
1165 #[serde(default)]
1166 #[serde(rename = "_meta")]
1167 pub meta: Option<Meta>,
1168}
1169
1170impl ElicitationUrlCapabilities {
1171 #[must_use]
1173 pub fn new() -> Self {
1174 Self::default()
1175 }
1176
1177 #[must_use]
1183 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1184 self.meta = meta.into_option();
1185 self
1186 }
1187}
1188
1189#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1195#[serde(untagged)]
1196#[non_exhaustive]
1197pub enum ElicitationScope {
1198 Session(ElicitationSessionScope),
1200 Request(ElicitationRequestScope),
1203}
1204
1205#[serde_as]
1215#[skip_serializing_none]
1216#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1217#[serde(rename_all = "camelCase")]
1218#[non_exhaustive]
1219pub struct ElicitationSessionScope {
1220 pub session_id: SessionId,
1222 #[serde_as(deserialize_as = "DefaultOnError")]
1224 #[schemars(extend("x-deserialize-default-on-error" = true))]
1225 #[serde(default)]
1226 pub tool_call_id: Option<ToolCallId>,
1227}
1228
1229impl ElicitationSessionScope {
1230 #[must_use]
1232 pub fn new(session_id: impl Into<SessionId>) -> Self {
1233 Self {
1234 session_id: session_id.into(),
1235 tool_call_id: None,
1236 }
1237 }
1238
1239 #[must_use]
1241 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1242 self.tool_call_id = tool_call_id.into_option();
1243 self
1244 }
1245}
1246
1247#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1254#[serde(rename_all = "camelCase")]
1255#[non_exhaustive]
1256pub struct ElicitationRequestScope {
1257 pub request_id: RequestId,
1259}
1260
1261impl ElicitationRequestScope {
1262 #[must_use]
1264 pub fn new(request_id: impl Into<RequestId>) -> Self {
1265 Self {
1266 request_id: request_id.into(),
1267 }
1268 }
1269}
1270
1271impl From<ElicitationSessionScope> for ElicitationScope {
1272 fn from(scope: ElicitationSessionScope) -> Self {
1273 Self::Session(scope)
1274 }
1275}
1276
1277impl From<ElicitationRequestScope> for ElicitationScope {
1278 fn from(scope: ElicitationRequestScope) -> Self {
1279 Self::Request(scope)
1280 }
1281}
1282
1283#[serde_as]
1293#[skip_serializing_none]
1294#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1295#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1296#[serde(rename_all = "camelCase")]
1297#[non_exhaustive]
1298pub struct CreateElicitationRequest {
1299 #[serde(flatten)]
1301 pub mode: ElicitationMode,
1302 pub message: String,
1304 #[serde_as(deserialize_as = "DefaultOnError")]
1310 #[schemars(extend("x-deserialize-default-on-error" = true))]
1311 #[serde(default)]
1312 #[serde(rename = "_meta")]
1313 pub meta: Option<Meta>,
1314}
1315
1316impl CreateElicitationRequest {
1317 #[must_use]
1319 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1320 Self {
1321 mode: mode.into(),
1322 message: message.into(),
1323 meta: None,
1324 }
1325 }
1326
1327 #[must_use]
1329 pub fn scope(&self) -> &ElicitationScope {
1330 self.mode.scope()
1331 }
1332
1333 #[must_use]
1339 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1340 self.meta = meta.into_option();
1341 self
1342 }
1343}
1344
1345#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1351#[serde(tag = "mode", rename_all = "snake_case")]
1352#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1353#[non_exhaustive]
1354pub enum ElicitationMode {
1355 Form(ElicitationFormMode),
1357 Url(ElicitationUrlMode),
1359}
1360
1361impl From<ElicitationFormMode> for ElicitationMode {
1362 fn from(mode: ElicitationFormMode) -> Self {
1363 Self::Form(mode)
1364 }
1365}
1366
1367impl From<ElicitationUrlMode> for ElicitationMode {
1368 fn from(mode: ElicitationUrlMode) -> Self {
1369 Self::Url(mode)
1370 }
1371}
1372
1373impl ElicitationMode {
1374 #[must_use]
1376 pub fn scope(&self) -> &ElicitationScope {
1377 match self {
1378 Self::Form(f) => &f.scope,
1379 Self::Url(u) => &u.scope,
1380 }
1381 }
1382}
1383
1384#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1390#[serde(rename_all = "camelCase")]
1391#[non_exhaustive]
1392pub struct ElicitationFormMode {
1393 #[serde(flatten)]
1395 pub scope: ElicitationScope,
1396 pub requested_schema: ElicitationSchema,
1398}
1399
1400impl ElicitationFormMode {
1401 #[must_use]
1403 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1404 Self {
1405 scope: scope.into(),
1406 requested_schema,
1407 }
1408 }
1409}
1410
1411#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1417#[serde(rename_all = "camelCase")]
1418#[non_exhaustive]
1419pub struct ElicitationUrlMode {
1420 #[serde(flatten)]
1422 pub scope: ElicitationScope,
1423 pub elicitation_id: ElicitationId,
1425 #[schemars(extend("format" = "uri"))]
1427 pub url: String,
1428}
1429
1430impl ElicitationUrlMode {
1431 #[must_use]
1433 pub fn new(
1434 scope: impl Into<ElicitationScope>,
1435 elicitation_id: impl Into<ElicitationId>,
1436 url: impl Into<String>,
1437 ) -> Self {
1438 Self {
1439 scope: scope.into(),
1440 elicitation_id: elicitation_id.into(),
1441 url: url.into(),
1442 }
1443 }
1444}
1445
1446#[serde_as]
1452#[skip_serializing_none]
1453#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1454#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1455#[serde(rename_all = "camelCase")]
1456#[non_exhaustive]
1457pub struct CreateElicitationResponse {
1458 #[serde(flatten)]
1460 pub action: ElicitationAction,
1461 #[serde_as(deserialize_as = "DefaultOnError")]
1467 #[schemars(extend("x-deserialize-default-on-error" = true))]
1468 #[serde(default)]
1469 #[serde(rename = "_meta")]
1470 pub meta: Option<Meta>,
1471}
1472
1473impl CreateElicitationResponse {
1474 #[must_use]
1476 pub fn new(action: ElicitationAction) -> Self {
1477 Self { action, meta: None }
1478 }
1479
1480 #[must_use]
1486 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1487 self.meta = meta.into_option();
1488 self
1489 }
1490}
1491
1492#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1498#[serde(tag = "action", rename_all = "snake_case")]
1499#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1500#[non_exhaustive]
1501pub enum ElicitationAction {
1502 Accept(ElicitationAcceptAction),
1504 Decline,
1506 Cancel,
1508}
1509
1510#[serde_as]
1516#[skip_serializing_none]
1517#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1518#[serde(rename_all = "camelCase")]
1519#[non_exhaustive]
1520pub struct ElicitationAcceptAction {
1521 #[serde_as(deserialize_as = "DefaultOnError")]
1523 #[schemars(extend("x-deserialize-default-on-error" = true))]
1524 #[serde(default)]
1525 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1526}
1527
1528impl ElicitationAcceptAction {
1529 #[must_use]
1531 pub fn new() -> Self {
1532 Self { content: None }
1533 }
1534
1535 #[must_use]
1537 pub fn content(
1538 mut self,
1539 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1540 ) -> Self {
1541 self.content = content.into_option();
1542 self
1543 }
1544}
1545
1546#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1548#[serde(untagged)]
1549#[non_exhaustive]
1550pub enum ElicitationContentValue {
1551 String(String),
1553 Integer(i64),
1555 Number(f64),
1557 Boolean(bool),
1559 StringArray(Vec<String>),
1561}
1562
1563impl From<String> for ElicitationContentValue {
1564 fn from(value: String) -> Self {
1565 Self::String(value)
1566 }
1567}
1568
1569impl From<&str> for ElicitationContentValue {
1570 fn from(value: &str) -> Self {
1571 Self::String(value.to_string())
1572 }
1573}
1574
1575impl From<i64> for ElicitationContentValue {
1576 fn from(value: i64) -> Self {
1577 Self::Integer(value)
1578 }
1579}
1580
1581impl From<i32> for ElicitationContentValue {
1582 fn from(value: i32) -> Self {
1583 Self::Integer(i64::from(value))
1584 }
1585}
1586
1587impl From<f64> for ElicitationContentValue {
1588 fn from(value: f64) -> Self {
1589 Self::Number(value)
1590 }
1591}
1592
1593impl From<bool> for ElicitationContentValue {
1594 fn from(value: bool) -> Self {
1595 Self::Boolean(value)
1596 }
1597}
1598
1599impl From<Vec<String>> for ElicitationContentValue {
1600 fn from(value: Vec<String>) -> Self {
1601 Self::StringArray(value)
1602 }
1603}
1604
1605impl From<Vec<&str>> for ElicitationContentValue {
1606 fn from(value: Vec<&str>) -> Self {
1607 Self::StringArray(value.into_iter().map(str::to_string).collect())
1608 }
1609}
1610
1611impl Default for ElicitationAcceptAction {
1612 fn default() -> Self {
1613 Self::new()
1614 }
1615}
1616
1617#[serde_as]
1623#[skip_serializing_none]
1624#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1625#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1626#[serde(rename_all = "camelCase")]
1627#[non_exhaustive]
1628pub struct CompleteElicitationNotification {
1629 pub elicitation_id: ElicitationId,
1631 #[serde_as(deserialize_as = "DefaultOnError")]
1637 #[schemars(extend("x-deserialize-default-on-error" = true))]
1638 #[serde(default)]
1639 #[serde(rename = "_meta")]
1640 pub meta: Option<Meta>,
1641}
1642
1643impl CompleteElicitationNotification {
1644 #[must_use]
1646 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
1647 Self {
1648 elicitation_id: elicitation_id.into(),
1649 meta: None,
1650 }
1651 }
1652
1653 #[must_use]
1659 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1660 self.meta = meta.into_option();
1661 self
1662 }
1663}
1664
1665#[serde_as]
1672#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1673#[serde(rename_all = "camelCase")]
1674#[non_exhaustive]
1675pub struct UrlElicitationRequiredData {
1676 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1678 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1679 pub elicitations: Vec<UrlElicitationRequiredItem>,
1680}
1681
1682impl UrlElicitationRequiredData {
1683 #[must_use]
1685 pub fn new(elicitations: Vec<UrlElicitationRequiredItem>) -> Self {
1686 Self { elicitations }
1687 }
1688}
1689
1690#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1696#[serde(rename_all = "camelCase")]
1697#[non_exhaustive]
1698pub struct UrlElicitationRequiredItem {
1699 pub mode: ElicitationUrlOnlyMode,
1701 pub elicitation_id: ElicitationId,
1703 #[schemars(extend("format" = "uri"))]
1705 pub url: String,
1706 pub message: String,
1708}
1709
1710#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
1712#[serde(rename_all = "snake_case")]
1713#[non_exhaustive]
1714pub enum ElicitationUrlOnlyMode {
1715 #[default]
1717 Url,
1718}
1719
1720impl UrlElicitationRequiredItem {
1721 #[must_use]
1723 pub fn new(
1724 elicitation_id: impl Into<ElicitationId>,
1725 url: impl Into<String>,
1726 message: impl Into<String>,
1727 ) -> Self {
1728 Self {
1729 mode: ElicitationUrlOnlyMode::Url,
1730 elicitation_id: elicitation_id.into(),
1731 url: url.into(),
1732 message: message.into(),
1733 }
1734 }
1735}
1736
1737#[cfg(test)]
1738mod tests {
1739 use super::*;
1740 use serde_json::json;
1741
1742 #[test]
1743 fn form_mode_request_serialization() {
1744 let schema = ElicitationSchema::new().string("name", true);
1745 let req = CreateElicitationRequest::new(
1746 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
1747 "Please enter your name",
1748 );
1749
1750 let json = serde_json::to_value(&req).unwrap();
1751 assert_eq!(json["sessionId"], "sess_1");
1752 assert!(json.get("toolCallId").is_none());
1753 assert_eq!(json["mode"], "form");
1754 assert_eq!(json["message"], "Please enter your name");
1755 assert!(json["requestedSchema"].is_object());
1756 assert_eq!(json["requestedSchema"]["type"], "object");
1757 assert_eq!(
1758 json["requestedSchema"]["properties"]["name"]["type"],
1759 "string"
1760 );
1761
1762 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1763 assert_eq!(
1764 *roundtripped.scope(),
1765 ElicitationSessionScope::new("sess_1").into()
1766 );
1767 assert_eq!(roundtripped.message, "Please enter your name");
1768 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
1769 }
1770
1771 #[test]
1772 fn url_mode_request_serialization() {
1773 let req = CreateElicitationRequest::new(
1774 ElicitationUrlMode::new(
1775 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
1776 "elic_1",
1777 "https://example.com/auth",
1778 ),
1779 "Please authenticate",
1780 );
1781
1782 let json = serde_json::to_value(&req).unwrap();
1783 assert_eq!(json["sessionId"], "sess_2");
1784 assert_eq!(json["toolCallId"], "tc_1");
1785 assert_eq!(json["mode"], "url");
1786 assert_eq!(json["elicitationId"], "elic_1");
1787 assert_eq!(json["url"], "https://example.com/auth");
1788 assert_eq!(json["message"], "Please authenticate");
1789
1790 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1791 assert_eq!(
1792 *roundtripped.scope(),
1793 ElicitationSessionScope::new("sess_2")
1794 .tool_call_id("tc_1")
1795 .into()
1796 );
1797 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1798 }
1799
1800 #[test]
1801 fn response_accept_serialization() {
1802 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
1803 ElicitationAcceptAction::new().content(BTreeMap::from([(
1804 "name".to_string(),
1805 ElicitationContentValue::from("Alice"),
1806 )])),
1807 ));
1808
1809 let json = serde_json::to_value(&resp).unwrap();
1810 assert_eq!(json["action"], "accept");
1811 assert_eq!(json["content"]["name"], "Alice");
1812
1813 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1814 assert!(matches!(
1815 roundtripped.action,
1816 ElicitationAction::Accept(ElicitationAcceptAction {
1817 content: Some(_),
1818 ..
1819 })
1820 ));
1821 }
1822
1823 #[test]
1824 fn response_decline_serialization() {
1825 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
1826
1827 let json = serde_json::to_value(&resp).unwrap();
1828 assert_eq!(json["action"], "decline");
1829
1830 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1831 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1832 }
1833
1834 #[test]
1835 fn response_cancel_serialization() {
1836 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
1837
1838 let json = serde_json::to_value(&resp).unwrap();
1839 assert_eq!(json["action"], "cancel");
1840
1841 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1842 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1843 }
1844
1845 #[test]
1846 fn url_mode_request_scope_serialization() {
1847 let req = CreateElicitationRequest::new(
1848 ElicitationUrlMode::new(
1849 ElicitationRequestScope::new(RequestId::Number(42)),
1850 "elic_2",
1851 "https://example.com/setup",
1852 ),
1853 "Please complete setup",
1854 );
1855
1856 let json = serde_json::to_value(&req).unwrap();
1857 assert_eq!(json["requestId"], 42);
1858 assert!(json.get("sessionId").is_none());
1859 assert_eq!(json["mode"], "url");
1860 assert_eq!(json["elicitationId"], "elic_2");
1861 assert_eq!(json["url"], "https://example.com/setup");
1862 assert_eq!(json["message"], "Please complete setup");
1863
1864 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1865 assert_eq!(
1866 *roundtripped.scope(),
1867 ElicitationRequestScope::new(RequestId::Number(42)).into()
1868 );
1869 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1870 }
1871
1872 #[test]
1873 fn request_scope_request_serialization() {
1874 let req = CreateElicitationRequest::new(
1875 ElicitationFormMode::new(
1876 ElicitationRequestScope::new(RequestId::Number(99)),
1877 ElicitationSchema::new().string("workspace", true),
1878 ),
1879 "Enter workspace name",
1880 );
1881
1882 let json = serde_json::to_value(&req).unwrap();
1883 assert_eq!(json["requestId"], 99);
1884 assert!(json.get("sessionId").is_none());
1885
1886 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1887 assert_eq!(
1888 *roundtripped.scope(),
1889 ElicitationRequestScope::new(RequestId::Number(99)).into()
1890 );
1891 }
1892
1893 #[test]
1897 fn client_response_serialization_accept() {
1898 use crate::v2::ClientResponse;
1899
1900 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1901 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
1902 "name".to_string(),
1903 ElicitationContentValue::from("Alice"),
1904 )]))),
1905 ));
1906 let json = serde_json::to_value(&resp).unwrap();
1907 assert_eq!(json["action"], "accept");
1908 assert_eq!(json["content"]["name"], "Alice");
1909
1910 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1912 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
1913 }
1914
1915 #[test]
1916 fn client_response_serialization_decline() {
1917 use crate::v2::ClientResponse;
1918
1919 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1920 ElicitationAction::Decline,
1921 ));
1922 let json = serde_json::to_value(&resp).unwrap();
1923 assert_eq!(json["action"], "decline");
1924
1925 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1926 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1927 }
1928
1929 #[test]
1930 fn client_response_serialization_cancel() {
1931 use crate::v2::ClientResponse;
1932
1933 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1934 ElicitationAction::Cancel,
1935 ));
1936 let json = serde_json::to_value(&resp).unwrap();
1937 assert_eq!(json["action"], "cancel");
1938
1939 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1940 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1941 }
1942
1943 #[test]
1946 fn request_tolerates_extra_fields() {
1947 let json = json!({
1948 "sessionId": "sess_1",
1949 "mode": "form",
1950 "message": "Enter your name",
1951 "requestedSchema": {
1952 "type": "object",
1953 "properties": {
1954 "name": { "type": "string", "title": "Name" }
1955 },
1956 "required": ["name"]
1957 },
1958 "unknownStringField": "hello",
1959 "unknownNumberField": 42
1960 });
1961
1962 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1963 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
1964 assert_eq!(req.message, "Enter your name");
1965 assert!(matches!(req.mode, ElicitationMode::Form(_)));
1966 }
1967
1968 #[test]
1969 fn completion_notification_serialization() {
1970 let notif = CompleteElicitationNotification::new("elic_1");
1971
1972 let json = serde_json::to_value(¬if).unwrap();
1973 assert_eq!(json["elicitationId"], "elic_1");
1974
1975 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
1976 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
1977 }
1978
1979 #[test]
1980 fn capabilities_form_only() {
1981 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
1982
1983 let json = serde_json::to_value(&caps).unwrap();
1984 assert!(json["form"].is_object());
1985 assert!(json.get("url").is_none());
1986
1987 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1988 assert!(roundtripped.form.is_some());
1989 assert!(roundtripped.url.is_none());
1990 }
1991
1992 #[test]
1993 fn capabilities_url_only() {
1994 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
1995
1996 let json = serde_json::to_value(&caps).unwrap();
1997 assert!(json.get("form").is_none());
1998 assert!(json["url"].is_object());
1999
2000 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2001 assert!(roundtripped.form.is_none());
2002 assert!(roundtripped.url.is_some());
2003 }
2004
2005 #[test]
2006 fn capabilities_both() {
2007 let caps = ElicitationCapabilities::new()
2008 .form(ElicitationFormCapabilities::new())
2009 .url(ElicitationUrlCapabilities::new());
2010
2011 let json = serde_json::to_value(&caps).unwrap();
2012 assert!(json["form"].is_object());
2013 assert!(json["url"].is_object());
2014
2015 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2016 assert!(roundtripped.form.is_some());
2017 assert!(roundtripped.url.is_some());
2018 }
2019
2020 #[test]
2021 fn url_elicitation_required_data_serialization() {
2022 let data = UrlElicitationRequiredData::new(vec![UrlElicitationRequiredItem::new(
2023 "elic_1",
2024 "https://example.com/auth",
2025 "Please authenticate",
2026 )]);
2027
2028 let json = serde_json::to_value(&data).unwrap();
2029 assert_eq!(json["elicitations"][0]["mode"], "url");
2030 assert_eq!(json["elicitations"][0]["elicitationId"], "elic_1");
2031 assert_eq!(json["elicitations"][0]["url"], "https://example.com/auth");
2032
2033 let roundtripped: UrlElicitationRequiredData = serde_json::from_value(json).unwrap();
2034 assert_eq!(roundtripped.elicitations.len(), 1);
2035 assert_eq!(
2036 roundtripped.elicitations[0].mode,
2037 ElicitationUrlOnlyMode::Url
2038 );
2039 }
2040
2041 #[test]
2042 fn schema_default_sets_object_type() {
2043 let schema = ElicitationSchema::default();
2044
2045 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2046 assert!(schema.properties.is_empty());
2047
2048 let json = serde_json::to_value(&schema).unwrap();
2049 assert_eq!(json["type"], "object");
2050 }
2051
2052 #[test]
2053 fn schema_builder_serialization() {
2054 let schema = ElicitationSchema::new()
2055 .string("name", true)
2056 .email("email", true)
2057 .integer("age", 0, 150, true)
2058 .boolean("newsletter", false)
2059 .description("User registration");
2060
2061 let json = serde_json::to_value(&schema).unwrap();
2062 assert_eq!(json["type"], "object");
2063 assert_eq!(json["description"], "User registration");
2064 assert_eq!(json["properties"]["name"]["type"], "string");
2065 assert_eq!(json["properties"]["email"]["type"], "string");
2066 assert_eq!(json["properties"]["email"]["format"], "email");
2067 assert_eq!(json["properties"]["age"]["type"], "integer");
2068 assert_eq!(json["properties"]["age"]["minimum"], 0);
2069 assert_eq!(json["properties"]["age"]["maximum"], 150);
2070 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2071
2072 let required = json["required"].as_array().unwrap();
2073 assert!(required.contains(&json!("name")));
2074 assert!(required.contains(&json!("email")));
2075 assert!(required.contains(&json!("age")));
2076 assert!(!required.contains(&json!("newsletter")));
2077
2078 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2079 assert_eq!(roundtripped.properties.len(), 4);
2080 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2081 }
2082
2083 #[test]
2084 fn schema_string_enum_serialization() {
2085 let schema = ElicitationSchema::new().property(
2086 "color",
2087 StringPropertySchema::new().enum_values(vec![
2088 "red".into(),
2089 "green".into(),
2090 "blue".into(),
2091 ]),
2092 true,
2093 );
2094
2095 let json = serde_json::to_value(&schema).unwrap();
2096 assert_eq!(json["properties"]["color"]["type"], "string");
2097 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2098 assert_eq!(enum_vals.len(), 3);
2099
2100 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2101 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2102 {
2103 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2104 } else {
2105 panic!("expected String variant");
2106 }
2107 }
2108
2109 #[test]
2110 fn schema_multi_select_serialization() {
2111 let schema = ElicitationSchema::new().property(
2112 "colors",
2113 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2114 .min_items(1)
2115 .max_items(3),
2116 false,
2117 );
2118
2119 let json = serde_json::to_value(&schema).unwrap();
2120 assert_eq!(json["properties"]["colors"]["type"], "array");
2121 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2122 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2123 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2124
2125 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2126 assert!(matches!(
2127 roundtripped.properties.get("colors").unwrap(),
2128 ElicitationPropertySchema::Array(_)
2129 ));
2130 }
2131
2132 #[test]
2133 fn schema_titled_enum_serialization() {
2134 let schema = ElicitationSchema::new().property(
2135 "country",
2136 StringPropertySchema::new().one_of(vec![
2137 EnumOption::new("us", "United States"),
2138 EnumOption::new("uk", "United Kingdom"),
2139 ]),
2140 true,
2141 );
2142
2143 let json = serde_json::to_value(&schema).unwrap();
2144 assert_eq!(json["properties"]["country"]["type"], "string");
2145 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2146 assert_eq!(one_of.len(), 2);
2147 assert_eq!(one_of[0]["const"], "us");
2148 assert_eq!(one_of[0]["title"], "United States");
2149
2150 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2151 if let ElicitationPropertySchema::String(s) =
2152 roundtripped.properties.get("country").unwrap()
2153 {
2154 assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
2155 } else {
2156 panic!("expected String variant");
2157 }
2158 }
2159
2160 #[test]
2161 fn schema_number_property_serialization() {
2162 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2163
2164 let json = serde_json::to_value(&schema).unwrap();
2165 assert_eq!(json["properties"]["rating"]["type"], "number");
2166 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2167 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2168
2169 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2170 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2171 {
2172 assert_eq!(n.minimum, Some(0.0));
2173 assert_eq!(n.maximum, Some(5.0));
2174 } else {
2175 panic!("expected Number variant");
2176 }
2177 }
2178
2179 #[test]
2180 fn schema_string_format_serialization() {
2181 let schema = ElicitationSchema::new()
2182 .uri("website", true)
2183 .date("birthday", true)
2184 .date_time("updated_at", false);
2185
2186 let json = serde_json::to_value(&schema).unwrap();
2187 assert_eq!(json["properties"]["website"]["type"], "string");
2188 assert_eq!(json["properties"]["website"]["format"], "uri");
2189 assert_eq!(json["properties"]["birthday"]["type"], "string");
2190 assert_eq!(json["properties"]["birthday"]["format"], "date");
2191 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2192 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2193
2194 let required = json["required"].as_array().unwrap();
2195 assert!(required.contains(&json!("website")));
2196 assert!(required.contains(&json!("birthday")));
2197 assert!(!required.contains(&json!("updated_at")));
2198 }
2199
2200 #[test]
2201 fn schema_string_pattern_serialization() {
2202 let schema = ElicitationSchema::new().property(
2203 "name",
2204 StringPropertySchema::new()
2205 .min_length(1)
2206 .max_length(64)
2207 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2208 true,
2209 );
2210
2211 let json = serde_json::to_value(&schema).unwrap();
2212 assert_eq!(json["properties"]["name"]["type"], "string");
2213 assert_eq!(
2214 json["properties"]["name"]["pattern"],
2215 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2216 );
2217
2218 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2219 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2220 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2221 } else {
2222 panic!("expected String variant");
2223 }
2224 }
2225
2226 #[test]
2227 fn schema_property_updates_required_state() {
2228 let schema = ElicitationSchema::new()
2229 .string("name", true)
2230 .email("name", false);
2231
2232 let json = serde_json::to_value(&schema).unwrap();
2233 assert!(json.get("required").is_none());
2234 assert_eq!(json["properties"]["name"]["format"], "email");
2235 }
2236
2237 #[test]
2238 fn schema_defaults_invalid_object_type() {
2239 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2240 "type": "array",
2241 "properties": {
2242 "name": {
2243 "type": "string"
2244 }
2245 }
2246 }))
2247 .unwrap();
2248
2249 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2250 assert!(schema.properties.contains_key("name"));
2251 }
2252
2253 #[test]
2254 fn titled_multi_select_items_reject_one_of() {
2255 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2256 "oneOf": [
2257 {
2258 "const": "red",
2259 "title": "Red"
2260 }
2261 ]
2262 }))
2263 .unwrap_err();
2264
2265 assert!(err.to_string().contains("missing field `anyOf`"));
2266 }
2267
2268 #[test]
2269 fn response_accept_defaults_non_object_content() {
2270 let response = serde_json::from_value::<CreateElicitationResponse>(json!({
2271 "action": "accept",
2272 "content": "Alice"
2273 }))
2274 .unwrap();
2275
2276 assert_eq!(
2277 response.action,
2278 ElicitationAction::Accept(ElicitationAcceptAction::new())
2279 );
2280 }
2281
2282 #[test]
2283 fn response_accept_defaults_nested_object_content() {
2284 let response = serde_json::from_value::<CreateElicitationResponse>(json!({
2285 "action": "accept",
2286 "content": {
2287 "profile": {
2288 "name": "Alice"
2289 }
2290 }
2291 }))
2292 .unwrap();
2293
2294 assert_eq!(
2295 response.action,
2296 ElicitationAction::Accept(ElicitationAcceptAction::new())
2297 );
2298 }
2299
2300 #[test]
2301 fn response_accept_allows_primitive_and_string_array_content() {
2302 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2303 ElicitationAcceptAction::new().content(BTreeMap::from([
2304 ("name".to_string(), ElicitationContentValue::from("Alice")),
2305 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2306 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2307 (
2308 "subscribed".to_string(),
2309 ElicitationContentValue::from(true),
2310 ),
2311 (
2312 "tags".to_string(),
2313 ElicitationContentValue::from(vec!["rust", "acp"]),
2314 ),
2315 ])),
2316 ));
2317
2318 let json = serde_json::to_value(&response).unwrap();
2319 assert_eq!(json["action"], "accept");
2320 assert_eq!(json["content"]["name"], "Alice");
2321 assert_eq!(json["content"]["age"], 30);
2322 assert_eq!(json["content"]["score"], 9.5);
2323 assert_eq!(json["content"]["subscribed"], true);
2324 assert_eq!(json["content"]["tags"][0], "rust");
2325 assert_eq!(json["content"]["tags"][1], "acp");
2326 }
2327}