@askalf/dario 5.5.51 → 5.5.53

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.
Files changed (2) hide show
  1. package/dist/cc-template.js +63 -29
  2. package/package.json +1 -1
@@ -1584,6 +1584,61 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1584
1584
  const messages = clientBody.messages || [];
1585
1585
  const clientTools = clientBody.tools;
1586
1586
  const stream = clientBody.stream ?? false;
1587
+ // ── Drop empty text blocks from history (ALL paths) ──
1588
+ // Upstream rejects empty text blocks OUTRIGHT — stamped or not — and treats
1589
+ // whitespace-only text the same way ("messages: text content blocks must be
1590
+ // non-empty"; with a breakpoint on it, "cache_control cannot be set for
1591
+ // empty text blocks"). One such block entering the transcript mid-run
1592
+ // therefore killed every subsequent request of the session (dario#1066).
1593
+ // Filtering here fixes the REQUEST, which no breakpoint guard can: skipping
1594
+ // the stamp still leaves the illegal block on the wire.
1595
+ //
1596
+ // Sits ABOVE the genuine-CC early return on purpose. #1067 placed this
1597
+ // below it, where real Claude Code — the one client whose messages are
1598
+ // forwarded verbatim — never reached it, and the #1066 session-killer
1599
+ // survived on exactly the client it hurts most (dario#1077). An empty text
1600
+ // block is not a wire shape the client is authority over; it is a
1601
+ // guaranteed upstream 400. `messages` aliases clientBody.messages, so
1602
+ // these mutations reach the genuine-CC body, which spreads clientBody.
1603
+ for (const msg of messages) {
1604
+ if (!Array.isArray(msg.content))
1605
+ continue;
1606
+ msg.content = msg.content.filter((b) => !(b.type === 'text' && (typeof b.text !== 'string' || b.text.trim() === '')));
1607
+ }
1608
+ // A USER turn left with no blocks is dropped — but only MID-conversation,
1609
+ // where the API combines the now-adjacent same-role turns and where the
1610
+ // #1066 session-killer lives (the empty turn sits in history, so every
1611
+ // later request carries it). The FINAL turn is deliberately left in place
1612
+ // per the dario#1033 decision below: popping it would expose the assistant
1613
+ // turn behind it and convert an honest "content must contain at least one
1614
+ // block" into a misleading prefill rejection. Hoisted with the filter
1615
+ // (dario#1077): removing a block can empty a turn on any path.
1616
+ for (let i = messages.length - 2; i >= 0; i--) {
1617
+ const m = messages[i];
1618
+ if (m.role === 'user' && Array.isArray(m.content) && m.content.length === 0) {
1619
+ messages.splice(i, 1);
1620
+ }
1621
+ }
1622
+ // A TRAILING assistant turn the filter emptied is dropped too — content: []
1623
+ // at the end reads as a prefill upstream, which Opus under adaptive thinking
1624
+ // + the claude-code beta refuses outright. This is a copy of the trailing
1625
+ // drop further down (which must ALSO stay there, running after the general
1626
+ // path's thinking strip — the shape it was written for); the copy exists so
1627
+ // the genuine-CC early return inherits it (dario#1077 follow-up: stage 3 of
1628
+ // 3, the "failure moves rather than disappears" case). Same constraints as
1629
+ // the original: assistant turns ONLY — popping an empty final user turn
1630
+ // would convert an honest "content must contain at least one block" into a
1631
+ // misleading prefill rejection (dario#1033), and popping a NON-empty
1632
+ // trailing assistant is the OpenClaw runaway loop (#37). Idempotent, so
1633
+ // running it twice on the general path changes nothing.
1634
+ while (messages.length > 0) {
1635
+ const last = messages[messages.length - 1];
1636
+ if (last.role === 'assistant' && Array.isArray(last.content) && last.content.length === 0) {
1637
+ messages.pop();
1638
+ continue;
1639
+ }
1640
+ break;
1641
+ }
1587
1642
  // ── Genuine Claude Code client → byte-faithful passthrough ──
1588
1643
  // A real CC request already IS the CC wire shape. Replacing its system
1589
1644
  // prompt with the template (prepending ~25KB per request shape) and
@@ -1600,9 +1655,11 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1600
1655
  // here, conversation in applyCcPromptCaching) so the 4-breakpoint
1601
1656
  // budget stays deterministic.
1602
1657
  // Messages, thinking, effort, max_tokens, top-level key order: untouched —
1603
- // the client is the authority on its own wire shape. Outranks the
1604
- // tool-mode flags: those configure how NON-CC clients are dressed up as
1605
- // CC, which a genuine CC client doesn't need.
1658
+ // the client is the authority on its own wire shape. One exception, the
1659
+ // empty-text filter above (dario#1077): a guaranteed upstream 400 is a
1660
+ // defect, not a wire-shape preference. Outranks the tool-mode flags: those
1661
+ // configure how NON-CC clients are dressed up as CC, which a genuine CC
1662
+ // client doesn't need.
1606
1663
  if (isGenuineCCClient(clientBody)) {
1607
1664
  const clientSystem = clientBody.system;
1608
1665
  const system = clientSystem.map((b, i) => {
@@ -1677,32 +1734,9 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1677
1734
  }
1678
1735
  }
1679
1736
  }
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
- }
1737
+ // (Empty text blocks and the mid-conversation empty-user-turn drop moved
1738
+ // above the genuine-CC branchdario#1077. Only the thinking strip stays
1739
+ // path-local: genuine CC forwards its signed thinking verbatim.)
1706
1740
  // ── Drop trailing empty turns ──
1707
1741
  // An assistant turn that was thinking-only before the strip above becomes
1708
1742
  // 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.51",
3
+ "version": "5.5.53",
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": {