aigetwey 1.2.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -1
- package/README.md +30 -7
- package/assets/screenshot.png +0 -0
- package/config.example.yaml +0 -1
- package/dashboard/src/app/(console)/quota/page.tsx +2 -2
- package/dashboard/src/app/layout.tsx +3 -2
- package/dashboard/src/components/BudgetForm.tsx +15 -17
- package/dashboard/src/components/{QuotaView.tsx → BudgetTracker.tsx} +71 -56
- package/dashboard/src/components/CooldownTimer.tsx +1 -1
- package/dashboard/src/components/EndpointView.tsx +255 -47
- package/dashboard/src/components/LogTable.tsx +36 -26
- package/dashboard/src/components/ProviderManager.tsx +3 -28
- package/dashboard/src/components/Rail.tsx +1 -1
- package/dashboard/src/components/RoutingView.tsx +6 -2
- package/dashboard/src/components/TopBar.tsx +1 -1
- package/dashboard/src/components/ui.tsx +6 -1
- package/dashboard/src/lib/client.ts +6 -5
- package/dashboard/src/lib/gateway.ts +24 -16
- package/dist/adapters/gemini.js +1 -0
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/openai.js +13 -1
- package/dist/adapters/openai.js.map +1 -1
- package/dist/config.js +86 -23
- package/dist/config.js.map +1 -1
- package/dist/core/budget.js +1 -1
- package/dist/core/budget.js.map +1 -1
- package/dist/core/fallback.js +0 -6
- package/dist/core/fallback.js.map +1 -1
- package/dist/core/handler.js +13 -7
- package/dist/core/handler.js.map +1 -1
- package/dist/core/keysUsage.js +15 -0
- package/dist/core/keysUsage.js.map +1 -0
- package/dist/core/ratelimit.js +15 -0
- package/dist/core/ratelimit.js.map +1 -0
- package/dist/core/state.js +5 -13
- package/dist/core/state.js.map +1 -1
- package/dist/core/window.js +35 -0
- package/dist/core/window.js.map +1 -0
- package/dist/db.js +34 -29
- package/dist/db.js.map +1 -1
- package/dist/routes/admin.js +55 -10
- package/dist/routes/admin.js.map +1 -1
- package/dist/routes/v1.js +14 -1
- package/dist/routes/v1.js.map +1 -1
- package/dist/server.js +1 -7
- package/dist/server.js.map +1 -1
- package/dist/stream/anthropic-stream.js +7 -0
- package/dist/stream/anthropic-stream.js.map +1 -1
- package/dist/stream/gemini-stream.js +2 -1
- package/dist/stream/gemini-stream.js.map +1 -1
- package/dist/stream/openai-stream.js +10 -0
- package/dist/stream/openai-stream.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +2 -0
- package/src/adapters/openai.ts +18 -1
- package/src/config.ts +89 -23
- package/src/core/budget.ts +1 -1
- package/src/core/fallback.ts +0 -9
- package/src/core/handler.ts +16 -9
- package/src/core/keysUsage.ts +49 -0
- package/src/core/ratelimit.ts +25 -0
- package/src/core/state.ts +4 -14
- package/src/core/window.ts +45 -0
- package/src/db.ts +35 -31
- package/src/routes/admin.ts +61 -9
- package/src/routes/v1.ts +18 -1
- package/src/server.ts +1 -8
- package/src/stream/anthropic-stream.ts +10 -1
- package/src/stream/chunk.ts +2 -0
- package/src/stream/gemini-stream.ts +3 -2
- package/src/stream/openai-stream.ts +12 -1
- package/src/core/quota.ts +0 -253
package/dist/core/handler.js
CHANGED
|
@@ -20,8 +20,8 @@ export class GatewayError extends Error {
|
|
|
20
20
|
function recordUsage(deps, route, usage, status, latencyMs, stream) {
|
|
21
21
|
const tokensIn = usage?.prompt_tokens ?? 0;
|
|
22
22
|
const tokensOut = usage?.completion_tokens ?? 0;
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const reasoningTokens = usage?.reasoning_tokens ?? 0;
|
|
24
|
+
const cachedTokens = usage?.cached_tokens ?? 0;
|
|
25
25
|
if (!deps.db)
|
|
26
26
|
return;
|
|
27
27
|
// Cost: a combo/route may set explicit prices; otherwise fall back to the ported
|
|
@@ -29,14 +29,17 @@ function recordUsage(deps, route, usage, status, latencyMs, stream) {
|
|
|
29
29
|
const pricing = getPricingForModel(route.provider.id, route.model);
|
|
30
30
|
const priceIn = route.price_in ?? pricing?.input;
|
|
31
31
|
const priceOut = route.price_out ?? pricing?.output;
|
|
32
|
+
const priceCachedRead = pricing?.cached;
|
|
33
|
+
const priceReasoning = pricing?.reasoning;
|
|
32
34
|
deps.db.record({
|
|
33
35
|
alias: route.alias,
|
|
34
36
|
provider: route.provider.id,
|
|
35
37
|
model: route.model,
|
|
36
38
|
tokens_in: tokensIn,
|
|
37
39
|
tokens_out: tokensOut,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
reasoning_tokens: reasoningTokens,
|
|
41
|
+
cached_tokens: cachedTokens,
|
|
42
|
+
cost: computeCost(tokensIn, tokensOut, priceIn, priceOut, priceReasoning, priceCachedRead, cachedTokens, reasoningTokens),
|
|
40
43
|
status,
|
|
41
44
|
latency_ms: latencyMs,
|
|
42
45
|
stream: stream ? 1 : 0,
|
|
@@ -60,14 +63,18 @@ export async function handle(deps, clientFormat, body, signal) {
|
|
|
60
63
|
// that can't reason. Matches aigetwey's capture-before-translate flow.
|
|
61
64
|
const { cleanModel, override } = parseSuffix(canonical.model);
|
|
62
65
|
canonical.model = cleanModel;
|
|
66
|
+
// per-key allowlist: a key may be restricted to specific call-strings. Empty/
|
|
67
|
+
// absent → unrestricted. Match the literal clean model the client asked for.
|
|
68
|
+
if (deps.clientKeyModels && deps.clientKeyModels.length > 0 && !deps.clientKeyModels.includes(cleanModel)) {
|
|
69
|
+
throw new GatewayError(403, { error: "model not allowed for this key" });
|
|
70
|
+
}
|
|
63
71
|
const thinkingIntent = override ?? captureThinking(canonical);
|
|
64
72
|
let routes = config.resolve(canonical.model);
|
|
65
73
|
if (routes.length === 0) {
|
|
66
74
|
throw new GatewayError(404, { error: `unknown model "${canonical.model}"` });
|
|
67
75
|
}
|
|
68
76
|
// Budget hard-stop. Global overrun fails fast. Provider/model budgets bar the
|
|
69
|
-
// matching routes
|
|
70
|
-
// there's nothing to serve → 402.
|
|
77
|
+
// matching routes; if every candidate is barred, there's nothing to serve → 402.
|
|
71
78
|
if (deps.budget) {
|
|
72
79
|
const g = deps.budget.globalStatus();
|
|
73
80
|
if (g?.exhausted)
|
|
@@ -130,7 +137,6 @@ export async function handle(deps, clientFormat, body, signal) {
|
|
|
130
137
|
stream: wantStream,
|
|
131
138
|
signal,
|
|
132
139
|
thinkingIntent,
|
|
133
|
-
isExhausted: deps.quota ? (p) => deps.quota.isExhausted(p) : undefined,
|
|
134
140
|
onAttempt: (a) => deps.log?.(`[fallback] ${a.provider}/${a.model} ${a.status ?? "-"} -> ${a.outcome}${a.detail ? ` (${a.detail})` : ""}`),
|
|
135
141
|
});
|
|
136
142
|
}
|
package/dist/core/handler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/core/handler.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/core/handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function buildKeyUsageRow(input) {
|
|
2
|
+
const b = input.budget;
|
|
3
|
+
return {
|
|
4
|
+
fingerprint: input.fingerprint,
|
|
5
|
+
name: input.name,
|
|
6
|
+
masked: input.masked,
|
|
7
|
+
expires: input.expires,
|
|
8
|
+
spent: input.totals.cost,
|
|
9
|
+
tokens: input.totals.tokens_in + input.totals.tokens_out,
|
|
10
|
+
budget: b
|
|
11
|
+
? { unit: b.unit, limit: b.limit, spent: b.spent, pct: b.pct, window: b.window, reset_in_ms: b.reset_in_ms, exhausted: b.exhausted, alert: b.alert }
|
|
12
|
+
: null,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=keysUsage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keysUsage.js","sourceRoot":"","sources":["../../src/core/keysUsage.ts"],"names":[],"mappings":"AA4BA,MAAM,UAAU,gBAAgB,CAAC,KAOhC;IACC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IACvB,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;QACxB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU;QACxD,MAAM,EAAE,CAAC;YACP,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE;YACpJ,CAAC,CAAC,IAAI;KACT,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class RateLimiter {
|
|
2
|
+
buckets = new Map();
|
|
3
|
+
/** Record a hit for `key`; return true if it now EXCEEDS `limit` this minute. */
|
|
4
|
+
over(key, limit, now = Date.now()) {
|
|
5
|
+
const minute = Math.floor(now / 60_000);
|
|
6
|
+
const b = this.buckets.get(key);
|
|
7
|
+
if (!b || b.minute !== minute) {
|
|
8
|
+
this.buckets.set(key, { minute, count: 1 });
|
|
9
|
+
return 1 > limit;
|
|
10
|
+
}
|
|
11
|
+
b.count += 1;
|
|
12
|
+
return b.count > limit;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=ratelimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../../src/core/ratelimit.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,WAAW;IACL,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAErD,iFAAiF;IACjF,IAAI,CAAC,GAAW,EAAE,KAAa,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5C,OAAO,CAAC,GAAG,KAAK,CAAC;QACnB,CAAC;QACD,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QACb,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC;IACzB,CAAC;CACF"}
|
package/dist/core/state.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mutable holder for the live gateway config, key pool, and
|
|
2
|
+
* Mutable holder for the live gateway config, key pool, and budget tracker.
|
|
3
3
|
*
|
|
4
4
|
* Config loads once at boot, but the dashboard edits it at runtime. Routes read
|
|
5
|
-
* `state.config` / `state.pool` / `state.
|
|
5
|
+
* `state.config` / `state.pool` / `state.budget` fresh per request (never close
|
|
6
6
|
* over them), so a successful reload swaps in the new config + pool atomically —
|
|
7
7
|
* no restart.
|
|
8
8
|
*
|
|
9
9
|
* reload() validates and persists BEFORE swapping: an invalid edit throws and
|
|
10
|
-
* the old config keeps serving. The pool is rebuilt (cooldown is transient)
|
|
11
|
-
* the quota tracker is KEPT across reloads — a budget consumed this window must
|
|
12
|
-
* survive a config edit, else editing config would silently reset every quota.
|
|
10
|
+
* the old config keeps serving. The pool is rebuilt (cooldown is transient).
|
|
13
11
|
*/
|
|
14
12
|
import { parseConfigText, validateConfig, unmaskSecrets, writeConfigFile, maskKey, } from "../config.js";
|
|
15
13
|
import { clientKeyFingerprint } from "../middleware/auth.js";
|
|
16
14
|
import { KeyPool } from "./keypool.js";
|
|
17
|
-
import { QuotaTracker } from "./quota.js";
|
|
18
15
|
import { BudgetTracker } from "./budget.js";
|
|
19
16
|
function serverKeyLabel(server, fp) {
|
|
20
17
|
for (const k of server.api_keys) {
|
|
@@ -27,13 +24,11 @@ export class GatewayState {
|
|
|
27
24
|
configPath;
|
|
28
25
|
_config;
|
|
29
26
|
_pool;
|
|
30
|
-
_quota;
|
|
31
27
|
_budget;
|
|
32
|
-
constructor(configPath, initial,
|
|
28
|
+
constructor(configPath, initial, budgetDb) {
|
|
33
29
|
this.configPath = configPath;
|
|
34
30
|
this._config = initial;
|
|
35
31
|
this._pool = new KeyPool();
|
|
36
|
-
this._quota = quota ?? new QuotaTracker();
|
|
37
32
|
this._budget = new BudgetTracker(() => this._config.raw.budgets, budgetDb ?? { totals: () => ({ tokens_in: 0, tokens_out: 0, cost: 0 }) }, undefined, undefined, (fp) => serverKeyLabel(this._config.raw.server, fp));
|
|
38
33
|
}
|
|
39
34
|
get config() {
|
|
@@ -42,9 +37,6 @@ export class GatewayState {
|
|
|
42
37
|
get pool() {
|
|
43
38
|
return this._pool;
|
|
44
39
|
}
|
|
45
|
-
get quota() {
|
|
46
|
-
return this._quota;
|
|
47
|
-
}
|
|
48
40
|
get budget() {
|
|
49
41
|
return this._budget;
|
|
50
42
|
}
|
|
@@ -52,7 +44,7 @@ export class GatewayState {
|
|
|
52
44
|
* Validate edited config text, restore masked secrets from the live config,
|
|
53
45
|
* persist atomically, then swap in a fresh config + pool. Throws without
|
|
54
46
|
* changing anything if validation fails or a masked key can't be resolved —
|
|
55
|
-
* the old config keeps serving.
|
|
47
|
+
* the old config keeps serving.
|
|
56
48
|
*/
|
|
57
49
|
reload(text) {
|
|
58
50
|
const parsed = parseConfigText(text);
|
package/dist/core/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAEL,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,OAAO,GACR,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,SAAS,cAAc,CAAC,MAAkE,EAAE,EAAU;IACpG,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAChC,IAAI,oBAAoB,CAAC,CAAC,CAAC,KAAK,EAAE;YAAE,OAAO,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,QAAQ,EAAE,EAAE,CAAC;AACtB,CAAC;AAED,MAAM,OAAO,YAAY;IAMJ;IALX,OAAO,CAAgB;IACvB,KAAK,CAAU;IACN,OAAO,CAAgB;IAExC,YACmB,UAAkB,EACnC,OAAsB,EACtB,QAAkK;QAFjJ,eAAU,GAAV,UAAU,CAAQ;QAInC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,aAAa,CAC9B,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAC9B,QAAQ,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,EACxE,SAAS,EACT,SAAS,EACT,CAAC,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CACpD,CAAC;IACJ,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,IAAY;QACjB,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QACpC,eAAe,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,KAAK,GAAG,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rolling-window engine: every budget window is a fixed-duration tumbling bucket
|
|
3
|
+
* aligned to the epoch grid (no calendar/timezone math). `5h` resets every five
|
|
4
|
+
* hours, `24h` daily, `7day` weekly, `30day` monthly — each on a rolling grid
|
|
5
|
+
* rather than a calendar boundary. Shared by the budget tracker.
|
|
6
|
+
*/
|
|
7
|
+
const HOUR_MS = 3600_000;
|
|
8
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
9
|
+
const DURATION_MS = {
|
|
10
|
+
"5h": 5 * HOUR_MS,
|
|
11
|
+
"24h": 24 * HOUR_MS,
|
|
12
|
+
"7day": 7 * DAY_MS,
|
|
13
|
+
"30day": 30 * DAY_MS,
|
|
14
|
+
};
|
|
15
|
+
/** Length (ms) of one window bucket. */
|
|
16
|
+
export function windowDuration(spec) {
|
|
17
|
+
return DURATION_MS[spec.window];
|
|
18
|
+
}
|
|
19
|
+
/** Epoch ms of the START of the bucket containing `now`. Anchored to `spec.anchor`
|
|
20
|
+
* when present (cycles tumble from the anchor); otherwise floored to the epoch grid. */
|
|
21
|
+
export function currentWindowStart(spec, now) {
|
|
22
|
+
const dur = DURATION_MS[spec.window];
|
|
23
|
+
if (spec.anchor === undefined)
|
|
24
|
+
return Math.floor(now / dur) * dur;
|
|
25
|
+
if (now <= spec.anchor)
|
|
26
|
+
return spec.anchor;
|
|
27
|
+
return spec.anchor + Math.floor((now - spec.anchor) / dur) * dur;
|
|
28
|
+
}
|
|
29
|
+
/** Next reset instant: the end of the current bucket (windowStart + duration). */
|
|
30
|
+
export function nextResetAt(spec, windowStart, _now) {
|
|
31
|
+
return windowStart + DURATION_MS[spec.window];
|
|
32
|
+
}
|
|
33
|
+
// `DAY_MS` is exported for any future window math that needs a day constant.
|
|
34
|
+
export { DAY_MS };
|
|
35
|
+
//# sourceMappingURL=window.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"window.js","sourceRoot":"","sources":["../../src/core/window.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,GAAG,QAAQ,CAAC;AACzB,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,CAAC;AAU5B,MAAM,WAAW,GAA+B;IAC9C,IAAI,EAAE,CAAC,GAAG,OAAO;IACjB,KAAK,EAAE,EAAE,GAAG,OAAO;IACnB,MAAM,EAAE,CAAC,GAAG,MAAM;IAClB,OAAO,EAAE,EAAE,GAAG,MAAM;CACrB,CAAC;AAEF,wCAAwC;AACxC,MAAM,UAAU,cAAc,CAAC,IAAgB;IAC7C,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC;AAED;yFACyF;AACzF,MAAM,UAAU,kBAAkB,CAAC,IAAgB,EAAE,GAAW;IAC9D,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IAClE,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IAC3C,OAAO,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACnE,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,IAAgB,EAAE,WAAmB,EAAE,IAAY;IAC7E,OAAO,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,6EAA6E;AAC7E,OAAO,EAAE,MAAM,EAAE,CAAC"}
|
package/dist/db.js
CHANGED
|
@@ -15,7 +15,6 @@ export class UsageDB {
|
|
|
15
15
|
db;
|
|
16
16
|
insertUsage;
|
|
17
17
|
insertLog;
|
|
18
|
-
upsertQuota;
|
|
19
18
|
now;
|
|
20
19
|
constructor(path, now = Date.now) {
|
|
21
20
|
if (path !== ":memory:")
|
|
@@ -30,6 +29,7 @@ export class UsageDB {
|
|
|
30
29
|
model TEXT NOT NULL,
|
|
31
30
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
32
31
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
reasoning_tokens INTEGER NOT NULL DEFAULT 0,
|
|
33
33
|
cached_tokens INTEGER NOT NULL DEFAULT 0,
|
|
34
34
|
cost REAL NOT NULL DEFAULT 0,
|
|
35
35
|
status INTEGER NOT NULL,
|
|
@@ -60,25 +60,21 @@ export class UsageDB {
|
|
|
60
60
|
if (!cols.some((c) => String(c.name) === "client_key")) {
|
|
61
61
|
this.db.exec(`ALTER TABLE usage ADD COLUMN client_key TEXT NOT NULL DEFAULT ''`);
|
|
62
62
|
}
|
|
63
|
+
if (!cols.some((c) => String(c.name) === "reasoning_tokens")) {
|
|
64
|
+
this.db.exec(`ALTER TABLE usage ADD COLUMN reasoning_tokens INTEGER NOT NULL DEFAULT 0`);
|
|
65
|
+
}
|
|
63
66
|
this.now = now;
|
|
64
67
|
this.insertUsage = this.db.prepare(`
|
|
65
|
-
INSERT INTO usage (ts, alias, provider, model, tokens_in, tokens_out, cached_tokens, cost, status, latency_ms, stream, client_key)
|
|
66
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
68
|
+
INSERT INTO usage (ts, alias, provider, model, tokens_in, tokens_out, reasoning_tokens, cached_tokens, cost, status, latency_ms, stream, client_key)
|
|
69
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
67
70
|
`);
|
|
68
71
|
this.insertLog = this.db.prepare(`
|
|
69
72
|
INSERT INTO logs (ts, direction, provider, status, request_summary, response_summary)
|
|
70
73
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
71
|
-
`);
|
|
72
|
-
// upsert keyed on provider_id so each provider keeps one live window row.
|
|
73
|
-
this.upsertQuota = this.db.prepare(`
|
|
74
|
-
INSERT INTO quota_state (provider_id, window_start, consumed, last_reset)
|
|
75
|
-
VALUES (?, ?, ?, ?)
|
|
76
|
-
ON CONFLICT(provider_id) DO UPDATE SET window_start = excluded.window_start,
|
|
77
|
-
consumed = excluded.consumed, last_reset = excluded.last_reset
|
|
78
74
|
`);
|
|
79
75
|
}
|
|
80
76
|
record(row) {
|
|
81
|
-
this.insertUsage.run(row.ts ?? this.now(), row.alias, row.provider, row.model, row.tokens_in, row.tokens_out, row.cached_tokens, row.cost, row.status, row.latency_ms, row.stream, row.client_key ?? "");
|
|
77
|
+
this.insertUsage.run(row.ts ?? this.now(), row.alias, row.provider, row.model, row.tokens_in, row.tokens_out, row.reasoning_tokens ?? 0, row.cached_tokens, row.cost, row.status, row.latency_ms, row.stream, row.client_key ?? "");
|
|
82
78
|
}
|
|
83
79
|
log(row) {
|
|
84
80
|
this.insertLog.run(row.ts ?? this.now(), row.direction, row.provider, row.status, row.request_summary, row.response_summary);
|
|
@@ -183,7 +179,7 @@ export class UsageDB {
|
|
|
183
179
|
/** Most recent usage rows, newest first. For the dashboard logs page. */
|
|
184
180
|
recent(limit = 100) {
|
|
185
181
|
const rows = this.db
|
|
186
|
-
.prepare(`SELECT ts, alias, provider, model, tokens_in, tokens_out, cached_tokens,
|
|
182
|
+
.prepare(`SELECT ts, alias, provider, model, tokens_in, tokens_out, reasoning_tokens, cached_tokens,
|
|
187
183
|
cost, status, latency_ms, stream, client_key
|
|
188
184
|
FROM usage ORDER BY id DESC LIMIT ?`)
|
|
189
185
|
.all(Math.max(1, Math.min(limit, 1000)));
|
|
@@ -194,6 +190,7 @@ export class UsageDB {
|
|
|
194
190
|
model: String(r.model),
|
|
195
191
|
tokens_in: num(r.tokens_in),
|
|
196
192
|
tokens_out: num(r.tokens_out),
|
|
193
|
+
reasoning_tokens: num(r.reasoning_tokens),
|
|
197
194
|
cached_tokens: num(r.cached_tokens),
|
|
198
195
|
cost: num(r.cost),
|
|
199
196
|
status: num(r.status),
|
|
@@ -202,26 +199,34 @@ export class UsageDB {
|
|
|
202
199
|
client_key: String(r.client_key ?? ""),
|
|
203
200
|
}));
|
|
204
201
|
}
|
|
205
|
-
// ---- QuotaStore: one live window row per provider (survives restart) ----
|
|
206
|
-
loadQuota() {
|
|
207
|
-
const rows = this.db.prepare(`SELECT provider_id, window_start, consumed FROM quota_state`).all();
|
|
208
|
-
return rows.map((r) => ({
|
|
209
|
-
provider_id: String(r.provider_id),
|
|
210
|
-
window_start: num(r.window_start),
|
|
211
|
-
consumed: num(r.consumed),
|
|
212
|
-
}));
|
|
213
|
-
}
|
|
214
|
-
saveQuota(providerId, windowStart, consumed) {
|
|
215
|
-
this.upsertQuota.run(providerId, windowStart, consumed, this.now());
|
|
216
|
-
}
|
|
217
202
|
close() {
|
|
218
203
|
this.db.close();
|
|
219
204
|
}
|
|
220
205
|
}
|
|
221
|
-
/** Compute USD cost from token counts and per-1M prices. */
|
|
222
|
-
export function computeCost(tokensIn, tokensOut, priceIn, priceOut) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
206
|
+
/** Compute USD cost from token counts and per-1M prices. Separate rates for input (non-cache), cache_read, output, reasoning. */
|
|
207
|
+
export function computeCost(tokensIn, tokensOut, priceIn, priceOut, priceReasoning, priceCachedRead, cachedTokens, reasoningTokens) {
|
|
208
|
+
let cost = 0;
|
|
209
|
+
// Non-cached input (input minus cache_read)
|
|
210
|
+
const nonCachedInput = Math.max(0, tokensIn - (cachedTokens ?? 0));
|
|
211
|
+
if (priceIn)
|
|
212
|
+
cost += (nonCachedInput / 1_000_000) * priceIn;
|
|
213
|
+
// Cached read — uses separate rate or falls back to input rate
|
|
214
|
+
if (cachedTokens && priceCachedRead) {
|
|
215
|
+
cost += (cachedTokens / 1_000_000) * priceCachedRead;
|
|
216
|
+
}
|
|
217
|
+
else if (cachedTokens && priceIn) {
|
|
218
|
+
cost += (cachedTokens / 1_000_000) * priceIn;
|
|
219
|
+
}
|
|
220
|
+
// Output completion
|
|
221
|
+
if (priceOut)
|
|
222
|
+
cost += (tokensOut / 1_000_000) * priceOut;
|
|
223
|
+
// Reasoning tokens — uses dedicated rate or falls back to output rate
|
|
224
|
+
if (reasoningTokens) {
|
|
225
|
+
if (priceReasoning)
|
|
226
|
+
cost += (reasoningTokens / 1_000_000) * priceReasoning;
|
|
227
|
+
else if (priceOut)
|
|
228
|
+
cost += (reasoningTokens / 1_000_000) * priceOut;
|
|
229
|
+
}
|
|
230
|
+
return cost;
|
|
226
231
|
}
|
|
227
232
|
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,2EAA2E;AAC3E,4EAA4E;AAC5E,MAAM,EAAE,YAAY,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,aAAa,CAEpE,CAAC;
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/admin.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import { readFileSync } from "node:fs";
|
|
14
14
|
import { resolve } from "node:path";
|
|
15
15
|
import { checkAdminAuth, clientKeyFingerprint } from "../middleware/auth.js";
|
|
16
|
-
import {
|
|
16
|
+
import { buildKeyUsageRow } from "../core/keysUsage.js";
|
|
17
|
+
import { maskKey, serializeConfig, addProvider, editProvider, renameProvider, removeProvider, addProviderKey, removeProviderKey, editProviderKey, reorderProviderKey, toggleProviderKey, setProviderStrategy, setProviderDisabled, addProviderModel, removeProviderModel, addProviderModels, clearProviderModels, setProviderModelPrice, setRoute, removeRoute, setRtk, setCaveman, setPonytail, setHeadroom, addServerKey, editServerKey, removeServerKey, setServerKeyScope, setBudget, clearBudget, } from "../config.js";
|
|
17
18
|
import { pingProvider } from "../upstream/client.js";
|
|
18
19
|
import { handle, GatewayError } from "../core/handler.js";
|
|
19
20
|
import { fetchModels } from "../providers/free.js";
|
|
@@ -41,6 +42,17 @@ function maskedConfig(config) {
|
|
|
41
42
|
if (clone.server.key_names) {
|
|
42
43
|
clone.server.key_names = Object.fromEntries(Object.entries(clone.server.key_names).map(([k, name]) => [maskKey(k), name]));
|
|
43
44
|
}
|
|
45
|
+
// key_models / key_rpm are keyed by the RAW key — re-key to the masked form so
|
|
46
|
+
// real keys never leak through /admin/config.
|
|
47
|
+
if (clone.server.key_models) {
|
|
48
|
+
clone.server.key_models = Object.fromEntries(Object.entries(clone.server.key_models).map(([k, v]) => [maskKey(k), v]));
|
|
49
|
+
}
|
|
50
|
+
if (clone.server.key_rpm) {
|
|
51
|
+
clone.server.key_rpm = Object.fromEntries(Object.entries(clone.server.key_rpm).map(([k, v]) => [maskKey(k), v]));
|
|
52
|
+
}
|
|
53
|
+
if (clone.server.key_expires) {
|
|
54
|
+
clone.server.key_expires = Object.fromEntries(Object.entries(clone.server.key_expires).map(([k, v]) => [maskKey(k), v]));
|
|
55
|
+
}
|
|
44
56
|
return clone;
|
|
45
57
|
}
|
|
46
58
|
export function registerAdminRoutes(app, deps) {
|
|
@@ -94,12 +106,9 @@ export function registerAdminRoutes(app, deps) {
|
|
|
94
106
|
app.get("/admin/providers", requireAdmin, (_req, reply) => {
|
|
95
107
|
reply.send({ providers: deps.state.pool.snapshot(deps.state.config.listProviders()) });
|
|
96
108
|
});
|
|
97
|
-
//
|
|
98
|
-
app.get("/admin/
|
|
99
|
-
reply.send({
|
|
100
|
-
quota: deps.state.quota.snapshot(deps.state.config.listProviders()),
|
|
101
|
-
budgets: deps.state.budget.statuses(),
|
|
102
|
-
});
|
|
109
|
+
// budget statuses: consumed, limit, and ms until the next scheduled reset.
|
|
110
|
+
app.get("/admin/budgets", requireAdmin, (_req, reply) => {
|
|
111
|
+
reply.send({ budgets: deps.state.budget.statuses() });
|
|
103
112
|
});
|
|
104
113
|
// add or replace a budget (keyed by scope). Body = Budget; invalid shape or an
|
|
105
114
|
// unknown provider scope -> 400 via zod / setBudget through state.reload().
|
|
@@ -334,7 +343,7 @@ export function registerAdminRoutes(app, deps) {
|
|
|
334
343
|
reply.send(await pingProvider(provider, keys[i]));
|
|
335
344
|
});
|
|
336
345
|
// Test ONE model end-to-end (aigetwey's per-model science button). Routes through
|
|
337
|
-
// the real pipeline via handle(), so the ping lands in usage
|
|
346
|
+
// the real pipeline via handle(), so the ping lands in usage exactly like
|
|
338
347
|
// a normal call — and it catches "model not found / not entitled" a /models
|
|
339
348
|
// ping can't. Model id travels as ?model= to survive slashes through the proxy.
|
|
340
349
|
app.post("/admin/providers/:id/models/test", requireAdmin, async (req, reply) => {
|
|
@@ -346,7 +355,7 @@ export function registerAdminRoutes(app, deps) {
|
|
|
346
355
|
if (!provider)
|
|
347
356
|
return reply.code(404).send({ error: `provider "${id}" not found` });
|
|
348
357
|
try {
|
|
349
|
-
await handle({ config: deps.state.config, pool: deps.state.pool, db: deps.db
|
|
358
|
+
await handle({ config: deps.state.config, pool: deps.state.pool, db: deps.db }, "openai", { model: `${id}/${modelId}`, messages: [{ role: "user", content: "ping" }], max_tokens: 1, stream: false });
|
|
350
359
|
reply.send({ ok: true });
|
|
351
360
|
}
|
|
352
361
|
catch (e) {
|
|
@@ -475,6 +484,15 @@ export function registerAdminRoutes(app, deps) {
|
|
|
475
484
|
const b = req.body;
|
|
476
485
|
applyMutation(reply, (c) => editServerKey(c, i, { name: b?.name }));
|
|
477
486
|
});
|
|
487
|
+
// set/clear ONE gateway key's scopes (model allowlist + rpm), by index.
|
|
488
|
+
app.put("/admin/endpoint/keys/:index/scope", requireAdmin, (req, reply) => {
|
|
489
|
+
const { index } = req.params;
|
|
490
|
+
const i = Number(index);
|
|
491
|
+
if (!Number.isInteger(i))
|
|
492
|
+
return reply.code(400).send({ error: "index must be an integer" });
|
|
493
|
+
const b = (req.body ?? {});
|
|
494
|
+
applyMutation(reply, (c) => setServerKeyScope(c, i, { models: b.models, rpm: b.rpm, expires: b.expires }));
|
|
495
|
+
});
|
|
478
496
|
app.delete("/admin/endpoint/keys/:index", requireAdmin, (req, reply) => {
|
|
479
497
|
const { index } = req.params;
|
|
480
498
|
const i = Number(index);
|
|
@@ -492,6 +510,26 @@ export function registerAdminRoutes(app, deps) {
|
|
|
492
510
|
masked: maskKey(k),
|
|
493
511
|
})));
|
|
494
512
|
});
|
|
513
|
+
// per-key spend for the Budgets page "Keys" section: every gateway key, its
|
|
514
|
+
// all-time usage, expiry, and key-scoped budget status (null when uncapped).
|
|
515
|
+
app.get("/admin/keys/usage", requireAdmin, (_req, reply) => {
|
|
516
|
+
if (!deps.db)
|
|
517
|
+
return reply.code(503).send({ error: "usage tracking disabled" });
|
|
518
|
+
const cfg = deps.state.config.raw;
|
|
519
|
+
const statuses = deps.state.budget.statuses();
|
|
520
|
+
const keys = cfg.server.api_keys.map((k) => {
|
|
521
|
+
const fp = clientKeyFingerprint(k);
|
|
522
|
+
return buildKeyUsageRow({
|
|
523
|
+
fingerprint: fp,
|
|
524
|
+
name: cfg.server.key_names?.[k] ?? maskKey(k),
|
|
525
|
+
masked: maskKey(k),
|
|
526
|
+
expires: cfg.server.key_expires?.[k],
|
|
527
|
+
totals: deps.db.totals(0, { client_key: fp }),
|
|
528
|
+
budget: statuses.find((s) => s.scope.type === "key" && s.scope.id === fp) ?? null,
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
reply.send({ keys });
|
|
532
|
+
});
|
|
495
533
|
// reveal ONE raw gateway key (the "show key" button on the Endpoint page).
|
|
496
534
|
app.get("/admin/endpoint/keys/:index/reveal", requireAdmin, (req, reply) => {
|
|
497
535
|
const { index } = req.params;
|
|
@@ -646,7 +684,14 @@ function endpointPayload(config) {
|
|
|
646
684
|
caveman: config.endpoint.caveman,
|
|
647
685
|
ponytail: config.endpoint.ponytail,
|
|
648
686
|
headroom: config.endpoint.headroom,
|
|
649
|
-
keys: config.server.api_keys.map((k) => ({
|
|
687
|
+
keys: config.server.api_keys.map((k) => ({
|
|
688
|
+
key: maskKey(k),
|
|
689
|
+
fingerprint: clientKeyFingerprint(k),
|
|
690
|
+
name: config.server.key_names?.[k],
|
|
691
|
+
models: config.server.key_models?.[k],
|
|
692
|
+
rpm: config.server.key_rpm?.[k],
|
|
693
|
+
expires: config.server.key_expires?.[k],
|
|
694
|
+
})),
|
|
650
695
|
};
|
|
651
696
|
}
|
|
652
697
|
//# sourceMappingURL=admin.js.map
|