fix(opencode): retry empty reasoning turns

MA
3mo ago
+205-4

Root cause

Gemini can return a protocol-valid stop after reasoning without emitting text or a tool call. BrowserCode treated that semantically empty turn as complete, so Cloud later had no final response to surface.

Fix

Retry the identical model request once when a text-mode stop/unknown turn emits neither nonblank text nor a completed tool call. A second empty turn becomes an explicit non-retryable API error. Structured output, compaction, content filters, output limits, text, and tool calls keep their existing behavior.

The retry is action-safe because the rejected attempt emitted no tool call. This is a surgical Yellow-zone change at the stream semantic boundary.

Validation

  • bun run typecheck in packages/opencode
  • focused processor tests: reasoning-only then text recovers in exactly two requests; two reasoning-only turns fail after exactly two requests
  • repository pre-push typecheck: 16/16 packages

Summary by cubic

Retry empty reasoning turns in packages/opencode once; if the model stops twice without text or a tool call, surface a non‑retryable SessionV1.APIError. This prevents silent “no final answer” states with Gemini.

  • Bug Fixes
    • Track emitted nonblank text and tool calls; on text-mode stop/unknown with neither emitted and toolChoice not required, retry the identical request once. On a second empty turn, throw SessionV1.APIError (not retryable). Do not retry if any nonblank text was emitted.
    • Preserve error semantics by passing through SessionV1.APIError instances instead of wrapping them.
    • Added tests for single-retry recovery, failure after two empty turns, no-retry on partial text, and verifying the second request is identical to the first.