Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f4169d4
refactor(thinking): per-provider strategy classes with explicit confi…
xianhongtao Aug 14, 2026
b8f6b45
refactor(request): split body builders into per-endpoint modules (ope…
xianhongtao Aug 14, 2026
287cd8b
refactor(extension): wire request builders + thinking provider into t…
xianhongtao Aug 14, 2026
32030e1
fix(scripts): run npm bin shims through the shell on Windows
xianhongtao Aug 14, 2026
97b6412
fix(usage): match cwd path segments with both separators (Windows)
xianhongtao Aug 14, 2026
dd21bbc
fix(scripts): make npm run lint pass on Windows
xianhongtao Aug 14, 2026
30cf2f8
docs: changelog + devlog for thinking refactor and request split
xianhongtao Aug 14, 2026
379d86a
refactor(api-key): drop command-palette key entry, BYOK panel only
xianhongtao Aug 14, 2026
6fcc7a0
Merge remote-tracking branch 'upstream/main'
xianhongtao Aug 14, 2026
18c1e1f
Merge remote-tracking branch 'upstream/main'
xianhongtao Aug 14, 2026
ea4947d
refactor(usage): split goUsageTracker into src/usage/ modules
xianhongtao Aug 14, 2026
2bf8b17
refactor(transports): split streaming.ts into src/transports/ + core/…
xianhongtao Aug 14, 2026
e2e10b8
refactor(models,core): move model metadata + routing into domain folders
xianhongtao Aug 14, 2026
2622c1e
refactor(extension): extract usage dashboard + model metadata fetch
xianhongtao Aug 14, 2026
8b1176f
refactor(provider): extract provider definitions into provider/defini…
xianhongtao Aug 14, 2026
b50752a
refactor(provider): extract message conversion + token estimation
xianhongtao Aug 14, 2026
5fedaa9
refactor(provider): extract settings, vision proxy, pricing + request…
xianhongtao Aug 14, 2026
e8fb6b2
refactor(provider): move OpenCodeProvider class to provider/OpenCodeP…
xianhongtao Aug 14, 2026
2a3589d
refactor(commands): extract agent-window / diagnostics / thinking pic…
xianhongtao Aug 14, 2026
db0b205
refactor: remove goUsageTracker/streaming barrels, import canonical p…
xianhongtao Aug 14, 2026
e743c17
docs: changelog + devlog for god-file split
xianhongtao Aug 14, 2026
e6ec6d2
style: normalize JSDoc continuation indent in usage/tracker.ts
xianhongtao Aug 14, 2026
b6564cf
feat(core): data-driven model registry (transport + thinking family)
xianhongtao Aug 14, 2026
1ad1f5f
docs: changelog + devlog + architecture timeline for data-driven regi…
xianhongtao Aug 14, 2026
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
10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Normalize every text file to LF in the index and on checkout. This keeps
# `npm run lint` (prettier + shellcheck, which require LF per .editorconfig)
# green on Windows, where core.autocrlf=true would otherwise check files out
# as CRLF. Binary files are untouched (text=auto detects them).
* text=auto eol=lf

# Shell scripts must stay LF: husky hooks (checked by shellcheck via
# `npm run lint`) and any POSIX scripts break with CRLF on Windows checkouts.
.husky/* text eol=lf
*.sh text eol=lf
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@

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

## [Unreleased]

### Changed

- **`[Internal]` API keys are configured through the BYOK panel only.** The `OpenCode Go: Set API Key` / `OpenCode Zen: Set API Key` commands and the "Set / Clear API Key" menu items inside `Manage Provider` are removed β€” keys are entered once via **Chat: Manage Language Models β†’ "+ Add Models"** (the native BYOK flow). `SecretStorage` is no longer a user-facing entry point; it stays as an internal per-vendor mirror (`opencodego.apiKey` / `opencodezen.apiKey`) that the BYOK resolution writes so agent-host variants and cold-start requests inherit the group key. Splitting the secret per vendor also fixes a latent collision where Go and Zen shared a single `opencodego.apiKey` and overwrote each other's key. `Refresh Models` / `Test Connection` now point at the BYOK flow when no key is configured instead of prompting for one.

- **`[Internal]` Per-provider Thinking strategy classes + single config authority.** The thinking/reasoning system is refactored from one monolithic builder into a per-provider strategy (`src/thinking/`): an interface + factory (`provider.ts`), a shared base class, and one class per model family (`deepseek`, `glm`, `kimi`, `minimax`, `openai`, `qwen`, `mimo`, `fallback`). Each provider now owns its reasoning picker schema, its request-payload mapping, and whether its `reasoning_content` is surfaced as chat content. Configuration resolves from a **single authority** β€” the VS Code per-model configuration (model picker / Manage), with workspace settings and per-family defaults as fallbacks β€” instead of competing sources (workspace + modelConfiguration + a `globalState` shadow copy + defaults). The shadow copy is removed, so a thinking effort chosen for one model can no longer silently leak onto another model or override an explicit "Off". Model IDs are normalized to `effectiveModelId` (the `::sk-***` fp suffix is gone), which also stops the per-model settings group from being recreated on every pick. Request builders are split out of `extension.ts` into per-endpoint modules (`src/request/{types,schema,shared,openai,anthropic,google}.ts`). Windows tooling fixes: `scripts/lint.ts` runs npm `.cmd` shims through the shell and a new `.gitattributes` enforces LF normalization, so `npm run lint` (prettier + shellcheck) is green on Windows; `scripts/staged-lint.ts` and `isCwdInWorkspace` get the same treatment.

- **`[Internal]` God files split into domain modules (no behavior change).** The three monolithic files β€” `src/extension.ts` (4653 lines), `src/streaming.ts` (1620) and `src/goUsageTracker.ts` (1510) β€” are split into domain folders:
- `src/usage/` β€” Go usage domain: `tracker.ts`, `history.ts` (OpenCode CLI SQLite read/aggregation), `pricing.ts`, `formatting.ts`, `dashboard.ts` (status bar + usage webview incl. the HTML template + tooltip SVG), plus the moved `usage.ts` / `usageProfile.ts` / `goUsageSync.ts`.
- `src/transports/` β€” one file per transport (`chatCompletions`, `responses`, `anthropic`, `google`) plus the shared streaming `engine`, pure `sse` parser, `extractors`, `extract` helpers and `thinkTags` filter; the transport contract types live in `src/core/transport.ts` (routing in `src/core/routing.ts`).
- `src/provider/` β€” `OpenCodeProvider` class, `definitions` (PROVIDERS table + model types), `messages`/`tokens` (message conversion + token estimation), `settings` (schema/getSettings/limits/capabilities), `visionProxy`.
- `src/models/` β€” `metadata`, `modelLimits`, `modelCapabilities`, `modelNames`, `pricing`, `metadataFetcher` (models.dev cache).
- `src/commands/` β€” provider, agent-window, diagnostics and thinking-picker command handlers; `src/request/headers.ts` for the OpenCode request headers.
- `extension.ts` is now a thin entry (~400 lines) that only wires activation + command registration. The two compat barrels (`streaming.ts`, `goUsageTracker.ts`) are removed and every importer references canonical paths. All behavior-preserving β€” verified by `npm run compile` + 291 unit tests + mock-server retry E2E (`npm run test-retry`) + `npm run lint`.

- **`[Internal]` Data-driven model registry (`src/core/registry.ts`).** The transport router and the thinking-family detector previously each owned a hardcoded model-prefix table. Both now read ONE data-driven table: `MODEL_REGISTRY` rows map model-family patterns β†’ `{ endpointKind, sdkPackage, thinkingFamily, vendors? }`. `resolveModelRouting()` honors per-vendor restrictions (e.g. MiniMax `m2.x` β†’ Messages on Go, Gemini β†’ Google on Zen); `thinkingFamily()` reads the same table vendor-agnostically. Adding a new model family = adding one row (+ optionally a thinking strategy class). Context limits / capabilities stay metadata-driven (live models.dev) rather than duplicated in a static table. Behavior-preserving β€” verified by 14 new registry tests (305 total).

### Fixed

- **DeepSeek / Mimo thinking content no longer leaks into the chat transcript.** `treatReasoningAsContent` was mis-detecting native-reasoning families as "no reasoning in body" and echoing their `reasoning_content` as plain chat text. The decision now comes from the provider strategy (always `false` for DeepSeek and Mimo), so chain-of-thought stays in the thinking panel.

## [0.6.0] β€” 2026-08-13

### Added
Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
| 🎯 **Smart routing** | Each model family auto-routes to its native transport (`/responses`, `/messages`, `streamGenerateContent`, `/chat/completions`) |
| πŸ–ΌοΈ **Vision + PDF + Audio** | Multimodal models pass through image, PDF, audio, and video inputs. Oversized images auto-resize to 2000Γ—2000 / 5MB to match the gateway contract. |
| πŸ“ **Context-size picker** | Kimi K3 and other tiered-context models expose `256K` vs full-window selection in the per-model configuration, with the cheaper tier selected by default. |
| πŸ”’ **Your key, your control** | API key stored in VS Code SecretStorage β€” never leaves your machine |
| πŸ”’ **Your key, your control** | API key entered once in Language Models β†’ **Add Models…** β€” stored by VS Code, never leaves your machine |

---

Expand All @@ -69,7 +69,7 @@
5. **Click the model picker** (current model name) β†’ **Add Models…**
6. **Select** **OpenCode Go** or **OpenCode Zen**.
7. **Press Enter** to accept the default group name.
8. **Paste your API key** when prompted (stored securely in VS Code SecretStorage).
8. **Paste your API key** when prompted (stored by VS Code in your language-models configuration).
9. **Pick the models** you want enabled.
10. **Select any OpenCode model** from the picker and start chatting. πŸš€

Expand Down Expand Up @@ -421,11 +421,10 @@ The easiest way to manage your key is **Settings β†’ Language Models** (gear βš™

| Command | Description |
| --------------------------------------------------------- | --------------------------------------------------------------- |
| `OpenCode Go: Manage Provider` | Manage legacy API key, refresh models, test connection |
| `OpenCode Go: Set API Key` | Store/update legacy OpenCode Go API key |
| `OpenCode Go: Manage Provider` | Test connection, refresh models, configure utility models |
| `OpenCode Go: Refresh Models` | Force a fresh model-list fetch (bypasses the Manage menu) |
| `OpenCode Go: Diagnostics` | Report of Go models + request history |
| `OpenCode Zen: Manage Provider` | Manage Zen API key, refresh models, test connection |
| `OpenCode Zen: Manage Provider` | Test connection, refresh models, configure utility models |
| `OpenCode Zen: Refresh Models` | Force a fresh Zen model-list fetch (bypasses the Manage menu) |
| `OpenCode Zen: Diagnostics` | Report of Zen models + request history |
| `OpenCode: Model Picker Diagnostics` | All registered models (Go + Zen + Copilot) side-by-side |
Expand Down Expand Up @@ -474,7 +473,7 @@ Inline suggestions, next-edit suggestions, semantic search, and embedding-backed
<details>
<summary><b>Where is my API key stored?</b></summary>

In VS Code's **SecretStorage** β€” the same encrypted store used by GitHub auth. It never leaves your machine and is never sent anywhere except directly to `opencode.ai`.
In your VS Code **language-models configuration** β€” add it via **Chat: Manage Language Models β†’ Add Models… β†’ OpenCode Go / OpenCode Zen**. VS Code stores the key in its encrypted language-models storage, it never leaves your machine, and it is only sent to `opencode.ai`.

</details>

Expand Down
11 changes: 5 additions & 6 deletions docs/architecture/01-20260514-open-code-provider-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,15 @@ The native provider configuration schema is declared in `package.json` under `co

| Command | Purpose |
| --------------------------------------------------------- | -------------------------------------------------------------------------- |
| `OpenCode Go: Manage Provider` | Legacy fallback key management, refresh, and connection test |
| `OpenCode Go: Set API Key` | Legacy fallback key storage |
| `OpenCode Go: Manage Provider` | Refresh models, test connection, configure utility models |
| `OpenCode Go: Remove/Re-add Provider in Language Models` | Toggle `opencodego.enabled` (remove/re-add the provider, requires reload) |
| `OpenCode Zen: Remove/Re-add Provider in Language Models` | Toggle `opencodezen.enabled` (remove/re-add the provider, requires reload) |
| `OpenCode Go: Diagnostics` | Go model and transport diagnostics |
| `OpenCode Zen: Diagnostics` | Zen model and transport diagnostics |
| `OpenCode: Model Picker Diagnostics` | Cross-provider model metadata comparison |
| `OpenCode: Set Thinking Effort...` | Global thinking-mode helper for supported families |

The recommended setup path is still VS Code's native **Language Models** UI. The legacy commands remain for diagnostics and fallback compatibility.
The recommended β€” and only β€” setup path is VS Code's native **Language Models** UI ("+ Add Models"). The `Set API Key` command and the legacy key-management menu items were removed (the old single `opencodego.apiKey` secret could not represent both Go and Zen keys); the remaining manage commands cover refresh, connection testing, and diagnostics.

---

Expand All @@ -119,14 +118,14 @@ For model discovery (`provideLanguageModelChatInformation`), the extension resol
1. Read `options.configuration.apiKey` (the native BYOK value) if VS Code supplied one.
2. If step 1 produced nothing, fall back to `SecretStorage` unconditionally.

The unconditional fallback (since 0.5.0, [#86](https://github.com/ltmoerdani/opencode-copilot-chat/issues/86)) covers users who stored the key via the extension command `OpenCode Go: Set API Key` instead of the native BYOK flow. It mirrors Copilot's own `AbstractLanguageModelChatProvider`, which always falls back to its own storage when `configuration.apiKey` is absent. A per-vendor flag (`hasConfiguredByokGroup`) suppresses the groupless call once a native BYOK group exists, so models are not listed twice ([#106](https://github.com/ltmoerdani/opencode-copilot-chat/issues/106)). A group call whose `configuration` is present but carries no API key is treated as a **per-model configuration group** (only `settings`, created when the user picks e.g. `reasoningEffort` in the model picker) and returns no models, so the groupless call remains the single source; per-model settings still apply at request time via `modelConfiguration` ([#131](https://github.com/ltmoerdani/opencode-copilot-chat/issues/131)).
The fallback is an internal mirror of Copilot's own `AbstractLanguageModelChatProvider`, which always falls back to its own storage when `configuration.apiKey` is absent. Since the `Set API Key` command was removed, the only writer is the BYOK group resolution itself: when a non-agent provider resolves a key it persists it into its **per-vendor** secret (`opencodego.apiKey` / `opencodezen.apiKey`, resolved via `secretKeyFor()` in `src/config.ts`), so agent-host variants and cold-start requests inherit it. A per-vendor flag (`hasConfiguredByokGroup`) suppresses the groupless call once a native BYOK group exists, so models are not listed twice ([#106](https://github.com/ltmoerdani/opencode-copilot-chat/issues/106)). A group call whose `configuration` is present but carries no API key is treated as a **per-model configuration group** (only `settings`, created when the user picks e.g. `reasoningEffort` in the model picker) and returns no models, so the groupless call remains the single source; per-model settings still apply at request time via `modelConfiguration` ([#131](https://github.com/ltmoerdani/opencode-copilot-chat/issues/131)).

Security rules:

- Real API keys are never written to repository files.
- Documentation must use placeholders only.
- API keys should be entered through VS Code's native secret-backed provider configuration.
- Legacy `SecretStorage` support remains only as a fallback path.
- API keys are entered through VS Code's native secret-backed provider configuration ("+ Add Models"); there is no command-palette key entry.
- `SecretStorage` remains only as an internal fallback (per-vendor, mirroring the BYOK group key for agent variants and cold-start requests).

Safe placeholder example:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,11 @@ Each phase gate: `npm run compile` must pass + a targeted test with at least 1 m

## Timeline

| Date | Status | Change |
| ---------- | --------- | ----------------------------------------------------------------------------- |
| 2026-08-09 | 🟒 Active | Initial research + analysis document created. Proposal only; no code changed. |
| Date | Status | Change |
| ---------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 2026-08-09 | 🟒 Active | Initial research + analysis document created. Proposal only; no code changed. |
| 2026-08-14 | βœ… Done | God-file split executed on `refactor/split-god-files` (usage/ Β· transports/ Β· provider/ Β· models/ Β· core/ Β· commands/); `extension.ts` 4653 β†’ ~414 lines. See CHANGELOG `[Unreleased]`. |
| 2026-08-14 | βœ… Done | **Data-driven registry implemented** (`src/core/registry.ts`). `resolveModelRouting()` (transport) and `thinkingFamily()` both read the `MODEL_REGISTRY` table; `ModelEndpointKind` type moved to the registry. Scope note: context limits/capabilities stay metadata-driven (live models.dev) β€” not duplicated as a static table. The full `ModelTransport` port interface remains future work. |

---

Expand Down
Loading
Loading