Skip to content

fix(openai-responses): round-trip image-gen namespace - #588

Merged
Wibias merged 10 commits into
lidge-jun:devfrom
RailyW:fix/openai-responses-image-gen-namespace
Jul 28, 2026
Merged

fix(openai-responses): round-trip image-gen namespace#588
Wibias merged 10 commits into
lidge-jun:devfrom
RailyW:fix/openai-responses-image-gen-namespace

Conversation

@RailyW

@RailyW RailyW commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

This is a follow-up to #492. That change prevents one hosted-tool collision by removing a duplicate image_generation declaration, but API-key Responses providers can still reject Codex's private image_gen namespace itself. Replacing the namespace with a dotted function name is not sufficient either: public-compatible upstreams may enforce the flat ^[a-zA-Z0-9_-]+$ function-name pattern, while Codex dispatches namespaced tools through an explicit namespace field and does not reconstruct it by splitting a returned name. An outbound-only rewrite can therefore make chat succeed while the resulting image call fails as an unsupported client tool.

Request-side normalization

  • Apply the compatibility layer only to API-key openai-responses providers, including Azure's inherited keyed path. ChatGPT forward mode keeps its native Responses Lite payload unchanged.
  • Flatten each complete, non-empty image_gen namespace into public-compatible image_gen__<inner-name> function declarations while preserving the inner function schema, description, and strictness.
  • Scan both top-level body.tools and Codex Desktop's nested input[].type = "additional_tools" containers, because an upstream validates their combined tool namespace.
  • Remove a duplicate hosted image_generation declaration only when a usable client alias replaces it. Malformed namespaces and replay-only calls keep the hosted fallback; equivalent usable aliases are deduplicated in stable container order.
  • Encode replayed { namespace: "image_gen", name: "<inner-name>" } calls, plus legacy dotted image_gen.<inner-name> calls, back to the same safe wire alias before they return upstream.
  • Normalize forced function and allowed_tools choices to the same alias only when the matching callable declaration was successfully lowered, preventing declaration/selector mismatches without rewriting unrelated choices.
  • Leave hosted-only requests, unrelated namespaces, and malformed or future namespace shapes unflattened instead of guessing at client capabilities.

Response-side restoration

  • Build an exact reverse map from explicit image-gen namespace tools and legacy dotted declarations in the current request; arbitrary double-underscore function names are never parsed or rewritten heuristically.
  • Restore non-streaming JSON function calls and streaming SSE calls from image_gen__<inner-name> to { namespace: "image_gen", name: "<inner-name>" } before Codex receives them. The SSE path covers incremental output-item events and the completed response snapshot, including frames split across transport chunks.
  • Keep the inspection/logging branch and local continuation cache in the upstream-safe alias form. Only the client-facing branch is restored, so expanded previous_response_id history can be encoded and replayed without leaking the private namespace shape upstream.
  • Preserve cancellation, invalid JSON, SSE framing, [DONE], and the existing Windows native-relay optimization whenever no client rewrite is required.

Compatibility boundaries

  • ChatGPT forward-mode image generation is unchanged.
  • A provider that only uses hosted image_generation keeps that hosted tool.
  • The standalone /v1/images/generations and /v1/images/edits relay paths are unchanged.
  • No authentication headers, credentials, provider selection, or unsafe defaults are changed.
  • Public and maintainer-facing transport documentation now records the bidirectional alias contract.

Verification

  • bun test tests/openai-responses-passthrough.test.ts tests/responses-image-gen-repair.test.ts tests/responses-tool-groups.test.ts tests/azure-adapter.test.ts tests/passthrough-abort.test.ts (70 passed, 0 failed, 201 assertions)
  • bun node_modules/typescript/bin/tsc --noEmit
  • bun scripts/privacy-scan.ts
  • git diff origin/dev...HEAD --check
  • cd docs-site && bun install --frozen-lockfile && bun run build (141 pages built)
  • Required cross-platform CI provides the broader full-suite gate for this shared adapter/server change.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • New Features
    • Improved image_gen compatibility for API-key Responses integrations via safe aliasing, normalization, and replay-safe function-call mapping (including legacy dotted formats).
    • Added restoration for client-facing JSON and streaming outputs, including nested tool scenarios.
  • Bug Fixes
    • Fixed image_gen/hosted image_generation conflict handling and ensured consistent naming restoration during passthrough relays.
    • Refined Windows passthrough relay conditions to match the expected repair/alias behavior.
  • Documentation
    • Updated Codex integration and passthrough transport docs (including multiple languages) with clarified alias/repair behavior.
  • Tests
    • Expanded coverage for keyed vs forward modes, streaming chunk boundaries, tool-group collection robustness, and Windows relay gating.

Lower Codex's private image_gen namespace to safe API-key aliases, restore function calls in JSON and SSE responses, and encode replayed history without changing ChatGPT forward mode.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

