@askalf/dario 6.9.0 → 6.9.1
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/pacing.d.ts +29 -0
- package/dist/pacing.js +22 -0
- package/dist/proxy.js +19 -18
- package/docs/wire-fidelity.md +1 -1
- package/package.json +1 -1
package/dist/pacing.d.ts
CHANGED
|
@@ -141,3 +141,32 @@ export declare function resolveSessionStartConfig(explicit?: {
|
|
|
141
141
|
jitterMs?: number;
|
|
142
142
|
stealth?: boolean;
|
|
143
143
|
}, env?: NodeJS.ProcessEnv): SessionStartConfig;
|
|
144
|
+
/**
|
|
145
|
+
* The governor's clocks, one set per seat (v6.9.1).
|
|
146
|
+
*
|
|
147
|
+
* The floors above model ONE account's cadence — that is what the provider
|
|
148
|
+
* observes. Through v6.9.0 the proxy kept a single `lastRequestTime` for the
|
|
149
|
+
* whole process, so a pool paced every seat against every other: a three-seat
|
|
150
|
+
* pool could put at most one request per floor on the wire, and a request to
|
|
151
|
+
* an idle seat waited for a stranger's request on a busy one (dario#1244's
|
|
152
|
+
* family — the pool running behind one seat's limits). Keying the clocks by
|
|
153
|
+
* seat keeps each account's observed rhythm exactly as before while the pool
|
|
154
|
+
* as a whole moves at pool speed. API-key mode has one seat and one clock.
|
|
155
|
+
*/
|
|
156
|
+
export interface SeatPacingState {
|
|
157
|
+
/** When the seat's last request STARTED (feeds the inter-request floor). */
|
|
158
|
+
lastRequestTime: number;
|
|
159
|
+
/** When the seat's last 2xx response COMPLETED (feeds think-time). */
|
|
160
|
+
lastResponseTime: number;
|
|
161
|
+
/** Output tokens of that response (feeds think-time's per-token term). */
|
|
162
|
+
lastResponseTokens: number;
|
|
163
|
+
}
|
|
164
|
+
export declare class PacingRegistry {
|
|
165
|
+
private readonly seats;
|
|
166
|
+
/** The seat's clocks, created at zero on first sight (a fresh seat is never paced). */
|
|
167
|
+
seat(key: string): SeatPacingState;
|
|
168
|
+
/** Stamp a completed 2xx response on the seat, for the next request's think-time. */
|
|
169
|
+
noteResponse(key: string, at: number, outputTokens: number): void;
|
|
170
|
+
/** Seats the registry has seen, for tests and status. */
|
|
171
|
+
keys(): string[];
|
|
172
|
+
}
|
package/dist/pacing.js
CHANGED
|
@@ -140,3 +140,25 @@ export function resolveSessionStartConfig(explicit = {}, env = process.env) {
|
|
|
140
140
|
const jitter = pickNonNegativeInt(explicit.jitterMs, env.DARIO_SESSION_START_JITTER_MS) ?? (stealth ? 3000 : 0);
|
|
141
141
|
return { minMs: min, jitterMs: jitter };
|
|
142
142
|
}
|
|
143
|
+
export class PacingRegistry {
|
|
144
|
+
seats = new Map();
|
|
145
|
+
/** The seat's clocks, created at zero on first sight (a fresh seat is never paced). */
|
|
146
|
+
seat(key) {
|
|
147
|
+
let s = this.seats.get(key);
|
|
148
|
+
if (!s) {
|
|
149
|
+
s = { lastRequestTime: 0, lastResponseTime: 0, lastResponseTokens: 0 };
|
|
150
|
+
this.seats.set(key, s);
|
|
151
|
+
}
|
|
152
|
+
return s;
|
|
153
|
+
}
|
|
154
|
+
/** Stamp a completed 2xx response on the seat, for the next request's think-time. */
|
|
155
|
+
noteResponse(key, at, outputTokens) {
|
|
156
|
+
const s = this.seat(key);
|
|
157
|
+
s.lastResponseTime = at;
|
|
158
|
+
s.lastResponseTokens = outputTokens;
|
|
159
|
+
}
|
|
160
|
+
/** Seats the registry has seen, for tests and status. */
|
|
161
|
+
keys() {
|
|
162
|
+
return [...this.seats.keys()];
|
|
163
|
+
}
|
|
164
|
+
}
|
package/dist/proxy.js
CHANGED
|
@@ -1896,15 +1896,13 @@ export async function startProxy(opts = {}) {
|
|
|
1896
1896
|
// 500ms floor keeps the default behavior identical to v3.23; `--pace-min`
|
|
1897
1897
|
// and `--pace-jitter` let callers tune the distribution. Pure calc lives
|
|
1898
1898
|
// in src/pacing.ts so the edge cases are unit-tested without timers.
|
|
1899
|
-
const { computePacingDelay, resolvePacingConfig, computeThinkTimeDelay, resolveThinkTimeConfig, computeSessionStartDelay, resolveSessionStartConfig, } = await import('./pacing.js');
|
|
1900
|
-
|
|
1901
|
-
//
|
|
1902
|
-
// how many output tokens
|
|
1903
|
-
//
|
|
1904
|
-
//
|
|
1905
|
-
|
|
1906
|
-
let lastResponseTime = 0;
|
|
1907
|
-
let lastResponseTokens = 0;
|
|
1899
|
+
const { computePacingDelay, resolvePacingConfig, computeThinkTimeDelay, resolveThinkTimeConfig, computeSessionStartDelay, resolveSessionStartConfig, PacingRegistry, } = await import('./pacing.js');
|
|
1900
|
+
// The governor's clocks, PER SEAT (v6.9.1, src/pacing.ts PacingRegistry):
|
|
1901
|
+
// when a seat's last request started (the inter-request floor) and when its
|
|
1902
|
+
// last 2xx response completed with how many output tokens (think-time).
|
|
1903
|
+
// One clock for the whole proxy used to pace every seat against every
|
|
1904
|
+
// other, so a pool moved at one seat's speed. API-key mode is one seat.
|
|
1905
|
+
const pacingClocks = new PacingRegistry();
|
|
1908
1906
|
// --stealth toggles the behavioral-stealth preset across all three
|
|
1909
1907
|
// pacing layers (pace, think-time, session-start). When on, each
|
|
1910
1908
|
// resolver's zero-default flips to its stealth preset; explicit flags
|
|
@@ -1932,7 +1930,7 @@ export async function startProxy(opts = {}) {
|
|
|
1932
1930
|
if (verbose) {
|
|
1933
1931
|
if (stealth)
|
|
1934
1932
|
console.log('[dario] stealth: behavioral-stealth preset active (pace+think+session-start defaults non-zero)');
|
|
1935
|
-
console.log(`[dario] pacing: min=${pacingCfg.minGapMs}ms jitter=${pacingCfg.jitterMs}ms`);
|
|
1933
|
+
console.log(`[dario] pacing: min=${pacingCfg.minGapMs}ms jitter=${pacingCfg.jitterMs}ms (per seat)`);
|
|
1936
1934
|
if (thinkTimeEnabled) {
|
|
1937
1935
|
console.log(`[dario] think-time: base=${thinkTimeCfg.baseMs}ms perToken=${thinkTimeCfg.perTokenMs}ms jitter=${thinkTimeCfg.jitterMs}ms max=${thinkTimeCfg.maxMs}ms`);
|
|
1938
1936
|
}
|
|
@@ -4506,12 +4504,17 @@ export async function startProxy(opts = {}) {
|
|
|
4506
4504
|
// Opt-in via --session-start-* flags.
|
|
4507
4505
|
// We take the max because each layer enforces an independent floor
|
|
4508
4506
|
// — waiting longer satisfies all of them, so we never need to sum.
|
|
4507
|
+
// The clocks are the selected seat's. A mid-flight failover to a peer
|
|
4508
|
+
// (below) does not re-pace on the peer: the retry is the rare path, and
|
|
4509
|
+
// holding it for a floor would stack a second wait on a request that
|
|
4510
|
+
// has already been refused once.
|
|
4511
|
+
const seatClock = pacingClocks.seat(poolAccount?.alias ?? ACCOUNT_KEY_APIKEY);
|
|
4509
4512
|
const nowForPacing = Date.now();
|
|
4510
|
-
const pacingDelay = computePacingDelay(nowForPacing, lastRequestTime, pacingCfg);
|
|
4513
|
+
const pacingDelay = computePacingDelay(nowForPacing, seatClock.lastRequestTime, pacingCfg);
|
|
4511
4514
|
const thinkDelay = thinkTimeEnabled
|
|
4512
|
-
? computeThinkTimeDelay(nowForPacing, lastResponseTime, lastResponseTokens, thinkTimeCfg)
|
|
4515
|
+
? computeThinkTimeDelay(nowForPacing, seatClock.lastResponseTime, seatClock.lastResponseTokens, thinkTimeCfg)
|
|
4513
4516
|
: 0;
|
|
4514
|
-
const sessionStartDelay = (sessionStartEnabled && lastResponseTime === 0 && lastRequestTime === 0)
|
|
4517
|
+
const sessionStartDelay = (sessionStartEnabled && seatClock.lastResponseTime === 0 && seatClock.lastRequestTime === 0)
|
|
4515
4518
|
? computeSessionStartDelay(sessionStartCfg)
|
|
4516
4519
|
: 0;
|
|
4517
4520
|
const totalDelay = Math.max(pacingDelay, thinkDelay, sessionStartDelay);
|
|
@@ -4519,7 +4522,7 @@ export async function startProxy(opts = {}) {
|
|
|
4519
4522
|
pacingMs += totalDelay;
|
|
4520
4523
|
await new Promise(r => setTimeout(r, totalDelay));
|
|
4521
4524
|
}
|
|
4522
|
-
lastRequestTime = Date.now();
|
|
4525
|
+
seatClock.lastRequestTime = Date.now();
|
|
4523
4526
|
// Session ID: resolved through the rotation registry keyed by the selected
|
|
4524
4527
|
// account (src/session-rotation.ts), applying the configured idle / jitter
|
|
4525
4528
|
// / max-age / per-client policy. The template-build path resolves it
|
|
@@ -5401,8 +5404,7 @@ export async function startProxy(opts = {}) {
|
|
|
5401
5404
|
// would read, and using their (often zero) output_tokens would
|
|
5402
5405
|
// pin think time to baseMs+jitter on the next request needlessly.
|
|
5403
5406
|
if (upstream.status >= 200 && upstream.status < 300) {
|
|
5404
|
-
|
|
5405
|
-
lastResponseTokens = streamOutputTokens;
|
|
5407
|
+
pacingClocks.noteResponse(poolAccount?.alias ?? ACCOUNT_KEY_APIKEY, Date.now(), streamOutputTokens);
|
|
5406
5408
|
}
|
|
5407
5409
|
{
|
|
5408
5410
|
const rl = poolAccount?.rateLimit ?? parseRateLimits(upstream.headers);
|
|
@@ -5472,8 +5474,7 @@ export async function startProxy(opts = {}) {
|
|
|
5472
5474
|
// tokens when the body wasn't JSON or had no usage block — base +
|
|
5473
5475
|
// jitter still apply but the per-token component is 0.
|
|
5474
5476
|
if (upstream.status >= 200 && upstream.status < 300) {
|
|
5475
|
-
|
|
5476
|
-
lastResponseTokens = bufferedUsage?.outputTokens ?? 0;
|
|
5477
|
+
pacingClocks.noteResponse(poolAccount?.alias ?? ACCOUNT_KEY_APIKEY, Date.now(), bufferedUsage?.outputTokens ?? 0);
|
|
5477
5478
|
}
|
|
5478
5479
|
if (bufferedUsage) {
|
|
5479
5480
|
try {
|
package/docs/wire-fidelity.md
CHANGED
|
@@ -6,7 +6,7 @@ Between v3.22 and v3.28, dario's Claude backend closed six axes along which a pr
|
|
|
6
6
|
|---|---|---|---|
|
|
7
7
|
| **Request body key order** | v3.22 | Top-level JSON key order of the outbound `/v1/messages` body is captured from CC's wire serialization and replayed byte-for-byte. Schema bumped v2 → v3; stale caches quarantined. | Automatic once a live capture exists. The baked fallback carries a v2.1.112 snapshot. |
|
|
8
8
|
| **Runtime / TLS ClientHello** | v3.23 | Classifies the runtime as `bun-match` / `bun-ja3-unverified` / `bun-bypassed` / `node-only` and surfaces the class + hint in `dario doctor`. Bun yields the BoringSSL ClientHello CC presents; Node yields OpenSSL's (distinct JA3). Being on Bun is necessary but not sufficient — only Bun ≥ v1.3.14 is measured to reproduce CC's JA3, so an older Bun is flagged `bun-ja3-unverified` rather than green (#813). | `--strict-tls` (or `DARIO_STRICT_TLS=1`) refuses to start proxy mode unless `bun-match`. `DARIO_QUIET_TLS=1` silences the startup banner in known-fine environments. |
|
|
9
|
-
| **Inter-request timing** | v3.24 | Replaces the hardcoded 500 ms floor with a configurable floor + uniform jitter. A fixed 500 ms minimum-inter-arrival is an observable edge at scale; jitter dissolves the edge. | `--pace-min=MS`, `--pace-jitter=MS`, or `DARIO_PACE_MIN_MS` / `DARIO_PACE_JITTER_MS`. Legacy `DARIO_MIN_INTERVAL_MS` still honored. |
|
|
9
|
+
| **Inter-request timing** | v3.24 | Replaces the hardcoded 500 ms floor with a configurable floor + uniform jitter. A fixed 500 ms minimum-inter-arrival is an observable edge at scale; jitter dissolves the edge. Since 6.9.1 the floor (and think-time / session-start) is kept **per seat**: the cadence the provider sees per account is unchanged, and a pool no longer paces its seats against each other. | `--pace-min=MS`, `--pace-jitter=MS`, or `DARIO_PACE_MIN_MS` / `DARIO_PACE_JITTER_MS`. Legacy `DARIO_MIN_INTERVAL_MS` still honored. |
|
|
10
10
|
| **Stream-consumption shape** | v3.25 | When a downstream client disconnects mid-stream, CC keeps reading SSE to EOF. Dario now offers the same: drain upstream to completion even when the consumer has left. Default off — don't silently burn tokens. | `--drain-on-close` / `DARIO_DRAIN_ON_CLOSE=1`. Bounded by the existing 5-minute upstream timeout. |
|
|
11
11
|
| **Session-ID lifecycle** | v3.28 | Generalizes the v3.19 hardcoded 15-minute idle rotation into a tunable `SessionRegistry` with jitter, max-age, and per-client bucketing. Fixes a v3.27 body/header rotation race as a side effect. | `--session-idle-rotate=MS` (default 900000), `--session-rotate-jitter=MS`, `--session-max-age=MS`, `--session-per-client`. Env mirrors `DARIO_SESSION_*`. Defaults are bit-identical to v3.27. |
|
|
12
12
|
| **MCP / sub-agent reach** | v3.26 + v3.27 | Not a wire axis — a *surface* axis. CC-aware tools can now address dario directly (sub-agent from inside CC, MCP server for any MCP client), so operators don't have to switch terminals to introspect the proxy. Read-only by design. | `dario subagent install` / `dario mcp`. See [`mcp-server.md`](./mcp-server.md) and [`sub-agent.md`](./sub-agent.md). |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.9.
|
|
3
|
+
"version": "6.9.1",
|
|
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": {
|