Skip to content

fix(ai): replay opaque Responses item ids - #56879

Open
rekram1-node wants to merge 1 commit into
devfrom
replay-item-ids
Open

rekram1-node wants to merge 1 commit into
devfrom
replay-item-ids

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #55357.

Problem

When replaying Responses history, itemID() only kept stored item ids shaped prefix_suffix and dropped everything else. Providers that issue other valid ids lost them on replay:

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.

to-llm-message (core)
  same provider + model, no error?  → keep stored item ids and reasoning state
  otherwise                         → strip them (text/tool calls sent without id)
        ↓
itemID() (packages/ai)
  before: keep only `prefix_suffix` ids
  after:  keep any whitespace-free, non-empty id

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:

Provider Example ids
DeepSeek, Novita 6c671c78-c7c8-4980-910d-81c97b759ff3
DigitalOcean (streamed) b402f82349296b98
Meta reasoning rs_<hex>:rs_<uuid>, message rs_<uuid>
MiniMax <hex32>_rs, <hex32>_fc_0
Alibaba msg_<uuid> for every item type

Empty and whitespace-containing ids are still dropped, since some servers reject "".

Out of scope

Verification

  • packages/ai: bun test (1822 pass, 0 fail) and bun typecheck pass. 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.
  • Live, through the real CLI from source, with request logging, against this branch and dev:
    • 290 sessions and 383 model switches across OpenAI, Azure, xAI, Meta, Anthropic, Google, DeepSeek, Mistral, Bedrock, OpenRouter, Fireworks, Groq, and OpenCode Zen. Every turn forced a tool call, including error, interrupted, and return-to-previous-model turns. No request failed because of replayed state, and no item id or reasoning reached a different provider. Results were the same on both checkouts.
    • Bare-id providers (DeepSeek, DigitalOcean kimi/glm/deepseek, Novita, Groq Qwen) in same-model, switch-away-and-back, and switch-in sessions: all pass on this branch, and their own ids are now replayed verbatim.
    • A local strict gateway reproducing fix(ai): replayed Responses reasoning items are sent without id when the server's item ids have no prefix_suffix shape #55357: every turn fails on dev with the reported 400, and every turn passes on this branch.
  • Direct replay probes against about 45 provider and model setups: replaying a provider's own ids verbatim was accepted everywhere.

Added by the opencode bot (AI-generated).

Related: #56776

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

This branch has not been deployed

No deployments
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.

fix(ai): replayed Responses reasoning items are sent without id when the server's item ids have no prefix_suffix shape

1 participant