mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 16:48:45 +08:00
stream=false 时上游仍可能回 SSE(其他 sub2api 实例、部分 OpenAI 兼容上游), 容量/限流错误经 HTTP 200 的 response.failed / error 终止事件回传。 handleSSEToJSON 与 handlePassthroughSSEToJSON 把所有终止事件塞进固定 502 (writeOpenAINonStreamingProtocolError),而几百行外的流式读取器对同一帧走 openAIStreamFailedEventShouldFailover / openAIStreamErrorEventShouldFailover 判定并返回 UpstreamFailoverError。 同一个上游、同一个事件,仅因请求上的 stream 标志而结果相反:流式换号, 非流式把上游原文直接抛给客户端,池里还有可调度账号也不换。 新增 nonStreamingTerminalFailureFailover,在两条非流式路径上复用流式的同一套 分类,并按 terminalType 分派——裸 error 帧走更保守的那个(仅正向识别为瞬时才 换号),response.failed 走完整的那个,与流式读取器逐字一致。 安全前提:响应体已被 ReadUpstreamResponseBody 完整缓冲,判定发生在写出任何 语义字节之前。能否真正换号仍由 handler 的 openAIForwardMayFailover 用 keepalive 调整后的写出量仲裁(#3887),service 侧只额外拒绝 IsResponseCommitted 与 account 为 nil 的场景,不重复实现心跳判定,避免与 handler 口径分叉。 行为变更:未被分类为不可重试的泛化 response.failed 在非流式路径上由「回写 502」 变为「换号」——这正是与流式对齐的结果。既有用例 TestHandleSSEToJSON_ResponseFailedReturnsProtocolError 传的是 nil account, 无账号则无可换对象,故其断言原样保留,仅更名为 ...WithoutAccountReturnsProtocolError 并注明原因;带真实账号的同一报文由新增用例钉死。 Fixes #5281