Fork of ericc-ch/copilot-api — a reverse-engineered proxy that exposes GitHub Copilot as an OpenAI- and Anthropic-compatible API, usable as a backend for Claude Code.
Warning
Reverse-engineered and unsupported by GitHub. Excessive automated/bulk requests may trigger GitHub's abuse detection and get your Copilot access suspended. Use responsibly. See GitHub Acceptable Use and the Copilot Terms.
- VS Code token bridge / proxy-only auth — obtain the Copilot token from a running VS Code instead of the GitHub device-code flow. Lets the proxy run with no
GH_TOKEN(see Token sources). - Model mappings —
MODEL_MAPPINGSenv var rewrites incoming model IDs (e.g. theclaude-*IDs Claude Code sends) to the internal Copilot model names. This is what makes Claude Code work against Copilot. /v1/responsesendpoint — OpenAI Responses API support, in addition to the upstream chat/messages/embeddings routes.- Hardened token refresh — centralized 401 handling, retry with backoff, and survives long-running sessions.
- Enterprise account type by default —
bun run dev/bun run authpass--account-type enterprise. - Windows
.cmdhelpers + Claude Codesettings.jsonfor a one-double-click workflow (see below).
- Bun (>= 1.2.x)
- A GitHub account with a Copilot subscription, or a running VS Code signed in to Copilot (for the token bridge)
bun installThe repo ships .cmd launchers. Set them up once as Windows Terminal profiles (Settings → Add a new profile → New empty profile → paste the script path into Command line), or just double-click them.
| Script | What it does |
|---|---|
start-copilot-api.cmd |
Starts the proxy server (npm run dev) on http://localhost:4141. |
start-claude.cmd |
Launches Claude Code pointed at the proxy (working dir c:\src\controlplane). |
start-cc-copilot-api.cmd |
Launches Claude Code in this repo's dir (handy for working on the proxy itself). |
re-auth.cmd |
Re-runs the GitHub auth flow (npm run auth) when the token expires. |
Typical flow: run start-copilot-api.cmd, then start-claude.cmd.
The model IDs and effort level Claude Code uses are set as set ANTHROPIC_* lines at the top of start-claude.cmd / start-cc-copilot-api.cmd — edit those to change models.
bun run dev # watch mode (account-type enterprise)
bun run start # production
bun run auth # GitHub auth onlySee the upstream README for npx, Docker, and the full CLI option tables — they apply unchanged.
The proxy resolves a Copilot token in this order:
- GitHub token (
GH_TOKEN/--github-token, or the interactive device-code flow) → exchanged for a Copilot token, then auto-refreshed. - VS Code token bridge (fallback, or proxy-only mode when no GitHub token is present) — the proxy fetches a token from a small VS Code extension at
http://127.0.0.1:<VSCODE_PROXY_PORT>/token.
Source: wenbo97/copilot-token-bridge — the extension is open source; the bundled
.vsixbelow is a prebuilt copy.
- Install the bundled extension:
copilot-token-bridge-0.3.0.vsixcode --install-extension copilot-token-bridge-0.3.0.vsix
- Make sure VS Code is signed in to GitHub Copilot and left running.
- (Optional) If the extension uses a non-default port, set
VSCODE_PROXY_PORTin.env(default18774).
With the bridge available the proxy can run with no GitHub token at all — useful when the device-code flow is blocked.
Copy .env.example to .env. Fork-relevant keys:
| Variable | Purpose | Default |
|---|---|---|
MODEL_MAPPINGS |
Rewrite model IDs, source:target comma-separated (e.g. claude-opus-4-8:claude-opus-4.8). |
none |
VSCODE_PROXY_PORT |
Port of the Copilot Token Bridge VS Code extension. | 18774 |
IDLE_TIMEOUT |
Bun server idle timeout (seconds). | 255 |
TRACE_OUTPUT_FOLDER |
Where request/response traces go when --trace is set. |
./traces |
MODEL_MAPPINGS example (maps the IDs Claude Code sends to internal Copilot model names):
MODEL_MAPPINGS="claude-opus-4-8:claude-opus-4.8,claude-opus-4-7:claude-opus-4.7-1m-internal,claude-sonnet-4-6:claude-sonnet-4.6,claude-haiku-4-5:claude-haiku-4.5"- Copy
settings.jsontoC:/Users/<user-name>/.claude/settings.json(create the file if needed). - Restart the Claude chat (close and reopen it).
settings.json points ANTHROPIC_BASE_URL at http://localhost:4141 and sets the model IDs — adjust to taste.
| Command | |
|---|---|
bun run dev |
Watch-mode server (--account-type enterprise, port 4141) |
bun run lint |
ESLint (@echristian/eslint-config) |
bun test |
Unit test suite |
bun run typecheck |
tsc |
See AGENTS.md for code-style conventions.
Three layers, cheapest first. Run 1 and 2 on every change; run 3 before declaring a routing or translation change complete.
No network, no proxy, no CLI. fetch is mocked.
bun test # whole suite
bun test tests/create-responses.test.ts # one file
bun test --test-name-pattern "encrypted" # by test namebun run typecheck # tsc, no emit
bun run lint -- --fix src/foo.ts tests/foo.test.ts # autofix specific files
bun run lint:all # whole repo — see caveat belowNote the -- before flags you want to reach ESLint: bun run lint already
expands to eslint --cache, so bun run lint -- --fix <paths> is the correct
form. A simple-git-hooks pre-commit hook runs lint-staged on staged files
automatically, so a commit will reformat what you are committing.
Note
lint:all does not pass on a fresh Windows checkout, and that is expected —
nothing is actually broken. This repo has core.autocrlf=true and no
.gitattributes, so Git stores LF but checks files out as CRLF, while the
Prettier config expects LF: one prettier/prettier "Delete ␍" error per
line, ~10k repo-wide. Committed content and diffs are unaffected, and the
pre-commit hook only lints staged files, so day-to-day work is unaffected
too. Lint the files you changed rather than the whole repo.
If you do want lint:all to pass, the zero-churn fix is one line in
eslint.config.js. It keeps the CRLF working tree and just tells Prettier to
accept each file's existing endings (verified: takes an untouched file from
434 errors to 0):
export default config({
prettier: { plugins: ["prettier-plugin-packagejson"], endOfLine: "auto" },
})The heavier alternative — a .gitattributes with * text=auto eol=lf plus a
re-checkout — converts the entire working tree to LF instead.
This is the only thing that may declare a routing change complete. It starts a
fresh proxy on :4143 from the current worktree (so it tests your edits,
not the running server), drives the real claude -p and codex exec
binaries against it, and judges each cell by a trace-tag oracle rather than by
eyeballing replies.
Prerequisites
claudeandcodexonPATHand already authenticated- A working Copilot token source (GitHub token or the VS Code bridge)
- Port
4143free - Network access to the Copilot backend
bun run tests/acceptance/run.ts # full matrix (24 cells, ~10 min)
bun run tests/acceptance/run.ts --list # print the cells, run nothing
bun run tests/acceptance/run.ts --only 1a,1f # subset by cell id
bun run tests/acceptance/run.ts --mandate 1 # one mandate groupA cell passes only when all of these hold:
- the egress trace tag matches the expected one,
- the client process exits
0, - the final assistant text is non-empty,
- no
unsupported_api_for_modelor raw400appears in client output, - any cell-specific extra assertions hold.
Results are written to tests/acceptance/RESULTS-<date>.md (one row per cell:
expected vs actual tag, exit code, PASS/FAIL, trace path). The runner exits
non-zero if any cell fails.
Trace tags — the .type field of each <traceDir>/<ts>.req, i.e. which
egress leg the request actually took:
| Tag | Inbound → egress |
|---|---|
anthropic-passthrough |
Claude Code → Copilot /v1/messages, no translation |
anthropic-via-responses |
Claude Code → Copilot /responses |
responses-passthrough |
Codex → Copilot /responses, no translation |
responses |
Codex → Copilot /chat/completions (translate-down) |
anthropic / chat |
Anthropic / OpenAI chat-completions legs |
For each (model × client) combo, runs N live iterations cycling through client
features, so the runs exercise different code paths instead of the same call N
times. Writes SOAK-RESULTS-<date>.md.
bun run tests/acceptance/soak.ts # all combos, 50 runs each
bun run tests/acceptance/soak.ts --runs 10 # smoke
bun run tests/acceptance/soak.ts --only claude:gpt-5.5The harness deliberately never touches a port you may be using interactively:
| Port | Use |
|---|---|
4141 |
Your live proxy (start-copilot-api.cmd / bun run dev) |
4142 |
Scratch instance for manual probing |
4143 |
Reserved for the acceptance harness — started and stopped by it |
To inspect exactly what goes on the wire, run a second instance with tracing and
verbose logging, and leave :4141 alone:
bun run ./src/main.ts start --account-type enterprise \
--port 4142 --trace --trace-folder ./traces-dbg --verboseEach request writes <ts>.req and <ts>.resp into the trace folder. --verbose
additionally logs every raw upstream stream event, which is how you tell a
proxy-side fault from an upstream rejection.
When a real Codex session misbehaves, replay its history instead of guessing.
Codex stores every session as JSONL under
~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-*.jsonl; the response_item
records are exactly the Responses API input array. Collect them, POST them to
:4142/v1/responses, and bisect by removing item kinds until the failure
disappears. That turns "the client is broken" into a red/green loop in seconds.
Credit: original work by Erick Christian (ko-fi). This fork only layers on the Windows/Claude-Code/token-bridge changes described above.