VS Code BYOK: run Copilot Chat on any model (2026)
By Jose Sabater -
TL;DR: We published an Opper extension for VS Code. It adds Opper's 700+ models to the VS Code chat model picker, plus your pools and dynamic routes. Agent mode included. No Copilot subscription, no GitHub sign-in.
code --install-extension opperai.opper-vscode
Then run Opper: Manage API Key from the command palette and paste a key from platform.opper.ai.
What BYOK means in VS Code now
VS Code 1.104 stabilised the Language Model Chat Provider API, and it changed the bring-your-own-key story in two ways worth knowing before you pick an approach.
First, models contributed this way don't require a Copilot plan. Chat and agent mode run on the model you brought, without a subscription and without signing in to GitHub. (Inline completions still belong to Copilot; this is about chat.)
Second, there are now two ways to bring a model in:
- Custom Endpoint. Built into VS Code. You paste a base URL and a key, then hand-type every model id you want to use. Fine for two or three models. Painful past that, and it can't know what your key is actually allowed to reach.
- A provider extension. One that implements the API and discovers models at runtime. The picker fills itself, and the extension can show things a bare endpoint can't: where a model is hosted, whether it retains data, whether it's a single model or a load-balancing pool.
The Opper extension is the second kind.
Install the Opper extension for VS Code
- Install it from the Marketplace, or run
code --install-extension opperai.opper-vscode. VS Code 1.104 or later. - Run Opper: Manage API Key and paste a key from platform.opper.ai. It goes into VS Code's
SecretStorage, never intosettings.json, so it won't ride along into a synced or committed settings file. - Open Chat, click the model picker, and pick an Opper model.
Nothing is listed until there's a key, because the list is scoped to it.
What lands in the model picker
Three kinds of entry, and the distinction matters more than it looks:
| Entry | What it is |
|---|---|
| Model | One model on one provider, e.g. gcp/claude-sonnet-4-5-eu. You know exactly who serves it and from where. |
| Pool | A bare name like claude-sonnet-4-5 that load-balances across every provider serving that model, and falls back when one of them rate-limits or goes down. |
| Dynamic route | One of your own deployed routing graphs, as dynamic/<name>. The graph decides which model handles each request: cheap model for the easy turns, frontier model when it matters. |
Pools and dynamic routes are the reason a provider extension beats a hand-typed endpoint: neither one is a model id you could have guessed, and both are specific to your project.
Each entry's detail line carries its region and whether it's zero-data-retention, so the choice is visible at the moment you make it rather than buried in a catalog somewhere. The full list of what's available is in the model directory.
Narrow the list to EU or ZDR models
700+ entries is a lot of scrolling. Everything below is a normal VS Code setting:
| Setting | Default | What it does |
|---|---|---|
opper.euOnly | false | EU-hosted models only. |
opper.zdrOnly | false | Zero-data-retention models only. |
opper.modelFilter | [] | Substrings to narrow a long list. |
opper.showModels | true | List concrete models. |
opper.showPools | true | List load-balancing names. |
opper.showDynamicRoutes | true | List your deployed routes. |
For the kind toggles there's a faster path: the gear next to Opper in the model picker → Choose what to list…. Untick Models and you're left with just your pools and routes, which is how most people end up using it day to day.
Worth saying plainly: discovery is filtered server-side by your project's compliance rules. A model your policy denies is never offered in the picker in the first place. You can't accidentally pick it, and you don't have to trust the client to enforce anything.
Custom Endpoint vs. a provider extension
| Custom Endpoint | Opper extension | |
|---|---|---|
| Model ids | Typed by hand, one at a time | Discovered at runtime |
| Pools and dynamic routes | Not reachable | In the picker |
| Region / ZDR visible | No | On each entry |
| Scoped to your policy | No | Server-side |
| Copilot plan required | No | No |
If you're already on the Custom Endpoint route (see the GitHub Copilot setup page), it keeps working. The extension just removes the copy-pasting.
Troubleshooting
The picker is empty. No key is stored. Run Opper: Manage API Key.
I'm seeing models from the wrong project. Compliance rules are scoped to the key's project, so a key pointing somewhere unexpected looks like a filtering bug. The gear menu leads with Opper · <org> · project <name> · N models, and that line is the fastest way to spot it. If you have OPPER_API_KEY exported in your shell it's used only when no key is stored, and the line says so when it is.
Agent mode won't use my dynamic route. Opper can't know ahead of time whether a route supports tool calling, because the graph decides per request. opper.dynamicRouteToolCalling is a promise you make on the route's behalf; turn it off if a route can land on a model without tool support.
Discovery works but the first message fails. Check whether the project is blocked on spend. Listing models and spending on them are different permissions, so the picker looks healthy right up until you send something.
Get started
Grab a key at opper.ai, install the extension, and pick a model. The extension is MIT-licensed and the source is on GitHub.
Same idea, other tools: Claude Code, Codex CLI, and Claude Cowork all route through the same gateway. Questions? Find us on Discord or drop us a line at hello@opper.ai.