gproxy_protocol/transform/claude/generate_content/gemini/
request.rs1use 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}