From 52c2f4cb105a178b88a2ae04d5d32c895fd17e36 Mon Sep 17 00:00:00 2001 From: ResidualBlood Date: Sat, 26 Sep 2026 05:29:18 +0000 Subject: [PATCH 1/2] fix(antigravity): trigger failover retry on empty stream caused by MALFORMED_FUNCTION_CALL --- .../internal/pkg/antigravity/stream_transformer.go | 9 +++++++++ .../service/antigravity_gateway_compat_stream.go | 12 +++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/internal/pkg/antigravity/stream_transformer.go b/backend/internal/pkg/antigravity/stream_transformer.go index 902646aaab..13471ddddc 100644 --- a/backend/internal/pkg/antigravity/stream_transformer.go +++ b/backend/internal/pkg/antigravity/stream_transformer.go @@ -40,6 +40,7 @@ type StreamingProcessor struct { outputTokens int cacheReadTokens int imageOutputTokens int + hasContent bool } // NewStreamingProcessor 创建流式响应处理器 @@ -175,6 +176,11 @@ func (p *StreamingProcessor) MessageStartSent() bool { return p.messageStartSent } +// HasContent reports whether any substantive text, thinking, or tool calls were emitted. +func (p *StreamingProcessor) HasContent() bool { + return p.hasContent +} + // emitMessageStart 发送 message_start 事件 func (p *StreamingProcessor) emitMessageStart(v1Resp *V1InternalResponse) []byte { if p.messageStartSent { @@ -296,6 +302,7 @@ func (p *StreamingProcessor) processThinking(text, signature string) []byte { } if text != "" { + p.hasContent = true _, _ = result.Write(p.emitDelta("thinking_delta", map[string]any{ "thinking": text, })) @@ -350,6 +357,7 @@ func (p *StreamingProcessor) processText(text, signature string) []byte { })) } + p.hasContent = true _, _ = result.Write(p.emitDelta("text_delta", map[string]any{ "text": text, })) @@ -362,6 +370,7 @@ func (p *StreamingProcessor) processFunctionCall(fc *GeminiFunctionCall, signatu var result bytes.Buffer p.usedTool = true + p.hasContent = true toolID := fc.ID if toolID == "" { diff --git a/backend/internal/service/antigravity_gateway_compat_stream.go b/backend/internal/service/antigravity_gateway_compat_stream.go index 26d30ddb46..307c37ac43 100644 --- a/backend/internal/service/antigravity_gateway_compat_stream.go +++ b/backend/internal/service/antigravity_gateway_compat_stream.go @@ -144,12 +144,15 @@ func (s *antigravityCompatStreamSession) hasMeaningfulData() bool { return s.meaningfulData } -func (s *antigravityCompatStreamSession) finish() *antigravityStreamResult { +func (s *antigravityCompatStreamSession) finish() (*antigravityStreamResult, error) { finalEvents, usage := s.processor.Finish() mergeAntigravityCompatUsage(s.usage, usage) s.consumeClaudeEvents(finalEvents) + if !s.hasMeaningfulData() && !s.writer.Disconnected() { + return nil, antigravityCompatEmptyStreamError() + } s.adapter.Finalize(s.writer) - return s.result(s.writer.Disconnected()) + return s.result(s.writer.Disconnected()), nil } func (s *antigravityCompatStreamSession) collectResult(clientDisconnect bool) *antigravityStreamResult { @@ -236,8 +239,7 @@ func isMeaningfulAntigravityCompatEvent(event *apicompat.AnthropicStreamEvent) b return delta.Text != "" || delta.PartialJSON != "" || delta.Thinking != "" || - delta.Signature != "" || - delta.StopReason != "" + delta.Signature != "" } return false } @@ -295,7 +297,7 @@ func (s *AntigravityGatewayService) handleAntigravityCompatStream( if !session.hasMeaningfulData() && !writer.Disconnected() { return nil, antigravityCompatEmptyStreamError() } - return session.finish(), nil + return session.finish() } if event.err != nil { return s.handleAntigravityCompatReadError(c, session, event.err, maxLineSize, prefix) From 6b40449c3f31afffcbf76fddaa1c63f65f2a8608 Mon Sep 17 00:00:00 2001 From: ResidualBlood Date: Sun, 27 Sep 2026 11:27:00 +0000 Subject: [PATCH 2/2] fix(antigravity): exclude Signature-only events from meaningful data check MALFORMED_FUNCTION_CALL responses from upstream contain only thoughtSignature without any text, thinking, or tool calls. The previous fix still treated delta.Signature and block.Signature as meaningful data, so the empty-stream failover was never triggered. - Remove Signature from isMeaningfulAntigravityCompatEvent checks - Let hasMeaningfulData() also consult processor.HasContent() as a secondary guard for edge cases --- .../service/antigravity_gateway_compat_stream.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/backend/internal/service/antigravity_gateway_compat_stream.go b/backend/internal/service/antigravity_gateway_compat_stream.go index 307c37ac43..1a69bea601 100644 --- a/backend/internal/service/antigravity_gateway_compat_stream.go +++ b/backend/internal/service/antigravity_gateway_compat_stream.go @@ -141,7 +141,7 @@ func (s *antigravityCompatStreamSession) consume(line string) { } func (s *antigravityCompatStreamSession) hasMeaningfulData() bool { - return s.meaningfulData + return s.meaningfulData || s.processor.HasContent() } func (s *antigravityCompatStreamSession) finish() (*antigravityStreamResult, error) { @@ -223,23 +223,18 @@ func isMeaningfulAntigravityCompatEvent(event *apicompat.AnthropicStreamEvent) b if event == nil { return false } - if event.Type == "message_stop" { - return true - } if event.ContentBlock != nil { block := event.ContentBlock return block.Type == "tool_use" || block.Text != "" || block.Thinking != "" || - block.Signature != "" || block.Source != nil } if event.Delta != nil { delta := event.Delta return delta.Text != "" || delta.PartialJSON != "" || - delta.Thinking != "" || - delta.Signature != "" + delta.Thinking != "" } return false }