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