ghc-proxy 0.9.1 → 0.9.2
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 +568 -529
- package/dist/main.mjs +2052 -773
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,529 +1,568 @@
|
|
|
1
|
-
# ghc-proxy
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/ghc-proxy)
|
|
4
|
-
[](https://github.com/wxxb789/ghc-proxy/actions/workflows/ci.yml)
|
|
5
|
-
[](https://github.com/wxxb789/ghc-proxy/blob/master/LICENSE)
|
|
6
|
-
|
|
7
|
-
A proxy that turns your GitHub Copilot subscription into an OpenAI and Anthropic compatible API. Use it to power [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview), [Cursor](https://www.cursor.com/), or any tool that speaks the OpenAI Chat Completions, OpenAI Responses, or Anthropic Messages protocol.
|
|
8
|
-
|
|
9
|
-
> [!WARNING]
|
|
10
|
-
> Reverse-engineered, unofficial, may break at any time. Excessive use can trigger GitHub abuse detection. **Use at your own risk.**
|
|
11
|
-
|
|
12
|
-
**TL;DR** — Install [Bun](https://bun.com/docs/installation), then run:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
bunx ghc-proxy@latest start
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Prerequisites
|
|
19
|
-
|
|
20
|
-
Before you start, make sure you have:
|
|
21
|
-
|
|
22
|
-
1. **Bun** (>= 1.3) -- a fast JavaScript runtime used to run the proxy
|
|
23
|
-
- **Windows:** `winget install --id Oven-sh.Bun`
|
|
24
|
-
- **Other platforms:** see the [official installation guide](https://bun.com/docs/installation)
|
|
25
|
-
2. **A GitHub Copilot subscription** -- individual, business, or enterprise
|
|
26
|
-
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
|
-
1. Start the proxy:
|
|
30
|
-
|
|
31
|
-
bunx ghc-proxy@latest start
|
|
32
|
-
|
|
33
|
-
2. On the first run, you will be guided through GitHub's device-code authentication flow. Follow the prompts to authorize the proxy.
|
|
34
|
-
|
|
35
|
-
3. Once authenticated, the proxy starts on **`http://localhost:4141`** and is ready to accept requests.
|
|
36
|
-
|
|
37
|
-
That's it. Any tool that supports the OpenAI or Anthropic API can now point to `http://localhost:4141`.
|
|
38
|
-
|
|
39
|
-
> **Tip:** If you set `--rate-limit`, add `--wait` to queue requests instead of rejecting them with 429 when the cooldown has not elapsed yet. See [Rate Limiting](#rate-limiting) for details.
|
|
40
|
-
|
|
41
|
-
## Using with Claude Code
|
|
42
|
-
|
|
43
|
-
This is the most common use case. There are two ways to set it up:
|
|
44
|
-
|
|
45
|
-
### Option A: One-command launch
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
bunx ghc-proxy@latest start --claude-code
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
This starts the proxy, opens an interactive model picker, and prints a ready-to-paste environment command. Run that command in another terminal to launch Claude Code with the correct configuration.
|
|
52
|
-
|
|
53
|
-
### Option B: Permanent config (Recommended)
|
|
54
|
-
|
|
55
|
-
Create or edit `~/.claude/settings.json` (this applies globally to all projects):
|
|
56
|
-
|
|
57
|
-
```json
|
|
58
|
-
{
|
|
59
|
-
"env": {
|
|
60
|
-
"ANTHROPIC_BASE_URL": "http://localhost:4141",
|
|
61
|
-
"ANTHROPIC_AUTH_TOKEN": "dummy-token",
|
|
62
|
-
"ANTHROPIC_MODEL": "claude-opus-5",
|
|
63
|
-
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-5",
|
|
64
|
-
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4.5",
|
|
65
|
-
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
|
|
66
|
-
},
|
|
67
|
-
"permissions": {
|
|
68
|
-
"deny": ["WebSearch"]
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Then simply start the proxy and use Claude Code as usual:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
bunx ghc-proxy@latest start
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**What each environment variable does:**
|
|
80
|
-
|
|
81
|
-
| Variable | Purpose |
|
|
82
|
-
|----------|---------|
|
|
83
|
-
| `ANTHROPIC_BASE_URL` | Points Claude Code to the proxy instead of Anthropic's servers |
|
|
84
|
-
| `ANTHROPIC_AUTH_TOKEN` | Any non-empty string; the proxy handles real authentication |
|
|
85
|
-
| `ANTHROPIC_MODEL` | The model Claude Code uses for primary/Opus tasks |
|
|
86
|
-
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | The model used for Sonnet-tier tasks |
|
|
87
|
-
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | The model used for Haiku-tier (fast/cheap) tasks |
|
|
88
|
-
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | Disables telemetry and non-essential network traffic |
|
|
89
|
-
|
|
90
|
-
> **Tip:** The model names above (e.g. `claude-opus-5`) are mapped to actual Copilot models by the proxy. See [Model Mapping](#model-mapping) below for details.
|
|
91
|
-
|
|
92
|
-
See the [Claude Code settings docs](https://docs.anthropic.com/en/docs/claude-code/settings#environment-variables) for more options.
|
|
93
|
-
|
|
94
|
-
## CLI Reference
|
|
95
|
-
|
|
96
|
-
ghc-proxy uses a subcommand structure:
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
bunx ghc-proxy@latest start # Start the proxy server
|
|
100
|
-
bunx ghc-proxy@latest auth # Run GitHub auth flow without starting the server
|
|
101
|
-
bunx ghc-proxy@latest check-usage # Show your Copilot usage/quota in the terminal
|
|
102
|
-
bunx ghc-proxy@latest debug # Print diagnostic info (version, paths, token status)
|
|
103
|
-
bunx ghc-proxy@latest selfcheck # Probe
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### `start` Options
|
|
107
|
-
|
|
108
|
-
| Option | Alias | Default | Description |
|
|
109
|
-
|--------|-------|---------|-------------|
|
|
110
|
-
| `--port` | `-p` | `4141` | Port to listen on |
|
|
111
|
-
| `--verbose` | `-v` | `false` | Enable verbose logging |
|
|
112
|
-
| `--account-type` | `-a` | `individual` | `individual`, `business`, or `enterprise` |
|
|
113
|
-
| `--rate-limit` | `-r` | -- | Minimum seconds between requests |
|
|
114
|
-
| `--wait` | `-w` | `false` | Queue requests instead of rejecting with 429 when `--rate-limit` cooldown has not elapsed (requires `--rate-limit`) |
|
|
115
|
-
| `--manual` | -- | `false` | Manually approve each request |
|
|
116
|
-
| `--github-token` | `-g` | -- | Pass a GitHub token directly (from `auth`) |
|
|
117
|
-
| `--claude-code` | `-c` | `false` | Generate a Claude Code launch command |
|
|
118
|
-
| `--show-token` | -- | `false` | Display tokens on auth and refresh |
|
|
119
|
-
| `--dump-failed-payloads` | `-D` | `false` | Dump failed `/responses` payloads on upstream 400 errors for debugging. Can also be enabled with `DUMP_FAILED_PAYLOADS=1`. |
|
|
120
|
-
| `--proxy-env` | -- | `false` | Use `HTTP_PROXY`/`HTTPS_PROXY` from env (Node.js only; Bun reads proxy env natively) |
|
|
121
|
-
| `--idle-timeout` | -- | `120` | Bun server idle timeout in seconds (`0` disables; Bun max is `255`; streaming routes disable idle timeout automatically) |
|
|
122
|
-
| `--upstream-timeout` | -- | `1800` | Upstream request timeout in seconds (`0` disables). Enforced as a total-duration `AbortSignal`. Note both runtimes also apply their own ~300s **idle** timeout to `fetch` (Bun's built-in limit; Node's undici `headersTimeout`/`bodyTimeout`), which fires when no byte arrives for that long — a steadily streaming response is not capped by it, but a stalled one is rejected at ~300s and returned as a `504`. |
|
|
123
|
-
| `--upstream-queue-concurrency` | -- | `10` | Maximum concurrent Copilot upstream requests |
|
|
124
|
-
| `--upstream-queue-retries` | -- | `
|
|
125
|
-
| `--upstream-
|
|
126
|
-
| `--upstream-queue-
|
|
127
|
-
| `--
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
bunx ghc-proxy@latest start --account-type
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `modelFallback
|
|
195
|
-
| `modelFallback.
|
|
196
|
-
| `modelFallback.
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"
|
|
222
|
-
"
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
`/v1/messages
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
-
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
bun
|
|
520
|
-
bun
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
#
|
|
528
|
-
|
|
529
|
-
|
|
1
|
+
# ghc-proxy
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/ghc-proxy)
|
|
4
|
+
[](https://github.com/wxxb789/ghc-proxy/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/wxxb789/ghc-proxy/blob/master/LICENSE)
|
|
6
|
+
|
|
7
|
+
A proxy that turns your GitHub Copilot subscription into an OpenAI and Anthropic compatible API. Use it to power [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview), [Cursor](https://www.cursor.com/), or any tool that speaks the OpenAI Chat Completions, OpenAI Responses, or Anthropic Messages protocol.
|
|
8
|
+
|
|
9
|
+
> [!WARNING]
|
|
10
|
+
> Reverse-engineered, unofficial, may break at any time. Excessive use can trigger GitHub abuse detection. **Use at your own risk.**
|
|
11
|
+
|
|
12
|
+
**TL;DR** — Install [Bun](https://bun.com/docs/installation), then run:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bunx ghc-proxy@latest start
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
Before you start, make sure you have:
|
|
21
|
+
|
|
22
|
+
1. **Bun** (>= 1.3) -- a fast JavaScript runtime used to run the proxy
|
|
23
|
+
- **Windows:** `winget install --id Oven-sh.Bun`
|
|
24
|
+
- **Other platforms:** see the [official installation guide](https://bun.com/docs/installation)
|
|
25
|
+
2. **A GitHub Copilot subscription** -- individual, business, or enterprise
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
1. Start the proxy:
|
|
30
|
+
|
|
31
|
+
bunx ghc-proxy@latest start
|
|
32
|
+
|
|
33
|
+
2. On the first run, you will be guided through GitHub's device-code authentication flow. Follow the prompts to authorize the proxy.
|
|
34
|
+
|
|
35
|
+
3. Once authenticated, the proxy starts on **`http://localhost:4141`** and is ready to accept requests.
|
|
36
|
+
|
|
37
|
+
That's it. Any tool that supports the OpenAI or Anthropic API can now point to `http://localhost:4141`.
|
|
38
|
+
|
|
39
|
+
> **Tip:** If you set `--rate-limit`, add `--wait` to queue requests instead of rejecting them with 429 when the cooldown has not elapsed yet. See [Rate Limiting](#rate-limiting) for details.
|
|
40
|
+
|
|
41
|
+
## Using with Claude Code
|
|
42
|
+
|
|
43
|
+
This is the most common use case. There are two ways to set it up:
|
|
44
|
+
|
|
45
|
+
### Option A: One-command launch
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bunx ghc-proxy@latest start --claude-code
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This starts the proxy, opens an interactive model picker, and prints a ready-to-paste environment command. Run that command in another terminal to launch Claude Code with the correct configuration.
|
|
52
|
+
|
|
53
|
+
### Option B: Permanent config (Recommended)
|
|
54
|
+
|
|
55
|
+
Create or edit `~/.claude/settings.json` (this applies globally to all projects):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"env": {
|
|
60
|
+
"ANTHROPIC_BASE_URL": "http://localhost:4141",
|
|
61
|
+
"ANTHROPIC_AUTH_TOKEN": "dummy-token",
|
|
62
|
+
"ANTHROPIC_MODEL": "claude-opus-5",
|
|
63
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-5",
|
|
64
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4.5",
|
|
65
|
+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
|
|
66
|
+
},
|
|
67
|
+
"permissions": {
|
|
68
|
+
"deny": ["WebSearch"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Then simply start the proxy and use Claude Code as usual:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bunx ghc-proxy@latest start
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**What each environment variable does:**
|
|
80
|
+
|
|
81
|
+
| Variable | Purpose |
|
|
82
|
+
|----------|---------|
|
|
83
|
+
| `ANTHROPIC_BASE_URL` | Points Claude Code to the proxy instead of Anthropic's servers |
|
|
84
|
+
| `ANTHROPIC_AUTH_TOKEN` | Any non-empty string; the proxy handles real authentication |
|
|
85
|
+
| `ANTHROPIC_MODEL` | The model Claude Code uses for primary/Opus tasks |
|
|
86
|
+
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | The model used for Sonnet-tier tasks |
|
|
87
|
+
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | The model used for Haiku-tier (fast/cheap) tasks |
|
|
88
|
+
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | Disables telemetry and non-essential network traffic |
|
|
89
|
+
|
|
90
|
+
> **Tip:** The model names above (e.g. `claude-opus-5`) are mapped to actual Copilot models by the proxy. See [Model Mapping](#model-mapping) below for details.
|
|
91
|
+
|
|
92
|
+
See the [Claude Code settings docs](https://docs.anthropic.com/en/docs/claude-code/settings#environment-variables) for more options.
|
|
93
|
+
|
|
94
|
+
## CLI Reference
|
|
95
|
+
|
|
96
|
+
ghc-proxy uses a subcommand structure:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bunx ghc-proxy@latest start # Start the proxy server
|
|
100
|
+
bunx ghc-proxy@latest auth # Run GitHub auth flow without starting the server
|
|
101
|
+
bunx ghc-proxy@latest check-usage # Show your Copilot usage/quota in the terminal
|
|
102
|
+
bunx ghc-proxy@latest debug # Print diagnostic info (version, paths, token status)
|
|
103
|
+
bunx ghc-proxy@latest selfcheck # Probe tokenizer chunks and Bun/Node runtime contracts in the packaged bundle
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `start` Options
|
|
107
|
+
|
|
108
|
+
| Option | Alias | Default | Description |
|
|
109
|
+
|--------|-------|---------|-------------|
|
|
110
|
+
| `--port` | `-p` | `4141` | Port to listen on |
|
|
111
|
+
| `--verbose` | `-v` | `false` | Enable verbose logging |
|
|
112
|
+
| `--account-type` | `-a` | `individual` | `individual`, `business`, or `enterprise` |
|
|
113
|
+
| `--rate-limit` | `-r` | -- | Minimum seconds between requests |
|
|
114
|
+
| `--wait` | `-w` | `false` | Queue requests instead of rejecting with 429 when `--rate-limit` cooldown has not elapsed (requires `--rate-limit`) |
|
|
115
|
+
| `--manual` | -- | `false` | Manually approve each request |
|
|
116
|
+
| `--github-token` | `-g` | -- | Pass a GitHub token directly (from `auth`) |
|
|
117
|
+
| `--claude-code` | `-c` | `false` | Generate a Claude Code launch command |
|
|
118
|
+
| `--show-token` | -- | `false` | Display tokens on auth and refresh |
|
|
119
|
+
| `--dump-failed-payloads` | `-D` | `false` | Dump failed `/responses` payloads on upstream 400 errors for debugging. Can also be enabled with `DUMP_FAILED_PAYLOADS=1`. |
|
|
120
|
+
| `--proxy-env` | -- | `false` | Use `HTTP_PROXY`/`HTTPS_PROXY` from env (Node.js only; Bun reads proxy env natively) |
|
|
121
|
+
| `--idle-timeout` | -- | `120` | Bun server idle timeout in seconds (`0` disables; Bun max is `255`; streaming routes disable idle timeout automatically) |
|
|
122
|
+
| `--upstream-timeout` | -- | `1800` | Upstream request timeout in seconds (`0` disables). Enforced as a total-duration `AbortSignal`. Note both runtimes also apply their own ~300s **idle** timeout to `fetch` (Bun's built-in limit; Node's undici `headersTimeout`/`bodyTimeout`), which fires when no byte arrives for that long — a steadily streaming response is not capped by it, but a stalled one is rejected at ~300s and returned as a `504`. |
|
|
123
|
+
| `--upstream-queue-concurrency` | -- | `10` | Maximum concurrent Copilot upstream requests |
|
|
124
|
+
| `--upstream-queue-retries` | -- | `1` | Maximum retries across capacity and approved pre-connection failures (`0..2`). Generation requests retry HTTP `429`/`529`; effect-free requests retain the broader transient-status policy |
|
|
125
|
+
| `--upstream-recovery-budget` | -- | `60` | Seconds available after the first approved retryable outcome or active-cooldown encounter for all later waits and pre-`Response` attempts (`1..120`) |
|
|
126
|
+
| `--upstream-queue-base-delay` | -- | `2` | Base delay in seconds for upstream retry backoff when `Retry-After` is absent |
|
|
127
|
+
| `--upstream-queue-max-delay` | -- | `60` | Maximum computed backoff in seconds; never shortens a valid `Retry-After` minimum |
|
|
128
|
+
| `--ghe-domain` | `--ghe` | -- | GitHub Enterprise Cloud company domain (e.g. `company.ghe.com`). Required for GHE.com device login on first run; persisted automatically for later runs. |
|
|
129
|
+
|
|
130
|
+
## Rate Limiting
|
|
131
|
+
|
|
132
|
+
If you want to throttle how often the proxy forwards requests:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Enforce a 30-second cooldown between requests
|
|
136
|
+
bunx ghc-proxy@latest start --rate-limit 30
|
|
137
|
+
|
|
138
|
+
# Same, but queue requests instead of returning 429
|
|
139
|
+
bunx ghc-proxy@latest start --rate-limit 30 --wait
|
|
140
|
+
|
|
141
|
+
# Manually approve every request (useful for debugging)
|
|
142
|
+
bunx ghc-proxy@latest start --manual
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`--wait` only takes effect when `--rate-limit` is also set. Without `--rate-limit`, there is no cooldown to wait on and `--wait` has no effect.
|
|
146
|
+
|
|
147
|
+
## Account Types
|
|
148
|
+
|
|
149
|
+
If you have a GitHub Business or Enterprise Copilot plan, pass `--account-type`:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
bunx ghc-proxy@latest start --account-type business
|
|
153
|
+
bunx ghc-proxy@latest start --account-type enterprise
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This routes requests to the correct Copilot API endpoint for your plan. See the [GitHub docs on network routing](https://docs.github.com/en/enterprise-cloud@latest/copilot/managing-copilot/managing-github-copilot-in-your-organization/managing-access-to-github-copilot-in-your-organization/managing-github-copilot-access-to-your-organizations-network#configuring-copilot-subscription-based-network-routing-for-your-enterprise-or-organization) for details.
|
|
157
|
+
|
|
158
|
+
### GitHub Enterprise Cloud (GHE.com)
|
|
159
|
+
|
|
160
|
+
If your organization uses GitHub Enterprise Cloud (`*.ghe.com`), the standard GitHub device login URL differs from `github.com`. Pass your company's GHE domain on first auth:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
bunx ghc-proxy@latest start --account-type enterprise --ghe-domain company.ghe.com
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Or authenticate first, then start without the flag on subsequent runs:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# First run (authenticates and persists the domain)
|
|
170
|
+
bunx ghc-proxy@latest auth --ghe-domain company.ghe.com
|
|
171
|
+
|
|
172
|
+
# Later runs (domain is read from persisted config)
|
|
173
|
+
bunx ghc-proxy@latest start --account-type enterprise
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The proxy normalizes and persists the GHE domain automatically after a successful authentication, so you only need to pass `--ghe-domain` on the first run or when switching tenants.
|
|
177
|
+
|
|
178
|
+
> **Note:** `--account-type enterprise` alone is not sufficient for GHE.com login — the proxy needs the company domain to construct the correct device login URL (`https://<company>.ghe.com/login/device`). GHE.com support is scoped to `*.ghe.com` only and does not apply to self-hosted GitHub Enterprise Server instances.
|
|
179
|
+
|
|
180
|
+
## Configuration
|
|
181
|
+
|
|
182
|
+
The proxy reads an optional JSON config file at:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
~/.local/share/ghc-proxy/config.json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
All fields are optional. The full schema:
|
|
189
|
+
|
|
190
|
+
| Field | Type | Default | Description |
|
|
191
|
+
|-------|------|---------|-------------|
|
|
192
|
+
| `githubToken` | `string` | -- | Persisted GitHub token. Normally written automatically by `auth` / `--github-token`; you rarely set this by hand |
|
|
193
|
+
| `modelRewrites` | `{ from, to }[]` | -- | Glob-pattern model substitution rules (see [Model Rewrites](#model-rewrites)) |
|
|
194
|
+
| `modelFallback` | `object` | -- | Override default model fallbacks (see [Customizing Fallbacks](#customizing-fallbacks)) |
|
|
195
|
+
| `modelFallback.claudeOpus` | `string` | `claude-opus-5` | Fallback for `claude-opus-*` models |
|
|
196
|
+
| `modelFallback.claudeSonnet` | `string` | `claude-sonnet-5` | Fallback for `claude-sonnet-*` models |
|
|
197
|
+
| `modelFallback.claudeHaiku` | `string` | `claude-haiku-4.5` | Fallback for `claude-haiku-*` models |
|
|
198
|
+
| `smallModel` | `string` | -- | Target model for compact request routing (see [Small-Model Routing](#small-model-routing)) |
|
|
199
|
+
| `compactUseSmallModel` | `boolean` | `false` | Route compact/summarization requests to `smallModel` |
|
|
200
|
+
| `useFunctionApplyPatch` | `boolean` | `true` | Rewrite `apply_patch` custom tool as function tool on Responses path |
|
|
201
|
+
| `responsesApiAutoCompactInput` | `boolean` | `false` | Automatically trim Responses `input` to the latest `compaction` item |
|
|
202
|
+
| `responsesApiAutoContextManagement` | `boolean` | `false` | Automatically inject Responses `context_management` for selected models |
|
|
203
|
+
| `responsesApiContextManagementModels` | `string[]` | -- | Models eligible for auto-injected Responses `context_management` |
|
|
204
|
+
| `responsesApiParameterFilters` | `{ models, params }[]` | -- | Extra rules to strip request parameters on the Responses boundary (see [Responses Parameter Filters](#responses-parameter-filters)) |
|
|
205
|
+
| `responsesApiParameterFiltersReplaceDefault` | `boolean` | `false` | Disable the built-in reasoning-model default rule so only your `responsesApiParameterFilters` apply |
|
|
206
|
+
| `responsesOfficialEmulator` | `boolean` | `false` | Enable local OpenAI-style Responses state emulation for `previous_response_id`, `conversation`, retrieve, input_items, delete, and input_tokens |
|
|
207
|
+
| `responsesOfficialEmulatorTtlSeconds` | `number` | `14400` | In-memory TTL for locally emulated Responses state |
|
|
208
|
+
| `modelReasoningEfforts` | `Record<string, string>` | -- | Per-model reasoning effort defaults for Anthropic-to-Responses translation. Each value must be one of `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max` (ascending) |
|
|
209
|
+
| `upstreamQueueConcurrency` | `number` | `10` | Maximum concurrent Copilot upstream requests |
|
|
210
|
+
| `upstreamQueueMaxRetries` | `number` | `1` | Maximum retries across capacity and approved pre-connection failures (`0..2`) |
|
|
211
|
+
| `upstreamRecoveryBudgetSeconds` | `number` | `60` | Shared recovery deadline after the first retryable outcome or active-cooldown encounter (`1..120` seconds) |
|
|
212
|
+
| `overloadFallbacks` | `Record<string, string>` | -- | Exact effective-model mappings for one opt-in fallback dispatch after terminal model `529`; absent means disabled |
|
|
213
|
+
| `upstreamQueueBaseDelaySeconds` | `number` | `2` | Base delay (seconds) for upstream retry backoff when `Retry-After` is absent |
|
|
214
|
+
| `upstreamQueueMaxDelaySeconds` | `number` | `60` | Maximum computed backoff (seconds); does not clamp `Retry-After` |
|
|
215
|
+
| `gheDomain` | `string` | -- | GitHub Enterprise Cloud company domain (persisted automatically after GHE.com auth) |
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"modelRewrites": [
|
|
222
|
+
{ "from": "claude-haiku-*", "to": "gpt-4.1-mini" }
|
|
223
|
+
],
|
|
224
|
+
"modelFallback": {
|
|
225
|
+
"claudeOpus": "claude-opus-5",
|
|
226
|
+
"claudeSonnet": "claude-sonnet-5"
|
|
227
|
+
},
|
|
228
|
+
"smallModel": "gpt-4.1-mini",
|
|
229
|
+
"compactUseSmallModel": true,
|
|
230
|
+
"useFunctionApplyPatch": true,
|
|
231
|
+
"responsesApiAutoCompactInput": false,
|
|
232
|
+
"responsesApiAutoContextManagement": false,
|
|
233
|
+
"responsesApiContextManagementModels": ["gpt-5", "gpt-5-mini"],
|
|
234
|
+
"responsesOfficialEmulator": false,
|
|
235
|
+
"responsesOfficialEmulatorTtlSeconds": 14400,
|
|
236
|
+
"modelReasoningEfforts": {
|
|
237
|
+
"gpt-5": "high",
|
|
238
|
+
"gpt-5-mini": "medium"
|
|
239
|
+
},
|
|
240
|
+
"overloadFallbacks": {
|
|
241
|
+
"claude-opus-5": "claude-opus-4.8"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Priority order** for model fallbacks: environment variable > config.json > built-in default.
|
|
247
|
+
|
|
248
|
+
## Model Mapping
|
|
249
|
+
|
|
250
|
+
When Claude Code sends a request for a model like `claude-sonnet-4.6`, the proxy maps it to an actual model available on Copilot. The mapping logic works as follows:
|
|
251
|
+
|
|
252
|
+
1. If the requested model ID is known to Copilot (e.g. `gpt-4.1`, `claude-sonnet-4.5`), it is used as-is.
|
|
253
|
+
2. If the model starts with `claude-opus-`, `claude-sonnet-`, or `claude-haiku-`, it falls back to a configured model.
|
|
254
|
+
|
|
255
|
+
### Default Fallbacks
|
|
256
|
+
|
|
257
|
+
| Prefix | Default Fallback |
|
|
258
|
+
|--------|-----------------|
|
|
259
|
+
| `claude-opus-*` | `claude-opus-5` |
|
|
260
|
+
| `claude-sonnet-*` | `claude-sonnet-5` |
|
|
261
|
+
| `claude-haiku-*` | `claude-haiku-4.5` |
|
|
262
|
+
|
|
263
|
+
### Customizing Fallbacks
|
|
264
|
+
|
|
265
|
+
You can override the defaults with **environment variables**:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
MODEL_FALLBACK_CLAUDE_OPUS=claude-opus-5
|
|
269
|
+
MODEL_FALLBACK_CLAUDE_SONNET=claude-sonnet-5
|
|
270
|
+
MODEL_FALLBACK_CLAUDE_HAIKU=claude-haiku-4.5
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Or in the proxy's **config file** (`~/.local/share/ghc-proxy/config.json`):
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"modelFallback": {
|
|
278
|
+
"claudeOpus": "claude-opus-5",
|
|
279
|
+
"claudeSonnet": "claude-sonnet-5",
|
|
280
|
+
"claudeHaiku": "claude-haiku-4.5"
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
> **Note:** Model fallbacks only apply to the **chat completions translation path**. The native Messages and Responses API strategies pass the model ID through to Copilot as-is.
|
|
286
|
+
|
|
287
|
+
### Model Rewrites
|
|
288
|
+
|
|
289
|
+
For more general model substitution, use `modelRewrites` in the config file. Each rule maps a `from` pattern to a `to` model ID. The `from` field supports glob patterns with `*` wildcards, and the first matching rule wins.
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"modelRewrites": [
|
|
294
|
+
{ "from": "claude-haiku-*", "to": "gpt-4.1-mini" },
|
|
295
|
+
{ "from": "gpt-5.4*", "to": "gpt-5.2" }
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Unlike model fallbacks (which only apply to the chat completions path), rewrites are applied **uniformly to all three endpoints** — `/v1/messages`, `/v1/chat/completions`, and `/v1/responses`. Target model names are normalized against Copilot's known model list using dash/dot equivalence (e.g. `gpt-4.1` matches `gpt-4-1`).
|
|
301
|
+
|
|
302
|
+
Rewrites run **before** any other model policy — small-model routing and strategy selection all see the rewritten model.
|
|
303
|
+
|
|
304
|
+
### Overload Fallbacks
|
|
305
|
+
|
|
306
|
+
`overloadFallbacks` is a separate, opt-in recovery policy. Each key and value is an exact advertised model ID after normal rewrite/compact resolution:
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"overloadFallbacks": {
|
|
311
|
+
"claude-opus-5": "claude-opus-4.8"
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Fallback is considered only after a terminal source-model `529` or a pre-existing local cooldown for that source. It never runs for account `429`, connection failures, timeouts, cancellation, validation failures, other statuses, or failures after an upstream `Response` exists. The target must be distinct, advertised, not locally cooled, and compatible with the request's endpoint, tools, parallel tools, streaming, vision, reasoning/thinking, and structured-output needs. The pipeline rebuilds target-dependent transforms and strategy selection from pristine input, dispatches once with no fresh retry allowance, and reports the actual served target in response model fields and the `OVERLOAD_FALLBACK` model trace.
|
|
317
|
+
|
|
318
|
+
Mappings are exact one-hop choices, not a traversed graph. Blank, same-model, and reciprocal two-node entries such as `A -> B` plus `B -> A` are ignored with a configuration warning. An unknown or incompatible runtime target preserves the source `529`.
|
|
319
|
+
|
|
320
|
+
## Upstream Capacity Recovery
|
|
321
|
+
|
|
322
|
+
An upstream `429` establishes an account cooldown. A `529` is scoped to the final effective upstream model; if no effective model is known, it remains request-only. Eligible models can bypass cooled waiters while a global slot is free, but active slots and the maximum pending depth remain process-global limits.
|
|
323
|
+
|
|
324
|
+
The first attempt keeps the normal upstream timeout. The recovery budget starts at the first approved retryable outcome or the first encounter with an already-active cooldown, then covers every later cooldown/backoff wait, queue acquisition, same-model attempt until `Response`, and overload fallback. A valid integer-seconds, HTTP-date, or full-string fractional-seconds `Retry-After` is a strict lower bound and installs its full cooldown deadline. If that minimum cannot fit, the proxy skips the same-model retry instead of shortening it. Without a valid header, full jitter is sampled from zero through the smallest of exponential backoff, `upstreamQueueMaxDelaySeconds`, and remaining budget.
|
|
325
|
+
|
|
326
|
+
Generation requests additionally retry only measured pre-connection shapes: Bun `ConnectionRefused`, or Node `ECONNREFUSED`, `ENOTFOUND`, and `EAI_AGAIN`. Caller aborts, every timeout, TLS/configuration failures, resets, generic fetch errors, body failures, and stream failures are excluded. Once `fetch()` returns a `Response`, that attempt is committed: a later JSON/SSE/body failure is never replayed or sent to fallback.
|
|
327
|
+
|
|
328
|
+
Recovery logs use the existing request ID and structured fields such as event, retry count, status/connection class, effective model, scope, active/max slots, pending/max depth, queue wait, delay source/delay, elapsed/remaining budget, `nextRetryAt`, and decision. Public responses keep protocol-compatible payloads and only safe standard metadata such as `Retry-After`; no retry-progress SSE event or recovery payload extension is added. Per inbound request, the attempt ceiling is `1 + upstreamQueueMaxRetries + at most one configured fallback`; outer SDK retries multiply that ceiling independently.
|
|
329
|
+
|
|
330
|
+
## Small-Model Routing
|
|
331
|
+
|
|
332
|
+
`/v1/messages` can optionally reroute specific low-value requests to a cheaper model:
|
|
333
|
+
|
|
334
|
+
- `smallModel`: the model to reroute to
|
|
335
|
+
- `compactUseSmallModel`: reroute recognized compact/summarization requests
|
|
336
|
+
|
|
337
|
+
The switch defaults to `false`. Routing is conservative:
|
|
338
|
+
|
|
339
|
+
- the target `smallModel` must exist in Copilot's model list
|
|
340
|
+
- it must preserve the original model's declared endpoint support
|
|
341
|
+
- tool, thinking, and vision requests are not rerouted to a model that lacks the required capabilities
|
|
342
|
+
|
|
343
|
+
## How it Works
|
|
344
|
+
|
|
345
|
+
ghc-proxy sits between your tools and the GitHub Copilot API:
|
|
346
|
+
|
|
347
|
+
```text
|
|
348
|
+
┌──────────────┐ ┌───────────┐ ┌───────────────────────┐
|
|
349
|
+
│ Claude Code │──────│ ghc-proxy │──────│ api.githubcopilot.com │
|
|
350
|
+
│ Cursor │ │ :4141 │ │ │
|
|
351
|
+
│ Any client │ │ │ │ │
|
|
352
|
+
└──────────────┘ └───────────┘ └───────────────────────┘
|
|
353
|
+
OpenAI or Translates GitHub Copilot
|
|
354
|
+
Anthropic between API
|
|
355
|
+
format formats
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
The proxy authenticates with GitHub using the [device code OAuth flow](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow) (the same flow VS Code uses), then exchanges the GitHub token for a short-lived Copilot token that auto-refreshes.
|
|
359
|
+
|
|
360
|
+
When the Copilot token response includes `endpoints.api`, `ghc-proxy` now prefers that runtime API base automatically instead of relying only on the configured account type. This keeps enterprise/business routing aligned with the endpoint GitHub actually returned for the current token.
|
|
361
|
+
|
|
362
|
+
Incoming requests hit an [Elysia](https://elysiajs.com/) server. `chat/completions` requests are validated, normalized into the shared planning pipeline, and then forwarded to Copilot. `responses` requests use a native Responses path with explicit compatibility policies. `messages` requests are routed per-model and can use native Anthropic passthrough, the Responses translation path, or the existing chat-completions fallback. The translator tracks exact vs lossy vs unsupported behavior explicitly; see the [Messages Routing and Translation Guide](./docs/messages-routing-and-translation.md) and the [Anthropic Translation Matrix](./docs/anthropic-translation-matrix.md) for the current support surface.
|
|
363
|
+
|
|
364
|
+
For Anthropic `search_result` blocks, current live probes show Copilot native `/v1/messages` accepts top-level search results and pure search-result tool outputs, but rejects top-level `citations` and mixed text/search-result tool output arrays. The native path sanitizes those known rejection cases, while translated paths flatten search results to text.
|
|
365
|
+
|
|
366
|
+
### Request Routing
|
|
367
|
+
|
|
368
|
+
`ghc-proxy` does not force every request through one protocol. The current routing rules are:
|
|
369
|
+
|
|
370
|
+
- `POST /v1/chat/completions`: OpenAI Chat Completions -> shared planning pipeline -> Copilot `/chat/completions`
|
|
371
|
+
- `POST /v1/responses`: OpenAI Responses create -> native Responses handler -> Copilot `/responses`
|
|
372
|
+
- `POST /v1/responses/input_tokens`: Responses input-token counting passthrough by default, or local estimation in official emulator mode
|
|
373
|
+
- `GET /v1/responses/:responseId`: Responses retrieve passthrough by default, or local retrieval in official emulator mode
|
|
374
|
+
- `GET /v1/responses/:responseId/input_items`: Responses input-items passthrough by default, or local retrieval in official emulator mode
|
|
375
|
+
- `DELETE /v1/responses/:responseId`: Responses delete passthrough by default, or local deletion in official emulator mode
|
|
376
|
+
- `POST /v1/messages`: Anthropic Messages -> choose the best available upstream path for the selected model:
|
|
377
|
+
- native Copilot `/v1/messages` when supported
|
|
378
|
+
- Anthropic -> Responses -> Anthropic translation when the model only supports `/responses`
|
|
379
|
+
- Anthropic -> Chat Completions -> Anthropic fallback otherwise
|
|
380
|
+
|
|
381
|
+
This keeps the existing chat pipeline stable while allowing newer Copilot models to use the endpoint they actually expose.
|
|
382
|
+
|
|
383
|
+
### Endpoints
|
|
384
|
+
|
|
385
|
+
**OpenAI compatible:**
|
|
386
|
+
|
|
387
|
+
| Method | Path | Description |
|
|
388
|
+
|--------|------|-------------|
|
|
389
|
+
| `POST` | `/v1/chat/completions` | Chat completions (streaming and non-streaming) |
|
|
390
|
+
| `POST` | `/v1/responses` | Create a Responses API response |
|
|
391
|
+
| `POST` | `/v1/responses/input_tokens` | Count Responses input tokens via upstream passthrough or the local official emulator |
|
|
392
|
+
| `GET` | `/v1/responses/:responseId` | Retrieve one response via upstream passthrough or the local official emulator |
|
|
393
|
+
| `GET` | `/v1/responses/:responseId/input_items` | Retrieve response input items via upstream passthrough or the local official emulator |
|
|
394
|
+
| `DELETE` | `/v1/responses/:responseId` | Delete one response via upstream passthrough or the local official emulator |
|
|
395
|
+
| `GET` | `/v1/models` | List available models |
|
|
396
|
+
| `POST` | `/v1/embeddings` | Generate embeddings |
|
|
397
|
+
|
|
398
|
+
**Anthropic compatible:**
|
|
399
|
+
|
|
400
|
+
| Method | Path | Description |
|
|
401
|
+
|--------|------|-------------|
|
|
402
|
+
| `POST` | `/v1/messages` | Messages API with per-model routing across native Messages, Responses translation, or chat-completions fallback |
|
|
403
|
+
| `POST` | `/v1/messages/count_tokens` | Token counting |
|
|
404
|
+
|
|
405
|
+
**Utility:**
|
|
406
|
+
|
|
407
|
+
| Method | Path | Description |
|
|
408
|
+
|--------|------|-------------|
|
|
409
|
+
| `GET` | `/health` | Liveness/readiness probe — returns `{ status, copilotToken, modelsLoaded, version }` |
|
|
410
|
+
| `GET` | `/usage` | Copilot quota / usage monitoring |
|
|
411
|
+
| `GET` | `/token` | Inspect the current Copilot token |
|
|
412
|
+
|
|
413
|
+
> **Note:** The `/v1/` prefix is optional for OpenAI-compatible endpoints (`/chat/completions`, `/responses`, `/models`, `/embeddings`). Anthropic endpoints (`/v1/messages`, `/v1/messages/count_tokens`) require the `/v1` prefix. The utility endpoints (`/health`, `/usage`, `/token`) are root-only and not exposed under `/v1`.
|
|
414
|
+
|
|
415
|
+
## Responses Compatibility
|
|
416
|
+
|
|
417
|
+
`/v1/responses` is designed to stay close to the OpenAI wire format while making Copilot limitations explicit:
|
|
418
|
+
|
|
419
|
+
- requests are validated before any mutation
|
|
420
|
+
- common official request fields such as `conversation`, `previous_response_id`, `max_tool_calls`, `truncation`, `user`, `prompt`, and `text` are now modeled explicitly instead of relying on loose passthrough alone
|
|
421
|
+
- official `text.format` options are modeled explicitly, including `text`, `json_object`, and `json_schema`
|
|
422
|
+
- an opt-in `responsesOfficialEmulator` mode adds in-memory OpenAI-style state for `previous_response_id`, `conversation`, `GET /responses/{id}`, `GET /responses/{id}/input_items`, `DELETE /responses/{id}`, and `POST /responses/input_tokens`
|
|
423
|
+
- emulator state is memory-only and expires after `responsesOfficialEmulatorTtlSeconds` (default `14400`, or 4 hours)
|
|
424
|
+
- `background: true` is rejected explicitly while emulator mode is enabled
|
|
425
|
+
- `custom` `apply_patch` can be rewritten as a function tool when `useFunctionApplyPatch` is enabled
|
|
426
|
+
- automatic Responses `context_management` injection is disabled by default and only applies when `responsesApiAutoContextManagement` is `true` and the model matches `responsesApiContextManagementModels`
|
|
427
|
+
- automatic trimming of Responses `input` to the latest `compaction` item is disabled by default and only applies when `responsesApiAutoCompactInput` is `true`
|
|
428
|
+
- reasoning defaults for Anthropic -> Responses translation can be tuned with `modelReasoningEfforts`
|
|
429
|
+
- request parameters that a model rejects (e.g. `temperature`/`top_p` on reasoning models) are stripped on the Responses boundary rather than leaked upstream as a `400`; see [Responses Parameter Filters](#responses-parameter-filters)
|
|
430
|
+
- built-in web search (`web_search`, `web_search_preview`, and their dated variants) is forwarded to Copilot rather than blocked; every `/responses` model probed accepts it and runs a real search, see [docs/research/responses-web-search.md](docs/research/responses-web-search.md)
|
|
431
|
+
- external image URLs on the Responses path fail explicitly with `400`; use `file_id` or data URL image input instead
|
|
432
|
+
- official `input_file` and `item_reference` input items are modeled explicitly and validated before forwarding
|
|
433
|
+
|
|
434
|
+
Example opt-in configuration for these two Responses-specific policies:
|
|
435
|
+
|
|
436
|
+
```json
|
|
437
|
+
{
|
|
438
|
+
"responsesApiAutoContextManagement": true,
|
|
439
|
+
"responsesApiContextManagementModels": ["gpt-5"],
|
|
440
|
+
"responsesApiAutoCompactInput": true,
|
|
441
|
+
"responsesOfficialEmulator": true,
|
|
442
|
+
"responsesOfficialEmulatorTtlSeconds": 14400
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
> See [Responses Upstream Notes](./docs/responses-upstream-notes.md) for detailed upstream compatibility observations from live testing.
|
|
447
|
+
|
|
448
|
+
### Responses Parameter Filters
|
|
449
|
+
|
|
450
|
+
Some Copilot models reject request parameters that the OpenAI wire format allows. The clearest case: **reasoning models** (the `gpt-5` family, o-series, codex) reject sampling parameters and answer `POST /responses` with `400 Unsupported parameter: 'temperature' is not supported with this model.` Since the client cannot always be changed, the proxy strips the offending parameters on the Responses boundary instead of leaking the incompatibility outward.
|
|
451
|
+
|
|
452
|
+
This is expressed as a small rule engine that runs on both the native `/v1/responses` path and the `/v1/messages` → Responses translation path:
|
|
453
|
+
|
|
454
|
+
- **Built-in default rule:** any model that advertises `reasoning_effort` has `temperature` and `top_p` stripped. This covers the whole reasoning family (including future point releases like `gpt-5.4-mini`) with no configuration.
|
|
455
|
+
- **`responsesApiParameterFilters`:** add your own rules. Each rule is `{ "models": [glob, ...], "params": [name, ...] }`; every rule whose `models` glob matches the resolved model contributes its `params`. Rules are **added** to the default (the union of parameters is stripped). Model globs use the same `*` wildcard as `modelRewrites`.
|
|
456
|
+
- **`responsesApiParameterFiltersReplaceDefault`:** set to `true` to disable the built-in reasoning-model rule, so only your `responsesApiParameterFilters` apply — use this to fully **overwrite** the default behavior.
|
|
457
|
+
|
|
458
|
+
Stripped parameters are removed entirely (never sent as `null`), because upstream rejects the mere presence of the key.
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
{
|
|
462
|
+
"responsesApiParameterFilters": [
|
|
463
|
+
{ "models": ["gpt-5*", "o1*"], "params": ["temperature", "top_p"] },
|
|
464
|
+
{ "models": ["some-model"], "params": ["top_k"] }
|
|
465
|
+
],
|
|
466
|
+
"responsesApiParameterFiltersReplaceDefault": false
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Docker
|
|
471
|
+
|
|
472
|
+
Pre-built images are available on GHCR:
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
docker pull ghcr.io/wxxb789/ghc-proxy
|
|
476
|
+
docker run -p 4141:4141 ghcr.io/wxxb789/ghc-proxy
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Or build locally:
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
docker build -t ghc-proxy .
|
|
483
|
+
mkdir -p ./copilot-data
|
|
484
|
+
docker run -p 4141:4141 -v $(pwd)/copilot-data:/root/.local/share/ghc-proxy ghc-proxy
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Authentication and settings are persisted in `copilot-data/config.json` so they survive container restarts.
|
|
488
|
+
|
|
489
|
+
You can also pass a GitHub token via environment variable. The container [entrypoint](entrypoint.sh) forwards `GH_TOKEN` to `start --github-token`, so this is Docker-specific — the proxy binary itself does not read `GH_TOKEN` from the environment (outside Docker, use the `--github-token` flag or a persisted `config.json`):
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
docker run -p 4141:4141 -e GH_TOKEN=your_token ghcr.io/wxxb789/ghc-proxy
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
To run the one-time device-code auth flow inside the container instead (writes the token into the mounted data volume):
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
docker run -it -v $(pwd)/copilot-data:/root/.local/share/ghc-proxy ghc-proxy --auth
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Docker Compose:
|
|
502
|
+
|
|
503
|
+
```yaml
|
|
504
|
+
services:
|
|
505
|
+
ghc-proxy:
|
|
506
|
+
image: ghcr.io/wxxb789/ghc-proxy
|
|
507
|
+
ports:
|
|
508
|
+
- '4141:4141'
|
|
509
|
+
environment:
|
|
510
|
+
- GH_TOKEN=your_token_here
|
|
511
|
+
restart: unless-stopped
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Running from Source
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
git clone https://github.com/wxxb789/ghc-proxy.git
|
|
518
|
+
cd ghc-proxy
|
|
519
|
+
bun install
|
|
520
|
+
bun run dev
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Development
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
bun install # Install dependencies
|
|
527
|
+
bun run dev # Start with --watch
|
|
528
|
+
bun run build # Build with tsdown
|
|
529
|
+
bun run lint # ESLint
|
|
530
|
+
bun run typecheck # tsc --noEmit
|
|
531
|
+
bun test # Run tests
|
|
532
|
+
bun run matrix:live # Real Copilot upstream compatibility matrix
|
|
533
|
+
bun run matrix:live --vision-only --all-responses-models --json
|
|
534
|
+
bun run matrix:live --stateful-only --json --model=gpt-5.2-codex
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
> **Note:** `bun run matrix:live` uses your configured GitHub/Copilot credentials and spends real upstream requests. Use it when you want end-to-end verification against the current Copilot service, not for every local edit.
|
|
538
|
+
>
|
|
539
|
+
> Useful flags:
|
|
540
|
+
> - `--json`: emit machine-readable JSON only
|
|
541
|
+
> - `--vision-only`: run just the Responses image probes
|
|
542
|
+
> - `--stateful-only`: run follow-up/resource probes such as `previous_response_id`, `input_tokens`, and `input_items`
|
|
543
|
+
> - `--all-responses-models`: scan every model that advertises `/responses`
|
|
544
|
+
> - `--model=<id>`: pin the Responses scan to one specific model
|
|
545
|
+
|
|
546
|
+
### Tool Support Probe
|
|
547
|
+
|
|
548
|
+
Answers whether a tool **works**, not merely whether upstream returns `200` when you mention it. For each (model × tool) it declares the tool, then — unless `--accept-only` — sends a prompt that cannot be answered without it and looks in the response for proof it ran.
|
|
549
|
+
|
|
550
|
+
Verdicts: `supported` (the tool ran or was called), `inert` (accepted but never invoked), `unsupported` (upstream rejected it), `unmeasured` (capacity/gateway fault — no verdict, re-run before publishing).
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
bun scripts/probes/tool-support.ts # both boundaries
|
|
554
|
+
bun scripts/probes/tool-support.ts --json # JSON snapshot to stdout
|
|
555
|
+
bun scripts/probes/tool-support.ts --model=claude-opus-5 # single model
|
|
556
|
+
bun scripts/probes/tool-support.ts --boundary=responses # or: messages
|
|
557
|
+
bun scripts/probes/tool-support.ts --accept-only # skip the functional pass (half the quota)
|
|
558
|
+
bun scripts/probes/tool-support.ts --names # also probe client tool NAMES (WebSearch, shell, ...)
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Latest results: [docs/research/builtin-tool-support.md](docs/research/builtin-tool-support.md).
|
|
562
|
+
|
|
563
|
+
The JSON output is designed for weekly diffing — `generatedAt` is the only volatile field:
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
# Compare two weekly snapshots
|
|
567
|
+
diff <(jq -S 'del(.generatedAt)' week1.json) <(jq -S 'del(.generatedAt)' week2.json)
|
|
568
|
+
```
|