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
@@ -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());
}