fix(proxy): filtra chunks SSE sin id y corta en [DONE]

Grok Build siempre usa streaming. El upstream (OpenCode Zen)
envía chunks no-estándar (inference-cost sin id, cost tras
[DONE]) que rompen el deserializador serde de Rust con
'missing field id'. streamResponse ahora filtra eventos SSE
que no tengan campo id y deja de forwardear después de [DONE].
This commit is contained in:
renato97
2026-07-29 00:51:59 -03:00
parent 46127431a3
commit 6dc29f358b
+28 -4
View File
@@ -197,7 +197,8 @@ func (p *Proxy) ForwardChatCompletion(w http.ResponseWriter, r *http.Request, bo
return p.nonStreamResponse(w, resp, req.Model)
}
// streamResponse forwards SSE chunks directly from upstream to client.
// streamResponse forwards SSE chunks from upstream to client, filtering out
// non-standard events (missing `id`) and stopping after [DONE].
func (p *Proxy) streamResponse(w http.ResponseWriter, resp *http.Response) error {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
@@ -212,17 +213,40 @@ func (p *Proxy) streamResponse(w http.ResponseWriter, resp *http.Response) error
scanner := bufio.NewScanner(resp.Body)
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
done := false
for scanner.Scan() {
line := scanner.Text()
// Write the line. If the client disconnects, stop silently.
if done {
continue
}
if strings.HasPrefix(line, "data: ") {
data := strings.TrimPrefix(line, "data: ")
if data == "[DONE]" {
if _, err := fmt.Fprint(w, line+"\n"); err != nil {
return nil
}
flusher.Flush()
done = true
continue
}
// Only forward SSE events that have an "id" field — filters out
// non-standard chunks (e.g. x-opencode-type, standalone cost)
var obj map[string]any
if json.Unmarshal([]byte(data), &obj) == nil {
if _, ok := obj["id"]; !ok {
continue
}
}
}
if _, err := fmt.Fprint(w, line+"\n"); err != nil {
return nil
}
flusher.Flush()
}
// Scanner errors (e.g., connection reset) are not real errors
// after streaming has started — the client likely disconnected.
return nil
}