@astralform/js 4.9.0 → 4.9.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/dist/index.cjs +66 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +66 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1338,6 +1338,7 @@ function translateWireEvent(wire) {
|
|
|
1338
1338
|
|
|
1339
1339
|
// src/session.ts
|
|
1340
1340
|
var SSE_MAX_RECONNECTS = 6;
|
|
1341
|
+
var SSE_STALL_TIMEOUT_MS = 45e3;
|
|
1341
1342
|
var TOOL_RESULT_MAX_RETRIES = 3;
|
|
1342
1343
|
var CONVERSATION_PAGE_SIZE = 50;
|
|
1343
1344
|
function sseReconnectDelayMs(attempt) {
|
|
@@ -1589,14 +1590,23 @@ var ChatSession = class {
|
|
|
1589
1590
|
async consumeEventStream(jobId, conversationId, messageId, executeClientTools) {
|
|
1590
1591
|
const signal = this.abortController?.signal;
|
|
1591
1592
|
for (let attempt = 0; ; attempt++) {
|
|
1592
|
-
|
|
1593
|
+
if (signal?.aborted) return;
|
|
1594
|
+
const attemptController = new AbortController();
|
|
1595
|
+
const linkAbort = () => attemptController.abort();
|
|
1596
|
+
signal?.addEventListener("abort", linkAbort);
|
|
1593
1597
|
let sawTerminal;
|
|
1594
1598
|
try {
|
|
1599
|
+
const stream = this.client.streamJobEvents(
|
|
1600
|
+
jobId,
|
|
1601
|
+
this.lastSeq,
|
|
1602
|
+
attemptController.signal
|
|
1603
|
+
);
|
|
1595
1604
|
sawTerminal = await this.pumpStream(
|
|
1596
1605
|
stream,
|
|
1597
1606
|
conversationId,
|
|
1598
1607
|
messageId,
|
|
1599
|
-
executeClientTools
|
|
1608
|
+
executeClientTools,
|
|
1609
|
+
() => attemptController.abort()
|
|
1600
1610
|
);
|
|
1601
1611
|
} catch (err) {
|
|
1602
1612
|
if (signal?.aborted) return;
|
|
@@ -1606,6 +1616,8 @@ var ChatSession = class {
|
|
|
1606
1616
|
if (attempt >= SSE_MAX_RECONNECTS) throw err;
|
|
1607
1617
|
await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
|
|
1608
1618
|
continue;
|
|
1619
|
+
} finally {
|
|
1620
|
+
signal?.removeEventListener("abort", linkAbort);
|
|
1609
1621
|
}
|
|
1610
1622
|
if (sawTerminal || signal?.aborted) return;
|
|
1611
1623
|
if (attempt >= SSE_MAX_RECONNECTS) {
|
|
@@ -1618,35 +1630,66 @@ var ChatSession = class {
|
|
|
1618
1630
|
* Consume a single SSE stream to exhaustion. Returns whether a terminal
|
|
1619
1631
|
* event (``message_stop`` / ``error``) was seen, so the caller can decide
|
|
1620
1632
|
* whether an ended stream means "turn done" vs "dropped, reconnect".
|
|
1633
|
+
*
|
|
1634
|
+
* ``onStall`` aborts the per-attempt connection: if no event arrives within
|
|
1635
|
+
* SSE_STALL_TIMEOUT_MS (backend keepalives land every 15s), the stream is a
|
|
1636
|
+
* zombie — ``reader.read()`` will never settle — so we kill the fetch and
|
|
1637
|
+
* throw a ConnectionError, feeding the caller's reconnect-from-lastSeq loop.
|
|
1621
1638
|
*/
|
|
1622
|
-
async pumpStream(stream, conversationId, messageId, executeClientTools) {
|
|
1639
|
+
async pumpStream(stream, conversationId, messageId, executeClientTools, onStall) {
|
|
1623
1640
|
let sawTerminal = false;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
const
|
|
1628
|
-
|
|
1629
|
-
|
|
1641
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
1642
|
+
try {
|
|
1643
|
+
while (true) {
|
|
1644
|
+
const next = iterator.next();
|
|
1645
|
+
let stallTimer;
|
|
1646
|
+
const stall = new Promise((_, reject) => {
|
|
1647
|
+
stallTimer = setTimeout(() => {
|
|
1648
|
+
reject(
|
|
1649
|
+
new ConnectionError(
|
|
1650
|
+
`Stream stalled: no events for ${SSE_STALL_TIMEOUT_MS}ms`
|
|
1651
|
+
)
|
|
1652
|
+
);
|
|
1653
|
+
onStall?.();
|
|
1654
|
+
}, SSE_STALL_TIMEOUT_MS);
|
|
1655
|
+
});
|
|
1656
|
+
let result;
|
|
1657
|
+
try {
|
|
1658
|
+
result = await Promise.race([next, stall]);
|
|
1659
|
+
} finally {
|
|
1660
|
+
clearTimeout(stallTimer);
|
|
1661
|
+
}
|
|
1662
|
+
if (result.done) break;
|
|
1663
|
+
const raw = result.value;
|
|
1664
|
+
let parsed;
|
|
1665
|
+
try {
|
|
1666
|
+
const data = JSON.parse(raw.data);
|
|
1667
|
+
if (typeof data !== "object" || data === null || typeof data.type !== "string") {
|
|
1668
|
+
if (typeof data?.seq === "number") {
|
|
1669
|
+
this.lastSeq = data.seq;
|
|
1670
|
+
}
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
parsed = data;
|
|
1674
|
+
if (typeof data.seq === "number") {
|
|
1630
1675
|
this.lastSeq = data.seq;
|
|
1631
1676
|
}
|
|
1677
|
+
} catch {
|
|
1632
1678
|
continue;
|
|
1633
1679
|
}
|
|
1634
|
-
parsed
|
|
1635
|
-
|
|
1636
|
-
this.lastSeq = data.seq;
|
|
1680
|
+
if (parsed.type === "message_stop" || parsed.type === "error") {
|
|
1681
|
+
sawTerminal = true;
|
|
1637
1682
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1683
|
+
await this.dispatchWireEvent(
|
|
1684
|
+
parsed,
|
|
1685
|
+
conversationId,
|
|
1686
|
+
messageId,
|
|
1687
|
+
executeClientTools
|
|
1688
|
+
);
|
|
1643
1689
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
messageId,
|
|
1648
|
-
executeClientTools
|
|
1649
|
-
);
|
|
1690
|
+
} finally {
|
|
1691
|
+
void iterator.return?.(void 0).catch(() => {
|
|
1692
|
+
});
|
|
1650
1693
|
}
|
|
1651
1694
|
return sawTerminal;
|
|
1652
1695
|
}
|