@askalf/dario 3.23.0 → 3.24.0
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/cli.js +28 -1
- package/dist/pacing.d.ts +62 -0
- package/dist/pacing.js +78 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +17 -8
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -208,7 +208,25 @@ async function proxy() {
|
|
|
208
208
|
const strictTls = args.includes('--strict-tls');
|
|
209
209
|
const modelArg = args.find(a => a.startsWith('--model='));
|
|
210
210
|
const model = modelArg ? modelArg.split('=')[1] : undefined;
|
|
211
|
-
|
|
211
|
+
// --pace-min=MS / --pace-jitter=MS (v3.24, direction #6 — behavioral
|
|
212
|
+
// smoothing). Inter-request gap floor + optional uniform-random jitter.
|
|
213
|
+
// Defaults preserve v3.23 behavior (500ms floor, no jitter). The pure
|
|
214
|
+
// calc lives in src/pacing.ts; the flags just feed it.
|
|
215
|
+
const pacingMinMs = parsePositiveIntFlag('--pace-min=');
|
|
216
|
+
const pacingJitterMs = parsePositiveIntFlag('--pace-jitter=');
|
|
217
|
+
await startProxy({ port, host, verbose, verboseBodies, model, passthrough, preserveTools, hybridTools, noAutoDetect, strictTls, pacingMinMs, pacingJitterMs });
|
|
218
|
+
}
|
|
219
|
+
function parsePositiveIntFlag(prefix) {
|
|
220
|
+
const found = args.find(a => a.startsWith(prefix));
|
|
221
|
+
if (!found)
|
|
222
|
+
return undefined;
|
|
223
|
+
const raw = found.slice(prefix.length);
|
|
224
|
+
const n = parseInt(raw, 10);
|
|
225
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
226
|
+
console.error(`[dario] Invalid ${prefix.replace(/=$/, '')} value: ${JSON.stringify(raw)}. Must be a non-negative integer (ms).`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
return n;
|
|
212
230
|
}
|
|
213
231
|
async function accounts() {
|
|
214
232
|
const sub = args[1];
|
|
@@ -459,6 +477,15 @@ async function help() {
|
|
|
459
477
|
from a stock CC request. Install Bun
|
|
460
478
|
(https://bun.sh) so dario auto-relaunches
|
|
461
479
|
under it, or use shim mode. (v3.23)
|
|
480
|
+
--pace-min=MS Minimum ms between upstream requests
|
|
481
|
+
(default: 500). Prevents request floods
|
|
482
|
+
that are distinguishable from human-paced
|
|
483
|
+
CC traffic.
|
|
484
|
+
--pace-jitter=MS Max additional uniform-random jitter (ms)
|
|
485
|
+
added on top of --pace-min per request.
|
|
486
|
+
Default: 0 (off). Set to e.g. 300 to hide
|
|
487
|
+
the floor from long-run inter-arrival
|
|
488
|
+
statistics. (v3.24)
|
|
462
489
|
--port=PORT Port to listen on (default: 3456)
|
|
463
490
|
--host=ADDRESS Address to bind to (default: 127.0.0.1)
|
|
464
491
|
Use 0.0.0.0 for LAN; see README for DARIO_API_KEY
|
package/dist/pacing.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter-request pacing (v3.24, direction #6 — behavioral smoothing).
|
|
3
|
+
*
|
|
4
|
+
* Real CC traffic has human-paced gaps between requests — sub-second when
|
|
5
|
+
* the model is streaming tool-loop output, multi-second when the user is
|
|
6
|
+
* typing the next message. A proxy that fires requests at machine speed
|
|
7
|
+
* with perfectly uniform spacing stands out against that rhythm.
|
|
8
|
+
*
|
|
9
|
+
* This module supplies the pure gap-calculation function the proxy's
|
|
10
|
+
* rate governor calls before every outbound fetch. Two knobs:
|
|
11
|
+
*
|
|
12
|
+
* minGapMs — lower bound on the wall-clock distance between requests.
|
|
13
|
+
* Was a hardcoded 500ms through v3.23; keep 500 as default
|
|
14
|
+
* so back-compat is exact when both knobs stay at defaults.
|
|
15
|
+
*
|
|
16
|
+
* jitterMs — uniform random addition on top of minGap. The *effective*
|
|
17
|
+
* gap for a given request is minGap + U(0, jitter). Adds
|
|
18
|
+
* non-uniformity so an observer can't infer the floor from
|
|
19
|
+
* the long-run minimum of inter-arrival times.
|
|
20
|
+
*
|
|
21
|
+
* Pure over (now, lastRequestTime, minGap, jitter, rng) so the tests can
|
|
22
|
+
* exercise every edge without spawning timers. The proxy passes
|
|
23
|
+
* `Math.random` as the rng at runtime; tests pass a deterministic stub.
|
|
24
|
+
*
|
|
25
|
+
* The first request in a session (lastRequestTime === 0) is never paced —
|
|
26
|
+
* the purpose is smoothing the *gap between* requests, not delaying the
|
|
27
|
+
* first one from whenever the consumer happens to connect.
|
|
28
|
+
*/
|
|
29
|
+
export interface PacingConfig {
|
|
30
|
+
/** Minimum wall-clock milliseconds between the completion of one request and the start of the next. */
|
|
31
|
+
minGapMs: number;
|
|
32
|
+
/** Max additional uniform-random jitter (ms) added on top of minGap. Pass 0 to disable. */
|
|
33
|
+
jitterMs: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* How many milliseconds to sleep before the next upstream fetch.
|
|
37
|
+
*
|
|
38
|
+
* Returns 0 when no delay is required — either because this is the first
|
|
39
|
+
* request of the session, or enough wall-clock time has already elapsed
|
|
40
|
+
* since `lastRequestTime`.
|
|
41
|
+
*
|
|
42
|
+
* `rng` defaults to Math.random; tests inject a deterministic stub.
|
|
43
|
+
* Negative configuration values are clamped to 0 (lenient, not an error).
|
|
44
|
+
*/
|
|
45
|
+
export declare function computePacingDelay(now: number, lastRequestTime: number, cfg: PacingConfig, rng?: () => number): number;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a PacingConfig from explicit options, env vars, and defaults.
|
|
48
|
+
*
|
|
49
|
+
* Precedence (highest first):
|
|
50
|
+
* 1. Explicit argument (typically from CLI flag)
|
|
51
|
+
* 2. DARIO_PACE_MIN_MS / DARIO_PACE_JITTER_MS env vars
|
|
52
|
+
* 3. Legacy DARIO_MIN_INTERVAL_MS env var (minGap only — matches v3.23
|
|
53
|
+
* behavior so existing setups don't regress silently)
|
|
54
|
+
* 4. Defaults: minGap=500, jitter=0
|
|
55
|
+
*
|
|
56
|
+
* Invalid strings (non-numeric, negative) are ignored and fall through to
|
|
57
|
+
* the next source — a typoed env var shouldn't fail-loud at startup.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolvePacingConfig(explicit?: {
|
|
60
|
+
minGapMs?: number;
|
|
61
|
+
jitterMs?: number;
|
|
62
|
+
}, env?: NodeJS.ProcessEnv): PacingConfig;
|
package/dist/pacing.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter-request pacing (v3.24, direction #6 — behavioral smoothing).
|
|
3
|
+
*
|
|
4
|
+
* Real CC traffic has human-paced gaps between requests — sub-second when
|
|
5
|
+
* the model is streaming tool-loop output, multi-second when the user is
|
|
6
|
+
* typing the next message. A proxy that fires requests at machine speed
|
|
7
|
+
* with perfectly uniform spacing stands out against that rhythm.
|
|
8
|
+
*
|
|
9
|
+
* This module supplies the pure gap-calculation function the proxy's
|
|
10
|
+
* rate governor calls before every outbound fetch. Two knobs:
|
|
11
|
+
*
|
|
12
|
+
* minGapMs — lower bound on the wall-clock distance between requests.
|
|
13
|
+
* Was a hardcoded 500ms through v3.23; keep 500 as default
|
|
14
|
+
* so back-compat is exact when both knobs stay at defaults.
|
|
15
|
+
*
|
|
16
|
+
* jitterMs — uniform random addition on top of minGap. The *effective*
|
|
17
|
+
* gap for a given request is minGap + U(0, jitter). Adds
|
|
18
|
+
* non-uniformity so an observer can't infer the floor from
|
|
19
|
+
* the long-run minimum of inter-arrival times.
|
|
20
|
+
*
|
|
21
|
+
* Pure over (now, lastRequestTime, minGap, jitter, rng) so the tests can
|
|
22
|
+
* exercise every edge without spawning timers. The proxy passes
|
|
23
|
+
* `Math.random` as the rng at runtime; tests pass a deterministic stub.
|
|
24
|
+
*
|
|
25
|
+
* The first request in a session (lastRequestTime === 0) is never paced —
|
|
26
|
+
* the purpose is smoothing the *gap between* requests, not delaying the
|
|
27
|
+
* first one from whenever the consumer happens to connect.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* How many milliseconds to sleep before the next upstream fetch.
|
|
31
|
+
*
|
|
32
|
+
* Returns 0 when no delay is required — either because this is the first
|
|
33
|
+
* request of the session, or enough wall-clock time has already elapsed
|
|
34
|
+
* since `lastRequestTime`.
|
|
35
|
+
*
|
|
36
|
+
* `rng` defaults to Math.random; tests inject a deterministic stub.
|
|
37
|
+
* Negative configuration values are clamped to 0 (lenient, not an error).
|
|
38
|
+
*/
|
|
39
|
+
export function computePacingDelay(now, lastRequestTime, cfg, rng = Math.random) {
|
|
40
|
+
if (lastRequestTime <= 0)
|
|
41
|
+
return 0;
|
|
42
|
+
const minGap = Math.max(0, cfg.minGapMs);
|
|
43
|
+
const jitter = Math.max(0, cfg.jitterMs);
|
|
44
|
+
const jitterAdd = jitter > 0 ? Math.floor(rng() * jitter) : 0;
|
|
45
|
+
const effectiveGap = minGap + jitterAdd;
|
|
46
|
+
const elapsed = now - lastRequestTime;
|
|
47
|
+
if (elapsed >= effectiveGap)
|
|
48
|
+
return 0;
|
|
49
|
+
return effectiveGap - elapsed;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a PacingConfig from explicit options, env vars, and defaults.
|
|
53
|
+
*
|
|
54
|
+
* Precedence (highest first):
|
|
55
|
+
* 1. Explicit argument (typically from CLI flag)
|
|
56
|
+
* 2. DARIO_PACE_MIN_MS / DARIO_PACE_JITTER_MS env vars
|
|
57
|
+
* 3. Legacy DARIO_MIN_INTERVAL_MS env var (minGap only — matches v3.23
|
|
58
|
+
* behavior so existing setups don't regress silently)
|
|
59
|
+
* 4. Defaults: minGap=500, jitter=0
|
|
60
|
+
*
|
|
61
|
+
* Invalid strings (non-numeric, negative) are ignored and fall through to
|
|
62
|
+
* the next source — a typoed env var shouldn't fail-loud at startup.
|
|
63
|
+
*/
|
|
64
|
+
export function resolvePacingConfig(explicit = {}, env = process.env) {
|
|
65
|
+
const minGap = pickNonNegativeInt(explicit.minGapMs, env.DARIO_PACE_MIN_MS, env.DARIO_MIN_INTERVAL_MS) ?? 500;
|
|
66
|
+
const jitter = pickNonNegativeInt(explicit.jitterMs, env.DARIO_PACE_JITTER_MS) ?? 0;
|
|
67
|
+
return { minGapMs: minGap, jitterMs: jitter };
|
|
68
|
+
}
|
|
69
|
+
function pickNonNegativeInt(...candidates) {
|
|
70
|
+
for (const c of candidates) {
|
|
71
|
+
if (c === undefined || c === null || c === '')
|
|
72
|
+
continue;
|
|
73
|
+
const n = typeof c === 'number' ? c : parseInt(c, 10);
|
|
74
|
+
if (Number.isFinite(n) && n >= 0)
|
|
75
|
+
return Math.floor(n);
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
package/dist/proxy.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ interface ProxyOptions {
|
|
|
13
13
|
hybridTools?: boolean;
|
|
14
14
|
noAutoDetect?: boolean;
|
|
15
15
|
strictTls?: boolean;
|
|
16
|
+
pacingMinMs?: number;
|
|
17
|
+
pacingJitterMs?: number;
|
|
16
18
|
}
|
|
17
19
|
export declare function sanitizeError(err: unknown): string;
|
|
18
20
|
export declare function startProxy(opts?: ProxyOptions): Promise<void>;
|
package/dist/proxy.js
CHANGED
|
@@ -571,10 +571,19 @@ export async function startProxy(opts = {}) {
|
|
|
571
571
|
betaBase = betaBase ? `${betaBase},oauth-2025-04-20` : 'oauth-2025-04-20';
|
|
572
572
|
}
|
|
573
573
|
const betaWithoutContext1m = betaBase.split(',').filter((t) => t !== 'context-1m-2025-08-07').join(',');
|
|
574
|
-
// Rate governor —
|
|
575
|
-
//
|
|
574
|
+
// Rate governor — floor + optional jitter between requests. A hardcoded
|
|
575
|
+
// 500ms floor keeps the default behavior identical to v3.23; `--pace-min`
|
|
576
|
+
// and `--pace-jitter` let callers tune the distribution. Pure calc lives
|
|
577
|
+
// in src/pacing.ts so the edge cases are unit-tested without timers.
|
|
578
|
+
const { computePacingDelay, resolvePacingConfig } = await import('./pacing.js');
|
|
576
579
|
let lastRequestTime = 0;
|
|
577
|
-
const
|
|
580
|
+
const pacingCfg = resolvePacingConfig({
|
|
581
|
+
minGapMs: opts.pacingMinMs,
|
|
582
|
+
jitterMs: opts.pacingJitterMs,
|
|
583
|
+
});
|
|
584
|
+
if (verbose) {
|
|
585
|
+
console.log(`[dario] pacing: min=${pacingCfg.minGapMs}ms jitter=${pacingCfg.jitterMs}ms`);
|
|
586
|
+
}
|
|
578
587
|
// Optional proxy authentication — pre-encode key buffer for performance
|
|
579
588
|
const apiKey = process.env.DARIO_API_KEY;
|
|
580
589
|
const apiKeyBuf = apiKey ? Buffer.from(apiKey) : null;
|
|
@@ -1076,11 +1085,11 @@ export async function startProxy(opts = {}) {
|
|
|
1076
1085
|
beta = beta.split(',').filter((t) => t.length > 0 && !rejectedSet.has(t)).join(',');
|
|
1077
1086
|
}
|
|
1078
1087
|
}
|
|
1079
|
-
// Rate governor — prevent inhuman request cadence
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1082
|
-
if (
|
|
1083
|
-
await new Promise(r => setTimeout(r,
|
|
1088
|
+
// Rate governor — prevent inhuman request cadence. See src/pacing.ts
|
|
1089
|
+
// for the pure delay calculator (floor + uniform jitter).
|
|
1090
|
+
const pacingDelay = computePacingDelay(Date.now(), lastRequestTime, pacingCfg);
|
|
1091
|
+
if (pacingDelay > 0) {
|
|
1092
|
+
await new Promise(r => setTimeout(r, pacingDelay));
|
|
1084
1093
|
}
|
|
1085
1094
|
lastRequestTime = Date.now();
|
|
1086
1095
|
// Session ID: pool mode uses the per-account identity.sessionId (stable
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.24.0",
|
|
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": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc && cp src/cc-template-data.json dist/ && node -e \"require('fs').mkdirSync('dist/shim',{recursive:true})\" && cp src/shim/runtime.cjs dist/shim/",
|
|
24
|
-
"test": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/tool-schema-contract.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/pool-sticky.mjs && node test/sealed-pool.mjs && node test/live-fingerprint.mjs && node test/shim-runtime.mjs && node test/shim-e2e.mjs && node test/proxy-header-order.mjs && node test/proxy-body-order.mjs && node test/runtime-fingerprint.mjs && node test/drift-detection.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs && node test/scrub-template.mjs",
|
|
24
|
+
"test": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/tool-schema-contract.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/pool-sticky.mjs && node test/sealed-pool.mjs && node test/live-fingerprint.mjs && node test/shim-runtime.mjs && node test/shim-e2e.mjs && node test/proxy-header-order.mjs && node test/proxy-body-order.mjs && node test/runtime-fingerprint.mjs && node test/pacing.mjs && node test/drift-detection.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs && node test/scrub-template.mjs",
|
|
25
25
|
"audit": "npm audit --production --audit-level=high",
|
|
26
26
|
"prepublishOnly": "npm run build",
|
|
27
27
|
"start": "node dist/cli.js",
|