gproxy_protocol/adapt/generate/resources/
claude.rs1use super::*;
2use crate::wire::{
3 DeclaredFields,
4 claude::{content as c, generate_content as cg},
5};
6
7impl<R: ResourceAccess> GenerationResources<'_, R> {
8 pub async fn claude(
9 &self,
10 input: cg::GenerateContentRequestBody,
11 ) -> Result<cg::GenerateContentRequestBody, TransformError> {
12 let mut input = input.into_declared();
13 let mut budget = self.budget(crate::Dialect::Claude);
14 for message in &mut input.messages {
15 if let c::MessageContent::Blocks(blocks) = &mut message.content {
16 for block in blocks {
17 match block {
18 c::ContentBlock::Image(image) => budget.image(image).await?,
19 c::ContentBlock::Document(doc) => budget.document(doc).await?,
20 c::ContentBlock::ToolResult(result) => {
21 if let Some(c::ToolResultContent::Blocks(blocks)) = &mut result.content
22 {
23 for block in blocks {
24 match block {
25 c::ToolResultContentBlock::Image(image) => {
26 budget.image(image).await?
27 }
28 c::ToolResultContentBlock::Document(doc) => {
29 budget.document(doc).await?
30 }
31 _ => {}
32 }
33 }
34 }
35 }
36 _ => {}
37 }
38 }
39 }
40 }
41 Ok(input)
42 }
43}
44
45impl<R: ResourceAccess> Budget<'_, '_, R> {
46 async fn image(&mut self, image: &mut c::ImageBlock) -> Result<(), TransformError> {
47 let reference = match &image.source {
48 c::ImageSource::Url(source) => ResourceReference::Url(source.url.clone()),
49 c::ImageSource::File(source) => ResourceReference::Id(source.file_id.clone()),
50 c::ImageSource::Base64(_) => return Ok(()),
51 };
52 let Some(media) = self.read(reference, true).await? else {
53 return Ok(());
54 };
55 let mime = match media.mime.as_str() {
56 "image/png" => c::ImageMediaType::Png,
57 "image/jpeg" => c::ImageMediaType::Jpeg,
58 "image/gif" => c::ImageMediaType::Gif,
59 "image/webp" => c::ImageMediaType::Webp,
60 _ => {
61 return Err(TransformError::unsupported(
62 "image.mime",
63 "Claude has no native image variant for this MIME",
64 ));
65 }
66 };
67 image.source = c::ImageSource::Base64(
68 c::Base64Source::builder(STANDARD.encode(media.bytes), mime).build(),
69 );
70 Ok(())
71 }
72 async fn document(&mut self, doc: &mut c::DocumentBlock) -> Result<(), TransformError> {
73 let reference = match &mut doc.source {
74 c::DocumentSource::Url(source) => ResourceReference::Url(source.url.clone()),
75 c::DocumentSource::File(source) => ResourceReference::Id(source.file_id.clone()),
76 c::DocumentSource::Content(source) => {
77 if let c::DocumentContent::Blocks(blocks) = &mut source.content {
78 for block in blocks {
79 if let c::DocumentContentBlock::Image(image) = block {
80 self.image(image).await?;
81 }
82 }
83 }
84 return Ok(());
85 }
86 _ => return Ok(()),
87 };
88 let Some(media) = self.read(reference, false).await? else {
89 return Ok(());
90 };
91 doc.source = match media.mime.as_str() {
92 "application/pdf" => {
93 if !media.bytes.starts_with(b"%PDF-") {
94 return Err(TransformError::invalid_result(
95 "document.mime",
96 "PDF metadata does not match bytes",
97 ));
98 }
99 c::DocumentSource::Base64(
100 c::PdfBase64Source::builder(STANDARD.encode(media.bytes), c::PdfMediaType::Pdf)
101 .build(),
102 )
103 }
104 "text/plain" => c::DocumentSource::Text(
105 c::PlainTextSource::builder(
106 String::from_utf8(media.bytes.to_vec()).map_err(|e| {
107 TransformError::invalid_result("document.utf8", e.to_string())
108 })?,
109 c::TextMediaType::Plain,
110 )
111 .build(),
112 ),
113 _ => {
114 return Err(TransformError::unsupported(
115 "document.mime",
116 "Claude document requires PDF or plain text",
117 ));
118 }
119 };
120 Ok(())
121 }
122}