refactor: 拆分 gateway 单体为独立 crate,新增 systemd 部署方案

将 gateway 内部的 model-fetch、provider-transport、scheduler-core、
usage-runtime、video-tasks-core 模块提取为独立 crate;重构 gateway
内部模块结构(state/router/cache/data/query 等);移除大量遗留模块
文件;新增 systemd 二进制部署骨架及相关文档;更新前端 usage 相关
API 和组件。
This commit is contained in:
fawney19
2026-04-05 20:23:16 +08:00
parent cbc811f6ce
commit 763ff03a7b
777 changed files with 42659 additions and 21469 deletions

View File

@@ -0,0 +1,15 @@
[package]
name = "aether-video-tasks-core"
version = "0.1.0"
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
aether-contracts.workspace = true
aether-data.workspace = true
async-trait.workspace = true
serde.workspace = true
serde_json.workspace = true
url.workspace = true
uuid.workspace = true

View File

@@ -0,0 +1,108 @@
use serde_json::{Map, Value};
pub 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 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 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 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 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,
})
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{
context_text, context_u64, request_body_string, request_body_text, request_body_u32,
};
#[test]
fn context_helpers_trim_and_parse_values() {
let context = json!({
"request_id": " req_123 ",
"local_created_at": "42",
});
let context = context.as_object().expect("object");
assert_eq!(
context_text(context, "request_id").as_deref(),
Some("req_123")
);
assert_eq!(context_u64(context, "local_created_at"), Some(42));
}
#[test]
fn request_body_helpers_read_nested_original_request_body() {
let context = json!({
"original_request_body": {
"prompt": " hello ",
"seconds": "8",
}
});
let context = context.as_object().expect("object");
assert_eq!(
request_body_text(context, "prompt").as_deref(),
Some("hello")
);
assert_eq!(request_body_text(context, "seconds").as_deref(), Some("8"));
}
#[test]
fn request_body_helpers_read_flat_json_values() {
let body = json!({
"prompt": " hello ",
"seconds": 8,
});
assert_eq!(
request_body_string(&body, "prompt").as_deref(),
Some("hello")
);
assert_eq!(request_body_u32(&body, "seconds"), Some(8));
}
}

View File

@@ -0,0 +1,123 @@
use serde_json::{Map, Value};
pub fn build_video_follow_up_report_context(
request_id: &str,
user_id: &str,
api_key_id: &str,
task_id: &str,
provider_id: &str,
endpoint_id: &str,
key_id: &str,
provider_name: Option<&str>,
model_name: Option<&str>,
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(provider_id.to_string()),
);
context.insert(
"endpoint_id".to_string(),
Value::String(endpoint_id.to_string()),
);
context.insert("key_id".to_string(), Value::String(key_id.to_string()));
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) = provider_name.filter(|value| !value.is_empty()) {
context.insert(
"provider_name".to_string(),
Value::String(provider_name.to_string()),
);
}
if let Some(model_name) = model_name.filter(|value| !value.is_empty()) {
context.insert("model".to_string(), Value::String(model_name.to_string()));
}
Value::Object(context)
}
pub fn resolve_follow_up_auth(
user_id: Option<&str>,
api_key_id: Option<&str>,
fallback_user_id: Option<&str>,
fallback_api_key_id: Option<&str>,
) -> Option<(String, String)> {
let resolved_user_id = user_id
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
.or_else(|| {
fallback_user_id
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
})?;
let resolved_api_key_id = api_key_id
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
.or_else(|| {
fallback_api_key_id
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
})?;
Some((resolved_user_id, resolved_api_key_id))
}
#[cfg(test)]
mod tests {
use super::{build_video_follow_up_report_context, resolve_follow_up_auth};
#[test]
fn builds_follow_up_report_context_with_transport_metadata() {
let context = build_video_follow_up_report_context(
"req_123",
"user_123",
"key_123",
"task_123",
"provider_123",
"endpoint_123",
"transport_key_123",
Some("provider-name"),
Some("model-name"),
"openai:video",
"openai:video",
);
assert_eq!(context["request_id"].as_str(), Some("req_123"));
assert_eq!(context["provider_id"].as_str(), Some("provider_123"));
assert_eq!(context["provider_name"].as_str(), Some("provider-name"));
assert_eq!(context["model"].as_str(), Some("model-name"));
}
#[test]
fn resolves_follow_up_auth_from_primary_or_fallback_values() {
assert_eq!(
resolve_follow_up_auth(Some(" user_123 "), Some(" key_123 "), None, None),
Some(("user_123".to_string(), "key_123".to_string()))
);
assert_eq!(
resolve_follow_up_auth(None, None, Some("user_fallback"), Some("key_fallback")),
Some(("user_fallback".to_string(), "key_fallback".to_string()))
);
assert_eq!(resolve_follow_up_auth(None, None, None, None), None);
}
}

View File

