refactor: 移除独立 hub/proxy/executor/gateway crate,统一为 gateway tunnel 架构

- 删除 aether-hub、aether-proxy 独立项目及其 Dockerfile/配置
- 删除 crates/aether-executor 和 crates/aether-gateway 全部模块
- 新增 apps/ 目录作为应用入口
- 将 hub 概念重构为 gateway tunnel transport
- 将 executor 重构为 execution runtime
- 新增 tunnel.rs 合约定义和 testkit tunnel/execution_runtime 模块
- 更新 Python 服务层和测试适配新架构命名
This commit is contained in:
fawney19
2026-04-03 14:59:58 +08:00
parent ddf18fed9a
commit 8f26e1a31f
983 changed files with 103098 additions and 105837 deletions

View File

@@ -0,0 +1,25 @@
use super::*;
mod body;
mod path;
mod transport;
mod util;
pub(crate) use self::path::{
extract_gemini_short_id_from_cancel_path, extract_gemini_short_id_from_path,
extract_openai_task_id_from_cancel_path, extract_openai_task_id_from_content_path,
extract_openai_task_id_from_path, extract_openai_task_id_from_remix_path,
};
pub(crate) use self::body::{
context_text, context_u64, request_body_string, request_body_text, request_body_u32,
};
pub(crate) use self::path::{
current_unix_timestamp_secs, generate_local_short_id, local_status_from_stored,
resolve_local_video_registry_mutation,
};
pub(crate) use self::transport::{
build_video_follow_up_report_context, gemini_metadata_video_url, map_openai_task_status,
parse_video_content_variant, resolve_follow_up_auth,
};
pub(crate) use self::util::non_empty_owned;

View File

@@ -0,0 +1,53 @@
use super::*;
pub(crate) fn context_text(context: &Map<String, Value>, key: &str) -> Option<String> {
context
.get(key)
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
}
pub(crate) fn context_u64(context: &Map<String, Value>, key: &str) -> Option<u64> {
let value = context.get(key)?;
match value {
Value::Number(number) => number.as_u64(),
Value::String(text) => text.trim().parse().ok(),
_ => None,
}
}
pub(crate) fn request_body_text(context: &Map<String, Value>, key: &str) -> Option<String> {
context
.get("original_request_body")
.and_then(Value::as_object)
.and_then(|body| body.get(key))
.and_then(|value| match value {
Value::String(text) => Some(text.trim().to_string()),
Value::Number(number) => Some(number.to_string()),
_ => None,
})
.filter(|value| !value.is_empty())
}
pub(crate) fn request_body_string(body: &Value, key: &str) -> Option<String> {
body.as_object()
.and_then(|map| map.get(key))
.and_then(|value| match value {
Value::String(text) => Some(text.trim().to_string()),
Value::Number(number) => Some(number.to_string()),
_ => None,
})
.filter(|value| !value.is_empty())
}
pub(crate) fn request_body_u32(body: &Value, key: &str) -> Option<u32> {
body.as_object()
.and_then(|map| map.get(key))
.and_then(|value| match value {
Value::Number(number) => number.as_u64().and_then(|value| u32::try_from(value).ok()),
Value::String(text) => text.trim().parse().ok(),
_ => None,
})
}

View File

@@ -0,0 +1,116 @@
use super::*;
pub(crate) fn extract_openai_task_id_from_path(path: &str) -> Option<&str> {
let suffix = path.strip_prefix("/v1/videos/")?;
if suffix.is_empty()
|| suffix.contains('/')
|| suffix.ends_with(":cancel")
|| suffix.ends_with(":delete")
{
return None;
}
Some(suffix)
}
pub(crate) fn extract_gemini_short_id_from_path(path: &str) -> Option<&str> {
let operations_index = path.find("/operations/")?;
let suffix = &path[(operations_index + "/operations/".len())..];
if suffix.is_empty() || suffix.contains('/') || suffix.ends_with(":cancel") {
return None;
}
Some(suffix)
}
pub(crate) fn extract_openai_task_id_from_cancel_path(path: &str) -> Option<&str> {
let suffix = path.strip_prefix("/v1/videos/")?;
suffix
.strip_suffix("/cancel")
.filter(|value| !value.is_empty())
}
pub(crate) fn extract_openai_task_id_from_remix_path(path: &str) -> Option<&str> {
let suffix = path.strip_prefix("/v1/videos/")?;
suffix
.strip_suffix("/remix")
.filter(|value| !value.is_empty())
}
pub(crate) fn extract_openai_task_id_from_content_path(path: &str) -> Option<&str> {
let suffix = path.strip_prefix("/v1/videos/")?;
suffix
.strip_suffix("/content")
.filter(|value| !value.is_empty())
}
pub(crate) fn extract_gemini_short_id_from_cancel_path(path: &str) -> Option<&str> {
let operations_index = path.find("/operations/")?;
let suffix = &path[(operations_index + "/operations/".len())..];
let short_id = suffix.strip_suffix(":cancel")?;
if short_id.is_empty() || short_id.contains('/') {
return None;
}
Some(short_id)
}
pub(crate) fn resolve_local_video_registry_mutation(
truth_source_mode: VideoTaskTruthSourceMode,
request_path: &str,
report_kind: &str,
) -> Option<LocalVideoTaskRegistryMutation> {
if truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
match report_kind {
"openai_video_delete_sync_finalize" => {
let task_id = extract_openai_task_id_from_path(request_path)?;
Some(LocalVideoTaskRegistryMutation::OpenAiDeleted {
task_id: task_id.to_string(),
})
}
"openai_video_cancel_sync_finalize" => {
let task_id = extract_openai_task_id_from_cancel_path(request_path)?;
Some(LocalVideoTaskRegistryMutation::OpenAiCancelled {
task_id: task_id.to_string(),
})
}
"gemini_video_cancel_sync_finalize" => {
let short_id = extract_gemini_short_id_from_cancel_path(request_path)?;
Some(LocalVideoTaskRegistryMutation::GeminiCancelled {
short_id: short_id.to_string(),
})
}
_ => None,
}
}
pub(crate) fn current_unix_timestamp_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
pub(crate) fn local_status_from_stored(status: StoredVideoTaskStatus) -> LocalVideoTaskStatus {
match status {
StoredVideoTaskStatus::Pending | StoredVideoTaskStatus::Submitted => {
LocalVideoTaskStatus::Submitted
}
StoredVideoTaskStatus::Queued => LocalVideoTaskStatus::Queued,
StoredVideoTaskStatus::Processing => LocalVideoTaskStatus::Processing,
StoredVideoTaskStatus::Completed => LocalVideoTaskStatus::Completed,
StoredVideoTaskStatus::Failed => LocalVideoTaskStatus::Failed,
StoredVideoTaskStatus::Cancelled => LocalVideoTaskStatus::Cancelled,
StoredVideoTaskStatus::Expired => LocalVideoTaskStatus::Expired,
StoredVideoTaskStatus::Deleted => LocalVideoTaskStatus::Deleted,
}
}
pub(crate) fn generate_local_short_id() -> String {
Uuid::new_v4()
.simple()
.to_string()
.chars()
.take(12)
.collect()
}

View File

@@ -0,0 +1,237 @@
use super::*;
impl LocalVideoTaskTransport {
pub(crate) fn from_plan(plan: &ExecutionPlan) -> Option<Self> {
let upstream_base_url = match plan.provider_api_format.as_str() {
"openai:video" => plan.url.split("/v1/videos").next()?.to_string(),
"gemini:video" => plan.url.split("/v1beta/").next()?.to_string(),
_ => return None,
};
if upstream_base_url.is_empty() {
return None;
}
Some(Self {
upstream_base_url,
provider_name: plan.provider_name.clone(),
provider_id: plan.provider_id.clone(),
endpoint_id: plan.endpoint_id.clone(),
key_id: plan.key_id.clone(),
headers: plan.headers.clone(),
content_type: plan.content_type.clone(),
model_name: plan.model_name.clone(),
proxy: plan.proxy.clone(),
tls_profile: plan.tls_profile.clone(),
timeouts: plan.timeouts.clone(),
})
}
pub(crate) fn from_provider_transport(
transport: &GatewayProviderTransportSnapshot,
api_format: &str,
model_name: Option<String>,
) -> Option<Self> {
let (auth_header, auth_value) = match api_format {
"openai:video" => {
if !supports_local_standard_transport(transport, api_format) {
return None;
}
resolve_local_standard_auth(transport)?
}
"gemini:video" => {
if !supports_local_gemini_transport(transport, api_format) {
return None;
}
resolve_local_gemini_auth(transport)?
}
_ => return None,
};
let mut headers = BTreeMap::new();
headers.insert(auth_header, auth_value);
Some(Self {
upstream_base_url: transport.endpoint.base_url.clone(),
provider_name: Some(transport.provider.name.clone()),
provider_id: transport.provider.id.clone(),
endpoint_id: transport.endpoint.id.clone(),
key_id: transport.key.id.clone(),
headers,
content_type: Some("application/json".to_string()),
model_name,
proxy: None,
tls_profile: None,
timeouts: resolve_transport_execution_timeouts(transport),
})
}
}
impl LocalVideoTaskPersistence {
pub(crate) fn from_report_context(
report_context: &Map<String, Value>,
plan: &ExecutionPlan,
) -> Self {
Self {
request_id: context_text(report_context, "request_id")
.unwrap_or_else(|| plan.request_id.clone()),
username: context_text(report_context, "username"),
api_key_name: context_text(report_context, "api_key_name"),
client_api_format: context_text(report_context, "client_api_format")
.unwrap_or_else(|| plan.client_api_format.clone()),
provider_api_format: context_text(report_context, "provider_api_format")
.unwrap_or_else(|| plan.provider_api_format.clone()),
original_request_body: report_context
.get("original_request_body")
.cloned()
.unwrap_or_else(|| Value::Object(Map::new())),
format_converted: report_context
.get("format_converted")
.and_then(Value::as_bool)
.unwrap_or(false),
}
}
pub(crate) fn from_stored_task(task: &StoredVideoTask) -> Option<Self> {
let client_api_format = non_empty_owned(task.client_api_format.as_ref())
.or_else(|| non_empty_owned(task.provider_api_format.as_ref()))?;
let provider_api_format = non_empty_owned(task.provider_api_format.as_ref())
.or_else(|| non_empty_owned(task.client_api_format.as_ref()))?;
Some(Self {
request_id: task.request_id.clone(),
username: task.username.clone(),
api_key_name: task.api_key_name.clone(),
client_api_format,
provider_api_format,
original_request_body: task
.original_request_body
.clone()
.unwrap_or_else(|| Value::Object(Map::new())),
format_converted: task.format_converted,
})
}
}
impl LocalVideoTaskStatus {
pub(crate) fn as_database_status(self) -> StoredVideoTaskStatus {
match self {
Self::Submitted => StoredVideoTaskStatus::Submitted,
Self::Queued => StoredVideoTaskStatus::Queued,
Self::Processing => StoredVideoTaskStatus::Processing,
Self::Completed => StoredVideoTaskStatus::Completed,
Self::Failed => StoredVideoTaskStatus::Failed,
Self::Cancelled => StoredVideoTaskStatus::Cancelled,
Self::Expired => StoredVideoTaskStatus::Expired,
Self::Deleted => StoredVideoTaskStatus::Deleted,
}
}
}
pub(crate) fn parse_video_content_variant(query_string: Option<&str>) -> Option<&'static str> {
let mut variant = "video";
if let Some(query_string) = query_string {
for (key, value) in url::form_urlencoded::parse(query_string.as_bytes()) {
if key == "variant" {
variant = match value.as_ref() {
"video" => "video",
"thumbnail" => "thumbnail",
"spritesheet" => "spritesheet",
_ => return None,
};
}
}
}
Some(variant)
}
pub(crate) fn gemini_metadata_video_url(metadata: &Value) -> Option<String> {
metadata
.get("response")
.and_then(|value| value.get("generateVideoResponse"))
.and_then(|value| value.get("generatedSamples"))
.and_then(Value::as_array)
.and_then(|value| value.first())
.and_then(|value| value.get("video"))
.and_then(|value| value.get("uri"))
.and_then(Value::as_str)
.map(str::to_string)
}
pub(crate) fn build_video_follow_up_report_context(
request_id: &str,
user_id: &str,
api_key_id: &str,
task_id: &str,
model_name: Option<String>,
transport: &LocalVideoTaskTransport,
client_api_format: &str,
provider_api_format: &str,
) -> Value {
let mut context = Map::new();
context.insert(
"request_id".to_string(),
Value::String(request_id.to_string()),
);
context.insert("user_id".to_string(), Value::String(user_id.to_string()));
context.insert(
"api_key_id".to_string(),
Value::String(api_key_id.to_string()),
);
context.insert("task_id".to_string(), Value::String(task_id.to_string()));
context.insert(
"provider_id".to_string(),
Value::String(transport.provider_id.clone()),
);
context.insert(
"endpoint_id".to_string(),
Value::String(transport.endpoint_id.clone()),
);
context.insert(
"key_id".to_string(),
Value::String(transport.key_id.clone()),
);
context.insert(
"client_api_format".to_string(),
Value::String(client_api_format.to_string()),
);
context.insert(
"provider_api_format".to_string(),
Value::String(provider_api_format.to_string()),
);
if let Some(provider_name) = transport.provider_name.clone() {
context.insert("provider_name".to_string(), Value::String(provider_name));
}
if let Some(model_name) = model_name.filter(|value| !value.is_empty()) {
context.insert("model".to_string(), Value::String(model_name));
}
Value::Object(context)
}
pub(crate) fn map_openai_task_status(status: LocalVideoTaskStatus) -> &'static str {
match status {
LocalVideoTaskStatus::Submitted | LocalVideoTaskStatus::Queued => "queued",
LocalVideoTaskStatus::Processing => "processing",
LocalVideoTaskStatus::Completed => "completed",
LocalVideoTaskStatus::Failed
| LocalVideoTaskStatus::Cancelled
| LocalVideoTaskStatus::Expired => "failed",
LocalVideoTaskStatus::Deleted => "deleted",
}
}
pub(crate) fn resolve_follow_up_auth(
user_id: Option<&str>,
api_key_id: Option<&str>,
auth_context: Option<&GatewayControlAuthContext>,
) -> Option<(String, String)> {
let resolved_user_id = user_id
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
.or_else(|| auth_context.map(|value| value.user_id.clone()))?;
let resolved_api_key_id = api_key_id
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
.or_else(|| auth_context.map(|value| value.api_key_id.clone()))?;
Some((resolved_user_id, resolved_api_key_id))
}

View File

@@ -0,0 +1,7 @@
pub(crate) fn non_empty_owned(value: Option<&String>) -> Option<String> {
value
.map(String::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
}

View File

@@ -0,0 +1,57 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, ProxySnapshot, RequestBody};
use aether_data::repository::video_tasks::{
StoredVideoTask, UpsertVideoTask, VideoTaskStatus as StoredVideoTaskStatus,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
use uuid::Uuid;
use crate::gateway::provider_transport::{
resolve_local_gemini_auth, resolve_local_standard_auth, resolve_transport_execution_timeouts,
supports_local_gemini_transport, supports_local_standard_transport,
GatewayProviderTransportSnapshot,
};
use crate::gateway::GatewayControlAuthContext;
const DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS: u32 = 10;
const DEFAULT_VIDEO_TASK_MAX_POLL_COUNT: u32 = 360;
pub(crate) use self::service::VideoTaskService;
pub use self::types::VideoTaskTruthSourceMode;
pub(crate) use self::types::{
GeminiVideoTaskSeed, LocalVideoTaskContentAction, LocalVideoTaskFollowUpPlan,
LocalVideoTaskPersistence, LocalVideoTaskReadRefreshPlan, LocalVideoTaskReadResponse,
LocalVideoTaskRegistryMutation, LocalVideoTaskSeed, LocalVideoTaskSnapshot,
LocalVideoTaskStatus, LocalVideoTaskSuccessPlan, LocalVideoTaskTransport, OpenAiVideoTaskSeed,
VideoTaskSyncReportMode,
};
pub(crate) use self::helpers::{
extract_gemini_short_id_from_cancel_path, extract_gemini_short_id_from_path,
extract_openai_task_id_from_cancel_path, extract_openai_task_id_from_content_path,
extract_openai_task_id_from_path, extract_openai_task_id_from_remix_path,
};
use self::helpers::{
build_video_follow_up_report_context, context_text, context_u64, current_unix_timestamp_secs,
gemini_metadata_video_url, generate_local_short_id, local_status_from_stored,
map_openai_task_status, non_empty_owned, parse_video_content_variant, request_body_string,
request_body_text, request_body_u32, resolve_follow_up_auth,
resolve_local_video_registry_mutation,
};
use self::store::{FileVideoTaskStore, InMemoryVideoTaskStore, VideoTaskStore};
use self::types::LocalVideoTaskProjectionTarget;
mod helpers;
mod seed;
mod service;
mod store;
mod types;
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,6 @@
use super::*;
mod gemini;
mod openai;
mod snapshot;
mod sync;

View File

@@ -0,0 +1,271 @@
use super::*;
impl GeminiVideoTaskSeed {
pub(crate) fn apply_provider_body(&mut self, provider_body: &Map<String, Value>) {
let done = provider_body
.get("done")
.and_then(Value::as_bool)
.unwrap_or(false);
if done {
let error = provider_body.get("error").and_then(Value::as_object);
if let Some(error) = error {
self.status = LocalVideoTaskStatus::Failed;
self.progress_percent = 100;
self.error_code = error
.get("code")
.and_then(Value::as_str)
.map(str::to_string);
self.error_message = error
.get("message")
.and_then(Value::as_str)
.map(str::to_string);
} else {
self.status = LocalVideoTaskStatus::Completed;
self.progress_percent = 100;
self.error_code = None;
self.error_message = None;
}
self.metadata = json!({});
return;
}
self.status = LocalVideoTaskStatus::Processing;
self.progress_percent = 50;
self.error_code = None;
self.error_message = None;
self.metadata = provider_body
.get("metadata")
.cloned()
.unwrap_or_else(|| json!({}));
}
pub(crate) fn build_get_follow_up_plan(&self, trace_id: &str) -> Option<ExecutionPlan> {
if !matches!(
self.status,
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing
) {
return None;
}
let operation_path = self.resolve_operation_path()?;
let mut headers = self.transport.headers.clone();
headers.remove("content-type");
headers.remove("content-length");
Some(ExecutionPlan {
request_id: trace_id.to_string(),
candidate_id: None,
provider_name: self.transport.provider_name.clone(),
provider_id: self.transport.provider_id.clone(),
endpoint_id: self.transport.endpoint_id.clone(),
key_id: self.transport.key_id.clone(),
method: "GET".to_string(),
url: format!(
"{}/v1beta/{}",
self.transport.upstream_base_url.trim_end_matches('/'),
operation_path
),
headers,
content_type: None,
content_encoding: None,
body: RequestBody {
json_body: None,
body_bytes_b64: None,
body_ref: None,
},
stream: false,
client_api_format: "gemini:video".to_string(),
provider_api_format: "gemini:video".to_string(),
model_name: Some(self.model.clone()),
proxy: self.transport.proxy.clone(),
tls_profile: self.transport.tls_profile.clone(),
timeouts: self.transport.timeouts.clone(),
})
}
fn resolve_operation_path(&self) -> Option<String> {
if self.upstream_operation_name.starts_with("models/") {
Some(self.upstream_operation_name.clone())
} else if self.upstream_operation_name.starts_with("operations/") && !self.model.is_empty()
{
Some(format!(
"models/{}/{}",
self.model, self.upstream_operation_name
))
} else {
None
}
}
pub(crate) fn build_cancel_follow_up_plan(
&self,
auth_context: Option<&GatewayControlAuthContext>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
if !matches!(
self.status,
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing
) {
return None;
}
let (user_id, api_key_id) = resolve_follow_up_auth(
self.user_id.as_deref(),
self.api_key_id.as_deref(),
auth_context,
)?;
let operation_path = self.resolve_operation_path()?;
let mut headers = self.transport.headers.clone();
let content_type = self
.transport
.content_type
.clone()
.unwrap_or_else(|| "application/json".to_string());
headers
.entry("content-type".to_string())
.or_insert_with(|| content_type.clone());
Some(LocalVideoTaskFollowUpPlan {
plan: ExecutionPlan {
request_id: trace_id.to_string(),
candidate_id: None,
provider_name: self.transport.provider_name.clone(),
provider_id: self.transport.provider_id.clone(),
endpoint_id: self.transport.endpoint_id.clone(),
key_id: self.transport.key_id.clone(),
method: "POST".to_string(),
url: format!(
"{}/v1beta/{}:cancel",
self.transport.upstream_base_url.trim_end_matches('/'),
operation_path
),
headers,
content_type: Some(content_type),
content_encoding: None,
body: RequestBody::from_json(json!({})),
stream: false,
client_api_format: "gemini:video".to_string(),
provider_api_format: "gemini:video".to_string(),
model_name: Some(self.model.clone()),
proxy: self.transport.proxy.clone(),
tls_profile: self.transport.tls_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
report_kind: Some("gemini_video_cancel_sync_finalize".to_string()),
report_context: Some(build_video_follow_up_report_context(
&self.persistence.request_id,
&user_id,
&api_key_id,
&self.local_short_id,
Some(self.model.clone()),
&self.transport,
"gemini:video",
"gemini:video",
)),
})
}
pub(crate) fn client_body_json(&self) -> Value {
let operation_name = format!("models/{}/operations/{}", self.model, self.local_short_id);
match self.status {
LocalVideoTaskStatus::Completed => json!({
"name": operation_name,
"done": true,
"response": {
"generateVideoResponse": {
"generatedSamples": [
{
"video": {
"uri": format!("/v1beta/files/aev_{}:download?alt=media", self.local_short_id),
"mimeType": "video/mp4"
}
}
]
}
}
}),
LocalVideoTaskStatus::Failed | LocalVideoTaskStatus::Expired => json!({
"name": operation_name,
"done": true,
"error": {
"code": self.error_code.clone().unwrap_or_else(|| "UNKNOWN".to_string()),
"message": self
.error_message
.clone()
.unwrap_or_else(|| "Video generation failed".to_string()),
}
}),
_ => json!({
"name": operation_name,
"done": false,
"metadata": self.metadata.clone(),
}),
}
}
pub(crate) fn to_upsert_record(&self) -> UpsertVideoTask {
let now_unix_secs = current_unix_timestamp_secs();
let next_poll_at_unix_secs = match self.status {
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing => Some(
now_unix_secs.saturating_add(u64::from(DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS)),
),
_ => None,
};
UpsertVideoTask {
id: self.local_short_id.clone(),
short_id: Some(self.local_short_id.clone()),
request_id: self.persistence.request_id.clone(),
user_id: self.user_id.clone(),
api_key_id: self.api_key_id.clone(),
username: self.persistence.username.clone(),
api_key_name: self.persistence.api_key_name.clone(),
external_task_id: Some(self.upstream_operation_name.clone()),
provider_id: Some(self.transport.provider_id.clone()),
endpoint_id: Some(self.transport.endpoint_id.clone()),
key_id: Some(self.transport.key_id.clone()),
client_api_format: Some(self.persistence.client_api_format.clone()),
provider_api_format: Some(self.persistence.provider_api_format.clone()),
format_converted: self.persistence.format_converted,
model: Some(self.model.clone()),
prompt: request_body_string(&self.persistence.original_request_body, "prompt")
.or_else(|| Some(String::new())),
original_request_body: Some(self.persistence.original_request_body.clone()),
duration_seconds: request_body_u32(&self.persistence.original_request_body, "seconds")
.or_else(|| {
request_body_u32(&self.persistence.original_request_body, "duration_seconds")
}),
resolution: request_body_string(&self.persistence.original_request_body, "resolution"),
aspect_ratio: request_body_string(
&self.persistence.original_request_body,
"aspect_ratio",
),
size: request_body_string(&self.persistence.original_request_body, "size"),
status: self.status.as_database_status(),
progress_percent: self.progress_percent,
progress_message: None,
retry_count: 0,
poll_interval_seconds: DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS,
next_poll_at_unix_secs,
poll_count: 0,
max_poll_count: DEFAULT_VIDEO_TASK_MAX_POLL_COUNT,
created_at_unix_secs: now_unix_secs,
submitted_at_unix_secs: Some(now_unix_secs),
completed_at_unix_secs: None,
updated_at_unix_secs: now_unix_secs,
error_code: self.error_code.clone(),
error_message: self.error_message.clone(),
video_url: gemini_metadata_video_url(&self.metadata),
request_metadata: Some(json!({
"rust_owner": "async_task",
"rust_local_snapshot": LocalVideoTaskSnapshot::Gemini(self.clone()),
})),
}
}
}

View File

@@ -0,0 +1,6 @@
use super::*;
mod client;
mod follow_up;
mod persist;
mod provider;

View File

@@ -0,0 +1,160 @@
use super::*;
impl OpenAiVideoTaskSeed {
pub(crate) fn build_content_stream_action(
&self,
query_string: Option<&str>,
trace_id: &str,
) -> Option<LocalVideoTaskContentAction> {
match self.status {
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing => {
return Some(LocalVideoTaskContentAction::Immediate {
status_code: 202,
body_json: json!({
"detail": format!(
"Video is still processing (status: {})",
map_openai_task_status(self.status)
)
}),
});
}
LocalVideoTaskStatus::Failed | LocalVideoTaskStatus::Expired => {
return Some(LocalVideoTaskContentAction::Immediate {
status_code: 422,
body_json: json!({
"detail": format!(
"Video generation failed: {}",
self.error_message
.clone()
.unwrap_or_else(|| "Unknown error".to_string())
)
}),
});
}
LocalVideoTaskStatus::Cancelled => {
return Some(LocalVideoTaskContentAction::Immediate {
status_code: 404,
body_json: json!({"detail": "Video task was cancelled"}),
});
}
LocalVideoTaskStatus::Deleted => {
return Some(LocalVideoTaskContentAction::Immediate {
status_code: 404,
body_json: json!({"detail": "Video task not found"}),
});
}
LocalVideoTaskStatus::Completed => {}
}
let variant = parse_video_content_variant(query_string)?;
let (url, headers) = if variant == "video" {
if let Some(video_url) = self
.video_url
.clone()
.filter(|value| value.starts_with("http://") || value.starts_with("https://"))
{
(video_url, BTreeMap::new())
} else {
let mut headers = self.transport.headers.clone();
headers.remove("content-type");
headers.remove("content-length");
(
format!(
"{}/v1/videos/{}/content",
self.transport.upstream_base_url.trim_end_matches('/'),
self.upstream_task_id
),
headers,
)
}
} else {
let mut headers = self.transport.headers.clone();
headers.remove("content-type");
headers.remove("content-length");
(
format!(
"{}/v1/videos/{}/content?variant={variant}",
self.transport.upstream_base_url.trim_end_matches('/'),
self.upstream_task_id
),
headers,
)
};
Some(LocalVideoTaskContentAction::StreamPlan(ExecutionPlan {
request_id: trace_id.to_string(),
candidate_id: None,
provider_name: self.transport.provider_name.clone(),
provider_id: self.transport.provider_id.clone(),
endpoint_id: self.transport.endpoint_id.clone(),
key_id: self.transport.key_id.clone(),
method: "GET".to_string(),
url,
headers,
content_type: None,
content_encoding: None,
body: RequestBody {
json_body: None,
body_bytes_b64: None,
body_ref: None,
},
stream: true,
client_api_format: "openai:video".to_string(),
provider_api_format: "openai:video".to_string(),
model_name: self
.model
.clone()
.or_else(|| self.transport.model_name.clone()),
proxy: self.transport.proxy.clone(),
tls_profile: self.transport.tls_profile.clone(),
timeouts: self.transport.timeouts.clone(),
}))
}
pub(crate) fn client_body_json(&self) -> Value {
let mut body = json!({
"id": self.local_task_id,
"object": "video",
"status": map_openai_task_status(self.status),
"progress": self.progress_percent,
"created_at": self.created_at_unix_secs,
});
if let Some(model) = &self.model {
body["model"] = Value::String(model.clone());
}
if let Some(prompt) = &self.prompt {
body["prompt"] = Value::String(prompt.clone());
}
if let Some(size) = &self.size {
body["size"] = Value::String(size.clone());
}
if let Some(seconds) = &self.seconds {
body["seconds"] = Value::String(seconds.clone());
}
if let Some(remixed_from_video_id) = &self.remixed_from_video_id {
body["remixed_from_video_id"] = Value::String(remixed_from_video_id.clone());
}
if let Some(completed_at) = self.completed_at_unix_secs {
body["completed_at"] = Value::Number(completed_at.into());
}
if let Some(expires_at) = self.expires_at_unix_secs {
body["expires_at"] = Value::Number(expires_at.into());
}
if self.status == LocalVideoTaskStatus::Failed
|| self.status == LocalVideoTaskStatus::Expired
{
body["error"] = json!({
"code": self.error_code.clone().unwrap_or_else(|| "unknown".to_string()),
"message": self
.error_message
.clone()
.unwrap_or_else(|| "Video generation failed".to_string()),
});
}
body
}
}

View File

@@ -0,0 +1,272 @@
use super::*;
impl OpenAiVideoTaskSeed {
pub(crate) fn build_delete_follow_up_plan(
&self,
auth_context: Option<&GatewayControlAuthContext>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
if !matches!(
self.status,
LocalVideoTaskStatus::Completed | LocalVideoTaskStatus::Failed
) {
return None;
}
let (user_id, api_key_id) = resolve_follow_up_auth(
self.user_id.as_deref(),
self.api_key_id.as_deref(),
auth_context,
)?;
let mut headers = self.transport.headers.clone();
headers.remove("content-type");
headers.remove("content-length");
Some(LocalVideoTaskFollowUpPlan {
plan: ExecutionPlan {
request_id: trace_id.to_string(),
candidate_id: None,
provider_name: self.transport.provider_name.clone(),
provider_id: self.transport.provider_id.clone(),
endpoint_id: self.transport.endpoint_id.clone(),
key_id: self.transport.key_id.clone(),
method: "DELETE".to_string(),
url: format!(
"{}/v1/videos/{}",
self.transport.upstream_base_url.trim_end_matches('/'),
self.upstream_task_id
),
headers,
content_type: None,
content_encoding: None,
body: RequestBody {
json_body: None,
body_bytes_b64: None,
body_ref: None,
},
stream: false,
client_api_format: "openai:video".to_string(),
provider_api_format: "openai:video".to_string(),
model_name: self
.model
.clone()
.or_else(|| self.transport.model_name.clone()),
proxy: self.transport.proxy.clone(),
tls_profile: self.transport.tls_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
report_kind: Some("openai_video_delete_sync_finalize".to_string()),
report_context: Some(build_video_follow_up_report_context(
&self.persistence.request_id,
&user_id,
&api_key_id,
&self.local_task_id,
self.model
.clone()
.or_else(|| self.transport.model_name.clone()),
&self.transport,
"openai:video",
"openai:video",
)),
})
}
pub(crate) fn build_get_follow_up_plan(&self, trace_id: &str) -> Option<ExecutionPlan> {
if !matches!(
self.status,
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing
) {
return None;
}
let mut headers = self.transport.headers.clone();
headers.remove("content-type");
headers.remove("content-length");
Some(ExecutionPlan {
request_id: trace_id.to_string(),
candidate_id: None,
provider_name: self.transport.provider_name.clone(),
provider_id: self.transport.provider_id.clone(),
endpoint_id: self.transport.endpoint_id.clone(),
key_id: self.transport.key_id.clone(),
method: "GET".to_string(),
url: format!(
"{}/v1/videos/{}",
self.transport.upstream_base_url.trim_end_matches('/'),
self.upstream_task_id
),
headers,
content_type: None,
content_encoding: None,
body: RequestBody {
json_body: None,
body_bytes_b64: None,
body_ref: None,
},
stream: false,
client_api_format: "openai:video".to_string(),
provider_api_format: "openai:video".to_string(),
model_name: self
.model
.clone()
.or_else(|| self.transport.model_name.clone()),
proxy: self.transport.proxy.clone(),
tls_profile: self.transport.tls_profile.clone(),
timeouts: self.transport.timeouts.clone(),
})
}
pub(crate) fn build_cancel_follow_up_plan(
&self,
auth_context: Option<&GatewayControlAuthContext>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
if !matches!(
self.status,
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing
) {
return None;
}
let (user_id, api_key_id) = resolve_follow_up_auth(
self.user_id.as_deref(),
self.api_key_id.as_deref(),
auth_context,
)?;
let mut headers = self.transport.headers.clone();
headers.remove("content-type");
headers.remove("content-length");
Some(LocalVideoTaskFollowUpPlan {
plan: ExecutionPlan {
request_id: trace_id.to_string(),
candidate_id: None,
provider_name: self.transport.provider_name.clone(),
provider_id: self.transport.provider_id.clone(),
endpoint_id: self.transport.endpoint_id.clone(),
key_id: self.transport.key_id.clone(),
method: "DELETE".to_string(),
url: format!(
"{}/v1/videos/{}",
self.transport.upstream_base_url.trim_end_matches('/'),
self.upstream_task_id
),
headers,
content_type: None,
content_encoding: None,
body: RequestBody {
json_body: None,
body_bytes_b64: None,
body_ref: None,
},
stream: false,
client_api_format: "openai:video".to_string(),
provider_api_format: "openai:video".to_string(),
model_name: self
.model
.clone()
.or_else(|| self.transport.model_name.clone()),
proxy: self.transport.proxy.clone(),
tls_profile: self.transport.tls_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
report_kind: Some("openai_video_cancel_sync_finalize".to_string()),
report_context: Some(build_video_follow_up_report_context(
&self.persistence.request_id,
&user_id,
&api_key_id,
&self.local_task_id,
self.model
.clone()
.or_else(|| self.transport.model_name.clone()),
&self.transport,
"openai:video",
"openai:video",
)),
})
}
pub(crate) fn build_remix_follow_up_plan(
&self,
body_json: &Value,
auth_context: Option<&GatewayControlAuthContext>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
if !matches!(self.status, LocalVideoTaskStatus::Completed) {
return None;
}
if body_json.is_null() {
return None;
}
let (user_id, api_key_id) = resolve_follow_up_auth(
self.user_id.as_deref(),
self.api_key_id.as_deref(),
auth_context,
)?;
let mut headers = self.transport.headers.clone();
headers.remove("content-length");
let content_type = self
.transport
.content_type
.clone()
.unwrap_or_else(|| "application/json".to_string());
headers
.entry("content-type".to_string())
.or_insert_with(|| content_type.clone());
let mut report_context = build_video_follow_up_report_context(
&self.persistence.request_id,
&user_id,
&api_key_id,
&self.local_task_id,
self.model
.clone()
.or_else(|| self.transport.model_name.clone()),
&self.transport,
"openai:video",
"openai:video",
);
if let Some(report_context_object) = report_context.as_object_mut() {
report_context_object.insert("original_request_body".to_string(), body_json.clone());
}
Some(LocalVideoTaskFollowUpPlan {
plan: ExecutionPlan {
request_id: trace_id.to_string(),
candidate_id: None,
provider_name: self.transport.provider_name.clone(),
provider_id: self.transport.provider_id.clone(),
endpoint_id: self.transport.endpoint_id.clone(),
key_id: self.transport.key_id.clone(),
method: "POST".to_string(),
url: format!(
"{}/v1/videos/{}/remix",
self.transport.upstream_base_url.trim_end_matches('/'),
self.upstream_task_id
),
headers,
content_type: Some(content_type),
content_encoding: None,
body: RequestBody::from_json(body_json.clone()),
stream: false,
client_api_format: "openai:video".to_string(),
provider_api_format: "openai:video".to_string(),
model_name: self
.model
.clone()
.or_else(|| self.transport.model_name.clone()),
proxy: self.transport.proxy.clone(),
tls_profile: self.transport.tls_profile.clone(),
timeouts: self.transport.timeouts.clone(),
},
report_kind: Some("openai_video_remix_sync_finalize".to_string()),
report_context: Some(report_context),
})
}
}

View File

@@ -0,0 +1,61 @@
use super::*;
impl OpenAiVideoTaskSeed {
pub(crate) fn to_upsert_record(&self) -> UpsertVideoTask {
let now_unix_secs = current_unix_timestamp_secs();
let next_poll_at_unix_secs = match self.status {
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing => Some(
self.created_at_unix_secs
.saturating_add(u64::from(DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS)),
),
_ => None,
};
UpsertVideoTask {
id: self.local_task_id.clone(),
short_id: None,
request_id: self.persistence.request_id.clone(),
user_id: self.user_id.clone(),
api_key_id: self.api_key_id.clone(),
username: self.persistence.username.clone(),
api_key_name: self.persistence.api_key_name.clone(),
external_task_id: Some(self.upstream_task_id.clone()),
provider_id: Some(self.transport.provider_id.clone()),
endpoint_id: Some(self.transport.endpoint_id.clone()),
key_id: Some(self.transport.key_id.clone()),
client_api_format: Some(self.persistence.client_api_format.clone()),
provider_api_format: Some(self.persistence.provider_api_format.clone()),
format_converted: self.persistence.format_converted,
model: self.model.clone().or_else(|| Some(String::new())),
prompt: self.prompt.clone().or_else(|| Some(String::new())),
original_request_body: Some(self.persistence.original_request_body.clone()),
duration_seconds: request_body_u32(&self.persistence.original_request_body, "seconds"),
resolution: request_body_string(&self.persistence.original_request_body, "resolution"),
aspect_ratio: request_body_string(
&self.persistence.original_request_body,
"aspect_ratio",
),
size: self.size.clone(),
status: self.status.as_database_status(),
progress_percent: self.progress_percent,
progress_message: None,
retry_count: 0,
poll_interval_seconds: DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS,
next_poll_at_unix_secs,
poll_count: 0,
max_poll_count: DEFAULT_VIDEO_TASK_MAX_POLL_COUNT,
created_at_unix_secs: self.created_at_unix_secs,
submitted_at_unix_secs: Some(self.created_at_unix_secs),
completed_at_unix_secs: self.completed_at_unix_secs,
updated_at_unix_secs: self.completed_at_unix_secs.unwrap_or(now_unix_secs),
error_code: self.error_code.clone(),
error_message: self.error_message.clone(),
video_url: self.video_url.clone(),
request_metadata: Some(json!({
"rust_owner": "async_task",
"rust_local_snapshot": LocalVideoTaskSnapshot::OpenAi(self.clone()),
})),
}
}
}

View File

@@ -0,0 +1,46 @@
use super::*;
impl OpenAiVideoTaskSeed {
pub(crate) fn apply_provider_body(&mut self, provider_body: &Map<String, Value>) {
let raw_status = provider_body
.get("status")
.and_then(Value::as_str)
.map(str::trim)
.unwrap_or_default();
self.status = match raw_status {
"queued" => LocalVideoTaskStatus::Queued,
"processing" => LocalVideoTaskStatus::Processing,
"completed" => LocalVideoTaskStatus::Completed,
"failed" => LocalVideoTaskStatus::Failed,
"cancelled" => LocalVideoTaskStatus::Cancelled,
"expired" => LocalVideoTaskStatus::Expired,
_ => LocalVideoTaskStatus::Submitted,
};
self.progress_percent = provider_body
.get("progress")
.and_then(Value::as_u64)
.and_then(|value| u16::try_from(value).ok())
.unwrap_or(match self.status {
LocalVideoTaskStatus::Completed => 100,
LocalVideoTaskStatus::Processing => 50,
_ => self.progress_percent,
});
self.completed_at_unix_secs = provider_body.get("completed_at").and_then(Value::as_u64);
self.expires_at_unix_secs = provider_body.get("expires_at").and_then(Value::as_u64);
let error = provider_body.get("error").and_then(Value::as_object);
self.error_code = error
.and_then(|value| value.get("code"))
.and_then(Value::as_str)
.map(str::to_string);
self.error_message = error
.and_then(|value| value.get("message"))
.and_then(Value::as_str)
.map(str::to_string);
self.video_url = provider_body
.get("video_url")
.or_else(|| provider_body.get("url"))
.or_else(|| provider_body.get("result_url"))
.and_then(Value::as_str)
.map(str::to_string);
}
}

View File

@@ -0,0 +1,162 @@
use super::*;
impl LocalVideoTaskSnapshot {
pub(crate) fn to_upsert_record(&self) -> UpsertVideoTask {
match self {
Self::OpenAi(seed) => seed.to_upsert_record(),
Self::Gemini(seed) => seed.to_upsert_record(),
}
}
pub(crate) fn from_stored_task(task: &StoredVideoTask) -> Option<Self> {
task.request_metadata
.as_ref()
.and_then(|metadata| metadata.get("rust_local_snapshot"))
.cloned()
.and_then(|value| serde_json::from_value::<LocalVideoTaskSnapshot>(value).ok())
}
pub(crate) fn from_stored_task_with_transport(
task: &StoredVideoTask,
transport: &GatewayProviderTransportSnapshot,
) -> Option<Self> {
let provider_api_format = task.provider_api_format.as_deref()?.trim();
let persistence = LocalVideoTaskPersistence::from_stored_task(task)?;
let transport = LocalVideoTaskTransport::from_provider_transport(
transport,
provider_api_format,
non_empty_owned(task.model.as_ref()),
)?;
match provider_api_format {
"openai:video" => {
let upstream_task_id = non_empty_owned(task.external_task_id.as_ref())?;
Some(Self::OpenAi(OpenAiVideoTaskSeed {
local_task_id: task.id.clone(),
upstream_task_id,
created_at_unix_secs: task.created_at_unix_secs,
user_id: task.user_id.clone(),
api_key_id: task.api_key_id.clone(),
model: non_empty_owned(task.model.as_ref()),
prompt: non_empty_owned(task.prompt.as_ref()).or_else(|| {
request_body_string(&persistence.original_request_body, "prompt")
}),
size: non_empty_owned(task.size.as_ref()).or_else(|| {
request_body_string(&persistence.original_request_body, "size")
}),
seconds: task
.duration_seconds
.map(|value| value.to_string())
.or_else(|| {
request_body_string(&persistence.original_request_body, "seconds")
}),
remixed_from_video_id: request_body_string(
&persistence.original_request_body,
"remix_video_id",
)
.or_else(|| {
request_body_string(
&persistence.original_request_body,
"remixed_from_video_id",
)
}),
status: local_status_from_stored(task.status),
progress_percent: task.progress_percent,
completed_at_unix_secs: task.completed_at_unix_secs,
expires_at_unix_secs: None,
error_code: task.error_code.clone(),
error_message: task.error_message.clone(),
video_url: non_empty_owned(task.video_url.as_ref()),
persistence,
transport,
}))
}
"gemini:video" => {
let local_short_id =
non_empty_owned(task.short_id.as_ref()).unwrap_or_else(|| task.id.clone());
let upstream_operation_name = non_empty_owned(task.external_task_id.as_ref())?;
let model = non_empty_owned(task.model.as_ref())?;
Some(Self::Gemini(GeminiVideoTaskSeed {
local_short_id,
upstream_operation_name,
user_id: task.user_id.clone(),
api_key_id: task.api_key_id.clone(),
model,
status: local_status_from_stored(task.status),
progress_percent: task.progress_percent,
error_code: task.error_code.clone(),
error_message: task.error_message.clone(),
metadata: Value::Object(Map::new()),
persistence,
transport,
}))
}
_ => None,
}
}
pub(crate) fn read_response(&self) -> LocalVideoTaskReadResponse {
match self {
Self::OpenAi(seed) => match seed.status {
LocalVideoTaskStatus::Cancelled => LocalVideoTaskReadResponse {
status_code: 404,
body_json: json!({"detail": "Video task was cancelled"}),
},
LocalVideoTaskStatus::Deleted => LocalVideoTaskReadResponse {
status_code: 404,
body_json: json!({"detail": "Video task not found"}),
},
_ => LocalVideoTaskReadResponse {
status_code: 200,
body_json: seed.client_body_json(),
},
},
Self::Gemini(seed) => match seed.status {
LocalVideoTaskStatus::Cancelled => LocalVideoTaskReadResponse {
status_code: 404,
body_json: json!({"detail": "Video task was cancelled"}),
},
LocalVideoTaskStatus::Deleted => LocalVideoTaskReadResponse {
status_code: 404,
body_json: json!({"detail": "Video task not found"}),
},
_ => LocalVideoTaskReadResponse {
status_code: 200,
body_json: seed.client_body_json(),
},
},
}
}
#[cfg(test)]
pub(crate) fn is_active_for_refresh(&self) -> bool {
match self {
Self::OpenAi(seed) => matches!(
seed.status,
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing
),
Self::Gemini(seed) => matches!(
seed.status,
LocalVideoTaskStatus::Submitted
| LocalVideoTaskStatus::Queued
| LocalVideoTaskStatus::Processing
),
}
}
pub(crate) fn apply_provider_body(&mut self, provider_body: &Map<String, Value>) {
match self {
Self::OpenAi(seed) => seed.apply_provider_body(provider_body),
Self::Gemini(seed) => seed.apply_provider_body(provider_body),
}
}
pub(crate) fn provider_name(&self) -> Option<&str> {
match self {
Self::OpenAi(seed) => seed.transport.provider_name.as_deref(),
Self::Gemini(seed) => seed.transport.provider_name.as_deref(),
}
}
}

View File

@@ -0,0 +1,194 @@
use super::*;
impl LocalVideoTaskSeed {
pub(crate) fn from_sync_finalize(
report_kind: &str,
provider_body: &Map<String, Value>,
report_context: &Map<String, Value>,
plan: &ExecutionPlan,
) -> Option<Self> {
let transport = LocalVideoTaskTransport::from_plan(plan)?;
let persistence = LocalVideoTaskPersistence::from_report_context(report_context, plan);
match report_kind {
"openai_video_create_sync_finalize" => {
let upstream_id = provider_body.get("id").and_then(Value::as_str)?.trim();
if upstream_id.is_empty() {
return None;
}
Some(Self::OpenAiCreate(OpenAiVideoTaskSeed {
local_task_id: context_text(report_context, "local_task_id")
.unwrap_or_else(|| Uuid::new_v4().to_string()),
upstream_task_id: upstream_id.to_string(),
created_at_unix_secs: context_u64(report_context, "local_created_at")
.unwrap_or_else(current_unix_timestamp_secs),
user_id: context_text(report_context, "user_id"),
api_key_id: context_text(report_context, "api_key_id"),
model: context_text(report_context, "model")
.or_else(|| request_body_text(report_context, "model")),
prompt: request_body_text(report_context, "prompt"),
size: request_body_text(report_context, "size"),
seconds: request_body_text(report_context, "seconds"),
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: persistence.clone(),
transport: transport.clone(),
}))
}
"openai_video_remix_sync_finalize" => {
let upstream_id = provider_body.get("id").and_then(Value::as_str)?.trim();
if upstream_id.is_empty() {
return None;
}
Some(Self::OpenAiRemix(OpenAiVideoTaskSeed {
local_task_id: context_text(report_context, "local_task_id")
.unwrap_or_else(|| Uuid::new_v4().to_string()),
upstream_task_id: upstream_id.to_string(),
created_at_unix_secs: context_u64(report_context, "local_created_at")
.unwrap_or_else(current_unix_timestamp_secs),
user_id: context_text(report_context, "user_id"),
api_key_id: context_text(report_context, "api_key_id"),
model: context_text(report_context, "model")
.or_else(|| request_body_text(report_context, "model")),
prompt: request_body_text(report_context, "prompt"),
size: request_body_text(report_context, "size"),
seconds: request_body_text(report_context, "seconds"),
remixed_from_video_id: context_text(report_context, "task_id")
.or_else(|| request_body_text(report_context, "remix_video_id")),
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: persistence.clone(),
transport: transport.clone(),
}))
}
"gemini_video_create_sync_finalize" => {
let operation_name = provider_body
.get("name")
.or_else(|| provider_body.get("id"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty());
operation_name?;
Some(Self::GeminiCreate(GeminiVideoTaskSeed {
local_short_id: context_text(report_context, "local_short_id")
.unwrap_or_else(generate_local_short_id),
upstream_operation_name: operation_name?.to_string(),
user_id: context_text(report_context, "user_id"),
api_key_id: context_text(report_context, "api_key_id"),
model: context_text(report_context, "model")
.or_else(|| context_text(report_context, "model_name"))
.unwrap_or_else(|| "unknown".to_string()),
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
error_code: None,
error_message: None,
metadata: json!({}),
persistence,
transport,
}))
}
_ => None,
}
}
pub(crate) fn success_report_kind(&self) -> &'static str {
match self {
Self::OpenAiCreate(_) => "openai_video_create_sync_success",
Self::OpenAiRemix(_) => "openai_video_remix_sync_success",
Self::GeminiCreate(_) => "gemini_video_create_sync_success",
}
}
pub(crate) fn apply_to_report_context(&self, report_context: &mut Map<String, Value>) {
match self {
Self::OpenAiCreate(seed) | Self::OpenAiRemix(seed) => {
report_context.insert(
"local_task_id".to_string(),
Value::String(seed.local_task_id.clone()),
);
report_context.insert(
"local_created_at".to_string(),
Value::Number(seed.created_at_unix_secs.into()),
);
}
Self::GeminiCreate(seed) => {
report_context.insert(
"local_short_id".to_string(),
Value::String(seed.local_short_id.clone()),
);
}
}
}
pub(crate) fn client_body_json(&self) -> Value {
match self {
Self::OpenAiCreate(seed) | Self::OpenAiRemix(seed) => seed.client_body_json(),
Self::GeminiCreate(seed) => seed.client_body_json(),
}
}
}
impl VideoTaskTruthSourceMode {
pub(crate) fn prepare_sync_success(
self,
report_kind: &str,
provider_body: &Map<String, Value>,
report_context: &Map<String, Value>,
plan: &ExecutionPlan,
) -> Option<LocalVideoTaskSuccessPlan> {
let seed = LocalVideoTaskSeed::from_sync_finalize(
report_kind,
provider_body,
report_context,
plan,
)?;
let report_mode = match self {
Self::PythonSyncReport => VideoTaskSyncReportMode::InlineSync,
Self::RustAuthoritative => VideoTaskSyncReportMode::Background,
};
Some(LocalVideoTaskSuccessPlan { seed, report_mode })
}
}
impl LocalVideoTaskSuccessPlan {
pub(crate) fn success_report_kind(&self) -> &'static str {
self.seed.success_report_kind()
}
pub(crate) fn report_mode(&self) -> VideoTaskSyncReportMode {
self.report_mode
}
pub(crate) fn apply_to_report_context(&self, report_context: &mut Map<String, Value>) {
self.seed.apply_to_report_context(report_context);
if matches!(self.report_mode, VideoTaskSyncReportMode::Background) {
report_context.insert("rust_video_task_persisted".to_string(), Value::Bool(true));
}
}
pub(crate) fn client_body_json(&self) -> Value {
self.seed.client_body_json()
}
pub(crate) fn to_snapshot(&self) -> LocalVideoTaskSnapshot {
match &self.seed {
LocalVideoTaskSeed::OpenAiCreate(seed) | LocalVideoTaskSeed::OpenAiRemix(seed) => {
LocalVideoTaskSnapshot::OpenAi(seed.clone())
}
LocalVideoTaskSeed::GeminiCreate(seed) => LocalVideoTaskSnapshot::Gemini(seed.clone()),
}
}
}

View File

@@ -0,0 +1,50 @@
use super::*;
mod follow_up;
mod lifecycle;
mod read;
mod refresh;
#[derive(Debug)]
pub(crate) struct VideoTaskService {
truth_source_mode: VideoTaskTruthSourceMode,
store: Arc<dyn VideoTaskStore>,
}
impl VideoTaskService {
pub(crate) fn new(mode: VideoTaskTruthSourceMode) -> Self {
Self::with_store(mode, Arc::new(InMemoryVideoTaskStore::default()))
}
pub(crate) fn with_file_store(
mode: VideoTaskTruthSourceMode,
path: impl Into<PathBuf>,
) -> std::io::Result<Self> {
Ok(Self::with_store(
mode,
Arc::new(FileVideoTaskStore::new(path)?),
))
}
fn with_store(mode: VideoTaskTruthSourceMode, store: Arc<dyn VideoTaskStore>) -> Self {
Self {
truth_source_mode: mode,
store,
}
}
pub(crate) fn with_truth_source_mode(&self, mode: VideoTaskTruthSourceMode) -> Self {
Self {
truth_source_mode: mode,
store: self.store.clone(),
}
}
pub(crate) fn is_rust_authoritative(&self) -> bool {
self.truth_source_mode == VideoTaskTruthSourceMode::RustAuthoritative
}
pub(crate) fn truth_source_mode(&self) -> VideoTaskTruthSourceMode {
self.truth_source_mode
}
}

View File

@@ -0,0 +1,36 @@
use super::*;
impl VideoTaskService {
pub(crate) fn prepare_follow_up_sync_plan(
&self,
plan_kind: &str,
request_path: &str,
body_json: Option<&Value>,
auth_context: Option<&GatewayControlAuthContext>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
match plan_kind {
"openai_video_remix_sync" => {
let task_id = extract_openai_task_id_from_remix_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
seed.build_remix_follow_up_plan(body_json?, auth_context, trace_id)
}
"openai_video_delete_sync" => {
let task_id = extract_openai_task_id_from_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
seed.build_delete_follow_up_plan(auth_context, trace_id)
}
"openai_video_cancel_sync" => {
let task_id = extract_openai_task_id_from_cancel_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
seed.build_cancel_follow_up_plan(auth_context, trace_id)
}
"gemini_video_cancel_sync" => {
let short_id = extract_gemini_short_id_from_cancel_path(request_path)?;
let seed = self.store.clone_gemini(short_id)?;
seed.build_cancel_follow_up_plan(auth_context, trace_id)
}
_ => None,
}
}
}

View File

@@ -0,0 +1,41 @@
use super::*;
impl VideoTaskService {
pub(crate) fn prepare_sync_success(
&self,
report_kind: &str,
provider_body: &Map<String, Value>,
report_context: &Map<String, Value>,
plan: &ExecutionPlan,
) -> Option<LocalVideoTaskSuccessPlan> {
self.truth_source_mode.prepare_sync_success(
report_kind,
provider_body,
report_context,
plan,
)
}
pub(crate) fn record_snapshot(&self, snapshot: LocalVideoTaskSnapshot) {
self.store.insert(snapshot);
}
pub(crate) fn hydrate_from_stored_task(&self, task: &StoredVideoTask) -> bool {
let Some(snapshot) = LocalVideoTaskSnapshot::from_stored_task(task) else {
return false;
};
self.store.insert(snapshot);
true
}
pub(crate) fn apply_finalize_mutation(&self, request_path: &str, report_kind: &str) {
let Some(mutation) = resolve_local_video_registry_mutation(
self.truth_source_mode,
request_path,
report_kind,
) else {
return;
};
self.store.apply_mutation(mutation);
}
}

View File

@@ -0,0 +1,50 @@
use super::*;
impl VideoTaskService {
pub(crate) fn read_response(
&self,
route_family: Option<&str>,
request_path: &str,
) -> Option<LocalVideoTaskReadResponse> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
match route_family {
Some("openai") => extract_openai_task_id_from_path(request_path)
.and_then(|task_id| self.store.read_openai(task_id)),
Some("gemini") => extract_gemini_short_id_from_path(request_path)
.and_then(|short_id| self.store.read_gemini(short_id)),
_ => None,
}
}
pub(crate) fn snapshot_for_route(
&self,
route_family: Option<&str>,
request_path: &str,
) -> Option<LocalVideoTaskSnapshot> {
match route_family {
Some("openai") => extract_openai_task_id_from_path(request_path)
.and_then(|task_id| self.store.clone_openai(task_id))
.map(LocalVideoTaskSnapshot::OpenAi),
Some("gemini") => extract_gemini_short_id_from_path(request_path)
.and_then(|short_id| self.store.clone_gemini(short_id))
.map(LocalVideoTaskSnapshot::Gemini),
_ => None,
}
}
pub(crate) fn prepare_openai_content_stream_action(
&self,
request_path: &str,
query_string: Option<&str>,
trace_id: &str,
) -> Option<LocalVideoTaskContentAction> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
let task_id = extract_openai_task_id_from_content_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
seed.build_content_stream_action(query_string, trace_id)
}
}

View File

@@ -0,0 +1,150 @@
use super::*;
impl VideoTaskService {
pub(crate) fn snapshot_for_refresh_plan(
&self,
refresh_plan: &LocalVideoTaskReadRefreshPlan,
) -> Option<LocalVideoTaskSnapshot> {
match &refresh_plan.projection_target {
LocalVideoTaskProjectionTarget::OpenAi { task_id } => self
.store
.clone_openai(task_id)
.map(LocalVideoTaskSnapshot::OpenAi),
LocalVideoTaskProjectionTarget::Gemini { short_id } => self
.store
.clone_gemini(short_id)
.map(LocalVideoTaskSnapshot::Gemini),
}
}
pub(crate) fn project_openai_task_response(
&self,
task_id: &str,
provider_body: &Map<String, Value>,
) -> bool {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return false;
}
self.store.project_openai(task_id, provider_body)
}
pub(crate) fn project_gemini_task_response(
&self,
short_id: &str,
provider_body: &Map<String, Value>,
) -> bool {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return false;
}
self.store.project_gemini(short_id, provider_body)
}
pub(crate) fn prepare_read_refresh_sync_plan(
&self,
route_family: Option<&str>,
request_path: &str,
trace_id: &str,
) -> Option<LocalVideoTaskReadRefreshPlan> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
match route_family {
Some("openai") => {
let task_id = extract_openai_task_id_from_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::OpenAi {
task_id: task_id.to_string(),
},
})
}
Some("gemini") => {
let short_id = extract_gemini_short_id_from_path(request_path)?;
let seed = self.store.clone_gemini(short_id)?;
Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::Gemini {
short_id: short_id.to_string(),
},
})
}
_ => None,
}
}
#[cfg(test)]
pub(crate) fn prepare_poll_refresh_batch(
&self,
limit: usize,
trace_prefix: &str,
) -> Vec<LocalVideoTaskReadRefreshPlan> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative || limit == 0 {
return Vec::new();
}
self.store
.list_active_snapshots(limit)
.into_iter()
.enumerate()
.filter_map(|(index, snapshot)| {
let trace_id = format!("{trace_prefix}-{index}");
match snapshot {
LocalVideoTaskSnapshot::OpenAi(seed) => Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(&trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::OpenAi {
task_id: seed.local_task_id.clone(),
},
}),
LocalVideoTaskSnapshot::Gemini(seed) => Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(&trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::Gemini {
short_id: seed.local_short_id.clone(),
},
}),
}
})
.collect()
}
pub(crate) fn prepare_poll_refresh_plan_for_stored_task(
&self,
task: &StoredVideoTask,
trace_id: &str,
) -> Option<LocalVideoTaskReadRefreshPlan> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
let snapshot = LocalVideoTaskSnapshot::from_stored_task(task)?;
match snapshot {
LocalVideoTaskSnapshot::OpenAi(seed) => Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::OpenAi {
task_id: seed.local_task_id.clone(),
},
}),
LocalVideoTaskSnapshot::Gemini(seed) => Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::Gemini {
short_id: seed.local_short_id.clone(),
},
}),
}
}
pub(crate) fn apply_read_refresh_projection(
&self,
refresh_plan: &LocalVideoTaskReadRefreshPlan,
provider_body: &Map<String, Value>,
) -> bool {
match &refresh_plan.projection_target {
LocalVideoTaskProjectionTarget::OpenAi { task_id } => {
self.project_openai_task_response(task_id, provider_body)
}
LocalVideoTaskProjectionTarget::Gemini { short_id } => {
self.project_gemini_task_response(short_id, provider_body)
}
}
}
}

