Skip to content

[Bug]: deepseek-v4-flash-free can emit incomplete tool calls that break streamed turns #1514

Closed

Description

@yxr1995-maker

Current status

This issue is now narrowed to the remaining OpenCode Zen / DeepSeek streaming compatibility defect.

The startup syntax crash reported against 2.12.0 is already resolved on current dev: the invalid continue outside a loop is no longer present. That part should not be reimplemented.

Current dev also already accepts [DONE] without a finish_reason: the [DONE] branch flushes pending tool calls and emits done. A raw EOF with neither [DONE] nor finish_reason remains a truncation condition and should stay fail-closed unless provider evidence proves a safe alternative.

Remaining defect

openai-chat can still flush a pending tool call with an empty function name.

Current flushToolCalls() emits:

yield { type: "tool_call_start", id: call.id, name: call.name };

without first proving that call.name is non-empty.

The affected OpenCode Zen route can stream argument deltas while omitting the matching function.name. When that incomplete call is flushed, the downstream Codex tool-call contract receives an unusable call and the turn can fail.

This issue now tracks only the provider-compatibility question around incomplete streamed tool-call metadata.

Reproduction

  1. Route Codex through opencode-free/deepseek-v4-flash-free using the openai-chat adapter.
  2. Run a turn that causes the upstream to emit tool-call argument deltas without a usable function.name.
  3. Allow the stream to reach a normal flush boundary such as [DONE].
  4. Observe the pending call being emitted with an empty name and the downstream turn failing.

Expected behavior

OpenCodex must not emit a completed usable tool call with an empty name.

A fix must preserve the existing fail-closed rules for genuinely malformed or truncated tool calls. In particular:

Resolved portion

The 2.12.0 startup failure:

Cannot use "continue" here

is no longer present on current dev and is closed as part of this issue's history.

Related

Acceptance criteria

  • A pending streamed tool call with no usable function name is never emitted as a valid completed tool call.
  • Normal named tool calls remain unchanged.
  • [DONE] without finish_reason remains accepted.
  • Raw EOF without a terminal signal remains fail-closed unless separately proven safe.
  • Malformed nested tool_calls shapes remain fail-closed.
  • Focused regression coverage reproduces the OpenCode Zen incomplete-name shape without weakening generic tool-call safety.

Activity

  1. added
    bugSomething isn't working
    cliCLI, config inject, packaging flags
    installInstallation or packaging
    streamingSSE, WebSocket, terminal stream frames
    toolstool_calls, MCP, web-search / sidecar tools
    on Aug 12, 2026
  2. changed the title [-][Bug]: ocx start crashes (continue outside loop) + deepseek-v4-flash-free streams die with "invalid tool calls" on 2.12.0[/-] [+][Bug]: deepseek-v4-flash-free can emit incomplete tool calls that break streamed turns[/+] on Aug 12, 2026
  3. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    and removed
    installInstallation or packaging
    on Aug 12, 2026
  4. Wibias commented on Aug 12, 2026

    @Wibias
    Contributor

    Triage update: I narrowed this issue to the one defect that still exists on current dev.

    The startup parser crash is already fixed, and [DONE] without finish_reason is already accepted. The remaining actionable problem is that flushToolCalls() can emit a pending call with an empty function name.

    I updated the title/body and labels accordingly. Please keep any fix focused on incomplete tool-call metadata and preserve the existing fail-closed behavior for genuinely malformed or truncated streams.

  5. github-actions commented on Aug 12, 2026

    @github-actions
    Contributor

    Maintainer decision respected

    A maintainer has reopened this issue. The automated closure has been deactivated.

  6. reopened this on Aug 12, 2026
  7. lidge-jun commented on Aug 12, 2026

    @lidge-jun
    Owner

    Fixed on dev by #1531, merged as 4e0ffb2cbef0d3724a3aa9ebef26279aed44b694.

    The narrowed defect is closed: flushToolCalls() no longer emits a pending call whose function.name never arrived. An unnamed streamed call now terminates the turn through the adapter error channel, so the downstream tool-call contract never receives a call it cannot dispatch.

    The constraints listed in this issue were kept:

    Two things were found while implementing it and are included:

    1. The buffered parseResponse validator checked the type of name but not its emptiness, so a blank name was emitted there too. It is now rejected the same way.
    2. The delta.tool_calls cast is a TypeScript convenience over upstream JSON, so a truthy non-string name reached the accumulator unvalidated and threw during string handling. Present function, name, arguments, and id fields are now runtime-checked at ingest and routed to the existing invalid-tool-calls error.

    Verification at the merged tree: bun x tsc --noEmit exit 0; focused suites 48/48; 11-file adapter sweep 161/161; full bun run test 11,310 pass / 0 fail across 698 files; all four Linux CI shards plus macOS green on the PR.

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

    bugSomething isn't workingcliCLI, config inject, packaging flagsproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream framestoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions