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
132 changes: 132 additions & 0 deletions docs/copilot-vscode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# GitHub Copilot in VS Code → Opper

The CLI now ships an adapter that does the Stable-channel setup for you:

```bash
opper editors github-copilot-vscode
```

It detects the OAI Compatible community extension, prompts before
installing it (with a marketplace link and a clean cancel path), then
writes the Opper provider block into your VS Code user `settings.json`.
Remove with `opper editors github-copilot-vscode --remove`.

The artifacts in this directory are the manual recipe — useful as a
reference, for users who don't have the CLI installed, or for the
Insiders channel where the native BYOK path isn't currently usable.

## What this gets you

- **In scope:** Copilot **Chat** and **Agent mode** in VS Code, answered by
Opper-routed models from the curated picker (`src/config/models.ts`).
- **Out of scope:** Inline ghost-text completions still go to GitHub's own
Copilot service. BYOK does not redirect them. Embeddings, repository
indexing, intent detection and a few other side queries also keep
hitting GitHub's service.
- **Auth:** API key entered once via VS Code's "Manage Models" UI; stored
in the OS keychain (Insiders) or by the community extension (Stable),
not in `settings.json`.
- **Subscription gates:** Copilot Free / Pro have BYOK on by default.
Copilot Business / Enterprise users need their org admin to enable the
"Bring Your Own Language Model Key in VS Code" policy.

## Files

- `generate.ts` — derives both JSON snippets from `PICKER_MODELS`. Run
`npx tsx docs/copilot-vscode/generate.ts` after any model-list change.
- `insiders-settings.json` — paste-ready block for **VS Code Insiders**
using the native `github.copilot.chat.customOAIModels` setting.
- `stable-settings.json` — paste-ready block for **VS Code Stable**, used
with the community extension `johnny-zhao.oai-compatible-copilot`.

## VS Code Insiders setup (UI path)

> **Status (May 2026):** The native settings-driven path
> (`github.copilot.chat.customOAIModels`) is **deprecated** in the Copilot
> Chat extension shipped with Insiders 1.120 — it lives under
> `ConfigKey.Deprecated.CustomOAIModels` in `vscode-copilot-chat` source
> with a "remove after 6 months" TODO. It runs a one-shot migration into
> a new BYOK storage system, then ignores subsequent edits.
>
> So Insiders has no working declarative configuration today. Use the
> UI flow below; we'll revisit declarative setup once Microsoft ships
> the array-based replacement for `customOAIModels`.

The `insiders-settings.json` snippet in this directory is preserved as a
reference but should **not** be merged into Insiders user settings —
it'll be ignored.

1. Open Copilot Chat → click the model picker → **Manage Models**.
2. Click **+ Add Models…** → **OpenAI Compatible**.
3. When prompted:
- Base URL: `https://api.opper.ai/v3/compat`
- API key: your `OPPER_API_KEY`
- Model id: `claude-opus-4-7` (start here; add more once one works)
4. Repeat step 2 for any additional model id from `PICKER_MODELS` you
want surfaced.
5. Pick the model in the chat picker → send a message → confirm the
trace lands on `https://platform.opper.ai/traces`.

## VS Code Stable setup (community extension)

Until the native custom-OAI provider lands in Stable, the community
extension is the most painless path.

1. Install the extension:
`code --install-extension johnny-zhao.oai-compatible-copilot`
(or search for "OAI Compatible Provider for Copilot" in the
Extensions sidebar).
2. Open user `settings.json` and merge the contents of
`stable-settings.json` into it (top-level keys: `oaicopilot.baseUrl`
and `oaicopilot.models`).
3. Reload the window.
4. Open Copilot Chat → model picker → **Manage Models** → choose **OAI
Compatible** → enter your `OPPER_API_KEY` when prompted → select the
Opper models to surface in the picker.
5. Send a chat message using one of the new models and verify the trace
appears on Opper.

## Testing checklist

For each model worth validating (start with `claude-opus-4-7` and one
non-Anthropic model — `gpt-5.5` or `gemini-3.1-pro-preview`):

- [ ] Plain chat reply renders end-to-end.
- [ ] Streaming chunks arrive incrementally (not just at the end).
- [ ] **Agent mode** runs a multi-step task that uses tools (e.g.
`read_file`, `apply_patch`).
- [ ] Long-context request (paste a 20k-token prompt) doesn't truncate.
- [ ] Vision: drag in an image — for models flagged `vision: true` it
should be accepted; for `vision: false` it should be rejected
cleanly rather than silently ignored.
- [ ] Thinking / reasoning content surfaces (or is suppressed cleanly)
for models flagged `thinking: true`.
- [ ] Trace lands on `https://platform.opper.ai/traces` with the right
model id.

## What to flag back

If anything in the table below is wrong, ping me and I'll regenerate:

- A capability flag (`toolCalling`, `vision`, `thinking`) that doesn't
match what Opper actually reports for a model.
- A `maxInputTokens` / `maxOutputTokens` that VS Code rejects or that the
upstream model can't honour.
- A model id that `/v3/compat` returns "unknown model" for.
- Any model that needs a different `apiMode` than `"openai"` (Stable
extension only — Insiders pins to OpenAI shape).

## Promotion to phase 2

Once the JSON is stable, the adapter lives in
`src/agents/github-copilot-vscode.ts` as a configure-only adapter (mirror
of `claude-desktop.ts`):

- `configure()` writes a sentinel-bracketed `customOAIModels` entry into
the user `settings.json` for whichever channel is detected.
- `unconfigure()` strips it.
- No `spawn()` — VS Code is launched by the user, not by us.

