Commit 204e751
fix(copilot): route VS Code inline completions to Copilot, not OpenAI (#3077)
## Description
Fixes #3076.
When `github.copilot.advanced.debug.overrideProxyUrl` points at
Headroom, the VS Code Copilot extension sends its inline ("ghost text")
completions to `/v1/engines/<engine>/completions`. No route matches that
path, so it falls into the catch-all passthrough — and
`select_passthrough_base_url()` resolves an upstream from the **auth
headers alone**, never looking at the path. Copilot sends none of the
headers the earlier branches key on, so the request reached the final
line (default to OpenAI) and Headroom forwarded editor keystrokes to:
```
https://api.openai.com/v1/engines/gpt-41-copilot/completions
```
Wrong under every configuration — OpenAI removed the Engines API years
ago — and blocked outright on corporate networks that permit GitHub
Copilot but not OpenAI, which is how it was reported. Inline completions
stopped working for every user behind such a policy.
The Copilot **CLI** was unaffected: it speaks the CAPI shape
(`/chat/completions`), which already resolved correctly. That is the
exact asymmetry in the report.
## Type of Change
- [x] Bug fix
## Changes Made
**Routing.** `select_passthrough_base_url()` now takes the request path
and sends this one path to Copilot. The shape identifies Copilot on its
own, so the redirect is unambiguous. It is scoped to the OpenAI
fall-through — the branch that is wrong here — because every other
branch reflects an upstream the caller chose with its own auth headers.
**The destination is not hardcoded.** GitHub's token exchange advertises
the completions host in `endpoints.proxy`, alongside the `endpoints.api`
chat host Headroom already reads. It is now recorded at the single
chokepoint every exchange passes through, and preferred. Resolution
order:
1. `GITHUB_COPILOT_PROXY_URL` — operator override
2. `endpoints.proxy` from the last token exchange — GitHub's own answer
3. The Copilot API URL
No I/O on the request path, and GHE deployments keep their host. This
matters: it means the destination is not an assumption about which host
serves completions, and if it is wrong for a given network it is an env
var rather than a release.
**Path preservation.** `build_copilot_upstream_url()` strips `/v1` when
the upstream is a Copilot host, because Copilot serves its
OpenAI-compatible surface unprefixed (`/chat/completions`, `/models`).
But the extension built `/v1/engines/<engine>/completions` itself, so
that path is already exactly what Copilot serves — stripping the prefix
rewrites a working request into a 404. Preserved, the same carve-out
`/v1/messages` needed in #2409. The rule: strip only for clients
speaking generic-OpenAI at Copilot, never for Copilot's own paths.
## Testing
- [x] New suite: `tests/test_copilot_vscode_completions_routing.py` (30
tests) — path recognition and its near-misses, upstream selection, the
`endpoints.proxy` resolution order, and URL construction in both
directions
- [x] 286 passed across the Copilot, provider-routing and passthrough
suites
- [x] Ruff check and format pass
### Real Behavior Proof
Environment: this branch, a `POST
/v1/engines/gpt-41-copilot/completions` driven through the real app with
`OPENAI_API_URL=https://api.openai.com` and the outbound HTTP client
captured.
```
BEFORE (main): https://api.openai.com/v1/engines/gpt-41-copilot/completions
AFTER (this): https://api.githubcopilot.com/v1/engines/gpt-41-copilot/completions
```
The "before" line reproduces the reported URL exactly.
**Not tested:** a live VS Code Copilot session confirming GitHub accepts
the forwarded request. That needs a real Copilot account and editor. If
the completions host turns out to differ, the `endpoints.proxy` lookup
or `GITHUB_COPILOT_PROXY_URL` covers it without a code change.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Tejas Chopra <tejas@Tejass-MacBook-Pro.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 6d2254d commit 204e751
4 files changed
Lines changed: 410 additions & 5 deletions
File tree
- headroom
- providers
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
245 | 245 | | |
246 | 246 | | |
247 | 247 | | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
248 | 307 | | |
249 | 308 | | |
250 | 309 | | |
| |||
1056 | 1115 | | |
1057 | 1116 | | |
1058 | 1117 | | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
1059 | 1141 | | |
1060 | 1142 | | |
1061 | 1143 | | |
| |||
1071 | 1153 | | |
1072 | 1154 | | |
1073 | 1155 | | |
1074 | | - | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
1075 | 1167 | | |
1076 | 1168 | | |
1077 | 1169 | | |
| |||
1207 | 1299 | | |
1208 | 1300 | | |
1209 | 1301 | | |
1210 | | - | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
1211 | 1308 | | |
1212 | 1309 | | |
1213 | 1310 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
530 | 530 | | |
531 | 531 | | |
532 | 532 | | |
533 | | - | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
534 | 536 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
8 | 13 | | |
9 | 14 | | |
10 | 15 | | |
| |||
29 | 34 | | |
30 | 35 | | |
31 | 36 | | |
32 | | - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
33 | 40 | | |
34 | 41 | | |
35 | 42 | | |
| |||
41 | 48 | | |
42 | 49 | | |
43 | 50 | | |
44 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
0 commit comments