Skip to content

[bug] openai-compatible + Gemini: thought_signature dropped on replay (SDK stores providerMetadata[provider], converter reads providerOptions.google) #50833

Description

@dj-speed

Summary

On opencode 1.18.32, Gemini tool loops over an OpenAI-compatible provider fail the moment a replayed assistant turn is served by a strict Google-backed seller:

400 Function call is missing a thought_signature in functionCall parts.
This is required for tools to work correctly, and missing thought_signature may lead to degraded model performance.

Reproduced with providerID=surplus / modelID=gemini-3.8-flash, which runs llm.runtime=ai-sdk.

Root cause — key mismatch inside @ai-sdk/openai-compatible@2.0.41

The signature is stored under the provider id but read from a hardcoded google key:

  • Store side (response parsing) — dist/index.js:600-608 reads toolCall.extra_content.google.thought_signature and stores it as providerMetadata: { [metadataKey]: { thoughtSignature } }, where metadataKey = resolveProviderOptionsKey(this.providerOptionsName, providerOptions) (dist/index.js:513) — i.e. the provider id (surplus / surplus-untrusted in my case).
  • Read side (request conversion) — dist/index.js:235 reads part.providerOptions?.google?.thoughtSignature — hardcoded google — and only then emits extra_content.google.thought_signature.

opencode preserves the key faithfully on replay (session/processor.ts stores metadata: value.providerMetadata on the tool-call part; session/message-v2.ts maps it to callProviderMetadata without renaming keys), so the part replays as providerOptions: { surplus: { thoughtSignature } } and the converter drops it.

Confirmed in the local opencode DB: a tool part's metadata contains {"surplus-untrusted":{"thoughtSignature":"…"}}.

Version matrix (published dists)

  • 2.0.41 — broken (part.providerOptions?.google?.thoughtSignature).
  • 2.0.76 (latest 2.x / ai-v6 line, 2026-09-21) — still broken.
  • 3.0.53 — fixed: convertToOpenAICompatibleChatMessages(prompt, { providerOptionsKey = "google" } = {}) (dist/index.js:117), reads part.providerOptions?.[providerOptionsKey] ?? part.providerOptions?.google (:259), and getArgs passes providerOptionsKey: metadataKey (:606).
  • opencode cannot take 3.x yet (bundled ai is v6 / spec v3).

Live repro (through a local proxy, 2026-09-23)

  • 3 parallel tool calls replayed unsigned → 400 Function call is missing a thought_signature in functionCall parts (request id 01M363XTM10E9ZRA5SBX6CK2HV; trace 01M369DBBJSMK4XCPZRDSYWRVA).
  • Same replay with the captured signature on the first call → 200 (trace 01M369DCZZ99AKMNKWN45EDVT8).
  • Same replay with extra_content.google.thought_signature: "skip_thought_signature_validator" → 200 (trace 01M369DJRCBWCD1HH4JY2D5YR2).
  • A single unsigned function call is tolerated by Google; the strict 400 is the parallel-call shape.

Related

Request

Backport the 3.x converter change to the 2.x line (accept providerOptionsKey, pass metadataKey from getArgs), or otherwise route these providers onto a fixed path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions