@coder/ai-sdk-agent 0.6.1 → 0.6.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.
package/dist/index.js CHANGED
@@ -1328,10 +1328,12 @@ var TurnTranslator = class {
1328
1328
  // false only for announce-style empty snapshots, whose in-flight deltas are
1329
1329
  // still claimable by a same-id commit.
1330
1330
  #emittedByMessageId = /* @__PURE__ */ new Map();
1331
- // Same-id revision snapshots that arrived while the NEXT step's deltas were
1332
- // open (latest wins). chatd sends each revision once on a healthy
1333
- // connection, so the suffix cannot wait for a replay — it is reconciled as
1334
- // soon as the next step's snapshot settles ownership of the pending deltas.
1331
+ // Same-id revision snapshots (their full wire content) that arrived while
1332
+ // the NEXT step's deltas were open (latest wins). chatd sends each revision
1333
+ // once on a healthy connection, so the suffix cannot wait for a replay — it
1334
+ // is reconciled as soon as the next step's snapshot settles ownership of
1335
+ // the pending deltas, or flushed by finish() when the segment ends without
1336
+ // one (see #78).
1335
1337
  #deferredRevisions = /* @__PURE__ */ new Map();
1336
1338
  #error;
1337
1339
  #terminalStatus;
@@ -1406,22 +1408,34 @@ var TurnTranslator = class {
1406
1408
  * next delta simply opens a fresh block (its pending content is tracked
1407
1409
  * independently of blocks).
1408
1410
  */
1409
- #reconcileRevision(out, rec, fullText, fullReasoning) {
1410
- const textSuffix = fullText.length > rec.text.length && fullText.startsWith(rec.text) ? fullText.slice(rec.text.length) : "";
1411
- const reasoningSuffix = fullReasoning.length > rec.reasoning.length && fullReasoning.startsWith(rec.reasoning) ? fullReasoning.slice(rec.reasoning.length) : "";
1412
- if (!textSuffix && !reasoningSuffix) return;
1411
+ #reconcileRevision(out, rec, content) {
1412
+ const fullText = joinContent(content, "text");
1413
+ const fullReasoning = joinContent(content, "reasoning");
1414
+ const textOk = fullText.length > rec.text.length && fullText.startsWith(rec.text);
1415
+ const reasoningOk = fullReasoning.length > rec.reasoning.length && fullReasoning.startsWith(rec.reasoning);
1416
+ if (!textOk && !reasoningOk) return;
1413
1417
  this.#closeText(out);
1414
1418
  this.#closeReasoning(out);
1415
- if (reasoningSuffix) {
1416
- this.#emitReasoningDelta(out, reasoningSuffix);
1417
- rec.reasoning = fullReasoning;
1418
- this.#closeReasoning(out);
1419
- }
1420
- if (textSuffix) {
1421
- this.#emitTextDelta(out, textSuffix);
1422
- rec.text = fullText;
1423
- this.#closeText(out);
1419
+ let seenText = 0;
1420
+ let seenReasoning = 0;
1421
+ for (const p of content) {
1422
+ if (p.type === "text") {
1423
+ const end = seenText + (p.text ?? "").length;
1424
+ const from = Math.max(seenText, rec.text.length);
1425
+ if (textOk && end > from) this.#emitTextDelta(out, fullText.slice(from, end));
1426
+ seenText = end;
1427
+ } else if (p.type === "reasoning") {
1428
+ const end = seenReasoning + (p.text ?? "").length;
1429
+ const from = Math.max(seenReasoning, rec.reasoning.length);
1430
+ if (reasoningOk && end > from)
1431
+ this.#emitReasoningDelta(out, fullReasoning.slice(from, end));
1432
+ seenReasoning = end;
1433
+ }
1424
1434
  }
1435
+ if (textOk) rec.text = fullText;
1436
+ if (reasoningOk) rec.reasoning = fullReasoning;
1437
+ this.#closeText(out);
1438
+ this.#closeReasoning(out);
1425
1439
  }
1426
1440
  // --- tool helpers ---------------------------------------------------------
1427
1441
  #isClientTool(name) {
@@ -1504,6 +1518,7 @@ var TurnTranslator = class {
1504
1518
  for (const tc of ev.action_required?.tool_calls ?? []) {
1505
1519
  if (this.#clientToolCalls.has(tc.tool_call_id)) continue;
1506
1520
  if (this.#submittedToolCallIds.has(tc.tool_call_id)) continue;
1521
+ this.#flushDeferredRevisions(out);
1507
1522
  this.#closeText(out);
1508
1523
  this.#closeReasoning(out);
1509
1524
  this.#clientToolCalls.add(tc.tool_call_id);
@@ -1521,6 +1536,7 @@ var TurnTranslator = class {
1521
1536
  case "error":
1522
1537
  if (ev.error) {
1523
1538
  this.#error = ev.error;
1539
+ this.#flushDeferredRevisions(out);
1524
1540
  out.push({ type: "error", error: new CoderChatError(ev.error) });
1525
1541
  }
1526
1542
  break;
@@ -1586,9 +1602,9 @@ var TurnTranslator = class {
1586
1602
  const fullReasoning = joinContent(content, "reasoning");
1587
1603
  const known = this.#emittedByMessageId.get(message.id);
1588
1604
  if (known && message.id !== this.#currentAssistantId) {
1589
- this.#reconcileRevision(out, known, fullText, fullReasoning);
1605
+ this.#reconcileRevision(out, known, content);
1590
1606
  } else if (known && this.#deltasSinceSnapshot && known.substantive) {
1591
- this.#deferredRevisions.set(message.id, { text: fullText, reasoning: fullReasoning });
1607
+ this.#deferredRevisions.set(message.id, content);
1592
1608
  } else {
1593
1609
  if (this.#currentAssistantId !== void 0 && this.#currentAssistantId !== message.id && !this.#deltasSinceSnapshot) {
1594
1610
  this.#closeText(out);
@@ -1628,8 +1644,7 @@ var TurnTranslator = class {
1628
1644
  for (const [id, deferred] of this.#deferredRevisions) {
1629
1645
  if (id === message.id) continue;
1630
1646
  const deferredRec = this.#emittedByMessageId.get(id);
1631
- if (deferredRec)
1632
- this.#reconcileRevision(out, deferredRec, deferred.text, deferred.reasoning);
1647
+ if (deferredRec) this.#reconcileRevision(out, deferredRec, deferred);
1633
1648
  this.#deferredRevisions.delete(id);
1634
1649
  }
1635
1650
  }
@@ -1649,8 +1664,27 @@ var TurnTranslator = class {
1649
1664
  }
1650
1665
  }
1651
1666
  // --- finish ---------------------------------------------------------------
1667
+ /**
1668
+ * Flushes revisions still deferred behind a delta-ownership race that never
1669
+ * settled — the racing step ended terminally without committing a snapshot,
1670
+ * so the drain in #ingestMessage never ran. chatd sent each revision once;
1671
+ * this is the segment's last chance to emit its suffix (#78). Attribution
1672
+ * care: the pending deltas' owner never committed, so they attribute to no
1673
+ * one — reconciliation is against the ledger alone, and only
1674
+ * ledger-extension suffixes may emit. Runs at finish() and, because
1675
+ * doGenerate() throws on a streamed error part and closes the generator
1676
+ * before finish() (issue #72), ahead of the error part in ingest().
1677
+ */
1678
+ #flushDeferredRevisions(out) {
1679
+ for (const [id, deferred] of this.#deferredRevisions) {
1680
+ const rec = this.#emittedByMessageId.get(id);
1681
+ if (rec) this.#reconcileRevision(out, rec, deferred);
1682
+ }
1683
+ this.#deferredRevisions.clear();
1684
+ }
1652
1685
  finish() {
1653
1686
  const out = [];
1687
+ this.#flushDeferredRevisions(out);
1654
1688
  this.#closeText(out);
1655
1689
  this.#closeReasoning(out);
1656
1690
  let unified;