View File

@@ -0,0 +1,20 @@
use super::*;
mod backend;
mod registry;
pub(crate) use self::backend::{FileVideoTaskStore, InMemoryVideoTaskStore};
use self::registry::VideoTaskRegistry;
pub(super) trait VideoTaskStore: std::fmt::Debug + Send + Sync {
fn insert(&self, snapshot: LocalVideoTaskSnapshot);
fn read_openai(&self, task_id: &str) -> Option<LocalVideoTaskReadResponse>;
fn read_gemini(&self, short_id: &str) -> Option<LocalVideoTaskReadResponse>;
fn clone_openai(&self, task_id: &str) -> Option<OpenAiVideoTaskSeed>;
fn clone_gemini(&self, short_id: &str) -> Option<GeminiVideoTaskSeed>;
#[cfg(test)]
fn list_active_snapshots(&self, limit: usize) -> Vec<LocalVideoTaskSnapshot>;
fn apply_mutation(&self, mutation: LocalVideoTaskRegistryMutation);
fn project_openai(&self, task_id: &str, provider_body: &Map<String, Value>) -> bool;
fn project_gemini(&self, short_id: &str, provider_body: &Map<String, Value>) -> bool;
}

View File

@@ -0,0 +1,165 @@
use super::*;
#[derive(Debug, Default)]
pub(crate) struct InMemoryVideoTaskStore {
registry: Mutex<VideoTaskRegistry>,
}
#[derive(Debug)]
pub(crate) struct FileVideoTaskStore {
path: PathBuf,
registry: Mutex<VideoTaskRegistry>,
}
impl VideoTaskStore for InMemoryVideoTaskStore {
fn insert(&self, snapshot: LocalVideoTaskSnapshot) {
if let Ok(mut registry) = self.registry.lock() {
registry.insert(snapshot);
}
}
fn read_openai(&self, task_id: &str) -> Option<LocalVideoTaskReadResponse> {
let registry = self.registry.lock().ok()?;
registry.read_openai(task_id)
}
fn read_gemini(&self, short_id: &str) -> Option<LocalVideoTaskReadResponse> {
let registry = self.registry.lock().ok()?;
registry.read_gemini(short_id)
}
fn clone_openai(&self, task_id: &str) -> Option<OpenAiVideoTaskSeed> {
let registry = self.registry.lock().ok()?;
registry.clone_openai(task_id)
}
fn clone_gemini(&self, short_id: &str) -> Option<GeminiVideoTaskSeed> {
let registry = self.registry.lock().ok()?;
registry.clone_gemini(short_id)
}
#[cfg(test)]
fn list_active_snapshots(&self, limit: usize) -> Vec<LocalVideoTaskSnapshot> {
let Ok(registry) = self.registry.lock() else {
return Vec::new();
};
registry.list_active_snapshots(limit)
}
fn apply_mutation(&self, mutation: LocalVideoTaskRegistryMutation) {
if let Ok(mut registry) = self.registry.lock() {
registry.apply_mutation(mutation);
}
}
fn project_openai(&self, task_id: &str, provider_body: &Map<String, Value>) -> bool {
let Ok(mut registry) = self.registry.lock() else {
return false;
};
registry.project_openai(task_id, provider_body)
}
fn project_gemini(&self, short_id: &str, provider_body: &Map<String, Value>) -> bool {
let Ok(mut registry) = self.registry.lock() else {
return false;
};
registry.project_gemini(short_id, provider_body)
}
}
impl FileVideoTaskStore {
pub(crate) fn new(path: impl Into<PathBuf>) -> std::io::Result<Self> {
let path = path.into();
let registry = Self::load_registry(&path)?;
Ok(Self {
path,
registry: Mutex::new(registry),
})
}
fn load_registry(path: &Path) -> std::io::Result<VideoTaskRegistry> {
if !path.exists() {
return Ok(VideoTaskRegistry::default());
}
let bytes = std::fs::read(path)?;
if bytes.is_empty() {
return Ok(VideoTaskRegistry::default());
}
serde_json::from_slice(&bytes)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
}
fn persist_registry(&self, registry: &VideoTaskRegistry) -> std::io::Result<()> {
if let Some(parent) = self.path.parent() {
std::fs::create_dir_all(parent)?;
}
let bytes = serde_json::to_vec_pretty(registry)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
let temp_path = self.path.with_extension("tmp");
std::fs::write(&temp_path, bytes)?;
std::fs::rename(temp_path, &self.path)?;
Ok(())
}
fn mutate_registry(&self, mutator: impl FnOnce(&mut VideoTaskRegistry) -> bool) -> bool {
let Ok(mut registry) = self.registry.lock() else {
return false;
};
if !mutator(&mut registry) {
return false;
}
self.persist_registry(&registry).is_ok()
}
}
impl VideoTaskStore for FileVideoTaskStore {
fn insert(&self, snapshot: LocalVideoTaskSnapshot) {
let _ = self.mutate_registry(|registry| {
registry.insert(snapshot);
true
});
}
fn read_openai(&self, task_id: &str) -> Option<LocalVideoTaskReadResponse> {
let registry = self.registry.lock().ok()?;
registry.read_openai(task_id)
}
fn read_gemini(&self, short_id: &str) -> Option<LocalVideoTaskReadResponse> {
let registry = self.registry.lock().ok()?;
registry.read_gemini(short_id)
}
fn clone_openai(&self, task_id: &str) -> Option<OpenAiVideoTaskSeed> {
let registry = self.registry.lock().ok()?;
registry.clone_openai(task_id)
}
fn clone_gemini(&self, short_id: &str) -> Option<GeminiVideoTaskSeed> {
let registry = self.registry.lock().ok()?;
registry.clone_gemini(short_id)
}
#[cfg(test)]
fn list_active_snapshots(&self, limit: usize) -> Vec<LocalVideoTaskSnapshot> {
let Ok(registry) = self.registry.lock() else {
return Vec::new();
};
registry.list_active_snapshots(limit)
}
fn apply_mutation(&self, mutation: LocalVideoTaskRegistryMutation) {
let _ = self.mutate_registry(|registry| {
registry.apply_mutation(mutation);
true
});
}
fn project_openai(&self, task_id: &str, provider_body: &Map<String, Value>) -> bool {
self.mutate_registry(|registry| registry.project_openai(task_id, provider_body))
}
fn project_gemini(&self, short_id: &str, provider_body: &Map<String, Value>) -> bool {
self.mutate_registry(|registry| registry.project_gemini(short_id, provider_body))
}
}

