🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@askalf/dario

Package Overview
Dependencies
Maintainers
1
Versions
384
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@askalf/dario - npm Package Compare versions

Comparing version
5.2.5
to
5.2.6
+26
-0
dist/config-file.d.ts

@@ -83,4 +83,30 @@ /**

};
pool?: {
/**
* `headroom` (default) spreads new conversations to the seat with the
* most headroom; `fill-first` concentrates them on the alphabetically-
* first eligible seat until it drains to the 2% floor, then spills to
* the next — primary/backup semantics, alias order is the knob.
*/
strategy?: 'headroom' | 'fill-first';
};
effort?: string | null;
maxTokens?: number | 'client' | null;
/**
* Pool-exhausted fallback. When `model` is a non-empty string and an
* openai-compat backend is configured, OpenAI-shape requests that the
* Claude pool can't serve are forwarded to that backend as `model`
* (response marked `x-dario-pool-fallback`) instead of surfacing the
* 429/503. Null/absent = off.
*/
poolFallback?: {
model?: string | null;
};
/**
* User-defined model aliases: client-visible name → target model.
* Resolved at request time before provider-prefix parsing, so a target
* may carry a prefix (`"my-fast": "openai:gpt-4o-mini"`). Names are
* matched case-insensitively; one step, never recursive.
*/
modelAliases?: Record<string, string>;
passthroughBetas?: string[];

@@ -87,0 +113,0 @@ systemPrompt?: string | null;

@@ -68,4 +68,7 @@ /**

queue: { maxConcurrent: null, maxQueued: null, timeoutMs: null },
pool: { strategy: 'headroom' },
effort: null,
maxTokens: null,
poolFallback: { model: null },
modelAliases: {},
passthroughBetas: [],

@@ -307,2 +310,14 @@ systemPrompt: null,

}
if (isPlainObject(parsed.pool)) {
out.pool = {};
if (parsed.pool.strategy === 'headroom' || parsed.pool.strategy === 'fill-first') {
out.pool.strategy = parsed.pool.strategy;
}
}
if (isPlainObject(parsed.poolFallback)) {
out.poolFallback = {};
if (parsed.poolFallback.model === null || typeof parsed.poolFallback.model === 'string') {
out.poolFallback.model = parsed.poolFallback.model;
}
}
const effort = pickStringOrNull('effort');

@@ -321,2 +336,15 @@ if (effort !== undefined)

}
if (isPlainObject(parsed.modelAliases)) {
const aliases = {};
for (const [k, v] of Object.entries(parsed.modelAliases)) {
if (typeof v !== 'string')
continue;
const name = k.trim().toLowerCase();
const target = v.trim();
if (!name || !target)
continue;
aliases[name] = target;
}
out.modelAliases = aliases;
}
if (Array.isArray(parsed.passthroughBetas)) {

@@ -323,0 +351,0 @@ out.passthroughBetas = parsed.passthroughBetas

+1
-1

@@ -285,3 +285,3 @@ /**

readonly min: "1.0.0";
readonly maxTested: "2.1.211";
readonly maxTested: "2.1.212";
};

@@ -288,0 +288,0 @@ /**

@@ -809,3 +809,3 @@ /**

min: '1.0.0',
maxTested: '2.1.211',
maxTested: '2.1.212',
};

@@ -812,0 +812,0 @@ /**

@@ -78,2 +78,28 @@ /**

}
/**
* Pool routing strategy.
*
* `headroom` (default) — every selection picks the account with the most
* headroom, spreading new conversations across all seats.
*
* `fill-first` — concentrate new conversations on the lexicographically-
* first eligible account (by alias) until its headroom drops to the 2%
* floor, then spill to the next. Two things headroom spreading can't give
* you: primary/backup semantics (a `z-backup` seat stays untouched until
* `a-main` is actually drained), and cache concentration (every fresh
* conversation lands where the prompt-cache pressure already is, keeping
* the spill seat's windows fully fresh for when they're needed). Alias
* order is the operator's knob — name seats `1-main` / `2-overflow` to
* pick the fill order. Sticky bindings behave identically in both modes;
* strategy only decides where UNBOUND (new) conversations land.
*/
export type PoolStrategy = 'headroom' | 'fill-first';
/**
* Resolve the pool strategy from an explicit value (CLI flag / config file,
* already precedence-merged by the caller) with `DARIO_POOL_STRATEGY` as
* the env fallback. Unrecognized values fall through — a typo behaves like
* the default rather than crashing startup, matching the other resolvers
* in this codebase (see resolveSessionRotationConfig).
*/
export declare function resolvePoolStrategy(explicit?: string | null, env?: NodeJS.ProcessEnv): PoolStrategy;
/** Parse an Anthropic response's rate-limit headers into a snapshot. */

