feat: provider api_formats 可空继承、OpenAI 图片 edit/variation 与用量配额多项补强

- 鉴权: provider_api_keys.api_formats 改为可空,OAuth 托管 key 自动继承 provider endpoints 激活格式,相关 handler/测试同步更新
- 图片 planner: OpenAI 图片路由新增 edit/variation 操作并完善参数校验、响应合并与流式处理
- 用量: user me usage 返回区分 client_requested_stream/upstream_is_stream,前端 usage 列表筛选与展示增强
- 统计: stats_daily_model 新增 cache_creation_ephemeral_5m/1h tokens 字段与回填链路
- 配额/observability: quota repository 新增内存与 SQL 扩展,admin observability usage 字段扩充
- 其它: OAuth 导入/轮询收敛、provider 汇总与 pool admin 读写链路小修、新增 system_config 缓存与 provider template handler

Closes #318

Co-authored-by: Entropy.Xu <53283266+Entropy-Xu@users.noreply.github.com>
This commit is contained in:
fawney19
2026-04-23 14:42:51 +08:00
parent f55f22d2e8
commit fa328e18a1
128 changed files with 5583 additions and 690 deletions

View File

@@ -1,6 +1,7 @@
use crate::ai_pipeline_api::{
build_local_gemini_files_stream_plan_and_reports_for_kind,
build_local_gemini_files_sync_plan_and_reports_for_kind,
build_local_image_stream_plan_and_reports_for_kind,
build_local_image_sync_plan_and_reports_for_kind,
build_local_openai_chat_stream_plan_and_reports_for_kind,
build_local_openai_chat_sync_plan_and_reports_for_kind,
@@ -464,6 +465,33 @@ pub(crate) async fn maybe_execute_stream_via_local_gemini_files_decision(
execute_stream_plan_and_reports(state, trace_id, decision, plan_kind, plan_and_reports).await
}
pub(crate) async fn maybe_execute_stream_via_local_image_decision(
state: &AppState,
parts: &http::request::Parts,
body_json: &serde_json::Value,
body_base64: Option<&str>,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
) -> Result<LocalExecutionRequestOutcome, GatewayError> {
let plan_and_reports: Vec<LocalStreamPlanAndReport> =
build_local_image_stream_plan_and_reports_for_kind(
state,
parts,
body_json,
body_base64,
trace_id,
decision,
plan_kind,
)
.await?;
if plan_and_reports.is_empty() {
return Ok(LocalExecutionRequestOutcome::NoPath);
}
execute_stream_plan_and_reports(state, trace_id, decision, plan_kind, plan_and_reports).await
}
pub(crate) async fn maybe_execute_sync_via_local_video_decision(
state: &AppState,
parts: &http::request::Parts,

View File

@@ -74,8 +74,15 @@ pub(crate) async fn maybe_execute_stream_via_plan_fallback(
_bypass_cache_key: String,
_fallback_reason: GatewayFallbackReason,
) -> Result<LocalExecutionRequestOutcome, GatewayError> {
let Some(payload) =
maybe_build_stream_plan_payload(state, parts, trace_id, decision, body_json).await?
let Some(payload) = maybe_build_stream_plan_payload(
state,
parts,
trace_id,
decision,
body_json,
body_base64.as_deref(),
)
.await?
else {
return Ok(LocalExecutionRequestOutcome::NoPath);
};

View File

@@ -14,6 +14,7 @@ use crate::{AppState, GatewayError, GatewayFallbackReason};
use super::{
build_direct_plan_bypass_cache_key, execute_stream_plan_and_reports,
maybe_execute_stream_via_local_decision, maybe_execute_stream_via_local_gemini_files_decision,
maybe_execute_stream_via_local_image_decision,
maybe_execute_stream_via_local_openai_cli_decision,
maybe_execute_stream_via_local_same_format_provider_decision,
maybe_execute_stream_via_local_standard_decision, maybe_execute_stream_via_plan_fallback,
@@ -36,7 +37,7 @@ pub(crate) async fn maybe_execute_via_stream_decision_path(
return Ok(LocalExecutionRequestOutcome::NoPath);
};
if !is_matching_stream_request(plan_kind, parts, &body_json) {
if !is_matching_stream_request(plan_kind, parts, &body_json, body_base64.as_deref()) {
return Ok(LocalExecutionRequestOutcome::NoPath);
}
@@ -59,6 +60,24 @@ pub(crate) async fn maybe_execute_via_stream_decision_path(
}
if supports_stream_scheduler_decision_kind(plan_kind) {
match maybe_execute_stream_via_local_image_decision(
state,
parts,
&body_json,
body_base64.as_deref(),
trace_id,
decision,
plan_kind,
)
.await?
{
LocalExecutionRequestOutcome::Responded(response) => {
return Ok(LocalExecutionRequestOutcome::Responded(response));
}
LocalExecutionRequestOutcome::Exhausted(outcome) => exhausted = Some(outcome),
LocalExecutionRequestOutcome::NoPath => {}
}
match maybe_execute_stream_via_local_decision(
state, parts, trace_id, decision, &body_json, plan_kind,
)

View File

@@ -45,7 +45,7 @@ pub(crate) async fn maybe_execute_via_sync_decision_path(
};
if let Some(stream_plan_kind) = resolve_execution_runtime_stream_plan_kind(parts, decision) {
if is_matching_stream_request(stream_plan_kind, parts, &body_json) {
if is_matching_stream_request(stream_plan_kind, parts, &body_json, body_base64.as_deref()) {
return Ok(LocalExecutionRequestOutcome::NoPath);
}
}