API-key Responses image-generation tools now use upstream-safe aliases, while passthrough JSON and SSE responses restore Codex’s image_gen namespace. Forward mode remains unchanged, with expanded adapter, relay, integration, documentation, and Windows-path tests.

Changes

Image-generation Responses bridge

Layer / File(s) Summary
Request tool normalization
src/adapters/openai-responses.ts, src/responses/tool-groups.ts, tests/openai-responses-passthrough.test.ts, tests/azure-adapter.test.ts, tests/responses-tool-groups.test.ts
API-key requests collect tools from top-level and nested containers, flatten image_gen tools to image_gen__<tool>, normalize calls and choices, remove conflicting hosted declarations when applicable, and deduplicate aliases.
Response call restoration
src/server/responses-image-gen-repair.ts, src/server/responses/core.ts, tests/responses-image-gen-repair.test.ts
JSON and SSE passthrough responses restore aliased calls to { namespace: "image_gen", name: ... }, including chunk-boundary handling and Windows relay decisions.
Document and validate bridge behavior
docs-site/src/content/docs/..., structure/04-transports-and-sidecars.md, src/server/index.ts, tests/passthrough-abort.test.ts
Documentation and relay invariants describe aliasing and forwarding behavior, while forward-mode and Windows gating expectations are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Codex
  participant OpenAIResponsesAdapter
  participant ResponsesProvider
  participant ResponsesPassthrough
  Codex->>OpenAIResponsesAdapter: image_gen tool declaration
  OpenAIResponsesAdapter->>ResponsesProvider: image_gen__imagegen alias
  ResponsesProvider-->>ResponsesPassthrough: aliased function-call response
  ResponsesPassthrough-->>Codex: restored image_gen namespace and name
Loading

Possibly related PRs

Suggested reviewers: ingwannu, lidge-jun

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: round-tripping the image-gen namespace in openai-responses.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Jul 28, 2026
@RailyW

RailyW commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/guides/codex-integration.md`:
- Around line 66-72: Update the translated codex-integration pages for ja, ko,
ru, and zh-cn to reflect the English paragraph’s API-key Responses behavior:
image_gen aliasing, removal of duplicate hosted image_generation, response
namespace restoration, and replay encoding. If a translation cannot be updated,
explicitly mark its corresponding content as stale rather than leaving it
contradictory; preserve the unchanged ChatGPT forward-mode behavior.
- Around line 67-69: Update the documentation wording around the Codex namespace
adapter to describe the generic image_gen__<inner-name> alias pattern instead of
hardcoding image_gen__imagegen, matching the adapter and related documentation.
Simplify “maps the returned function call back to” by removing the redundant
“returned” while preserving the explicit image_gen namespace behavior.

In `@src/adapters/openai-responses.ts`:
- Around line 631-664: Update src/adapters/openai-responses.ts lines 631-664 so
hosted image_generation removal depends on successful lowering by
flattenImageGenNamespace or an existing usable flat alias, not the broad
hasImageGenClientTool gate; preserve the hosted tool when no callable alias is
produced. Update tests/openai-responses-passthrough.test.ts lines 881-888 to
expect the hosted tool for an empty or malformed namespace, and revise
structure/04_transports-and-sidecars.md lines 67-68 to document removal only
when a usable image_gen__<inner-name> alias replaces it.

In `@src/server/responses-image-gen-repair.ts`:
- Around line 76-112: Extract the duplicated SSE parsing and ReadableStream
relay logic from nextSseBlock, sseDataPayload, replaceSseDataPayload, and the
corresponding relaySseWithResponsesItemIdRepair flow into a shared
relaySseWithPayloadRewrite utility. Have each repair module provide only its
payload rewrite callback, then update responses/core.ts to compose both rewrites
in one relay so each SSE event is parsed, transformed, and encoded only once.

In `@src/server/responses/core.ts`:
- Around line 1552-1554: Make the win32 eager-relay invariant explicit around
needsClientRewrite, winNoClientRewrite, and the eager branch: since
winNoClientRewrite guarantees no image-generation aliases, do not invoke
relaySseWithImageGenCallRestore on this path, or otherwise clearly document and
enforce that the eager body is passed through unchanged. Preserve client
rewriting for paths where aliases are present and avoid introducing a JS pull
wrapper on win32.

In `@structure/04_transports-and-sidecars.md`:
- Around line 63-70: Update the gate description near the “win32-no-repair only”
text to use the renamed needsClientRewrite predicate and describe the condition
as win32 with no client-facing rewrite, including neither image-gen aliases nor
item-id repair. Keep this document synchronized with the corresponding core.ts
and src/server/index.ts comments.

In `@tests/responses-image-gen-repair.test.ts`:
- Line 191: The test name in the “handleResponses restores client-facing SSE
calls on the Windows rewrite path” test overstates coverage because it does not
force or verify the win32 branch. Either rename the test to describe the
platform-neutral passthrough rewrite behavior, or explicitly mock/set
process.platform to win32 and assert the branch controlled by handleResponses
executes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e3da1fb9-7174-4f11-bb14-19a307051177

📥 Commits

Reviewing files that changed from the base of the PR and between 903b62c and e5d1dd1.

📒 Files selected for processing (10)
  • docs-site/src/content/docs/guides/codex-integration.md
  • src/adapters/openai-responses.ts
  • src/server/index.ts
  • src/server/responses-image-gen-repair.ts
  • src/server/responses/core.ts
  • structure/04_transports-and-sidecars.md
  • tests/azure-adapter.test.ts
  • tests/openai-responses-passthrough.test.ts
  • tests/passthrough-abort.test.ts
  • tests/responses-image-gen-repair.test.ts

Comment thread docs-site/src/content/docs/guides/codex-integration.md Outdated
Comment thread docs-site/src/content/docs/guides/codex-integration.md Outdated
Comment thread src/adapters/openai-responses.ts
Comment on lines +76 to +112
function nextSseBlock(buffer: string): { block: string; delimiter: string; rest: string } | null {
const match = buffer.match(/\r?\n\r?\n/);
if (!match || match.index === undefined) return null;
return {
block: buffer.slice(0, match.index),
delimiter: match[0],
rest: buffer.slice(match.index + match[0].length),
};
}

function sseDataPayload(block: string): string | null {
const data: string[] = [];
for (const line of block.split(/\r?\n/)) {
if (!line.startsWith("data:")) continue;
const value = line.slice(5);
data.push(value.startsWith(" ") ? value.slice(1) : value);
}
return data.length > 0 ? data.join("\n") : null;
}

function replaceSseDataPayload(block: string, payload: string): string {
const newline = block.includes("\r\n") ? "\r\n" : "\n";
const lines = block.split(/\r?\n/);
const rewritten: string[] = [];
let replaced = false;
for (const line of lines) {
if (!line.startsWith("data:")) {
rewritten.push(line);
continue;
}
if (!replaced) {
rewritten.push(`data: ${payload}`);
replaced = true;
}
}
return replaced ? rewritten.join(newline) : block;
}

@coderabbitai coderabbitai Bot Jul 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

nextSseBlock, sseDataPayload, and replaceSseDataPayload are verbatim duplicates of src/server/responses-item-id-repair.ts.

The three parsers here plus the ReadableStream shell at Lines 118-169 are byte-for-byte the same shape as relaySseWithResponsesItemIdRepair (src/server/responses-item-id-repair.ts Lines 224-275). Beyond the duplication, src/server/responses/core.ts Lines 1665-1668 now chains both wrappers on the same stream, so every SSE block is split, JSON.parsed, re-stringifyd, and re-encoded twice per event.

Suggested extraction: a shared relaySseWithPayloadRewrite(body, rewrite: (payload: string) => string) in a small sse-rewrite.ts, with both modules supplying only their rewrite callback. core.ts could then compose the two rewrites into a single pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/responses-image-gen-repair.ts` around lines 76 - 112, Extract the
duplicated SSE parsing and ReadableStream relay logic from nextSseBlock,
sseDataPayload, replaceSseDataPayload, and the corresponding
relaySseWithResponsesItemIdRepair flow into a shared relaySseWithPayloadRewrite
utility. Have each repair module provide only its payload rewrite callback, then
update responses/core.ts to compose both rewrites in one relay so each SSE event
is parsed, transformed, and encoded only once.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed: the SSE framing/relay shell is duplicated, and chaining both opt-in rewrites performs two passes. I am intentionally deferring that extraction from this correctness-focused PR because a true single-pass composition would also restructure the existing provider-local item-id repair path and broaden the regression surface. This thread is left unresolved for maintainer direction or a focused follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@RailyW, understood. The duplication and double SSE transformation remain valid follow-up work, but deferring the shared relay extraction is reasonable for this correctness-focused change: consolidating src/server/responses-image-gen-repair.ts with the provider-local item-ID repair path in src/server/responses-item-id-repair.ts would alter a broader, stream-sensitive code path.

I’ll leave this finding unresolved for maintainer direction. Would you like me to open a dedicated GitHub issue for the single-pass SSE payload-rewrite utility, with #588 and this discussion linked as context?

