@astralform/js 1.1.0 → 1.2.0
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 +103 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +103 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1190,6 +1190,11 @@ function translateWireEvent(wire) {
|
|
|
1190
1190
|
}
|
|
1191
1191
|
|
|
1192
1192
|
// src/session.ts
|
|
1193
|
+
var SSE_MAX_RECONNECTS = 6;
|
|
1194
|
+
var TOOL_RESULT_MAX_RETRIES = 3;
|
|
1195
|
+
function sseReconnectDelayMs(attempt) {
|
|
1196
|
+
return Math.min(500 * 2 ** (attempt - 1), 5e3);
|
|
1197
|
+
}
|
|
1193
1198
|
function pathEquals(a, b) {
|
|
1194
1199
|
if (a.length !== b.length) return false;
|
|
1195
1200
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -1226,6 +1231,13 @@ var ChatSession = class {
|
|
|
1226
1231
|
this.abortController = null;
|
|
1227
1232
|
/** Last received sequence number for resumable reconnection */
|
|
1228
1233
|
this.lastSeq = -1;
|
|
1234
|
+
/**
|
|
1235
|
+
* Client-tool call_ids whose result was already submitted this turn. On a
|
|
1236
|
+
* reconnect the resumed stream can replay a tool request we already handled;
|
|
1237
|
+
* this dedups so each is executed + submitted at most once (but a request we
|
|
1238
|
+
* never submitted still runs). Cleared at the start of each turn.
|
|
1239
|
+
*/
|
|
1240
|
+
this.submittedToolCallIds = /* @__PURE__ */ new Set();
|
|
1229
1241
|
/** Current job ID for cancellation */
|
|
1230
1242
|
this.currentJobId = null;
|
|
1231
1243
|
this.client = new AstralformClient(config);
|
|
@@ -1360,13 +1372,9 @@ var ChatSession = class {
|
|
|
1360
1372
|
}
|
|
1361
1373
|
const messageId = job.message_id;
|
|
1362
1374
|
this.lastSeq = -1;
|
|
1363
|
-
|
|
1364
|
-
job.job_id,
|
|
1365
|
-
this.lastSeq,
|
|
1366
|
-
this.abortController?.signal
|
|
1367
|
-
);
|
|
1375
|
+
this.submittedToolCallIds.clear();
|
|
1368
1376
|
await this.consumeEventStream(
|
|
1369
|
-
|
|
1377
|
+
job.job_id,
|
|
1370
1378
|
conversationId,
|
|
1371
1379
|
messageId,
|
|
1372
1380
|
true
|
|
@@ -1377,7 +1385,41 @@ var ChatSession = class {
|
|
|
1377
1385
|
* Shared event consumption loop. Parses each wire event, updates
|
|
1378
1386
|
* minimal session state, and emits typed ChatEvents to consumers.
|
|
1379
1387
|
*/
|
|
1380
|
-
async consumeEventStream(
|
|
1388
|
+
async consumeEventStream(jobId, conversationId, messageId, executeClientTools) {
|
|
1389
|
+
const signal = this.abortController?.signal;
|
|
1390
|
+
for (let attempt = 0; ; attempt++) {
|
|
1391
|
+
const stream = this.client.streamJobEvents(jobId, this.lastSeq, signal);
|
|
1392
|
+
let sawTerminal;
|
|
1393
|
+
try {
|
|
1394
|
+
sawTerminal = await this.pumpStream(
|
|
1395
|
+
stream,
|
|
1396
|
+
conversationId,
|
|
1397
|
+
messageId,
|
|
1398
|
+
executeClientTools
|
|
1399
|
+
);
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
if (signal?.aborted) return;
|
|
1402
|
+
if (err instanceof AuthenticationError || err instanceof RateLimitError) {
|
|
1403
|
+
throw err;
|
|
1404
|
+
}
|
|
1405
|
+
if (attempt >= SSE_MAX_RECONNECTS) throw err;
|
|
1406
|
+
await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
if (sawTerminal || signal?.aborted) return;
|
|
1410
|
+
if (attempt >= SSE_MAX_RECONNECTS) {
|
|
1411
|
+
throw new ConnectionError("Lost connection to the response stream.");
|
|
1412
|
+
}
|
|
1413
|
+
await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Consume a single SSE stream to exhaustion. Returns whether a terminal
|
|
1418
|
+
* event (``message_stop`` / ``error``) was seen, so the caller can decide
|
|
1419
|
+
* whether an ended stream means "turn done" vs "dropped, reconnect".
|
|
1420
|
+
*/
|
|
1421
|
+
async pumpStream(stream, conversationId, messageId, executeClientTools) {
|
|
1422
|
+
let sawTerminal = false;
|
|
1381
1423
|
for await (const raw of stream) {
|
|
1382
1424
|
let parsed;
|
|
1383
1425
|
try {
|
|
@@ -1395,6 +1437,9 @@ var ChatSession = class {
|
|
|
1395
1437
|
} catch {
|
|
1396
1438
|
continue;
|
|
1397
1439
|
}
|
|
1440
|
+
if (parsed.type === "message_stop" || parsed.type === "error") {
|
|
1441
|
+
sawTerminal = true;
|
|
1442
|
+
}
|
|
1398
1443
|
await this.dispatchWireEvent(
|
|
1399
1444
|
parsed,
|
|
1400
1445
|
conversationId,
|
|
@@ -1402,6 +1447,39 @@ var ChatSession = class {
|
|
|
1402
1447
|
executeClientTools
|
|
1403
1448
|
);
|
|
1404
1449
|
}
|
|
1450
|
+
return sawTerminal;
|
|
1451
|
+
}
|
|
1452
|
+
/** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */
|
|
1453
|
+
sleepUnlessAborted(ms, signal) {
|
|
1454
|
+
return new Promise((resolve) => {
|
|
1455
|
+
if (signal?.aborted) return resolve();
|
|
1456
|
+
const timer = setTimeout(() => {
|
|
1457
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1458
|
+
resolve();
|
|
1459
|
+
}, ms);
|
|
1460
|
+
const onAbort = () => {
|
|
1461
|
+
clearTimeout(timer);
|
|
1462
|
+
resolve();
|
|
1463
|
+
};
|
|
1464
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
/** POST a client-tool result, retrying transient failures a few times. */
|
|
1468
|
+
async submitToolResultWithRetry(payload) {
|
|
1469
|
+
const signal = this.abortController?.signal;
|
|
1470
|
+
for (let attempt = 0; ; attempt++) {
|
|
1471
|
+
try {
|
|
1472
|
+
await this.client.submitToolResult(payload);
|
|
1473
|
+
return;
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
if (signal?.aborted) throw err;
|
|
1476
|
+
if (err instanceof AuthenticationError || err instanceof RateLimitError) {
|
|
1477
|
+
throw err;
|
|
1478
|
+
}
|
|
1479
|
+
if (attempt >= TOOL_RESULT_MAX_RETRIES) throw err;
|
|
1480
|
+
await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1405
1483
|
}
|
|
1406
1484
|
async dispatchWireEvent(wire, conversationId, messageId, executeClientTools) {
|
|
1407
1485
|
this.applyWireSideEffects(wire, conversationId, messageId);
|
|
@@ -1411,18 +1489,22 @@ var ChatSession = class {
|
|
|
1411
1489
|
}
|
|
1412
1490
|
if (executeClientTools && wire.type === "block_stop" && wire.status === "awaiting_client_result" && wire.final?.call_id) {
|
|
1413
1491
|
const f = wire.final;
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1492
|
+
const callId = f.call_id ?? "";
|
|
1493
|
+
if (callId && !this.submittedToolCallIds.has(callId)) {
|
|
1494
|
+
const request = {
|
|
1495
|
+
callId,
|
|
1496
|
+
toolName: f.tool_name ?? "",
|
|
1497
|
+
arguments: f.input ?? {},
|
|
1498
|
+
isClientTool: true
|
|
1499
|
+
};
|
|
1500
|
+
const results = await this.executeClientTools([request]);
|
|
1501
|
+
await this.submitToolResultWithRetry({
|
|
1502
|
+
conversation_id: conversationId,
|
|
1503
|
+
message_id: messageId,
|
|
1504
|
+
tool_results: results
|
|
1505
|
+
});
|
|
1506
|
+
this.submittedToolCallIds.add(callId);
|
|
1507
|
+
}
|
|
1426
1508
|
}
|
|
1427
1509
|
}
|
|
1428
1510
|
/**
|
|
@@ -1519,16 +1601,12 @@ var ChatSession = class {
|
|
|
1519
1601
|
this.isStreaming = true;
|
|
1520
1602
|
this.currentJobId = jobId;
|
|
1521
1603
|
this.lastSeq = -1;
|
|
1604
|
+
this.submittedToolCallIds.clear();
|
|
1522
1605
|
this.resetStreamingState();
|
|
1523
1606
|
this.abortController = new AbortController();
|
|
1524
1607
|
try {
|
|
1525
|
-
const stream = this.client.streamJobEvents(
|
|
1526
|
-
jobId,
|
|
1527
|
-
this.lastSeq,
|
|
1528
|
-
this.abortController?.signal
|
|
1529
|
-
);
|
|
1530
1608
|
await this.consumeEventStream(
|
|
1531
|
-
|
|
1609
|
+
jobId,
|
|
1532
1610
|
this.conversationId ?? "",
|
|
1533
1611
|
"",
|
|
1534
1612
|
false
|