@askalf/dario 6.0.37 → 6.0.38
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/anthropic-responses-translate.d.ts +20 -1
- package/dist/anthropic-responses-translate.js +15 -2
- package/dist/codex-backend.d.ts +16 -3
- package/dist/codex-backend.js +30 -15
- package/dist/effort.d.ts +14 -0
- package/dist/effort.js +26 -0
- package/dist/proxy.js +56 -8
- package/docs/configuration.md +33 -0
- package/package.json +1 -1
|
@@ -221,7 +221,14 @@ export type ResponsesToolChoice = 'auto' | 'none' | 'required' | {
|
|
|
221
221
|
name: string;
|
|
222
222
|
};
|
|
223
223
|
export interface ResponsesReasoningConfig {
|
|
224
|
-
|
|
224
|
+
/**
|
|
225
|
+
* The backend accepts more levels than the thinking-budget mapping can
|
|
226
|
+
* produce: probed 2026-08-29 against a live account, `none, minimal, low,
|
|
227
|
+
* medium, high, xhigh, max` are all valid and `ultra` 400s. The wider set
|
|
228
|
+
* is reachable only when a caller names an effort outright (dario#1260);
|
|
229
|
+
* `thinkingToReasoningEffort` still only ever yields low/medium/high.
|
|
230
|
+
*/
|
|
231
|
+
effort?: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
225
232
|
summary?: 'auto' | 'concise' | 'detailed';
|
|
226
233
|
}
|
|
227
234
|
export interface ResponsesRequest {
|
|
@@ -338,9 +345,13 @@ export interface ResponsesResponse {
|
|
|
338
345
|
* (max_output_tokens caps reasoning + output combined on the Responses API).
|
|
339
346
|
*/
|
|
340
347
|
export declare const REASONING_HEADROOM: {
|
|
348
|
+
readonly none: 0;
|
|
349
|
+
readonly minimal: 6000;
|
|
341
350
|
readonly low: 12000;
|
|
342
351
|
readonly medium: 25000;
|
|
343
352
|
readonly high: 50000;
|
|
353
|
+
readonly xhigh: 80000;
|
|
354
|
+
readonly max: 120000;
|
|
344
355
|
};
|
|
345
356
|
/** gpt-5.x / o-series output ceiling (tokens). */
|
|
346
357
|
export declare const RESPONSES_MAX_OUTPUT_CAP = 128000;
|
|
@@ -352,6 +363,14 @@ export interface AnthropicToResponsesOptions {
|
|
|
352
363
|
* Pass `null` to omit `summary` entirely.
|
|
353
364
|
*/
|
|
354
365
|
reasoningSummary?: 'auto' | 'concise' | 'detailed' | null;
|
|
366
|
+
/**
|
|
367
|
+
* Force `reasoning.effort`, overriding whatever `thinking.budget_tokens`
|
|
368
|
+
* would have implied. Set from a model-name effort suffix on the Codex
|
|
369
|
+
* route (dario#1260, `gpt-5.6-terra:high`), which is the only way an
|
|
370
|
+
* Anthropic-shape caller can choose an effort the budget thresholds cannot
|
|
371
|
+
* express. Unset means the existing thinking-derived behaviour, unchanged.
|
|
372
|
+
*/
|
|
373
|
+
effort?: ResponsesReasoningConfig['effort'];
|
|
355
374
|
/**
|
|
356
375
|
* `store`. Default false — dario is stateless and keeps no server-side
|
|
357
376
|
* conversation. Set true only if a caller wants OpenAI-side retention.
|
|
@@ -312,7 +312,17 @@ function translateToolChoice(choice) {
|
|
|
312
312
|
* client's intended visible-output budget survives on a reasoning model
|
|
313
313
|
* (max_output_tokens caps reasoning + output combined on the Responses API).
|
|
314
314
|
*/
|
|
315
|
-
export const REASONING_HEADROOM = {
|
|
315
|
+
export const REASONING_HEADROOM = {
|
|
316
|
+
none: 0, minimal: 6000, low: 12000, medium: 25000, high: 50000,
|
|
317
|
+
// dario#1260 widened the reachable levels beyond what the thinking-budget
|
|
318
|
+
// mapping produces. xhigh and max reason MUCH harder — measured through
|
|
319
|
+
// dario on one identical prompt, same model, only the level changing:
|
|
320
|
+
// low 1,982 output tokens, high 3,947, max 11,160. Reserve above the trend
|
|
321
|
+
// rather than on it: under-reserving is the failure this table exists to
|
|
322
|
+
// prevent (reasoning eats the whole budget, status incomplete, empty turn),
|
|
323
|
+
// and both values are clamped by RESPONSES_MAX_OUTPUT_CAP anyway.
|
|
324
|
+
xhigh: 80000, max: 120000,
|
|
325
|
+
};
|
|
316
326
|
/** gpt-5.x / o-series output ceiling (tokens). */
|
|
317
327
|
export const RESPONSES_MAX_OUTPUT_CAP = 128000;
|
|
318
328
|
/**
|
|
@@ -376,7 +386,10 @@ export function anthropicToResponsesRequest(body, targetModel, options = {}) {
|
|
|
376
386
|
if (body.tool_choice?.disable_parallel_tool_use === true && out.tools) {
|
|
377
387
|
out.parallel_tool_calls = false;
|
|
378
388
|
}
|
|
379
|
-
|
|
389
|
+
// An explicitly named effort wins over the thinking-budget mapping: the
|
|
390
|
+
// caller asked for a level, not a budget, and the thresholds cannot express
|
|
391
|
+
// xhigh or max at all.
|
|
392
|
+
const effort = options.effort ?? thinkingToReasoningEffort(body.thinking);
|
|
380
393
|
if (effort) {
|
|
381
394
|
out.reasoning = { effort };
|
|
382
395
|
const summary = options.reasoningSummary === undefined ? 'auto' : options.reasoningSummary;
|
package/dist/codex-backend.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import type { CodexAccountCredentials } from './codex-accounts.js';
|
|
3
|
-
import { type ResponsesUsage } from './anthropic-responses-translate.js';
|
|
3
|
+
import { type ResponsesReasoningConfig, type ResponsesUsage } from './anthropic-responses-translate.js';
|
|
4
4
|
import { type ModelResolver, type ClaudeTarget } from './claude-model.js';
|
|
5
|
+
import { type EffortValue } from './effort.js';
|
|
5
6
|
export declare const CODEX_BACKEND_BASE_URL: string;
|
|
6
7
|
/**
|
|
7
8
|
* Client version sent on the model-discovery call. The backend REQUIRES the
|
|
@@ -72,7 +73,13 @@ export declare function isCodexModel(model: string, slugs: readonly string[]): b
|
|
|
72
73
|
* own. A single-entry chain keeps the pre-6.0 meaning exactly, so configs
|
|
73
74
|
* written before this release behave identically.
|
|
74
75
|
*/
|
|
75
|
-
export
|
|
76
|
+
export interface CodexTarget {
|
|
77
|
+
/** The slug as the account lists it — what goes in the outbound body. */
|
|
78
|
+
model: string;
|
|
79
|
+
/** Effort the entry declared through a `:high`-style suffix, if any. */
|
|
80
|
+
effort?: EffortValue;
|
|
81
|
+
}
|
|
82
|
+
export declare function pickCodexFallback(models: readonly string[], slugs: readonly string[]): CodexTarget | null;
|
|
76
83
|
export declare function pickClaudeFallback(models: readonly string[], slugs: readonly string[], bases?: readonly string[], resolve?: ModelResolver): string | null;
|
|
77
84
|
/**
|
|
78
85
|
* {@link pickClaudeFallback} with the effort the winning entry asked for.
|
|
@@ -276,4 +283,10 @@ export declare function buildCodexHeaders(creds: CodexAccountCredentials): Recor
|
|
|
276
283
|
* testable without network (test/codex-backend.mjs), matching the pattern
|
|
277
284
|
* test/codex-oauth.mjs already uses.
|
|
278
285
|
*/
|
|
279
|
-
export declare function forwardToCodex(req: IncomingMessage, res: ServerResponse, body: Buffer, creds: CodexAccountCredentials, corsOrigin: string, securityHeaders: Record<string, string>, upstreamTimeoutMs: number, verbose: boolean, shape?: CodexRequestShape, fetchImpl?: typeof fetch, deferOnUnavailable?: boolean, onDone?: (outcome: CodexForwardOutcome) => void, onDecline?: (info: CodexDecline) => void
|
|
286
|
+
export declare function forwardToCodex(req: IncomingMessage, res: ServerResponse, body: Buffer, creds: CodexAccountCredentials, corsOrigin: string, securityHeaders: Record<string, string>, upstreamTimeoutMs: number, verbose: boolean, shape?: CodexRequestShape, fetchImpl?: typeof fetch, deferOnUnavailable?: boolean, onDone?: (outcome: CodexForwardOutcome) => void, onDecline?: (info: CodexDecline) => void,
|
|
287
|
+
/**
|
|
288
|
+
* Effort named by a model-name suffix (dario#1260). Anthropic-shape only:
|
|
289
|
+
* a chat/completions caller sets `reasoning_effort` itself and that already
|
|
290
|
+
* translates. Undefined leaves the request exactly as it was.
|
|
291
|
+
*/
|
|
292
|
+
effort?: ResponsesReasoningConfig['effort']): Promise<boolean>;
|
package/dist/codex-backend.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import { createHash } from 'node:crypto';
|
|
25
25
|
import { anthropicToResponsesRequest, anthropicUsageFromResponses, createResponsesSSEParser, formatResponsesAnthropicSSE, createAnthropicMessageAssembler, responsesStreamToAnthropicSSE, } from './anthropic-responses-translate.js';
|
|
26
26
|
import { resolveClaudeTarget } from './claude-model.js';
|
|
27
|
+
import { parseEffortSuffix } from './effort.js';
|
|
27
28
|
import { BAKED_BASE_MODELS } from './model-catalog.js';
|
|
28
29
|
import { parseRetryAfterMs } from './provider-cooldown.js';
|
|
29
30
|
export const CODEX_BACKEND_BASE_URL = process.env.DARIO_CODEX_BASE_URL || 'https://chatgpt.com/backend-api/codex';
|
|
@@ -121,20 +122,28 @@ export function isCodexModel(model, slugs) {
|
|
|
121
122
|
const m = model.toLowerCase();
|
|
122
123
|
return slugs.some(s => s.toLowerCase() === m);
|
|
123
124
|
}
|
|
124
|
-
/**
|
|
125
|
-
* A pool-fallback value may name a CHAIN — `gpt-5.6-sol,claude-sonnet-5` — and
|
|
126
|
-
* each provider takes the first entry it can actually serve. These two pickers
|
|
127
|
-
* are the whole selection rule, kept pure so it is testable without a socket.
|
|
128
|
-
*
|
|
129
|
-
* Reading the chain from both ends is what makes failover SYMMETRIC in v6.0.0:
|
|
130
|
-
* `pickCodexFallback` catches a drained Claude pool, `pickClaudeFallback`
|
|
131
|
-
* catches a ChatGPT subscription that is rate-limited or down. Neither
|
|
132
|
-
* subscription hitting its ceiling can take the whole deployment dark on its
|
|
133
|
-
* own. A single-entry chain keeps the pre-6.0 meaning exactly, so configs
|
|
134
|
-
* written before this release behave identically.
|
|
135
|
-
*/
|
|
136
125
|
export function pickCodexFallback(models, slugs) {
|
|
137
|
-
|
|
126
|
+
for (const m of models) {
|
|
127
|
+
// The name AS WRITTEN wins, and only a name that matches no slug at all is
|
|
128
|
+
// re-read as `model:effort` — the same two-pass rule resolveClaudeTarget
|
|
129
|
+
// uses for chain entries (dario#1161), so a slug that genuinely ends in an
|
|
130
|
+
// effort word keeps priority over the suffix reading.
|
|
131
|
+
//
|
|
132
|
+
// Without the second pass a codex chain entry carrying the very suffix the
|
|
133
|
+
// Claude half accepts (`gpt-5.6-terra:high`) matched nothing and was
|
|
134
|
+
// silently SKIPPED: no error, no log, just a failover the operator
|
|
135
|
+
// configured that never fired (dario#1260).
|
|
136
|
+
//
|
|
137
|
+
// Per entry rather than two passes over the whole list, because the chain is
|
|
138
|
+
// priority-ordered and a full as-written sweep would let a later entry
|
|
139
|
+
// overtake an earlier one purely for being spelled without a suffix.
|
|
140
|
+
if (isCodexModel(m, slugs))
|
|
141
|
+
return { model: m };
|
|
142
|
+
const eff = parseEffortSuffix(m);
|
|
143
|
+
if (eff.effort && isCodexModel(eff.model, slugs))
|
|
144
|
+
return { model: eff.model, effort: eff.effort };
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
138
147
|
}
|
|
139
148
|
export function pickClaudeFallback(models, slugs, bases = BAKED_BASE_MODELS, resolve) {
|
|
140
149
|
// "Not a codex slug" is NOT the same as "the Claude pool can serve it". A
|
|
@@ -746,7 +755,13 @@ export function buildCodexHeaders(creds) {
|
|
|
746
755
|
* testable without network (test/codex-backend.mjs), matching the pattern
|
|
747
756
|
* test/codex-oauth.mjs already uses.
|
|
748
757
|
*/
|
|
749
|
-
export async function forwardToCodex(req, res, body, creds, corsOrigin, securityHeaders, upstreamTimeoutMs, verbose, shape = 'openai', fetchImpl = fetch, deferOnUnavailable = false, onDone, onDecline
|
|
758
|
+
export async function forwardToCodex(req, res, body, creds, corsOrigin, securityHeaders, upstreamTimeoutMs, verbose, shape = 'openai', fetchImpl = fetch, deferOnUnavailable = false, onDone, onDecline,
|
|
759
|
+
/**
|
|
760
|
+
* Effort named by a model-name suffix (dario#1260). Anthropic-shape only:
|
|
761
|
+
* a chat/completions caller sets `reasoning_effort` itself and that already
|
|
762
|
+
* translates. Undefined leaves the request exactly as it was.
|
|
763
|
+
*/
|
|
764
|
+
effort) {
|
|
750
765
|
void req;
|
|
751
766
|
const isAnthropic = shape === 'anthropic';
|
|
752
767
|
// Reported exactly once, on every exit that answered the client. Without
|
|
@@ -789,7 +804,7 @@ export async function forwardToCodex(req, res, body, creds, corsOrigin, security
|
|
|
789
804
|
const model = String(parsed.model ?? '');
|
|
790
805
|
// stream is forced: the backend is always streamed and collapsed here.
|
|
791
806
|
const upstreamBody = isAnthropic
|
|
792
|
-
? { ...anthropicToResponsesRequest(parsed, model), stream: true }
|
|
807
|
+
? { ...anthropicToResponsesRequest(parsed, model, effort ? { effort } : {}), stream: true }
|
|
793
808
|
: chatCompletionsToResponses(parsed);
|
|
794
809
|
const scrubbed = toCodexSupportedBody({
|
|
795
810
|
...upstreamBody,
|
package/dist/effort.d.ts
CHANGED
|
@@ -18,6 +18,20 @@
|
|
|
18
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
19
|
export type EffortValue = 'low' | 'medium' | 'high' | 'xhigh' | 'ultracode' | 'max' | 'client';
|
|
20
20
|
export declare const VALID_EFFORT_VALUES: ReadonlyArray<EffortValue>;
|
|
21
|
+
/**
|
|
22
|
+
* dario's effort tiers onto the levels the Codex Responses backend accepts
|
|
23
|
+
* (dario#1260). Probed 2026-08-29: `none, minimal, low, medium, high, xhigh,
|
|
24
|
+
* max` are valid upstream; `ultra` 400s.
|
|
25
|
+
*
|
|
26
|
+
* - low/medium/high/xhigh/max pass through — same name on both sides.
|
|
27
|
+
* - `ultracode` is dario's own tier with no upstream equivalent. It maps to
|
|
28
|
+
* `max`, the nearest thing the backend has, rather than being dropped:
|
|
29
|
+
* a caller who asked for the most effort available should not silently
|
|
30
|
+
* get the default.
|
|
31
|
+
* - `client` means "whatever the client asked for", so it forces nothing
|
|
32
|
+
* and the request's own thinking budget decides, as before.
|
|
33
|
+
*/
|
|
34
|
+
export declare function effortForCodex(effort: EffortValue | undefined): 'low' | 'medium' | 'high' | 'xhigh' | 'max' | undefined;
|
|
21
35
|
export declare function parseEffortSuffix(model: string): {
|
|
22
36
|
model: string;
|
|
23
37
|
effort?: EffortValue;
|
package/dist/effort.js
CHANGED
|
@@ -26,6 +26,32 @@ export const VALID_EFFORT_VALUES = ['low', 'medium', 'high', 'xhigh', 'ultracode
|
|
|
26
26
|
* suffix removed plus the parsed effort (undefined when none). Exported for tests.
|
|
27
27
|
*/
|
|
28
28
|
const SUFFIX_EFFORTS = ['ultracode', 'medium', 'xhigh', 'high', 'low', 'max'];
|
|
29
|
+
/**
|
|
30
|
+
* dario's effort tiers onto the levels the Codex Responses backend accepts
|
|
31
|
+
* (dario#1260). Probed 2026-08-29: `none, minimal, low, medium, high, xhigh,
|
|
32
|
+
* max` are valid upstream; `ultra` 400s.
|
|
33
|
+
*
|
|
34
|
+
* - low/medium/high/xhigh/max pass through — same name on both sides.
|
|
35
|
+
* - `ultracode` is dario's own tier with no upstream equivalent. It maps to
|
|
36
|
+
* `max`, the nearest thing the backend has, rather than being dropped:
|
|
37
|
+
* a caller who asked for the most effort available should not silently
|
|
38
|
+
* get the default.
|
|
39
|
+
* - `client` means "whatever the client asked for", so it forces nothing
|
|
40
|
+
* and the request's own thinking budget decides, as before.
|
|
41
|
+
*/
|
|
42
|
+
export function effortForCodex(effort) {
|
|
43
|
+
switch (effort) {
|
|
44
|
+
case 'low':
|
|
45
|
+
case 'medium':
|
|
46
|
+
case 'high':
|
|
47
|
+
case 'xhigh':
|
|
48
|
+
case 'max': return effort;
|
|
49
|
+
case 'ultracode': return 'max';
|
|
50
|
+
case 'client':
|
|
51
|
+
case undefined: return undefined;
|
|
52
|
+
default: return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
29
55
|
export function parseEffortSuffix(model) {
|
|
30
56
|
for (const e of SUFFIX_EFFORTS) {
|
|
31
57
|
for (const sep of [':', '-']) {
|
package/dist/proxy.js
CHANGED
|
@@ -23,7 +23,8 @@ import { loadAllAccounts, loadAccount, saveAccount, refreshAccountToken, resyncL
|
|
|
23
23
|
import { handleAdminRequest } from './admin-api.js';
|
|
24
24
|
import { createTokenBucket } from './rate-limit.js';
|
|
25
25
|
import { getOpenAIBackend, isOpenAIModel, forwardToOpenAI } from './openai-backend.js';
|
|
26
|
-
import { forwardToCodex, getCodexModelSlugs, peekCodexModelSlugs, pickCodexFallback, pickClaudeTarget, CODEX_BACKEND_BASE_URL } from './codex-backend.js';
|
|
26
|
+
import { forwardToCodex, getCodexModelSlugs, peekCodexModelSlugs, isCodexModel, pickCodexFallback, pickClaudeTarget, CODEX_BACKEND_BASE_URL } from './codex-backend.js';
|
|
27
|
+
import { effortForCodex } from './effort.js';
|
|
27
28
|
import { isClaudeServableModel } from './claude-model.js';
|
|
28
29
|
import { MODEL_UNROUTABLE } from './upstream-rejection.js';
|
|
29
30
|
import { readCompareTarget, teeResponse, runCompare, writeCompareRecord, COMPARE_RESULT_HEADER } from './compare.js';
|
|
@@ -1945,9 +1946,10 @@ export async function startProxy(opts = {}) {
|
|
|
1945
1946
|
return false;
|
|
1946
1947
|
}
|
|
1947
1948
|
const slugs = await getCodexModelSlugs(creds).catch(() => []);
|
|
1948
|
-
const
|
|
1949
|
-
if (!
|
|
1949
|
+
const fallbackPick = pickCodexFallback(fallbackModels, slugs);
|
|
1950
|
+
if (!fallbackPick)
|
|
1950
1951
|
return false;
|
|
1952
|
+
const fallbackModel = fallbackPick.model;
|
|
1951
1953
|
const fallbackBody = buildPoolFallbackBody(body, fallbackModel);
|
|
1952
1954
|
if (!fallbackBody)
|
|
1953
1955
|
return false;
|
|
@@ -1969,7 +1971,12 @@ export async function startProxy(opts = {}) {
|
|
|
1969
1971
|
// an outage, not quota — cooling it would park a provider that may be
|
|
1970
1972
|
// back on the next request, which is the opposite of the fix.
|
|
1971
1973
|
(d) => { if (d.status === 429)
|
|
1972
|
-
providerCooldowns.note('codex', d.retryAfterMs); }
|
|
1974
|
+
providerCooldowns.note('codex', d.retryAfterMs); },
|
|
1975
|
+
// The mirror of the Claude side (dario#1161): an operator who writes
|
|
1976
|
+
// `--pool-fallback=gpt-5.6-terra:high` is choosing the effort the
|
|
1977
|
+
// failover runs at, so the entry's own suffix reaches the request rather
|
|
1978
|
+
// than the failover quietly running at the backend default.
|
|
1979
|
+
effortForCodex(fallbackPick.effort));
|
|
1973
1980
|
if (served)
|
|
1974
1981
|
providerCooldowns.clear('codex');
|
|
1975
1982
|
return served;
|
|
@@ -3066,9 +3073,9 @@ export async function startProxy(opts = {}) {
|
|
|
3066
3073
|
if (body.length > 0) {
|
|
3067
3074
|
try {
|
|
3068
3075
|
const peek = (parsedBody ?? {}); // parsed once by the invalid-body guard; `body` is re-serialized from it
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3076
|
+
// Reassignable: the codex effort-suffix strip below rewrites it, and
|
|
3077
|
+
// every routing decision after that point must see the stripped name.
|
|
3078
|
+
let rawModel = (peek.model || '').toString();
|
|
3072
3079
|
// Credentials are re-read per request (not cached at startup) because
|
|
3073
3080
|
// a refresh rotates them on disk; getFreshCodexAccount refreshes when
|
|
3074
3081
|
// inside the expiry buffer, collapsing concurrent refreshes per alias.
|
|
@@ -3105,6 +3112,43 @@ export async function startProxy(opts = {}) {
|
|
|
3105
3112
|
}
|
|
3106
3113
|
}
|
|
3107
3114
|
}
|
|
3115
|
+
// dario#1260 — an effort suffix on a CODEX model (`gpt-5.6-terra:high`).
|
|
3116
|
+
// The two strip sites further up deliberately leave OpenAI-shaped names
|
|
3117
|
+
// alone: an openai-compat backend may serve a model whose real id ends
|
|
3118
|
+
// in `-high`/`-low`, and stripping there would rewrite a legitimate
|
|
3119
|
+
// name against a catalog this proxy cannot see. Codex is the one
|
|
3120
|
+
// provider that publishes its routable set, so here — and only here —
|
|
3121
|
+
// the ambiguity is decidable: strip when the name as written matches no
|
|
3122
|
+
// slug and the stripped name matches one.
|
|
3123
|
+
//
|
|
3124
|
+
// That guard is what makes this strictly additive. The only requests
|
|
3125
|
+
// whose routing changes are the ones that answer 400 `model_unroutable`
|
|
3126
|
+
// today, because a name that already routes is never re-read.
|
|
3127
|
+
//
|
|
3128
|
+
// Placed HERE rather than beside its siblings because `codexModels` is
|
|
3129
|
+
// THIS request's account's list, resolved just above; the suffix cannot
|
|
3130
|
+
// be told apart from a real id without it.
|
|
3131
|
+
if (rawModel && codexModels.length > 0 && !isCodexModel(rawModel, codexModels)) {
|
|
3132
|
+
const eff = parseEffortSuffix(rawModel);
|
|
3133
|
+
if (eff.effort && isCodexModel(eff.model, codexModels)) {
|
|
3134
|
+
if (verbose)
|
|
3135
|
+
console.log(`[dario] effort suffix: ${rawModel} → model ${eff.model} (codex, effort: ${eff.effort})`);
|
|
3136
|
+
requestEffort = eff.effort;
|
|
3137
|
+
rawModel = eff.model;
|
|
3138
|
+
// The suffix must not survive into the outbound body: the backend
|
|
3139
|
+
// 400s on a slug it does not list, which is the very failure this
|
|
3140
|
+
// fixes. `body` is re-serialized from `parsedBody` exactly as the
|
|
3141
|
+
// alias and provider-prefix blocks above do.
|
|
3142
|
+
peek.model = eff.model;
|
|
3143
|
+
body = Buffer.from(JSON.stringify(parsedBody));
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
// Chosen AFTER the strip above, not before it: a per-model fallback
|
|
3147
|
+
// spec is keyed on the model being routed, and keying it on a name
|
|
3148
|
+
// still carrying a dario-side effort suffix would miss the operator's
|
|
3149
|
+
// own entry for that model and fall back to the unscoped chain.
|
|
3150
|
+
const requestPoolFallbackModels = selectPoolFallbackModels(poolFallbackSpec, rawModel);
|
|
3151
|
+
const requestPoolFallbackModel = requestPoolFallbackModels[0] ?? null;
|
|
3108
3152
|
const decision = routeProvider({
|
|
3109
3153
|
isOpenAIPath: isOpenAI,
|
|
3110
3154
|
model: rawModel,
|
|
@@ -3205,7 +3249,11 @@ export async function startProxy(opts = {}) {
|
|
|
3205
3249
|
// is an outage, and parking a provider for that would keep it out
|
|
3206
3250
|
// of the chain while it was already coming back.
|
|
3207
3251
|
(d) => { if (d.status === 429)
|
|
3208
|
-
providerCooldowns.note('codex', d.retryAfterMs); }
|
|
3252
|
+
providerCooldowns.note('codex', d.retryAfterMs); },
|
|
3253
|
+
// dario#1260 — the effort named by the model-name suffix stripped
|
|
3254
|
+
// above. Undefined for every request that did not name one, which
|
|
3255
|
+
// leaves the outbound body exactly as it was.
|
|
3256
|
+
effortForCodex(requestEffort));
|
|
3209
3257
|
if (served) {
|
|
3210
3258
|
// A provider that just served is not rate-limited.
|
|
3211
3259
|
providerCooldowns.clear('codex');
|
package/docs/configuration.md
CHANGED
|
@@ -74,6 +74,39 @@ Off by default, each one a deliberate divergence from what real CC sends.
|
|
|
74
74
|
| `DARIO_EFFORT` | `--effort=` | Forces a reasoning-effort level. Can flip requests to overage billing — watch `-v` logs for representative-claim changes ([`#87`](https://github.com/askalf/dario/issues/87)). |
|
|
75
75
|
| `DARIO_MAX_TOKENS` | `--max-tokens=` | Anthropic enforces the per-model ceiling server-side, so too-high values return a clean 400 ([`#88`](https://github.com/askalf/dario/issues/88)). |
|
|
76
76
|
|
|
77
|
+
### Per-request effort, by model name
|
|
78
|
+
|
|
79
|
+
`DARIO_EFFORT` is process-wide: it applies to every caller, Claude and Codex
|
|
80
|
+
alike. A client that has no way to set `output_config.effort` can instead name
|
|
81
|
+
the level in the model itself, and only that request changes:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
claude-opus-4-8:high colon form
|
|
85
|
+
claude-opus-4-8-high hyphen form, for Cursor, which rewrites colons
|
|
86
|
+
gpt-5.6-terra:high a Codex model, same two spellings
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Levels a suffix may name: `low`, `medium`, `high`, `xhigh`, `max`, and dario's
|
|
90
|
+
own `ultracode`, which reaches a Codex backend as `max`. On the Codex path the
|
|
91
|
+
level is sent as `reasoning.effort`; on the Claude path as
|
|
92
|
+
`output_config.effort`.
|
|
93
|
+
|
|
94
|
+
`client` is a valid `DARIO_EFFORT` value but deliberately **not** a suffix. It
|
|
95
|
+
means "leave the client's own choice alone", so naming it in a model would be a
|
|
96
|
+
no-op, and accepting it would let a model genuinely named `...-client` be
|
|
97
|
+
stripped to a name the backend does not list.
|
|
98
|
+
|
|
99
|
+
The suffix is only read when the name as written matches no model the provider
|
|
100
|
+
lists, so a real model id that happens to end in an effort word is routed
|
|
101
|
+
exactly as written and never re-read. A `--pool-fallback` chain entry may carry
|
|
102
|
+
one too, which sets the effort the failover request runs at.
|
|
103
|
+
|
|
104
|
+
Effort is not free. Measured through dario on one prompt with only the level
|
|
105
|
+
changing: `low` returned 1,982 output tokens in 18s, `high` 3,947 in 35s, and
|
|
106
|
+
`max` 11,160 in 100s. Reasoning is billed as output, so `max` is a real cost
|
|
107
|
+
increase on every request that names it, and on a long agent loop it can push a
|
|
108
|
+
run into its own timeout.
|
|
109
|
+
|
|
77
110
|
## Pacing
|
|
78
111
|
|
|
79
112
|
Only meaningful with stealth, and all default to 0 (off) except the cap. See [`wire-fidelity.md`](./wire-fidelity.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.38",
|
|
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": {
|