@@ -0,0 +1,419 @@
use aether_contracts::{ExecutionPlan, RequestBody};
use aether_data::repository::video_tasks::{StoredVideoTask, UpsertVideoTask, VideoTaskStatus};
use serde_json::{json, Map, Value};
use crate::{
build_video_follow_up_report_context, current_unix_timestamp_secs, gemini_metadata_video_url,
request_body_string, request_body_u32, resolve_follow_up_auth, GeminiVideoTaskSeed,
LocalVideoTaskFollowUpPlan, LocalVideoTaskReadResponse, LocalVideoTaskSnapshot,
LocalVideoTaskStatus, DEFAULT_VIDEO_TASK_MAX_POLL_COUNT,
DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS,
};
pub fn map_gemini_stored_task_to_read_response(
task: StoredVideoTask,
) -> LocalVideoTaskReadResponse {
match task.status {
VideoTaskStatus::Cancelled => LocalVideoTaskReadResponse {
status_code: 404,
body_json: json!({"detail": "Video task was cancelled"}),
},
VideoTaskStatus::Deleted => LocalVideoTaskReadResponse {
status_code: 404,
body_json: json!({"detail": "Video task not found"}),
},
VideoTaskStatus::Completed => LocalVideoTaskReadResponse {
status_code: 200,
body_json: build_gemini_completed_body(task),
},
VideoTaskStatus::Failed | VideoTaskStatus::Expired => LocalVideoTaskReadResponse {
status_code: 200,
body_json: build_gemini_failed_body(task),
},
_ => LocalVideoTaskReadResponse {
status_code: 200,
body_json: build_gemini_pending_body(task),
},
}
}
fn build_gemini_completed_body(task: StoredVideoTask) -> Value {
let operation_name = stored_task_operation_name(&task);
let short_id = task.short_id.unwrap_or_default();
json!({
"name": operation_name,
"done": true,
"response": {
"generateVideoResponse": {
"generatedSamples": [
{
"video": {
"uri": format!("/v1beta/files/aev_{short_id}:download?alt=media"),
"mimeType": "video/mp4"
}
}
]
}
}
})
}
fn build_gemini_failed_body(task: StoredVideoTask) -> Value {
json!({
"name": stored_task_operation_name(&task),
"done": true,
"error": {
"code": task.error_code.unwrap_or_else(|| "UNKNOWN".to_string()),
"message": task
.error_message
.unwrap_or_else(|| "Video generation failed".to_string()),
}
})
}
fn build_gemini_pending_body(task: StoredVideoTask) -> Value {
json!({
"name": stored_task_operation_name(&task),
"done": false,
"metadata": {}
})
}
fn stored_task_operation_name(task: &StoredVideoTask) -> String {
let model = task.model.clone().unwrap_or_else(|| "unknown".to_string());
let short_id = task.short_id.clone().unwrap_or_else(|| task.id.clone());
format!("models/{model}/operations/{short_id}")
}
impl GeminiVideoTaskSeed {
pub 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 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(),
})
}
pub fn build_cancel_follow_up_plan(
&self,
fallback_user_id: Option<&str>,
fallback_api_key_id: Option<&str>,
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(),
fallback_user_id,
fallback_api_key_id,
)?;
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,
&self.transport.provider_id,
&self.transport.endpoint_id,
&self.transport.key_id,
self.transport.provider_name.as_deref(),
Some(self.model.as_str()),
"gemini:video",
"gemini:video",
)),
})
}
pub 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 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()),
})),
}
}
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
}
}
}
#[cfg(test)]
mod tests {
use aether_data::repository::video_tasks::{StoredVideoTask, VideoTaskStatus};
use super::map_gemini_stored_task_to_read_response;
fn sample_stored_task(status: VideoTaskStatus) -> StoredVideoTask {
StoredVideoTask {
id: "task-gemini-123".to_string(),
short_id: Some("localshort123".to_string()),
request_id: "req-gemini-123".to_string(),
user_id: None,
api_key_id: None,
username: None,
api_key_name: None,
external_task_id: Some("operations/ext-gemini-123".to_string()),
provider_id: None,
endpoint_id: None,
key_id: None,
client_api_format: Some("gemini:video".to_string()),
provider_api_format: Some("gemini:video".to_string()),
format_converted: false,
model: Some("veo-3".to_string()),
prompt: None,
original_request_body: None,
duration_seconds: None,
resolution: None,
aspect_ratio: None,
size: None,
status,
progress_percent: 50,
progress_message: None,
retry_count: 0,
poll_interval_seconds: 10,
next_poll_at_unix_secs: None,
poll_count: 0,
max_poll_count: 360,
created_at_unix_secs: 1712345678,
submitted_at_unix_secs: Some(1712345678),
completed_at_unix_secs: None,
updated_at_unix_secs: 1712345679,
error_code: Some("UNKNOWN".to_string()),
error_message: Some("provider failed".to_string()),
video_url: None,
request_metadata: None,
}
}
#[test]
fn maps_cancelled_gemini_stored_task_into_not_found_response() {
let response =
map_gemini_stored_task_to_read_response(sample_stored_task(VideoTaskStatus::Cancelled));
assert_eq!(response.status_code, 404);
assert_eq!(response.body_json["detail"], "Video task was cancelled");
}
}

View File

@@ -0,0 +1,55 @@
mod body;
mod follow_up;
mod gemini;
mod openai;
mod path;
mod read_side;
mod service;
mod snapshot;
mod store;
mod store_backend;
mod store_registry;
mod sync;
mod transport;
mod transport_domain;
mod types;
mod util;
pub use body::{
context_text, context_u64, request_body_string, request_body_text, request_body_u32,
};
pub use follow_up::{build_video_follow_up_report_context, resolve_follow_up_auth};
pub use gemini::map_gemini_stored_task_to_read_response;
pub use openai::map_openai_stored_task_to_read_response;
pub use path::{
build_local_sync_finalize_request_path, current_unix_timestamp_secs,
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,
generate_local_short_id, local_status_from_stored, resolve_local_video_registry_mutation,
resolve_video_task_hydration_lookup_key, resolve_video_task_read_lookup_key,
resolve_video_task_report_lookup, VideoTaskReportLookup,
};
pub use read_side::{read_data_backed_video_task_response, StoredVideoTaskReadSide};
pub use service::VideoTaskService;
pub use store::VideoTaskStore;
pub use store_backend::{FileVideoTaskStore, InMemoryVideoTaskStore};
pub use store_registry::VideoTaskRegistry;
pub use sync::{
build_internal_finalize_video_plan, build_local_sync_finalize_read_response,
resolve_local_sync_error_background_report_kind,
resolve_local_sync_success_background_report_kind,
};
pub use transport::{
gemini_metadata_video_url, map_openai_task_status, parse_video_content_variant,
};
pub use types::{
GeminiVideoTaskSeed, LocalVideoTaskContentAction, LocalVideoTaskFollowUpPlan,
LocalVideoTaskPersistence, LocalVideoTaskProjectionTarget, LocalVideoTaskReadRefreshPlan,
LocalVideoTaskReadResponse, LocalVideoTaskRegistryMutation, LocalVideoTaskSeed,
LocalVideoTaskSnapshot, LocalVideoTaskStatus, LocalVideoTaskSuccessPlan,
LocalVideoTaskTransport, LocalVideoTaskTransportBridgeInput, OpenAiVideoTaskSeed,
VideoTaskSyncReportMode, VideoTaskTruthSourceMode, DEFAULT_VIDEO_TASK_MAX_POLL_COUNT,
DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS,
};
pub use util::non_empty_owned;

View File

@@ -0,0 +1,684 @@
use std::collections::BTreeMap;
use aether_contracts::{ExecutionPlan, RequestBody};
use aether_data::repository::video_tasks::{StoredVideoTask, UpsertVideoTask, VideoTaskStatus};
use serde_json::{json, Map, Value};
use crate::{
build_video_follow_up_report_context, current_unix_timestamp_secs, map_openai_task_status,
parse_video_content_variant, request_body_string, request_body_u32, resolve_follow_up_auth,
LocalVideoTaskContentAction, LocalVideoTaskFollowUpPlan, LocalVideoTaskReadResponse,
LocalVideoTaskSnapshot, LocalVideoTaskStatus, OpenAiVideoTaskSeed,
DEFAULT_VIDEO_TASK_MAX_POLL_COUNT, DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS,
};
pub fn map_openai_stored_task_to_read_response(
task: StoredVideoTask,
) -> LocalVideoTaskReadResponse {
match task.status {
VideoTaskStatus::Cancelled => LocalVideoTaskReadResponse {
status_code: 404,
body_json: json!({"detail": "Video task was cancelled"}),
},
VideoTaskStatus::Deleted => LocalVideoTaskReadResponse {
status_code: 404,
body_json: json!({"detail": "Video task not found"}),
},
status => LocalVideoTaskReadResponse {
status_code: 200,
body_json: build_openai_stored_task_body(task, status),
},
}
}
fn build_openai_stored_task_body(task: StoredVideoTask, status: VideoTaskStatus) -> Value {
let mut body = json!({
"id": task.id,
"object": "video",
"status": map_openai_stored_task_status(status),
"progress": task.progress_percent,
"created_at": task.created_at_unix_secs,
});
if let Some(model) = task.model {
body["model"] = Value::String(model);
}
if let Some(prompt) = task.prompt {
body["prompt"] = Value::String(prompt);
}
if let Some(size) = task.size {
body["size"] = Value::String(size);
}
if let Some(video_url) = task.video_url {
body["video_url"] = Value::String(video_url);
}
if let Some(completed_at) = task.completed_at_unix_secs {
body["completed_at"] = Value::Number(completed_at.into());
}
if matches!(
status,
VideoTaskStatus::Failed | VideoTaskStatus::Expired | VideoTaskStatus::Cancelled
) {
body["error"] = json!({
"code": task.error_code.unwrap_or_else(|| "unknown".to_string()),
"message": task
.error_message
.unwrap_or_else(|| "Video generation failed".to_string()),
});
}
body
}
fn map_openai_stored_task_status(status: VideoTaskStatus) -> &'static str {
match status {
VideoTaskStatus::Pending | VideoTaskStatus::Submitted | VideoTaskStatus::Queued => "queued",
VideoTaskStatus::Processing => "processing",
VideoTaskStatus::Completed => "completed",
VideoTaskStatus::Failed | VideoTaskStatus::Cancelled | VideoTaskStatus::Expired => "failed",
VideoTaskStatus::Deleted => "deleted",
}
}
impl OpenAiVideoTaskSeed {
pub 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);
}
pub 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 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
}
pub fn build_delete_follow_up_plan(
&self,
fallback_user_id: Option<&str>,
fallback_api_key_id: Option<&str>,
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(),
fallback_user_id,
fallback_api_key_id,
)?;
let model_name = self
.model
.clone()
.or_else(|| self.transport.model_name.clone());
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: 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.transport.provider_id,
&self.transport.endpoint_id,
&self.transport.key_id,
self.transport.provider_name.as_deref(),
model_name.as_deref(),
"openai:video",
"openai:video",
)),
})
}
pub 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 fn build_cancel_follow_up_plan(
&self,
fallback_user_id: Option<&str>,
fallback_api_key_id: Option<&str>,
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(),
fallback_user_id,
fallback_api_key_id,
)?;
let model_name = self
.model
.clone()
.or_else(|| self.transport.model_name.clone());
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: 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.transport.provider_id,
&self.transport.endpoint_id,
&self.transport.key_id,
self.transport.provider_name.as_deref(),
model_name.as_deref(),
"openai:video",
"openai:video",
)),
})
}
pub fn build_remix_follow_up_plan(
&self,
body_json: &Value,
fallback_user_id: Option<&str>,
fallback_api_key_id: Option<&str>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
if !matches!(self.status, LocalVideoTaskStatus::Completed) || 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(),
fallback_user_id,
fallback_api_key_id,
)?;
let model_name = self
.model
.clone()
.or_else(|| self.transport.model_name.clone());
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.transport.provider_id,
&self.transport.endpoint_id,
&self.transport.key_id,
self.transport.provider_name.as_deref(),
model_name.as_deref(),
"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,
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),
})
}
pub 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()),
})),
}
}
}
#[cfg(test)]
mod tests {
use aether_data::repository::video_tasks::{StoredVideoTask, VideoTaskStatus};
use super::map_openai_stored_task_to_read_response;
fn sample_stored_task(status: VideoTaskStatus) -> StoredVideoTask {
StoredVideoTask {
id: "task-openai-123".to_string(),
short_id: None,
request_id: "req-openai-123".to_string(),
user_id: None,
api_key_id: None,
username: None,
api_key_name: None,
external_task_id: Some("ext-openai-123".to_string()),
provider_id: None,
endpoint_id: None,
key_id: None,
client_api_format: Some("openai:video".to_string()),
provider_api_format: Some("openai:video".to_string()),
format_converted: false,
model: Some("sora-2".to_string()),
prompt: Some("hello".to_string()),
original_request_body: None,
duration_seconds: None,
resolution: None,
aspect_ratio: None,
size: Some("1280x720".to_string()),
status,
progress_percent: 100,
progress_message: None,
retry_count: 0,
poll_interval_seconds: 10,
next_poll_at_unix_secs: None,
poll_count: 0,
max_poll_count: 360,
created_at_unix_secs: 1712345678,
submitted_at_unix_secs: Some(1712345678),
completed_at_unix_secs: Some(1712345688),
updated_at_unix_secs: 1712345688,
error_code: Some("upstream_failed".to_string()),
error_message: Some("provider failed".to_string()),
video_url: Some("https://cdn.example.com/video.mp4".to_string()),
request_metadata: None,
}
}
#[test]
fn maps_openai_failed_stored_task_into_read_response() {
let response =
map_openai_stored_task_to_read_response(sample_stored_task(VideoTaskStatus::Failed));
assert_eq!(response.status_code, 200);
assert_eq!(response.body_json["id"], "task-openai-123");
assert_eq!(response.body_json["status"], "failed");
assert_eq!(response.body_json["completed_at"], 1712345688u64);
assert_eq!(response.body_json["error"]["code"], "upstream_failed");
assert_eq!(
response.body_json["video_url"],
"https://cdn.example.com/video.mp4"
);
}
}

View File

