1use std::fmt;
20
21use aws_sdk_bedrockruntime::types as aws_bedrock;
22use serde::{Deserialize, Serialize};
23
24use super::errors::TypeConversionError;
25use super::json::AwsDocument;
26
27#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
30pub struct InternalConverseOutput {
31 pub output: Option<ConverseOutput>,
33 pub stop_reason: StopReason,
35 pub usage: Option<TokenUsage>,
37 pub metrics: Option<ConverseMetrics>,
39 pub additional_model_response_fields: Option<serde_json::Value>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub request_id: Option<String>,
46 #[serde(skip)]
54 pub trace: Option<aws_bedrock::ConverseTrace>,
55 #[serde(skip)]
57 pub performance_config: Option<aws_bedrock::PerformanceConfiguration>,
58 #[serde(skip)]
60 pub service_tier: Option<aws_bedrock::ServiceTier>,
61}
62
63impl InternalConverseOutput {
64 pub fn usage(&self) -> Option<&TokenUsage> {
65 self.usage.as_ref()
66 }
67
68 pub fn trace(&self) -> Option<&aws_bedrock::ConverseTrace> {
70 self.trace.as_ref()
71 }
72
73 pub fn request_id(&self) -> Option<&str> {
75 self.request_id.as_deref()
76 }
77}
78
79impl TryFrom<aws_sdk_bedrockruntime::operation::converse::ConverseOutput>
80 for InternalConverseOutput
81{
82 type Error = TypeConversionError;
83
84 fn try_from(
85 value: aws_sdk_bedrockruntime::operation::converse::ConverseOutput,
86 ) -> Result<Self, Self::Error> {
87 let request_id =
90 aws_sdk_bedrockruntime::operation::RequestId::request_id(&value).map(str::to_string);
91
92 let aws_sdk_bedrockruntime::operation::converse::ConverseOutput {
100 output,
101 stop_reason,
102 usage,
103 metrics,
104 additional_model_response_fields,
105 trace,
106 performance_config,
107 service_tier,
108 ..
109 } = value;
110
111 Ok(Self {
112 output: output.map(|x| x.try_into()).transpose()?,
113 stop_reason: stop_reason.try_into()?,
114 usage: usage.map(|x| x.try_into()).transpose()?,
115 metrics: metrics.map(|x| x.try_into()).transpose()?,
116 additional_model_response_fields: additional_model_response_fields
117 .map(|doc| AwsDocument(doc).into()),
118 request_id,
119 trace,
120 performance_config,
121 service_tier,
122 })
123 }
124}
125
126#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, Debug, Hash, Serialize, Deserialize)]
131pub struct UnknownVariantValue(pub(crate) String);
132
133impl fmt::Display for UnknownVariantValue {
134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 write!(f, "{}", self.0)
136 }
137}
138
139#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
140pub enum StopReason {
141 ContentFiltered,
142 EndTurn,
143 GuardrailIntervened,
144 MaxTokens,
145 StopSequence,
146 ToolUse,
147 Unknown(UnknownVariantValue),
148}
149
150#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
151pub struct TokenUsage {
152 pub input_tokens: i32,
153 pub output_tokens: i32,
154 pub total_tokens: i32,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 pub cache_read_input_tokens: Option<i32>,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub cache_write_input_tokens: Option<i32>,
159}
160
161#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
162pub struct ConverseMetrics {
163 pub latency_ms: i64,
164}
165
166#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
167pub enum ConverseOutput {
168 Message(Message),
169 Unknown,
170}
171
172impl ConverseOutput {
173 pub fn as_message(&self) -> Result<&Message, TypeConversionError> {
174 match self {
175 Self::Message(message) => Ok(message),
176 invalid => Err(TypeConversionError::new(
177 format!("Tried to get ConverseOutput as message but is invalid: {invalid:?}")
178 .as_ref(),
179 )),
180 }
181 }
182}
183
184#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
185pub struct Message {
186 pub role: ConversationRole,
187 pub content: Vec<ContentBlock>,
188}
189#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
190pub enum ConversationRole {
191 Assistant,
192 User,
193 Unknown(UnknownVariantValue),
194}
195#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
196pub enum ContentBlock {
197 CachePoint(CachePointBlock),
198 CitationsContent(CitationsContentBlock),
199 Document(DocumentBlock),
200 GuardContent(GuardrailConverseContentBlock),
201 Image(ImageBlock),
202 ReasoningContent(ReasoningContentBlock),
203 Text(String),
204 ToolResult(ToolResultBlock),
205 ToolUse(ToolUseBlock),
206 Video(VideoBlock),
207 Unknown,
208}
209#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
210pub struct CachePointBlock {
211 #[serde(rename = "type")]
212 pub kind: CachePointType,
213}
214
215#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
216pub enum CachePointType {
217 Default,
218 Unknown(UnknownVariantValue),
219}
220
221#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
222pub struct CitationsContentBlock {
223 pub content: Option<Vec<CitationGeneratedContent>>,
224 pub citations: Option<Vec<Citation>>,
225}
226#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
227pub enum CitationGeneratedContent {
228 Text(String),
229 Unknown,
230}
231#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
232pub struct Citation {
233 pub title: Option<String>,
234 pub source_content: Option<Vec<CitationSourceContent>>,
235 pub location: Option<CitationLocation>,
236}
237
238#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
239pub enum CitationSourceContent {
240 Text(String),
241 Unknown,
242}
243
244#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
245pub enum CitationLocation {
246 DocumentChar(DocumentCharLocation),
247 DocumentChunk(DocumentChunkLocation),
248 DocumentPage(DocumentPageLocation),
249 Unknown,
250}
251#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
252pub struct DocumentCharLocation {
253 pub document_index: Option<i32>,
254 pub start: Option<i32>,
255 pub end: Option<i32>,
256}
257#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
258pub struct DocumentChunkLocation {
259 pub document_index: Option<i32>,
260 pub start: Option<i32>,
261 pub end: Option<i32>,
262}
263#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
264pub struct DocumentPageLocation {
265 pub document_index: Option<i32>,
266 pub start: Option<i32>,
267 pub end: Option<i32>,
268}
269
270#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
271pub struct DocumentBlock {
272 pub format: DocumentFormat,
273 pub name: String,
274 pub source: Option<DocumentSource>,
275 pub context: Option<String>,
276 pub citations: Option<CitationsConfig>,
277}
278#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
279pub enum DocumentFormat {
280 Csv,
281 Doc,
282 Docx,
283 Html,
284 Md,
285 Pdf,
286 Txt,
287 Xls,
288 Xlsx,
289 Unknown(UnknownVariantValue),
290}
291#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
292pub enum DocumentSource {
293 Bytes(Blob),
294 Content(Vec<DocumentContentBlock>),
295 S3Location(S3Location),
296 Text(String),
297 Unknown,
298}
299#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
300pub enum DocumentContentBlock {
301 Text(String),
302 Unknown,
303}
304#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
305pub struct S3Location {
306 pub uri: String,
307 pub bucket_owner: Option<String>,
308}
309
310#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
311pub struct Blob {
312 pub inner: Vec<u8>,
313}
314#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
315pub struct CitationsConfig {
316 pub enabled: bool,
317}
318#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
319pub enum GuardrailConverseContentBlock {
320 Image(GuardrailConverseImageBlock),
321 Text(GuardrailConverseTextBlock),
322 Unknown,
323}
324#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
325pub struct GuardrailConverseImageBlock {
326 pub format: GuardrailConverseImageFormat,
327 pub source: Option<GuardrailConverseImageSource>,
328}
329#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
330pub enum GuardrailConverseImageFormat {
331 Jpeg,
332 Png,
333 Unknown(UnknownVariantValue),
334}
335#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
336pub enum GuardrailConverseImageSource {
337 Bytes(Blob),
338 Unknown,
339}
340#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
341pub struct GuardrailConverseTextBlock {
342 pub text: String,
343 pub qualifiers: Option<Vec<GuardrailConverseContentQualifier>>,
344}
345#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
346pub enum GuardrailConverseContentQualifier {
347 GroundingSource,
348 GuardContent,
349 Query,
350 Unknown(UnknownVariantValue),
351}
352#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
353pub struct ImageBlock {
354 pub format: ImageFormat,
355 pub source: Option<ImageSource>,
356}
357#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
358pub enum ImageFormat {
359 Gif,
360 Jpeg,
361 Png,
362 Webp,
363 Unknown(UnknownVariantValue),
364}
365#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
366pub enum ImageSource {
367 Bytes(Blob),
368 S3Location(S3Location),
369 Unknown,
370}
371
372#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
373pub enum ReasoningContentBlock {
374 ReasoningText(ReasoningTextBlock),
375 RedactedContent(Blob),
376 Unknown,
377}
378#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
379pub struct ReasoningTextBlock {
380 pub text: String,
381 pub signature: Option<String>,
382}
383
384#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
385pub struct ToolResultBlock {
386 pub tool_use_id: String,
387 pub content: Vec<ToolResultContentBlock>,
388 pub status: Option<ToolResultStatus>,
389}
390
391#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
392pub enum ToolResultContentBlock {
393 Document(DocumentBlock),
394 Image(ImageBlock),
395 Json(serde_json::Value),
396 Text(String),
397 Video(VideoBlock),
398 Unknown,
399}
400#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
401pub struct VideoBlock {
402 pub format: VideoFormat,
403 pub source: Option<VideoSource>,
404}
405#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
406pub enum VideoFormat {
407 Flv,
408 Mkv,
409 Mov,
410 Mp4,
411 Mpeg,
412 Mpg,
413 ThreeGp,
414 Webm,
415 Wmv,
416 Unknown(UnknownVariantValue),
417}
418#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
419pub enum VideoSource {
420 Bytes(Blob),
421 S3Location(S3Location),
422 Unknown,
423}
424#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
425pub struct ToolUseBlock {
426 pub tool_use_id: String,
427 pub name: String,
428 pub input: serde_json::Value,
429}
430
431#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
432pub enum ToolResultStatus {
433 #[serde(rename = "Error")]
435 IsError,
436 Success,
437 Unknown(UnknownVariantValue),
438}
439
440macro_rules! mirror_enum {
451 ($ours:ident, $aws:ty { $($aws_variant:ident => $ours_variant:ident),+ $(,)? }) => {
452 impl TryFrom<$aws> for $ours {
453 type Error = TypeConversionError;
454 fn try_from(value: $aws) -> Result<Self, Self::Error> {
455 <$ours>::try_from(&value)
456 }
457 }
458 impl TryFrom<&$aws> for $ours {
459 type Error = TypeConversionError;
460 fn try_from(value: &$aws) -> Result<Self, Self::Error> {
461 type Aws = $aws;
462 match value {
463 $(Aws::$aws_variant => Ok($ours::$ours_variant),)+
464 invalid => Err(TypeConversionError::new(&format!(
465 concat!("Unknown variant for ", stringify!($ours), ": {:?}"),
466 invalid
467 ))),
468 }
469 }
470 }
471 };
472}
473
474macro_rules! mirror_union {
477 ($ours:ident, $aws:ty { $($aws_variant:ident => $ours_variant:ident),+ $(,)? }) => {
478 impl TryFrom<$aws> for $ours {
479 type Error = TypeConversionError;
480 fn try_from(value: $aws) -> Result<Self, Self::Error> {
481 type Aws = $aws;
482 match value {
483 $(Aws::$aws_variant(value) => Ok($ours::$ours_variant(value.try_into()?)),)+
484 invalid => Err(TypeConversionError::new(&format!(
485 concat!("Unknown variant for ", stringify!($ours), ": {:?}"),
486 invalid
487 ))),
488 }
489 }
490 }
491 };
492}
493
494mirror_enum!(StopReason, aws_bedrock::StopReason {
495 ContentFiltered => ContentFiltered,
496 EndTurn => EndTurn,
497 GuardrailIntervened => GuardrailIntervened,
498 MaxTokens => MaxTokens,
499 StopSequence => StopSequence,
500 ToolUse => ToolUse,
501});
502mirror_enum!(ConversationRole, aws_bedrock::ConversationRole {
503 Assistant => Assistant,
504 User => User,
505});
506mirror_enum!(CachePointType, aws_bedrock::CachePointType {
507 Default => Default,
508});
509mirror_enum!(DocumentFormat, aws_bedrock::DocumentFormat {
510 Csv => Csv,
511 Doc => Doc,
512 Docx => Docx,
513 Html => Html,
514 Md => Md,
515 Pdf => Pdf,
516 Txt => Txt,
517 Xls => Xls,
518 Xlsx => Xlsx,
519});
520mirror_enum!(ImageFormat, aws_bedrock::ImageFormat {
521 Gif => Gif,
522 Jpeg => Jpeg,
523 Png => Png,
524 Webp => Webp,
525});
526mirror_enum!(VideoFormat, aws_bedrock::VideoFormat {
527 Flv => Flv,
528 Mkv => Mkv,
529 Mov => Mov,
530 Mp4 => Mp4,
531 Mpeg => Mpeg,
532 Mpg => Mpg,
533 ThreeGp => ThreeGp,
534 Webm => Webm,
535 Wmv => Wmv,
536});
537mirror_enum!(ToolResultStatus, aws_bedrock::ToolResultStatus {
538 Error => IsError,
539 Success => Success,
540});
541mirror_enum!(GuardrailConverseImageFormat, aws_bedrock::GuardrailConverseImageFormat {
542 Jpeg => Jpeg,
543 Png => Png,
544});
545mirror_enum!(GuardrailConverseContentQualifier, aws_bedrock::GuardrailConverseContentQualifier {
546 GroundingSource => GroundingSource,
547 GuardContent => GuardContent,
548 Query => Query,
549});
550
551mirror_union!(ConverseOutput, aws_bedrock::ConverseOutput {
552 Message => Message,
553});
554mirror_union!(ContentBlock, aws_bedrock::ContentBlock {
555 CachePoint => CachePoint,
556 CitationsContent => CitationsContent,
557 Document => Document,
558 GuardContent => GuardContent,
559 Image => Image,
560 ReasoningContent => ReasoningContent,
561 Text => Text,
562 ToolResult => ToolResult,
563 ToolUse => ToolUse,
564 Video => Video,
565});
566mirror_union!(CitationGeneratedContent, aws_bedrock::CitationGeneratedContent {
567 Text => Text,
568});
569mirror_union!(CitationSourceContent, aws_bedrock::CitationSourceContent {
570 Text => Text,
571});
572mirror_union!(CitationLocation, aws_bedrock::CitationLocation {
573 DocumentChar => DocumentChar,
574 DocumentChunk => DocumentChunk,
575 DocumentPage => DocumentPage,
576});
577mirror_union!(DocumentContentBlock, aws_bedrock::DocumentContentBlock {
578 Text => Text,
579});
580mirror_union!(GuardrailConverseContentBlock, aws_bedrock::GuardrailConverseContentBlock {
581 Image => Image,
582 Text => Text,
583});
584mirror_union!(GuardrailConverseImageSource, aws_bedrock::GuardrailConverseImageSource {
585 Bytes => Bytes,
586});
587mirror_union!(ImageSource, aws_bedrock::ImageSource {
588 Bytes => Bytes,
589 S3Location => S3Location,
590});
591mirror_union!(VideoSource, aws_bedrock::VideoSource {
592 Bytes => Bytes,
593 S3Location => S3Location,
594});
595mirror_union!(ReasoningContentBlock, aws_bedrock::ReasoningContentBlock {
596 ReasoningText => ReasoningText,
597 RedactedContent => RedactedContent,
598});
599
600impl TryFrom<aws_bedrock::DocumentSource> for DocumentSource {
605 type Error = TypeConversionError;
606 fn try_from(value: aws_bedrock::DocumentSource) -> Result<Self, Self::Error> {
607 match value {
608 aws_bedrock::DocumentSource::Bytes(value) => {
609 Ok(DocumentSource::Bytes(value.try_into()?))
610 }
611 aws_bedrock::DocumentSource::Content(value) => Ok(DocumentSource::Content(
612 value
613 .into_iter()
614 .map(TryInto::try_into)
615 .collect::<Result<_, Self::Error>>()?,
616 )),
617 aws_bedrock::DocumentSource::S3Location(value) => {
618 Ok(DocumentSource::S3Location(value.try_into()?))
619 }
620 aws_bedrock::DocumentSource::Text(value) => Ok(DocumentSource::Text(value)),
621 invalid => Err(TypeConversionError::new(&format!(
622 "Unknown variant for DocumentSource: {invalid:?}"
623 ))),
624 }
625 }
626}
627
628impl TryFrom<aws_bedrock::ToolResultContentBlock> for ToolResultContentBlock {
629 type Error = TypeConversionError;
630 fn try_from(value: aws_bedrock::ToolResultContentBlock) -> Result<Self, Self::Error> {
631 match value {
632 aws_bedrock::ToolResultContentBlock::Document(value) => {
633 Ok(ToolResultContentBlock::Document(value.try_into()?))
634 }
635 aws_bedrock::ToolResultContentBlock::Image(value) => {
636 Ok(ToolResultContentBlock::Image(value.try_into()?))
637 }
638 aws_bedrock::ToolResultContentBlock::Json(value) => {
639 Ok(ToolResultContentBlock::Json(AwsDocument(value).into()))
640 }
641 aws_bedrock::ToolResultContentBlock::Text(value) => {
642 Ok(ToolResultContentBlock::Text(value))
643 }
644 aws_bedrock::ToolResultContentBlock::Video(value) => {
645 Ok(ToolResultContentBlock::Video(value.try_into()?))
646 }
647 invalid => Err(TypeConversionError::new(&format!(
648 "Unknown variant for ToolResultContentBlock: {invalid:?}"
649 ))),
650 }
651 }
652}
653
654impl TryFrom<aws_bedrock::TokenUsage> for TokenUsage {
657 type Error = TypeConversionError;
658 fn try_from(value: aws_bedrock::TokenUsage) -> Result<Self, Self::Error> {
659 Ok(TokenUsage {
660 input_tokens: value.input_tokens,
661 output_tokens: value.output_tokens,
662 total_tokens: value.total_tokens,
663 cache_read_input_tokens: value.cache_read_input_tokens,
664 cache_write_input_tokens: value.cache_write_input_tokens,
665 })
666 }
667}
668
669impl TryFrom<aws_bedrock::ConverseMetrics> for ConverseMetrics {
670 type Error = TypeConversionError;
671 fn try_from(value: aws_bedrock::ConverseMetrics) -> Result<Self, Self::Error> {
672 Ok(ConverseMetrics {
673 latency_ms: value.latency_ms,
674 })
675 }
676}
677
678impl TryFrom<aws_bedrock::Message> for Message {
679 type Error = TypeConversionError;
680 fn try_from(value: aws_bedrock::Message) -> Result<Self, Self::Error> {
681 Ok(Message {
682 role: value.role.try_into()?,
683 content: value
684 .content
685 .into_iter()
686 .map(TryInto::try_into)
687 .collect::<Result<_, Self::Error>>()?,
688 })
689 }
690}
691
692impl TryFrom<aws_bedrock::CachePointBlock> for CachePointBlock {
693 type Error = TypeConversionError;
694 fn try_from(value: aws_bedrock::CachePointBlock) -> Result<Self, Self::Error> {
695 Ok(CachePointBlock {
696 kind: value.r#type.try_into()?,
697 })
698 }
699}
700
701impl TryFrom<aws_bedrock::CitationsContentBlock> for CitationsContentBlock {
702 type Error = TypeConversionError;
703 fn try_from(value: aws_bedrock::CitationsContentBlock) -> Result<Self, Self::Error> {
704 Ok(CitationsContentBlock {
705 content: Some(
706 value
707 .content
708 .unwrap_or_default()
709 .into_iter()
710 .map(TryInto::try_into)
711 .collect::<Result<_, Self::Error>>()?,
712 ),
713 citations: Some(
714 value
715 .citations
716 .unwrap_or_default()
717 .into_iter()
718 .map(TryInto::try_into)
719 .collect::<Result<_, Self::Error>>()?,
720 ),
721 })
722 }
723}
724
725impl TryFrom<aws_bedrock::Citation> for Citation {
726 type Error = TypeConversionError;
727 fn try_from(value: aws_bedrock::Citation) -> Result<Self, Self::Error> {
728 Ok(Citation {
729 title: value.title,
730 source_content: Some(
731 value
732 .source_content
733 .unwrap_or_default()
734 .into_iter()
735 .map(TryInto::try_into)
736 .collect::<Result<_, Self::Error>>()?,
737 ),
738 location: value.location.map(TryInto::try_into).transpose()?,
739 })
740 }
741}
742
743macro_rules! mirror_location {
746 ($($name:ident),+ $(,)?) => {$(
747 impl TryFrom<aws_bedrock::$name> for $name {
748 type Error = TypeConversionError;
749 fn try_from(value: aws_bedrock::$name) -> Result<Self, Self::Error> {
750 Ok($name {
751 document_index: value.document_index,
752 start: value.start,
753 end: value.end,
754 })
755 }
756 }
757 )+};
758}
759mirror_location!(
760 DocumentCharLocation,
761 DocumentChunkLocation,
762 DocumentPageLocation
763);
764
765impl TryFrom<aws_bedrock::DocumentBlock> for DocumentBlock {
766 type Error = TypeConversionError;
767 fn try_from(value: aws_bedrock::DocumentBlock) -> Result<Self, Self::Error> {
768 Ok(DocumentBlock {
769 format: value.format.try_into()?,
770 name: value.name,
771 source: value.source.map(TryInto::try_into).transpose()?,
772 context: value.context,
773 citations: value.citations.map(TryInto::try_into).transpose()?,
774 })
775 }
776}
777
778impl TryFrom<aws_bedrock::S3Location> for S3Location {
779 type Error = TypeConversionError;
780 fn try_from(value: aws_bedrock::S3Location) -> Result<Self, Self::Error> {
781 Ok(S3Location {
782 uri: value.uri,
783 bucket_owner: value.bucket_owner,
784 })
785 }
786}
787
788impl TryFrom<aws_sdk_bedrockruntime::primitives::Blob> for Blob {
789 type Error = TypeConversionError;
790 fn try_from(value: aws_sdk_bedrockruntime::primitives::Blob) -> Result<Self, Self::Error> {
791 Ok(Blob {
792 inner: value.into_inner(),
793 })
794 }
795}
796
797impl TryFrom<aws_bedrock::CitationsConfig> for CitationsConfig {
798 type Error = TypeConversionError;
799 fn try_from(value: aws_bedrock::CitationsConfig) -> Result<Self, Self::Error> {
800 Ok(CitationsConfig {
801 enabled: value.enabled,
802 })
803 }
804}
805
806impl TryFrom<aws_bedrock::GuardrailConverseImageBlock> for GuardrailConverseImageBlock {
807 type Error = TypeConversionError;
808 fn try_from(value: aws_bedrock::GuardrailConverseImageBlock) -> Result<Self, Self::Error> {
809 Ok(GuardrailConverseImageBlock {
810 format: value.format.try_into()?,
811 source: value.source.map(TryInto::try_into).transpose()?,
812 })
813 }
814}
815
816impl TryFrom<aws_bedrock::GuardrailConverseTextBlock> for GuardrailConverseTextBlock {
817 type Error = TypeConversionError;
818 fn try_from(value: aws_bedrock::GuardrailConverseTextBlock) -> Result<Self, Self::Error> {
819 Ok(GuardrailConverseTextBlock {
820 text: value.text,
821 qualifiers: Some(
822 value
823 .qualifiers
824 .unwrap_or_default()
825 .into_iter()
826 .map(|v| (&v).try_into())
827 .collect::<Result<_, Self::Error>>()?,
828 ),
829 })
830 }
831}
832
833impl TryFrom<aws_bedrock::ImageBlock> for ImageBlock {
834 type Error = TypeConversionError;
835 fn try_from(value: aws_bedrock::ImageBlock) -> Result<Self, Self::Error> {
836 Ok(ImageBlock {
837 format: value.format.try_into()?,
838 source: value.source.map(TryInto::try_into).transpose()?,
839 })
840 }
841}
842
843impl TryFrom<aws_bedrock::ReasoningTextBlock> for ReasoningTextBlock {
844 type Error = TypeConversionError;
845 fn try_from(value: aws_bedrock::ReasoningTextBlock) -> Result<Self, Self::Error> {
846 Ok(ReasoningTextBlock {
847 text: value.text,
848 signature: value.signature,
849 })
850 }
851}
852
853impl TryFrom<aws_bedrock::ToolResultBlock> for ToolResultBlock {
854 type Error = TypeConversionError;
855 fn try_from(value: aws_bedrock::ToolResultBlock) -> Result<Self, Self::Error> {
856 Ok(ToolResultBlock {
857 tool_use_id: value.tool_use_id,
858 content: value
859 .content
860 .into_iter()
861 .map(TryInto::try_into)
862 .collect::<Result<_, Self::Error>>()?,
863 status: value.status.map(|v| (&v).try_into()).transpose()?,
864 })
865 }
866}
867
868impl TryFrom<aws_bedrock::VideoBlock> for VideoBlock {
869 type Error = TypeConversionError;
870 fn try_from(value: aws_bedrock::VideoBlock) -> Result<Self, Self::Error> {
871 Ok(VideoBlock {
872 format: value.format.try_into()?,
873 source: value.source.map(TryInto::try_into).transpose()?,
874 })
875 }
876}
877
878impl TryFrom<aws_bedrock::ToolUseBlock> for ToolUseBlock {
879 type Error = TypeConversionError;
880 fn try_from(value: aws_bedrock::ToolUseBlock) -> Result<Self, Self::Error> {
881 Ok(ToolUseBlock {
882 tool_use_id: value.tool_use_id,
883 name: value.name,
884 input: AwsDocument(value.input).into(),
885 })
886 }
887}
888
889#[cfg(test)]
890mod tests {
891 use super::*;
892 use serde_json::json;
893
894 #[test]
900 fn converse_output_carries_every_sdk_field() {
901 let sdk_output = aws_sdk_bedrockruntime::operation::converse::ConverseOutput::builder()
902 .stop_reason(aws_bedrock::StopReason::GuardrailIntervened)
903 .output(aws_bedrock::ConverseOutput::Message(
904 aws_bedrock::Message::builder()
905 .role(aws_bedrock::ConversationRole::Assistant)
906 .content(aws_bedrock::ContentBlock::Text("blocked".into()))
907 .build()
908 .unwrap(),
909 ))
910 .usage(
911 aws_bedrock::TokenUsage::builder()
912 .input_tokens(1)
913 .output_tokens(2)
914 .total_tokens(3)
915 .build()
916 .unwrap(),
917 )
918 .metrics(
919 aws_bedrock::ConverseMetrics::builder()
920 .latency_ms(4)
921 .build()
922 .unwrap(),
923 )
924 .trace(
925 aws_bedrock::ConverseTrace::builder()
926 .guardrail(aws_bedrock::GuardrailTraceAssessment::builder().build())
927 .build(),
928 )
929 .performance_config(
930 aws_bedrock::PerformanceConfiguration::builder()
931 .latency(aws_bedrock::PerformanceConfigLatency::Standard)
932 .build(),
933 )
934 .service_tier(
935 aws_bedrock::ServiceTier::builder()
936 .r#type(aws_bedrock::ServiceTierType::Default)
937 .build()
938 .unwrap(),
939 )
940 .build()
941 .unwrap();
942
943 let mirrored = InternalConverseOutput::try_from(sdk_output).unwrap();
944
945 assert!(mirrored.output.is_some());
946 assert_eq!(mirrored.stop_reason, StopReason::GuardrailIntervened);
947 assert!(mirrored.usage.is_some());
948 assert!(mirrored.metrics.is_some());
949 assert!(
950 mirrored.trace().is_some(),
951 "the guardrail trace must survive the conversion"
952 );
953 assert!(
954 mirrored.performance_config.is_some(),
955 "the performance configuration must survive the conversion"
956 );
957 assert!(
958 mirrored.service_tier.is_some(),
959 "the service tier must survive the conversion"
960 );
961 }
962
963 #[test]
967 fn skipped_provider_fields_round_trip_as_absent() {
968 let output = InternalConverseOutput {
969 output: None,
970 stop_reason: StopReason::EndTurn,
971 usage: None,
972 metrics: None,
973 additional_model_response_fields: None,
974 request_id: Some("req-1".to_string()),
975 trace: None,
976 performance_config: None,
977 service_tier: None,
978 };
979
980 let json = serde_json::to_string(&output).unwrap();
981 let restored: InternalConverseOutput = serde_json::from_str(&json).unwrap();
982
983 assert_eq!(restored.request_id(), Some("req-1"));
984 assert!(restored.trace().is_none());
985 assert!(restored.performance_config.is_none());
986 assert!(restored.service_tier.is_none());
987 }
988
989 #[test]
990 fn mirror_enum_converts_known_variants() {
991 assert_eq!(
992 StopReason::try_from(aws_bedrock::StopReason::EndTurn).unwrap(),
993 StopReason::EndTurn
994 );
995 assert_eq!(
997 StopReason::try_from(&aws_bedrock::StopReason::ToolUse).unwrap(),
998 StopReason::ToolUse
999 );
1000 assert_eq!(
1002 ToolResultStatus::try_from(aws_bedrock::ToolResultStatus::Error).unwrap(),
1003 ToolResultStatus::IsError
1004 );
1005 }
1006
1007 #[test]
1008 fn mirror_enum_unknown_variant_preserves_error_string() {
1009 let unknown = aws_bedrock::StopReason::from("weird_stop");
1010 let err = StopReason::try_from(unknown.clone()).unwrap_err();
1011 assert_eq!(
1012 err.to_string(),
1013 format!("Unknown variant for StopReason: {unknown:?}")
1014 );
1015
1016 let err =
1017 ConversationRole::try_from(aws_bedrock::ConversationRole::from("nope")).unwrap_err();
1018 assert!(
1019 err.to_string()
1020 .starts_with("Unknown variant for ConversationRole:")
1021 );
1022 }
1023
1024 #[test]
1025 fn additional_model_response_fields_survive_as_json() {
1026 let doc: AwsDocument = json!({"reasoning_effort": "low", "depth": 3}).into();
1027 let output = aws_sdk_bedrockruntime::operation::converse::ConverseOutput::builder()
1028 .stop_reason(aws_bedrock::StopReason::EndTurn)
1029 .additional_model_response_fields(doc.0)
1030 .build()
1031 .unwrap();
1032
1033 let internal = InternalConverseOutput::try_from(output).unwrap();
1034 assert_eq!(
1035 internal.additional_model_response_fields,
1036 Some(json!({"reasoning_effort": "low", "depth": 3}))
1037 );
1038
1039 let value = serde_json::to_value(&internal).unwrap();
1042 assert_eq!(
1043 value.get("additional_model_response_fields"),
1044 Some(&json!({"reasoning_effort": "low", "depth": 3}))
1045 );
1046 let back: InternalConverseOutput = serde_json::from_value(value).unwrap();
1047 assert_eq!(back, internal);
1048 }
1049
1050 #[test]
1051 fn tool_use_input_decodes_into_json_value() {
1052 let aws_block = aws_bedrock::ToolUseBlock::builder()
1053 .tool_use_id("call_1")
1054 .name("add")
1055 .input(AwsDocument::from(json!({"x": 1, "y": 2})).0)
1056 .build()
1057 .unwrap();
1058
1059 let ours = ToolUseBlock::try_from(aws_block).unwrap();
1060 assert_eq!(ours.tool_use_id, "call_1");
1061 assert_eq!(ours.name, "add");
1062 assert_eq!(ours.input, json!({"x": 1, "y": 2}));
1063 }
1064}