Commit Graph
5193 Commits
Author SHA1 Message Date
Wesley Liddick 93c3da03eb Merge pull request #7555 from Yukinoshita-Yukino/fix/openai-auto-reset-credit-sched-projection
fix(scheduler): keep auto reset-credit fields in the scheduler projection
2026-09-28 10:27:00 +08:00
Wesley Liddick ff6b85e30b Merge pull request #7615 from UnlastingR/fix/codex-ws-context-rollover-upstream
fix(openai): break websocket chain on context rollover
2026-09-28 10:05:05 +08:00
Wesley Liddick 79c18ec836 Merge pull request #7609 from feeeei/fix/client-disconnect-499
fix(gateway): 客户端断开统一标记 499,不再被记成 502/200 上游错误
2026-09-28 10:04:07 +08:00
Wesley Liddick 1ff2bd6d95 Merge pull request #7573 from Yukinoshita-Yukino/fix/channel-image-price-inherit-when-unset
fix(billing): keep catalog image prices when channel pricing leaves them unset
2026-09-28 09:57:02 +08:00
Wesley Liddick f592b336a3 Merge pull request #7595 from wucm667/feat/issue-7587-allowlist-glob
feat(groups): support wildcards anywhere in model allowlists
2026-09-28 09:55:55 +08:00
Wesley Liddick 37f35b12ea Merge pull request #7524 from Achordchan/fix/plaza-video-rate
fix(model-plaza): apply independent video billing multipliers
2026-09-28 09:55:34 +08:00
Wesley Liddick e4b34446af Merge pull request #7619 from SavitarC/fix/account-stats-long-context-billing
fix(billing): 账号统计成本按 OpenAI 账号开关计入长上下文价
2026-09-28 09:55:19 +08:00
Wesley Liddick 90ae81e9f8 Merge pull request #7624 from wucm667/fix/issue-7620-future-quota-reset
fix(openai): retain quota pause until known future reset
2026-09-28 09:54:52 +08:00
Wesley Liddick 9375e288ba Merge pull request #7571 from SPHINX998/fix/openai-probe-model-unavailable-inconclusive
fix(openai): keep Responses support unknown when the probe model is missing
2026-09-28 09:54:31 +08:00
Wesley Liddick 06f70a6232 Merge pull request #7572 from SPHINX998/fix/openai-alpha-search-require-completion
fix(openai): require a successful completion before billing an alpha search fallback
2026-09-28 09:54:12 +08:00
Wesley Liddick 6906062187 Merge pull request #7597 from far5man/codex/opus55-openrouter-alias-main
fix: recognize OpenRouter Claude Opus 5.5 alias for billing
2026-09-28 09:53:49 +08:00
Wesley Liddick dc4874a2f1 Merge pull request #7613 from yujunkim/fix/openai-free-fast-missing-pricing
fix(billing): keep zero-cost usage log when Free Fast has no pricing
2026-09-28 09:53:26 +08:00
Wesley Liddick 11608c51f0 Merge pull request #7568 from SPHINX998/fix/apicompat-gpt6-reasoning-models
fix(apicompat): treat every GPT generation from 5 on as a reasoning model
2026-09-28 09:53:05 +08:00
Wesley Liddick 8aa7fb8c2d Merge pull request #7569 from SPHINX998/fix/apicompat-recover-empty-final-text
fix(apicompat): recover streamed text when the final output message is empty
2026-09-28 09:52:48 +08:00
Wesley Liddick 00ae7c38e2 Merge pull request #7570 from SPHINX998/fix/apicompat-tool-input-on-block-start
fix(apicompat): keep tool arguments sent on content_block_start
2026-09-28 09:52:13 +08:00
Wesley Liddick 51a10e42f7 Merge pull request #7585 from ResidualBlood/fix/antigravity-pdf-inline-data
fix(antigravity): forward chat PDF file parts as Gemini inlineData
2026-09-28 09:51:28 +08:00
Wesley Liddick 6655f4ec0e Merge pull request #7393 from Achordchan/fix/antigravity-string-const
fix(antigravity): intersect string const with existing enums
2026-09-28 09:51:04 +08:00
Wesley Liddick be64b553f9 Merge pull request #7635 from wucm667/fix/issue-7629-responses-message-type
fix(apicompat): mark converted Chat role items as Responses messages
2026-09-28 09:50:34 +08:00
wucm667 512aa8bc93 fix(setup): omit obsolete rate limit defaults 2026-09-28 08:50:36 +08:00
wucm667 82d2552360 fix(apicompat): mark converted role items as messages 2026-09-28 08:40:41 +08:00
wucm667 02088be2d1 fix(openai): retain quota pause until known future reset 2026-09-27 14:56:38 +08:00
SavitarC fd7af1d462 fix(billing): honor long-context gate in account stats pricing 2026-09-27 12:12:25 +08:00
UnlastingR 7626110d0b fix(openai): break websocket chain on context rollover 2026-09-26 07:45:10 -04:00
Yujun KimandClaude Opus 5.5 3c5ea297ff fix(billing): keep zero-cost usage log when Free Fast has no pricing
RecordUsage treats a missing price as zero cost on the first pass, but
the Free Fast Standard re-evaluation returned the same pricing error.
Requests in a free_openai_fast group sent at priority/fast tier to a
model without pricing therefore lost their usage log entirely.

Ignore pricing-unavailable errors on the Standard pass and keep the
zero-cost entry, matching the first pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 20:11:03 +09:00
feeeei f4f8ff04db fix(gateway): 客户端断开统一标记 499,不再被记成 502/200 上游错误
客户端在响应提交前断开时,只有排队/换号阶段和 OpenAI 的兜底写入会标 499。
Claude 非流式与 Gemini 的 Chat Completions / Messages / Responses 兼容入口
在等待上游时断开,由 GatewayHandler.ensureForwardErrorResponse 补写 502;
Gemini 原生入口什么都不写,落成 200 并被 ops 记成一条 provider 的
"Recovered upstream error";Antigravity 回写 502。这些行都按上游失败计入
SLA 与上游错误数。

- GatewayHandler.ensureForwardErrorResponse 补客户端断开检查,与 OpenAI 版一致
- GatewayHandler 的 Messages / ChatCompletions / Responses 与 Gemini 原生入口:
  首次选号失败时先判断客户端断开;ChatCompletions / Responses 循环开头改用
  failoverClientGone,不再直接 return 落成 200
- Gemini 原生入口:forward 失败时补 499 标记;并发槽错误改走
  concurrencyErrorResponse 统一映射(断开 499、排队满/超时 429、其余 503)
- Antigravity 上游响应前断开回写 499;googleapi 与 Antigravity 的 Google 错误
  格式把 499 映射为 CANCELLED
- OpenAI alpha search 非 failover 错误分支补断开检查
- Claude / Gemini / OpenAI 传输层错误:请求 context 已取消时不记 ops 上游事件
- ops:IgnoreContextCanceled 开启时跳过没有上游错误上下文的 499;带上游错误的
  499(上游失败后客户端没等到换号结果就离开)仍按上游失败落库

计费不变:Gemini 流式中途断开的用量处理不在本次范围。新增 handler/service
单测覆盖各入口的断开场景与 ops 过滤规则。
2026-09-26 17:13:10 +08:00
Achord Chan 510b680fca Merge upstream main and retain upstream scheduler test fix 2026-09-26 15:47:52 +08:00
Achord Chan afbe51abf8 Merge upstream main and retain string const enum intersection 2026-09-26 15:42:47 +08:00
far5man 630639f747 test: preserve Opus 5.5 thinking for OpenRouter alias 2026-09-25 07:01:27 +02:00
far5man 11ffee5543 fix: recognize OpenRouter Claude Opus 5.5 alias for billing 2026-09-25 06:56:40 +02:00
Yuan LiuandClaude Opus 5.5 85810365a6 fix(auto-reset): stop re-querying confirmed no-credit accounts and back off after query failures
With the credit config now visible to the scheduler, every scheduling pass
notifies the auto-reset worker for paused credit-enabled accounts.
evaluateAccount forced an upstream query whenever the reset threshold was
reached, regardless of the last result. Accounts with no credits left and
7d >= 95% were therefore polled on /wham/usage every ~15-20s across
instances (previously once a minute by the scanner), and during upstream
outages the failed state caused the same storm.

- At the reset threshold, skip the forced query while a no_credit result is
  still within the state TTL (10 min); re-query once it expires so newly
  granted credits are still picked up.
- After a query-stage failure (RESET_CREDIT_QUERY_FAILED,
  USAGE_SNAPSHOT_WRITE_FAILED, RESET_CREDIT_DETAILS_UNAVAILABLE), wait one
  minute before querying again, including snapshot-staleness triggers.
- Redemption-stage failures are unaffected and still retry immediately with
  the same idempotency key; accounts with available credits still query and
  redeem immediately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:17:11 +08:00
wucm667 f0ebc183e1 feat(groups): allow wildcard positions in model allowlist 2026-09-25 09:12:09 +08:00
ResidualBlood ca2746f3bb fix(antigravity): forward chat PDF file parts as Gemini inlineData
Chat Completions file parts already become Responses input_file, but the
Antigravity path dropped them when converting to Anthropic and then to
Gemini. Map data-URI input_file to a document block and emit it as
inlineData so Gemini receives the PDF.

Fixes #7583
2026-09-24 15:17:25 +00:00
SPHINX998andClaude Opus 5.5 1d126c833b fix(openai): require a successful completion before billing an alpha search fallback
The personal-access-token /v1/alpha/search fallback turned any upstream
Responses SSE body into a 200 search result and billed one web search call.
Error, response.failed and response.incomplete events, a response.completed
with a non-success status or without its response, a truncated stream and a
bare [DONE] were all accepted, returning partial text as a successful and
billable search.

Parse the stream strictly: those shapes now return an error, so nothing is
written to the client and no WebSearchCalls result is produced. A successful
response.completed keeps the existing output and citation handling. The
existing PAT metadata test now feeds a Responses SSE stream, which is what
that upstream path returns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:54:15 +08:00
SPHINX998andClaude Opus 5.5 9461196864 fix(openai): keep Responses support unknown when the probe model is missing
The API-key Responses capability probe treated every 404 as conclusive and
stored responses_supported=false. The probe model is the first mapped
upstream model in lexical order, so on third-party accounts it is often an
auxiliary entry such as codex-auto-review; a model_not_found answer then
permanently routed the account away from /v1/responses although the
endpoint works.

A 400/404 whose error code/type or message says the probe model does not
exist or is unavailable is now inconclusive, so the stored capability is not
overwritten. Plain 404/405 still mean the endpoint is missing. The probe
also prefers a general gpt-* text model (not an image model) among the
mapped models before falling back to the first one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:50:00 +08:00
Yuan LiuandClaude Opus 5.5 a2007db707 fix(billing): keep catalog image prices when channel pricing leaves them unset
When a channel pricing entry left image_output_price empty,
GetModelPricingWithChannel and the pricing resolver zeroed the image
output price and marked it explicit, so image output tokens were billed
at $0. image_input_price followed the same "unset means zero" rule and
fell back to the text input price instead of the catalog image input
price. A channel that lists an image model such as gpt-image-2 in token
mode without filling in prices therefore generated images without
charging for image output tokens, while the available-channels view
(#2475) displayed the catalog image price for the same entry.

#2930 wanted an explicit 0 on the channel to stop falling back to the
catalog; the change applied the same treatment to unset values as well.

Treat the two image fields like every other token price field:
- Set on the channel (including 0): override the catalog. An explicit
  image_output_price keeps ImageOutputPriceExplicit, so 0 stays free.
- Left empty: keep the catalog price (LiteLLM or built-in fallback). If
  the catalog has none, computeTokenBreakdown still falls back to the
  text input/output price.

The admin UI saves an empty field as null and only an entered 0 as 0,
so the two cases stay distinguishable. The flat, interval and
GetModelPricingWithChannel paths now share applyChannelImagePriceOverrides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:46:14 +08:00
SPHINX998andClaude Opus 5.5 97bdde3135 fix(apicompat): keep tool arguments sent on content_block_start
The canonical Anthropic stream leaves tool_use input empty on
content_block_start and streams the arguments as input_json_delta.
Anthropic-compatible relays may instead put the complete arguments on
content_block_start and send no delta. The Anthropic-to-Responses stream
converter ignored that input, so function_call_arguments.done carried ""
and the closed item "{}", and clients executed the tool with no arguments.

Keep the inline input as a seed. A real input_json_delta supersedes it, so
canonical streams keep their exact event sequence and two JSON documents are
never concatenated. When no delta arrived by content_block_stop, emit the
seed as a single arguments delta before the done event, which still repeats
exactly what the deltas streamed. Empty, "{}" and null inputs keep the
existing "{}" fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:45:14 +08:00
SPHINX998andClaude Opus 5.5 090b2cc000 fix(apicompat): recover streamed text when the final output message is empty
BufferedResponseAccumulator.SupplementResponseOutput rebuilt the output only
when the terminal response had an empty output array. Upstreams can also end
with a non-empty output whose message carries no (or only blank) output_text.
The buffered Chat Completions and Anthropic Messages paths then returned an
empty reply although the text had streamed, while usage still billed the
terminal output_tokens.

When the terminal output has no usable message text, fill the first empty
output_text part (or append one, or append a message item) from the
accumulated deltas. Non-empty terminal text stays authoritative, and nothing
is synthesized when no text streamed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:44:05 +08:00
SPHINX998andClaude Opus 5.5 82b092ffc2 fix(apicompat): treat every GPT generation from 5 on as a reasoning model
isReasoningModel matched the literal "gpt-5" prefix plus the GPT-6 Sol/Luna
spellings, so gpt-6 and gpt-6-astra (both already in the model catalog and
price list) fell into the sampling branch. Anthropic Messages and Chat
Completions requests converted to the Responses API then forwarded
temperature/top_p, which the Responses API rejects for these models with
"Unsupported parameter".

Key the check on the parsed GPT generation number instead, keeping the
existing Sol/Luna spelling match, so later families are covered without
another edit. Non-generation GPT families (gpt-image-1, gpt-audio) and
gpt-4.x are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:37:16 +08:00
Yuan LiuandClaude Opus 5.5 1ca7b2a519 fix(scheduler): keep auto reset-credit fields in the scheduler projection
OpenAI candidate filtering reads the Redis metadata projection, but
filterSchedulerExtra dropped auto_reset_credit_enabled, the 5h/7d
reset-credit thresholds and codex_auto_reset_credit_state. The scheduler
therefore always saw automatic reset credits as disabled, so accounts
were paused at the ops quota auto-pause threshold even when a fresh
state showed an available reset credit. The "keep scheduling until the
reset-credit threshold" branch never took effect, and accounts with
credits stayed idle until their window reset naturally.

- Keep the four keys in the scheduler projection; the test round-trips
  the real JSON payload and resolves the credit config from it.
- Once visible, every scheduling pass notifies the auto-reset worker for
  paused credit-enabled accounts, and each notification reloads the
  account. Add a 30s per-account cooldown for the scheduler hot path
  only; other event-driven notifications are unchanged and the
  per-minute scan remains the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:02:50 +08:00
alfadb dc9216512d fix(opencode): normalize the quota endpoint for the /zen/go base variant
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 保持同一语义。
2026-09-23 15:26:44 +08:00
alfadb d0731be2f0 fix(opencode): lite 列表 DTO 补 opencode_go_usage,修复列表页显示余额 2026-09-23 15:26:44 +08:00
alfadb a2e1618700 fix(opencode): 纠正 CASE 求值顺序注释并补齐 mode 语义护栏 2026-09-23 15:26:44 +08:00
alfadb d03274ad5f feat(opencode): 用量窗口资格支持 OpenCode 平台并扩展到其它厂商挂载 2026-09-23 15:26:44 +08:00
alfadb 0a43efb9a4 fix(opencode): preserve managed usage state on account updates 2026-09-23 15:26:35 +08:00
alfadb f7f0a56f35 fix(opencode): preserve auto-refresh on usage refresh 2026-09-23 15:26:35 +08:00
alfadb ae2735cdb6 feat(opencode): 用量窗口支持同 Key 组共享、活动防抖与超窗强刷
与 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 零错误。
2026-09-23 15:26:35 +08:00
alfadb 6624947b62 feat(opencode): 支持 OpenCode Go 官方用量窗口查询与自动刷新
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 零错误
2026-09-23 15:26:35 +08:00
Wesley Liddick afd069b169 Merge pull request #7532 from alfadb/feature/claude-cli-version-sync
feat(claude): 自动同步 Claude Code 客户端版本号,无需改代码跟随官方版本
2026-09-23 14:54:36 +08:00
shaw 6452aeffa1 fix(claude): 修复版本同步契约测试和缓存刷新 2026-09-23 14:39:29 +08:00
alfadb ca08825935 feat(claude): 自动同步 Claude Code 客户端版本号
出站伪装用的 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,避免缓存翻转
  瞬间头体版本不一致被判非正版客户端
- 管理后台设置页新增配置区块,含中英文案与「当前同步到」只读展示
2026-09-23 13:35:44 +08:00