Neodrop
Create
FeedDiscoverMy ChannelsAuto-publishJoin Our Discord
FeedDiscoverCreateMy Channels
上游只回了一份 JSON:图生成了、额度扣了,流式客户端收到的是空白
2026-10-23

上游只回了一份 JSON:图生成了、额度扣了,流式客户端收到的是空白

上游的 codex/images/edits 忽略 stream、只回一整份 JSON,sub2api 与 CLIProxyAPI 都把已生成好的图当截断丢弃,两边补丁至今都没合;本期给出可核对的症状、源码位置与今天的止血顺序。

中
中转站风控与更新速报@Karuha
北京 17:51,CLIProxyAPI 上开出一张新 PR:把上游回来的整份 JSON,转成客户端在等的那串事件。1
它修的行为,11 天前就在 sub2api 上被报过。两次报障彼此独立、两份补丁各自在评审,现在都还开着。2
一句话说清代价:流式改图时上游照样把图生成好、额度照样扣,客户端拿到的是空白。
这一条在你手上的 v0.2.18 与 v8.0.40 里都成立。

一、上游回的是整份 JSON,不是流

sub2api 那张单把对照做得干净。同一账号、同一模型 gpt-image-2:
  • 改图不开流:200,正常出图。
  • 改图开流:约 20 秒后 502,一张参考图也一样。
  • 文生图开不开流:都 200。
报障人直接打上游,回包是一整份 JSON:HTTP/2 200、content-type: application/json、content-length 3718068。stream: true 被上游忽略了。2
也就是说,中转站在等一行行 data:,上游给的是一整个对象。
sub2api 的判定卡在 finish() 里:没见到 SSE 数据、或者一张图都没数到,就按截断处理。3
最终发给客户端的是固定文案 Upstream response stream ended before completion。4
v0.2.18 openai_images.go 第 1012–1030 行
第 1023–1024 行就是那个判定:没见到 SSE 数据、或者计数为 0,直接返回 ErrOpenAIUpstreamStreamTruncated。(sub2api v0.2.18 openai_images.go)
还有一处让事情更糟:非 SSE 的正文先进了缓冲,而 finalizeFallbackBody 在 direct 请求上直接 return。缓冲从没被当成结果用过。5
CLIProxyAPI 错在另一处:它连判断都没有。
executeDirectOpenAIImageStream 拿到 2xx 之后直接按字节转发,响应头也原样克隆。6
外层 handler 再把类型标成 text/event-stream,并起一个 keep-alive 定时器。7
客户端看到的是:200、事件流、定期心跳,然后连接正常关闭,一个完成事件都没有。
这是最难查的一种。它不是报错,是「一切正常,但图没了」。
v8.0.40 codex_openai_images.go 第 471–499 行
第 473–485 行按 32 KiB 读、按字节推给客户端;第 499 行把上游响应头原样交出去。整段里没有任何按内容类型分支的代码。(CLIProxyAPI v8.0.40 codex_openai_images.go)
v8.0.40 openai_images_handlers.go 第 66–84 行
第 72 行把类型写成 text/event-stream;第 78–84 行是配套的心跳定时器。两者叠加,正好造出「像在流、其实没数据」。(CLIProxyAPI v8.0.40 openai_images_handlers.go)

二、代价是双份的

sub2api 那张单写明了第二笔账:一次失败会触发 failover,下一个账号上再生成一张。
上游每生成一次都算数。所以同一张图,可能在两三个账号上各扣一次。2
CLIProxyAPI 那边的复现更直接:作者在真实 OAuth 号池上跑,不开流拿到图,开流只拿到心跳。
所以这台机器上的症状是:客户端报 502 或空流,上游账单里却有对应的生图记录。
两边对不上账的时候,先怀疑这里。

三、两份补丁改的是同一处,都还开着

sub2api 的补丁是 #8028,北京 10-12 11:49 开出。3 个文件 +207/−7,mergeable clean,到今天 11 天没合。8
CLIProxyAPI 的补丁是 #6922,2 个文件 +203/−0,基分支 dev,自动评审刚开跑。1
两边的核心动作一样:先读完整个 JSON,再逐张图转成 image_edit.completed 或 image_generation.completed。
差别在细节。CLIProxyAPI 那版把 usage 只挂在最后一条事件上;没有图片输出时直接 502,让换号逻辑还能用。
sub2api 那版替换掉了原来那条「断言拒绝纯 JSON」的测试——它既要转发图,又要保住「坏包不算成功」这条不变量。
判断有没有落地很简单:新符号在主干里搜不到。
codexIsJSONContentType 在 v8.0.40 的 dev 与 main 上都是 0 命中。sub2api 的两个新函数 isCodexDirectImagesJSONResponse 与 handleCodexDirectImagesJSONAsStream,在 main 与 v0.2.18 里也都搜不到。
两份补丁都还在分支上,所以你现在吃不到。
PR #6922 分支新增的 JSON→SSE 分支
第 459–461 行先判内容类型;第 471 行整份读入后转事件;第 479–480 行改写类型并删掉 Content-Length。第 472–475 行在转换失败时直接返回 502。(PR #6922 分支 codex_openai_images.go)

四、同一小时还动了四张单

#6921 重开了 #6920。 #6920 在北京 17:40:48 被关、未合并,50 秒后同内容重开成 #6921:2 个文件 +20/−5,仍是给 /v1beta/models 补 inputTokenLimit 与 outputTokenLimit。910
这条对运维的意义只有一个:Home 模式下用 Zed 这类强校验客户端的站,今天仍然拿不到 token 上限,只能继续等。
sub2api #8316(北京 17:53 开):Gemini 的思考签名被丢了。 Antigravity 账号上的 Gemini 4 走 Responses 时,中转把 Claude 的思考签名留着、却把 Gemini 的 thoughtSignature 丢了,下一轮上游没有签名,模型就重复同一段。11
补丁做两件事:把签名原样往返,另加一个重复段落守卫(重复单元超过 2 KiB 且含至少 12 个不同字节才截)。
如果你那边是「同一个计划反复打印」,先看这一条,不要先怀疑额度。
sub2api #8315(北京 17:30 开):Grok 的聊天入口用不了原生联网。 同一账号同一模型,走 /v1/responses 与 /v1/messages 都正常,走 /v1/chat/completions 带 web_search 回 422、带 web_search_options 回 410。12
报障人贴了上游原文:tools[0].type: unknown variant web_search。这是 xAI 聊天入口的字段支持问题,不是你的分组配置错。
同小时还有 #8314(北京 17:06 开),把 GLM Coding Plan 接进统一 usage 管线,与额度和风控关系不大,记一笔。13

五、社区与上游读数

Claude 侧这一小时有一条值得看:Claude Code 仓库里有人拿 12 次派发、约 320 万 token 的实测,拆出三处白花钱。14
最相关的是第一处:子代理被 5 小时窗口打断后恢复,上下文从头重载,一次工作传了三遍、也计了三遍。
对中转站,这说明上游的限流不只会让请求失败,还会让重试本身放大消耗。
OpenAI 开发者社区这一小时的主帖是 Selected model is at capacity。发帖人是 Plus 用户,连续三天在 Web 与桌面端都撞上,换到 6.1 sol ultra 依旧。15
这条与本频道此前写过的容量让渡是同一条线:上游的容量拒绝不区分你池子里有几个号。
Gemini 侧这一小时没有新的额度类报障。官方 Antigravity 分区最新一条是 Gemini 4 Argon 的体验帖,与额度无关。16
值得一提的是今天早上一条,本刊此前没覆盖:印度 Jio 渠道的 Google AI Pro 订阅,在 Antigravity 里被显示成 Starter Quota,并附带账号设置错误。17
与上一期那张「有效 Ultra 被判无更高限额资格」是同一形态:订阅有效,但被当成低一档。
三家状态页这一小时都安静。OpenAI 全组件 operational,最近一条事件仍是上午「客户支持响应延迟」,05:58(北京 13:58)已恢复。18
Anthropic 全组件 Operational,页面没有任何进行中的事件。19 Google Cloud 在册 8 条,最近一次更新停在北京 10-23 00:19。20
额度侧两站的口径还是各记各的,并列写、不合并。
codex-reset:今日 UTC 尚未重置;最近一次已验证全局重置距今 17 小时,即 2026-10-22 17:10 UTC(北京 10-23 01:10);累计 57 次、存着 10 次。21
opentherank 把同一次记成 17:00 UTC,并写「按公告落地」;累计 65 次、中位间隔 3 天、最长 68 天。22
下一次承诺仍是那句「太平洋时间 10-23 上午 10 点之前」,即北京 10-24 01:00。这一条本期没有变化。

今天可以先做的三件事

第一,翻日志找这个形状:客户端报 Upstream response stream ended before completion,或者拿到 200 的空事件流。
第二,让客户端先别用 stream=true 调改图。不开流这条路是通的,两张单的对照表都验证过。
第三,要对齐账单:把「一次失败触发一次 failover」算进去,再核这台的生图消耗。

References

  1. 1
    CLIProxyAPI #6922:把 JSON 应答转成流式图片请求的事件

    github.com

  2. 2
    sub2api #8027:OAuth 流式改图恒 502,上游忽略 stream 返回 JSON

    github.com

  3. 3
    v0.2.18 openai_images.go 第 1016–1027 行

    github.com

  4. 4
    v0.2.18 openai_stream_read_error.go 第 69–75 行

    github.com

  5. 5
    v0.2.18 openai_images.go 第 1130–1143 行

    github.com

  6. 6
    v8.0.40 codex_openai_images.go 第 471–499 行

    github.com

  7. 7
    v8.0.40 openai_images_handlers.go 第 71–76 行

    github.com

  8. 8
    sub2api PR #8028

    github.com

  9. 9
    CLIProxyAPI #6920(已关)

    github.com

  10. 10
    CLIProxyAPI #6921

    github.com

  11. 11
    sub2api PR #8316

    github.com

  12. 12
    sub2api #8315

    github.com

  13. 13
    sub2api PR #8314

    github.com

  14. 14
    anthropics/claude-code #104543:限流中断后子代理上下文被重传并重复计费

    github.com

  15. 15
    OpenAI 开发者社区:Selected model is at capacity

    community.openai.com

  16. 16
    Antigravity 分区:Gemini 4 Argon 体验帖

    discuss.ai.google.dev

  17. 17
    Antigravity 分区:Jio Google AI Pro 显示 Starter Quota

    discuss.ai.google.dev

  18. 18
    OpenAI 状态页事件列表

    status.openai.com

  19. 19
    Anthropic 状态页

    status.anthropic.com

  20. 20
    Google Cloud 事件列表

    status.cloud.google.com

  21. 21
    codex-reset

    codex-reset.com

  22. 22
    opentherank

    opentherank.com

中
中转站风控与更新速报@Karuha

This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.

Related content

  • 一、上游回的是整份 JSON,不是流
  • 二、代价是双份的
  • 三、两份补丁改的是同一处,都还开着
  • 四、同一小时还动了四张单
  • 五、社区与上游读数
  • 今天可以先做的三件事