refactor: 大规模模块拆分与代码精简,新增 ai-pipeline/data-contracts 独立 crate

- 新增 aether-ai-pipeline 和 aether-data-contracts crate,将 pipeline 逻辑与数据契约从 gateway 中解耦
- 重构 admin handlers:拆分单体模块为 auth/billing/endpoint/features/model/observability/provider/system 等独立子模块
- 合并 chat/cli 重复代码路径:精简 conversion、finalize、planner 中的 sync/chat/cli 分支
- 重构 scheduler/executor/data 层,引入 facade 模式降低模块间耦合
- 移除冗余的 intent 模块,将 plan_fallback/policy/stream_path/sync_path 迁移至 executor
- 前端适配:调整 admin API 调用和 provider 模型测试对话框
This commit is contained in:
fawney19
2026-04-07 02:50:19 +08:00
parent 763ff03a7b
commit 5d96d6673b
732 changed files with 28593 additions and 20666 deletions

View File

@@ -10,6 +10,7 @@ description = "Provider transport core extracted from aether-gateway"
aether-contracts.workspace = true
aether-crypto.workspace = true
aether-data.workspace = true
aether-data-contracts.workspace = true
aether-video-tasks-core.workspace = true
async-trait.workspace = true
http.workspace = true

View File

@@ -38,7 +38,7 @@ pub use policy::{
};
pub use rules::{
apply_local_body_rules, apply_local_header_rules, body_rules_are_locally_supported,
header_rules_are_locally_supported,
body_rules_handle_path, header_rules_are_locally_supported,
};
pub use snapshot::{
read_provider_transport_snapshot, GatewayProviderTransportSnapshot,

View File

@@ -199,6 +199,46 @@ pub fn body_rules_are_locally_supported(rules: Option<&Value>) -> bool {
})
}
pub fn body_rules_handle_path(rules: Option<&Value>, path: &str) -> bool {
let Some(target_path) = parse_body_path(path) else {
return false;
};
let Some(rules) = rules.and_then(Value::as_array) else {
return false;
};
rules.iter().any(|rule| {
let Some(rule) = rule.as_object() else {
return false;
};
match rule
.get("action")
.and_then(Value::as_str)
.map(str::trim)
.map(str::to_ascii_lowercase)
.as_deref()
{
Some("set") | Some("drop") => rule
.get("path")
.and_then(Value::as_str)
.and_then(parse_body_path)
.is_some_and(|candidate| candidate == target_path),
Some("rename") => {
rule.get("from")
.and_then(Value::as_str)
.and_then(parse_body_path)
.is_some_and(|candidate| candidate == target_path)
|| rule
.get("to")
.and_then(Value::as_str)
.and_then(parse_body_path)
.is_some_and(|candidate| candidate == target_path)
}
_ => false,
}
})
}
pub fn apply_local_body_rules(
body: &mut Value,
rules: Option<&Value>,
@@ -705,7 +745,7 @@ fn rename_nested_value(
mod tests {
use super::{
apply_local_body_rules, apply_local_header_rules, body_rules_are_locally_supported,
header_rules_are_locally_supported,
body_rules_handle_path, header_rules_are_locally_supported,
};
#[test]
@@ -817,4 +857,19 @@ mod tests {
let wildcard_rules = serde_json::json!([{"action":"drop","path":"items[*].value"}]);
assert!(!body_rules_are_locally_supported(Some(&wildcard_rules)));
}
#[test]
fn body_rules_handle_exact_paths_for_set_drop_and_rename() {
let rules = serde_json::json!([
{"action":"set","path":"store","value":false},
{"action":"drop","path":"metadata"},
{"action":"rename","from":"max_output_tokens","to":"limit"}
]);
assert!(body_rules_handle_path(Some(&rules), "store"));
assert!(body_rules_handle_path(Some(&rules), "metadata"));
assert!(body_rules_handle_path(Some(&rules), "max_output_tokens"));
assert!(body_rules_handle_path(Some(&rules), "limit"));
assert!(!body_rules_handle_path(Some(&rules), "instructions"));
}
}

View File

@@ -1,7 +1,7 @@
use aether_data::repository::provider_catalog::{
use aether_data_contracts::repository::provider_catalog::{
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
};
use aether_data::DataLayerError;
use aether_data_contracts::DataLayerError;
use async_trait::async_trait;
use super::auth_config::{absorb_local_auth_config_safe_subset, LocalAuthConfigAbsorption};
@@ -164,14 +164,11 @@ pub async fn read_provider_transport_snapshot(
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
use aether_data::repository::provider_catalog::{
InMemoryProviderCatalogReadRepository, ProviderCatalogReadRepository,
use aether_data_contracts::repository::provider_catalog::{
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
};
use aether_data::DataLayerError;
use aether_data_contracts::DataLayerError;
use async_trait::async_trait;
use super::super::policy::{
@@ -183,17 +180,23 @@ mod tests {
};
struct TestSnapshotSource {
repository: Arc<InMemoryProviderCatalogReadRepository>,
providers: Vec<StoredProviderCatalogProvider>,
endpoints: Vec<StoredProviderCatalogEndpoint>,
keys: Vec<StoredProviderCatalogKey>,
encryption_key: Option<String>,
}
impl TestSnapshotSource {
fn new(
repository: Arc<InMemoryProviderCatalogReadRepository>,
providers: Vec<StoredProviderCatalogProvider>,
endpoints: Vec<StoredProviderCatalogEndpoint>,
keys: Vec<StoredProviderCatalogKey>,
encryption_key: impl Into<Option<String>>,
) -> Self {
Self {
repository,
providers,
endpoints,
keys,
encryption_key: encryption_key.into(),
}
}
@@ -209,21 +212,36 @@ mod tests {
&self,
ids: &[String],
) -> Result<Vec<StoredProviderCatalogProvider>, DataLayerError> {
self.repository.list_providers_by_ids(ids).await
Ok(self
.providers
.iter()
.filter(|provider| ids.iter().any(|id| id == &provider.id))
.cloned()
.collect())
}
async fn list_provider_catalog_endpoints_by_ids(
&self,
ids: &[String],
) -> Result<Vec<StoredProviderCatalogEndpoint>, DataLayerError> {
self.repository.list_endpoints_by_ids(ids).await
Ok(self
.endpoints
.iter()
.filter(|endpoint| ids.iter().any(|id| id == &endpoint.id))
.cloned()
.collect())
}
async fn list_provider_catalog_keys_by_ids(
&self,
ids: &[String],
) -> Result<Vec<StoredProviderCatalogKey>, DataLayerError> {
self.repository.list_keys_by_ids(ids).await
Ok(self
.keys
.iter()
.filter(|key| ids.iter().any(|id| id == &key.id))
.cloned()
.collect())
}
}
@@ -304,12 +322,12 @@ mod tests {
}
fn read_state() -> TestSnapshotSource {
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
TestSnapshotSource::new(
vec![sample_provider()],
vec![sample_endpoint()],
vec![sample_key()],
));
TestSnapshotSource::new(repository, Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()))
Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()),
)
}
#[tokio::test]
@@ -383,12 +401,12 @@ mod tests {
#[tokio::test]
async fn returns_none_when_encryption_key_is_not_configured() {
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
let state = TestSnapshotSource::new(
vec![sample_provider()],
vec![sample_endpoint()],
vec![sample_key()],
));
let state = TestSnapshotSource::new(repository, None);
None,
);
let snapshot =
read_provider_transport_snapshot(&state, "provider-1", "endpoint-1", "key-1")
@@ -449,13 +467,12 @@ mod tests {
None,
)
.expect("key transport fields should build");
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
let state = TestSnapshotSource::new(
vec![provider],
vec![endpoint],
vec![key],
));
let state =
TestSnapshotSource::new(repository, Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()));
Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()),
);
let snapshot =
read_provider_transport_snapshot(&state, "provider-1", "endpoint-safe-1", "key-safe-1")
@@ -523,13 +540,12 @@ mod tests {
None,
)
.expect("key transport fields should build");
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
let state = TestSnapshotSource::new(
vec![provider],
vec![endpoint],
vec![key],
));
let state =
TestSnapshotSource::new(repository, Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()));
Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()),
);
let snapshot = read_provider_transport_snapshot(
&state,
@@ -702,13 +718,12 @@ mod tests {
None,
)
.expect("key transport fields should build");
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
let state = TestSnapshotSource::new(
vec![provider],
vec![endpoint],
vec![key],
));
let state =
TestSnapshotSource::new(repository, Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()));
Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()),
);
let snapshot =
read_provider_transport_snapshot(&state, "provider-1", "endpoint-safe-2", "key-safe-2")
@@ -778,13 +793,12 @@ mod tests {
None,
)
.expect("key transport fields should build");
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
let state = TestSnapshotSource::new(
vec![provider],
vec![endpoint],
vec![key],
));
let state =
TestSnapshotSource::new(repository, Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()));
Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()),
);
let snapshot =
read_provider_transport_snapshot(&state, "provider-1", "endpoint-safe-3", "key-safe-3")
@@ -859,13 +873,12 @@ mod tests {
None,
)
.expect("key transport fields should build");
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
let state = TestSnapshotSource::new(
vec![provider],
vec![endpoint],
vec![key],
));
let state =
TestSnapshotSource::new(repository, Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()));
Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()),
);
let snapshot =
read_provider_transport_snapshot(&state, "provider-1", "endpoint-safe-4", "key-safe-4")
@@ -948,13 +961,12 @@ mod tests {
Some(serde_json::Value::Null),
)
.expect("key transport fields should build");
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
let state = TestSnapshotSource::new(
vec![provider],
vec![endpoint],
vec![key],
));
let state =
TestSnapshotSource::new(repository, Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()));
Some(DEVELOPMENT_ENCRYPTION_KEY.to_string()),
);
let snapshot = read_provider_transport_snapshot(
&state,

View File

@@ -1,8 +1,8 @@
use aether_crypto::{decrypt_python_fernet_ciphertext, looks_like_python_fernet_ciphertext};
use aether_data::repository::provider_catalog::{
use aether_data_contracts::repository::provider_catalog::{
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
};
use aether_data::DataLayerError;
use aether_data_contracts::DataLayerError;
use super::{
GatewayProviderTransportEndpoint, GatewayProviderTransportKey, GatewayProviderTransportProvider,

View File

@@ -1,4 +1,4 @@
use aether_data::repository::video_tasks::StoredVideoTask;
use aether_data_contracts::repository::video_tasks::StoredVideoTask;
use aether_video_tasks_core::{
LocalVideoTaskSnapshot, LocalVideoTaskTransport, LocalVideoTaskTransportBridgeInput,
};
@@ -103,7 +103,7 @@ pub async fn reconstruct_local_video_task_snapshot(
#[cfg(test)]
mod tests {
use aether_data::repository::video_tasks::{StoredVideoTask, VideoTaskStatus};
use aether_data_contracts::repository::video_tasks::{StoredVideoTask, VideoTaskStatus};
use aether_video_tasks_core::LocalVideoTaskSnapshot;
use async_trait::async_trait;
use serde_json::json;