fix DeepSeek Responses UUID item ids for Codex - #940
Conversation
|
✅ PR quality gates passed This pull request now targets The |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds opt-in rewriting for non-canonical Responses IDs and reasoning streams. It also allows SSE rewrites to drop events, clears dropped buffered data, and emits a ChangesResponses SSE repair
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Provider
participant responsesItemIdRepair
participant relaySseWithPayloadRewrite
participant Client
Provider->>responsesItemIdRepair: Responses SSE events
responsesItemIdRepair->>responsesItemIdRepair: Map IDs and normalize reasoning
responsesItemIdRepair-->>relaySseWithPayloadRewrite: Event or null
relaySseWithPayloadRewrite->>Client: Canonical event
responsesItemIdRepair-->>relaySseWithPayloadRewrite: Completion trailer
relaySseWithPayloadRewrite->>Client: [DONE]
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/server/relay-eager.ts (1)
147-170: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winAdd the repair trailer to the eager relay.
On Windows,
win32EagerRewriteroutes repairedopenai-responsesstreams throughrelaySseEagerBoundedand suppliesrewritePayload(src/server/responses/core.ts:1760-1817). The eager EOF branch only flushes the rewrite tail (src/server/relay-eager.ts:219-227). If the upstream sendsresponse.completedwithoutdata: [DONE],sawTerminal()is already true, so no synthetic terminal is emitted and the client stream closes without[DONE].Add a trailer option to
EagerRelayOptions, pass the samecreateResponsesItemIdDoneTrailerresult used byrelaySseWithPayloadRewrite(src/server/responses-item-id-repair.ts:423-435), and enqueue it afterflushRewriteTail()before closing the eager stream.🤖 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/relay-eager.ts` around lines 147 - 170, Add a trailer field to EagerRelayOptions and update the eager EOF path to enqueue it after flushRewriteTail() and before closing the stream. In the win32EagerRewrite call to relaySseEagerBounded, pass the same createResponsesItemIdDoneTrailer result already used by relaySseWithPayloadRewrite, ensuring repaired streams emit [DONE] even when response.completed arrives without a data: [DONE] event.
🤖 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-item-id-repair.ts`:
- Around line 148-157: Update mapResponseId so newly minted canonical IDs
incorporate the current responseIdMap size, while retaining the existing
scope-based prefix. Ensure each distinct non-canonical rawId receives a unique
ID and preserve reuse of previously mapped IDs.
- Around line 159-183: The normalizeReasoningItem path can emit a reasoning item
without an id when rewriteNonCanonicalIds is enabled without
repairMissingTerminalIds. Update the id resolution around
rememberMappedId/mapRawId to mint a canonical reasoning id for missing ids
whenever id rewriting is active, preserving existing ids and mappings otherwise.
Add a focused regression test beside the existing DeepSeek repair tests covering
a missing-id reasoning item with only rewriteNonCanonicalIds enabled and
asserting the canonical id format.
- Around line 407-426: Refactor createResponsesItemIdPayloadRewrite into a
single factory that owns the repair state in a closure and returns both the
payload rewrite and its trailer callback, so composition cannot lose __state.
Thread the returned trailer separately through relaySseWithPayloadRewrite and
the eager relay path, updating callers and tests accordingly; then remove the
existing createResponsesItemIdPayloadRewrite and
createResponsesItemIdDoneTrailer exports.
In `@src/server/sse-payload-rewrite.ts`:
- Around line 143-171: Update both payload checks in the pull relay’s normal
block and flush-final handling to test `payload !== null` instead of truthiness,
ensuring empty string payloads still pass through `rewrite` and can be
suppressed consistently with the eager relay.
In `@tests/responses-item-id-repair-deepseek.test.ts`:
- Around line 54-90: Add a focused test beside the existing response item ID
repair test that feeds response.completed followed by an upstream data: [DONE]
trailer through relaySseWithResponsesItemIdRepair, then asserts the repaired
stream contains exactly one data: [DONE] occurrence. This should specifically
exercise the sawDoneTrailer guard rather than only the synthetic trailer path.
---
Outside diff comments:
In `@src/server/relay-eager.ts`:
- Around line 147-170: Add a trailer field to EagerRelayOptions and update the
eager EOF path to enqueue it after flushRewriteTail() and before closing the
stream. In the win32EagerRewrite call to relaySseEagerBounded, pass the same
createResponsesItemIdDoneTrailer result already used by
relaySseWithPayloadRewrite, ensuring repaired streams emit [DONE] even when
response.completed arrives without a data: [DONE] event.
🪄 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: 0c8caefe-4183-4f20-a286-b59910fdacc3
📒 Files selected for processing (7)
src/config.tssrc/server/relay-eager.tssrc/server/responses-item-id-repair.tssrc/server/sse-payload-rewrite.tssrc/types.tstests/responses-item-id-repair-deepseek.test.tstests/responses-item-id-repair.test.ts
c214ffc to
c86af23
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/server/relay-eager.ts (1)
229-239: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winAdd the responses item-id repair trailer to the eager relay.
On the Windows rewrite path,
src/server/responses/core.ts:1874-1876passes only the payload rewrite.src/server/relay-eager.ts:229-239flushes the rewrite tail but never invokescreateResponsesItemIdDoneTrailerfromsrc/server/responses-item-id-repair.ts:417-435. When the upstream sendsresponse.completedwithout[DONE], the eager client stream ends withoutdata: [DONE]\n\n, unlike the pull relay. Pass a trailer callback throughEagerRelayHooksand enqueue it at upstream EOF.hooks.sawTerminal()may already be true forresponse.completed, so this is a missing client terminator, not necessarily a syntheticincomplete.🤖 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/relay-eager.ts` around lines 229 - 239, The eager relay must emit the responses item-id repair trailer at upstream EOF, including when hooks.sawTerminal() is already true. Extend EagerRelayHooks with a trailer callback, pass createResponsesItemIdDoneTrailer through the Windows rewrite path in responses/core.ts, and invoke/enqueue the trailer from relay-eager.ts alongside the rewrite tail before ending the stream; preserve synthetic incomplete handling separately.
🤖 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-item-id-repair.ts`:
- Around line 286-295: Update accumulateReasoningText and the direct
reasoningTextByOutputIndex replacements in repairEventPayload to use one helper
that releases any existing entry before replacement, charges each appended delta
through TranslatorBudget, and retains the updated value. When the reasoning
stream ends, release the entire stored entry and remove it from
reasoningTextByOutputIndex.
- Around line 371-390: Remove the inner logprobs-removal block from the
output_text normalization branch, including its destructuring and reassignment,
while preserving the unconditional logprobs stripping block guarded by
state.rewriteNonCanonicalIds.
- Around line 297-341: Update the terminal-event tracking in repairEventPayload
so response.completed, response.failed, and response.incomplete all mark the
stream as terminal for trailer generation. Rename sawCompleted to
sawTerminalResponse throughout ResponsesItemIdRepairState and
createResponsesItemIdDoneTrailer, preserving the existing behavior that emits
[DONE] only when a terminal response event was observed.
In `@src/server/sse-payload-rewrite.ts`:
- Around line 177-196: Preserve the item-ID rewrite reference in the composed
relay setup and pass createResponsesItemIdDoneTrailer(itemIdRewrite) as the
trailer to relaySseWithPayloadRewrite, matching the existing
relaySseWithResponsesItemIdRepair behavior. Add a composed-path test covering a
stream that ends without an upstream [DONE] event and verifies the synthetic
trailer is emitted.
---
Outside diff comments:
In `@src/server/relay-eager.ts`:
- Around line 229-239: The eager relay must emit the responses item-id repair
trailer at upstream EOF, including when hooks.sawTerminal() is already true.
Extend EagerRelayHooks with a trailer callback, pass
createResponsesItemIdDoneTrailer through the Windows rewrite path in
responses/core.ts, and invoke/enqueue the trailer from relay-eager.ts alongside
the rewrite tail before ending the stream; preserve synthetic incomplete
handling separately.
🪄 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: b3e43054-f882-419e-acba-3e3638476f23
📒 Files selected for processing (5)
src/config.tssrc/server/relay-eager.tssrc/server/responses-item-id-repair.tssrc/server/sse-payload-rewrite.tssrc/types.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c86af23544
ℹ️ 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".
| if (terminal || text) { | ||
| next.encrypted_content = encodeReasoningEnvelope({ txt: text || " " }); |
There was a problem hiding this comment.
Preserve plaintext reasoning for DeepSeek replay
When Codex replays this repaired reasoning item during a stateless DeepSeek tool-call continuation, it sends back only the new ocxr1 envelope because this normalization removes the original content. sanitizeReasoningInputContent then strips that proxy-only envelope before native forwarding; even with preserveResponsesReasoningContent, there is no plaintext content left to preserve, so DeepSeek loses the prior reasoning required for the continuation. Decode the envelope back into reasoning_text for providers that preserve raw reasoning, or otherwise retain a replayable plaintext representation.
Useful? React with 👍 / 👎.
| export function createResponsesItemIdDoneTrailer( | ||
| rewrite: SsePayloadRewrite, | ||
| ): () => string | undefined { | ||
| return () => { | ||
| const state = (rewrite as SsePayloadRewrite & { __state?: ResponsesItemIdRepairState }).__state; | ||
| if (!state) return undefined; | ||
| if (state.sawCompleted && !state.sawDoneTrailer) return "data: [DONE]\n\n"; |
There was a problem hiding this comment.
Attach the DONE trailer to the main Responses relay
For the normal /v1/responses passthrough flow, core.ts constructs this repair through createResponsesItemIdPayloadRewrite directly and calls relaySseWithPayloadRewrite without the trailer option; the Windows eager path likewise receives only the payload rewrite. Therefore, when DeepSeek cleanly ends after response.completed without [DONE], this callback is never consulted and Codex can remain stuck on Working—the exact failure this change intends to repair. Thread the repair trailer through both the composed pull relay and eager relay paths.
Useful? React with 👍 / 👎.
| message: z.array(z.string().min(1)).optional(), | ||
| reasoning: z.array(z.string().min(1)).optional(), | ||
| repairMissingTerminalIds: z.boolean().optional(), | ||
| rewriteNonCanonicalIds: z.boolean().optional(), |
There was a problem hiding this comment.
Document the new repair option
Custom Responses providers must explicitly set responsesItemIdRepair.rewriteNonCanonicalIds, but this new user-facing configuration field is not documented anywhere under docs-site/; users installing the released package therefore have no shipped reference for enabling the DeepSeek compatibility behavior or understanding its stream normalization effects. Add it to the relevant provider/configuration documentation.
AGENTS.md reference: src/AGENTS.md:L28-L28
Useful? React with 👍 / 👎.
| state.reasoningTextByOutputIndex.set( | ||
| outputIndex, | ||
| (state.reasoningTextByOutputIndex.get(outputIndex) ?? "") + delta, | ||
| ); |
There was a problem hiding this comment.
Bound accumulated reasoning text
When rewriteNonCanonicalIds is enabled, every response.reasoning_text.delta is retained here by concatenating it onto the complete prior text, without charging the request's TranslatorBudget or enforcing a cumulative limit. A provider that emits a very long reasoning stream can therefore grow the proxy heap without bound and incur quadratic string-copying work before the terminal item is converted to an envelope. Track and release these retained bytes through the translator budget and fail at the existing turn limit, or use an equivalently bounded accumulator.
Useful? React with 👍 / 👎.
| const existing = state.responseIdMap.get(rawId); | ||
| if (existing) return existing; | ||
| const minted = `resp_ocx_${state.scope}`; | ||
| state.responseIdMap.set(rawId, minted); |
There was a problem hiding this comment.
Alias rewritten response IDs in continuation state
When Codex chains a turn with previous_response_id, it sends the synthetic resp_ocx_* value minted here, but the passthrough inspection branch records the untouched upstream response under its raw UUID. The local continuation cache therefore cannot find the synthetic ID; a stateless DeepSeek route loses the earlier input expansion, while a non-stateless custom route may forward an ID the upstream has never issued. Preserve the client-visible prefix while registering an alias from the synthetic response ID to the raw completed snapshot, or otherwise keep continuation lookup synchronized with this rewrite.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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-item-id-repair.ts`:
- Around line 156-159: Update the response-ID minting path around
responseIdMap.set to charge TranslatorBudget for each new rawId-to-minted
mapping before storing it, using the existing item_ids budget scope and matching
the accounting used by the equivalent item-ID mappings. Add a regression test
with a small budget that verifies many distinct provider response IDs cannot
grow responseIdMap beyond the request limit.
In `@tests/responses-item-id-repair-deepseek.test.ts`:
- Around line 122-131: Add a test alongside the existing trailer test that
invokes relaySseEagerBounded with a dropping rewritePayload and the repair
trailer, using a terminal response stream without an upstream [DONE]. Assert the
repaired output contains exactly one data: [DONE], covering the eager trailer
path and its wiring through relaySseEagerBounded.
🪄 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: 1c958d9d-ff10-4393-8f66-01a9c03fe8f2
📒 Files selected for processing (5)
src/server/relay-eager.tssrc/server/responses-item-id-repair.tssrc/server/responses/core.tssrc/server/sse-payload-rewrite.tstests/responses-item-id-repair-deepseek.test.ts
Ingwannu
left a comment
There was a problem hiding this comment.
Thanks — the opt-in SSE normalization is valuable and is the right direction for #938, but I found two correctness blockers on the current head.
-
rawToCanonicalis global across message/reasoning item types and is consulted before the event type. If a provider reuses the same configured placeholder ID for both types, a reasoning lifecycle event can be rewritten to themsg_...ID. Please make the alias mapping type-scoped and add a regression where message and reasoning share one raw placeholder while their lifecycle IDs remain distinct. -
When an output index already has a mapped ID, each new raw alias is retained in
rawToCanonicalwithout chargingTranslatorBudget. A malformed stream can therefore grow the alias map outside the request budget. Charge every newly retained alias and add a small-budget regression proving the map is bounded.
There is also an important scope distinction. This PR can fix #938 as an explicit provider-local SSE compatibility option, but it does not yet fix the built-in DeepSeek/Codex App residual tracked in #875/#946: the built-in DeepSeek preset does not enable the option, and the WebSocket path forces bounded upstream JSON and bypasses this SSE rewrite. Either keep the PR and its closing claim explicitly scoped to #938, or add the built-in preset plus JSON/WebSocket normalization and an end-to-end function-call continuation test.
Please also sync with current dev and rerun the required full CI after the corrections. The current head is 21 commits behind and only target/label checks have run; I am not approving external code execution for this head while the blockers remain.
Make openai-responses passthrough optionally rewrite non-canonical message/reasoning ids, fold reasoning_text into encrypted_content, and keep SSE rewrite consumers alive when events are dropped so DeepSeek Responses works with Codex CLI/TUI.
Make response ids unique, mint missing item ids under rewriteNonCanonicalIds, share rewrite/trailer state through one factory, pass trailers through the Windows eager path, and add focused regression tests. Also link the PR to lidge-jun#938.
Budget responseIdMap and reasoning text, emit [DONE] for failed/incomplete, preserve plaintext reasoning for DeepSeek replay, alias rewritten response ids into local previous_response_id state, document rewriteNonCanonicalIds, and cover eager trailer plus budget regressions.
Scope rawToCanonical by message/reasoning, charge newly retained aliases, and turn on rewriteNonCanonicalIds for the built-in DeepSeek preset so the native Responses route gets the lidge-jun#938 UUID repair by default.
a9db780 to
9f25360
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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/ja/reference/configuration/providers.md`:
- Around line 200-201: Document the enabled repair behavior after the JSON
example in all three translated pages:
docs-site/src/content/docs/ja/reference/configuration/providers.md lines
200-201, docs-site/src/content/docs/ko/reference/configuration/providers.md
lines 203-204, and
docs-site/src/content/docs/ru/reference/configuration/providers.md lines
248-249. Match the English source by explaining that rewriteNonCanonicalIds
rewrites UUID-style response/item IDs, normalizes reasoning_text to
encrypted_content, preserves plaintext for replay, and appends a terminal
[DONE], while disabled mode guarantees passthrough behavior.
In `@src/providers/derive.ts`:
- Line 272: Update the responsesItemIdRepair assignment in the derive flow to
clone its nested message and reasoning arrays instead of only shallow-copying
the config object. Preserve undefined fields and match the existing
collection-copying convention used for seed.models and seed.modelContextWindows,
ensuring runtime provider state cannot alias PROVIDER_REGISTRY.
In `@src/server/responses-item-id-repair.ts`:
- Around line 220-253: Update normalizeReasoningItem to initialize next from the
original item so status and future upstream metadata are preserved, while
continuing to normalize type, id, and summary. Explicitly rebuild or remove
proxy-owned content and encrypted_content before applying the existing reasoning
text/envelope logic, and add an assertion verifying status is retained.
- Around line 497-503: Update the composition test to call
createResponsesItemIdRepairHandlers instead of
createResponsesItemIdPayloadRewrite, while preserving the backward-compatible
helper export. Compose itemId.rewrite for payload rewriting and pass
itemId.trailer to relaySseWithPayloadRewrite so the repair trailer is emitted.
- Around line 383-407: Update the rewriteNonCanonicalIds flow in the response
event handling and trailer() so every retained entry in
reasoningTextByOutputIndex is synthesized into a reasoning item before the
terminal event is forwarded. Ensure this flush applies whenever the option is
enabled, including general provider configuration rather than relying on the
DeepSeek registry default, while preserving existing handling for
reasoning_text.* and reasoning content-part events.
In `@src/server/responses/core.ts`:
- Around line 1646-1658: Add a concise comment beside mapClientResponseId
explaining that it is assigned only for SSE responses, while JSON responses do
not use Responses item-ID repair; state that any future JSON repair support must
initialize the alias before the JSON path. Do not change the existing aliasing
logic.
🪄 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: 5e7adbba-a122-48b8-bf23-5631a2f825d0
📒 Files selected for processing (15)
docs-site/src/content/docs/ja/reference/configuration/providers.mddocs-site/src/content/docs/ko/reference/configuration/providers.mddocs-site/src/content/docs/reference/configuration/providers.mddocs-site/src/content/docs/ru/reference/configuration/providers.mddocs-site/src/content/docs/zh-cn/reference/configuration/providers.mdsrc/config.tssrc/providers/derive.tssrc/providers/registry.tssrc/server/relay-eager.tssrc/server/responses-item-id-repair.tssrc/server/responses/core.tssrc/server/sse-payload-rewrite.tssrc/types.tstests/responses-item-id-repair-deepseek.test.tstests/responses-item-id-repair.test.ts
Deep-clone registry repair arrays, preserve reasoning status metadata, flush retained reasoning into terminal snapshots, document rewrite behavior in ja/ko/ru, and cover composition trailer plus residual reasoning flush.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/ru/reference/configuration/providers.md`:
- Around line 256-257: Correct the Russian number agreement in the provider
configuration description around rewriteNonCanonicalIds: either replace
“gateway” with plural “шлюзов” and retain the plural relative clause, or make
the existing singular “gateway” take singular agreement (“который возвращает”).
In `@src/server/responses-item-id-repair.ts`:
- Around line 341-378: The synthesis path in flushRetainedReasoningIntoResponse
must preserve an existing non-reasoning output item at outputIndex instead of
overwriting it. Insert the synthetic reasoning item without deleting the
assistant message or its content, and add a focused regression test in the
DeepSeek repair tests verifying the existing assistant message remains
unchanged.
🪄 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: 681bdfd9-fca7-408c-b986-fe6fae0d34ab
📒 Files selected for processing (8)
docs-site/src/content/docs/ja/reference/configuration/providers.mddocs-site/src/content/docs/ko/reference/configuration/providers.mddocs-site/src/content/docs/ru/reference/configuration/providers.mdsrc/providers/derive.tssrc/server/responses-item-id-repair.tssrc/server/responses/core.tstests/responses-item-id-repair-deepseek.test.tstests/sse-payload-rewrite.test.ts
Do not overwrite existing assistant messages when synthesizing retained reasoning, dual-write rewritten response ids for previous_response_id continuity, fix Russian docs agreement, and add regressions.
Summary
responsesItemIdRepair.rewriteNonCanonicalIdsfor openai-responses passthrough providers.resp_/msg_/rs_ids, foldreasoning_textintoencrypted_content(while keeping plaintextcontentfor DeepSeek replay), and ensure a terminal[DONE]trailer.messagevsreasoning) and charge every newly retained alias throughTranslatorBudget.deepseek/deepseek-v4-flashResponses route no longer requires a hand-edited provider config for the [Bug]: Responses passthrough UUID item IDs leave Codex turn stuck on Thinking #938 UUID stall.Fixes #938
Scope note
This PR is primarily the provider-local / built-in SSE UUID repair for #938.
It also turns the same repair on for the built-in DeepSeek preset, which covers the UUID-stall half of the #875/#946 residual on SSE. It does not claim a complete fix for the separate “no function_call_output continuation request is sent after tools” residual tracked in #875: that path still needs end-to-end tool-loop evidence on Codex App WebSocket after this lands.
Why
DeepSeek V4 Flash/Pro Responses API can return UUID item ids and raw
reasoning_textstreams. Codex App/CLI may stay on Thinking/Working even when OpenCodex logs HTTP 200 and reported usage. This keeps OpenCodex passthrough intact while normalizing the client-facing SSE for Codex.Usage
{ "providers": { "deepseeknew": { "adapter": "openai-responses", "baseUrl": "https://api.deepseek.com", "authMode": "key", "models": ["deepseek-v4-flash"], "responsesItemIdRepair": { "rewriteNonCanonicalIds": true, "repairMissingTerminalIds": true } } } }Built-in DeepSeek now seeds the same repair automatically via registry fill-only defaults.
Test plan
bun test tests/responses-item-id-repair.test.tsbun test tests/responses-item-id-repair-deepseek.test.tsbun test tests/sse-payload-rewrite.test.tsSummary by CodeRabbit
New Features
[DONE]termination marker.Bug Fixes