Repository navigation
Upgrade Sub2API to v0.2.17 while preserving product contracts - #68
Merged
Merged
Conversation
- Vertex 按量付费返回的 "Resource exhausted ... try again later" (RESOURCE_EXHAUSTED、无错误明细)表示共享资源池当时没有容量,与账号额度 无关;此前一律冷却账号 1 分钟,冷却期间账号被调度快照剔除,绑定显式缓存 的请求因无法换号直接 503 - 这类 429 只记日志、账号保持可调度,由请求内同号 1/2/4/8s 退避重试消化, 重试用尽后未绑定缓存的请求照常换号 - 带 retryDelay、文案重试时长、配额/预留吞吐字样或其他形态的 429 仍按账号 自身限速冷却;非 Vertex 账号行为不变
- 换取 SA access token 时 token 端点返回 400
{"error":"invalid_grant","error_description":"Invalid grant: account not found"}
表示服务账号已被删除,凭据再也换不到 token;此前三条 Gemini 转发路径把它当作
本地构造请求失败直接回 502,不换号也不改账号状态,账号持续被调度
- 识别该错误后账号置为 error 并停止调度(Service account token rejected (400): …),
记录凭据获取阶段(stage=account_auth)的 Ops 事件,请求交给 handler 换号
- 只匹配 400 + invalid_grant + account not found:invalid_grant 还涵盖时钟偏差、
签名无效等情况,其他换 token 失败保持原行为
- token 端点地址改为包级变量,测试中替换为本地服务
- 三条 Gemini 转发路径在重试循环内和循环外各对最终上游错误响应检查一次错误策略。 配了账号级 401 临时不可调度规则的 API Key / Vertex 账号首次收到 401 时,第一次 检查写入 401 暂停记录,第二次检查读回该记录判定为重复 401,同一请求内即升级为 error 永久停用 - 循环内的检查结果带到循环外复用;循环内未检查的最终响应(Claude 兼容层的签名 400 分支、原生路径的 cachedContent 透传分支)仍在循环外检查
- 内置兜底价:gemini-4-argon 为 $2 输入 / $10 输出 / $0.10 缓存读取(每百万 token,优惠价有效期至 2026-11-30,2026-12-01 起为 $4 / $20 / $0.20); Cyber 变体(gemini-4-argon-cyber-permissive)不参与优惠,为 $4 / $20 / $0.20, 匹配时先于基础型号判断 - Antigravity 的 -low / -medium / -high 档位名归一到基础价 - 本地价格快照新增两个不带前缀的条目,含 Priority 档价格;所有提示词长度同价, 不带长上下文阶梯;只支持隐式缓存,不带存储价
…counts Chat Completions conversion drops Anthropic web_search tools, so Claude Code's WebSearch on OpenAI-platform groups (e.g. DeepSeek upstreams) got no web_search_tool_result and reported 0 results. Run the existing search emulation in ForwardAsAnthropic and let OpenAI API Key accounts opt in via extra.web_search_emulation.
Keep OpenAI non-API-key accounts on native search before channel inheritance. Cover 72 combinations of account types, modes, channel flags and global enablement in the shared interception gate. Validation: the regression test fails in six OAuth/setup-token channel-enabled cases on the original code and passes with this guard. Related WebSearch unit tests pass. The broad service/handler test run was blocked by automatic approval review due to an unrelated daily-cloudcode-pa.googleapis.com request with unverified payload/credential use.
…Call/thoughtSignature, and composite /v1beta routing
parseZhipuTokenTiers 只在 limitType == "TOKENS_LIMIT" 分支里按 unit 显式分类, 而智谱当前把 5h / weekly 两条窗口都标成 CREDIT_LIMIT(level=lite)。这些条目被放进 creditFallback,entry 结构体又不带 unit,信息在此永久丢失,降级路径退化为 「按 nextResetTime 升序填空位」。当周窗口先于 5h 重置时(每个周期临尾的几小时), 周窗口会被先填进 5h 槽,两档必然标反 —— 函数文档注释第 1 条已警告过该场景, 只是防线建在了错误的 type 分支上。 因为只在「CREDIT_LIMIT 且周窗口更早重置」这一种组合下出错,症状是间歇性的, 容易被误判为随机抽风。该函数自 v0.2.0 到 v0.2.16 逐字节未变,不属于版本回归。 改动: - entry 增加 unit 字段;classify 改为直接接收 unit,不再依赖 item。 - 仅在降级路径(无任何 TOKENS_LIMIT 条目)上,对 creditFallback 中 unit 可识别的 条目先按 unit 分类;unit 仍不可识别的才落入原有的 reset 时间启发式。 - TOKENS_LIMIT 优先占槽的既有语义保持不变:两种 type 同时返回时 CREDIT_LIMIT 依旧不参与槽位竞争。 - 更新 cnZhipuWindow 与 parseZhipuTokenTiers 的文档注释。 - cn_providers_test.go 补充 3 个回归用例。 验证: - 反向验证:仅回退源文件保留测试,CreditLimitUnitClassification 失败于 14 vs 90(两档被对调);另两个钉住原语义的用例修复前后均通过。 - go build ./... 通过;gofmt -l 无输出;golangci-lint v2.14.0 0 issues。 - go test -tags=unit ./internal/service/ 全量通过。 影响范围:backend/internal/service 两个文件(service + unit test)。
composite 分组中 openai OAuth 账号与 deepseek API Key 账号同时显式映射同一 gpt 模型时,模型归属判为歧义、路由层不解析平台,请求落到默认 Anthropic GatewayHandler;handler 再按模型名前缀猜回 openai 并选中 OpenAI OAuth 账号, ChatGPT token 被发往 api.anthropic.com,上游 401 "Invalid bearer token" 经 failover 逐号打成临时不可调度,单请求即可扫空号池。 - 归属记录声明方是否含 OpenAI 网关族 OAuth / setup-token;当模型名兜底也 落在 OpenAI 网关族时判为配置错误,路由中间件与 WS 入口直接 400 拒绝并 提示配置显式路由。纯 API Key 歧义及 Anthropic/Antigravity/Gemini OAuth 歧义保持原行为。 - 模型归属改按配置计算(ListModelAvailabilityCandidates),不再随限流 / 临时不可调度在「唯一归属」与「歧义」之间翻转。 - Anthropic 请求构建前校验凭据:非 Anthropic 网关族的 OAuth 凭据不出站, 返回仅换号、不处罚账号的 failover;count_tokens 返回 404 不支持。 API Key 不受限(上游可能兼容多种协议格式)。
…misroute fix(gateway): 拒绝含 OpenAI 网关 OAuth 的 composite 歧义路由,防止凭据误入 Anthropic 管线
…-classification fix(zhipu): 修正 GLM Coding Plan 5h/weekly 用量窗口标反 / fix swapped GLM coding plan usage windows
…y-429 fix(gemini): Vertex 共享容量不足的 429 不冷却账号,改为同号退避重试
…olicy fix(gemini): Vertex SA 已删除时停用账号并换号,401 临时不可调度规则首次命中不再永久停用
feat(gemini): 补齐 Gemini 4 Argon 计费
…n-support Resolve Gemini 4 Argon pricing conflicts in favor of main (#8078), which already provides the base and Cyber Permissive price cards, thinking-tier pricing aliases, and the local pricing snapshot.
…5 tier IDs at base rates
- Restore runtime default passthroughs and Gemini 3.1 Pro aliases for
explicit Antigravity model_mapping. Mappings saved before the Gemini
3.x Flash / 3.1 Pro releases still depend on them; dropping them would
silently remove those models from existing accounts on upgrade.
- Capability sync now skips injected-only passthrough targets, so models
the upstream catalog omits no longer mark the sync as partial.
- Price Antigravity claude-{opus,sonnet}-5-5-{high,medium,low} at the base
Opus/Sonnet 5.5 card instead of substring-matching claude-opus-5 /
claude-sonnet-4-5 (1.25x-3x overbilling).
feat(antigravity): support Gemini 4 Argon and accurate upstream model catalogs
- chat/messages 兼容入口把 reasoning_effort、thinking、output_config.effort 映射为 Gemini thinkingConfig:2.5 用 thinkingBudget,3 及以上用 thinkingLevel; minimal 只发给 flash 系列,medium 只发给 flash 系列与 3.1 Pro,其余升为 high; 图像/语音/向量与无法识别版本的型号、带档位后缀的型号不写档位 - 流式请求开启 includeThoughts(messages 需客户端开启 thinking),thought part 转为 Anthropic thinking 块,chat 侧输出为 reasoning_content - 历史中的 thinking 块与 chat 回传的推理内容不再发给 Gemini;签名降级重试先删 历史思考块再降级,思考参数沿用客户端原始请求 - messages 兼容路径记录实际转发的思考档位,与 chat 兼容路径一致
…n-output feat(apicompat): configure minimum output token floor
fix(gateway): honor Anthropic protocol base URLs.
…import fix(openai): isolate Codex imports by workspace
Revert 679a718 ("feat(accounts): compact account search and filters"), which moved the type / privacy / group filters behind a collapsed "More filters" panel on /admin/accounts. Polish on top of the revert: - Widen the privacy select (w-48) so "全部Privacy状态" / "All Privacy States" no longer truncates. - Narrow the search input to w-52 so all six controls fit on one row at 1440px. - Below sm, lay the filters out as a two-column grid that fills the width instead of fixed-width selects with a ragged edge.
revert(accounts): restore inline account filters (#7853)
…ending - Validate order_type (balance/subscription) at order creation and in fulfillment; other values are rejected with INVALID_ORDER_TYPE. - Keep the balance/subscription deduction while a gateway refund is pending. The deduction is recorded in the REFUND_PENDING audit (written in the same transaction as the status change), not applied again on successful finalization, and returned once if the gateway reports failure. Legacy pending audits keep the previous behaviour. - REFUND_PENDING orders are finalized via refund query only; PrepareRefund/ExecuteRefund no longer accept them. - Invalidate the billing balance cache after refund deductions/returns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The /payment/stripe route no longer carries client_secret in its query. The page reads it from the recovery snapshot written when the order was created and requires the snapshot order to match order_id, consistent with AirwallexPaymentView. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Claim REFUND_PENDING -> REFUND_FAILED, return the held deduction and write the audit in one transaction. If the deduction cannot be returned, the order is still marked failed together with a REFUND_ROLLBACK_FAILED audit so a retried refund skips the deduction. - Return an error when the REFUND_PENDING audit cannot be loaded or parsed instead of falling back to the legacy finalization path.
…types - Alipay: record the generated out_request_no as the refund ID and implement QueryRefund (alipay.trade.fastpay.refund.query), so refunds that return fund_change=N can be finalized from the admin refund query. Refunds recorded before this change return REFUND_QUERY_UNSUPPORTED. - Fulfillment: mark paid orders with an unsupported order type as FAILED with an audit entry instead of erroring, so payment callbacks are acknowledged; RetryFulfillment rejects them with INVALID_ORDER_TYPE.
…ot-found failure - The SDK returns signed business-failure responses (for example ACQ.SYSTEM_ERROR) with a nil error and empty fields. QueryRefund now checks the response code and returns an error, so the order stays REFUND_PENDING instead of being read as "no refund record" and failed. - A missing refund record is treated as failed only after a 10 minute grace period from when the refund became pending. Alipay notes that a query sent too soon can miss a refund that later succeeds. The pending time comes from the REFUND_PENDING audit entry.
…hold fix(payment): validate order types, keep refund deductions while pending, load Stripe secret from local snapshot
…ore crediting
Order query:
- 彩虹易支付 and ZPay read act=order parameters from the URL query only;
a body-only request is answered with "No Act!", so order status queries
(reconcile, payment status checks) never reached the order. Parameters
are now sent in the URL query and mirrored in the form body for forks
that read the body.
- Decode status/money/trade_no leniently: PHP deployments commonly return
integers as strings ("status":"1"), which previously failed the whole
response decode. JSON null falls back to the nested data object.
- Treat a non-success code as a query error instead of an unpaid order,
and require an echoed out_trade_no to match the queried order.
- Strip the request URL (which carries the merchant key) from transport
errors before they are returned.
Notifications:
- Add payment.NotificationConfirmer. After a notification is verified, the
webhook asks the same provider instance to confirm it; EasyPay queries
the upstream order and requires it to be paid with a matching amount and
trade_no (a missing notify trade_no is filled from the query). Failures
are recorded as PAYMENT_UPSTREAM_UNCONFIRMED and answered with 502 so the
gateway retries; the order stays pending and reconcile still applies.
- EasyPay notifications must carry the instance's configured pid, and
field values other than name must be plain tokens.
- Return URLs must use a plain hostname or IP literal without userinfo.
fix(payment): fix EasyPay order queries and confirm notifications before crediting
# Conflicts: # backend/internal/service/gemini_chat_completions_compat_service.go
feat(gemini): 兼容入口转发思考档位并流式返回思考摘要
grok-build / Codex 等 Rust serde 客户端把 Response.created_at 与事件的 sequence_number 当必填字段,缺失即 `missing field 'created_at'` 整轮读不出响应。 #5601 只补了 apicompat 两座桥的合成路径,以下出口仍会送出缺字段的对象: - 合成的 response.failed:buildOpenAIResponseFailedSSE(主流 + 透传)与 buildOpenAIWSHTTPBridgeFailedEvent 在流以裸 error 结束时补发终止帧, 缺 created_at,前者还缺 sequence_number。现统一写出:created_at 取本流 上游值(无则当前时间),sequence_number 沿用被替换的裸 error 帧序号(无则 0)。 - 上游原样透出:Grok 账号挂第三方中转时,上游 Response 对象可能不带 created_at,网关逐字节转发后客户端同样失败。现于 Responses 流式出口、 WS HTTP 桥(Grok WS 恒走此路)与非流式 JSON 出口,对缺失/null 的 created_at 补一个整条流固定的时间戳;上游已带时输出逐字节不变。 透传路径保持不改写上游事件,只观察 created_at 供合成终止帧对齐。 Fixes #8118
网关现会为缺 created_at 的 Response 对象补齐该字段(#8118),该测试以 "输出包含上游原始字节"断言模型改写,夹具缺 created_at 时输出多出补齐字段 而失配。夹具改为真实上游必带 created_at 的形态,测试意图(仅改写 model)不变。
网关现会为缺 created_at 的 Response 对象补齐该字段(#8118),该测试以 "输出包含上游原始字节"断言模型改写,夹具缺 created_at 时输出多出补齐字段 而失配。夹具改为真实上游必带 created_at 的形态,测试意图(仅改写 model)不变。
…ement fix(openai): respect GPT-5.5 retirement on ChatGPT upstreams
…earch-emulation fix(openai): emulate web_search on /v1/messages for OpenAI API key accounts
…ed-at fix(responses): 网关送出的 Responses 对象一律带 created_at,修复 grok-build 反序列化失败
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integrates the stable Sub2API v0.2.17 baseline (
060d0160417782cd962a0c7f460f6cd4565d4916), including Gemini 4 Argon/thinking support, Responses compatibility, subscription-model retirement, payment confirmation and atomic refund-hold fixes. The deterministic change map covers all 153 upstream paths. Existing pricing gates, request-time cost evidence, explicit-cache ownership, public authorization and immutable production assets remain protected; this release adds no SQL migration files.Upstream editor changes enter the existing shared account panel, with regression coverage through its public wrapper. Retired duplicate dialogs and Monitor V2 remain absent. Version snapshots now consistently use 0.2.17, and the channel-status screenshot pauses its refresh clock for repeatability. The existing exact braces advisory exception is renewed through 2026-11-18 after verifying the depth patch and the absence of a published fixed version.
Validation completed before pushing:
make test: Go ordinary/unit/integration, zero lint issues, Console 342 files / 2548 tests and Landing 18 files / 134 tests, using the CI-pinned tool versions.The release contract in
docs/upgrades/v0.2.17.mdrequires exact-main CI evidence, same-SHA immutable Backend/Edge images, fresh encrypted off-host archives with actual restore and new/old/new compatibility, unchanged original business-column fingerprints, Backend-first deployment, dedicated limited smoke requests and at least 30 minutes of observation. Production evidence is retained outside Git in the restricted release recovery set.