Repository navigation
fix(ai): replay opaque Responses item ids - #56879
rekram1-node wants to merge 1 commit into
Conversation
Co-authored-by: f.preuschoff <[email protected]>
There was a problem hiding this comment.
Looks good. I ran the #55357 reproduction from source against both checkouts. On the base, the second turn fails with the reported missing required properties including: 'id' 400, and both the reasoning and message ids are dropped from the replayed request. With this change, the turn succeeds and both bare ids are replayed exactly as the server issued them. The two changed test files pass, and the packages/ai typecheck passes.
I also checked to-llm-message.ts: stored item ids are only replayed to the same provider and model that produced them (hosted tool results to the same provider), so the looser itemID() check doesn't send ids to a different provider.
Public API: unchanged (this only changes how the Responses protocol builds its requests).
Related: #56776
Fixes #55357.
Problem
When replaying Responses history,
itemID()only kept stored item ids shapedprefix_suffixand dropped everything else. Providers that issue other valid ids lost them on replay:idon replayed reasoning items (fix(ai): replayed Responses reasoning items are sent withoutidwhen the server's item ids have noprefix_suffixshape #55357, bare hex ids like8a482c218291d43b) failed every follow-up turn with400 … missing required properties including: 'id'.zdcp8tq5p) had their own ids silently dropped.The rule was meant to keep foreign ids out after a model switch, but core already handles that: provider metadata, including item ids, is only replayed to the provider and model that produced it.
Change
const itemID = (providerMetadata, providerMetadataKey) => { const metadata = providerMetadata?.[providerMetadataKey] if (!isRecord(metadata) || typeof metadata.itemId !== "string") return undefined - const separator = metadata.itemId.indexOf("_") - return separator > 0 && separator < metadata.itemId.length - 1 ? metadata.itemId : undefined + return /^\S+$/.test(metadata.itemId) ? metadata.itemId : undefined }Ids are opaque and provider-defined, so no shape rule fits every provider:
6c671c78-c7c8-4980-910d-81c97b759ff3b402f82349296b98rs_<hex>:rs_<uuid>, messagers_<uuid><hex32>_rs,<hex32>_fc_0msg_<uuid>for every item typeEmpty and whitespace-containing ids are still dropped, since some servers reject
"".Out of scope
call_id) are a different path. They are kept across model switches to pair calls with results, and some foreign shapes can still break some targets (bug(llm): cross-provider tool-call IDs can exceed OpenAI-compatible length limit and permanently break sessions #54146, Duplicate function_call_output for reused tool-call IDs makes session unrecoverable #54588). A follow-up will add one shared, collision-safe normalizer per target.idon every replayed assistant message still rejects text that switched in from another provider, because core correctly strips foreign ids.Verification
packages/ai:bun test(1822 pass, 0 fail) andbun typecheckpass. Updated the two tests that pinned the old rule, adding real id shapes (UUIDs, bare hex,:-joined, suffix-style) and keeping empty and whitespace ids dropped.dev:idwhen the server's item ids have noprefix_suffixshape #55357: every turn fails ondevwith the reported 400, and every turn passes on this branch.Added by the opencode bot (AI-generated).
Related: #56776