5671 Commits
Author SHA1 Message Date
Wesley Liddick 8f2cbb53d9 Merge pull request #8177 from feeeei/fix/gemini-negative-input-tokens
fix(gemini): prompt 被拦截只报缓存命中时 input_tokens 不再为负
2026-10-19 20:45:15 +08:00
Wesley Liddick 0fa02816af Merge pull request #8168 from feeeei/fix/gemini-stream-client-disconnect-billing
fix(gemini): 流式中途客户端断开时按已收集用量计费
2026-10-19 20:45:02 +08:00
shaw ab71e12ec4 fix(images): 异步生图转存的图片下载与同步回填策略对齐
异步生图任务把上游返回的图片 url 下载后转存对象存储,此前沿用默认 http.Client,
与同步 Images 端点 url 回填(c227863d)的下载策略不一致。

- 下载目的地为 localhost、回环、私网、链路本地、CGNAT 等非公网地址时不下载,
  重定向的每一跳同样校验;直连时在 socket 层按实际连接 IP 判断
- 继续支持环境变量代理:经代理时先校验目标主机的解析结果
- 响应声明类型与字节嗅探都不是图片时任务失败,不再按 png 落盘
- 下载客户端进程内共享,复用连接池;超时与大小上限不变
2026-10-19 17:51:18 +08:00
feeeei 9c41b32695 fix(gemini): prompt 被拦截只报缓存命中时 input_tokens 不再为负
- Vertex 拦截 prompt(promptFeedback.blockReason,如 PROHIBITED_CONTENT)时 usageMetadata
  可能只带 cachedContentTokenCount、缺 promptTokenCount,promptTokenCount − cachedContentTokenCount
  得到负的 input_tokens 与负费用
- extractGeminiUsage 与 antigravity 非流式 / 流式(累计用量、message_start)四处换算改为差值下限 0,
  缓存命中仍照上游所报计入 cache_read_input_tokens
- 补充表测试、原生流式拦截用例与 antigravity 两条路径用例
2026-10-17 19:09:23 +08:00
feeeei 2b1f129385 fix(gemini): 流式中途客户端断开时按已收集用量计费
- 原生 /v1beta、Claude 兼容 /v1/messages、Chat 兼容三条流式路径:读取中断由客户端
  断开引起(context.Canceled 或请求 context 已取消),且已观测到 token 用量或已回吐
  图片时,以已收集结果收尾并返回成功,handler 照常提交 usage;ForwardResult 标记
  ClientDisconnect
- 读到结束分片后断开按完整用量计费;中途断开按断开前最后一份 usageMetadata 计费,
  断开前无任何用量时仍不计费
- 图片数:收到 finishReason 前断开只计已送达的图片(原生按已观测 inlineData,Claude/
  Chat 兼容流不转发图片计 0);收到 finishReason 后断开与正常完成同口径
- Chat 兼容流写失败断开的既有返回同样带上断开标记与结束状态
- 原生路径读错误时不处理未以换行结尾的残行,避免截断的 usageMetadata 覆盖已收集用量
- 上游 context 不解绑:Gemini 兼容链路没有流空闲超时,解绑后上游挂起会让读取无限期阻塞
- 客户端未断开时的上游读错误、非流式客户端断开、Antigravity 非流式转换的读错误
  行为不变