@@ -109,2 +135,3 @@ export declare function parseRateLimits(headers: Headers): RateLimitSnapshot;

export declare class AccountPool {
private readonly strategy;
private accounts;

@@ -117,2 +144,3 @@ private queue;

private lastStickyCleanup;
constructor(strategy?: PoolStrategy);
add(alias: string, opts: {

@@ -119,0 +147,0 @@ accessToken: string;

@@ -58,2 +58,19 @@ /**

/**
* Resolve the pool strategy from an explicit value (CLI flag / config file,
* already precedence-merged by the caller) with `DARIO_POOL_STRATEGY` as
* the env fallback. Unrecognized values fall through — a typo behaves like
* the default rather than crashing startup, matching the other resolvers
* in this codebase (see resolveSessionRotationConfig).
*/
export function resolvePoolStrategy(explicit, env = process.env) {
for (const c of [explicit, env.DARIO_POOL_STRATEGY]) {
if (typeof c !== 'string')
continue;
const s = c.trim().toLowerCase();
if (s === 'headroom' || s === 'fill-first')
return s;
}
return 'headroom';
}
/**
* Match `anthropic-ratelimit-unified-7d_<family>-utilization`. Generic on

@@ -170,3 +187,19 @@ * `<family>` so a future `7d_opus` / `7d_haiku` (or anything Anthropic

}
// Fill-first pick: lexicographically-first eligible account still above the
// headroom floor. Alias order (not insertion order) — accounts load from a
// readdir whose order the OS doesn't guarantee, and the operator can control
// alias names but not readdir. Returns null when every candidate is at/below
// the floor so the caller can fall back to max-headroom.
function pickFillFirst(accounts, family) {
let best = null;
for (const a of accounts) {
if (best !== null && a.alias >= best.alias)
continue;
if (computeHeadroom(a.rateLimit, family) > POOL_HEADROOM_FLOOR)
best = a;
}
return best;
}
export class AccountPool {
strategy;
accounts = new Map();

@@ -180,2 +213,5 @@ queue = [];

lastStickyCleanup = 0;
constructor(strategy = 'headroom') {
this.strategy = strategy;
}
add(alias, opts) {

@@ -263,2 +299,10 @@ const existing = this.accounts.get(alias);

if (eligible.length > 0) {
if (this.strategy === 'fill-first') {
const first = pickFillFirst(eligible, family);
if (first)
return first;
// Every eligible account is at/below the floor — the terminal state
// both strategies share. Fall through to max-headroom so the caller
// still gets the least-drained account instead of null.
}
return pickMaxHeadroom(eligible, family);

@@ -381,2 +425,11 @@ }

if (eligible.length > 0) {
// Fill-first failover keeps the fill order: the next account tried
// after a 429 is the next alias in line, not the max-headroom seat —
// otherwise a single failover would defeat the concentration the
// strategy exists to provide.
if (this.strategy === 'fill-first') {
const first = pickFillFirst(eligible, family);
if (first)
return first;
}
return pickMaxHeadroom(eligible, family);

@@ -383,0 +436,0 @@ }

@@ -41,2 +41,20 @@ import { type IncomingMessage } from 'node:http';

/**
* User-defined model aliases: client-visible name → target model.
*
* Complements the built-in family shorthands above — those track the model
* catalog; these are operator-declared (config `modelAliases`, repeatable
* `--model-alias=name=target`, `DARIO_MODEL_ALIASES=name=target,…`) and
* resolve FIRST at request time, before provider-prefix parsing, so a
* target may carry a prefix (`my-fast` → `openai:gpt-4o-mini`) and
* retarget the backend. One step, never recursive: a target that names
* another alias is forwarded as-is. Alias names match case-insensitively;
* targets forward verbatim. An alias may shadow a real model id or a
* built-in shorthand — deliberate, that's how you downgrade every `opus`
* call from a client whose picker you don't control.
*/
export declare function parseModelAliasSpecs(specs: readonly string[]): Record<string, string>;
/** Resolve `model` through user aliases. Null = no alias applies (also on
* self-mapping, so a misconfigured `opus=opus` can't loop the caller). */
export declare function applyModelAlias(model: string | null | undefined, aliases: Record<string, string> | undefined): string | null;
/**
* Pick the per-request model override under a forced `--model`.

@@ -56,2 +74,9 @@ *

export declare function selectModelOverride(incomingModel: string, modelOverride: string | null, fastModelOverride: string | null): string | null;
/**
* Rebuild an OpenAI-shape request body with the model swapped to the pool-
* fallback target. Null when the body isn't a JSON object — the caller
* surfaces the original error instead of forwarding garbage. Exported for
* tests; the two call sites are the pool-exhausted dispatch paths.
*/
export declare function buildPoolFallbackBody(body: Buffer, fallbackModel: string): Buffer | null;
export declare function parseProviderPrefix(model: string): {

@@ -293,2 +318,12 @@ provider: 'openai' | 'claude';

strictTemplate?: boolean;
/**
* Pool routing strategy. `headroom` (default) spreads new conversations
* to the seat with the most headroom; `fill-first` concentrates them on
* the alphabetically-first eligible seat until it drains to the 2%
* floor, then spills to the next — primary/backup semantics where a
* `z-backup` seat stays untouched until `a-main` is actually drained.
* Sticky bindings behave identically in both modes. Sourced from
* `--pool-strategy` / `DARIO_POOL_STRATEGY` / config `pool.strategy`.
*/
poolStrategy?: string;
/** Max concurrent in-flight requests. Default 10. dario#80. */

@@ -319,2 +354,23 @@ maxConcurrent?: number;

/**
* Pool-exhausted fallback model (strictly opt-in; off when unset/empty).
* When the Claude pool can't serve — selection finds every seat drained
* or cooling, or a mid-flight 429 has no peer left — OpenAI-shape
* requests (/v1/chat/completions) are forwarded to the configured
* openai-compat backend with the model swapped to this value, instead
* of surfacing the 429/503. Responses carry `x-dario-pool-fallback`.
* Anthropic-shape requests keep the error: dario has no OpenAI→Anthropic
* response translation. Inert without a configured backend. Sourced from
* `--pool-fallback` / `DARIO_POOL_FALLBACK` / config `poolFallback.model`.
*/
poolFallbackModel?: string;
/**
* User-defined model aliases, client-visible name (lowercase) → target.
* Resolved per request BEFORE provider-prefix parsing (a target may
* carry a prefix and retarget the backend) and advertised on
* /v1/models. One step, never recursive. See parseModelAliasSpecs.
* Sourced from config `modelAliases` < `DARIO_MODEL_ALIASES` <
* repeatable `--model-alias=name=target`, merged per-key by the CLI.
*/
modelAliases?: Record<string, string>;
/**
* Append-only request log file. One JSON line per completed request,

@@ -321,0 +377,0 @@ * with secrets scrubbed via redactSecrets. Useful for backgrounded

@@ -41,2 +41,3 @@ /**

{ path: 'sessionStart.jitterMs', label: 'Session-start jitter', type: 'number' },
{ path: 'pool.strategy', label: 'Pool strategy', type: 'string', hint: '"headroom" (default) or "fill-first"' },
// ── Overage-guard (v4.1, dario#288) ─────────────────────────

@@ -259,2 +260,3 @@ { path: 'overageGuard.enabled', label: 'Overage-guard', type: 'bool', hint: 'halt proxy on any representative-claim=overage' },

'overageGuard.behavior': ['halt', 'warn'],
'pool.strategy': ['headroom', 'fill-first'],
};

@@ -261,0 +263,0 @@ function doSave(state) {

{
"name": "@askalf/dario",
"version": "5.2.5",
"version": "5.2.6",
"description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",

@@ -5,0 +5,0 @@ "type": "module",

@@ -13,2 +13,3 @@ <div align="center">

<a href="https://scorecard.dev/viewer/?uri=github.com/askalf/dario"><img src="https://img.shields.io/ossf-scorecard/github.com/askalf/dario?label=OpenSSF%20Scorecard&color=6f42c1" alt="OpenSSF Scorecard"></a>
<a href="https://www.bestpractices.dev/projects/13638"><img src="https://www.bestpractices.dev/projects/13638/badge" alt="OpenSSF Best Practices"></a>
<a href="https://github.com/askalf/dario/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@askalf/dario?color=6f42c1" alt="License"></a>

@@ -15,0 +16,0 @@ <a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/dm/@askalf/dario?color=6f42c1" alt="Downloads"></a>

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display