mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat: 全栈功能增强 - 扩展 provider/pool 管理、完善调度与数据层、重构前端 Pool 页面
后端: - 扩展 pool_admin payloads 和 provider query models,增强 endpoint key 管理 - 完善 scheduler-core 候选排序与请求候选逻辑 - 增强 usage-runtime 写入、provider-transport 网络层与 OAuth 刷新 - 改进 AI pipeline 响应转换与流式处理 - 扩展 global_models/provider_catalog 数据层查询能力 - 增强 video-tasks-core 多 provider 支持 - 新增大量集成测试覆盖 pool/keys/provider_query/frontdoor 前端: - 重构 PoolManagement 页面,拆分状态管理/对话框逻辑到独立模块 - 新增 poolAdvancedDialog/poolSchedulingDialog/poolManagementState/poolMobilePresentation 工具函数及测试 - 改进 Dialog 组件与 provider tabs 显示 部署: - 更新 Rust CI workflow 和 Dockerfile 构建配置 Closes #275 Co-authored-by: AAEE86 <ppk0227@hotmail.com>
This commit is contained in:
@@ -85,13 +85,13 @@ impl RedisKvRunner {
|
||||
.get_multiplexed_async_connection()
|
||||
.await
|
||||
.map_redis_err()?;
|
||||
Ok(redis::cmd("SETEX")
|
||||
redis::cmd("SETEX")
|
||||
.arg(&namespaced_key)
|
||||
.arg(resolved_ttl)
|
||||
.arg(value)
|
||||
.query_async(&mut connection)
|
||||
.await
|
||||
.map_redis_err()?)
|
||||
.map_redis_err()
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -104,11 +104,11 @@ impl RedisKvRunner {
|
||||
.get_multiplexed_async_connection()
|
||||
.await
|
||||
.map_redis_err()?;
|
||||
Ok(redis::cmd("DEL")
|
||||
redis::cmd("DEL")
|
||||
.arg(&namespaced_key)
|
||||
.query_async(&mut connection)
|
||||
.await
|
||||
.map_redis_err()?)
|
||||
.map_redis_err()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -214,10 +214,10 @@ impl RedisStreamRunner {
|
||||
for (key, value) in fields {
|
||||
command.arg(key).arg(value);
|
||||
}
|
||||
Ok(command
|
||||
command
|
||||
.query_async::<String>(&mut connection)
|
||||
.await
|
||||
.map_redis_err()?)
|
||||
.map_redis_err()
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -323,10 +323,10 @@ impl RedisStreamRunner {
|
||||
for id in ids {
|
||||
command.arg(id);
|
||||
}
|
||||
Ok(command
|
||||
command
|
||||
.query_async::<usize>(&mut connection)
|
||||
.await
|
||||
.map_redis_err()?)
|
||||
.map_redis_err()
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -352,10 +352,10 @@ impl RedisStreamRunner {
|
||||
for id in ids {
|
||||
command.arg(id);
|
||||
}
|
||||
Ok(command
|
||||
command
|
||||
.query_async::<usize>(&mut connection)
|
||||
.await
|
||||
.map_redis_err()?)
|
||||
.map_redis_err()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ impl RequestCandidateWriteRepository for InMemoryRequestCandidateRepository {
|
||||
.filter(|row| row.created_at_unix_secs < created_before_unix_secs)
|
||||
.map(|row| (row.created_at_unix_secs, row.id.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
ids.sort_by(|left, right| left.cmp(right));
|
||||
ids.sort();
|
||||
|
||||
let mut deleted = 0usize;
|
||||
for (_, id) in ids.into_iter().take(limit) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -91,6 +92,35 @@ impl InMemoryGlobalModelReadRepository {
|
||||
.expect("admin global model repository lock") = items.into_iter().collect();
|
||||
self
|
||||
}
|
||||
|
||||
fn admin_global_model_provider_counts(&self, global_model_id: &str) -> (u64, u64) {
|
||||
let items = self
|
||||
.admin_provider_model_items
|
||||
.read()
|
||||
.expect("admin provider model repository lock");
|
||||
let provider_count = items
|
||||
.iter()
|
||||
.filter(|item| item.global_model_id == global_model_id)
|
||||
.map(|item| item.provider_id.clone())
|
||||
.collect::<BTreeSet<_>>()
|
||||
.len() as u64;
|
||||
let active_provider_count = items
|
||||
.iter()
|
||||
.filter(|item| item.global_model_id == global_model_id && item.is_active)
|
||||
.map(|item| item.provider_id.clone())
|
||||
.collect::<BTreeSet<_>>()
|
||||
.len() as u64;
|
||||
(provider_count, active_provider_count)
|
||||
}
|
||||
|
||||
fn enrich_admin_global_model(&self, item: &StoredAdminGlobalModel) -> StoredAdminGlobalModel {
|
||||
let mut enriched = item.clone();
|
||||
let (provider_count, active_provider_count) =
|
||||
self.admin_global_model_provider_counts(&item.id);
|
||||
enriched.provider_count = provider_count;
|
||||
enriched.active_provider_count = active_provider_count;
|
||||
enriched
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -260,6 +290,7 @@ impl GlobalModelReadRepository for InMemoryGlobalModelReadRepository {
|
||||
.into_iter()
|
||||
.skip(query.offset)
|
||||
.take(query.limit)
|
||||
.map(|item| self.enrich_admin_global_model(&item))
|
||||
.collect();
|
||||
Ok(StoredAdminGlobalModelPage { items, total })
|
||||
}
|
||||
@@ -354,7 +385,7 @@ impl GlobalModelReadRepository for InMemoryGlobalModelReadRepository {
|
||||
Ok(items
|
||||
.iter()
|
||||
.find(|item| item.id == global_model_id)
|
||||
.cloned())
|
||||
.map(|item| self.enrich_admin_global_model(item)))
|
||||
}
|
||||
|
||||
async fn get_admin_global_model_by_name(
|
||||
@@ -365,7 +396,10 @@ impl GlobalModelReadRepository for InMemoryGlobalModelReadRepository {
|
||||
.admin_global_model_items
|
||||
.read()
|
||||
.expect("admin global model repository lock");
|
||||
Ok(items.iter().find(|item| item.name == model_name).cloned())
|
||||
Ok(items
|
||||
.iter()
|
||||
.find(|item| item.name == model_name)
|
||||
.map(|item| self.enrich_admin_global_model(item)))
|
||||
}
|
||||
|
||||
async fn list_admin_provider_models_by_global_model_id(
|
||||
@@ -537,35 +571,40 @@ impl GlobalModelWriteRepository for InMemoryGlobalModelReadRepository {
|
||||
record.default_tiered_pricing.clone(),
|
||||
record.supported_capabilities.clone(),
|
||||
record.config.clone(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
Some(1_711_000_000),
|
||||
Some(1_711_000_000),
|
||||
)?;
|
||||
self.admin_global_model_items
|
||||
.write()
|
||||
.expect("admin global model repository lock")
|
||||
.push(stored.clone());
|
||||
Ok(Some(stored))
|
||||
.push(stored);
|
||||
self.get_admin_global_model_by_id(&record.id).await
|
||||
}
|
||||
|
||||
async fn update_admin_global_model(
|
||||
&self,
|
||||
record: &UpdateAdminGlobalModelRecord,
|
||||
) -> Result<Option<StoredAdminGlobalModel>, DataLayerError> {
|
||||
let mut items = self
|
||||
.admin_global_model_items
|
||||
.write()
|
||||
.expect("admin global model repository lock");
|
||||
let Some(existing) = items.iter_mut().find(|item| item.id == record.id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
existing.display_name = record.display_name.clone();
|
||||
existing.is_active = record.is_active;
|
||||
existing.default_price_per_request = record.default_price_per_request;
|
||||
existing.default_tiered_pricing = record.default_tiered_pricing.clone();
|
||||
existing.supported_capabilities = record.supported_capabilities.clone();
|
||||
existing.config = record.config.clone();
|
||||
existing.updated_at_unix_secs = Some(1_711_000_100);
|
||||
Ok(Some(existing.clone()))
|
||||
{
|
||||
let mut items = self
|
||||
.admin_global_model_items
|
||||
.write()
|
||||
.expect("admin global model repository lock");
|
||||
let Some(existing) = items.iter_mut().find(|item| item.id == record.id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
existing.display_name = record.display_name.clone();
|
||||
existing.is_active = record.is_active;
|
||||
existing.default_price_per_request = record.default_price_per_request;
|
||||
existing.default_tiered_pricing = record.default_tiered_pricing.clone();
|
||||
existing.supported_capabilities = record.supported_capabilities.clone();
|
||||
existing.config = record.config.clone();
|
||||
existing.updated_at_unix_secs = Some(1_711_000_100);
|
||||
}
|
||||
self.get_admin_global_model_by_id(&record.id).await
|
||||
}
|
||||
|
||||
async fn delete_admin_global_model(
|
||||
|
||||
@@ -104,22 +104,39 @@ LEFT JOIN global_models gm ON gm.id = m.global_model_id
|
||||
|
||||
const LIST_ADMIN_GLOBAL_MODELS_PREFIX: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
COALESCE(NULLIF(display_name, ''), name) AS display_name,
|
||||
is_active,
|
||||
CAST(default_price_per_request AS DOUBLE PRECISION) AS default_price_per_request,
|
||||
default_tiered_pricing,
|
||||
supported_capabilities,
|
||||
config,
|
||||
EXTRACT(EPOCH FROM created_at)::bigint AS created_at_unix_secs,
|
||||
EXTRACT(EPOCH FROM updated_at)::bigint AS updated_at_unix_secs
|
||||
FROM global_models
|
||||
gm.id,
|
||||
gm.name,
|
||||
COALESCE(NULLIF(gm.display_name, ''), gm.name) AS display_name,
|
||||
gm.is_active,
|
||||
CAST(gm.default_price_per_request AS DOUBLE PRECISION) AS default_price_per_request,
|
||||
gm.default_tiered_pricing,
|
||||
gm.supported_capabilities,
|
||||
gm.config,
|
||||
COALESCE(gm_stats.provider_count, 0) AS provider_count,
|
||||
COALESCE(gm_stats.active_provider_count, 0) AS active_provider_count,
|
||||
COALESCE(gm.usage_count, 0)::bigint AS usage_count,
|
||||
EXTRACT(EPOCH FROM gm.created_at)::bigint AS created_at_unix_secs,
|
||||
EXTRACT(EPOCH FROM gm.updated_at)::bigint AS updated_at_unix_secs
|
||||
FROM global_models gm
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
m.global_model_id,
|
||||
COUNT(DISTINCT m.provider_id)::bigint AS provider_count,
|
||||
COUNT(
|
||||
DISTINCT CASE
|
||||
WHEN m.is_active = TRUE AND p.is_active = TRUE THEN m.provider_id
|
||||
ELSE NULL
|
||||
END
|
||||
)::bigint AS active_provider_count
|
||||
FROM models m
|
||||
JOIN providers p ON p.id = m.provider_id
|
||||
GROUP BY m.global_model_id
|
||||
) gm_stats ON gm_stats.global_model_id = gm.id
|
||||
"#;
|
||||
|
||||
const COUNT_ADMIN_GLOBAL_MODELS_PREFIX: &str = r#"
|
||||
SELECT COUNT(id) AS total
|
||||
FROM global_models
|
||||
FROM global_models gm
|
||||
"#;
|
||||
|
||||
const LIST_ACTIVE_GLOBAL_MODEL_IDS_BY_PROVIDER_IDS_PREFIX: &str = r#"
|
||||
@@ -374,19 +391,35 @@ ORDER BY gm.name ASC, m.created_at DESC, m.id ASC
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
COALESCE(NULLIF(display_name, ''), name) AS display_name,
|
||||
is_active,
|
||||
CAST(default_price_per_request AS DOUBLE PRECISION) AS default_price_per_request,
|
||||
default_tiered_pricing,
|
||||
supported_capabilities,
|
||||
config
|
||||
,
|
||||
EXTRACT(EPOCH FROM created_at)::bigint AS created_at_unix_secs,
|
||||
EXTRACT(EPOCH FROM updated_at)::bigint AS updated_at_unix_secs
|
||||
FROM global_models
|
||||
WHERE id = $1
|
||||
gm.id,
|
||||
gm.name,
|
||||
COALESCE(NULLIF(gm.display_name, ''), gm.name) AS display_name,
|
||||
gm.is_active,
|
||||
CAST(gm.default_price_per_request AS DOUBLE PRECISION) AS default_price_per_request,
|
||||
gm.default_tiered_pricing,
|
||||
gm.supported_capabilities,
|
||||
gm.config,
|
||||
COALESCE(gm_stats.provider_count, 0) AS provider_count,
|
||||
COALESCE(gm_stats.active_provider_count, 0) AS active_provider_count,
|
||||
COALESCE(gm.usage_count, 0)::bigint AS usage_count,
|
||||
EXTRACT(EPOCH FROM gm.created_at)::bigint AS created_at_unix_secs,
|
||||
EXTRACT(EPOCH FROM gm.updated_at)::bigint AS updated_at_unix_secs
|
||||
FROM global_models gm
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
m.global_model_id,
|
||||
COUNT(DISTINCT m.provider_id)::bigint AS provider_count,
|
||||
COUNT(
|
||||
DISTINCT CASE
|
||||
WHEN m.is_active = TRUE AND p.is_active = TRUE THEN m.provider_id
|
||||
ELSE NULL
|
||||
END
|
||||
)::bigint AS active_provider_count
|
||||
FROM models m
|
||||
JOIN providers p ON p.id = m.provider_id
|
||||
GROUP BY m.global_model_id
|
||||
) gm_stats ON gm_stats.global_model_id = gm.id
|
||||
WHERE gm.id = $1
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
@@ -405,19 +438,35 @@ LIMIT 1
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
COALESCE(NULLIF(display_name, ''), name) AS display_name,
|
||||
is_active,
|
||||
CAST(default_price_per_request AS DOUBLE PRECISION) AS default_price_per_request,
|
||||
default_tiered_pricing,
|
||||
supported_capabilities,
|
||||
config
|
||||
,
|
||||
EXTRACT(EPOCH FROM created_at)::bigint AS created_at_unix_secs,
|
||||
EXTRACT(EPOCH FROM updated_at)::bigint AS updated_at_unix_secs
|
||||
FROM global_models
|
||||
WHERE name = $1
|
||||
gm.id,
|
||||
gm.name,
|
||||
COALESCE(NULLIF(gm.display_name, ''), gm.name) AS display_name,
|
||||
gm.is_active,
|
||||
CAST(gm.default_price_per_request AS DOUBLE PRECISION) AS default_price_per_request,
|
||||
gm.default_tiered_pricing,
|
||||
gm.supported_capabilities,
|
||||
gm.config,
|
||||
COALESCE(gm_stats.provider_count, 0) AS provider_count,
|
||||
COALESCE(gm_stats.active_provider_count, 0) AS active_provider_count,
|
||||
COALESCE(gm.usage_count, 0)::bigint AS usage_count,
|
||||
EXTRACT(EPOCH FROM gm.created_at)::bigint AS created_at_unix_secs,
|
||||
EXTRACT(EPOCH FROM gm.updated_at)::bigint AS updated_at_unix_secs
|
||||
FROM global_models gm
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
m.global_model_id,
|
||||
COUNT(DISTINCT m.provider_id)::bigint AS provider_count,
|
||||
COUNT(
|
||||
DISTINCT CASE
|
||||
WHEN m.is_active = TRUE AND p.is_active = TRUE THEN m.provider_id
|
||||
ELSE NULL
|
||||
END
|
||||
)::bigint AS active_provider_count
|
||||
FROM models m
|
||||
JOIN providers p ON p.id = m.provider_id
|
||||
GROUP BY m.global_model_id
|
||||
) gm_stats ON gm_stats.global_model_id = gm.id
|
||||
WHERE gm.name = $1
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
@@ -928,7 +977,7 @@ fn apply_admin_global_model_filters(
|
||||
) {
|
||||
builder.push(" WHERE 1=1");
|
||||
if let Some(is_active) = query.is_active {
|
||||
builder.push(" AND is_active = ").push_bind(is_active);
|
||||
builder.push(" AND gm.is_active = ").push_bind(is_active);
|
||||
}
|
||||
if let Some(search) = query
|
||||
.search
|
||||
@@ -938,9 +987,9 @@ fn apply_admin_global_model_filters(
|
||||
{
|
||||
let pattern = format!("%{search}%");
|
||||
builder
|
||||
.push(" AND (name ILIKE ")
|
||||
.push(" AND (gm.name ILIKE ")
|
||||
.push_bind(pattern.clone())
|
||||
.push(" OR display_name ILIKE ")
|
||||
.push(" OR gm.display_name ILIKE ")
|
||||
.push_bind(pattern)
|
||||
.push(")");
|
||||
}
|
||||
@@ -1062,6 +1111,18 @@ fn map_admin_global_model_row(row: &PgRow) -> Result<StoredAdminGlobalModel, Dat
|
||||
.try_get::<Option<i64>, _>("updated_at_unix_secs")
|
||||
.map_postgres_err()?
|
||||
.map(|value| value.max(0) as u64);
|
||||
let provider_count = row
|
||||
.try_get::<i64, _>("provider_count")
|
||||
.map_postgres_err()?
|
||||
.max(0) as u64;
|
||||
let active_provider_count = row
|
||||
.try_get::<i64, _>("active_provider_count")
|
||||
.map_postgres_err()?
|
||||
.max(0) as u64;
|
||||
let usage_count = row
|
||||
.try_get::<i64, _>("usage_count")
|
||||
.map_postgres_err()?
|
||||
.max(0) as u64;
|
||||
StoredAdminGlobalModel::new(
|
||||
row.try_get("id").map_postgres_err()?,
|
||||
row.try_get("name").map_postgres_err()?,
|
||||
@@ -1072,6 +1133,9 @@ fn map_admin_global_model_row(row: &PgRow) -> Result<StoredAdminGlobalModel, Dat
|
||||
row.try_get("default_tiered_pricing").map_postgres_err()?,
|
||||
row.try_get("supported_capabilities").map_postgres_err()?,
|
||||
row.try_get("config").map_postgres_err()?,
|
||||
provider_count,
|
||||
active_provider_count,
|
||||
usage_count,
|
||||
created_at_unix_secs,
|
||||
updated_at_unix_secs,
|
||||
)
|
||||
|
||||
@@ -161,6 +161,8 @@ SELECT
|
||||
utilization_samples,
|
||||
EXTRACT(EPOCH FROM last_probe_increase_at)::bigint AS last_probe_increase_at_unix_secs,
|
||||
request_count,
|
||||
total_tokens,
|
||||
CAST(total_cost_usd AS DOUBLE PRECISION) AS total_cost_usd,
|
||||
success_count,
|
||||
error_count,
|
||||
total_response_time_ms,
|
||||
@@ -214,6 +216,8 @@ SELECT
|
||||
utilization_samples,
|
||||
EXTRACT(EPOCH FROM last_probe_increase_at)::bigint AS last_probe_increase_at_unix_secs,
|
||||
request_count,
|
||||
total_tokens,
|
||||
CAST(total_cost_usd AS DOUBLE PRECISION) AS total_cost_usd,
|
||||
success_count,
|
||||
error_count,
|
||||
total_response_time_ms,
|
||||
@@ -503,6 +507,8 @@ SELECT
|
||||
utilization_samples,
|
||||
EXTRACT(EPOCH FROM last_probe_increase_at)::bigint AS last_probe_increase_at_unix_secs,
|
||||
request_count,
|
||||
total_tokens,
|
||||
CAST(total_cost_usd AS DOUBLE PRECISION) AS total_cost_usd,
|
||||
success_count,
|
||||
error_count,
|
||||
total_response_time_ms,
|
||||
@@ -1164,30 +1170,30 @@ INSERT INTO provider_api_keys (
|
||||
ELSE TO_TIMESTAMP($35::double precision)
|
||||
END,
|
||||
COALESCE($36, 0),
|
||||
0,
|
||||
0,
|
||||
COALESCE($37, 0),
|
||||
COALESCE($38, 0),
|
||||
COALESCE($39, 0),
|
||||
COALESCE($40, 0),
|
||||
COALESCE($41, 0),
|
||||
CASE
|
||||
WHEN $40::double precision IS NULL THEN NULL
|
||||
ELSE TO_TIMESTAMP($40::double precision)
|
||||
WHEN $42::double precision IS NULL THEN NULL
|
||||
ELSE TO_TIMESTAMP($42::double precision)
|
||||
END,
|
||||
CASE
|
||||
WHEN $41::double precision IS NULL THEN NULL
|
||||
ELSE TO_TIMESTAMP($41::double precision)
|
||||
WHEN $43::double precision IS NULL THEN NULL
|
||||
ELSE TO_TIMESTAMP($43::double precision)
|
||||
END,
|
||||
$42,
|
||||
$43,
|
||||
$44,
|
||||
$45,
|
||||
$46,
|
||||
$47,
|
||||
CASE
|
||||
WHEN $46::double precision IS NULL THEN NOW()
|
||||
ELSE TO_TIMESTAMP($46::double precision)
|
||||
WHEN $48::double precision IS NULL THEN NOW()
|
||||
ELSE TO_TIMESTAMP($48::double precision)
|
||||
END,
|
||||
CASE
|
||||
WHEN $47::double precision IS NULL THEN NOW()
|
||||
ELSE TO_TIMESTAMP($47::double precision)
|
||||
WHEN $49::double precision IS NULL THEN NOW()
|
||||
ELSE TO_TIMESTAMP($49::double precision)
|
||||
END
|
||||
)
|
||||
"#,
|
||||
@@ -1231,6 +1237,13 @@ INSERT INTO provider_api_keys (
|
||||
.map(|value| value as f64),
|
||||
)
|
||||
.bind(key.request_count.map(|value| value as i32))
|
||||
.bind(Some(i64::try_from(key.total_tokens).map_err(|_| {
|
||||
DataLayerError::InvalidInput(format!(
|
||||
"provider catalog key.total_tokens exceeds i64: {}",
|
||||
key.total_tokens
|
||||
))
|
||||
})?))
|
||||
.bind(key.total_cost_usd)
|
||||
.bind(key.success_count.map(|value| value as i32))
|
||||
.bind(key.error_count.map(|value| value as i32))
|
||||
.bind(key.total_response_time_ms.map(|value| value as i32))
|
||||
@@ -2092,6 +2105,18 @@ fn map_key_row(row: &PgRow) -> Result<StoredProviderCatalogKey, DataLayerError>
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
let total_tokens = row_get::<Option<i64>>(row, "total_tokens")?
|
||||
.unwrap_or(0)
|
||||
.try_into()
|
||||
.map_err(|_| {
|
||||
DataLayerError::UnexpectedValue("invalid provider_api_keys.total_tokens".to_string())
|
||||
})?;
|
||||
let total_cost_usd = row_get::<Option<f64>>(row, "total_cost_usd")?.unwrap_or(0.0);
|
||||
if !total_cost_usd.is_finite() {
|
||||
return Err(DataLayerError::UnexpectedValue(
|
||||
"invalid provider_api_keys.total_cost_usd".to_string(),
|
||||
));
|
||||
}
|
||||
let success_count = row_get::<Option<i32>>(row, "success_count")?
|
||||
.map(|value| {
|
||||
u32::try_from(value).map_err(|_| {
|
||||
@@ -2212,6 +2237,7 @@ fn map_key_row(row: &PgRow) -> Result<StoredProviderCatalogKey, DataLayerError>
|
||||
success_count,
|
||||
)
|
||||
.with_usage_fields(error_count, total_response_time_ms)
|
||||
.with_usage_totals(total_tokens, total_cost_usd)
|
||||
.with_health_fields(
|
||||
row.try_get("health_by_format").ok(),
|
||||
row.try_get("circuit_breaker_by_format").ok(),
|
||||
@@ -2263,4 +2289,15 @@ mod tests {
|
||||
let repository = SqlxProviderCatalogReadRepository::new(pool);
|
||||
let _ = repository.pool();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_queries_include_usage_totals() {
|
||||
for sql in [
|
||||
super::LIST_KEYS_BY_IDS_PREFIX,
|
||||
super::LIST_KEYS_BY_PROVIDER_IDS_PREFIX,
|
||||
] {
|
||||
assert!(sql.contains("total_tokens"));
|
||||
assert!(sql.contains("total_cost_usd"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +459,7 @@ pub struct CreateWalletRechargeOrderInput {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum CreateWalletRechargeOrderOutcome {
|
||||
Created(StoredAdminPaymentOrder),
|
||||
WalletInactive,
|
||||
@@ -506,6 +507,7 @@ pub struct ProcessPaymentCallbackInput {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ProcessPaymentCallbackOutcome {
|
||||
DuplicateProcessed {
|
||||
order_id: Option<String>,
|
||||
|
||||
Reference in New Issue
Block a user