mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-12 04:09:48 +08:00
Expose the xAI Imagine image and video surfaces on top of the `xai` provider, and make the shared OpenAI video-task layer survive the production configuration they need. Native video requests live under /v1 (generations, edits, extensions, with /v1/videos as a creation alias that only selects xAI candidates); the OpenAI-compatible adapter stays under /openai/v1/videos and maps `seconds` / `size` onto numeric duration, aspect ratio and resolution. Clients receive an opaque Aether task ID scoped to the owning user; polling uses the upstream task ID and the original credential, and completed downloads fetch the returned media URL without forwarding provider authorization to the media host. Three fixes to the shared video layer are required for this to work outside tests: - OpenAI/xAI task persistence now supplies a stable 16-character short_id, which the PostgreSQL schema requires. Existing rows keep their original value across reconstruction, so no schema change or historical rewrite is needed. - Task retrieval and content downloads are admitted by the production GET execution gate, and reconstructed tasks resolve proxy nodes, system proxy defaults, tunnel affinity and transport profiles through the same deployment resolver used for creation. A configured proxy route no longer silently becomes a direct request after restart. - When the gateway also serves the frontend, /openai/v1/videos and its subpaths bypass the static SPA handler. Otherwise a video query returns HTTP 200 with text/html instead of the task JSON. Co-Authored-By: Claude Opus 5 <[email protected]>
391 lines
15 KiB
Rust
391 lines
15 KiB
Rust
use std::sync::Arc;
|
|
|
|
use aether_data_contracts::repository::candidates::RequestCandidateRepository;
|
|
use aether_data_contracts::repository::video_tasks::VideoTaskRepository;
|
|
|
|
use super::{
|
|
AuthApiKeyReadRepository, GatewayDataConfig, GatewayDataState, ProviderCatalogReadRepository,
|
|
RequestCandidateReadRepository, RequestCandidateWriteRepository, VideoTaskReadRepository,
|
|
VideoTaskWriteRepository,
|
|
};
|
|
|
|
impl GatewayDataState {
|
|
pub(crate) fn with_video_task_reader_for_tests(
|
|
repository: Arc<dyn VideoTaskReadRepository>,
|
|
) -> Self {
|
|
Self {
|
|
config: GatewayDataConfig::disabled(),
|
|
backends: None,
|
|
auth_api_key_reader: None,
|
|
auth_api_key_writer: None,
|
|
auth_module_reader: None,
|
|
auth_module_writer: None,
|
|
announcement_reader: None,
|
|
announcement_writer: None,
|
|
management_token_reader: None,
|
|
management_token_writer: None,
|
|
oauth_provider_reader: None,
|
|
oauth_provider_writer: None,
|
|
proxy_node_reader: None,
|
|
proxy_node_writer: None,
|
|
billing_reader: None,
|
|
gemini_file_mapping_reader: None,
|
|
gemini_file_mapping_writer: None,
|
|
global_model_reader: None,
|
|
global_model_writer: None,
|
|
minimal_candidate_selection_reader: None,
|
|
request_candidate_reader: None,
|
|
request_candidate_writer: None,
|
|
provider_catalog_reader: None,
|
|
provider_catalog_writer: None,
|
|
pool_score_reader: None,
|
|
pool_score_writer: None,
|
|
provider_quota_reader: None,
|
|
provider_quota_writer: None,
|
|
routing_group_reader: None,
|
|
routing_group_writer: None,
|
|
usage_reader: None,
|
|
usage_writer: None,
|
|
user_reader: None,
|
|
user_preferences: None,
|
|
usage_worker_queue: None,
|
|
video_task_reader: Some(repository),
|
|
video_task_writer: None,
|
|
background_task_reader: None,
|
|
background_task_writer: None,
|
|
wallet_reader: None,
|
|
wallet_writer: None,
|
|
settlement_writer: None,
|
|
system_config_values: None,
|
|
system_config_value_cache: Default::default(),
|
|
billing_model_context_cache: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn with_auth_and_video_task_repository_for_tests<T>(
|
|
auth_repository: Arc<dyn AuthApiKeyReadRepository>,
|
|
repository: Arc<T>,
|
|
) -> Self
|
|
where
|
|
T: VideoTaskRepository + 'static,
|
|
{
|
|
let video_task_reader: Arc<dyn VideoTaskReadRepository> = repository.clone();
|
|
let video_task_writer: Arc<dyn VideoTaskWriteRepository> = repository;
|
|
|
|
Self {
|
|
config: GatewayDataConfig::disabled(),
|
|
backends: None,
|
|
auth_api_key_reader: Some(auth_repository),
|
|
auth_api_key_writer: None,
|
|
auth_module_reader: None,
|
|
auth_module_writer: None,
|
|
announcement_reader: None,
|
|
announcement_writer: None,
|
|
management_token_reader: None,
|
|
management_token_writer: None,
|
|
oauth_provider_reader: None,
|
|
oauth_provider_writer: None,
|
|
proxy_node_reader: None,
|
|
proxy_node_writer: None,
|
|
billing_reader: None,
|
|
gemini_file_mapping_reader: None,
|
|
gemini_file_mapping_writer: None,
|
|
global_model_reader: None,
|
|
global_model_writer: None,
|
|
minimal_candidate_selection_reader: None,
|
|
request_candidate_reader: None,
|
|
request_candidate_writer: None,
|
|
provider_catalog_reader: None,
|
|
provider_catalog_writer: None,
|
|
pool_score_reader: None,
|
|
pool_score_writer: None,
|
|
provider_quota_reader: None,
|
|
provider_quota_writer: None,
|
|
routing_group_reader: None,
|
|
routing_group_writer: None,
|
|
usage_reader: None,
|
|
usage_writer: None,
|
|
user_reader: None,
|
|
user_preferences: None,
|
|
usage_worker_queue: None,
|
|
video_task_reader: Some(video_task_reader),
|
|
video_task_writer: Some(video_task_writer),
|
|
background_task_reader: None,
|
|
background_task_writer: None,
|
|
wallet_reader: None,
|
|
wallet_writer: None,
|
|
settlement_writer: None,
|
|
system_config_values: None,
|
|
system_config_value_cache: Default::default(),
|
|
billing_model_context_cache: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn attach_video_task_repository_for_tests<T>(mut self, repository: Arc<T>) -> Self
|
|
where
|
|
T: VideoTaskRepository + 'static,
|
|
{
|
|
self.video_task_reader = Some(repository.clone());
|
|
self.video_task_writer = Some(repository);
|
|
self
|
|
}
|
|
|
|
pub(crate) fn with_video_task_repository_for_tests<T>(repository: Arc<T>) -> Self
|
|
where
|
|
T: VideoTaskRepository + 'static,
|
|
{
|
|
let video_task_reader: Arc<dyn VideoTaskReadRepository> = repository.clone();
|
|
let video_task_writer: Arc<dyn VideoTaskWriteRepository> = repository;
|
|
|
|
Self {
|
|
config: GatewayDataConfig::disabled(),
|
|
backends: None,
|
|
auth_api_key_reader: None,
|
|
auth_api_key_writer: None,
|
|
auth_module_reader: None,
|
|
auth_module_writer: None,
|
|
announcement_reader: None,
|
|
announcement_writer: None,
|
|
management_token_reader: None,
|
|
management_token_writer: None,
|
|
oauth_provider_reader: None,
|
|
oauth_provider_writer: None,
|
|
proxy_node_reader: None,
|
|
proxy_node_writer: None,
|
|
billing_reader: None,
|
|
gemini_file_mapping_reader: None,
|
|
gemini_file_mapping_writer: None,
|
|
global_model_reader: None,
|
|
global_model_writer: None,
|
|
minimal_candidate_selection_reader: None,
|
|
request_candidate_reader: None,
|
|
request_candidate_writer: None,
|
|
provider_catalog_reader: None,
|
|
provider_catalog_writer: None,
|
|
pool_score_reader: None,
|
|
pool_score_writer: None,
|
|
provider_quota_reader: None,
|
|
provider_quota_writer: None,
|
|
routing_group_reader: None,
|
|
routing_group_writer: None,
|
|
usage_reader: None,
|
|
usage_writer: None,
|
|
user_reader: None,
|
|
user_preferences: None,
|
|
usage_worker_queue: None,
|
|
video_task_reader: Some(video_task_reader),
|
|
video_task_writer: Some(video_task_writer),
|
|
background_task_reader: None,
|
|
background_task_writer: None,
|
|
wallet_reader: None,
|
|
wallet_writer: None,
|
|
settlement_writer: None,
|
|
system_config_values: None,
|
|
system_config_value_cache: Default::default(),
|
|
billing_model_context_cache: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn with_video_task_repository_and_provider_transport_for_tests<T>(
|
|
repository: Arc<T>,
|
|
provider_catalog_repository: Arc<dyn ProviderCatalogReadRepository>,
|
|
encryption_key: impl Into<String>,
|
|
) -> Self
|
|
where
|
|
T: VideoTaskRepository + 'static,
|
|
{
|
|
let video_task_reader: Arc<dyn VideoTaskReadRepository> = repository.clone();
|
|
let video_task_writer: Arc<dyn VideoTaskWriteRepository> = repository;
|
|
|
|
Self {
|
|
config: GatewayDataConfig::disabled().with_encryption_key(encryption_key),
|
|
backends: None,
|
|
auth_api_key_reader: None,
|
|
auth_api_key_writer: None,
|
|
auth_module_reader: None,
|
|
auth_module_writer: None,
|
|
announcement_reader: None,
|
|
announcement_writer: None,
|
|
management_token_reader: None,
|
|
management_token_writer: None,
|
|
oauth_provider_reader: None,
|
|
oauth_provider_writer: None,
|
|
proxy_node_reader: None,
|
|
proxy_node_writer: None,
|
|
billing_reader: None,
|
|
gemini_file_mapping_reader: None,
|
|
gemini_file_mapping_writer: None,
|
|
global_model_reader: None,
|
|
global_model_writer: None,
|
|
minimal_candidate_selection_reader: None,
|
|
request_candidate_reader: None,
|
|
request_candidate_writer: None,
|
|
provider_catalog_reader: Some(provider_catalog_repository),
|
|
provider_catalog_writer: None,
|
|
pool_score_reader: None,
|
|
pool_score_writer: None,
|
|
provider_quota_reader: None,
|
|
provider_quota_writer: None,
|
|
routing_group_reader: None,
|
|
routing_group_writer: None,
|
|
usage_reader: None,
|
|
usage_writer: None,
|
|
user_reader: None,
|
|
user_preferences: None,
|
|
usage_worker_queue: None,
|
|
video_task_reader: Some(video_task_reader),
|
|
video_task_writer: Some(video_task_writer),
|
|
background_task_reader: None,
|
|
background_task_writer: None,
|
|
wallet_reader: None,
|
|
wallet_writer: None,
|
|
settlement_writer: None,
|
|
system_config_values: None,
|
|
system_config_value_cache: Default::default(),
|
|
billing_model_context_cache: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn with_video_task_and_request_candidate_repository_for_tests<T, U>(
|
|
repository: Arc<T>,
|
|
request_candidate_repository: Arc<U>,
|
|
) -> Self
|
|
where
|
|
T: VideoTaskRepository + 'static,
|
|
U: RequestCandidateRepository + 'static,
|
|
{
|
|
let video_task_reader: Arc<dyn VideoTaskReadRepository> = repository.clone();
|
|
let video_task_writer: Arc<dyn VideoTaskWriteRepository> = repository;
|
|
let request_candidate_reader: Arc<dyn RequestCandidateReadRepository> =
|
|
request_candidate_repository.clone();
|
|
let request_candidate_writer: Arc<dyn RequestCandidateWriteRepository> =
|
|
request_candidate_repository;
|
|
|
|
Self {
|
|
config: GatewayDataConfig::disabled(),
|
|
backends: None,
|
|
auth_api_key_reader: None,
|
|
auth_api_key_writer: None,
|
|
auth_module_reader: None,
|
|
auth_module_writer: None,
|
|
announcement_reader: None,
|
|
announcement_writer: None,
|
|
management_token_reader: None,
|
|
management_token_writer: None,
|
|
oauth_provider_reader: None,
|
|
oauth_provider_writer: None,
|
|
proxy_node_reader: None,
|
|
proxy_node_writer: None,
|
|
billing_reader: None,
|
|
gemini_file_mapping_reader: None,
|
|
gemini_file_mapping_writer: None,
|
|
global_model_reader: None,
|
|
global_model_writer: None,
|
|
minimal_candidate_selection_reader: None,
|
|
request_candidate_reader: Some(request_candidate_reader),
|
|
request_candidate_writer: Some(request_candidate_writer),
|
|
provider_catalog_reader: None,
|
|
provider_catalog_writer: None,
|
|
pool_score_reader: None,
|
|
pool_score_writer: None,
|
|
provider_quota_reader: None,
|
|
provider_quota_writer: None,
|
|
routing_group_reader: None,
|
|
routing_group_writer: None,
|
|
usage_reader: None,
|
|
usage_writer: None,
|
|
user_reader: None,
|
|
user_preferences: None,
|
|
usage_worker_queue: None,
|
|
video_task_reader: Some(video_task_reader),
|
|
video_task_writer: Some(video_task_writer),
|
|
background_task_reader: None,
|
|
background_task_writer: None,
|
|
wallet_reader: None,
|
|
wallet_writer: None,
|
|
settlement_writer: None,
|
|
system_config_values: None,
|
|
system_config_value_cache: Default::default(),
|
|
billing_model_context_cache: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn with_video_task_provider_transport_and_request_candidate_repository_for_tests<
|
|
T,
|
|
U,
|
|
V,
|
|
>(
|
|
repository: Arc<T>,
|
|
provider_catalog_repository: Arc<U>,
|
|
request_candidate_repository: Arc<V>,
|
|
encryption_key: impl Into<String>,
|
|
) -> Self
|
|
where
|
|
T: VideoTaskRepository + 'static,
|
|
U: ProviderCatalogReadRepository + 'static,
|
|
V: RequestCandidateRepository + 'static,
|
|
{
|
|
let video_task_reader: Arc<dyn VideoTaskReadRepository> = repository.clone();
|
|
let video_task_writer: Arc<dyn VideoTaskWriteRepository> = repository;
|
|
let request_candidate_reader: Arc<dyn RequestCandidateReadRepository> =
|
|
request_candidate_repository.clone();
|
|
let request_candidate_writer: Arc<dyn RequestCandidateWriteRepository> =
|
|
request_candidate_repository;
|
|
let provider_catalog_reader: Arc<dyn ProviderCatalogReadRepository> =
|
|
provider_catalog_repository;
|
|
|
|
Self {
|
|
config: GatewayDataConfig::disabled().with_encryption_key(encryption_key),
|
|
backends: None,
|
|
auth_api_key_reader: None,
|
|
auth_api_key_writer: None,
|
|
auth_module_reader: None,
|
|
auth_module_writer: None,
|
|
announcement_reader: None,
|
|
announcement_writer: None,
|
|
management_token_reader: None,
|
|
management_token_writer: None,
|
|
oauth_provider_reader: None,
|
|
oauth_provider_writer: None,
|
|
proxy_node_reader: None,
|
|
proxy_node_writer: None,
|
|
billing_reader: None,
|
|
gemini_file_mapping_reader: None,
|
|
gemini_file_mapping_writer: None,
|
|
global_model_reader: None,
|
|
global_model_writer: None,
|
|
minimal_candidate_selection_reader: None,
|
|
request_candidate_reader: Some(request_candidate_reader),
|
|
request_candidate_writer: Some(request_candidate_writer),
|
|
provider_catalog_reader: Some(provider_catalog_reader),
|
|
provider_catalog_writer: None,
|
|
pool_score_reader: None,
|
|
pool_score_writer: None,
|
|
provider_quota_reader: None,
|
|
provider_quota_writer: None,
|
|
routing_group_reader: None,
|
|
routing_group_writer: None,
|
|
usage_reader: None,
|
|
usage_writer: None,
|
|
user_reader: None,
|
|
user_preferences: None,
|
|
usage_worker_queue: None,
|
|
video_task_reader: Some(video_task_reader),
|
|
video_task_writer: Some(video_task_writer),
|
|
background_task_reader: None,
|
|
background_task_writer: None,
|
|
wallet_reader: None,
|
|
wallet_writer: None,
|
|
settlement_writer: None,
|
|
system_config_values: None,
|
|
system_config_value_cache: Default::default(),
|
|
billing_model_context_cache: Default::default(),
|
|
}
|
|
}
|
|
}
|