From b57bc4c43fd27334309aae1887c7630ef5eb386d Mon Sep 17 00:00:00 2001 From: Fahad Iftikhar Date: Mon, 10 Aug 2026 17:03:04 +0500 Subject: [PATCH 1/5] chore: ignore tmp/ for temporary files Placeholders for temporary working copies (e.g. a VS Code source checkout used for API research) belong under tmp/ and must never be committed nor shipped inside the VSIX. --- .gitignore | 1 + .vscodeignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c5942d9..9079b96 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ out/ .DS_Store scripts/validate-models.mjs scripts/validate-models.mjs.map +tmp/ diff --git a/.vscodeignore b/.vscodeignore index 0a4ae1d..353b928 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -23,3 +23,4 @@ src/** tsconfig.json package-lock.json media/opencodego.svg +tmp/** From 9abbe61c161c46cc2ac8f80f306b448b77188cbd Mon Sep 17 00:00:00 2001 From: Fahad Iftikhar Date: Mon, 10 Aug 2026 17:03:56 +0500 Subject: [PATCH 2/5] chore(tooling): linters/formatters honor .gitignore at runtime Stop maintaining parallel, drifting ignore lists. Every tool now derives its exclusions from .gitignore at run time: - ESLint: flat config reads .gitignore patterns via the new shared scripts/gitignore-patterns.mjs (single source of truth). - Prettier: format/format:check pass --ignore-path .gitignore explicitly (native gitignore parsing) instead of relying on implicit fallback. - markdownlint-cli2: config renamed .markdownlint.json -> .markdownlint-cli2.jsonc and gains "gitignore": true (native gitignore parsing, including nested .gitignore files), so the hardcoded #node_modules exclusion is no longer needed. docs/** stays excluded from the default lint script. Files under tmp/, out/, node_modules/ etc. are now ignored by all three tools automatically. --- .markdownlint-cli2.jsonc | 12 ++++++++++++ .markdownlint.json | 9 --------- .vscodeignore | 2 +- eslint.config.mjs | 11 ++++------- package.json | 8 ++++---- scripts/gitignore-patterns.mjs | 31 +++++++++++++++++++++++++++++++ 6 files changed, 52 insertions(+), 21 deletions(-) create mode 100644 .markdownlint-cli2.jsonc delete mode 100644 .markdownlint.json create mode 100644 scripts/gitignore-patterns.mjs diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..eddce7a --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,12 @@ +{ + "config": { + "MD013": false, + "MD024": { + "siblings_only": true, + }, + "MD033": false, + "MD041": false, + "MD060": false, + }, + "gitignore": true, +} diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index 99971f5..0000000 --- a/.markdownlint.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "MD013": false, - "MD024": { - "siblings_only": true - }, - "MD033": false, - "MD041": false, - "MD060": false -} diff --git a/.vscodeignore b/.vscodeignore index 353b928..2372dff 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,7 +1,7 @@ .gitignore .github/** .husky/** -.markdownlint.json +.markdownlint-cli2.jsonc .prettierrc.json .vscode/** .vscode-test/** diff --git a/eslint.config.mjs b/eslint.config.mjs index 7601852..b1a56f7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,14 +1,11 @@ -import { readFileSync } from "node:fs"; import tseslint from "typescript-eslint"; - -const gitignore = readFileSync(new URL(".gitignore", import.meta.url), "utf8") - .split(/\r?\n/) - .map((line) => line.trim()) - .filter((line) => line && !line.startsWith("#") && !line.startsWith("!")); +import { readGitignorePatterns } from "./scripts/gitignore-patterns.mjs"; export default tseslint.config( { - ignores: gitignore, + // Ignore everything `.gitignore` ignores (tmp/, out/, generated scripts, + // …) so linting stays in sync with the repo's ignore rules at runtime. + ignores: readGitignorePatterns(), }, ...tseslint.configs.recommended, { diff --git a/package.json b/package.json index 8a13bdf..79710d1 100644 --- a/package.json +++ b/package.json @@ -334,10 +334,10 @@ "lint": "npm run lint:js && npm run lint:md", "lint:js": "eslint .", "lint:fix": "eslint . --fix", - "lint:md": "markdownlint-cli2 --config .markdownlint.json \"**/*.md\" \"#node_modules\" \"#docs/**\"", - "lint:md:all": "markdownlint-cli2 --config .markdownlint.json \"**/*.md\" \"#node_modules\"", - "format": "npx prettier --write .", - "format:check": "npx prettier --check .", + "lint:md": "markdownlint-cli2 --config .markdownlint-cli2.jsonc \"**/*.md\" \"#docs/**\"", + "lint:md:all": "markdownlint-cli2 --config .markdownlint-cli2.jsonc \"**/*.md\"", + "format": "npx prettier --write . --ignore-path .gitignore", + "format:check": "npx prettier --check . --ignore-path .gitignore", "clean": "node -e \"require('node:fs').rmSync('out', { recursive: true, force: true })\"", "compile": "npm run clean && tsc -p ./", "test": "npm run compile && node scripts/run-unit-tests.mjs", diff --git a/scripts/gitignore-patterns.mjs b/scripts/gitignore-patterns.mjs new file mode 100644 index 0000000..fa76478 --- /dev/null +++ b/scripts/gitignore-patterns.mjs @@ -0,0 +1,31 @@ +import { readFileSync } from "node:fs"; + +/** + * Read `.gitignore` and return its patterns as an array suitable for tools + * that accept gitignore-style patterns (ESLint flat config `ignores`). + * + * This is the single source of truth for "which files are temporary / + * generated / excluded", so every linter and formatter that supports + * gitignore-style patterns should derive its ignores from here (or point + * straight at `.gitignore` itself) instead of maintaining a parallel, + * drifting ignore list: + * + * - ESLint: `ignores: readGitignorePatterns()` (this module). + * - Prettier: `--ignore-path .gitignore` (native gitignore parsing). + * - markdownlint-cli2: `gitignore: true` in `.markdownlint-cli2.jsonc` + * (native gitignore parsing, including nested `.gitignore` files). + * + * CONTRACT: + * - Blank lines and `#` comments are dropped. + * - `!` negations are dropped: ESLint's flat-config `ignores` handles them + * differently from git (relative vs. anchored semantics), and this + * repository's `.gitignore` does not use negations. + * - Lines are trimmed so accidental leading/trailing whitespace can't + * silently disable an ignore rule. + */ +export function readGitignorePatterns(file = ".gitignore") { + return readFileSync(new URL(`../${file}`, import.meta.url), "utf8") + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#") && !line.startsWith("!")); +} From 75da393eb414fb337f9356e60390a3bd2259981d Mon Sep 17 00:00:00 2001 From: Fahad Iftikhar Date: Mon, 10 Aug 2026 17:04:04 +0500 Subject: [PATCH 3/5] fix(agents): make OpenCode Go/Zen appear in the Agents window (+ Add Models) (#122) VS Code >= 1.129 keeps two mechanisms this feature depends on off by default: 1. chat.agentHost.byokModels.enabled (experimental) - the BYOK language-model bridge that mirrors extension BYOK models into agent-host sessions. 2. extensions.supportAgentsWindow. - the only way a code extension is allowed to run in the Agents window (sessions window) process. Without it the extension is disabled there, so its languageModelChatProviders vendors are not registered: neither the model picker nor the '+ Add Models' list in the Agents window can show OpenCode Go/Zen. The extension now auto-enables both settings on activation when opencodego.agentsWindow is on (gated by the new opencodego.autoEnableAgentsWindow setting, default true), merging with any existing user values, records what it flipped in globalState, and offers a reload button the first time. When the user disables agentsWindow, only the settings the extension itself enabled are reverted. Legacy agent-host providers (targetChatSessionType: 'copilotcli') remain registered for VS Code 1.125-1.128, where the bridge does not exist. --- CHANGELOG.md | 4 ++ README.md | 42 +++++++------- package.json | 7 ++- src/extension.ts | 144 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fec7ca3..c87b847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to the **OpenCode Go BYOK Provider** extension are documente ## [Unreleased] +### Added + +- **`[Agents]` OpenCode Go/Zen in the VS Code Agents window (#122).** VS Code ≥1.129 runs the Agents window in a separate agent host process and keeps the two mechanisms this feature depends on off by default: the experimental BYOK model bridge (`chat.agentHost.byokModels.enabled`) that mirrors extension BYOK models into agent-host sessions, and `extensions.supportAgentsWindow`, without which code extensions are disabled in the Agents window process entirely — so OpenCode Go/Zen were missing from both the Agents window model picker and its "+ Add Models" vendor list. The extension now auto-enables both settings on activation (gated by the new `opencodego.autoEnableAgentsWindow` setting, default `true`, merging with any existing user values), records what it flipped, and offers a reload button the first time. When the user disables `opencodego.agentsWindow` afterwards, only the settings the extension itself enabled are reverted. The legacy agent-host providers (`targetChatSessionType: "copilotcli"`) remain registered for VS Code 1.125–1.128, where the bridge does not exist. + ### Fixed - **`[VS Code]` Provider context menu unresponsive; "+ Add Models" dead; leftover groups undeletable (#121).** The `languageModelChatProviders` contributions declared both `managementCommand` and a `configuration` schema. VS Code's native BYOK flow (`configureLanguageModelsProviderGroup`) short-circuits on `managementCommand` — it re-resolves models and returns without prompting for a group name or API key — so a BYOK group could never be created through "+ Add Models", and every built-in context-menu action (Rename Group, Update API Key, Delete, Open in Language Models (JSON)) throws "group not found", failing silently. Dropped `managementCommand` from the `opencodego`, `opencodezen`, and agent-variant contributions; "+ Add Models" now runs the native prompt flow, the context-menu actions work against the created group, and leftover groups (e.g. created by per-model configuration) can finally be deleted. The extension's own commands (`OpenCode Go: Manage Provider`, `Set API Key`, etc.) remain available in the Command Palette and keep working as a legacy fallback. diff --git a/README.md b/README.md index c24d5e3..b4eb4c9 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ | 📊 **Live usage tracking** | Status bar shows Go subscription burn-rate across 5h / weekly / monthly tiers | | 🔌 **Dual providers** | OpenCode **Go** ($10/mo subscription) + OpenCode **Zen** (free + paid models) — run both at once, switch instantly | | 🎯 **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. | +| 🖼️ **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 | @@ -270,36 +270,34 @@ Delete Active Profile` help you manage your profiles. The label you give ### 🪟 Agents Window (Copilot CLI) Support -OpenCode models appear in the VS Code **Agents window** model picker when starting a Copilot CLI / Background agent session — not just the regular Chat view. Two sets of models are available: +OpenCode models appear in the VS Code **Agents window** model picker when starting a Copilot CLI / Background agent session — not just the regular Chat view: -| Provider | Appears under | Notes | -| ---------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `opencodego` / `opencodezen` | **Local** | Normal models, no `targetChatSessionType`. From VS Code ≥1.126 they appear naturally in the Local section (once the extension loads in the sessions window). | -| `opencodego-agent` / `opencodezen-agent` | **Copilot** | Agent variants with `targetChatSessionType: "copilotcli"`. Picked up by `CopilotChatSessionsProvider` for agent sessions. | +| Provider | Appears under | Notes | +| ---------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `opencodego` / `opencodezen` | **Local** | Normal models, no `targetChatSessionType`. On VS Code 1.129+ they reach agent-host sessions through VS Code's BYOK model bridge. | +| `opencodego-agent` / `opencodezen-agent` | **Copilot** | Agent variants with `targetChatSessionType: "copilotcli"`. Picked up by `CopilotChatSessionsProvider` for legacy agent sessions (VS Code ≤1.128). | **How it works:** -| Setting | Default | What it controls | -| ----------------------------------------- | ------- | ------------------------------------------------------- | -| `opencodego.agentsWindow` | `true` | Registers agent-host providers at runtime | -| `opencodego.showAgentModelsInManagePanel` | `false` | Shows agent vendors in the Manage Language Models panel | +VS Code ≥1.129 runs the Agents window in a separate agent host process and keeps two knobs **off by default** that this extension depends on: -**Setup:** +1. `chat.agentHost.byokModels.enabled` (experimental) — the BYOK language-model bridge that mirrors extension-provided BYOK models into agent-host sessions. +2. `extensions.supportAgentsWindow` — without it, code extensions are **disabled in the Agents window process**, so OpenCode Go/Zen don't appear in the Agents window picker nor in its **+ Add Models** list. -1. Agent models are enabled by default (`agentsWindow: true`). No changes needed for basic usage. -2. Add this to your VS Code `settings.json` to enable the extension in the Agents window process: +The extension auto-enables both when `agentsWindow` is on: - ```json - "extensions.supportAgentsWindow": { - "ltmoerdani.opencode-copilot-chat": true - } - ``` +| Setting | Default | What it controls | +| ----------------------------------------- | ------- | ------------------------------------------------------------------------------------------------- | +| `opencodego.agentsWindow` | `true` | Master switch for Agents window support | +| `opencodego.autoEnableAgentsWindow` | `true` | Auto-manage the two VS Code core settings above (and revert them when `agentsWindow` is disabled) | +| `opencodego.showAgentModelsInManagePanel` | `false` | Shows agent vendors in the Manage Language Models panel | -3. Reload the window (`Developer: Reload Window`). -4. Open the **Agents window** → start a new session → select **Copilot CLI** as the agent type. -5. Open the model picker — OpenCode models appear under **Local** (normal models) and **Copilot** (agent-host variants). +**Setup:** -Normal OpenCode models (`opencodego`, `opencodezen`) appear in the **Local** section of the Agents window picker from VS Code ≥1.126 onwards. On ≤1.125 they may require the `supportAgentsWindow` setting. Agent-host variants (`opencodego-agent`, `opencodezen-agent`) appear under **Copilot** because they carry `targetChatSessionType: "copilotcli"` and are matched by `CopilotChatSessionsProvider`. +1. Agent models are enabled by default (`agentsWindow: true`). The extension auto-enables the required VS Code settings on first activation and offers a **Reload Now** button — a window reload is required for them to take effect. +2. Reload the window (`Developer: Reload Window`) if you haven't been prompted. +3. Open the **Agents window** → start a new session → select **Copilot CLI** as the agent type. +4. Open the model picker — OpenCode models appear under their provider; the **+ Add Models** list in the Agents window's Language Models view now includes **OpenCode Go** and **OpenCode Zen**. To manage agent API keys separately or see agent vendors in the Manage panel, enable: diff --git a/package.json b/package.json index 79710d1..ba858a6 100644 --- a/package.json +++ b/package.json @@ -189,7 +189,12 @@ "opencodego.agentsWindow": { "type": "boolean", "default": true, - "markdownDescription": "Register separate agent-host providers for the Copilot Agents window. Disable to hide the Agents section entirely. Requires a window reload to take effect." + "markdownDescription": "Enable OpenCode models in the VS Code Agents window. On VS Code 1.129+ this enables VS Code's BYOK model bridge (`chat.agentHost.byokModels.enabled`) so OpenCode Go/Zen models run in agent-host sessions; on all versions it also opts the extension in to run in the Agents window process (`extensions.supportAgentsWindow`) so OpenCode Go/Zen appear in the Agents window's '+ Add Models' list. Both core settings are auto-managed (see `#opencodego.autoEnableAgentsWindow#`). Disable to hide the Agents section entirely and revert the auto-managed settings. Requires a window reload to take effect." + }, + "opencodego.autoEnableAgentsWindow": { + "type": "boolean", + "default": true, + "markdownDescription": "When `#opencodego.agentsWindow#` is on, automatically enable the VS Code core settings the Agents window support depends on: `chat.agentHost.byokModels.enabled` (BYOK model bridge, experimental, off by default) and `extensions.supportAgentsWindow: { \"ltmoerdani.opencode-copilot-chat\": true }` (without this the extension is disabled in the Agents window process and its providers, including the '+ Add Models' vendors, are missing). The extension records the values it flips and reverts them when `agentsWindow` is disabled. A window reload is required after the first auto-enable. Disable to manage those settings manually." }, "opencodego.showAgentModelsInManagePanel": { "type": "boolean", diff --git a/src/extension.ts b/src/extension.ts index ef9d75e..1539729 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -84,6 +84,28 @@ const SECRET_KEY = "opencodego.apiKey"; const RECENT_TRANSPORT_SUMMARY_LIMIT = 25; const RECENT_TRANSPORT_SUMMARY_STORAGE_PREFIX = "opencode.recentTransportSummaries"; +/** + * VS Code core settings the extension manages (auto-configures and reverts) + * so OpenCode models work in the Agents window (issue #122): + * + * - `chat.agentHost.byokModels.enabled`: wires the agent-host BYOK bridge + * (VS Code 1.129+); off by default, so extension-provided BYOK models never + * reach agent-host sessions until it is flipped on. + * - `extensions.supportAgentsWindow.`: the ONLY way a code extension is + * allowed to run in the Agents window (sessions window) process. Without + * it the extension is disabled there, its `languageModelChatProviders` + * vendors are not registered, and neither the model picker nor the + * "+ Add Models" list knows OpenCode Go/Zen. + */ +const AGENT_HOST_BYOK_ENABLED_SETTING = "byokModels.enabled"; +const SUPPORT_AGENTS_WINDOW_SETTING = "supportAgentsWindow"; +const EXTENSION_ID = "ltmoerdani.opencode-copilot-chat"; +/** How many VS Code versions old the agent-host BYOK bridge goes back to. */ +const AGENT_HOST_BYOK_MINOR_VERSION = 129; +/** globalState keys tracking that the extension enabled each setting itself. */ +const AGENTS_BYOK_BRIDGE_STATE_KEY = "opencode.agentsByokBridge.v1"; +const SUPPORT_AGENTS_WINDOW_STATE_KEY = "opencode.supportAgentsWindow.v1"; + let usageStatusBarItem: vscode.StatusBarItem | undefined; let goUsageStatusBarItem: vscode.StatusBarItem | undefined; /** Singleton tracker — the first/legacy account. Used for backward compat until first migration. */ @@ -876,6 +898,11 @@ export function activate(context: vscode.ExtensionContext) { vscode.lm.registerLanguageModelChatProvider(AGENT_GO_VENDOR, agentGoProvider), vscode.lm.registerLanguageModelChatProvider(AGENT_ZEN_VENDOR, agentZenProvider), ); + // On VS Code 1.129+ the Agents window runs in the agent host process, + // where extension BYOK models are only reachable through VS Code's BYOK + // language-model bridge — which is off by default. Make sure it is on so + // OpenCode models actually show up there (issue #122). + void ensureAgentsWindowSupport(context); } context.subscriptions.push(...subscriptions); @@ -890,6 +917,18 @@ export function activate(context: vscode.ExtensionContext) { provider.notifyModelInfoChanged(); } } + if (event.affectsConfiguration("opencodego.agentsWindow") || event.affectsConfiguration("opencodego.autoEnableAgentsWindow")) { + const agentsWindowEnabled = vscode.workspace.getConfiguration("opencodego").get("agentsWindow", true); + const autoEnabled = vscode.workspace.getConfiguration("opencodego").get("autoEnableAgentsWindow", true); + if (agentsWindowEnabled && autoEnabled) { + void ensureAgentsWindowSupport(context); + } else if (!agentsWindowEnabled) { + // We may have enabled core settings for the Agents window; revert + // them when the user turns the feature off so the user's global + // configuration is restored. + void revertAgentsWindowSupport(context); + } + } }), ); @@ -903,6 +942,111 @@ async function configureUtilityModels(): Promise { ); } +/** + * Whether this VS Code has the modern agent-host BYOK bridge (1.129+). + * + * From VS Code 1.129 the Agents window runs in a separate agent host + * process. Extension-provided BYOK models (isBYOK, no `targetChatSessionType`) + * are mirrored into agent-host sessions exclusively through the BYOK + * language-model bridge, which VS Code keeps OFF by default + * (`chat.agentHost.byokModels.enabled`, experimental). On older versions the + * extension's own agent-host providers (`targetChatSessionType: "copilotcli"`) + * are the only path, which is why they stay registered. + */ +function isModernAgentHostVscode(): boolean { + const [major = 1, minor = 0] = (vscode.version ?? "").split(".").map(Number); + return major > 1 || (major === 1 && minor >= AGENT_HOST_BYOK_MINOR_VERSION); +} + +/** + * Ensure the VS Code core settings that make OpenCode Go/Zen models usable in + * the Agents window are enabled (issue #122): + * + * 1. `extensions.supportAgentsWindow.` — the only way a code extension is + * allowed to run in the Agents window (sessions window) process. VS Code + * disables any extension with a `main` entry there by default, so without + * this setting the extension's `languageModelChatProviders` vendors are + * not registered in that window: neither the model picker nor the + * "+ Add Models" list can show OpenCode Go/Zen. + * 2. `chat.agentHost.byokModels.enabled` (VS Code 1.129+) — the BYOK + * language-model bridge that mirrors extension BYOK models into + * agent-host sessions. Off by default and experimental. + * + * CONTRACT: + * - Only writes the settings while the user keeps `opencodego.agentsWindow` + * and `opencodego.autoEnableAgentsWindow` on; the settings are merged with + * existing user values (never clobbering unrelated entries). + * - Records in globalState which settings the extension flipped itself, so + * {@link revertAgentsWindowSupport} can restore them when the user disables + * the Agents feature. + * - Both settings take effect after a window reload (extension host / + * agent host restart) — surface an actionable notification the first time + * anything was changed. + */ +async function ensureAgentsWindowSupport(context: vscode.ExtensionContext): Promise { + const opencodeCfg = vscode.workspace.getConfiguration("opencodego"); + if (!opencodeCfg.get("agentsWindow", true) || !opencodeCfg.get("autoEnableAgentsWindow", true)) { + return; + } + + let changed = false; + const extensionCfg = vscode.workspace.getConfiguration("extensions"); + const support = extensionCfg.get>(SUPPORT_AGENTS_WINDOW_SETTING, {}); + if (!support[EXTENSION_ID]) { + await extensionCfg.update(SUPPORT_AGENTS_WINDOW_SETTING, { ...support, [EXTENSION_ID]: true }, vscode.ConfigurationTarget.Global); + await context.globalState.update(SUPPORT_AGENTS_WINDOW_STATE_KEY, true); + changed = true; + } + + if (isModernAgentHostVscode()) { + const agentHostCfg = vscode.workspace.getConfiguration("chat.agentHost"); + if (!agentHostCfg.get(AGENT_HOST_BYOK_ENABLED_SETTING, false)) { + await agentHostCfg.update(AGENT_HOST_BYOK_ENABLED_SETTING, true, vscode.ConfigurationTarget.Global); + await context.globalState.update(AGENTS_BYOK_BRIDGE_STATE_KEY, true); + changed = true; + } + } + + if (changed) { + const reload = await vscode.window.showInformationMessage( + "OpenCode: enabled VS Code's Agents window support so OpenCode Go/Zen models can run in the Agents window. Reload the window for it to take effect.", + "Reload Now", + ); + if (reload === "Reload Now") { + await vscode.commands.executeCommand("workbench.action.reloadWindow"); + } + } +} + +/** + * Revert the core settings that {@link ensureAgentsWindowSupport} enabled on + * this machine (and only those — settings the user configured manually are + * left untouched). + */ +async function revertAgentsWindowSupport(context: vscode.ExtensionContext): Promise { + const extensionCfg = vscode.workspace.getConfiguration("extensions"); + if (context.globalState.get(SUPPORT_AGENTS_WINDOW_STATE_KEY)) { + const support = extensionCfg.get>(SUPPORT_AGENTS_WINDOW_SETTING, {}); + if (support[EXTENSION_ID]) { + const next = { ...support }; + delete next[EXTENSION_ID]; + await extensionCfg.update( + SUPPORT_AGENTS_WINDOW_SETTING, + Object.keys(next).length > 0 ? next : undefined, + vscode.ConfigurationTarget.Global, + ); + } + await context.globalState.update(SUPPORT_AGENTS_WINDOW_STATE_KEY, undefined); + } + + if (context.globalState.get(AGENTS_BYOK_BRIDGE_STATE_KEY)) { + await vscode.workspace + .getConfiguration("chat.agentHost") + .update(AGENT_HOST_BYOK_ENABLED_SETTING, false, vscode.ConfigurationTarget.Global); + await context.globalState.update(AGENTS_BYOK_BRIDGE_STATE_KEY, undefined); + } +} + async function warmModelPickerMetadata(): Promise { const vendors: string[] = [GO_VENDOR, ZEN_VENDOR]; if (vscode.workspace.getConfiguration("opencodego").get("agentsWindow", true)) { From 487d762927b717366957d93fb2f996545e6ef32b Mon Sep 17 00:00:00 2001 From: Fahad Iftikhar Date: Mon, 10 Aug 2026 17:42:08 +0500 Subject: [PATCH 4/5] feat(models): allow removing OpenCode Go/Zen from Language Models The provider vendors contributed via languageModelChatProviders always showed up in the Manage Language Models list and every model picker with no way to remove them. Add a per-provider kill switch: - New opencodego.enabled / opencodezen.enabled settings (default true). - The vendor contributions carry the matching 'when' clause, so a disabled provider disappears from the Manage Language Models view and the '+ Add Models' list; runtime registration of the main and agent providers is skipped so models vanish from all pickers. - New 'OpenCode Go/Zen: Remove/Re-add Provider in Language Models' commands, plus a 'Remove from Language Models' action in the Manage Provider QuickPick (shown even without an API key). - API keys and BYOK groups are kept, so re-enabling restores the provider unchanged. A window reload is required after toggling. --- CHANGELOG.md | 1 + README.md | 17 ++++++++++++ package.json | 20 ++++++++++++++ src/extension.ts | 72 ++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 96 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c87b847..824e6b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **OpenCode Go BYOK Provider** extension are documente ### Added +- **`[VS Code]` Remove OpenCode Go / OpenCode Zen from Language Models.** Providers can now be removed from the Language Models list and every model picker like in GitHub Copilot's Manage Language Models: new `opencodego.enabled` / `opencodezen.enabled` settings (with matching `when` clauses on the vendor contributions) skip provider registration, plus `OpenCode Go/Zen: Remove/Re-add Provider in Language Models` commands and a **Remove from Language Models** action in the Manage Provider QuickPick. API keys and BYOK group settings are kept so re-enabling restores the provider unchanged. A window reload is required after toggling. - **`[Agents]` OpenCode Go/Zen in the VS Code Agents window (#122).** VS Code ≥1.129 runs the Agents window in a separate agent host process and keeps the two mechanisms this feature depends on off by default: the experimental BYOK model bridge (`chat.agentHost.byokModels.enabled`) that mirrors extension BYOK models into agent-host sessions, and `extensions.supportAgentsWindow`, without which code extensions are disabled in the Agents window process entirely — so OpenCode Go/Zen were missing from both the Agents window model picker and its "+ Add Models" vendor list. The extension now auto-enables both settings on activation (gated by the new `opencodego.autoEnableAgentsWindow` setting, default `true`, merging with any existing user values), records what it flipped, and offers a reload button the first time. When the user disables `opencodego.agentsWindow` afterwards, only the settings the extension itself enabled are reverted. The legacy agent-host providers (`targetChatSessionType: "copilotcli"`) remain registered for VS Code 1.125–1.128, where the bridge does not exist. ### Fixed diff --git a/README.md b/README.md index b4eb4c9..5229fe7 100644 --- a/README.md +++ b/README.md @@ -305,6 +305,23 @@ To manage agent API keys separately or see agent vendors in the Manage panel, en "opencodego.showAgentModelsInManagePanel": true ``` +#### ❌ Removing a provider from Language Models + +Like deleting a provider in GitHub Copilot's Manage Language Models, you can +remove **OpenCode Go** or **OpenCode Zen** from the Language Models list and +every model picker: + +- **Command Palette** — `OpenCode Go: Remove/Re-add Provider in Language +Models` (same for Zen), or use **Manage Provider → Remove from Language + Models**. +- **Settings** — set `opencodego.enabled` / `opencodezen.enabled` to `false`. + +The provider's vendor row and models disappear from the Manage Language +Models view, the `+ Add Models` list, the Chat picker, and the Agents window. +Your API key and BYOK group settings are kept, so re-enabling (or the +`Re-add to Language Models` action) restores everything. A window reload is +required after toggling. + ### 🛠️ Smart Routing & Reliability - **Native endpoint routing** per family (see [Models](#-models) table) diff --git a/package.json b/package.json index ba858a6..37f72bb 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,10 @@ "command": "opencodego.setApiKey", "title": "OpenCode Go: Set API Key" }, + { + "command": "opencodego.toggleProvider", + "title": "OpenCode Go: Remove/Re-add Provider in Language Models" + }, { "command": "opencodego.refreshModels", "title": "OpenCode Go: Refresh Models" @@ -90,6 +94,10 @@ "command": "opencodezen.manage", "title": "OpenCode Zen: Manage Provider" }, + { + "command": "opencodezen.toggleProvider", + "title": "OpenCode Zen: Remove/Re-add Provider in Language Models" + }, { "command": "opencodezen.refreshModels", "title": "OpenCode Zen: Refresh Models" @@ -186,6 +194,16 @@ "default": true, "description": "When enabled, only free OpenCode Zen models are shown in the Copilot model selector. Disable to include paid Zen models as well." }, + "opencodego.enabled": { + "type": "boolean", + "default": true, + "markdownDescription": "Register the OpenCode Go provider. Set to `false` to remove OpenCode Go from the Language Models list and all model pickers (like deleting the provider in GitHub Copilot's Manage Language Models). Models you previously configured stay in your language-models config so they come back if you re-enable. Requires a window reload to take effect." + }, + "opencodezen.enabled": { + "type": "boolean", + "default": true, + "markdownDescription": "Register the OpenCode Zen provider. Set to `false` to remove OpenCode Zen from the Language Models list and all model pickers (like deleting the provider in GitHub Copilot's Manage Language Models). Models you previously configured stay in your language-models config so they come back if you re-enable. Requires a window reload to take effect." + }, "opencodego.agentsWindow": { "type": "boolean", "default": true, @@ -290,6 +308,7 @@ { "vendor": "opencodego", "displayName": "OpenCode Go", + "when": "config.opencodego.enabled", "configuration": { "type": "object", "required": [ @@ -308,6 +327,7 @@ { "vendor": "opencodezen", "displayName": "OpenCode Zen", + "when": "config.opencodezen.enabled", "configuration": { "type": "object", "required": [ diff --git a/src/extension.ts b/src/extension.ts index 1539729..24efa40 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -718,21 +718,31 @@ export function activate(context: vscode.ExtensionContext) { ensureUsageStatusBar(context); ensureGoUsageStatusBar(context); + const opencodeCfg = vscode.workspace.getConfiguration("opencodego"); + const goProviderEnabled = opencodeCfg.get("enabled", true); + const zenProviderEnabled = opencodeCfg.get("opencodezen.enabled", true); const goProvider = new OpenCodeProvider(context, PROVIDERS[GO_VENDOR]); const zenProvider = new OpenCodeProvider(context, PROVIDERS[ZEN_VENDOR]); const modelInfoProviders: OpenCodeProvider[] = [goProvider, zenProvider]; const subscriptions: vscode.Disposable[] = [ - vscode.lm.registerLanguageModelChatProvider(GO_VENDOR, goProvider), - vscode.lm.registerLanguageModelChatProvider(ZEN_VENDOR, zenProvider), + // Register the chat providers only while the matching `opencodego.enabled` + // / `opencodezen.enabled` setting is on, so a disabled provider disappears + // from the Language Models list and every model picker (its vendor + // contribution carries the same `when` clause). The provider instances are + // still created so the management commands keep working for re-enabling. + ...(goProviderEnabled ? [vscode.lm.registerLanguageModelChatProvider(GO_VENDOR, goProvider)] : []), + ...(zenProviderEnabled ? [vscode.lm.registerLanguageModelChatProvider(ZEN_VENDOR, zenProvider)] : []), vscode.commands.registerCommand("opencodego.manage", () => goProvider.manage()), vscode.commands.registerCommand("opencodego.diagnostics", () => goProvider.showDiagnostics()), vscode.commands.registerCommand("opencodego.setApiKey", () => goProvider.setApiKey()), vscode.commands.registerCommand("opencodego.refreshModels", () => goProvider.refreshModels()), + vscode.commands.registerCommand("opencodego.toggleProvider", () => toggleProviderEnabled("opencodego", "OpenCode Go")), vscode.commands.registerCommand("opencodego.configureUtilityModels", () => configureUtilityModels()), vscode.commands.registerCommand("opencodezen.diagnostics", () => zenProvider.showDiagnostics()), vscode.commands.registerCommand("opencodezen.manage", () => zenProvider.manage()), vscode.commands.registerCommand("opencodezen.refreshModels", () => zenProvider.refreshModels()), + vscode.commands.registerCommand("opencodezen.toggleProvider", () => toggleProviderEnabled("opencodezen", "OpenCode Zen")), vscode.commands.registerCommand("opencodego.modelPickerDiagnostics", () => showModelPickerDiagnostics()), vscode.commands.registerCommand("opencodego.setThinkingEffort", () => showThinkingEffortPicker()), vscode.commands.registerCommand("opencodego.showUsageDetails", () => showUsageWebview(context)), @@ -890,13 +900,13 @@ export function activate(context: vscode.ExtensionContext) { // Agent-host providers for the Copilot Agents window (opt-in via config). const enableAgents = vscode.workspace.getConfiguration("opencodego").get("agentsWindow", true); - if (enableAgents) { + if (enableAgents && (goProviderEnabled || zenProviderEnabled)) { const agentGoProvider = new OpenCodeProvider(context, PROVIDERS[AGENT_GO_VENDOR]); const agentZenProvider = new OpenCodeProvider(context, PROVIDERS[AGENT_ZEN_VENDOR]); modelInfoProviders.push(agentGoProvider, agentZenProvider); subscriptions.push( - vscode.lm.registerLanguageModelChatProvider(AGENT_GO_VENDOR, agentGoProvider), - vscode.lm.registerLanguageModelChatProvider(AGENT_ZEN_VENDOR, agentZenProvider), + ...(goProviderEnabled ? [vscode.lm.registerLanguageModelChatProvider(AGENT_GO_VENDOR, agentGoProvider)] : []), + ...(zenProviderEnabled ? [vscode.lm.registerLanguageModelChatProvider(AGENT_ZEN_VENDOR, agentZenProvider)] : []), ); // On VS Code 1.129+ the Agents window runs in the agent host process, // where extension BYOK models are only reachable through VS Code's BYOK @@ -942,6 +952,34 @@ async function configureUtilityModels(): Promise { ); } +/** + * Toggle whether a provider (`opencodego` / `opencodezen`) is registered at + * all. Disabling removes the provider from the Language Models list and every + * model picker — the provider's vendor contribution is gated by the same + * `when` clause (`config..enabled`) and its runtime registration is + * skipped. Previously configured BYOK groups and API keys are kept, so + * re-enabling restores the provider exactly as it was. + * + * Provider registration happens at startup, so a window reload is required + * for the change to take effect. + */ +async function toggleProviderEnabled(vendor: string, displayName: string): Promise { + const cfg = vscode.workspace.getConfiguration(vendor); + const current = cfg.get("enabled", true); + const next = !current; + await cfg.update("enabled", next, vscode.ConfigurationTarget.Global); + + const reload = await vscode.window.showInformationMessage( + next + ? `${displayName} re-enabled. Reload the window for the provider to appear in Language Models again.` + : `${displayName} removed from Language Models. Reload the window for it to disappear from the model picker and the manage list. Your API key and group settings are kept.`, + "Reload Now", + ); + if (reload === "Reload Now") { + await vscode.commands.executeCommand("workbench.action.reloadWindow"); + } +} + /** * Whether this VS Code has the modern agent-host BYOK bridge (1.129+). * @@ -1048,8 +1086,12 @@ async function revertAgentsWindowSupport(context: vscode.ExtensionContext): Prom } async function warmModelPickerMetadata(): Promise { - const vendors: string[] = [GO_VENDOR, ZEN_VENDOR]; - if (vscode.workspace.getConfiguration("opencodego").get("agentsWindow", true)) { + const cfg = vscode.workspace.getConfiguration("opencodego"); + const vendors: string[] = [ + ...(cfg.get("enabled", true) ? [GO_VENDOR] : []), + ...(cfg.get("opencodezen.enabled", true) ? [ZEN_VENDOR] : []), + ]; + if (vscode.workspace.getConfiguration("opencodego").get("agentsWindow", true) && vendors.length > 0) { vendors.push(AGENT_GO_VENDOR, AGENT_ZEN_VENDOR); } await Promise.allSettled(vendors.map((v) => vscode.lm.selectChatModels({ vendor: v }))); @@ -1769,13 +1811,7 @@ class OpenCodeProvider implements vscode.LanguageModelChatProvider { - const apiKey = await this.context.secrets.get(SECRET_KEY); - - if (!apiKey) { - await this.setApiKey(); - return; - } - + const providerEnabled = vscode.workspace.getConfiguration(this.definition.vendor).get("enabled", true); const choice = await vscode.window.showQuickPick( [ { label: "Set API Key", action: "set" as const }, @@ -1784,6 +1820,9 @@ class OpenCodeProvider implements vscode.LanguageModelChatProvider Date: Tue, 11 Aug 2026 08:14:10 +0500 Subject: [PATCH 5/5] fix(models): read provider enabled flag from the correct config section Review feedback on PR #125: getConfiguration("opencodego") resolves keys relative to that section, so reading "opencodezen.enabled" through it silently read opencodego.opencodezen.enabled and always fell back to true. Net effect: disabling Zen hid the vendor from Manage Models (when clause) but still registered the provider, so Zen models stayed in the Chat picker. Extract providerEnabledSetting() into a pure, unit-tested module that maps every vendor (including agent-host variants, resolved to their base vendor) to the full root-configuration key, and read from the root configuration in activate(), warmModelPickerMetadata() and manage(). --- src/extension.ts | 37 ++++++++++++++++------------- src/providerEnablement.ts | 18 ++++++++++++++ src/test/providerEnablement.test.ts | 24 +++++++++++++++++++ 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/providerEnablement.ts create mode 100644 src/test/providerEnablement.test.ts diff --git a/src/extension.ts b/src/extension.ts index 24efa40..ed87950 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -44,6 +44,7 @@ import { type AllProviderVendor, type ProviderVendor, } from "./providerTypes"; +import { providerEnabledSetting } from "./providerEnablement"; import { isInternalDataPart } from "./chatParts"; import { getImageDataUrlBase64Bytes, MAX_IMAGE_BASE64_BYTES, normalizeImageDataUrl } from "./imageNormalizer"; import { imageDescriptionKey, lookupImageDescriptions, storeImageDescriptions } from "./visionProxyCache"; @@ -718,9 +719,11 @@ export function activate(context: vscode.ExtensionContext) { ensureUsageStatusBar(context); ensureGoUsageStatusBar(context); - const opencodeCfg = vscode.workspace.getConfiguration("opencodego"); - const goProviderEnabled = opencodeCfg.get("enabled", true); - const zenProviderEnabled = opencodeCfg.get("opencodezen.enabled", true); + // Read from the root configuration with the FULL setting key: section-scoped + // reads (getConfiguration("opencodego")) resolve keys relative to the + // section, which would misread the Zen flag as opencodego.opencodezen.enabled. + const goProviderEnabled = vscode.workspace.getConfiguration().get(providerEnabledSetting(GO_VENDOR), true); + const zenProviderEnabled = vscode.workspace.getConfiguration().get(providerEnabledSetting(ZEN_VENDOR), true); const goProvider = new OpenCodeProvider(context, PROVIDERS[GO_VENDOR]); const zenProvider = new OpenCodeProvider(context, PROVIDERS[ZEN_VENDOR]); const modelInfoProviders: OpenCodeProvider[] = [goProvider, zenProvider]; @@ -1086,10 +1089,9 @@ async function revertAgentsWindowSupport(context: vscode.ExtensionContext): Prom } async function warmModelPickerMetadata(): Promise { - const cfg = vscode.workspace.getConfiguration("opencodego"); const vendors: string[] = [ - ...(cfg.get("enabled", true) ? [GO_VENDOR] : []), - ...(cfg.get("opencodezen.enabled", true) ? [ZEN_VENDOR] : []), + ...(vscode.workspace.getConfiguration().get(providerEnabledSetting(GO_VENDOR), true) ? [GO_VENDOR] : []), + ...(vscode.workspace.getConfiguration().get(providerEnabledSetting(ZEN_VENDOR), true) ? [ZEN_VENDOR] : []), ]; if (vscode.workspace.getConfiguration("opencodego").get("agentsWindow", true) && vendors.length > 0) { vendors.push(AGENT_GO_VENDOR, AGENT_ZEN_VENDOR); @@ -1811,7 +1813,9 @@ class OpenCodeProvider implements vscode.LanguageModelChatProvider { - const providerEnabled = vscode.workspace.getConfiguration(this.definition.vendor).get("enabled", true); + // Read via the base-vendor full key so agent variants (opencodego-agent, + // opencodezen-agent) follow the same switch as the vendor they mirror. + const providerEnabled = vscode.workspace.getConfiguration().get(providerEnabledSetting(this.definition.vendor), true); const choice = await vscode.window.showQuickPick( [ { label: "Set API Key", action: "set" as const }, @@ -2264,7 +2268,13 @@ class OpenCodeProvider implements vscode.LanguageModelChatProvider 0) { const fallbackDescription = descriptions.values().next().value ?? ""; for (let i = 0; i < flatMessages.length; i++) { @@ -4122,10 +4132,7 @@ function collectRequestParts( * the proxy describe ONLY the message that contains a new image, instead of * re-sending the whole conversation on every turn. */ -function buildVisionRequestMessage( - msg: vscode.LanguageModelChatRequestMessage, - visionPrompt: string, -): vscode.LanguageModelChatMessage[] { +function buildVisionRequestMessage(msg: vscode.LanguageModelChatRequestMessage, visionPrompt: string): vscode.LanguageModelChatMessage[] { const requestMessages: vscode.LanguageModelChatMessage[] = []; const parts = collectRequestParts(msg); if (parts.length > 0) { @@ -4250,8 +4257,7 @@ async function proxyVision( for (let index = 0; index < messages.length; index++) { const msg = messages[index]; const imageParts = msg.content.filter( - (part): part is vscode.LanguageModelDataPart => - part instanceof vscode.LanguageModelDataPart && part.mimeType.startsWith("image/"), + (part): part is vscode.LanguageModelDataPart => part instanceof vscode.LanguageModelDataPart && part.mimeType.startsWith("image/"), ); if (imageParts.length === 0) { continue; @@ -4280,8 +4286,7 @@ async function proxyVision( for (let index = 0; index < messages.length; index++) { const msg = messages[index]; const imageParts = msg.content.filter( - (part): part is vscode.LanguageModelDataPart => - part instanceof vscode.LanguageModelDataPart && part.mimeType.startsWith("image/"), + (part): part is vscode.LanguageModelDataPart => part instanceof vscode.LanguageModelDataPart && part.mimeType.startsWith("image/"), ); if (imageParts.length === 0) { continue; diff --git a/src/providerEnablement.ts b/src/providerEnablement.ts new file mode 100644 index 0000000..805d3eb --- /dev/null +++ b/src/providerEnablement.ts @@ -0,0 +1,18 @@ +import { resolveBaseVendor, type AllProviderVendor } from "./providerTypes"; + +/** + * The full configuration key that gates whether a provider is registered at + * all (`opencodego.enabled` / `opencodezen.enabled`). Agent-host variants + * resolve to their base vendor, so the agent providers follow the same switch + * as the vendor they mirror. + * + * CONTRACT: callers must read this from the ROOT configuration + * (`vscode.workspace.getConfiguration().get(key, ...)`), never from a + * section-scoped configuration — `getConfiguration("opencodego")` resolves + * keys relative to that section, so passing the full `opencodezen.enabled` + * key there would silently read `opencodego.opencodezen.enabled` and always + * fall back to the default. + */ +export function providerEnabledSetting(vendor: AllProviderVendor): string { + return `${resolveBaseVendor(vendor)}.enabled`; +} diff --git a/src/test/providerEnablement.test.ts b/src/test/providerEnablement.test.ts new file mode 100644 index 0000000..bf22bcb --- /dev/null +++ b/src/test/providerEnablement.test.ts @@ -0,0 +1,24 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { providerEnabledSetting } from "../providerEnablement"; +import { AGENT_GO_VENDOR, AGENT_ZEN_VENDOR, GO_VENDOR, ZEN_VENDOR } from "../providerTypes"; + +test("providerEnabledSetting — base vendors map to their own setting", () => { + assert.equal(providerEnabledSetting(GO_VENDOR), "opencodego.enabled"); + assert.equal(providerEnabledSetting(ZEN_VENDOR), "opencodezen.enabled"); +}); + +test("providerEnabledSetting — agent-host variants follow their base vendor", () => { + assert.equal(providerEnabledSetting(AGENT_GO_VENDOR), "opencodego.enabled"); + assert.equal(providerEnabledSetting(AGENT_ZEN_VENDOR), "opencodezen.enabled"); +}); + +test("providerEnabledSetting — keys are full root-configuration keys (regression: #125 review)", () => { + // Section-scoped reads (getConfiguration("opencodego")) resolve keys relative + // to the section. The Zen flag must be read from the root configuration with + // the full "opencodezen.enabled" key, otherwise the read silently hits + // "opencodego.opencodezen.enabled" and always falls back to the default. + assert.ok(providerEnabledSetting(ZEN_VENDOR).startsWith("opencodezen.")); + assert.ok(providerEnabledSetting(GO_VENDOR).startsWith("opencodego.")); + assert.ok(!providerEnabledSetting(ZEN_VENDOR).startsWith("opencodego.")); +});