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

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

View File

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

View File

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

View File

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

View File

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