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
6 changes: 3 additions & 3 deletions ARCHITECTURE-MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ flowchart TD
| Claude | `/^claude-/i` | `messages` | `null` | any |
| MiniMax m2.x | `/^minimax-m2\./i` | `messages` | `minimax` | `opencodego` only (Zen → chat-completions) |
| Qwen Messages | `/^qwen3\.(?:5\|6)-plus(?:-free)?$/` · `/^qwen3\.7-max$/` | `messages` | `qwen` | any |
| Gemini | `/^gemini-/i` | `google` | `null` | `opencodezen` only |
| Gemini | `/^gemini-/i` | `google` | `gemini` | any |
| MiniMax | `/^minimax-/i` | `chat-completions` | `minimax` | any |
| DeepSeek | `/^deepseek-/i` | `chat-completions` | `deepseek` | any |
| GLM | `/^glm-/i` | `chat-completions` | `glm` | any |
Expand All @@ -245,7 +245,7 @@ flowchart TD

> **Scope note:** context limits / capabilities / cost are **NOT** in this table — they stay metadata-driven (live models.dev via `src/models/metadata.ts`). Do not duplicate them into a static table.

**Endpoint URL resolution** (`resolveModelRouting`): agent-host variants resolve to their base vendor first; `responses` → `provider.responsesUrl`, `messages` → `provider.messagesUrl`, `google` → `${provider.modelsUrl}/${modelId}`, default → `provider.chatCompletionsUrl`.
**Endpoint URL resolution** (`resolveModelRouting`): agent-host variants resolve to their base vendor first; `responses` → `provider.responsesUrl`, `messages` → `provider.messagesUrl`, `google` → `${provider.googleModelsUrl ?? provider.modelsUrl}/${modelId}`, default → `provider.chatCompletionsUrl`.

---

Expand Down Expand Up @@ -476,7 +476,7 @@ On push/PR to `main`/`develop`, Node 20: `npm ci` → `compile` → `lint` (with
- **Activation:** `onStartupFinished`, `onLanguageModelChatProvider:opencodego`, `onLanguageModelChatProvider:opencodezen`
- **Contribution points:** `commands` (16), `configuration` (35+ keys), `languageModelChatProviders` (4 vendors: `opencodego`, `opencodezen`, `opencodego-agent`, `opencodezen-agent`)
- **4 providers:** Go (paid) + Zen (free default) + agent-host variants mirroring each base vendor
- **Endpoints:** `https://opencode.ai/zen/{go/,}v1/{models,chat/completions,messages,responses}` + `https://opencode.ai/zen/go/v1/usage` (server meters) + `https://models.dev/api.json` (metadata)
- **Endpoints:** `https://opencode.ai/zen/{go/,}v1/{models,chat/completions,messages,responses}` + `https://opencode.ai/inference/go/v1/models` (Go Gemini route, see `googleModelsUrl`) + `https://opencode.ai/zen/go/v1/usage` (server meters) + `https://models.dev/api.json` (metadata)

---

Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

All notable changes to the **OpenCode Go BYOK Provider** extension are documented here.

## [0.7.10] — unreleased

### Added

- **`[Thinking]` Gemini models now get a Thinking Effort picker.** Gemini had no thinking control at all: its registry row declared no thinking family, and the generic `reasoning_effort` fallback is deliberately gated to the chat-completions endpoint (that field is meaningless on the Google wire format), so the picker never rendered and the request went out with `thinkingPayload={}` — the model always answered at its default effort. A new `GeminiThinking` strategy maps the pick to Google's native `generationConfig.thinkingConfig` (`thinkingLevel` + `includeThoughts: true`); verified live on `gemini-4-argon-contributor` that `thinkingLevel: "medium"/"high"` is accepted (HTTP 200) and thinking is actually performed (`usageMetadata.thoughtsTokenCount` billed). Caveat documented in `thinking/gemini.ts`: the gateway relays only an opaque `thoughtSignature`, not thought summaries, so the thinking panel stays empty for Gemini (same class as Muse Spark's `encrypted_content`) — if upstream starts relaying `thought: true` parts the existing Google normalizer maps them with no further client work. Levels come from the model's own models.dev `reasoning_options` (`off`/`on`/`low`/`medium`/`high`), with `opencodego.thinking.gemini` as the workspace default. `off` sends no thinking config at all (model default, unchanged from before).

