1use super::{Endpoint, GenerationIdentity, GenerationOutcome, GenerationProgress, transport};
2use crate::transform::generate::chat_responses as p;
3use crate::wire::openai::chat as h;
4use crate::wire::openai::responses as r;
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 ChatViaResponses {
16 original_request: h::GenerateContentRequestBody,
17 target_request: r::GenerateContentRequestBody,
18 selected_model: String,
19 endpoint: Endpoint,
20 identities: GenerationIdentity,
21 report: Report,
22}
23
24impl ChatViaResponses {
25 pub fn prepare(
26 input: h::GenerateContentRequestBody,
27 selected_model: impl Into<String>,
28 endpoint: Endpoint,
29 identities: GenerationIdentity,
30 ) -> Result<Self, TransformError> {
31 Self::prepare_with_calls(
32 input,
33 selected_model,
34 endpoint,
35 identities,
36 &Default::default(),
37 )
38 }
39 fn prepare_with_calls(
40 input: h::GenerateContentRequestBody,
41 selected_model: impl Into<String>,
42 endpoint: Endpoint,
43 mut identities: GenerationIdentity,
44 prior_calls: &std::collections::BTreeMap<String, p::ToolCallKind>,
45 ) -> Result<Self, TransformError> {
46 let selected_model = selected_model.into();
47 if selected_model.trim().is_empty() {
48 return Err(TransformError::missing_metadata("selected_model"));
49 }
50 let original_request = input.into_declared();
51 if original_request.stream.flatten() == Some(true) {
52 return Err(TransformError::unsupported(
53 "stream",
54 "use the incremental invocation entrypoint",
55 ));
56 }
57 let converted = p::chat_to_responses_request_with_calls(
58 original_request.clone(),
59 &selected_model,
60 prior_calls,
61 &mut identities.request,
62 &identities.request_policy,
63 )?;
64 Ok(Self {
65 original_request,
66 target_request: converted.value.into_declared(),
67 selected_model,
68 endpoint,
69 identities,
70 report: converted.report,
71 })
72 }
73 pub async fn prepare_with_state<S: crate::capability::StateStore>(
75 input: h::GenerateContentRequestBody,
76 endpoint: Endpoint,
77 identities: GenerationIdentity,
78 state: &super::GenerationStateAccess<'_, S>,
79 ) -> Result<Self, TransformError> {
80 let selected_model = state.target.model.clone();
81 let original = input.into_declared();
82 let (restored, names) = super::history::chat(original.clone(), state).await?;
83 let mut prepared =
84 Self::prepare_with_calls(restored, selected_model, endpoint, identities, &names.kinds)?;
85 prepared.original_request = original;
86 Ok(prepared)
87 }
88 pub async fn prepare_with_capabilities<
90 S: crate::capability::StateStore,
91 R: crate::capability::ResourceAccess,
92 >(
93 input: h::GenerateContentRequestBody,
94 endpoint: Endpoint,
95 identities: GenerationIdentity,
96 state: &super::GenerationStateAccess<'_, S>,
97 resources: &super::GenerationResources<'_, R>,
98 ) -> Result<Self, TransformError> {
99 let original = input.into_declared();
100 let materialized = resources.chat(original.clone()).await?;
101 let mut prepared =
102 Self::prepare_with_state(materialized, endpoint, identities, state).await?;
103 prepared.original_request = original;
104 Ok(prepared)
105 }
106
107 pub fn original_request(&self) -> &h::GenerateContentRequestBody {
108 &self.original_request
109 }
110 pub fn target_request(&self) -> &r::GenerateContentRequestBody {
111 &self.target_request
112 }
113 pub fn identities(&self) -> &GenerationIdentity {
114 &self.identities
115 }
116 pub fn report(&self) -> &Report {
117 &self.report
118 }
119 pub fn selected_model(&self) -> &str {
120 &self.selected_model
121 }
122 pub fn convert_response(
123 &mut self,
124 native: r::GenerateContentResponseBody,
125 _facts: (),
126 ) -> Result<Converted<h::GenerateContentResponseBody>, TransformError> {
127 let native = native.into_declared();
128 p::responses_to_chat_response(
129 native,
130 &mut self.identities.response,
131 &self.identities.response_policy,
132 )
133 }
134 pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
137 &mut self,
138 upstream: &U,
139 target: &U::Target,
140 limits: CodecLimits,
141 state: &super::GenerationStateAccess<'_, S>,
142 progress: &mut GenerationProgress<r::GenerateContentResponseBody>,
143 facts: impl FnOnce(&r::GenerateContentResponseBody) -> Result<(), 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<r::GenerateContentResponseBody>,
174 facts: impl FnOnce(&r::GenerateContentResponseBody) -> Result<(), TransformError>,
175 ) -> Result<GenerationOutcome<h::GenerateContentResponseBody>, TransformError> {
176 transport::bind(
177 (&self.endpoint, &self.selected_model),
178 &self.identities,
179 (&self.original_request, &self.target_request),
180 state,
181 limits,
182 progress,
183 true,
184 )?;
185 if progress
186 .raw_response
187 .as_ref()
188 .is_none_or(|r| !r.status.is_success())
189 {
190 return Err(TransformError::invalid_result(
191 "generation.recovery",
192 "successful native response required",
193 ));
194 }
195 let native = transport::recover_native(progress, limits)?;
196 facts(&native)?;
197 let converted = self.convert_response(native, ())?;
198 transport::finish(progress, converted, self.report.clone(), limits)
199 }
200}
201
202#[derive(Debug)]
204pub struct ChatReturnFacts {
205 pub parallel_tool_calls: bool,
206 pub tool_choice: r::input::ToolChoice,
207 pub prompt_cache_options: Option<r::response::ResponsePromptCacheOptions>,
208 pub usage: p::ChatUsageSupplement,
209}
210
211#[derive(Debug)]
213pub struct ResponsesViaChat {
214 original_request: r::GenerateContentRequestBody,
215 target_request: h::GenerateContentRequestBody,
216 selected_model: String,
217 endpoint: Endpoint,
218 identities: GenerationIdentity,
219 report: Report,
220}
221
222impl ResponsesViaChat {
223 pub fn prepare(
224 input: r::GenerateContentRequestBody,
225 selected_model: impl Into<String>,
226 endpoint: Endpoint,
227 identities: GenerationIdentity,
228 ) -> Result<Self, TransformError> {
229 Self::prepare_with_replay(
230 input,
231 selected_model,
232 endpoint,
233 identities,
234 &Default::default(),
235 )
236 }
237 fn prepare_with_replay(
238 input: r::GenerateContentRequestBody,
239 selected_model: impl Into<String>,
240 endpoint: Endpoint,
241 mut identities: GenerationIdentity,
242 replay: &super::GenerationToolReplay,
243 ) -> Result<Self, TransformError> {
244 let selected_model = selected_model.into();
245 if selected_model.trim().is_empty() {
246 return Err(TransformError::missing_metadata("selected_model"));
247 }
248 let original_request = input.into_declared();
249 if original_request.stream.flatten() == Some(true) {
250 return Err(TransformError::unsupported(
251 "stream",
252 "use the incremental invocation entrypoint",
253 ));
254 }
255 let mut converted =
256 p::responses_to_chat_request(original_request.clone(), &selected_model)?;
257 super::legacy_chat::restore(&mut converted.value, replay, &mut converted.report)?;
258 super::claude_chat_ids::chat_request(
259 &mut converted.value,
260 &mut identities,
261 Dialect::OpenAi,
262 )?;
263 Ok(Self {
264 original_request,
265 target_request: converted.value.into_declared(),
266 selected_model,
267 endpoint,
268 identities,
269 report: converted.report,
270 })
271 }
272 pub async fn prepare_with_state<S: crate::capability::StateStore>(
274 input: r::GenerateContentRequestBody,
275 endpoint: Endpoint,
276 identities: GenerationIdentity,
277 state: &super::GenerationStateAccess<'_, S>,
278 ) -> Result<Self, TransformError> {
279 let selected_model = state.target.model.clone();
280 let original = input.into_declared();
281 let mut lowered = original.clone();
282 let mut tool_report = Report::default();
283 p::client_tools::Bindings::new(&original)?.lower(&mut lowered, &mut tool_report)?;
284 let (restored, names) = super::history::responses(lowered, state).await?;
285 let mut prepared =
286 Self::prepare_with_replay(restored, selected_model, endpoint, identities, &names)?;
287 prepared.original_request = original;
288 prepared.report.diagnostics.extend(tool_report.diagnostics);
289 Ok(prepared)
290 }
291 pub async fn prepare_with_capabilities<
293 S: crate::capability::StateStore,
294 R: crate::capability::ResourceAccess,
295 >(
296 input: r::GenerateContentRequestBody,
297 endpoint: Endpoint,
298 identities: GenerationIdentity,
299 state: &super::GenerationStateAccess<'_, S>,
300 resources: &super::GenerationResources<'_, R>,
301 ) -> Result<Self, TransformError> {
302 let original = input.into_declared();
303 let materialized = resources.responses(original.clone()).await?;
304 let mut prepared =
305 Self::prepare_with_state(materialized, endpoint, identities, state).await?;
306 prepared.original_request = original;
307 Ok(prepared)
308 }
309
310 pub fn original_request(&self) -> &r::GenerateContentRequestBody {
311 &self.original_request
312 }
313 pub fn target_request(&self) -> &h::GenerateContentRequestBody {
314 &self.target_request
315 }
316 pub fn identities(&self) -> &GenerationIdentity {
317 &self.identities
318 }
319 pub fn report(&self) -> &Report {
320 &self.report
321 }
322 pub fn selected_model(&self) -> &str {
323 &self.selected_model
324 }
325 pub fn convert_response(
326 &mut self,
327 native: h::GenerateContentResponseBody,
328 facts: ChatReturnFacts,
329 ) -> Result<Converted<r::GenerateContentResponseBody>, TransformError> {
330 let native = native.into_declared();
331 p::chat_to_responses_response(
332 native,
333 p::ResponsesResponseContext {
334 request: self.original_request.clone(),
335 effective_parallel_tool_calls: facts.parallel_tool_calls,
336 effective_tool_choice: facts.tool_choice,
337 usage: facts.usage,
338 effective_prompt_cache_options: facts.prompt_cache_options,
339 },
340 &mut self.identities.response,
341 &self.identities.response_policy,
342 )
343 }
344 pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
347 &mut self,
348 upstream: &U,
349 target: &U::Target,
350 limits: CodecLimits,
351 state: &super::GenerationStateAccess<'_, S>,
352 progress: &mut GenerationProgress<h::GenerateContentResponseBody>,
353 facts: impl FnOnce(&h::GenerateContentResponseBody) -> Result<ChatReturnFacts, TransformError>,
354 ) -> Result<GenerationOutcome<r::GenerateContentResponseBody>, TransformError> {
355 transport::bind(
356 (&self.endpoint, &self.selected_model),
357 &self.identities,
358 (&self.original_request, &self.target_request),
359 state,
360 limits,
361 progress,
362 false,
363 )?;
364 if !transport::send(
365 upstream,
366 target,
367 self.endpoint.clone(),
368 self.target_request.clone(),
369 limits,
370 progress,
371 )
372 .await?
373 {
374 return Ok(transport::rejected(progress));
375 }
376 self.recover(limits, state, progress, facts).await
377 }
378 pub async fn recover<S: crate::capability::StateStore>(
380 &mut self,
381 limits: CodecLimits,
382 state: &super::GenerationStateAccess<'_, S>,
383 progress: &mut GenerationProgress<h::GenerateContentResponseBody>,
384 facts: impl FnOnce(&h::GenerateContentResponseBody) -> Result<ChatReturnFacts, TransformError>,
385 ) -> Result<GenerationOutcome<r::GenerateContentResponseBody>, TransformError> {
386 transport::bind(
387 (&self.endpoint, &self.selected_model),
388 &self.identities,
389 (&self.original_request, &self.target_request),
390 state,
391 limits,
392 progress,
393 true,
394 )?;
395 if progress
396 .raw_response
397 .as_ref()
398 .is_none_or(|r| !r.status.is_success())
399 {
400 return Err(TransformError::invalid_result(
401 "generation.recovery",
402 "successful native response required",
403 ));
404 }
405 let native = transport::recover_native(progress, limits)?;
406 let facts = facts(&native)?;
407 let converted = self.convert_response(native, facts)?;
408 transport::finish(progress, converted, self.report.clone(), limits)
409 }
410}
411
412super::prepared::prepared_generation!(ChatViaResponses, h::GenerateContentRequestBody => r::GenerateContentRequestBody);
413super::prepared::stream_synthesis!(ChatViaResponses, h::GenerateContentRequestBody);
414super::prepared::prepared_generation!(ResponsesViaChat, r::GenerateContentRequestBody => h::GenerateContentRequestBody);
415super::prepared::stream_synthesis!(ResponsesViaChat, r::GenerateContentRequestBody);