@askalf/dario 6.0.16 → 6.0.18

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.
@@ -496,7 +496,7 @@ export declare function detectDrift(t: TemplateData, installedOverride?: string
496
496
  */
497
497
  export declare const SUPPORTED_CC_RANGE: {
498
498
  readonly min: "1.0.0";
499
- readonly maxTested: "2.1.259";
499
+ readonly maxTested: "2.1.260";
500
500
  };
501
501
  /**
502
502
  * Compare two dotted-numeric version strings. Returns negative if `a<b`,
@@ -1194,7 +1194,7 @@ export function detectDrift(t, installedOverride) {
1194
1194
  */
1195
1195
  export const SUPPORTED_CC_RANGE = {
1196
1196
  min: '1.0.0',
1197
- maxTested: '2.1.259',
1197
+ maxTested: '2.1.260',
1198
1198
  };
1199
1199
  /**
1200
1200
  * Compare two dotted-numeric version strings. Returns negative if `a<b`,
@@ -0,0 +1,6 @@
1
+ /** Backward-compatible selection of a pool-fallback target for one request.
2
+ * A bare value or comma-separated model chain retains the legacy meaning.
3
+ * Tier maps use `tier:model`, e.g. `haiku:gpt-5.4-mini,sonnet:gpt-5.6-terra`.
4
+ * Unknown models use `default`, or the first (normally cheapest) configured tier.
5
+ */
6
+ export declare function selectPoolFallbackModels(spec: string | undefined, requestedModel: string): string[];
@@ -0,0 +1,28 @@
1
+ /** Backward-compatible selection of a pool-fallback target for one request.
2
+ * A bare value or comma-separated model chain retains the legacy meaning.
3
+ * Tier maps use `tier:model`, e.g. `haiku:gpt-5.4-mini,sonnet:gpt-5.6-terra`.
4
+ * Unknown models use `default`, or the first (normally cheapest) configured tier.
5
+ */
6
+ export function selectPoolFallbackModels(spec, requestedModel) {
7
+ const entries = (spec ?? '').trim().split(',').map((entry) => entry.trim()).filter(Boolean);
8
+ if (entries.length === 0)
9
+ return [];
10
+ const mapped = entries.every((entry) => /^(haiku|sonnet|opus|default):/i.test(entry));
11
+ if (!mapped)
12
+ return entries;
13
+ const tiers = new Map();
14
+ for (const entry of entries) {
15
+ const colon = entry.indexOf(':');
16
+ const tier = entry.slice(0, colon).trim().toLowerCase();
17
+ const model = entry.slice(colon + 1).trim();
18
+ if (tier && model)
19
+ tiers.set(tier, model);
20
+ }
21
+ const model = requestedModel.toLowerCase();
22
+ const tier = /haiku|mini|small/.test(model) ? 'haiku'
23
+ : /opus|sol|large/.test(model) ? 'opus'
24
+ : /sonnet|terra|medium/.test(model) ? 'sonnet'
25
+ : 'default';
26
+ const target = tiers.get(tier) ?? tiers.get('default') ?? tiers.values().next().value;
27
+ return target ? [target] : [];
28
+ }
package/dist/proxy.js CHANGED
@@ -24,6 +24,7 @@ import { forwardToCodex, getCodexModelSlugs, peekCodexModelSlugs, pickCodexFallb
24
24
  import { readCompareTarget, teeResponse, runCompare, writeCompareRecord, COMPARE_RESULT_HEADER } from './compare.js';
25
25
  import { listCodexAccountAliases, loadAllCodexAccounts, codexAccountNeedsRefresh, hasAnyCodexAccount, selectCodexAccount, getFreshCodexAccount } from './codex-accounts.js';
26
26
  import { route as routeProvider } from './provider-adapter.js';
27
+ import { selectPoolFallbackModels } from './pool-fallback-tier.js';
27
28
  import { RequestQueue, QueueFullError, QueueTimeoutError, DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_QUEUED, DEFAULT_QUEUE_TIMEOUT_MS } from './request-queue.js';
28
29
  import { redactSecrets } from './redact.js';
29
30
  import { BAKED_BASE_MODELS, withLongContextVariants, buildOpenAIModelsList, getModelCatalog, getCachedBases, resolveAliasAgainst, prewarmModelCatalog, retryModelCatalogNow, isSuspendedModel } from './model-catalog.js';
@@ -1256,9 +1257,19 @@ export async function startProxy(opts = {}) {
1256
1257
  // The value may name a chain — see pickCodexFallback/pickClaudeFallback.
1257
1258
  // `poolFallbackModel` stays the FIRST entry so every pre-6.0 reference and
1258
1259
  // every single-value config keeps its exact previous meaning.
1259
- const poolFallbackModels = ((opts.poolFallbackModel ?? '').trim() || '')
1260
- .split(',').map((m) => m.trim()).filter(Boolean);
1260
+ const poolFallbackSpec = opts.poolFallbackModel;
1261
+ // Tier maps are selected per request; this retains the legacy chain for startup reporting.
1262
+ const poolFallbackModels = selectPoolFallbackModels(poolFallbackSpec, 'default');
1261
1263
  const poolFallbackModel = poolFallbackModels[0] ?? null;
1264
+ const selectPoolFallbackForBody = (body) => {
1265
+ try {
1266
+ const parsed = JSON.parse(body.toString());
1267
+ return selectPoolFallbackModels(poolFallbackSpec, String(parsed.model ?? ""));
1268
+ }
1269
+ catch {
1270
+ return poolFallbackModels;
1271
+ }
1272
+ };
1262
1273
  if (poolFallbackModel) {
1263
1274
  const targets = [];
1264
1275
  if (startupCodexAliases.length > 0)
@@ -1905,15 +1916,16 @@ export async function startProxy(opts = {}) {
1905
1916
  * this by construction, not by remembering to copy six lines correctly.
1906
1917
  */
1907
1918
  const attemptPoolFallbackOn429 = async (req, res, body, isOpenAI, attempted) => {
1908
- if (await tryCodexPoolFallback(req, res, body, poolFallbackModels, isOpenAI ? 'openai' : 'anthropic', 'pool exhausted mid-flight (429, no peer)', attempted)) {
1919
+ if (await tryCodexPoolFallback(req, res, body, selectPoolFallbackForBody(body), isOpenAI ? 'openai' : 'anthropic', 'pool exhausted mid-flight (429, no peer)', attempted)) {
1909
1920
  return true;
1910
1921
  }
1911
- if (isOpenAI && poolFallbackModel && openaiBackend) {
1912
- const fallbackBody = buildPoolFallbackBody(body, poolFallbackModel);
1922
+ const fallbackModel = selectPoolFallbackForBody(body)[0] ?? null;
1923
+ if (isOpenAI && fallbackModel && openaiBackend) {
1924
+ const fallbackBody = buildPoolFallbackBody(body, fallbackModel);
1913
1925
  if (fallbackBody) {
1914
- console.log(`[dario] #${requestCount} pool exhausted mid-flight (429, no peer) → ${openaiBackend.name} as ${poolFallbackModel}`);
1926
+ console.log(`[dario] #${requestCount} pool exhausted mid-flight (429, no peer) → ${openaiBackend.name} as ${fallbackModel}`);
1915
1927
  requestCount++;
1916
- await forwardToOpenAI(req, res, fallbackBody, openaiBackend, corsOrigin, { ...SECURITY_HEADERS, 'x-dario-pool-fallback': poolFallbackModel }, upstreamTimeoutMs, verbose);
1928
+ await forwardToOpenAI(req, res, fallbackBody, openaiBackend, corsOrigin, { ...SECURITY_HEADERS, 'x-dario-pool-fallback': fallbackModel }, upstreamTimeoutMs, verbose);
1917
1929
  return true;
1918
1930
  }
1919
1931
  }
@@ -2755,6 +2767,8 @@ export async function startProxy(opts = {}) {
2755
2767
  try {
2756
2768
  const peek = JSON.parse(body.toString());
2757
2769
  const rawModel = (peek.model || '').toString();
2770
+ const requestPoolFallbackModels = selectPoolFallbackModels(poolFallbackSpec, rawModel);
2771
+ const requestPoolFallbackModel = requestPoolFallbackModels[0] ?? null;
2758
2772
  // Credentials are re-read per request (not cached at startup) because
2759
2773
  // a refresh rotates them on disk; getFreshCodexAccount refreshes when
2760
2774
  // inside the expiry buffer, collapsing concurrent refreshes per alias.
@@ -2781,7 +2795,7 @@ export async function startProxy(opts = {}) {
2781
2795
  hasOpenAIBackend: openaiBackend !== null,
2782
2796
  hasCodexAccount: codexCreds !== null,
2783
2797
  codexModels,
2784
- poolFallbackModel,
2798
+ poolFallbackModel: requestPoolFallbackModel,
2785
2799
  poolSize: pool.size,
2786
2800
  });
2787
2801
  if (rawModel && codexCreds && decision.provider === 'codex') {
@@ -2801,7 +2815,7 @@ export async function startProxy(opts = {}) {
2801
2815
  // request, and the classifier validates the id that would actually
2802
2816
  // be forwarded. The entry's own effort suffix (`claude:opus:high`)
2803
2817
  // comes back with it — see the swap below.
2804
- const claudeTarget = pickClaudeTarget(poolFallbackModels, codexModels, getCachedBases(), (m) => resolveClaudeAlias(applyModelAlias(m, modelAliases) ?? m));
2818
+ const claudeTarget = pickClaudeTarget(requestPoolFallbackModels, codexModels, getCachedBases(), (m) => resolveClaudeAlias(applyModelAlias(m, modelAliases) ?? m));
2805
2819
  const claudeTargetModel = claudeTarget?.model ?? null;
2806
2820
  const canDefer = claudeTarget !== null && pool.size > 0 && !upstreamApiKey;
2807
2821
  // DEV-f66b131c: an account still inside its 429 cool-down is not
@@ -2921,7 +2935,7 @@ export async function startProxy(opts = {}) {
2921
2935
  if (!upstreamApiKey && !poolAccount) {
2922
2936
  attemptedProviders.add('claude');
2923
2937
  }
2924
- if (!upstreamApiKey && !poolAccount && await tryCodexPoolFallback(req, res, body, poolFallbackModels, isOpenAI ? 'openai' : 'anthropic', 'pool exhausted', attemptedProviders)) {
2938
+ if (!upstreamApiKey && !poolAccount && await tryCodexPoolFallback(req, res, body, selectPoolFallbackForBody(body), isOpenAI ? 'openai' : 'anthropic', 'pool exhausted', attemptedProviders)) {
2925
2939
  return;
2926
2940
  }
2927
2941
  // `isOpenAI` is REQUIRED here and was not, before v6.0.0 — the selector's
@@ -2931,15 +2945,16 @@ export async function startProxy(opts = {}) {
2931
2945
  // hand the client an OpenAI-shaped response for a Messages request. This
2932
2946
  // route still has no reverse translation; the codex route above does,
2933
2947
  // which is why it takes both shapes and this one does not.
2934
- if (!upstreamApiKey && !poolAccount && poolFallbackModel && openaiBackend && isOpenAI) {
2935
- const fallbackBody = buildPoolFallbackBody(body, poolFallbackModel);
2948
+ const fallbackModel = selectPoolFallbackForBody(body)[0] ?? null;
2949
+ if (!upstreamApiKey && !poolAccount && fallbackModel && openaiBackend && isOpenAI) {
2950
+ const fallbackBody = buildPoolFallbackBody(body, fallbackModel);
2936
2951
  if (!fallbackBody) {
2937
2952
  writePoolUnavailable();
2938
2953
  return;
2939
2954
  }
2940
- console.log(`[dario] #${requestCount} pool exhausted — /v1/chat/completions → ${openaiBackend.name} as ${poolFallbackModel}`);
2955
+ console.log(`[dario] #${requestCount} pool exhausted — /v1/chat/completions → ${openaiBackend.name} as ${fallbackModel}`);
2941
2956
  requestCount++;
2942
- await forwardToOpenAI(req, res, fallbackBody, openaiBackend, corsOrigin, { ...SECURITY_HEADERS, 'x-dario-pool-fallback': poolFallbackModel }, upstreamTimeoutMs, verbose);
2957
+ await forwardToOpenAI(req, res, fallbackBody, openaiBackend, corsOrigin, { ...SECURITY_HEADERS, 'x-dario-pool-fallback': fallbackModel }, upstreamTimeoutMs, verbose);
2943
2958
  return;
2944
2959
  }
2945
2960
  // Deferred at selection because a fallback was armed, but nothing could
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "6.0.16",
3
+ "version": "6.0.18",
4
4
  "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
5
  "type": "module",
6
6
  "bin": {