ghc-proxy 0.10.0 → 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.
- package/README.md +59 -37
- package/dist/main.mjs +344 -145
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
|
|
|
@@ -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` | -- |
|
|
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` |
|
|
193
|
-
| `modelRewrites` | `{ from, to }[]` |
|
|
194
|
-
| `modelFallback` | `object` |
|
|
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` |
|
|
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[]` |
|
|
204
|
-
| `responsesApiParameterFilters` | `{ models, params }[]` |
|
|
205
|
-
| `responsesApiParameterFiltersReplaceDefault` | `boolean` | `false` | Disable the built-in reasoning-model default rule so only your `responsesApiParameterFilters` apply |
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
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>` |
|
|
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` |
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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`
|
|
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": ["
|
|
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
|
|
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
|
package/dist/main.mjs
CHANGED
|
@@ -6735,14 +6735,27 @@ var FixedRing = class {
|
|
|
6735
6735
|
}
|
|
6736
6736
|
return values;
|
|
6737
6737
|
}
|
|
6738
|
+
updateFirst(predicate, update) {
|
|
6739
|
+
for (let offset = 0; offset < this.size; offset++) {
|
|
6740
|
+
const index = (this.next - 1 - offset + this.entries.length) % this.entries.length;
|
|
6741
|
+
const value = this.entries[index];
|
|
6742
|
+
if (value !== void 0 && predicate(value)) {
|
|
6743
|
+
this.entries[index] = update(value);
|
|
6744
|
+
return true;
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6747
|
+
return false;
|
|
6748
|
+
}
|
|
6738
6749
|
};
|
|
6739
6750
|
var RequestActivityStore = class {
|
|
6740
6751
|
active = /* @__PURE__ */ new Map();
|
|
6741
6752
|
recent = new FixedRing(256);
|
|
6742
6753
|
effectCounts = {};
|
|
6754
|
+
effectBuffers = /* @__PURE__ */ new Map();
|
|
6743
6755
|
started = 0;
|
|
6744
6756
|
completed = 0;
|
|
6745
6757
|
failed = 0;
|
|
6758
|
+
aborted = 0;
|
|
6746
6759
|
now;
|
|
6747
6760
|
constructor(now = Date.now) {
|
|
6748
6761
|
this.now = now;
|
|
@@ -6787,11 +6800,28 @@ var RequestActivityStore = class {
|
|
|
6787
6800
|
const request = this.active.get(requestId);
|
|
6788
6801
|
if (request) request.selectedStrategy = sanitizeTag(strategy);
|
|
6789
6802
|
}
|
|
6803
|
+
beginEffectBuffer(requestId) {
|
|
6804
|
+
if (this.active.has(requestId) && !this.effectBuffers.has(requestId)) this.effectBuffers.set(requestId, {});
|
|
6805
|
+
}
|
|
6806
|
+
commitEffectBuffer(requestId) {
|
|
6807
|
+
const buffer = this.effectBuffers.get(requestId);
|
|
6808
|
+
if (!buffer) return;
|
|
6809
|
+
this.effectBuffers.delete(requestId);
|
|
6810
|
+
for (const [effect, count] of Object.entries(buffer)) if (count) this.recordEffect(requestId, effect, count);
|
|
6811
|
+
}
|
|
6812
|
+
discardEffectBuffer(requestId) {
|
|
6813
|
+
this.effectBuffers.delete(requestId);
|
|
6814
|
+
}
|
|
6790
6815
|
recordEffect(requestId, effect, count = 1) {
|
|
6791
6816
|
if (!Number.isFinite(count) || count <= 0) return;
|
|
6792
6817
|
const request = this.active.get(requestId);
|
|
6793
6818
|
if (!request) return;
|
|
6794
6819
|
const normalizedCount = Math.floor(count);
|
|
6820
|
+
const buffer = this.effectBuffers.get(requestId);
|
|
6821
|
+
if (buffer) {
|
|
6822
|
+
buffer[effect] = (buffer[effect] ?? 0) + normalizedCount;
|
|
6823
|
+
return;
|
|
6824
|
+
}
|
|
6795
6825
|
this.effectCounts[effect] = (this.effectCounts[effect] ?? 0) + normalizedCount;
|
|
6796
6826
|
request.effectCounts[effect] = (request.effectCounts[effect] ?? 0) + normalizedCount;
|
|
6797
6827
|
}
|
|
@@ -6801,16 +6831,33 @@ var RequestActivityStore = class {
|
|
|
6801
6831
|
}
|
|
6802
6832
|
markStreaming(requestId) {
|
|
6803
6833
|
const request = this.active.get(requestId);
|
|
6804
|
-
if (request) request.state = "streaming";
|
|
6834
|
+
if (request && request.state !== "aborted") request.state = "streaming";
|
|
6835
|
+
}
|
|
6836
|
+
markAborted(requestId) {
|
|
6837
|
+
const request = this.active.get(requestId);
|
|
6838
|
+
if (request) {
|
|
6839
|
+
request.state = "aborted";
|
|
6840
|
+
return;
|
|
6841
|
+
}
|
|
6842
|
+
if (this.recent.updateFirst((request) => request.requestId === requestId && request.state === "completed", (request) => ({
|
|
6843
|
+
...request,
|
|
6844
|
+
state: "aborted"
|
|
6845
|
+
}))) {
|
|
6846
|
+
this.completed--;
|
|
6847
|
+
this.aborted++;
|
|
6848
|
+
}
|
|
6805
6849
|
}
|
|
6806
6850
|
complete(requestId, status) {
|
|
6807
6851
|
const request = this.active.get(requestId);
|
|
6808
6852
|
if (!request) return false;
|
|
6809
6853
|
this.active.delete(requestId);
|
|
6810
|
-
|
|
6811
|
-
const
|
|
6854
|
+
this.effectBuffers.delete(requestId);
|
|
6855
|
+
const aborted = request.state === "aborted" && request.errorSummary === void 0;
|
|
6856
|
+
const failed = !aborted && (status >= 400 || request.errorSummary !== void 0);
|
|
6857
|
+
const completedRequest = projectRequest(request, Math.max(0, this.now() - request.startedAtMs), aborted ? "aborted" : failed ? "failed" : "completed", status);
|
|
6812
6858
|
this.recent.push(completedRequest);
|
|
6813
|
-
this.
|
|
6859
|
+
if (aborted) this.aborted++;
|
|
6860
|
+
else this.completed++;
|
|
6814
6861
|
if (failed) this.failed++;
|
|
6815
6862
|
return true;
|
|
6816
6863
|
}
|
|
@@ -6822,7 +6869,8 @@ var RequestActivityStore = class {
|
|
|
6822
6869
|
totals: {
|
|
6823
6870
|
started: this.started,
|
|
6824
6871
|
completed: this.completed,
|
|
6825
|
-
failed: this.failed
|
|
6872
|
+
failed: this.failed,
|
|
6873
|
+
aborted: this.aborted
|
|
6826
6874
|
},
|
|
6827
6875
|
effectCounts: { ...this.effectCounts }
|
|
6828
6876
|
};
|
|
@@ -6834,18 +6882,21 @@ var RequestActivityStore = class {
|
|
|
6834
6882
|
totals: {
|
|
6835
6883
|
started: this.started,
|
|
6836
6884
|
completed: this.completed,
|
|
6837
|
-
failed: this.failed
|
|
6885
|
+
failed: this.failed,
|
|
6886
|
+
aborted: this.aborted
|
|
6838
6887
|
},
|
|
6839
6888
|
effectCounts: { ...this.effectCounts }
|
|
6840
6889
|
};
|
|
6841
6890
|
}
|
|
6842
6891
|
reset() {
|
|
6843
6892
|
this.active.clear();
|
|
6893
|
+
this.effectBuffers.clear();
|
|
6844
6894
|
this.recent = new FixedRing(256);
|
|
6845
6895
|
for (const key of Object.keys(this.effectCounts)) delete this.effectCounts[key];
|
|
6846
6896
|
this.started = 0;
|
|
6847
6897
|
this.completed = 0;
|
|
6848
6898
|
this.failed = 0;
|
|
6899
|
+
this.aborted = 0;
|
|
6849
6900
|
}
|
|
6850
6901
|
};
|
|
6851
6902
|
function classifyObservedEndpoint(rawUrl) {
|
|
@@ -6920,14 +6971,21 @@ function readSafeErrorCategory(error) {
|
|
|
6920
6971
|
const body = "body" in error ? error.body : void 0;
|
|
6921
6972
|
if (typeof body !== "object" || body === null) return void 0;
|
|
6922
6973
|
const nested = "error" in body ? body.error : void 0;
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6974
|
+
if (typeof nested === "object" && nested !== null) {
|
|
6975
|
+
const code = "code" in nested ? nested.code : void 0;
|
|
6976
|
+
if (isObservedErrorCategory(code)) return code;
|
|
6977
|
+
const type = "type" in nested ? nested.type : void 0;
|
|
6978
|
+
return isObservedErrorCategory(type) ? type : void 0;
|
|
6979
|
+
}
|
|
6980
|
+
const type = "type" in body ? body.type : void 0;
|
|
6981
|
+
return isObservedErrorCategory(type) ? type : void 0;
|
|
6927
6982
|
} catch {
|
|
6928
6983
|
return;
|
|
6929
6984
|
}
|
|
6930
6985
|
}
|
|
6986
|
+
function isObservedErrorCategory(value) {
|
|
6987
|
+
return typeof value === "string" && OBSERVED_ERROR_CATEGORIES.has(value);
|
|
6988
|
+
}
|
|
6931
6989
|
//#endregion
|
|
6932
6990
|
//#region src/state/runtime.ts
|
|
6933
6991
|
var RuntimeStore = class {
|
|
@@ -7496,7 +7554,10 @@ var UpstreamRequestQueue = class {
|
|
|
7496
7554
|
let response;
|
|
7497
7555
|
try {
|
|
7498
7556
|
this.throwIfRecoveryExpired(recovery, lastConnectionError);
|
|
7499
|
-
if (context.fallbackAttempt)
|
|
7557
|
+
if (context.fallbackAttempt) {
|
|
7558
|
+
recovery.fallbackFetchStarted = true;
|
|
7559
|
+
recovery.onFallbackFetchStart?.();
|
|
7560
|
+
}
|
|
7500
7561
|
response = await this.fetchBeforeDeadline(fetcher, signal, recovery, lastConnectionError);
|
|
7501
7562
|
lastConnectionError = void 0;
|
|
7502
7563
|
} catch (error) {
|
|
@@ -8745,7 +8806,7 @@ const checkUsage = defineCommand({
|
|
|
8745
8806
|
});
|
|
8746
8807
|
//#endregion
|
|
8747
8808
|
//#region src/util/version.ts
|
|
8748
|
-
const VERSION = "0.10.
|
|
8809
|
+
const VERSION = "0.10.1";
|
|
8749
8810
|
//#endregion
|
|
8750
8811
|
//#region src/debug.ts
|
|
8751
8812
|
function getRuntimeInfo() {
|
|
@@ -48660,8 +48721,8 @@ async function getTokenCount(payload, model) {
|
|
|
48660
48721
|
output: outputTokens
|
|
48661
48722
|
};
|
|
48662
48723
|
}
|
|
48663
|
-
async function
|
|
48664
|
-
return (await getEncoder(getTokenizerFromModel(model))).encode(JSON.stringify(
|
|
48724
|
+
async function estimateSerializedTokens(value, model) {
|
|
48725
|
+
return (await getEncoder(getTokenizerFromModel(model))).encode(JSON.stringify(value)).length;
|
|
48665
48726
|
}
|
|
48666
48727
|
//#endregion
|
|
48667
48728
|
//#region \0@oxc-project+runtime@0.134.0/helpers/esm/taggedTemplateLiteral.js
|
|
@@ -48671,7 +48732,7 @@ function _taggedTemplateLiteral(e, t) {
|
|
|
48671
48732
|
//#endregion
|
|
48672
48733
|
//#region src/routes/dashboard/assets.ts
|
|
48673
48734
|
var _templateObject;
|
|
48674
|
-
const DASHBOARD_HTML = String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>ghc-proxy Dashboard</title>\n <link rel=\"icon\" href=\"data:,\">\n <link rel=\"stylesheet\" href=\"/dashboard/styles.css\">\n</head>\n<body>\n <header class=\"app-header\">\n <div class=\"brand-block\">\n <div class=\"brand\">ghc-proxy</div>\n <div class=\"status-line\">\n <span id=\"health-dot\" class=\"status-dot unknown\"></span>\n <span id=\"health-label\">Connecting</span>\n <span id=\"version-label\"></span>\n </div>\n </div>\n <nav class=\"tabs\" aria-label=\"Dashboard views\">\n <button type=\"button\" class=\"tab active\" data-tab=\"overview\">Overview</button>\n <button type=\"button\" class=\"tab\" data-tab=\"models\">Models</button>\n <button type=\"button\" class=\"tab\" data-tab=\"behavior\">Behavior</button>\n <button type=\"button\" class=\"tab\" data-tab=\"requests\">Requests</button>\n </nav>\n <div class=\"header-actions\">\n <label class=\"toggle\"><input id=\"live-refresh\" type=\"checkbox\" checked> Live</label>\n <label class=\"theme-toggle\">\n <input id=\"theme-toggle\" type=\"checkbox\" role=\"switch\" aria-label=\"Use dark theme\">\n <span id=\"theme-label\">Light</span>\n </label>\n <button id=\"refresh-button\" type=\"button\" class=\"command\">Refresh</button>\n </div>\n </header>\n\n <main>\n <section id=\"view-overview\" class=\"view active\" data-view=\"overview\">\n <div class=\"metric-strip\" aria-label=\"Runtime summary\">\n <div><span class=\"metric-label\">Uptime</span><strong id=\"metric-uptime\">-</strong></div>\n <div><span class=\"metric-label\">Active</span><strong id=\"metric-active\">0</strong></div>\n <div><span class=\"metric-label\">Completed</span><strong id=\"metric-completed\">0</strong></div>\n <div><span class=\"metric-label\">Failed</span><strong id=\"metric-failed\">0</strong></div>\n <div><span class=\"metric-label\">Queue</span><strong id=\"metric-queue\">0 / 0</strong></div>\n </div>\n\n <div class=\"overview-grid\">\n <section class=\"panel\">\n <div class=\"section-heading\"><h1>Runtime</h1><span id=\"overview-updated\" class=\"muted\"></span></div>\n <dl id=\"runtime-details\" class=\"kv-list\"></dl>\n </section>\n <section class=\"panel\">\n <div class=\"section-heading\"><h1>Authentication</h1></div>\n <div class=\"table-scroll\"><table><thead><tr><th>Service</th><th>Status</th><th>Identity</th><th>Last check</th></tr></thead><tbody id=\"auth-body\"></tbody></table></div>\n </section>\n </div>\n\n <section class=\"panel full-width\">\n <div class=\"section-heading\"><h1>Quota</h1><span id=\"quota-status\" class=\"muted\"></span></div>\n <div class=\"table-scroll\"><table><thead><tr><th>Pool</th><th>Remaining</th><th>Entitlement</th><th>Percent</th><th>Overage</th></tr></thead><tbody id=\"quota-body\"></tbody></table></div>\n </section>\n </section>\n\n <section id=\"view-models\" class=\"view\" data-view=\"models\" hidden>\n <div class=\"toolbar model-toolbar\">\n <div><h1>Models</h1><span id=\"model-count\" class=\"muted\"></span></div>\n <div class=\"model-controls\">\n <label class=\"toggle\"><input id=\"model-group-vendor\" type=\"checkbox\" checked> Group vendor</label>\n <select id=\"model-sort\" aria-label=\"Order models by name\">\n <option value=\"asc\">Name A-Z</option>\n <option value=\"desc\">Name Z-A</option>\n </select>\n <input id=\"model-filter\" type=\"search\" placeholder=\"Filter models\" autocomplete=\"off\">\n <button id=\"copy-models\" type=\"button\" class=\"command\" disabled>Copy selected (0)</button>\n </div>\n </div>\n <div id=\"model-copy-status\" class=\"muted model-copy-status\" role=\"status\" aria-live=\"polite\"></div>\n <div class=\"table-scroll model-table\"><table><thead><tr><th class=\"model-select-header\" aria-label=\"Select model\"></th><th>Model</th><th>Vendor</th><th>Messages route</th><th>Upstream endpoints</th><th>Limits</th><th>Capabilities</th><th>Proxy compatibility</th></tr></thead><tbody id=\"models-body\"></tbody></table></div>\n </section>\n\n <section id=\"view-behavior\" class=\"view\" data-view=\"behavior\" hidden>\n <div class=\"toolbar\"><div><h1>Behavior</h1><span class=\"muted\">Current process configuration</span></div></div>\n <div class=\"behavior-grid\">\n <section class=\"panel\"><div class=\"section-heading\"><h2>Model routing</h2></div><dl id=\"behavior-routing\" class=\"kv-list\"></dl></section>\n <section class=\"panel\"><div class=\"section-heading\"><h2>Parameters and context</h2></div><dl id=\"behavior-parameters\" class=\"kv-list\"></dl></section>\n </div>\n <section class=\"panel full-width\">\n <div class=\"section-heading\"><h2>Effects since startup</h2></div>\n <div class=\"table-scroll\"><table><thead><tr><th>Category</th><th>Effect</th><th>Count</th></tr></thead><tbody id=\"effects-body\"></tbody></table></div>\n </section>\n </section>\n\n <section id=\"view-requests\" class=\"view requests-view\" data-view=\"requests\" hidden>\n <div class=\"toolbar\"><div><h1>Requests</h1><span id=\"request-count\" class=\"muted\"></span></div><span class=\"muted\">256
|
|
48735
|
+
const DASHBOARD_HTML = String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>ghc-proxy Dashboard</title>\n <link rel=\"icon\" href=\"data:,\">\n <link rel=\"stylesheet\" href=\"/dashboard/styles.css\">\n</head>\n<body>\n <header class=\"app-header\">\n <div class=\"brand-block\">\n <div class=\"brand\">ghc-proxy</div>\n <div class=\"status-line\">\n <span id=\"health-dot\" class=\"status-dot unknown\"></span>\n <span id=\"health-label\">Connecting</span>\n <span id=\"version-label\"></span>\n </div>\n </div>\n <nav class=\"tabs\" aria-label=\"Dashboard views\">\n <button type=\"button\" class=\"tab active\" data-tab=\"overview\">Overview</button>\n <button type=\"button\" class=\"tab\" data-tab=\"models\">Models</button>\n <button type=\"button\" class=\"tab\" data-tab=\"behavior\">Behavior</button>\n <button type=\"button\" class=\"tab\" data-tab=\"requests\">Requests</button>\n </nav>\n <div class=\"header-actions\">\n <label class=\"toggle\"><input id=\"live-refresh\" type=\"checkbox\" checked> Live</label>\n <label class=\"theme-toggle\">\n <input id=\"theme-toggle\" type=\"checkbox\" role=\"switch\" aria-label=\"Use dark theme\">\n <span id=\"theme-label\">Light</span>\n </label>\n <button id=\"refresh-button\" type=\"button\" class=\"command\">Refresh</button>\n </div>\n </header>\n\n <main>\n <section id=\"view-overview\" class=\"view active\" data-view=\"overview\">\n <div class=\"metric-strip\" aria-label=\"Runtime summary\">\n <div><span class=\"metric-label\">Uptime</span><strong id=\"metric-uptime\">-</strong></div>\n <div><span class=\"metric-label\">Active</span><strong id=\"metric-active\">0</strong></div>\n <div><span class=\"metric-label\">Completed</span><strong id=\"metric-completed\">0</strong></div>\n <div><span class=\"metric-label\">Failed</span><strong id=\"metric-failed\">0</strong></div>\n <div><span class=\"metric-label\">Aborted</span><strong id=\"metric-aborted\">0</strong></div>\n <div><span class=\"metric-label\">Queue</span><strong id=\"metric-queue\">0 / 0</strong></div>\n </div>\n\n <div class=\"overview-grid\">\n <section class=\"panel\">\n <div class=\"section-heading\"><h1>Runtime</h1><span id=\"overview-updated\" class=\"muted\"></span></div>\n <dl id=\"runtime-details\" class=\"kv-list\"></dl>\n </section>\n <section class=\"panel\">\n <div class=\"section-heading\"><h1>Authentication</h1></div>\n <div class=\"table-scroll\"><table><thead><tr><th>Service</th><th>Status</th><th>Identity</th><th>Last check</th></tr></thead><tbody id=\"auth-body\"></tbody></table></div>\n </section>\n </div>\n\n <section class=\"panel full-width\">\n <div class=\"section-heading\"><h1>Quota</h1><span id=\"quota-status\" class=\"muted\"></span></div>\n <div class=\"table-scroll\"><table><thead><tr><th>Pool</th><th>Remaining</th><th>Entitlement</th><th>Percent</th><th>Overage</th></tr></thead><tbody id=\"quota-body\"></tbody></table></div>\n </section>\n </section>\n\n <section id=\"view-models\" class=\"view\" data-view=\"models\" hidden>\n <div class=\"toolbar model-toolbar\">\n <div><h1>Models</h1><span id=\"model-count\" class=\"muted\"></span></div>\n <div class=\"model-controls\">\n <label class=\"toggle\"><input id=\"model-group-vendor\" type=\"checkbox\" checked> Group vendor</label>\n <select id=\"model-sort\" aria-label=\"Order models by name\">\n <option value=\"asc\">Name A-Z</option>\n <option value=\"desc\">Name Z-A</option>\n </select>\n <input id=\"model-filter\" type=\"search\" placeholder=\"Filter models\" autocomplete=\"off\">\n <button id=\"copy-models\" type=\"button\" class=\"command\" disabled>Copy selected (0)</button>\n </div>\n </div>\n <div id=\"model-copy-status\" class=\"muted model-copy-status\" role=\"status\" aria-live=\"polite\"></div>\n <div class=\"table-scroll model-table\"><table><thead><tr><th class=\"model-select-header\" aria-label=\"Select model\"></th><th>Model</th><th>Vendor</th><th>Messages route</th><th>Upstream endpoints</th><th>Limits</th><th>Capabilities</th><th>Proxy compatibility</th></tr></thead><tbody id=\"models-body\"></tbody></table></div>\n </section>\n\n <section id=\"view-behavior\" class=\"view\" data-view=\"behavior\" hidden>\n <div class=\"toolbar\"><div><h1>Behavior</h1><span class=\"muted\">Current process configuration</span></div></div>\n <div class=\"behavior-grid\">\n <section class=\"panel\"><div class=\"section-heading\"><h2>Model routing</h2></div><dl id=\"behavior-routing\" class=\"kv-list\"></dl></section>\n <section class=\"panel\"><div class=\"section-heading\"><h2>Parameters and context</h2></div><dl id=\"behavior-parameters\" class=\"kv-list\"></dl></section>\n </div>\n <section class=\"panel full-width\">\n <div class=\"section-heading\"><h2>Effects since startup</h2></div>\n <div class=\"table-scroll\"><table><thead><tr><th>Category</th><th>Effect</th><th>Count</th></tr></thead><tbody id=\"effects-body\"></tbody></table></div>\n </section>\n </section>\n\n <section id=\"view-requests\" class=\"view requests-view\" data-view=\"requests\" hidden>\n <div class=\"toolbar\"><div><h1>Requests</h1><span id=\"request-count\" class=\"muted\"></span></div><span class=\"muted\">256 finished max</span></div>\n <div class=\"request-layout\">\n <div class=\"request-list table-scroll\"><table><thead><tr><th>State</th><th>Endpoint</th><th>Model</th><th>Strategy</th><th>Status</th><th>Duration</th><th>Started</th></tr></thead><tbody id=\"requests-body\"></tbody></table></div>\n <aside class=\"request-detail\" aria-label=\"Selected request details\">\n <div class=\"section-heading\"><h2>Request detail</h2></div>\n <pre id=\"request-detail\">No request selected</pre>\n </aside>\n </div>\n </section>\n </main>\n\n <div id=\"error-banner\" class=\"error-banner\" role=\"status\" hidden></div>\n <script src=\"/dashboard/app.js\" defer><\/script>\n</body>\n</html>"])));
|
|
48675
48736
|
const DASHBOARD_CSS = String.raw`:root {
|
|
48676
48737
|
color-scheme: light;
|
|
48677
48738
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -48804,7 +48865,7 @@ h2 { font-size: 15px; }
|
|
|
48804
48865
|
|
|
48805
48866
|
.metric-strip {
|
|
48806
48867
|
display: grid;
|
|
48807
|
-
grid-template-columns: repeat(
|
|
48868
|
+
grid-template-columns: repeat(6, minmax(100px, 1fr));
|
|
48808
48869
|
margin-bottom: 18px;
|
|
48809
48870
|
border: 1px solid var(--border);
|
|
48810
48871
|
background: var(--surface);
|
|
@@ -48848,7 +48909,7 @@ tbody tr:hover td { background: var(--surface-hover); }
|
|
|
48848
48909
|
.mono { font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace; font-size: 12px; overflow-wrap: anywhere; }
|
|
48849
48910
|
.badge { display: inline-flex; align-items: center; min-height: 22px; padding: 2px 7px; border: 1px solid var(--border); border-radius: 4px; background: var(--surface-subtle); color: var(--text-secondary); font-size: 11px; white-space: nowrap; }
|
|
48850
48911
|
.badge.ok, .badge.completed { color: var(--ok); border-color: var(--ok-border); background: var(--ok-bg); }
|
|
48851
|
-
.badge.degraded, .badge.streaming, .badge.in_flight { color: var(--warn); border-color: var(--warn-border); background: var(--warn-bg); }
|
|
48912
|
+
.badge.degraded, .badge.streaming, .badge.in_flight, .badge.aborted { color: var(--warn); border-color: var(--warn-border); background: var(--warn-bg); }
|
|
48852
48913
|
.badge.failed, .badge.missing { color: var(--bad); border-color: var(--bad-border); background: var(--bad-bg); }
|
|
48853
48914
|
|
|
48854
48915
|
.behavior-grid { margin-bottom: 18px; }
|
|
@@ -48870,7 +48931,7 @@ tbody tr:hover td { background: var(--surface-hover); }
|
|
|
48870
48931
|
.metric-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
48871
48932
|
.metric-strip > div { border-bottom: 1px solid var(--border); }
|
|
48872
48933
|
.metric-strip > div:nth-child(2n) { border-right: 0; }
|
|
48873
|
-
.metric-strip > div:last-child
|
|
48934
|
+
.metric-strip > div:nth-last-child(-n + 2) { border-bottom: 0; }
|
|
48874
48935
|
.overview-grid, .behavior-grid, .request-layout { grid-template-columns: 1fr; }
|
|
48875
48936
|
.request-list, .request-detail pre { max-height: none; }
|
|
48876
48937
|
}
|
|
@@ -49017,6 +49078,7 @@ function renderOverview(data) {
|
|
|
49017
49078
|
byId('metric-active').textContent = formatNumber(data.activity.activeRequests);
|
|
49018
49079
|
byId('metric-completed').textContent = formatNumber(data.activity.completed);
|
|
49019
49080
|
byId('metric-failed').textContent = formatNumber(data.activity.failed);
|
|
49081
|
+
byId('metric-aborted').textContent = formatNumber(data.activity.aborted);
|
|
49020
49082
|
const queue = data.activity.upstreamQueue || {};
|
|
49021
49083
|
byId('metric-queue').textContent = formatNumber(queue.active) + ' / ' + formatNumber(queue.pending);
|
|
49022
49084
|
byId('overview-updated').textContent = 'Updated ' + new Date().toLocaleTimeString();
|
|
@@ -49024,7 +49086,7 @@ function renderOverview(data) {
|
|
|
49024
49086
|
const runtime = byId('runtime-details');
|
|
49025
49087
|
clearNode(runtime);
|
|
49026
49088
|
appendKv(runtime, 'Started', formatDate(data.startedAt));
|
|
49027
|
-
appendKv(runtime, 'Recent
|
|
49089
|
+
appendKv(runtime, 'Recent finished', data.activity.recentRequests);
|
|
49028
49090
|
appendKv(runtime, 'Upstream slots', formatNumber(queue.active) + ' / ' + formatNumber(queue.concurrency));
|
|
49029
49091
|
appendKv(runtime, 'Pending queue', formatNumber(queue.pending) + ' / ' + formatNumber(queue.maxPending));
|
|
49030
49092
|
appendKv(runtime, 'Cooldowns', (queue.accountCooldown ? 'account ' : '') + formatNumber(queue.modelCooldowns) + ' model');
|
|
@@ -49298,7 +49360,7 @@ function renderRequests(data) {
|
|
|
49298
49360
|
? dashboardState.requests[0].requestId
|
|
49299
49361
|
: null;
|
|
49300
49362
|
}
|
|
49301
|
-
byId('request-count').textContent = active.length + ' active / ' + recent.length + '
|
|
49363
|
+
byId('request-count').textContent = active.length + ' active / ' + recent.length + ' finished';
|
|
49302
49364
|
const body = byId('requests-body');
|
|
49303
49365
|
clearNode(body);
|
|
49304
49366
|
dashboardState.requests.forEach(function (request) {
|
|
@@ -50140,6 +50202,11 @@ function formatSearchResultBlock(block) {
|
|
|
50140
50202
|
].filter((part) => Boolean(part)).join("\n");
|
|
50141
50203
|
}
|
|
50142
50204
|
//#endregion
|
|
50205
|
+
//#region src/translator/anthropic/types.ts
|
|
50206
|
+
function isAnthropicBuiltinTool(tool) {
|
|
50207
|
+
return typeof tool.type === "string" && tool.type !== "custom";
|
|
50208
|
+
}
|
|
50209
|
+
//#endregion
|
|
50143
50210
|
//#region src/translator/anthropic/anthropic-normalizer.ts
|
|
50144
50211
|
function textBlock(text) {
|
|
50145
50212
|
return {
|
|
@@ -50280,11 +50347,11 @@ function normalizeAnthropicRequest(payload) {
|
|
|
50280
50347
|
topP: payload.top_p,
|
|
50281
50348
|
topK: payload.top_k,
|
|
50282
50349
|
userId: payload.metadata?.user_id,
|
|
50283
|
-
tools: payload.tools?.
|
|
50350
|
+
tools: payload.tools?.flatMap((tool) => !isAnthropicBuiltinTool(tool) && tool.name !== void 0 && tool.input_schema !== void 0 ? [{
|
|
50284
50351
|
name: tool.name,
|
|
50285
50352
|
description: tool.description,
|
|
50286
50353
|
inputSchema: tool.input_schema
|
|
50287
|
-
})
|
|
50354
|
+
}] : []),
|
|
50288
50355
|
toolChoice: normalizeToolChoice(payload.tool_choice),
|
|
50289
50356
|
thinking: normalizeThinking(payload.thinking),
|
|
50290
50357
|
outputEffort: payload.output_config?.effort ?? void 0,
|
|
@@ -50757,11 +50824,6 @@ function toConversationTurn(turn) {
|
|
|
50757
50824
|
};
|
|
50758
50825
|
}
|
|
50759
50826
|
function recordAnthropicRequestIssues(request, context) {
|
|
50760
|
-
if (request.serviceTier !== void 0) context.record({
|
|
50761
|
-
kind: "unsupported_service_tier",
|
|
50762
|
-
severity: "warning",
|
|
50763
|
-
message: "Anthropic service_tier is not supported by the upstream Copilot CAPI payload and was dropped."
|
|
50764
|
-
}, { fatalInStrict: true });
|
|
50765
50827
|
for (const turn of request.turns) {
|
|
50766
50828
|
if (turn.role === "user") {
|
|
50767
50829
|
for (const block of turn.blocks) if (block.kind === "tool_result" && block.isError) context.record({
|
|
@@ -50811,6 +50873,15 @@ function applyThinkingBudgetOverride(plan, request, options) {
|
|
|
50811
50873
|
}
|
|
50812
50874
|
function normalizeAnthropicConversation(payload, policy) {
|
|
50813
50875
|
const context = new TranslationContext(policy);
|
|
50876
|
+
if (payload.service_tier !== void 0) throw new TranslationFailure("Anthropic service_tier cannot be translated to Chat Completions.", {
|
|
50877
|
+
status: 400,
|
|
50878
|
+
kind: "unsupported_service_tier"
|
|
50879
|
+
});
|
|
50880
|
+
const builtinTool = payload.tools?.find(isAnthropicBuiltinTool);
|
|
50881
|
+
if (builtinTool) throw new TranslationFailure(`Anthropic built-in/toolset "${builtinTool.type ?? builtinTool.name ?? "unknown"}" cannot be translated to Chat Completions.`, {
|
|
50882
|
+
status: 400,
|
|
50883
|
+
kind: "unsupported_server_tool"
|
|
50884
|
+
});
|
|
50814
50885
|
const normalized = normalizeAnthropicRequest(payload);
|
|
50815
50886
|
recordAnthropicRequestIssues(normalized, context);
|
|
50816
50887
|
return {
|
|
@@ -52249,12 +52320,22 @@ function translateSystemPrompt(system) {
|
|
|
52249
52320
|
}
|
|
52250
52321
|
function convertAnthropicTools(tools) {
|
|
52251
52322
|
if (!tools || tools.length === 0) return null;
|
|
52252
|
-
return tools.map((tool) =>
|
|
52253
|
-
type
|
|
52254
|
-
|
|
52255
|
-
|
|
52256
|
-
|
|
52257
|
-
|
|
52323
|
+
return tools.map((tool) => {
|
|
52324
|
+
if (isAnthropicBuiltinTool(tool)) throw new TranslationFailure(`Anthropic built-in/toolset "${tool.type ?? tool.name ?? "unknown"}" cannot be translated to the Responses API.`, {
|
|
52325
|
+
status: 400,
|
|
52326
|
+
kind: "unsupported_server_tool"
|
|
52327
|
+
});
|
|
52328
|
+
if (tool.name === void 0 || tool.input_schema === void 0) throw new TranslationFailure("Anthropic function tools require both name and input_schema.", {
|
|
52329
|
+
status: 400,
|
|
52330
|
+
kind: "invalid_tool_schema"
|
|
52331
|
+
});
|
|
52332
|
+
return {
|
|
52333
|
+
type: "function",
|
|
52334
|
+
name: tool.name,
|
|
52335
|
+
parameters: normalizeFunctionParametersSchemaForCopilot(tool.input_schema),
|
|
52336
|
+
...tool.description ? { description: tool.description } : {}
|
|
52337
|
+
};
|
|
52338
|
+
});
|
|
52258
52339
|
}
|
|
52259
52340
|
function convertAnthropicToolChoice(choice) {
|
|
52260
52341
|
if (!choice) return "auto";
|
|
@@ -54356,6 +54437,69 @@ var cors = (config) => {
|
|
|
54356
54437
|
});
|
|
54357
54438
|
};
|
|
54358
54439
|
//#endregion
|
|
54440
|
+
//#region src/lib/upstream-signal.ts
|
|
54441
|
+
const DEFAULT_TIMEOUT_MS = 18e5;
|
|
54442
|
+
const CLIENT_ABORT_MARKER = "ghcProxyClientAbort";
|
|
54443
|
+
var ClientAbortError = class extends DOMException {
|
|
54444
|
+
[CLIENT_ABORT_MARKER] = true;
|
|
54445
|
+
constructor() {
|
|
54446
|
+
super("The client aborted the request.", "AbortError");
|
|
54447
|
+
}
|
|
54448
|
+
};
|
|
54449
|
+
function isClientAbortError(error) {
|
|
54450
|
+
return errorCauseChainSome(error, (candidate) => candidate[CLIENT_ABORT_MARKER] === true);
|
|
54451
|
+
}
|
|
54452
|
+
function createUpstreamDeadlineFromConfig(now = performance.now()) {
|
|
54453
|
+
const timeoutMs = authStore.upstreamTimeoutSeconds !== void 0 ? authStore.upstreamTimeoutSeconds * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
54454
|
+
return timeoutMs > 0 ? now + timeoutMs : null;
|
|
54455
|
+
}
|
|
54456
|
+
function createUpstreamSignal(clientSignal, timeoutMs = DEFAULT_TIMEOUT_MS, onClientAbort) {
|
|
54457
|
+
const controller = new AbortController();
|
|
54458
|
+
const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
54459
|
+
const onAbort = () => {
|
|
54460
|
+
onClientAbort?.();
|
|
54461
|
+
controller.abort(new ClientAbortError());
|
|
54462
|
+
};
|
|
54463
|
+
if (clientSignal && !clientSignal.aborted) clientSignal.addEventListener("abort", onAbort);
|
|
54464
|
+
return {
|
|
54465
|
+
signal: controller.signal,
|
|
54466
|
+
clientSignal,
|
|
54467
|
+
cleanup: () => {
|
|
54468
|
+
if (timeout) clearTimeout(timeout);
|
|
54469
|
+
clientSignal?.removeEventListener("abort", onAbort);
|
|
54470
|
+
}
|
|
54471
|
+
};
|
|
54472
|
+
}
|
|
54473
|
+
/**
|
|
54474
|
+
* Convenience wrapper that reads the upstream timeout from runtime config.
|
|
54475
|
+
*
|
|
54476
|
+
* This signal is a *total-duration* limit. Both runtimes separately apply an
|
|
54477
|
+
* ~300s **idle** timeout to `fetch` — Bun's is built in, Node's is undici's
|
|
54478
|
+
* `headersTimeout` / `bodyTimeout` default of `300e3` — which resets on every
|
|
54479
|
+
* byte received. A response that keeps streaming therefore runs past 300s and
|
|
54480
|
+
* is bounded only by this signal; a stalled one is rejected at ~300s by the
|
|
54481
|
+
* runtime instead. `isTimeoutLikeError` recognizes both runtimes' shapes so
|
|
54482
|
+
* every path maps to a 504.
|
|
54483
|
+
*/
|
|
54484
|
+
function createUpstreamSignalFromConfig(clientSignal, deadlineMonotonicMs = createUpstreamDeadlineFromConfig(), onClientAbort) {
|
|
54485
|
+
const remainingMs = deadlineMonotonicMs === null ? void 0 : deadlineMonotonicMs - performance.now();
|
|
54486
|
+
return {
|
|
54487
|
+
...remainingMs !== void 0 && remainingMs <= 0 ? createExpiredUpstreamSignal(clientSignal, onClientAbort) : createUpstreamSignal(clientSignal, remainingMs ?? 0, onClientAbort),
|
|
54488
|
+
deadlineMonotonicMs
|
|
54489
|
+
};
|
|
54490
|
+
}
|
|
54491
|
+
function createExpiredUpstreamSignal(clientSignal, onClientAbort) {
|
|
54492
|
+
const controller = new AbortController();
|
|
54493
|
+
controller.abort();
|
|
54494
|
+
const onAbort = () => onClientAbort?.();
|
|
54495
|
+
if (!clientSignal.aborted) clientSignal.addEventListener("abort", onAbort);
|
|
54496
|
+
return {
|
|
54497
|
+
signal: controller.signal,
|
|
54498
|
+
clientSignal,
|
|
54499
|
+
cleanup: () => clientSignal.removeEventListener("abort", onAbort)
|
|
54500
|
+
};
|
|
54501
|
+
}
|
|
54502
|
+
//#endregion
|
|
54359
54503
|
//#region src/deliver/index.ts
|
|
54360
54504
|
function deliverResult(request, result, modelMapping) {
|
|
54361
54505
|
setRequestModelMapping(request, modelMapping);
|
|
@@ -54621,10 +54765,23 @@ const anthropicMessageSchema = union([
|
|
|
54621
54765
|
}).loose()
|
|
54622
54766
|
]);
|
|
54623
54767
|
const anthropicToolSchema = object({
|
|
54624
|
-
|
|
54768
|
+
type: string().min(1).nullable().optional(),
|
|
54769
|
+
name: string().min(1).optional(),
|
|
54625
54770
|
description: string().optional(),
|
|
54626
|
-
input_schema: createObjectSchemaDefinitionSchema("tool input_schema must describe an object")
|
|
54627
|
-
}).loose()
|
|
54771
|
+
input_schema: createObjectSchemaDefinitionSchema("tool input_schema must describe an object").optional()
|
|
54772
|
+
}).loose().superRefine((tool, ctx) => {
|
|
54773
|
+
const isBuiltinTool = isAnthropicBuiltinTool(tool);
|
|
54774
|
+
if (!isBuiltinTool && tool.input_schema === void 0) ctx.addIssue({
|
|
54775
|
+
code: "custom",
|
|
54776
|
+
message: "tool input_schema must describe an object",
|
|
54777
|
+
path: ["input_schema"]
|
|
54778
|
+
});
|
|
54779
|
+
if (!isBuiltinTool && tool.name === void 0) ctx.addIssue({
|
|
54780
|
+
code: "custom",
|
|
54781
|
+
message: "function tools require a name",
|
|
54782
|
+
path: ["name"]
|
|
54783
|
+
});
|
|
54784
|
+
});
|
|
54628
54785
|
const anthropicToolChoiceSchema = union([
|
|
54629
54786
|
object({ type: literal("auto") }).loose(),
|
|
54630
54787
|
object({ type: literal("any") }).loose(),
|
|
@@ -54831,6 +54988,11 @@ const openAIChatPayloadSchema = object({
|
|
|
54831
54988
|
reasoning_effort: _enum(REASONING_EFFORT_VALUES).nullable().optional(),
|
|
54832
54989
|
thinking_budget: number().int().positive().nullable().optional()
|
|
54833
54990
|
}).loose().superRefine((payload, ctx) => {
|
|
54991
|
+
if (Object.hasOwn(payload, "top_k")) ctx.addIssue({
|
|
54992
|
+
code: "custom",
|
|
54993
|
+
message: "top_k is not supported by the OpenAI Chat Completions API",
|
|
54994
|
+
path: ["top_k"]
|
|
54995
|
+
});
|
|
54834
54996
|
const toolChoice = payload.tool_choice;
|
|
54835
54997
|
if (toolChoice && typeof toolChoice === "object" && "function" in toolChoice && !payload.tools?.some((tool) => tool.function.name === toolChoice.function.name)) ctx.addIssue({
|
|
54836
54998
|
code: "custom",
|
|
@@ -55135,6 +55297,11 @@ function createResponsesPayloadSchema(options) {
|
|
|
55135
55297
|
"priority"
|
|
55136
55298
|
]).nullable().optional()
|
|
55137
55299
|
}).loose().superRefine((payload, ctx) => {
|
|
55300
|
+
if (Object.hasOwn(payload, "top_k")) ctx.addIssue({
|
|
55301
|
+
code: "custom",
|
|
55302
|
+
message: "top_k is not supported by the OpenAI Responses API",
|
|
55303
|
+
path: ["top_k"]
|
|
55304
|
+
});
|
|
55138
55305
|
if (payload.previous_response_id && payload.conversation) ctx.addIssue({
|
|
55139
55306
|
code: "custom",
|
|
55140
55307
|
message: "previous_response_id cannot be used together with conversation",
|
|
@@ -55266,54 +55433,6 @@ protocolRegistry.register("responses", responsesProtocol);
|
|
|
55266
55433
|
protocolRegistry.register("responses-input-tokens", responsesInputTokensProtocol);
|
|
55267
55434
|
protocolRegistry.register("embeddings", embeddingsProtocol);
|
|
55268
55435
|
//#endregion
|
|
55269
|
-
//#region src/lib/upstream-signal.ts
|
|
55270
|
-
const DEFAULT_TIMEOUT_MS = 18e5;
|
|
55271
|
-
function createUpstreamDeadlineFromConfig(now = performance.now()) {
|
|
55272
|
-
const timeoutMs = authStore.upstreamTimeoutSeconds !== void 0 ? authStore.upstreamTimeoutSeconds * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
55273
|
-
return timeoutMs > 0 ? now + timeoutMs : null;
|
|
55274
|
-
}
|
|
55275
|
-
function createUpstreamSignal(clientSignal, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
55276
|
-
const controller = new AbortController();
|
|
55277
|
-
const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
55278
|
-
const onAbort = () => controller.abort();
|
|
55279
|
-
if (clientSignal && !clientSignal.aborted) clientSignal.addEventListener("abort", onAbort);
|
|
55280
|
-
return {
|
|
55281
|
-
signal: controller.signal,
|
|
55282
|
-
clientSignal,
|
|
55283
|
-
cleanup: () => {
|
|
55284
|
-
if (timeout) clearTimeout(timeout);
|
|
55285
|
-
clientSignal?.removeEventListener("abort", onAbort);
|
|
55286
|
-
}
|
|
55287
|
-
};
|
|
55288
|
-
}
|
|
55289
|
-
/**
|
|
55290
|
-
* Convenience wrapper that reads the upstream timeout from runtime config.
|
|
55291
|
-
*
|
|
55292
|
-
* This signal is a *total-duration* limit. Both runtimes separately apply an
|
|
55293
|
-
* ~300s **idle** timeout to `fetch` — Bun's is built in, Node's is undici's
|
|
55294
|
-
* `headersTimeout` / `bodyTimeout` default of `300e3` — which resets on every
|
|
55295
|
-
* byte received. A response that keeps streaming therefore runs past 300s and
|
|
55296
|
-
* is bounded only by this signal; a stalled one is rejected at ~300s by the
|
|
55297
|
-
* runtime instead. `isTimeoutLikeError` recognizes both runtimes' shapes so
|
|
55298
|
-
* every path maps to a 504.
|
|
55299
|
-
*/
|
|
55300
|
-
function createUpstreamSignalFromConfig(clientSignal, deadlineMonotonicMs = createUpstreamDeadlineFromConfig()) {
|
|
55301
|
-
const remainingMs = deadlineMonotonicMs === null ? void 0 : deadlineMonotonicMs - performance.now();
|
|
55302
|
-
return {
|
|
55303
|
-
...remainingMs !== void 0 && remainingMs <= 0 ? createExpiredUpstreamSignal(clientSignal) : createUpstreamSignal(clientSignal, remainingMs ?? 0),
|
|
55304
|
-
deadlineMonotonicMs
|
|
55305
|
-
};
|
|
55306
|
-
}
|
|
55307
|
-
function createExpiredUpstreamSignal(clientSignal) {
|
|
55308
|
-
const controller = new AbortController();
|
|
55309
|
-
controller.abort();
|
|
55310
|
-
return {
|
|
55311
|
-
signal: controller.signal,
|
|
55312
|
-
clientSignal,
|
|
55313
|
-
cleanup: () => {}
|
|
55314
|
-
};
|
|
55315
|
-
}
|
|
55316
|
-
//#endregion
|
|
55317
55436
|
//#region src/pipeline/runner.ts
|
|
55318
55437
|
async function runPipeline(params, config) {
|
|
55319
55438
|
const upstreamDeadlineMonotonicMs = createUpstreamDeadlineFromConfig();
|
|
@@ -55329,6 +55448,7 @@ async function runPipeline(params, config) {
|
|
|
55329
55448
|
const fallbackPossible = shouldPreservePristinePayload(config.protocol, baseSourceModel);
|
|
55330
55449
|
const pristinePayload = fallbackPossible ? structuredClone(payload) : payload;
|
|
55331
55450
|
const sourceAttempt = await prepareAttempt(payload, meta, params, config, recovery, upstreamDeadlineMonotonicMs, { offerLocalModelCooldown: (sourceModel) => fallbackPossible && validateFallback(config.protocol, pristinePayload, sourceModel).ok });
|
|
55451
|
+
sourceAttempt.commitObservability();
|
|
55332
55452
|
try {
|
|
55333
55453
|
return {
|
|
55334
55454
|
result: await sourceAttempt.execute(),
|
|
@@ -55375,14 +55495,18 @@ async function runPipeline(params, config) {
|
|
|
55375
55495
|
}
|
|
55376
55496
|
error.recovery.retryLimit = error.recovery.retryCount;
|
|
55377
55497
|
emitFallbackEvent(error.recovery, candidate.target.id, "selected", 529);
|
|
55498
|
+
error.recovery.onFallbackFetchStart = fallbackAttempt.commitObservability;
|
|
55378
55499
|
try {
|
|
55379
55500
|
const result = await fallbackAttempt.execute();
|
|
55501
|
+
fallbackAttempt.commitObservability();
|
|
55380
55502
|
emitFallbackEvent(error.recovery, candidate.target.id, "succeeded");
|
|
55381
55503
|
return {
|
|
55382
55504
|
result: discloseActualModel(result, candidate.target.id),
|
|
55383
55505
|
modelMapping: fallbackMapping
|
|
55384
55506
|
};
|
|
55385
55507
|
} catch (fallbackError) {
|
|
55508
|
+
if (error.recovery.fallbackFetchStarted) fallbackAttempt.commitObservability();
|
|
55509
|
+
else fallbackAttempt.discardObservability();
|
|
55386
55510
|
if (params.signal.aborted) throw params.signal.reason;
|
|
55387
55511
|
if (fallbackError instanceof FallbackCooldownError) {
|
|
55388
55512
|
emitFallbackEvent(error.recovery, candidate.target.id, "target-cooldown", 529);
|
|
@@ -55394,6 +55518,8 @@ async function runPipeline(params, config) {
|
|
|
55394
55518
|
}
|
|
55395
55519
|
emitFallbackEvent(error.recovery, candidate.target.id, "target-failed", fallbackError instanceof HTTPError ? fallbackError.status : void 0, isRetryableConnectionEstablishmentError(fallbackError));
|
|
55396
55520
|
throw fallbackError;
|
|
55521
|
+
} finally {
|
|
55522
|
+
delete error.recovery.onFallbackFetchStart;
|
|
55397
55523
|
}
|
|
55398
55524
|
}
|
|
55399
55525
|
}
|
|
@@ -55406,17 +55532,24 @@ async function prepareAttempt(payload, meta, params, config, recovery, upstreamD
|
|
|
55406
55532
|
const baseModel = resolved.model;
|
|
55407
55533
|
const selectedModel = options.target ?? resolved.resolvedModel;
|
|
55408
55534
|
const modelMapping = options.modelMapping ?? resolved.modelMapping;
|
|
55409
|
-
|
|
55535
|
+
const deferObservability = options.fallbackAttempt === true;
|
|
55536
|
+
if (deferObservability) runtimeStore.requests.beginEffectBuffer(params.requestId);
|
|
55537
|
+
else runtimeStore.requests.recordModelMapping(params.requestId, modelMapping);
|
|
55410
55538
|
const recordedModelStepCount = modelMapping.steps.length;
|
|
55411
|
-
|
|
55412
|
-
|
|
55413
|
-
|
|
55414
|
-
|
|
55415
|
-
|
|
55416
|
-
|
|
55417
|
-
|
|
55539
|
+
try {
|
|
55540
|
+
if (options.target) payload.model = options.target.id;
|
|
55541
|
+
if (config.afterTransform) await config.afterTransform({
|
|
55542
|
+
payload,
|
|
55543
|
+
meta,
|
|
55544
|
+
headers: params.headers,
|
|
55545
|
+
selectedModel
|
|
55546
|
+
});
|
|
55547
|
+
} catch (error) {
|
|
55548
|
+
if (deferObservability) runtimeStore.requests.discardEffectBuffer(params.requestId);
|
|
55549
|
+
throw error;
|
|
55550
|
+
}
|
|
55418
55551
|
params.signal.throwIfAborted();
|
|
55419
|
-
const upstreamSignal = createUpstreamSignalFromConfig(params.signal, upstreamDeadlineMonotonicMs);
|
|
55552
|
+
const upstreamSignal = createUpstreamSignalFromConfig(params.signal, upstreamDeadlineMonotonicMs, () => runtimeStore.requests.markAborted(params.requestId));
|
|
55420
55553
|
const copilotClient = createCopilotClient(recovery, {
|
|
55421
55554
|
offerLocalModelCooldown: options.offerLocalModelCooldown,
|
|
55422
55555
|
fallbackAttempt: options.fallbackAttempt
|
|
@@ -55433,12 +55566,31 @@ async function prepareAttempt(payload, meta, params, config, recovery, upstreamD
|
|
|
55433
55566
|
recovery
|
|
55434
55567
|
});
|
|
55435
55568
|
const entry = config.strategyRegistry.select(selectedModel, ctx);
|
|
55436
|
-
runtimeStore.requests.recordStrategy(params.requestId, entry.name);
|
|
55437
55569
|
const strategyEffect = effectForStrategy(config.protocol, entry.name);
|
|
55438
|
-
|
|
55570
|
+
let observabilityCommitted = !deferObservability;
|
|
55571
|
+
let observabilityDiscarded = false;
|
|
55572
|
+
if (!deferObservability) {
|
|
55573
|
+
runtimeStore.requests.recordStrategy(params.requestId, entry.name);
|
|
55574
|
+
if (strategyEffect) runtimeStore.requests.recordEffect(params.requestId, strategyEffect);
|
|
55575
|
+
}
|
|
55576
|
+
const commitObservability = () => {
|
|
55577
|
+
if (observabilityCommitted || observabilityDiscarded) return;
|
|
55578
|
+
observabilityCommitted = true;
|
|
55579
|
+
runtimeStore.requests.recordModelMapping(params.requestId, modelMapping);
|
|
55580
|
+
runtimeStore.requests.recordStrategy(params.requestId, entry.name);
|
|
55581
|
+
if (strategyEffect) runtimeStore.requests.recordEffect(params.requestId, strategyEffect);
|
|
55582
|
+
runtimeStore.requests.commitEffectBuffer(params.requestId);
|
|
55583
|
+
};
|
|
55584
|
+
const discardObservability = () => {
|
|
55585
|
+
if (observabilityCommitted || observabilityDiscarded) return;
|
|
55586
|
+
observabilityDiscarded = true;
|
|
55587
|
+
runtimeStore.requests.discardEffectBuffer(params.requestId);
|
|
55588
|
+
};
|
|
55439
55589
|
return {
|
|
55440
55590
|
baseModel,
|
|
55441
55591
|
modelMapping,
|
|
55592
|
+
commitObservability,
|
|
55593
|
+
discardObservability,
|
|
55442
55594
|
execute: async () => {
|
|
55443
55595
|
try {
|
|
55444
55596
|
params.signal.throwIfAborted();
|
|
@@ -55447,12 +55599,13 @@ async function prepareAttempt(payload, meta, params, config, recovery, upstreamD
|
|
|
55447
55599
|
upstreamSignal.cleanup();
|
|
55448
55600
|
throw error;
|
|
55449
55601
|
} finally {
|
|
55450
|
-
if (modelMapping.steps.length !== recordedModelStepCount) runtimeStore.requests.recordModelMapping(params.requestId, modelMapping);
|
|
55602
|
+
if (observabilityCommitted && modelMapping.steps.length !== recordedModelStepCount) runtimeStore.requests.recordModelMapping(params.requestId, modelMapping);
|
|
55451
55603
|
}
|
|
55452
55604
|
}
|
|
55453
55605
|
};
|
|
55454
55606
|
} catch (error) {
|
|
55455
55607
|
upstreamSignal.cleanup();
|
|
55608
|
+
if (deferObservability) runtimeStore.requests.discardEffectBuffer(params.requestId);
|
|
55456
55609
|
throw error;
|
|
55457
55610
|
}
|
|
55458
55611
|
}
|
|
@@ -55725,10 +55878,10 @@ function normalizeEmbeddingRequest(payload) {
|
|
|
55725
55878
|
* derives its own upstream signal — otherwise a client disconnect would leave
|
|
55726
55879
|
* the upstream request running and the configured timeout unenforced.
|
|
55727
55880
|
*/
|
|
55728
|
-
async function handleEmbeddingsCore(body, headers, client, signal, recovery) {
|
|
55881
|
+
async function handleEmbeddingsCore(body, headers, client, signal, recovery, onClientAbort) {
|
|
55729
55882
|
const { payload } = protocolRegistry.ingest("embeddings", body, headers);
|
|
55730
55883
|
const copilotClient = client ?? createCopilotClient(recovery);
|
|
55731
|
-
const upstreamSignal = signal ? createUpstreamSignalFromConfig(signal) : void 0;
|
|
55884
|
+
const upstreamSignal = signal ? createUpstreamSignalFromConfig(signal, void 0, onClientAbort) : void 0;
|
|
55732
55885
|
try {
|
|
55733
55886
|
return await copilotClient.createEmbeddings(normalizeEmbeddingRequest(payload), { signal: upstreamSignal?.signal });
|
|
55734
55887
|
} finally {
|
|
@@ -55739,7 +55892,8 @@ async function handleEmbeddingsCore(body, headers, client, signal, recovery) {
|
|
|
55739
55892
|
//#region src/routes/embeddings/route.ts
|
|
55740
55893
|
function createEmbeddingRoutes() {
|
|
55741
55894
|
return new Elysia().use(requestGuardPlugin).post("/embeddings", async ({ body, request }) => {
|
|
55742
|
-
|
|
55895
|
+
const { requestId } = getOrCreateRequestCorrelation(request);
|
|
55896
|
+
return handleEmbeddingsCore(body, request.headers, void 0, request.signal, createRequestRecoveryRecord(request), () => runtimeStore.requests.markAborted(requestId));
|
|
55743
55897
|
}, { guarded: true });
|
|
55744
55898
|
}
|
|
55745
55899
|
//#endregion
|
|
@@ -55754,18 +55908,42 @@ const ESTIMATION_FACTOR = {
|
|
|
55754
55908
|
grok: 1.03,
|
|
55755
55909
|
gpt: 1.1
|
|
55756
55910
|
};
|
|
55911
|
+
const BUILTIN_TOOLSET_TOKENS = {
|
|
55912
|
+
browser_toolset_20260801: 7550,
|
|
55913
|
+
computer_toolset_20260801: 4590
|
|
55914
|
+
};
|
|
55757
55915
|
/**
|
|
55758
55916
|
* Core handler for counting tokens.
|
|
55759
55917
|
*/
|
|
55760
55918
|
async function handleCountTokensCore({ body, headers }) {
|
|
55761
55919
|
const anthropicBeta = headers.get("anthropic-beta") ?? void 0;
|
|
55762
55920
|
const { payload: anthropicPayload } = protocolRegistry.ingest("anthropic-count-tokens", body, headers);
|
|
55921
|
+
const functionTools = [];
|
|
55922
|
+
const uncalibratedBuiltinTools = [];
|
|
55923
|
+
let calibratedBuiltinTokens = 0;
|
|
55924
|
+
let hasBuiltinTools = false;
|
|
55925
|
+
for (const tool of anthropicPayload.tools ?? []) {
|
|
55926
|
+
if (!isAnthropicBuiltinTool(tool)) {
|
|
55927
|
+
functionTools.push(tool);
|
|
55928
|
+
continue;
|
|
55929
|
+
}
|
|
55930
|
+
hasBuiltinTools = true;
|
|
55931
|
+
const builtinTokens = tool.type ? BUILTIN_TOOLSET_TOKENS[tool.type] : void 0;
|
|
55932
|
+
if (builtinTokens === void 0) uncalibratedBuiltinTools.push(tool);
|
|
55933
|
+
else calibratedBuiltinTokens += builtinTokens;
|
|
55934
|
+
}
|
|
55935
|
+
const countPayload = hasBuiltinTools ? {
|
|
55936
|
+
...anthropicPayload,
|
|
55937
|
+
tools: functionTools
|
|
55938
|
+
} : anthropicPayload;
|
|
55763
55939
|
const adapter = createAnthropicAdapter();
|
|
55764
|
-
const openAIPayload = withTranslationErrors(() => adapter.toTokenCountPayload(
|
|
55765
|
-
const
|
|
55766
|
-
|
|
55940
|
+
const openAIPayload = withTranslationErrors(() => adapter.toTokenCountPayload(countPayload));
|
|
55941
|
+
const selectedModel = resolveModelOrThrow(openAIPayload.model);
|
|
55942
|
+
const tokenCount = await getTokenCount(openAIPayload, selectedModel);
|
|
55943
|
+
if (uncalibratedBuiltinTools.length > 0) tokenCount.input += await estimateSerializedTokens(uncalibratedBuiltinTools, selectedModel);
|
|
55944
|
+
if (functionTools.length > 0) {
|
|
55767
55945
|
let mcpToolExist = false;
|
|
55768
|
-
if (anthropicBeta?.startsWith("claude-code")) mcpToolExist =
|
|
55946
|
+
if (anthropicBeta?.startsWith("claude-code")) mcpToolExist = functionTools.some((tool) => tool.name?.startsWith("mcp__") ?? false);
|
|
55769
55947
|
if (!mcpToolExist) {
|
|
55770
55948
|
const overhead = TOOL_OVERHEAD_TOKENS[inferModelFamily(anthropicPayload.model)];
|
|
55771
55949
|
if (overhead) tokenCount.input = tokenCount.input + overhead;
|
|
@@ -55774,6 +55952,7 @@ async function handleCountTokensCore({ body, headers }) {
|
|
|
55774
55952
|
let finalTokenCount = tokenCount.input + tokenCount.output;
|
|
55775
55953
|
const factor = ESTIMATION_FACTOR[inferModelFamily(anthropicPayload.model)];
|
|
55776
55954
|
if (factor) finalTokenCount = Math.round(finalTokenCount * factor);
|
|
55955
|
+
finalTokenCount += calibratedBuiltinTokens;
|
|
55777
55956
|
consola.info("Token count:", finalTokenCount);
|
|
55778
55957
|
return { input_tokens: finalTokenCount };
|
|
55779
55958
|
}
|
|
@@ -55958,7 +56137,7 @@ function deleteStoredResponseOrThrow(responseId) {
|
|
|
55958
56137
|
async function estimateEmulatorInputTokens(payload, selectedModel) {
|
|
55959
56138
|
return {
|
|
55960
56139
|
object: "response.input_tokens",
|
|
55961
|
-
input_tokens: await
|
|
56140
|
+
input_tokens: await estimateSerializedTokens(resolveEffectiveInputForInputTokens(payload), selectedModel)
|
|
55962
56141
|
};
|
|
55963
56142
|
}
|
|
55964
56143
|
function resolveEffectiveInputForInputTokens(payload) {
|
|
@@ -56155,6 +56334,8 @@ async function handleResponsesCore({ body, signal, headers, requestId, callerReq
|
|
|
56155
56334
|
const { vision, initiator } = getResponsesRequestOptions(payload);
|
|
56156
56335
|
const prepared = emulatorPrepared;
|
|
56157
56336
|
const requestPayload = originalPayload ?? payload;
|
|
56337
|
+
let effectiveInputItems;
|
|
56338
|
+
if (prepared?.shouldStore) effectiveInputItems = Array.isArray(payload.input) ? structuredClone(payload.input) : [];
|
|
56158
56339
|
const strategyRequestId = recovery.requestId;
|
|
56159
56340
|
return {
|
|
56160
56341
|
requestId: strategyRequestId,
|
|
@@ -56170,7 +56351,7 @@ async function handleResponsesCore({ body, signal, headers, requestId, callerReq
|
|
|
56170
56351
|
}, requestPayload, prepared) : void 0,
|
|
56171
56352
|
onTerminalResponse(terminalResponse) {
|
|
56172
56353
|
if (terminalResponse.status === "failed" || terminalResponse.error) runtimeStore.requests.recordError(strategyRequestId, "Upstream HTTP 200 (response_failed)");
|
|
56173
|
-
if (
|
|
56354
|
+
if (effectiveInputItems) persistEmulatorResponse(terminalResponse, effectiveInputItems);
|
|
56174
56355
|
},
|
|
56175
56356
|
onStreamEndWithoutTerminal() {
|
|
56176
56357
|
if (upstreamSignal.clientSignal?.aborted) return;
|
|
@@ -56250,7 +56431,7 @@ function applyResponsesInputPolicies(payload) {
|
|
|
56250
56431
|
}
|
|
56251
56432
|
/**
|
|
56252
56433
|
* Remove input items that Copilot cannot resolve and would trigger 404:
|
|
56253
|
-
* - `item_reference` items (
|
|
56434
|
+
* - `item_reference` items (Copilot does not resolve returned item IDs later)
|
|
56254
56435
|
* - `function_call_output` items whose `call_id` has no matching prior
|
|
56255
56436
|
* `function_call` in the same input array (orphaned outputs)
|
|
56256
56437
|
*/
|
|
@@ -56316,7 +56497,10 @@ var UpstreamResourceDispatcher = class {
|
|
|
56316
56497
|
return this.client.getResponseInputItems(responseId, params, options);
|
|
56317
56498
|
}
|
|
56318
56499
|
createInputTokens(payload, options) {
|
|
56319
|
-
return this.client.createResponseInputTokens(
|
|
56500
|
+
return this.client.createResponseInputTokens({
|
|
56501
|
+
...payload,
|
|
56502
|
+
store: false
|
|
56503
|
+
}, options);
|
|
56320
56504
|
}
|
|
56321
56505
|
delete(responseId, options) {
|
|
56322
56506
|
return this.client.deleteResponse(responseId, options);
|
|
@@ -56334,33 +56518,45 @@ function createResourceDispatcher(client, recovery) {
|
|
|
56334
56518
|
}
|
|
56335
56519
|
//#endregion
|
|
56336
56520
|
//#region src/routes/responses/resource-handler.ts
|
|
56337
|
-
async function handleRetrieveResponseCore({ params, url, headers, signal, client, recovery }) {
|
|
56521
|
+
async function handleRetrieveResponseCore({ params, url, headers, signal, client, recovery, onClientAbort }) {
|
|
56338
56522
|
const responseId = requireResponseId(params.responseId);
|
|
56339
|
-
|
|
56523
|
+
const dispatcher = createResourceDispatcher(client, recovery);
|
|
56524
|
+
return withUpstreamSignal(signal, onClientAbort, (upstreamSignal) => dispatcher.retrieve(responseId, getRetrieveParamsFromUrl(url), {
|
|
56340
56525
|
requestContext: readCapiRequestContext(headers),
|
|
56341
|
-
signal
|
|
56342
|
-
});
|
|
56526
|
+
signal: upstreamSignal
|
|
56527
|
+
}));
|
|
56343
56528
|
}
|
|
56344
|
-
async function handleListResponseInputItemsCore({ params, url, headers, signal, client, recovery }) {
|
|
56529
|
+
async function handleListResponseInputItemsCore({ params, url, headers, signal, client, recovery, onClientAbort }) {
|
|
56345
56530
|
const responseId = requireResponseId(params.responseId);
|
|
56346
|
-
|
|
56531
|
+
const dispatcher = createResourceDispatcher(client, recovery);
|
|
56532
|
+
return withUpstreamSignal(signal, onClientAbort, (upstreamSignal) => dispatcher.listInputItems(responseId, getInputItemsParamsFromUrl(url), {
|
|
56347
56533
|
requestContext: readCapiRequestContext(headers),
|
|
56348
|
-
signal
|
|
56349
|
-
});
|
|
56534
|
+
signal: upstreamSignal
|
|
56535
|
+
}));
|
|
56350
56536
|
}
|
|
56351
|
-
async function handleCreateResponseInputTokensCore({ body, headers, signal, client, recovery }) {
|
|
56537
|
+
async function handleCreateResponseInputTokensCore({ body, headers, signal, client, recovery, onClientAbort }) {
|
|
56352
56538
|
const { payload, meta } = protocolRegistry.ingest("responses-input-tokens", body, headers);
|
|
56353
|
-
|
|
56539
|
+
const dispatcher = createResourceDispatcher(client, recovery);
|
|
56540
|
+
return withUpstreamSignal(signal, onClientAbort, (upstreamSignal) => dispatcher.createInputTokens(payload, {
|
|
56354
56541
|
requestContext: meta.requestContext,
|
|
56355
|
-
signal
|
|
56356
|
-
});
|
|
56542
|
+
signal: upstreamSignal
|
|
56543
|
+
}));
|
|
56357
56544
|
}
|
|
56358
|
-
async function handleDeleteResponseCore({ params, headers, signal, client, recovery }) {
|
|
56545
|
+
async function handleDeleteResponseCore({ params, headers, signal, client, recovery, onClientAbort }) {
|
|
56359
56546
|
const responseId = requireResponseId(params.responseId);
|
|
56360
|
-
|
|
56547
|
+
const dispatcher = createResourceDispatcher(client, recovery);
|
|
56548
|
+
return withUpstreamSignal(signal, onClientAbort, (upstreamSignal) => dispatcher.delete(responseId, {
|
|
56361
56549
|
requestContext: readCapiRequestContext(headers),
|
|
56362
|
-
signal
|
|
56363
|
-
});
|
|
56550
|
+
signal: upstreamSignal
|
|
56551
|
+
}));
|
|
56552
|
+
}
|
|
56553
|
+
async function withUpstreamSignal(clientSignal, onClientAbort, dispatch) {
|
|
56554
|
+
const upstreamSignal = createUpstreamSignalFromConfig(clientSignal, void 0, onClientAbort);
|
|
56555
|
+
try {
|
|
56556
|
+
return await dispatch(upstreamSignal.signal);
|
|
56557
|
+
} finally {
|
|
56558
|
+
upstreamSignal.cleanup();
|
|
56559
|
+
}
|
|
56364
56560
|
}
|
|
56365
56561
|
function requireResponseId(responseId) {
|
|
56366
56562
|
if (!responseId) throwInvalidRequestError("Response id is required.", "response_id");
|
|
@@ -56432,35 +56628,43 @@ function createResponsesRoutes() {
|
|
|
56432
56628
|
yield* delivery.stream;
|
|
56433
56629
|
}, { guarded: true }).post("/responses/input_tokens", async ({ body, request, server }) => {
|
|
56434
56630
|
disableIdleTimeout(server, request);
|
|
56631
|
+
const { requestId } = getOrCreateRequestCorrelation(request);
|
|
56435
56632
|
return handleCreateResponseInputTokensCore({
|
|
56436
56633
|
body,
|
|
56437
56634
|
headers: request.headers,
|
|
56438
56635
|
signal: request.signal,
|
|
56439
|
-
recovery: createRequestRecoveryRecord(request)
|
|
56636
|
+
recovery: createRequestRecoveryRecord(request),
|
|
56637
|
+
onClientAbort: () => runtimeStore.requests.markAborted(requestId)
|
|
56440
56638
|
});
|
|
56441
56639
|
}, { guarded: true }).get("/responses/:responseId/input_items", async ({ params, request }) => {
|
|
56640
|
+
const { requestId } = getOrCreateRequestCorrelation(request);
|
|
56442
56641
|
return handleListResponseInputItemsCore({
|
|
56443
56642
|
params,
|
|
56444
56643
|
url: request.url,
|
|
56445
56644
|
headers: request.headers,
|
|
56446
56645
|
signal: request.signal,
|
|
56447
|
-
recovery: createRequestRecoveryRecord(request)
|
|
56646
|
+
recovery: createRequestRecoveryRecord(request),
|
|
56647
|
+
onClientAbort: () => runtimeStore.requests.markAborted(requestId)
|
|
56448
56648
|
});
|
|
56449
56649
|
}, { guarded: true }).get("/responses/:responseId", async ({ params, request, server }) => {
|
|
56450
56650
|
if (hasStreamingResponsesQuery(request)) disableIdleTimeout(server, request);
|
|
56651
|
+
const { requestId } = getOrCreateRequestCorrelation(request);
|
|
56451
56652
|
return handleRetrieveResponseCore({
|
|
56452
56653
|
params,
|
|
56453
56654
|
url: request.url,
|
|
56454
56655
|
headers: request.headers,
|
|
56455
56656
|
signal: request.signal,
|
|
56456
|
-
recovery: createRequestRecoveryRecord(request)
|
|
56657
|
+
recovery: createRequestRecoveryRecord(request),
|
|
56658
|
+
onClientAbort: () => runtimeStore.requests.markAborted(requestId)
|
|
56457
56659
|
});
|
|
56458
56660
|
}, { guarded: true }).delete("/responses/:responseId", async ({ params, request }) => {
|
|
56661
|
+
const { requestId } = getOrCreateRequestCorrelation(request);
|
|
56459
56662
|
return handleDeleteResponseCore({
|
|
56460
56663
|
params,
|
|
56461
56664
|
headers: request.headers,
|
|
56462
56665
|
signal: request.signal,
|
|
56463
|
-
recovery: createRequestRecoveryRecord(request)
|
|
56666
|
+
recovery: createRequestRecoveryRecord(request),
|
|
56667
|
+
onClientAbort: () => runtimeStore.requests.markAborted(requestId)
|
|
56464
56668
|
});
|
|
56465
56669
|
}, { guarded: true });
|
|
56466
56670
|
}
|
|
@@ -56619,7 +56823,7 @@ function createServer$1(options) {
|
|
|
56619
56823
|
set
|
|
56620
56824
|
});
|
|
56621
56825
|
const status = response?.status ?? (error instanceof HTTPError ? error.status : 500);
|
|
56622
|
-
runtimeStore.requests.recordError(getOrCreateRequestCorrelation(request).requestId, sanitizeObservedError(error, code, status));
|
|
56826
|
+
if (!isClientAbortError(error)) runtimeStore.requests.recordError(getOrCreateRequestCorrelation(request).requestId, sanitizeObservedError(error, code, status));
|
|
56623
56827
|
return response;
|
|
56624
56828
|
}).get("/", () => "Server running").get("/health", () => ({
|
|
56625
56829
|
status: "ok",
|
|
@@ -56632,6 +56836,7 @@ function createServer$1(options) {
|
|
|
56632
56836
|
}
|
|
56633
56837
|
//#endregion
|
|
56634
56838
|
//#region src/start.ts
|
|
56839
|
+
const UNSIGNED_INTEGER_RE = /^\d+$/;
|
|
56635
56840
|
async function maybeCopyClaudeCodeCommand(serverUrl) {
|
|
56636
56841
|
const models = modelCache.getModels();
|
|
56637
56842
|
if (!models) return;
|
|
@@ -56711,24 +56916,16 @@ async function runServer(options) {
|
|
|
56711
56916
|
process$1.on("SIGTERM", shutdown);
|
|
56712
56917
|
process$1.on("SIGINT", shutdown);
|
|
56713
56918
|
}
|
|
56714
|
-
function parseIntArg(raw, name, fallbackMsg) {
|
|
56919
|
+
function parseIntArg(raw, name, fallbackMsg, min = 0, max = Number.MAX_SAFE_INTEGER) {
|
|
56715
56920
|
if (raw === void 0) return void 0;
|
|
56716
|
-
const
|
|
56717
|
-
|
|
56921
|
+
const normalized = raw.trim();
|
|
56922
|
+
const n = Number(normalized);
|
|
56923
|
+
if (!UNSIGNED_INTEGER_RE.test(normalized) || !Number.isSafeInteger(n) || n < min || n > max) {
|
|
56718
56924
|
consola.warn(`Invalid --${name} value "${raw}". ${fallbackMsg}`);
|
|
56719
56925
|
return;
|
|
56720
56926
|
}
|
|
56721
56927
|
return n;
|
|
56722
56928
|
}
|
|
56723
|
-
function parseBoundedIntArg(raw, name, fallbackMsg, min, max) {
|
|
56724
|
-
if (raw === void 0) return void 0;
|
|
56725
|
-
const value = Number(raw);
|
|
56726
|
-
if (!raw.trim() || !Number.isInteger(value) || value < min || value > max) {
|
|
56727
|
-
consola.warn(`Invalid --${name} value "${raw}". ${fallbackMsg}`);
|
|
56728
|
-
return;
|
|
56729
|
-
}
|
|
56730
|
-
return value;
|
|
56731
|
-
}
|
|
56732
56929
|
function secondsToMs(seconds) {
|
|
56733
56930
|
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
56734
56931
|
}
|
|
@@ -56840,16 +57037,18 @@ const start = defineCommand({
|
|
|
56840
57037
|
}
|
|
56841
57038
|
},
|
|
56842
57039
|
run({ args }) {
|
|
57040
|
+
const port = parseIntArg(args.port, "port", "Server not started.", 1, 65535);
|
|
57041
|
+
if (port === void 0) throw new Error(`Invalid --port value "${args.port}".`);
|
|
56843
57042
|
const rateLimit = parseIntArg(args["rate-limit"], "rate-limit", "Rate limiting disabled.");
|
|
56844
57043
|
const idleTimeoutSeconds = parseIntArg(args["idle-timeout"], "idle-timeout", "Falling back to Bun default.");
|
|
56845
|
-
const upstreamTimeoutSeconds = parseIntArg(args["upstream-timeout"], "upstream-timeout", "Falling back to default (
|
|
56846
|
-
const upstreamQueueConcurrency = parseIntArg(args["upstream-queue-concurrency"], "upstream-queue-concurrency", "Using default upstream queue concurrency.");
|
|
56847
|
-
const upstreamQueueMaxRetries =
|
|
56848
|
-
const upstreamRecoveryBudgetSeconds =
|
|
57044
|
+
const upstreamTimeoutSeconds = parseIntArg(args["upstream-timeout"], "upstream-timeout", "Falling back to default (1800s).");
|
|
57045
|
+
const upstreamQueueConcurrency = parseIntArg(args["upstream-queue-concurrency"], "upstream-queue-concurrency", "Using default upstream queue concurrency.", 1);
|
|
57046
|
+
const upstreamQueueMaxRetries = parseIntArg(args["upstream-queue-retries"], "upstream-queue-retries", "Using default upstream queue retry count.", 0, 2);
|
|
57047
|
+
const upstreamRecoveryBudgetSeconds = parseIntArg(args["upstream-recovery-budget"], "upstream-recovery-budget", "Using default upstream recovery budget.", 1, 120);
|
|
56849
57048
|
const upstreamQueueBaseDelaySeconds = parseIntArg(args["upstream-queue-base-delay"], "upstream-queue-base-delay", "Using default upstream queue base delay.");
|
|
56850
57049
|
const upstreamQueueMaxDelaySeconds = parseIntArg(args["upstream-queue-max-delay"], "upstream-queue-max-delay", "Using default upstream queue max delay.");
|
|
56851
57050
|
return runServer({
|
|
56852
|
-
port
|
|
57051
|
+
port,
|
|
56853
57052
|
verbose: args.verbose,
|
|
56854
57053
|
accountType: args["account-type"],
|
|
56855
57054
|
manual: args.manual,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghc-proxy",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.1",
|
|
5
5
|
"description": "GitHub Copilot to OpenAI/Anthropic API proxy - Use Copilot with Claude Code, Cursor, and more",
|
|
6
6
|
"author": "wxxb789 <wxxb789@outlook.com>",
|
|
7
7
|
"homepage": "https://github.com/wxxb789/ghc-proxy",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsdown",
|
|
30
|
-
"dev": "bun run --watch ./src/main.ts",
|
|
30
|
+
"dev": "bun run --watch ./src/main.ts start",
|
|
31
31
|
"knip": "knip-bun",
|
|
32
32
|
"lint": "eslint --cache",
|
|
33
33
|
"lint:all": "eslint --cache .",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"smoke:packaged": "bun run scripts/smoke/packaged-cli.ts",
|
|
43
43
|
"smoke:translation": "bun run scripts/smoke/translation.ts",
|
|
44
44
|
"serve": "NODE_ENV=production bun run ./src/main.ts start --wait --idle-timeout=255 --upstream-timeout=1800",
|
|
45
|
-
"start": "NODE_ENV=production bun run ./src/main.ts",
|
|
45
|
+
"start": "NODE_ENV=production bun run ./src/main.ts start",
|
|
46
46
|
"typecheck": "npx tsc --project tsconfig.json"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|