View File

@@ -0,0 +1,103 @@
use super::*;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub(super) struct VideoTaskRegistry {
openai: BTreeMap<String, LocalVideoTaskSnapshot>,
gemini: BTreeMap<String, LocalVideoTaskSnapshot>,
}
impl VideoTaskRegistry {
pub(super) fn insert(&mut self, snapshot: LocalVideoTaskSnapshot) {
match &snapshot {
LocalVideoTaskSnapshot::OpenAi(seed) => {
self.openai.insert(seed.local_task_id.clone(), snapshot);
}
LocalVideoTaskSnapshot::Gemini(seed) => {
self.gemini.insert(seed.local_short_id.clone(), snapshot);
}
}
}
pub(super) fn read_openai(&self, task_id: &str) -> Option<LocalVideoTaskReadResponse> {
self.openai
.get(task_id)
.map(LocalVideoTaskSnapshot::read_response)
}
pub(super) fn read_gemini(&self, short_id: &str) -> Option<LocalVideoTaskReadResponse> {
self.gemini
.get(short_id)
.map(LocalVideoTaskSnapshot::read_response)
}
pub(super) fn clone_openai(&self, task_id: &str) -> Option<OpenAiVideoTaskSeed> {
let LocalVideoTaskSnapshot::OpenAi(seed) = self.openai.get(task_id)?.clone() else {
return None;
};
Some(seed)
}
pub(super) fn clone_gemini(&self, short_id: &str) -> Option<GeminiVideoTaskSeed> {
let LocalVideoTaskSnapshot::Gemini(seed) = self.gemini.get(short_id)?.clone() else {
return None;
};
Some(seed)
}
#[cfg(test)]
pub(super) fn list_active_snapshots(&self, limit: usize) -> Vec<LocalVideoTaskSnapshot> {
self.openai
.values()
.chain(self.gemini.values())
.filter(|snapshot| snapshot.is_active_for_refresh())
.take(limit)
.cloned()
.collect()
}
pub(super) fn apply_mutation(&mut self, mutation: LocalVideoTaskRegistryMutation) {
match mutation {
LocalVideoTaskRegistryMutation::OpenAiCancelled { task_id } => {
if let Some(LocalVideoTaskSnapshot::OpenAi(seed)) = self.openai.get_mut(&task_id) {
seed.status = LocalVideoTaskStatus::Cancelled;
}
}
LocalVideoTaskRegistryMutation::OpenAiDeleted { task_id } => {
if let Some(LocalVideoTaskSnapshot::OpenAi(seed)) = self.openai.get_mut(&task_id) {
seed.status = LocalVideoTaskStatus::Deleted;
}
}
LocalVideoTaskRegistryMutation::GeminiCancelled { short_id } => {
if let Some(LocalVideoTaskSnapshot::Gemini(seed)) = self.gemini.get_mut(&short_id) {
seed.status = LocalVideoTaskStatus::Cancelled;
}
}
}
}
#[allow(dead_code)]
pub(super) fn project_openai(
&mut self,
task_id: &str,
provider_body: &Map<String, Value>,
) -> bool {
let Some(LocalVideoTaskSnapshot::OpenAi(seed)) = self.openai.get_mut(task_id) else {
return false;
};
seed.apply_provider_body(provider_body);
true
}
#[allow(dead_code)]
pub(super) fn project_gemini(
&mut self,
short_id: &str,
provider_body: &Map<String, Value>,
) -> bool {
let Some(LocalVideoTaskSnapshot::Gemini(seed)) = self.gemini.get_mut(short_id) else {
return false;
};
seed.apply_provider_body(provider_body);
true
}
}

View File

@@ -0,0 +1,6 @@
use super::*;
mod fixtures;
mod plans;
mod projection;
mod sync;

View File

@@ -0,0 +1,102 @@
use super::*;
pub(super) fn sample_transport(
base_url: &str,
provider_api_format: &str,
) -> LocalVideoTaskTransport {
let url = match provider_api_format {
"openai:video" => format!("{base_url}/v1/videos"),
"gemini:video" => {
format!("{base_url}/v1beta/models/veo-3:predictLongRunning")
}
_ => panic!("unsupported provider api format"),
};
LocalVideoTaskTransport::from_plan(&sample_plan(&url, provider_api_format))
.expect("transport should build")
}
pub(super) fn sample_persistence(provider_api_format: &str) -> LocalVideoTaskPersistence {
LocalVideoTaskPersistence {
request_id: "request-123".to_string(),
username: Some("user".to_string()),
api_key_name: Some("primary".to_string()),
client_api_format: provider_api_format.to_string(),
provider_api_format: provider_api_format.to_string(),
original_request_body: json!({
"prompt": "hello",
"seconds": 4,
"resolution": "720p",
"aspect_ratio": "16:9",
"size": "1280x720"
}),
format_converted: false,
}
}
pub(super) fn sample_auth_context() -> GatewayControlAuthContext {
GatewayControlAuthContext {
user_id: "user-123".to_string(),
api_key_id: "key-123".to_string(),
balance_remaining: None,
access_allowed: true,
user_rate_limit: None,
api_key_rate_limit: None,
api_key_is_standalone: false,
local_rejection: None,
allowed_models: None,
}
}
pub(super) fn sample_plan(url: &str, provider_api_format: &str) -> ExecutionPlan {
ExecutionPlan {
request_id: "req-123".to_string(),
candidate_id: None,
provider_name: Some(
provider_api_format
.split(':')
.next()
.expect("provider name should exist")
.to_string(),
),
provider_id: "provider-123".to_string(),
endpoint_id: "endpoint-123".to_string(),
key_id: "key-123".to_string(),
method: "POST".to_string(),
url: url.to_string(),
headers: BTreeMap::from([(
"authorization".to_string(),
"Bearer upstream-key".to_string(),
)]),
content_type: Some("application/json".to_string()),
content_encoding: None,
body: RequestBody::from_json(json!({})),
stream: false,
client_api_format: provider_api_format.to_string(),
provider_api_format: provider_api_format.to_string(),
model_name: Some(
if provider_api_format == "gemini:video" {
"veo-3"
} else {
"sora-2"
}
.to_string(),
),
proxy: Some(ProxySnapshot {
enabled: Some(false),
mode: Some("direct".to_string()),
node_id: None,
label: None,
url: None,
extra: None,
}),
tls_profile: Some("chrome".to_string()),
timeouts: Some(ExecutionTimeouts {
connect_ms: Some(10_000),
read_ms: Some(30_000),
first_byte_ms: None,
write_ms: Some(30_000),
pool_ms: Some(10_000),
total_ms: Some(300_000),
}),
}
}

