3727 Commits

Author SHA1 Message Date
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
a879f254c8
Merge pull request #3012 from visa2/fix/responses-anthropic-tool-pairing
fix(apicompat): repair tool_use/tool_result pairing on the Responses→Anthropic path
2026-06-05 13:53:31 +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
Wesley Liddick
b34480df68
Merge pull request #3001 from Pluviobyte: bump Go patch version to 1.26.4
chore: bump Go patch version to 1.26.4
2026-06-05 13:46:37 +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
Wesley Liddick
f1aa589646
Merge pull request #2993 from ghostg00/fix/openai-5h-used-percent-direct
fix(usage): revert OpenAI 5h used_percent inversion (#2918 regression)
2026-06-04 16:02:52 +08:00
visa2
60867022b6 fix(apicompat): repair tool_use/tool_result pairing on the Responses→Anthropic path
When an OpenAI Chat Completions client targets an Anthropic-platform group,
ForwardAsChatCompletions converts the request CC → Responses → Anthropic
(ChatCompletionsToResponses → ResponsesToAnthropicRequest) before forwarding it
upstream. The Responses→Anthropic converter emits each function_call as its own
assistant message and each function_call_output as its own user message and
relies solely on mergeConsecutiveMessages to alternate roles. That is not enough
to satisfy Anthropic's tool-pairing invariants, so a trimmed or partial tool
history produces an upstream 400, e.g.:

    tool_use_id found in tool_result blocks: call_00_...
    Each tool_result block must have a corresponding tool_use block in the
    previous message.

The failures this leaves unrepaired:

  - orphan tool_result — a client that does sliding-window context management
    keeps a recent tool result but drops the assistant tool_calls message that
    announced it, so the tool_result has no matching tool_use;
  - unanswered/dangling tool_use — a parallel call whose sibling result never
    came back, or a call left dangling, which Anthropic also rejects.

Add normalizeAnthropicToolPairing, run between two merge passes: the first merge
groups parallel calls and their results; the pairing pass indexes every
tool_result by its tool_use id, keeps only answered tool_use blocks (dropping
unanswered/dangling calls, and the assistant message entirely when nothing else
remains) and re-emits the matching tool_result blocks as the immediately
following user message; standalone/orphan tool_results are dropped from their
original position; the second merge restores alternation. This mirrors
normalizeChatMessages on the Responses→Chat path.

Tested two ways: responses_to_anthropic_tool_pairing_test.go covers the repair
on direct Responses input (developer message between call and output, parallel
both-answered kept grouped, parallel one-unanswered dropped, orphan tool_result,
dangling call, single-call baseline); responses_to_anthropic_cc_chain_test.go
drives the real ChatCompletionsToResponses → ResponsesToAnthropicRequest chain
and reproduces the production 400 (orphan and unanswered-parallel) — both fail
without the repair and pass with it. The full apicompat suite stays green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:26:05 +08:00
Pluviobyte
5634cc83eb chore: bump Go patch version 2026-06-03 14:24:50 +08:00
ghostg00
a8ffb052ca Revert "fix(usage): 修正 OpenAI 5h 用量百分比语义"
This reverts commit b65dde634bf7a5338f22b46ad0f9d98a197526fc.
2026-06-03 11:46:31 +08:00
wucm667
134687782c build(go): bump toolchain to 1.26.4 2026-06-03 09:48:46 +08:00
wucm667
c40a74d983 fix(risk-control): exempt admins from moderation auto-ban 2026-06-03 09:33:37 +08:00
Wesley Liddick
aa69e3947d
Merge pull request #2926 from visa2/feat/codex-responses-bridge-redesign
refactor(apicompat): redesign the Codex Responses ↔ Chat Completions …
2026-06-01 15:07:44 +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
visa2
003b2786da test(apicompat): check type assertions in responses stream wire tests
errcheck (check-type-assertions) flagged unchecked single-value type
assertions; switch to the comma-ok form so golangci-lint passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 12:03:15 +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
910ff3cd58
Merge pull request #2892 from Arron196/feat/sync-upstream-models-on-create
feat: 添加账号时支持同步上游支持的模型
2026-06-01 09:58:33 +08:00
Wesley Liddick
ed8f5666c6
Merge pull request #2895 from wucm667/feat/usage-window-tooltip
feat(admin): 账号用量窗口 5h/7d 增加说明 tooltip
2026-06-01 09:57:03 +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
Wesley Liddick
c596b0a294
Merge pull request #2908 from DaydreamCoding/feat/admin-usage
feat(usage): /admin/usage 支持查看已删除用户 + 打开速度与刷新优化
2026-06-01 09:40:22 +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
visa2
f10bca8155 refactor(apicompat): redesign the Codex Responses ↔ Chat Completions bridge
Codex CLI speaks the OpenAI Responses protocol (streaming, store:false), while
many upstreams (e.g. DeepSeek in thinking mode) only expose Chat Completions.
The bridge that translates between the two had grown field by field and leaned
on Go's serialization defaults, which both the Responses client (Codex) and the
Chat upstream reject in ways the official OpenAI endpoints tolerate.

Problems this fixes (all observed running Codex CLI against a DeepSeek upstream):
  - Streaming reasoning was never shown in the Codex TUI (the answer appeared
    with no visible thinking): reasoning deltas were emitted before the reasoning
    item was opened, so the strict client discarded them.
  - A tool-using turn could wedge the session into a "no response" state: the
    function_call stream was never closed (no function_call_arguments.done /
    output_item.done), so Codex never saw the tool call complete.
  - Parallel tool calls were rejected upstream (400/502): each function_call
    became its own assistant message, producing consecutive assistant messages
    with mismatched tool replies.
  - A tool turn was rejected with "reasoning_content in the thinking mode must be
    passed back": the reasoning that produced the tool call was dropped instead
    of being returned on the assistant message.
  - Items with no Chat equivalent (web_search_call, ...) and Codex's
    command-approval notice landed between an assistant tool_calls message and
    its tool reply, triggering "An assistant message with 'tool_calls' must be
    followed by tool messages responding to each 'tool_call_id'".
  - Interrupt/reconnect left an unanswered or dangling tool_call in the history,
    triggering the same 400.

The shared root cause is reliance on serialization defaults — omitempty dropping
protocol-required zero values, and unrecognized item types falling through a
generic path — rather than deliberately reproducing the target protocol. The
bridge is reworked into two explicit layers.

Request direction (Responses input -> Chat messages): a parse -> build ->
normalize pipeline.
  - reasoning_content is carried back on the assistant message that produced a
    tool call (DeepSeek thinking mode requires it to continue the same thought)
  - consecutive function_call items (parallel tool calls) are merged into a
    single assistant message's tool_calls array
  - item types with no Chat equivalent are skipped instead of leaking through a
    generic path
  - normalizeChatMessages is the single invariant gate: it guarantees every
    assistant tool_calls message is immediately followed by one tool reply per
    tool_call_id — reordering any intervening message (such as a command-approval
    notice) to after the replies, dropping unanswered tool_calls and orphan tool
    replies, and preserving bare passthrough tool messages.

Response direction (Chat SSE -> Responses SSE): ResponsesStreamEvent.MarshalJSON
constructs each streamed event explicitly so protocol-required fields are always
present (output_index/content_index/summary_index at 0, message content:[],
reasoning summary:[], function_call call_id/name/arguments, output_text part
text/annotations/logprobs). This is a single source of truth that removes any
post-hoc JSON patching. Reasoning is emitted as its own output item, opened
before its deltas, and tool calls are fully closed
(function_call_arguments.done + output_item.done with complete arguments).

Tests cover request-direction message invariants against golden Codex request
shapes (parallel calls, unknown items, intervening messages, partial/dangling
calls), per-event wire completeness, and streaming lifecycle ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 16:14:58 +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
bf24b61139 perf(usage): 优化 /admin/usage 打开速度与刷新响应
根因:页面 mount 并发 6 个请求,其中 5 个在原始 usage_logs 上 live 聚合,
且有 1 个重复 getModelStats。优化(不引入预聚合):

前端
- UsageView 经 :model-options 下传 model 列表,移除 UsageFilters 重复的
  getModelStats(mount 请求 6→5,少一次 usage_logs 全表 GROUP BY model)
- 刷新/换筛选保留旧模型数据(invalidateModelStatsCache 只失效标记不清空数据),
  图表不再闪空,刷新期间页面保持可交互

后端
- GetStatsWithFilters 4 条聚合 errgroup 并行(仅 *sql.DB 连接池路径,ent.Tx
  顺序回退以保事务内不并发),endpoint 明细 best-effort;抑制取消级联噪声日志
- /admin/usage/stats 复用 dashboard 的 newSnapshotCache 30s 处理器层缓存,按
  filters+窗口为 key;前端手动刷新带 nocache=1 强制回源(刷新=最新)

注:自合并提交 4c8396c 迁移而来,仅取"列表打开速度与刷新响应"部分;原提交的
"审计查看弹窗大 body 渲染"改动(AuditLogModal / audit-log-format / 审计 i18n)
依赖尚未迁移的审计功能(d8389ade),本次已排除。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 18:56:08 +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