Skip to main content

gproxy_protocol/adapt/video/
direct.rs

1use super::*;
2
3pub(super) fn facts(
4    resources: &BTreeMap<String, ResolvedVideoResource>,
5    limits: VideoLimits,
6) -> Result<(), TransformError> {
7    super::resources::bound_value(
8        &resources
9            .iter()
10            .map(|(key, value)| {
11                (
12                    key,
13                    &value.reference,
14                    &value.url,
15                    &value.bytes_base64_encoded,
16                    &value.mime_type,
17                )
18            })
19            .collect::<Vec<_>>(),
20        limits,
21    )?;
22    Ok(())
23}
24
25pub(super) fn check_template(
26    request: &WireRequest<()>,
27    method: http::Method,
28) -> Result<(), TransformError> {
29    if request.method != method
30        || request
31            .query
32            .as_ref()
33            .is_some_and(|query| !query.is_empty())
34        || !request.path.starts_with('/')
35        || request.path.starts_with("//")
36        || request.path.contains(['?', '#', '\\', '\r', '\n'])
37    {
38        return Err(TransformError::shape(
39            "video.template",
40            "origin-relative method-matched template required",
41        ));
42    }
43    Ok(())
44}
45
46#[allow(clippy::too_many_arguments)]
47pub async fn openai_to_gemini_create<U: Upstream>(
48    upstream: &U,
49    target: &U::Target,
50    template: WireRequest<()>,
51    input: o::CreateVideoRequestBody,
52    target_model: &str,
53    resources: BTreeMap<String, ResolvedVideoResource>,
54    context: OpenAiVideoResponseContext,
55    limits: VideoLimits,
56) -> Result<JsonInvocation<Converted<o::VideoGenerationResponseBody>>, TransformError> {
57    facts(&context.resources, limits)?;
58    facts(&resources, limits)?;
59    check_template(&template, http::Method::POST)?;
60    let input = input.into_declared();
61    super::resources::bound_value(&input, limits)?;
62    let prepared = video::openai_to_gemini_request(input, target_model, &resources)?;
63    let request = WireRequest {
64        method: template.method,
65        path: template.path,
66        query: template.query,
67        headers: template.headers,
68        body: prepared.value.body,
69    };
70    match invoke_json::<_, _, g::VideoOperation>(upstream, target, request, limits.codec).await? {
71        JsonInvocation::Rejected(response) => Ok(JsonInvocation::Rejected(response)),
72        JsonInvocation::Success(response) => Ok(JsonInvocation::Success(crate::WireResponse {
73            status: response.status,
74            headers: response.headers,
75            body: video::gemini_operation_to_openai_response(response.body, &context)?,
76        })),
77    }
78}
79
80pub async fn openai_to_gemini_query<U: Upstream>(
81    upstream: &U,
82    target: &U::Target,
83    template: WireRequest<()>,
84    context: OpenAiVideoResponseContext,
85    limits: VideoLimits,
86) -> Result<JsonInvocation<Converted<o::VideoGenerationResponseBody>>, TransformError> {
87    facts(&context.resources, limits)?;
88    check_template(&template, http::Method::GET)?;
89    let response =
90        invoke_empty::<_, g::VideoOperation>(upstream, target, template, limits.codec).await?;
91    match response {
92        JsonInvocation::Success(response) => Ok(JsonInvocation::Success(crate::WireResponse {
93            status: response.status,
94            headers: response.headers,
95            body: video::gemini_operation_to_openai_response(response.body, &context)?,
96        })),
97        JsonInvocation::Rejected(response) => Ok(JsonInvocation::Rejected(response)),
98    }
99}
100
101#[allow(clippy::too_many_arguments)]
102pub async fn gemini_to_openai_create<U: Upstream>(
103    upstream: &U,
104    target: &U::Target,
105    template: WireRequest<()>,
106    input: g::PredictLongRunningRequestBody,
107    target_model: &str,
108    resources: BTreeMap<String, ResolvedVideoResource>,
109    context: GeminiOperationContext,
110    limits: VideoLimits,
111) -> Result<JsonInvocation<Converted<g::VideoOperation>>, TransformError> {
112    facts(&resources, limits)?;
113    check_template(&template, http::Method::POST)?;
114    let input = input.into_declared();
115    super::resources::bound_value(&input, limits)?;
116    let prepared = video::gemini_to_openai_request(input, target_model, &resources)?;
117    let request = WireRequest {
118        method: template.method,
119        path: template.path,
120        query: template.query,
121        headers: template.headers,
122        body: prepared.value.body,
123    };
124    match invoke_json::<_, _, o::VideoGenerationResponseBody>(
125        upstream,
126        target,
127        request,
128        limits.codec,
129    )
130    .await?
131    {
132        JsonInvocation::Rejected(response) => Ok(JsonInvocation::Rejected(response)),
133        JsonInvocation::Success(response) => Ok(JsonInvocation::Success(crate::WireResponse {
134            status: response.status,
135            headers: response.headers,
136            body: video::openai_response_to_gemini_operation(response.body, &context)?,
137        })),
138    }
139}
140
141pub async fn gemini_to_openai_query<U: Upstream>(
142    upstream: &U,
143    target: &U::Target,
144    template: WireRequest<()>,
145    context: GeminiOperationContext,
146    limits: VideoLimits,
147) -> Result<JsonInvocation<Converted<g::VideoOperation>>, TransformError> {
148    check_template(&template, http::Method::GET)?;
149    let response =
150        invoke_empty::<_, o::VideoGenerationResponseBody>(upstream, target, template, limits.codec)
151            .await?;
152    match response {
153        JsonInvocation::Success(response) => Ok(JsonInvocation::Success(crate::WireResponse {
154            status: response.status,
155            headers: response.headers,
156            body: video::openai_response_to_gemini_operation(response.body, &context)?,
157        })),
158        JsonInvocation::Rejected(response) => Ok(JsonInvocation::Rejected(response)),
159    }
160}
161
162/// Native Sora-style create/retrieve preserve their own ID, seconds and size
163/// contract; they do not pass through the OpenRouter/Veo mapper.
164pub async fn native_create<U: Upstream>(
165    upstream: &U,
166    target: &U::Target,
167    request: WireRequest<o::NativeCreateVideoRequestBody>,
168    limits: VideoLimits,
169) -> Result<JsonInvocation<o::NativeVideo>, TransformError> {
170    if request.method != http::Method::POST
171        || request
172            .query
173            .as_ref()
174            .is_some_and(|query| !query.is_empty())
175        || !request.path.starts_with('/')
176        || request.path.starts_with("//")
177        || request.path.contains(['?', '#', '\\', '\r', '\n'])
178    {
179        return Err(TransformError::shape(
180            "video.template",
181            "origin-relative POST template required",
182        ));
183    }
184    invoke_json(upstream, target, request, limits.codec).await
185}
186
187pub async fn native_retrieve<U: Upstream>(
188    upstream: &U,
189    target: &U::Target,
190    request: WireRequest<()>,
191    limits: VideoLimits,
192) -> Result<JsonInvocation<o::NativeVideo>, TransformError> {
193    check_template(&request, http::Method::GET)?;
194    invoke_empty(upstream, target, request, limits.codec).await
195}