### Fixed

- **`[Models]` Gemini models served by OpenCode Go now route to the Google API instead of chat-completions.** Every request to `gemini-4-argon-contributor` failed with `400 Model does not support this protocol.` because the `gemini` row in `core/registry.ts` was restricted to `opencodezen`, so on Go the lookup fell through to the catch-all row and the model was sent to `chat/completions` — while the gateway serves it Google-native (models.dev marks it `@ai-sdk/google`, and OpenCode's own Go endpoint table lists it under `@ai-sdk/google` too). The row now applies to every vendor. Fixing that exposed the second half of the bug: the Google route is built as `{base}/{modelId}:streamGenerateContent?alt=sse`, and Go's built-in `/zen/go/v1` base does not serve that path at all (it answers with the opencode.ai website 404 page), so provider routing gained an optional `googleModelsUrl` — Go's built-in default points at `https://opencode.ai/inference/go/v1/models` (the base OpenCode's own endpoint table documents for the model), while Zen and any configured `apiBaseUrl` gateway keep deriving the route from `modelsUrl` exactly as before (issue #247 contract intact). Every other route (chat/completions, messages, responses, models, usage) is untouched. Routing tests now cover Go, Zen, and both agent-host variants. Registry drift found while investigating (Go/Zen `minimax-m3`, `qwen3.8-max`, `qwen3.8-flash`, `qwen3.7-plus` documented as Anthropic `messages`) is deliberately not changed here — each needs live verification first. Documented in `docs/features/17-20260814-data-driven-model-registry.md`.

- **`[Usage]` Google-route responses now report usage and finish reasons.** `updateRequestUsageSummary` understood OpenAI, Responses, and Anthropic payload shapes but not Google's `usageMetadata` / `candidates[].finishReason`, so every Gemini request logged `promptTokens=n/a … finishReason=<unknown>` and the Go usage meter recorded `promptTokens=0` — silently losing cost tracking for the whole family. The Google normalizer's own usage mapping is now reused for the summary. With usage visible again, the empty-response diagnostic (`[diag-empty-response]`, the raw-SSE dump built for exactly the "gateway sent bytes but nothing parsed" case) no longer requires completion tokens to fire — that precondition made it unreachable on Google responses and left an empty `200 OK` stream of 5 unparsed events undiagnosable.

- **`[Models]` Gemini requests no longer send `temperature` to models that declare it unsupported.** The Google body builder sent `temperature` unconditionally even when models.dev marks the model `temperature: false` (`gemini-4-argon-contributor`) — the OpenAI and Anthropic builders already guarded this. Guarded the same way, and the builder now receives the model metadata it needs for both this and the thinking payload.

## [0.7.9] — 2026-10-13

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Deprecated/unavailable models are filtered before registration. Per-provider lim
| Family | Endpoint | Why |
| ----------------------------------------------------- | -------------------------------- | -------------------- |
| Zen GPT (`gpt-*`) | `/responses` | OpenAI native |
| Zen Gemini (`gemini-*`) | `:streamGenerateContent?alt=sse` | Google native |
| Gemini (`gemini-*`, Zen + Go) | `:streamGenerateContent?alt=sse` | Google native |
| Zen Claude (`claude-*`) + Go MiniMax (`minimax-m2.*`) | `/messages` | Anthropic-compatible |
| Everything else (Qwen, DeepSeek, GLM, Kimi, MiMo…) | `/chat/completions` | OpenAI-compatible |

Expand Down Expand Up @@ -248,6 +248,7 @@ Per-model reasoning configuration, dynamically enhanced with `reasoning_options`
| **MiniMax** | `off` / `on` | `opencodego.thinking.minimax` |
| **Mimo (Xiaomi)** | `off` / `low` / `medium` / `high` | `opencodego.thinking.mimo` |
| **Qwen** | `auto` / `on` / `off` + `thinking_budget` (4096–81920) | `opencodego.thinking.qwen` + `.qwenBudget` |
| **Gemini (Google)** | `off` / `on` / `low` / `medium` / `high` (`thinkingLevel`, thoughts streamed) | `opencodego.thinking.gemini` |
| **Any future reasoning model** | `off` / `low` / `medium` / `high` / `xhigh` / `max` (auto-detected from `models.dev`) | `opencodego.thinking.generic` |

> **`opencodego.debugReasoning`** writes provider `reasoning_content` to **Output → OpenCode** for debugging.
Expand Down Expand Up @@ -414,6 +415,7 @@ Provider diagnostics also include the VS Code/extension versions, extension host
| `opencodego.thinking.mimo` | `"off"` | `off`/`low`/`medium`/`high` |
| `opencodego.thinking.qwen` | `"off"` | `auto`/`on`/`off` |
| `opencodego.thinking.qwenBudget` | `"auto"` | `auto`/`4096`/`16384`/`32768`/`81920` |
| `opencodego.thinking.gemini` | `"off"` | `off`/`on`/`low`/`medium`/`high` |

<details>
<summary><b>📜 Full settings reference with descriptions</b></summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Routing is centralized in `src/routing.ts`.
| Claude family | `messages` | `/zen/v1/messages` |
| Go MiniMax M2 family | `messages` | `/zen/go/v1/messages` |
| Qwen 3.5/3.6 Plus and Qwen 3.7 Max | `messages` | provider messages endpoint |
| Zen Gemini family | `google` | `streamGenerateContent?alt=sse` style route |
| Gemini family (Zen + Go) | `google` | `streamGenerateContent?alt=sse` style route |
| All other models | `chat-completions` | provider chat-completions endpoint |

The request layer maps VS Code chat parts and tools into the correct request body for the selected endpoint.
Expand Down
34 changes: 17 additions & 17 deletions docs/features/17-20260814-data-driven-model-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,22 +116,22 @@ interface ModelRegistryEntry {

### Current rows (11 families + catch-all)

| Order | Family | Patterns | endpointKind | thinkingFamily | vendors |
| ----- | --------------- | ---------------------------------------------------------- | ---------------- | -------------- | ----------- |
| 1 | `gpt` | `/^gpt-/i` | responses | openai | — |
| 2 | `claude` | `/^claude-/i` | messages | null | — |
| 3 | `minimax-m2` | `/^minimax-m2\./i` | messages | minimax | opencodego |
| 4 | `qwen-messages` | `/^qwen3\.(?:5\|6)-plus(?:-free)?$/i`, `/^qwen3\.7-max$/i` | messages | qwen | — |
| 5 | `gemini` | `/^gemini-/i` | google | null | opencodezen |
| 6 | `minimax` | `/^minimax-/i` | chat-completions | minimax | — |
| 7 | `deepseek` | `/^deepseek-/i` | chat-completions | deepseek | — |
| 8 | `glm` | `/^glm-/i` | chat-completions | glm | — |
| 9 | `kimi` | `/^kimi-/i` | chat-completions | kimi | — |
| 10 | `mimo` | `/^mimo-/i` | chat-completions | mimo | — |
| 11 | `qwen` | `/^qwen3(?:\.\|-)/i` | chat-completions | qwen | — |
| 12 | `default` | `/.*/` | chat-completions | null | — |

**Vendor-restriction semantics:** `minimax-m2.*` served by **Go** → Messages API (Anthropic); on **Zen** it falls through to the generic `minimax` row → chat-completions. `gemini-*` served by **Zen** → Google API; on Go → chat-completions. Agent-host variants are resolved to their base vendor via `resolveBaseVendor()` before lookup.
| Order | Family | Patterns | endpointKind | thinkingFamily | vendors |
| ----- | --------------- | ---------------------------------------------------------- | ---------------- | -------------- | ---------- |
| 1 | `gpt` | `/^gpt-/i` | responses | openai | — |
| 2 | `claude` | `/^claude-/i` | messages | null | — |
| 3 | `minimax-m2` | `/^minimax-m2\./i` | messages | minimax | opencodego |
| 4 | `qwen-messages` | `/^qwen3\.(?:5\|6)-plus(?:-free)?$/i`, `/^qwen3\.7-max$/i` | messages | qwen | — |
| 5 | `gemini` | `/^gemini-/i` | google | gemini | — |
| 6 | `minimax` | `/^minimax-/i` | chat-completions | minimax | — |
| 7 | `deepseek` | `/^deepseek-/i` | chat-completions | deepseek | — |
| 8 | `glm` | `/^glm-/i` | chat-completions | glm | — |
| 9 | `kimi` | `/^kimi-/i` | chat-completions | kimi | — |
| 10 | `mimo` | `/^mimo-/i` | chat-completions | mimo | — |
| 11 | `qwen` | `/^qwen3(?:\.\|-)/i` | chat-completions | qwen | — |
| 12 | `default` | `/.*/` | chat-completions | null | — |

**Vendor-restriction semantics:** `minimax-m2.*` served by **Go** → Messages API (Anthropic); on **Zen** it falls through to the generic `minimax` row → chat-completions. `gemini-*` → Google API on **both** vendors; the route base is `googleModelsUrl ?? modelsUrl`, so Go's built-in gateway uses its own Google base while Zen (and any custom `apiBaseUrl` gateway) keeps building from `modelsUrl`. Agent-host variants are resolved to their base vendor via `resolveBaseVendor()` before lookup.

### How to add a model family

Expand All @@ -149,7 +149,7 @@ interface ModelRegistryEntry {
| `claude-` | **messages** (Anthropic) | any vendor |
| `minimax-m2.*` (Go) | **messages** | on Zen → chat-completions |
| `qwen3.5-plus(-free)`, `qwen3.6-plus(-free)`, `qwen3.7-max` | **messages** | others → chat-completions |
| `gemini-*` (Zen) | **google** | on Go → chat-completions |
| `gemini-*` (Zen + Go) | **google** | Go uses `googleModelsUrl` base, Zen uses `modelsUrl` |
| everything else | **chat-completions** | default |

---
Expand Down
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,18 @@
"default": "off",
"markdownDescription": "Thinking mode for Muse Spark models (`muse-spark-1.2-contributor`, `muse-spark-1.2-contributor-free`). Maps to the Responses API `reasoning.effort` field. Higher effort increases reasoning depth and may increase latency and usage."
},
"opencodego.thinking.gemini": {
"type": "string",
"enum": [
"off",
"on",
"low",
"medium",
"high"
],
"default": "off",
"markdownDescription": "Thinking level for Gemini models (Google API wire format). Maps to `generationConfig.thinkingConfig.thinkingLevel` and sets `includeThoughts: true` so reasoning streams into the thinking panel. `off` sends no thinking config (model default); `on` surfaces thoughts without pinning a level. Higher levels increase reasoning depth and latency."
},
"opencodego.thinking.generic": {
"type": "string",
"enum": [
Expand Down
11 changes: 11 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const SETTING_THINKING_QWEN = "thinking.qwen";
export const SETTING_THINKING_QWEN_BUDGET = "thinking.qwenBudget";
export const SETTING_THINKING_MIMO = "thinking.mimo";
export const SETTING_THINKING_MUSE = "thinking.muse";
export const SETTING_THINKING_GEMINI = "thinking.gemini";
export const SETTING_THINKING_GENERIC = "thinking.generic";

// ─── Inline completions (issue #49) ─────────────────────────────────────────
Expand Down Expand Up @@ -128,6 +129,15 @@ export const DEFAULT_MODEL_MAX_OUTPUT_TOKENS = 65536;

/** Default OpenCode Go API base URL; can be overridden in VS Code settings. */
export const DEFAULT_GO_API_BASE_URL = "https://opencode.ai/zen/go/v1";
/**
* Base of the Google-native (Gemini) generate route on the built-in Go gateway.
* Go serves that route on a different base than the rest of its routes: the
* `/zen/go/v1/models/{id}:streamGenerateContent` path is not routed at all
* (404), while `/inference/go/v1/models/{id}` is (verified 2026-10-14). A
* configured `apiBaseUrl` drives every route including this one (issue #247),
* so only the built-in default carries the split (see `provider/definitions.ts`).
*/
export const DEFAULT_GO_GOOGLE_MODELS_URL = "https://opencode.ai/inference/go/v1/models";
/** Default OpenCode Zen API base URL; can be overridden in VS Code settings. */
export const DEFAULT_ZEN_API_BASE_URL = "https://opencode.ai/zen/v1";

Expand Down Expand Up @@ -426,5 +436,6 @@ export const THINKING_DEFAULTS = {
qwenBudget: "auto",
mimo: "off",
muse: "off",
gemini: "off",
generic: "off",
} as const;
Loading
Loading