mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
Antigravity 账号的 Gemini 原生流式路径在上游空闲超过 gateway.stream_keepalive_interval (默认 10s)时会向下游写 ":\n\n" 作为心跳。SSE 规范允许注释行,但 Google 官方 SDK 里 go-genai 与 python-genai 都不会忽略它: - go-genai(Antigravity CLI 在用)api_client.go iterateResponseStream 对任何非 data: 前缀的 事件直接返回 "iterateResponseStream: invalid stream chunk: :",整个流中断; - python-genai _api_client.py 把非 data: 行当作错误 JSON 拼接后 json.loads,同样抛错。 Google 官方 API 从不下发注释行,这两个 SDK 从未处理过这种输入。js-genai 用规范的 event-stream 解析器,不受影响。 现在按下游 User-Agent / X-Goog-Api-Client 识别这两类 SDK(形如 google-genai-sdk/1.71.0 gl-go/go1.28 …、google-genai-sdk/x gl-python/3.12),命中时该路径 不再发注释心跳;其他客户端(curl、js-genai、Gemini CLI)行为不变。 生产复现:Antigravity CLI 1.2.5 经网关(sub2api 0.2.5)跑长思考请求,上游首个 chunk 超过 10s 时客户端报 "agent executor error: … iterateResponseStream: invalid stream chunk: :"。
36 lines
1.6 KiB
Go
36 lines
1.6 KiB
Go
package service
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// geminiClientRejectsSSEComments 判断下游客户端是否是「无法容忍 SSE 注释行」的 Google GenAI SDK。
|
||
//
|
||
// Gemini 原生流里我们用 ":\n\n" 作为空闲心跳(防止代理/Cloudflare Tunnel 断开),但官方 SDK 里
|
||
// 有两个实现不按 SSE 规范忽略注释行:
|
||
// - go-genai(Antigravity CLI 在用):api_client.go iterateResponseStream 对任何非 "data:" 前缀的
|
||
// 事件直接报 "invalid stream chunk: :",整个流中断;
|
||
// - python-genai:_api_client.py 把非 "data:" 行当作错误 JSON 拼接后 json.loads,同样抛错。
|
||
//
|
||
// js-genai 用的是规范的 event-stream 解析器,能正确忽略注释。Google 官方 API 从不下发注释行,
|
||
// 所以这两个 SDK 从未处理过这种输入。识别依据是 SDK 固定携带的 User-Agent / X-Goog-Api-Client,
|
||
// 形如 "google-genai-sdk/1.71.0 gl-go/go1.28 ..." 或 "google-genai-sdk/1.x gl-python/3.12"。
|
||
func geminiClientRejectsSSEComments(clientHint string) bool {
|
||
hint := strings.ToLower(strings.TrimSpace(clientHint))
|
||
if !strings.Contains(hint, "google-genai-sdk/") {
|
||
return false
|
||
}
|
||
return strings.Contains(hint, "gl-go/") || strings.Contains(hint, "gl-python/")
|
||
}
|
||
|
||
// downstreamRejectsSSEComments 从请求头识别下游客户端(同时看 User-Agent 与 X-Goog-Api-Client)。
|
||
func downstreamRejectsSSEComments(c *gin.Context) bool {
|
||
if c == nil || c.Request == nil {
|
||
return false
|
||
}
|
||
return geminiClientRejectsSSEComments(c.GetHeader("User-Agent")) ||
|
||
geminiClientRejectsSSEComments(c.GetHeader("X-Goog-Api-Client"))
|
||
}
|