1use super::{Endpoint, GenerationIdentity, GenerationOutcome, GenerationProgress, transport};
2use crate::transform::generate::claude_chat as p;
3use crate::wire::claude::generate_content as c;
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 ChatViaClaude {
16 original_request: h::GenerateContentRequestBody,
17 target_request: c::GenerateContentRequestBody,
18 selected_model: String,
19 endpoint: Endpoint,
20 identities: GenerationIdentity,
21 report: Report,
22}
23
24impl ChatViaClaude {
25 pub fn prepare(
26 input: h::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.flatten() == Some(true) {
37 return Err(TransformError::unsupported(
38 "stream",
39 "use the incremental invocation entrypoint",
40 ));
41 }
42 let mut converted = p::openai_to_claude(&original_request, &selected_model)?;
43 super::claude_chat_ids::claude_request(
44 &mut converted.value,
45 &mut identities,
46 Dialect::OpenAiChat,
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: h::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::chat(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: h::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.chat(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) -> &h::GenerateContentRequestBody {
92 &self.original_request
93 }
94 pub fn target_request(&self) -> &c::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: c::GenerateContentResponseBody,
109 facts: p::ResponseSupplement,
110 ) -> Result<Converted<h::GenerateContentResponseBody>, TransformError> {
111 let native = native.into_declared();
112 let mut converted = p::claude_response_to_openai(&native, &self.selected_model, &facts)?;
113 super::claude_chat_ids::chat_response(&mut converted.value, &mut self.identities)?;
114 Ok(converted)
115 }
116 pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
119 &mut self,
120 upstream: &U,
121 target: &U::Target,
122 limits: CodecLimits,
123 state: &super::GenerationStateAccess<'_, S>,
124 progress: &mut GenerationProgress<c::GenerateContentResponseBody>,
125 facts: impl FnOnce(
126 &c::GenerateContentResponseBody,
127 ) -> Result<p::ResponseSupplement, TransformError>,
128 ) -> Result<GenerationOutcome<h::GenerateContentResponseBody>, TransformError> {
129 transport::bind(
130 (&self.endpoint, &self.selected_model),
131 &self.identities,
132 (&self.original_request, &self.target_request),
133 state,
134 limits,
135 progress,
136 false,
137 )?;
138 if !transport::send(
139 upstream,
140 target,
141 self.endpoint.clone(),
142 self.target_request.clone(),
143 limits,
144 progress,
145 )
146 .await?
147 {
148 return Ok(transport::rejected(progress));
149 }
150 self.recover(limits, state, progress, facts).await
151 }
152 pub async fn recover<S: crate::capability::StateStore>(
154 &mut self,
155 limits: CodecLimits,
156 state: &super::GenerationStateAccess<'_, S>,
157 progress: &mut GenerationProgress<c::GenerateContentResponseBody>,
158 facts: impl FnOnce(
159 &c::GenerateContentResponseBody,
160 ) -> Result<p::ResponseSupplement, TransformError>,
161 ) -> Result<GenerationOutcome<h::GenerateContentResponseBody>, TransformError> {
162 transport::bind(
163 (&self.endpoint, &self.selected_model),
164 &self.identities,
165 (&self.original_request, &self.target_request),
166 state,
167 limits,
168 progress,
169 true,
170 )?;
171 if progress
172 .raw_response
173 .as_ref()
174 .is_none_or(|r| !r.status.is_success())
175 {
176 return Err(TransformError::invalid_result(
177 "generation.recovery",
178 "successful native response required",
179 ));
180 }
181 let native = transport::recover_native(progress, limits)?;
182 let facts = facts(&native)?;
183 let converted = self.convert_response(native, facts)?;
184 transport::finish(progress, converted, self.report.clone(), limits)
185 }
186}
187
188#[derive(Debug)]
190pub struct ClaudeViaChat {
191 original_request: c::GenerateContentRequestBody,
192 target_request: h::GenerateContentRequestBody,
193 selected_model: String,
194 endpoint: Endpoint,
195 identities: GenerationIdentity,
196 report: Report,
197}
198
199impl ClaudeViaChat {
200 pub fn prepare(
201 input: c::GenerateContentRequestBody,
202 selected_model: impl Into<String>,
203 endpoint: Endpoint,
204 identities: GenerationIdentity,
205 ) -> Result<Self, TransformError> {
206 Self::prepare_with_replay(
207 input,
208 selected_model,
209 endpoint,
210 identities,
211 &Default::default(),
212 )
213 }
214 fn prepare_with_replay(
215 input: c::GenerateContentRequestBody,
216 selected_model: impl Into<String>,
217 endpoint: Endpoint,
218 mut identities: GenerationIdentity,
219 replay: &super::GenerationToolReplay,
220 ) -> Result<Self, TransformError> {
221 let selected_model = selected_model.into();
222 if selected_model.trim().is_empty() {
223 return Err(TransformError::missing_metadata("selected_model"));
224 }
225 let original_request = input.into_declared();
226 if original_request.stream == Some(true) {
227 return Err(TransformError::unsupported(
228 "stream",
229 "use the incremental invocation entrypoint",
230 ));
231 }
232 let mut converted = p::claude_to_openai(&original_request, &selected_model)?;
233 super::legacy_chat::restore(&mut converted.value, replay, &mut converted.report)?;
234 super::claude_chat_ids::chat_request(
235 &mut converted.value,
236 &mut identities,
237 Dialect::Claude,
238 )?;
239 Ok(Self {
240 original_request,
241 target_request: converted.value.into_declared(),
242 selected_model,
243 endpoint,
244 identities,
245 report: converted.report,
246 })
247 }
248 pub async fn prepare_with_state<S: crate::capability::StateStore>(
250 input: c::GenerateContentRequestBody,
251 endpoint: Endpoint,
252 identities: GenerationIdentity,
253 state: &super::GenerationStateAccess<'_, S>,
254 ) -> Result<Self, TransformError> {
255 let selected_model = state.target.model.clone();
256 let original = input.into_declared();
257 let (restored, names) = super::history::claude(original.clone(), state).await?;
258 let mut prepared =
259 Self::prepare_with_replay(restored, selected_model, endpoint, identities, &names)?;
260 prepared.original_request = original;
261 Ok(prepared)
262 }
263 pub async fn prepare_with_capabilities<
265 S: crate::capability::StateStore,
266 R: crate::capability::ResourceAccess,
267 >(
268 input: c::GenerateContentRequestBody,
269 endpoint: Endpoint,
270 identities: GenerationIdentity,
271 state: &super::GenerationStateAccess<'_, S>,
272 resources: &super::GenerationResources<'_, R>,
273 ) -> Result<Self, TransformError> {
274 let original = input.into_declared();
275 let materialized = resources.claude(original.clone()).await?;
276 let mut prepared =
277 Self::prepare_with_state(materialized, endpoint, identities, state).await?;
278 prepared.original_request = original;
279 Ok(prepared)
280 }
281
282 pub fn original_request(&self) -> &c::GenerateContentRequestBody {
283 &self.original_request
284 }
285 pub fn target_request(&self) -> &h::GenerateContentRequestBody {
286 &self.target_request
287 }
288 pub fn identities(&self) -> &GenerationIdentity {
289 &self.identities
290 }
291 pub fn report(&self) -> &Report {
292 &self.report
293 }
294 pub fn selected_model(&self) -> &str {
295 &self.selected_model
296 }
297 pub fn convert_response(
298 &mut self,
299 native: h::GenerateContentResponseBody,
300 facts: p::ResponseSupplement,
301 ) -> Result<Converted<c::GenerateContentResponseBody>, TransformError> {
302 let native = native.into_declared();
303 let mut converted = p::openai_response_to_claude(&native, &self.selected_model, &facts)?;
304 super::claude_chat_ids::claude_response(&mut converted.value, &mut self.identities)?;
305 Ok(converted)
306 }
307 pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
310 &mut self,
311 upstream: &U,
312 target: &U::Target,
313 limits: CodecLimits,
314 state: &super::GenerationStateAccess<'_, S>,
315 progress: &mut GenerationProgress<h::GenerateContentResponseBody>,
316 facts: impl FnOnce(
317 &h::GenerateContentResponseBody,
318 ) -> Result<p::ResponseSupplement, TransformError>,
319 ) -> Result<GenerationOutcome<c::GenerateContentResponseBody>, TransformError> {
320 transport::bind(
321 (&self.endpoint, &self.selected_model),
322 &self.identities,
323 (&self.original_request, &self.target_request),
324 state,
325 limits,
326 progress,
327 false,
328 )?;
329 if !transport::send(
330 upstream,
331 target,
332 self.endpoint.clone(),
333 self.target_request.clone(),
334 limits,
335 progress,
336 )
337 .await?
338 {
339 return Ok(transport::rejected(progress));
340 }
341 self.recover(limits, state, progress, facts).await
342 }
343 pub async fn recover<S: crate::capability::StateStore>(
345 &mut self,
346 limits: CodecLimits,
347 state: &super::GenerationStateAccess<'_, S>,
348 progress: &mut GenerationProgress<h::GenerateContentResponseBody>,
349 facts: impl FnOnce(
350 &h::GenerateContentResponseBody,
351 ) -> Result<p::ResponseSupplement, TransformError>,
352 ) -> Result<GenerationOutcome<c::GenerateContentResponseBody>, TransformError> {
353 transport::bind(
354 (&self.endpoint, &self.selected_model),
355 &self.identities,
356 (&self.original_request, &self.target_request),
357 state,
358 limits,
359 progress,
360 true,
361 )?;
362 if progress
363 .raw_response
364 .as_ref()
365 .is_none_or(|r| !r.status.is_success())
366 {
367 return Err(TransformError::invalid_result(
368 "generation.recovery",
369 "successful native response required",
370 ));
371 }
372 let native = transport::recover_native(progress, limits)?;
373 let facts = facts(&native)?;
374 let converted = self.convert_response(native, facts)?;
375 transport::finish(progress, converted, self.report.clone(), limits)
376 }
377}
378
379super::prepared::prepared_generation!(ChatViaClaude, h::GenerateContentRequestBody => c::GenerateContentRequestBody);
380super::prepared::stream_synthesis!(ChatViaClaude, h::GenerateContentRequestBody);
381super::prepared::prepared_generation!(ClaudeViaChat, c::GenerateContentRequestBody => h::GenerateContentRequestBody);
382super::prepared::stream_synthesis!(ClaudeViaChat, c::GenerateContentRequestBody);