View File

@@ -0,0 +1,403 @@
use super::fixtures::*;
use super::*;
#[test]
fn rust_authoritative_service_builds_openai_cancel_follow_up_plan() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
let follow_up = service
.prepare_follow_up_sync_plan(
"openai_video_cancel_sync",
"/v1/videos/task-local-123/cancel",
None,
Some(&sample_auth_context()),
"trace-openai-cancel-123",
)
.expect("follow-up plan should build");
assert_eq!(follow_up.plan.method, "DELETE");
assert_eq!(
follow_up.plan.url,
"https://api.openai.example/v1/videos/ext-video-task-123"
);
assert_eq!(follow_up.plan.provider_api_format, "openai:video");
assert!(follow_up.plan.body.json_body.is_none());
assert_eq!(
follow_up.report_kind.as_deref(),
Some("openai_video_cancel_sync_finalize")
);
assert_eq!(
follow_up
.report_context
.as_ref()
.and_then(Value::as_object)
.and_then(|value| value.get("task_id"))
.and_then(Value::as_str),
Some("task-local-123")
);
}
#[test]
fn rust_authoritative_service_builds_openai_remix_follow_up_plan() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Completed,
progress_percent: 100,
completed_at_unix_secs: Some(1712345688),
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: Some("https://cdn.example.com/original.mp4".to_string()),
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
let remix_body = json!({
"prompt": "remix this",
"model": "sora-2",
});
let follow_up = service
.prepare_follow_up_sync_plan(
"openai_video_remix_sync",
"/v1/videos/task-local-123/remix",
Some(&remix_body),
Some(&sample_auth_context()),
"trace-openai-remix-123",
)
.expect("follow-up plan should build");
assert_eq!(follow_up.plan.method, "POST");
assert_eq!(
follow_up.plan.url,
"https://api.openai.example/v1/videos/ext-video-task-123/remix"
);
assert_eq!(follow_up.plan.provider_api_format, "openai:video");
assert_eq!(follow_up.plan.body.json_body, Some(remix_body.clone()));
assert_eq!(
follow_up.report_kind.as_deref(),
Some("openai_video_remix_sync_finalize")
);
assert_eq!(
follow_up
.report_context
.as_ref()
.and_then(Value::as_object)
.and_then(|value| value.get("task_id"))
.and_then(Value::as_str),
Some("task-local-123")
);
}
#[test]
fn rust_authoritative_service_builds_openai_delete_follow_up_plan() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Completed,
progress_percent: 100,
completed_at_unix_secs: Some(1712345688),
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
let follow_up = service
.prepare_follow_up_sync_plan(
"openai_video_delete_sync",
"/v1/videos/task-local-123",
None,
Some(&sample_auth_context()),
"trace-openai-delete-123",
)
.expect("follow-up plan should build");
assert_eq!(follow_up.plan.method, "DELETE");
assert_eq!(
follow_up.plan.url,
"https://api.openai.example/v1/videos/ext-video-task-123"
);
assert_eq!(follow_up.plan.provider_api_format, "openai:video");
assert!(follow_up.plan.body.json_body.is_none());
assert_eq!(
follow_up.report_kind.as_deref(),
Some("openai_video_delete_sync_finalize")
);
assert_eq!(
follow_up
.report_context
.as_ref()
.and_then(Value::as_object)
.and_then(|value| value.get("task_id"))
.and_then(Value::as_str),
Some("task-local-123")
);
}
#[test]
fn rust_authoritative_service_builds_gemini_cancel_follow_up_plan() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
local_short_id: "localshort123".to_string(),
upstream_operation_name: "operations/ext-video-123".to_string(),
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: "veo-3".to_string(),
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
error_code: None,
error_message: None,
metadata: json!({}),
persistence: sample_persistence("gemini:video"),
transport: sample_transport("https://generativelanguage.googleapis.com", "gemini:video"),
}));
let follow_up = service
.prepare_follow_up_sync_plan(
"gemini_video_cancel_sync",
"/v1beta/models/veo-3/operations/localshort123:cancel",
None,
Some(&sample_auth_context()),
"trace-gemini-cancel-123",
)
.expect("follow-up plan should build");
assert_eq!(follow_up.plan.method, "POST");
assert_eq!(
follow_up.plan.url,
"https://generativelanguage.googleapis.com/v1beta/models/veo-3/operations/ext-video-123:cancel"
);
assert_eq!(follow_up.plan.provider_api_format, "gemini:video");
assert_eq!(follow_up.plan.body.json_body, Some(json!({})));
assert_eq!(
follow_up.report_kind.as_deref(),
Some("gemini_video_cancel_sync_finalize")
);
assert_eq!(
follow_up
.report_context
.as_ref()
.and_then(Value::as_object)
.and_then(|value| value.get("task_id"))
.and_then(Value::as_str),
Some("localshort123")
);
}
#[test]
fn rust_authoritative_service_builds_openai_read_refresh_plan() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
let refresh = service
.prepare_read_refresh_sync_plan(
Some("openai"),
"/v1/videos/task-local-123",
"trace-openai-read-123",
)
.expect("read refresh plan should build");
assert_eq!(refresh.plan.method, "GET");
assert_eq!(
refresh.plan.url,
"https://api.openai.example/v1/videos/ext-video-task-123"
);
assert!(refresh.plan.body.json_body.is_none());
}
#[test]
fn rust_authoritative_service_builds_gemini_read_refresh_plan() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
local_short_id: "localshort123".to_string(),
upstream_operation_name: "operations/ext-video-123".to_string(),
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: "veo-3".to_string(),
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
error_code: None,
error_message: None,
metadata: json!({}),
persistence: sample_persistence("gemini:video"),
transport: sample_transport("https://generativelanguage.googleapis.com", "gemini:video"),
}));
let refresh = service
.prepare_read_refresh_sync_plan(
Some("gemini"),
"/v1beta/models/veo-3/operations/localshort123",
"trace-gemini-read-123",
)
.expect("read refresh plan should build");
assert_eq!(refresh.plan.method, "GET");
assert_eq!(
refresh.plan.url,
"https://generativelanguage.googleapis.com/v1beta/models/veo-3/operations/ext-video-123"
);
assert!(refresh.plan.body.json_body.is_none());
}
#[test]
fn rust_authoritative_service_builds_poll_refresh_batch_for_active_tasks_only() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-active-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Processing,
progress_percent: 42,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-completed-123".to_string(),
upstream_task_id: "ext-video-task-999".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Completed,
progress_percent: 100,
completed_at_unix_secs: Some(1712345688),
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: Some("https://cdn.example.com/ext-video-task-999.mp4".to_string()),
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
let batch = service.prepare_poll_refresh_batch(10, "trace-poller");
assert_eq!(batch.len(), 1);
assert_eq!(batch[0].plan.method, "GET");
assert_eq!(
batch[0].plan.url,
"https://api.openai.example/v1/videos/ext-video-task-123"
);
}
#[test]
fn file_video_task_store_persists_snapshots_across_service_rebuilds() {
let store_path =
std::env::temp_dir().join(format!("aether-video-task-store-{}.json", Uuid::new_v4()));
let service =
VideoTaskService::with_file_store(VideoTaskTruthSourceMode::RustAuthoritative, &store_path)
.expect("file-backed service should build");
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-file-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: Some("hello".to_string()),
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
drop(service);
let reopened =
VideoTaskService::with_file_store(VideoTaskTruthSourceMode::RustAuthoritative, &store_path)
.expect("reopened file-backed service should build");
let response = reopened
.read_response(Some("openai"), "/v1/videos/task-file-123")
.expect("persisted read response should exist");
assert_eq!(response.status_code, 200);
assert_eq!(
response.body_json.get("id").and_then(Value::as_str),
Some("task-file-123")
);
let _ = std::fs::remove_file(store_path);
}

