Responses adapter hygiene: reasoning drop, dedupe, indexer leak, tool-name codec, double SSE parse (CL-6912) - #566
Merged
Conversation
- signatureForModel no longer drops reasoning when a persisted turn is missing its model field; a genuine model mismatch now also drops the function_call items that reasoning produced, avoiding the orphaned shape that degenerates reasoning models - dedupeToolOutputs (renamed dedupeToolItems) keeps the latest function_call/function_call_output on a duplicate call_id instead of the stale first one, and now covers duplicate function_call items too - the Responses block indexer is recreated per buildRequest instead of once per adapter instance, fixing an unbounded per-conversation leak - all three Responses adapters route tool names through the shared encode/decode codec instead of sending raw package-qualified ids - openai-compatible only re-parses SSE frames for DeepSeek/NIM models; every other frame hits the base parser once instead of twice
… too sanitizeReplayTurns runs before the adapter's own buildRequest via withReplaySanitizer, and its stripForeignBlocks gate had the identical undefined-model-is-foreign bug as signatureForModel, deleting the signature before the adapter ever saw it. The vendored transformMessages has the same same-model check and is not ours to change, so a model-less assistant turn is now stamped with the target model before either stage runs, instead of loosening either stage's foreign check directly. Genuine cross-provider turns (a turn with a real, different model) are untouched -- still stripped, still fail closed.
TheGreatAxios
enabled auto-merge
August 23, 2026 20:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CL-6912. Grab-bag hygiene fixes across the three Responses adapters (
codex-responses-adapter.ts,grok-responses-adapter.ts,openai-responses-adapter.ts) andopenai-compatible-adapter.ts.This touches a provider adapter inside the inference loop. Needs an eval matrix run before merge — do not auto-merge.
needs-sawyer-review.Per-defect verdict
signatureForModelrequiresturn.model === requestModel;modelis optional on the persisted turn schema, so turns saved without it lose reasoning on resumesignatureForModelincodex-responses-adapter.tswas the visible site but not the load-bearing one: every real request goes throughwithReplaySanitizerfirst (src/provider/inference-dependencies.ts), whosesanitizeReplayTurns(replay-sanitizer.ts) had the identical undefined-model-is-foreign bug instripForeignBlocks's gate, deleting the signature before the adapter ever ran. Fixed both. The vendoredtransformMessages(invendor/intx-inference, not editable) has the same same-model check baked in, so a model-less assistant turn is now stamped with the target model before either sanitizer stage runs, rather than trying to loosen the vendored check. Genuinely foreign turns (a real, differentmodel) are untouched. Also added: a genuine model mismatch now drops thefunction_callitem(s) that the dropped reasoning produced, instead of leaving them orphaned — the exact shape that degenerates reasoning modelscodex-responses-adapter.test.ts:signatureForModel+ "orphaned function_call suppression" describe blocks (direct-adapter unit tests);replay-sanitizer.test.ts: "carries a reasoning signature through the real buildRequest path when the turn has no model" — goes throughwithReplaySanitizerend-to-end, the actual production path, and fails without thereplay-sanitizer.tsfix even with the adapter fix in placededupeToolOutputskeeps the stale FIRSTfunction_call_outputon a duplicatecall_id; duplicatefunction_callitems aren't deduped at allgrok-responses-adapter.tsandopenai-responses-adapter.ts, the two files that have this function)dedupeToolItems; keeps the LAST occurrence for bothfunction_callandfunction_call_outputgrok-responses-adapter.test.ts: "keeps the latest function_call_output..." / "dedupes a duplicate function_call..."; updated the now-incorrect pre-existing assertion intests/unit/grok-responses-adapter.test.tsthat expected the stale-first behaviorvendor/intx-inference/src/adapter.ts:146) and the harness resolves a fresh adapter per generator invocation, so each HTTP round trip probably already gets a new adapter object in practice. Fixed anyway — defensively correct and removes the dependency on that memoization behaviorbuildRequest, so each HTTP round trip starts a fresh indexer regardless of adapter lifetimecodex-responses-adapter.test.ts: "block indexer reset" describe blocktoResponsesTools/toResponsesItemsnow callencodeToolName, and the sharedparseResponse'sresponse.output_item.addedhandler callsdecodeToolName, using a sharedRESPONSES_TOOL_NAME_LIMIT(64 chars, same OpenAI wire charset)codex-responses-adapter.test.ts: "tool-name codec" describe blockopenai-compatibleparses every SSE frame twice (JSON.parse → restringify → base parses again)buildRequestnow gates it, so every non-DeepSeek frame callsbase.parseResponsedirectly with a single parseopenai-compatible-adapter.test.ts: "SSE parse count" describe blockCL-6904 (prompt_cache_key) and CL-6905 (cross-provider signature tagging) already landed in this area and are untouched — this PR builds on top of the provider-tagged signature scheme CL-6905 introduced.
Did not pursue the CL-6811 grok-adapter fork-dedup consolidation mentioned as optional in the ticket — it would have widened this diff considerably without changing behavior, and increases the odds of an eval regression outside this ticket's scope.
Noticed but not touched (out of scope)
codex-responses-adapter.ts'sbuildRequestnever calls a dedupe pass at all (unlike grok/openai-responses) — same duplicate-call_id class of bug could reach Codex, just via a different code path. Left alone since the ticket's defect list names the existingdedupeToolOutputsfunction specifically, which codex doesn't have.