cartethyia 2.5.1 → 2.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -1
- package/dist/dashboard/assets/{AccountCooldown-ChpdzxRH.js → AccountCooldown-Bjo92Qat.js} +1 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- package/dist/dashboard/assets/{BannedPage-ChqnTRpZ.js → BannedPage-hfCzi-Ux.js} +1 -1
- package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BXUaRCQQ.js → CliToolsPage-DEDyKBlC.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +1 -0
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DDVIfZGW.js +1 -0
- package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +1 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js +1 -0
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +7 -0
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +1 -0
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D6pmQ86L.js +1 -0
- package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DWvKiuSl.js +1 -0
- package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-DqK5E26W.js → StudioPage-CmkUrgdj.js} +3 -3
- package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B47uOydr.js +56 -0
- package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BmDbsior.js → api-keys-uNywYlfv.js} +1 -1
- package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
- package/dist/dashboard/assets/{app-C-Y4oZT0.js → app-CafWtVXA.js} +1 -1
- package/dist/dashboard/assets/{app-C-Y4oZT0.js.gz → app-CafWtVXA.js.gz} +0 -0
- package/dist/dashboard/assets/app-DxivpfYG.css +2 -0
- package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
- package/dist/dashboard/assets/{assert-BHfaei-X.js → assert-NHwYq95t.js} +1 -1
- package/dist/dashboard/assets/{assert-BHfaei-X.js.gz → assert-NHwYq95t.js.gz} +0 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js +1 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js +1 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
- package/dist/dashboard/assets/card--AtFedAq.js +1 -0
- package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-B0wBq-RL.js → cli-tools-B3e7dx3N.js} +1 -1
- package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js +1 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js +1 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js +1 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
- package/dist/dashboard/assets/entry-CkryrIea.js +4 -0
- package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js +1 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
- package/dist/dashboard/assets/{entry-D4O2229X.js → entry-jy9_4-Ke.js} +1 -1
- package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js +1 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
- package/dist/dashboard/assets/{index-CcD1eSJ6.js → index-BydyKaDz.js} +2 -2
- package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js +1 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
- package/dist/dashboard/assets/input-BG41et9G.js +1 -0
- package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js +1 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js +1 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
- package/dist/dashboard/assets/{live-yfEJFF9T.js → live-BJGYRBnR.js} +1 -1
- package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CT-TxSNX.js → model-bans-DeVLDA7I.js} +1 -1
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js +2 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Cs6jl49L.js → routing-CcusqXA7.js} +1 -1
- package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
- package/dist/dashboard/assets/select-BO6h8yNv.js +41 -0
- package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CfcTcOyU.js → settings-mpmXTgUa.js} +1 -1
- package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js +1 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js +1 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js +1 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BQKMcJ1H.js → useMutation-w4u4CVue.js} +1 -1
- package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -54
- package/package.json +1 -1
- package/scripts/cli/command/console-menu.ts +248 -246
- package/scripts/cli/command/tray.ts +208 -204
- package/src/boot/app.ts +3 -2
- package/src/config/spec.ts +26 -2
- package/src/console/backup/routes.ts +52 -1
- package/src/console/backup/service.ts +60 -4
- package/src/console/observability/contracts.ts +11 -1
- package/src/console/observability/store.ts +9 -6
- package/src/console/share/share-router.ts +6 -9
- package/src/observability/payload-capture.ts +366 -344
- package/src/observability/payload-store.ts +12 -2
- package/src/observability/telemetry-status.ts +28 -16
- package/src/persistence/stores/api-key-store.ts +6 -0
- package/src/persistence/stores/share-store.ts +583 -556
- package/src/persistence/stores/telemetry-store.ts +1 -1
- package/src/protocol/response/gemini.ts +18 -4
- package/src/providers/discovery/openrouter-free-roster.ts +3 -11
- package/src/providers/discovery/probe-phases.ts +42 -5
- package/src/providers/discovery/probing-service.ts +25 -2
- package/src/providers/integrations/antigravity/antigravity-quota.ts +11 -3
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +4 -4
- package/src/providers/integrations/buddy/workbuddy/shared.ts +201 -200
- package/src/providers/integrations/claude/claude.ts +3 -1
- package/src/providers/integrations/cline/cline-quota.ts +8 -11
- package/src/providers/integrations/cloudflareai/cloudflareai-models.ts +3 -1
- package/src/providers/integrations/inferhub.ts +3 -1
- package/src/providers/integrations/kilo/kilo.ts +0 -1
- package/src/providers/integrations/kiro/kiro-catalog.ts +8 -2
- package/src/providers/integrations/opencode/provider.ts +532 -532
- package/src/providers/integrations/openrouter/provider.ts +5 -15
- package/src/providers/integrations/tokenharbor.ts +2 -2
- package/src/providers/integrations/trae/trae-catalog.ts +5 -3
- package/src/providers/operations/account-health-service.ts +23 -0
- package/src/providers/operations/client-versions.ts +9 -2
- package/src/providers/provider-metadata.ts +7 -1
- package/src/security/api-key-auth.ts +23 -0
- package/src/transport/dispatch/attempt-finalize.ts +46 -5
- package/src/transport/dispatch/proxy-request.ts +9 -0
- package/src/transport/dispatch/streaming/stream.ts +16 -2
- package/src/transport/guards/auth-guard.ts +2 -3
- package/src/transport/routing/route-catalog.ts +806 -799
- package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-ChqnTRpZ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BXUaRCQQ.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CisNZOUt.js +0 -1
- package/dist/dashboard/assets/CombosPage-CisNZOUt.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-CytxSeGP.js +0 -1
- package/dist/dashboard/assets/LoginPage-CytxSeGP.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +0 -1
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js +0 -7
- package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js +0 -1
- package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-8-eVadhc.js +0 -1
- package/dist/dashboard/assets/SettingsPage-8-eVadhc.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-uEIXPyGO.js +0 -1
- package/dist/dashboard/assets/SetupPage-uEIXPyGO.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DCY0TPTZ.js +0 -1
- package/dist/dashboard/assets/SortableList-DCY0TPTZ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-DqK5E26W.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-38JfiZ0H.js +0 -56
- package/dist/dashboard/assets/UsagePage-38JfiZ0H.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
- package/dist/dashboard/assets/app-BbpSarVV.css +0 -2
- package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
- package/dist/dashboard/assets/badge-DpXT4Ixt.js +0 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/banner-CSAAWLJv.js +0 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/card-DEhRXXw4.js +0 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/check-D69FzdvC.js +0 -1
- package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +0 -1
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js +0 -1
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BvQeBMfP.js +0 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CMwPXwQB.js +0 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/entry--yj84zk3.js +0 -4
- package/dist/dashboard/assets/entry--yj84zk3.js.gz +0 -0
- package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
- package/dist/dashboard/assets/entry-DKvr5Q9K.js +0 -1
- package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-zliLvBwr.js +0 -1
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/index-CcD1eSJ6.js.gz +0 -0
- package/dist/dashboard/assets/inline-D1IlZZ8H.js +0 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/input-BpVZdrbc.js +0 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/layout-BioLW7gN.js +0 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CwVcGAIc.js +0 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js +0 -1
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
- package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
- package/dist/dashboard/assets/select-XPJ-dIw3.js +0 -41
- package/dist/dashboard/assets/select-XPJ-dIw3.js.gz +0 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
- package/dist/dashboard/assets/stack-C9_uUjaA.js +0 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/state-DA0B95UQ.js +0 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/switch-Cittxy-x.js +0 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/toast-CNt_jzO4.js +0 -2
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
|
@@ -44,7 +44,7 @@ function addUsageDelta(
|
|
|
44
44
|
}
|
|
45
45
|
delta.requests += 1;
|
|
46
46
|
// Same definition as every read-side error count, including the HTTP-status
|
|
47
|
-
// exclusions (404/499/503): this rollup is durable, so a request counted here
|
|
47
|
+
// exclusions (404/499/503/504): this rollup is durable, so a request counted here
|
|
48
48
|
// stays counted after its raw row is pruned and cannot be corrected later.
|
|
49
49
|
if (isGatewayError(row.status, row.httpStatus)) delta.errors += 1;
|
|
50
50
|
delta.inputTokens += row.inputTokens ?? 0;
|
|
@@ -63,11 +63,25 @@ export function responseParts(parts: readonly Record<string, unknown>[]): {
|
|
|
63
63
|
|
|
64
64
|
export function mapGeminiUsage(body: Record<string, unknown>): Record<string, unknown> {
|
|
65
65
|
const usage = isRecord(body["usageMetadata"]) ? (body["usageMetadata"] as Record<string, unknown>) : {};
|
|
66
|
+
const prompt = nullableNumber(usage["promptTokenCount"]) ?? 0;
|
|
67
|
+
const candidates = nullableNumber(usage["candidatesTokenCount"]) ?? 0;
|
|
68
|
+
// Reasoning is billed output that Gemini reports in its own counter:
|
|
69
|
+
// `candidatesTokenCount` excludes `thoughtsTokenCount`, so dropping it here
|
|
70
|
+
// silently under-reported every thinking model's completion tokens. It is
|
|
71
|
+
// surfaced through `output_tokens_details` because that is the slot
|
|
72
|
+
// `normalizeUsage` reads for reasoning — keeping it separate from the
|
|
73
|
+
// candidate count it is added into, so downstream can still split them.
|
|
74
|
+
const thoughts = nullableNumber(usage["thoughtsTokenCount"]) ?? 0;
|
|
75
|
+
const output = candidates + thoughts;
|
|
76
|
+
const cached = nullableNumber(usage["cachedContentTokenCount"]) ?? 0;
|
|
66
77
|
return {
|
|
67
|
-
input_tokens:
|
|
68
|
-
output_tokens:
|
|
69
|
-
total_tokens: nullableNumber(usage["totalTokenCount"]) ??
|
|
70
|
-
cached_tokens:
|
|
78
|
+
input_tokens: prompt,
|
|
79
|
+
output_tokens: output,
|
|
80
|
+
total_tokens: nullableNumber(usage["totalTokenCount"]) ?? prompt + output,
|
|
81
|
+
cached_tokens: cached,
|
|
82
|
+
...(thoughts > 0
|
|
83
|
+
? { output_tokens_details: { reasoning_tokens: thoughts } }
|
|
84
|
+
: {}),
|
|
71
85
|
};
|
|
72
86
|
}
|
|
73
87
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The free tier shared by the OpenRouter-backed providers.
|
|
3
3
|
*
|
|
4
4
|
* Cline, Kilo Code and OpenRouter serve one model catalog: the same 469 ids,
|
|
5
|
-
* and the same
|
|
5
|
+
* and the same 9 of them carrying a `:free` suffix. Verified against all three
|
|
6
6
|
* live listings — every id below is present under `openrouter`, `kilo` and
|
|
7
7
|
* `cline` — so this roster is a catalog fact, not a per-provider guess, and it
|
|
8
8
|
* lives in one place rather than being restated three times.
|
|
@@ -37,10 +37,10 @@ interface FreeRow {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* The
|
|
40
|
+
* The 9 `:free` ids, with the limits the catalog states for each.
|
|
41
41
|
*
|
|
42
42
|
* Every row declares `reasoning`: `supported_parameters` lists it on all
|
|
43
|
-
*
|
|
43
|
+
* nine. `toolCall` is `true` unless stated otherwise, because a row that
|
|
44
44
|
* advertises tools must keep them — a false `toolCall` makes the preflight
|
|
45
45
|
* strip `tools` from the request, which is a wrong capability rather than a
|
|
46
46
|
* conservative one.
|
|
@@ -48,8 +48,6 @@ interface FreeRow {
|
|
|
48
48
|
const FREE_ROWS: readonly FreeRow[] = [
|
|
49
49
|
{ id: "cohere/north-mini-code:free", ctx: 256_000, out: 64_000 },
|
|
50
50
|
{ id: "dots-studio/dots-3-note-preview:free", ctx: 512_000, out: 460_800, vision: true },
|
|
51
|
-
{ id: "google/gemma-4-26b-a4b-it:free", ctx: 262_144, out: 32_768, vision: true },
|
|
52
|
-
{ id: "google/gemma-4-31b-it:free", ctx: 262_144, out: 32_768, vision: true },
|
|
53
51
|
{ id: "liquid/lfm-2.5-2.6b:free", ctx: 65_536, out: 8_192 },
|
|
54
52
|
{ id: "minimax/minimax-m3.1-flash:free", ctx: 1_000_000, out: 524_288, vision: true },
|
|
55
53
|
{
|
|
@@ -58,7 +56,6 @@ const FREE_ROWS: readonly FreeRow[] = [
|
|
|
58
56
|
out: 65_536,
|
|
59
57
|
vision: true,
|
|
60
58
|
},
|
|
61
|
-
{ id: "nvidia/nemotron-3-super-120b-a12b:free", ctx: 262_144, out: 235_929 },
|
|
62
59
|
{ id: "nvidia/nemotron-3-ultra-550b-a55b:free", ctx: 1_000_000, out: 65_536 },
|
|
63
60
|
{
|
|
64
61
|
id: "nvidia/nemotron-3.5-content-safety:free",
|
|
@@ -69,11 +66,6 @@ const FREE_ROWS: readonly FreeRow[] = [
|
|
|
69
66
|
},
|
|
70
67
|
{ id: "nvidia/nemotron-3.5-lightning:free", ctx: 1_000_000, out: 65_536 },
|
|
71
68
|
{ id: "nvidia/nemotron-3.5-super-vl:free", ctx: 1_000_000, out: 131_072, vision: true },
|
|
72
|
-
{ id: "poolside/laguna-s-2.1:free", ctx: 262_144, out: 32_768 },
|
|
73
|
-
{ id: "poolside/laguna-xs-2.1:free", ctx: 262_144, out: 32_768 },
|
|
74
|
-
{ id: "thinkingmachines/inkling-small:free", ctx: 1_048_576, out: 262_144, vision: true },
|
|
75
|
-
{ id: "thinkingmachines/inkling:free", ctx: 1_048_576, out: 262_144, vision: true },
|
|
76
|
-
{ id: "xingchen-agi/xing4.0-29b-a4b:free", ctx: 262_144, out: 235_929 },
|
|
77
69
|
];
|
|
78
70
|
|
|
79
71
|
/** Builds this roster for one provider, since each spells its own endpoint. */
|
|
@@ -37,6 +37,7 @@ import { resolveByokWireProfile } from "../operations/byok-wire-profile";
|
|
|
37
37
|
import { recordAccountFailure, recordAccountSuccess } from "../operations/account-health-service";
|
|
38
38
|
import { buildCapabilityProfile } from "../../transport/routing/route-catalog";
|
|
39
39
|
import { classifyUpstreamFailure } from "../../transport/failure-policy";
|
|
40
|
+
import { terminalFailure } from "../../transport/dispatch/attempt-finalize";
|
|
40
41
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
41
42
|
import type { ModelDefinition } from "../provider-model-definition";
|
|
42
43
|
import type { ProviderAdapter, ProviderId } from "../provider-contracts";
|
|
@@ -579,13 +580,33 @@ export async function recordProbeHealth(args: {
|
|
|
579
580
|
readonly invalidator: { invalidate(): unknown } | undefined;
|
|
580
581
|
readonly accountId: string | undefined;
|
|
581
582
|
readonly dispatchError: unknown;
|
|
583
|
+
/**
|
|
584
|
+
* The probe's canonical events, when there are any.
|
|
585
|
+
*
|
|
586
|
+
* Needed because some adapters deliver a refusal as a terminal event rather
|
|
587
|
+
* than as a thrown error — Cursor's `resource_exhausted` is the case — so
|
|
588
|
+
* `dispatchError` stays `undefined` for a Run the upstream refused. Without
|
|
589
|
+
* the events, that account looked like a success and was recovered.
|
|
590
|
+
*/
|
|
591
|
+
readonly events?: readonly CanonicalEvent[] | undefined;
|
|
582
592
|
readonly modelId: string;
|
|
583
593
|
readonly upstreamStatusCode: number | undefined;
|
|
584
594
|
}): Promise<void> {
|
|
585
595
|
const { db, invalidator, accountId, dispatchError, modelId, upstreamStatusCode } = args;
|
|
586
|
-
|
|
596
|
+
// A terminal `failed` state is written only by the upstream decoders, so it
|
|
597
|
+
// is the upstream refusing — the same thing a thrown error would have meant.
|
|
598
|
+
const terminal = args.events?.find((event) => event.type === "terminal");
|
|
599
|
+
// Reconstructed through the very function the live dispatch path uses, so a
|
|
600
|
+
// refusal carries the same code, status, and health classification in both.
|
|
601
|
+
// Hand-rolling a category here is how the two paths drifted apart.
|
|
602
|
+
const refusedUpstream =
|
|
603
|
+
terminal?.type === "terminal" && terminal.state === "failed"
|
|
604
|
+
? terminalFailure(terminal)
|
|
605
|
+
: undefined;
|
|
606
|
+
const failure = dispatchError ?? refusedUpstream;
|
|
607
|
+
if (failure !== undefined && accountId) {
|
|
587
608
|
const failureEvidence = {
|
|
588
|
-
...classifyUpstreamFailure(
|
|
609
|
+
...classifyUpstreamFailure(failure),
|
|
589
610
|
modelId,
|
|
590
611
|
...(upstreamStatusCode === undefined ? {} : { statusCode: upstreamStatusCode }),
|
|
591
612
|
};
|
|
@@ -593,7 +614,7 @@ export async function recordProbeHealth(args: {
|
|
|
593
614
|
// already classified as `rate_limit_transient` with a real cooldown by
|
|
594
615
|
// `classifyAccountError`, so writing a status here only clobbered that
|
|
595
616
|
// cooldown with a state nothing recovers from.
|
|
596
|
-
await recordAccountFailure(db, accountId,
|
|
617
|
+
await recordAccountFailure(db, accountId, failure, failureEvidence)
|
|
597
618
|
.then((classification) => {
|
|
598
619
|
if (classification?.mutatesAccount) void invalidator?.invalidate();
|
|
599
620
|
})
|
|
@@ -603,7 +624,7 @@ export async function recordProbeHealth(args: {
|
|
|
603
624
|
// (a cooling account flips back to active instead of staying stale).
|
|
604
625
|
// recordAccountSuccess is a no-op write for already-healthy accounts and
|
|
605
626
|
// never throws.
|
|
606
|
-
if (
|
|
627
|
+
if (failure === undefined && accountId) {
|
|
607
628
|
await recordAccountSuccess(db, accountId)
|
|
608
629
|
.then((recovered) => {
|
|
609
630
|
if (recovered) void invalidator?.invalidate();
|
|
@@ -703,7 +724,23 @@ export function computeProbeVerdict(args: {
|
|
|
703
724
|
errorMessage = `${wireFamily} — ${tagged}${upstreamReq ? ` [req ${upstreamReq}]` : ""}`;
|
|
704
725
|
|
|
705
726
|
} else if (terminal?.type === "terminal" && terminal.state !== "complete") {
|
|
706
|
-
|
|
727
|
+
// Read the terminal's own detail before falling back to the stop reason.
|
|
728
|
+
//
|
|
729
|
+
// `stop_reason` is a canonical one-word label — "error" — so reporting it
|
|
730
|
+
// alone turned a Cursor quota refusal into a toast whose entire body was
|
|
731
|
+
// the word "error". The upstream sentence an operator needs is in
|
|
732
|
+
// `stop_details`, which is where the adapter parks it and where the live
|
|
733
|
+
// dispatch path already reads it (see `terminalFailure` in
|
|
734
|
+
// `attempt-finalize.ts`). The probe path disagreed with the dispatch path
|
|
735
|
+
// about the same provider response.
|
|
736
|
+
const detail = terminal.stop_details;
|
|
737
|
+
const message =
|
|
738
|
+
typeof detail?.["message"] === "string" && detail["message"].trim().length > 0
|
|
739
|
+
? detail["message"].trim()
|
|
740
|
+
: undefined;
|
|
741
|
+
const code = typeof detail?.["code"] === "string" ? detail["code"] : undefined;
|
|
742
|
+
const tag = code === undefined ? "" : ` (${code})`;
|
|
743
|
+
errorMessage = `${message ?? terminal.stop_reason ?? "Model probe failed"}${tag}`;
|
|
707
744
|
} else {
|
|
708
745
|
errorMessage = undefined;
|
|
709
746
|
}
|
|
@@ -274,7 +274,13 @@ export class ProviderProbingService {
|
|
|
274
274
|
// The same wrapper a real dispatch uses, so the probe reads the model the
|
|
275
275
|
// upstream named from the raw bytes rather than from a synthesized event.
|
|
276
276
|
const probeFetch = createProbeFetch(
|
|
277
|
-
|
|
277
|
+
// Body capture on, unlike a live dispatch: a probe is one-shot and
|
|
278
|
+
// operator-initiated, so it exists to be inspected. With it off, the
|
|
279
|
+
// provider-response tee resolved to null and every probe row reported
|
|
280
|
+
// the middle two stages as absent — a constant, not a measurement,
|
|
281
|
+
// which made the stage markers useless for the diagnosis they exist
|
|
282
|
+
// for. The 256 KiB cap in the tee bounds the cost.
|
|
283
|
+
captureProviderExchange(outbound.fetch, probeCapture, true),
|
|
278
284
|
);
|
|
279
285
|
try {
|
|
280
286
|
if (serviceKind === "websearch") {
|
|
@@ -435,6 +441,11 @@ export class ProviderProbingService {
|
|
|
435
441
|
invalidator: this.snapshotInvalidator,
|
|
436
442
|
accountId,
|
|
437
443
|
dispatchError,
|
|
444
|
+
// Passed so a refusal delivered as a terminal event — not as a thrown
|
|
445
|
+
// error — still reaches the health machine. Without it, Cursor's
|
|
446
|
+
// `resource_exhausted` left `dispatchError` undefined and the probe
|
|
447
|
+
// recovered an exhausted account.
|
|
448
|
+
...(events === undefined ? {} : { events }),
|
|
438
449
|
modelId,
|
|
439
450
|
upstreamStatusCode,
|
|
440
451
|
});
|
|
@@ -496,12 +507,24 @@ export class ProviderProbingService {
|
|
|
496
507
|
...(tokensPerSec !== undefined ? { tokensPerSec } : {}),
|
|
497
508
|
});
|
|
498
509
|
if (this.telemetryBuffer && payloadCaptureEnabled) {
|
|
510
|
+
// All four surfaces, so the row's stage markers mean the same thing here
|
|
511
|
+
// as they do on a live request: which hop actually produced bytes.
|
|
512
|
+
const providerRequest = probeCapture?.request ?? null;
|
|
513
|
+
const providerResponse = await probeCapture?.response.catch(() => null) ?? null;
|
|
499
514
|
await this.payloadCapture
|
|
500
515
|
.capture({
|
|
501
516
|
tenantId,
|
|
502
517
|
requestId,
|
|
503
518
|
requestBody: capturedRequest,
|
|
504
|
-
|
|
519
|
+
// `events` is what the *client* receives, so it belongs in the client
|
|
520
|
+
// surface. Passing it as `responseBody` lit the client bit on probes
|
|
521
|
+
// whose event list was empty, over a body that was never captured.
|
|
522
|
+
// The legacy alias is explicitly absent here; `null` leaves bit 8 to
|
|
523
|
+
// be decided by the real client body alone.
|
|
524
|
+
responseBody: null,
|
|
525
|
+
...(events === undefined ? {} : { clientResponseBody: events }),
|
|
526
|
+
...(providerRequest === null ? {} : { providerRequestBody: providerRequest }),
|
|
527
|
+
...(providerResponse === null ? {} : { providerResponseBody: providerResponse }),
|
|
505
528
|
scope: "tenant",
|
|
506
529
|
tenantOptIn: true,
|
|
507
530
|
})
|
|
@@ -189,12 +189,20 @@ function parseModelQuotas(payload: Record<string, unknown>): ProviderQuotaWindow
|
|
|
189
189
|
const family = modelFamily(collapseAntigravityVariant(modelId));
|
|
190
190
|
const familyLabel = FAMILY_LABELS[family.key] ?? family.label;
|
|
191
191
|
for (const entry of modelQuotaEntries(model)) {
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
// A bucket with no fraction but a reset time is exhausted, not unknown:
|
|
193
|
+
// the upstream omits the field when there is nothing left to report, so
|
|
194
|
+
// dropping it let a healthy sibling window mask a lane that is blocked
|
|
195
|
+
// until reset. Only a bucket with neither signal is genuinely unknown.
|
|
196
|
+
// This matches what the summary path already does for a disabled bucket.
|
|
197
|
+
const reported = number(entry.info.remainingFraction);
|
|
198
|
+
const resetAt = isoDate(entry.info.resetTime);
|
|
199
|
+
const exhausted = reported === null && resetAt !== null;
|
|
200
|
+
if (reported === null && !exhausted) continue;
|
|
201
|
+
const fraction = reported ?? 0;
|
|
194
202
|
const kind = entry.kind ?? summaryWindowKind(entry.info);
|
|
195
203
|
const label = kind === undefined ? familyLabel : `${familyLabel} (${SUMMARY_WINDOW_LABELS[kind]})`;
|
|
196
204
|
const key = `${family.key}:${kind ?? "model"}`;
|
|
197
|
-
const window = fractionWindow(key, label, fraction,
|
|
205
|
+
const window = fractionWindow(key, label, fraction, resetAt);
|
|
198
206
|
grouped.set(key, keepWorse(grouped.get(key), window));
|
|
199
207
|
}
|
|
200
208
|
}
|
|
@@ -257,8 +257,8 @@ export const BUDDY_SHARED_RAW: readonly BuddyRawEntry[] = [
|
|
|
257
257
|
["claude-opus-4.6", "Claude Opus 4.6", true, true, 200_000, 64_000],
|
|
258
258
|
["claude-opus-4.7-1m", "Claude Opus 4.7 1M", true, true, 1_000_000, 64_000],
|
|
259
259
|
["claude-sonnet-4.6", "Claude Sonnet 4.6", true, true, 200_000, 32_000],
|
|
260
|
-
["deepseek-v4.1-flash", "DeepSeek V4.1 Flash", true,
|
|
261
|
-
["deepseek-v4.1-flash-sg", "DeepSeek V4.1 Flash SG", true,
|
|
260
|
+
["deepseek-v4.1-flash", "DeepSeek V4.1 Flash", true, true, 1_000_000, 384_000],
|
|
261
|
+
["deepseek-v4.1-flash-sg", "DeepSeek V4.1 Flash SG", true, true, 1_000_000, 128_000],
|
|
262
262
|
["deepseek-v4.1-pro", "DeepSeek V4.1 Pro", true, false, 1_000_000, 384_000],
|
|
263
263
|
["gemini-2.5-flash-image", "Gemini 2.5 Flash Image", true, true, 1_000_000, 64_000],
|
|
264
264
|
["gemini-3.0-pro-image", "Gemini 3.0 Pro Image", true, true, 1_000_000, 64_000],
|
|
@@ -286,8 +286,8 @@ export const BUDDY_SHARED_RAW: readonly BuddyRawEntry[] = [
|
|
|
286
286
|
["grok-4.7", "Grok 4.7", true, true, 500_000, 500_000],
|
|
287
287
|
["hy3", "Hy3", true, false, 192_000, 64_000, true],
|
|
288
288
|
["hy3-preview", "Hy3 Preview", true, false, 262_144, 64_000],
|
|
289
|
-
["hy4-preview", "Hy4 Preview", true,
|
|
290
|
-
["hy4-preview-f", "Hy4 Preview F", true,
|
|
289
|
+
["hy4-preview", "Hy4 Preview", true, true, 1_000_000, 64_000, true],
|
|
290
|
+
["hy4-preview-f", "Hy4 Preview F", true, true, 1_000_000, 64_000, true],
|
|
291
291
|
["kimi-k2.5", "Kimi K2.5", true, false, 164_000, 262_144],
|
|
292
292
|
["kimi-k2.6", "Kimi K2.6", true, false, 256_000, 262_144],
|
|
293
293
|
["kimi-k2.7", "Kimi K2.7", true, false, 256_000, 65_536],
|