View File

@@ -0,0 +1,246 @@
use super::fixtures::*;
use super::*;
#[test]
fn rust_authoritative_service_projects_openai_status_into_local_read_response() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: Some("hello".to_string()),
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
assert!(service.project_openai_task_response(
"task-local-123",
json!({
"status": "processing",
"progress": 42
})
.as_object()
.expect("provider body should be object"),
));
let processing = service
.read_response(Some("openai"), "/v1/videos/task-local-123")
.expect("processing read response should exist");
assert_eq!(processing.status_code, 200);
assert_eq!(
processing.body_json.get("status"),
Some(&json!("processing"))
);
assert_eq!(processing.body_json.get("progress"), Some(&json!(42)));
assert!(service.project_openai_task_response(
"task-local-123",
json!({
"status": "failed",
"progress": 100,
"completed_at": 1712345688u64,
"error": {
"code": "upstream_failed",
"message": "provider failed"
}
})
.as_object()
.expect("provider body should be object"),
));
let failed = service
.read_response(Some("openai"), "/v1/videos/task-local-123")
.expect("failed read response should exist");
assert_eq!(failed.status_code, 200);
assert_eq!(failed.body_json.get("status"), Some(&json!("failed")));
assert_eq!(
failed.body_json.get("completed_at"),
Some(&json!(1712345688u64))
);
assert_eq!(
failed
.body_json
.get("error")
.and_then(Value::as_object)
.and_then(|value| value.get("code")),
Some(&json!("upstream_failed"))
);
}
#[test]
fn rust_authoritative_service_builds_openai_content_stream_plan_from_direct_video_url() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Completed,
progress_percent: 100,
completed_at_unix_secs: Some(1712345688),
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: Some("https://cdn.example.com/ext-video-task-123.mp4".to_string()),
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
let action = service
.prepare_openai_content_stream_action(
"/v1/videos/task-local-123/content",
Some("variant=video"),
"trace-openai-content-123",
)
.expect("content action should exist");
let LocalVideoTaskContentAction::StreamPlan(plan) = action else {
panic!("content action should be stream plan");
};
assert_eq!(plan.method, "GET");
assert_eq!(plan.url, "https://cdn.example.com/ext-video-task-123.mp4");
assert!(plan.headers.is_empty());
}
#[test]
fn rust_authoritative_service_returns_processing_content_response_for_pending_openai_task() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Processing,
progress_percent: 42,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
let action = service
.prepare_openai_content_stream_action(
"/v1/videos/task-local-123/content",
Some("variant=video"),
"trace-openai-content-processing-123",
)
.expect("content action should exist");
let LocalVideoTaskContentAction::Immediate {
status_code,
body_json,
} = action
else {
panic!("content action should be immediate response");
};
assert_eq!(status_code, 202);
assert_eq!(
body_json,
json!({"detail": "Video is still processing (status: processing)"})
);
}
#[test]
fn rust_authoritative_service_projects_gemini_status_into_local_read_response() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
local_short_id: "localshort123".to_string(),
upstream_operation_name: "operations/ext-video-123".to_string(),
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: "veo-3".to_string(),
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
error_code: None,
error_message: None,
metadata: json!({}),
persistence: sample_persistence("gemini:video"),
transport: sample_transport("https://generativelanguage.googleapis.com", "gemini:video"),
}));
assert!(service.project_gemini_task_response(
"localshort123",
json!({
"done": false,
"metadata": {
"state": "PROCESSING"
}
})
.as_object()
.expect("provider body should be object"),
));
let processing = service
.read_response(
Some("gemini"),
"/v1beta/models/veo-3/operations/localshort123",
)
.expect("processing read response should exist");
assert_eq!(processing.status_code, 200);
assert_eq!(processing.body_json.get("done"), Some(&json!(false)));
assert_eq!(
processing.body_json.get("metadata"),
Some(&json!({"state": "PROCESSING"}))
);
assert!(service.project_gemini_task_response(
"localshort123",
json!({
"done": true,
"response": {
"generateVideoResponse": {
"generatedSamples": [
{"video": {"uri": "https://example.invalid/video.mp4"}}
]
}
}
})
.as_object()
.expect("provider body should be object"),
));
let completed = service
.read_response(
Some("gemini"),
"/v1beta/models/veo-3/operations/localshort123",
)
.expect("completed read response should exist");
assert_eq!(completed.status_code, 200);
assert_eq!(completed.body_json.get("done"), Some(&json!(true)));
assert_eq!(
completed
.body_json
.get("response")
.and_then(|value| value.get("generateVideoResponse"))
.and_then(|value| value.get("generatedSamples"))
.and_then(Value::as_array)
.and_then(|value| value.first())
.and_then(|value| value.get("video"))
.and_then(|value| value.get("uri")),
Some(&json!("/v1beta/files/aev_localshort123:download?alt=media"))
);
}

View File

