@askalf/dario 5.5.58 → 5.5.59

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.
@@ -1605,17 +1605,21 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1605
1605
  continue;
1606
1606
  msg.content = msg.content.filter((b) => !(b.type === 'text' && (typeof b.text !== 'string' || b.text.trim() === '')));
1607
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.
1608
+ // A turn left with no blocks is dropped — but only MID-conversation, where
1609
+ // the API combines the now-adjacent same-role turns and where the #1066
1610
+ // session-killer lives (the empty turn sits in history, so every later
1611
+ // request carries it). BOTH roles: a user turn whose only block was empty,
1612
+ // and an assistant turn the filter emptied (a null/non-string text block the
1613
+ // proxy-level string scrub leaves behind, dario#1092) either one mid-stream
1614
+ // is "messages.N: content must contain at least one block" upstream. The
1615
+ // FINAL turn is deliberately left in place per the dario#1033 decision below:
1616
+ // popping it would expose the assistant turn behind it and convert an honest
1617
+ // "content must contain at least one block" into a misleading prefill
1618
+ // rejection. Hoisted with the filter (dario#1077): removing a block can empty
1619
+ // a turn on any path.
1616
1620
  for (let i = messages.length - 2; i >= 0; i--) {
1617
1621
  const m = messages[i];
1618
- if (m.role === 'user' && Array.isArray(m.content) && m.content.length === 0) {
1622
+ if (Array.isArray(m.content) && m.content.length === 0) {
1619
1623
  messages.splice(i, 1);
1620
1624
  }
1621
1625
  }
@@ -1661,6 +1665,33 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1661
1665
  // configure how NON-CC clients are dressed up as CC, which a genuine CC
1662
1666
  // client doesn't need.
1663
1667
  if (isGenuineCCClient(clientBody)) {
1668
+ // ── dario#1092: recover an empty FINAL user turn (genuine CC only) ──
1669
+ // CC's stream-interruption retry path can append a user turn with
1670
+ // content:[] as the final message. #1033 leaves an empty final user turn in
1671
+ // place on the GENERAL path so a malformed third-party client gets an honest
1672
+ // upstream error rather than a mangled conversation. But on a genuine CC
1673
+ // session that empty turn is CC's OWN retry artifact, and "let upstream name
1674
+ // it" is the #1066 session-killer: it sticks in history and every later
1675
+ // request dies with "messages.N: user messages must have non-empty content".
1676
+ // The retry's intent is to regenerate the response whose stream was cut, so
1677
+ // rewind to it — drop the empty user turn and the assistant turn it
1678
+ // interrupted, landing the request back on the last real user turn for a
1679
+ // clean regeneration.
1680
+ //
1681
+ // Bounded and distinct from the #37 runaway loop: that popped a NON-empty
1682
+ // trailing assistant with no empty user after it; this fires ONLY on a
1683
+ // trailing EMPTY user turn and pops at most the one pair. Mid-conversation
1684
+ // empties are already gone above, so a trailing empty user is the only shape
1685
+ // that can reach here, and the assistant behind it is real.
1686
+ {
1687
+ const last = messages.length > 0 ? messages[messages.length - 1] : undefined;
1688
+ if (last && last.role === 'user' && Array.isArray(last.content) && last.content.length === 0) {
1689
+ messages.pop();
1690
+ const prev = messages[messages.length - 1];
1691
+ if (prev && prev.role === 'assistant')
1692
+ messages.pop();
1693
+ }
1694
+ }
1664
1695
  const clientSystem = clientBody.system;
1665
1696
  const system = clientSystem.map((b, i) => {
1666
1697
  const copy = { ...b };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "5.5.58",
3
+ "version": "5.5.59",
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": {