943 Commits

Author SHA1 Message Date
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
0f70f84182
Merge pull request #2925 from is7Qin/feat/openai-oom
refactor(gateway): 降低大请求体内存保留
2026-06-01 09:40:31 +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
benjamin
57d9e15e04 feat: 添加账号时支持同步上游模型
新增 POST /admin/accounts/models/sync-upstream-preview 端点,
允许在创建账号流程中直接使用凭证同步上游支持的模型,
无需先保存账号获取 ID。

- 后端: 新增 SyncUpstreamModelsPreview handler
- 前端: 新增 syncUpstreamModelsPreview API 函数
- 前端: ModelWhitelistSelector 支持 syncCredentials prop
- 前端: CreateAccountModal 传递凭证给 ModelWhitelistSelector
2026-05-30 12:19:38 +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
Wesley Liddick
69e7c4db30
Merge pull request #2865 from wey-gu/feat/usage-request-context
fix(gateway): preserve usage request context
2026-05-29 16:21:59 +08:00
Wesley Liddick
21cd382f39
Merge pull request #2855 from gaoren002/fix/concurrency-error-classification
fix: classify concurrency acquire failures
2026-05-29 10:33:40 +08:00
Wesley Liddick
433f8dcd13
Merge pull request #2834 from DaydreamCoding/pr/openai-codex-cli-allow-claude-code
feat(openai): codex_cli_only 新增放行 Claude Code Codex 插件的机制
2026-05-29 10:30:33 +08:00
Wesley Liddick
6bc1983506
Merge pull request #2853 from wucm667/fix/system-update-already-up-to-date-response
fix(admin): system/update 在无更新时返回 200 + already_up_to_date 而非 500
2026-05-29 10:29:49 +08:00
shaw
6010c3cca9 test: 修复内容审计日志异步断言 2026-05-29 09:57:02 +08:00
shaw
ed1b57c597 fix(openai): gate routing by endpoint capability 2026-05-29 08:58:10 +08:00
Wey Gu
2bd3125d0f Preserve usage request context 2026-05-28 22:44:25 +08:00
Wesley Liddick
8c1a07852c
Merge pull request #2858 from wey-gu/feat/openai-embeddings-gateway
feat(gateway): Add OpenAI embeddings gateway
2026-05-28 22:15:19 +08:00
lyen1688
1b2d8873b0 feat: 完善前置拦截审核运行态 2026-05-28 20:05:24 +08:00
Wey Gu
ccace69d4e Add OpenAI embeddings gateway 2026-05-28 19:39:52 +08:00
gaoren002
56e96fdd8c fix: classify concurrency acquire failures 2026-05-28 10:03:41 +00:00
wucm667
b15375dfb4 fix(admin): handle already up-to-date updates 2026-05-28 17:27:01 +08:00
DaydreamCoding
56908d3c4c feat(openai): codex_cli_only 新增放行 Claude Code Codex 插件的机制
适用场景:在 Claude Code 中使用 https://github.com/openai/codex-plugin-cc
插件时,插件经官方 codex app-server 以 clientInfo.name="Claude Code" 完成
initialize 握手,请求头被设为 originator=Claude Code、User-Agent 含
"Claude Code/",不在官方客户端白名单内,原本会被 codex_cli_only 拦截 403。

在官方客户端白名单未命中时评估两层独立放行(OR 语义):

- 按账号:account.Extra.codex_cli_only_allowed_clients 引用命名预设
  (目前仅 claude_code),detector reason=allowed_client_matched
- 全局开关:/admin/settings 网关服务 OpenAI 区块新增
  openai_allow_claude_code_codex_plugin(默认 false),开启后对所有
  codex_cli_only 账号统一放行,detector reason=global_allowed_client_matched

签名仍要求 originator=Claude Code 精确等值 + UA 含 "Claude Code/"。
上游转发保持透传不变。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:55:34 +08:00
Wesley Liddick
cc077862b3
Merge pull request #2797 from wucm667/feat/account-list-created-at-column
feat(admin): 账号管理列表新增创建时间列
2026-05-27 22:10:21 +08:00
lyen1688
f597c1581b feat(group): 支持自定义 /v1/models 模型列表 2026-05-27 18:00:45 +08:00
Wesley Liddick
2387cf9934
Merge pull request #2799 from siyuan-123/fix/ws-rate-limit-failover
修复 OpenAI WS 限额时不自动切换账号
2026-05-27 15:14:28 +08:00
Wesley Liddick
4b9b63443f
Merge pull request #2790 from Arron196/from-arron-main
修复 Ops SLA 本地限制错误统计
2026-05-26 20:21:11 +08:00
siyuan
08061717b8 fix: enable account failover for OpenAI WS rate limits 2026-05-26 20:07:00 +08:00
wucm667
b6a38ddab7 feat(admin): 账号管理列表新增创建时间列 2026-05-26 19:59:12 +08:00
DaydreamCoding
11fe7de926 fix(account): 重新授权不再清空 Extra 配置
Claude / OpenAI 账号重新授权走通用 PUT /accounts/:id 时,后端
UpdateAccount 会全量覆盖 account.Extra(仅保留 5 个 quota 用量键),
导致 base_rpm / window_cost_limit / window_cost_sticky_reserve /
max_sessions / quota_* / privacy_mode 等持久化配置全部丢失。

新增专用接口 POST /accounts/:id/apply-oauth-credentials,沿用
现有 /refresh 路径模式:Credentials-only update + Extra JSONB
key 级合并(UpdateAccountExtra) + ClearError + InvalidateToken。

作用域:Claude OAuth / Claude Cookie auth / OpenAI OAuth 三个
调用点。Gemini / Antigravity 现有路径本就不传 extra,保持不变。

顺带修复:旧重新授权路径未调用 InvalidateToken,导致重新授权后
首请求可能仍用缓存中的旧 token 而立即 401。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:46:08 +08:00
benjamin
c3e7476992 fix(gateway): mark local platform gates business-limited
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:19:23 +08:00
benjamin
c782c2d9c3 fix(ops): classify local policy denials outside SLA
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:19:09 +08:00
Wesley Liddick
bebc082306
Merge pull request #2766 from DaydreamCoding/feat/user-platform-quota
feat(quota): 用户 × 平台 USD 配额
2026-05-26 14:13:18 +08:00
Wesley Liddick
83248478e2
Merge pull request #2777 from lyen1688/feat/content-moderation-risk-threshold
feat: 支持内容审计风险阈值配置
2026-05-26 14:12:54 +08:00
lyen1688
23f3d426c6 feat: 支持内容审计风险阈值配置 2026-05-26 13:58:02 +08:00
DaydreamCoding
6b39b344d8 feat(quota): 用户 × 平台 USD 配额
为用户在 anthropic/openai/gemini/antigravity 四个平台上提供日/周/月
三个窗口的 USD 配额管控。配额语义:未设置=不限制,0=禁用,>0=美元上限。

两层模型:
- 配置层:系统默认配额,以及 email/linuxdo/oidc/wechat/github/google/
  dingtalk 七个鉴权来源的默认配额,存于 settings,以嵌套 JSON 整体读写
  (系统 1 个 key + 每个来源 1 个 key),整体替换语义。
- 运行时层:user_platform_quota 表按用户记录实际配额,与配置层解耦。

后端:新增 ent schema 与 140_user_platform_quotas.sql 迁移、repository
与 service 端口、计费链路集成、管理端与用户端读写接口。
前端:管理端设置页配额编辑、用户配额管理 Modal、用户 Dashboard 展示、
中英文案。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:49:20 +08:00
shaw
53acde1efd style: fix lint errors in response.failed SSE writer
Errcheck flagged three unchecked strings.Builder.WriteString calls and
gofmt rejected over-aligned trailing comment in the route table.

Rewrite writeResponsesFailedSSE with json.Marshal on typed structs
instead of Builder+strconv.Quote. Same wire format, but:
- no unchecked Write returns to silence
- strict JSON escaping (strconv.Quote emits \a and \v which are not
  valid JSON; Marshal handles all runes correctly)
- omitempty model field via struct tag instead of conditional Builder
- consistent with the json.Marshal style used elsewhere in handler/

Collapse trailing comment whitespace in stream_error_event_test.go to
satisfy gofmt.

All 30+ subtests in the package still pass.
2026-05-25 18:16:46 +08:00
Jamie Wong
b34cc71bee fix(openai): also emit response.failed in ensureForwardErrorResponse after Writer.Written
Case B: when a slot wait flushes SSE ping comments first (Writer.Written
becomes true), the previous ensureForwardErrorResponse short-circuited
on `c.Writer.Written()` and returned false without notifying the client.
Subsequent upstream errors (http2 timeout, stream INTERNAL_ERROR, etc.)
produced silent EOF; Codex CLI reported "stream closed before
response.completed" just like the user-slot timeout case.

Remove the Written() early return; coerce streamStarted to true when
Writer has already been written to, and let handleStreamingAwareError
walk the existing logic — which now (thanks to the previous commits)
emits a protocol-compliant response.failed for /responses paths and the
legacy `event: error` for others.

Update tests that previously asserted "do not override written response":
the new contract is to *append* an SSE terminal frame so the client sees
a clean close instead of EOF. recoverResponsesPanic inherits this fix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 22:00:56 +08:00
Jamie Wong
cff2f291be fix(openai): also match bare /responses route in handleStreamingAwareError
The first revision compared GetInboundEndpoint(c) against EndpointResponses
("/v1/responses"). NormalizeInboundEndpoint only recognizes paths that
contain the literal "/v1/responses" substring, but the project actually
registers six /responses routes — three of which (top-level
r.POST("/responses", ...) and codexDirect's "/backend-api/codex/responses")
have FullPath values without the "/v1" prefix and therefore fall through
to the default branch.

Codex CLI users targeting the bare /responses route at the production
deployment (observed 2026-05-24 ~11:05 UTC, user 16) never reached the
new writeResponsesFailedSSE path: the endpoint check was false, the
legacy `event: error` frame fired, and the strict SDK kept reporting
"stream closed before response.completed".

Replace the strict equality check with inboundIsResponses(c), which
uses suffix detection on FullPath (falling back to URL.Path when
FullPath is empty in test fixtures) and covers all six route variants:

  /v1/responses[/...]
  /responses[/...]
  /backend-api/codex/responses[/...]

Add test table covering all routes plus negative cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 19:32:08 +08:00
Jamie Wong
5e5c2062bf fix(openai): emit response.failed for /v1/responses after stream started
When /v1/responses streaming hits the user/account concurrency wait, the
wait loop sends SSE ping comments to keep the connection alive, which
flushes HTTP 200 + headers. If the wait then times out (or any other
post-flush error fires), handleStreamingAwareError previously emitted a
generic `event: error` frame. Codex CLI requires the stream to end with
a Responses terminal event (response.completed/failed/incomplete/cancelled),
so it reports "stream closed before response.completed" and the user-facing
rate-limit intent is lost.

This change detects inbound = /v1/responses in both handleStreamingAwareError
implementations and emits a protocol-compliant response.failed event whose
field set mirrors apicompat.makeResponsesCompletedEvent
(id/object/model/status/output/error). The synthetic id reuses
ctxkey.RequestID so client errors can be grepped against server logs.
sequence_number is intentionally omitted to preserve monotonicity on streams
that already emitted real events.

Other inbound endpoints (/v1/chat/completions, /v1/messages) keep their
legacy formats untouched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:58:29 +08:00
shaw
1e406fed52 fix: optimize OpenAI account cooldown scheduling 2026-05-23 10:18:43 +08:00
Wesley Liddick
f59d9a5f8e
Merge pull request #2674 from wucm667/feat/moderation-per-model-toggle
feat(risk-control): 内容审计支持按模型生效
2026-05-22 20:10:38 +08:00
Wesley Liddick
9f91a8af17
Merge pull request #2662 from touwaeriol/feat/bedrock-cc-compat
feat(bedrock): add Claude Code compatibility for AWS Bedrock
2026-05-22 17:32:11 +08:00
wucm667
0d5c6f7cc7 feat(risk-control): 内容审计支持按模型生效 2026-05-21 21:18:43 +08:00
shaw
aae20ef437 fix(oidc): harden verified-email fast path 2026-05-21 15:19:29 +08:00
Wesley Liddick
35901a174b
Merge pull request #2655 from ye4241/feat/oidc-trust-verified-email-fast-path
feat(oidc): 上游邮箱已验证时跳过 choice 页直接登录注册
2026-05-21 14:47:08 +08:00
shaw
a613a587ba feat: add subscription expiry email toggle 2026-05-21 14:27:50 +08:00
ye4241
55554adc18 chore(oidc): 回应 Copilot review
- ProviderType 从 identity.ProviderType 取(不再硬编码)
- fast-path 日志改用 infraerrors.Reason(err) 避免泄露 PII / 降噪
2026-05-21 13:32:20 +08:00
ye4241
39fe7aa0eb feat(oidc): 上游邮箱已验证时跳过 choice 页直接登录注册
当前 OIDC 首次登录无条件创建 choose_account_action_required 的 pending
session,即使 force_email_on_third_party_signup 关闭,前端仍然会强制
弹出"创建账号 / 绑定已有账号"的二选一界面,并展示内部合成邮箱
(oidc-xxx@oidc-connect.invalid),用户体验差。

本次复用已存在的 LoginOrRegisterVerifiedEmailOAuth 路径(原本仅供
github/google 使用),在以下条件全部满足时跳过 choice 页,直接
信任上游身份完成注册/登录:

- force_email_on_third_party_signup = false
- 邀请码模式未启用
- 上游声明 email_verified = true 且 compat_email 非空
- 本地不存在同邮箱已有账号

失败时(如邮箱后缀不在白名单、注册关闭等)自动回退到现有 choice
流程,行为完全向后兼容。

测试覆盖:
- TestTryOIDCVerifiedEmailFastPathCreatesUserAndIdentity
- TestTryOIDCVerifiedEmailFastPathSkippedWhenInvitationCodeRequired
- TestTryOIDCVerifiedEmailFastPathSkippedWhenForceEmailEnabled
2026-05-21 13:32:20 +08:00