Comment thread src/server/responses/core.ts
Comment thread structure/04_transports-and-sidecars.md Outdated
Comment thread tests/responses-image-gen-repair.test.ts Outdated
RailyW added 7 commits July 28, 2026 11:21
Remove hosted image_generation only when a usable client alias replaces it, while retaining the hosted tool for malformed namespaces and replay-only calls.
Build the reverse map from explicitly declared dotted image-gen functions so safe upstream aliases return to Codex with an explicit namespace without guessing unrelated double-underscore names.
Keep the win32 eager branch free of client rewrite wrappers and align the source invariant and transport documentation with the no-client-rewrite gate.
Describe the integration test as the platform-neutral passthrough rewrite path while the separate source invariant continues to guard the win32 native relay branch.
Document the generic image_gen__<inner-name> contract and clarify that hosted image generation is removed only after a usable client alias replaces it.
Update the Japanese, Korean, Russian, and Simplified Chinese Codex integration guides with the API-key alias, response restoration, replay, and forward-mode compatibility contract.
Document the request-normalization and response-restoration helper contracts without changing the deferred SSE relay structure.
@RailyW
RailyW marked this pull request as ready for review July 28, 2026 03:49

@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: ad020ca329

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/adapters/openai-responses.ts

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server/responses-image-gen-repair.ts`:
- Around line 13-26: Export declaredToolGroups from
src/server/responses-image-gen-repair.ts (lines 13-26) as the canonical
tool-group collector, then update src/adapters/openai-responses.ts (lines
644-652) to import and reuse it instead of duplicating the collection loop;
preserve the existing ordering and handling of body.tools and
input[].additional_tools.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: db86ad9d-c85f-4da4-9ead-313fc1294896

📥 Commits

Reviewing files that changed from the base of the PR and between e5d1dd1 and ad020ca.

📒 Files selected for processing (13)
  • docs-site/src/content/docs/guides/codex-integration.md
  • docs-site/src/content/docs/ja/guides/codex-integration.md
  • docs-site/src/content/docs/ko/guides/codex-integration.md
  • docs-site/src/content/docs/ru/guides/codex-integration.md
  • docs-site/src/content/docs/zh-cn/guides/codex-integration.md
  • src/adapters/openai-responses.ts
  • src/server/index.ts
  • src/server/responses-image-gen-repair.ts
  • src/server/responses/core.ts
  • structure/04_transports-and-sidecars.md
  • tests/openai-responses-passthrough.test.ts
  • tests/passthrough-abort.test.ts
  • tests/responses-image-gen-repair.test.ts

Comment thread src/server/responses-image-gen-repair.ts Outdated
@RailyW

RailyW commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Wibias

Wibias commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Maintainer review verdict — approve to merge

Reviewed head 74fee9c9 against dev (bug + security). Target branch dev is correct.

Security

No medium/high/critical issues. Alias maps are built only from the caller's declared image_gen tools; restore is exact-match on function_call names; forward/ChatGPT auth skips rewrite; inspection/continuation keep upstream wire names; no new logging of bodies/credentials.

Correctness

No blocking bugs found. Focused suite passed locally (70/70):
bun test tests/openai-responses-passthrough.test.ts tests/responses-image-gen-repair.test.ts tests/responses-tool-groups.test.ts tests/passthrough-abort.test.ts tests/azure-adapter.test.ts

CI already green on ubuntu/windows/macos + npm-global.

Open CodeRabbit / Codex feedback

Source Finding Status
Codex Rewrite tool_choice / allowed_tools with aliases FixednormalizeImageGenToolChoice + regression tests
Rabbit Hosted image_generation removed without usable alias Fixed — gated on hasUsableImageGenAlias; empty/malformed/replay-only keep hosted
Rabbit Locale docs / wording / win32 invariant / test naming / shared tool-groups Fixed
Rabbit (open) Duplicate SSE relay shell + double-pass when image-gen restore chains with item-id repair Defer — real maintainability follow-up, not a correctness/security blocker. Author's deferral is appropriate; track as a focused follow-up (single-pass relaySseWithPayloadRewrite), do not hold #588

Verdict

Merge. Optional follow-up issue for single-pass SSE payload rewrite composition; nothing else outstanding from Rabbit/Codex that should block.

@Wibias

Wibias commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Thanks @RailyW — solid correctness work on the image-gen namespace round-trip, and appreciated the thorough follow-through on the review threads (hosted-tool gate, tool_choice, locales, win32 invariant). Squash-merging to dev now; the deferred single-pass SSE rewrite composition will land as a focused follow-up.

@Wibias Wibias left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maintainer approval after bug+security review. Mergeable; deferred SSE single-pass extraction is follow-up only.

@Wibias
Wibias merged commit 285e2bf into lidge-jun:dev Jul 28, 2026
13 checks passed
@Wibias

Wibias commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Follow-up for the deferred single-pass SSE rewrite composition: https://github.com/lidge-jun/opencodex/pull/

Wibias added a commit that referenced this pull request Jul 28, 2026
Follow-up to #588: extract shared sse-payload-rewrite shell and compose image-gen restore with item-id repair in one parse/stringify pass instead of chaining two JS pull wrappers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants