use super::{Endpoint, GenerationIdentity, GenerationOutcome, GenerationProgress, transport};
use crate::transform::generate::claude_chat as p;
use crate::wire::claude::generate_content as c;
use crate::wire::openai::chat as h;
use crate::{
Dialect,
capability::Upstream,
codec::CodecLimits,
transform::{Converted, Report, TransformError},
wire::DeclaredFields,
};
#[derive(Debug)]
pub struct ChatViaClaude {
original_request: h::GenerateContentRequestBody,
target_request: c::GenerateContentRequestBody,
selected_model: String,
endpoint: Endpoint,
identities: GenerationIdentity,
report: Report,
}
impl ChatViaClaude {
pub fn prepare(
input: h::GenerateContentRequestBody,
selected_model: impl Into<String>,
endpoint: Endpoint,
mut identities: GenerationIdentity,
) -> Result<Self, TransformError> {
let selected_model = selected_model.into();
if selected_model.trim().is_empty() {
return Err(TransformError::missing_metadata("selected_model"));
}
let original_request = input.into_declared();
if original_request.stream.flatten() == Some(true) {
return Err(TransformError::unsupported(
"stream",
"use the incremental invocation entrypoint",
));
}
let mut converted = p::openai_to_claude(&original_request, &selected_model)?;
super::claude_chat_ids::claude_request(
&mut converted.value,
&mut identities,
Dialect::OpenAiChat,
)?;
Ok(Self {
original_request,
target_request: converted.value.into_declared(),
selected_model,
endpoint,
identities,
report: converted.report,
})
}
pub async fn prepare_with_state<S: crate::capability::StateStore>(
input: h::GenerateContentRequestBody,
endpoint: Endpoint,
identities: GenerationIdentity,
state: &super::GenerationStateAccess<'_, S>,
) -> Result<Self, TransformError> {
let selected_model = state.target.model.clone();
let original = input.into_declared();
let (restored, names) = super::history::chat(original.clone(), state).await?;
let _ = names;
let mut prepared = Self::prepare(restored, selected_model, endpoint, identities)?;
prepared.original_request = original;
Ok(prepared)
}
pub async fn prepare_with_capabilities<
S: crate::capability::StateStore,
R: crate::capability::ResourceAccess,
>(
input: h::GenerateContentRequestBody,
endpoint: Endpoint,
identities: GenerationIdentity,
state: &super::GenerationStateAccess<'_, S>,
resources: &super::GenerationResources<'_, R>,
) -> Result<Self, TransformError> {
let original = input.into_declared();
let materialized = resources.chat(original.clone()).await?;
let mut prepared =
Self::prepare_with_state(materialized, endpoint, identities, state).await?;
prepared.original_request = original;
Ok(prepared)
}
pub fn original_request(&self) -> &h::GenerateContentRequestBody {
&self.original_request
}
pub fn target_request(&self) -> &c::GenerateContentRequestBody {
&self.target_request
}
pub fn identities(&self) -> &GenerationIdentity {
&self.identities
}
pub fn report(&self) -> &Report {
&self.report
}
pub fn selected_model(&self) -> &str {
&self.selected_model
}
pub fn convert_response(
&mut self,
native: c::GenerateContentResponseBody,
facts: p::ResponseSupplement,
) -> Result<Converted<h::GenerateContentResponseBody>, TransformError> {
let native = native.into_declared();
let mut converted = p::claude_response_to_openai(&native, &self.selected_model, &facts)?;
super::claude_chat_ids::chat_response(&mut converted.value, &mut self.identities)?;
Ok(converted)
}
pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
&mut self,
upstream: &U,
target: &U::Target,
limits: CodecLimits,
state: &super::GenerationStateAccess<'_, S>,
progress: &mut GenerationProgress<c::GenerateContentResponseBody>,
facts: impl FnOnce(
&c::GenerateContentResponseBody,
) -> Result<p::ResponseSupplement, TransformError>,
) -> Result<GenerationOutcome<h::GenerateContentResponseBody>, TransformError> {
transport::bind(
(&self.endpoint, &self.selected_model),
&self.identities,
(&self.original_request, &self.target_request),
state,
limits,
progress,
false,
)?;
if !transport::send(
upstream,
target,
self.endpoint.clone(),
self.target_request.clone(),
limits,
progress,
)
.await?
{
return Ok(transport::rejected(progress));
}
self.recover(limits, state, progress, facts).await
}
pub async fn recover<S: crate::capability::StateStore>(
&mut self,
limits: CodecLimits,
state: &super::GenerationStateAccess<'_, S>,
progress: &mut GenerationProgress<c::GenerateContentResponseBody>,
facts: impl FnOnce(
&c::GenerateContentResponseBody,
) -> Result<p::ResponseSupplement, TransformError>,
) -> Result<GenerationOutcome<h::GenerateContentResponseBody>, TransformError> {
transport::bind(
(&self.endpoint, &self.selected_model),
&self.identities,
(&self.original_request, &self.target_request),
state,
limits,
progress,
true,
)?;
if progress
.raw_response
.as_ref()
.is_none_or(|r| !r.status.is_success())
{
return Err(TransformError::invalid_result(
"generation.recovery",
"successful native response required",
));
}
let native = transport::recover_native(progress, limits)?;
let facts = facts(&native)?;
let converted = self.convert_response(native, facts)?;
transport::finish(progress, converted, self.report.clone(), limits)
}
}
#[derive(Debug)]
pub struct ClaudeViaChat {
original_request: c::GenerateContentRequestBody,
target_request: h::GenerateContentRequestBody,
selected_model: String,
endpoint: Endpoint,
identities: GenerationIdentity,
report: Report,
}
impl ClaudeViaChat {
pub fn prepare(
input: c::GenerateContentRequestBody,
selected_model: impl Into<String>,
endpoint: Endpoint,
identities: GenerationIdentity,
) -> Result<Self, TransformError> {
Self::prepare_with_replay(
input,
selected_model,
endpoint,
identities,
&Default::default(),
)
}
fn prepare_with_replay(
input: c::GenerateContentRequestBody,
selected_model: impl Into<String>,
endpoint: Endpoint,
mut identities: GenerationIdentity,
replay: &super::GenerationToolReplay,
) -> Result<Self, TransformError> {
let selected_model = selected_model.into();
if selected_model.trim().is_empty() {
return Err(TransformError::missing_metadata("selected_model"));
}
let original_request = input.into_declared();
if original_request.stream == Some(true) {
return Err(TransformError::unsupported(
"stream",
"use the incremental invocation entrypoint",
));
}
let mut converted = p::claude_to_openai(&original_request, &selected_model)?;
super::legacy_chat::restore(&mut converted.value, replay, &mut converted.report)?;
super::claude_chat_ids::chat_request(
&mut converted.value,
&mut identities,
Dialect::Claude,
)?;
Ok(Self {
original_request,
target_request: converted.value.into_declared(),
selected_model,
endpoint,
identities,
report: converted.report,
})
}
pub async fn prepare_with_state<S: crate::capability::StateStore>(
input: c::GenerateContentRequestBody,
endpoint: Endpoint,
identities: GenerationIdentity,
state: &super::GenerationStateAccess<'_, S>,
) -> Result<Self, TransformError> {
let selected_model = state.target.model.clone();
let original = input.into_declared();
let (restored, names) = super::history::claude(original.clone(), state).await?;
let mut prepared =
Self::prepare_with_replay(restored, selected_model, endpoint, identities, &names)?;
prepared.original_request = original;
Ok(prepared)
}
pub async fn prepare_with_capabilities<
S: crate::capability::StateStore,
R: crate::capability::ResourceAccess,
>(
input: c::GenerateContentRequestBody,
endpoint: Endpoint,
identities: GenerationIdentity,
state: &super::GenerationStateAccess<'_, S>,
resources: &super::GenerationResources<'_, R>,
) -> Result<Self, TransformError> {
let original = input.into_declared();
let materialized = resources.claude(original.clone()).await?;
let mut prepared =
Self::prepare_with_state(materialized, endpoint, identities, state).await?;
prepared.original_request = original;
Ok(prepared)
}
pub fn original_request(&self) -> &c::GenerateContentRequestBody {
&self.original_request
}
pub fn target_request(&self) -> &h::GenerateContentRequestBody {
&self.target_request
}
pub fn identities(&self) -> &GenerationIdentity {
&self.identities
}
pub fn report(&self) -> &Report {
&self.report
}
pub fn selected_model(&self) -> &str {
&self.selected_model
}
pub fn convert_response(
&mut self,
native: h::GenerateContentResponseBody,
facts: p::ResponseSupplement,
) -> Result<Converted<c::GenerateContentResponseBody>, TransformError> {
let native = native.into_declared();
let mut converted = p::openai_response_to_claude(&native, &self.selected_model, &facts)?;
super::claude_chat_ids::claude_response(&mut converted.value, &mut self.identities)?;
Ok(converted)
}
pub async fn invoke<U: Upstream, S: crate::capability::StateStore>(
&mut self,
upstream: &U,
target: &U::Target,
limits: CodecLimits,
state: &super::GenerationStateAccess<'_, S>,
progress: &mut GenerationProgress<h::GenerateContentResponseBody>,
facts: impl FnOnce(
&h::GenerateContentResponseBody,
) -> Result<p::ResponseSupplement, TransformError>,
) -> Result<GenerationOutcome<c::GenerateContentResponseBody>, TransformError> {
transport::bind(
(&self.endpoint, &self.selected_model),
&self.identities,
(&self.original_request, &self.target_request),
state,
limits,
progress,
false,
)?;
if !transport::send(
upstream,
target,
self.endpoint.clone(),
self.target_request.clone(),
limits,
progress,
)
.await?
{
return Ok(transport::rejected(progress));
}
self.recover(limits, state, progress, facts).await
}
pub async fn recover<S: crate::capability::StateStore>(
&mut self,
limits: CodecLimits,
state: &super::GenerationStateAccess<'_, S>,
progress: &mut GenerationProgress<h::GenerateContentResponseBody>,
facts: impl FnOnce(
&h::GenerateContentResponseBody,
) -> Result<p::ResponseSupplement, TransformError>,
) -> Result<GenerationOutcome<c::GenerateContentResponseBody>, TransformError> {
transport::bind(
(&self.endpoint, &self.selected_model),
&self.identities,
(&self.original_request, &self.target_request),
state,
limits,
progress,
true,
)?;
if progress
.raw_response
.as_ref()
.is_none_or(|r| !r.status.is_success())
{
return Err(TransformError::invalid_result(
"generation.recovery",
"successful native response required",
));
}
let native = transport::recover_native(progress, limits)?;
let facts = facts(&native)?;
let converted = self.convert_response(native, facts)?;
transport::finish(progress, converted, self.report.clone(), limits)
}
}
mod synthesis;