ghc-proxy 0.10.0 → 0.10.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 +166 -107
- package/dist/{GptEncoding-DdY2evDX.mjs → GptEncoding-DGbju5p-.mjs} +11 -9
- package/dist/{cl100k_base-ChJqEXhP.mjs → cl100k_base-CR_-ZzWY.mjs} +2 -2
- package/dist/main.mjs +4297 -4566
- package/dist/{o200k_base-DXNwToXP.mjs → o200k_base-8n5G7cJ5.mjs} +2 -2
- package/dist/{p50k_base-Cab7w92R.mjs → p50k_base-Coo3riw5.mjs} +2 -2
- package/dist/{p50k_edit-DkrRw_em.mjs → p50k_edit-Df1WiLDq.mjs} +2 -2
- package/dist/{prompt-DsMdjS4d.mjs → prompt-B9CExZli.mjs} +66 -28
- package/dist/{r50k_base-1vVxWqTY.mjs → r50k_base-CrlP1QVX.mjs} +2 -2
- package/dist/{file-type-BwbWtW7C.mjs → source-BpNO27ea.mjs} +1417 -1248
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -2,39 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/ghc-proxy)
|
|
4
4
|
[](https://github.com/wxxb789/ghc-proxy/actions/workflows/ci.yml)
|
|
5
|
-
[](https://github.com/wxxb789/ghc-proxy/blob/
|
|
5
|
+
[](https://github.com/wxxb789/ghc-proxy/blob/main/LICENSE)
|
|
6
6
|
|
|
7
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
8
|
|
|
9
9
|
> [!WARNING]
|
|
10
10
|
> Reverse-engineered, unofficial, may break at any time. Excessive use can trigger GitHub abuse detection. **Use at your own risk.**
|
|
11
11
|
|
|
12
|
-
**TL;DR** —
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
**TL;DR** — Choose either supported runtime:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Bun >= 1.4
|
|
16
|
+
bunx --bun ghc-proxy@latest start
|
|
17
|
+
|
|
18
|
+
# Node.js >= 24
|
|
19
|
+
npx ghc-proxy@latest start
|
|
20
|
+
```
|
|
17
21
|
|
|
18
22
|
## Prerequisites
|
|
19
23
|
|
|
20
24
|
Before you start, make sure you have:
|
|
21
25
|
|
|
22
|
-
1. **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
2. **A GitHub Copilot subscription** -- individual, business, or enterprise
|
|
26
|
+
1. **One supported JavaScript runtime:**
|
|
27
|
+
- **Bun >= 1.4:** `winget install --id Oven-sh.Bun` on Windows, or see the [official installation guide](https://bun.com/docs/installation)
|
|
28
|
+
- **Node.js >= 24:** install the latest LTS release from the [official Node.js download page](https://nodejs.org/en/download)
|
|
29
|
+
2. **A GitHub Copilot subscription** -- individual, business, or enterprise
|
|
26
30
|
|
|
27
31
|
## Quick Start
|
|
28
32
|
|
|
29
|
-
1. Start the proxy:
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
1. Start the proxy with your chosen runtime:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Bun
|
|
37
|
+
bunx --bun ghc-proxy@latest start
|
|
38
|
+
|
|
39
|
+
# Node.js
|
|
40
|
+
npx ghc-proxy@latest start
|
|
41
|
+
```
|
|
32
42
|
|
|
33
43
|
2. On the first run, you will be guided through GitHub's device-code authentication flow. Follow the prompts to authorize the proxy.
|
|
34
44
|
|
|
35
45
|
3. Once authenticated, the proxy starts on **`http://localhost:4141`** and is ready to accept requests.
|
|
36
46
|
|
|
37
|
-
That's it. Any tool that supports the OpenAI or Anthropic API can now point to `http://localhost:4141`.
|
|
47
|
+
That's it. Any tool that supports the OpenAI or Anthropic API can now point to `http://localhost:4141`.
|
|
48
|
+
|
|
49
|
+
The examples below use `bunx --bun`. If you chose Node.js, replace `bunx --bun` with `npx`; the published CLI and commands are the same.
|
|
38
50
|
|
|
39
51
|
> **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
52
|
|
|
@@ -45,7 +57,7 @@ This is the most common use case. There are two ways to set it up:
|
|
|
45
57
|
### Option A: One-command launch
|
|
46
58
|
|
|
47
59
|
```bash
|
|
48
|
-
bunx ghc-proxy@latest start --claude-code
|
|
60
|
+
bunx --bun ghc-proxy@latest start --claude-code
|
|
49
61
|
```
|
|
50
62
|
|
|
51
63
|
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.
|
|
@@ -73,7 +85,7 @@ Create or edit `~/.claude/settings.json` (this applies globally to all projects)
|
|
|
73
85
|
Then simply start the proxy and use Claude Code as usual:
|
|
74
86
|
|
|
75
87
|
```bash
|
|
76
|
-
bunx ghc-proxy@latest start
|
|
88
|
+
bunx --bun ghc-proxy@latest start
|
|
77
89
|
```
|
|
78
90
|
|
|
79
91
|
**What each environment variable does:**
|
|
@@ -96,24 +108,24 @@ See the [Claude Code settings docs](https://docs.anthropic.com/en/docs/claude-co
|
|
|
96
108
|
ghc-proxy uses a subcommand structure:
|
|
97
109
|
|
|
98
110
|
```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
|
|
111
|
+
bunx --bun ghc-proxy@latest start # Start the proxy server
|
|
112
|
+
bunx --bun ghc-proxy@latest auth # Run GitHub auth flow without starting the server
|
|
113
|
+
bunx --bun ghc-proxy@latest check-usage # Show your Copilot usage/quota in the terminal
|
|
114
|
+
bunx --bun ghc-proxy@latest debug # Print diagnostic info (version, paths, token status)
|
|
115
|
+
bunx --bun ghc-proxy@latest selfcheck # Probe tokenizer chunks and Bun/Node runtime contracts in the packaged bundle
|
|
104
116
|
```
|
|
105
117
|
|
|
106
118
|
### `start` Options
|
|
107
119
|
|
|
108
120
|
| Option | Alias | Default | Description |
|
|
109
121
|
|--------|-------|---------|-------------|
|
|
110
|
-
| `--port` | `-p` | `4141` | Port to listen on |
|
|
122
|
+
| `--port` | `-p` | `4141` | Port to listen on (`1..65535`; malformed or zero values fail before startup) |
|
|
111
123
|
| `--verbose` | `-v` | `false` | Enable verbose logging |
|
|
112
124
|
| `--account-type` | `-a` | `individual` | `individual`, `business`, or `enterprise` |
|
|
113
125
|
| `--rate-limit` | `-r` | -- | Minimum seconds between requests |
|
|
114
126
|
| `--wait` | `-w` | `false` | Queue requests instead of rejecting with 429 when `--rate-limit` cooldown has not elapsed (requires `--rate-limit`) |
|
|
115
127
|
| `--manual` | -- | `false` | Manually approve each request |
|
|
116
|
-
| `--github-token` | `-g` | -- |
|
|
128
|
+
| `--github-token` | `-g` | -- | Use a GitHub token for this process only (normally obtained with `auth`); this flag does not persist it to `config.json` |
|
|
117
129
|
| `--claude-code` | `-c` | `false` | Generate a Claude Code launch command |
|
|
118
130
|
| `--show-token` | -- | `false` | Display tokens on auth and refresh |
|
|
119
131
|
| `--dump-failed-payloads` | `-D` | `false` | Dump failed `/responses` payloads on upstream 400 errors for debugging. Can also be enabled with `DUMP_FAILED_PAYLOADS=1`. |
|
|
@@ -133,13 +145,13 @@ If you want to throttle how often the proxy forwards requests:
|
|
|
133
145
|
|
|
134
146
|
```bash
|
|
135
147
|
# Enforce a 30-second cooldown between requests
|
|
136
|
-
bunx ghc-proxy@latest start --rate-limit 30
|
|
148
|
+
bunx --bun ghc-proxy@latest start --rate-limit 30
|
|
137
149
|
|
|
138
150
|
# Same, but queue requests instead of returning 429
|
|
139
|
-
bunx ghc-proxy@latest start --rate-limit 30 --wait
|
|
151
|
+
bunx --bun ghc-proxy@latest start --rate-limit 30 --wait
|
|
140
152
|
|
|
141
153
|
# Manually approve every request (useful for debugging)
|
|
142
|
-
bunx ghc-proxy@latest start --manual
|
|
154
|
+
bunx --bun ghc-proxy@latest start --manual
|
|
143
155
|
```
|
|
144
156
|
|
|
145
157
|
`--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.
|
|
@@ -149,8 +161,8 @@ bunx ghc-proxy@latest start --manual
|
|
|
149
161
|
If you have a GitHub Business or Enterprise Copilot plan, pass `--account-type`:
|
|
150
162
|
|
|
151
163
|
```bash
|
|
152
|
-
bunx ghc-proxy@latest start --account-type business
|
|
153
|
-
bunx ghc-proxy@latest start --account-type enterprise
|
|
164
|
+
bunx --bun ghc-proxy@latest start --account-type business
|
|
165
|
+
bunx --bun ghc-proxy@latest start --account-type enterprise
|
|
154
166
|
```
|
|
155
167
|
|
|
156
168
|
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.
|
|
@@ -160,17 +172,17 @@ This routes requests to the correct Copilot API endpoint for your plan. See the
|
|
|
160
172
|
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
173
|
|
|
162
174
|
```bash
|
|
163
|
-
bunx ghc-proxy@latest start --account-type enterprise --ghe-domain company.ghe.com
|
|
175
|
+
bunx --bun ghc-proxy@latest start --account-type enterprise --ghe-domain company.ghe.com
|
|
164
176
|
```
|
|
165
177
|
|
|
166
178
|
Or authenticate first, then start without the flag on subsequent runs:
|
|
167
179
|
|
|
168
180
|
```bash
|
|
169
181
|
# First run (authenticates and persists the domain)
|
|
170
|
-
bunx ghc-proxy@latest auth --ghe-domain company.ghe.com
|
|
182
|
+
bunx --bun ghc-proxy@latest auth --ghe-domain company.ghe.com
|
|
171
183
|
|
|
172
184
|
# Later runs (domain is read from persisted config)
|
|
173
|
-
bunx ghc-proxy@latest start --account-type enterprise
|
|
185
|
+
bunx --bun ghc-proxy@latest start --account-type enterprise
|
|
174
186
|
```
|
|
175
187
|
|
|
176
188
|
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.
|
|
@@ -189,30 +201,31 @@ All fields are optional. The full schema:
|
|
|
189
201
|
|
|
190
202
|
| Field | Type | Default | Description |
|
|
191
203
|
|-------|------|---------|-------------|
|
|
192
|
-
| `githubToken` | `string` |
|
|
193
|
-
| `modelRewrites` | `{ from, to }[]` |
|
|
194
|
-
| `modelFallback` | `object` |
|
|
204
|
+
| `githubToken` | `string` | unset | Persisted GitHub token. The device-code flow (`auth` or first startup) writes it automatically; `start --github-token` is runtime-only and does not write this field |
|
|
205
|
+
| `modelRewrites` | `{ from, to }[]` | `[]` | Glob-pattern model substitution rules (see [Model Rewrites](#model-rewrites)) |
|
|
206
|
+
| `modelFallback` | `object` | built-in family defaults | Override default model fallbacks (see [Customizing Fallbacks](#customizing-fallbacks)) |
|
|
195
207
|
| `modelFallback.claudeOpus` | `string` | `claude-opus-5` | Fallback for `claude-opus-*` models |
|
|
196
208
|
| `modelFallback.claudeSonnet` | `string` | `claude-sonnet-5` | Fallback for `claude-sonnet-*` models |
|
|
197
209
|
| `modelFallback.claudeHaiku` | `string` | `claude-haiku-4.5` | Fallback for `claude-haiku-*` models |
|
|
198
|
-
| `smallModel` | `string` |
|
|
210
|
+
| `smallModel` | `string` | unset | Target model for compact request routing (see [Small-Model Routing](#small-model-routing)) |
|
|
199
211
|
| `compactUseSmallModel` | `boolean` | `false` | Route compact/summarization requests to `smallModel` |
|
|
200
212
|
| `useFunctionApplyPatch` | `boolean` | `true` | Rewrite `apply_patch` custom tool as function tool on Responses path |
|
|
201
213
|
| `responsesApiAutoCompactInput` | `boolean` | `false` | Automatically trim Responses `input` to the latest `compaction` item |
|
|
202
214
|
| `responsesApiAutoContextManagement` | `boolean` | `false` | Automatically inject Responses `context_management` for selected models |
|
|
203
|
-
| `responsesApiContextManagementModels` | `string[]` |
|
|
204
|
-
| `responsesApiParameterFilters` | `{ models, params }[]` |
|
|
205
|
-
| `responsesApiParameterFiltersReplaceDefault` | `boolean` | `false` | Disable the built-in reasoning-model default rule so only your `responsesApiParameterFilters` apply |
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
215
|
+
| `responsesApiContextManagementModels` | `string[]` | `[]` | Models eligible for auto-injected Responses `context_management` |
|
|
216
|
+
| `responsesApiParameterFilters` | `{ models, params }[]` | `[]` | Extra rules to strip request parameters on the Responses boundary; the built-in reasoning-model rule remains active unless replaced (see [Responses Parameter Filters](#responses-parameter-filters)) |
|
|
217
|
+
| `responsesApiParameterFiltersReplaceDefault` | `boolean` | `false` | Disable the built-in reasoning-model default rule so only your `responsesApiParameterFilters` apply |
|
|
218
|
+
| `chatCompletionsUseMaxCompletionTokens` | `string[]` | `[]` | Extra model globs that rename Chat Completions `max_tokens` to `max_completion_tokens`; adds to the built-in `gpt-5.4` / `gpt-5.4-*` rules |
|
|
219
|
+
| `responsesOfficialEmulator` | `boolean` | `false` | Enable local OpenAI-style Responses state emulation for `previous_response_id`, `conversation`, retrieve, input_items, delete, and input_tokens |
|
|
220
|
+
| `responsesOfficialEmulatorTtlSeconds` | `number` | `14400` | In-memory TTL for locally emulated Responses state |
|
|
221
|
+
| `modelReasoningEfforts` | `Record<string, string>` | `{}`; unlisted models use `high` | 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
222
|
| `upstreamQueueConcurrency` | `number` | `10` | Maximum concurrent Copilot upstream requests |
|
|
210
223
|
| `upstreamQueueMaxRetries` | `number` | `1` | Maximum retries across capacity and approved pre-connection failures (`0..2`) |
|
|
211
224
|
| `upstreamRecoveryBudgetSeconds` | `number` | `60` | Shared recovery deadline after the first retryable outcome or active-cooldown encounter (`1..120` seconds) |
|
|
212
|
-
| `overloadFallbacks` | `Record<string, string>` |
|
|
225
|
+
| `overloadFallbacks` | `Record<string, string>` | `{}` (disabled) | Exact effective-model mappings for one opt-in fallback dispatch after terminal model `529` |
|
|
213
226
|
| `upstreamQueueBaseDelaySeconds` | `number` | `2` | Base delay (seconds) for upstream retry backoff when `Retry-After` is absent |
|
|
214
227
|
| `upstreamQueueMaxDelaySeconds` | `number` | `60` | Maximum computed backoff (seconds); does not clamp `Retry-After` |
|
|
215
|
-
| `gheDomain` | `string` |
|
|
228
|
+
| `gheDomain` | `string` | unset | GitHub Enterprise Cloud company domain (persisted automatically after GHE.com auth) |
|
|
216
229
|
|
|
217
230
|
Example:
|
|
218
231
|
|
|
@@ -229,9 +242,10 @@ Example:
|
|
|
229
242
|
"compactUseSmallModel": true,
|
|
230
243
|
"useFunctionApplyPatch": true,
|
|
231
244
|
"responsesApiAutoCompactInput": false,
|
|
232
|
-
"responsesApiAutoContextManagement": false,
|
|
233
|
-
"responsesApiContextManagementModels": ["gpt-5", "gpt-5-mini"],
|
|
234
|
-
"
|
|
245
|
+
"responsesApiAutoContextManagement": false,
|
|
246
|
+
"responsesApiContextManagementModels": ["gpt-5", "gpt-5-mini"],
|
|
247
|
+
"chatCompletionsUseMaxCompletionTokens": [],
|
|
248
|
+
"responsesOfficialEmulator": false,
|
|
235
249
|
"responsesOfficialEmulatorTtlSeconds": 14400,
|
|
236
250
|
"modelReasoningEfforts": {
|
|
237
251
|
"gpt-5": "high",
|
|
@@ -359,9 +373,11 @@ The proxy authenticates with GitHub using the [device code OAuth flow](https://d
|
|
|
359
373
|
|
|
360
374
|
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
375
|
|
|
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
|
-
|
|
376
|
+
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.
|
|
377
|
+
|
|
378
|
+
The built-in, read-only Dashboard projects process health, model routing, behavior, and recent request lifecycle metadata without storing request or response content. See [Dashboard Observability](./docs/design/dashboard-observability.md).
|
|
379
|
+
|
|
380
|
+
For Anthropic `search_result` blocks, an April 17, 2026 probe against `claude-opus-4.6` on Copilot native `/v1/messages` accepted top-level search results and pure search-result tool outputs, but rejected top-level `citations` and mixed text/search-result tool output arrays. The native path sanitizes those observed rejection cases, while translated paths flatten search results to text; re-run the probe before treating that dated upstream result as universal.
|
|
365
381
|
|
|
366
382
|
### Request Routing
|
|
367
383
|
|
|
@@ -402,22 +418,37 @@ This keeps the existing chat pipeline stable while allowing newer Copilot models
|
|
|
402
418
|
| `POST` | `/v1/messages` | Messages API with per-model routing across native Messages, Responses translation, or chat-completions fallback |
|
|
403
419
|
| `POST` | `/v1/messages/count_tokens` | Token counting |
|
|
404
420
|
|
|
405
|
-
**Utility:**
|
|
421
|
+
**Utility:**
|
|
406
422
|
|
|
407
423
|
| Method | Path | Description |
|
|
408
424
|
|--------|------|-------------|
|
|
409
425
|
| `GET` | `/health` | Liveness/readiness probe — returns `{ status, copilotToken, modelsLoaded, version }` |
|
|
410
426
|
| `GET` | `/usage` | Copilot quota / usage monitoring |
|
|
411
|
-
| `GET` | `/token` | Inspect the current Copilot token |
|
|
412
|
-
|
|
413
|
-
|
|
427
|
+
| `GET` | `/token` | Inspect the current Copilot token |
|
|
428
|
+
|
|
429
|
+
**Local Dashboard (read-only):**
|
|
430
|
+
|
|
431
|
+
| Method | Path | Description |
|
|
432
|
+
|--------|------|-------------|
|
|
433
|
+
| `GET` | `/dashboard` | Dashboard application |
|
|
434
|
+
| `GET` | `/dashboard/styles.css` | Dashboard stylesheet |
|
|
435
|
+
| `GET` | `/dashboard/app.js` | Dashboard client script |
|
|
436
|
+
| `GET` | `/dashboard/api/overview` | Process, authentication, quota, request, and queue summary |
|
|
437
|
+
| `GET` | `/dashboard/api/models` | Upstream model metadata and effective proxy capabilities |
|
|
438
|
+
| `GET` | `/dashboard/api/behavior` | Active routing, compatibility policies, strategies, and effect counters |
|
|
439
|
+
| `GET` | `/dashboard/api/requests` | Active requests and the most recent 256 completed request summaries |
|
|
440
|
+
|
|
441
|
+
Dashboard routes are restricted to local access and return `403` when the peer, request host, or supplied `Origin` fails the loopback/same-origin checks. They are excluded from request history and access logging. See [Dashboard Observability](./docs/design/dashboard-observability.md) for the projection and security contract.
|
|
442
|
+
|
|
443
|
+
> **Note:** The `/v1/` prefix is optional for OpenAI-compatible endpoints (`/chat/completions`, `/responses` and its resource routes, `/models`, `/embeddings`). Anthropic endpoints (`/v1/messages`, `/v1/messages/count_tokens`) require the `/v1` prefix. The utility and Dashboard endpoints are root-only and not exposed under `/v1`.
|
|
414
444
|
|
|
415
445
|
## Responses Compatibility
|
|
416
446
|
|
|
417
447
|
`/v1/responses` is designed to stay close to the OpenAI wire format while making Copilot limitations explicit:
|
|
418
448
|
|
|
419
|
-
- requests are validated before any mutation
|
|
420
|
-
-
|
|
449
|
+
- requests are validated before any mutation
|
|
450
|
+
- client-supplied `top_k` is rejected with `400` on the OpenAI Chat Completions and Responses boundaries because neither official OpenAI schema defines it; clients that send it by mistake receive an explicit error instead of a silent drop. Anthropic Messages `top_k` remains supported and is preserved when the proxy translates that request internally for Copilot
|
|
451
|
+
- 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
452
|
- official `text.format` options are modeled explicitly, including `text`, `json_object`, and `json_schema`
|
|
422
453
|
- 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
454
|
- emulator state is memory-only and expires after `responsesOfficialEmulatorTtlSeconds` (default `14400`, or 4 hours)
|
|
@@ -427,9 +458,9 @@ This keeps the existing chat pipeline stable while allowing newer Copilot models
|
|
|
427
458
|
- automatic trimming of Responses `input` to the latest `compaction` item is disabled by default and only applies when `responsesApiAutoCompactInput` is `true`
|
|
428
459
|
- reasoning defaults for Anthropic -> Responses translation can be tuned with `modelReasoningEfforts`
|
|
429
460
|
- 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
|
|
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
|
|
461
|
+
- built-in web search (`web_search`, `web_search_preview`, and their dated variants) is forwarded to Copilot rather than blocked; every `/responses` model reached by the August 4, 2026 acceptance sweep accepted the tool, while functional search execution was verified on `gpt-5.6-sol` and `gpt-5.6-terra`, see [docs/research/responses-web-search.md](docs/research/responses-web-search.md)
|
|
462
|
+
- external image URLs on the Responses path fail explicitly with `400`; use `file_id` or data URL image input instead
|
|
463
|
+
- official `input_file` and `item_reference` input items are modeled explicitly and validated, but the verified Copilot GPT Responses boundary is stateless: it rejects `store: true` and cannot resolve returned item IDs on later requests. The proxy deliberately applies a proxy-wide `store: false` policy, removes all `item_reference` items before dispatch, and removes `function_call_output` items whose `call_id` has no matching `function_call` in the same input array. Without the optional emulator, a caller that requested storage still receives a successful stateless response; retrieve/delete/continuation semantics are available only from the local emulator
|
|
433
464
|
|
|
434
465
|
Example opt-in configuration for these two Responses-specific policies:
|
|
435
466
|
|
|
@@ -451,7 +482,7 @@ Some Copilot models reject request parameters that the OpenAI wire format allows
|
|
|
451
482
|
|
|
452
483
|
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
484
|
|
|
454
|
-
- **Built-in default rule:** any model that advertises `reasoning_effort` has `temperature`
|
|
485
|
+
- **Built-in default rule:** any model that advertises `reasoning_effort` has `temperature` stripped. It also has `top_p` stripped except for `*-codex` / `*-codex-*` models, which are exempt because the July 26, 2026 probe found the tested Codex model accepted `top_p` while its reasoning-model siblings rejected it. This exemption narrows only the built-in rule; an operator rule can still strip `top_p`.
|
|
455
486
|
- **`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
487
|
- **`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
488
|
|
|
@@ -461,7 +492,7 @@ Stripped parameters are removed entirely (never sent as `null`), because upstrea
|
|
|
461
492
|
{
|
|
462
493
|
"responsesApiParameterFilters": [
|
|
463
494
|
{ "models": ["gpt-5*", "o1*"], "params": ["temperature", "top_p"] },
|
|
464
|
-
{ "models": ["some-model"], "params": ["
|
|
495
|
+
{ "models": ["some-model"], "params": ["service_tier"] }
|
|
465
496
|
],
|
|
466
497
|
"responsesApiParameterFiltersReplaceDefault": false
|
|
467
498
|
}
|
|
@@ -469,63 +500,91 @@ Stripped parameters are removed entirely (never sent as `null`), because upstrea
|
|
|
469
500
|
|
|
470
501
|
## Docker
|
|
471
502
|
|
|
472
|
-
Pre-built images are available on GHCR:
|
|
473
|
-
|
|
474
|
-
```bash
|
|
475
|
-
docker pull ghcr.io/wxxb789/ghc-proxy
|
|
476
|
-
docker
|
|
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
|
-
|
|
503
|
+
Pre-built images are available on GHCR:
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
docker pull ghcr.io/wxxb789/ghc-proxy
|
|
507
|
+
docker volume create ghc-proxy-data
|
|
508
|
+
docker run --rm -p 127.0.0.1:4141:4141 \
|
|
509
|
+
-v ghc-proxy-data:/home/bun/.local/share/ghc-proxy \
|
|
510
|
+
ghcr.io/wxxb789/ghc-proxy
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Or build locally:
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
docker build -t ghc-proxy .
|
|
517
|
+
docker volume create ghc-proxy-data
|
|
518
|
+
docker run --rm -p 127.0.0.1:4141:4141 \
|
|
519
|
+
-v ghc-proxy-data:/home/bun/.local/share/ghc-proxy \
|
|
520
|
+
ghc-proxy
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Authentication and settings are persisted in the `ghc-proxy-data` volume so they survive container restarts. The proxy does not provide API authentication. Keep the port bound to loopback as shown; any non-loopback deployment needs an authenticated TLS reverse proxy or a firewall that restricts access.
|
|
524
|
+
|
|
525
|
+
Run the device-code authentication flow once against the same volume:
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
docker run --rm -it \
|
|
529
|
+
-v ghc-proxy-data:/home/bun/.local/share/ghc-proxy \
|
|
530
|
+
ghcr.io/wxxb789/ghc-proxy auth
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
The legacy `--auth` container argument remains supported, but `auth` is the standard CLI subcommand:
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
docker run --rm -it \
|
|
537
|
+
-v ghc-proxy-data:/home/bun/.local/share/ghc-proxy \
|
|
538
|
+
ghcr.io/wxxb789/ghc-proxy --auth
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
You can also pass a GitHub token via `GH_TOKEN`. The container [entrypoint](entrypoint.sh) forwards a non-empty value only when starting the proxy, as `start --github-token`:
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
docker run --rm -p 127.0.0.1:4141:4141 \
|
|
545
|
+
-v ghc-proxy-data:/home/bun/.local/share/ghc-proxy \
|
|
546
|
+
-e GH_TOKEN=your_token \
|
|
547
|
+
ghcr.io/wxxb789/ghc-proxy
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Docker Compose:
|
|
502
551
|
|
|
503
552
|
```yaml
|
|
504
553
|
services:
|
|
505
|
-
ghc-proxy:
|
|
506
|
-
image: ghcr.io/wxxb789/ghc-proxy
|
|
507
|
-
ports:
|
|
508
|
-
- '4141:4141'
|
|
509
|
-
|
|
510
|
-
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
554
|
+
ghc-proxy:
|
|
555
|
+
image: ghcr.io/wxxb789/ghc-proxy
|
|
556
|
+
ports:
|
|
557
|
+
- '127.0.0.1:4141:4141'
|
|
558
|
+
volumes:
|
|
559
|
+
- ghc-proxy-data:/home/bun/.local/share/ghc-proxy
|
|
560
|
+
environment:
|
|
561
|
+
- GH_TOKEN=your_token_here
|
|
562
|
+
restart: unless-stopped
|
|
563
|
+
|
|
564
|
+
volumes:
|
|
565
|
+
ghc-proxy-data:
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## Running from Source
|
|
569
|
+
|
|
570
|
+
Repository development uses Bun >= 1.4 even if you run the published package with Node.js.
|
|
571
|
+
|
|
572
|
+
```bash
|
|
517
573
|
git clone https://github.com/wxxb789/ghc-proxy.git
|
|
518
574
|
cd ghc-proxy
|
|
519
|
-
bun install
|
|
520
|
-
bun run dev
|
|
575
|
+
bun install
|
|
576
|
+
bun run dev # Start with --watch
|
|
577
|
+
# Or use the production-style source command:
|
|
578
|
+
bun run start
|
|
521
579
|
```
|
|
522
580
|
|
|
523
581
|
## Development
|
|
524
582
|
|
|
525
583
|
```bash
|
|
526
|
-
bun install # Install dependencies
|
|
527
|
-
bun run dev # Start with --watch
|
|
528
|
-
bun run
|
|
584
|
+
bun install # Install dependencies
|
|
585
|
+
bun run dev # Start with --watch
|
|
586
|
+
bun run start # Start without --watch
|
|
587
|
+
bun run build # Build with tsdown
|
|
529
588
|
bun run lint # ESLint
|
|
530
589
|
bun run typecheck # tsc --noEmit
|
|
531
590
|
bun test # Run tests
|
|
@@ -76,7 +76,7 @@ function getSpecialTokenRegex(tokens) {
|
|
|
76
76
|
}
|
|
77
77
|
//#endregion
|
|
78
78
|
//#region node_modules/gpt-tokenizer/esm/BytePairEncodingCore.js
|
|
79
|
-
const emptyBuffer = new Uint8Array(0);
|
|
79
|
+
const emptyBuffer = /* @__PURE__ */ new Uint8Array(0);
|
|
80
80
|
const decoder = new TextDecoder("utf8");
|
|
81
81
|
var BytePairEncodingCore = class {
|
|
82
82
|
mergeableBytePairRankCount;
|
|
@@ -674,7 +674,7 @@ function Cl100KBase(bytePairRankDecoder) {
|
|
|
674
674
|
return {
|
|
675
675
|
tokenSplitRegex: CL100K_TOKEN_SPLIT_REGEX,
|
|
676
676
|
bytePairRankDecoder,
|
|
677
|
-
specialTokensEncoder: new Map([
|
|
677
|
+
specialTokensEncoder: /* @__PURE__ */ new Map([
|
|
678
678
|
[EndOfText, 100257],
|
|
679
679
|
[FimPrefix, 100258],
|
|
680
680
|
[FimMiddle, 100259],
|
|
@@ -745,7 +745,7 @@ function P50KBase(bytePairRankDecoder) {
|
|
|
745
745
|
expectedVocabularySize: 50281,
|
|
746
746
|
tokenSplitRegex: R50K_TOKEN_SPLIT_REGEX,
|
|
747
747
|
bytePairRankDecoder,
|
|
748
|
-
specialTokensEncoder: new Map([[EndOfText, 50256]])
|
|
748
|
+
specialTokensEncoder: /* @__PURE__ */ new Map([[EndOfText, 50256]])
|
|
749
749
|
};
|
|
750
750
|
}
|
|
751
751
|
//#endregion
|
|
@@ -754,7 +754,7 @@ function P50KEdit(bytePairRankDecoder) {
|
|
|
754
754
|
return {
|
|
755
755
|
tokenSplitRegex: R50K_TOKEN_SPLIT_REGEX,
|
|
756
756
|
bytePairRankDecoder,
|
|
757
|
-
specialTokensEncoder: new Map([
|
|
757
|
+
specialTokensEncoder: /* @__PURE__ */ new Map([
|
|
758
758
|
[EndOfText, 50256],
|
|
759
759
|
[FimPrefix, 50281],
|
|
760
760
|
[FimMiddle, 50282],
|
|
@@ -769,7 +769,7 @@ function R50KBase(bytePairRankDecoder) {
|
|
|
769
769
|
expectedVocabularySize: 50257,
|
|
770
770
|
tokenSplitRegex: R50K_TOKEN_SPLIT_REGEX,
|
|
771
771
|
bytePairRankDecoder,
|
|
772
|
-
specialTokensEncoder: new Map([[EndOfText, 50256]])
|
|
772
|
+
specialTokensEncoder: /* @__PURE__ */ new Map([[EndOfText, 50256]])
|
|
773
773
|
};
|
|
774
774
|
}
|
|
775
775
|
//#endregion
|
|
@@ -883,11 +883,13 @@ var GptEncoding = class GptEncoding {
|
|
|
883
883
|
}
|
|
884
884
|
}
|
|
885
885
|
static getEncodingApi(encodingName, getMergeableRanks) {
|
|
886
|
-
|
|
886
|
+
const modelParams = getEncodingParams(encodingName, getMergeableRanks);
|
|
887
|
+
return new GptEncoding(modelParams);
|
|
887
888
|
}
|
|
888
889
|
static getEncodingApiForModel(modelName, getMergeableRanks, modelSpec) {
|
|
890
|
+
const modelParams = getEncodingParams(modelToEncodingMap[modelName] ?? "o200k_base", getMergeableRanks);
|
|
889
891
|
return new GptEncoding({
|
|
890
|
-
...
|
|
892
|
+
...modelParams,
|
|
891
893
|
modelName,
|
|
892
894
|
modelSpec
|
|
893
895
|
});
|
|
@@ -950,7 +952,7 @@ var GptEncoding = class GptEncoding {
|
|
|
950
952
|
const chatStartToken = this.specialTokensEncoder.get(ImStart);
|
|
951
953
|
const chatEndToken = this.specialTokensEncoder.get(ImEnd);
|
|
952
954
|
if (chatStartToken === void 0 || chatEndToken === void 0) throw new Error(`Model '${model}' does not support chat.`);
|
|
953
|
-
const allowedSpecial = new Set([ImSep]);
|
|
955
|
+
const allowedSpecial = /* @__PURE__ */ new Set([ImSep]);
|
|
954
956
|
const { messageSeparator, roleSeparator } = params;
|
|
955
957
|
const encodedMessageSeparator = messageSeparator.length > 0 ? this.encode(messageSeparator) : [];
|
|
956
958
|
const encodedRoleSeparator = roleSeparator.length > 0 ? this.encode(roleSeparator, { allowedSpecial }) : [];
|
|
@@ -1098,4 +1100,4 @@ var GptEncoding = class GptEncoding {
|
|
|
1098
1100
|
//#endregion
|
|
1099
1101
|
export { ImStart as _, FimPrefix as a, HarmonyChannel as c, HarmonyMessage as d, HarmonyReturn as f, ImSep as g, ImEnd as h, FimMiddle as i, HarmonyConstrain as l, HarmonyStartOfText as m, EndOfPrompt as n, FimSuffix as o, HarmonyStart as p, EndOfText as r, HarmonyCall as s, GptEncoding as t, HarmonyEnd as u, ALL_SPECIAL_TOKENS as v, DEFAULT_MERGE_CACHE_SIZE as y };
|
|
1100
1102
|
|
|
1101
|
-
//# sourceMappingURL=GptEncoding-
|
|
1103
|
+
//# sourceMappingURL=GptEncoding-DGbju5p-.mjs.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as ImStart, a as FimPrefix, c as HarmonyChannel, d as HarmonyMessage, f as HarmonyReturn, g as ImSep, h as ImEnd, i as FimMiddle, l as HarmonyConstrain, m as HarmonyStartOfText, n as EndOfPrompt, o as FimSuffix, p as HarmonyStart, r as EndOfText, s as HarmonyCall, t as GptEncoding, u as HarmonyEnd, v as ALL_SPECIAL_TOKENS, y as DEFAULT_MERGE_CACHE_SIZE } from "./GptEncoding-
|
|
1
|
+
import { _ as ImStart, a as FimPrefix, c as HarmonyChannel, d as HarmonyMessage, f as HarmonyReturn, g as ImSep, h as ImEnd, i as FimMiddle, l as HarmonyConstrain, m as HarmonyStartOfText, n as EndOfPrompt, o as FimSuffix, p as HarmonyStart, r as EndOfText, s as HarmonyCall, t as GptEncoding, u as HarmonyEnd, v as ALL_SPECIAL_TOKENS, y as DEFAULT_MERGE_CACHE_SIZE } from "./GptEncoding-DGbju5p-.mjs";
|
|
2
2
|
//#region node_modules/gpt-tokenizer/esm/bpeRanks/cl100k_base.js
|
|
3
3
|
const c0 = [
|
|
4
4
|
"!",
|
|
@@ -101368,4 +101368,4 @@ const { decode, decodeAsyncGenerator, decodeGenerator, encode, encodeGenerator,
|
|
|
101368
101368
|
//#endregion
|
|
101369
101369
|
export { ALL_SPECIAL_TOKENS, DEFAULT_MERGE_CACHE_SIZE, EndOfPrompt, EndOfText, FimMiddle, FimPrefix, FimSuffix, HarmonyCall, HarmonyChannel, HarmonyConstrain, HarmonyEnd, HarmonyMessage, HarmonyReturn, HarmonyStart, HarmonyStartOfText, ImEnd, ImSep, ImStart, clearMergeCache, countTokens, decode, decodeAsyncGenerator, decodeGenerator, api as default, encode, encodeChat, encodeChatGenerator, encodeGenerator, estimateCost, isWithinTokenLimit, setMergeCacheSize, vocabularySize };
|
|
101370
101370
|
|
|
101371
|
-
//# sourceMappingURL=cl100k_base-
|
|
101371
|
+
//# sourceMappingURL=cl100k_base-CR_-ZzWY.mjs.map
|