@askalf/dario 6.8.15 → 6.8.17
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 +12 -1
- package/dist/live-fingerprint.d.ts +1 -1
- package/dist/live-fingerprint.js +1 -1
- package/dist/proxy.d.ts +6 -0
- package/dist/proxy.js +11 -8
- package/dist/shutdown-drain.d.ts +43 -0
- package/dist/shutdown-drain.js +48 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -479,6 +479,11 @@ async function proxy() {
|
|
|
479
479
|
// user waits at the cap while everyone else keeps flowing. 0 = off.
|
|
480
480
|
const maxConcurrentPerConsumer = parsePositiveIntFlag('--max-concurrent-per-consumer=')
|
|
481
481
|
?? parsePositiveIntEnv(process.env['DARIO_MAX_CONCURRENT_PER_CONSUMER']);
|
|
482
|
+
// --shutdown-grace=MS — how long SIGTERM waits for in-flight requests to
|
|
483
|
+
// finish before the process exits (default 90000). A container recreate
|
|
484
|
+
// used to sever every streaming response after five seconds. dario#1370.
|
|
485
|
+
const shutdownGraceMs = parsePositiveIntFlag('--shutdown-grace=')
|
|
486
|
+
?? parsePositiveIntEnv(process.env['DARIO_SHUTDOWN_GRACE_MS']);
|
|
482
487
|
// --pool-strategy=headroom|fill-first — where UNBOUND (new) conversations
|
|
483
488
|
// land. `headroom` (default) spreads them to the seat with the most slack;
|
|
484
489
|
// `fill-first` concentrates them on the alphabetically-first eligible seat
|
|
@@ -704,7 +709,7 @@ async function proxy() {
|
|
|
704
709
|
console.error(`[dario] Override (not recommended): pass --unsafe-no-auth if you have out-of-band network controls and accept the risk.`);
|
|
705
710
|
process.exit(1);
|
|
706
711
|
}
|
|
707
|
-
await startProxy({ port, host, verbose, verboseBodies, model, fastModel, noClaudeAuth, analyticsToken, passthrough, preserveTools, hybridTools, mergeTools, noAutoDetect, strictTls, pacingMinMs, pacingJitterMs, thinkTimeBaseMs, thinkTimePerTokenMs, thinkTimeJitterMs, thinkTimeMaxMs, sessionStartMinMs, sessionStartJitterMs, stealth, drainOnClose, sessionIdleRotateMs, sessionRotateJitterMs, sessionMaxAgeMs, sessionPerClient, preserveOrchestrationTags, noLiveCapture, strictTemplate, maxConcurrent, maxQueued, queueTimeoutMs, maxConcurrentPerConsumer, poolStrategy, poolHeadroomFloor, poolSharedState, poolSharedStateIntervalMs, effort, maxTokens, poolFallbackModel, modelAliases, logFile, passthroughBetas, skipFields, systemPrompt, overageGuardEnabled, overageGuardBehavior, overageGuardCooldownMs, overageGuardNotifyOs, honorClientThinking, preserveOutputFormat, midstreamContinue, ledger, keys, keysPath });
|
|
712
|
+
await startProxy({ port, host, verbose, verboseBodies, model, fastModel, noClaudeAuth, analyticsToken, passthrough, preserveTools, hybridTools, mergeTools, noAutoDetect, strictTls, pacingMinMs, pacingJitterMs, thinkTimeBaseMs, thinkTimePerTokenMs, thinkTimeJitterMs, thinkTimeMaxMs, sessionStartMinMs, sessionStartJitterMs, stealth, drainOnClose, sessionIdleRotateMs, sessionRotateJitterMs, sessionMaxAgeMs, sessionPerClient, preserveOrchestrationTags, noLiveCapture, strictTemplate, maxConcurrent, maxQueued, queueTimeoutMs, maxConcurrentPerConsumer, shutdownGraceMs, poolStrategy, poolHeadroomFloor, poolSharedState, poolSharedStateIntervalMs, effort, maxTokens, poolFallbackModel, modelAliases, logFile, passthroughBetas, skipFields, systemPrompt, overageGuardEnabled, overageGuardBehavior, overageGuardCooldownMs, overageGuardNotifyOs, honorClientThinking, preserveOutputFormat, midstreamContinue, ledger, keys, keysPath });
|
|
708
713
|
}
|
|
709
714
|
/**
|
|
710
715
|
* `dario keys` — named keys for a shared dario (v6.8, dario#1318). One
|
|
@@ -2074,6 +2079,12 @@ async function help() {
|
|
|
2074
2079
|
as --strict-tls: make the unsafe state
|
|
2075
2080
|
require intent. Env: DARIO_STRICT_TEMPLATE=1.
|
|
2076
2081
|
(v3.30.8, dario#77)
|
|
2082
|
+
--shutdown-grace=MS How long SIGTERM waits for in-flight requests
|
|
2083
|
+
to finish before exiting (default: 90000).
|
|
2084
|
+
The listener closes at once. Set the
|
|
2085
|
+
container's stop grace above it, or Docker
|
|
2086
|
+
SIGKILLs the drain. Env:
|
|
2087
|
+
DARIO_SHUTDOWN_GRACE_MS. (dario#1370)
|
|
2077
2088
|
--max-concurrent=N Max in-flight requests across the WHOLE proxy,
|
|
2078
2089
|
not per seat (default: 10; a pool defaults to
|
|
2079
2090
|
10 per seat). Past it, requests wait in dario.
|
|
@@ -510,7 +510,7 @@ export declare function detectDrift(t: TemplateData, installedOverride?: string
|
|
|
510
510
|
*/
|
|
511
511
|
export declare const SUPPORTED_CC_RANGE: {
|
|
512
512
|
readonly min: "1.0.0";
|
|
513
|
-
readonly maxTested: "2.1.
|
|
513
|
+
readonly maxTested: "2.1.278";
|
|
514
514
|
};
|
|
515
515
|
/**
|
|
516
516
|
* Compare two dotted-numeric version strings. Returns negative if `a<b`,
|
package/dist/live-fingerprint.js
CHANGED
|
@@ -1211,7 +1211,7 @@ export function detectDrift(t, installedOverride) {
|
|
|
1211
1211
|
*/
|
|
1212
1212
|
export const SUPPORTED_CC_RANGE = {
|
|
1213
1213
|
min: '1.0.0',
|
|
1214
|
-
maxTested: '2.1.
|
|
1214
|
+
maxTested: '2.1.278',
|
|
1215
1215
|
};
|
|
1216
1216
|
/**
|
|
1217
1217
|
* Compare two dotted-numeric version strings. Returns negative if `a<b`,
|
package/dist/proxy.d.ts
CHANGED
|
@@ -395,6 +395,12 @@ interface ProxyOptions {
|
|
|
395
395
|
poolSharedState?: boolean;
|
|
396
396
|
/** How often to pull peers' readings, ms. Default 2000. */
|
|
397
397
|
poolSharedStateIntervalMs?: number;
|
|
398
|
+
/**
|
|
399
|
+
* How long a SIGTERM waits for in-flight requests to finish before the
|
|
400
|
+
* process exits. Default 90s. The listener closes at once, so nothing new
|
|
401
|
+
* is accepted while the wait runs. dario#1370.
|
|
402
|
+
*/
|
|
403
|
+
shutdownGraceMs?: number;
|
|
398
404
|
/** Max concurrent in-flight requests. Default 10. dario#80. */
|
|
399
405
|
maxConcurrent?: number;
|
|
400
406
|
/** Max requests buffered waiting for a concurrency slot. Default 128. dario#80. */
|
package/dist/proxy.js
CHANGED
|
@@ -51,6 +51,7 @@ import { listCodexAccountAliases, loadAllCodexAccounts, codexAccountNeedsRefresh
|
|
|
51
51
|
import { route as routeProvider } from './provider-adapter.js';
|
|
52
52
|
import { selectPoolFallbackModels } from './pool-fallback-tier.js';
|
|
53
53
|
import { RequestQueue, QueueFullError, QueueTimeoutError, DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_QUEUED, DEFAULT_QUEUE_TIMEOUT_MS, resolveMaxConcurrent } from './request-queue.js';
|
|
54
|
+
import { waitForIdle, DEFAULT_SHUTDOWN_GRACE_MS } from './shutdown-drain.js';
|
|
54
55
|
import { redactSecrets } from './redact.js';
|
|
55
56
|
import { BAKED_BASE_MODELS, withLongContextVariants, buildOpenAIModelsList, getModelCatalog, getCachedBases, resolveAliasAgainst, prewarmModelCatalog, retryModelCatalogNow, isSuspendedModel } from './model-catalog.js';
|
|
56
57
|
import { classifyUpstreamRejection, diagnosticSnippet, POOL_PARKED } from './upstream-rejection.js';
|
|
@@ -5761,15 +5762,17 @@ export async function startProxy(opts = {}) {
|
|
|
5761
5762
|
poolSync?.stop();
|
|
5762
5763
|
if (logFileStream)
|
|
5763
5764
|
logFileStream.end();
|
|
5764
|
-
// Flush tokens first (best-effort, bounded), then close the server. The
|
|
5765
|
-
// flush is fire-and-forget under the same 5s force-exit guard below so a
|
|
5766
|
-
// hung fsync can't wedge shutdown.
|
|
5767
5765
|
keyStore?.close();
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
//
|
|
5772
|
-
|
|
5766
|
+
// Stop accepting connections now; responses already streaming keep going.
|
|
5767
|
+
// Then flush tokens (best-effort), drain what is in flight up to the
|
|
5768
|
+
// grace, and exit. The force-exit guard sits past the grace so a stream
|
|
5769
|
+
// that never ends cannot wedge shutdown, and a hung fsync cannot either.
|
|
5770
|
+
server.close();
|
|
5771
|
+
const graceMs = opts.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;
|
|
5772
|
+
void Promise.all([flushPoolTokens(), ledger?.close()])
|
|
5773
|
+
.then(() => waitForIdle(() => queue.snapshot().active, { graceMs }))
|
|
5774
|
+
.finally(() => process.exit(0));
|
|
5775
|
+
setTimeout(() => process.exit(0), graceMs + 5000).unref();
|
|
5773
5776
|
};
|
|
5774
5777
|
process.on('SIGINT', shutdown);
|
|
5775
5778
|
process.on('SIGTERM', shutdown);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shutdown drain (dario#1370).
|
|
3
|
+
*
|
|
4
|
+
* A SIGTERM used to give in-flight requests five seconds and then exit. The
|
|
5
|
+
* fleet's agent runs are one long streamed response each, so a container
|
|
6
|
+
* recreate — every within-minor autodeploy of a bot release — severed every
|
|
7
|
+
* run that was in flight: three dropped executions in one night, each
|
|
8
|
+
* re-armed a quarter of an hour later with its spend gone.
|
|
9
|
+
*
|
|
10
|
+
* `waitForIdle` is the wait `shutdown` now does between closing the listener
|
|
11
|
+
* and exiting: poll the in-flight count until it reaches zero or the grace
|
|
12
|
+
* runs out. It is pure over its inputs — the count, the clock and the sleep
|
|
13
|
+
* are injected — so the policy is tested without a server or real timers.
|
|
14
|
+
*/
|
|
15
|
+
/** How long a SIGTERM waits for in-flight requests by default. */
|
|
16
|
+
export declare const DEFAULT_SHUTDOWN_GRACE_MS = 90000;
|
|
17
|
+
/** How often the drain re-reads the in-flight count. */
|
|
18
|
+
export declare const SHUTDOWN_POLL_MS = 250;
|
|
19
|
+
export interface DrainOptions {
|
|
20
|
+
/** Longest the drain waits before giving up on the remaining requests. */
|
|
21
|
+
graceMs: number;
|
|
22
|
+
/** Poll interval; defaults to SHUTDOWN_POLL_MS. */
|
|
23
|
+
pollMs?: number;
|
|
24
|
+
/** Clock and sleep, injectable for tests. */
|
|
25
|
+
now?: () => number;
|
|
26
|
+
sleep?: (ms: number) => Promise<void>;
|
|
27
|
+
/** Where the drain narrates; defaults to console.log. */
|
|
28
|
+
log?: (line: string) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface DrainResult {
|
|
31
|
+
/** True when the in-flight count reached zero within the grace. */
|
|
32
|
+
drained: boolean;
|
|
33
|
+
/** How long the drain waited. */
|
|
34
|
+
waitedMs: number;
|
|
35
|
+
/** In-flight requests left when the drain returned. */
|
|
36
|
+
remaining: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wait until `getActive()` reports no in-flight request, or `graceMs` has
|
|
40
|
+
* passed. Logs once when it starts waiting and once when it stops, never per
|
|
41
|
+
* poll, so a long drain is two lines rather than a scroll.
|
|
42
|
+
*/
|
|
43
|
+
export declare function waitForIdle(getActive: () => number, opts: DrainOptions): Promise<DrainResult>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shutdown drain (dario#1370).
|
|
3
|
+
*
|
|
4
|
+
* A SIGTERM used to give in-flight requests five seconds and then exit. The
|
|
5
|
+
* fleet's agent runs are one long streamed response each, so a container
|
|
6
|
+
* recreate — every within-minor autodeploy of a bot release — severed every
|
|
7
|
+
* run that was in flight: three dropped executions in one night, each
|
|
8
|
+
* re-armed a quarter of an hour later with its spend gone.
|
|
9
|
+
*
|
|
10
|
+
* `waitForIdle` is the wait `shutdown` now does between closing the listener
|
|
11
|
+
* and exiting: poll the in-flight count until it reaches zero or the grace
|
|
12
|
+
* runs out. It is pure over its inputs — the count, the clock and the sleep
|
|
13
|
+
* are injected — so the policy is tested without a server or real timers.
|
|
14
|
+
*/
|
|
15
|
+
/** How long a SIGTERM waits for in-flight requests by default. */
|
|
16
|
+
export const DEFAULT_SHUTDOWN_GRACE_MS = 90_000;
|
|
17
|
+
/** How often the drain re-reads the in-flight count. */
|
|
18
|
+
export const SHUTDOWN_POLL_MS = 250;
|
|
19
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
/**
|
|
21
|
+
* Wait until `getActive()` reports no in-flight request, or `graceMs` has
|
|
22
|
+
* passed. Logs once when it starts waiting and once when it stops, never per
|
|
23
|
+
* poll, so a long drain is two lines rather than a scroll.
|
|
24
|
+
*/
|
|
25
|
+
export async function waitForIdle(getActive, opts) {
|
|
26
|
+
const pollMs = Math.max(1, opts.pollMs ?? SHUTDOWN_POLL_MS);
|
|
27
|
+
const now = opts.now ?? Date.now;
|
|
28
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
29
|
+
const log = opts.log ?? ((line) => console.log(line));
|
|
30
|
+
let active = getActive();
|
|
31
|
+
if (active <= 0)
|
|
32
|
+
return { drained: true, waitedMs: 0, remaining: 0 };
|
|
33
|
+
const started = now();
|
|
34
|
+
log(`[dario] draining ${active} in-flight request(s) before exit (up to ${Math.round(opts.graceMs / 1000)}s)`);
|
|
35
|
+
for (;;) {
|
|
36
|
+
await sleep(pollMs);
|
|
37
|
+
active = getActive();
|
|
38
|
+
const waitedMs = now() - started;
|
|
39
|
+
if (active <= 0) {
|
|
40
|
+
log(`[dario] drained after ${(waitedMs / 1000).toFixed(1)}s`);
|
|
41
|
+
return { drained: true, waitedMs, remaining: 0 };
|
|
42
|
+
}
|
|
43
|
+
if (waitedMs >= opts.graceMs) {
|
|
44
|
+
log(`[dario] still ${active} in flight after ${Math.round(waitedMs / 1000)}s; exiting`);
|
|
45
|
+
return { drained: false, waitedMs, remaining: active };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.8.
|
|
3
|
+
"version": "6.8.17",
|
|
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": {
|