sync: merge upstream v0.1.127 fixes#2
Conversation
Account.Credentials 是 JSONB map,混合存放可编辑的非敏感配置(base_url、 model_mapping、project_id 等)与敏感秘钥(OAuth access/refresh/id token、 API key、AWS secret、Vertex private key 等)。当前所有 admin 账号接口直接 透传该 map,token 经由浏览器 DevTools、抓包、日志等途径泄漏。 - service 包新增 SensitiveCredentialKeys 清单与 MergePreservingSensitiveCreds 作为单一权威定义。 - dto 层 RedactCredentials 在响应里剥离敏感子键,输出 credentials_status (has_<key> 布尔标识)告知前端存在性,不暴露原值。 - AccountFromServiceShallow 接入脱敏,覆盖 list、get、create、update、 refresh、batch、bulk-update、OAuth 创建等 9 个 handler。 - service.UpdateAccount 改为合并语义:incoming 没传敏感键则保留 existing, 让前端"全对象 PUT"流程在脱敏后无感工作;显式提供新 token 仍会覆盖。 - 前端 EditAccountModal 修复脱敏后会崩的两处兜底:apikey 必填检查和 Vertex SA JSON 存在性校验改读 credentials_status.has_*。 - 导出端点 /admin/accounts/data 走独立的 DataAccount 结构,按设计保留 完整 credentials 作为管理员备份路径。 测试:RedactCredentials 单元测试、mapper 端到端 JSON 断言(确认序列化 后无 token 子串)、UpdateAccount 合并语义三种场景(保留 / 覆盖 / 空 map 跳过)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a hidden input with autocomplete="one-time-code" so password managers (1Password, Bitwarden, Chrome, Apple Keychain) can detect and auto-fill TOTP verification codes during 2FA login.
- announcement ListActive: 添加 Limit(200) 防止无界查询 - group listWithAccountCountSort: 改为先只查 ID + sort_order, 再批量加载账户统计,排序分页后仅加载当前页的完整实体, 避免全量加载所有字段后做内存排序。 Co-Authored-By: deepseek-v4-pro[1m] <deepseek-ai@claude-code-best.win>
…ation Image generation requests (forwardOpenAIImagesOAuth and forwardOpenAIImagesAPIKey) were calling detachStreamUpstreamContext with parsed.Stream, which for non-streaming requests (Stream=false) simply returned the original client context unchanged. When the client disconnected before the upstream completed (30-80s for image gen), the context cancellation propagated to the upstream HTTP request, causing a 502 error despite the upstream having already started processing. Switch to detachUpstreamContext (unconditional detach) so the upstream image generation request is always bound to a background context and completes regardless of client lifecycle. Fixes Wei-Shaw#2310
image 与 per_request 模式的层级按 tier_label (1K/2K/4K) 匹配, 不依赖 min/max token 范围, 多个层级共用 min=0/max=null 是预期形态。 原校验器一律按 token 上下文分段处理, 新增第二条图片层级时会被 "无上限区间只能是最后一个" 误拦, 导致 OpenAI gpt-image 等模型 无法保存按次定价。 validateIntervals 新增 mode 参数, image / per_request 模式跳过 区间重叠与 last-unlimited 检查, 保留单条 min/max 自洽与价格非负 校验。token 模式行为不变。
「可用渠道」展示链路有两个未覆盖场景导致用户看到"未配置定价": 1. admin 在 UI 里建了 ModelPricing 条目但没填任何价格 (常见于 per_request / image 模式只填了 tier_label 没填单价): 原 fallback 只检查 Pricing == nil, 这种空条目会跳过 LiteLLM 兜底。 2. LiteLLM 把图片模型标记 mode=image_generation, 但合成器固定按 token 模式合成, 把 OutputCostPerImage / 图片 token 价丢到错误字段。 改动 (仅 backend/internal/service/channel_available.go): - 新增 pricingNeedsFallback: 价格字段全空 (含 intervals 全空) 视为 未配置, 触发 LiteLLM 兜底。 - synthesizePricingFromLiteLLM 加 existing 参数: 优先尊重渠道已选 BillingMode (per_request / image 也按此模式合成), 没选才看 LiteLLM mode, 仍未命中默认 token。 - image / per_request 分支用 OutputCostPerImage 填 PerRequestPrice, OutputCostPerImageToken 填 ImageOutputPrice, 让 gpt-image / dall-e 系列展示出参考价。 仅影响展示链路, 真实计费走 BillingService / ModelPricingResolver 完全不受影响。新增 8 个单元测试覆盖 pricingNeedsFallback 各分支、 合成器三种模式选择、空条目兜底与既有价格保护。
前端在上一个 commit 已对 image / per_request 模式跳过 unbounded-last
和重叠检查, 但保存时后端仍按 token 语义校验, 导致添加第二个图片层级
时报错:
invalid pricing intervals for platform 'openai' models
[gpt-image-2 gpt-image-1.5 gpt-image-1]:
interval #1: unbounded interval (max_tokens=null) must be the last one
ValidateIntervals 加 mode 参数, 与前端校验逻辑对齐:
- token 模式行为不变 (区间重叠 / last-unlimited 仍校验)
- per_request / image 模式跳过区间重叠和 last-unlimited 检查,
保留单条 min/max 自洽校验与价格非负校验。
调用方 validatePricingIntervals 把 pricing.BillingMode 透给校验器。
既有单测全部加上 BillingModeToken 显式参数, 新增 3 个 image 模式用例
(允许多条 unbounded / 仍拒绝负价 / 仍拒绝 max <= min)。
…call ids
`fixCallIDPrefix` builds malformed ids when the input has the standard
OpenAI `call_<nanoid>` prefix:
input: call_YYen1qxDejd2myJwcTCf7Nyp
output: fcYYen1qxDejd2myJwcTCf7Nyp ← no underscore between 'fc' and the nanoid
ChatGPT's codex backend then rejects the replayed item with:
400 Invalid 'input[N].id': 'fcYYen1qxDejd2myJwcTCf7Nyp'.
Expected an ID that contains letters, numbers, underscores, or
dashes, but this value contained additional characters.
Sub2api wraps that into 502 to the client. Clients using the OpenAI SDK
on the OAuth/codex path see every multi-hop turn (after the first tool
call) fail because the item_reference rewritten this way gets sent on
every subsequent hop.
The other two branches of the same function correctly emit `fc_`
(line 1029: pass-through when already `fc*`; line 1035 fallback:
`fc_" + id`). Only the `call_` → `fc_` rewrite was missing the
underscore — looks like a copy-paste slip during the original commit.
Fix: change `"fc"` to `"fc_"` on the call_ branch. One character.
Repro:
client (OpenAI SDK) sends a function_call_output whose call_id is
`call_<nanoid>` (default OpenAI format). The sub2api request body
also contains an item_reference whose id mirrors the call_id (also
`call_<nanoid>`). On the codex OAuth path, this rewrite fires for
the item_reference's id, producing the malformed value.
Affects: `platform=openai type=oauth` accounts whose clients use the
official OpenAI SDK / Responses API conventions (id prefix `call_`).
API-key accounts and bridge-mode requests are untouched.
Vue's scoped-CSS compiler was dropping the `:global(.dark) .settings-tabs-shell` rules in the production build, so the tab strip kept its light-mode white background and the inactive tab labels (text-gray-300) showed at ~1.6:1 contrast — effectively unreadable. Hoist the three dark-mode overrides into an unscoped `<style>` block so they survive the scoped-CSS transform.
…-platform feat(usage): 用户用量按平台拆分 + UsersView 列设置可配置 + 用量列排序
feat(dingtalk): 钉钉 OAuth 登录接入 + internal_only 用户属性同步
fix: avoid ops deep link initialization error
- AccountsView: display email under account name by checking extra.email and credentials.email in addition to extra.email_address; OpenAI OAuth stores email under the 'email' key while Anthropic uses 'email_address' - AccountUsageCell: add per-account refresh button to the OpenAI OAuth usage section, identical to the existing Anthropic OAuth pattern; reuses loadActiveUsage, activeQueryLoading, and the activeQuery i18n key
…l refresh The refresh button had no effect because two independent gates blocked the Codex probe: 1. isOpenAICodexSnapshotStale returned false for non-WS-v2 accounts even when data was stale — this is correct for background auto-refresh (Codex quota is only auto-tracked for Codex CLI / WS v2 accounts), so this behavior is preserved. 2. shouldProbeOpenAICodexSnapshot had a 10-min in-memory rate limit with no bypass for explicit user requests. Fix: add a force parameter threaded from handler → GetUsage → getOpenAIUsage → shouldProbeOpenAICodexSnapshot. When force=true (manual refresh button), both gates are bypassed and the probe always runs. Background auto-loads continue to respect the original WS v2 logic and 10-min rate limit.
…ter-init fix(setup): 初始化完成后阻止访问 setup 页面
…denied fix(ops): 用户 IP 限制导致的 ACCESS_DENIED 不计入 SLA 错误
…redentials fix(security): 屏蔽 admin 账号接口返回的敏感凭证字段
…al-failover fix(openai): 识别上游静默拒绝(空流+finish_reason=stop)并触发 failover
feat(redeem): 兑换码支持设置使用有效期
…t-email-and-usage-refresh fix(accounts): show email and fix usage refresh for OpenAI OAuth accounts
…ty-thinking-sse fix(apicompat): preserve empty streaming thinking blocks
…ding-reconcile Fix wxpay pending order reconciliation
…ption-model-scopes fix: hide model scopes for non-antigravity plans
…-refresh-disable fix(openai): 修复 access_token 已过期且 refresh_token 缺失时 持续命中,呈现502错误的bug
…g-content fix: preserve DeepSeek reasoning_content in chat compatibility paths
|
Thank you for your contribution! Before we can merge this PR, we need you all to sign our Contributor License Agreement (CLA). To sign, please reply with the following comment:
You only need to sign once — it will be valid for all your future contributions to this project. I have read the CLA Document and I hereby sign the CLA 19 out of 22 committers have signed the CLA. |
|
I have read the CLA Document and I hereby sign the CLA |
Summary
Wei-Shaw/sub2api:main(8927ab091e5dffb3a4f631f8cf02028292ea8987) into the production fork.Verification
git diff --checkbash -n scripts/verify-sub2api-image.sh scripts/verify-sub2api-deploy.shmake -C backend test-unitmake -C backend test-integrationgolangci-lint run ./... --timeout=30mfrombackendmake test-frontendmake buildNotes
upstream/mainis an ancestor of this merge branch.5f70d9bfis an ancestor of this merge branch.git rev-list --left-right --count upstream/main...HEADis0 8.