Goal
Resolve the remaining Codex auto-continuation translator review blocker wu-review-remediation-6e183da7d5a2b00a for the Claude translator.
Rationale
The mission is blocked by three contract issues in internal/translator/codex/claude/codex_claude_response.go after the latest review of wu-review-remediation-67c6892edc1c3d36:
- A
response.output_item.done keyed only by call_id can bind to LastPendingFunctionCallKey when multiple unnamed function calls are pending, closing the wrong block.
response.output_item.added for an unnamed function call increments BlockIndex before any Claude block is emitted, so the first visible terminal block can start at index 1 instead of 0.
- Terminal
response.output arguments are dropped for an already-open named tool call if no streamed argument delta/done arrived before terminal fallback.
Project policy requires write/maintain/admin permission before making a translator-only change. gh repo view --json viewerPermission -q .viewerPermission returned READ, so I cannot apply the fix directly.
Intended implementation
Keep this scoped to internal/translator/codex/claude/codex_claude_response.go and focused tests in internal/translator/codex/claude/codex_claude_response_test.go.
1. Track pending unnamed function calls by both output key and call_id
When deferring an unnamed function_call, store one pending object under output:<output_index> and, when present, call:<call_id>. Lookup should use exact key first, then call-id alias from the done item. Avoid falling back to LastPendingFunctionCallKey for response.output_item.done when the event has a call_id that does not match.
Sketch:
func recordPendingCodexFunctionCall(params *ConvertCodexResponseToClaudeParams, rootResult, itemResult gjson.Result) {
if params.PendingFunctionCalls == nil {
params.PendingFunctionCalls = map[string]*pendingCodexFunctionCall{}
}
pending := &pendingCodexFunctionCall{CallID: codexFunctionCallID(itemResult)}
key := codexFunctionCallKey(rootResult, itemResult)
params.PendingFunctionCalls[key] = pending
if pending.CallID != "" {
params.PendingFunctionCalls["call:"+pending.CallID] = pending
}
params.LastPendingFunctionCallKey = key
}
func pendingCodexFunctionCallForDone(params *ConvertCodexResponseToClaudeParams, rootResult, itemResult gjson.Result) (*pendingCodexFunctionCall, []string) {
keys := []string{codexFunctionCallKey(rootResult, itemResult)}
if callID := codexFunctionCallID(itemResult); callID != "" {
keys = append(keys, "call:"+callID)
} else if rootResult.Get("output_index").Exists() {
keys = append(keys, "output:"+rootResult.Get("output_index").Raw)
} else if params.LastPendingFunctionCallKey != "" {
keys = append(keys, params.LastPendingFunctionCallKey)
}
for _, key := range keys {
if pending, ok := params.PendingFunctionCalls[key]; ok {
return pending, keysForPendingFunctionCall(params, pending)
}
}
return nil, nil
}
Delete every alias for the matched pending object after emission.
2. Do not reserve BlockIndex for invisible deferred calls
For unnamed response.output_item.added, do not increment params.BlockIndex. When the deferred call becomes visible on named response.output_item.done, emit at the current params.BlockIndex, stop that block, then increment once. This keeps visible Claude block indexes dense and ordered.
Sketch replacement in the deferred done path:
blockIndex := params.BlockIndex
output = appendCodexFunctionCallStart(output, originalRequestRawJSON, callID, name, blockIndex)
...
output = appendCodexFunctionCallStop(output, blockIndex)
params.BlockIndex++
3. Hydrate already-open tool calls from terminal output when arguments never streamed
If response.completed / response.incomplete terminal output contains a function call matching the currently open FunctionCallBlockCallID and HasReceivedArgumentsDelta is false, emit the terminal arguments delta before finalizeCodexOpenContentBlocks closes the block, or teach appendCodexTerminalFunctionCall to hydrate the open block instead of returning early on the emission key.
Sketch before closing open blocks on terminal events:
func hydrateOpenCodexFunctionCallFromTerminal(output []byte, params *ConvertCodexResponseToClaudeParams, responseData gjson.Result) []byte {
if params == nil || !params.FunctionCallBlockOpen || params.HasReceivedArgumentsDelta {
return output
}
responseData.Get("output").ForEach(func(_, item gjson.Result) bool {
if item.Get("type").String() != "function_call" || codexFunctionCallID(item) != params.FunctionCallBlockCallID {
return true
}
if args := item.Get("arguments").String(); args != "" {
output = appendCodexFunctionCallArgumentDelta(output, args, params.FunctionCallBlockIndex)
params.HasReceivedArgumentsDelta = true
}
return false
})
return output
}
Then call it before finalizeCodexOpenContentBlocks(params) for terminal stream events.
Required tests
Add focused stream tests for:
- Two unnamed pending function calls; done for the earlier call keyed only by
call_id emits at the correct block and does not consume the later pending slot.
- Deferred unnamed function call that never becomes visible before terminal text/fallback does not shift the first visible block index from
0 to 1.
- Named tool call opened by
response.output_item.added, no streamed argument event, terminal response.output includes final arguments; Claude receives those arguments before content_block_stop.
Focused validation should include existing translator cases covering ghost stops, incomplete close, terminal hydration, mixed output_index/call_id close, and stop-reason mapping.
Goal
Resolve the remaining Codex auto-continuation translator review blocker
wu-review-remediation-6e183da7d5a2b00afor the Claude translator.Rationale
The mission is blocked by three contract issues in
internal/translator/codex/claude/codex_claude_response.goafter the latest review ofwu-review-remediation-67c6892edc1c3d36:response.output_item.donekeyed only bycall_idcan bind toLastPendingFunctionCallKeywhen multiple unnamed function calls are pending, closing the wrong block.response.output_item.addedfor an unnamed function call incrementsBlockIndexbefore any Claude block is emitted, so the first visible terminal block can start at index1instead of0.response.outputarguments are dropped for an already-open named tool call if no streamed argument delta/done arrived before terminal fallback.Project policy requires write/maintain/admin permission before making a translator-only change.
gh repo view --json viewerPermission -q .viewerPermissionreturnedREAD, so I cannot apply the fix directly.Intended implementation
Keep this scoped to
internal/translator/codex/claude/codex_claude_response.goand focused tests ininternal/translator/codex/claude/codex_claude_response_test.go.1. Track pending unnamed function calls by both output key and call_id
When deferring an unnamed
function_call, store one pending object underoutput:<output_index>and, when present,call:<call_id>. Lookup should use exact key first, then call-id alias from the done item. Avoid falling back toLastPendingFunctionCallKeyforresponse.output_item.donewhen the event has acall_idthat does not match.Sketch:
Delete every alias for the matched pending object after emission.
2. Do not reserve
BlockIndexfor invisible deferred callsFor unnamed
response.output_item.added, do not incrementparams.BlockIndex. When the deferred call becomes visible on namedresponse.output_item.done, emit at the currentparams.BlockIndex, stop that block, then increment once. This keeps visible Claude block indexes dense and ordered.Sketch replacement in the deferred done path:
3. Hydrate already-open tool calls from terminal output when arguments never streamed
If
response.completed/response.incompleteterminal output contains a function call matching the currently openFunctionCallBlockCallIDandHasReceivedArgumentsDeltais false, emit the terminalargumentsdelta beforefinalizeCodexOpenContentBlockscloses the block, or teachappendCodexTerminalFunctionCallto hydrate the open block instead of returning early on the emission key.Sketch before closing open blocks on terminal events:
Then call it before
finalizeCodexOpenContentBlocks(params)for terminal stream events.Required tests
Add focused stream tests for:
call_idemits at the correct block and does not consume the later pending slot.0to1.response.output_item.added, no streamed argument event, terminalresponse.outputincludes final arguments; Claude receives those arguments beforecontent_block_stop.Focused validation should include existing translator cases covering ghost stops, incomplete close, terminal hydration, mixed output_index/call_id close, and stop-reason mapping.