Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions backend/internal/handler/gateway_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ const (
type SSEPingFormat string

const (
// SSEPingFormatClaude is the Claude/Anthropic SSE ping format
SSEPingFormatClaude SSEPingFormat = "data: {\"type\": \"ping\"}\n\n"
// SSEPingFormatClaude uses SSE comment format: keeps HTTP connection alive without
// being yielded by Anthropic SDK, so client idle watchdogs work correctly.
SSEPingFormatClaude SSEPingFormat = ": keepalive\n\n"
// SSEPingFormatNone indicates no ping should be sent (e.g., OpenAI has no ping spec)
SSEPingFormatNone SSEPingFormat = ""
// SSEPingFormatComment is an SSE comment ping for OpenAI/Codex CLI clients
Expand Down
10 changes: 4 additions & 6 deletions backend/internal/service/antigravity_gateway_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4053,9 +4053,8 @@ func (s *AntigravityGatewayService) handleClaudeStreamingResponse(c *gin.Context
if time.Since(lastDataAt) < keepaliveInterval {
continue
}
// SSE ping 事件:Anthropic 原生格式,客户端会正确处理,
// 同时保持连接活跃防止 Cloudflare Tunnel 等代理断开
if !cw.Fprintf("event: ping\ndata: {\"type\": \"ping\"}\n\n") {
// SSE 注释格式 keepalive:在 HTTP 层保持连接活跃,不干扰客户端 idle watchdog。
if !cw.Fprintf(": keepalive\n\n") {
logger.LegacyPrintf("service.antigravity_gateway", "Client disconnected during keepalive ping (antigravity claude), continuing to drain upstream for billing")
continue
}
Expand Down Expand Up @@ -4457,9 +4456,8 @@ func (s *AntigravityGatewayService) streamUpstreamResponse(c *gin.Context, resp
if time.Since(lastDataAt) < keepaliveInterval {
continue
}
// SSE ping 事件:Anthropic 原生格式,客户端会正确处理,
// 同时保持连接活跃防止 Cloudflare Tunnel 等代理断开
if !cw.Fprintf("event: ping\ndata: {\"type\": \"ping\"}\n\n") {
// SSE 注释格式 keepalive:在 HTTP 层保持连接活跃,不干扰客户端 idle watchdog。
if !cw.Fprintf(": keepalive\n\n") {
logger.LegacyPrintf("service.antigravity_gateway", "Client disconnected during keepalive ping (antigravity upstream), continuing to drain upstream for billing")
continue
}
Expand Down
6 changes: 3 additions & 3 deletions backend/internal/service/gateway_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6939,9 +6939,9 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
if time.Since(lastDataAt) < keepaliveInterval {
continue
}
// SSE ping 事件:Anthropic 原生格式,客户端会正确处理
// 同时保持连接活跃防止 Cloudflare Tunnel 等代理断开
if _, werr := fmt.Fprint(w, "event: ping\ndata: {\"type\": \"ping\"}\n\n"); werr != nil {
// SSE 注释格式 keepalive:在 HTTP 层保持连接活跃(防止 Cloudflare Tunnel 等代理因空闲断开)
// 但不会被 Anthropic SDK yield 给消费者,不干扰客户端(如 Claude Code)的 idle watchdog。
if _, werr := fmt.Fprint(w, ": keepalive\n\n"); werr != nil {
clientDisconnected = true
logger.LegacyPrintf("service.gateway", "Client disconnected during keepalive ping, continuing to drain upstream for billing")
continue
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/service/openai_gateway_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,8 @@ func (s *OpenAIGatewayService) handleAnthropicStreamingResponse(
if time.Since(lastDataAt) < keepaliveInterval {
continue
}
// Send Anthropic-format ping event
if _, err := fmt.Fprint(c.Writer, "event: ping\ndata: {\"type\":\"ping\"}\n\n"); err != nil {
// SSE 注释格式 keepalive:在 HTTP 层保持连接活跃,不干扰客户端 idle watchdog。
if _, err := fmt.Fprint(c.Writer, ": keepalive\n\n"); err != nil {
// Client disconnected
logger.L().Info("openai messages stream: client disconnected during keepalive",
zap.String("request_id", requestID),
Expand Down