@askalf/dario 3.31.13 → 3.31.15
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/README.md +17 -1
- package/dist/accounts.d.ts +34 -0
- package/dist/accounts.js +68 -4
- package/dist/cc-authorize-probe.js +5 -1
- package/dist/cli.js +63 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/oauth.js +6 -2
- package/dist/request-queue.d.ts +11 -0
- package/dist/request-queue.js +5 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -202,6 +202,8 @@ dario accounts list
|
|
|
202
202
|
dario proxy
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
+
If you already have a single-account `dario login` set up and run `dario accounts add <alias>` for the first time, dario **back-fills** your existing login credentials into the pool under the reserved alias `login` before running OAuth for the new alias. Net effect: your first `accounts add` gives you two pool accounts (login + new alias), pool mode activates immediately. Back-fill is one-shot, idempotent, and never touches your existing `credentials.json` — if you later `dario accounts remove` below the 2+ threshold, single-account mode reads it unchanged. Skipped if you explicitly pick `login` as the new alias — your intent wins.
|
|
206
|
+
|
|
205
207
|
Each request picks the account with the highest headroom:
|
|
206
208
|
|
|
207
209
|
```
|
|
@@ -340,7 +342,7 @@ A version marker (`<!-- dario-sub-agent-version: X -->`) embedded in the markdow
|
|
|
340
342
|
| `dario status` | Show Claude backend OAuth token health and expiry |
|
|
341
343
|
| `dario refresh` | Force an immediate Claude token refresh |
|
|
342
344
|
| `dario logout` | Delete stored Claude credentials |
|
|
343
|
-
| `dario accounts list` / `add <alias>` / `remove <alias>` | Multi-account pool management |
|
|
345
|
+
| `dario accounts list` / `add <alias>` / `remove <alias>` | Multi-account pool management. `add <alias>` on a fresh pool auto back-fills your existing `dario login` credentials as `login`, so your first `add` trips the 2+ pool threshold on its own — see [Multi-account pool mode](#multi-account-pool-mode). |
|
|
344
346
|
| `dario backend list` / `add <name> --key=<key> [--base-url=<url>]` / `remove <name>` | OpenAI-compat backend management |
|
|
345
347
|
| `dario shim -- <cmd> [args...]` | Run a child process with the in-process fetch patch (see [Shim mode](#shim-mode)) |
|
|
346
348
|
| `dario subagent install` / `remove` / `status` | CC sub-agent lifecycle (v3.26 — see [sub-agent hook](#claude-code-sub-agent-hook-v326)) |
|
|
@@ -762,6 +764,17 @@ Yes — anything that speaks the OpenAI Chat Completions API. Groq, OpenRouter,
|
|
|
762
764
|
**Something's wrong. Where do I start?**
|
|
763
765
|
`dario doctor`. One command, one aggregated report — dario version, Node, platform, runtime/TLS classification, CC binary compat, template source + age + drift, OAuth status, pool state, backends, sub-agent install state, home dir. Exit code 1 if any check fails. Paste the output when you file an issue. (If you're inside Claude Code, `dario subagent install` once and then ask CC to "use the dario sub-agent to run doctor" — same output, no context switch.)
|
|
764
766
|
|
|
767
|
+
**OpenClaw returns 401 after I set `DARIO_API_KEY` (or upgrade past v3.30.6).**
|
|
768
|
+
If you run `dario proxy --host=0.0.0.0` (non-loopback), dario requires `DARIO_API_KEY` to be set so it's not an open subscription relay. OpenClaw 2026.2.17+ prefers `~/.openclaw/agents/main/agent/auth-profiles.json` over `openclaw.json`'s `apiKey` field or the `ANTHROPIC_API_KEY` env var — so if you have a stale Anthropic token in `auth-profiles.json` from an earlier setup, OpenClaw sends *that* token instead of `dario`, and dario rejects the request with `Authorization present but value mismatch` (visible under `dario proxy -v`, added in v3.31.2).
|
|
769
|
+
|
|
770
|
+
Three fixes, in order of simplicity:
|
|
771
|
+
|
|
772
|
+
1. **Use loopback.** `dario proxy --host=127.0.0.1` — auth only enforced on non-loopback binds, no `DARIO_API_KEY` required, no OpenClaw changes. Best if you don't actually need LAN reach to dario.
|
|
773
|
+
2. **Delete the Anthropic auth profile.** Remove the `"anthropic:default"` entry from `~/.openclaw/agents/main/agent/auth-profiles.json`. OpenClaw then falls back through the config chain and picks up `ANTHROPIC_API_KEY=dario` from the env. Confirmed working by [@tetsuco in #97](https://github.com/askalf/dario/issues/97).
|
|
774
|
+
3. **Overwrite the auth profile.** `openclaw models auth paste-token --provider anthropic` and paste `dario`. Replaces whatever key was in there — keep a backup if you use it elsewhere.
|
|
775
|
+
|
|
776
|
+
Diagnose with `dario proxy -v` — the reject log (v3.31.2+) reports header-name only (never the value, since it may be a real credential you mistyped) and tells you which of the three configs is actually being hit.
|
|
777
|
+
|
|
765
778
|
**What happens when Anthropic rotates the OAuth config?**
|
|
766
779
|
Dario auto-detects OAuth config from the installed Claude Code binary. When CC ships a new version with rotated values, dario picks them up on the next run. Cache at `~/.dario/cc-oauth-cache-v6.json`, keyed by the CC binary fingerprint. The cache path version bumps each time the canonical OAuth config shape changes so stale caches regenerate automatically on upgrade — v3 → v4 in v3.19.4 (scope-list flip CC v2.1.104 → v2.1.107), v4 → v5 in v3.31.3 (authorize URL `claude.com/cai/` → `claude.ai/` host normalization), v5 → v6 in v3.31.4 (6-scope restore after CC v2.1.116).
|
|
767
780
|
|
|
@@ -781,6 +794,9 @@ Env vars win over the file. Set `DARIO_OAUTH_DISABLE_OVERRIDE=1` to force pure a
|
|
|
781
794
|
**What happens when Anthropic changes the CC request template?**
|
|
782
795
|
Dario extracts the live request template from your installed Claude Code binary on startup — the system prompt, tool schemas, user-agent, beta flags, header insertion order, static header values, and top-level request-body key order — and uses those to replay requests instead of a version pinned into dario itself. When CC ships a new version with a tweaked template, the next `dario proxy` run picks it up automatically. Drift detection forces a refresh when the installed CC version changes under dario, and the nightly `cc-drift-watch` workflow catches upstream rotations (client_id, URLs, tool set, version) the day they ship on npm.
|
|
783
796
|
|
|
797
|
+
**Why does `dario accounts list` show an account called `login` I never added?**
|
|
798
|
+
That's your existing `dario login` credentials, back-filled into the pool automatically on your first `dario accounts add <alias>`. Pool mode activates at 2+ accounts in `~/.dario/accounts/`, and the single-account `credentials.json` store lives outside that directory — so without the back-fill, one `accounts add` would leave you at 1 pool entry and your login account orphaned. The `login` alias is reserved for this path. Safe to `dario accounts remove login` if you don't want it pooled; the original `credentials.json` is untouched by the back-fill, so single-account mode resumes reading it after removal drops you below the 2+ threshold. See [Multi-account pool mode](#multi-account-pool-mode) for the full picture.
|
|
799
|
+
|
|
784
800
|
**First time setup on a fresh Claude account.**
|
|
785
801
|
If dario is the first thing you run against a brand-new Claude account, prime the account with a few real Claude Code commands first:
|
|
786
802
|
```bash
|
package/dist/accounts.d.ts
CHANGED
|
@@ -23,3 +23,37 @@ export declare function _accountRefreshesInFlightSizeForTest(): number;
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function addAccountViaOAuth(alias: string): Promise<AccountCredentials>;
|
|
25
25
|
export declare function getAccountsDir(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Alias reserved for credentials auto-migrated from the single-account
|
|
28
|
+
* `dario login` store. Named `login` so it's semantically obvious where
|
|
29
|
+
* the entry came from and unlikely to collide with user-chosen aliases
|
|
30
|
+
* like `work`, `personal`, etc. If a user specifically requests `login`
|
|
31
|
+
* as the alias for `dario accounts add`, the caller falls back to
|
|
32
|
+
* `default` so the migration doesn't step on the user's intent.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MIGRATED_LOGIN_ALIAS = "login";
|
|
35
|
+
/**
|
|
36
|
+
* Promote the user's existing single-account `dario login` credentials
|
|
37
|
+
* (`~/.dario/credentials.json`, `~/.claude/.credentials.json`, or OS
|
|
38
|
+
* keychain — whichever `loadCredentials` finds) into the pool under a
|
|
39
|
+
* reserved alias.
|
|
40
|
+
*
|
|
41
|
+
* Why: the pool activation threshold is 2+ accounts in `~/.dario/accounts/`.
|
|
42
|
+
* A user with one `dario login` account + one `dario accounts add bar`
|
|
43
|
+
* ends up with only one account in `accounts/` (bar), pool mode never
|
|
44
|
+
* trips, and the login account is effectively orphaned while pool is off.
|
|
45
|
+
* Calling this on the first `dario accounts add` back-fills the login
|
|
46
|
+
* account into the pool so the second `add` crosses the threshold.
|
|
47
|
+
*
|
|
48
|
+
* Idempotent: no-op if `accounts/` already has any entry, no-op if no
|
|
49
|
+
* credentials are reachable anywhere. Returns the alias written to, or
|
|
50
|
+
* `null` when nothing happened.
|
|
51
|
+
*
|
|
52
|
+
* The source `credentials.json` (if present) is left untouched — single-
|
|
53
|
+
* account mode still reads it if the user later `accounts remove`s down
|
|
54
|
+
* below the pool threshold. Migration is copy-only, never destructive.
|
|
55
|
+
*
|
|
56
|
+
* @param preferredAlias caller may request a specific alias. If it's
|
|
57
|
+
* already the reserved `login` (or collides), falls back to `default`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function ensureLoginCredentialsInPool(alias?: string): Promise<string | null>;
|
package/dist/accounts.js
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
* Multi-account credential storage.
|
|
3
3
|
*
|
|
4
4
|
* Accounts live at `~/.dario/accounts/<alias>.json`. Single-account dario
|
|
5
|
-
*
|
|
6
|
-
* When `~/.dario/accounts/` contains 2+ files the proxy
|
|
7
|
-
* (see pool.ts). Each account has its own independent
|
|
8
|
-
* can refresh without affecting the others.
|
|
5
|
+
* uses `~/.dario/credentials.json` (plus the CC file + OS keychain fallback
|
|
6
|
+
* paths in oauth.ts). When `~/.dario/accounts/` contains 2+ files the proxy
|
|
7
|
+
* activates pool mode (see pool.ts). Each account has its own independent
|
|
8
|
+
* OAuth lifecycle and can refresh without affecting the others.
|
|
9
|
+
*
|
|
10
|
+
* `ensureLoginCredentialsInPool` (below) bridges the two stores on the
|
|
11
|
+
* first `dario accounts add` — it promotes the user's existing login
|
|
12
|
+
* credentials into the pool under a reserved alias so that adding a
|
|
13
|
+
* second account actually trips the 2+ threshold and activates pooling.
|
|
9
14
|
*
|
|
10
15
|
* OAuth config (client_id, scopes, authorize URL, token URL) comes from
|
|
11
16
|
* dario's cc-oauth-detect scanner — the same source the single-account
|
|
@@ -17,6 +22,7 @@ import { homedir } from 'node:os';
|
|
|
17
22
|
import { randomUUID, randomBytes, createHash } from 'node:crypto';
|
|
18
23
|
import { createServer } from 'node:http';
|
|
19
24
|
import { detectCCOAuthConfig } from './cc-oauth-detect.js';
|
|
25
|
+
import { loadCredentials } from './oauth.js';
|
|
20
26
|
const DARIO_DIR = join(homedir(), '.dario');
|
|
21
27
|
const ACCOUNTS_DIR = join(DARIO_DIR, 'accounts');
|
|
22
28
|
/**
|
|
@@ -308,3 +314,61 @@ export async function addAccountViaOAuth(alias) {
|
|
|
308
314
|
export function getAccountsDir() {
|
|
309
315
|
return ACCOUNTS_DIR;
|
|
310
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Alias reserved for credentials auto-migrated from the single-account
|
|
319
|
+
* `dario login` store. Named `login` so it's semantically obvious where
|
|
320
|
+
* the entry came from and unlikely to collide with user-chosen aliases
|
|
321
|
+
* like `work`, `personal`, etc. If a user specifically requests `login`
|
|
322
|
+
* as the alias for `dario accounts add`, the caller falls back to
|
|
323
|
+
* `default` so the migration doesn't step on the user's intent.
|
|
324
|
+
*/
|
|
325
|
+
export const MIGRATED_LOGIN_ALIAS = 'login';
|
|
326
|
+
/**
|
|
327
|
+
* Promote the user's existing single-account `dario login` credentials
|
|
328
|
+
* (`~/.dario/credentials.json`, `~/.claude/.credentials.json`, or OS
|
|
329
|
+
* keychain — whichever `loadCredentials` finds) into the pool under a
|
|
330
|
+
* reserved alias.
|
|
331
|
+
*
|
|
332
|
+
* Why: the pool activation threshold is 2+ accounts in `~/.dario/accounts/`.
|
|
333
|
+
* A user with one `dario login` account + one `dario accounts add bar`
|
|
334
|
+
* ends up with only one account in `accounts/` (bar), pool mode never
|
|
335
|
+
* trips, and the login account is effectively orphaned while pool is off.
|
|
336
|
+
* Calling this on the first `dario accounts add` back-fills the login
|
|
337
|
+
* account into the pool so the second `add` crosses the threshold.
|
|
338
|
+
*
|
|
339
|
+
* Idempotent: no-op if `accounts/` already has any entry, no-op if no
|
|
340
|
+
* credentials are reachable anywhere. Returns the alias written to, or
|
|
341
|
+
* `null` when nothing happened.
|
|
342
|
+
*
|
|
343
|
+
* The source `credentials.json` (if present) is left untouched — single-
|
|
344
|
+
* account mode still reads it if the user later `accounts remove`s down
|
|
345
|
+
* below the pool threshold. Migration is copy-only, never destructive.
|
|
346
|
+
*
|
|
347
|
+
* @param preferredAlias caller may request a specific alias. If it's
|
|
348
|
+
* already the reserved `login` (or collides), falls back to `default`.
|
|
349
|
+
*/
|
|
350
|
+
export async function ensureLoginCredentialsInPool(alias = MIGRATED_LOGIN_ALIAS) {
|
|
351
|
+
if (!safeAliasPath(alias))
|
|
352
|
+
return null;
|
|
353
|
+
const existing = await listAccountAliases();
|
|
354
|
+
if (existing.length > 0)
|
|
355
|
+
return null;
|
|
356
|
+
const creds = await loadCredentials();
|
|
357
|
+
const tok = creds?.claudeAiOauth;
|
|
358
|
+
if (!tok?.accessToken || !tok?.refreshToken)
|
|
359
|
+
return null;
|
|
360
|
+
const identity = (await detectClaudeIdentity()) ?? {
|
|
361
|
+
deviceId: randomUUID(),
|
|
362
|
+
accountUuid: randomUUID(),
|
|
363
|
+
};
|
|
364
|
+
await saveAccount({
|
|
365
|
+
alias,
|
|
366
|
+
accessToken: tok.accessToken,
|
|
367
|
+
refreshToken: tok.refreshToken,
|
|
368
|
+
expiresAt: tok.expiresAt,
|
|
369
|
+
scopes: tok.scopes ?? [],
|
|
370
|
+
deviceId: identity.deviceId,
|
|
371
|
+
accountUuid: identity.accountUuid,
|
|
372
|
+
});
|
|
373
|
+
return alias;
|
|
374
|
+
}
|
|
@@ -131,7 +131,11 @@ export function buildProbeAuthorizeUrl(cfg) {
|
|
|
131
131
|
scope: cfg.scopes,
|
|
132
132
|
code_challenge: pkceChallenge(),
|
|
133
133
|
code_challenge_method: 'S256',
|
|
134
|
-
|
|
134
|
+
// 32 bytes — match what CC v2.1.116+ actually sends. See dario#71.
|
|
135
|
+
// Shorter states produce "Invalid request format" from Anthropic's
|
|
136
|
+
// authorize endpoint, which the probe classifier would otherwise mis-
|
|
137
|
+
// attribute to drift when it's actually our own request shape.
|
|
138
|
+
state: base64url(randomBytes(32)),
|
|
135
139
|
});
|
|
136
140
|
return `${cfg.authorizeUrl}?${params.toString()}`;
|
|
137
141
|
}
|
package/dist/cli.js
CHANGED
|
@@ -12,34 +12,22 @@
|
|
|
12
12
|
// ── Bun auto-relaunch ──
|
|
13
13
|
// Bun's TLS fingerprint matches Claude Code's runtime (both use Bun/BoringSSL).
|
|
14
14
|
// If Bun is installed and we're running on Node, relaunch under Bun for
|
|
15
|
-
// network-level fingerprint fidelity.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// Check if bun exists
|
|
20
|
-
execFileSync('bun', ['--version'], { stdio: 'ignore', timeout: 3000 });
|
|
21
|
-
// Relaunch under bun
|
|
22
|
-
const { spawn } = await import('node:child_process');
|
|
23
|
-
const child = spawn('bun', ['run', ...process.argv.slice(1)], {
|
|
24
|
-
stdio: 'inherit',
|
|
25
|
-
env: { ...process.env, DARIO_NO_BUN: '1' },
|
|
26
|
-
});
|
|
27
|
-
child.on('exit', (code) => process.exit(code ?? 0));
|
|
28
|
-
// Prevent this process from continuing
|
|
29
|
-
await new Promise(() => { });
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// Bun not available, continue with Node
|
|
33
|
-
}
|
|
34
|
-
}
|
|
15
|
+
// network-level fingerprint fidelity. Moved below into the main-entry guard
|
|
16
|
+
// at the bottom of the file so importing this module (e.g. from tests that
|
|
17
|
+
// just want `parsePositiveIntEnv`) doesn't trigger a Bun relaunch or any
|
|
18
|
+
// other startup side effect.
|
|
35
19
|
import { unlink } from 'node:fs/promises';
|
|
36
20
|
import { join } from 'node:path';
|
|
37
21
|
import { homedir } from 'node:os';
|
|
22
|
+
import { pathToFileURL } from 'node:url';
|
|
38
23
|
import { startAutoOAuthFlow, startManualOAuthFlow, detectHeadlessEnvironment, getStatus, refreshTokens, loadCredentials } from './oauth.js';
|
|
39
24
|
import { startProxy, sanitizeError } from './proxy.js';
|
|
40
25
|
import { VALID_EFFORT_VALUES } from './cc-template.js';
|
|
41
|
-
import { listAccountAliases, loadAllAccounts, addAccountViaOAuth, removeAccount } from './accounts.js';
|
|
26
|
+
import { listAccountAliases, loadAllAccounts, addAccountViaOAuth, removeAccount, ensureLoginCredentialsInPool, MIGRATED_LOGIN_ALIAS } from './accounts.js';
|
|
42
27
|
import { listBackends, saveBackend, removeBackend } from './openai-backend.js';
|
|
28
|
+
// `args` / `command` at module scope — command handlers below close over
|
|
29
|
+
// `args` to read their own flags. Reading argv is harmless on import; only
|
|
30
|
+
// the handler dispatch at the bottom is gated behind the main-entry check.
|
|
43
31
|
const args = process.argv.slice(2);
|
|
44
32
|
const command = args[0] ?? 'proxy';
|
|
45
33
|
async function login() {
|
|
@@ -463,6 +451,22 @@ async function accounts() {
|
|
|
463
451
|
console.error(`[dario] Account "${alias}" already exists. Remove it first with \`dario accounts remove ${alias}\`.`);
|
|
464
452
|
process.exit(1);
|
|
465
453
|
}
|
|
454
|
+
// If the user has `dario login` credentials on disk or in the keychain
|
|
455
|
+
// and the pool is empty, migrate those credentials into the pool first.
|
|
456
|
+
// Otherwise the new account lives alone in accounts/, pool mode never
|
|
457
|
+
// trips the 2+ threshold, and the login account is orphaned from the
|
|
458
|
+
// pool until the user figures out they have to re-`accounts add` it.
|
|
459
|
+
// Skip silently when the user explicitly picks the reserved alias —
|
|
460
|
+
// their intent wins, they can run `accounts add` again for the login
|
|
461
|
+
// migration under a different alias.
|
|
462
|
+
if (existing.length === 0 && alias !== MIGRATED_LOGIN_ALIAS) {
|
|
463
|
+
const migrated = await ensureLoginCredentialsInPool();
|
|
464
|
+
if (migrated) {
|
|
465
|
+
console.log('');
|
|
466
|
+
console.log(` Migrated your existing \`dario login\` account into the pool as "${migrated}".`);
|
|
467
|
+
console.log(` (Pool mode activates on 2+ accounts — this back-fill plus "${alias}" crosses that.)`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
466
470
|
console.log('');
|
|
467
471
|
console.log(` Adding account "${alias}" to the pool...`);
|
|
468
472
|
console.log('');
|
|
@@ -1143,13 +1147,42 @@ const commands = {
|
|
|
1143
1147
|
'--version': version,
|
|
1144
1148
|
'-V': version,
|
|
1145
1149
|
};
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1150
|
+
// Main-entry guard. Only run the Bun auto-relaunch and handler dispatch when
|
|
1151
|
+
// this module is the direct entry point — importing it (from tests or for
|
|
1152
|
+
// a library helper like `parsePositiveIntEnv`) must NOT start the proxy.
|
|
1153
|
+
// Before this guard, `import { parsePositiveIntEnv } from './cli.js'` would
|
|
1154
|
+
// fall through to `command = args[0] ?? 'proxy'` and fire `handler()`, which
|
|
1155
|
+
// tried to run `startProxy()` and failed the test with "Not authenticated".
|
|
1156
|
+
const isDirectEntry = typeof process.argv[1] === 'string' &&
|
|
1157
|
+
import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
1158
|
+
if (isDirectEntry) {
|
|
1159
|
+
// Bun auto-relaunch for TLS fingerprint fidelity. Only meaningful when
|
|
1160
|
+
// dario is the direct entry — if we're imported, whoever imported us
|
|
1161
|
+
// already chose their runtime.
|
|
1162
|
+
if (!('Bun' in globalThis) && !process.env.DARIO_NO_BUN) {
|
|
1163
|
+
try {
|
|
1164
|
+
const { execFileSync, spawn } = await import('node:child_process');
|
|
1165
|
+
execFileSync('bun', ['--version'], { stdio: 'ignore', timeout: 3000 });
|
|
1166
|
+
const child = spawn('bun', ['run', ...process.argv.slice(1)], {
|
|
1167
|
+
stdio: 'inherit',
|
|
1168
|
+
env: { ...process.env, DARIO_NO_BUN: '1' },
|
|
1169
|
+
});
|
|
1170
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
1171
|
+
// Prevent this process from continuing
|
|
1172
|
+
await new Promise(() => { });
|
|
1173
|
+
}
|
|
1174
|
+
catch {
|
|
1175
|
+
// Bun not available, continue with Node
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const handler = commands[command];
|
|
1179
|
+
if (!handler) {
|
|
1180
|
+
console.error(`Unknown command: ${command}`);
|
|
1181
|
+
console.error('Run `dario help` for usage.');
|
|
1182
|
+
process.exit(1);
|
|
1183
|
+
}
|
|
1184
|
+
handler().catch(err => {
|
|
1185
|
+
console.error('Fatal error:', sanitizeError(err));
|
|
1186
|
+
process.exit(1);
|
|
1187
|
+
});
|
|
1151
1188
|
}
|
|
1152
|
-
handler().catch(err => {
|
|
1153
|
-
console.error('Fatal error:', sanitizeError(err));
|
|
1154
|
-
process.exit(1);
|
|
1155
|
-
});
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { OAuthTokens, CredentialsFile } from './oauth.js';
|
|
|
9
9
|
export { startProxy, sanitizeError } from './proxy.js';
|
|
10
10
|
export { AccountPool, parseRateLimits } from './pool.js';
|
|
11
11
|
export type { PoolAccount, PoolStatus, RateLimitSnapshot, AccountIdentity } from './pool.js';
|
|
12
|
-
export { listAccountAliases, loadAccount, loadAllAccounts, saveAccount, removeAccount, refreshAccountToken, addAccountViaOAuth, getAccountsDir, } from './accounts.js';
|
|
12
|
+
export { listAccountAliases, loadAccount, loadAllAccounts, saveAccount, removeAccount, refreshAccountToken, addAccountViaOAuth, ensureLoginCredentialsInPool, MIGRATED_LOGIN_ALIAS, getAccountsDir, } from './accounts.js';
|
|
13
13
|
export type { AccountCredentials } from './accounts.js';
|
|
14
14
|
export { Analytics } from './analytics.js';
|
|
15
15
|
export type { RequestRecord, AnalyticsSummary } from './analytics.js';
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { startProxy, sanitizeError } from './proxy.js';
|
|
|
10
10
|
// contains 2+ accounts; see README for the progression from single-account
|
|
11
11
|
// mode to pool mode).
|
|
12
12
|
export { AccountPool, parseRateLimits } from './pool.js';
|
|
13
|
-
export { listAccountAliases, loadAccount, loadAllAccounts, saveAccount, removeAccount, refreshAccountToken, addAccountViaOAuth, getAccountsDir, } from './accounts.js';
|
|
13
|
+
export { listAccountAliases, loadAccount, loadAllAccounts, saveAccount, removeAccount, refreshAccountToken, addAccountViaOAuth, ensureLoginCredentialsInPool, MIGRATED_LOGIN_ALIAS, getAccountsDir, } from './accounts.js';
|
|
14
14
|
export { Analytics } from './analytics.js';
|
|
15
15
|
// Multi-provider backends (v3.6.0+). Secondary OpenAI-compat providers
|
|
16
16
|
// (OpenAI, OpenRouter, Groq, local LiteLLM, etc.) configured via
|
package/dist/oauth.js
CHANGED
|
@@ -198,7 +198,10 @@ async function saveCredentials(creds) {
|
|
|
198
198
|
export async function startAutoOAuthFlow() {
|
|
199
199
|
const { createServer } = await import('node:http');
|
|
200
200
|
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
201
|
-
|
|
201
|
+
// 32 random bytes → 43-char base64url state. See dario#71 — Anthropic's
|
|
202
|
+
// authorize endpoint rejects shorter states with "Invalid request format";
|
|
203
|
+
// CC v2.1.116+ ships 32. Keep in lockstep with CC's entropy-per-state.
|
|
204
|
+
const state = base64url(randomBytes(32));
|
|
202
205
|
return new Promise((resolve, reject) => {
|
|
203
206
|
const server = createServer((req, res) => {
|
|
204
207
|
const url = new URL(req.url || '', `http://${req.headers.host || 'localhost'}`);
|
|
@@ -387,7 +390,8 @@ export function detectHeadlessEnvironment() {
|
|
|
387
390
|
*/
|
|
388
391
|
export async function startManualOAuthFlow() {
|
|
389
392
|
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
390
|
-
|
|
393
|
+
// 32 bytes — same reason as startAutoOAuthFlow. See dario#71.
|
|
394
|
+
const state = base64url(randomBytes(32));
|
|
391
395
|
const cfg = await getOAuthConfig();
|
|
392
396
|
const authUrl = buildManualAuthorizeUrl(cfg, codeChallenge, state);
|
|
393
397
|
console.log('');
|
package/dist/request-queue.d.ts
CHANGED
|
@@ -50,6 +50,16 @@ export interface RequestQueueOptions {
|
|
|
50
50
|
maxConcurrent?: number;
|
|
51
51
|
maxQueued?: number;
|
|
52
52
|
queueTimeoutMs?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Whether timeout timers are `unref`'d so they don't by themselves keep
|
|
55
|
+
* the Node event loop alive. Default `true` — appropriate for the proxy,
|
|
56
|
+
* where a leaked queue entry should never hang shutdown. Pass `false` in
|
|
57
|
+
* tests where the queue is the only pending work on the loop: an
|
|
58
|
+
* `unref`'d timer won't fire in that case (Node exits with "unsettled
|
|
59
|
+
* top-level await" before the 50ms timeout elapses), so the reject the
|
|
60
|
+
* test is waiting for never arrives.
|
|
61
|
+
*/
|
|
62
|
+
unrefTimers?: boolean;
|
|
53
63
|
}
|
|
54
64
|
export declare const DEFAULT_MAX_CONCURRENT = 10;
|
|
55
65
|
export declare const DEFAULT_MAX_QUEUED = 128;
|
|
@@ -58,6 +68,7 @@ export declare class RequestQueue {
|
|
|
58
68
|
readonly maxConcurrent: number;
|
|
59
69
|
readonly maxQueued: number;
|
|
60
70
|
readonly queueTimeoutMs: number;
|
|
71
|
+
readonly unrefTimers: boolean;
|
|
61
72
|
private active;
|
|
62
73
|
private queue;
|
|
63
74
|
constructor(opts?: RequestQueueOptions);
|
package/dist/request-queue.js
CHANGED
|
@@ -47,12 +47,14 @@ export class RequestQueue {
|
|
|
47
47
|
maxConcurrent;
|
|
48
48
|
maxQueued;
|
|
49
49
|
queueTimeoutMs;
|
|
50
|
+
unrefTimers;
|
|
50
51
|
active = 0;
|
|
51
52
|
queue = [];
|
|
52
53
|
constructor(opts = {}) {
|
|
53
54
|
this.maxConcurrent = opts.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
54
55
|
this.maxQueued = opts.maxQueued ?? DEFAULT_MAX_QUEUED;
|
|
55
56
|
this.queueTimeoutMs = opts.queueTimeoutMs ?? DEFAULT_QUEUE_TIMEOUT_MS;
|
|
57
|
+
this.unrefTimers = opts.unrefTimers ?? true;
|
|
56
58
|
}
|
|
57
59
|
/**
|
|
58
60
|
* Acquire a concurrency slot. Resolves when admitted; throws
|
|
@@ -80,7 +82,9 @@ export class RequestQueue {
|
|
|
80
82
|
}, this.queueTimeoutMs);
|
|
81
83
|
// Keep the timer from pinning the event loop open on shutdown. A queued
|
|
82
84
|
// request waiting for a slot shouldn't by itself keep the process alive.
|
|
83
|
-
|
|
85
|
+
// Opt-out for tests — see `unrefTimers` comment in RequestQueueOptions.
|
|
86
|
+
if (this.unrefTimers)
|
|
87
|
+
timeoutHandle.unref?.();
|
|
84
88
|
const entry = { resolve, reject, enqueuedAt, timeoutHandle };
|
|
85
89
|
this.queue.push(entry);
|
|
86
90
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "3.31.
|
|
3
|
+
"version": "3.31.15",
|
|
4
4
|
"description": "A local LLM router. One endpoint, every provider — Claude subscriptions, OpenAI, OpenRouter, Groq, local LiteLLM, any OpenAI-compat endpoint — your tools don't need to change.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"node": ">=18.0.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@types/node": "^
|
|
72
|
+
"@types/node": "^25.6.0",
|
|
73
73
|
"tsx": "^4.19.0",
|
|
74
74
|
"typescript": "^5.7.0"
|
|
75
75
|
}
|