Emit streamed image-generation result on output_item.done when no partial was sent - #1060
Conversation
…tial was sent The streaming ResponseOutputItemImageGenerationCall done handler was a no-op, delegating image emission to the partial_image events. But the Responses API only sends partial_image events when partial_images > 0, which this provider never requests. In the default case the finished image arrives only on output_item.done, so a streamed image generation dropped the result entirely - while the non-streaming path still emits it (a streaming-vs-non-streaming parity gap with user-visible data loss). Emit the finished image from the done item unless a partial_image event was already seen for that item (tracked in responsesStreamState), so the default no-partial case is covered without double-emitting when partials are enabled.
There was a problem hiding this comment.
🟡 Changes recommended
The done-item path can duplicate tool calls, resumed streams can lose partial state, and the test does not assert exactly one call and result.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Fixes streamed image-generation results being dropped when no partial image event is emitted.
Changes:
- Tracks image items that emit partial results.
- Emits completed images from
output_item.donewhen needed. - Adds regression coverage for the no-partial path.
File summaries
| File | Description |
|---|---|
provider/openaiprovider/responses.go |
Updates streaming image result handling. |
provider/openaiprovider/responses_test.go |
Adds no-partial streaming coverage. |
Review details
Suppressed comments (2)
provider/openaiprovider/responses.go:1641
- When a background stream is resumed,
responses.go:150creates a freshresponsesStreamState, and the GET starts after the continuation token's sequence number. If that token was emitted for apartial_imageevent, the prior partial is not replayed, so this lookup is false atoutput_item.doneand emits the final image again, defeating the no-duplicate behavior across resumption. Persist the partial item IDs in the continuation state (or otherwise restore them when resuming) before applying this check.
if !state.imagePartialsSeen[item.ID] {
provider/openaiprovider/responses_test.go:7821
- This test only retains the last result pointer, so it passes even if the no-partial path emits duplicate call/result content. Count the streamed image-generation call and result contents and assert exactly one of each; that would catch the duplicate introduced by the done handler and protect the partial/no-partial distinction.
for _, content := range update.Contents {
if r, ok := content.(*message.ImageGenerationToolResultContent); ok {
result = r
}
}
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if !state.imagePartialsSeen[item.ID] { | ||
| u.Contents = imageGenerationContents(item) |
|
Scope: internal-only (bug fix in unexported streaming state; no exported Go API changed) Changed Go contract: None exported. Upstream evidence reviewed:
Result: aligned (no cross-repo divergence). This is a Go-internal correctness fix that brings the Go streaming path in line with its own non-streaming behavior; it does not diverge from upstream since Python has an equivalent (unfixed) gap and .NET has no comparable client-side streaming handler. No exported Go API surface changed, so
|
Problem
In the streaming Responses handler, the
ResponseOutputItemImageGenerationCallcase onoutput_item.doneis a no-op:It delegates image emission entirely to
response.image_generation_call.partial_imageevents. But the Responses API only sends those whenpartial_images > 0, which this provider never requests (nopartial_imagesanywhere in non-test source). In the default case the finished image arrives only onoutput_item.done, so a streamed image generation drops the result entirely — while the non-streaming path (imageGenerationContents) still emits it. This is a streaming-vs-non-streaming parity gap with user-visible data loss, introduced when #1038 rewrote this handler.Fix
Emit the finished image from the done item unless a
partial_imageevent was already seen for that item.responsesStreamStatenow tracks the image-generation item IDs that produced partials, so:output_item.done;webp/metadata) and avoiding a duplicate.Test
TestResponsesStreamingImageGenerationCall_NoPartial_EmitsResultOnDonestreams an image generation with no partial_image event and asserts theImageGenerationToolResultContentwith the image is surfaced. Fails before the fix (result dropped), passes after. The existing..._MapsToToolContentspartial-path test remains green (no double-emit).