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.
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:
Reproduced with
providerID=surplus/modelID=gemini-3.8-flash, which runsllm.runtime=ai-sdk.Root cause — key mismatch inside
@ai-sdk/openai-compatible@2.0.41The signature is stored under the provider id but read from a hardcoded
googlekey:dist/index.js:600-608readstoolCall.extra_content.google.thought_signatureand stores it asproviderMetadata: { [metadataKey]: { thoughtSignature } }, wheremetadataKey = resolveProviderOptionsKey(this.providerOptionsName, providerOptions)(dist/index.js:513) — i.e. the provider id (surplus/surplus-untrustedin my case).dist/index.js:235readspart.providerOptions?.google?.thoughtSignature— hardcodedgoogle— and only then emitsextra_content.google.thought_signature.opencode preserves the key faithfully on replay (
session/processor.tsstoresmetadata: value.providerMetadataon the tool-call part;session/message-v2.tsmaps it tocallProviderMetadatawithout renaming keys), so the part replays asproviderOptions: { surplus: { thoughtSignature } }and the converter drops it.Confirmed in the local opencode DB: a tool part's
metadatacontains{"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), readspart.providerOptions?.[providerOptionsKey] ?? part.providerOptions?.google(:259), andgetArgspassesproviderOptionsKey: metadataKey(:606).aiis v6 / spec v3).Live repro (through a local proxy, 2026-09-23)
Function call is missing a thought_signature in functionCall parts(request id01M363XTM10E9ZRA5SBX6CK2HV; trace01M369DBBJSMK4XCPZRDSYWRVA).01M369DCZZ99AKMNKWN45EDVT8).extra_content.google.thought_signature: "skip_thought_signature_validator"→ 200 (trace01M369DJRCBWCD1HH4JY2D5YR2).Related
llm.runtime=ai-sdk(the default for these providers in 1.18.32) the SDK key mismatch still drops the signature.Request
Backport the 3.x converter change to the 2.x line (accept
providerOptionsKey, passmetadataKeyfromgetArgs), or otherwise route these providers onto a fixed path.