1use super::{Endpoint, GenerationIdentity, GenerationOutcome, GenerationProgress, transport};
2use crate::transform::generate::claude_responses as p;
3use crate::wire::claude::generate_content as c;
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 ClaudeViaResponses {
16 original_request: c::GenerateContentRequestBody,
17 target_request: r::GenerateContentRequestBody,
18 selected_model: String,
19 endpoint: Endpoint,
20 identities: GenerationIdentity,
21 report: Report,
22}
23
24impl ClaudeViaResponses {
25 pub fn prepare(
26 input: c::GenerateContentRequestBody,
27 selected_model: impl Into<String>,
28 endpoint: Endpoint,
29 mut identities: GenerationIdentity,
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_responses_request(
43 original_request.clone(),
44 &selected_model,
45 &mut identities.request,
46 &identities.request_policy,
47 )?;
48 Ok(Self {
49 original_request,
50 target_request: converted.value.into_declared(),
51 selected_model,
52 endpoint,
53 identities,
54 report: converted.report,
55 })
56 }
57 pub async fn prepare_with_state<S: crate::capability::StateStore>(
59 input: c::GenerateContentRequestBody,
60 endpoint: Endpoint,
61 identities: GenerationIdentity,
62 state: &super::GenerationStateAccess<'_, S>,
63 ) -> Result<Self, TransformError> {
64 let selected_model = state.target.model.clone();
65 let original = input.into_declared();
66 let (restored, names) = super::history::claude(original.clone(), state).await?;
67 let _ = names;
68 let mut prepared = Self::prepare(restored, selected_model, endpoint, identities)?;
69 prepared.original_request = original;
70 Ok(prepared)
71 }
72 pub async fn prepare_with_capabilities<
74 S: crate::capability::StateStore,
75 R: crate::capability::ResourceAccess,
76 >(
77 input: c::GenerateContentRequestBody,
78 endpoint: Endpoint,
79 identities: GenerationIdentity,
80 state: &super::GenerationStateAccess<'_, S>,
81 resources: &super::GenerationResources<'_, R>,
82 ) -> Result<Self, TransformError> {
83 let original = input.into_declared();
84 let materialized = resources.claude(original.clone()).await?;
85 let mut prepared =
86 Self::prepare_with_state(materialized, endpoint, identities, state).await?;
87 prepared.original_request = original;
88 Ok(prepared)
89 }
90
91 pub fn original_request(&self) -> &c::GenerateContentRequestBody {
92 &self.original_request
93 }
94 pub fn target_request(&self) -> &r::GenerateContentRequestBody {
95 &self.target_request
96 }
97 pub fn identities(&self) -> &GenerationIdentity {
98 &self.identities
99 }
100 pub fn report(&self) -> &Report {
101 &self.report
102 }
103 pub fn selected_model(&self) -> &str {
104 &self.selected_model
105 }
106 pub fn convert_response(
107 &mut self,
108 native: r::GenerateContentResponseBody,
109 _facts: p::ClaudeRequestContext,
110 ) -> Result<Converted<c::GenerateContentResponseBody>, TransformError> {
111 let native = native.into_declared();
112 p::responses_to_claude_response(
113 native,
114 &mut self.identities.response,
115 &self.identities.response_policy,
116 )
117 }
118 pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
121 &mut self,
122 upstream: &U,
123 target: &U::Target,
124 limits: CodecLimits,
125 state: &super::GenerationStateAccess<'_, S>,
126 progress: &mut GenerationProgress<r::GenerateContentResponseBody>,
127 facts: impl FnOnce(
128 &r::GenerateContentResponseBody,
129 ) -> Result<p::ClaudeRequestContext, TransformError>,
130 ) -> Result<GenerationOutcome<c::GenerateContentResponseBody>, TransformError> {
131 transport::bind(
132 (&self.endpoint, &self.selected_model),
133 &self.identities,
134 (&self.original_request, &self.target_request),
135 state,
136 limits,
137 progress,
138 false,
139 )?;
140 if !transport::send(
141 upstream,
142 target,
143 self.endpoint.clone(),
144 self.target_request.clone(),
145 limits,
146 progress,
147 )
148 .await?
149 {
150 return Ok(transport::rejected(progress));
151 }
152 self.recover(limits, state, progress, facts).await
153 }
154 pub async fn recover<S: crate::capability::StateStore>(
156 &mut self,
157 limits: CodecLimits,
158 state: &super::GenerationStateAccess<'_, S>,
159 progress: &mut GenerationProgress<r::GenerateContentResponseBody>,
160 facts: impl FnOnce(
161 &r::GenerateContentResponseBody,
162 ) -> Result<p::ClaudeRequestContext, TransformError>,
163 ) -> Result<GenerationOutcome<c::GenerateContentResponseBody>, TransformError> {
164 transport::bind(
165 (&self.endpoint, &self.selected_model),
166 &self.identities,
167 (&self.original_request, &self.target_request),
168 state,
169 limits,
170 progress,
171 true,
172 )?;
173 if progress
174 .raw_response
175 .as_ref()
176 .is_none_or(|r| !r.status.is_success())
177 {
178 return Err(TransformError::invalid_result(
179 "generation.recovery",
180 "successful native response required",
181 ));
182 }
183 let native = transport::recover_native(progress, limits)?;
184 let facts = facts(&native)?;
185 let converted = self.convert_response(native, facts)?;
186 transport::finish(progress, converted, self.report.clone(), limits)
187 }
188}
189
190#[derive(Debug)]
192pub struct ClaudeReturnFacts {
193 pub parallel_tool_calls: bool,
194 pub tool_choice: r::input::ToolChoice,
195 pub prompt_cache_options: Option<r::response::ResponsePromptCacheOptions>,
196 pub usage: p::ResponsesUsageFacts,
197 pub created_at: i64,
198}
199
200#[derive(Debug)]
202pub struct ResponsesViaClaude {
203 original_request: r::GenerateContentRequestBody,
204 target_request: c::GenerateContentRequestBody,
205 selected_model: String,
206 endpoint: Endpoint,
207 identities: GenerationIdentity,
208 report: Report,
209}
210
211impl ResponsesViaClaude {
212 pub fn prepare(
213 input: r::GenerateContentRequestBody,
214 selected_model: impl Into<String>,
215 endpoint: Endpoint,
216 mut identities: GenerationIdentity,
217 context: p::ClaudeRequestContext,
218 ) -> Result<Self, TransformError> {
219 let selected_model = selected_model.into();
220 if selected_model.trim().is_empty() {
221 return Err(TransformError::missing_metadata("selected_model"));
222 }
223 let original_request = input.into_declared();
224 if original_request.stream.flatten() == Some(true) {
225 return Err(TransformError::unsupported(
226 "stream",
227 "use the incremental invocation entrypoint",
228 ));
229 }
230 let mut converted =
231 p::responses_to_claude_request(original_request.clone(), &selected_model, context)?;
232 super::claude_chat_ids::claude_request(
233 &mut converted.value,
234 &mut identities,
235 Dialect::OpenAi,
236 )?;
237 Ok(Self {
238 original_request,
239 target_request: converted.value.into_declared(),
240 selected_model,
241 endpoint,
242 identities,
243 report: converted.report,
244 })
245 }
246 pub async fn prepare_with_state<S: crate::capability::StateStore>(
248 input: r::GenerateContentRequestBody,
249 endpoint: Endpoint,
250 identities: GenerationIdentity,
251 state: &super::GenerationStateAccess<'_, S>,
252 context: p::ClaudeRequestContext,
253 ) -> Result<Self, TransformError> {
254 let selected_model = state.target.model.clone();
255 let original = input.into_declared();
256 let mut lowered = original.clone();
257 let mut tool_report = Report::default();
258 crate::transform::generate::client_tools::Bindings::for_target(&original, Dialect::Claude)?
259 .lower(&mut lowered, &mut tool_report)?;
260 let (restored, _) = super::history::responses(lowered, state).await?;
261 if context
262 .target
263 .as_ref()
264 .is_some_and(|target| target != &state.target)
265 {
266 return Err(TransformError::shape(
267 "signature.context",
268 "caller replay binding conflicts with selected state",
269 ));
270 }
271 let mut prepared = Self::prepare(restored, selected_model, endpoint, identities, context)?;
272 prepared.original_request = original;
273 prepared.report.diagnostics.extend(tool_report.diagnostics);
274 Ok(prepared)
275 }
276 pub async fn prepare_with_capabilities<
278 S: crate::capability::StateStore,
279 R: crate::capability::ResourceAccess,
280 >(
281 input: r::GenerateContentRequestBody,
282 endpoint: Endpoint,
283 identities: GenerationIdentity,
284 state: &super::GenerationStateAccess<'_, S>,
285 resources: &super::GenerationResources<'_, R>,
286 context: p::ClaudeRequestContext,
287 ) -> Result<Self, TransformError> {
288 let original = input.into_declared();
289 let materialized = resources.responses(original.clone()).await?;
290 let mut prepared =
291 Self::prepare_with_state(materialized, endpoint, identities, state, context).await?;
292 prepared.original_request = original;
293 Ok(prepared)
294 }
295
296 pub fn original_request(&self) -> &r::GenerateContentRequestBody {
297 &self.original_request
298 }
299 pub fn target_request(&self) -> &c::GenerateContentRequestBody {
300 &self.target_request
301 }
302 pub fn identities(&self) -> &GenerationIdentity {
303 &self.identities
304 }
305 pub fn report(&self) -> &Report {
306 &self.report
307 }
308 pub fn selected_model(&self) -> &str {
309 &self.selected_model
310 }
311 pub fn convert_response(
312 &mut self,
313 native: c::GenerateContentResponseBody,
314 facts: ClaudeReturnFacts,
315 ) -> Result<Converted<r::GenerateContentResponseBody>, TransformError> {
316 let native = native.into_declared();
317 p::claude_to_responses_response(
318 native,
319 p::ClaudeResponseContext {
320 request: self.original_request.clone(),
321 effective_parallel_tool_calls: facts.parallel_tool_calls,
322 effective_tool_choice: facts.tool_choice,
323 usage: facts.usage,
324 created_at: facts.created_at,
325 effective_prompt_cache_options: facts.prompt_cache_options,
326 },
327 &mut self.identities.response,
328 &self.identities.response_policy,
329 )
330 }
331 pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
334 &mut self,
335 upstream: &U,
336 target: &U::Target,
337 limits: CodecLimits,
338 state: &super::GenerationStateAccess<'_, S>,
339 progress: &mut GenerationProgress<c::GenerateContentResponseBody>,
340 facts: impl FnOnce(&c::GenerateContentResponseBody) -> Result<ClaudeReturnFacts, TransformError>,
341 ) -> Result<GenerationOutcome<r::GenerateContentResponseBody>, TransformError> {
342 transport::bind(
343 (&self.endpoint, &self.selected_model),
344 &self.identities,
345 (&self.original_request, &self.target_request),
346 state,
347 limits,
348 progress,
349 false,
350 )?;
351 if !transport::send(
352 upstream,
353 target,
354 self.endpoint.clone(),
355 self.target_request.clone(),
356 limits,
357 progress,
358 )
359 .await?
360 {
361 return Ok(transport::rejected(progress));
362 }
363 self.recover(limits, state, progress, facts).await
364 }
365 pub async fn recover<S: crate::capability::StateStore>(
367 &mut self,
368 limits: CodecLimits,
369 state: &super::GenerationStateAccess<'_, S>,
370 progress: &mut GenerationProgress<c::GenerateContentResponseBody>,
371 facts: impl FnOnce(&c::GenerateContentResponseBody) -> Result<ClaudeReturnFacts, TransformError>,
372 ) -> Result<GenerationOutcome<r::GenerateContentResponseBody>, TransformError> {
373 transport::bind(
374 (&self.endpoint, &self.selected_model),
375 &self.identities,
376 (&self.original_request, &self.target_request),
377 state,
378 limits,
379 progress,
380 true,
381 )?;
382 if progress
383 .raw_response
384 .as_ref()
385 .is_none_or(|r| !r.status.is_success())
386 {
387 return Err(TransformError::invalid_result(
388 "generation.recovery",
389 "successful native response required",
390 ));
391 }
392 let native = transport::recover_native(progress, limits)?;
393 let facts = facts(&native)?;
394 let converted = self.convert_response(native, facts)?;
395 transport::finish(progress, converted, self.report.clone(), limits)
396 }
397}
398
399mod synthesis;