@@ -0,0 +1,369 @@
use super::fixtures::*;
use super::*;
#[test]
fn openai_video_seed_preserves_existing_local_identity() {
let provider_body = json!({"id": "ext-video-task-123"});
let report_context = json!({
"local_task_id": "task-local-123",
"local_created_at": 1712345678u64,
"model": "sora-2",
"original_request_body": {
"prompt": "hello",
"size": "1024x1024",
"seconds": "8"
}
});
let seed = LocalVideoTaskSeed::from_sync_finalize(
"openai_video_create_sync_finalize",
provider_body
.as_object()
.expect("provider body should be object"),
report_context
.as_object()
.expect("report context should be object"),
&sample_plan("https://api.openai.example/v1/videos", "openai:video"),
)
.expect("seed should build");
let mut patched_context = report_context
.as_object()
.expect("report context should be object")
.clone();
seed.apply_to_report_context(&mut patched_context);
let client_body = seed.client_body_json();
assert_eq!(
seed.success_report_kind(),
"openai_video_create_sync_success"
);
assert_eq!(
patched_context.get("local_task_id").and_then(Value::as_str),
Some("task-local-123")
);
assert_eq!(
patched_context
.get("local_created_at")
.and_then(Value::as_u64),
Some(1712345678)
);
assert_eq!(
client_body.get("id").and_then(Value::as_str),
Some("task-local-123")
);
assert_eq!(
client_body.get("created_at").and_then(Value::as_u64),
Some(1712345678)
);
assert_eq!(
client_body.get("model").and_then(Value::as_str),
Some("sora-2")
);
assert_eq!(
client_body.get("prompt").and_then(Value::as_str),
Some("hello")
);
}
#[test]
fn openai_video_remix_seed_carries_source_task() {
let provider_body = json!({"id": "ext-remix-task-123"});
let report_context = json!({
"task_id": "task-source-123",
"original_request_body": {
"prompt": "remix it"
}
});
let seed = LocalVideoTaskSeed::from_sync_finalize(
"openai_video_remix_sync_finalize",
provider_body
.as_object()
.expect("provider body should be object"),
report_context
.as_object()
.expect("report context should be object"),
&sample_plan(
"https://api.openai.example/v1/videos/ext-source/remix",
"openai:video",
),
)
.expect("seed should build");
let client_body = seed.client_body_json();
assert_eq!(
seed.success_report_kind(),
"openai_video_remix_sync_success"
);
assert_eq!(
client_body
.get("remixed_from_video_id")
.and_then(Value::as_str),
Some("task-source-123")
);
}
#[test]
fn gemini_video_seed_generates_short_id_and_pending_operation_name() {
let provider_body = json!({"name": "operations/ext-video-task-123"});
let report_context = json!({
"model": "veo-3"
});
let seed = LocalVideoTaskSeed::from_sync_finalize(
"gemini_video_create_sync_finalize",
provider_body
.as_object()
.expect("provider body should be object"),
report_context
.as_object()
.expect("report context should be object"),
&sample_plan(
"https://generativelanguage.googleapis.com/v1beta/models/veo-3:predictLongRunning",
"gemini:video",
),
)
.expect("seed should build");
let mut patched_context = Map::new();
seed.apply_to_report_context(&mut patched_context);
let client_body = seed.client_body_json();
let local_short_id = patched_context
.get("local_short_id")
.and_then(Value::as_str)
.expect("local_short_id should exist");
assert_eq!(
seed.success_report_kind(),
"gemini_video_create_sync_success"
);
assert_eq!(local_short_id.len(), 12);
assert_eq!(
client_body.get("name").and_then(Value::as_str),
Some(format!("models/veo-3/operations/{local_short_id}").as_str())
);
assert_eq!(
client_body.get("done").and_then(Value::as_bool),
Some(false)
);
}
#[test]
fn python_backed_video_truth_source_requires_inline_sync_report() {
let provider_body = json!({"id": "ext-video-task-123"});
let report_context = json!({});
let plan = VideoTaskTruthSourceMode::PythonSyncReport
.prepare_sync_success(
"openai_video_create_sync_finalize",
provider_body
.as_object()
.expect("provider body should be object"),
report_context
.as_object()
.expect("report context should be object"),
&sample_plan("https://api.openai.example/v1/videos", "openai:video"),
)
.expect("plan should build");
assert_eq!(plan.report_mode(), VideoTaskSyncReportMode::InlineSync);
assert_eq!(
plan.success_report_kind(),
"openai_video_create_sync_success"
);
}
#[test]
fn rust_authoritative_video_truth_source_can_background_success_report() {
let provider_body = json!({"name": "operations/ext-video-task-123"});
let report_context = json!({
"model": "veo-3"
});
let plan = VideoTaskTruthSourceMode::RustAuthoritative
.prepare_sync_success(
"gemini_video_create_sync_finalize",
provider_body
.as_object()
.expect("provider body should be object"),
report_context
.as_object()
.expect("report context should be object"),
&sample_plan(
"https://generativelanguage.googleapis.com/v1beta/models/veo-3:predictLongRunning",
"gemini:video",
),
)
.expect("plan should build");
assert_eq!(plan.report_mode(), VideoTaskSyncReportMode::Background);
assert_eq!(
plan.success_report_kind(),
"gemini_video_create_sync_success"
);
let mut report_context = Map::new();
plan.apply_to_report_context(&mut report_context);
assert_eq!(
report_context.get("rust_video_task_persisted"),
Some(&Value::Bool(true))
);
}
#[test]
fn rust_authoritative_service_reads_openai_task_from_local_registry() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
let snapshot = LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: Some("hello".to_string()),
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
});
service.record_snapshot(snapshot);
let response = service
.read_response(Some("openai"), "/v1/videos/task-local-123")
.expect("read response should exist");
assert_eq!(response.status_code, 200);
assert_eq!(
response.body_json.get("id").and_then(Value::as_str),
Some("task-local-123")
);
assert_eq!(
response.body_json.get("status").and_then(Value::as_str),
Some("queued")
);
}
#[test]
fn rust_authoritative_service_applies_cancel_and_delete_mutations() {
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
local_task_id: "task-local-123".to_string(),
upstream_task_id: "ext-video-task-123".to_string(),
created_at_unix_secs: 1712345678,
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: Some("sora-2".to_string()),
prompt: None,
size: None,
seconds: None,
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
completed_at_unix_secs: None,
expires_at_unix_secs: None,
error_code: None,
error_message: None,
video_url: None,
persistence: sample_persistence("openai:video"),
transport: sample_transport("https://api.openai.example", "openai:video"),
}));
service.record_snapshot(LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
local_short_id: "short12345678".to_string(),
upstream_operation_name: "operations/ext-video-task-123".to_string(),
user_id: Some("user-123".to_string()),
api_key_id: Some("key-123".to_string()),
model: "veo-3".to_string(),
status: LocalVideoTaskStatus::Submitted,
progress_percent: 0,
error_code: None,
error_message: None,
metadata: json!({}),
persistence: sample_persistence("gemini:video"),
transport: sample_transport("https://generativelanguage.googleapis.com", "gemini:video"),
}));
service.apply_finalize_mutation(
"/v1/videos/task-local-123/cancel",
"openai_video_cancel_sync_finalize",
);
let cancelled_openai = service
.read_response(Some("openai"), "/v1/videos/task-local-123")
.expect("openai read response should exist");
assert_eq!(cancelled_openai.status_code, 404);
assert_eq!(
cancelled_openai.body_json,
json!({"detail": "Video task was cancelled"})
);
service.apply_finalize_mutation(
"/v1beta/models/veo-3/operations/short12345678:cancel",
"gemini_video_cancel_sync_finalize",
);
let cancelled_gemini = service
.read_response(
Some("gemini"),
"/v1beta/models/veo-3/operations/short12345678",
)
.expect("gemini read response should exist");
assert_eq!(cancelled_gemini.status_code, 404);
assert_eq!(
cancelled_gemini.body_json,
json!({"detail": "Video task was cancelled"})
);
service.apply_finalize_mutation(
"/v1/videos/task-local-123",
"openai_video_delete_sync_finalize",
);
let deleted_openai = service
.read_response(Some("openai"), "/v1/videos/task-local-123")
.expect("deleted openai read response should exist");
assert_eq!(deleted_openai.status_code, 404);
assert_eq!(
deleted_openai.body_json,
json!({"detail": "Video task not found"})
);
}
#[test]
fn seed_captures_transport_metadata_from_execution_plan() {
let provider_body = json!({"id": "ext-video-task-123"});
let report_context = json!({});
let seed = LocalVideoTaskSeed::from_sync_finalize(
"openai_video_create_sync_finalize",
provider_body
.as_object()
.expect("provider body should be object"),
report_context
.as_object()
.expect("report context should be object"),
&sample_plan("https://api.openai.example/v1/videos", "openai:video"),
)
.expect("seed should build");
let LocalVideoTaskSeed::OpenAiCreate(seed) = seed else {
panic!("seed should be openai");
};
assert_eq!(
seed.transport.upstream_base_url,
"https://api.openai.example"
);
assert_eq!(seed.transport.provider_id, "provider-123");
assert_eq!(seed.transport.endpoint_id, "endpoint-123");
assert_eq!(seed.transport.key_id, "key-123");
assert_eq!(
seed.transport
.headers
.get("authorization")
.map(String::as_str),
Some("Bearer upstream-key")
);
}

View File

@@ -0,0 +1,150 @@
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum VideoTaskSyncReportMode {
InlineSync,
Background,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VideoTaskTruthSourceMode {
#[default]
PythonSyncReport,
#[allow(dead_code)] // reserved for the next migration step when Rust owns video_tasks writes
RustAuthoritative,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LocalVideoTaskSuccessPlan {
pub(super) seed: LocalVideoTaskSeed,
pub(super) report_mode: VideoTaskSyncReportMode,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LocalVideoTaskFollowUpPlan {
pub(crate) plan: ExecutionPlan,
pub(crate) report_kind: Option<String>,
pub(crate) report_context: Option<Value>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LocalVideoTaskReadRefreshPlan {
pub(crate) plan: ExecutionPlan,
pub(super) projection_target: LocalVideoTaskProjectionTarget,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum LocalVideoTaskContentAction {
Immediate { status_code: u16, body_json: Value },
StreamPlan(ExecutionPlan),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum LocalVideoTaskProjectionTarget {
OpenAi { task_id: String },
Gemini { short_id: String },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) enum LocalVideoTaskSnapshot {
OpenAi(OpenAiVideoTaskSeed),
Gemini(GeminiVideoTaskSeed),
}
#[allow(dead_code)] // projection states are staged for upcoming poller/store wiring
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) enum LocalVideoTaskStatus {
Submitted,
Queued,
Processing,
Completed,
Failed,
Cancelled,
Expired,
Deleted,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LocalVideoTaskReadResponse {
pub(crate) status_code: u16,
pub(crate) body_json: Value,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum LocalVideoTaskRegistryMutation {
OpenAiCancelled { task_id: String },
OpenAiDeleted { task_id: String },
GeminiCancelled { short_id: String },
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum LocalVideoTaskSeed {
OpenAiCreate(OpenAiVideoTaskSeed),
OpenAiRemix(OpenAiVideoTaskSeed),
GeminiCreate(GeminiVideoTaskSeed),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct LocalVideoTaskTransport {
pub(super) upstream_base_url: String,
pub(super) provider_name: Option<String>,
pub(super) provider_id: String,
pub(super) endpoint_id: String,
pub(super) key_id: String,
pub(super) headers: BTreeMap<String, String>,
pub(super) content_type: Option<String>,
pub(super) model_name: Option<String>,
pub(super) proxy: Option<ProxySnapshot>,
pub(super) tls_profile: Option<String>,
pub(super) timeouts: Option<ExecutionTimeouts>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct LocalVideoTaskPersistence {
pub(super) request_id: String,
pub(super) username: Option<String>,
pub(super) api_key_name: Option<String>,
pub(super) client_api_format: String,
pub(super) provider_api_format: String,
pub(super) original_request_body: Value,
pub(super) format_converted: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct OpenAiVideoTaskSeed {
pub(super) local_task_id: String,
pub(super) upstream_task_id: String,
pub(super) created_at_unix_secs: u64,
pub(super) user_id: Option<String>,
pub(super) api_key_id: Option<String>,
pub(super) model: Option<String>,
pub(super) prompt: Option<String>,
pub(super) size: Option<String>,
pub(super) seconds: Option<String>,
pub(super) remixed_from_video_id: Option<String>,
pub(super) status: LocalVideoTaskStatus,
pub(super) progress_percent: u16,
pub(super) completed_at_unix_secs: Option<u64>,
pub(super) expires_at_unix_secs: Option<u64>,
pub(super) error_code: Option<String>,
pub(super) error_message: Option<String>,
pub(super) video_url: Option<String>,
pub(super) persistence: LocalVideoTaskPersistence,
pub(super) transport: LocalVideoTaskTransport,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct GeminiVideoTaskSeed {
pub(super) local_short_id: String,
pub(super) upstream_operation_name: String,
pub(super) user_id: Option<String>,
pub(super) api_key_id: Option<String>,
pub(super) model: String,
pub(super) status: LocalVideoTaskStatus,
pub(super) progress_percent: u16,
pub(super) error_code: Option<String>,
pub(super) error_message: Option<String>,
pub(super) metadata: Value,
pub(super) persistence: LocalVideoTaskPersistence,
pub(super) transport: LocalVideoTaskTransport,
}