@askalf/dario 6.0.4 → 6.0.6
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/README.md +11 -7
- package/dist/cc-template.d.ts +3 -7
- package/dist/cc-template.js +9 -22
- package/dist/claude-model.d.ts +23 -6
- package/dist/claude-model.js +56 -17
- package/dist/codex-backend.d.ts +13 -1
- package/dist/codex-backend.js +18 -4
- package/dist/effort.d.ts +24 -0
- package/dist/effort.js +39 -0
- package/dist/proxy.js +16 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# `dario`
|
|
4
4
|
|
|
5
|
-
### Your Claude
|
|
5
|
+
### Your Claude and ChatGPT subscriptions each work in exactly one place.<br/>dario makes them work **everywhere** — at subscription pricing, not per-token API bills.
|
|
6
6
|
|
|
7
7
|
<p>
|
|
8
8
|
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/v/@askalf/dario?color=6f42c1&label=npm&logo=npm" alt="npm version"></a>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<a href="https://x.com/ask_alf"><img src="https://img.shields.io/badge/follow-@ask__alf-1da1f2?style=flat-square" alt="Follow on X"></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
<p><strong>One local endpoint. Every AI tool you own. The
|
|
19
|
+
<p><strong>One local endpoint. Every AI tool you own. The subscriptions you already pay for.</strong></p>
|
|
20
20
|
|
|
21
21
|
<sub><code>npm i -g @askalf/dario</code> · <strong>0</strong> runtime deps · <a href="https://www.npmjs.com/package/@askalf/dario">SLSA-attested</a> every release · nothing phones home · ~29k lines you can read in a weekend · independent, unofficial, third-party (<a href="DISCLAIMER.md">DISCLAIMER.md</a>)</sub>
|
|
22
22
|
|
|
@@ -26,14 +26,17 @@
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
> ## 🎉 dario `
|
|
29
|
+
> ## 🎉 dario `v6.0` — any client shape, any subscription, and failover between them
|
|
30
30
|
>
|
|
31
|
-
> v5
|
|
31
|
+
> Through v5, dario was a Claude proxy that had recently learned to reach a ChatGPT subscription on one path. v6 finishes that: **either subscription can serve either wire shape, and either one can cover for the other.**
|
|
32
32
|
>
|
|
33
|
-
> -
|
|
34
|
-
> -
|
|
33
|
+
> - **🔀 Both wire shapes, both plans.** Your ChatGPT plan now answers `/v1/messages`, not just `/v1/chat/completions` — so Claude Code, the Anthropic SDKs and agent runtimes can be served by it without knowing. Your Claude plan already answered both.
|
|
34
|
+
> - **🪂 Failover between subscriptions.** `--pool-fallback=gpt-5.6-sol,claude-sonnet-5` is a *chain*: a drained Claude pool is served by ChatGPT, and a rate-limited ChatGPT is handed back to Claude. Two consumer plans, no API keys, and neither one going down takes you with it. → [Failover](#failover-between-subscriptions)
|
|
35
|
+
> - **🧪 Shadow compare.** `x-dario-compare: <model>` answers you normally *and* runs the same prompt past the other family, writing both to `~/.dario/compare/`. Which model is better at **your** work, measured on your own traffic. → [Shadow compare](#shadow-compare)
|
|
36
|
+
> - **➕ `dario add altman` / `dario add amodei`.** Attach a plan by whose it is.
|
|
37
|
+
> - **🩺 `dario doctor` reports failover readiness** — including *armed but INERT*, the state that is green on every other check and cannot actually do anything.
|
|
35
38
|
>
|
|
36
|
-
> **Upgrading from
|
|
39
|
+
> **Upgrading from v5?** Nothing to do — every v6 feature is opt-in and a single-value `--pool-fallback` behaves exactly as it did. [CHANGELOG](CHANGELOG.md#600---2026-08-30)
|
|
37
40
|
|
|
38
41
|
---
|
|
39
42
|
|
|
@@ -119,6 +122,7 @@ You point every tool at one URL. dario reads each request, decides which backend
|
|
|
119
122
|
| Client speaks | Model | Routes to | What happens |
|
|
120
123
|
|---|---|---|---|
|
|
121
124
|
| Anthropic Messages | `claude-*` / `opus` / `sonnet` / `haiku` | Claude backend | OAuth swap + CC template → `api.anthropic.com` |
|
|
125
|
+
| Anthropic Messages | a slug your ChatGPT account lists | Codex backend | Messages→Responses translation, subscription auth |
|
|
122
126
|
| Anthropic Messages | `gpt-*`, `llama-*`, … | OpenAI-compat backend | Anthropic→OpenAI translation, forwarded |
|
|
123
127
|
| OpenAI Chat | `gpt-*` / `o1-*` / `o3-*` | OpenAI-compat backend | Auth swap, body forwarded byte-for-byte |
|
|
124
128
|
| OpenAI Chat | a slug your ChatGPT account lists | Codex backend | chat/completions→Responses translation, subscription auth |
|
package/dist/cc-template.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* action required. See src/live-fingerprint.ts for the capture pipeline.
|
|
9
9
|
*/
|
|
10
10
|
import { TemplateData, PLATFORM_ONLY_TOOLS, INTERACTIVE_ONLY_TOOLS, CONFIG_SCOPED_TOOLS } from './live-fingerprint.js';
|
|
11
|
+
import { VALID_EFFORT_VALUES, parseEffortSuffix, type EffortValue } from './effort.js';
|
|
11
12
|
export { PLATFORM_ONLY_TOOLS, INTERACTIVE_ONLY_TOOLS, CONFIG_SCOPED_TOOLS };
|
|
12
13
|
/** The loaded template itself — source, version, capture age, all fields. Startup banners and drift checks read this directly. */
|
|
13
14
|
export declare const CC_TEMPLATE: TemplateData;
|
|
@@ -364,13 +365,8 @@ export declare const DEFAULT_MAX_TOKENS = 64000;
|
|
|
364
365
|
* Sonnet; pinning to 32k silently truncated its output capacity).
|
|
365
366
|
*/
|
|
366
367
|
export declare function resolveMaxTokens(flag: number | 'client' | undefined, clientBody: Record<string, unknown>): number;
|
|
367
|
-
|
|
368
|
-
export type EffortValue
|
|
369
|
-
export declare const VALID_EFFORT_VALUES: ReadonlyArray<EffortValue>;
|
|
370
|
-
export declare function parseEffortSuffix(model: string): {
|
|
371
|
-
model: string;
|
|
372
|
-
effort?: EffortValue;
|
|
373
|
-
};
|
|
368
|
+
export { VALID_EFFORT_VALUES, parseEffortSuffix };
|
|
369
|
+
export type { EffortValue };
|
|
374
370
|
/**
|
|
375
371
|
* Resolve the outbound `output_config.effort` value.
|
|
376
372
|
*
|
package/dist/cc-template.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* action required. See src/live-fingerprint.ts for the capture pipeline.
|
|
9
9
|
*/
|
|
10
10
|
import { loadTemplate, promptVariantsOf, VARIANT_FAMILIES, PLATFORM_ONLY_TOOLS, INTERACTIVE_ONLY_TOOLS, CONFIG_SCOPED_TOOLS, } from './live-fingerprint.js';
|
|
11
|
+
import { VALID_EFFORT_VALUES, parseEffortSuffix } from './effort.js';
|
|
11
12
|
// Re-exported so existing importers (scripts/capture-and-bake.mjs, the template
|
|
12
13
|
// invariant tests) keep their import site. The definitions live in
|
|
13
14
|
// live-fingerprint.ts because loadTemplate must apply the same superset rule to
|
|
@@ -1183,28 +1184,14 @@ export function resolveMaxTokens(flag, clientBody) {
|
|
|
1183
1184
|
}
|
|
1184
1185
|
return flag;
|
|
1185
1186
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
* suffix removed plus the parsed effort (undefined when none). Exported for tests.
|
|
1195
|
-
*/
|
|
1196
|
-
const SUFFIX_EFFORTS = ['ultracode', 'medium', 'xhigh', 'high', 'low', 'max'];
|
|
1197
|
-
export function parseEffortSuffix(model) {
|
|
1198
|
-
for (const e of SUFFIX_EFFORTS) {
|
|
1199
|
-
for (const sep of [':', '-']) {
|
|
1200
|
-
const tag = sep + e;
|
|
1201
|
-
if (model.length > tag.length && model.endsWith(tag)) {
|
|
1202
|
-
return { model: model.slice(0, -tag.length), effort: e };
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
return { model };
|
|
1207
|
-
}
|
|
1187
|
+
// The effort vocabulary and the model-name effort suffix live in effort.ts so
|
|
1188
|
+
// the pool-fallback classifier can parse the SAME suffix the request path
|
|
1189
|
+
// parses without importing this module — claude-model.ts stays decidable over
|
|
1190
|
+
// a base set alone, and hand-copying the suffix list there is what would let
|
|
1191
|
+
// `claude:opus:high` drift from `opus:high` (dario#1156 review). Re-exported
|
|
1192
|
+
// here because proxy.ts, cli.ts and test/effort-flag.mjs import them from
|
|
1193
|
+
// cc-template.
|
|
1194
|
+
export { VALID_EFFORT_VALUES, parseEffortSuffix };
|
|
1208
1195
|
/**
|
|
1209
1196
|
* Normalize an effort value to a wire-valid `output_config.effort`. The
|
|
1210
1197
|
* Messages API accepts only low|medium|high|xhigh|max. CC's `ultracode` is a
|
package/dist/claude-model.d.ts
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
import { type EffortValue } from './effort.js';
|
|
1
2
|
/** Maps a bare name to a canonical id, or returns it unchanged. */
|
|
2
3
|
export type ModelResolver = (model: string) => string;
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* What the pool would serve a chain entry as: the canonical id, plus the effort
|
|
6
|
+
* the entry ASKED FOR when it carried a suffix (`claude:opus:high`). Both
|
|
7
|
+
* halves travel together because the caller needs both — the id goes into the
|
|
8
|
+
* body, the effort into the outbound `output_config` — and dropping the second
|
|
9
|
+
* is how `claude:opus:high` reached Anthropic at the pool default (dario#1161).
|
|
10
|
+
*/
|
|
11
|
+
export type ClaudeTarget = {
|
|
12
|
+
model: string;
|
|
13
|
+
effort?: EffortValue;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The id the Claude pool would serve `model` as AND the effort the entry asked
|
|
17
|
+
* for, or null when the pool cannot serve it. `bases` is the catalog base set —
|
|
18
|
+
* `getCachedBases()` at a call site, a fixture in a test. `resolve` is the alias
|
|
19
|
+
* pipeline; the default knows only catalog family shorthands, so pass the
|
|
20
|
+
* proxy's full resolver (operator aliases + pinned aliases) where one exists.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveClaudeTarget(model: string, bases?: readonly string[], resolve?: ModelResolver): ClaudeTarget | null;
|
|
23
|
+
/**
|
|
24
|
+
* The canonical id alone, for callers that only need to name the model. See
|
|
25
|
+
* resolveClaudeTarget for the effort half.
|
|
9
26
|
*/
|
|
10
27
|
export declare function resolveClaudeServable(model: string, bases?: readonly string[], resolve?: ModelResolver): string | null;
|
|
11
|
-
/** Whether the Claude pool can serve `model`. See
|
|
28
|
+
/** Whether the Claude pool can serve `model`. See resolveClaudeTarget. */
|
|
12
29
|
export declare function isClaudeServableModel(model: string, bases?: readonly string[], resolve?: ModelResolver): boolean;
|
package/dist/claude-model.js
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
* supplies that resolver so classification and forwarding cannot
|
|
22
22
|
* disagree. The prefix rule applies to the alias TARGET too, since an
|
|
23
23
|
* operator alias is written the way a request model is written — see
|
|
24
|
-
* stripClaudePrefix.
|
|
24
|
+
* stripClaudePrefix. So does the effort suffix (`:high`, `-high`) — see
|
|
25
|
+
* servableTarget.
|
|
25
26
|
* 2. VALIDATE the resolved id against the catalog base set. A `claude-`
|
|
26
27
|
* prefix on its own proves nothing — `claude-sonnet-6` has the prefix and
|
|
27
28
|
* does not exist — so the id (sans `[1m]`) must be a base the pool can
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
* cold catalog must still admit real ids, and must still refuse typos.
|
|
38
39
|
*/
|
|
39
40
|
import { BAKED_BASE_MODELS, resolveAliasAgainst } from './model-catalog.js';
|
|
41
|
+
import { parseEffortSuffix } from './effort.js';
|
|
40
42
|
/** Provider prefixes that force the Claude path (mirrors proxy.ts's PROVIDER_PREFIXES). */
|
|
41
43
|
const CLAUDE_PREFIXES = new Set(['claude', 'anthropic']);
|
|
42
44
|
/**
|
|
@@ -57,13 +59,29 @@ function stripClaudePrefix(model) {
|
|
|
57
59
|
return model.slice(idx + 1) || null;
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* One spelling of the target: strip its prefix, run the catalog pass, keep it
|
|
63
|
+
* only if the result is a base the pool can forward.
|
|
64
|
+
*
|
|
65
|
+
* A target that arrived prefixed still holds a shorthand (`claude:opus` →
|
|
66
|
+
* `opus`), so the catalog pass runs on it. Idempotent for a target already
|
|
67
|
+
* canonical: resolveAliasAgainst only answers for family shorthands.
|
|
65
68
|
*/
|
|
66
|
-
|
|
69
|
+
function servableTarget(target, bases) {
|
|
70
|
+
const stripped = stripClaudePrefix(target);
|
|
71
|
+
if (stripped === null)
|
|
72
|
+
return null;
|
|
73
|
+
const resolved = resolveAliasAgainst(stripped, bases) ?? stripped;
|
|
74
|
+
const base = resolved.endsWith('[1m]') ? resolved.slice(0, -4) : resolved;
|
|
75
|
+
return bases.some((b) => b.toLowerCase() === base) ? resolved : null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The id the Claude pool would serve `model` as AND the effort the entry asked
|
|
79
|
+
* for, or null when the pool cannot serve it. `bases` is the catalog base set —
|
|
80
|
+
* `getCachedBases()` at a call site, a fixture in a test. `resolve` is the alias
|
|
81
|
+
* pipeline; the default knows only catalog family shorthands, so pass the
|
|
82
|
+
* proxy's full resolver (operator aliases + pinned aliases) where one exists.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveClaudeTarget(model, bases = BAKED_BASE_MODELS, resolve) {
|
|
67
85
|
const entry = stripClaudePrefix(model.trim().toLowerCase());
|
|
68
86
|
if (entry === null)
|
|
69
87
|
return null;
|
|
@@ -73,17 +91,38 @@ export function resolveClaudeServable(model, bases = BAKED_BASE_MODELS, resolve)
|
|
|
73
91
|
// parses that prefix AFTER alias resolution; without the same pass here the
|
|
74
92
|
// target reached the base check as the literal `claude:opus`, matched
|
|
75
93
|
// nothing, and a perfectly valid fallback was skipped.
|
|
76
|
-
const target =
|
|
77
|
-
|
|
94
|
+
const target = (resolve ? resolve(entry) : (resolveAliasAgainst(entry, bases) ?? entry))
|
|
95
|
+
.trim().toLowerCase();
|
|
96
|
+
const direct = servableTarget(target, bases);
|
|
97
|
+
if (direct)
|
|
98
|
+
return { model: direct };
|
|
99
|
+
// …and it may carry an effort suffix as well (`claude:opus:high`,
|
|
100
|
+
// `claude-opus-4-8-high`), which the request path strips on the Claude side
|
|
101
|
+
// exactly like this (dario#419, proxy.ts). Without the same pass the suffix
|
|
102
|
+
// survived into the base check, matched nothing, and the entry was silently
|
|
103
|
+
// unservable — the prefix fix in #1156 left this half unresolved. Tried only
|
|
104
|
+
// AFTER the name as written fails, so a real id that happens to end in an
|
|
105
|
+
// effort word keeps priority over the suffix reading.
|
|
106
|
+
//
|
|
107
|
+
// The effort is RETURNED, not discarded: an operator who writes `:high` on a
|
|
108
|
+
// chain entry is choosing the effort the failover request runs at, and the
|
|
109
|
+
// request path honours exactly that spelling for a client-sent model. Serving
|
|
110
|
+
// the failover at the pool default instead would silently answer a `high`
|
|
111
|
+
// request at whatever `--effort` happens to be (dario#1161).
|
|
112
|
+
const eff = parseEffortSuffix(target);
|
|
113
|
+
if (!eff.effort)
|
|
78
114
|
return null;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
const stripped = servableTarget(eff.model, bases);
|
|
116
|
+
return stripped ? { model: stripped, effort: eff.effort } : null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The canonical id alone, for callers that only need to name the model. See
|
|
120
|
+
* resolveClaudeTarget for the effort half.
|
|
121
|
+
*/
|
|
122
|
+
export function resolveClaudeServable(model, bases = BAKED_BASE_MODELS, resolve) {
|
|
123
|
+
return resolveClaudeTarget(model, bases, resolve)?.model ?? null;
|
|
85
124
|
}
|
|
86
|
-
/** Whether the Claude pool can serve `model`. See
|
|
125
|
+
/** Whether the Claude pool can serve `model`. See resolveClaudeTarget. */
|
|
87
126
|
export function isClaudeServableModel(model, bases = BAKED_BASE_MODELS, resolve) {
|
|
88
|
-
return
|
|
127
|
+
return resolveClaudeTarget(model, bases, resolve) !== null;
|
|
89
128
|
}
|
package/dist/codex-backend.d.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
25
25
|
import type { CodexAccountCredentials } from './codex-accounts.js';
|
|
26
|
-
import { type ModelResolver } from './claude-model.js';
|
|
26
|
+
import { type ModelResolver, type ClaudeTarget } from './claude-model.js';
|
|
27
27
|
export declare const CODEX_BACKEND_BASE_URL: string;
|
|
28
28
|
/**
|
|
29
29
|
* Client version sent on the model-discovery call. The backend REQUIRES the
|
|
@@ -87,6 +87,18 @@ export declare function isCodexModel(model: string, slugs: readonly string[]): b
|
|
|
87
87
|
*/
|
|
88
88
|
export declare function pickCodexFallback(models: readonly string[], slugs: readonly string[]): string | null;
|
|
89
89
|
export declare function pickClaudeFallback(models: readonly string[], slugs: readonly string[], bases?: readonly string[], resolve?: ModelResolver): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* {@link pickClaudeFallback} with the effort the winning entry asked for.
|
|
92
|
+
*
|
|
93
|
+
* `claude:opus:high` names BOTH a model and an effort, and the request path
|
|
94
|
+
* honours exactly that spelling for a client-sent model. Selection kept only
|
|
95
|
+
* the model half, so a declined codex request configured that way reached the
|
|
96
|
+
* Claude pool at the pool default effort — the operator's `high` silently
|
|
97
|
+
* dropped (dario#1161). The caller applies `effort` when it rebuilds the
|
|
98
|
+
* Claude-bound request; `undefined` means the entry named no effort and the
|
|
99
|
+
* pool default stands.
|
|
100
|
+
*/
|
|
101
|
+
export declare function pickClaudeTarget(models: readonly string[], slugs: readonly string[], bases?: readonly string[], resolve?: ModelResolver): ClaudeTarget | null;
|
|
90
102
|
/**
|
|
91
103
|
* Pull `chatgpt_account_id` out of the id_token's `https://api.openai.com/auth`
|
|
92
104
|
* claim. Payload only — this is reading our own token for a routing header, not
|
package/dist/codex-backend.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { anthropicToResponsesRequest, createResponsesSSEParser, formatResponsesAnthropicSSE, createAnthropicMessageAssembler, responsesStreamToAnthropicSSE, } from './anthropic-responses-translate.js';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveClaudeTarget } from './claude-model.js';
|
|
3
3
|
import { BAKED_BASE_MODELS } from './model-catalog.js';
|
|
4
4
|
export const CODEX_BACKEND_BASE_URL = process.env.DARIO_CODEX_BASE_URL || 'https://chatgpt.com/backend-api/codex';
|
|
5
5
|
/** Originator string the codex CLI identifies itself with. */
|
|
@@ -133,12 +133,26 @@ export function pickClaudeFallback(models, slugs, bases = BAKED_BASE_MODELS, res
|
|
|
133
133
|
// Returns the RESOLVED canonical id, not the entry as written: the caller
|
|
134
134
|
// swaps it into the body after the proxy's own alias pass has already run,
|
|
135
135
|
// so an alias returned raw would reach Anthropic unresolved and 400.
|
|
136
|
+
return pickClaudeTarget(models, slugs, bases, resolve)?.model ?? null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* {@link pickClaudeFallback} with the effort the winning entry asked for.
|
|
140
|
+
*
|
|
141
|
+
* `claude:opus:high` names BOTH a model and an effort, and the request path
|
|
142
|
+
* honours exactly that spelling for a client-sent model. Selection kept only
|
|
143
|
+
* the model half, so a declined codex request configured that way reached the
|
|
144
|
+
* Claude pool at the pool default effort — the operator's `high` silently
|
|
145
|
+
* dropped (dario#1161). The caller applies `effort` when it rebuilds the
|
|
146
|
+
* Claude-bound request; `undefined` means the entry named no effort and the
|
|
147
|
+
* pool default stands.
|
|
148
|
+
*/
|
|
149
|
+
export function pickClaudeTarget(models, slugs, bases = BAKED_BASE_MODELS, resolve) {
|
|
136
150
|
for (const m of models) {
|
|
137
151
|
if (isCodexModel(m, slugs))
|
|
138
152
|
continue;
|
|
139
|
-
const
|
|
140
|
-
if (
|
|
141
|
-
return
|
|
153
|
+
const target = resolveClaudeTarget(m, bases, resolve);
|
|
154
|
+
if (target)
|
|
155
|
+
return target;
|
|
142
156
|
}
|
|
143
157
|
return null;
|
|
144
158
|
}
|
package/dist/effort.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* effort.ts — the effort vocabulary and the model-name effort suffix.
|
|
3
|
+
*
|
|
4
|
+
* These lived in cc-template.ts, which is where the request path uses them.
|
|
5
|
+
* They moved down here because the pool-fallback classifier (claude-model.ts)
|
|
6
|
+
* has to parse the SAME suffix the request path parses, and that module is
|
|
7
|
+
* deliberately pure — it decides what the Claude pool can serve out of a base
|
|
8
|
+
* set and nothing else. Importing cc-template.js there would drag the CC
|
|
9
|
+
* template load (live capture / bundled snapshot, read at module init) into a
|
|
10
|
+
* classifier whose whole value is being decidable without one, and a second
|
|
11
|
+
* hand-written copy of the suffix list would be the other way to get it: two
|
|
12
|
+
* definitions of "what counts as an effort suffix" that drift apart silently,
|
|
13
|
+
* which is exactly the bug this file was extracted to fix (dario#1156 review).
|
|
14
|
+
*
|
|
15
|
+
* cc-template.ts re-exports all three, so every existing import site
|
|
16
|
+
* (proxy.ts, cli.ts, test/effort-flag.mjs) is unchanged.
|
|
17
|
+
*/
|
|
18
|
+
/** Valid values for the `--effort` flag. Mirrors CC's effort set (`low|medium|high|xhigh|max`) plus CC's `ultracode` mode and dario's pseudo-value `'client'` for passthrough. `'ultracode'` is CC's xhigh-plus-dynamic-workflow-orchestration mode (CC 2.1.154); the Messages API accepts only low|medium|high|xhigh|max, so dario normalizes ultracode → 'xhigh' on the wire (see normalizeEffortForWire). `'client'` passes through the client's own `output_config.effort` (falling back to `'xhigh'`). dario#87, `'max'` added in dario#190, `'ultracode'` added 2026-05-28. */
|
|
19
|
+
export type EffortValue = 'low' | 'medium' | 'high' | 'xhigh' | 'ultracode' | 'max' | 'client';
|
|
20
|
+
export declare const VALID_EFFORT_VALUES: ReadonlyArray<EffortValue>;
|
|
21
|
+
export declare function parseEffortSuffix(model: string): {
|
|
22
|
+
model: string;
|
|
23
|
+
effort?: EffortValue;
|
|
24
|
+
};
|
package/dist/effort.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* effort.ts — the effort vocabulary and the model-name effort suffix.
|
|
3
|
+
*
|
|
4
|
+
* These lived in cc-template.ts, which is where the request path uses them.
|
|
5
|
+
* They moved down here because the pool-fallback classifier (claude-model.ts)
|
|
6
|
+
* has to parse the SAME suffix the request path parses, and that module is
|
|
7
|
+
* deliberately pure — it decides what the Claude pool can serve out of a base
|
|
8
|
+
* set and nothing else. Importing cc-template.js there would drag the CC
|
|
9
|
+
* template load (live capture / bundled snapshot, read at module init) into a
|
|
10
|
+
* classifier whose whole value is being decidable without one, and a second
|
|
11
|
+
* hand-written copy of the suffix list would be the other way to get it: two
|
|
12
|
+
* definitions of "what counts as an effort suffix" that drift apart silently,
|
|
13
|
+
* which is exactly the bug this file was extracted to fix (dario#1156 review).
|
|
14
|
+
*
|
|
15
|
+
* cc-template.ts re-exports all three, so every existing import site
|
|
16
|
+
* (proxy.ts, cli.ts, test/effort-flag.mjs) is unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export const VALID_EFFORT_VALUES = ['low', 'medium', 'high', 'xhigh', 'ultracode', 'max', 'client'];
|
|
19
|
+
/**
|
|
20
|
+
* dario#419 — strip an optional effort suffix off a model name, so OpenAI-compat
|
|
21
|
+
* clients that can't set `output_config.effort` (e.g. Cursor) can choose effort
|
|
22
|
+
* by model name: `opus-4-8:high` (colon) or Cursor-style `claude-opus-4-8-high`
|
|
23
|
+
* (hyphen). Only the wire-valid effort levels are recognized as a suffix — any
|
|
24
|
+
* other trailing token is left as part of the model name, and a bare model that
|
|
25
|
+
* IS an effort word (e.g. just "high") is left alone. Returns the model with the
|
|
26
|
+
* suffix removed plus the parsed effort (undefined when none). Exported for tests.
|
|
27
|
+
*/
|
|
28
|
+
const SUFFIX_EFFORTS = ['ultracode', 'medium', 'xhigh', 'high', 'low', 'max'];
|
|
29
|
+
export function parseEffortSuffix(model) {
|
|
30
|
+
for (const e of SUFFIX_EFFORTS) {
|
|
31
|
+
for (const sep of [':', '-']) {
|
|
32
|
+
const tag = sep + e;
|
|
33
|
+
if (model.length > tag.length && model.endsWith(tag)) {
|
|
34
|
+
return { model: model.slice(0, -tag.length), effort: e };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { model };
|
|
39
|
+
}
|
package/dist/proxy.js
CHANGED
|
@@ -20,7 +20,7 @@ import { loadAllAccounts, loadAccount, saveAccount, refreshAccountToken, resyncL
|
|
|
20
20
|
import { handleAdminRequest } from './admin-api.js';
|
|
21
21
|
import { createTokenBucket } from './rate-limit.js';
|
|
22
22
|
import { getOpenAIBackend, isOpenAIModel, forwardToOpenAI } from './openai-backend.js';
|
|
23
|
-
import { forwardToCodex, getCodexModelSlugs, peekCodexModelSlugs, pickCodexFallback,
|
|
23
|
+
import { forwardToCodex, getCodexModelSlugs, peekCodexModelSlugs, pickCodexFallback, pickClaudeTarget, CODEX_BACKEND_BASE_URL } from './codex-backend.js';
|
|
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';
|
|
@@ -2746,8 +2746,10 @@ export async function startProxy(opts = {}) {
|
|
|
2746
2746
|
// operator --model-alias first, then pinned + catalog aliases — so
|
|
2747
2747
|
// `backup` or `opus48` in the chain works the way it does on a
|
|
2748
2748
|
// request, and the classifier validates the id that would actually
|
|
2749
|
-
// be forwarded.
|
|
2750
|
-
|
|
2749
|
+
// be forwarded. The entry's own effort suffix (`claude:opus:high`)
|
|
2750
|
+
// comes back with it — see the swap below.
|
|
2751
|
+
const claudeTarget = pickClaudeTarget(poolFallbackModels, codexModels, getCachedBases(), (m) => resolveClaudeAlias(applyModelAlias(m, modelAliases) ?? m));
|
|
2752
|
+
const claudeTargetModel = claudeTarget?.model ?? null;
|
|
2751
2753
|
const canDefer = claudeTarget !== null && pool.size > 0 && !upstreamApiKey;
|
|
2752
2754
|
const codexReq = requestCount;
|
|
2753
2755
|
const served = await forwardToCodex(req, res, body, codexCreds, corsOrigin, SECURITY_HEADERS, upstreamTimeoutMs, verbose, isOpenAI ? 'openai' : 'anthropic', fetch, canDefer,
|
|
@@ -2780,13 +2782,21 @@ export async function startProxy(opts = {}) {
|
|
|
2780
2782
|
});
|
|
2781
2783
|
if (served)
|
|
2782
2784
|
return;
|
|
2783
|
-
const swapped = buildPoolFallbackBody(body,
|
|
2785
|
+
const swapped = buildPoolFallbackBody(body, claudeTargetModel);
|
|
2784
2786
|
if (!swapped) {
|
|
2785
2787
|
res.writeHead(503, { 'Content-Type': 'application/json', ...SECURITY_HEADERS });
|
|
2786
2788
|
res.end(JSON.stringify({ error: { type: 'upstream_unavailable', message: 'Codex backend unavailable and the request body could not be re-pointed at the Claude pool.' } }));
|
|
2787
2789
|
return;
|
|
2788
2790
|
}
|
|
2789
|
-
|
|
2791
|
+
// An operator who writes `--pool-fallback=gpt-5.6-sol,claude:opus:high`
|
|
2792
|
+
// is choosing the effort the failover runs at, exactly as a client
|
|
2793
|
+
// naming `claude:opus:high` on the request is. Selection parsed the
|
|
2794
|
+
// suffix and then dropped it, so the rebuilt Claude request carried
|
|
2795
|
+
// the pool default instead (dario#1161). A chain entry with no
|
|
2796
|
+
// suffix leaves whatever the request itself asked for.
|
|
2797
|
+
if (claudeTarget?.effort)
|
|
2798
|
+
requestEffort = claudeTarget.effort;
|
|
2799
|
+
console.log(`[dario] #${requestCount} codex unavailable -> claude pool as ${claudeTargetModel}${claudeTarget?.effort ? ` (effort: ${claudeTarget.effort})` : ''}`);
|
|
2790
2800
|
// Copy so the type matches the ArrayBuffer-backed buffer this
|
|
2791
2801
|
// handler threads through (Buffer.concat's), as the other in-place
|
|
2792
2802
|
// body rewrites above already do.
|
|
@@ -2797,7 +2807,7 @@ export async function startProxy(opts = {}) {
|
|
|
2797
2807
|
// reads as a success, which is the exact bug class this release
|
|
2798
2808
|
// spent its whole review budget hunting.
|
|
2799
2809
|
parsedBody = null;
|
|
2800
|
-
res.setHeader('x-dario-pool-fallback',
|
|
2810
|
+
res.setHeader('x-dario-pool-fallback', claudeTargetModel);
|
|
2801
2811
|
// fall through to Claude's turn below
|
|
2802
2812
|
}
|
|
2803
2813
|
if (rawModel && openaiBackend && decision.provider === 'openai') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.6",
|
|
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": {
|