2026-10-17 01:00:51 +08:00
github-actions[bot] b0a02a3304 chore: sync VERSION to 0.2.17 [skip ci] 2026-10-16 04:42:02 +00:00
Wesley Liddick 060d016041 Merge pull request #8140 from Wei-Shaw/fix/issue-8118-responses-created-at
fix(responses): 网关送出的 Responses 对象一律带 created_at,修复 grok-build 反序列化失败
2026-10-16 12:28:11 +08:00
Wesley Liddick b14f316ca2 Merge pull request #8080 from SipengXie2024/fix/openai-messages-web-search-emulation
fix(openai): emulate web_search on /v1/messages for OpenAI API key accounts
2026-10-16 12:27:52 +08:00
Wesley Liddick 43157c478e Merge pull request #8121 from Larry-Labs/fix/gpt55-subscription-retirement
fix(openai): respect GPT-5.5 retirement on ChatGPT upstreams
2026-10-16 12:27:39 +08:00
shaw 71e6b6a971 test(responses): 模型改写转发测试夹具补齐上游 created_at
网关现会为缺 created_at 的 Response 对象补齐该字段(#8118),该测试以
"输出包含上游原始字节"断言模型改写,夹具缺 created_at 时输出多出补齐字段
而失配。夹具改为真实上游必带 created_at 的形态,测试意图(仅改写 model)不变。
2026-10-16 11:38:13 +08:00
shaw f2e5b55f3e test(responses): 模型改写转发测试夹具补齐上游 created_at
网关现会为缺 created_at 的 Response 对象补齐该字段(#8118),该测试以
"输出包含上游原始字节"断言模型改写,夹具缺 created_at 时输出多出补齐字段
而失配。夹具改为真实上游必带 created_at 的形态,测试意图(仅改写 model)不变。
2026-10-16 11:37:24 +08:00
shaw bdfe215cbb fix(responses): 网关送出的 Responses 对象一律带 created_at,修复 grok-build 反序列化失败
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
2026-10-16 11:22:48 +08:00
shaw a431e53f9e Merge remote-tracking branch 'origin/main' into pr-8114
# Conflicts:
#	backend/internal/service/gemini_chat_completions_compat_service.go
2026-10-16 10:09:46 +08:00
shaw 1b92038eaf fix(payment): fix EasyPay order queries and confirm notifications before 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.
2026-10-16 09:39:55 +08:00
xugao 86f8443045 fix(openai): evaluate retirement after public alias projection 2026-10-15 22:42:45 +08:00
shaw f37b69b779 fix(payment): treat Alipay refund query errors as pending and delay not-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.
2026-10-15 22:26:53 +08:00
shaw 83337a0c65 fix(payment): query Alipay refund status and close unsupported order 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.
2026-10-15 22:03:15 +08:00
shaw 44e35fbf84 fix(payment): finalize failed pending refunds atomically
- 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.
2026-10-15 21:38:57 +08:00
harukaandClaude Opus 5.5 7c6f70680d fix(payment): validate order types and keep refund deductions while pending
- 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>
2026-10-15 21:38:48 +08:00
xugao 26aab6dc07 fix(openai): respect GPT-5.5 retirement on ChatGPT upstreams 2026-10-15 20:53:15 +08:00
Wesley Liddick 698d142468 Merge pull request #8088 from wucm667/fix/issue-8077-workspace-aware-import
fix(openai): isolate Codex imports by workspace
2026-10-15 16:40:38 +08:00
Wesley Liddick ddef05527e Merge pull request #8079 from wucm667/fix/issue-8074-anthropic-base-url
fix(gateway): honor Anthropic protocol base URLs.
2026-10-15 16:40:19 +08:00
Wesley Liddick 58b5e858c8 Merge pull request #8094 from wucm667/feat/issue-8092-configurable-min-output
feat(apicompat): configure minimum output token floor
2026-10-15 16:40:06 +08:00
feeeei 64211de8de feat(gemini): 兼容入口转发思考档位并流式返回思考摘要
- 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 兼容路径一致
2026-10-15 16:26:46 +08:00
shaw 9d3cc4507f fix(antigravity): keep legacy default passthroughs and bill Claude 5.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).
2026-10-15 15:47:42 +08:00
shaw 8afd2abd97 Merge remote-tracking branch 'origin/main' into contrib/gemini-4-argon-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.
2026-10-15 15:37:14 +08:00
Wesley Liddick dcde68aa58 Merge pull request #8078 from feeeei/feat/gemini-4-argon-pricing
feat(gemini): 补齐 Gemini 4 Argon 计费
2026-10-15 15:27:15 +08:00
Wesley Liddick abdc82725f Merge pull request #8076 from feeeei/fix/gemini-vertex-sa-and-error-policy
fix(gemini): Vertex SA 已删除时停用账号并换号,401 临时不可调度规则首次命中不再永久停用
2026-10-15 15:27:01 +08:00
Wesley Liddick 3b83e045ed Merge pull request #8060 from feeeei/fix/gemini-vertex-shared-capacity-429
fix(gemini): Vertex 共享容量不足的 429 不冷却账号,改为同号退避重试
2026-10-15 15:26:51 +08:00
Wesley Liddick fdc7f1fa01 Merge pull request #8087 from coder-zhw/fix/zhipu-credit-limit-window-classification
fix(zhipu): 修正 GLM Coding Plan 5h/weekly 用量窗口标反 / fix swapped GLM coding plan usage windows
2026-10-15 15:26:38 +08:00
shaw d19265ddb2 fix(gateway): 拒绝含 OpenAI 网关 OAuth 的 composite 歧义路由,防止凭据误入 Anthropic 管线
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 不受限(上游可能兼容多种协议格式)。
2026-10-15 14:57:03 +08:00
coder-zhw 1774b79dcb fix(zhipu): 修正 GLM Coding Plan 5h/weekly 用量窗口标反
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)。
2026-10-15 14:56:00 +08:00
Long Li feab6d80dc fix(antigravity): preserve tool_result images, native Gemini functionCall/thoughtSignature, and composite /v1beta routing 2026-10-15 06:55:32 +09:00
wucm667 2a8cb088af feat(apicompat): configure minimum output token floor 2026-10-15 01:56:00 +08:00
Long Li 373d43533a feat(antigravity): support Gemini 4 Argon and accurate upstream model catalogs 2026-10-15 02:06:23 +09:00
wucm667 16611a8ee0 fix(openai): isolate Codex imports by workspace 2026-10-14 18:34:52 +08:00
SipengXie2024 7de15113c7 fix(openai): exclude OAuth accounts from 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.
2026-10-14 16:44:53 +08:00
SipengXie2024 26e55cf332 fix(openai): emulate web_search on /v1/messages for OpenAI API Key accounts
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.
2026-10-14 07:02:52 +00:00
wucm667 867563ae76 fix(gateway): honor Anthropic protocol base URLs 2026-10-14 14:58:26 +08:00
feeeei c6c9935ae3 feat(gemini): 补齐 Gemini 4 Argon 计费
- 内置兜底价: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 档价格;所有提示词长度同价,
  不带长上下文阶梯;只支持隐式缓存,不带存储价
