@askalf/dario 4.8.141 → 4.8.143

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.
@@ -372,21 +372,28 @@ export declare function parseEffortSuffix(model: string): {
372
372
  /**
373
373
  * Resolve the outbound `output_config.effort` value.
374
374
  *
375
- * Tracks CC's wire default. Evolution:
376
- * - Apr 2026, CC ~2.1.116: effort = 'medium' (Discussion #13 documented this)
377
- * - mid-May 2026: effort = 'high' (dario#87 pinned to match)
378
- * - May 17 2026, CC 2.1.143: effort = 'xhigh' (verified by capture-full-body.mjs)
379
- *
380
- * undefined 'max' (highest *universally*-supported level. CC's own wire
381
- * default is 'xhigh', but that's Opus/Fable-only Sonnet/Haiku-class
382
- * 400 on 'xhigh' ("supported: high|low|max|medium"). 'max' is
383
- * accepted by all and still routes to the subscription pool
384
- * (verified: representative-claim=five_hour on Opus + Sonnet).
385
- * Set --effort=xhigh / DARIO_EFFORT=xhigh for the Opus/Fable tier.)
386
- * 'low' / 'medium' / 'high' / 'xhigh' / 'max' → pin to that value
375
+ * Effort is a USER KNOB, not a wire constant: real CC sends whatever the
376
+ * user's effort setting is tuned to, and "what CC sends" in a capture is just
377
+ * that install's knob position. dario chased captured values for months
378
+ * ('medium' 'high' → 'xhigh' across CC releases each one somebody's
379
+ * setting, not a version default) and pinned the outbound effort to a fixed
380
+ * value, silently clamping every client's explicit choice. Since 4.8.142 the
381
+ * default is client-first, matching how real CC actually behaves:
382
+ *
383
+ * undefined / 'client' the client's `clientBody.output_config.effort`
384
+ * (normalized for the wire) forward the knob untouched.
385
+ * Falls back to 'high' only when the client sent none
386
+ * (OpenAI-compat clients that can't set effort).
387
+ * 'low' / 'medium' / 'high' / 'xhigh' / 'max' → operator pin via
388
+ * --effort / DARIO_EFFORT (explicit override, wins over client)
387
389
  * 'ultracode' → 'xhigh' (CC's ultracode mode; xhigh on the wire)
388
- * 'client' → extract from `clientBody.output_config.effort` (normalized
389
- * for the wire); fall back to the default if absent/non-string
390
+ *
391
+ * The 'high' fallback (not 'max') is dario#658 scar tissue: 'max' plus
392
+ * unbounded adaptive thinking exhausts max_tokens on long prompts — the
393
+ * stream ends `stop_reason: max_tokens` with ZERO text blocks. 'high' thinks
394
+ * proportionally and leaves room for the answer. A client that explicitly
395
+ * asks for 'max' gets 'max' — same outcome it would get talking to Anthropic
396
+ * directly.
390
397
  *
391
398
  * FABLE CLAMP — REMOVED 2026-07-01. Fable 5 was SUSPENDED 2026-06-12 (US-gov
392
399
  * directive) and REDEPLOYED 2026-07-01. The pre-suspension model (2026-06-09
@@ -395,12 +402,9 @@ export declare function parseEffortSuffix(model: string): {
395
402
  * through the deployed proxy (CC 2.1.198's verbatim fable body, only
396
403
  * output_config.effort mutated) shows the redeployed fable now ANSWERS all three
397
404
  * — high/xhigh/max → end_turn, zero refusals. So the clamp + the fable-only
398
- * default are gone: fable now takes the general path (no clamp), matching how
399
- * dario treats opus. The general default is `high` (see resolveEffort below)
400
- * Claude Code's out-of-box default. Effort is a user-adjustable knob, so no
401
- * single value is "the" value; `high` is the safe unconfigured baseline.
402
- * `model` is retained in the signature in case a future model needs per-family
403
- * effort handling again.
405
+ * default are gone: fable takes the general path, matching how dario treats
406
+ * opus. `model` is retained in the signature in case a future model needs
407
+ * per-family effort handling again.
404
408
  *
405
409
  * Exported for tests.
406
410
  */
@@ -442,28 +446,48 @@ export type CacheControl = {
442
446
  ttl?: '5m' | '1h';
443
447
  };
444
448
  /**
445
- * The cache-control dario stamps on every breakpoint (2 system + tools +
446
- * conversation). `ttl: '1h'` mirrors real CC — dario shipped the 5-min
447
- * ephemeral default, so its prefix expired 12x sooner than CC's and any
448
- * interactive turn past the 5-min window re-created the whole prefix
449
- * (system + all tools) at cache-creation cost, draining the Max window far
450
- * faster than direct CC (root cause of dario#678). Single source of truth so
451
- * the emitted TTL can't silently drift back to 5m.
449
+ * The cache-control dario stamps on every breakpoint (2 system + 2
450
+ * conversation). Plain `{type:'ephemeral'}` (the 5-minute default) mirrors
451
+ * real CC: a loopback MITM capture of CC v2.1.203 shows no `ttl` field on any
452
+ * breakpoint. v4.8.140 stamped `ttl:'1h'` here on the theory that real CC
453
+ * used 1h it doesn't, and 1h cache WRITES bill at 2x base input vs 1.25x
454
+ * for 5m, so every write in a write-heavy agentic session cost 60% more than
455
+ * direct CC. The dario#678 re-test caught it: the reporter's cold-start burn
456
+ * went +8% -> +19% on the "fixed" build. Single source of truth so the
457
+ * emitted shape can't drift from CC again.
452
458
  */
453
459
  export declare const CC_CACHE_CONTROL: CacheControl;
454
460
  /**
455
- * Place CC-style prompt-cache breakpoints on the tools array and the
456
- * conversation. The system prompt is already cached at build time (2 system
457
- * breakpoints); this adds the last tool + a single rolling breakpoint on the
458
- * last message — total 4, the Anthropic max, mirroring Claude Code.
459
- *
460
- * Why: dario previously cached ONLY the system prompt and stripped every
461
- * message breakpoint, so the tools schema (10-20KB) and the entire growing
462
- * conversation re-billed as FRESH input every turn. Fleet cache-read ran ~1.9%
463
- * vs CC's ~70-90%, draining the Max 5h/7d token window 10-50x faster — which is
464
- * exactly why long agentic sessions hit a wall through dario that real CC
465
- * sails through. CC genuinely caches tools + conversation, so NOT caching them
466
- * was itself a wire divergence from CC. Exported for unit testing.
461
+ * Place CC-style prompt-cache breakpoints on the conversation. The system
462
+ * prompt is already cached at build time (2 system breakpoints); this adds a
463
+ * rolling breakpoint on the last user message plus an anchor on the previous
464
+ * one — total 4, the Anthropic max.
465
+ *
466
+ * Placement mirrors a live capture of CC v2.1.203 (dario#678):
467
+ *
468
+ * - Tools carry NO breakpoint. Real CC sends its tool array unstamped —
469
+ * Anthropic renders tools -> system -> messages, so the system breakpoints
470
+ * already cache the tools prefix. Stamping the last tool (pre-4.8.142) both
471
+ * diverged from CC's wire shape and spent the fourth slot the conversation
472
+ * anchor below needs.
473
+ *
474
+ * - The rolling breakpoint goes on the last USER message, not the last
475
+ * message. CC skips trailing role:"system" injections (agent-type updates
476
+ * etc.); stamping "the last message" meant any turn ending in one wrote no
477
+ * conversation entry at all, and the next request re-paid the entire
478
+ * history as fresh input.
479
+ *
480
+ * - The previous user message is anchored too. Anthropic's cache lookup
481
+ * walks back at most ~20 content blocks from a breakpoint; one parallel-
482
+ * tool turn (N tool_use + N tool_result blocks) can exceed that alone, and
483
+ * the rolling breakpoint then can't reach the prior turn's entry — the
484
+ * whole conversation re-bills at cache-WRITE cost every fan-out turn,
485
+ * which is the dario#678 burn ("read every file" sessions draining the
486
+ * Max window ~10x faster than direct CC). The anchor sits exactly where
487
+ * the previous request's rolling breakpoint was, so the lookup hits it
488
+ * positionally with no walk-back.
489
+ *
490
+ * Exported for unit testing.
467
491
  */
468
492
  export declare function applyCcPromptCaching(ccRequest: Record<string, unknown>, cacheControl: CacheControl): void;
469
493
  export declare function buildCCRequest(clientBody: Record<string, unknown>, billingTag: string, cacheControl: CacheControl, identity: {
@@ -1126,21 +1126,28 @@ function normalizeEffortForWire(effort) {
1126
1126
  /**
1127
1127
  * Resolve the outbound `output_config.effort` value.
1128
1128
  *
1129
- * Tracks CC's wire default. Evolution:
1130
- * - Apr 2026, CC ~2.1.116: effort = 'medium' (Discussion #13 documented this)
1131
- * - mid-May 2026: effort = 'high' (dario#87 pinned to match)
1132
- * - May 17 2026, CC 2.1.143: effort = 'xhigh' (verified by capture-full-body.mjs)
1129
+ * Effort is a USER KNOB, not a wire constant: real CC sends whatever the
1130
+ * user's effort setting is tuned to, and "what CC sends" in a capture is just
1131
+ * that install's knob position. dario chased captured values for months
1132
+ * ('medium' 'high' → 'xhigh' across CC releases each one somebody's
1133
+ * setting, not a version default) and pinned the outbound effort to a fixed
1134
+ * value, silently clamping every client's explicit choice. Since 4.8.142 the
1135
+ * default is client-first, matching how real CC actually behaves:
1133
1136
  *
1134
- * undefined 'max' (highest *universally*-supported level. CC's own wire
1135
- * default is 'xhigh', but that's Opus/Fable-only Sonnet/Haiku-class
1136
- * 400 on 'xhigh' ("supported: high|low|max|medium"). 'max' is
1137
- * accepted by all and still routes to the subscription pool
1138
- * (verified: representative-claim=five_hour on Opus + Sonnet).
1139
- * Set --effort=xhigh / DARIO_EFFORT=xhigh for the Opus/Fable tier.)
1140
- * 'low' / 'medium' / 'high' / 'xhigh' / 'max' → pin to that value
1137
+ * undefined / 'client' the client's `clientBody.output_config.effort`
1138
+ * (normalized for the wire) forward the knob untouched.
1139
+ * Falls back to 'high' only when the client sent none
1140
+ * (OpenAI-compat clients that can't set effort).
1141
+ * 'low' / 'medium' / 'high' / 'xhigh' / 'max' → operator pin via
1142
+ * --effort / DARIO_EFFORT (explicit override, wins over client)
1141
1143
  * 'ultracode' → 'xhigh' (CC's ultracode mode; xhigh on the wire)
1142
- * 'client' → extract from `clientBody.output_config.effort` (normalized
1143
- * for the wire); fall back to the default if absent/non-string
1144
+ *
1145
+ * The 'high' fallback (not 'max') is dario#658 scar tissue: 'max' plus
1146
+ * unbounded adaptive thinking exhausts max_tokens on long prompts — the
1147
+ * stream ends `stop_reason: max_tokens` with ZERO text blocks. 'high' thinks
1148
+ * proportionally and leaves room for the answer. A client that explicitly
1149
+ * asks for 'max' gets 'max' — same outcome it would get talking to Anthropic
1150
+ * directly.
1144
1151
  *
1145
1152
  * FABLE CLAMP — REMOVED 2026-07-01. Fable 5 was SUSPENDED 2026-06-12 (US-gov
1146
1153
  * directive) and REDEPLOYED 2026-07-01. The pre-suspension model (2026-06-09
@@ -1149,33 +1156,21 @@ function normalizeEffortForWire(effort) {
1149
1156
  * through the deployed proxy (CC 2.1.198's verbatim fable body, only
1150
1157
  * output_config.effort mutated) shows the redeployed fable now ANSWERS all three
1151
1158
  * — high/xhigh/max → end_turn, zero refusals. So the clamp + the fable-only
1152
- * default are gone: fable now takes the general path (no clamp), matching how
1153
- * dario treats opus. The general default is `high` (see resolveEffort below)
1154
- * Claude Code's out-of-box default. Effort is a user-adjustable knob, so no
1155
- * single value is "the" value; `high` is the safe unconfigured baseline.
1156
- * `model` is retained in the signature in case a future model needs per-family
1157
- * effort handling again.
1159
+ * default are gone: fable takes the general path, matching how dario treats
1160
+ * opus. `model` is retained in the signature in case a future model needs
1161
+ * per-family effort handling again.
1158
1162
  *
1159
1163
  * Exported for tests.
1160
1164
  */
1161
1165
  export function resolveEffort(flag, clientBody, model) {
1162
1166
  void model; // no per-family effort handling at present (see FABLE CLAMP note above)
1163
- // `high` is Claude Code's out-of-box default effort. The prior default `'max'`
1164
- // was the reasoning ceiling: combined with unbounded adaptive thinking it makes
1165
- // the model reason until it exhausts `max_tokens` — on prompts over ~5K input
1166
- // tokens the thinking phase consumes the entire budget, so the stream ends
1167
- // `stop_reason: max_tokens` with ZERO text blocks (dario#658). `high` thinks
1168
- // proportionally and leaves room for the answer. Effort is a user-adjustable
1169
- // knob; operators who want a higher tier pin it via `--effort` / DARIO_EFFORT.
1170
- const familyDefault = 'high';
1171
- if (flag === undefined)
1172
- return familyDefault;
1173
- if (flag === 'client') {
1167
+ const fallback = 'high';
1168
+ if (flag === undefined || flag === 'client') {
1174
1169
  const clientOC = clientBody.output_config;
1175
1170
  const clientEffort = clientOC?.effort;
1176
1171
  if (typeof clientEffort === 'string' && clientEffort.length > 0)
1177
1172
  return normalizeEffortForWire(clientEffort);
1178
- return familyDefault;
1173
+ return fallback;
1179
1174
  }
1180
1175
  return normalizeEffortForWire(flag);
1181
1176
  }
@@ -1236,55 +1231,80 @@ export function supportsAdaptiveThinking(modelId) {
1236
1231
  return false;
1237
1232
  }
1238
1233
  /**
1239
- * The cache-control dario stamps on every breakpoint (2 system + tools +
1240
- * conversation). `ttl: '1h'` mirrors real CC — dario shipped the 5-min
1241
- * ephemeral default, so its prefix expired 12x sooner than CC's and any
1242
- * interactive turn past the 5-min window re-created the whole prefix
1243
- * (system + all tools) at cache-creation cost, draining the Max window far
1244
- * faster than direct CC (root cause of dario#678). Single source of truth so
1245
- * the emitted TTL can't silently drift back to 5m.
1234
+ * The cache-control dario stamps on every breakpoint (2 system + 2
1235
+ * conversation). Plain `{type:'ephemeral'}` (the 5-minute default) mirrors
1236
+ * real CC: a loopback MITM capture of CC v2.1.203 shows no `ttl` field on any
1237
+ * breakpoint. v4.8.140 stamped `ttl:'1h'` here on the theory that real CC
1238
+ * used 1h it doesn't, and 1h cache WRITES bill at 2x base input vs 1.25x
1239
+ * for 5m, so every write in a write-heavy agentic session cost 60% more than
1240
+ * direct CC. The dario#678 re-test caught it: the reporter's cold-start burn
1241
+ * went +8% -> +19% on the "fixed" build. Single source of truth so the
1242
+ * emitted shape can't drift from CC again.
1246
1243
  */
1247
- export const CC_CACHE_CONTROL = { type: 'ephemeral', ttl: '1h' };
1244
+ export const CC_CACHE_CONTROL = { type: 'ephemeral' };
1248
1245
  /**
1249
- * Place CC-style prompt-cache breakpoints on the tools array and the
1250
- * conversation. The system prompt is already cached at build time (2 system
1251
- * breakpoints); this adds the last tool + a single rolling breakpoint on the
1252
- * last message — total 4, the Anthropic max, mirroring Claude Code.
1246
+ * Place CC-style prompt-cache breakpoints on the conversation. The system
1247
+ * prompt is already cached at build time (2 system breakpoints); this adds a
1248
+ * rolling breakpoint on the last user message plus an anchor on the previous
1249
+ * one — total 4, the Anthropic max.
1250
+ *
1251
+ * Placement mirrors a live capture of CC v2.1.203 (dario#678):
1252
+ *
1253
+ * - Tools carry NO breakpoint. Real CC sends its tool array unstamped —
1254
+ * Anthropic renders tools -> system -> messages, so the system breakpoints
1255
+ * already cache the tools prefix. Stamping the last tool (pre-4.8.142) both
1256
+ * diverged from CC's wire shape and spent the fourth slot the conversation
1257
+ * anchor below needs.
1253
1258
  *
1254
- * Why: dario previously cached ONLY the system prompt and stripped every
1255
- * message breakpoint, so the tools schema (10-20KB) and the entire growing
1256
- * conversation re-billed as FRESH input every turn. Fleet cache-read ran ~1.9%
1257
- * vs CC's ~70-90%, draining the Max 5h/7d token window 10-50x faster — which is
1258
- * exactly why long agentic sessions hit a wall through dario that real CC
1259
- * sails through. CC genuinely caches tools + conversation, so NOT caching them
1260
- * was itself a wire divergence from CC. Exported for unit testing.
1259
+ * - The rolling breakpoint goes on the last USER message, not the last
1260
+ * message. CC skips trailing role:"system" injections (agent-type updates
1261
+ * etc.); stamping "the last message" meant any turn ending in one wrote no
1262
+ * conversation entry at all, and the next request re-paid the entire
1263
+ * history as fresh input.
1264
+ *
1265
+ * - The previous user message is anchored too. Anthropic's cache lookup
1266
+ * walks back at most ~20 content blocks from a breakpoint; one parallel-
1267
+ * tool turn (N tool_use + N tool_result blocks) can exceed that alone, and
1268
+ * the rolling breakpoint then can't reach the prior turn's entry — the
1269
+ * whole conversation re-bills at cache-WRITE cost every fan-out turn,
1270
+ * which is the dario#678 burn ("read every file" sessions draining the
1271
+ * Max window ~10x faster than direct CC). The anchor sits exactly where
1272
+ * the previous request's rolling breakpoint was, so the lookup hits it
1273
+ * positionally with no walk-back.
1274
+ *
1275
+ * Exported for unit testing.
1261
1276
  */
1262
1277
  export function applyCcPromptCaching(ccRequest, cacheControl) {
1263
- // Tools — clone (CC_TOOL_DEFINITIONS is a shared module constant), strip any
1264
- // stray breakpoints, cache the LAST tool (caches the whole tools prefix).
1278
+ // Tools — strip any stray client breakpoints (they'd count against the
1279
+ // 4-breakpoint budget) without mutating shared element objects
1280
+ // (CC_TOOL_DEFINITIONS is a module constant).
1265
1281
  const tools = ccRequest.tools;
1266
1282
  if (Array.isArray(tools) && tools.length > 0) {
1267
- const cloned = tools.map((t) => {
1283
+ ccRequest.tools = tools.map((t) => {
1284
+ if (!('cache_control' in t))
1285
+ return t;
1268
1286
  const copy = { ...t };
1269
1287
  delete copy.cache_control;
1270
1288
  return copy;
1271
1289
  });
1272
- cloned[cloned.length - 1] = { ...cloned[cloned.length - 1], cache_control: cacheControl };
1273
- ccRequest.tools = cloned;
1274
1290
  }
1275
- // Conversation — cache up to and including the last message so the NEXT turn
1276
- // reads the whole prefix from cache. Client breakpoints were already stripped
1277
- // upstream; this is the single rolling breakpoint CC uses.
1291
+ // Conversation — last two user messages, walking backward. Client
1292
+ // breakpoints were already stripped upstream. Only block-array content gets
1293
+ // a breakpoint: string content (some SDK clients) is left untouched —
1294
+ // wrapping it would change the wire shape, and a bare string user turn is
1295
+ // tiny anyway. Real CC / agentic sessions use block arrays, which DO cache.
1278
1296
  const msgs = ccRequest.messages;
1279
1297
  if (Array.isArray(msgs) && msgs.length > 0) {
1280
- const last = msgs[msgs.length - 1];
1281
- // Only block-array content gets a breakpoint. String content (some SDK
1282
- // clients) is left untouched — wrapping it would change the wire shape, and
1283
- // a bare string user turn is tiny anyway, so system+tools caching is the
1284
- // win. Real CC / agentic sessions use block arrays, which DO get cached.
1285
- if (Array.isArray(last.content) && last.content.length > 0) {
1286
- const blocks = last.content;
1298
+ let stamped = 0;
1299
+ for (let i = msgs.length - 1; i >= 0 && stamped < 2; i--) {
1300
+ const msg = msgs[i];
1301
+ if (msg.role !== 'user')
1302
+ continue;
1303
+ if (!Array.isArray(msg.content) || msg.content.length === 0)
1304
+ continue;
1305
+ const blocks = msg.content;
1287
1306
  blocks[blocks.length - 1] = { ...blocks[blocks.length - 1], cache_control: cacheControl };
1307
+ stamped++;
1288
1308
  }
1289
1309
  }
1290
1310
  }
@@ -1557,8 +1577,8 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1557
1577
  //
1558
1578
  // System prompt structure (3 blocks, matching real CC):
1559
1579
  // [0] billing tag (no cache)
1560
- // [1] agent identity (1h cache)
1561
- // [2] CC's full 25KB system prompt + client's custom prompt appended (1h cache)
1580
+ // [1] agent identity (ephemeral cache)
1581
+ // [2] CC's full 25KB system prompt + client's custom prompt appended (ephemeral cache)
1562
1582
  // resolveSystemPrompt is the seam for --system-prompt=verbatim|partial|
1563
1583
  // aggressive|<file>. Default (undefined) returns CC_SYSTEM_PROMPT
1564
1584
  // unchanged. See docs/research/system-prompt-classifier-study.md for the empirical
@@ -1690,10 +1710,10 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1690
1710
  // — Max billing pool routing is unchanged.
1691
1711
  //
1692
1712
  // `output_config.effort` is independent of thinking and ships for all
1693
- // non-Haiku models that aren't opted out via skipFields. Default `'high'`
1694
- // matches CC 2.1.116's wire value; `--effort` flag overrides; `'client'`
1695
- // passes through whatever the client sent (or falls back to `'high'` if
1696
- // absent). See dario#87.
1713
+ // non-Haiku models that aren't opted out via skipFields. Default: forward
1714
+ // the client's own effort (it's a user knob real CC wires whatever the
1715
+ // user tuned), falling back to 'high' when the client sent none; `--effort`
1716
+ // flag pins an operator override. See resolveEffort / dario#87.
1697
1717
  if (!isHaiku) {
1698
1718
  const skip = opts.skipFields;
1699
1719
  // Client-supplied thinking shape takes precedence when honorClientThinking
package/dist/cli.js CHANGED
@@ -400,15 +400,17 @@ async function proxy() {
400
400
  ?? parsePositiveIntEnv(process.env['DARIO_MAX_QUEUED']);
401
401
  const queueTimeoutMs = parsePositiveIntFlag('--queue-timeout=')
402
402
  ?? parsePositiveIntEnv(process.env['DARIO_QUEUE_TIMEOUT_MS']);
403
- // --effort=low|medium|high|xhigh|ultracode|max|client — override the outbound
404
- // output_config.effort (dario#87). Default (unset) pins 'high' to match
405
- // CC 2.1.116's wire value. 'client' passes through whatever the client
406
- // sent, falling back to 'high' if the client didn't include one.
403
+ // --effort=low|medium|high|xhigh|ultracode|max|client — pin the outbound
404
+ // output_config.effort (dario#87). Default (unset) forwards the client's
405
+ // own effort — it's a user knob, real CC wires whatever the user tuned —
406
+ // falling back to 'high' when the client sent none. 'client' is a
407
+ // compatibility alias for the default.
407
408
  //
408
- // Risk: setting effort to a non-CC-default value may cause Anthropic's
409
- // classifier to flip requests to 'overage' billing. Users opting in
410
- // should watch the `representative-claim` response header via -v logs
411
- // and revert to default if subscription billing breaks.
409
+ // Risk: pinning effort overrides every client's explicit choice, and a
410
+ // non-CC-plausible value may cause Anthropic's classifier to flip requests
411
+ // to 'overage' billing. Users opting in should watch the
412
+ // `representative-claim` response header via -v logs and revert to default
413
+ // if subscription billing breaks.
412
414
  const effort = resolveEffortFlag(args, process.env['DARIO_EFFORT']);
413
415
  // --max-tokens=<N|client> — override outbound max_tokens (dario#88,
414
416
  // Hermes compat). Default unset pins 32000 (CC 2.1.116's wire default).
@@ -1321,17 +1323,19 @@ async function help() {
1321
1323
  (default: 60000).
1322
1324
  Env: DARIO_QUEUE_TIMEOUT_MS. (dario#80)
1323
1325
  --effort=<low|medium|high|xhigh|ultracode|max|client>
1324
- Override the outbound output_config.effort
1325
- on non-haiku requests. Default (unset)
1326
- pins 'high' matches CC 2.1.116's wire
1327
- value. 'max' is CC's highest reasoning
1328
- budget (added in CC v2.1.x; verified in
1329
- v2.1.126). 'client' passes through what
1330
- the client sent (falls back to 'high' if
1331
- none).
1332
- WARNING: non-'high' values may cause
1333
- Anthropic's classifier to flip requests
1334
- to 'overage' billing; watch -v logs for
1326
+ Pin the outbound output_config.effort on
1327
+ non-haiku requests, overriding the
1328
+ client's choice. Default (unset) forwards
1329
+ the client's own effort — it's a user
1330
+ knob; real CC wires whatever the user
1331
+ tuned — falling back to 'high' when the
1332
+ client sent none. 'client' is a compat
1333
+ alias for the default. 'ultracode' rides
1334
+ the wire as 'xhigh'.
1335
+ WARNING: pinned values override every
1336
+ client and may cause Anthropic's
1337
+ classifier to flip requests to 'overage'
1338
+ billing; watch -v logs for
1335
1339
  representative-claim changes.
1336
1340
  Env: DARIO_EFFORT. (dario#87)
1337
1341
  --max-tokens=<N|client> Override outbound max_tokens. Default
package/dist/doctor.js CHANGED
@@ -334,7 +334,7 @@ export async function runChecks(opts = {}) {
334
334
  // non-passthrough request and dominate the input-token cost on small
335
335
  // turns. Anthropic caches them after the first hit (cache_creation
336
336
  // tokens on call 1, then cache_read on subsequent calls within the
337
- // 1-hour TTL), but non-CC users routing heavy tooling get
337
+ // 5-minute TTL), but non-CC users routing heavy tooling get
338
338
  // surprised by the first-request charge. Surface the size up front
339
339
  // so they can plan.
340
340
  //
@@ -355,7 +355,7 @@ export async function runChecks(opts = {}) {
355
355
  detail: `${promptChars.toLocaleString()} chars system prompt + ${toolCount} tool defs ` +
356
356
  `(${toolChars.toLocaleString()} chars JSON-serialized) injected per non-passthrough ` +
357
357
  `request. Cached after first hit; read-cost only on subsequent calls within ` +
358
- `the 1-hour TTL. Exact token count surfaces as cache_creation_input_tokens ` +
358
+ `the 5-minute TTL. Exact token count surfaces as cache_creation_input_tokens ` +
359
359
  `on the first response (or run \`dario doctor --usage\`).`,
360
360
  });
361
361
  }
@@ -282,7 +282,7 @@ export declare function _resetInstalledVersionProbeForTest(): void;
282
282
  */
283
283
  export declare const SUPPORTED_CC_RANGE: {
284
284
  readonly min: "1.0.0";
285
- readonly maxTested: "2.1.203";
285
+ readonly maxTested: "2.1.204";
286
286
  };
287
287
  /**
288
288
  * Compare two dotted-numeric version strings. Returns negative if `a<b`,
@@ -542,8 +542,8 @@ export function extractTemplate(captured) {
542
542
  return null;
543
543
  // CC's system is a 3-block structure:
544
544
  // [0] billing tag (no cache_control, tiny)
545
- // [1] agent identity ("You are Claude Code..."), cache_control 1h
546
- // [2] system prompt (~25KB), cache_control 1h
545
+ // [1] agent identity ("You are Claude Code..."), cache_control ephemeral (5m — no ttl field, verified CC v2.1.203)
546
+ // [2] system prompt (~25KB), cache_control ephemeral (5m)
547
547
  // Billing tag is per-request — we never cache it. Identity + prompt are
548
548
  // what we want.
549
549
  const agentIdentity = pickTextBlock(systemBlocks[1]);
@@ -786,7 +786,7 @@ export function _resetInstalledVersionProbeForTest() {
786
786
  */
787
787
  export const SUPPORTED_CC_RANGE = {
788
788
  min: '1.0.0',
789
- maxTested: '2.1.203',
789
+ maxTested: '2.1.204',
790
790
  };
791
791
  /**
792
792
  * Compare two dotted-numeric version strings. Returns negative if `a<b`,
package/dist/proxy.js CHANGED
@@ -2049,7 +2049,7 @@ export async function startProxy(opts = {}) {
2049
2049
  // current Claude Code, which sends none. dario#528.
2050
2050
  const cch = hasCchSeed(cliVersion) ? computeCch() : null;
2051
2051
  const billingTag = buildBillingTag(cliVersion, cch);
2052
- // 1h cache TTL, matching real CC (see CC_CACHE_CONTROL / dario#678).
2052
+ // Plain 5m ephemeral, matching real CC (see CC_CACHE_CONTROL / dario#678).
2053
2053
  const CACHE_EPHEMERAL = CC_CACHE_CONTROL;
2054
2054
  // Session stickiness: rebind the pre-selected pool account to
2055
2055
  // whatever the sticky-key resolver picks. If this is a new
@@ -70,6 +70,14 @@ export declare class ProxyClient {
70
70
  * tab can render "unknown" without crashing.
71
71
  */
72
72
  getOverageGuard(): Promise<OverageGuardStatus | null>;
73
+ /**
74
+ * Advertised model ids — GET /v1/models (the upstream-autodetected
75
+ * catalog with the baked fallback, so a running proxy always answers).
76
+ * Returns the raw id list (base ids + generated `[1m]` variants) or
77
+ * null on any error so the Status tab renders without a Models panel
78
+ * instead of crashing.
79
+ */
80
+ listModels(): Promise<string[] | null>;
73
81
  /**
74
82
  * Clear the overage-guard halt state. POSTs /admin/resume. Returns the
75
83
  * server's response (`wasHalted` indicates whether the call actually
@@ -186,6 +186,24 @@ export class ProxyClient {
186
186
  return null;
187
187
  }
188
188
  }
189
+ /**
190
+ * Advertised model ids — GET /v1/models (the upstream-autodetected
191
+ * catalog with the baked fallback, so a running proxy always answers).
192
+ * Returns the raw id list (base ids + generated `[1m]` variants) or
193
+ * null on any error so the Status tab renders without a Models panel
194
+ * instead of crashing.
195
+ */
196
+ async listModels() {
197
+ try {
198
+ const r = await this.getJson('/v1/models');
199
+ if (!Array.isArray(r.data))
200
+ return null;
201
+ return r.data.map((m) => String(m.id ?? '')).filter((id) => id.length > 0);
202
+ }
203
+ catch {
204
+ return null;
205
+ }
206
+ }
189
207
  /**
190
208
  * Clear the overage-guard halt state. POSTs /admin/resume. Returns the
191
209
  * server's response (`wasHalted` indicates whether the call actually
@@ -18,6 +18,12 @@
18
18
  * │ schema: v1 │
19
19
  * │ …per-knob effective values (read-only) │
20
20
  * └─────────────────────────────────────────────────┘
21
+ * ┌─ Models ────────────────────────────────────────┐
22
+ * │ claude-fable-5 +[1m] │
23
+ * │ claude-opus-4-8 +[1m] │
24
+ * │ claude-sonnet-5 +[1m] │
25
+ * │ …the advertised catalog, live from /v1/models │
26
+ * └─────────────────────────────────────────────────┘
21
27
  */
22
28
  import type { Tab, TabContext } from '../tab.js';
23
29
  import type { OverageGuardStatus } from '../proxy-client.js';
@@ -32,6 +38,8 @@ export interface StatusState {
32
38
  } | null;
33
39
  /** Config-file load source: file | missing | invalid. */
34
40
  configSource: 'file' | 'missing' | 'invalid' | null;
41
+ /** Advertised model ids from /v1/models — null if unreachable. */
42
+ models: string[] | null;
35
43
  /** Overage-guard state from /admin/resume — null if unreachable. */
36
44
  overageGuard: OverageGuardStatus | null;
37
45
  /** Transient: did we just attempt a manual resume? */
@@ -44,6 +52,18 @@ export interface StatusState {
44
52
  error: string | null;
45
53
  }
46
54
  export declare const StatusTab: Tab<StatusState>;
55
+ /**
56
+ * Fold `<base>[1m]` variants onto their base id: the /v1/models listing
57
+ * advertises both forms for every long-context-capable family, and showing
58
+ * fourteen rows for seven models is noise. Preserves the catalog's order
59
+ * (family rank, version desc). A `[1m]`-only id with no base row (shouldn't
60
+ * happen — the generator always emits the pair) still gets its own row so
61
+ * nothing silently disappears. Exported for unit testing.
62
+ */
63
+ export declare function foldLongContextVariants(ids: readonly string[]): Array<{
64
+ base: string;
65
+ has1m: boolean;
66
+ }>;
47
67
  /**
48
68
  * Refresh the Status tab's data — probe /health, load config file
49
69
  * metadata. Exported separately so the parent can re-invoke on key
@@ -18,6 +18,12 @@
18
18
  * │ schema: v1 │
19
19
  * │ …per-knob effective values (read-only) │
20
20
  * └─────────────────────────────────────────────────┘
21
+ * ┌─ Models ────────────────────────────────────────┐
22
+ * │ claude-fable-5 +[1m] │
23
+ * │ claude-opus-4-8 +[1m] │
24
+ * │ claude-sonnet-5 +[1m] │
25
+ * │ …the advertised catalog, live from /v1/models │
26
+ * └─────────────────────────────────────────────────┘
21
27
  */
22
28
  import { fg, dim, brand } from '../render.js';
23
29
  import { renderKvRow } from '../layout.js';
@@ -30,6 +36,7 @@ export const StatusTab = {
30
36
  loading: true,
31
37
  health: null,
32
38
  configSource: null,
39
+ models: null,
33
40
  overageGuard: null,
34
41
  resumePending: false,
35
42
  resumeMessage: null,
@@ -112,6 +119,18 @@ export const StatusTab = {
112
119
  : dim('not loaded');
113
120
  lines.push(' ' + renderKvRow('Source', sourceLabel, w - 4));
114
121
  lines.push('');
122
+ // ── Models section ─────────────────────────────────────────
123
+ // Live from the proxy's /v1/models (upstream-autodetected catalog,
124
+ // baked fallback) so newly-shipped families — Sonnet 5, Fable 5 —
125
+ // show up here without a TUI change. `[1m]` variants are folded
126
+ // onto their base id as a +[1m] marker instead of doubling the list.
127
+ if (state.models && state.models.length > 0) {
128
+ lines.push(' ' + brand('Models'));
129
+ for (const row of foldLongContextVariants(state.models)) {
130
+ lines.push(' ' + renderKvRow(row.base, row.has1m ? dim('+[1m]') : '', w - 4));
131
+ }
132
+ lines.push('');
133
+ }
115
134
  // ── Overage-guard section (v4.1, dario#288) ────────────────
116
135
  if (state.overageGuard) {
117
136
  lines.push(' ' + brand('Overage-guard'));
@@ -144,6 +163,40 @@ export const StatusTab = {
144
163
  return lines.join('\n');
145
164
  },
146
165
  };
166
+ /**
167
+ * Fold `<base>[1m]` variants onto their base id: the /v1/models listing
168
+ * advertises both forms for every long-context-capable family, and showing
169
+ * fourteen rows for seven models is noise. Preserves the catalog's order
170
+ * (family rank, version desc). A `[1m]`-only id with no base row (shouldn't
171
+ * happen — the generator always emits the pair) still gets its own row so
172
+ * nothing silently disappears. Exported for unit testing.
173
+ */
174
+ export function foldLongContextVariants(ids) {
175
+ const bases = [];
176
+ const seen = new Map();
177
+ for (const id of ids) {
178
+ const m = id.match(/^(.*)\[1m\]$/);
179
+ if (m) {
180
+ const existing = seen.get(m[1]);
181
+ if (existing) {
182
+ existing.has1m = true;
183
+ continue;
184
+ }
185
+ // [1m] before (or without) its base — record under the base id.
186
+ const row = { base: m[1], has1m: true };
187
+ seen.set(m[1], row);
188
+ bases.push(row);
189
+ continue;
190
+ }
191
+ const existing = seen.get(id);
192
+ if (existing)
193
+ continue;
194
+ const row = { base: id, has1m: false };
195
+ seen.set(id, row);
196
+ bases.push(row);
197
+ }
198
+ return bases;
199
+ }
147
200
  function formatDuration(ms) {
148
201
  if (ms <= 0)
149
202
  return '0s';
@@ -175,13 +228,15 @@ export async function refreshStatus(ctx) {
175
228
  catch (e) {
176
229
  error = e.message;
177
230
  }
178
- // Overage-guard state — best-effort; never throws (proxy-client wraps the
179
- // GET in try/catch and returns null). Surface as 'unknown' when null.
231
+ // Overage-guard state + model catalog — best-effort; never throw
232
+ // (proxy-client wraps both GETs in try/catch and returns null).
180
233
  const overageGuard = await ctx.client.getOverageGuard();
234
+ const models = await ctx.client.listModels();
181
235
  return {
182
236
  loading: false,
183
237
  health,
184
238
  configSource: fileResult.source,
239
+ models,
185
240
  overageGuard,
186
241
  resumePending: false,
187
242
  resumeMessage: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "4.8.141",
3
+ "version": "4.8.143",
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": {