@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.d.cts
CHANGED
|
@@ -1180,6 +1180,11 @@ declare class ChatSession {
|
|
|
1180
1180
|
* Consume a single SSE stream to exhaustion. Returns whether a terminal
|
|
1181
1181
|
* event (``message_stop`` / ``error``) was seen, so the caller can decide
|
|
1182
1182
|
* whether an ended stream means "turn done" vs "dropped, reconnect".
|
|
1183
|
+
*
|
|
1184
|
+
* ``onStall`` aborts the per-attempt connection: if no event arrives within
|
|
1185
|
+
* SSE_STALL_TIMEOUT_MS (backend keepalives land every 15s), the stream is a
|
|
1186
|
+
* zombie — ``reader.read()`` will never settle — so we kill the fetch and
|
|
1187
|
+
* throw a ConnectionError, feeding the caller's reconnect-from-lastSeq loop.
|
|
1183
1188
|
*/
|
|
1184
1189
|
private pumpStream;
|
|
1185
1190
|
/** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1180,6 +1180,11 @@ declare class ChatSession {
|
|
|
1180
1180
|
* Consume a single SSE stream to exhaustion. Returns whether a terminal
|
|
1181
1181
|
* event (``message_stop`` / ``error``) was seen, so the caller can decide
|
|
1182
1182
|
* whether an ended stream means "turn done" vs "dropped, reconnect".
|
|
1183
|
+
*
|
|
1184
|
+
* ``onStall`` aborts the per-attempt connection: if no event arrives within
|
|
1185
|
+
* SSE_STALL_TIMEOUT_MS (backend keepalives land every 15s), the stream is a
|
|
1186
|
+
* zombie — ``reader.read()`` will never settle — so we kill the fetch and
|
|
1187
|
+
* throw a ConnectionError, feeding the caller's reconnect-from-lastSeq loop.
|
|
1183
1188
|
*/
|
|
1184
1189
|
private pumpStream;
|
|
1185
1190
|
/** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */
|
package/dist/index.js
CHANGED
|
@@ -1291,6 +1291,7 @@ function translateWireEvent(wire) {
|
|
|
1291
1291
|
|
|
1292
1292
|
// src/session.ts
|
|
1293
1293
|
var SSE_MAX_RECONNECTS = 6;
|
|
1294
|
+
var SSE_STALL_TIMEOUT_MS = 45e3;
|
|
1294
1295
|
var TOOL_RESULT_MAX_RETRIES = 3;
|
|
1295
1296
|
var CONVERSATION_PAGE_SIZE = 50;
|
|
1296
1297
|
function sseReconnectDelayMs(attempt) {
|
|
@@ -1542,14 +1543,23 @@ var ChatSession = class {
|
|
|
1542
1543
|
async consumeEventStream(jobId, conversationId, messageId, executeClientTools) {
|
|
1543
1544
|
const signal = this.abortController?.signal;
|
|
1544
1545
|
for (let attempt = 0; ; attempt++) {
|
|
1545
|
-
|
|
1546
|
+
if (signal?.aborted) return;
|
|
1547
|
+
const attemptController = new AbortController();
|
|
1548
|
+
const linkAbort = () => attemptController.abort();
|
|
1549
|
+
signal?.addEventListener("abort", linkAbort);
|
|
1546
1550
|
let sawTerminal;
|
|
1547
1551
|
try {
|
|
1552
|
+
const stream = this.client.streamJobEvents(
|
|
1553
|
+
jobId,
|
|
1554
|
+
this.lastSeq,
|
|
1555
|
+
attemptController.signal
|
|
1556
|
+
);
|
|
1548
1557
|
sawTerminal = await this.pumpStream(
|
|
1549
1558
|
stream,
|
|
1550
1559
|
conversationId,
|
|
1551
1560
|
messageId,
|
|
1552
|
-
executeClientTools
|
|
1561
|
+
executeClientTools,
|
|
1562
|
+
() => attemptController.abort()
|
|
1553
1563
|
);
|
|
1554
1564
|
} catch (err) {
|
|
1555
1565
|
if (signal?.aborted) return;
|
|
@@ -1559,6 +1569,8 @@ var ChatSession = class {
|
|
|
1559
1569
|
if (attempt >= SSE_MAX_RECONNECTS) throw err;
|
|
1560
1570
|
await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
|
|
1561
1571
|
continue;
|
|
1572
|
+
} finally {
|
|
1573
|
+
signal?.removeEventListener("abort", linkAbort);
|
|
1562
1574
|
}
|
|
1563
1575
|
if (sawTerminal || signal?.aborted) return;
|
|
1564
1576
|
if (attempt >= SSE_MAX_RECONNECTS) {
|
|
@@ -1571,35 +1583,66 @@ var ChatSession = class {
|
|
|
1571
1583
|
* Consume a single SSE stream to exhaustion. Returns whether a terminal
|
|
1572
1584
|
* event (``message_stop`` / ``error``) was seen, so the caller can decide
|
|
1573
1585
|
* whether an ended stream means "turn done" vs "dropped, reconnect".
|
|
1586
|
+
*
|
|
1587
|
+
* ``onStall`` aborts the per-attempt connection: if no event arrives within
|
|
1588
|
+
* SSE_STALL_TIMEOUT_MS (backend keepalives land every 15s), the stream is a
|
|
1589
|
+
* zombie — ``reader.read()`` will never settle — so we kill the fetch and
|
|
1590
|
+
* throw a ConnectionError, feeding the caller's reconnect-from-lastSeq loop.
|
|
1574
1591
|
*/
|
|
1575
|
-
async pumpStream(stream, conversationId, messageId, executeClientTools) {
|
|
1592
|
+
async pumpStream(stream, conversationId, messageId, executeClientTools, onStall) {
|
|
1576
1593
|
let sawTerminal = false;
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
|
|
1594
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
1595
|
+
try {
|
|
1596
|
+
while (true) {
|
|
1597
|
+
const next = iterator.next();
|
|
1598
|
+
let stallTimer;
|
|
1599
|
+
const stall = new Promise((_, reject) => {
|
|
1600
|
+
stallTimer = setTimeout(() => {
|
|
1601
|
+
reject(
|
|
1602
|
+
new ConnectionError(
|
|
1603
|
+
`Stream stalled: no events for ${SSE_STALL_TIMEOUT_MS}ms`
|
|
1604
|
+
)
|
|
1605
|
+
);
|
|
1606
|
+
onStall?.();
|
|
1607
|
+
}, SSE_STALL_TIMEOUT_MS);
|
|
1608
|
+
});
|
|
1609
|
+
let result;
|
|
1610
|
+
try {
|
|
1611
|
+
result = await Promise.race([next, stall]);
|
|
1612
|
+
} finally {
|
|
1613
|
+
clearTimeout(stallTimer);
|
|
1614
|
+
}
|
|
1615
|
+
if (result.done) break;
|
|
1616
|
+
const raw = result.value;
|
|
1617
|
+
let parsed;
|
|
1618
|
+
try {
|
|
1619
|
+
const data = JSON.parse(raw.data);
|
|
1620
|
+
if (typeof data !== "object" || data === null || typeof data.type !== "string") {
|
|
1621
|
+
if (typeof data?.seq === "number") {
|
|
1622
|
+
this.lastSeq = data.seq;
|
|
1623
|
+
}
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
parsed = data;
|
|
1627
|
+
if (typeof data.seq === "number") {
|
|
1583
1628
|
this.lastSeq = data.seq;
|
|
1584
1629
|
}
|
|
1630
|
+
} catch {
|
|
1585
1631
|
continue;
|
|
1586
1632
|
}
|
|
1587
|
-
parsed
|
|
1588
|
-
|
|
1589
|
-
this.lastSeq = data.seq;
|
|
1633
|
+
if (parsed.type === "message_stop" || parsed.type === "error") {
|
|
1634
|
+
sawTerminal = true;
|
|
1590
1635
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1636
|
+
await this.dispatchWireEvent(
|
|
1637
|
+
parsed,
|
|
1638
|
+
conversationId,
|
|
1639
|
+
messageId,
|
|
1640
|
+
executeClientTools
|
|
1641
|
+
);
|
|
1596
1642
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
messageId,
|
|
1601
|
-
executeClientTools
|
|
1602
|
-
);
|
|
1643
|
+
} finally {
|
|
1644
|
+
void iterator.return?.(void 0).catch(() => {
|
|
1645
|
+
});
|
|
1603
1646
|
}
|
|
1604
1647
|
return sawTerminal;
|
|
1605
1648
|
}
|