betahi-copilot-bridge 0.20.1 → 0.20.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -65
- package/dist/main.js +32 -55
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,34 +1,15 @@
|
|
|
1
1
|
<h1 align="center">copilot-bridge</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="https://www.npmjs.com/package/betahi-copilot-bridge"><img src="https://img.shields.io/npm/v/betahi-copilot-bridge.svg?v=0.20.
|
|
4
|
+
<a href="https://www.npmjs.com/package/betahi-copilot-bridge"><img src="https://img.shields.io/npm/v/betahi-copilot-bridge.svg?v=0.20.3" alt="npm version"></a>
|
|
5
5
|
<a href="https://github.com/betahi/copilot-bridge/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/betahi-copilot-bridge.svg" alt="license"></a>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
> Use GitHub Copilot as a local OpenAI/Anthropic-compatible API, so [Codex CLI](https://developers.openai.com/codex/cli), [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) and Continue can talk to Copilot with minimal configuration.
|
|
9
9
|
|
|
10
|
-
> [!
|
|
11
|
-
> This is
|
|
12
|
-
>
|
|
13
|
-
|
|
14
|
-
> [!WARNING]
|
|
15
|
-
> **GitHub Security Notice:**
|
|
16
|
-
> Excessive automated or scripted use of Copilot (including rapid or bulk
|
|
17
|
-
> requests, such as via automated tools) may trigger GitHub's abuse-detection
|
|
18
|
-
> systems. You may receive a warning from GitHub Security, and further
|
|
19
|
-
> anomalous activity could result in temporary suspension of your Copilot
|
|
20
|
-
> access.
|
|
21
|
-
>
|
|
22
|
-
> GitHub prohibits use of their servers for excessive automated bulk activity
|
|
23
|
-
> or any activity that places undue burden on their infrastructure.
|
|
24
|
-
>
|
|
25
|
-
> Please review:
|
|
26
|
-
>
|
|
27
|
-
> - [GitHub Acceptable Use Policies](https://docs.github.com/site-policy/acceptable-use-policies/github-acceptable-use-policies#4-spam-and-inauthentic-activity-on-github)
|
|
28
|
-
> - [GitHub Copilot Terms](https://docs.github.com/site-policy/github-terms/github-terms-for-additional-products-and-features#github-copilot)
|
|
29
|
-
>
|
|
30
|
-
> Use this bridge responsibly to avoid account restrictions. The
|
|
31
|
-
> `--rate-limit <seconds>` flag is provided to help throttle upstream traffic.
|
|
10
|
+
> [!CAUTION]
|
|
11
|
+
> This is an unofficial bridge for the GitHub Copilot API and may break if the
|
|
12
|
+
> upstream API changes.
|
|
32
13
|
|
|
33
14
|
## Demo
|
|
34
15
|
|
|
@@ -50,10 +31,6 @@ npx betahi-copilot-bridge@latest auth
|
|
|
50
31
|
npx betahi-copilot-bridge@latest start
|
|
51
32
|
```
|
|
52
33
|
|
|
53
|
-
`start` flags: `--host`, `--port`, `--show-token`, `--debug`,
|
|
54
|
-
`--no-codex-setup`, `--select-model`, `--no-prompt`,
|
|
55
|
-
`--rate-limit <seconds>`, `--wait`.
|
|
56
|
-
|
|
57
34
|
After startup the banner prints a **Usage Viewer** link of the form
|
|
58
35
|
`https://betahi.github.io/copilot-bridge?endpoint=http://127.0.0.1:4142/usage`,
|
|
59
36
|
which renders the Copilot quota snapshot (chat / completions / premium
|
|
@@ -65,22 +42,18 @@ The bridge exposes both adapter-style endpoints (`/v1/responses`,
|
|
|
65
42
|
LiteLLM, Continue, Cline and Aider work out of the box. CORS is enabled
|
|
66
43
|
globally for browser-based clients.
|
|
67
44
|
|
|
68
|
-
`--rate-limit N` enforces a minimum of N seconds between upstream
|
|
69
|
-
requests (anti–abuse-detection throttle). Add `--wait` to block instead
|
|
70
|
-
of returning HTTP 429 when the window has not elapsed.
|
|
71
|
-
|
|
72
|
-
`--debug` enables extra upstream error diagnostics in console logs.
|
|
73
|
-
- Includes: token limits, stream mode, tool count, invalid tool names, and suspicious tool-schema paths.
|
|
74
|
-
- Does not include: request messages, prompt text, bearer tokens, tool descriptions, or the full request body.
|
|
75
|
-
|
|
76
|
-
**Review or redact debug logs before sharing them publicly.**
|
|
77
|
-
|
|
78
45
|
## Configure Codex CLI
|
|
79
46
|
|
|
80
47
|
`start` writes a managed block into **`~/.codex/config.toml`**. You don't edit
|
|
81
|
-
|
|
48
|
+
between the markers; the bridge regenerates that block on every start. To pin
|
|
49
|
+
the **default model** for `codex` (without passing `-m` every time), add your
|
|
50
|
+
top-level keys above the managed block:
|
|
82
51
|
|
|
83
52
|
```toml
|
|
53
|
+
# User defaults; edit these freely.
|
|
54
|
+
model = "gpt-5.3-codex"
|
|
55
|
+
model_reasoning_effort = "high"
|
|
56
|
+
|
|
84
57
|
# >>> copilot-bridge managed block — auto-generated, do not edit between markers >>>
|
|
85
58
|
model_provider = "bridge"
|
|
86
59
|
model_supports_reasoning_summaries = true
|
|
@@ -94,37 +67,21 @@ requires_openai_auth = false
|
|
|
94
67
|
# <<< copilot-bridge managed block — edits outside this block are preserved <<<
|
|
95
68
|
```
|
|
96
69
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
these keys across rewrites:
|
|
100
|
-
|
|
101
|
-
```toml
|
|
102
|
-
model = "gpt-5.3-codex"
|
|
103
|
-
model_reasoning_effort = "high"
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
That's it — `codex exec '...'` will now route through the bridge to Copilot.
|
|
107
|
-
|
|
108
|
-
Use `--no-codex-setup` to skip the managed-block writer entirely (e.g. if you
|
|
109
|
-
manage `~/.codex/config.toml` yourself).
|
|
70
|
+
Use `--no-codex-setup` to skip this writer if you manage
|
|
71
|
+
`~/.codex/config.toml` yourself.
|
|
110
72
|
|
|
111
73
|
### Codex warning: "Model metadata ... not found"
|
|
112
74
|
|
|
113
|
-
This is a Codex client-side metadata warning, not a bridge routing failure.
|
|
114
|
-
Requests can still complete through the bridge.
|
|
75
|
+
This is a Codex client-side metadata warning, not a bridge routing failure, requests can still complete through the bridge.
|
|
115
76
|
|
|
116
|
-
For
|
|
77
|
+
For 1M models, upstream still enforces a 1,000,000-token prompt
|
|
117
78
|
limit (about 900k succeeds; around 1,000,046 is rejected as too long).
|
|
118
79
|
|
|
119
80
|
## Configure Claude Code
|
|
120
81
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`ANTHROPIC_AUTH_TOKEN` if absent) into **`~/.claude/settings.json`**, so
|
|
125
|
-
`claude` works the moment the bridge is listening — even on a different host
|
|
126
|
-
or port. Pass `--no-claude-setup` to skip this. All other keys in your
|
|
127
|
-
settings file (model overrides, plugins, marketplaces) are preserved.
|
|
82
|
+
`copilot-bridge start` writes `ANTHROPIC_BASE_URL` and a dummy
|
|
83
|
+
`ANTHROPIC_AUTH_TOKEN` into **`~/.claude/settings.json`**. Other settings are
|
|
84
|
+
preserved; use `--no-claude-setup` to skip this writer.
|
|
128
85
|
|
|
129
86
|
Minimal recommended config is:
|
|
130
87
|
|
|
@@ -146,12 +103,12 @@ the bridge maps it to the real Copilot `-1m` model upstream:
|
|
|
146
103
|
```json
|
|
147
104
|
{
|
|
148
105
|
"env": {
|
|
149
|
-
"ANTHROPIC_MODEL": "claude-opus-4.7-[1m]"
|
|
106
|
+
"ANTHROPIC_MODEL": "claude-opus-4.7-[1m]"
|
|
150
107
|
}
|
|
151
108
|
}
|
|
152
109
|
```
|
|
153
110
|
|
|
154
|
-
|
|
111
|
+
Slot-specific overrides:
|
|
155
112
|
|
|
156
113
|
```json
|
|
157
114
|
{
|
|
@@ -171,6 +128,44 @@ project-local `.claude/settings.json` and `.claude/settings.local.json` and
|
|
|
171
128
|
applied to Claude requests only when the model supports reasoning. If it is not
|
|
172
129
|
configured, Claude requests do not infer or attach a reasoning effort.
|
|
173
130
|
|
|
131
|
+
## Start flags
|
|
132
|
+
|
|
133
|
+
Common:
|
|
134
|
+
|
|
135
|
+
| Flag | Purpose |
|
|
136
|
+
| ---- | ------- |
|
|
137
|
+
| `--host <host>` | Bind address. Defaults to `127.0.0.1`. |
|
|
138
|
+
| `--port <port>` | Listen port. Overrides `$PORT` and the port inferred from Claude settings. |
|
|
139
|
+
| `--model <model>` | Override the request model for this bridge process only; does not edit config files. |
|
|
140
|
+
| `--rate-limit <seconds>` | Enforce a minimum delay between upstream requests. |
|
|
141
|
+
| `--wait` | With `--rate-limit`, wait instead of returning HTTP 429. |
|
|
142
|
+
|
|
143
|
+
Codex:
|
|
144
|
+
|
|
145
|
+
| Flag | Purpose |
|
|
146
|
+
| ---- | ------- |
|
|
147
|
+
| `--no-codex-setup` | Skip writing the managed block into `~/.codex/config.toml`. |
|
|
148
|
+
| `--no-prompt` | Never prompt for a Codex default model. |
|
|
149
|
+
|
|
150
|
+
Claude:
|
|
151
|
+
|
|
152
|
+
| Flag | Purpose |
|
|
153
|
+
| ---- | ------- |
|
|
154
|
+
| `--no-claude-setup` | Skip writing `ANTHROPIC_BASE_URL` into `~/.claude/settings.json`. |
|
|
155
|
+
|
|
156
|
+
Diagnostics:
|
|
157
|
+
|
|
158
|
+
| Flag | Purpose |
|
|
159
|
+
| ---- | ------- |
|
|
160
|
+
| `--debug` | Print extra upstream error diagnostics. |
|
|
161
|
+
| `--show-token` | Print GitHub and Copilot tokens during startup. Sensitive; use only for local debugging. |
|
|
162
|
+
|
|
163
|
+
`--debug` enables extra upstream error diagnostics in console logs.
|
|
164
|
+
- Includes: token limits, stream mode, tool count, invalid tool names, and suspicious tool-schema paths.
|
|
165
|
+
- Does not include: request messages, prompt text, bearer tokens, tool descriptions, or the full request body.
|
|
166
|
+
|
|
167
|
+
**Review or redact debug logs before sharing them publicly.**
|
|
168
|
+
|
|
174
169
|
## Environment overrides
|
|
175
170
|
|
|
176
171
|
| Variable | Purpose |
|
|
@@ -181,8 +176,6 @@ configured, Claude requests do not infer or attach a reasoning effort.
|
|
|
181
176
|
| `COPILOT_VSCODE_VERSION` | Override the VS Code version sent upstream. |
|
|
182
177
|
| `MODEL_REASONING_EFFORT` | Claude-side reasoning effort override. |
|
|
183
178
|
|
|
184
|
-
|
|
185
|
-
|
|
186
179
|
## Supported models
|
|
187
180
|
|
|
188
181
|
The bridge resolves aliases and clamps reasoning effort to what each model
|
|
@@ -265,6 +258,11 @@ Adding another CLI: drop a new translator under `src/bridges/<client>/`,
|
|
|
265
258
|
reuse `src/services/copilot/` for upstream calls, register routes in
|
|
266
259
|
`src/server.ts`, and add tests under `tests/`.
|
|
267
260
|
|
|
261
|
+
## Acknowledgements
|
|
262
|
+
|
|
263
|
+
Claude Code bridge notes inspired by
|
|
264
|
+
[ericc-ch/copilot-api](https://github.com/ericc-ch/copilot-api). Respect.
|
|
265
|
+
|
|
268
266
|
## License
|
|
269
267
|
|
|
270
268
|
MIT — see [LICENSE](./LICENSE).
|
package/dist/main.js
CHANGED
|
@@ -417,22 +417,6 @@ async function applyClaudeConfig(input) {
|
|
|
417
417
|
};
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
-
//#endregion
|
|
421
|
-
//#region src/lib/claude-launch.ts
|
|
422
|
-
const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
423
|
-
const pickClaudeLaunchDefaults = (availableModels, preferredModel) => {
|
|
424
|
-
return { model: preferredModel && availableModels.includes(preferredModel) ? preferredModel : availableModels.includes("gpt-5.3-codex") ? "gpt-5.3-codex" : availableModels[0] ?? "gpt-5.3-codex" };
|
|
425
|
-
};
|
|
426
|
-
const buildClaudeLaunchCommand = (input) => {
|
|
427
|
-
const env = {
|
|
428
|
-
ANTHROPIC_BASE_URL: input.baseUrl,
|
|
429
|
-
ANTHROPIC_AUTH_TOKEN: "dummy",
|
|
430
|
-
DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
|
|
431
|
-
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
432
|
-
};
|
|
433
|
-
return `${Object.entries(env).map(([key, value]) => `${key}=${shellQuote(value)}`).join(" ")} && claude --model ${shellQuote(input.model)}`;
|
|
434
|
-
};
|
|
435
|
-
|
|
436
420
|
//#endregion
|
|
437
421
|
//#region src/lib/codex-config.ts
|
|
438
422
|
const BEGIN_MARK = "# >>> copilot-bridge managed block — auto-generated, do not edit between markers >>>";
|
|
@@ -1353,7 +1337,11 @@ chatCompletionRoutes.post("/", async (c) => {
|
|
|
1353
1337
|
try {
|
|
1354
1338
|
await checkRateLimit();
|
|
1355
1339
|
const payload = await c.req.json();
|
|
1356
|
-
const
|
|
1340
|
+
const effectivePayload = runtimeState.modelOverride ? {
|
|
1341
|
+
...payload,
|
|
1342
|
+
model: runtimeState.modelOverride
|
|
1343
|
+
} : payload;
|
|
1344
|
+
const response = await createChatCompletions(c.var.config, effectivePayload, { client: "generic" });
|
|
1357
1345
|
if (isNonStreaming(response)) return c.json(response);
|
|
1358
1346
|
return streamSSE(c, async (stream) => {
|
|
1359
1347
|
for await (const chunk of response) await stream.writeSSE(chunk);
|
|
@@ -2197,9 +2185,13 @@ messageRoutes.post("/", async (c) => {
|
|
|
2197
2185
|
}
|
|
2198
2186
|
const anthropicPayload = await c.req.json();
|
|
2199
2187
|
const claudeSettings = await getClaudeSettings();
|
|
2200
|
-
const
|
|
2188
|
+
const effectivePayload = runtimeState.modelOverride ? {
|
|
2189
|
+
...anthropicPayload,
|
|
2190
|
+
model: runtimeState.modelOverride
|
|
2191
|
+
} : anthropicPayload;
|
|
2192
|
+
const upstreamModel = translateModelName(effectivePayload.model, claudeSettings);
|
|
2201
2193
|
const toolNameMapper = createAnthropicToolNameMapper(anthropicPayload.tools, { ...getToolNameMapperOptionsForModel(upstreamModel) });
|
|
2202
|
-
const openAIPayload = translateToOpenAI(
|
|
2194
|
+
const openAIPayload = translateToOpenAI(effectivePayload, claudeSettings, toolNameMapper);
|
|
2203
2195
|
try {
|
|
2204
2196
|
const response = await createChatCompletions(config, openAIPayload, { client: "claude" });
|
|
2205
2197
|
if (isNonStreamingResponse(response)) return c.json(translateToAnthropic(response, toolNameMapper));
|
|
@@ -2254,7 +2246,11 @@ messageRoutes.post("/count_tokens", async (c) => {
|
|
|
2254
2246
|
try {
|
|
2255
2247
|
const anthropicBeta = c.req.header("anthropic-beta");
|
|
2256
2248
|
const anthropicPayload = await c.req.json();
|
|
2257
|
-
const
|
|
2249
|
+
const claudeSettings = await getClaudeSettings();
|
|
2250
|
+
const openAIPayload = translateToOpenAI(runtimeState.modelOverride ? {
|
|
2251
|
+
...anthropicPayload,
|
|
2252
|
+
model: runtimeState.modelOverride
|
|
2253
|
+
} : anthropicPayload, claudeSettings);
|
|
2258
2254
|
const selectedModel = resolveModel(openAIPayload.model);
|
|
2259
2255
|
if (!selectedModel) {
|
|
2260
2256
|
consola.warn(`Model ${openAIPayload.model} not found in registry, returning fallback token count`);
|
|
@@ -2718,7 +2714,11 @@ responsesRoutes.post("/", async (c) => {
|
|
|
2718
2714
|
if (error instanceof RateLimitError) return c.json({ error: { message: error.message } }, 429);
|
|
2719
2715
|
throw error;
|
|
2720
2716
|
}
|
|
2721
|
-
const
|
|
2717
|
+
const rawPayload = await c.req.json();
|
|
2718
|
+
const payload = normalizeCodexResponsesRequest(runtimeState.modelOverride ? {
|
|
2719
|
+
...rawPayload,
|
|
2720
|
+
model: runtimeState.modelOverride
|
|
2721
|
+
} : rawPayload, await readConfiguredCodexReasoningEffort());
|
|
2722
2722
|
const provider = getCopilotProviderContext(c.get("config"));
|
|
2723
2723
|
const search = new URL(c.req.url).search;
|
|
2724
2724
|
const capability = getModelCapability(payload.model);
|
|
@@ -2920,15 +2920,9 @@ const start = defineCommand({
|
|
|
2920
2920
|
default: true,
|
|
2921
2921
|
description: "Skip writing ANTHROPIC_BASE_URL into ~/.claude/settings.json."
|
|
2922
2922
|
},
|
|
2923
|
-
|
|
2924
|
-
type: "
|
|
2925
|
-
|
|
2926
|
-
description: "Print a one-shot `export ... && claude` command and do not modify user config files."
|
|
2927
|
-
},
|
|
2928
|
-
"select-model": {
|
|
2929
|
-
type: "boolean",
|
|
2930
|
-
default: false,
|
|
2931
|
-
description: "Force the model picker even when ~/.codex/config.toml already has a model."
|
|
2923
|
+
model: {
|
|
2924
|
+
type: "string",
|
|
2925
|
+
description: "Override the request model for this bridge process only."
|
|
2932
2926
|
},
|
|
2933
2927
|
prompt: {
|
|
2934
2928
|
type: "boolean",
|
|
@@ -2973,9 +2967,12 @@ const start = defineCommand({
|
|
|
2973
2967
|
consola.info(`copilot base url: ${config.copilotBaseUrl}`);
|
|
2974
2968
|
const baseUrl = `http://${config.host}:${config.port}`;
|
|
2975
2969
|
const codexConfigPath = CODEX_DEFAULTS.configPath;
|
|
2976
|
-
const
|
|
2977
|
-
if (
|
|
2978
|
-
|
|
2970
|
+
const requestedModel = args.model !== void 0 ? String(args.model) : void 0;
|
|
2971
|
+
if (requestedModel) {
|
|
2972
|
+
runtimeState.modelOverride = requestedModel;
|
|
2973
|
+
consola.info(`Runtime model override: ${requestedModel}`);
|
|
2974
|
+
} else delete runtimeState.modelOverride;
|
|
2975
|
+
if (args["claude-setup"]) try {
|
|
2979
2976
|
const claudeResult = await applyClaudeConfig({
|
|
2980
2977
|
baseUrl,
|
|
2981
2978
|
configPath: claudeConfigPath
|
|
@@ -2996,7 +2993,7 @@ const start = defineCommand({
|
|
|
2996
2993
|
else if (capability?.reasoning) consola.info(`codex model_reasoning_effort not set; leaving reasoning effort unset for ${chosenModel}`);
|
|
2997
2994
|
else consola.info(`codex model_reasoning_effort not set; leaving reasoning effort unset for ${chosenModel}`);
|
|
2998
2995
|
}
|
|
2999
|
-
if (
|
|
2996
|
+
if (args["codex-setup"]) try {
|
|
3000
2997
|
const result = await applyCodexConfig({
|
|
3001
2998
|
baseUrl: `${baseUrl}/v1`,
|
|
3002
2999
|
configPath: codexConfigPath,
|
|
@@ -3026,7 +3023,7 @@ const start = defineCommand({
|
|
|
3026
3023
|
``,
|
|
3027
3024
|
` https://betahi.github.io/copilot-bridge?endpoint=${baseUrl}/usage`
|
|
3028
3025
|
].join("\n"));
|
|
3029
|
-
if (
|
|
3026
|
+
if (args.prompt && finalPickable.length > 0 && !chosenModel && !requestedModel) {
|
|
3030
3027
|
const defaultId = chosenModel && finalPickable.includes(chosenModel) ? chosenModel : finalPickable.includes("gpt-5.3-codex") ? "gpt-5.3-codex" : finalPickable[0];
|
|
3031
3028
|
const selected = await consola.prompt(`Select a model for codex (writes to ${codexConfigPath})`, {
|
|
3032
3029
|
type: "select",
|
|
@@ -3051,26 +3048,6 @@ const start = defineCommand({
|
|
|
3051
3048
|
}
|
|
3052
3049
|
}
|
|
3053
3050
|
}
|
|
3054
|
-
if (isClaudeCodeMode) {
|
|
3055
|
-
const defaults = pickClaudeLaunchDefaults(finalPickable, chosenModel);
|
|
3056
|
-
let selectedModel = defaults.model;
|
|
3057
|
-
if (args.prompt && finalPickable.length > 0) selectedModel = await consola.prompt("Select a model to use with Claude Code", {
|
|
3058
|
-
type: "select",
|
|
3059
|
-
options: finalPickable,
|
|
3060
|
-
initial: defaults.model
|
|
3061
|
-
});
|
|
3062
|
-
const command = buildClaudeLaunchCommand({
|
|
3063
|
-
baseUrl,
|
|
3064
|
-
model: selectedModel
|
|
3065
|
-
});
|
|
3066
|
-
consola.box([
|
|
3067
|
-
"Claude one-shot command",
|
|
3068
|
-
"",
|
|
3069
|
-
` ${command}`,
|
|
3070
|
-
"",
|
|
3071
|
-
" The bridge verified this path with explicit --model; current Claude CLI did not reliably honor ANTHROPIC_MODEL env defaults."
|
|
3072
|
-
].join("\n"));
|
|
3073
|
-
}
|
|
3074
3051
|
await new Promise((resolve, reject) => {
|
|
3075
3052
|
server.on("close", resolve);
|
|
3076
3053
|
server.on("error", reject);
|