@askalf/dario 6.10.2 → 6.10.3

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.
@@ -510,7 +510,7 @@ export declare function detectDrift(t: TemplateData, installedOverride?: string
510
510
  */
511
511
  export declare const SUPPORTED_CC_RANGE: {
512
512
  readonly min: "1.0.0";
513
- readonly maxTested: "2.1.278";
513
+ readonly maxTested: "2.1.280";
514
514
  };
515
515
  /**
516
516
  * Compare two dotted-numeric version strings. Returns negative if `a<b`,
@@ -1211,7 +1211,7 @@ export function detectDrift(t, installedOverride) {
1211
1211
  */
1212
1212
  export const SUPPORTED_CC_RANGE = {
1213
1213
  min: '1.0.0',
1214
- maxTested: '2.1.278',
1214
+ maxTested: '2.1.280',
1215
1215
  };
1216
1216
  /**
1217
1217
  * Compare two dotted-numeric version strings. Returns negative if `a<b`,
package/dist/proxy.d.ts CHANGED
@@ -181,6 +181,38 @@ export declare function parseEffortRejection(body: string): {
181
181
  * an EMPTY supported set in effortSupportByModel. Exported for tests.
182
182
  */
183
183
  export declare function isEffortParamUnsupported(body: string): boolean;
184
+ /**
185
+ * Parse upstream's CLIENT-VERSION gate:
186
+ *
187
+ * 400 {"type":"invalid_request_error","message":"Claude Code 2.1.278 does
188
+ * not support this model; version 2.1.280 or newer is required. Run
189
+ * 'claude update', or update the Claude Code SDK."}
190
+ *
191
+ * Observed live 2026-09-22 the day `claude-opus-5-5` shipped: Anthropic gates a
192
+ * new model on the CLIENT version it reads off the request, which for dario is
193
+ * the bundled template's `user-agent: claude-cli/<_version>`. The bundle claimed
194
+ * 2.1.278 and the model wanted 2.1.280, so every request for it 400'd with a
195
+ * message naming Claude Code -- which the caller usually is not (Cursor, Cline,
196
+ * the Agent SDK). Nothing in the pool, the seat or the model is wrong, and the
197
+ * daily sdk-drift watch can be up to a day behind the npm publish, so the first
198
+ * symptom is this 400 and it reads like a model problem.
199
+ *
200
+ * Returns the version the bundle claimed and the version upstream wants, or
201
+ * null for any other 400. Exported for tests.
202
+ */
203
+ export declare function parseClientVersionGate(body: string): {
204
+ claimed: string;
205
+ required: string;
206
+ } | null;
207
+ /**
208
+ * The operator-facing sentence for a client-version gate. One line, names the
209
+ * cause and the fix, and says it in terms of dario rather than of Claude Code:
210
+ * the caller is usually a different client entirely.
211
+ */
212
+ export declare function describeClientVersionGate(g: {
213
+ claimed: string;
214
+ required: string;
215
+ }, model: string): string;
184
216
  /**
185
217
  * Pick the strongest effort level a model says it supports. Preference is
186
218
  * descending capability — the caller asked for more than the model can do,
package/dist/proxy.js CHANGED
@@ -602,6 +602,40 @@ export function parseEffortRejection(body) {
602
602
  export function isEffortParamUnsupported(body) {
603
603
  return /does not support the effort parameter/i.test(body);
604
604
  }
605
+ /**
606
+ * Parse upstream's CLIENT-VERSION gate:
607
+ *
608
+ * 400 {"type":"invalid_request_error","message":"Claude Code 2.1.278 does
609
+ * not support this model; version 2.1.280 or newer is required. Run
610
+ * 'claude update', or update the Claude Code SDK."}
611
+ *
612
+ * Observed live 2026-09-22 the day `claude-opus-5-5` shipped: Anthropic gates a
613
+ * new model on the CLIENT version it reads off the request, which for dario is
614
+ * the bundled template's `user-agent: claude-cli/<_version>`. The bundle claimed
615
+ * 2.1.278 and the model wanted 2.1.280, so every request for it 400'd with a
616
+ * message naming Claude Code -- which the caller usually is not (Cursor, Cline,
617
+ * the Agent SDK). Nothing in the pool, the seat or the model is wrong, and the
618
+ * daily sdk-drift watch can be up to a day behind the npm publish, so the first
619
+ * symptom is this 400 and it reads like a model problem.
620
+ *
621
+ * Returns the version the bundle claimed and the version upstream wants, or
622
+ * null for any other 400. Exported for tests.
623
+ */
624
+ export function parseClientVersionGate(body) {
625
+ const m = body.match(/Claude Code ([\d.]+) does not support this model;\s*version ([\d.]+) or newer is required/i);
626
+ return m ? { claimed: m[1], required: m[2] } : null;
627
+ }
628
+ /**
629
+ * The operator-facing sentence for a client-version gate. One line, names the
630
+ * cause and the fix, and says it in terms of dario rather than of Claude Code:
631
+ * the caller is usually a different client entirely.
632
+ */
633
+ export function describeClientVersionGate(g, model) {
634
+ return `${model} requires Claude Code >= ${g.required}, and dario's bundled template claims ${g.claimed}. `
635
+ + `This is the template's version label, not your client and not the seat: upgrade Claude Code on the dario host `
636
+ + `(npm i -g @anthropic-ai/claude-code@latest) and run the cc-drift-template-watch workflow, which re-labels the `
637
+ + `bundle (a label refresh when the wire shape is unchanged). Until then this model cannot be served.`;
638
+ }
605
639
  /**
606
640
  * Pick the strongest effort level a model says it supports. Preference is
607
641
  * descending capability — the caller asked for more than the model can do,
@@ -1899,6 +1933,11 @@ export async function startProxy(opts = {}) {
1899
1933
  // front so older models capped below dario's DEFAULT_MAX_TOKENS pin never
1900
1934
  // re-pay the rejection.
1901
1935
  const maxTokensCapByModel = new Map();
1936
+ // Client-version gates already announced this process, keyed `model@required`.
1937
+ // The gate is a property of (model, installed CC), so it repeats on every
1938
+ // request for that model until the template is re-labelled; saying it once is
1939
+ // the difference between a diagnosis and a log flood.
1940
+ const announcedVersionGates = new Set();
1902
1941
  // Beta flag set — sourced from the live template when the capture recorded
1903
1942
  // one (schema v2+), else falls back to the v2.1.104 bundled default.
1904
1943
  // Computed once per proxy because it's a function of the loaded template,
@@ -4989,6 +5028,34 @@ export async function startProxy(opts = {}) {
4989
5028
  return;
4990
5029
  }
4991
5030
  }
5031
+ else if (upstream.status === 400 && parseClientVersionGate(peekedBody)) {
5032
+ // Client-version gate. There is nothing to retry -- no body edit makes a
5033
+ // stale version label acceptable -- so this only has to be SAID well:
5034
+ // once per (model, required) at error level, and in the response the
5035
+ // caller actually reads. Without this the client gets a 400 blaming a
5036
+ // Claude Code version it is not running.
5037
+ const gate = parseClientVersionGate(peekedBody);
5038
+ const gateKey = `${requestModel}@${gate.required}`;
5039
+ if (!announcedVersionGates.has(gateKey)) {
5040
+ announcedVersionGates.add(gateKey);
5041
+ console.error(`[dario] #${requestCount} ${describeClientVersionGate(gate, requestModel || 'this model')}`);
5042
+ }
5043
+ const responseHeaders = {
5044
+ 'Content-Type': 'application/json',
5045
+ 'Access-Control-Allow-Origin': corsOrigin,
5046
+ ...SECURITY_HEADERS,
5047
+ };
5048
+ for (const [key, value] of upstream.headers.entries()) {
5049
+ if (key === 'request-id')
5050
+ responseHeaders[key] = value;
5051
+ }
5052
+ requestCount++;
5053
+ res.writeHead(400, responseHeaders);
5054
+ res.end(JSON.stringify(isOpenAI
5055
+ ? { error: { message: describeClientVersionGate(gate, requestModel || 'this model'), type: 'invalid_request_error', param: 'model', code: 'client_version_too_old' } }
5056
+ : { type: 'error', error: { type: 'invalid_request_error', message: describeClientVersionGate(gate, requestModel || 'this model') } }));
5057
+ return;
5058
+ }
4992
5059
  else if (upstream.status === 400 && parseMaxTokensRejection(peekedBody) !== null && finalBody && recoveryPasses < MAX_RECOVERY_PASSES) {
4993
5060
  // max_tokens-cap rejection — dario's DEFAULT_MAX_TOKENS pin exceeds
4994
5061
  // this (older) model's per-model output cap (e.g. opus-4-1 caps at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "6.10.2",
3
+ "version": "6.10.3",
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": {