@@ -0,0 +1,410 @@
use std::time::{SystemTime, UNIX_EPOCH};
use aether_data::repository::video_tasks::VideoTaskLookupKey;
use aether_data::repository::video_tasks::VideoTaskStatus as StoredVideoTaskStatus;
use serde_json::Value;
use uuid::Uuid;
use crate::{LocalVideoTaskRegistryMutation, LocalVideoTaskStatus, VideoTaskTruthSourceMode};
pub 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 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 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 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 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 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 fn resolve_video_task_read_lookup_key<'a>(
route_family: Option<&str>,
request_path: &'a str,
) -> Option<VideoTaskLookupKey<'a>> {
match route_family {
Some("openai") => {
extract_openai_task_id_from_path(request_path).map(VideoTaskLookupKey::Id)
}
Some("gemini") => {
extract_gemini_short_id_from_path(request_path).map(VideoTaskLookupKey::ShortId)
}
_ => None,
}
}
pub fn resolve_video_task_hydration_lookup_key<'a>(
route_family: Option<&str>,
request_path: &'a str,
) -> Option<VideoTaskLookupKey<'a>> {
match route_family {
Some("openai") => extract_openai_task_id_from_path(request_path)
.or_else(|| extract_openai_task_id_from_cancel_path(request_path))
.or_else(|| extract_openai_task_id_from_remix_path(request_path))
.or_else(|| extract_openai_task_id_from_content_path(request_path))
.map(VideoTaskLookupKey::Id),
Some("gemini") => extract_gemini_short_id_from_path(request_path)
.or_else(|| extract_gemini_short_id_from_cancel_path(request_path))
.map(VideoTaskLookupKey::ShortId),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VideoTaskReportLookup<'a> {
Lookup(VideoTaskLookupKey<'a>),
TaskIdOrExternal {
task_id: &'a str,
user_id: Option<&'a str>,
},
}
pub fn resolve_video_task_report_lookup<'a>(
context: &'a Value,
) -> Option<VideoTaskReportLookup<'a>> {
if let Some(task_id) = non_empty_context_str(context, "local_task_id") {
return Some(VideoTaskReportLookup::Lookup(VideoTaskLookupKey::Id(
task_id,
)));
}
if let Some(short_id) = non_empty_context_str(context, "local_short_id") {
return Some(VideoTaskReportLookup::Lookup(VideoTaskLookupKey::ShortId(
short_id,
)));
}
let task_id = non_empty_context_str(context, "task_id")?;
Some(VideoTaskReportLookup::TaskIdOrExternal {
task_id,
user_id: non_empty_context_str(context, "user_id"),
})
}
pub fn build_local_sync_finalize_request_path(
report_kind: &str,
signature: &str,
report_context: Option<&Value>,
) -> Option<String> {
match report_kind {
"openai_video_delete_sync_finalize" => {
let task_id =
report_context.and_then(|value| non_empty_context_str(value, "task_id"))?;
Some(format!("/v1/videos/{task_id}"))
}
"openai_video_cancel_sync_finalize" => {
let task_id =
report_context.and_then(|value| non_empty_context_str(value, "task_id"))?;
Some(format!("/v1/videos/{task_id}/cancel"))
}
"gemini_video_cancel_sync_finalize" => {
let short_id = report_context
.and_then(|value| non_empty_context_str(value, "task_id"))
.or_else(|| {
report_context.and_then(|value| non_empty_context_str(value, "local_short_id"))
})
.or_else(|| {
report_context
.and_then(|value| non_empty_context_str(value, "operation_name"))
.and_then(|value| value.rsplit('/').next())
.map(str::trim)
.filter(|value| !value.is_empty())
})?;
let model = report_context
.and_then(|value| non_empty_context_str(value, "model"))
.or_else(|| {
report_context.and_then(|value| non_empty_context_str(value, "model_name"))
})
.unwrap_or(match signature {
"gemini:video" => "veo-3",
_ => "unknown",
});
Some(format!(
"/v1beta/models/{model}/operations/{short_id}:cancel"
))
}
_ => None,
}
}
pub 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 fn current_unix_timestamp_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
pub fn generate_local_short_id() -> String {
Uuid::new_v4()
.simple()
.to_string()
.chars()
.take(12)
.collect()
}
pub 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,
}
}
fn non_empty_context_str<'a>(context: &'a Value, key: &str) -> Option<&'a str> {
context
.get(key)
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
}
#[cfg(test)]
mod tests {
use super::{
build_local_sync_finalize_request_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, generate_local_short_id,
resolve_video_task_hydration_lookup_key, resolve_video_task_read_lookup_key,
resolve_video_task_report_lookup, VideoTaskReportLookup,
};
use aether_data::repository::video_tasks::VideoTaskLookupKey;
use serde_json::json;
#[test]
fn path_extractors_handle_supported_video_routes() {
assert_eq!(
extract_openai_task_id_from_path("/v1/videos/task_123"),
Some("task_123")
);
assert_eq!(
extract_openai_task_id_from_cancel_path("/v1/videos/task_123/cancel"),
Some("task_123")
);
assert_eq!(
extract_openai_task_id_from_remix_path("/v1/videos/task_123/remix"),
Some("task_123")
);
assert_eq!(
extract_openai_task_id_from_content_path("/v1/videos/task_123/content"),
Some("task_123")
);
assert_eq!(
extract_gemini_short_id_from_path("/v1beta/models/foo/operations/abc123"),
Some("abc123")
);
assert_eq!(
extract_gemini_short_id_from_cancel_path("/v1beta/models/foo/operations/abc123:cancel"),
Some("abc123")
);
}
#[test]
fn local_short_id_has_expected_length() {
assert_eq!(generate_local_short_id().len(), 12);
}
#[test]
fn resolves_video_task_read_lookup_key_for_supported_read_paths() {
assert_eq!(
resolve_video_task_read_lookup_key(Some("openai"), "/v1/videos/task_123"),
Some(VideoTaskLookupKey::Id("task_123"))
);
assert_eq!(
resolve_video_task_read_lookup_key(
Some("gemini"),
"/v1beta/models/foo/operations/abc123"
),
Some(VideoTaskLookupKey::ShortId("abc123"))
);
assert_eq!(
resolve_video_task_read_lookup_key(Some("openai"), "/v1/videos/task_123/cancel"),
None
);
}
#[test]
fn resolves_video_task_hydration_lookup_key_for_supported_follow_up_paths() {
assert_eq!(
resolve_video_task_hydration_lookup_key(Some("openai"), "/v1/videos/task_123/cancel"),
Some(VideoTaskLookupKey::Id("task_123"))
);
assert_eq!(
resolve_video_task_hydration_lookup_key(Some("openai"), "/v1/videos/task_123/remix"),
Some(VideoTaskLookupKey::Id("task_123"))
);
assert_eq!(
resolve_video_task_hydration_lookup_key(
Some("gemini"),
"/v1beta/models/foo/operations/abc123:cancel"
),
Some(VideoTaskLookupKey::ShortId("abc123"))
);
}
#[test]
fn resolves_video_task_report_lookup_for_supported_context_shapes() {
assert_eq!(
resolve_video_task_report_lookup(&json!({
"local_task_id": "task-local-123"
})),
Some(VideoTaskReportLookup::Lookup(VideoTaskLookupKey::Id(
"task-local-123"
)))
);
assert_eq!(
resolve_video_task_report_lookup(&json!({
"local_short_id": "short-local-123"
})),
Some(VideoTaskReportLookup::Lookup(VideoTaskLookupKey::ShortId(
"short-local-123"
)))
);
assert_eq!(
resolve_video_task_report_lookup(&json!({
"task_id": "task-upstream-123",
"user_id": "user-123"
})),
Some(VideoTaskReportLookup::TaskIdOrExternal {
task_id: "task-upstream-123",
user_id: Some("user-123"),
})
);
}
#[test]
fn builds_local_sync_finalize_request_path_for_supported_video_finalize_kinds() {
assert_eq!(
build_local_sync_finalize_request_path(
"openai_video_delete_sync_finalize",
"openai:video",
Some(&json!({"task_id": "task_123"})),
),
Some("/v1/videos/task_123".to_string())
);
assert_eq!(
build_local_sync_finalize_request_path(
"openai_video_cancel_sync_finalize",
"openai:video",
Some(&json!({"task_id": "task_123"})),
),
Some("/v1/videos/task_123/cancel".to_string())
);
assert_eq!(
build_local_sync_finalize_request_path(
"gemini_video_cancel_sync_finalize",
"gemini:video",
Some(&json!({"operation_name": "models/veo-3/operations/abc123"})),
),
Some("/v1beta/models/veo-3/operations/abc123:cancel".to_string())
);
}
#[test]
fn rejects_local_sync_finalize_request_path_when_context_is_invalid() {
assert_eq!(
build_local_sync_finalize_request_path(
"openai_video_delete_sync_finalize",
"openai:video",
Some(&json!({})),
),
None
);
assert_eq!(
build_local_sync_finalize_request_path(
"gemini_video_cancel_sync_finalize",
"gemini:video",
Some(&json!({"task_id": ""})),
),
None
);
assert_eq!(
build_local_sync_finalize_request_path(
"unknown_finalize_kind",
"gemini:video",
Some(&json!({"task_id": "abc123"})),
),
None
);
}
}

View File

@@ -0,0 +1,66 @@
use aether_data::repository::video_tasks::{StoredVideoTask, VideoTaskLookupKey};
use aether_data::DataLayerError;
use async_trait::async_trait;
use crate::{
map_gemini_stored_task_to_read_response, map_openai_stored_task_to_read_response,
resolve_video_task_read_lookup_key, LocalVideoTaskReadResponse,
};
#[async_trait]
pub trait StoredVideoTaskReadSide: Send + Sync {
async fn find_stored_video_task(
&self,
key: VideoTaskLookupKey<'_>,
) -> Result<Option<StoredVideoTask>, DataLayerError>;
}
pub async fn read_data_backed_video_task_response(
state: &impl StoredVideoTaskReadSide,
route_family: Option<&str>,
request_path: &str,
) -> Result<Option<LocalVideoTaskReadResponse>, DataLayerError> {
match route_family {
Some("openai") => read_openai_video_task_response(state, request_path).await,
Some("gemini") => read_gemini_video_task_response(state, request_path).await,
_ => Ok(None),
}
}
async fn read_openai_video_task_response(
state: &impl StoredVideoTaskReadSide,
request_path: &str,
) -> Result<Option<LocalVideoTaskReadResponse>, DataLayerError> {
let Some(lookup) = resolve_video_task_read_lookup_key(Some("openai"), request_path) else {
return Ok(None);
};
let Some(task) = state.find_stored_video_task(lookup).await? else {
return Ok(None);
};
if !matches!(task.provider_api_format.as_deref(), Some("openai:video")) {
return Ok(None);
}
Ok(Some(map_openai_stored_task_to_read_response(task)))
}
async fn read_gemini_video_task_response(
state: &impl StoredVideoTaskReadSide,
request_path: &str,
) -> Result<Option<LocalVideoTaskReadResponse>, DataLayerError> {
let Some(lookup) = resolve_video_task_read_lookup_key(Some("gemini"), request_path) else {
return Ok(None);
};
let Some(task) = state.find_stored_video_task(lookup).await? else {
return Ok(None);
};
if !matches!(task.provider_api_format.as_deref(), Some("gemini:video")) {
return Ok(None);
}
Ok(Some(map_gemini_stored_task_to_read_response(task)))
}

View File

@@ -0,0 +1,330 @@
use std::path::PathBuf;
use std::sync::Arc;
use aether_contracts::ExecutionPlan;
use aether_data::repository::video_tasks::StoredVideoTask;
use serde_json::{Map, Value};
use crate::{
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,
resolve_local_video_registry_mutation, FileVideoTaskStore, InMemoryVideoTaskStore,
LocalVideoTaskContentAction, LocalVideoTaskFollowUpPlan, LocalVideoTaskProjectionTarget,
LocalVideoTaskReadRefreshPlan, LocalVideoTaskReadResponse, LocalVideoTaskSnapshot,
LocalVideoTaskSuccessPlan, VideoTaskStore, VideoTaskTruthSourceMode,
};
#[derive(Debug)]
pub struct VideoTaskService {
truth_source_mode: VideoTaskTruthSourceMode,
store: Arc<dyn VideoTaskStore>,
}
impl VideoTaskService {
pub fn new(mode: VideoTaskTruthSourceMode) -> Self {
Self::with_store(mode, Arc::new(InMemoryVideoTaskStore::default()))
}
pub 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 fn with_truth_source_mode(&self, mode: VideoTaskTruthSourceMode) -> Self {
Self {
truth_source_mode: mode,
store: self.store.clone(),
}
}
pub fn is_rust_authoritative(&self) -> bool {
self.truth_source_mode == VideoTaskTruthSourceMode::RustAuthoritative
}
pub fn truth_source_mode(&self) -> VideoTaskTruthSourceMode {
self.truth_source_mode
}
pub 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 fn record_snapshot(&self, snapshot: LocalVideoTaskSnapshot) {
self.store.insert(snapshot);
}
pub 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 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);
}
pub 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 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 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)
}
pub 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 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 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 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,
}
}
pub 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 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 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)
}
}
}
pub fn prepare_follow_up_sync_plan(
&self,
plan_kind: &str,
request_path: &str,
body_json: Option<&Value>,
fallback_user_id: Option<&str>,
fallback_api_key_id: Option<&str>,
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?,
fallback_user_id,
fallback_api_key_id,
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(fallback_user_id, fallback_api_key_id, 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(fallback_user_id, fallback_api_key_id, 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(fallback_user_id, fallback_api_key_id, trace_id)
}
_ => None,
}
}
}

View File

@@ -0,0 +1,163 @@
use aether_data::repository::video_tasks::{StoredVideoTask, UpsertVideoTask};
use serde_json::{json, Map, Value};
use crate::{
local_status_from_stored, non_empty_owned, request_body_string, GeminiVideoTaskSeed,
LocalVideoTaskPersistence, LocalVideoTaskReadResponse, LocalVideoTaskSnapshot,
LocalVideoTaskStatus, LocalVideoTaskTransport, OpenAiVideoTaskSeed,
};
impl LocalVideoTaskSnapshot {
pub fn to_upsert_record(&self) -> UpsertVideoTask {
match self {
Self::OpenAi(seed) => seed.to_upsert_record(),
Self::Gemini(seed) => seed.to_upsert_record(),
}
}
pub 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 fn from_stored_task_with_transport(
task: &StoredVideoTask,
transport: LocalVideoTaskTransport,
) -> Option<Self> {
let provider_api_format = task.provider_api_format.as_deref()?.trim();
let persistence = LocalVideoTaskPersistence::from_stored_task(task)?;
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 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(),
},
},
}
}
pub 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 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 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,18 @@
use serde_json::{Map, Value};
use crate::{
GeminiVideoTaskSeed, LocalVideoTaskReadResponse, LocalVideoTaskRegistryMutation,
LocalVideoTaskSnapshot, OpenAiVideoTaskSeed,
};
pub 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>;
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,171 @@
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use serde_json::{Map, Value};
use crate::{
GeminiVideoTaskSeed, LocalVideoTaskReadResponse, LocalVideoTaskRegistryMutation,
LocalVideoTaskSnapshot, OpenAiVideoTaskSeed, VideoTaskRegistry, VideoTaskStore,
};
#[derive(Debug, Default)]
pub struct InMemoryVideoTaskStore {
registry: Mutex<VideoTaskRegistry>,
}
#[derive(Debug)]
pub 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)
}
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 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)
}
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,100 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::{
GeminiVideoTaskSeed, LocalVideoTaskReadResponse, LocalVideoTaskRegistryMutation,
LocalVideoTaskSnapshot, LocalVideoTaskStatus, OpenAiVideoTaskSeed,
};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VideoTaskRegistry {
openai: BTreeMap<String, LocalVideoTaskSnapshot>,
gemini: BTreeMap<String, LocalVideoTaskSnapshot>,
}
impl VideoTaskRegistry {
pub 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 fn read_openai(&self, task_id: &str) -> Option<LocalVideoTaskReadResponse> {
self.openai
.get(task_id)
.map(LocalVideoTaskSnapshot::read_response)
}
pub fn read_gemini(&self, short_id: &str) -> Option<LocalVideoTaskReadResponse> {
self.gemini
.get(short_id)
.map(LocalVideoTaskSnapshot::read_response)
}
pub 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 fn clone_gemini(&self, short_id: &str) -> Option<GeminiVideoTaskSeed> {
let LocalVideoTaskSnapshot::Gemini(seed) = self.gemini.get(short_id)?.clone() else {
return None;
};
Some(seed)
}
pub 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 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;
}
}
}
}
pub 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
}
pub 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,473 @@
use aether_contracts::ExecutionPlan;
use serde_json::{json, Map, Value};
use uuid::Uuid;
use crate::{
context_text, context_u64, current_unix_timestamp_secs, generate_local_short_id,
request_body_text, GeminiVideoTaskSeed, LocalVideoTaskPersistence, LocalVideoTaskReadResponse,
LocalVideoTaskSeed, LocalVideoTaskSnapshot, LocalVideoTaskStatus, LocalVideoTaskSuccessPlan,
LocalVideoTaskTransport, OpenAiVideoTaskSeed, VideoTaskSyncReportMode,
VideoTaskTruthSourceMode,
};
impl LocalVideoTaskSeed {
pub 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())?;
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 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 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 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 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 fn success_report_kind(&self) -> &'static str {
self.seed.success_report_kind()
}
pub fn report_mode(&self) -> VideoTaskSyncReportMode {
self.report_mode
}
pub 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 fn client_body_json(&self) -> Value {
self.seed.client_body_json()
}
pub 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()),
}
}
}
pub fn build_internal_finalize_video_plan(
trace_id: &str,
signature: &str,
report_context: Option<&Value>,
) -> Option<ExecutionPlan> {
if !matches!(signature, "openai:video" | "gemini:video") {
return None;
}
let report_context = report_context.and_then(Value::as_object);
let context_text = |key: &str| {
report_context
.and_then(|value| value.get(key))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
};
let provider_name = signature
.split(':')
.next()
.expect("video finalize signature should include provider name")
.to_string();
let model_name = context_text("model")
.or_else(|| context_text("model_name"))
.or_else(|| match signature {
"openai:video" => Some("sora-2".to_string()),
"gemini:video" => Some("veo-3".to_string()),
_ => None,
});
let original_request_body = report_context
.and_then(|value| value.get("original_request_body"))
.cloned()
.unwrap_or_else(|| json!({}));
let url = match signature {
"openai:video" => "https://internal.gateway.invalid/v1/videos".to_string(),
"gemini:video" => format!(
"https://internal.gateway.invalid/v1beta/models/{}:predictLongRunning",
model_name.clone().unwrap_or_else(|| "veo-3".to_string())
),
_ => return None,
};
Some(ExecutionPlan {
request_id: context_text("request_id").unwrap_or_else(|| trace_id.to_string()),
candidate_id: None,
provider_name: Some(provider_name.clone()),
provider_id: context_text("provider_id")
.unwrap_or_else(|| format!("internal-{provider_name}-video-provider")),
endpoint_id: context_text("endpoint_id")
.unwrap_or_else(|| format!("internal-{provider_name}-video-endpoint")),
key_id: context_text("key_id")
.or_else(|| context_text("api_key_id"))
.unwrap_or_else(|| format!("internal-{provider_name}-video-key")),
method: "POST".to_string(),
url,
headers: std::collections::BTreeMap::from([(
"authorization".to_string(),
"Bearer internal-gateway".to_string(),
)]),
content_type: Some("application/json".to_string()),
content_encoding: None,
body: aether_contracts::RequestBody::from_json(original_request_body),
stream: false,
client_api_format: signature.to_string(),
provider_api_format: signature.to_string(),
model_name,
proxy: Some(aether_contracts::ProxySnapshot {
enabled: Some(false),
mode: Some("direct".to_string()),
node_id: None,
label: None,
url: None,
extra: None,
}),
tls_profile: None,
timeouts: None,
})
}
pub fn build_local_sync_finalize_read_response(
report_kind: &str,
upstream_status_code: u16,
report_context: Option<&Value>,
) -> Option<LocalVideoTaskReadResponse> {
match report_kind {
"openai_video_delete_sync_finalize" => {
if upstream_status_code >= 400 && upstream_status_code != 404 {
return None;
}
let task_id = report_context
.and_then(|value| value.get("task_id"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())?;
Some(LocalVideoTaskReadResponse {
status_code: 200,
body_json: json!({
"id": task_id,
"object": "video",
"deleted": true,
}),
})
}
"openai_video_cancel_sync_finalize" | "gemini_video_cancel_sync_finalize" => {
if upstream_status_code >= 400 {
return None;
}
Some(LocalVideoTaskReadResponse {
status_code: 200,
body_json: json!({}),
})
}
_ => None,
}
}
pub fn resolve_local_sync_success_background_report_kind(
report_kind: &str,
) -> Option<&'static str> {
match report_kind {
"openai_video_delete_sync_finalize" => Some("openai_video_delete_sync_success"),
"openai_video_cancel_sync_finalize" => Some("openai_video_cancel_sync_success"),
"gemini_video_cancel_sync_finalize" => Some("gemini_video_cancel_sync_success"),
_ => None,
}
}
pub fn resolve_local_sync_error_background_report_kind(report_kind: &str) -> Option<&'static str> {
match report_kind {
"openai_video_create_sync_finalize" => Some("openai_video_create_sync_error"),
"openai_video_remix_sync_finalize" => Some("openai_video_remix_sync_error"),
"gemini_video_create_sync_finalize" => Some("gemini_video_create_sync_error"),
"openai_video_delete_sync_finalize" => Some("openai_video_delete_sync_error"),
"openai_video_cancel_sync_finalize" => Some("openai_video_cancel_sync_error"),
"gemini_video_cancel_sync_finalize" => Some("gemini_video_cancel_sync_error"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{
build_internal_finalize_video_plan, build_local_sync_finalize_read_response,
resolve_local_sync_error_background_report_kind,
resolve_local_sync_success_background_report_kind,
};
#[test]
fn builds_local_sync_finalize_read_response_for_supported_video_finalize_kinds() {
let delete_response = build_local_sync_finalize_read_response(
"openai_video_delete_sync_finalize",
404,
Some(&json!({"task_id": "task-123"})),
)
.expect("delete finalize response should build");
assert_eq!(delete_response.status_code, 200);
assert_eq!(
delete_response.body_json,
json!({
"id": "task-123",
"object": "video",
"deleted": true,
})
);
let cancel_response = build_local_sync_finalize_read_response(
"gemini_video_cancel_sync_finalize",
200,
Some(&json!({})),
)
.expect("cancel finalize response should build");
assert_eq!(cancel_response.status_code, 200);
assert_eq!(cancel_response.body_json, json!({}));
}
#[test]
fn rejects_local_sync_finalize_read_response_when_status_or_context_is_invalid() {
assert!(build_local_sync_finalize_read_response(
"openai_video_delete_sync_finalize",
500,
Some(&json!({"task_id": "task-123"})),
)
.is_none());
assert!(build_local_sync_finalize_read_response(
"openai_video_delete_sync_finalize",
200,
Some(&json!({})),
)
.is_none());
assert!(build_local_sync_finalize_read_response(
"openai_video_cancel_sync_finalize",
500,
Some(&json!({})),
)
.is_none());
}
#[test]
fn resolves_local_sync_success_background_report_kind_for_supported_video_finalize_kinds() {
assert_eq!(
resolve_local_sync_success_background_report_kind("openai_video_delete_sync_finalize"),
Some("openai_video_delete_sync_success")
);
assert_eq!(
resolve_local_sync_success_background_report_kind("openai_video_create_sync_finalize"),
None
);
}
#[test]
fn resolves_local_sync_error_background_report_kind_for_supported_video_finalize_kinds() {
assert_eq!(
resolve_local_sync_error_background_report_kind("openai_video_remix_sync_finalize"),
Some("openai_video_remix_sync_error")
);
assert_eq!(
resolve_local_sync_error_background_report_kind("unknown_finalize_kind"),
None
);
}
#[test]
fn builds_internal_finalize_video_plan_for_supported_video_signatures() {
let openai_plan = build_internal_finalize_video_plan(
"trace-openai-video",
"openai:video",
Some(&json!({
"request_id": "req-openai-video",
"model": "sora-2",
"api_key_id": "api-key-openai-video",
"original_request_body": { "prompt": "make a trailer" }
})),
)
.expect("openai video plan should build");
assert_eq!(openai_plan.request_id, "req-openai-video");
assert_eq!(
openai_plan.url,
"https://internal.gateway.invalid/v1/videos"
);
assert_eq!(openai_plan.model_name.as_deref(), Some("sora-2"));
let gemini_plan = build_internal_finalize_video_plan(
"trace-gemini-video",
"gemini:video",
Some(&json!({
"local_short_id": "short-123"
})),
)
.expect("gemini video plan should build");
assert_eq!(
gemini_plan.url,
"https://internal.gateway.invalid/v1beta/models/veo-3:predictLongRunning"
);
assert_eq!(gemini_plan.model_name.as_deref(), Some("veo-3"));
assert_eq!(gemini_plan.request_id, "trace-gemini-video");
}
#[test]
fn rejects_internal_finalize_video_plan_for_non_video_signatures() {
assert!(
build_internal_finalize_video_plan("trace-123", "openai:chat", Some(&json!({})))
.is_none()
);
}
}

View File

@@ -0,0 +1,106 @@
use serde_json::Value;
use crate::LocalVideoTaskStatus;
pub 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 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 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",
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::LocalVideoTaskStatus;
use super::{gemini_metadata_video_url, map_openai_task_status, parse_video_content_variant};
#[test]
fn parses_supported_video_content_variants() {
assert_eq!(parse_video_content_variant(None), Some("video"));
assert_eq!(
parse_video_content_variant(Some("variant=thumbnail")),
Some("thumbnail")
);
assert_eq!(
parse_video_content_variant(Some("variant=spritesheet")),
Some("spritesheet")
);
assert_eq!(parse_video_content_variant(Some("variant=invalid")), None);
}
#[test]
fn extracts_gemini_metadata_video_url() {
let metadata = json!({
"response": {
"generateVideoResponse": {
"generatedSamples": [
{
"video": {
"uri": "https://example.com/video.mp4"
}
}
]
}
}
});
assert_eq!(
gemini_metadata_video_url(&metadata).as_deref(),
Some("https://example.com/video.mp4")
);
}
#[test]
fn maps_openai_task_status() {
assert_eq!(
map_openai_task_status(LocalVideoTaskStatus::Queued),
"queued"
);
assert_eq!(
map_openai_task_status(LocalVideoTaskStatus::Completed),
"completed"
);
assert_eq!(
map_openai_task_status(LocalVideoTaskStatus::Failed),
"failed"
);
}
}

View File

@@ -0,0 +1,114 @@
use aether_contracts::ExecutionPlan;
use aether_data::repository::video_tasks::{
StoredVideoTask, VideoTaskStatus as StoredVideoTaskStatus,
};
use serde_json::{Map, Value};
use std::collections::BTreeMap;
use crate::{
context_text, non_empty_owned, LocalVideoTaskPersistence, LocalVideoTaskStatus,
LocalVideoTaskTransport, LocalVideoTaskTransportBridgeInput,
};
impl LocalVideoTaskTransport {
pub 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 fn from_bridge_input(input: LocalVideoTaskTransportBridgeInput) -> Self {
let mut headers = BTreeMap::new();
headers.insert(input.auth_header, input.auth_value);
Self {
upstream_base_url: input.upstream_base_url,
provider_name: input.provider_name,
provider_id: input.provider_id,
endpoint_id: input.endpoint_id,
key_id: input.key_id,
headers,
content_type: input.content_type,
model_name: input.model_name,
proxy: input.proxy,
tls_profile: input.tls_profile,
timeouts: input.timeouts,
}
}
}
impl LocalVideoTaskPersistence {
pub 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 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 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,
}
}
}

View File

@@ -0,0 +1,171 @@
use std::collections::BTreeMap;
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, ProxySnapshot};
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS: u32 = 10;
pub const DEFAULT_VIDEO_TASK_MAX_POLL_COUNT: u32 = 360;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VideoTaskSyncReportMode {
InlineSync,
Background,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VideoTaskTruthSourceMode {
#[default]
PythonSyncReport,
RustAuthoritative,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LocalVideoTaskSuccessPlan {
pub seed: LocalVideoTaskSeed,
pub report_mode: VideoTaskSyncReportMode,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LocalVideoTaskFollowUpPlan {
pub plan: ExecutionPlan,
pub report_kind: Option<String>,
pub report_context: Option<Value>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LocalVideoTaskReadRefreshPlan {
pub plan: ExecutionPlan,
pub projection_target: LocalVideoTaskProjectionTarget,
}
#[derive(Debug, Clone, PartialEq)]
pub enum LocalVideoTaskContentAction {
Immediate { status_code: u16, body_json: Value },
StreamPlan(ExecutionPlan),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LocalVideoTaskProjectionTarget {
OpenAi { task_id: String },
Gemini { short_id: String },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LocalVideoTaskSnapshot {
OpenAi(OpenAiVideoTaskSeed),
Gemini(GeminiVideoTaskSeed),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LocalVideoTaskStatus {
Submitted,
Queued,
Processing,
Completed,
Failed,
Cancelled,
Expired,
Deleted,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalVideoTaskReadResponse {
pub status_code: u16,
pub body_json: Value,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LocalVideoTaskRegistryMutation {
OpenAiCancelled { task_id: String },
OpenAiDeleted { task_id: String },
GeminiCancelled { short_id: String },
}
#[derive(Debug, Clone, PartialEq)]
pub enum LocalVideoTaskSeed {
OpenAiCreate(OpenAiVideoTaskSeed),
OpenAiRemix(OpenAiVideoTaskSeed),
GeminiCreate(GeminiVideoTaskSeed),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LocalVideoTaskTransport {
pub upstream_base_url: String,
pub provider_name: Option<String>,
pub provider_id: String,
pub endpoint_id: String,
pub key_id: String,
pub headers: BTreeMap<String, String>,
pub content_type: Option<String>,
pub model_name: Option<String>,
pub proxy: Option<ProxySnapshot>,
pub tls_profile: Option<String>,
pub timeouts: Option<ExecutionTimeouts>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LocalVideoTaskTransportBridgeInput {
pub upstream_base_url: String,
pub provider_name: Option<String>,
pub provider_id: String,
pub endpoint_id: String,
pub key_id: String,
pub auth_header: String,
pub auth_value: String,
pub content_type: Option<String>,
pub model_name: Option<String>,
pub proxy: Option<ProxySnapshot>,
pub tls_profile: Option<String>,
pub timeouts: Option<ExecutionTimeouts>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LocalVideoTaskPersistence {
pub request_id: String,
pub username: Option<String>,
pub api_key_name: Option<String>,
pub client_api_format: String,
pub provider_api_format: String,
pub original_request_body: Value,
pub format_converted: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OpenAiVideoTaskSeed {
pub local_task_id: String,
pub upstream_task_id: String,
pub created_at_unix_secs: u64,
pub user_id: Option<String>,
pub api_key_id: Option<String>,
pub model: Option<String>,
pub prompt: Option<String>,
pub size: Option<String>,
pub seconds: Option<String>,
pub remixed_from_video_id: Option<String>,
pub status: LocalVideoTaskStatus,
pub progress_percent: u16,
pub completed_at_unix_secs: Option<u64>,
pub expires_at_unix_secs: Option<u64>,
pub error_code: Option<String>,
pub error_message: Option<String>,
pub video_url: Option<String>,
pub persistence: LocalVideoTaskPersistence,
pub transport: LocalVideoTaskTransport,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GeminiVideoTaskSeed {
pub local_short_id: String,
pub upstream_operation_name: String,
pub user_id: Option<String>,
pub api_key_id: Option<String>,
pub model: String,
pub status: LocalVideoTaskStatus,
pub progress_percent: u16,
pub error_code: Option<String>,
pub error_message: Option<String>,
pub metadata: Value,
pub persistence: LocalVideoTaskPersistence,
pub transport: LocalVideoTaskTransport,
}

View File

@@ -0,0 +1,21 @@
pub 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)
}
#[cfg(test)]
mod tests {
use super::non_empty_owned;
#[test]
fn discards_blank_strings() {
let empty = String::from(" ");
let value = String::from(" hello ");
assert_eq!(non_empty_owned(Some(&empty)), None);
assert_eq!(non_empty_owned(Some(&value)).as_deref(), Some("hello"));
}
}