@coder/ai-sdk-agent 0.6.0 → 0.6.1
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/README.md +6 -2
- package/dist/index.d.ts +12 -9
- package/dist/index.js +150 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -723,8 +723,12 @@ CoderAgent (implements ai.Agent)
|
|
|
723
723
|
- One `doStream` call advances the chat until it **settles** (`waiting`/`completed`) or
|
|
724
724
|
**pauses** for a client tool (`requires_action`). The SDK loop and the server‑side loop
|
|
725
725
|
mesh at the client‑tool boundary, so there's no double loop.
|
|
726
|
-
- Streaming text is emitted from `message_part` deltas;
|
|
727
|
-
|
|
726
|
+
- Streaming text is emitted from `message_part` deltas; every `message` snapshot is then
|
|
727
|
+
reconciled against a per‑message emitted‑content ledger, so nothing double‑counts: a
|
|
728
|
+
trailing snapshot after deltas is a no‑op, a fast snapshot‑only turn emits in full, a
|
|
729
|
+
message that commits while the stream is redialing yields exactly its missing tail, and a
|
|
730
|
+
revision that appends to an earlier message yields the appended suffix (rewrites that
|
|
731
|
+
can't be expressed as deltas are safely suppressed).
|
|
728
732
|
|
|
729
733
|
## Durable workflows (Vercel Workflow, step functions, …)
|
|
730
734
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1127,15 +1127,18 @@ declare class CoderLanguageModel implements LanguageModelV4 {
|
|
|
1127
1127
|
* Translates one chatd turn's {@link ChatStreamEvent} stream into a sequence of
|
|
1128
1128
|
* `LanguageModelV4StreamPart`s for the AI SDK.
|
|
1129
1129
|
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1130
|
+
* Text/reasoning is reconciled against a per-message emitted-content ledger:
|
|
1131
|
+
* `message_part` deltas are emitted as they stream (they carry no message id,
|
|
1132
|
+
* so they accumulate as *pending* content), and each assistant `message`
|
|
1133
|
+
* snapshot attributes the pending content to its id and emits whatever suffix
|
|
1134
|
+
* of the snapshot's full content is still missing. That makes fast
|
|
1135
|
+
* snapshot-only messages (everything missing), trailing snapshots after deltas
|
|
1136
|
+
* (nothing missing), commit-during-disconnect snapshots (the tail the lost
|
|
1137
|
+
* deltas never delivered), and append-revisions of earlier messages (the
|
|
1138
|
+
* appended suffix) all the same operation — and a snapshot whose content does
|
|
1139
|
+
* not extend what was emitted (a byte-identical replay, or a rewrite that
|
|
1140
|
+
* cannot be expressed as deltas) is safely suppressed instead of
|
|
1141
|
+
* double-emitted.
|
|
1139
1142
|
*
|
|
1140
1143
|
* Client (custom) tool calls are emitted from the reliable `action_required`
|
|
1141
1144
|
* event and left for the AI SDK to execute. chatd's own server-side tools are
|
package/dist/index.js
CHANGED
|
@@ -249,6 +249,7 @@ async function* streamChatEventsLoop(options) {
|
|
|
249
249
|
const query = options.afterId !== void 0 ? `?after_id=${options.afterId}` : "";
|
|
250
250
|
const url = `${wsBase}${path}${query}`;
|
|
251
251
|
let maxCommittedId = options.afterId;
|
|
252
|
+
const lastMessageJson = /* @__PURE__ */ new Map();
|
|
252
253
|
let lastHistoryVersion;
|
|
253
254
|
let lastGenerationAttempt;
|
|
254
255
|
let lastSeq;
|
|
@@ -391,9 +392,13 @@ async function* streamChatEventsLoop(options) {
|
|
|
391
392
|
sawUntrackableDelta = true;
|
|
392
393
|
}
|
|
393
394
|
} else if (next.type === "message" && next.message) {
|
|
395
|
+
const serialized = JSON.stringify(next.message);
|
|
396
|
+
if (lastMessageJson.get(next.message.id) !== serialized) {
|
|
397
|
+
lastMessageJson.set(next.message.id, serialized);
|
|
398
|
+
progress = true;
|
|
399
|
+
}
|
|
394
400
|
if (maxCommittedId === void 0 || next.message.id > maxCommittedId) {
|
|
395
401
|
maxCommittedId = next.message.id;
|
|
396
|
-
progress = true;
|
|
397
402
|
sawUntrackableDelta = false;
|
|
398
403
|
}
|
|
399
404
|
}
|
|
@@ -1283,12 +1288,21 @@ function accumulateUsage(byMessageId) {
|
|
|
1283
1288
|
function jsonResult(value) {
|
|
1284
1289
|
return value ?? {};
|
|
1285
1290
|
}
|
|
1291
|
+
function joinContent(content, kind) {
|
|
1292
|
+
let full = "";
|
|
1293
|
+
for (const p of content) if (p.type === kind) full += p.text ?? "";
|
|
1294
|
+
return full;
|
|
1295
|
+
}
|
|
1286
1296
|
var TurnTranslator = class {
|
|
1287
1297
|
#dynamicToolNames;
|
|
1288
1298
|
#submittedToolCallIds;
|
|
1289
1299
|
#seq = 0;
|
|
1290
|
-
|
|
1291
|
-
|
|
1300
|
+
// `pending` accumulates delta content that has been emitted but not yet
|
|
1301
|
+
// attributed to a message id (deltas carry none); the next assistant
|
|
1302
|
+
// snapshot claims it. It survives block closes — a closed block loses its
|
|
1303
|
+
// stream id, never the record of what was emitted.
|
|
1304
|
+
#text = { id: void 0, pending: "" };
|
|
1305
|
+
#reasoning = { id: void 0, pending: "" };
|
|
1292
1306
|
#currentAssistantId;
|
|
1293
1307
|
// Whether text/reasoning deltas arrived since the last assistant `message`
|
|
1294
1308
|
// snapshot — i.e. the next assistant snapshot is the trailing snapshot of the
|
|
@@ -1303,11 +1317,22 @@ var TurnTranslator = class {
|
|
|
1303
1317
|
// the same message replaces its earlier entry instead of double-counting
|
|
1304
1318
|
// (chatd re-sends full snapshots on revision bumps and history resets).
|
|
1305
1319
|
#usageByMessageId = /* @__PURE__ */ new Map();
|
|
1306
|
-
//
|
|
1307
|
-
//
|
|
1308
|
-
//
|
|
1309
|
-
//
|
|
1310
|
-
|
|
1320
|
+
// The emitted-content ledger: the text/reasoning this translator has emitted
|
|
1321
|
+
// for each processed assistant message id. A redial replays the turn from
|
|
1322
|
+
// its original cursor and chatd re-sends full snapshots on revision bumps,
|
|
1323
|
+
// so already-processed messages arrive again — the ledger is what lets a
|
|
1324
|
+
// byte-identical replay reconcile to a no-op while an APPENDING revision (or
|
|
1325
|
+
// a commit-during-disconnect snapshot) yields exactly its missing suffix
|
|
1326
|
+
// (see #ingestMessage). `substantive` records whether any processed snapshot
|
|
1327
|
+
// of the message carried content (text, reasoning, tools, or sources) —
|
|
1328
|
+
// false only for announce-style empty snapshots, whose in-flight deltas are
|
|
1329
|
+
// still claimable by a same-id commit.
|
|
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.
|
|
1335
|
+
#deferredRevisions = /* @__PURE__ */ new Map();
|
|
1311
1336
|
#error;
|
|
1312
1337
|
#terminalStatus;
|
|
1313
1338
|
#maxMessageId = 0;
|
|
@@ -1342,7 +1367,6 @@ var TurnTranslator = class {
|
|
|
1342
1367
|
if (this.#reasoning.id) this.#closeReasoning(out);
|
|
1343
1368
|
if (!this.#text.id) {
|
|
1344
1369
|
this.#text.id = `text-${++this.#seq}`;
|
|
1345
|
-
this.#text.len = 0;
|
|
1346
1370
|
out.push({ type: "text-start", id: this.#text.id });
|
|
1347
1371
|
}
|
|
1348
1372
|
}
|
|
@@ -1350,14 +1374,12 @@ var TurnTranslator = class {
|
|
|
1350
1374
|
if (this.#text.id) {
|
|
1351
1375
|
out.push({ type: "text-end", id: this.#text.id });
|
|
1352
1376
|
this.#text.id = void 0;
|
|
1353
|
-
this.#text.len = 0;
|
|
1354
1377
|
}
|
|
1355
1378
|
}
|
|
1356
1379
|
#openReasoning(out) {
|
|
1357
1380
|
if (this.#text.id) this.#closeText(out);
|
|
1358
1381
|
if (!this.#reasoning.id) {
|
|
1359
1382
|
this.#reasoning.id = `reasoning-${++this.#seq}`;
|
|
1360
|
-
this.#reasoning.len = 0;
|
|
1361
1383
|
out.push({ type: "reasoning-start", id: this.#reasoning.id });
|
|
1362
1384
|
}
|
|
1363
1385
|
}
|
|
@@ -1365,31 +1387,40 @@ var TurnTranslator = class {
|
|
|
1365
1387
|
if (this.#reasoning.id) {
|
|
1366
1388
|
out.push({ type: "reasoning-end", id: this.#reasoning.id });
|
|
1367
1389
|
this.#reasoning.id = void 0;
|
|
1368
|
-
this.#reasoning.len = 0;
|
|
1369
1390
|
}
|
|
1370
1391
|
}
|
|
1371
|
-
#
|
|
1372
|
-
if (full.length <= this.#text.len && this.#text.id) return;
|
|
1392
|
+
#emitTextDelta(out, delta) {
|
|
1373
1393
|
this.#openText(out);
|
|
1374
|
-
|
|
1375
|
-
out.push({
|
|
1376
|
-
type: "text-delta",
|
|
1377
|
-
id: this.#text.id,
|
|
1378
|
-
delta: full.slice(this.#text.len)
|
|
1379
|
-
});
|
|
1380
|
-
this.#text.len = full.length;
|
|
1381
|
-
}
|
|
1394
|
+
out.push({ type: "text-delta", id: this.#text.id, delta });
|
|
1382
1395
|
}
|
|
1383
|
-
#
|
|
1384
|
-
if (full.length <= this.#reasoning.len && this.#reasoning.id) return;
|
|
1396
|
+
#emitReasoningDelta(out, delta) {
|
|
1385
1397
|
this.#openReasoning(out);
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1398
|
+
out.push({ type: "reasoning-delta", id: this.#reasoning.id, delta });
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Reconciles a replayed/revised snapshot of an EARLIER (non-current)
|
|
1402
|
+
* assistant message against its ledger entry: an APPENDING revision emits
|
|
1403
|
+
* its suffix, anything else is a no-op. The suffix is bracketed in its own
|
|
1404
|
+
* block(s) — an open block belongs to the CURRENT step, and splicing another
|
|
1405
|
+
* message's content into it would corrupt both — and the interrupted step's
|
|
1406
|
+
* next delta simply opens a fresh block (its pending content is tracked
|
|
1407
|
+
* independently of blocks).
|
|
1408
|
+
*/
|
|
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;
|
|
1413
|
+
this.#closeText(out);
|
|
1414
|
+
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);
|
|
1393
1424
|
}
|
|
1394
1425
|
}
|
|
1395
1426
|
// --- tool helpers ---------------------------------------------------------
|
|
@@ -1513,21 +1544,19 @@ var TurnTranslator = class {
|
|
|
1513
1544
|
const part = mp.part;
|
|
1514
1545
|
switch (part.type) {
|
|
1515
1546
|
case "text":
|
|
1516
|
-
this.#text.sawDelta = true;
|
|
1517
1547
|
this.#deltasSinceSnapshot = true;
|
|
1518
1548
|
this.#openText(out);
|
|
1519
1549
|
if (part.text) {
|
|
1520
1550
|
out.push({ type: "text-delta", id: this.#text.id, delta: part.text });
|
|
1521
|
-
this.#text.
|
|
1551
|
+
this.#text.pending += part.text;
|
|
1522
1552
|
}
|
|
1523
1553
|
break;
|
|
1524
1554
|
case "reasoning":
|
|
1525
|
-
this.#reasoning.sawDelta = true;
|
|
1526
1555
|
this.#deltasSinceSnapshot = true;
|
|
1527
1556
|
this.#openReasoning(out);
|
|
1528
1557
|
if (part.text) {
|
|
1529
1558
|
out.push({ type: "reasoning-delta", id: this.#reasoning.id, delta: part.text });
|
|
1530
|
-
this.#reasoning.
|
|
1559
|
+
this.#reasoning.pending += part.text;
|
|
1531
1560
|
}
|
|
1532
1561
|
break;
|
|
1533
1562
|
case "tool-call":
|
|
@@ -1553,42 +1582,56 @@ var TurnTranslator = class {
|
|
|
1553
1582
|
}
|
|
1554
1583
|
const content = message.content ?? [];
|
|
1555
1584
|
if (message.role === "assistant") {
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1585
|
+
const fullText = joinContent(content, "text");
|
|
1586
|
+
const fullReasoning = joinContent(content, "reasoning");
|
|
1587
|
+
const known = this.#emittedByMessageId.get(message.id);
|
|
1588
|
+
if (known && message.id !== this.#currentAssistantId) {
|
|
1589
|
+
this.#reconcileRevision(out, known, fullText, fullReasoning);
|
|
1590
|
+
} else if (known && this.#deltasSinceSnapshot && known.substantive) {
|
|
1591
|
+
this.#deferredRevisions.set(message.id, { text: fullText, reasoning: fullReasoning });
|
|
1592
|
+
} else {
|
|
1593
|
+
if (this.#currentAssistantId !== void 0 && this.#currentAssistantId !== message.id && !this.#deltasSinceSnapshot) {
|
|
1594
|
+
this.#closeText(out);
|
|
1595
|
+
this.#closeReasoning(out);
|
|
1563
1596
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1597
|
+
this.#currentAssistantId = message.id;
|
|
1598
|
+
const rec = known ?? { text: "", reasoning: "", substantive: false };
|
|
1599
|
+
const textAttributed = rec.text + this.#text.pending;
|
|
1600
|
+
const reasoningAttributed = rec.reasoning + this.#reasoning.pending;
|
|
1601
|
+
const textOk = fullText.startsWith(textAttributed);
|
|
1602
|
+
const reasoningOk = fullReasoning.startsWith(reasoningAttributed);
|
|
1603
|
+
let seenText = 0;
|
|
1604
|
+
let seenReasoning = 0;
|
|
1605
|
+
for (const p of content) {
|
|
1606
|
+
if (p.type === "text") {
|
|
1607
|
+
const end = seenText + (p.text ?? "").length;
|
|
1608
|
+
const from = Math.max(seenText, textAttributed.length);
|
|
1609
|
+
if (textOk && end > from) this.#emitTextDelta(out, fullText.slice(from, end));
|
|
1610
|
+
seenText = end;
|
|
1611
|
+
} else if (p.type === "reasoning") {
|
|
1612
|
+
const end = seenReasoning + (p.text ?? "").length;
|
|
1613
|
+
const from = Math.max(seenReasoning, reasoningAttributed.length);
|
|
1614
|
+
if (reasoningOk && end > from)
|
|
1615
|
+
this.#emitReasoningDelta(out, fullReasoning.slice(from, end));
|
|
1616
|
+
seenReasoning = end;
|
|
1617
|
+
}
|
|
1578
1618
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1619
|
+
if (textOk) rec.text = fullText;
|
|
1620
|
+
if (reasoningOk) rec.reasoning = fullReasoning;
|
|
1621
|
+
this.#text.pending = "";
|
|
1622
|
+
this.#reasoning.pending = "";
|
|
1623
|
+
rec.substantive ||= content.some(
|
|
1624
|
+
(p) => p.type === "text" || p.type === "reasoning" ? (p.text ?? "").length > 0 : true
|
|
1625
|
+
);
|
|
1626
|
+
this.#emittedByMessageId.set(message.id, rec);
|
|
1627
|
+
this.#deltasSinceSnapshot = false;
|
|
1628
|
+
for (const [id, deferred] of this.#deferredRevisions) {
|
|
1629
|
+
if (id === message.id) continue;
|
|
1630
|
+
const deferredRec = this.#emittedByMessageId.get(id);
|
|
1631
|
+
if (deferredRec)
|
|
1632
|
+
this.#reconcileRevision(out, deferredRec, deferred.text, deferred.reasoning);
|
|
1633
|
+
this.#deferredRevisions.delete(id);
|
|
1584
1634
|
}
|
|
1585
|
-
};
|
|
1586
|
-
if (this.#reasoning.id) {
|
|
1587
|
-
emitSnapshotReasoning();
|
|
1588
|
-
emitSnapshotText();
|
|
1589
|
-
} else {
|
|
1590
|
-
emitSnapshotText();
|
|
1591
|
-
emitSnapshotReasoning();
|
|
1592
1635
|
}
|
|
1593
1636
|
for (const part of content) {
|
|
1594
1637
|
if (part.type === "tool-call" && !this.#isClientTool(part.tool_name))
|
|
@@ -1597,7 +1640,6 @@ var TurnTranslator = class {
|
|
|
1597
1640
|
this.#emitServerToolResult(out, part);
|
|
1598
1641
|
else if (part.type === "source") this.#emitSource(out, part);
|
|
1599
1642
|
}
|
|
1600
|
-
this.#deltasSinceSnapshot = false;
|
|
1601
1643
|
} else if (message.role === "tool") {
|
|
1602
1644
|
for (const part of content) {
|
|
1603
1645
|
if (part.type === "tool-result" && !this.#isClientTool(part.tool_name))
|
|
@@ -1854,7 +1896,9 @@ var CoderLanguageModel = class {
|
|
|
1854
1896
|
* {@link #runTurnInner} once known) — a retry-safe stream failure on a
|
|
1855
1897
|
* freshly created chat discards the session (clearing `this.#chatId`)
|
|
1856
1898
|
* before the finally runs, and the settle must still name the chat that
|
|
1857
|
-
* the preceding `ws:*`/`http:*` events identified.
|
|
1899
|
+
* the preceding `ws:*`/`http:*` events identified. The same out-param
|
|
1900
|
+
* carries the translator's terminal status and the internal-timeout
|
|
1901
|
+
* marker for generator-return exits (see {@link SegmentChatRef}).
|
|
1858
1902
|
*/
|
|
1859
1903
|
async *#runTurn(options, isConsumerCancel) {
|
|
1860
1904
|
const emit = this.#emitTransportEvent;
|
|
@@ -1899,8 +1943,11 @@ var CoderLanguageModel = class {
|
|
|
1899
1943
|
};
|
|
1900
1944
|
const chatId = segmentChat.chatId ?? this.#chatId;
|
|
1901
1945
|
if (chatId !== void 0) settleEvent.chatId = chatId;
|
|
1902
|
-
if (!failed && finishReason === void 0
|
|
1903
|
-
if (
|
|
1946
|
+
if (!failed && finishReason === void 0) {
|
|
1947
|
+
if (segmentChat.timeoutError) {
|
|
1948
|
+
failure = segmentChat.timeoutError;
|
|
1949
|
+
failed = true;
|
|
1950
|
+
} else if (options.abortSignal?.aborted && !isConsumerCancel?.()) {
|
|
1904
1951
|
failure = options.abortSignal.reason;
|
|
1905
1952
|
failed = true;
|
|
1906
1953
|
}
|
|
@@ -1914,6 +1961,8 @@ var CoderLanguageModel = class {
|
|
|
1914
1961
|
if (finishReason !== void 0) {
|
|
1915
1962
|
if (finishReason.raw !== void 0) settleEvent.status = finishReason.raw;
|
|
1916
1963
|
if (!failed) settleEvent.finishReason = finishReason.unified;
|
|
1964
|
+
} else if (segmentChat.terminalStatus !== void 0) {
|
|
1965
|
+
settleEvent.status = segmentChat.terminalStatus;
|
|
1917
1966
|
}
|
|
1918
1967
|
emit(settleEvent);
|
|
1919
1968
|
}
|
|
@@ -1945,13 +1994,14 @@ var CoderLanguageModel = class {
|
|
|
1945
1994
|
});
|
|
1946
1995
|
};
|
|
1947
1996
|
signal?.addEventListener("abort", interrupt, { once: true });
|
|
1997
|
+
const timeoutError = () => new CoderChatError({
|
|
1998
|
+
message: `Coder Agent turn exceeded its ${timeoutMs}ms requestTimeoutMs budget.`,
|
|
1999
|
+
kind: "timeout",
|
|
2000
|
+
retryable: true
|
|
2001
|
+
});
|
|
1948
2002
|
const throwIfAborted = () => {
|
|
1949
2003
|
if (timeoutSignal?.aborted) {
|
|
1950
|
-
throw
|
|
1951
|
-
message: `Coder Agent turn exceeded its ${timeoutMs}ms requestTimeoutMs budget.`,
|
|
1952
|
-
kind: "timeout",
|
|
1953
|
-
retryable: true
|
|
1954
|
-
});
|
|
2004
|
+
throw timeoutError();
|
|
1955
2005
|
}
|
|
1956
2006
|
if (signal?.aborted) {
|
|
1957
2007
|
throw externalSignal?.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
@@ -1978,6 +2028,7 @@ var CoderLanguageModel = class {
|
|
|
1978
2028
|
let turnCreatedChat = false;
|
|
1979
2029
|
let uploadedAttachment = false;
|
|
1980
2030
|
let chatHasWorkspace = Boolean(this.#config.workspaceId);
|
|
2031
|
+
let chatHasMcpServers = Boolean(this.#config.mcpServerIds?.length);
|
|
1981
2032
|
if (this.#chatId && this.#lastSeenMessageId === 0) {
|
|
1982
2033
|
const { messages } = await this.#config.client.getMessages(
|
|
1983
2034
|
this.#chatId,
|
|
@@ -2011,6 +2062,7 @@ var CoderLanguageModel = class {
|
|
|
2011
2062
|
this.#chatId = chat.id;
|
|
2012
2063
|
turnCreatedChat = true;
|
|
2013
2064
|
if (chat.workspace_id) chatHasWorkspace = true;
|
|
2065
|
+
if (chat.mcp_server_ids?.length) chatHasMcpServers = true;
|
|
2014
2066
|
afterId = this.#lastSeenMessageId > 0 ? this.#lastSeenMessageId : void 0;
|
|
2015
2067
|
} else {
|
|
2016
2068
|
const resp = await this.#config.client.createChatMessage(
|
|
@@ -2109,7 +2161,20 @@ var CoderLanguageModel = class {
|
|
|
2109
2161
|
}
|
|
2110
2162
|
stream.consumed(next);
|
|
2111
2163
|
if (raced.done) break;
|
|
2112
|
-
|
|
2164
|
+
const parts = translator.ingest(raced.value);
|
|
2165
|
+
if (raced.value.type === "error" && translator.error && !translator.terminalStatus) {
|
|
2166
|
+
const peek = stream.read();
|
|
2167
|
+
const bound = startGraceTimer(0);
|
|
2168
|
+
const peeked = await raceSegment(Promise.race([peek, bound.expired])).catch(
|
|
2169
|
+
() => GRACE_EXPIRED
|
|
2170
|
+
);
|
|
2171
|
+
bound.cancel();
|
|
2172
|
+
if (peeked !== GRACE_EXPIRED && peeked !== SEGMENT_ABORTED && !peeked.done) {
|
|
2173
|
+
stream.consumed(peek);
|
|
2174
|
+
parts.push(...translator.ingest(peeked.value));
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
for (const part of parts) yield part;
|
|
2113
2178
|
const status = translator.terminalStatus;
|
|
2114
2179
|
if (status) {
|
|
2115
2180
|
if (status !== "requires_action") break;
|
|
@@ -2124,7 +2189,7 @@ var CoderLanguageModel = class {
|
|
|
2124
2189
|
if (turnCreatedChat) {
|
|
2125
2190
|
discardSession = true;
|
|
2126
2191
|
}
|
|
2127
|
-
const effectful = chatHasWorkspace ||
|
|
2192
|
+
const effectful = chatHasWorkspace || chatHasMcpServers || uploadedAttachment;
|
|
2128
2193
|
if (turnCreatedChat && !effectful) throw err;
|
|
2129
2194
|
throw new CoderStreamError({
|
|
2130
2195
|
message: `${err.message}; automatic retry is disabled because ${turnCreatedChat ? "the turn has server-side effects a replay would repeat (workspace/MCP tools that may have executed, or freshly uploaded attachments)" : "the chat has prior state (a retry would resubmit this turn's prompt as a new user turn)"}`,
|
|
@@ -2166,6 +2231,12 @@ var CoderLanguageModel = class {
|
|
|
2166
2231
|
} finally {
|
|
2167
2232
|
if (translator && translator.maxMessageId > this.#lastSeenMessageId)
|
|
2168
2233
|
this.#lastSeenMessageId = translator.maxMessageId;
|
|
2234
|
+
if (segmentChat) {
|
|
2235
|
+
if (translator?.terminalStatus !== void 0) {
|
|
2236
|
+
segmentChat.terminalStatus = translator.terminalStatus;
|
|
2237
|
+
}
|
|
2238
|
+
if (timeoutSignal?.aborted) segmentChat.timeoutError = timeoutError();
|
|
2239
|
+
}
|
|
2169
2240
|
signal?.removeEventListener("abort", interrupt);
|
|
2170
2241
|
if (!translator?.terminalStatus) interrupt();
|
|
2171
2242
|
if (!streamAcquired && this.#retainedStream) {
|