2026-10-14 14:22:17 +08:00
feeeei 14cd587175 fix(gemini): 每个上游错误响应只检查一次错误策略
- 三条 Gemini 转发路径在重试循环内和循环外各对最终上游错误响应检查一次错误策略。
  配了账号级 401 临时不可调度规则的 API Key / Vertex 账号首次收到 401 时,第一次
  检查写入 401 暂停记录,第二次检查读回该记录判定为重复 401,同一请求内即升级为
  error 永久停用
- 循环内的检查结果带到循环外复用;循环内未检查的最终响应(Claude 兼容层的签名 400
  分支、原生路径的 cachedContent 透传分支)仍在循环外检查
2026-10-14 12:00:44 +08:00
feeeei 20c4bf5ae5 fix(gemini): Vertex SA 已不存在时账号置为 error 并换号
- 换取 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 端点地址改为包级变量,测试中替换为本地服务
2026-10-14 11:58:37 +08:00
feeeei 89ad4c2caa fix(gemini): Vertex 共享容量不足的 429 不冷却账号,改为同号退避重试
- Vertex 按量付费返回的 "Resource exhausted ... try again later"
  (RESOURCE_EXHAUSTED、无错误明细)表示共享资源池当时没有容量,与账号额度
  无关;此前一律冷却账号 1 分钟,冷却期间账号被调度快照剔除,绑定显式缓存
  的请求因无法换号直接 503
- 这类 429 只记日志、账号保持可调度,由请求内同号 1/2/4/8s 退避重试消化,
  重试用尽后未绑定缓存的请求照常换号
- 带 retryDelay、文案重试时长、配额/预留吞吐字样或其他形态的 429 仍按账号
  自身限速冷却;非 Vertex 账号行为不变
2026-10-13 23:51:43 +08:00
github-actions[bot] 4d9ac3e482 chore: sync VERSION to 0.2.16 [skip ci] 2026-10-13 07:20:40 +00:00
Wesley Liddick 01842576b3 Merge pull request #7810 from wucm667/fix/issue-6247-responses-chat-fallback
fix(gateway): retry rejected structured input through chat endpoint
2026-10-13 15:01:20 +08:00
Wesley Liddick 49b2122dc6 Merge pull request #7991 from wucm667/fix/issue-7988-international-quota
fix(quota): recognize international coding endpoints
2026-10-13 15:00:31 +08:00
Wesley Liddick 8d7d3185e9 Merge pull request #8038 from wucm667/fix/issue-8035-gemini-tool-result-images
Preserve images from tool results
2026-10-13 15:00:04 +08:00
Wesley Liddick bba85923b9 Merge pull request #8046 from wucm667/fix/issue-8040-antigravity-image-rate-limit
Isolate Antigravity image model rate limits
2026-10-13 14:59:39 +08:00
shaw 9d5e63a598 合并 main 并解决 PR #7828 的测试文件冲突
完整保留临时不可调度规则作用范围测试与 Gemini 显式缓存测试;保留双方生产代码改动。
2026-10-13 14:59:02 +08:00
Wesley Liddick 53c21fed25 Merge pull request #8006 from feeeei/feat/gemini-explicit-cache
feat(gemini): 支持显式上下文缓存,并完善 Vertex 与第三方 API Key 账号
2026-10-13 14:51:11 +08:00