@askalf/dario 6.0.3 → 6.0.5
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/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 +82 -24
- 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/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
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
* `--model-alias` values, pinned aliases (`opus48`) and catalog family
|
|
20
20
|
* shorthands (`opus`, `sonnet1m`) map to a canonical id. The caller
|
|
21
21
|
* supplies that resolver so classification and forwarding cannot
|
|
22
|
-
* disagree.
|
|
22
|
+
* disagree. The prefix rule applies to the alias TARGET too, since an
|
|
23
|
+
* operator alias is written the way a request model is written — see
|
|
24
|
+
* stripClaudePrefix. So does the effort suffix (`:high`, `-high`) — see
|
|
25
|
+
* servableTarget.
|
|
23
26
|
* 2. VALIDATE the resolved id against the catalog base set. A `claude-`
|
|
24
27
|
* prefix on its own proves nothing — `claude-sonnet-6` has the prefix and
|
|
25
28
|
* does not exist — so the id (sans `[1m]`) must be a base the pool can
|
|
@@ -35,36 +38,91 @@
|
|
|
35
38
|
* cold catalog must still admit real ids, and must still refuse typos.
|
|
36
39
|
*/
|
|
37
40
|
import { BAKED_BASE_MODELS, resolveAliasAgainst } from './model-catalog.js';
|
|
41
|
+
import { parseEffortSuffix } from './effort.js';
|
|
38
42
|
/** Provider prefixes that force the Claude path (mirrors proxy.ts's PROVIDER_PREFIXES). */
|
|
39
43
|
const CLAUDE_PREFIXES = new Set(['claude', 'anthropic']);
|
|
40
44
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* (
|
|
45
|
+
* Strip a Claude-side provider prefix, or refuse the name outright.
|
|
46
|
+
*
|
|
47
|
+
* Null on any prefix that isn't `claude:`/`anthropic:` — a recognized
|
|
48
|
+
* non-Claude prefix is a definite NO, not a fall-through to the name test
|
|
49
|
+
* (`openai:claude-sonnet-5` is the operator pointing somewhere else on
|
|
50
|
+
* purpose), and an unrecognized one names a model this pool has no claim on.
|
|
51
|
+
* `model` is expected already trimmed + lowercased.
|
|
46
52
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
53
|
+
function stripClaudePrefix(model) {
|
|
54
|
+
const idx = model.indexOf(':');
|
|
55
|
+
if (idx <= 0)
|
|
56
|
+
return model || null;
|
|
57
|
+
if (!CLAUDE_PREFIXES.has(model.slice(0, idx)))
|
|
58
|
+
return null;
|
|
59
|
+
return model.slice(idx + 1) || null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
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.
|
|
68
|
+
*/
|
|
69
|
+
function servableTarget(target, bases) {
|
|
70
|
+
const stripped = stripClaudePrefix(target);
|
|
71
|
+
if (stripped === null)
|
|
50
72
|
return null;
|
|
51
|
-
const
|
|
52
|
-
if (idx > 0) {
|
|
53
|
-
const prefix = m.slice(0, idx);
|
|
54
|
-
// A recognized non-Claude prefix is a definite NO, not a fall-through to
|
|
55
|
-
// the name test below — `openai:claude-sonnet-5` is the operator pointing
|
|
56
|
-
// somewhere else on purpose.
|
|
57
|
-
if (!CLAUDE_PREFIXES.has(prefix))
|
|
58
|
-
return null;
|
|
59
|
-
m = m.slice(idx + 1);
|
|
60
|
-
if (!m)
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
const resolved = (resolve ? resolve(m) : (resolveAliasAgainst(m, bases) ?? m)).trim().toLowerCase();
|
|
73
|
+
const resolved = resolveAliasAgainst(stripped, bases) ?? stripped;
|
|
64
74
|
const base = resolved.endsWith('[1m]') ? resolved.slice(0, -4) : resolved;
|
|
65
75
|
return bases.some((b) => b.toLowerCase() === base) ? resolved : null;
|
|
66
76
|
}
|
|
67
|
-
/**
|
|
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) {
|
|
85
|
+
const entry = stripClaudePrefix(model.trim().toLowerCase());
|
|
86
|
+
if (entry === null)
|
|
87
|
+
return null;
|
|
88
|
+
// The alias TARGET may carry a prefix of its own — operator aliases are
|
|
89
|
+
// declared the way a request model is written, and `--model-alias=backup=
|
|
90
|
+
// claude:opus` is the natural way to say it (dario#1151). The request path
|
|
91
|
+
// parses that prefix AFTER alias resolution; without the same pass here the
|
|
92
|
+
// target reached the base check as the literal `claude:opus`, matched
|
|
93
|
+
// nothing, and a perfectly valid fallback was skipped.
|
|
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)
|
|
114
|
+
return null;
|
|
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;
|
|
124
|
+
}
|
|
125
|
+
/** Whether the Claude pool can serve `model`. See resolveClaudeTarget. */
|
|
68
126
|
export function isClaudeServableModel(model, bases = BAKED_BASE_MODELS, resolve) {
|
|
69
|
-
return
|
|
127
|
+
return resolveClaudeTarget(model, bases, resolve) !== null;
|
|
70
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.5",
|
|
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": {
|