mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 18:37:46 +08:00
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:
@@ -0,0 +1,7 @@
|
||||
mod paths;
|
||||
mod payloads;
|
||||
mod support;
|
||||
|
||||
pub(crate) use self::paths::*;
|
||||
pub(crate) use self::payloads::*;
|
||||
pub(crate) use self::support::*;
|
||||
@@ -0,0 +1,418 @@
|
||||
pub(crate) fn admin_provider_id_for_keys(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/endpoints/providers/")?
|
||||
.strip_suffix("/keys")
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn is_admin_provider_ops_architectures_root(request_path: &str) -> bool {
|
||||
matches!(
|
||||
request_path,
|
||||
"/api/admin/provider-ops/architectures" | "/api/admin/provider-ops/architectures/"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_ops_architecture_id_from_path(request_path: &str) -> Option<String> {
|
||||
let raw = request_path.strip_prefix("/api/admin/provider-ops/architectures/")?;
|
||||
let normalized = raw.trim().trim_matches('/');
|
||||
if normalized.is_empty() || normalized.contains('/') {
|
||||
None
|
||||
} else {
|
||||
Some(normalized.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_ops_status(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-ops/providers/")?
|
||||
.strip_suffix("/status")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_ops_config(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-ops/providers/")?
|
||||
.strip_suffix("/config")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_ops_disconnect(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-ops/providers/")?
|
||||
.strip_suffix("/disconnect")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_ops_connect(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-ops/providers/")?
|
||||
.strip_suffix("/connect")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_ops_verify(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-ops/providers/")?
|
||||
.strip_suffix("/verify")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_ops_balance(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-ops/providers/")?
|
||||
.strip_suffix("/balance")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_ops_checkin(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-ops/providers/")?
|
||||
.strip_suffix("/checkin")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn is_admin_provider_strategy_strategies_root(request_path: &str) -> bool {
|
||||
matches!(
|
||||
request_path,
|
||||
"/api/admin/provider-strategy/strategies" | "/api/admin/provider-strategy/strategies/"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_strategy_billing(
|
||||
request_path: &str,
|
||||
) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-strategy/providers/")?
|
||||
.strip_suffix("/billing")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_strategy_stats(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-strategy/providers/")?
|
||||
.strip_suffix("/stats")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_provider_strategy_quota(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-strategy/providers/")?
|
||||
.strip_suffix("/quota")
|
||||
.map(|value| value.trim().trim_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_ops_action_route_parts(
|
||||
request_path: &str,
|
||||
) -> Option<(String, String)> {
|
||||
let raw = request_path.strip_prefix("/api/admin/provider-ops/providers/")?;
|
||||
let (provider_id, action_type) = raw.split_once("/actions/")?;
|
||||
let provider_id = provider_id.trim().trim_matches('/');
|
||||
let action_type = action_type.trim().trim_matches('/');
|
||||
if provider_id.is_empty()
|
||||
|| action_type.is_empty()
|
||||
|| provider_id.contains('/')
|
||||
|| action_type.contains('/')
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((provider_id.to_string(), action_type.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_admin_provider_ops_batch_balance_root(request_path: &str) -> bool {
|
||||
matches!(
|
||||
request_path,
|
||||
"/api/admin/provider-ops/batch/balance" | "/api/admin/provider-ops/batch/balance/"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_refresh_quota(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/endpoints/providers/")?
|
||||
.strip_suffix("/refresh-quota")
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_health_monitor(request_path: &str) -> Option<String> {
|
||||
let raw = request_path.strip_prefix("/api/admin/providers/")?;
|
||||
let raw = raw.strip_suffix("/health-monitor")?;
|
||||
let normalized = raw.trim().trim_matches('/');
|
||||
if normalized.is_empty() || normalized.contains('/') {
|
||||
None
|
||||
} else {
|
||||
Some(normalized.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_mapping_preview(request_path: &str) -> Option<String> {
|
||||
let raw = request_path.strip_prefix("/api/admin/providers/")?;
|
||||
let raw = raw.strip_suffix("/mapping-preview")?;
|
||||
let normalized = raw.trim().trim_matches('/');
|
||||
if normalized.is_empty() || normalized.contains('/') {
|
||||
None
|
||||
} else {
|
||||
Some(normalized.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_pool_status(request_path: &str) -> Option<String> {
|
||||
admin_provider_id_for_suffix(request_path, "/pool-status")
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_pool_key_route_parts(
|
||||
request_path: &str,
|
||||
marker: &str,
|
||||
) -> Option<(String, String)> {
|
||||
let raw = request_path.strip_prefix("/api/admin/providers/")?;
|
||||
let (provider_id, key_id) = raw.split_once(marker)?;
|
||||
let provider_id = provider_id.trim().trim_matches('/');
|
||||
let key_id = key_id.trim().trim_matches('/');
|
||||
if provider_id.is_empty()
|
||||
|| key_id.is_empty()
|
||||
|| provider_id.contains('/')
|
||||
|| key_id.contains('/')
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((provider_id.to_string(), key_id.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_clear_pool_cooldown_parts(
|
||||
request_path: &str,
|
||||
) -> Option<(String, String)> {
|
||||
admin_provider_pool_key_route_parts(request_path, "/pool/clear-cooldown/")
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_reset_pool_cost_parts(request_path: &str) -> Option<(String, String)> {
|
||||
admin_provider_pool_key_route_parts(request_path, "/pool/reset-cost/")
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_delete_task_parts(request_path: &str) -> Option<(String, String)> {
|
||||
let raw = request_path.strip_prefix("/api/admin/providers/")?;
|
||||
let (provider_id, task_id) = raw.split_once("/delete-task/")?;
|
||||
let provider_id = provider_id.trim().trim_matches('/');
|
||||
let task_id = task_id.trim().trim_matches('/');
|
||||
if provider_id.is_empty()
|
||||
|| task_id.is_empty()
|
||||
|| provider_id.contains('/')
|
||||
|| task_id.contains('/')
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((provider_id.to_string(), task_id.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_summary(request_path: &str) -> Option<String> {
|
||||
let raw = request_path.strip_prefix("/api/admin/providers/")?;
|
||||
let raw = raw.strip_suffix("/summary")?;
|
||||
let normalized = raw.trim().trim_matches('/');
|
||||
if normalized.is_empty() || normalized.contains('/') {
|
||||
None
|
||||
} else {
|
||||
Some(normalized.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_manage_path(request_path: &str) -> Option<String> {
|
||||
let raw = request_path.strip_prefix("/api/admin/providers/")?;
|
||||
let normalized = raw.trim().trim_matches('/');
|
||||
if normalized.is_empty() || normalized.contains('/') {
|
||||
None
|
||||
} else {
|
||||
Some(normalized.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_admin_providers_root(request_path: &str) -> bool {
|
||||
matches!(
|
||||
request_path,
|
||||
"/api/admin/providers" | "/api/admin/providers/"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_models_list(request_path: &str) -> Option<String> {
|
||||
admin_provider_id_for_suffix(request_path, "/models")
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_id_for_suffix(request_path: &str, suffix: &str) -> Option<String> {
|
||||
let raw = request_path.strip_prefix("/api/admin/providers/")?;
|
||||
let raw = raw.strip_suffix(suffix)?;
|
||||
let normalized = raw.trim().trim_matches('/');
|
||||
if normalized.is_empty() || normalized.contains('/') {
|
||||
None
|
||||
} else {
|
||||
Some(normalized.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_model_route_parts(request_path: &str) -> Option<(String, String)> {
|
||||
let raw = request_path.strip_prefix("/api/admin/providers/")?;
|
||||
let (provider_id, model_id) = raw.split_once("/models/")?;
|
||||
let provider_id = provider_id.trim().trim_matches('/');
|
||||
let model_id = model_id.trim().trim_matches('/');
|
||||
if provider_id.is_empty()
|
||||
|| model_id.is_empty()
|
||||
|| provider_id.contains('/')
|
||||
|| model_id.contains('/')
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((provider_id.to_string(), model_id.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_models_batch_path(request_path: &str) -> Option<String> {
|
||||
admin_provider_id_for_suffix(request_path, "/models/batch")
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_available_source_models_path(request_path: &str) -> Option<String> {
|
||||
admin_provider_id_for_suffix(request_path, "/available-source-models")
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_assign_global_models_path(request_path: &str) -> Option<String> {
|
||||
admin_provider_id_for_suffix(request_path, "/assign-global-models")
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_import_models_path(request_path: &str) -> Option<String> {
|
||||
admin_provider_id_for_suffix(request_path, "/import-from-upstream")
|
||||
}
|
||||
|
||||
pub(crate) fn admin_reveal_key_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/endpoints/keys/")?
|
||||
.strip_suffix("/reveal")
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_export_key_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/endpoints/keys/")?
|
||||
.strip_suffix("/export")
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_clear_oauth_invalid_key_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/endpoints/keys/")?
|
||||
.strip_suffix("/clear-oauth-invalid")
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_update_key_id(request_path: &str) -> Option<String> {
|
||||
let key_id = request_path.strip_prefix("/api/admin/endpoints/keys/")?;
|
||||
(!key_id.is_empty() && !key_id.contains('/')).then_some(key_id.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_start_key_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/keys/")?
|
||||
.strip_suffix("/start")
|
||||
.filter(|key_id| !key_id.is_empty() && !key_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_start_provider_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/providers/")?
|
||||
.strip_suffix("/start")
|
||||
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_complete_key_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/keys/")?
|
||||
.strip_suffix("/complete")
|
||||
.filter(|key_id| !key_id.is_empty() && !key_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_refresh_key_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/keys/")?
|
||||
.strip_suffix("/refresh")
|
||||
.filter(|key_id| !key_id.is_empty() && !key_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_complete_provider_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/providers/")?
|
||||
.strip_suffix("/complete")
|
||||
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_import_provider_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/providers/")?
|
||||
.strip_suffix("/import-refresh-token")
|
||||
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_batch_import_provider_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/providers/")?
|
||||
.strip_suffix("/batch-import")
|
||||
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_batch_import_task_provider_id(
|
||||
request_path: &str,
|
||||
) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/providers/")?
|
||||
.strip_suffix("/batch-import/tasks")
|
||||
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_batch_import_task_path(
|
||||
request_path: &str,
|
||||
) -> Option<(String, String)> {
|
||||
let suffix = request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/providers/")?
|
||||
.strip_suffix("/")
|
||||
.unwrap_or(request_path.strip_prefix("/api/admin/provider-oauth/providers/")?);
|
||||
let (provider_id, task_path) = suffix.split_once("/batch-import/tasks/")?;
|
||||
if provider_id.is_empty()
|
||||
|| provider_id.contains('/')
|
||||
|| task_path.is_empty()
|
||||
|| task_path.contains('/')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some((provider_id.to_string(), task_path.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_device_authorize_provider_id(
|
||||
request_path: &str,
|
||||
) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/providers/")?
|
||||
.strip_suffix("/device-authorize")
|
||||
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn admin_provider_oauth_device_poll_provider_id(request_path: &str) -> Option<String> {
|
||||
request_path
|
||||
.strip_prefix("/api/admin/provider-oauth/providers/")?
|
||||
.strip_suffix("/device-poll")
|
||||
.filter(|provider_id| !provider_id.is_empty() && !provider_id.contains('/'))
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminProviderKeyCreateRequest {
|
||||
#[serde(default)]
|
||||
pub(crate) api_formats: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) api_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) auth_type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) auth_config: Option<serde_json::Value>,
|
||||
pub(crate) name: String,
|
||||
#[serde(default)]
|
||||
pub(crate) rate_multipliers: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) internal_priority: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) rpm_limit: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub(crate) allowed_models: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) capabilities: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) cache_ttl_minutes: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) max_probe_interval_minutes: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) note: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) auto_fetch_models: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) locked_models: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) model_include_patterns: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) model_exclude_patterns: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminProviderKeyUpdateRequest {
|
||||
#[serde(default)]
|
||||
pub(crate) api_formats: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) api_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) auth_type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) auth_config: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) rate_multipliers: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) internal_priority: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) global_priority_by_format: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) rpm_limit: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub(crate) allowed_models: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) capabilities: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) cache_ttl_minutes: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) max_probe_interval_minutes: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) is_active: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) note: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) auto_fetch_models: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) locked_models: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) model_include_patterns: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) model_exclude_patterns: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub(crate) proxy: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) fingerprint: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminProviderKeyBatchDeleteRequest {
|
||||
pub(crate) ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminProviderQuotaRefreshRequest {
|
||||
#[serde(default)]
|
||||
pub(crate) key_ids: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminProviderCreateRequest {
|
||||
pub(crate) name: String,
|
||||
#[serde(default)]
|
||||
pub(crate) provider_type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) website: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) billing_type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) monthly_quota_usd: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub(crate) quota_reset_day: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub(crate) quota_last_reset_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) quota_expires_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) provider_priority: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) keep_priority_on_conversion: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) is_active: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) concurrent_limit: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) max_retries: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) proxy: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) stream_first_byte_timeout: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub(crate) request_timeout: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub(crate) pool_advanced: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) claude_code_advanced: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) failover_rules: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminProviderUpdateRequest {
|
||||
#[serde(default)]
|
||||
pub(crate) name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) provider_type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) website: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) billing_type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) monthly_quota_usd: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub(crate) quota_reset_day: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub(crate) quota_last_reset_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) quota_expires_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) provider_priority: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) keep_priority_on_conversion: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) is_active: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) concurrent_limit: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) max_retries: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub(crate) proxy: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) stream_first_byte_timeout: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub(crate) request_timeout: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub(crate) pool_advanced: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) claude_code_advanced: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) failover_rules: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) enable_format_conversion: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub(crate) const CODEX_WHAM_USAGE_URL: &str = "https://chatgpt.com/backend-api/wham/usage";
|
||||
pub(crate) const KIRO_USAGE_LIMITS_PATH: &str = "/getUsageLimits";
|
||||
pub(crate) const KIRO_USAGE_SDK_VERSION: &str = "1.0.0";
|
||||
pub(crate) const ANTIGRAVITY_FETCH_AVAILABLE_MODELS_PATH: &str = "/v1internal:fetchAvailableModels";
|
||||
pub(crate) const OAUTH_ACCOUNT_BLOCK_PREFIX: &str = "[ACCOUNT_BLOCK] ";
|
||||
pub(crate) const OAUTH_REFRESH_FAILED_PREFIX: &str = "[REFRESH_FAILED] ";
|
||||
pub(crate) const OAUTH_EXPIRED_PREFIX: &str = "[OAUTH_EXPIRED] ";
|
||||
pub(crate) const OAUTH_REQUEST_FAILED_PREFIX: &str = "[REQUEST_FAILED] ";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminProviderModelCreateRequest {
|
||||
pub(crate) provider_model_name: String,
|
||||
#[serde(default)]
|
||||
pub(crate) provider_model_mappings: Option<serde_json::Value>,
|
||||
pub(crate) global_model_id: String,
|
||||
#[serde(default)]
|
||||
pub(crate) price_per_request: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub(crate) tiered_pricing: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) supports_vision: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) supports_function_calling: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) supports_streaming: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) supports_extended_thinking: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) is_active: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminProviderModelUpdateRequest {
|
||||
#[serde(default)]
|
||||
pub(crate) provider_model_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) provider_model_mappings: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) global_model_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) price_per_request: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub(crate) tiered_pricing: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) supports_vision: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) supports_function_calling: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) supports_streaming: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) supports_extended_thinking: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) is_active: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) is_available: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub(crate) config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminBatchAssignGlobalModelsRequest {
|
||||
pub(crate) global_model_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AdminImportProviderModelsRequest {
|
||||
pub(crate) model_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) tiered_pricing: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub(crate) price_per_request: Option<f64>,
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use crate::{AppState, LocalProviderDeleteTaskState};
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub(crate) const ADMIN_PROVIDER_MAPPING_PREVIEW_MAX_KEYS: usize = 200;
|
||||
pub(crate) const ADMIN_PROVIDER_MAPPING_PREVIEW_MAX_MODELS: usize = 500;
|
||||
pub(crate) const ADMIN_PROVIDER_MAPPING_PREVIEW_FETCH_LIMIT: usize = 10_000;
|
||||
pub(crate) const ADMIN_PROVIDER_POOL_SCAN_BATCH: u64 = 200;
|
||||
pub(crate) const ADMIN_PROVIDER_OAUTH_DATA_UNAVAILABLE_DETAIL: &str =
|
||||
"Admin provider OAuth data unavailable";
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct AdminProviderPoolConfig {
|
||||
pub(crate) lru_enabled: bool,
|
||||
pub(crate) cost_window_seconds: u64,
|
||||
pub(crate) cost_limit_per_key_tokens: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct AdminProviderPoolRuntimeState {
|
||||
pub(crate) total_sticky_sessions: usize,
|
||||
pub(crate) sticky_sessions_by_key: BTreeMap<String, usize>,
|
||||
pub(crate) cooldown_reason_by_key: BTreeMap<String, String>,
|
||||
pub(crate) cooldown_ttl_by_key: BTreeMap<String, u64>,
|
||||
pub(crate) cost_window_usage_by_key: BTreeMap<String, u64>,
|
||||
pub(crate) lru_score_by_key: BTreeMap<String, f64>,
|
||||
}
|
||||
|
||||
pub(crate) fn build_admin_provider_delete_task_payload(
|
||||
task: &LocalProviderDeleteTaskState,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"task_id": task.task_id,
|
||||
"provider_id": task.provider_id,
|
||||
"status": task.status,
|
||||
"stage": task.stage,
|
||||
"total_keys": task.total_keys,
|
||||
"deleted_keys": task.deleted_keys,
|
||||
"total_endpoints": task.total_endpoints,
|
||||
"deleted_endpoints": task.deleted_endpoints,
|
||||
"message": task.message,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn put_admin_provider_delete_task(
|
||||
state: &AppState,
|
||||
task: &LocalProviderDeleteTaskState,
|
||||
) {
|
||||
state.put_provider_delete_task(task.clone());
|
||||
}
|
||||
Reference in New Issue
Block a user