Skip to content

chore: merge upstream v0.2.8 with downstream compatibility - #196

Merged
github-actions[bot] merged 240 commits into
mainfrom
sync/upstream-0.2.8
Sep 23, 2026
Merged

github-actions[bot] merged 240 commits into
mainfrom
sync/upstream-0.2.8

Conversation

@HTExplicit

Copy link
Copy Markdown
Owner

Summary

Merge the official stable release v0.2.8 (fd80b08c90b55edcad5b00171b53f08721d30da1) into downstream main f78c2cf54c797a2ccb655848be5452c42a13adde (production v0.2.7-codexrip.4). The hourly sync stopped on text conflicts (#195); this is the hand-resolved candidate. Landing needs a merge commit, not a squash, so the upstream parent stays in history.

Decision record: .downstream/upstream-review-v0.2.8.md. Machine-readable report: .downstream/upstream-risk.json. It is the unmodified upstream_risk.py output for base v0.2.7, tag v0.2.8 and downstream f78c2cf54, plus initial_conflict_files.

Downstream overlap review

  • 175 overlapping paths / 166 critical upstream paths reviewed; 57 conflicted files / 126 hunks resolved.
  • Migrations: upstream 238b, 239 (per-effort reasoning multipliers, converted from the explicit legacy max value) and 240. They run after downstream 253 in production and touch no table downstream 239_codexrip…253 changes.
  • Adopted upstream over downstream duplicates: GPT-6 Sol/Luna pricing, detection and fallback prices (identical values). Downstream copies are removed, including duplicate DefaultModels entries, frontend preset buttons and pricing-file keys. Codex-only routing gates, pinned instruction templates and live capacity stay.
  • Kept downstream behavior where upstream differs:
    • reasoning-effort normalization and recording;
    • Sol lists Codex ultra;
    • explicit none kept on compatible hosts;
    • undocumented dated GPT-6 snapshots pass through unchanged;
    • Responses streams need an authoritative terminal event ([DONE] is only a transport marker);
    • the admin test picker treats the saved mapping as authoritative (FetchOpenAIAccountCatalogModels);
    • group profit control stays removed;
    • platform quotas include Cindy.
  • Folded upstream into downstream-only paths:
    • the Select fix is ported into the shared plugin UI component;
    • Gemini-compatible image models are admitted for API-key accounts in the downstream Images fences, while strict Cindy groups still reject them;
    • PluginAccountScope is threaded through the extension host;
    • OpenCode-Go columns are added to the locked probe-extra query (17 columns).
  • Fixes found during the merge:
    • Channel-priced OpenAI token billing dropped the reasoning effort, so configured reasoning multipliers never applied there. This is a pre-existing downstream gap; it now passes the forwarded effort.
    • Auto-reset credit state preservation, lost in the v0.2.7 merge, is restored.

Release preparation (second commit)

  • backend/cmd/server/VERSION is 0.2.8 and .downstream/upstream-base is v0.2.8; the target host is 0.2.8-codexrip.1.
  • The seven bundled plugins move to 0.2.10, because every host release re-signs them. requires.sub2api is >=0.2.7-codexrip.2 <0.2.9: the lower bound is unchanged because no new host API is needed.

Validation

Local Windows runs used a single process.

  • Backend:
    • go build ./... and go vet -tags=unit on the changed packages pass.
    • go test -tags=unit ./...: 56 packages pass.
    • Two tests fail only on Windows. Both are identical on downstream main and pass on Linux:
      • TestOllamaProbeCallback_StaleLongDoesNotOverrideNewShort (clock tick);
      • TestGPT6InstructionsMatchPinnedCatalog (CRLF checkout of the embedded templates).
  • Frontend:
    • lint:check, typecheck and the plugin UI vue-tsc pass.
    • Vitest: the Makefile critical set plus every spec touched by the merge (87 files / 939 tests), and the full Downstream frontend focused list (85 files / 892 tests).
  • Upstream tests that assumed upstream-only behavior were adapted to the recorded downstream contracts, each with a one-line reason: Cindy platform count, pgx array arguments, fork Account fields in the snapshot tripwire, the terminal-event contract, and preserved none.
  • No real model requests were made.

Release boundary

After the required checks pass and the review is recorded, promotion goes through the existing Upstream Reviewed Promotion route. It produces a merge commit, the immutable v0.2.8-codexrip.1 tag, one Downstream Release, and one approval-gated deploy-preserve. Runtime and resource flags are unchanged, and nothing else is deployed.

🤖 Generated with Claude Code

coo1white and others added 30 commits July 25, 2026 19:57
UpdateS3Config only encrypted the SecretAccessKey on the branch where the
caller supplied a new one. The other branch inherits the stored secret
through loadS3Config, which *decrypts* it — so that path wrote the
plaintext straight back to the settings row and overwrote the ciphertext.

It is the normal path, not an edge case. UpdateS3Config clears the secret
in its own return value, so the admin form's field is empty afterwards.
Any later save — changing the endpoint, saving the schedule, pressing save
twice — sends an empty secret and takes the inheriting branch. One extra
save is enough to leave the secret in plaintext at rest.

Nothing surfaced because loadS3Config falls back to the raw value when
decryption fails, for compatibility with pre-encryption rows. Backups kept
working and the only trace was a line per read:

  [Backup] S3 SecretAccessKey 解密失败(可能是旧的未加密数据):
  decrypt: cipher: message authentication failed

Seen on a live install: the stored value was 64 characters, exactly the
plaintext length of an R2 secret, where AES-GCM + base64 would have been
124. The compatibility fallback turns a one-time write bug into a
permanent plaintext secret.

It also feeds itself: pg_dump includes the settings table, so the backup
uploaded to the bucket carries the credentials for that same bucket.

Fix: hoist the encryption out of the if/else so it runs for a non-empty
secret whichever way it arrived. The ephemeral-key guard (Wei-Shaw#4524) moves
with it, and the no-secret-at-all case still skips encryption entirely, so
an update with no stored secret keeps working under an auto-generated key.

The existing KeepExistingSecret test passed either way, because it only
asserted loadS3Config returns the plaintext — true with the fallback too.
The new test asserts on the row itself: still ciphertext after a second
save, not double-wrapped after a third.

Checked the new test fails without the fix and passes with it.
…pstream EOF

The streaming forward loops treated upstream EOF as the only normal exit
condition; response.completed/[DONE] only set a flag. With keep-alive /
HTTP/2 connection reuse to the upstream, the upstream may hold the
stream open for 8-46s after the terminal event, during which the
gateway could only emit downstream keepalives, inflating tail latency.

Finish the stream as soon as the terminal event frame has been fully
written downstream (usage is already parsed from the terminal event),
and close the upstream body to unblock the reader goroutine. Applied
to the guarded/async, sync, and passthrough streaming paths. The WS
HTTP bridge already behaved this way.

Adds a regression test with an upstream that hangs after
response.completed.
…d items fallback

- Handle JSON Schema Draft 2020-12 prefixItems (tuple arrays) by converting to a best-match items schema
- Ensure array schema always defines a valid items schema to prevent Gemini 400 'missing field: items.items' error
- Fix recursive traversal when a schema defines both properties and items
- Add comprehensive unit test cases for tuple and array schemas
- Fix test assertion target location for whereItems prefixItems
- Support const keyword normalization to enum and infer scalar type
- Infer scalar type for enum-only schema to prevent object reason injection
- Recursively clean properties and items after anyOf/oneOf merge
- Ensure empty prefixItems is safely deleted
Upstream Anthropic keepalive frames (event: ping) leaked into OpenAI
Chat Completions SSE streams, crashing strict clients (Cursor, Hermes,
OpenAI SDKs) on unexpected event types. Error events still forward.

Fixes Wei-Shaw#5203
- Replace unchecked single-value type assertions with comma-ok form
  guarded by require.True (errcheck check-type-assertions)
- Remove trailing blank line flagged by gofmt

Co-Authored-By: Claude Code <noreply@anthropic.com>
….done

Gemini/Antigravity delivers tool arguments as one complete object, so this
converter synthesizes the Responses tool-call stream itself and therefore owns
its self-consistency. The done event was built without Arguments, leaving it ""
while the preceding delta carried the entire JSON. Clients that reconcile the
done payload against the accumulated deltas (OpenAI Responses SDK); Qoder's
reconcileToolCall) reject that mismatch as inconsistent_tool_call and abort the
turn non-retryably, so every tool call through the gateway failed.

Emit state.CurrentArgs, which closeCurrentResponsesItem already uses for the
item, restoring sum(deltas) == done.arguments == item.arguments.

Regression test asserts the delta sum equals the done payload and fails closed
when Arguments is dropped.
管理员在站外给邀请人打款后,系统里原本无法扣减对应的返利额度,用户仍可把
这部分额度转入余额。现在可在「邀请返利 → 提取记录」点「登记线下提现」,
从该用户的可提取返利额度中扣除已打款金额:

- POST /api/v1/admin/affiliates/users/:user_id/withdraw,请求体只含 amount,
  金额按 8 位小数舍入后校验
- 先解冻已到期的冻结额度,再以 aff_quota >= amount 为条件原子扣减,写入
  action=withdraw 的流水并带额度快照;额度不足返回 AFFILIATE_QUOTA_INSUFFICIENT
- 不新增字段与迁移:线下提现复用已有 action 列区分,操作人由管理端审计日志记录
- 提取记录同时列出转入余额与线下提现,新增「类型」列
- 弹窗选择用户后展示可提取额度,支持一键填入全部;从未产生返利的用户按 0 处理

返利记录改为 LEFT JOIN 订单与被邀请人,兑换码、管理员充值来源的返利也会出现
在明细中,接口里订单相关字段与被邀请人 ID 可为 null。
lyy0709 and others added 20 commits September 23, 2026 12:01
Close the existing catalog identity test before the new capability test and
assert that an explicit null capability remains present. Update the quota
snapshot fixture to the pointer parameter already used by the current base.

Verified six full backend unit packages, scoped lint and CGO-disabled build.
出站伪装用的 Claude Code 版本号此前硬编码在 claude.CLICurrentVersion,
只能靠环境变量 SUB2API_CLAUDE_CLI_VERSION 覆盖且需重启;Anthropic 抬高
版本下限时(如 Opus 5.5 要求 >= 2.1.280)必须改代码发版才能跟上。

改为每小时从官方 GitHub Releases 同步最新稳定版,无需重启也无需发版。

- 新增 ClaudeCodeVersionSyncService:1 小时间隔,数据源 anthropics/claude-code
  的 releases/latest,失败回退最近 30 条列表;过滤 draft/prerelease;
  只向前推进,拉取失败保留上次值不降级;面板可关闭
- 新增三个设置项:claude_code_client_version(管理员手动固定)、
  claude_code_client_version_synced(同步服务独占写)、
  claude_code_version_auto_sync_enabled(开关,默认开)
- 取值优先级:面板手动值 -> 同步值 -> SUB2API_CLAUDE_CLI_VERSION -> 内置基线,
  每层经 IsSupportedCLIVersion 校验,含 60s 缓存与 singleflight
- 版本号运行期化:claude.DefaultHeaders 由包级 var 改为函数,新增
  EffectiveCLIVersion/DefaultUserAgent 与 SetCLIVersionResolver(未注入时
  与 CLIVersion() 等价);identity 的 defaultFingerprint 不再在包 init 固化,
  指纹下限与主版本超前校验改用运行期值
- 守住一致性铁律:buildUpstreamRequest 与 count_tokens 各取一次 UA 字符串,
  同时喂给出站 User-Agent 头与请求体 billing 的 cc_version,避免缓存翻转
  瞬间头体版本不一致被判非正版客户端
- 管理后台设置页新增配置区块,含中英文案与「当前同步到」只读展示
feat(models): support GPT-6 Sol, Luna and Claude Opus 5.5
…on-sync

feat(claude): 自动同步 Claude Code 客户端版本号,无需改代码跟随官方版本
OpenCode Go 上游(https://opencode.ai/zen/go/v1)官方提供用量端点
GET /zen/go/v1/usage(Bearer 复用消费 key),返回 rolling(5h)/weekly/monthly
三窗口的 percent 与 resetsAt。本提交为 openai apikey 账号(base_url 为
opencode.ai/zen/go/v1)接入该用量窗口:

- 后端:OpenCodeGoUsageService——手动刷新(每账号 10s 节流)、
  周期自动刷新(全局开关 + 账号开关双开启,interval 5-1440 分钟,
  leader lock + 每轮 20 账号 + 并发 4)、失败指数退避(上限 24h)、
  Retry-After 尊重;401→unauthorized、403→failed;snapshot 持久化于
  accounts.extra(单账号写入,无分组 CAS、无会话加密)
- admin API:settings GET/PUT、/:id/opencode-go-usage GET、
  auto-refresh PUT、refresh POST(与 Ollama Cloud usage 同构)
- 前端:账号列表行内三窗口进度条(复用 UsageProgressBar)、
  编辑弹窗内配置面板(详情+手动刷新+auto-refresh)、全局设置卡片、
  en/zh i18n

验证:
- 上游端点已用生产真实 Go key 实测 200,响应
  {"usage":{"rolling":{"status":"ok","percent":6,...},...}}(2026-08-13)
- go build ./... OK;service/handler/repo/dto/cmd 全量回归 OK
- 新增单测 20 例(解析 401/403/畸形 JSON、退避、节流等)全绿
- frontend vitest 新增 7 例 + Ollama 回归 4 例全绿;vue-tsc 零错误
与 Ollama Cloud usage 对齐:OpenCode Go 作为同 Key 聚合订阅,
多个 openai/apikey/base_url=opencode.ai/zen/go/v1 账号共享同一份
用量状态与刷新节奏:

- 组共享:组指纹 sha256("opencode.ai\0"+api_key);auto_refresh 开关
  与 snapshot 按组写(事务 + FOR NO KEY UPDATE + 锚点 CAS + 纯合并,
  写 snapshot 不会抹掉开关);列表/详情经 ResolveAccounts 组内共享;
  账号换 Key → IdentityChanged → 组级关闭 auto_refresh 并清快照
- 活动防抖 + 超窗强刷:settings 新增 debounce_minutes(默认 1,
  1-60,必须小于 interval_minutes);模型请求活动经网关 5 处调用点
  驱动刷新,due = min(lastUsed+debounce, fetchedAt+maxWait),
  成功路径有 5 分钟最小抓取间隔,失败路径退避优先
- 手动刷新限频 10s→30s 且按组;singleflight/RunDue 按组去重;
  ListDue 在 SQL 内按组算 due(CTE 分组取组内 MAX(last_used_at))

验证:go build + 五包回归 OK;新增组共享/三态 due/组单飞/
IdentityChanged/debounce 校验单测全绿;vitest 11 例全绿;
vue-tsc 零错误。
OpenCode Go 有两个都合法的 base_url:https://opencode.ai/zen/go/v1(OpenAI
协议档,DefaultOpenCodeGoBaseURL)与 https://opencode.ai/zen/go(Anthropic
协议档,前端 defaultCNBaseUrl 会写入)。旧拼接只做 strings.TrimRight(base,
"/") + "/usage",Anthropic 档账号的额度探测必然打到 /zen/go/usage。

