use crate::{
transform::{Converted, Report, TransformError},
wire::{DeclaredFields, gemini::video as g, openai::video as o},
};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpenAiVideoResponseContext {
pub operation_name: String,
pub polling_url: String,
pub resources: BTreeMap<String, super::request::ResolvedVideoResource>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GeminiOperationContext {
pub source_id: String,
pub source_polling_url: String,
pub operation_name: String,
}
pub(super) fn operation_name(value: &str) -> Result<(), TransformError> {
let segments: Vec<_> = value.split('/').collect();
if segments.len() < 2
|| segments[segments.len() - 2] != "operations"
|| segments
.iter()
.any(|v| v.is_empty() || matches!(*v, "." | ".."))
|| value.contains(['?', '#', '\\'])
|| value.chars().any(char::is_control)
{
return Err(TransformError::shape(
"operation.name",
"valid operations/{id} resource required",
));
}
Ok(())
}
pub fn gemini_operation_to_openai_response(
input: g::VideoOperation,
context: &OpenAiVideoResponseContext,
) -> Result<Converted<o::VideoGenerationResponseBody>, TransformError> {
let input = input.into_declared();
let id = input
.name
.as_ref()
.filter(|v| !v.is_empty())
.ok_or_else(|| TransformError::missing_metadata("operation.name"))?;
operation_name(id)?;
if id != &context.operation_name {
return Err(TransformError::shape(
"operation.binding",
"operation name differs from registered polling context",
));
}
let polling_url = super::resources::url(&context.polling_url)?;
if input.error.is_some() && input.response.is_some() {
return Err(TransformError::invalid_result(
"operation",
"error and response cannot coexist",
));
}
if input.done != Some(true) && (input.error.is_some() || input.response.is_some()) {
return Err(TransformError::invalid_result(
"operation.done",
"result/error on pending operation",
));
}
let mut report = Report::default();
if input.metadata.is_some() {
report.omitted("metadata", "OpenRouter response lacks operation metadata");
}
let (status, error, urls) = if let Some(error) = input.error {
let message = error
.message
.filter(|v| !v.is_empty())
.or_else(|| error.code.map(|v| format!("Google RPC code {v}")))
.ok_or_else(|| TransformError::missing_metadata("operation.error message/code"))?;
if error.details.is_some() {
report.omitted("error.details", "OpenRouter exposes error text only");
}
(o::VideoStatus::Failed, Some(message), None)
} else if input.done == Some(true) {
let result = input
.response
.and_then(|r| r.generate_video_response)
.ok_or_else(|| {
TransformError::invalid_result(
"response.generateVideoResponse",
"completed operation has no video result",
)
})?;
let filtered = result.rai_media_filtered_count.unwrap_or(0);
if filtered < 0 {
return Err(TransformError::invalid_result(
"raiMediaFilteredCount",
"negative count",
));
}
let mut urls = Vec::new();
for sample in result.generated_samples.unwrap_or_default() {
let video = sample.video.ok_or_else(|| {
TransformError::invalid_result("generatedSamples.video", "sample missing video")
})?;
urls.push(super::resources::video_url(&video, &context.resources)?);
}
if filtered > 0 || result.rai_media_filtered_reasons.is_some() {
report.omitted(
"rai_filtering",
"OpenRouter has no filtered count/reason fields; surviving samples retained",
);
}
if urls.is_empty() {
if filtered > 0 {
(
o::VideoStatus::Failed,
Some(
result
.rai_media_filtered_reasons
.filter(|v| !v.is_empty())
.map(|v| v.join("; "))
.unwrap_or_else(|| format!("{filtered} video samples filtered")),
),
None,
)
} else {
return Err(TransformError::invalid_result(
"generatedSamples",
"completed operation produced no video",
));
}
} else {
(o::VideoStatus::Completed, None, Some(urls))
}
} else {
(o::VideoStatus::InProgress, None, None)
};
let mut out = o::VideoGenerationResponseBody::builder(id.clone(), polling_url, status).build();
out.error = error;
out.unsigned_urls = urls;
Ok(Converted { value: out, report })
}
pub fn openai_response_to_gemini_operation(
input: o::VideoGenerationResponseBody,
context: &GeminiOperationContext,
) -> Result<Converted<g::VideoOperation>, TransformError> {
let input = input.into_declared();
if input.id != context.source_id || input.polling_url != context.source_polling_url {
return Err(TransformError::shape(
"operation.binding",
"OpenRouter id/polling URL differ from stored invocation",
));
}
super::resources::url(&input.polling_url)?;
operation_name(&context.operation_name)?;
let mut out = g::VideoOperation::builder()
.name(context.operation_name.clone())
.build();
let mut report = Report::default();
match input.status {
o::VideoStatus::Pending | o::VideoStatus::InProgress => {
if input.error.is_some() || input.unsigned_urls.as_ref().is_some_and(|v| !v.is_empty())
{
return Err(TransformError::invalid_result(
"status",
"pending response carries terminal error/videos",
));
}
out.done = Some(false);
}
o::VideoStatus::Failed => {
if input.unsigned_urls.as_ref().is_some_and(|v| !v.is_empty()) {
return Err(TransformError::invalid_result(
"status",
"failed video carries successful URLs",
));
}
let error = input
.error
.filter(|v| !v.is_empty())
.ok_or_else(|| TransformError::missing_metadata("failed video error"))?;
out.done = Some(true);
out.error = Some(g::GoogleRpcStatus::builder().message(error).build());
}
o::VideoStatus::Completed => {
if input.error.is_some() {
return Err(TransformError::invalid_result(
"status/error",
"completed response has error",
));
}
let urls = input
.unsigned_urls
.filter(|v| !v.is_empty())
.ok_or_else(|| {
TransformError::invalid_result(
"unsigned_urls",
"completed video requires actual URLs",
)
})?;
let samples = urls
.into_iter()
.map(|url| {
super::resources::url(&url).map(|url| {
g::GeneratedVideoSample::builder()
.video(g::Video::builder().uri(url).build())
.build()
})
})
.filter_map(|value| crate::transform::optional(value).transpose())
.collect::<Result<Vec<_>, _>>()?;
out.done = Some(true);
out.response = Some(
g::VideoOperationResponse::builder()
.generate_video_response(
g::GenerateVideoResponse::builder()
.generated_samples(samples)
.build(),
)
.build(),
);
}
o::VideoStatus::Cancelled => {
if input.unsigned_urls.as_ref().is_some_and(|v| !v.is_empty()) {
return Err(TransformError::invalid_result(
"status",
"cancelled response carries successful video URLs",
));
}
out.done = Some(true);
out.error = Some(
g::GoogleRpcStatus::builder()
.code(1)
.message(
input
.error
.unwrap_or_else(|| "Video generation cancelled".into()),
)
.build(),
);
}
o::VideoStatus::Expired => {
return Err(TransformError::unsupported(
"status.expired",
"expired OpenRouter result has no identical Gemini operation lifecycle",
));
}
}
if input.generation_id.is_some() {
report.omitted(
"generation_id",
"Gemini has one operation resource identity",
);
}
if input.usage.is_some() {
report.omitted("usage", "Gemini operation lacks usage fields");
}
Ok(Converted { value: out, report })
}