@@ -86,7 +86,9 @@ function mapRawId(
8686 }
8787
8888 if ( ! rawId ) {
89- if ( ! state . repairMissingTerminalIds ) return null ;
89+ // When non-canonical rewrite is active, mint a request-local id even if the upstream
90+ // omitted the terminal id, so Codex lifecycle correlation stays stable.
91+ if ( ! state . repairMissingTerminalIds && ! state . rewriteNonCanonicalIds ) return null ;
9092 const minted = mintCanonicalId ( type , state . scope , outputIndex ) ;
9193 state . budget ?. chargeRetained ( new TextEncoder ( ) . encode ( JSON . stringify ( [ outputIndex , minted ] ) ) . byteLength , { kind : "item_ids" } ) ;
9294 state . outputIds [ type ] . set ( outputIndex , minted ) ;
@@ -151,7 +153,8 @@ function mapResponseId(state: ResponsesItemIdRepairState, rawId: string | undefi
151153 if ( rawId . startsWith ( "resp_" ) ) return rawId ;
152154 const existing = state . responseIdMap . get ( rawId ) ;
153155 if ( existing ) return existing ;
154- const minted = `resp_ocx_${ state . scope } ` ;
156+ // Keep each distinct upstream response id unique within the request-local scope.
157+ const minted = `resp_ocx_${ state . scope } _${ state . responseIdMap . size } ` ;
155158 state . responseIdMap . set ( rawId , minted ) ;
156159 return minted ;
157160}
@@ -220,7 +223,7 @@ function rewriteOutputItem(
220223 if ( ! mapped ) return { item, changed : false } ;
221224 const currentId = typeof item . id === "string" ? item . id : undefined ;
222225 if ( currentId === mapped ) return { item, changed : false } ;
223- if ( currentId === undefined && ! state . repairMissingTerminalIds ) return { item, changed : false } ;
226+ if ( currentId === undefined && ! state . repairMissingTerminalIds && ! state . rewriteNonCanonicalIds ) return { item, changed : false } ;
224227 return { item : { ...item , id : mapped } , changed : true } ;
225228}
226229
@@ -254,7 +257,7 @@ function rewriteItemIdField(
254257 }
255258 if ( ! mapped ) return { event, changed : false } ;
256259 if ( currentId === mapped ) return { event, changed : false } ;
257- if ( currentId === undefined && ! state . repairMissingTerminalIds ) return { event, changed : false } ;
260+ if ( currentId === undefined && ! state . repairMissingTerminalIds && ! state . rewriteNonCanonicalIds ) return { event, changed : false } ;
258261 const next : Record < string , unknown > = { ...event , item_id : mapped } ;
259262 if ( state . rewriteNonCanonicalIds && "logprobs" in next ) delete next . logprobs ;
260263 return { event : next , changed : true } ;
@@ -403,36 +406,45 @@ function repairEventPayload(
403406 * rewriteNonCanonicalIds rewrites DeepSeek-style UUID item/response ids, converts
404407 * reasoning_text streams into Codex-friendly encrypted_content reasoning items, and
405408 * ensures a terminal [DONE] trailer so CLI/TUI turns finish.
409+ *
410+ * The rewrite and trailer share one request-local state object via closure so
411+ * composition cannot lose the trailer state.
406412 */
407- export function createResponsesItemIdPayloadRewrite (
413+ export interface ResponsesItemIdRepairHandlers {
414+ rewrite : SsePayloadRewrite ;
415+ trailer : ( ) => string | undefined ;
416+ }
417+
418+ export function createResponsesItemIdRepairHandlers (
408419 config : ResponsesItemIdRepairConfig ,
409420 budget ?: TranslatorBudget ,
410- ) : SsePayloadRewrite {
421+ ) : ResponsesItemIdRepairHandlers {
411422 const state = createRepairState ( config , budget ) ;
412- const rewrite : SsePayloadRewrite = ( payload ) => repairEventPayload ( payload , state ) ;
413- ( rewrite as SsePayloadRewrite & { __state ?: ResponsesItemIdRepairState } ) . __state = state ;
414- return rewrite ;
423+ return {
424+ rewrite : ( payload ) => repairEventPayload ( payload , state ) ,
425+ trailer : ( ) => {
426+ if ( state . sawCompleted && ! state . sawDoneTrailer ) return "data: [DONE]\n\n" ;
427+ return undefined ;
428+ } ,
429+ } ;
415430}
416431
417- export function createResponsesItemIdDoneTrailer (
418- rewrite : SsePayloadRewrite ,
419- ) : ( ) => string | undefined {
420- return ( ) => {
421- const state = ( rewrite as SsePayloadRewrite & { __state ?: ResponsesItemIdRepairState } ) . __state ;
422- if ( ! state ) return undefined ;
423- if ( state . sawCompleted && ! state . sawDoneTrailer ) return "data: [DONE]\n\n" ;
424- return undefined ;
425- } ;
432+ /** Backward-compatible helper used by existing tests and callers. */
433+ export function createResponsesItemIdPayloadRewrite (
434+ config : ResponsesItemIdRepairConfig ,
435+ budget ?: TranslatorBudget ,
436+ ) : SsePayloadRewrite {
437+ return createResponsesItemIdRepairHandlers ( config , budget ) . rewrite ;
426438}
427439
428440export function relaySseWithResponsesItemIdRepair (
429441 body : ReadableStream < Uint8Array > ,
430442 config : ResponsesItemIdRepairConfig ,
431443 budget : TranslatorBudget ,
432444) : ReadableStream < Uint8Array > {
433- const rewrite = createResponsesItemIdPayloadRewrite ( config , budget ) ;
434- return relaySseWithPayloadRewrite ( body , rewrite , budget , {
435- trailer : createResponsesItemIdDoneTrailer ( rewrite ) ,
445+ const handlers = createResponsesItemIdRepairHandlers ( config , budget ) ;
446+ return relaySseWithPayloadRewrite ( body , handlers . rewrite , budget , {
447+ trailer : handlers . trailer ,
436448 } ) ;
437449}
438450
0 commit comments