实测证据(curl 无鉴权,401=端点存在,404=不存在):
  401 https://opencode.ai/zen/go/v1/usage
  404 https://opencode.ai/zen/go/usage

修法沿用同包 kimiQuotaURL(coding/v1 与 coding 两种 base)的既有惯例:
TrimSuffix(base, "/v1") 后统一拼回 /v1/usage,两个变体与尾斜杠写法都归一到
https://opencode.ai/zen/go/v1/usage;自定义 base 保持同一语义。
…e-window

feat(opencode): 支持 OpenCode Go 用量窗口(opencode_go 平台账号 + 多厂商挂载复用)
Merge official Sub2API v0.2.8 (fd80b08) into downstream main f78c2cf.
The hourly sync stopped on 57 conflicted files (issue #195); every hunk was
resolved by hand. See .downstream/upstream-review-v0.2.8.md for the full
decision record and .downstream/upstream-risk.json for the recomputable
risk report.

- Adopt upstream GPT-6 Sol/Luna pricing and detection and drop the
  downstream duplicates (pricing file, model lists, preset buttons).
- Adapt billing to per-effort reasoning multipliers (migration 239) and
  pass the forwarded effort on channel-priced OpenAI billing, which
  previously ignored configured multipliers.
- Keep downstream reasoning-effort semantics, explicit none on compatible
  hosts, unchanged dated GPT-6 snapshots, authoritative Responses
  terminal events and the saved-mapping test picker.
- Admit Gemini-compatible image models for API-key accounts in the
  downstream Images fences; strict Cindy groups still reject them.
- Port the upstream Select fix into the shared plugin UI component.
- Restore auto-reset credit state preservation lost in the v0.2.7 merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every host release re-signs the bundled packages, and an installed
immutable plugin version cannot take different bytes, so the seven
first-party plugins move from 0.2.9 to 0.2.10. The host range widens to
<0.2.9; the lower bound stays at 0.2.7-codexrip.2 because the plugins need
no new host API. The first-party plugin test bundle now targets the new
host version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HTExplicit HTExplicit added upstream-review-required Manual upstream review is required upstream-reviewed Human upstream overlap review completed labels Sep 23, 2026
golangci-lint (staticcheck QF1001) flagged the negated conjunction added in
the merge; the condition is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions
github-actions Bot merged commit 071f25c into main Sep 23, 2026
21 checks passed
@HTExplicit
HTExplicit deleted the sync/upstream-0.2.8 branch September 28, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

upstream-review-required Manual upstream review is required upstream-reviewed Human upstream overlap review completed

Projects

None yet

Development

Successfully merging this pull request may close these issues.