Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ _No unreleased changes yet._

### Added

- **`[Thinking]` `budget_tokens` cap for MiMo thinking payload (#36).** MiMo 2.5 / 2.5-Pro reasoning can loop indefinitely when `reasoning_effort` is set without a token budget. Each effort level now sends `budget_tokens`: `low` → 8,192, `medium` → 16,384, `high` → 32,768. Graceful degradation via `retry.ts` handler for HTTP 400 `"budget_tokens"` rejection.
- **`[Streaming]` Go gateway `reasoning_content` workaround (#36, #37635).** The opencode-go gateway places ALL streaming response text inside `reasoning_content` instead of `content` (upstream bug [#37635](https://github.com/anomalyco/opencode/issues/37635)). When a Go-gateway request has NO `reasoning_effort` in the body (MiMo thinking OFF), `reasoning_content` is emitted as visible text instead of a thinking part. When `reasoning_effort` IS present (thinking ON), CoT correctly stays in the thinking panel. Zen gateway and all other providers unaffected. Can be removed once upstream #37635 is fixed.
- **`[Streaming]` Suffix-repetition loop detection (#36).** `OpenAiResponseExtractor.handleReasoning()` now tracks 40-char suffix across consecutive reasoning chunks. If the same suffix repeats 6+ times, the model is stuck in a word-level loop — thinking parts are suppressed and a visible warning `[Reasoning loop detected — thinking output suppressed]` is emitted instead.
- **`[Commands]` Top-level `Refresh Models` commands for Go and Zen (#78).** The `Refresh Models` action was previously buried inside the `OpenCode Go: Manage Provider` QuickPick, and Zen had no manual refresh path at all. Two new commands are now registered: `OpenCode Go: Refresh Models` and `OpenCode Zen: Refresh Models`, each bypassing the Manage menu and going straight to a model-list fetch. For parity, `OpenCode Zen: Manage Provider` is also added, matching the existing Go command. The new commands are especially useful when the picker is showing a stale or bundled list at startup (issue #78) and you want to force a re-fetch without opening the Manage menu.
- **`[Vision]` Multimodal tool results — MCP screenshot forwarding (#77).** Images returned inside a `LanguageModelToolResultPart` (e.g. screenshots from `chrome-devtools-mcp`, `playwright-mcp`) are now forwarded to vision-capable models. Previously these images were silently dropped by the serialization layer and the model would report "I cannot see the image". Images are encoded as OpenAI-style `image_url` content parts and translated into the native multimodal shape for each transport: chat-completions (native array content), Anthropic messages (`tool_result.content: AnthropicContentBlock[]`), Google Gemini (`functionResponse.response.parts: [{inlineData}]`). Oversized images (>1 MB raw bytes) are replaced with an actionable placeholder note so a single full-page MCP screenshot can't push the request payload past the upstream limit. The Responses API cannot carry images in tool output and degrades to a placeholder note on that transport only.

### Fixed

- **`[Streaming]` Regressive visible-text suppression removed.** Previous guards (`contentAfterReasoning`, `shouldSuppressTextEmit`) incorrectly blocked visible text emission for ALL reasoning models. These models naturally produce `reasoning_content` first then `content` — this is normal, not degradation. Both guards removed; only suffix-repetition loop detection remains active.
- **`[Streaming]` Go gateway reasoning leak fix (#36, #37635).** MiMo 2.5 responses were leaking thinking content into the visible chat when thinking was OFF. The `treatReasoningAsContent` workaround now only activates when ALL conditions are met: (1) request URL includes `/zen/go/`, (2) `reasoning_effort` is NOT in the request body, (3) `delta.content` is empty. This ensures the workaround applies only to the MiMo-thinking-OFF scenario while leaving all other models untouched.
- **`[Logging]` Model registration log spam during UI refresh.** VS Code refreshes model info on roughly a 300 ms cadence during chat UI activity; each call previously produced one log line per registered model (22+ lines per call). `provideLanguageModelChatInformation` now emits a single summary line per invocation (`Models registered: count=N provider=… first=… last=…`). Output channel is dramatically cleaner during testing.
- **`[Logging]` Transient model-list fetch failures no longer pop a modal warning.** OpenCode's shared gateway occasionally returns transient 400/503 responses that resolve on retry within seconds, and the previous behavior called `showWarningMessage` on every failure — including from auto-registered provider variants the user may not actively use (e.g. `OpenCode Zen (Agents)`). Failures now log to the Output channel only; the bundled `fallbackModels` snapshot keeps the picker functional.
- **`[Resilience]` Model-list fetch now tolerates transient network failures (#78).** On flaky networks (and especially on VS Code 1.129 where the new agent host raises the rate of concurrent `provideLanguageModelChatInformation` calls), a single `TypeError: fetch failed` at startup — DNS wobble, TCP reset, undici socket reuse race ([`nodejs/undici#5450`](https://github.com/nodejs/undici/issues/5450)) — caused the picker to drop to the bundled list or empty out entirely ("flash then disappear"). `fetchModels()` now (1) wraps each attempt in `AbortSignal.timeout(15_000)` so a hung connect can't stall the picker for the full undici default of 5 minutes, (2) retries up to 3 times with exponential backoff (500 ms / 1 s / 2 s) on transient errors only — `ECONNRESET`, `EAI_AGAIN`, `UND_ERR_CONNECT_TIMEOUT`, HTTP 408/429/5xx, and the generic `TypeError: fetch failed` wrapper — never on `AbortError` from VS Code's `CancellationToken` or on HTTP 4xx, (3) sends a `User-Agent` header built from the extension's `packageJSON.version` so strict gateways don't silently drop the request and so the version string can't drift again, (4) caches every successful fetch to `globalState` (`opencode.modelListCache.v1::<vendor>`, TTL 1 hour) and prefers that snapshot over the bundled list when all retries fail, (5) composes the caller's `CancellationToken` with the timeout via `AbortSignal.any([...])` so a cancelled resolution tears down the in-flight fetch immediately, and (6) sends an explicit `Accept: application/json` header so SSL-inspecting corporate firewalls / VPN proxies (Zscaler, Netskope, Fortinet) don't drop the GET as an anonymous scanner — the #78 reporter sits behind a VPN + corporate firewall on Windows 11, where POST `/chat/completions` with a JSON content type was passing but the bare GET `/models` was being dropped. See `docs/issues/35-20260720-issue78-model-list-fetch-resilience.md`.

- **`[Logging]` Model registration log spam during UI refresh.** VS Code refreshes model info on roughly a 300 ms cadence during chat UI activity; each call previously produced one log line per registered model (22+ lines per call). `provideLanguageModelChatInformation` now emits a single summary line per invocation (`Models registered: count=N provider=… first=… last=…`). Output channel is dramatically cleaner during testing.
- **`[Logging]` Transient model-list fetch failures no longer pop a modal warning.** OpenCode's shared gateway occasionally returns transient 400/503 responses that resolve on retry within seconds, and the previous behavior called `showWarningMessage` on every failure — including from auto-registered provider variants the user may not actively use (e.g. `OpenCode Zen (Agents)`). Failures now log to the Output channel only; the bundled `fallbackModels` snapshot keeps the picker functional.
- **`[Resilience]` Model-list fetch now tolerates transient network failures (#78).** On flaky networks (and especially on VS Code 1.129 where the new agent host raises the rate of concurrent `provideLanguageModelChatInformation` calls), a single `TypeError: fetch failed` at startup — DNS wobble, TCP reset, undici socket reuse race ([`nodejs/undici#5450`](https://github.com/nodejs/undici/issues/5450)) — caused the picker to drop to the bundled list or empty out entirely ("flash then disappear"). `fetchModels()` now (1) wraps each attempt in `AbortSignal.timeout(15_000)` so a hung connect can't stall the picker for the full undici default of 5 minutes, (2) retries up to 3 times with exponential backoff (500 ms / 1 s / 2 s) on transient errors only — `ECONNRESET`, `EAI_AGAIN`, `UND_ERR_CONNECT_TIMEOUT`, HTTP 408/429/5xx, and the generic `TypeError: fetch failed` wrapper — never on `AbortError` from VS Code's `CancellationToken` or on HTTP 4xx, (3) sends a `User-Agent` header built from the extension's `packageJSON.version` so strict gateways don't silently drop the request and so the version string can't drift again, (4) caches every successful fetch to `globalState` (`opencode.modelListCache.v1::<vendor>`, TTL 1 hour) and prefers that snapshot over the bundled list when all retries fail, (5) composes the caller's `CancellationToken` with the timeout via `AbortSignal.any([...])` so a cancelled resolution tears down the in-flight fetch immediately, and (6) sends an explicit `Accept: application/json` header so SSL-inspecting corporate firewalls / VPN proxies (Zscaler, Netskope, Fortinet) don't drop the GET as an anonymous scanner — the #78 reporter sits behind a VPN + corporate firewall on Windows 11, where POST `/chat/completions` with a JSON content type was passing but the bare GET `/models` was being dropped. See `docs/issues/35-20260720-issue78-model-list-fetch-resilience.md`.
Expand Down
68 changes: 62 additions & 6 deletions docs/devlog.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,73 @@
# 🧠 OPENCODE COPILOT CHAT DEVLOG
**Branch:** `fix/issue-78-model-list-fetch-resilience` | **Updated:** 2026-07-23 Asia/Jakarta | **Current Phase:** Issue #78Model List Fetch Resilience ✅ Fixed, PR open pending merge
**Branch:** `fix/mimo-thinking-budget` | **Updated:** 2026-07-23 Asia/Jakarta | **Current Phase:** Issue #36 — ✅ Fixed, ready to push

---

## ⚡ Session Handoff

| Field | Value |
|-------|-------|
| **Last Session** | 2026-07-23 |
| **Worked On** | Triaged issue #78 (reported by `@leiyu1980`, Windows 11 + VPN + corporate firewall, VS Code 1.129.0). Initial symptom looked like the closed #51 picker crash, but investigation (with web research into Node undici defaults + `nodejs/undici#5450` socket-reuse race + VS Code 1.129 agent host concurrency) confirmed it was a **transient network failure that `fetchModels()` never tolerated**. Built a 6-part resilience fix: (1) `AbortSignal.timeout(15_000)` per attempt, (2) up to 3 retries with exponential backoff (500ms/1s/2s) gated by `isTransientFetchError()` classifier, (3) `User-Agent` read from `packageJSON.version` at runtime (killed the recurring version drift), (4) `CancellationToken` composed via `AbortSignal.any([...])`, (5) 1-hour cached snapshot in `globalState` so failure falls back to last-known-good list instead of bundled, (6) `Accept: application/json` header after reporter's reply revealed their VPN/firewall passed POST `/chat/completions` but dropped bare GET `/models`. **Drive-by UX gap:** the `Refresh Models` command only existed as a sub-item inside `OpenCode Go: Manage Provider` and Zen had no Manage Provider at all — added 3 top-level commands for parity. Research stored in `/memories/repo/issue78-*.md`. Wrote `docs/issues/35-20260720-issue78-model-list-fetch-resilience.md` + updated CHANGELOG + README + bumped version `0.4.1 → 0.4.2`. Build clean: `opencode-copilot-chat-0.4.2.vsix` (1.06 MB, 115 files). |
| **Stopped At** | Ready to push `fix/issue-78-model-list-fetch-resilience` (3 commits: `8fcde64` resilience, `a04939c` commands, `ccfcb75` Accept header + version bump) and open PR. User will merge with merge commit (NEVER squash). |
| **Next Action** | → Push branch → open PR `Fixes #78` → user reviews & merges → closes #78 automatically → draft reply to `@leiyu1980` via `avoid-ai-writing` + `writing-framework-v4` skills (peer-to-peer tone, acknowledge wrong instruction in prior reply about Refresh command name). |
| **Open Issues** | (1) VS Code API gap: thread ID → session cost. (2) Qwen image quota. (3) `qwen3.6-plus-free` tool-call loop. (4) #57/#58 agent model visibility. (5) Vision proxy quota not documented in README (minor). (6) `estimateTokenCount` under-counts base64 payloads (tracked in issue doc #34). (7) `bundledModelMetadataSnapshot` could use a refresh — model list drift since 0.3.5. (8) Manual test of retry/cache path in Test C-D not run yet (network throttle + Network Link Conditioner). |
| **Last Session** | 2026-07-23 (Session 4 — stabilization) |
| **Worked On** | Final stabilization of #36 fix. After iteration 3 (suffix-repetition detection), user reported: (a) `treatReasoningAsContent` was leaking thinking to visible text, (b) `contentAfterReasoning` guard was suppressing ALL models, (c) thinking/reasoning was being "cut off" and stopping without warning. Through 4 additional iterations: (1) removed `treatReasoningAsContent` to fix leak → thinking went back to thinking panel ✅, (2) reverted `contentAfterReasoning` and `shouldSuppressTextEmit` which were false-positiving on DeepSeek/GLM/Kimi (they legitimately use `reasoning_content` then `content`), (3) re-added `treatReasoningAsContent` with correct condition: only when Go gateway + NO `reasoning_effort` in body. Key insight from web research: upstream issue #37635 is confirmed (gateway bug) and PR #37558 merged `reasoning_content` parsing — but the gateway bug itself persists. |
| **Stopped At** | All fixes verified working. Documentation updated. Ready to push. |
| **Next Action** | → Commit all changes → push `fix/mimo-thinking-budget` branch → open PR. |
| **Open Issues** | (1)-(9) same as before. (10) Log spam from agent-host provider still high (#36 debugging showed it). (11) Upstream #37635 still open, workaround can be removed when fixed server-side. |

---

## 🔬 Issue #36 — MiMo 2.5 Thinking Loop — Stabilization (Session 4)

**Commits on branch `fix/mimo-thinking-budget`:**

| # | Commit | Description |
|---|--------|-------------|
| 1 | `52af4b3` | `budget_tokens` payload + `retry.ts` handler + initial issue doc |
| 2 | `4a7c380` | CHANGELOG + devlog + issue doc update |
| 3 | `db71214` | Suffix-repetition loop detection + `flushReasoningFallback` warning |
| 4 | *(pending)* | Final stabilization: `treatReasoningAsContent` conditional logic + revert regressions |

**Fixes applied in stabilization:**

1. **`treatReasoningAsContent` leak** — Thinking content was appearing as visible text in chat because the workaround was applied unconditionally for all Go gateway models. Fixed by adding condition: `treatReasoningAsContent` only activates when `reasoning_effort` is NOT in the request body. When thinking IS on (reasoning_effort present), `reasoning_content` is genuine CoT → stays in thinking panel.

2. **Regressive suppression guards** — `contentAfterReasoning` and `shouldSuppressTextEmit` were suppressing output for ALL reasoning models (DeepSeek, GLM, Kimi). These models legitimately produce `reasoning_content` first then `content` — this is normal behavior, not degradation. Both guards fully removed. Only suffix-repetition detection remains active.

3. **Surgical condition:** `isGoGateway && !hasReasoningEffort` — exact filter that protects MiMo thinking-OFF from gateway bug while leaving all other models untouched.

---

## 🔬 Issue #36 — MiMo 2.5 Thinking Loop — Session 2026-07-23 ✅ FIXED

**Action:** User reported MiMo 2.5 (and 2.5-Pro) thinking loops without end — same reasoning content repeated 30+ times, user blocked until 10-min total timeout. Initial investigation coded `budget_tokens` cap (low=8K, medium=16K, high=32K) in `buildThinkingPayload()`. User rightly questioned: "why does it loop even with a cap?"

**Branch:** `fix/mimo-thinking-budget` (created from `fix/issue-78-model-list-fetch-resilience`)

**Compile:** `npm run compile` exit 0 (verified every change)

**Root cause — two distinct problems found:**

| # | Problem | Layer | Fix |
|---|---------|-------|-----|
| 1 | **Go gateway bug #37635** — opencode-go places ALL streaming text in `reasoning_content` instead of `content`. All Go models affected (mimo, deepseek, kimi, glm, etc.). Confirmed via direct API test by issue reporter. | Gateway (upstream) | `treatReasoningAsContent` workaround: detect `/zen/go/` URL path, emit reasoning as visible text |
| 2 | **MiMo model not converging** — model's chain-of-thought enters self-referential loop generating same text repeatedly. | Model level | `budget_tokens` caps damage; upstream fix needed from model provider |

**Web research findings:**

- `anomalyco/opencode#37635` (5 days old, assigned MrMushrooooom): "opencode-go gateway returns reasoning_content instead of content in streaming responses" — confirmed ALL opencode-go models. Non-streaming OK. Zen gateway OK.
- `anomalyco/opencode#35209` (3 weeks, assigned StarpTech): "models go into extended thinking on simple prompts" — related: thinking options not gated by model capabilities.
- `anomalyco/opencode#36354` (2 weeks, assigned jlongster): "MiMo / DeepSeek tool-call Internal server error" — reasoning_content handling broken for tool calls.

**Changes made (1 commit `52af4b3`):**

1. `src/thinking.ts` — `buildThinkingPayload()` MiMo branch: add `budget_tokens` per effort level with retry.ts handler map.
2. `src/retry.ts` — Add HTTP 400 handler for `budget_tokens` rejection (graceful degradation).
3. `src/streaming.ts` — `OpenAiResponseExtractor`: add `treatReasoningAsContent` parameter + constructor + logic. `streamChatCompletions`: detect Go gateway via URL path `/zen/go/`.
4. `docs/issues/36-20260723-mimo-thinking-infinite-loop.md` — Full issue documentation.
5. `CHANGELOG.md` — Added to [Unreleased] section.

**Workaround trade-off:** Go models lose legitimate thinking surfacing (CoT appears as visible text), but they were already broken — gateway mixes CoT + answer in `reasoning_content`. Zen models unaffected. Fix reversible when upstream #37635 is resolved.

**Manual verification:** Not run (requires Go API key + MiMo model). Workaround is deterministic — URL check, no runtime deps on model behavior.

---

Expand Down
Loading
Loading