Skip to content

sync: merge upstream v0.1.127 fixes#2

Merged
BFanSYe merged 118 commits into
mainfrom
sync/upstream-v0.1.127-20260519
May 19, 2026
Merged

sync: merge upstream v0.1.127 fixes#2
BFanSYe merged 118 commits into
mainfrom
sync/upstream-v0.1.127-20260519

Conversation

@BFanSYe
Copy link
Copy Markdown
Owner

@BFanSYe BFanSYe commented May 19, 2026

Summary

  • Merge current Wei-Shaw/sub2api:main (8927ab091e5dffb3a4f631f8cf02028292ea8987) into the production fork.
  • Preserve the custom FusionGate/HomeView homepage and scroll reveal tuning.
  • Keep the self-built GHCR image workflow already merged in PR ci: add self-built image verification workflow #1.

Verification

  • git diff --check
  • bash -n scripts/verify-sub2api-image.sh scripts/verify-sub2api-deploy.sh
  • workflow YAML parse with Python/PyYAML
  • make -C backend test-unit
  • make -C backend test-integration
  • golangci-lint run ./... --timeout=30m from backend
  • make test-frontend
  • make build
  • Codex read-only review: PASS
  • Claude read-only review: PASS

Notes

  • upstream/main is an ancestor of this merge branch.
  • Previous production commit 5f70d9bf is an ancestor of this merge branch.
  • git rev-list --left-right --count upstream/main...HEAD is 0 8.

StarryKira and others added 30 commits May 8, 2026 03:44
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.
Wei-Shaw and others added 25 commits May 19, 2026 15:35
…-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
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 19, 2026

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:

I have read the CLA Document and I hereby sign the CLA

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.
✅ (StarryKira)[https://github.com/StarryKira]
✅ (imlewc)[https://github.com/imlewc]
✅ (2ue)[https://github.com/2ue]
✅ (gaoren002)[https://github.com/gaoren002]
✅ (wucm667)[https://github.com/wucm667]
✅ (astro-ge)[https://github.com/astro-ge]
✅ (Evsdrg)[https://github.com/Evsdrg]
✅ (is7Qin)[https://github.com/is7Qin]
✅ (weak-fox)[https://github.com/weak-fox]
✅ (yetone)[https://github.com/yetone]
✅ (Agoniedi)[https://github.com/Agoniedi]
✅ (yangzc2004-bit)[https://github.com/yangzc2004-bit]
✅ (honue)[https://github.com/honue]
✅ (Brisbanehuang)[https://github.com/Brisbanehuang]
✅ (DanisJiang)[https://github.com/DanisJiang]
✅ (L494264Tt)[https://github.com/L494264Tt]
✅ (Arron196)[https://github.com/Arron196]
✅ (Wei-Shaw)[https://github.com/Wei-Shaw]
✅ (DaydreamCoding)[https://github.com/DaydreamCoding]
@hoobnn
@HiNewWorld
@tiger-jr804
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@BFanSYe
Copy link
Copy Markdown
Owner Author

BFanSYe commented May 19, 2026

I have read the CLA Document and I hereby sign the CLA

@BFanSYe BFanSYe merged commit b9cf140 into main May 19, 2026
11 of 12 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators May 19, 2026
@BFanSYe BFanSYe deleted the sync/upstream-v0.1.127-20260519 branch May 19, 2026 11:50
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.