llm-relay 0.47.0 → 0.48.0
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/dist/accounting-store-io.js +1 -9
- package/dist/accounting-store-io.js.map +1 -1
- package/dist/accounting-store-schema.d.ts +0 -35
- package/dist/accounting-store-schema.js +14 -101
- package/dist/accounting-store-schema.js.map +1 -1
- package/dist/accounting-store.d.ts +7 -0
- package/dist/accounting-store.js +3 -1
- package/dist/accounting-store.js.map +1 -1
- package/dist/availability-snapshot.js +14 -44
- package/dist/availability-snapshot.js.map +1 -1
- package/dist/availability.d.ts +31 -4
- package/dist/availability.js +36 -0
- package/dist/availability.js.map +1 -1
- package/dist/backend.js +12 -20
- package/dist/backend.js.map +1 -1
- package/dist/candidates.js +12 -46
- package/dist/candidates.js.map +1 -1
- package/dist/cli.js +47 -178
- package/dist/cli.js.map +1 -1
- package/dist/configured-limits.js +55 -106
- package/dist/configured-limits.js.map +1 -1
- package/dist/cooldown-clear.d.ts +3 -0
- package/dist/cooldown-clear.js +158 -2
- package/dist/cooldown-clear.js.map +1 -1
- package/dist/dashboard/.vite/manifest.json +1 -1
- package/dist/dashboard/assets/{index-u-Ez_oE6.js → index-BA4GdK4p.js} +1 -1
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard-contract.js +1 -5
- package/dist/dashboard-contract.js.map +1 -1
- package/dist/dashboard-routes.js +29 -57
- package/dist/dashboard-routes.js.map +1 -1
- package/dist/dashboard-snapshot.js +17 -42
- package/dist/dashboard-snapshot.js.map +1 -1
- package/dist/dashboard-static.js +1 -3
- package/dist/dashboard-static.js.map +1 -1
- package/dist/dialect-stream.js +13 -21
- package/dist/dialect-stream.js.map +1 -1
- package/dist/hard-cap.d.ts +3 -1
- package/dist/hard-cap.js +11 -0
- package/dist/hard-cap.js.map +1 -1
- package/dist/json-shape.d.ts +22 -0
- package/dist/json-shape.js +40 -0
- package/dist/json-shape.js.map +1 -0
- package/dist/keys-cli.js +4 -60
- package/dist/keys-cli.js.map +1 -1
- package/dist/keystore.js +11 -18
- package/dist/keystore.js.map +1 -1
- package/dist/openai-dialect.js +46 -40
- package/dist/openai-dialect.js.map +1 -1
- package/dist/openai-request.d.ts +0 -26
- package/dist/openai-request.js +1 -3
- package/dist/openai-request.js.map +1 -1
- package/dist/quota-demotion.js +9 -34
- package/dist/quota-demotion.js.map +1 -1
- package/dist/refusal-interpretation.js +1 -4
- package/dist/refusal-interpretation.js.map +1 -1
- package/dist/responses-request.d.ts +0 -76
- package/dist/responses-request.js +1 -3
- package/dist/responses-request.js.map +1 -1
- package/dist/server.js +122 -150
- package/dist/server.js.map +1 -1
- package/dist/sse-frames.d.ts +39 -0
- package/dist/sse-frames.js +65 -0
- package/dist/sse-frames.js.map +1 -0
- package/dist/stream-commit.js +14 -24
- package/dist/stream-commit.js.map +1 -1
- package/dist/target-facts.d.ts +6 -0
- package/dist/target-facts.js +8 -5
- package/dist/target-facts.js.map +1 -1
- package/dist/think-tags.js +11 -30
- package/dist/think-tags.js.map +1 -1
- package/dist/tool-use-ids.d.ts +0 -31
- package/dist/tool-use-ids.js +11 -63
- package/dist/tool-use-ids.js.map +1 -1
- package/dist/usage-observer.js +22 -33
- package/dist/usage-observer.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { isRecord } from "./json-shape.js";
|
|
2
3
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { homedir } from "node:os";
|
|
@@ -8,7 +9,7 @@ import { loadEnvFile } from "./dotenv.js";
|
|
|
8
9
|
import { recoverWindowsEnv } from "./winenv.js";
|
|
9
10
|
import { offloadState, setOffload } from "./offload.js";
|
|
10
11
|
import { buildCandidates } from "./candidates.js";
|
|
11
|
-
import { CREDENTIAL_LABEL_PATTERN, makeCredentialId
|
|
12
|
+
import { CREDENTIAL_LABEL_PATTERN, makeCredentialId } from "./credential-id.js";
|
|
12
13
|
import { providerCredentialSlots, slotAllowsModel } from "./credential-fleet.js";
|
|
13
14
|
import { loadLaneManifest, verifyModel } from "./lane-manifest.js";
|
|
14
15
|
import { probeLanes } from "./lane-probe.js";
|
|
@@ -28,6 +29,7 @@ import { materializeDynamicPools } from "./dynamic-pools.js";
|
|
|
28
29
|
import { currentVersion, ensureUpToDate, shouldCheckUpdates } from "./self-update.js";
|
|
29
30
|
import { deleteConfigPath, parseConfigValue, readConfigDocument, readConfigPath, updateConfigDocument, writeConfigPath, } from "./config-edit.js";
|
|
30
31
|
import { createControlAuthorization, resolveControlAuthorizationConfigDir } from "./control-authorization.js";
|
|
32
|
+
import { isCooldownClearResult } from "./cooldown-clear.js";
|
|
31
33
|
import { createDashboardSnapshotReadPort } from "./dashboard-snapshot.js";
|
|
32
34
|
import { DASHBOARD_MEDIA_TYPE, isDashboardUtcTimestamp } from "./dashboard-contract.js";
|
|
33
35
|
import { DASHBOARD_BOOTSTRAP_SCHEMA, DASHBOARD_BOOTSTRAP_REQUEST_SCHEMA } from "./dashboard-routes.js";
|
|
@@ -751,136 +753,6 @@ function cooldownCommandFailure(message) {
|
|
|
751
753
|
process.stderr.write(`llm-relay cooldowns: ${message}\n`);
|
|
752
754
|
process.exit(1);
|
|
753
755
|
}
|
|
754
|
-
function isRecord(value) {
|
|
755
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
756
|
-
}
|
|
757
|
-
function hasExactKeys(value, keys) {
|
|
758
|
-
const actual = Object.keys(value);
|
|
759
|
-
return actual.length === keys.length && actual.every((key) => keys.includes(key));
|
|
760
|
-
}
|
|
761
|
-
function nonEmptyString(value) {
|
|
762
|
-
return typeof value === "string" && value.length > 0;
|
|
763
|
-
}
|
|
764
|
-
const COOLING_FACT_KINDS = new Set([
|
|
765
|
-
"allowance-exhausted",
|
|
766
|
-
"rate-limited",
|
|
767
|
-
"credential-invalid",
|
|
768
|
-
]);
|
|
769
|
-
function credentialIdBelongsTo(provider, value) {
|
|
770
|
-
if (typeof value !== "string")
|
|
771
|
-
return false;
|
|
772
|
-
return parseCredentialId(value)?.provider === provider;
|
|
773
|
-
}
|
|
774
|
-
function isCooldownFactScope(value) {
|
|
775
|
-
if (!isRecord(value))
|
|
776
|
-
return false;
|
|
777
|
-
switch (value.kind) {
|
|
778
|
-
case "attempt":
|
|
779
|
-
return hasExactKeys(value, ["kind", "provider", "credentialId", "model"]) &&
|
|
780
|
-
nonEmptyString(value.provider) &&
|
|
781
|
-
credentialIdBelongsTo(value.provider, value.credentialId) &&
|
|
782
|
-
nonEmptyString(value.model);
|
|
783
|
-
case "group": {
|
|
784
|
-
const keys = Object.hasOwn(value, "credentialId")
|
|
785
|
-
? ["kind", "provider", "credentialId", "members"]
|
|
786
|
-
: ["kind", "provider", "members"];
|
|
787
|
-
return hasExactKeys(value, keys) &&
|
|
788
|
-
nonEmptyString(value.provider) &&
|
|
789
|
-
Array.isArray(value.members) &&
|
|
790
|
-
value.members.length > 0 &&
|
|
791
|
-
value.members.every(nonEmptyString) &&
|
|
792
|
-
(!Object.hasOwn(value, "credentialId") || credentialIdBelongsTo(value.provider, value.credentialId));
|
|
793
|
-
}
|
|
794
|
-
case "deployment":
|
|
795
|
-
return hasExactKeys(value, ["kind", "provider", "model"]) &&
|
|
796
|
-
nonEmptyString(value.provider) && nonEmptyString(value.model);
|
|
797
|
-
case "credential":
|
|
798
|
-
return hasExactKeys(value, ["kind", "provider", "credentialId"]) &&
|
|
799
|
-
nonEmptyString(value.provider) && credentialIdBelongsTo(value.provider, value.credentialId);
|
|
800
|
-
case "provider":
|
|
801
|
-
return hasExactKeys(value, ["kind", "provider"]) && nonEmptyString(value.provider);
|
|
802
|
-
case "model":
|
|
803
|
-
return hasExactKeys(value, ["kind", "model"]) && nonEmptyString(value.model);
|
|
804
|
-
default:
|
|
805
|
-
return false;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
function scopeIsContainedByTarget(scope, target) {
|
|
809
|
-
const credentialId = target.credential === undefined
|
|
810
|
-
? undefined
|
|
811
|
-
: makeCredentialId(target.provider, target.credential);
|
|
812
|
-
switch (scope.kind) {
|
|
813
|
-
case "attempt":
|
|
814
|
-
return scope.provider === target.provider &&
|
|
815
|
-
(target.model === undefined || scope.model === target.model) &&
|
|
816
|
-
(credentialId === undefined || scope.credentialId === credentialId);
|
|
817
|
-
case "group":
|
|
818
|
-
return scope.provider === target.provider &&
|
|
819
|
-
(target.model === undefined || scope.members.every((member) => member === target.model)) &&
|
|
820
|
-
(credentialId === undefined || scope.credentialId === credentialId);
|
|
821
|
-
case "deployment":
|
|
822
|
-
return scope.provider === target.provider && credentialId === undefined &&
|
|
823
|
-
(target.model === undefined || scope.model === target.model);
|
|
824
|
-
case "credential":
|
|
825
|
-
return scope.provider === target.provider && target.model === undefined &&
|
|
826
|
-
(credentialId === undefined || scope.credentialId === credentialId);
|
|
827
|
-
case "provider":
|
|
828
|
-
return scope.provider === target.provider && target.model === undefined && credentialId === undefined;
|
|
829
|
-
case "model":
|
|
830
|
-
return false;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
function isCooldownCell(value, target) {
|
|
834
|
-
if (!isRecord(value) || !hasExactKeys(value, ["provider", "model", "credential"]))
|
|
835
|
-
return false;
|
|
836
|
-
if (!nonEmptyString(value.provider) || value.provider !== target.provider)
|
|
837
|
-
return false;
|
|
838
|
-
if (!(value.model === null || nonEmptyString(value.model)))
|
|
839
|
-
return false;
|
|
840
|
-
if (!nonEmptyString(value.credential) || !CREDENTIAL_LABEL_PATTERN.test(value.credential))
|
|
841
|
-
return false;
|
|
842
|
-
return (target.model === undefined || value.model === target.model) &&
|
|
843
|
-
(target.credential === undefined || value.credential === target.credential);
|
|
844
|
-
}
|
|
845
|
-
function isCooldownFact(value, target) {
|
|
846
|
-
return isRecord(value) &&
|
|
847
|
-
hasExactKeys(value, ["kind", "scope"]) &&
|
|
848
|
-
typeof value.kind === "string" &&
|
|
849
|
-
COOLING_FACT_KINDS.has(value.kind) &&
|
|
850
|
-
isCooldownFactScope(value.scope) &&
|
|
851
|
-
scopeIsContainedByTarget(value.scope, target);
|
|
852
|
-
}
|
|
853
|
-
function isClearedGroup(value, isItem) {
|
|
854
|
-
if (!isRecord(value) || !hasExactKeys(value, ["count", "items"]) ||
|
|
855
|
-
typeof value.count !== "number" || !Number.isSafeInteger(value.count) ||
|
|
856
|
-
value.count < 0 || !Array.isArray(value.items)) {
|
|
857
|
-
return false;
|
|
858
|
-
}
|
|
859
|
-
return value.count === value.items.length && value.items.every(isItem);
|
|
860
|
-
}
|
|
861
|
-
function isExactCooldownTarget(value, expected) {
|
|
862
|
-
if (!isRecord(value))
|
|
863
|
-
return false;
|
|
864
|
-
const keys = [
|
|
865
|
-
"provider",
|
|
866
|
-
...(expected.model === undefined ? [] : ["model"]),
|
|
867
|
-
...(expected.credential === undefined ? [] : ["credential"]),
|
|
868
|
-
];
|
|
869
|
-
return hasExactKeys(value, keys) &&
|
|
870
|
-
value.provider === expected.provider &&
|
|
871
|
-
(expected.model === undefined || value.model === expected.model) &&
|
|
872
|
-
(expected.credential === undefined || value.credential === expected.credential);
|
|
873
|
-
}
|
|
874
|
-
function isCooldownClearResult(value, target) {
|
|
875
|
-
if (!isRecord(value) || !hasExactKeys(value, ["target", "cleared"]) ||
|
|
876
|
-
!isExactCooldownTarget(value.target, target) || !isRecord(value.cleared) ||
|
|
877
|
-
!hasExactKeys(value.cleared, ["breakerCells", "credentialFaults", "facts"])) {
|
|
878
|
-
return false;
|
|
879
|
-
}
|
|
880
|
-
return isClearedGroup(value.cleared.breakerCells, (item) => isCooldownCell(item, target)) &&
|
|
881
|
-
isClearedGroup(value.cleared.credentialFaults, (item) => isCooldownCell(item, target)) &&
|
|
882
|
-
isClearedGroup(value.cleared.facts, (item) => isCooldownFact(item, target));
|
|
883
|
-
}
|
|
884
756
|
const COOLDOWN_CLEAR_OPTIONS = new Map([
|
|
885
757
|
["--credential", { semantic: "credential", takesValue: true }],
|
|
886
758
|
["--json", { semantic: "json", takesValue: false }],
|
|
@@ -1089,6 +961,11 @@ export async function runCooldowns(_action, _spec) {
|
|
|
1089
961
|
...(model === undefined ? {} : { model }),
|
|
1090
962
|
...(credential === undefined ? {} : { credential }),
|
|
1091
963
|
};
|
|
964
|
+
const acceptedTargetKeys = [
|
|
965
|
+
"provider",
|
|
966
|
+
...(model === undefined ? [] : ["model"]),
|
|
967
|
+
...(credential === undefined ? [] : ["credential"]),
|
|
968
|
+
];
|
|
1092
969
|
const cfg = loadOrExit();
|
|
1093
970
|
if (!Object.hasOwn(cfg.providers, provider)) {
|
|
1094
971
|
cooldownCommandFailure(`no provider "${provider}" configured`);
|
|
@@ -1126,7 +1003,7 @@ export async function runCooldowns(_action, _spec) {
|
|
|
1126
1003
|
const detail = controlErrorMessage(payload);
|
|
1127
1004
|
cooldownCommandFailure(`the running relay rejected the clear${detail === null ? "" : `: ${detail}`}`);
|
|
1128
1005
|
}
|
|
1129
|
-
if (!isCooldownClearResult(payload, target)) {
|
|
1006
|
+
if (!isCooldownClearResult(payload, target, acceptedTargetKeys)) {
|
|
1130
1007
|
cooldownCommandFailure("the running relay returned an invalid cooldown-clear response");
|
|
1131
1008
|
}
|
|
1132
1009
|
if (parsed.json) {
|
|
@@ -2348,29 +2225,7 @@ export async function runCandidates() {
|
|
|
2348
2225
|
.join(", ");
|
|
2349
2226
|
process.stdout.write(`Offload targets — ${clientState || `legacy/global: ${view.offload_enabled ? "on" : "off"}`}\n`);
|
|
2350
2227
|
process.stdout.write(`${view.note}\n\n`);
|
|
2351
|
-
const
|
|
2352
|
-
"credential".padEnd(40) +
|
|
2353
|
-
"pools / tiers".padEnd(24) +
|
|
2354
|
-
"fit".padEnd(7) +
|
|
2355
|
-
"raw".padEnd(7) +
|
|
2356
|
-
"cap".padEnd(11) +
|
|
2357
|
-
"agentic".padEnd(9) +
|
|
2358
|
-
"coding".padEnd(8) +
|
|
2359
|
-
"BFCL".padEnd(7) +
|
|
2360
|
-
"aider".padEnd(7) +
|
|
2361
|
-
"arena".padEnd(7) +
|
|
2362
|
-
"$/Mout".padEnd(8) +
|
|
2363
|
-
"verdict".padEnd(10) +
|
|
2364
|
-
"p95".padEnd(8) +
|
|
2365
|
-
// Latency actually observed on this proxy's own traffic. The synthetic-probe p95 beside it is
|
|
2366
|
-
// routinely blank, so the table could show the first pool member with NO latency signal at all
|
|
2367
|
-
// while the proxy had already measured it at 60+ seconds per call — which is the difference
|
|
2368
|
-
// between a pool that suits mechanical batch work and one that does not.
|
|
2369
|
-
"obs".padEnd(8) +
|
|
2370
|
-
"breaker".padEnd(9) +
|
|
2371
|
-
"ctx";
|
|
2372
|
-
process.stdout.write(head + "\n" + "-".repeat(head.length) + "\n");
|
|
2373
|
-
for (const c of view.candidates) {
|
|
2228
|
+
for (const [index, c] of view.candidates.entries()) {
|
|
2374
2229
|
const credential = candidateCredential(c);
|
|
2375
2230
|
const tags = [...c.pools, ...c.subagentTiers.map((t) => `@${t}`)].join(",") || "-";
|
|
2376
2231
|
const live = c.listed === null ? "?" : c.listed ? "yes" : "NO";
|
|
@@ -2387,32 +2242,46 @@ export async function runCandidates() {
|
|
|
2387
2242
|
: c.breaker.credentialFault
|
|
2388
2243
|
? `AUTH ${c.breaker.lastCredentialStatus ?? ""}`.trim()
|
|
2389
2244
|
: "closed";
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
c.sortInputs.
|
|
2245
|
+
const columns = [
|
|
2246
|
+
["target", 32, c.spec.slice(0, 31)],
|
|
2247
|
+
["credential", 40, `${credential.label} (${c.credentialId})`],
|
|
2248
|
+
["pools / tiers", 24, tags.slice(0, 23)],
|
|
2249
|
+
["fit", 7, c.sortInputs.fitness.toFixed(1)],
|
|
2250
|
+
["raw", 7, c.sortInputs.rawStrength.toFixed(1)],
|
|
2395
2251
|
// Capability plus how well-evidenced it is: "76.6/4" = 4 published signals behind it,
|
|
2396
2252
|
// "50.0 neut" = nothing known. Never show the number alone.
|
|
2397
|
-
`${c.sortInputs.strength.toFixed(1)}${strengthTag(c)}
|
|
2398
|
-
fmt(c.scores.aaAgentic)
|
|
2399
|
-
fmt(c.scores.aaCoding)
|
|
2400
|
-
fmt(c.scores.bfclOverall)
|
|
2401
|
-
fmt(c.scores.aiderPassRate)
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
fmt(c.health?.p95Ms ?? null, "ms")
|
|
2408
|
-
|
|
2409
|
-
|
|
2253
|
+
["cap", 11, `${c.sortInputs.strength.toFixed(1)}${strengthTag(c)}`],
|
|
2254
|
+
["agentic", 9, fmt(c.scores.aaAgentic)],
|
|
2255
|
+
["coding", 8, fmt(c.scores.aaCoding)],
|
|
2256
|
+
["BFCL", 7, fmt(c.scores.bfclOverall)],
|
|
2257
|
+
["aider", 7, fmt(c.scores.aiderPassRate)],
|
|
2258
|
+
["arena", 7, c.scores.arenaRating ? String(Math.round(c.scores.arenaRating)) : "-"],
|
|
2259
|
+
["$/Mout", 8, c.pricePerMTokOut !== null
|
|
2260
|
+
? `$${c.pricePerMTokOut}${c.priceSource === "reference" ? "~" : ""}`
|
|
2261
|
+
: "-"],
|
|
2262
|
+
["verdict", 10, c.health?.verdict ?? "-"],
|
|
2263
|
+
["p95", 8, fmt(c.health?.p95Ms ?? null, "ms")],
|
|
2264
|
+
// Latency actually observed on this proxy's own traffic. The synthetic-probe p95 beside it is
|
|
2265
|
+
// routinely blank, so the table could show the first pool member with NO latency signal at all
|
|
2266
|
+
// while the proxy had already measured it at 60+ seconds per call — which is the difference
|
|
2267
|
+
// between a pool that suits mechanical batch work and one that does not.
|
|
2268
|
+
["obs", 8, obsLatency(c.observed)],
|
|
2269
|
+
["breaker", 9, breaker],
|
|
2410
2270
|
// Provenance inline: "~" = another provider's figure for this model id. A NIM row must
|
|
2411
|
-
// never present OpenRouter's ceiling as its own.
|
|
2412
|
-
ctx +
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2271
|
+
// never present OpenRouter's ceiling as its own. `content` keeps this trailing column unpadded.
|
|
2272
|
+
["ctx", "content", ctx +
|
|
2273
|
+
(c.contextLengthSource === "reference" ? "~" : "") +
|
|
2274
|
+
(live === "NO" ? " ⚠UNLISTED" : "")],
|
|
2275
|
+
];
|
|
2276
|
+
const formatColumns = (part) => columns.map(([heading, width, cell]) => {
|
|
2277
|
+
const content = part === "heading" ? heading : cell;
|
|
2278
|
+
return width === "content" ? content : content.padEnd(width);
|
|
2279
|
+
}).join("");
|
|
2280
|
+
if (index === 0) {
|
|
2281
|
+
const head = formatColumns("heading");
|
|
2282
|
+
process.stdout.write(head + "\n" + "-".repeat(head.length) + "\n");
|
|
2283
|
+
}
|
|
2284
|
+
process.stdout.write(`${formatColumns("cell")}\n`);
|
|
2416
2285
|
// The resolved ladders print on their own line: raw observations first (what providers
|
|
2417
2286
|
// said), then the derived view with its basis, so a computed figure never appears without
|
|
2418
2287
|
// its provenance. Spec §5: unknown stays "-", and a negative remaining prints as-is.
|