Skip to content

fix(responses): done-event repair must not clobber tool-call identity (#244) - #246

Merged
ltmoerdani merged 2 commits into
mainfrom
fix/issue244-followup-id-clobber
Sep 25, 2026
Merged

ltmoerdani merged 2 commits into
mainfrom
fix/issue244-followup-id-clobber

Conversation

@ltmoerdani

Copy link
Copy Markdown
Owner

Follow-up to #244. Fixes the 0.7.7 regression reported in this comment: after updating, every OpenAI-model request fails with 400 No tool output found for function call call_*.

Root cause

The #244 fix (0.7.7) added a response.function_call_arguments.done repair delta with id: firstString(call_id, item_id). The real luna done event carries only item_id (fc_1) — never call_id — so the accumulator adopted the item id, REPLACING the real call_* id captured from output_item.added. Item ids are reused across turns: two turns both carrying fc_1 produced two function_call items against one function_call_output at the gateway, which rejects the whole request.

Proven empirically by replaying the captured luna event shapes: part id came out fc_1 instead of call_Vf1vzJwf5xa44CfwtrzYe4y7.

Fix (3 layers + identity rule)

Layer File Change
Root cause src/core/routing.ts Done event forwards id only when a real call_id is present — repair is arguments-only
Defense src/toolCallAccumulator.ts Id captured once, never overwritten by later fragments
Self-heal src/responsesRequest.ts pairResponsesFunctionCallItems collapses duplicate function_call items sharing a call_id — histories poisoned by 0.7.7 recover without clearing the chat

Identity rule enforced on every Responses entry point: output_item.added and normalizeResponsesFullResponse no longer fall back to item.id either.

The original #244 arguments-whitespace fix is preserved and pinned by test.

Verification

  • 6 regression tests in src/test/issue244-followup-regression.test.ts (identity preserved, round-trip pairing, arguments repair preserved, poisoned-history dedupe, added-handler id rule, full-response id rule)
  • 495/495 unit tests, lint 7/7, retry E2E mock server 9/9
  • 17-check pre-release E2E (tmp/e2e-issue244-prerelease.mjs): full tool-call loop over the captured luna shapes — whitespace preserved, wire pairing 1:1 per turn, cross-turn item-id reuse clean, poisoned history self-heals
  • VSIX 0.7.8 built and installed locally

Notes for merge

Per repo policy: merge commit (gh pr merge --merge), no squash.

…#244)

The 0.7.7 #244 repair delta forwarded id: firstString(call_id, item_id).
The real luna function_call_arguments.done event carries only item_id
(fc_1) — never call_id — so the accumulator adopted the item id,
replacing the real call_* id captured from output_item.added. Item ids
are reused across turns: two turns both carrying fc_1 produced two
function_call items against one function_call_output at the gateway,
which rejects the request with 400 "No tool output found for function
call call_*".

Three-layer fix:
- done event forwards id only when a real call_id is present (arguments-
  only repair — the #244 arguments fix is preserved)
- ToolCallAccumulator captures the id once, never overwritten by later
  fragments
- pairResponsesFunctionCallItems collapses duplicate function_call items
  sharing a call_id, self-healing histories poisoned by 0.7.7

Proven empirically against the captured luna event shapes: part id stays
call_* end to end. 4 regression tests; 493/493 pass. Version bump 0.7.8.

Docs: docs/issues/108, doc 107 annotated, CHANGELOG 0.7.8, devlog,
ARCHITECTURE-MAP.
Post-fix audit found the #244-followup fallback (firstString(call_id,
item_id)) in two more places: the output_item.added handler and the
normalizeResponsesFullResponse function_call mapping. Both now derive
call identity from call_id only — item ids (fc_*) are reused across
turns and must never become call identity. Gateways that send call_id
(all known real shapes) are unaffected.

Adds 2 regression tests and a 17-check pre-release E2E simulating the
full tool-call loop: whitespace preservation, wire pairing per turn,
cross-turn item-id reuse, and poisoned-history self-heal. 495/495 unit
tests, lint 7/7, retry E2E 9/9.
@ltmoerdani
ltmoerdani merged commit 191f6df into main Sep 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant