Repository navigation
Upgrade to Sub2API v0.2.19 with preserved product and migration contracts - #69
Merged
Merged
Conversation
- 原生 /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 非流式转换的读错误 行为不变
grok-4.7-fast 没有专属价格,落入「未知 Grok 文本模型」兜底按 grok-4.6 价卡计费,与普通 grok-4.7 同价($2 / $0.50 / $6),少收一半。 - 内置兜底价新增 grok-4.7-fast:<200k 为 $4 输入 / $1 缓存读取 / $12 输出 (每百万 token,标准价 2 倍);>=200k 整次请求为 $6 / $1.50 / $18, 即 Fast 基础价 1.5 倍,阈值口径与其它 xAI 价卡一致(达到即进高档) - grok-4.7-build-fast(Grok Build 侧的 Fast ID,见 #7502)同样命中该价卡 - 本地价格快照新增 grok-4.7-fast 条目,带 xai/ 前缀的写法也能识别 价格来源:https://docs.x.ai/developers/pricing(Grok 4.7 Fast,仅 Cursor 与 Grok Build 提供)。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Vertex 拦截 prompt(promptFeedback.blockReason,如 PROHIBITED_CONTENT)时 usageMetadata 可能只带 cachedContentTokenCount、缺 promptTokenCount,promptTokenCount − cachedContentTokenCount 得到负的 input_tokens 与负费用 - extractGeminiUsage 与 antigravity 非流式 / 流式(累计用量、message_start)四处换算改为差值下限 0, 缓存命中仍照上游所报计入 cache_read_input_tokens - 补充表测试、原生流式拦截用例与 antigravity 两条路径用例
handleCCStreamingFromAnthropic 与 handleResponsesStreamingResponse 写客户端失败时 立即返回,message_delta 的 output_tokens 未合并,usage 记为 0。改为断开后停止向 下游写入、继续读完上游合并 usage,与原生 /v1/messages 路径一致;ForwardResult 标记 ClientDisconnect。 两条路径改用 anthropicNativeLinePump 读上游,按 gateway.stream_data_interval_timeout 限制逐行读间隔,上游挂住时关闭 body 并按已累计 usage 收尾。
异步生图任务把上游返回的图片 url 下载后转存对象存储,此前沿用默认 http.Client, 与同步 Images 端点 url 回填(c227863d)的下载策略不一致。 - 下载目的地为 localhost、回环、私网、链路本地、CGNAT 等非公网地址时不下载, 重定向的每一跳同样校验;直连时在 socket 层按实际连接 IP 判断 - 继续支持环境变量代理:经代理时先校验目标主机的解析结果 - 响应声明类型与字节嗅探都不是图片时任务失败,不再按 png 落盘 - 下载客户端进程内共享,复用连接池;超时与大小上限不变
fix(images): 异步生图转存的图片下载与同步回填策略对齐
…ect-billing fix(gemini): 流式中途客户端断开时按已收集用量计费
fix(gemini): prompt 被拦截只报缓存命中时 input_tokens 不再为负
- 新增 AccountTypeSupportsQuota / SupportsAccountQuota(apikey/bedrock/service_account) 作为配额账号类型的唯一判定,累加、超限停调度、额度通知、接口输出与计费仓储统一改用; 池模式等其它特性仍只看 IsAPIKeyOrBedrock - service_account 配额语义与 apikey 一致:配置了任一上限才累加,用满停调度,跨阈值发额度通知 - 计费事务中账号已删除时跳过账号配额计数,与 API Key 对称,用户扣费不再整笔回滚 - 账号额度通知只对配置了上限的账号做检查 - 前端创建/编辑弹窗对 Vertex 开放配额设置,设上限后用量列显示配额进度条; 账号类型判断收敛到 supportsAccountQuota
- 门禁作用于展示该开关的 Gemini 系分组(gemini/antigravity/composite);无分组 Key、 anthropic 等其他平台分组不受影响,OpenAI/Grok 仍走各自门禁 - 生图判定:复用图片接口的 gemini-*-image 判定(isGeminiCompatibleImageModel),或请求体 generationConfig.responseModalities 含 IMAGE(camelCase/snake_case、字符串/整数枚举、 重复键逐个检查);countTokens 不拦 - 两道检查:/v1/messages、/v1/chat/completions、/v1/responses、/v1beta/models 入口在 选号前按客户端请求名与渠道映射名预检;Gemini 兼容与 Antigravity 的 6 个转发函数在 账号模型映射后再按上游模型名检查,覆盖账号级别名 - 拒绝统一返回 400(Gemini 原生 INVALID_ARGUMENT,其余 invalid_request_error), 不触发换号与账号处罚,下游 sub2api 把本实例当上游时不会因此停用账号; 请求标记为本地功能门禁 - 升级兼容:迁移 246 把存量 gemini/antigravity 分组的生图开关置为开启;新建分组与简易 模式默认分组对 gemini/antigravity 默认开启(与 grok 同为 OR 语义,创建时传 false 不生效), 前端创建表单同步默认值;composite 不回填(同一开关还控制 OpenAI 生图),未开启的 composite 分组升级后 Gemini 生图返回 400 - 未覆盖:最终模型名不符合 gemini-*-image 形态(如上游中转自定义的 nano-banana 别名) 且请求体未声明 IMAGE 模态;门禁按 Key 所属分组判断,Claude Code 专用分组降级到 其他分组时仍按原分组
客户端在线时,Anthropic 账号的 chat/responses 兼容流在上游未发出结束事件 (message_stop、带 stop_reason 的 message_delta 或 [DONE])就中断,不再补发 finish_reason / [DONE] / response.completed 报成功: - 读间隔超时、读错误、message_start 之后直接 EOF:service 写出协议内终止 事件(CC 为 error 帧,Responses 为沿用流内 id 与 sequence_number 的 response.failed),返回错误并带上已观测到的 usage;读间隔超时调用 HandleStreamTimeout。 - 流内 event: error:立即结束读取,与 /v1/messages 共用 sseStreamErrorEventFailover 转为 UpstreamFailoverError,未输出前换号重试, 客户端在线时不计费。 - 两个 handler 的错误分支对带结果的错误照常入账。 客户端断开后的读取:stream_data_interval_timeout 未启用时按 180s 启用读间隔 上限;请求 ctx 取消也视为断开。复用 anthropicNativeStreamInterval。行泵在 计时器到期且已有缓冲行时优先交付数据,下游写入慢不再被判为上游空闲。
GetModelMapping 命中缓存前要计算 model_mapping 的签名:收集全部 key、 排序、再逐个写入 fnv64a。签名只存放在 Account 的未导出字段里,不持久化、 不序列化、不跨进程比较,排序的唯一作用是消除 Go map 的随机遍历顺序。 改为逐条目计算 FNV-1a(key、分隔字节、value 或非字符串标记)并经 fmix64 收尾后求和:内容相同签名相同,单条改值、改键、两个 key 互换 value 都能检出, 且不分配内存。30 条映射单次计算约 2.3µs/1 次分配降到约 0.8µs/0 次分配; 选号时每个候选账号每次调用 3 次。
setAccountModelRateLimitSnapshot 原地写 account.Extra 以及其中的 model_rate_limits map。选号阶段的 Anthropic Fable 阈值过滤 (filterAccountsBySchedulingThreshold → ApplyAccountSchedulingThreshold) 会对候选账号调用它;候选账号的 Extra 一旦与其它请求共享,原地写就会造成 并发 map 读写并污染共享对象。 改为复制旧的 limits 与 Extra 顶层、写入新条目后替换 account.Extra, 不修改任何已有 map,本请求后续读取到的结果不变。其余调用方 (ratelimit_cline、cline_usage、openai_images_balance)都通过同一个 *Account 读取结果,不依赖原地修改可见。
三个入口(Gemini 原生、/v1/messages 的 Gemini 分支与 Claude 路径) 每个请求都会查询分组的 antigravity:forced 快照桶。纯 Gemini/Anthropic 分组的该桶恒为空,空快照按未命中处理,于是每个请求都回源 PG 并重新 发布快照,而判定结果只被 antigravity 账号的 503 原地重试与模型限流 预检查读取。 入口改为挂载延迟判定:antigravity 账号首次需要时才查询快照,同一请求 内只查一次;显式设置的 SingleAccountRetry 标记仍优先。其他平台的请求 不再触发该查询。
GetSnapshot 每次选号都 ZRANGE 全组账号 ID、分块 MGET 全组 sched:meta 并逐条 JSON 解码,Redis 出口、CPU 与 GC 都随分组号数线性增长。 - 每个 bucket 的解码结果常驻进程内存。每次读取用一次 MGET 取 ready、 active、全局修订号与 bucket 平台修订号(mixed 另加 antigravity),全部与 条目一致且条目未超过 10 秒才复用,否则重新加载。last_used 仍每次读取, 套用在本次调用独占的副本上,LRU 语义不变。 - 修订号是随机串:精简记录改写后写入所涉平台的修订号,与最后一批写入同一 pipeline 发出;删除记录、账号平台变化(SET ... GET 取回旧值比对)、 写入出错时写入全局修订号,出错路径在后台合并写入。 - 母本发布前预解析模型映射;副本在指针、长度与运行时版本都匹配时直接复用, 不计算签名。 - 未命中、空快照、记录缺失、解码失败、Redis 出错的返回与原实现一致;选中后 的整账号读取仍每次重新解码,转发路径对账号的原地修改不触及常驻对象。 真实 Redis 端到端(SelectAccountWithLoadAwareness,本机): 100 号:3.83ms → 0.73ms,Redis 出口 251KB → 7.0KB,分配 2.03MB → 0.23MB 1000 号:27.8ms → 1.92ms,Redis 出口 2.47MB → 25KB,分配 20.2MB → 1.97MB 每次都未命中:100 号 3.83ms → 3.10ms,1000 号 27.6ms → 21.9ms 常驻内存约为每号 7KB × 活跃 bucket 数;闲置超过 100 秒的条目在写入新 条目时清除。
…tadata-20261016 fix(openai): preserve overload retries after queued and metadata events
fix(accounts): persist platform and status filters
fix(security): clear dependency security scan failures
feat(account): Vertex 账号支持配额上限
…n-gate fix(gemini): 分组「允许当前分组生图」开关对 Gemini 系入口生效
fix(claude): align cc_version suffix with JavaScript UTF-16 indexing
fix(gemini): count fileData image outputs for billing
…ro-pricing fix(billing): retain DeepSeek V4 Pro pricing after September 14
fix(model-plaza): 修复模型广场定价被覆盖的问题
fix(usage): DeepSeek prompt_cache_hit_tokens 计为缓存读取
fix(gateway): 修复 OpenCode 转发 DeepSeek 时的 developer 角色 422
…rflow fix(pagination): keep page offsets from overflowing int
fix(images): reject oversized multipart uploads instead of truncating them
…on-call fix(antigravity): 非流式畸形工具调用返回错误而非空成功响应
…pdate The admin channel editor rendered and submitted only the anthropic dimension of features_config.web_search_emulation, while UpdateChannel replaces features_config as a whole block. Saving a channel from the UI therefore erased the openai / command_code / ... dimensions written via the API, silently disabling web search emulation for those platforms until they were written again. Backend: merge the key per platform dimension on update (request dimensions win, missing dimensions are inherited; a missing key inherits the whole value so clients unaware of the feature cannot wipe it; an explicit empty object still clears). Other features_config keys keep the historical whole-block replacement. Frontend: render and submit every platform in a whitelist that mirrors the backend account-level whitelist, including explicit false for disabled sections, so the stored value cannot go stale. Tests: merge unit tests + applyUpdateInput wiring tests (backend), and a source-contract spec for the channel editor (frontend). Reverting either side makes the new tests fail.
fix(admin): 管理员保护校验返回 400 而不是 500
golangci-lint (errcheck, check-type-assertions) flags single-value type assertions; switch to the two-value form so the assertions stay explicit.
govulncheck flags html.Parse quadratic text-node coalescing reached via parseOllamaCloudUsageHTML. Bump x/net (and its x/* siblings pulled by go get).
- 原先 /v1beta/models 与 /v1beta/models/{model} 从分组中按优先级挑一个账号透传其上游列表;
分组由多个第三方中转 Key 组成且各配 model_mapping 时,结果只反映选中账号的上游,
与 /v1/models(合并各账号映射)不一致,下游"同步上游支持的模型"会拿到残缺或不可路由的模型
- 分组内每个可调度 Gemini 账号都有生效映射时,列表返回映射键并集(并上混合调度
antigravity 账号的 Gemini 模型),分组白名单按 FilterForListing 与 /v1/models 同口径过滤,不再请求上游
- 详情按同一来源判断:键末尾 * 按前缀匹配,customtools 别名按请求路径的规则归一,未覆盖返回 404
- 有账号未配置映射(接受任意模型)或查询出错时,保持原选号透传逻辑
- 账号白名单"同步最新支持模型"、/v1/models 与 /v1beta/models 的兜底列表共用的三处 Gemini 默认列表 (前端 geminiModels、geminicli.DefaultModels、gemini.DefaultModels)统一为同一份 17 个现役模型: 3.x 生图、3-flash、3.1-pro-preview(-customtools)、3.1/3.5 flash(-lite)、3.6/3.7/3.8-flash、4-argon; 移除 2.0/2.5 系列与 3-pro-preview - 连接测试默认模型由 gemini-2.0-flash 改为 gemini-3.5-flash;Google One 账号仍用其保守模型集 - 前端下拉候选总表补入 antigravity 模型并去重,antigravity 账号白名单下拉不再只显示与其他平台重叠的条目 - 生图模型识别补上 gemini-3.1-flash-lite-image
…mensions fix(channels): 渠道保存不再覆盖 web_search_emulation 的平台维度
fix(gemini): /v1beta/models 按分组声明的模型应答,并刷新 Gemini 默认模型列表
…-config feat(gemini): 同账号重试次数改为可配置 gateway.max_same_account_retries_gemini
fix(gemini): 绑定显式缓存的请求在池模式上游网关账号上按配置同账号重试
…ight-recheck fix(models): 模型列表刷新进入 singleflight 后复查缓存,避免并发冷启动重复请求上游
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.
Upgrade the pinned Upstream Baseline from v0.2.17 to v0.2.19, including v0.2.18. Integrate scoped Administrator API Keys, optional per-image billing, Gemini/Grok catalogue and accounting fixes, and bounded stream-disconnect draining while preserving Zero One's semantic first-token timing, pricing isolation, request-time cost evidence, redeem claims and approved production UI.
The unmodified migrations 246–248 preserve existing Gemini/Antigravity image access under the new explicit gate, add scoped keys, and default image-count billing to false. The release controller compares the exact permitted permission projection while continuing to fingerprint all other original business columns. New upstream management UI is deferred; the API guide states the delivery boundary. Fix newly reported frontend dependency vulnerabilities, remove the withdrawn braces audit exception, align Go 1.27.3 across builds/CI, and pin reviewed version-badge screenshots without changing published assets or screenshot tolerances.
Validation completed before push:
GOFLAGS=-p=1 make test: Go ordinary/unit/integration suites, zero lint issues, Console 2551 tests, Landing 134 tests.Production deployment is gated on protected PR checks, exact-main CI/security evidence, immutable same-SHA image digests, fresh encrypted off-host restore and old/new compatibility rehearsals, a final drained backup, dedicated smoke tests and at least 30 minutes of observation. Release contract and reproducible path map:
docs/upgrades/v0.2.19.md.