The `generate.ts` logic here becomes the body of `configure()`; the
`VISION` set either graduates onto `PickerModel` in `src/config/models.ts`
or sits beside the new adapter.
86 changes: 86 additions & 0 deletions docs/copilot-vscode/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Phase-1 scratch generator for the GitHub Copilot in VS Code BYOK
* integration. Reads PICKER_MODELS and emits the two settings.json snippets
* sitting next to this file:
*
* - insiders-settings.json: native `github.copilot.chat.customOAIModels`
* block (works in VS Code Insiders 1.104+).
* - stable-settings.json: `oaicopilot.*` block consumed by the
* "OAI Compatible Provider for Copilot" community extension on stable.
*
* Run from the repo root:
* npx tsx docs/copilot-vscode/generate.ts
*
* When phase 2 promotes this to a real adapter, the logic here moves into
* `src/agents/github-copilot-vscode.ts` and the capability table either
* graduates onto `PickerModel` or sits beside the adapter.
*/
import { writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { PICKER_MODELS } from "../../src/config/models.js";

const PROVIDER_URL = "https://api.opper.ai/v3/compat";
const PROVIDER_NAME = "Opper";
const DEFAULT_MAX_OUTPUT = 32_768;

// Conservative vision allowlist — flip during testing as we confirm each
// model's compat behaviour. Keeping non-vision off by default avoids the
// picker advertising image support that the upstream model can't honour.
const VISION = new Set<string>([
"claude-opus-4-7",
"claude-sonnet-4-6",
"claude-haiku-4-5",
"gpt-5.5",
"gemini-3.1-pro-preview",
]);

const here = dirname(fileURLToPath(import.meta.url));

// Insiders' native customOAIModels uses an OBJECT keyed by model id, with
// each entry repeating the provider URL. The grouped-array shape (one
// provider, nested `models`) is a still-open feature request
// (microsoft/vscode#277102), not the implemented schema — using it
// registers the provider name but silently drops the model list.
const insidersBlock = {
"github.copilot.chat.customOAIModels": Object.fromEntries(
PICKER_MODELS.map((m) => [
m.id,
{
name: `${PROVIDER_NAME} · ${m.id}`,
url: PROVIDER_URL,
requiresAPIKey: true,
toolCalling: true,
vision: VISION.has(m.id),
thinking: m.reasoning,
maxInputTokens: m.contextWindow,
maxOutputTokens: DEFAULT_MAX_OUTPUT,
},
]),
),
};

const stableBlock = {
"oaicopilot.baseUrl": PROVIDER_URL,
"oaicopilot.models": PICKER_MODELS.map((m) => ({
id: m.id,
owned_by: "opper",
displayName: m.id,
apiMode: "openai",
context_length: m.contextWindow,
max_tokens: DEFAULT_MAX_OUTPUT,
vision: VISION.has(m.id),
enable_thinking: m.reasoning,
})),
};

await writeFile(
join(here, "insiders-settings.json"),
`${JSON.stringify(insidersBlock, null, 2)}\n`,
);
await writeFile(
join(here, "stable-settings.json"),
`${JSON.stringify(stableBlock, null, 2)}\n`,
);

console.log("wrote insiders-settings.json and stable-settings.json");
104 changes: 104 additions & 0 deletions docs/copilot-vscode/insiders-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"github.copilot.chat.customOAIModels": {
"claude-opus-4-7": {
"name": "Opper · claude-opus-4-7",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": true,
"thinking": true,
"maxInputTokens": 1000000,
"maxOutputTokens": 32768
},
"claude-sonnet-4-6": {
"name": "Opper · claude-sonnet-4-6",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": true,
"thinking": true,
"maxInputTokens": 1000000,
"maxOutputTokens": 32768
},
"claude-haiku-4-5": {
"name": "Opper · claude-haiku-4-5",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": true,
"thinking": false,
"maxInputTokens": 200000,
"maxOutputTokens": 32768
},
"gpt-5.5": {
"name": "Opper · gpt-5.5",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": true,
"thinking": true,
"maxInputTokens": 1050000,
"maxOutputTokens": 32768
},
"gemini-3.1-pro-preview": {
"name": "Opper · gemini-3.1-pro-preview",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": true,
"thinking": true,
"maxInputTokens": 1048576,
"maxOutputTokens": 32768
},
"deepinfra/kimi-k2.6": {
"name": "Opper · deepinfra/kimi-k2.6",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": false,
"thinking": true,
"maxInputTokens": 262144,
"maxOutputTokens": 32768
},
"deepinfra/glm-5.1": {
"name": "Opper · deepinfra/glm-5.1",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": false,
"thinking": true,
"maxInputTokens": 202752,
"maxOutputTokens": 32768
},
"fireworks/minimax-m2p7": {
"name": "Opper · fireworks/minimax-m2p7",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": false,
"thinking": false,
"maxInputTokens": 196608,
"maxOutputTokens": 32768
},
"deepinfra/deepseek-v4-pro": {
"name": "Opper · deepinfra/deepseek-v4-pro",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": false,
"thinking": true,
"maxInputTokens": 1048576,
"maxOutputTokens": 32768
},
"deepinfra/deepseek-v4-flash": {
"name": "Opper · deepinfra/deepseek-v4-flash",
"url": "https://api.opper.ai/v3/compat",
"requiresAPIKey": true,
"toolCalling": true,
"vision": false,
"thinking": true,
"maxInputTokens": 1048576,
"maxOutputTokens": 32768
}
}
}
Loading
Loading