Skip to content

Agent stops after tool execution with OpenAI-compatible providers (Gemini, LiteLLM) #14972

Closed

Description

@valenvivaldi

Description

When using OpenAI-compatible providers (e.g., Gemini 3 Flash via @ai-sdk/openai-compatible, LiteLLM), the agent loop stops after executing a tool call instead of continuing to process.

Root cause: These providers return finish_reason: "stop" even when the response contains tool calls. The OpenAI standard returns "tool_calls", but providers like Gemini and LiteLLM don't follow this convention.

In packages/opencode/src/session/prompt.ts, the loop exit condition only checks finish_reason:

if (
  lastAssistant?.finish &&
  !["tool-calls", "unknown"].includes(lastAssistant.finish) &&
  lastUser.id < lastAssistant.id
) {
  break // Agent stops here even though tools were called
}

Since finish_reason is "stop" (not "tool-calls"), the loop exits prematurely.

Steps to reproduce

  1. Configure an OpenAI-compatible provider with Gemini (e.g., gemini-3-flash-preview) or LiteLLM
  2. Send a prompt that triggers tool usage (e.g., "read file X")
  3. The agent executes the tool but then stops instead of continuing to process the tool result

Expected behavior

After executing a tool call, the agent should continue processing (make another LLM call with the tool results) regardless of the provider's finish_reason.

Actual behavior

The agent stops after executing the tool. The user sees the tool output but the agent never processes it further.

Related issues

OpenCode version

1.2.11

Operating System

macOS

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Feb 25, 2026
  2. github-actions commented on Feb 25, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If your issue is specifically about the finish_reason: "stop" root cause with OpenAI-compatible providers (as you've identified), it may be worth noting that in #14842 as additional context, or confirming whether they share the same underlying cause.

  3. added a commit that references this issue on Feb 25, 2026
    acdd9b7
  4. alexsmirnov commented on Feb 28, 2026

    @alexsmirnov

    I did test provided PR with litellm router and openai gpt-5.2-codex, gemini-3-flash, gemini-3.1-pro. Initially, all models stopped conversations after any tool call response from LLM.
    The PR fixed that, I did not affect other models that worked without it. Tested: anthropic sonnet-4.6 and opus-4.6 with bedrock provider, opensource models kimi, glm, qwen, and minimax from openrouter.
    All request made through litellm router
    Vote to merge PR ASAP

  5. lalanikarim commented on Mar 3, 2026

    @lalanikarim

    This is a major blocker for using local LLMs. Please advise on timeline to address this. TIA

  6. peaster commented on Mar 6, 2026

    @peaster

    Echoing the local LLM sentiment. Especially with the new Qwen 3.5 models that are otherwise excellent in OpenCode

  7. omavi commented on Mar 6, 2026

    @omavi

    Prioritizing support for local models also feels like it should be core to the OpenCode ethos.

    At least in situations like this where someone has a small and reasonable fix with big impact.

    Nothing more open than running open source models on local compute which I expect to get more and more popular as the space develops.

  8. peltax commented on Mar 8, 2026

    @peltax

    #14973 fixed Qwen3.5-9B toolcalls for me.
    Before the model would output xml and halt like this (task was to edit a single html file):

    Output
    Thinking: <tool_call>
      ┃  <function=edit>
      ┃  <parameter=filePath>
      ┃  /var/home/homepc/projects/blog/index.html
      ┃  </parameter>
      ┃  <parameter=oldString>
      ┃  <li><a href="#section-one">Section One</a></li>
      ┃  </parameter>
      ┃  <parameter=newString>
      ┃  <li><a href="/articles.html">Articles</a></li>
      ┃  </parameter>
      ┃  </function>
      ┃  </tool_call>
    
  9. valenvivaldi commented on Mar 13, 2026

    @valenvivaldi
    ContributorAuthor

    Hey everyone, thanks for all the thumbs up! 👍

    I've been keeping the PR branch (#14973) updated with the latest dev — the bug is still present upstream as of today.

    On our end, we need this fix since our team uses a LiteLLM proxy to route requests to Gemini, OpenAI, and other providers. Without it, the agent stops after every tool call, which makes it essentially unusable for agentic workflows.

    Great to see this is also helping folks running local LLMs — glad it's not just us!

    Hoping a maintainer gets a chance to review this at some point. The fix is minimal (7 lines) and all CI checks pass.

  10. whuanle commented on Mar 15, 2026

    @whuanle

    I also think this is the case. Using the opencode free model is normal. Using the OpenAI model, it works fine in the "plan" mode, but there will be issues in the "build" mode.

    Image
  11. thatdaveguy1 commented on Mar 29, 2026

    @thatdaveguy1

    I checked preserved local session history in ~/.local/share/opencode/opencode.db and found a concrete case that matches this issue in normal github-copilot / gemini-3-flash-preview usage.

    Observed across sessions from OpenCode 1.2.27, 1.3.0, 1.3.3, and 1.3.4.

    Representative stored example:

    • session: ses_2c985a0feffeXDeX2g4mxRvUnm
    • title: Adjust map zoom levels and unify map style
    • assistant message: msg_d367a9f53001VlicFCVcETMmIC

    That assistant message has these parts in order:

    • step-start
    • tool with tool = "task" and state.status = "completed"
    • step-finish with reason = "stop"

    Part IDs from the DB:

    • prt_d367aab57001TqwXlN1A62yYEI -> step-start
    • prt_d367aab5a0017DHPw77DfL01N0 -> completed tool(task)
    • prt_d367ac890002O5QsgpPAL5wNKL -> step-finish(reason=stop)

    So this is not just a UI impression: OpenCode recorded a real completed tool part, then still treated the turn as finished. That lines up exactly with the loop-exit bug described here and with the proposed fix in #14973 to check for tool parts before breaking on finish_reason = "stop".

    I also found a separate Copilot Gemini issue where tool intent is emitted as plain text instead of structured tool parts; I'm adding that evidence to #15315 separately since it seems distinct.

  12. valenvivaldi commented on Mar 30, 2026

    @valenvivaldi
    ContributorAuthor

    Branch updated — merged latest dev and resolved a conflict in packages/opencode/src/session/prompt.ts.

    The fix in the PR is still necessary after the merge. Details on the conflict resolution and why here: #14973 (comment)

  13. ctz23 commented on Apr 1, 2026

    @ctz23

    Confirming this on v1.3.13 via a self-hosted LiteLLM proxy, and can add two additional failure modes beyond the Gemini hang.

    1. Gemini (gemini-2.5-flash-preview) — same root cause as described
    Exact behavior from the issue: agent executes the tool call, LiteLLM logs show the response arriving cleanly, then nothing. The proxy's hanging request checker fires every ~2.5 minutes indefinitely. No second dispatch ever happens. The finish_reason: "stop" explanation fits perfectly.

    2. Qwen (via OpenRouter) — loop instead of hang
    Rather than stopping, the agent enters a dispatch loop. LiteLLM logs show the same cache key hashed and dispatched twice within the same session after 1–2 tool calls, then the hanging checker fires indefinitely — neither stopping nor progressing. This may be the same finish_reason misread causing the loop-continuation logic to re-enter instead of break, depending on how the provider varies its response.

    3. Qwen3-Coder (via NVIDIA) — infinite streaming loop, no stop token
    A harder failure: the model never emits a stop token at all. streaming_handler.py streamed the same ~5 bullet points on repeat for 60+ seconds until a forced container restart. After restart, the new container continued receiving the same in-flight stream before the connection dropped. Likely a related but distinct issue — the model may be looping on tool-result processing at the generation level.

    All three backends are disabled in config as a workaround.

  14. motclem commented on Jul 29, 2026

    @motclem

    Confirming this also reproduces with a third OpenAI-compatible provider, so it doesn't seem specific to Gemini/LiteLLM.

    Provider: MTPLX (local inference server, OpenAI-compatible endpoint at http://127.0.0.1:8001/v1)
    Model: gemma4-mtplx-optimized-speed
    opencode version: 1.18.9
    Reproduces in: both the plain TUI and via Xcode's ACP integration (opencode acp), so it's not client-specific.

    Same pattern as described in the issue — a tool call executes successfully (confirmed via git diff, real correct edits land on disk), but the session ends with no final assistant message. Log shows the loop advancing and exiting immediately after the tool round, with no second stream call and no error:

    timestamp=...T18:57:30.836Z level=INFO message=stream providerID=mtplx modelID=gemma4-mtplx-optimized-speed ... agent=build mode=primary
    timestamp=...T18:58:18.550Z level=INFO message=tracking hash=... (file change tracked)
    timestamp=...T18:58:18.570Z level=INFO message=loop session.id=... step=2
    timestamp=...T18:58:18.570Z level=INFO message="exiting loop" session.id=...

    This matches the root cause described above — the loop-exit check only looks at finish_reason against ["tool-calls", "unknown"], so any provider that returns "stop" on a turn that actually contained tool calls will silently truncate the session. Given it's now confirmed across Gemini, LiteLLM, and MTPLX, this seems like a fairly broad compatibility gap rather than a single-provider quirk — might be worth having the loop check for the presence of tool calls in the response directly, rather than trusting the provider's finish_reason string.

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

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions