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

@@ -2,6 +2,7 @@ use super::{
any, build_router_with_state, build_state_with_execution_runtime_override, json, start_server,
to_bytes, Arc, Body, Json, Mutex, Request, Router, StatusCode, TRACE_ID_HEADER,
};
use crate::ai_pipeline::CODEX_OPENAI_IMAGE_INTERNAL_MODEL;
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
use aether_data::repository::auth::{
InMemoryAuthApiKeySnapshotRepository, StoredAuthApiKeySnapshot,
@@ -27,13 +28,13 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
model: String,
authorization: String,
x_client_request_id: String,
user_agent: String,
version: String,
originator: String,
prompt: String,
content_is_string: bool,
tool_type: String,
tool_size: String,
tool_quality: String,
tool_background: String,
tool_choice_type: String,
tool_has_n: bool,
request_stream: bool,
plan_stream: bool,
@@ -268,24 +269,6 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
user_agent: payload
.get("headers")
.and_then(|value| value.get("user-agent"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
version: payload
.get("headers")
.and_then(|value| value.get("version"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
originator: payload
.get("headers")
.and_then(|value| value.get("originator"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
prompt: payload
.get("body")
.and_then(|value| value.get("json_body"))
@@ -320,6 +303,32 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_quality: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tools"))
.and_then(|value| value.get(0))
.and_then(|value| value.get("quality"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_background: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tools"))
.and_then(|value| value.get(0))
.and_then(|value| value.get("background"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_choice_type: payload
.get("body")
.and_then(|value| value.get("json_body"))
.and_then(|value| value.get("tool_choice"))
.and_then(|value| value.get("type"))
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
tool_has_n: payload
.get("body")
.and_then(|value| value.get("json_body"))
@@ -349,9 +358,13 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
concat!(
"data: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_img_123\",\"created_at\":1776839946}}\n\n",
"data: {\"type\":\"response.output_item.done\",\"output_index\":0,\"item\":{\"id\":\"ig_123\",\"type\":\"image_generation_call\",\"status\":\"generating\",\"output_format\":\"png\",\"quality\":\"medium\",\"size\":\"1024x1024\",\"revised_prompt\":\"中国历史视觉海报\",\"result\":\"aGVsbG8=\"}}\n\n",
"data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_img_123\",\"object\":\"response\",\"model\":\"gpt-5.4\",\"status\":\"completed\",\"output\":[],\"usage\":{\"input_tokens\":2440,\"output_tokens\":184,\"total_tokens\":2624},\"tool_usage\":{\"image_gen\":{\"input_tokens\":171,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":171},\"output_tokens\":1372,\"output_tokens_details\":{\"image_tokens\":1372,\"text_tokens\":0},\"total_tokens\":1543}}}}\n\n",
"data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_img_123\",\"object\":\"response\",\"model\":\"__CODEX_IMAGE_MODEL__\",\"status\":\"completed\",\"output\":[],\"usage\":{\"input_tokens\":2440,\"output_tokens\":184,\"total_tokens\":2624},\"tool_usage\":{\"image_gen\":{\"input_tokens\":171,\"input_tokens_details\":{\"image_tokens\":0,\"text_tokens\":171},\"output_tokens\":1372,\"output_tokens_details\":{\"image_tokens\":1372,\"text_tokens\":0},\"total_tokens\":1543}}}}\n\n",
"data: [DONE]\n\n"
)
.replace(
"__CODEX_IMAGE_MODEL__",
CODEX_OPENAI_IMAGE_INTERNAL_MODEL,
)
)
},
"telemetry": {
@@ -457,7 +470,10 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
seen_execution_runtime_request.url,
"https://chatgpt.com/backend-api/codex/responses"
);
assert_eq!(seen_execution_runtime_request.model, "gpt-5.4");
assert_eq!(
seen_execution_runtime_request.model,
CODEX_OPENAI_IMAGE_INTERNAL_MODEL
);
assert_eq!(
seen_execution_runtime_request.authorization,
"Bearer refreshed-codex-image-access-token"
@@ -466,11 +482,6 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
seen_execution_runtime_request.x_client_request_id,
"trace-codex-image-local-123"
);
assert!(seen_execution_runtime_request
.user_agent
.starts_with("codex-tui/0.122.0"));
assert_eq!(seen_execution_runtime_request.version, "0.122.0");
assert_eq!(seen_execution_runtime_request.originator, "codex_cli_rs");
assert_eq!(
seen_execution_runtime_request.prompt,
"生成一张中国历史视觉海报"
@@ -478,6 +489,12 @@ async fn gateway_executes_codex_image_sync_via_local_decision_gate_after_oauth_r
assert!(seen_execution_runtime_request.content_is_string);
assert_eq!(seen_execution_runtime_request.tool_type, "image_generation");
assert_eq!(seen_execution_runtime_request.tool_size, "1024x1024");
assert_eq!(seen_execution_runtime_request.tool_quality, "high");
assert_eq!(seen_execution_runtime_request.tool_background, "auto");
assert_eq!(
seen_execution_runtime_request.tool_choice_type,
"image_generation"
);
assert!(!seen_execution_runtime_request.tool_has_n);
assert!(seen_execution_runtime_request.request_stream);
assert!(!seen_execution_runtime_request.plan_stream);