1957 Commits

Author SHA1 Message Date
DaydreamCoding
cfb195c7b2 feat(usage): 记录并展示失败请求(用户端+管理端)
- 记录失败请求并在用户端/管理端展示;分类下拉改用统一 Select 组件
- 模型过滤改后端 ILIKE 模糊匹配;新增「Key 名称」列(含已删除标记)与按 Key 过滤;时间列移至末列
- 用户可见「已删除 key 失败请求」:OpsErrorLogFilter 加 MatchDeletedKeyOwner,用户侧归属
  放宽为 (user_id OR deleted_key_owner_user_id),让 key 原所有者能看到删除 key 后继续请求
  导致的认证失败记录(他人仍 NotFound,不泄露存在性)
- 迁移 148:ops_error_logs 用户+时间索引

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:00:57 +08:00
DaydreamCoding
ddf063352a feat(ops): 错误日志 key 归因与早退字段补全
让 /admin/ops 错误详情正确归因 API key 并补全早退场景字段,合并三项改动:

- 鉴权早退补全用户/分组/平台字段:引入 ops fallback key(ContextKeyOpsFallbackAPIKey),
  apiKey 一加载成功即写入,覆盖分组停用/删除、Key 停用/过期/额度、用户停用、IP 限制等早退
  路径;ops 错误日志改用 getOpsAPIKey(正式 key 优先、回退键兜底),不改「已鉴权」语义。
- 已删除 key 归因(迁移 145):删除 key 时同一事务写 deleted_api_key_audits 映射,认证失败
  时用明文反查命中原所有者,错误详情展示「已删除 Key 所有者」「尝试的 Key 前缀」。
- 有效 key 报错快照前缀(迁移 147):对绑定有效 key 的错误,落库时快照明文前 8 位到
  api_key_prefix(与 attempted_key_prefix 互斥),key 之后被删仍保留报错当时真实前缀。

均仅对上线后新产生的错误/删除生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:00:57 +08:00
Wesley Liddick
f332e0a83c
Merge pull request #2872 from wucm667/fix/scheduler-sticky-health-escape
fix(scheduler): session_hash sticky 引入健康度逃逸,慢账号不再独占用户会话
2026-06-05 13:54:37 +08:00
Wesley Liddick
83cce858a5
Merge pull request #3039 from StarryKira/fix/issue-2994-codex-5h-used-percent-selfheal
fix(openai): self-heal stale Codex used% snapshots + lock semantics (#2994)
2026-06-05 13:54:18 +08:00
Wesley Liddick
bebeaf57c5
Merge pull request #2854 from ttt132/fix/responses-stream-completed-output
fix: normalize responses streaming terminal output
2026-06-05 13:54:05 +08:00
Wesley Liddick
fbd25acae0
Merge pull request #3035 from ghostg00/fix/admin-group-clear-description
fix(group): 管理员清空分组描述时正确持久化
2026-06-05 13:53:20 +08:00
Wesley Liddick
05f0326e99
Merge pull request #2988 from wucm667: content audit auto-ban exempts admin accounts
fix(risk-control): 内容审计 auto-ban 豁免管理员账号,避免封禁管理员/超管
2026-06-05 13:47:10 +08:00
haruka
86d9b6bff9 fix(openai): self-heal stale Codex used% snapshots + lock semantics (#2994)
The OpenAI/Codex 5h "used %" inversion that caused fresh accounts to show
~96-99% used (PR #2918, commit b65dde63) was already reverted in #2993, so the
stored value is now the correct "used %" again. This commit hardens that fix:

1. Regression test locking in direct "used %" semantics. The semantics have
   flip-flopped twice (#2918 -> #2993) with no value-level guard — a fresh
   account (secondary_used_percent=1, 5h window) must store
   codex_5h_used_percent=1, not 99.

2. Stale-bounded self-heal in resolveOpenAIQuotaUtilization (the single
   auto-pause chokepoint). An account poisoned with an inflated used% gets
   excluded from scheduling, and a paused account never receives traffic to
   refresh its snapshot — so it stayed stuck until the window's reset_at passed
   (up to 5h/7d). When codex_usage_updated_at is older than 2h, the account is
   no longer auto-paused on that snapshot; it gets one request whose response
   headers refresh the snapshot and self-heal it. A missing timestamp is treated
   as fresh (stays paused), and an actively-served exhausted account refreshes
   the timestamp every response so it never crosses the bound — it cannot escape
   auto-pause.

No change to Normalize(); no 100-x reintroduced; no new dependency wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:07:36 +08:00
ghostg00
bc7ce18574 fix(group): 管理员清空分组描述时正确持久化
UpdateGroup 之前用 `if input.Description != ""` 判空,
把"未提供"和"显式置空"混为一谈,导致管理员在分组编辑表单
里清空备注后保存无效。

将 UpdateGroupRequest / UpdateGroupInput 的 Description 改为
*string:nil 表示未提供(保持原值),"" 表示显式清空。
2026-06-04 19:48:03 +08:00
ghostg00
a8ffb052ca Revert "fix(usage): 修正 OpenAI 5h 用量百分比语义"
This reverts commit b65dde634bf7a5338f22b46ad0f9d98a197526fc.
2026-06-03 11:46:31 +08:00
wucm667
c40a74d983 fix(risk-control): exempt admins from moderation auto-ban 2026-06-03 09:33:37 +08:00
Wesley Liddick
5f63fe1945
Merge pull request #2927 from moonagic/main
fix antigravity gemini rate limit and account scheduling
2026-06-01 14:50:36 +08:00
Wesley Liddick
3ba4a51355
Merge pull request #2937 from Kanshan03/fix/ws-codex-image-bridge
fix(openai): inject WS Codex image bridge tool
2026-06-01 14:18:58 +08:00
Wesley Liddick
a0057f44f2
Merge pull request #2932 from fatelei/issue-2917
fix: change balance to pointer type
2026-06-01 11:37:33 +08:00
Kanshan03
2a075a85bd fix(openai): 注入 WS Codex 生图桥接工具 2026-06-01 11:25:28 +08:00
Wesley Liddick
bc5813f023
Merge pull request #2936 from xlx0852/codex/ws-http-bridge-recovery
fix(openai): bridge oversized websocket requests
2026-06-01 11:11:11 +08:00
Wesley Liddick
80fa2f28c5
Merge pull request #2920 from wucm667/fix/openai-failover-stale-cached-body
test(openai): 覆盖 responses failover 请求体重映射,防止 alias 模型泄漏
2026-06-01 10:47:48 +08:00
xlx0852
08e19bb15c fix(openai): bridge oversized websocket requests 2026-06-01 10:42:55 +08:00
Wesley Liddick
7ad2dc74dd
Merge pull request #2922 from gsh20040816/codex/fix-openai-ws-usage-dedup
fix: avoid OpenAI WS usage dedup conflicts
2026-06-01 10:23:01 +08:00
wucm667
c8cd91e3ce test(openai): 覆盖 failover 请求体重映射 2026-06-01 10:19:24 +08:00
Wesley Liddick
418d09be56
Merge pull request #2881 from xiaoyiluck666/fix/openai-oauth-refresh-enrichment
修复 OpenAI OAuth 刷新未补全账号信息
2026-06-01 10:04:16 +08:00
Wesley Liddick
52ef848432
Merge pull request #2919 from wucm667/fix/claude-code-only-allow-count-tokens
fix(gateway): claude_code_only 不再误拦截官方 Claude Code 的 count_tokens 与非流式 messages 请求
2026-06-01 09:44:40 +08:00
Wesley Liddick
29c8b8aa4b
Merge pull request #2918 from wucm667/fix/openai-5h-usage-window-inverted
fix(usage): 修正 OpenAI 5h 用量窗口 used%/remaining% 颠倒
2026-06-01 09:42:20 +08:00
Wesley Liddick
0f70f84182
Merge pull request #2925 from is7Qin/feat/openai-oom
refactor(gateway): 降低大请求体内存保留
2026-06-01 09:40:31 +08:00
fatelei
0560340bd4
fix: change balance to pointer type 2026-06-01 08:41:35 +08:00
moonagic
a01686c637
fix antigravity gemini rate limit and account scheduling
Squash of 4 commits:
- Fix Gemini rate limit scheduling
- fix antigravity gemini rate limit scheduling
- fix antigravity gemini limited account scheduling
- fix antigravity test stubs for default lint
2026-05-31 22:00:03 +08:00
name
1afae0a4cd fix(gateway): address OpenAI OOM review lint 2026-05-31 16:38:42 +08:00
name
8ac2e23fb3 refactor(gateway): defer OpenAI request map decoding
Keep the Responses hot path on raw request bytes until complex mutation or retry branches need a decoded map, reducing large-body retention under high concurrency.
2026-05-31 16:00:35 +08:00
gsh
1e2193c3d2 fix: avoid websocket usage dedup conflicts 2026-05-31 15:09:06 +08:00
wucm667
bf3787de1f fix(gateway): allow Claude Code count_tokens 2026-05-31 08:43:20 +08:00
wucm667
b65dde634b fix(usage): 修正 OpenAI 5h 用量百分比语义 2026-05-31 08:39:37 +08:00
name
6a5f6b96b6 refactor(gateway): introduce OpenAI request view
Cache hot-path request scalars before full body decoding so later branches can avoid repeated map work while preserving current decode behavior.
2026-05-31 01:01:58 +08:00
name
34de99ee0e refactor(gateway): cap upstream error body reads 2026-05-30 20:01:40 +08:00
name
09af6ebd40 refactor(gateway): avoid extra OpenAI WS body map copy 2026-05-30 20:01:40 +08:00
name
5a3e193b53 refactor(gateway): shorten OpenAI request body retention 2026-05-30 20:01:39 +08:00
name
2caee9d884 refactor(gateway): snapshot usage worker inputs 2026-05-30 20:01:39 +08:00
name
619e5ae619 refactor(gateway): isolate anthropic body rewrites
Keep Anthropic request body rewrites attempt-local and synchronize the accepted wire body only after upstream success so failover and retry paths do not reuse stale parsed state.
2026-05-30 20:01:39 +08:00
name
b1c4be4ac8 refactor(gateway): remove parsed request object graphs
Keep large gateway payloads as raw body ranges and bind OpenAI parsed-body caches to the body bytes so failover and mapping do not reuse stale mutable state.
2026-05-30 20:01:38 +08:00
name
d8cbf9ab5c refactor(gateway): introduce request body refs 2026-05-30 20:01:38 +08:00
DaydreamCoding
b60d8bb4cc feat(usage): 在 /admin/usage 支持查看已删除用户的历史使用情况
用户软删除后使用记录仍在,但身份(邮箱)被 ent 软删除拦截器隐藏。本次在
三条管理员只读路径定点穿透软删除过滤,并把删除状态传播到前端标记,零新表/
迁移/回填:

- 后端穿透:富化 usage 日志(loadUsers)、用户搜索(ListWithFilters +
  UserListFilters.IncludeDeleted)、点击详情(GetByIDIncludeDeleted /
  GetUserIncludeDeleted + getById ?include_deleted 分支)
- 状态传播:service.User / dto.User 新增 DeletedAt;SearchUsers 标记 deleted
- 前端:表格与余额弹窗展示"已删除"徽标、筛选下拉标注并排序、点击走
  include_deleted;新增 i18n admin.usage.userDeletedBadge
- 安全:普通用户 usage 仅查本人(无 PII 泄漏);主用户列表与默认 getById
  行为不变(已删用户仍 404);仅 admin 搜索设 IncludeDeleted

后端 build / 三态 vet / unit 全量 / 仓储集成全绿;前端 typecheck / vitest /
改动文件 eslint 全清。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 17:52:18 +08:00
DaydreamCoding
5fd9a35093 test(pricing): 修复 codex-auto-review 定价测试断言
68901cbff 批量同步定价数据后,codex-auto-review 的 input_cost_per_token 从 2.5e-6 更新为 5e-6,output 从 1.5e-5 更新为 3e-5,cache_read 从 2.5e-7 更新为 5e-7。测试断言需要同步更新以匹配当前定价数据。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:48:49 +08:00
DaydreamCoding
f7f5e33830 feat(quota): user×platform 配额 DB 写聚合 flusher
Redis 同步权威 + DB 镜像,不在进程内维护 delta:
- 写入点 HasUserPlatformQuotaLimit 守卫:无 limit 跳过 Redis 写与持久化
- 累加 usage 的 Lua 在 flusher_enabled 时 SADD 脏集 billing:upq:dirty
- UserPlatformQuotaUsageFlusher 定时 SPOP 脏集 → 批量 HGETALL 读当前窗口 usage 快照
  → BatchSnapshotUsage 绝对值 UPSERT 覆盖 DB(去 SELECT FOR UPDATE 行锁)
  → 失败 SADD 回 / FK(23503)整批丢弃
- flusher 单批 clamp 到 ≤6000,保证一次 flush 只生成一条 UPSERT(单事务原子)
- flusher_enabled 默认 false(降级=旧异步直写 DB)

效果:DB 写连接从 O(QPS) 收敛到 O(副本)。
循环依赖:service 层独立 Snapshot/FK 类型,repository adapter 转换 + %w 映射 FK error。
admin reset/upsert 后失效 cache(脏残留被 flusher 当 MISS 跳过)。

健壮性与可观测性:
- flusher_enabled=false 时 Start 不注册定时器;flush_interval_ms 非法回退 2s
- Readd 回填失败单独计 dirty_lost(不再误记 dirty_readd)并 ALERT;脏集 Readd 补兜底 TTL
- 单 tick 达 max batches 上限仍有积压时记 log
- admin 失效 cache 失败升级为 ALERT(提示 enforcement 可能延迟至 sentinel TTL)
- BatchGet 单条命令失败 / usage 字段损坏均记 log,避免静默以 0 覆写 DB

三态 go vet + 单测/集成测全绿。
已知取舍(默认 flusher_enabled=false 不触发):
- FK 整批丢弃牵连同批正常 key(活跃 key 靠下次 SADD+绝对值快照自愈;Redis 仍权威)
- admin reset/upsert 直写 DB 与 flusher 异步刷存在覆盖竞态:flusher 持旧快照在途时可能覆盖
  admin 刚写值(limit 列不受影响;usage 有 preflight windowExpired 兜底;低频)。彻底消除需 version OCC。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 17:19:15 +08:00
DaydreamCoding
06fca66273 feat(quota): sentinel 回填消除无配额行用户 preflight 每请求回源 DB
无 user×platform 配额行的用户,preflight 每次 cache MISS 后回源 DB 查得"无行"
却不缓存该结论,导致每请求一次 DB 往返。本 PR 回填 sentinel 占位 entry,使后续
请求命中 Redis 后稳定判"无 limit",TTL 内不再查 DB。

- config: 加 UserPlatformQuotaSentinelTTLSeconds(默认 3600s,短于普通 quota
  cache 的 86400s 以控 Redis 内存)
- metrics: 加 userPlatformQuotaSentinelSetCacheErrorTotal,并入
  GatewayUserPlatformQuotaIncrStats 暴露
- billing_cache: checkUserPlatformQuotaEligibility 在 cache MISS + DB 无行且
  cacheErr==nil 时回填 sentinel(三 limit nil、三 window_start non-nil、SchemaV1);
  TTL<=0 fallback 1h 防 EXPIRE 立即删 key 击穿;SET 失败 fail-open + 计 metric
- billing_cache: HIT 路径对 sentinel(三 limit nil)跳过 windowExpired refresh,
  避免短 sentinel TTL 被误升级为 86400s

有配额 limit 的用户 enforcement 行为不变(rec!=nil 不回填、isSentinel=false 不跳过 refresh)。

测试:扩展 fakeFullCache 夹具(setCalls/lastSetTTL/getErr/setErr);新增回填正确性 /
Redis-GET-故障不回填 / SET-失败 fail-open / sentinel 跨窗口不 refresh 四个单测。
go build、quota+billing unit、三态 go vet 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:19:15 +08:00
Wesley Liddick
0a8c77f8c3
Merge pull request #2884 from Pluviobyte/fix/gemini-messages-tool-text-block-ordering
fix(gemini): close tool_use block before text in messages streaming
2026-05-29 16:22:34 +08:00
Wesley Liddick
52292741cb
Merge pull request #2849 from Pluviobyte/fix/count-tokens-payload-filter
fix(gateway): filter count_tokens generation fields
2026-05-29 16:11:01 +08:00
Wesley Liddick
f68d351158
Merge pull request #2873 from wucm667/feat/account-quota-threshold-auto-pause
feat(account): 支持按 5h/7d 用量阈值自动暂停账号调度
2026-05-29 15:40:33 +08:00
Pluviobyte
0a521f09fb
fix(gemini): close tool_use block before text in messages streaming
When the Gemini->Anthropic streaming bridge for the /v1/messages endpoint
receives a functionCall part followed by a text part, the text branch in
handleStreamingResponse opened a new text content block without closing the
already-open tool_use block. The tool block's content_block_stop was only
emitted at end-of-stream, after the text block's content_block_start, so the
Anthropic SSE stream contained overlapping/unterminated content blocks. Clients
that assemble messages by block index (e.g. Claude Code) can drop the tool
input or mis-parse the response.

The functionCall branch already closes an open text block before opening a tool
block, and the chat-completions sibling closes the tool block in its text branch
via closeOpenTool(). This applies the same symmetric handling to the messages
variant: close any open tool_use block (resetting openToolIndex/openToolName/
seenToolJSON) before starting text.

Adds a regression test that replays a tool->text Gemini stream and asserts the
Anthropic content-block lifecycle never overlaps.
2026-05-29 06:47:23 +00:00
wucm667
c9caadb378 fix(account): address second-round review on quota auto-pause
- TopK initial filter now drops quota-paused accounts: fold the quota check
  into isAccountRequestCompatible so session-hash, TopK pool, and per-candidate
  rechecks all skip paused accounts. Previously the candidate pool was built
  without the quota check, so paused accounts could fill TopK and leave the
  scheduler returning "no available accounts" even with healthy ones available.
- Add per-account explicit disable flags auto_pause_5h_disabled /
  auto_pause_7d_disabled with toggles in EditAccountModal. Without these,
  leaving the account threshold blank silently falls back to the global default,
  so admins could not exempt a single account once a global default existed.
  Disable is per-window: an account can opt out of 5h auto-pause while still
  honoring 7d. Schedule snapshot whitelist includes the new fields, i18n EN/ZH
  updated, threshold-hint text revised to explain "blank = global default".
- Move quota auto-pause settings off the request hot path: replace the per-repo
  TTL+singleflight sync DB read with a per-SettingService stale-while-revalidate
  in-memory snapshot. Get is non-blocking (atomic.Pointer load + async refresh
  on staleness); writes via UpdateOpsAdvancedSettings push directly into the
  cache through an injected sink; wire warms the cache at startup. Adds Warm
  (sync) for tests/init and SetOpenAIQuotaAutoPauseSettings (sink target).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:32:45 +08:00
xiaoyiluck666
eba2046320 fix: enrich OpenAI OAuth token refresh 2026-05-29 14:01:39 +08:00
wucm667
8b7a822706 fix(account): address review on OpenAI quota auto-pause
- gate previous_response_id sticky path with quota auto-pause check at both
  the snapshot and DB-recheck stages (previously bypassed, #1)
- skip pausing when the usage window already reset to avoid a stale stuck-pause;
  carry codex_*_reset_at / reset_after_seconds / codex_usage_updated_at through
  the scheduler snapshot whitelist (#2)
- remove the incomplete limit mode; percentage threshold only (#3)
- add global default 5h/7d threshold inputs to the Ops settings dialog with
  validation and en/zh i18n (#4)
- downgrade account_auto_paused_by_quota log from Info to Debug; it fires
  per-candidate on the scheduling hot path (#5)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 12:20:30 +08:00