fix(opencode): retry empty reasoning turns
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 typecheckinpackages/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/unknownwith neither emitted andtoolChoicenotrequired, retry the identical request once. On a second empty turn, throwSessionV1.APIError(not retryable). Do not retry if any nonblank text was emitted. - Preserve error semantics by passing through
SessionV1.APIErrorinstances 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.
- Track emitted nonblank text and tool calls; on text-mode