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

@@ -72,7 +72,7 @@ pub struct RedisStreamRunnerConfig {
impl Default for RedisStreamRunnerConfig {
fn default() -> Self {
Self {
command_timeout_ms: Some(1_000),
command_timeout_ms: Some(2_000),
read_block_ms: Some(1_000),
read_count: 32,
}
@@ -91,6 +91,16 @@ impl RedisStreamRunnerConfig {
"redis stream read_block_ms must be positive".to_string(),
));
}
if let (Some(command_timeout_ms), Some(read_block_ms)) =
(self.command_timeout_ms, self.read_block_ms)
{
if command_timeout_ms <= read_block_ms {
return Err(DataLayerError::InvalidConfiguration(
"redis stream command_timeout_ms must be greater than read_block_ms"
.to_string(),
));
}
}
if self.read_count == 0 {
return Err(DataLayerError::InvalidConfiguration(
"redis stream read_count must be positive".to_string(),
@@ -564,6 +574,13 @@ mod tests {
}
.validate()
.is_err());
assert!(RedisStreamRunnerConfig {
command_timeout_ms: Some(1_000),
read_block_ms: Some(1_000),
..RedisStreamRunnerConfig::default()
}
.validate()
.is_err());
assert!(RedisStreamRunnerConfig {
read_count: 0,
..RedisStreamRunnerConfig::default()

View File

@@ -178,7 +178,7 @@ impl ProxyNodeWriteRepository for InMemoryProxyNodeRepository {
};
let event_detail = mutation.detail.clone().unwrap_or_else(|| {
format!(
"[hub_node_status] conn_count={}",
"[tunnel_node_status] conn_count={}",
i32::max(mutation.conn_count, 0)
)
});
@@ -318,7 +318,7 @@ mod tests {
assert_eq!(stale_events[0].event_type, "disconnected");
assert_eq!(
stale_events[0].detail.as_deref(),
Some("[stale_ignored] [hub_node_status] conn_count=0")
Some("[stale_ignored] [tunnel_node_status] conn_count=0")
);
let updated = repository
@@ -344,7 +344,7 @@ mod tests {
assert_eq!(events[0].created_at_unix_secs, Some(1_800_000_000));
assert_eq!(
events[0].detail.as_deref(),
Some("[hub_node_status] conn_count=0")
Some("[tunnel_node_status] conn_count=0")
);
let found = repository

View File

@@ -278,7 +278,7 @@ impl ProxyNodeWriteRepository for SqlxProxyNodeRepository {
};
let event_detail = mutation.detail.clone().unwrap_or_else(|| {
format!(
"[hub_node_status] conn_count={}",
"[tunnel_node_status] conn_count={}",
i32::max(mutation.conn_count, 0)
)
});

View File

@@ -293,6 +293,33 @@ impl UsageWriteRepository for InMemoryUsageReadRepository {
first_byte_time_ms: usage.first_byte_time_ms,
status: usage.status,
billing_status: usage.billing_status,
request_headers: usage
.request_headers
.or_else(|| existing.and_then(|existing| existing.request_headers.clone())),
request_body: usage
.request_body
.or_else(|| existing.and_then(|existing| existing.request_body.clone())),
provider_request_headers: usage.provider_request_headers.or_else(|| {
existing.and_then(|existing| existing.provider_request_headers.clone())
}),
provider_request_body: usage
.provider_request_body
.or_else(|| existing.and_then(|existing| existing.provider_request_body.clone())),
response_headers: usage
.response_headers
.or_else(|| existing.and_then(|existing| existing.response_headers.clone())),
response_body: usage
.response_body
.or_else(|| existing.and_then(|existing| existing.response_body.clone())),
client_response_headers: usage
.client_response_headers
.or_else(|| existing.and_then(|existing| existing.client_response_headers.clone())),
client_response_body: usage
.client_response_body
.or_else(|| existing.and_then(|existing| existing.client_response_body.clone())),
request_metadata: usage
.request_metadata
.or_else(|| existing.and_then(|existing| existing.request_metadata.clone())),
created_at_unix_secs,
updated_at_unix_secs: usage.updated_at_unix_secs,
finalized_at_unix_secs: usage.finalized_at_unix_secs,

View File

@@ -50,6 +50,15 @@ SELECT
first_byte_time_ms,
status,
billing_status,
request_headers,
request_body,
provider_request_headers,
provider_request_body,
response_headers,
response_body,
client_response_headers,
client_response_body,
request_metadata,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM COALESCE(finalized_at, created_at)) AS BIGINT) AS updated_at_unix_secs,
CAST(EXTRACT(EPOCH FROM finalized_at) AS BIGINT) AS finalized_at_unix_secs
@@ -98,6 +107,15 @@ SELECT
first_byte_time_ms,
status,
billing_status,
request_headers,
request_body,
provider_request_headers,
provider_request_body,
response_headers,
response_body,
client_response_headers,
client_response_body,
request_metadata,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM COALESCE(finalized_at, created_at)) AS BIGINT) AS updated_at_unix_secs,
CAST(EXTRACT(EPOCH FROM finalized_at) AS BIGINT) AS finalized_at_unix_secs
@@ -176,6 +194,15 @@ SELECT
first_byte_time_ms,
status,
billing_status,
NULL::jsonb AS request_headers,
NULL::jsonb AS request_body,
NULL::jsonb AS provider_request_headers,
NULL::jsonb AS provider_request_body,
NULL::jsonb AS response_headers,
NULL::jsonb AS response_body,
NULL::jsonb AS client_response_headers,
NULL::jsonb AS client_response_body,
NULL::jsonb AS request_metadata,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM COALESCE(finalized_at, created_at)) AS BIGINT) AS updated_at_unix_secs,
CAST(EXTRACT(EPOCH FROM finalized_at) AS BIGINT) AS finalized_at_unix_secs
@@ -222,6 +249,15 @@ SELECT
first_byte_time_ms,
status,
billing_status,
NULL::jsonb AS request_headers,
NULL::jsonb AS request_body,
NULL::jsonb AS provider_request_headers,
NULL::jsonb AS provider_request_body,
NULL::jsonb AS response_headers,
NULL::jsonb AS response_body,
NULL::jsonb AS client_response_headers,
NULL::jsonb AS client_response_body,
NULL::jsonb AS request_metadata,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM COALESCE(finalized_at, created_at)) AS BIGINT) AS updated_at_unix_secs,
CAST(EXTRACT(EPOCH FROM finalized_at) AS BIGINT) AS finalized_at_unix_secs
@@ -326,11 +362,12 @@ INSERT INTO "usage" (
$44,
$45,
$46,
$47,
CASE
WHEN $47 IS NULL THEN NULL
ELSE TO_TIMESTAMP($47::double precision)
WHEN $48 IS NULL THEN NULL
ELSE TO_TIMESTAMP($48::double precision)
END,
COALESCE(TO_TIMESTAMP($48::double precision), NOW())
COALESCE(TO_TIMESTAMP($49::double precision), NOW())
)
ON CONFLICT (request_id)
DO UPDATE SET
@@ -419,6 +456,15 @@ RETURNING
first_byte_time_ms,
status,
billing_status,
request_headers,
request_body,
provider_request_headers,
provider_request_body,
response_headers,
response_body,
client_response_headers,
client_response_body,
request_metadata,
CAST(EXTRACT(EPOCH FROM created_at) AS BIGINT) AS created_at_unix_secs,
CAST(EXTRACT(EPOCH FROM COALESCE(finalized_at, created_at)) AS BIGINT) AS updated_at_unix_secs,
CAST(EXTRACT(EPOCH FROM finalized_at) AS BIGINT) AS finalized_at_unix_secs
@@ -760,6 +806,15 @@ fn map_usage_row(row: &sqlx::postgres::PgRow) -> Result<StoredRequestUsageAudit,
usage.cache_creation_cost_usd = row.try_get::<f64, _>("cache_creation_cost_usd")?;
usage.cache_read_cost_usd = row.try_get::<f64, _>("cache_read_cost_usd")?;
usage.output_price_per_1m = row.try_get("output_price_per_1m")?;
usage.request_headers = row.try_get("request_headers")?;
usage.request_body = row.try_get("request_body")?;
usage.provider_request_headers = row.try_get("provider_request_headers")?;
usage.provider_request_body = row.try_get("provider_request_body")?;
usage.response_headers = row.try_get("response_headers")?;
usage.response_body = row.try_get("response_body")?;
usage.client_response_headers = row.try_get("client_response_headers")?;
usage.client_response_body = row.try_get("client_response_body")?;
usage.request_metadata = row.try_get("request_metadata")?;
Ok(usage)
}
@@ -891,4 +946,11 @@ mod tests {
fn usage_sql_supports_recent_usage_audits_query() {
assert!(super::LIST_RECENT_USAGE_AUDITS_PREFIX.contains("FROM \"usage\""));
}
#[test]
fn usage_sql_insert_values_aligns_request_metadata_and_timestamps() {
assert!(super::UPSERT_SQL.contains("\n $46,\n $47,\n CASE"));
assert!(super::UPSERT_SQL.contains("WHEN $48 IS NULL THEN NULL"));
assert!(super::UPSERT_SQL.contains("TO_TIMESTAMP($49::double precision)"));
}
}

View File

@@ -41,6 +41,24 @@ pub struct StoredRequestUsageAudit {
pub first_byte_time_ms: Option<u64>,
pub status: String,
pub billing_status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_headers: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_body: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_request_headers: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_request_body: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_headers: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_body: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_response_headers: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_response_body: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_metadata: Option<Value>,
pub created_at_unix_secs: u64,
pub updated_at_unix_secs: u64,
pub finalized_at_unix_secs: Option<u64>,
@@ -161,6 +179,15 @@ impl StoredRequestUsageAudit {
first_byte_time_ms: parse_optional_u64(first_byte_time_ms, "usage.first_byte_time_ms")?,
status,
billing_status,
request_headers: None,
request_body: None,
provider_request_headers: None,
provider_request_body: None,
response_headers: None,
response_body: None,
client_response_headers: None,
client_response_body: None,
request_metadata: None,
created_at_unix_secs: parse_timestamp(
created_at_unix_secs,
"usage.created_at_unix_secs",