From f9891416c0b5b19e57692a3aed5b9da262d2a177 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Fri, 6 Mar 2026 12:35:44 +0000 Subject: [PATCH] fix: emit Responses API lifecycle events in mock OpenAI server (#22702) --- coderd/chatd/chattest/openai.go | 43 ++++++++++++++++++++++++++++++++- go.mod | 2 +- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/coderd/chatd/chattest/openai.go b/coderd/chatd/chattest/openai.go index e91726fd98..643930f943 100644 --- a/coderd/chatd/chattest/openai.go +++ b/coderd/chatd/chattest/openai.go @@ -11,6 +11,7 @@ import ( "time" "github.com/google/uuid" + "github.com/openai/openai-go/v3/responses" ) // OpenAIHandler handles OpenAI API requests and returns a response. @@ -306,6 +307,17 @@ func writeChatCompletionsStreaming(w http.ResponseWriter, r *http.Request, chunk } } +// writeSSEEvent marshals v as JSON and writes it as an SSE data +// frame. Returns any write error. +func writeSSEEvent(w http.ResponseWriter, v interface{}) error { + data, err := json.Marshal(v) + if err != nil { + return err + } + _, err = fmt.Fprintf(w, "data: %s\n\n", data) + return err +} + func writeResponsesAPIStreaming(w http.ResponseWriter, r *http.Request, chunks <-chan OpenAIChunk) { w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") @@ -329,7 +341,23 @@ func writeResponsesAPIStreaming(w http.ResponseWriter, r *http.Request, chunks < return case chunk, ok = <-chunks: if !ok { - _, _ = fmt.Fprintf(w, "data: [DONE]\n\n") + // Emit Responses API lifecycle events so + // the fantasy client closes open text + // blocks and persists the step content. + for outputIndex, itemID := range itemIDs { + _ = writeSSEEvent(w, responses.ResponseTextDoneEvent{ + ItemID: itemID, + OutputIndex: int64(outputIndex), + }) + _ = writeSSEEvent(w, responses.ResponseOutputItemDoneEvent{ + OutputIndex: int64(outputIndex), + Item: responses.ResponseOutputItemUnion{ + ID: itemID, + Type: "message", + }, + }) + } + _ = writeSSEEvent(w, responses.ResponseCompletedEvent{}) flusher.Flush() return } @@ -344,6 +372,19 @@ func writeResponsesAPIStreaming(w http.ResponseWriter, r *http.Request, chunks < if !found { itemID = fmt.Sprintf("msg_%s", uuid.New().String()[:8]) itemIDs[outputIndex] = itemID + + // Emit response.output_item.added so the + // fantasy client triggers TextStart. + if err := writeSSEEvent(w, responses.ResponseOutputItemAddedEvent{ + OutputIndex: int64(outputIndex), + Item: responses.ResponseOutputItemUnion{ + ID: itemID, + Type: "message", + }, + }); err != nil { + return + } + flusher.Flush() } chunkData := map[string]interface{}{ diff --git a/go.mod b/go.mod index 32c919d675..22f6fbddc1 100644 --- a/go.mod +++ b/go.mod @@ -490,6 +490,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/go-git/go-git/v5 v5.16.5 github.com/mark3labs/mcp-go v0.38.0 + github.com/openai/openai-go/v3 v3.15.0 github.com/sergi/go-diff v1.4.0 gonum.org/v1/gonum v0.17.0 ) @@ -574,7 +575,6 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/openai/openai-go v1.12.0 // indirect github.com/openai/openai-go/v2 v2.7.1 // indirect - github.com/openai/openai-go/v3 v3.15.0 // indirect github.com/package-url/packageurl-go v0.1.3 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect