@askalf/dario 6.8.8 → 6.8.9
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/codex-accounts.d.ts
CHANGED
|
@@ -67,6 +67,15 @@ export declare function _resetCodexRefreshFailuresForTest(): void;
|
|
|
67
67
|
* a misleading "run `dario login`" answer to the client.
|
|
68
68
|
*/
|
|
69
69
|
export declare function getFreshCodexAccount(creds: CodexAccountCredentials): Promise<CodexAccountCredentials>;
|
|
70
|
+
/**
|
|
71
|
+
* Refresh REGARDLESS of the clock, for the one caller that knows better than
|
|
72
|
+
* the clock does: the backend answered 401 on a token dario still believes in
|
|
73
|
+
* (dario#1338 shape, 2026-09-17 — a stored token valid for another day, every
|
|
74
|
+
* request on the seat rejected for six hours because nothing ever asked for a
|
|
75
|
+
* new one). Same single-flight and same failure cool-down as the clock path:
|
|
76
|
+
* a dead refresh token must not become a token-endpoint storm, one per request.
|
|
77
|
+
*/
|
|
78
|
+
export declare function forceRefreshCodexAccount(creds: CodexAccountCredentials): Promise<CodexAccountCredentials>;
|
|
70
79
|
/** Record that `alias` declined, for as long as the upstream asked. */
|
|
71
80
|
export declare function noteCodexDecline(alias: string, retryAfterMs?: number | null): number;
|
|
72
81
|
/** A seat that just served is not rate-limited — clear it. */
|
package/dist/codex-accounts.js
CHANGED
|
@@ -279,6 +279,21 @@ export async function getFreshCodexAccount(creds) {
|
|
|
279
279
|
noteRefreshRecovered(creds.alias);
|
|
280
280
|
return creds;
|
|
281
281
|
}
|
|
282
|
+
return refreshNow(creds);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Refresh REGARDLESS of the clock, for the one caller that knows better than
|
|
286
|
+
* the clock does: the backend answered 401 on a token dario still believes in
|
|
287
|
+
* (dario#1338 shape, 2026-09-17 — a stored token valid for another day, every
|
|
288
|
+
* request on the seat rejected for six hours because nothing ever asked for a
|
|
289
|
+
* new one). Same single-flight and same failure cool-down as the clock path:
|
|
290
|
+
* a dead refresh token must not become a token-endpoint storm, one per request.
|
|
291
|
+
*/
|
|
292
|
+
export async function forceRefreshCodexAccount(creds) {
|
|
293
|
+
return refreshNow(creds);
|
|
294
|
+
}
|
|
295
|
+
/** The refresh itself: one in flight per alias, and a remembered failure short-circuits. */
|
|
296
|
+
async function refreshNow(creds) {
|
|
282
297
|
const existing = inflightRefresh.get(creds.alias);
|
|
283
298
|
if (existing)
|
|
284
299
|
return existing;
|
package/dist/codex-backend.d.ts
CHANGED
|
@@ -278,6 +278,30 @@ export declare function buildCodexHeaders(creds: CodexAccountCredentials): Recor
|
|
|
278
278
|
* into a buffered response object is not built yet. A non-streaming client
|
|
279
279
|
* gets a 400 saying so.
|
|
280
280
|
*/
|
|
281
|
+
/**
|
|
282
|
+
* Is this status the backend saying the CREDENTIAL is no good, rather than the
|
|
283
|
+
* request or the quota? 401 and 403 both arrive that way from the Responses
|
|
284
|
+
* API — a revoked session, a token rotated by another client, an account whose
|
|
285
|
+
* plan changed underneath us.
|
|
286
|
+
*/
|
|
287
|
+
export declare function isCodexAuthFailure(status: number): boolean;
|
|
288
|
+
/**
|
|
289
|
+
* One forced refresh after an auth failure, then the caller retries once.
|
|
290
|
+
*
|
|
291
|
+
* `getFreshCodexAccount` refreshes on the CLOCK, so a token dario believes in
|
|
292
|
+
* is never re-fetched no matter how many times upstream rejects it. On
|
|
293
|
+
* 2026-09-17 that took the fleet's only fallback seat down for six hours: the
|
|
294
|
+
* stored token was valid until the next day, the backend answered 401 to every
|
|
295
|
+
* request, and each one was handed to the client unchanged — no refresh, no
|
|
296
|
+
* cool-down, no failover.
|
|
297
|
+
*
|
|
298
|
+
* Returns the fresh credentials when a retry is worth making, or null when it
|
|
299
|
+
* is not: no refresh token to spend, the refresh failed (its own cool-down then
|
|
300
|
+
* governs — the seat is reported unavailable and the chain moves on), or the
|
|
301
|
+
* token came back byte-identical, in which case retrying only reproduces the
|
|
302
|
+
* same 401.
|
|
303
|
+
*/
|
|
304
|
+
export declare function refreshAfterCodexAuthFailure(creds: CodexAccountCredentials, verbose: boolean): Promise<CodexAccountCredentials | null>;
|
|
281
305
|
export declare function forwardResponsesToCodex(res: ServerResponse, body: Record<string, unknown>, creds: CodexAccountCredentials, corsOrigin: string, securityHeaders: Record<string, string>, upstreamTimeoutMs: number, verbose: boolean, fetchImpl?: typeof fetch, onDone?: (outcome: CodexForwardOutcome) => void,
|
|
282
306
|
/** Mirrors forwardToCodex. A 429 or 5xx is the SEAT saying no, and the
|
|
283
307
|
* caller needs to know which seat and for how long — without it the pool
|
package/dist/codex-backend.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* translation in both directions, including SSE.
|
|
23
23
|
*/
|
|
24
24
|
import { createHash } from 'node:crypto';
|
|
25
|
+
import { forceRefreshCodexAccount } from './codex-accounts.js';
|
|
25
26
|
import { anthropicToResponsesRequest, anthropicUsageFromResponses, createResponsesSSEParser, formatResponsesAnthropicSSE, createAnthropicMessageAssembler, responsesStreamToAnthropicSSE, } from './anthropic-responses-translate.js';
|
|
26
27
|
import { resolveClaudeTarget } from './claude-model.js';
|
|
27
28
|
import { parseEffortSuffix } from './effort.js';
|
|
@@ -759,6 +760,48 @@ export function buildCodexHeaders(creds) {
|
|
|
759
760
|
* into a buffered response object is not built yet. A non-streaming client
|
|
760
761
|
* gets a 400 saying so.
|
|
761
762
|
*/
|
|
763
|
+
/**
|
|
764
|
+
* Is this status the backend saying the CREDENTIAL is no good, rather than the
|
|
765
|
+
* request or the quota? 401 and 403 both arrive that way from the Responses
|
|
766
|
+
* API — a revoked session, a token rotated by another client, an account whose
|
|
767
|
+
* plan changed underneath us.
|
|
768
|
+
*/
|
|
769
|
+
export function isCodexAuthFailure(status) {
|
|
770
|
+
return status === 401 || status === 403;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* One forced refresh after an auth failure, then the caller retries once.
|
|
774
|
+
*
|
|
775
|
+
* `getFreshCodexAccount` refreshes on the CLOCK, so a token dario believes in
|
|
776
|
+
* is never re-fetched no matter how many times upstream rejects it. On
|
|
777
|
+
* 2026-09-17 that took the fleet's only fallback seat down for six hours: the
|
|
778
|
+
* stored token was valid until the next day, the backend answered 401 to every
|
|
779
|
+
* request, and each one was handed to the client unchanged — no refresh, no
|
|
780
|
+
* cool-down, no failover.
|
|
781
|
+
*
|
|
782
|
+
* Returns the fresh credentials when a retry is worth making, or null when it
|
|
783
|
+
* is not: no refresh token to spend, the refresh failed (its own cool-down then
|
|
784
|
+
* governs — the seat is reported unavailable and the chain moves on), or the
|
|
785
|
+
* token came back byte-identical, in which case retrying only reproduces the
|
|
786
|
+
* same 401.
|
|
787
|
+
*/
|
|
788
|
+
export async function refreshAfterCodexAuthFailure(creds, verbose) {
|
|
789
|
+
if (!creds.refreshToken)
|
|
790
|
+
return null;
|
|
791
|
+
try {
|
|
792
|
+
const fresh = await forceRefreshCodexAccount(creds);
|
|
793
|
+
if (fresh.accessToken === creds.accessToken)
|
|
794
|
+
return null;
|
|
795
|
+
if (verbose)
|
|
796
|
+
console.log(`[dario] codex account ${creds.alias}: upstream rejected a stored token — refreshed, retrying once`);
|
|
797
|
+
return fresh;
|
|
798
|
+
}
|
|
799
|
+
catch (err) {
|
|
800
|
+
console.warn(`[dario] codex account ${creds.alias}: upstream rejected its token and the refresh failed `
|
|
801
|
+
+ `(${err instanceof Error ? err.message : String(err)}) — re-add the seat with \`dario add altman ${creds.alias}\``);
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
762
805
|
export async function forwardResponsesToCodex(res, body, creds, corsOrigin, securityHeaders, upstreamTimeoutMs, verbose, fetchImpl = fetch, onDone,
|
|
763
806
|
/** Mirrors forwardToCodex. A 429 or 5xx is the SEAT saying no, and the
|
|
764
807
|
* caller needs to know which seat and for how long — without it the pool
|
|
@@ -808,14 +851,26 @@ deferOnUnavailable = false) {
|
|
|
808
851
|
try {
|
|
809
852
|
if (verbose)
|
|
810
853
|
console.log(`[dario] → codex backend (responses passthrough): ${target} (model: ${model})`);
|
|
811
|
-
|
|
854
|
+
let activeCreds = creds;
|
|
855
|
+
let upstream = await fetchImpl(target, { method: 'POST', headers: buildCodexHeaders(activeCreds), body: JSON.stringify(upstreamBody), signal: abort.signal });
|
|
856
|
+
if (isCodexAuthFailure(upstream.status)) {
|
|
857
|
+
await upstream.text().catch(() => ''); // release the rejected response before retrying
|
|
858
|
+
const fresh = await refreshAfterCodexAuthFailure(activeCreds, verbose);
|
|
859
|
+
if (fresh) {
|
|
860
|
+
activeCreds = fresh;
|
|
861
|
+
upstream = await fetchImpl(target, { method: 'POST', headers: buildCodexHeaders(activeCreds), body: JSON.stringify(upstreamBody), signal: abort.signal });
|
|
862
|
+
}
|
|
863
|
+
}
|
|
812
864
|
if (!upstream.ok || !upstream.body) {
|
|
813
865
|
const detail = await upstream.text().catch(() => '');
|
|
814
866
|
if (verbose)
|
|
815
867
|
console.error(`[dario] codex backend ${upstream.status}: ${detail.slice(0, 300)}`);
|
|
816
868
|
// Same rule as the Messages path: a 429 or a 5xx is the seat declining,
|
|
817
|
-
// and that is true whether or not anything is waiting to take over.
|
|
818
|
-
|
|
869
|
+
// and that is true whether or not anything is waiting to take over. An
|
|
870
|
+
// auth failure that survived the forced refresh above joins them: the
|
|
871
|
+
// seat cannot serve until someone re-adds it, so cool it and let the
|
|
872
|
+
// chain move on instead of handing the client a 401 it cannot act on.
|
|
873
|
+
const unavailable = upstream.status === 429 || upstream.status >= 500 || isCodexAuthFailure(upstream.status);
|
|
819
874
|
if (unavailable) {
|
|
820
875
|
try {
|
|
821
876
|
onDecline?.({ status: upstream.status, retryAfterMs: parseRetryAfterMs(upstream.headers.get('retry-after')), alias: creds.alias });
|
|
@@ -1050,12 +1105,28 @@ midstream) {
|
|
|
1050
1105
|
try {
|
|
1051
1106
|
if (verbose)
|
|
1052
1107
|
console.log(`[dario] → codex backend: ${target} (model: ${model})`);
|
|
1053
|
-
|
|
1108
|
+
let activeCreds = creds;
|
|
1109
|
+
let upstream = await fetchImpl(target, {
|
|
1054
1110
|
method: 'POST',
|
|
1055
|
-
headers: buildCodexHeaders(
|
|
1111
|
+
headers: buildCodexHeaders(activeCreds),
|
|
1056
1112
|
body: JSON.stringify(scrubbed),
|
|
1057
1113
|
signal: abort.signal,
|
|
1058
1114
|
});
|
|
1115
|
+
// An auth failure on a token the clock still trusts: refresh it once and
|
|
1116
|
+
// ask again, before any of the decline/report machinery below runs.
|
|
1117
|
+
if (isCodexAuthFailure(upstream.status)) {
|
|
1118
|
+
await upstream.text().catch(() => ''); // release the rejected response before retrying
|
|
1119
|
+
const fresh = await refreshAfterCodexAuthFailure(activeCreds, verbose);
|
|
1120
|
+
if (fresh) {
|
|
1121
|
+
activeCreds = fresh;
|
|
1122
|
+
upstream = await fetchImpl(target, {
|
|
1123
|
+
method: 'POST',
|
|
1124
|
+
headers: buildCodexHeaders(activeCreds),
|
|
1125
|
+
body: JSON.stringify(scrubbed),
|
|
1126
|
+
signal: abort.signal,
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1059
1130
|
if (!upstream.ok) {
|
|
1060
1131
|
const detail = await upstream.text().catch(() => '');
|
|
1061
1132
|
if (verbose)
|
|
@@ -1073,7 +1144,12 @@ midstream) {
|
|
|
1073
1144
|
// cue to fail over, not something to hand the client. A 4xx that is our
|
|
1074
1145
|
// own fault (a bad body, an unsupported parameter) is NOT: failing over
|
|
1075
1146
|
// would just reproduce it somewhere else and hide the real error.
|
|
1076
|
-
|
|
1147
|
+
// An auth failure that survived the forced refresh above counts as the
|
|
1148
|
+
// seat declining, not as the client's error: nothing the caller sends
|
|
1149
|
+
// will fix a revoked token, and a 401 relayed to Claude Code reads as an
|
|
1150
|
+
// outage (2026-09-17, six hours of it). Cooling it also stops selection
|
|
1151
|
+
// from handing the same dead seat the next request.
|
|
1152
|
+
const unavailable = upstream.status === 429 || upstream.status >= 500 || isCodexAuthFailure(upstream.status);
|
|
1077
1153
|
// The seat said no, and that is true whether or not a fallback exists
|
|
1078
1154
|
// to defer to. Recording it outside the defer branch is what lets the
|
|
1079
1155
|
// POOL rotate on a deployment with no --pool-fallback configured: with
|
|
@@ -496,7 +496,7 @@ export declare function detectDrift(t: TemplateData, installedOverride?: string
|
|
|
496
496
|
*/
|
|
497
497
|
export declare const SUPPORTED_CC_RANGE: {
|
|
498
498
|
readonly min: "1.0.0";
|
|
499
|
-
readonly maxTested: "2.1.
|
|
499
|
+
readonly maxTested: "2.1.275";
|
|
500
500
|
};
|
|
501
501
|
/**
|
|
502
502
|
* Compare two dotted-numeric version strings. Returns negative if `a<b`,
|
package/dist/live-fingerprint.js
CHANGED
|
@@ -1194,7 +1194,7 @@ export function detectDrift(t, installedOverride) {
|
|
|
1194
1194
|
*/
|
|
1195
1195
|
export const SUPPORTED_CC_RANGE = {
|
|
1196
1196
|
min: '1.0.0',
|
|
1197
|
-
maxTested: '2.1.
|
|
1197
|
+
maxTested: '2.1.275',
|
|
1198
1198
|
};
|
|
1199
1199
|
/**
|
|
1200
1200
|
* Compare two dotted-numeric version strings. Returns negative if `a<b`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.8.
|
|
3
|
+
"version": "6.8.9",
|
|
4
4
|
"description": "Use your Claude and ChatGPT subscriptions in Cursor, Cline, Aider, Claude Code and the Agent SDK — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint: either plan answers either wire shape, with automatic failover when one hits its limit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|