Skip to main content

gproxy_protocol/transform/claude/generate_content/gemini/
request.rs

1use crate::claude::count_tokens::types::{
2    BetaContentBlockParam, BetaMessageContent, BetaMessageRole,
3};
4use crate::claude::create_message::request::ClaudeCreateMessageRequest;
5use crate::gemini::count_tokens::types::{
6    GeminiBlob, GeminiContentRole, GeminiFileData, GeminiFunctionCall, GeminiPart,
7};
8use crate::gemini::generate_content::request::{
9    GeminiGenerateContentRequest, PathParameters, QueryParameters, RequestBody, RequestHeaders,
10};
11use crate::gemini::generate_content::types::{GeminiContent, GeminiGenerationConfig, HttpMethod};
12use crate::transform::claude::generate_content::gemini::utils::{
13    gemini_system_instruction_from_claude, gemini_thinking_config_from_claude,
14    gemini_tool_config_from_claude, gemini_tools_from_claude,
15};
16use crate::transform::claude::generate_content::utils::{
17    beta_message_content_to_text, claude_model_to_string,
18};
19use crate::transform::claude::model_list::gemini::utils::ensure_models_prefix;
20use crate::transform::utils::TransformError;
21
22impl TryFrom<ClaudeCreateMessageRequest> for GeminiGenerateContentRequest {
23    type Error = TransformError;
24
25    fn try_from(value: ClaudeCreateMessageRequest) -> Result<Self, TransformError> {
26        let body = value.body;
27        let model = ensure_models_prefix(&claude_model_to_string(&body.model));
28
29        let contents = body
30            .messages
31            .into_iter()
32            .map(|message| {
33                let fallback_text = beta_message_content_to_text(&message.content);
34                let parts = match message.content {
35                    BetaMessageContent::Text(text) => vec![GeminiPart {
36                        text: Some(text),
37                        ..GeminiPart::default()
38                    }],
39                    BetaMessageContent::Blocks(blocks) => {
40                        let mut parts = Vec::new();
41                        for block in blocks {
42                            match block {
43                                BetaContentBlockParam::Text(block) => {
44                                    parts.push(GeminiPart {
45                                        text: Some(block.text),
46                                        ..GeminiPart::default()
47                                    });
48                                }
49                                BetaContentBlockParam::Thinking(block) => {
50                                    parts.push(GeminiPart {
51                                        thought: Some(true),
52                                        thought_signature: Some(block.signature),
53                                        text: Some(block.thinking),
54                                        ..GeminiPart::default()
55                                    });
56                                }
57                                BetaContentBlockParam::ToolUse(block) => {
58                                    parts.push(GeminiPart {
59                                        function_call: Some(GeminiFunctionCall {
60                                            id: Some(block.id),
61                                            name: block.name,
62                                            args: Some(block.input),
63                                        }),
64                                        ..GeminiPart::default()
65                                    });
66                                }
67                                BetaContentBlockParam::Image(block) => match block.source {
68                                    crate::claude::count_tokens::types::BetaImageSource::Base64(
69                                        source,
70                                    ) => {
71                                        let mime_type = match source.media_type {
72                                            crate::claude::count_tokens::types::BetaImageMediaType::ImageJpeg => "image/jpeg",
73                                            crate::claude::count_tokens::types::BetaImageMediaType::ImagePng => "image/png",
74                                            crate::claude::count_tokens::types::BetaImageMediaType::ImageGif => "image/gif",
75                                            crate::claude::count_tokens::types::BetaImageMediaType::ImageWebp => "image/webp",
76                                        };
77                                        parts.push(GeminiPart {
78                                            inline_data: Some(GeminiBlob {
79                                                mime_type: mime_type.to_string(),
80                                                data: source.data,
81                                            }),
82                                            ..GeminiPart::default()
83                                        });
84                                    }
85                                    crate::claude::count_tokens::types::BetaImageSource::Url(
86                                        source,
87                                    ) => {
88                                        parts.push(GeminiPart {
89                                            file_data: Some(GeminiFileData {
90                                                mime_type: None,
91                                                file_uri: source.url,
92                                            }),
93                                            ..GeminiPart::default()
94                                        });
95                                    }
96                                    crate::claude::count_tokens::types::BetaImageSource::File(
97                                        source,
98                                    ) => {
99                                        parts.push(GeminiPart {
100                                            text: Some(format!("file_id:{}", source.file_id)),
101                                            ..GeminiPart::default()
102                                        });
103                                    }
104                                },
105                                _ => {}
106                            }
107                        }
108
109                        if parts.is_empty() {
110                            vec![GeminiPart {
111                                text: Some(fallback_text),
112                                ..GeminiPart::default()
113                            }]
114                        } else {
115                            parts
116                        }
117                    }
118                };
119
120                GeminiContent {
121                    parts,
122                    role: Some(match message.role {
123                        BetaMessageRole::User => GeminiContentRole::User,
124                        BetaMessageRole::Assistant => GeminiContentRole::Model,
125                    }),
126                }
127            })
128            .collect::<Vec<_>>();
129        let system_instruction = gemini_system_instruction_from_claude(body.system);
130        let tools = gemini_tools_from_claude(body.tools, true);
131        let tool_config = gemini_tool_config_from_claude(body.tool_choice);
132
133        let mut generation_config = GeminiGenerationConfig::default();
134        let mut has_generation_config = true;
135        generation_config.max_output_tokens = Some(body.max_tokens.min(u32::MAX as u64) as u32);
136        if let Some(stop_sequences) = body.stop_sequences {
137            generation_config.stop_sequences = Some(stop_sequences);
138            has_generation_config = true;
139        }
140        if let Some(temperature) = body.temperature {
141            generation_config.temperature = Some(temperature);
142            has_generation_config = true;
143        }
144        if let Some(top_p) = body.top_p {
145            generation_config.top_p = Some(top_p);
146            has_generation_config = true;
147        }
148        if let Some(top_k) = body.top_k {
149            generation_config.top_k = Some(top_k.min(u32::MAX as u64) as u32);
150            has_generation_config = true;
151        }
152        let thinking_config = gemini_thinking_config_from_claude(
153            body.thinking,
154            body.output_config
155                .as_ref()
156                .and_then(|config| config.effort.as_ref()),
157        );
158        if let Some(thinking_config) = thinking_config {
159            generation_config.thinking_config = Some(thinking_config);
160            has_generation_config = true;
161        }
162        let json_output_requested = body
163            .output_config
164            .as_ref()
165            .and_then(|config| config.format.as_ref())
166            .is_some();
167        let response_json_schema = body
168            .output_config
169            .as_ref()
170            .and_then(|config| config.format.as_ref())
171            .and_then(|schema| serde_json::to_value(schema.schema.clone()).ok());
172        if json_output_requested {
173            generation_config.response_mime_type = Some("application/json".to_string());
174            has_generation_config = true;
175        }
176        if let Some(schema) = response_json_schema {
177            generation_config.response_json_schema = Some(schema);
178            has_generation_config = true;
179        }
180        let generation_config = if has_generation_config {
181            Some(generation_config)
182        } else {
183            None
184        };
185
186        Ok(Self {
187            method: HttpMethod::Post,
188            path: PathParameters { model },
189            query: QueryParameters::default(),
190            headers: RequestHeaders::default(),
191            body: RequestBody {
192                contents,
193                tools,
194                tool_config,
195                safety_settings: None,
196                system_instruction,
197                generation_config,
198                cached_content: None,
199                store: None,
200            },
201        })
202    }
203}