myapikey 0.9.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myapikey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Personal LLM API gateway & proxy — one address + one API key for all your models. Forwards OpenAI & Anthropic calls to your backends with failover and a circuit breaker. Pure passthrough, no format translation. Self-hosted (CLI + web UI).",
|
|
6
6
|
"keywords": [
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"scripts": {
|
|
55
55
|
"dev": "tsx watch packages/core/src/cli/index.ts serve",
|
|
56
56
|
"dev:web": "npm run dev -w @myapikey/web",
|
|
57
|
-
"start": "
|
|
57
|
+
"start": "npm run build:web && npm run serve",
|
|
58
|
+
"serve": "tsx packages/core/src/cli/index.ts serve",
|
|
58
59
|
"typecheck": "tsc -p packages/core/tsconfig.json --noEmit && tsc -p packages/core/tsconfig.test.json --noEmit && vue-tsc --noEmit -p packages/web/tsconfig.json",
|
|
59
60
|
"build:web": "npm run build -w @myapikey/web",
|
|
60
61
|
"test": "vitest run",
|
|
@@ -367,6 +367,12 @@ export function proxyApi(store: Store, auth: MiddlewareHandler): Hono {
|
|
|
367
367
|
// the slot each iteration, so failover never carries the previous slot's
|
|
368
368
|
// upstream name.
|
|
369
369
|
body.model = slot.model ?? model;
|
|
370
|
+
// The actual upstream model forwarded this attempt (after the per-slot
|
|
371
|
+
// rewrite). Recorded on the log row so history shows which real model a
|
|
372
|
+
// routed call landed on when a source remaps the public name. `undefined`
|
|
373
|
+
// when the public name went through verbatim — JSON.stringify drops it, so
|
|
374
|
+
// identity + legacy rows stay clean.
|
|
375
|
+
const upstreamModel = slot.model && slot.model !== model ? slot.model : undefined;
|
|
370
376
|
// Count this attempt toward the source's RPM window — but not for a pinned
|
|
371
377
|
// probe, which (like circuit state) takes no routing side-effects.
|
|
372
378
|
if (pinIndex == null) store.recordDispatch(provider.id);
|
|
@@ -384,7 +390,7 @@ export function proxyApi(store: Store, auth: MiddlewareHandler): Hono {
|
|
|
384
390
|
if (pinIndex != null) break; // per-source probe: fail fast, no circuit impact.
|
|
385
391
|
const r = store.recordCircuitFailure(provider.id, lastStatus, lastErr);
|
|
386
392
|
if (r.entered) {
|
|
387
|
-
store.pushLog({ ts: Date.now(), model, provider: provider.name, providerId: provider.id, format: wire, status: lastStatus, ms: Date.now() - start, stream, kind: "cooldown", cooldownMs: r.cooldownMs, fails: r.fails, error: lastErr });
|
|
393
|
+
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: lastStatus, ms: Date.now() - start, stream, kind: "cooldown", cooldownMs: r.cooldownMs, fails: r.fails, error: lastErr });
|
|
388
394
|
}
|
|
389
395
|
continue;
|
|
390
396
|
}
|
|
@@ -409,12 +415,12 @@ export function proxyApi(store: Store, auth: MiddlewareHandler): Hono {
|
|
|
409
415
|
onSettle: (info) => {
|
|
410
416
|
if (info.ok) {
|
|
411
417
|
store.recordCircuitSuccess(provider.id);
|
|
412
|
-
store.pushLog({ ts: Date.now(), model, provider: provider.name, providerId: provider.id, format: wire, status: 200, ms: ttfb, stream, usage: info.usage });
|
|
418
|
+
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: 200, ms: ttfb, stream, usage: info.usage });
|
|
413
419
|
} else {
|
|
414
420
|
// A pinned per-source probe takes no circuit side-effects (a manual
|
|
415
421
|
// test must not trip the breaker) — mirrors the retryable branch.
|
|
416
422
|
if (pinIndex == null) store.recordCircuitFailure(provider.id, info.status, info.error || "stream failed");
|
|
417
|
-
store.pushLog({ ts: Date.now(), model, provider: provider.name, providerId: provider.id, format: wire, status: info.status, ms: ttfb, stream, error: info.error });
|
|
423
|
+
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: info.status, ms: ttfb, stream, error: info.error });
|
|
418
424
|
}
|
|
419
425
|
},
|
|
420
426
|
});
|
|
@@ -436,19 +442,20 @@ export function proxyApi(store: Store, auth: MiddlewareHandler): Hono {
|
|
|
436
442
|
const resetInMs = retryAfterMs ? undefined : parseResetFromBody(txt);
|
|
437
443
|
const r = store.recordCircuitFailure(provider.id, lastStatus, lastErr, retryAfterMs ?? resetInMs, !!resetInMs);
|
|
438
444
|
if (r.entered) {
|
|
439
|
-
store.pushLog({ ts: Date.now(), model, provider: provider.name, providerId: provider.id, format: wire, status: lastStatus, ms: Date.now() - start, stream, kind: "cooldown", cooldownMs: r.cooldownMs, fails: r.fails, error: lastErr });
|
|
445
|
+
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: lastStatus, ms: Date.now() - start, stream, kind: "cooldown", cooldownMs: r.cooldownMs, fails: r.fails, error: lastErr });
|
|
440
446
|
}
|
|
441
447
|
continue;
|
|
442
448
|
}
|
|
443
449
|
// Non-retryable client error: return it to the caller as-is. Read the
|
|
444
450
|
// error text off a CLONE so the original body still streams back.
|
|
445
451
|
const errText = await upstream.clone().text().catch(() => "");
|
|
446
|
-
store.pushLog({ ts: Date.now(), model, provider: provider.name, providerId: provider.id, format: wire, status: upstream.status, ms: Date.now() - start, stream, error: shortError(errText) || `HTTP ${upstream.status}` });
|
|
452
|
+
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: upstream.status, ms: Date.now() - start, stream, error: shortError(errText) || `HTTP ${upstream.status}` });
|
|
447
453
|
return passThrough(upstream, isProbe ? provider.name : undefined);
|
|
448
454
|
}
|
|
449
455
|
|
|
450
456
|
const last = order[order.length - 1];
|
|
451
|
-
|
|
457
|
+
const lastUpstreamModel = last.model && last.model !== model ? last.model : undefined;
|
|
458
|
+
store.pushLog({ ts: Date.now(), model, upstreamModel: lastUpstreamModel, provider: last.provider.name, providerId: last.provider.id, format: wire, status: lastStatus, ms: Date.now() - start, stream, error: lastErr || `all providers failed (last status ${lastStatus})` });
|
|
452
459
|
// A pinned (per-source) probe failed: surface the REAL upstream status the
|
|
453
460
|
// one slot returned (429/500/…), not a collapsed 502, and tag it with
|
|
454
461
|
// x-myapikey-provider so the source-row badge names the tested source.
|
|
@@ -105,6 +105,8 @@ export interface ProviderModelStat {
|
|
|
105
105
|
success: number;
|
|
106
106
|
/** Fresh (non-cached) prompt tokens summed across this cell's rows. */
|
|
107
107
|
inputTokens: number;
|
|
108
|
+
/** Completion tokens summed across this cell's rows. */
|
|
109
|
+
outputTokens: number;
|
|
108
110
|
cacheRead: number;
|
|
109
111
|
cacheCreation: number;
|
|
110
112
|
cacheHitRate: number;
|
|
@@ -489,6 +491,7 @@ export class Store {
|
|
|
489
491
|
calls: a.calls,
|
|
490
492
|
success: a.success,
|
|
491
493
|
inputTokens: a.sumInput,
|
|
494
|
+
outputTokens: a.sumOutput,
|
|
492
495
|
cacheRead: a.sumCacheRead,
|
|
493
496
|
cacheCreation: a.sumCacheCreation,
|
|
494
497
|
cacheHitRate: hitRate(a.sumCacheRead, a.sumInput, a.sumCacheCreation),
|
|
@@ -118,6 +118,13 @@ export interface Usage {
|
|
|
118
118
|
export interface LogEntry {
|
|
119
119
|
ts: number;
|
|
120
120
|
model: string;
|
|
121
|
+
/** The actual upstream model name forwarded to the provider, when a per-slot
|
|
122
|
+
* rewrite changed it (a ChainSlot `model` that differs from the public name —
|
|
123
|
+
* an alias, a model swap, or one of several models on a repeated source).
|
|
124
|
+
* Absent when the public name was forwarded verbatim, so identity + legacy
|
|
125
|
+
* rows stay clean. Lets the log show which real model answered when a source
|
|
126
|
+
* remaps the public name. */
|
|
127
|
+
upstreamModel?: string;
|
|
121
128
|
provider: string;
|
|
122
129
|
/** Stable provider id (newer entries). Stats group by this so renaming a
|
|
123
130
|
* provider doesn't split its history; the display name is resolved from the
|