Skip to content

fix(responses): complete and persist Codex image outputs - #5014

Open
markchenlife wants to merge 1 commit into
router-for-me:devfrom
markchenlife:fix/codex-image-generation-completed-event
Open

fix(responses): complete and persist Codex image outputs#5014
markchenlife wants to merge 1 commit into
router-for-me:devfrom
markchenlife:fix/codex-image-generation-completed-event

Conversation

@markchenlife

Copy link
Copy Markdown

Summary

  • synthesize a missing response.image_generation_call.completed event when Codex upstream finishes an image item with a result
  • normalize terminal image generation statuses to completed, preserve monotonic SSE sequence numbers, and avoid duplicate completion events
  • add an opt-in codex-image-output-dir setting that persists PNG, JPEG, or WebP results and appends a renderable absolute Markdown image path for local Codex clients

Root cause

Codex upstream can emit a result-bearing response.output_item.done for an image_generation_call while leaving the item status as generating and omitting response.image_generation_call.completed. CLIProxy forwarded that incomplete lifecycle unchanged. Codex Desktop retained the Base64 result in its session data, but had no completed image event or local file reference it could render.

The output directory remains disabled by default, so existing remote and general-purpose deployments keep their current behavior.

Tests

  • go test ./internal/config ./internal/translator/codex/openai/responses ./sdk/api/handlers/openai
  • end-to-end Codex Desktop -> CLIProxy image generation with no bridge layer; the generated PNG was persisted and rendered through the returned Markdown path

@github-actions
github-actions Bot changed the base branch from main to dev August 16, 2026 09:53
@github-actions

Copy link
Copy Markdown

This pull request targeted main.

The base branch has been automatically changed to dev.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42bb970aae

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

failureEvent = "response.failed"
}
framer := &responsesSSEFramer{failureEvent: failureEvent}
framer := &responsesSSEFramer{failureEvent: failureEvent, imageOutputDir: h.codexImageOutputDir()}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply image repair to WebSocket responses

When a client upgrades /v1/responses to WebSocket, this SSE framer is bypassed: forwardResponsesWebsocket parses each payload and writes it directly in openai_responses_websocket_forward.go. Consequently, Codex WebSocket clients still receive no synthesized response.image_generation_call.completed event, and codex-image-output-dir neither persists nor links the image. Route WebSocket payloads through equivalent image lifecycle and persistence handling.

Useful? React with 👍 / 👎.

Comment on lines +121 to +124
item := gjson.GetBytes(payload, "item")
if item.Get("type").String() != "image_generation_call" {
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist image results from non-streaming responses

When stream is false or omitted, handleNonStreamingResponse writes the final response directly, so this event-specific function is never invoked; moreover, a non-streaming image is under output[], not item. Thus enabling codex-image-output-dir has no effect for valid non-streaming Responses requests even though the new configuration describes completed Responses results generally. Process final response output items before writing the response as well.

Useful? React with 👍 / 👎.

@markchenlife
markchenlife force-pushed the fix/codex-image-generation-completed-event branch from 42bb970 to a171261 Compare August 16, 2026 09:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a17126138e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +251 to +253
case "response.content_part.added", "response.content_part.done":
if gjson.GetBytes(payload, "part.type").String() == "output_text" {
return appendImageMarkdownAtPath(payload, "part.text", markdown)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inject the preview only once into the text stream

When codex-image-output-dir is enabled and the next assistant message follows the normal response.content_part.addedresponse.output_text.delta sequence, this branch appends the Markdown link to part.text, and the delta branch appends it again because imageMarkdownInjected is still false. A client that initializes content from the added part and then accumulates deltas therefore renders the same generated image twice; reserve the injection for the delta and terminal snapshots, or consume the pending preview consistently after this event.

Useful? React with 👍 / 👎.

Comment on lines +240 to +243
case "response.output_text.delta":
if f.imageMarkdownInjected {
return payload
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset preview state for newly persisted images

When one streamed response interleaves two image items with assistant text after each, this response-wide boolean is set by the first message and is never reset when the second image is persisted. The first text delta after the second image is consequently forwarded unchanged, so delta-driven clients never receive the second file path; track unannounced image paths and consume them per following assistant message rather than using a permanent stream-wide flag.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant