ghc-proxy 0.9.3 → 0.10.1

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.
Files changed (3) hide show
  1. package/README.md +59 -37
  2. package/dist/main.mjs +44670 -42595
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/ghc-proxy)](https://www.npmjs.com/package/ghc-proxy)
4
4
  [![CI](https://github.com/wxxb789/ghc-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/wxxb789/ghc-proxy/actions/workflows/ci.yml)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/wxxb789/ghc-proxy/blob/master/LICENSE)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
 
@@ -107,13 +107,13 @@ bunx ghc-proxy@latest selfcheck # Probe tokenizer chunks and Bun/Node runti
107
107
 
108
108
  | Option | Alias | Default | Description |
109
109
  |--------|-------|---------|-------------|
110
- | `--port` | `-p` | `4141` | Port to listen on |
110
+ | `--port` | `-p` | `4141` | Port to listen on (`1..65535`; malformed or zero values fail before startup) |
111
111
  | `--verbose` | `-v` | `false` | Enable verbose logging |
112
112
  | `--account-type` | `-a` | `individual` | `individual`, `business`, or `enterprise` |
113
113
  | `--rate-limit` | `-r` | -- | Minimum seconds between requests |
114
114
  | `--wait` | `-w` | `false` | Queue requests instead of rejecting with 429 when `--rate-limit` cooldown has not elapsed (requires `--rate-limit`) |
115
115
  | `--manual` | -- | `false` | Manually approve each request |
116
- | `--github-token` | `-g` | -- | Pass a GitHub token directly (from `auth`) |
116
+ | `--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
117
  | `--claude-code` | `-c` | `false` | Generate a Claude Code launch command |
118
118
  | `--show-token` | -- | `false` | Display tokens on auth and refresh |
119
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`. |
@@ -189,30 +189,31 @@ All fields are optional. The full schema:
189
189
 
190
190
  | Field | Type | Default | Description |
191
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)) |
192
+ | `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 |
193
+ | `modelRewrites` | `{ from, to }[]` | `[]` | Glob-pattern model substitution rules (see [Model Rewrites](#model-rewrites)) |
194
+ | `modelFallback` | `object` | built-in family defaults | Override default model fallbacks (see [Customizing Fallbacks](#customizing-fallbacks)) |
195
195
  | `modelFallback.claudeOpus` | `string` | `claude-opus-5` | Fallback for `claude-opus-*` models |
196
196
  | `modelFallback.claudeSonnet` | `string` | `claude-sonnet-5` | Fallback for `claude-sonnet-*` models |
197
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)) |
198
+ | `smallModel` | `string` | unset | Target model for compact request routing (see [Small-Model Routing](#small-model-routing)) |
199
199
  | `compactUseSmallModel` | `boolean` | `false` | Route compact/summarization requests to `smallModel` |
200
200
  | `useFunctionApplyPatch` | `boolean` | `true` | Rewrite `apply_patch` custom tool as function tool on Responses path |
201
201
  | `responsesApiAutoCompactInput` | `boolean` | `false` | Automatically trim Responses `input` to the latest `compaction` item |
202
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) |
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; the built-in reasoning-model rule remains active unless replaced (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
+ | `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 |
207
+ | `responsesOfficialEmulator` | `boolean` | `false` | Enable local OpenAI-style Responses state emulation for `previous_response_id`, `conversation`, retrieve, input_items, delete, and input_tokens |
208
+ | `responsesOfficialEmulatorTtlSeconds` | `number` | `14400` | In-memory TTL for locally emulated Responses state |
209
+ | `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
210
  | `upstreamQueueConcurrency` | `number` | `10` | Maximum concurrent Copilot upstream requests |
210
211
  | `upstreamQueueMaxRetries` | `number` | `1` | Maximum retries across capacity and approved pre-connection failures (`0..2`) |
211
212
  | `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
+ | `overloadFallbacks` | `Record<string, string>` | `{}` (disabled) | Exact effective-model mappings for one opt-in fallback dispatch after terminal model `529` |
213
214
  | `upstreamQueueBaseDelaySeconds` | `number` | `2` | Base delay (seconds) for upstream retry backoff when `Retry-After` is absent |
214
215
  | `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
+ | `gheDomain` | `string` | unset | GitHub Enterprise Cloud company domain (persisted automatically after GHE.com auth) |
216
217
 
217
218
  Example:
218
219
 
@@ -229,9 +230,10 @@ Example:
229
230
  "compactUseSmallModel": true,
230
231
  "useFunctionApplyPatch": true,
231
232
  "responsesApiAutoCompactInput": false,
232
- "responsesApiAutoContextManagement": false,
233
- "responsesApiContextManagementModels": ["gpt-5", "gpt-5-mini"],
234
- "responsesOfficialEmulator": false,
233
+ "responsesApiAutoContextManagement": false,
234
+ "responsesApiContextManagementModels": ["gpt-5", "gpt-5-mini"],
235
+ "chatCompletionsUseMaxCompletionTokens": [],
236
+ "responsesOfficialEmulator": false,
235
237
  "responsesOfficialEmulatorTtlSeconds": 14400,
236
238
  "modelReasoningEfforts": {
237
239
  "gpt-5": "high",
@@ -359,9 +361,11 @@ The proxy authenticates with GitHub using the [device code OAuth flow](https://d
359
361
 
360
362
  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
363
 
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.
364
+ 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.
365
+
366
+ 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).
367
+
368
+ 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
369
 
366
370
  ### Request Routing
367
371
 
@@ -402,22 +406,37 @@ This keeps the existing chat pipeline stable while allowing newer Copilot models
402
406
  | `POST` | `/v1/messages` | Messages API with per-model routing across native Messages, Responses translation, or chat-completions fallback |
403
407
  | `POST` | `/v1/messages/count_tokens` | Token counting |
404
408
 
405
- **Utility:**
409
+ **Utility:**
406
410
 
407
411
  | Method | Path | Description |
408
412
  |--------|------|-------------|
409
413
  | `GET` | `/health` | Liveness/readiness probe — returns `{ status, copilotToken, modelsLoaded, version }` |
410
414
  | `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`.
415
+ | `GET` | `/token` | Inspect the current Copilot token |
416
+
417
+ **Local Dashboard (read-only):**
418
+
419
+ | Method | Path | Description |
420
+ |--------|------|-------------|
421
+ | `GET` | `/dashboard` | Dashboard application |
422
+ | `GET` | `/dashboard/styles.css` | Dashboard stylesheet |
423
+ | `GET` | `/dashboard/app.js` | Dashboard client script |
424
+ | `GET` | `/dashboard/api/overview` | Process, authentication, quota, request, and queue summary |
425
+ | `GET` | `/dashboard/api/models` | Upstream model metadata and effective proxy capabilities |
426
+ | `GET` | `/dashboard/api/behavior` | Active routing, compatibility policies, strategies, and effect counters |
427
+ | `GET` | `/dashboard/api/requests` | Active requests and the most recent 256 completed request summaries |
428
+
429
+ 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.
430
+
431
+ > **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 and Dashboard endpoints are root-only and not exposed under `/v1`.
414
432
 
415
433
  ## Responses Compatibility
416
434
 
417
435
  `/v1/responses` is designed to stay close to the OpenAI wire format while making Copilot limitations explicit:
418
436
 
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
437
+ - requests are validated before any mutation
438
+ - 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
439
+ - 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
440
  - official `text.format` options are modeled explicitly, including `text`, `json_object`, and `json_schema`
422
441
  - 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
442
  - emulator state is memory-only and expires after `responsesOfficialEmulatorTtlSeconds` (default `14400`, or 4 hours)
@@ -427,9 +446,9 @@ This keeps the existing chat pipeline stable while allowing newer Copilot models
427
446
  - automatic trimming of Responses `input` to the latest `compaction` item is disabled by default and only applies when `responsesApiAutoCompactInput` is `true`
428
447
  - reasoning defaults for Anthropic -> Responses translation can be tuned with `modelReasoningEfforts`
429
448
  - 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
449
+ - 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)
450
+ - external image URLs on the Responses path fail explicitly with `400`; use `file_id` or data URL image input instead
451
+ - 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
452
 
434
453
  Example opt-in configuration for these two Responses-specific policies:
435
454
 
@@ -451,7 +470,7 @@ Some Copilot models reject request parameters that the OpenAI wire format allows
451
470
 
452
471
  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
472
 
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.
473
+ - **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
474
  - **`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
475
  - **`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
476
 
@@ -461,7 +480,7 @@ Stripped parameters are removed entirely (never sent as `null`), because upstrea
461
480
  {
462
481
  "responsesApiParameterFilters": [
463
482
  { "models": ["gpt-5*", "o1*"], "params": ["temperature", "top_p"] },
464
- { "models": ["some-model"], "params": ["top_k"] }
483
+ { "models": ["some-model"], "params": ["service_tier"] }
465
484
  ],
466
485
  "responsesApiParameterFiltersReplaceDefault": false
467
486
  }
@@ -516,16 +535,19 @@ services:
516
535
  ```bash
517
536
  git clone https://github.com/wxxb789/ghc-proxy.git
518
537
  cd ghc-proxy
519
- bun install
520
- bun run dev
538
+ bun install
539
+ bun run dev # Start with --watch
540
+ # Or use the production-style source command:
541
+ bun run start
521
542
  ```
522
543
 
523
544
  ## Development
524
545
 
525
546
  ```bash
526
- bun install # Install dependencies
527
- bun run dev # Start with --watch
528
- bun run build # Build with tsdown
547
+ bun install # Install dependencies
548
+ bun run dev # Start with --watch
549
+ bun run start # Start without --watch
550
+ bun run build # Build with tsdown
529
551
  bun run lint # ESLint
530
552
  bun run typecheck # tsc --noEmit
531
553
  bun test # Run tests