@askalf/dario 5.5.48 → 5.5.49

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.
@@ -505,38 +505,6 @@ export declare const EXTENDED_CACHE_TTL_BETA = "extended-cache-ttl-2025-04-11";
505
505
  * is off or the beta is already present.
506
506
  */
507
507
  export declare function withForced1hBeta(beta: string, env?: Record<string, string | undefined>): string;
508
- /**
509
- * Place CC-style prompt-cache breakpoints on the conversation. The system
510
- * prompt is already cached at build time (2 system breakpoints); this adds a
511
- * rolling breakpoint on the last user message plus an anchor on the previous
512
- * one — total 4, the Anthropic max.
513
- *
514
- * Placement mirrors a live capture of CC v2.1.203 (dario#678):
515
- *
516
- * - Tools carry NO breakpoint. Real CC sends its tool array unstamped —
517
- * Anthropic renders tools -> system -> messages, so the system breakpoints
518
- * already cache the tools prefix. Stamping the last tool (pre-4.8.142) both
519
- * diverged from CC's wire shape and spent the fourth slot the conversation
520
- * anchor below needs.
521
- *
522
- * - The rolling breakpoint goes on the last USER message, not the last
523
- * message. CC skips trailing role:"system" injections (agent-type updates
524
- * etc.); stamping "the last message" meant any turn ending in one wrote no
525
- * conversation entry at all, and the next request re-paid the entire
526
- * history as fresh input.
527
- *
528
- * - The previous user message is anchored too. Anthropic's cache lookup
529
- * walks back at most ~20 content blocks from a breakpoint; one parallel-
530
- * tool turn (N tool_use + N tool_result blocks) can exceed that alone, and
531
- * the rolling breakpoint then can't reach the prior turn's entry — the
532
- * whole conversation re-bills at cache-WRITE cost every fan-out turn,
533
- * which is the dario#678 burn ("read every file" sessions draining the
534
- * Max window ~10x faster than direct CC). The anchor sits exactly where
535
- * the previous request's rolling breakpoint was, so the lookup hits it
536
- * positionally with no walk-back.
537
- *
538
- * Exported for unit testing.
539
- */
540
508
  export declare function applyCcPromptCaching(ccRequest: Record<string, unknown>, cacheControl: CacheControl): void;
541
509
  /**
542
510
  * Drop later tools whose exact name already appeared. Upstream rejects the
@@ -1446,6 +1446,13 @@ export function withForced1hBeta(beta, env = process.env) {
1446
1446
  *
1447
1447
  * Exported for unit testing.
1448
1448
  */
1449
+ /**
1450
+ * A text block upstream treats as empty. `cache_control` on one of these is a
1451
+ * hard 400, so breakpoint placement must skip them.
1452
+ */
1453
+ function isEmptyTextBlock(block) {
1454
+ return block?.type === 'text' && (typeof block.text !== 'string' || block.text === '');
1455
+ }
1449
1456
  export function applyCcPromptCaching(ccRequest, cacheControl) {
1450
1457
  // Tools — strip any stray client breakpoints (they'd count against the
1451
1458
  // 4-breakpoint budget) without mutating shared element objects
@@ -1475,7 +1482,18 @@ export function applyCcPromptCaching(ccRequest, cacheControl) {
1475
1482
  if (!Array.isArray(msg.content) || msg.content.length === 0)
1476
1483
  continue;
1477
1484
  const blocks = msg.content;
1478
- blocks[blocks.length - 1] = { ...blocks[blocks.length - 1], cache_control: cacheControl };
1485
+ // Walk back past empty text blocks. Upstream rejects the whole request
1486
+ // with 400 "cache_control cannot be set for empty text blocks", so a
1487
+ // trailing empty text block would otherwise kill every request from
1488
+ // that turn onward (dario#1066). A turn with nothing else to stamp is
1489
+ // skipped rather than stamped illegally — it keeps its cache entry via
1490
+ // the next turn's anchor.
1491
+ let bi = blocks.length - 1;
1492
+ while (bi >= 0 && isEmptyTextBlock(blocks[bi]))
1493
+ bi--;
1494
+ if (bi < 0)
1495
+ continue;
1496
+ blocks[bi] = { ...blocks[bi], cache_control: cacheControl };
1479
1497
  stamped++;
1480
1498
  }
1481
1499
  }
@@ -1659,6 +1677,32 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1659
1677
  }
1660
1678
  }
1661
1679
  }
1680
+ // ── Drop empty text blocks from history ──
1681
+ // Upstream rejects empty text blocks OUTRIGHT — stamped or not — and treats
1682
+ // whitespace-only text the same way ("messages: text content blocks must be
1683
+ // non-empty"; with a breakpoint on it, "cache_control cannot be set for
1684
+ // empty text blocks"). One such block entering the transcript mid-run
1685
+ // therefore killed every subsequent request of the session (dario#1066).
1686
+ // Filtering here fixes the REQUEST, which no breakpoint guard can: skipping
1687
+ // the stamp still leaves the illegal block on the wire.
1688
+ for (const msg of messages) {
1689
+ if (!Array.isArray(msg.content))
1690
+ continue;
1691
+ msg.content = msg.content.filter((b) => !(b.type === 'text' && (typeof b.text !== 'string' || b.text.trim() === '')));
1692
+ }
1693
+ // A USER turn left with no blocks is dropped — but only MID-conversation,
1694
+ // where the API combines the now-adjacent same-role turns and where the
1695
+ // #1066 session-killer lives (the empty turn sits in history, so every
1696
+ // later request carries it). The FINAL turn is deliberately left in place
1697
+ // per the dario#1033 decision below: popping it would expose the assistant
1698
+ // turn behind it and convert an honest "content must contain at least one
1699
+ // block" into a misleading prefill rejection.
1700
+ for (let i = messages.length - 2; i >= 0; i--) {
1701
+ const m = messages[i];
1702
+ if (m.role === 'user' && Array.isArray(m.content) && m.content.length === 0) {
1703
+ messages.splice(i, 1);
1704
+ }
1705
+ }
1662
1706
  // ── Drop trailing empty turns ──
1663
1707
  // An assistant turn that was thinking-only before the strip above becomes
1664
1708
  // content: []. Forwarding that shape makes Anthropic interpret the request
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "5.5.48",
3
+ "version": "5.5.49",
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": {