@askalf/dario 6.0.2 → 6.0.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/dist/proxy.js +55 -17
- package/package.json +1 -1
package/dist/proxy.js
CHANGED
|
@@ -1863,6 +1863,42 @@ export async function startProxy(opts = {}) {
|
|
|
1863
1863
|
const hasNextOption = openaiBackend !== null && shape === 'openai';
|
|
1864
1864
|
return await forwardToCodex(req, res, fallbackBody, creds, corsOrigin, { ...SECURITY_HEADERS, 'x-dario-pool-fallback': fallbackModel }, upstreamTimeoutMs, verbose, shape, fetch, hasNextOption);
|
|
1865
1865
|
};
|
|
1866
|
+
/**
|
|
1867
|
+
* Mid-flight 429, no peer account left to retry — the pool is genuinely
|
|
1868
|
+
* exhausted mid-request, not just at selection. Tries every fallback in
|
|
1869
|
+
* order (subscription, then api-key backend) and returns true once one has
|
|
1870
|
+
* written the response; false means nothing could serve it and the caller
|
|
1871
|
+
* writes its own honest 429.
|
|
1872
|
+
*
|
|
1873
|
+
* A SINGLE function for BOTH mid-flight 429 sites in the request handler
|
|
1874
|
+
* (the non-passthrough recovery-loop path and the general/passthrough
|
|
1875
|
+
* path), by design. Those two sites used to carry independent, hand-copied
|
|
1876
|
+
* versions of this logic, and v6.0.0 wired the fallback into only ONE of
|
|
1877
|
+
* them. The other kept returning the raw upstream 429 — and that unwired
|
|
1878
|
+
* site is the one reached by every non-passthrough request, i.e. everything
|
|
1879
|
+
* except a literal Claude Code CLI session, forge's SDK Engine included.
|
|
1880
|
+
* That is what left the whole fleet unable to fail over during the live
|
|
1881
|
+
* outage on 2026-08-30 while `dario doctor` reported failover armed.
|
|
1882
|
+
*
|
|
1883
|
+
* One function reachable from both call sites is what makes that class of
|
|
1884
|
+
* bug structurally impossible to reintroduce: a future third call site gets
|
|
1885
|
+
* this by construction, not by remembering to copy six lines correctly.
|
|
1886
|
+
*/
|
|
1887
|
+
const attemptPoolFallbackOn429 = async (req, res, body, isOpenAI) => {
|
|
1888
|
+
if (await tryCodexPoolFallback(req, res, body, poolFallbackModels, isOpenAI ? 'openai' : 'anthropic', 'pool exhausted mid-flight (429, no peer)')) {
|
|
1889
|
+
return true;
|
|
1890
|
+
}
|
|
1891
|
+
if (isOpenAI && poolFallbackModel && openaiBackend) {
|
|
1892
|
+
const fallbackBody = buildPoolFallbackBody(body, poolFallbackModel);
|
|
1893
|
+
if (fallbackBody) {
|
|
1894
|
+
console.log(`[dario] #${requestCount} pool exhausted mid-flight (429, no peer) → ${openaiBackend.name} as ${poolFallbackModel}`);
|
|
1895
|
+
requestCount++;
|
|
1896
|
+
await forwardToOpenAI(req, res, fallbackBody, openaiBackend, corsOrigin, { ...SECURITY_HEADERS, 'x-dario-pool-fallback': poolFallbackModel }, upstreamTimeoutMs, verbose);
|
|
1897
|
+
return true;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
return false;
|
|
1901
|
+
};
|
|
1866
1902
|
const server = createServer(async (req, res) => {
|
|
1867
1903
|
if (req.method === 'OPTIONS') {
|
|
1868
1904
|
res.writeHead(204, CORS_HEADERS);
|
|
@@ -3590,6 +3626,19 @@ export async function startProxy(opts = {}) {
|
|
|
3590
3626
|
continue dispatchLoop;
|
|
3591
3627
|
}
|
|
3592
3628
|
}
|
|
3629
|
+
// Pool-exhausted fallback, mid-flight: no peer left to fail over
|
|
3630
|
+
// to. THE BUG (live incident, 2026-08-30) and its independent
|
|
3631
|
+
// rediscovery: this branch — reached by every non-passthrough
|
|
3632
|
+
// request, i.e. everything except a literal Claude Code CLI session
|
|
3633
|
+
// — never called the fallback wired into the OTHER, structurally
|
|
3634
|
+
// near-identical 429 site below. #1153 fixed it here with its own
|
|
3635
|
+
// inline copy; that copy is collapsed into the single shared
|
|
3636
|
+
// attemptPoolFallbackOn429 below, so the file goes back to having
|
|
3637
|
+
// exactly ONE copy of this logic instead of two near-identical ones
|
|
3638
|
+
// — which is the exact shape of bug this incident already was.
|
|
3639
|
+
if (await attemptPoolFallbackOn429(req, res, body, isOpenAI)) {
|
|
3640
|
+
return;
|
|
3641
|
+
}
|
|
3593
3642
|
const enriched = enrich429(peekedBody, upstream.headers);
|
|
3594
3643
|
const responseHeaders = {
|
|
3595
3644
|
'Content-Type': 'application/json',
|
|
@@ -3680,25 +3729,14 @@ export async function startProxy(opts = {}) {
|
|
|
3680
3729
|
continue dispatchLoop;
|
|
3681
3730
|
}
|
|
3682
3731
|
}
|
|
3683
|
-
// Pool-exhausted fallback: no peer left to fail over to.
|
|
3684
|
-
//
|
|
3685
|
-
//
|
|
3686
|
-
//
|
|
3687
|
-
//
|
|
3688
|
-
|
|
3689
|
-
// selection-time path.
|
|
3690
|
-
if (await tryCodexPoolFallback(req, res, body, poolFallbackModels, isOpenAI ? 'openai' : 'anthropic', 'pool exhausted mid-flight (429, no peer)')) {
|
|
3732
|
+
// Pool-exhausted fallback: no peer left to fail over to. `body`
|
|
3733
|
+
// still holds the client's own bytes as received — the Anthropic
|
|
3734
|
+
// translation (OpenAI-shape clients) went into finalBody, never back
|
|
3735
|
+
// into body. See attemptPoolFallbackOn429's doc comment for why this
|
|
3736
|
+
// is one shared function rather than an inline copy.
|
|
3737
|
+
if (await attemptPoolFallbackOn429(req, res, body, isOpenAI)) {
|
|
3691
3738
|
return;
|
|
3692
3739
|
}
|
|
3693
|
-
if (isOpenAI && poolFallbackModel && openaiBackend) {
|
|
3694
|
-
const fallbackBody = buildPoolFallbackBody(body, poolFallbackModel);
|
|
3695
|
-
if (fallbackBody) {
|
|
3696
|
-
console.log(`[dario] #${requestCount} pool exhausted mid-flight (429, no peer) — /v1/chat/completions → ${openaiBackend.name} as ${poolFallbackModel}`);
|
|
3697
|
-
requestCount++;
|
|
3698
|
-
await forwardToOpenAI(req, res, fallbackBody, openaiBackend, corsOrigin, { ...SECURITY_HEADERS, 'x-dario-pool-fallback': poolFallbackModel }, upstreamTimeoutMs, verbose);
|
|
3699
|
-
return;
|
|
3700
|
-
}
|
|
3701
|
-
}
|
|
3702
3740
|
const errBody = await upstream.text().catch(() => '');
|
|
3703
3741
|
const enriched = enrich429(errBody, upstream.headers);
|
|
3704
3742
|
const responseHeaders = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.3",
|
|
4
4
|
"description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|