diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index f14fa38..c91ba8b 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -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 }