myapikey 0.36.0 → 0.36.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.36.
|
|
3
|
+
"version": "0.36.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": [
|
|
@@ -6,8 +6,43 @@ import { UsageCollector } from "./tokens";
|
|
|
6
6
|
|
|
7
7
|
/** HTTP statuses that should trigger failover to the next provider. 401/403
|
|
8
8
|
* included: a banned/invalid credential (e.g. "User has been banned") is dead
|
|
9
|
-
* for THIS source only - the same request may be fine on the next one.
|
|
10
|
-
|
|
9
|
+
* for THIS source only - the same request may be fine on the next one. 402 is
|
|
10
|
+
* always account-level (exhausted balance). */
|
|
11
|
+
const RETRYABLE = new Set([401, 402, 403, 408, 425, 429, 500, 502, 503, 504]);
|
|
12
|
+
|
|
13
|
+
/** Error-body markers that turn a NON-retryable 4xx into an account-level
|
|
14
|
+
* failure worth failing over: dead credential / expired subscription / no
|
|
15
|
+
* balance, which some vendors smuggle under a 400 instead of 401/403
|
|
16
|
+
* (Volcengine Ark's expired coding-plan subscription is the case that bit
|
|
17
|
+
* us). Deliberately narrow — a genuine request-shape 400 (bad params, context
|
|
18
|
+
* overflow, "max_tokens too large") still returns to the client as-is, since
|
|
19
|
+
* every other source would 400 identically. Matched case-insensitively. */
|
|
20
|
+
const ACCOUNT_HINTS = [
|
|
21
|
+
"subscription",
|
|
22
|
+
"unauthorized",
|
|
23
|
+
"invalid_api_key",
|
|
24
|
+
"invalid api key",
|
|
25
|
+
"incorrect api key",
|
|
26
|
+
"quota",
|
|
27
|
+
"insufficient",
|
|
28
|
+
"balance",
|
|
29
|
+
"credit",
|
|
30
|
+
"expired",
|
|
31
|
+
"令牌",
|
|
32
|
+
"订阅",
|
|
33
|
+
"额度",
|
|
34
|
+
"余额",
|
|
35
|
+
"欠费",
|
|
36
|
+
"未开通",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/** True when `status`/`bodyText` describe an account-level failure on an
|
|
40
|
+
* otherwise non-retryable 4xx (see ACCOUNT_HINTS). */
|
|
41
|
+
function isAccountLevelError(status: number, bodyText: string): boolean {
|
|
42
|
+
if (RETRYABLE.has(status) || status < 400 || status >= 500) return false;
|
|
43
|
+
const t = bodyText.toLowerCase();
|
|
44
|
+
return ACCOUNT_HINTS.some((h) => t.includes(h));
|
|
45
|
+
}
|
|
11
46
|
|
|
12
47
|
/** Even pacing (per-model `paceRpm`) message constants. The queue itself lives
|
|
13
48
|
* in Store.paceClaim - 60s wait horizon, one release every 60/rpm seconds. */
|
|
@@ -259,11 +294,6 @@ function downHeaders(upstream: Response, servedBy?: string): Headers {
|
|
|
259
294
|
return headers;
|
|
260
295
|
}
|
|
261
296
|
|
|
262
|
-
function passThrough(upstream: Response, servedBy?: string): Response {
|
|
263
|
-
// Stream the upstream body straight through (handles SSE + normal JSON).
|
|
264
|
-
return new Response(upstream.body, { status: upstream.status, headers: downHeaders(upstream, servedBy) });
|
|
265
|
-
}
|
|
266
|
-
|
|
267
297
|
/** Pull a short human-readable message out of an upstream error body. */
|
|
268
298
|
export function shortError(text: string): string {
|
|
269
299
|
try {
|
|
@@ -712,13 +742,18 @@ export function proxyApi(
|
|
|
712
742
|
});
|
|
713
743
|
return new Response(out, { status: upstream.status, headers: downHeaders(upstream, isProbe ? provider.name : undefined) });
|
|
714
744
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
745
|
+
// Error from the upstream. Read the body ONCE (drains the connection
|
|
746
|
+
// for reuse), then either fail over or pass the error back verbatim —
|
|
747
|
+
// both branches share this text (log row + debug capture).
|
|
748
|
+
const txt = await upstream.text().catch(() => "");
|
|
749
|
+
lastStatus = upstream.status;
|
|
750
|
+
lastErr = shortError(txt) || `HTTP ${upstream.status}`;
|
|
751
|
+
capture(upstream.status, txt, false, lastErr);
|
|
752
|
+
// Fail over on a retryable status OR an account-level failure smuggled
|
|
753
|
+
// under a non-retryable 4xx (expired subscription / dead key / no
|
|
754
|
+
// balance returned as 400) — the source is dead for THIS request only,
|
|
755
|
+
// exactly like a 401/403.
|
|
756
|
+
if (RETRYABLE.has(upstream.status) || isAccountLevelError(upstream.status, txt)) {
|
|
722
757
|
if (pinIndex != null) break; // per-source probe: fail fast, no circuit impact.
|
|
723
758
|
sayFailover(provider, `HTTP ${lastStatus} (${lastErr})`);
|
|
724
759
|
// A 429/overloaded upstream usually carries Retry-After; honoring it
|
|
@@ -735,12 +770,9 @@ export function proxyApi(
|
|
|
735
770
|
}
|
|
736
771
|
continue;
|
|
737
772
|
}
|
|
738
|
-
// Non-retryable client error: return it to the caller as-is.
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: upstream.status, ms: Date.now() - start, stream, thinking: think, error: shortError(errText) || `HTTP ${upstream.status}` });
|
|
742
|
-
capture(upstream.status, errText, false, shortError(errText) || `HTTP ${upstream.status}`);
|
|
743
|
-
return passThrough(upstream, isProbe ? provider.name : undefined);
|
|
773
|
+
// Non-retryable client error: return it to the caller as-is.
|
|
774
|
+
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: upstream.status, ms: Date.now() - start, stream, thinking: think, error: lastErr });
|
|
775
|
+
return new Response(txt, { status: upstream.status, headers: downHeaders(upstream, isProbe ? provider.name : undefined) });
|
|
744
776
|
}
|
|
745
777
|
|
|
746
778
|
const last = order[order.length - 1];
|