1use super::{Endpoint, GenerationIdentity, GenerationOutcome, GenerationProgress, transport};
2use crate::transform::generate::claude_gemini as p;
3use crate::wire::claude::generate_content as c;
4use crate::wire::gemini as g;
5use crate::{
6 capability::Upstream,
7 codec::CodecLimits,
8 transform::{Converted, Report, TransformError},
9 wire::DeclaredFields,
10};
11
12#[derive(Debug)]
14pub struct ClaudeViaGemini {
15 original_request: c::GenerateContentRequestBody,
16 target_request: g::GenerateContentRequestBody,
17 selected_model: String,
18 endpoint: Endpoint,
19 identities: GenerationIdentity,
20 report: Report,
21}
22
23impl ClaudeViaGemini {
24 pub fn prepare(
25 input: c::GenerateContentRequestBody,
26 selected_model: impl Into<String>,
27 endpoint: Endpoint,
28 mut identities: GenerationIdentity,
29 context: p::ClaudeGeminiRequestContext,
30 ) -> Result<Self, TransformError> {
31 let selected_model = selected_model.into();
32 if selected_model.trim().is_empty() {
33 return Err(TransformError::missing_metadata("selected_model"));
34 }
35 let original_request = input.into_declared();
36 if original_request.stream == Some(true) {
37 return Err(TransformError::unsupported(
38 "stream",
39 "use the incremental invocation entrypoint",
40 ));
41 }
42 let converted = p::claude_to_gemini_request(
43 original_request.clone(),
44 &selected_model,
45 context,
46 &mut identities.request,
47 &identities.request_policy,
48 )?;
49 Ok(Self {
50 original_request,
51 target_request: converted.value.into_declared(),
52 selected_model,
53 endpoint,
54 identities,
55 report: converted.report,
56 })
57 }
58 pub async fn prepare_with_state<S: crate::capability::StateStore>(
60 input: c::GenerateContentRequestBody,
61 endpoint: Endpoint,
62 identities: GenerationIdentity,
63 state: &super::GenerationStateAccess<'_, S>,
64 context: p::ClaudeGeminiRequestContext,
65 ) -> Result<Self, TransformError> {
66 let selected_model = state.target.model.clone();
67 let original = input.into_declared();
68 let (restored, names) = super::history::claude(original.clone(), state).await?;
69 let mut context = context;
70 super::history::merge_names(&mut context.tool_names, names.names)?;
71 let mut prepared = Self::prepare(restored, selected_model, endpoint, identities, context)?;
72 prepared.original_request = original;
73 Ok(prepared)
74 }
75 pub async fn prepare_with_capabilities<
77 S: crate::capability::StateStore,
78 R: crate::capability::ResourceAccess,
79 >(
80 input: c::GenerateContentRequestBody,
81 endpoint: Endpoint,
82 identities: GenerationIdentity,
83 state: &super::GenerationStateAccess<'_, S>,
84 resources: &super::GenerationResources<'_, R>,
85 context: p::ClaudeGeminiRequestContext,
86 ) -> Result<Self, TransformError> {
87 let original = input.into_declared();
88 let materialized = resources.claude(original.clone()).await?;
89 let mut prepared =
90 Self::prepare_with_state(materialized, endpoint, identities, state, context).await?;
91 prepared.original_request = original;
92 Ok(prepared)
93 }
94
95 pub fn original_request(&self) -> &c::GenerateContentRequestBody {
96 &self.original_request
97 }
98 pub fn target_request(&self) -> &g::GenerateContentRequestBody {
99 &self.target_request
100 }
101 pub fn identities(&self) -> &GenerationIdentity {
102 &self.identities
103 }
104 pub fn report(&self) -> &Report {
105 &self.report
106 }
107 pub fn selected_model(&self) -> &str {
108 &self.selected_model
109 }
110 pub fn convert_response(
111 &mut self,
112 native: g::GenerateContentResponseBody,
113 facts: p::ClaudeGeminiUsageFacts,
114 ) -> Result<Converted<c::GenerateContentResponseBody>, TransformError> {
115 let native = native.into_declared();
116 p::gemini_to_claude_response(
117 native,
118 Some(self.selected_model.clone()),
119 facts,
120 &mut self.identities.response,
121 &self.identities.response_policy,
122 )
123 }
124 pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
127 &mut self,
128 upstream: &U,
129 target: &U::Target,
130 limits: CodecLimits,
131 state: &super::GenerationStateAccess<'_, S>,
132 progress: &mut GenerationProgress<g::GenerateContentResponseBody>,
133 facts: impl FnOnce(
134 &g::GenerateContentResponseBody,
135 ) -> Result<p::ClaudeGeminiUsageFacts, TransformError>,
136 ) -> Result<GenerationOutcome<c::GenerateContentResponseBody>, TransformError> {
137 transport::bind(
138 (&self.endpoint, &self.selected_model),
139 &self.identities,
140 (&self.original_request, &self.target_request),
141 state,
142 limits,
143 progress,
144 false,
145 )?;
146 if !transport::send(
147 upstream,
148 target,
149 self.endpoint.clone(),
150 self.target_request.clone(),
151 limits,
152 progress,
153 )
154 .await?
155 {
156 return Ok(transport::rejected(progress));
157 }
158 self.recover(limits, state, progress, facts).await
159 }
160 pub async fn recover<S: crate::capability::StateStore>(
162 &mut self,
163 limits: CodecLimits,
164 state: &super::GenerationStateAccess<'_, S>,
165 progress: &mut GenerationProgress<g::GenerateContentResponseBody>,
166 facts: impl FnOnce(
167 &g::GenerateContentResponseBody,
168 ) -> Result<p::ClaudeGeminiUsageFacts, TransformError>,
169 ) -> Result<GenerationOutcome<c::GenerateContentResponseBody>, TransformError> {
170 transport::bind(
171 (&self.endpoint, &self.selected_model),
172 &self.identities,
173 (&self.original_request, &self.target_request),
174 state,
175 limits,
176 progress,
177 true,
178 )?;
179 if progress
180 .raw_response
181 .as_ref()
182 .is_none_or(|r| !r.status.is_success())
183 {
184 return Err(TransformError::invalid_result(
185 "generation.recovery",
186 "successful native response required",
187 ));
188 }
189 let native = transport::recover_native(progress, limits)?;
190 let facts = facts(&native)?;
191 let converted = self.convert_response(native, facts)?;
192 transport::finish(progress, converted, self.report.clone(), limits)
193 }
194}
195
196#[derive(Debug)]
198pub struct GeminiViaClaude {
199 original_request: g::GenerateContentRequestBody,
200 target_request: c::GenerateContentRequestBody,
201 selected_model: String,
202 endpoint: Endpoint,
203 identities: GenerationIdentity,
204 report: Report,
205}
206
207impl GeminiViaClaude {
208 pub fn prepare(
209 input: g::GenerateContentRequestBody,
210 selected_model: impl Into<String>,
211 endpoint: Endpoint,
212 mut identities: GenerationIdentity,
213 max_tokens: Option<i64>,
214 ) -> Result<Self, TransformError> {
215 let selected_model = selected_model.into();
216 if selected_model.trim().is_empty() {
217 return Err(TransformError::missing_metadata("selected_model"));
218 }
219 let original_request = input.into_declared();
220
221 let converted = p::gemini_to_claude_request(
222 original_request.clone(),
223 &selected_model,
224 max_tokens,
225 &mut identities.request,
226 &identities.request_policy,
227 )?;
228 Ok(Self {
229 original_request,
230 target_request: converted.value.into_declared(),
231 selected_model,
232 endpoint,
233 identities,
234 report: converted.report,
235 })
236 }
237 pub async fn prepare_with_state<S: crate::capability::StateStore>(
239 input: g::GenerateContentRequestBody,
240 endpoint: Endpoint,
241 identities: GenerationIdentity,
242 state: &super::GenerationStateAccess<'_, S>,
243 max_tokens: Option<i64>,
244 ) -> Result<Self, TransformError> {
245 let selected_model = state.target.model.clone();
246 let original = input.into_declared();
247 let (restored, names) = super::history::gemini(original.clone(), state).await?;
248 let _ = names;
249 let mut prepared =
250 Self::prepare(restored, selected_model, endpoint, identities, max_tokens)?;
251 prepared.original_request = original;
252 Ok(prepared)
253 }
254 pub async fn prepare_with_capabilities<
256 S: crate::capability::StateStore,
257 R: crate::capability::ResourceAccess,
258 >(
259 input: g::GenerateContentRequestBody,
260 endpoint: Endpoint,
261 identities: GenerationIdentity,
262 state: &super::GenerationStateAccess<'_, S>,
263 resources: &super::GenerationResources<'_, R>,
264 max_tokens: Option<i64>,
265 ) -> Result<Self, TransformError> {
266 let original = input.into_declared();
267 let materialized = resources.gemini(original.clone()).await?;
268 let mut prepared =
269 Self::prepare_with_state(materialized, endpoint, identities, state, max_tokens).await?;
270 prepared.original_request = original;
271 Ok(prepared)
272 }
273
274 pub fn original_request(&self) -> &g::GenerateContentRequestBody {
275 &self.original_request
276 }
277 pub fn target_request(&self) -> &c::GenerateContentRequestBody {
278 &self.target_request
279 }
280 pub fn identities(&self) -> &GenerationIdentity {
281 &self.identities
282 }
283 pub fn report(&self) -> &Report {
284 &self.report
285 }
286 pub fn selected_model(&self) -> &str {
287 &self.selected_model
288 }
289 pub fn convert_response(
290 &mut self,
291 native: c::GenerateContentResponseBody,
292 facts: p::ClaudeGeminiUsageFacts,
293 ) -> Result<Converted<g::GenerateContentResponseBody>, TransformError> {
294 let facts = self.usage_facts(facts);
295 let native = native.into_declared();
296 p::claude_to_gemini_response(
297 native,
298 facts,
299 &mut self.identities.response,
300 &self.identities.response_policy,
301 )
302 }
303 pub(crate) fn usage_facts(
304 &self,
305 mut facts: p::ClaudeGeminiUsageFacts,
306 ) -> p::ClaudeGeminiUsageFacts {
307 if matches!(
311 self.target_request.thinking,
312 None | Some(crate::wire::claude::count_tokens::ThinkingConfig::Disabled(
313 _
314 ))
315 ) {
316 facts.thinking_tokens.get_or_insert(0);
317 }
318 facts
319 }
320 pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
323 &mut self,
324 upstream: &U,
325 target: &U::Target,
326 limits: CodecLimits,
327 state: &super::GenerationStateAccess<'_, S>,
328 progress: &mut GenerationProgress<c::GenerateContentResponseBody>,
329 facts: impl FnOnce(
330 &c::GenerateContentResponseBody,
331 ) -> Result<p::ClaudeGeminiUsageFacts, TransformError>,
332 ) -> Result<GenerationOutcome<g::GenerateContentResponseBody>, TransformError> {
333 transport::bind(
334 (&self.endpoint, &self.selected_model),
335 &self.identities,
336 (&self.original_request, &self.target_request),
337 state,
338 limits,
339 progress,
340 false,
341 )?;
342 if !transport::send(
343 upstream,
344 target,
345 self.endpoint.clone(),
346 self.target_request.clone(),
347 limits,
348 progress,
349 )
350 .await?
351 {
352 return Ok(transport::rejected(progress));
353 }
354 self.recover(limits, state, progress, facts).await
355 }
356 pub async fn recover<S: crate::capability::StateStore>(
358 &mut self,
359 limits: CodecLimits,
360 state: &super::GenerationStateAccess<'_, S>,
361 progress: &mut GenerationProgress<c::GenerateContentResponseBody>,
362 facts: impl FnOnce(
363 &c::GenerateContentResponseBody,
364 ) -> Result<p::ClaudeGeminiUsageFacts, TransformError>,
365 ) -> Result<GenerationOutcome<g::GenerateContentResponseBody>, TransformError> {
366 transport::bind(
367 (&self.endpoint, &self.selected_model),
368 &self.identities,
369 (&self.original_request, &self.target_request),
370 state,
371 limits,
372 progress,
373 true,
374 )?;
375 if progress
376 .raw_response
377 .as_ref()
378 .is_none_or(|r| !r.status.is_success())
379 {
380 return Err(TransformError::invalid_result(
381 "generation.recovery",
382 "successful native response required",
383 ));
384 }
385 let native = transport::recover_native(progress, limits)?;
386 let facts = facts(&native)?;
387 let converted = self.convert_response(native, facts)?;
388 transport::finish(progress, converted, self.report.clone(), limits)
389 }
390}
391
392super::prepared::prepared_generation!(ClaudeViaGemini, c::GenerateContentRequestBody => g::GenerateContentRequestBody);
393super::prepared::stream_synthesis!(ClaudeViaGemini, c::GenerateContentRequestBody, context: p::ClaudeGeminiRequestContext);
394super::prepared::prepared_generation!(GeminiViaClaude, g::GenerateContentRequestBody => c::GenerateContentRequestBody);
395super::prepared::stream_synthesis!(GeminiViaClaude, g::GenerateContentRequestBody, max_tokens: Option<i64>);