Skip to content

Bump bundled @ai-sdk/openai-compatible 2.0.41 -> 2.0.75 (fixes fragmented reasoning streams) #50290

Closed as not planned

Description

@lscar

Environment

  • opencode: v1.x (desktop, bundled SDK)
  • bundled @ai-sdk/openai-compatible: 2.0.41
  • Model: any OpenAI-compatible reasoning model that streams reasoning_content (DeepSeek, Kimi, Qwen, ...)
  • Platform: Windows

Problem

Reasoning output is fragmented in the UI: every single reasoning token opens and closes its own reasoning-start / reasoning-end cycle instead of accumulating into one contiguous reasoning block. The thinking text renders word-by-word on separate lines and flickers, instead of one continuous block.

Reproduction

  1. Configure any model routed through @ai-sdk/openai-compatible that returns reasoning_content.
  2. Send any prompt.
  3. Observe the thinking block: each token is toggled on/off separately rather than appended to one block.

Evidence

Replaying the same captured SSE stream (model streams reasoning_content token by token) under Node, counting the emitted events:

version reasoning-start reasoning-end reasoning-delta
2.0.41 (currently bundled) 268 268 268
2.0.75 (latest 2.x) 1 1 268

With 2.0.41 every token becomes its own reasoning cycle; with 2.0.75 the stream is correctly kept as a single contiguous block (268 deltas appended to one part).

Root cause

Already fixed upstream in 2.0.75:

  • vercel/ai commit f3c1980: fix(openai-compatible): keep reasoning streams contiguous when deltas include empty tool call arrays
  • Released in: @ai-sdk/[email protected] (2026-09-09)

opencode currently bundles 2.0.41 (see #17902, where a maintainer confirmed "we use @ai-sdk/openai-compatible: 2.0.41"; the lockfile pins 2.0.41 as well), which does not include this fix.

Request

Please bump the bundled @ai-sdk/openai-compatible from 2.0.41 to the latest 2.0.75:

  • This stays within the 2.x line (specificationVersion: v3) and remains compatible with the current AI SDK 6 core, so no core upgrade is required.
  • It is a pure patch bump with no breaking changes.

References

Activity

  1. lscar commented on Sep 21, 2026

    @lscar
    Author

    Adding a self-contained repro that needs no captured traffic — only the trigger condition (a delta carrying reasoning_content together with an empty tool_calls: [] array, which is what DeepSeek/llama.cpp-style streams emit on every reasoning chunk).

    npm i ai@6 @ai-sdk/[email protected]   # rerun with @2.0.75

    repro.mjs:

    import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
    import { streamText } from "ai"
    import { createRequire } from "node:module"
    
    const require = createRequire(import.meta.url)
    const pkgVersion = require("@ai-sdk/openai-compatible/package.json").version
    
    const TOKENS = ["We", " need", " to", " think", " first"]
    const sse = TOKENS.map(
      (t) =>
        `data: ${JSON.stringify({
          id: "chatcmpl-test",
          object: "chat.completion.chunk",
          choices: [
            {
              index: 0,
              delta: { role: "assistant", content: "", reasoning_content: t, tool_calls: [] },
              finish_reason: null,
            },
          ],
        })}`,
    ).concat([
      `data: ${JSON.stringify({
        id: "chatcmpl-test",
        object: "chat.completion.chunk",
        choices: [{ index: 0, delta: { content: "Because of Rayleigh scattering." }, finish_reason: "stop" }],
      })}`,
    ])
    
    const fetchMock = async () =>
      new Response(
        new ReadableStream({
          async start(ctl) {
            for (const line of sse) ctl.enqueue(new TextEncoder().encode(line + "\n\n"))
            ctl.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
            ctl.close()
          },
        }),
        { status: 200, headers: { "content-type": "text/event-stream" } },
      )
    
    const provider = createOpenAICompatible({ name: "replay", baseURL: "https://replay.local/v1", fetch: fetchMock })
    
    const counts = { "reasoning-start": 0, "reasoning-delta": 0, "reasoning-end": 0, "text-delta": 0, finish: 0 }
    const result = streamText({ model: provider.chatModel("test-model"), prompt: "hi" })
    for await (const part of result.fullStream) if (part.type in counts) counts[part.type]++
    
    console.log(`@ai-sdk/openai-compatible@${pkgVersion} + ai@${require("ai/package.json").version}`)
    console.log(`  reasoning tokens sent: ${TOKENS.length}`)
    console.log(`  ${JSON.stringify(counts)}`)

    Output — identical [email protected] core, only the provider differs:

    @ai-sdk/[email protected]:

    reasoning tokens sent: 5
    {"reasoning-start":5,"reasoning-delta":5,"reasoning-end":5,"text-delta":1,"finish":1}
    

    @ai-sdk/[email protected]:

    reasoning tokens sent: 5
    {"reasoning-start":1,"reasoning-delta":5,"reasoning-end":1,"text-delta":1,"finish":1}
    

    I also replayed a real captured DeepSeek stream (268 reasoning tokens, each delta carrying tool_calls: []):

    • 2.0.41 → reasoning-start:268 / reasoning-end:268
    • 2.0.75 → reasoning-start:1 / reasoning-end:1

    Both confirm the fix already present in 2.0.75 (f3c1980 — "keep reasoning streams contiguous when deltas include empty tool call arrays") is exactly what un-fragments these streams. Bumping the bundled version from 2.0.41 to 2.0.75 resolves it without touching the AI SDK 6 core.

  2. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

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

Metadata

Metadata

Assignees

No one assigned

    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