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 28589 additions and 20662 deletions
@@ -2,7 +2,7 @@ use std::sync::RwLock;
use async_trait::async_trait;
use super::types::{
use super::{
AdminGlobalModelListQuery, AdminProviderModelListQuery, CreateAdminGlobalModelRecord,
GlobalModelReadRepository, GlobalModelWriteRepository, PublicCatalogModelListQuery,
PublicCatalogModelSearchQuery, PublicGlobalModelQuery, StoredAdminGlobalModel,
@@ -1,10 +1,8 @@
mod memory;
mod sql;
mod types;
pub use memory::InMemoryGlobalModelReadRepository;
pub use sql::SqlxGlobalModelReadRepository;
pub use types::{
#[allow(unused_imports)]
pub(crate) use aether_data_contracts::repository::global_models::{
AdminGlobalModelListQuery, AdminProviderModelListQuery, CreateAdminGlobalModelRecord,
GlobalModelReadRepository, GlobalModelWriteRepository, PublicCatalogModelListQuery,
PublicCatalogModelSearchQuery, PublicGlobalModelQuery, StoredAdminGlobalModel,
@@ -12,3 +10,5 @@ pub use types::{
StoredProviderModelStats, StoredPublicCatalogModel, StoredPublicGlobalModel,
StoredPublicGlobalModelPage, UpdateAdminGlobalModelRecord, UpsertAdminProviderModelRecord,
};
pub use memory::InMemoryGlobalModelReadRepository;
pub use sql::SqlxGlobalModelReadRepository;
@@ -2,7 +2,7 @@ use async_trait::async_trait;
use serde_json::Value;
use sqlx::{postgres::PgRow, PgPool, Postgres, QueryBuilder, Row};
use super::types::{
use super::{
AdminGlobalModelListQuery, AdminProviderModelListQuery, CreateAdminGlobalModelRecord,
GlobalModelReadRepository, GlobalModelWriteRepository, PublicCatalogModelListQuery,
PublicCatalogModelSearchQuery, PublicGlobalModelQuery, StoredAdminGlobalModel,
@@ -10,7 +10,7 @@ use super::types::{
StoredProviderModelStats, StoredPublicCatalogModel, StoredPublicGlobalModel,
StoredPublicGlobalModelPage, UpdateAdminGlobalModelRecord, UpsertAdminProviderModelRecord,
};
use crate::DataLayerError;
use crate::{error::SqlxResultExt, DataLayerError};
const LIST_PUBLIC_GLOBAL_MODELS_PREFIX: &str = r#"
SELECT
@@ -146,10 +146,15 @@ impl SqlxGlobalModelReadRepository {
) -> Result<StoredPublicGlobalModelPage, DataLayerError> {
let mut count_builder = QueryBuilder::<Postgres>::new(COUNT_PUBLIC_GLOBAL_MODELS_PREFIX);
apply_public_model_filters(&mut count_builder, query);
let count_row = count_builder.build().fetch_one(&self.pool).await?;
let count_row = count_builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
let total = count_row
.try_get::<i64, _>("total")
.map(|value| value.max(0) as usize)?;
.map(|value| value.max(0) as usize)
.map_postgres_err()?;
let mut list_builder = QueryBuilder::<Postgres>::new(LIST_PUBLIC_GLOBAL_MODELS_PREFIX);
apply_public_model_filters(&mut list_builder, query);
@@ -158,7 +163,11 @@ impl SqlxGlobalModelReadRepository {
.push_bind(query.offset as i64)
.push(" LIMIT ")
.push_bind(query.limit as i64);
let rows = list_builder.build().fetch_all(&self.pool).await?;
let rows = list_builder
.build()
.fetch_all(&self.pool)
.await
.map_postgres_err()?;
let items = rows.iter().map(map_row).collect::<Result<Vec<_>, _>>()?;
Ok(StoredPublicGlobalModelPage { items, total })
@@ -179,7 +188,8 @@ impl SqlxGlobalModelReadRepository {
)
.build()
.fetch_all(&self.pool)
.await?;
.await
.map_postgres_err()?;
rows.iter().map(map_provider_model_stats_row).collect()
}
@@ -199,7 +209,8 @@ impl SqlxGlobalModelReadRepository {
)
.build()
.fetch_all(&self.pool)
.await?;
.await
.map_postgres_err()?;
rows.iter()
.map(map_provider_active_global_model_row)
@@ -222,7 +233,11 @@ impl SqlxGlobalModelReadRepository {
.push_bind(query.offset as i64)
.push(" LIMIT ")
.push_bind(query.limit as i64);
let rows = builder.build().fetch_all(&self.pool).await?;
let rows = builder
.build()
.fetch_all(&self.pool)
.await
.map_postgres_err()?;
rows.iter().map(map_admin_provider_model_row).collect()
}
@@ -232,10 +247,15 @@ impl SqlxGlobalModelReadRepository {
) -> Result<StoredAdminGlobalModelPage, DataLayerError> {
let mut count_builder = QueryBuilder::<Postgres>::new(COUNT_ADMIN_GLOBAL_MODELS_PREFIX);
apply_admin_global_model_filters(&mut count_builder, query);
let count_row = count_builder.build().fetch_one(&self.pool).await?;
let count_row = count_builder
.build()
.fetch_one(&self.pool)
.await
.map_postgres_err()?;
let total = count_row
.try_get::<i64, _>("total")
.map(|value| value.max(0) as usize)?;
.map(|value| value.max(0) as usize)
.map_postgres_err()?;
let mut list_builder = QueryBuilder::<Postgres>::new(LIST_ADMIN_GLOBAL_MODELS_PREFIX);
apply_admin_global_model_filters(&mut list_builder, query);
@@ -244,7 +264,11 @@ impl SqlxGlobalModelReadRepository {
.push_bind(query.offset as i64)
.push(" LIMIT ")
.push_bind(query.limit as i64);
let rows = list_builder.build().fetch_all(&self.pool).await?;
let rows = list_builder
.build()
.fetch_all(&self.pool)
.await
.map_postgres_err()?;
let items = rows
.iter()
.map(map_admin_global_model_row)
@@ -292,7 +316,8 @@ LIMIT 1
.bind(provider_id)
.bind(model_id)
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
row.as_ref().map(map_admin_provider_model_row).transpose()
}
@@ -336,7 +361,8 @@ ORDER BY gm.name ASC, m.created_at DESC, m.id ASC
)
.bind(provider_id)
.fetch_all(&self.pool)
.await?;
.await
.map_postgres_err()?;
rows.iter().map(map_admin_provider_model_row).collect()
}
@@ -366,7 +392,8 @@ LIMIT 1
)
.bind(global_model_id)
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
row.as_ref().map(map_admin_global_model_row).transpose()
}
@@ -396,7 +423,8 @@ LIMIT 1
)
.bind(model_name)
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
row.as_ref().map(map_admin_global_model_row).transpose()
}
@@ -438,7 +466,8 @@ ORDER BY m.created_at DESC, m.id ASC
)
.bind(global_model_id)
.fetch_all(&self.pool)
.await?;
.await
.map_postgres_err()?;
rows.iter().map(map_admin_provider_model_row).collect()
}
@@ -490,7 +519,8 @@ RETURNING id
.bind(record.is_available)
.bind(record.config.clone())
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
if inserted.is_none() {
return Ok(None);
@@ -543,7 +573,8 @@ RETURNING id
.bind(record.is_available)
.bind(record.config.clone())
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
if updated.is_none() {
return Ok(None);
@@ -569,7 +600,8 @@ RETURNING id
.bind(provider_id)
.bind(model_id)
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
Ok(deleted.is_some())
}
@@ -605,7 +637,8 @@ RETURNING id
.bind(record.supported_capabilities.clone())
.bind(record.config.clone())
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
if inserted.is_none() {
return Ok(None);
@@ -641,7 +674,8 @@ RETURNING id
.bind(record.supported_capabilities.clone())
.bind(record.config.clone())
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
if updated.is_none() {
return Ok(None);
@@ -663,7 +697,8 @@ RETURNING id
)
.bind(global_model_id)
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
Ok(deleted.is_some())
}
@@ -700,7 +735,8 @@ LIMIT 1
)
.bind(model_name)
.fetch_optional(&self.pool)
.await?;
.await
.map_postgres_err()?;
row.as_ref().map(map_row).transpose()
}
@@ -716,7 +752,11 @@ LIMIT 1
.push_bind(query.offset as i64)
.push(" LIMIT ")
.push_bind(query.limit as i64);
let rows = builder.build().fetch_all(&self.pool).await?;
let rows = builder
.build()
.fetch_all(&self.pool)
.await
.map_postgres_err()?;
rows.iter().map(map_public_catalog_model_row).collect()
}
@@ -733,7 +773,11 @@ LIMIT 1
builder
.push(" ORDER BY p.provider_priority ASC, p.name ASC, COALESCE(gm.name, m.provider_model_name) ASC, m.id ASC LIMIT ")
.push_bind(query.limit as i64);
let rows = builder.build().fetch_all(&self.pool).await?;
let rows = builder
.build()
.fetch_all(&self.pool)
.await
.map_postgres_err()?;
rows.iter().map(map_public_catalog_model_row).collect()
}
@@ -903,16 +947,18 @@ fn apply_admin_global_model_filters(
}
fn map_row(row: &PgRow) -> Result<StoredPublicGlobalModel, DataLayerError> {
let supported_capabilities: Option<Value> = row.try_get("supported_capabilities")?;
let supported_capabilities: Option<Value> =
row.try_get("supported_capabilities").map_postgres_err()?;
StoredPublicGlobalModel::new(
row.try_get("id")?,
row.try_get("name")?,
row.try_get("display_name")?,
row.try_get("is_active")?,
row.try_get("default_price_per_request")?,
row.try_get("default_tiered_pricing")?,
row.try_get("id").map_postgres_err()?,
row.try_get("name").map_postgres_err()?,
row.try_get("display_name").map_postgres_err()?,
row.try_get("is_active").map_postgres_err()?,
row.try_get("default_price_per_request")
.map_postgres_err()?,
row.try_get("default_tiered_pricing").map_postgres_err()?,
supported_capabilities,
row.try_get("config")?,
row.try_get("config").map_postgres_err()?,
0,
)
}
@@ -945,74 +991,87 @@ fn apply_public_catalog_model_filters(
fn map_public_catalog_model_row(row: &PgRow) -> Result<StoredPublicCatalogModel, DataLayerError> {
StoredPublicCatalogModel::new(
row.try_get("id")?,
row.try_get("provider_id")?,
row.try_get("provider_name")?,
row.try_get("provider_model_name")?,
row.try_get("name")?,
row.try_get("display_name")?,
row.try_get("description")?,
row.try_get("icon_url")?,
row.try_get("input_price_per_1m")?,
row.try_get("output_price_per_1m")?,
row.try_get("cache_creation_price_per_1m")?,
row.try_get("cache_read_price_per_1m")?,
row.try_get("supports_vision")?,
row.try_get("supports_function_calling")?,
row.try_get("supports_streaming")?,
row.try_get("is_active")?,
row.try_get("id").map_postgres_err()?,
row.try_get("provider_id").map_postgres_err()?,
row.try_get("provider_name").map_postgres_err()?,
row.try_get("provider_model_name").map_postgres_err()?,
row.try_get("name").map_postgres_err()?,
row.try_get("display_name").map_postgres_err()?,
row.try_get("description").map_postgres_err()?,
row.try_get("icon_url").map_postgres_err()?,
row.try_get("input_price_per_1m").map_postgres_err()?,
row.try_get("output_price_per_1m").map_postgres_err()?,
row.try_get("cache_creation_price_per_1m")
.map_postgres_err()?,
row.try_get("cache_read_price_per_1m").map_postgres_err()?,
row.try_get("supports_vision").map_postgres_err()?,
row.try_get("supports_function_calling")
.map_postgres_err()?,
row.try_get("supports_streaming").map_postgres_err()?,
row.try_get("is_active").map_postgres_err()?,
)
}
fn map_admin_provider_model_row(row: &PgRow) -> Result<StoredAdminProviderModel, DataLayerError> {
let created_at_unix_secs = row
.try_get::<Option<i64>, _>("created_at_unix_secs")?
.try_get::<Option<i64>, _>("created_at_unix_secs")
.map_postgres_err()?
.map(|value| value.max(0) as u64);
let updated_at_unix_secs = row
.try_get::<Option<i64>, _>("updated_at_unix_secs")?
.try_get::<Option<i64>, _>("updated_at_unix_secs")
.map_postgres_err()?
.map(|value| value.max(0) as u64);
StoredAdminProviderModel::new(
row.try_get("id")?,
row.try_get("provider_id")?,
row.try_get("global_model_id")?,
row.try_get("provider_model_name")?,
row.try_get("provider_model_mappings")?,
row.try_get("price_per_request")?,
row.try_get("tiered_pricing")?,
row.try_get("supports_vision")?,
row.try_get("supports_function_calling")?,
row.try_get("supports_streaming")?,
row.try_get("supports_extended_thinking")?,
row.try_get("supports_image_generation")?,
row.try_get("is_active")?,
row.try_get("is_available")?,
row.try_get("config")?,
row.try_get("id").map_postgres_err()?,
row.try_get("provider_id").map_postgres_err()?,
row.try_get("global_model_id").map_postgres_err()?,
row.try_get("provider_model_name").map_postgres_err()?,
row.try_get("provider_model_mappings").map_postgres_err()?,
row.try_get("price_per_request").map_postgres_err()?,
row.try_get("tiered_pricing").map_postgres_err()?,
row.try_get("supports_vision").map_postgres_err()?,
row.try_get("supports_function_calling")
.map_postgres_err()?,
row.try_get("supports_streaming").map_postgres_err()?,
row.try_get("supports_extended_thinking")
.map_postgres_err()?,
row.try_get("supports_image_generation")
.map_postgres_err()?,
row.try_get("is_active").map_postgres_err()?,
row.try_get("is_available").map_postgres_err()?,
row.try_get("config").map_postgres_err()?,
created_at_unix_secs,
updated_at_unix_secs,
row.try_get("global_model_name")?,
row.try_get("global_model_display_name")?,
row.try_get("global_model_default_price_per_request")?,
row.try_get("global_model_default_tiered_pricing")?,
row.try_get("global_model_config")?,
row.try_get("global_model_name").map_postgres_err()?,
row.try_get("global_model_display_name")
.map_postgres_err()?,
row.try_get("global_model_default_price_per_request")
.map_postgres_err()?,
row.try_get("global_model_default_tiered_pricing")
.map_postgres_err()?,
row.try_get("global_model_config").map_postgres_err()?,
)
}
fn map_admin_global_model_row(row: &PgRow) -> Result<StoredAdminGlobalModel, DataLayerError> {
let created_at_unix_secs = row
.try_get::<Option<i64>, _>("created_at_unix_secs")?
.try_get::<Option<i64>, _>("created_at_unix_secs")
.map_postgres_err()?
.map(|value| value.max(0) as u64);
let updated_at_unix_secs = row
.try_get::<Option<i64>, _>("updated_at_unix_secs")?
.try_get::<Option<i64>, _>("updated_at_unix_secs")
.map_postgres_err()?
.map(|value| value.max(0) as u64);
StoredAdminGlobalModel::new(
row.try_get("id")?,
row.try_get("name")?,
row.try_get("display_name")?,
row.try_get("is_active")?,
row.try_get("default_price_per_request")?,
row.try_get("default_tiered_pricing")?,
row.try_get("supported_capabilities")?,
row.try_get("config")?,
row.try_get("id").map_postgres_err()?,
row.try_get("name").map_postgres_err()?,
row.try_get("display_name").map_postgres_err()?,
row.try_get("is_active").map_postgres_err()?,
row.try_get("default_price_per_request")
.map_postgres_err()?,
row.try_get("default_tiered_pricing").map_postgres_err()?,
row.try_get("supported_capabilities").map_postgres_err()?,
row.try_get("config").map_postgres_err()?,
created_at_unix_secs,
updated_at_unix_secs,
)
@@ -1034,9 +1093,9 @@ fn build_provider_id_list_query<'a>(
fn map_provider_model_stats_row(row: &PgRow) -> Result<StoredProviderModelStats, DataLayerError> {
StoredProviderModelStats::new(
row.try_get("provider_id")?,
row.try_get("total_models")?,
row.try_get("active_models")?,
row.try_get("provider_id").map_postgres_err()?,
row.try_get("total_models").map_postgres_err()?,
row.try_get("active_models").map_postgres_err()?,
)
}
@@ -1044,8 +1103,8 @@ fn map_provider_active_global_model_row(
row: &PgRow,
) -> Result<StoredProviderActiveGlobalModel, DataLayerError> {
StoredProviderActiveGlobalModel::new(
row.try_get("provider_id")?,
row.try_get("global_model_id")?,
row.try_get("provider_id").map_postgres_err()?,
row.try_get("global_model_id").map_postgres_err()?,
)
}
@@ -1,688 +0,0 @@
use async_trait::async_trait;
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StoredPublicGlobalModel {
pub id: String,
pub name: String,
pub display_name: Option<String>,
pub is_active: bool,
pub default_price_per_request: Option<f64>,
pub default_tiered_pricing: Option<Value>,
pub supported_capabilities: Option<Value>,
pub config: Option<Value>,
pub usage_count: u64,
}
impl StoredPublicGlobalModel {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: String,
name: String,
display_name: Option<String>,
is_active: bool,
default_price_per_request: Option<f64>,
default_tiered_pricing: Option<Value>,
supported_capabilities: Option<Value>,
config: Option<Value>,
usage_count: u64,
) -> Result<Self, crate::DataLayerError> {
if id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.id is empty".to_string(),
));
}
if name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.name is empty".to_string(),
));
}
Ok(Self {
id,
name,
display_name,
is_active,
default_price_per_request,
default_tiered_pricing,
supported_capabilities,
config,
usage_count,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PublicGlobalModelQuery {
pub offset: usize,
pub limit: usize,
pub is_active: Option<bool>,
pub search: Option<String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StoredPublicCatalogModel {
pub id: String,
pub provider_id: String,
pub provider_name: String,
pub provider_model_name: String,
pub name: String,
pub display_name: String,
pub description: Option<String>,
pub icon_url: Option<String>,
pub input_price_per_1m: Option<f64>,
pub output_price_per_1m: Option<f64>,
pub cache_creation_price_per_1m: Option<f64>,
pub cache_read_price_per_1m: Option<f64>,
pub supports_vision: Option<bool>,
pub supports_function_calling: Option<bool>,
pub supports_streaming: Option<bool>,
pub is_active: bool,
}
impl StoredPublicCatalogModel {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: String,
provider_id: String,
provider_name: String,
provider_model_name: String,
name: String,
display_name: String,
description: Option<String>,
icon_url: Option<String>,
input_price_per_1m: Option<f64>,
output_price_per_1m: Option<f64>,
cache_creation_price_per_1m: Option<f64>,
cache_read_price_per_1m: Option<f64>,
supports_vision: Option<bool>,
supports_function_calling: Option<bool>,
supports_streaming: Option<bool>,
is_active: bool,
) -> Result<Self, crate::DataLayerError> {
if id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.id is empty".to_string(),
));
}
if provider_id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.provider_id is empty".to_string(),
));
}
if provider_name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"providers.name is empty".to_string(),
));
}
if provider_model_name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.provider_model_name is empty".to_string(),
));
}
if name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"public model name is empty".to_string(),
));
}
if display_name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"public model display_name is empty".to_string(),
));
}
Ok(Self {
id,
provider_id,
provider_name,
provider_model_name,
name,
display_name,
description,
icon_url,
input_price_per_1m,
output_price_per_1m,
cache_creation_price_per_1m,
cache_read_price_per_1m,
supports_vision,
supports_function_calling,
supports_streaming,
is_active,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PublicCatalogModelListQuery {
pub provider_id: Option<String>,
pub offset: usize,
pub limit: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicCatalogModelSearchQuery {
pub search: String,
pub provider_id: Option<String>,
pub limit: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdminProviderModelListQuery {
pub provider_id: String,
pub is_active: Option<bool>,
pub offset: usize,
pub limit: usize,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StoredAdminGlobalModel {
pub id: String,
pub name: String,
pub display_name: String,
pub is_active: bool,
pub default_price_per_request: Option<f64>,
pub default_tiered_pricing: Option<Value>,
pub supported_capabilities: Option<Value>,
pub config: Option<Value>,
pub created_at_unix_secs: Option<u64>,
pub updated_at_unix_secs: Option<u64>,
}
impl StoredAdminGlobalModel {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: String,
name: String,
display_name: String,
is_active: bool,
default_price_per_request: Option<f64>,
default_tiered_pricing: Option<Value>,
supported_capabilities: Option<Value>,
config: Option<Value>,
created_at_unix_secs: Option<u64>,
updated_at_unix_secs: Option<u64>,
) -> Result<Self, crate::DataLayerError> {
if id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.id is empty".to_string(),
));
}
if name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.name is empty".to_string(),
));
}
if display_name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.display_name is empty".to_string(),
));
}
Ok(Self {
id,
name,
display_name,
is_active,
default_price_per_request,
default_tiered_pricing,
supported_capabilities,
config,
created_at_unix_secs,
updated_at_unix_secs,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AdminGlobalModelListQuery {
pub offset: usize,
pub limit: usize,
pub is_active: Option<bool>,
pub search: Option<String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StoredAdminProviderModel {
pub id: String,
pub provider_id: String,
pub global_model_id: String,
pub provider_model_name: String,
pub provider_model_mappings: Option<Value>,
pub price_per_request: Option<f64>,
pub tiered_pricing: Option<Value>,
pub supports_vision: Option<bool>,
pub supports_function_calling: Option<bool>,
pub supports_streaming: Option<bool>,
pub supports_extended_thinking: Option<bool>,
pub supports_image_generation: Option<bool>,
pub is_active: bool,
pub is_available: bool,
pub config: Option<Value>,
pub created_at_unix_secs: Option<u64>,
pub updated_at_unix_secs: Option<u64>,
pub global_model_name: Option<String>,
pub global_model_display_name: Option<String>,
pub global_model_default_price_per_request: Option<f64>,
pub global_model_default_tiered_pricing: Option<Value>,
pub global_model_config: Option<Value>,
}
impl StoredAdminProviderModel {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: String,
provider_id: String,
global_model_id: String,
provider_model_name: String,
provider_model_mappings: Option<Value>,
price_per_request: Option<f64>,
tiered_pricing: Option<Value>,
supports_vision: Option<bool>,
supports_function_calling: Option<bool>,
supports_streaming: Option<bool>,
supports_extended_thinking: Option<bool>,
supports_image_generation: Option<bool>,
is_active: bool,
is_available: bool,
config: Option<Value>,
created_at_unix_secs: Option<u64>,
updated_at_unix_secs: Option<u64>,
global_model_name: Option<String>,
global_model_display_name: Option<String>,
global_model_default_price_per_request: Option<f64>,
global_model_default_tiered_pricing: Option<Value>,
global_model_config: Option<Value>,
) -> Result<Self, crate::DataLayerError> {
if id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.id is empty".to_string(),
));
}
if provider_id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.provider_id is empty".to_string(),
));
}
if global_model_id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.global_model_id is empty".to_string(),
));
}
if provider_model_name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.provider_model_name is empty".to_string(),
));
}
Ok(Self {
id,
provider_id,
global_model_id,
provider_model_name,
provider_model_mappings,
price_per_request,
tiered_pricing,
supports_vision,
supports_function_calling,
supports_streaming,
supports_extended_thinking,
supports_image_generation,
is_active,
is_available,
config,
created_at_unix_secs,
updated_at_unix_secs,
global_model_name,
global_model_display_name,
global_model_default_price_per_request,
global_model_default_tiered_pricing,
global_model_config,
})
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct UpsertAdminProviderModelRecord {
pub id: String,
pub provider_id: String,
pub global_model_id: String,
pub provider_model_name: String,
pub provider_model_mappings: Option<Value>,
pub price_per_request: Option<f64>,
pub tiered_pricing: Option<Value>,
pub supports_vision: Option<bool>,
pub supports_function_calling: Option<bool>,
pub supports_streaming: Option<bool>,
pub supports_extended_thinking: Option<bool>,
pub supports_image_generation: Option<bool>,
pub is_active: bool,
pub is_available: bool,
pub config: Option<Value>,
}
impl UpsertAdminProviderModelRecord {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: String,
provider_id: String,
global_model_id: String,
provider_model_name: String,
provider_model_mappings: Option<Value>,
price_per_request: Option<f64>,
tiered_pricing: Option<Value>,
supports_vision: Option<bool>,
supports_function_calling: Option<bool>,
supports_streaming: Option<bool>,
supports_extended_thinking: Option<bool>,
supports_image_generation: Option<bool>,
is_active: bool,
is_available: bool,
config: Option<Value>,
) -> Result<Self, crate::DataLayerError> {
if id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.id is empty".to_string(),
));
}
if provider_id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.provider_id is empty".to_string(),
));
}
if global_model_id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.global_model_id is empty".to_string(),
));
}
if provider_model_name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"models.provider_model_name is empty".to_string(),
));
}
Ok(Self {
id,
provider_id,
global_model_id,
provider_model_name,
provider_model_mappings,
price_per_request,
tiered_pricing,
supports_vision,
supports_function_calling,
supports_streaming,
supports_extended_thinking,
supports_image_generation,
is_active,
is_available,
config,
})
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CreateAdminGlobalModelRecord {
pub id: String,
pub name: String,
pub display_name: String,
pub is_active: bool,
pub default_price_per_request: Option<f64>,
pub default_tiered_pricing: Option<Value>,
pub supported_capabilities: Option<Value>,
pub config: Option<Value>,
}
impl CreateAdminGlobalModelRecord {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: String,
name: String,
display_name: String,
is_active: bool,
default_price_per_request: Option<f64>,
default_tiered_pricing: Option<Value>,
supported_capabilities: Option<Value>,
config: Option<Value>,
) -> Result<Self, crate::DataLayerError> {
if id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.id is empty".to_string(),
));
}
if name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.name is empty".to_string(),
));
}
if display_name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.display_name is empty".to_string(),
));
}
Ok(Self {
id,
name,
display_name,
is_active,
default_price_per_request,
default_tiered_pricing,
supported_capabilities,
config,
})
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct UpdateAdminGlobalModelRecord {
pub id: String,
pub display_name: String,
pub is_active: bool,
pub default_price_per_request: Option<f64>,
pub default_tiered_pricing: Option<Value>,
pub supported_capabilities: Option<Value>,
pub config: Option<Value>,
}
impl UpdateAdminGlobalModelRecord {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: String,
display_name: String,
is_active: bool,
default_price_per_request: Option<f64>,
default_tiered_pricing: Option<Value>,
supported_capabilities: Option<Value>,
config: Option<Value>,
) -> Result<Self, crate::DataLayerError> {
if id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.id is empty".to_string(),
));
}
if display_name.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"global_models.display_name is empty".to_string(),
));
}
Ok(Self {
id,
display_name,
is_active,
default_price_per_request,
default_tiered_pricing,
supported_capabilities,
config,
})
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StoredPublicGlobalModelPage {
pub items: Vec<StoredPublicGlobalModel>,
pub total: usize,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StoredAdminGlobalModelPage {
pub items: Vec<StoredAdminGlobalModel>,
pub total: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct StoredProviderModelStats {
pub provider_id: String,
pub total_models: u64,
pub active_models: u64,
}
impl StoredProviderModelStats {
pub fn new(
provider_id: String,
total_models: i64,
active_models: i64,
) -> Result<Self, crate::DataLayerError> {
if provider_id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"provider model stats provider_id is empty".to_string(),
));
}
if total_models < 0 || active_models < 0 {
return Err(crate::DataLayerError::UnexpectedValue(
"provider model stats count is negative".to_string(),
));
}
Ok(Self {
provider_id,
total_models: total_models as u64,
active_models: active_models as u64,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct StoredProviderActiveGlobalModel {
pub provider_id: String,
pub global_model_id: String,
}
impl StoredProviderActiveGlobalModel {
pub fn new(
provider_id: String,
global_model_id: String,
) -> Result<Self, crate::DataLayerError> {
if provider_id.trim().is_empty() || global_model_id.trim().is_empty() {
return Err(crate::DataLayerError::UnexpectedValue(
"provider active global model identity is empty".to_string(),
));
}
Ok(Self {
provider_id,
global_model_id,
})
}
}
#[async_trait]
pub trait GlobalModelReadRepository: Send + Sync {
async fn list_public_models(
&self,
query: &PublicGlobalModelQuery,
) -> Result<StoredPublicGlobalModelPage, crate::DataLayerError>;
async fn get_public_model_by_name(
&self,
model_name: &str,
) -> Result<Option<StoredPublicGlobalModel>, crate::DataLayerError>;
async fn list_public_catalog_models(
&self,
query: &PublicCatalogModelListQuery,
) -> Result<Vec<StoredPublicCatalogModel>, crate::DataLayerError>;
async fn search_public_catalog_models(
&self,
query: &PublicCatalogModelSearchQuery,
) -> Result<Vec<StoredPublicCatalogModel>, crate::DataLayerError>;
async fn list_admin_global_models(
&self,
query: &AdminGlobalModelListQuery,
) -> Result<StoredAdminGlobalModelPage, crate::DataLayerError>;
async fn list_admin_provider_models(
&self,
query: &AdminProviderModelListQuery,
) -> Result<Vec<StoredAdminProviderModel>, crate::DataLayerError>;
async fn list_admin_provider_available_source_models(
&self,
provider_id: &str,
) -> Result<Vec<StoredAdminProviderModel>, crate::DataLayerError>;
async fn get_admin_provider_model(
&self,
provider_id: &str,
model_id: &str,
) -> Result<Option<StoredAdminProviderModel>, crate::DataLayerError>;
async fn get_admin_global_model_by_id(
&self,
global_model_id: &str,
) -> Result<Option<StoredAdminGlobalModel>, crate::DataLayerError>;
async fn get_admin_global_model_by_name(
&self,
model_name: &str,
) -> Result<Option<StoredAdminGlobalModel>, crate::DataLayerError>;
async fn list_admin_provider_models_by_global_model_id(
&self,
global_model_id: &str,
) -> Result<Vec<StoredAdminProviderModel>, crate::DataLayerError>;
async fn list_provider_model_stats(
&self,
provider_ids: &[String],
) -> Result<Vec<StoredProviderModelStats>, crate::DataLayerError>;
async fn list_active_global_model_ids_by_provider_ids(
&self,
provider_ids: &[String],
) -> Result<Vec<StoredProviderActiveGlobalModel>, crate::DataLayerError>;
}
#[async_trait]
pub trait GlobalModelWriteRepository: Send + Sync {
async fn create_admin_provider_model(
&self,
record: &UpsertAdminProviderModelRecord,
) -> Result<Option<StoredAdminProviderModel>, crate::DataLayerError>;
async fn update_admin_provider_model(
&self,
record: &UpsertAdminProviderModelRecord,
) -> Result<Option<StoredAdminProviderModel>, crate::DataLayerError>;
async fn delete_admin_provider_model(
&self,
provider_id: &str,
model_id: &str,
) -> Result<bool, crate::DataLayerError>;
async fn create_admin_global_model(
&self,
record: &CreateAdminGlobalModelRecord,
) -> Result<Option<StoredAdminGlobalModel>, crate::DataLayerError>;
async fn update_admin_global_model(
&self,
record: &UpdateAdminGlobalModelRecord,
) -> Result<Option<StoredAdminGlobalModel>, crate::DataLayerError>;
async fn delete_admin_global_model(
&self,
global_model_id: &str,
) -> Result<bool, crate::DataLayerError>;
}