@astralform/js 0.2.0 → 0.2.2

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 CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  AstralformError: () => AstralformError,
25
25
  AuthenticationError: () => AuthenticationError,
26
26
  BlockBuilder: () => BlockBuilder,
27
+ ChatEventType: () => ChatEventType,
27
28
  ChatSession: () => ChatSession,
28
29
  ConnectionError: () => ConnectionError,
29
30
  InMemoryStorage: () => InMemoryStorage,
@@ -31,6 +32,7 @@ __export(index_exports, {
31
32
  RateLimitError: () => RateLimitError,
32
33
  ServerError: () => ServerError,
33
34
  StreamAbortedError: () => StreamAbortedError,
35
+ StreamManager: () => StreamManager,
34
36
  ToolRegistry: () => ToolRegistry,
35
37
  generateId: () => generateId,
36
38
  standardHandlers: () => standardHandlers,
@@ -287,10 +289,10 @@ var AstralformClient = class {
287
289
  isEnabled: s.is_enabled
288
290
  }));
289
291
  }
290
- async getConversationEvents(conversationId) {
291
- return this.get(
292
- `/v1/conversations/${encodeURIComponent(conversationId)}/events`
293
- );
292
+ async getConversationEvents(conversationId, jobId) {
293
+ let url = `/v1/conversations/${encodeURIComponent(conversationId)}/events`;
294
+ if (jobId) url += `?job_id=${encodeURIComponent(jobId)}`;
295
+ return this.get(url);
294
296
  }
295
297
  async submitToolResult(request) {
296
298
  await this.post("/v1/tool-result", request);
@@ -372,6 +374,8 @@ var BlockBuilder = class {
372
374
  this.activeTextId = null;
373
375
  this.activeThinkingId = null;
374
376
  this.thinkingStartMs = null;
377
+ this.activeEditorId = null;
378
+ this.activeTodoId = null;
375
379
  }
376
380
  // ── Registration ──────────────────────────────────────────────
377
381
  on(eventType, handler) {
@@ -398,6 +402,8 @@ var BlockBuilder = class {
398
402
  this.activeTextId = null;
399
403
  this.activeThinkingId = null;
400
404
  this.thinkingStartMs = null;
405
+ this.activeEditorId = null;
406
+ this.activeTodoId = null;
401
407
  }
402
408
  setOnChange(fn) {
403
409
  this._onChange = fn;
@@ -623,7 +629,6 @@ var ChatSession = class {
623
629
  this.thinkingContent = "";
624
630
  this.isThinking = false;
625
631
  this.activeSubagents = /* @__PURE__ */ new Map();
626
- this.sources = [];
627
632
  this.capsuleOutputs = [];
628
633
  this.todos = [];
629
634
  this.activeTools = /* @__PURE__ */ new Map();
@@ -727,7 +732,6 @@ var ChatSession = class {
727
732
  this.thinkingContent = "";
728
733
  this.isThinking = false;
729
734
  this.activeSubagents.clear();
730
- this.sources = [];
731
735
  this.capsuleOutputs = [];
732
736
  this.todos = [];
733
737
  this.activeTools.clear();
@@ -739,10 +743,12 @@ var ChatSession = class {
739
743
  try {
740
744
  await this.consumeJobStream(request);
741
745
  } catch (err) {
742
- this.emit({
743
- type: "error",
744
- error: err instanceof Error ? err : new ConnectionError(String(err))
745
- });
746
+ if (!(err instanceof DOMException && err.name === "AbortError")) {
747
+ this.emit({
748
+ type: "error",
749
+ error: err instanceof Error ? err : new ConnectionError(String(err))
750
+ });
751
+ }
746
752
  } finally {
747
753
  this.isStreaming = false;
748
754
  this.executingTool = null;
@@ -895,11 +901,24 @@ var ChatSession = class {
895
901
  applyEvent(event) {
896
902
  switch (event.type) {
897
903
  case "user_message":
898
- this.emit({ type: "user_message", content: event.content });
904
+ this.emit({
905
+ type: "user_message",
906
+ content: event.content,
907
+ createdAt: event.created_at
908
+ });
899
909
  break;
900
- case "title_generated":
910
+ case "title_generated": {
911
+ if (this.conversationId && event.title) {
912
+ const conv = this.conversations.find(
913
+ (c) => c.id === this.conversationId
914
+ );
915
+ if (conv) {
916
+ conv.title = event.title;
917
+ }
918
+ }
901
919
  this.emit({ type: "title_generated", title: event.title });
902
920
  break;
921
+ }
903
922
  case "message_start":
904
923
  if (event.conversation_id && !this.conversationId) {
905
924
  this.conversationId = event.conversation_id;
@@ -922,6 +941,23 @@ var ChatSession = class {
922
941
  this.isThinking = false;
923
942
  this.emit({ type: "thinking_complete" });
924
943
  break;
944
+ case "tool_executing":
945
+ this.emit({
946
+ type: "tool_executing",
947
+ name: event.tool,
948
+ call_id: event.call_id
949
+ });
950
+ break;
951
+ case "tool_progress":
952
+ this.emit({
953
+ type: "tool_progress",
954
+ callId: event.call_id,
955
+ tool: event.tool,
956
+ index: event.index,
957
+ total: event.total,
958
+ item: event.item
959
+ });
960
+ break;
925
961
  case "message_stop":
926
962
  this.emit({
927
963
  type: "complete",
@@ -1031,10 +1067,6 @@ var ChatSession = class {
1031
1067
  result: event.result
1032
1068
  });
1033
1069
  break;
1034
- case "sources":
1035
- this.sources.push(...event.sources);
1036
- this.emit({ type: "sources", sources: event.sources });
1037
- break;
1038
1070
  case "capsule_output": {
1039
1071
  const capsule = {
1040
1072
  toolName: event.tool_name,
@@ -1070,27 +1102,6 @@ var ChatSession = class {
1070
1102
  sizeBytes: event.size_bytes
1071
1103
  });
1072
1104
  break;
1073
- case "timeline_entry":
1074
- this.emit({
1075
- type: "timeline_entry",
1076
- id: event.id,
1077
- status: event.status,
1078
- kind: event.kind,
1079
- agent_name: event.agent_name,
1080
- tool_name: event.tool_name,
1081
- display_name: event.display_name,
1082
- tool_category: event.tool_category,
1083
- viewer: event.viewer,
1084
- call_id: event.call_id,
1085
- detail: event.detail,
1086
- started_at: event.started_at,
1087
- duration_ms: event.duration_ms,
1088
- output_summary: event.output_summary,
1089
- sources: event.sources,
1090
- parent_id: event.parent_id,
1091
- structured_output: event.structured_output
1092
- });
1093
- break;
1094
1105
  case "editor_content_start":
1095
1106
  this.emit({
1096
1107
  type: "editor_content_start",
@@ -1212,19 +1223,26 @@ var ChatSession = class {
1212
1223
  this.abortController = null;
1213
1224
  }
1214
1225
  }
1215
- disconnect() {
1216
- if (this.currentJobId) {
1217
- this.client.cancelJob(this.currentJobId).catch(() => {
1218
- });
1219
- this.currentJobId = null;
1220
- }
1226
+ /** Detach from the SSE stream without cancelling the job.
1227
+ * The backend job keeps running — caller can reconnect later. */
1228
+ detach() {
1221
1229
  this.abortController?.abort();
1222
1230
  this.abortController = null;
1223
1231
  this.isStreaming = false;
1224
1232
  this.streamingContent = "";
1225
1233
  this.executingTool = null;
1234
+ this.blockBuilder.reset();
1226
1235
  this.emit({ type: "disconnected" });
1227
1236
  }
1237
+ /** Stop the job and disconnect (explicit user action). */
1238
+ disconnect() {
1239
+ if (this.currentJobId) {
1240
+ this.client.cancelJob(this.currentJobId).catch(() => {
1241
+ });
1242
+ }
1243
+ this.detach();
1244
+ this.currentJobId = null;
1245
+ }
1228
1246
  async createNewConversation() {
1229
1247
  const id = generateId();
1230
1248
  const conversation = await this.storage.createConversation(
@@ -1237,13 +1255,13 @@ var ChatSession = class {
1237
1255
  this.streamingContent = "";
1238
1256
  return id;
1239
1257
  }
1240
- async switchConversation(id) {
1258
+ async switchConversation(id, jobId) {
1241
1259
  this.conversationId = id;
1242
1260
  this.resetStreamingState();
1243
1261
  this.blockBuilder.reset();
1244
1262
  const [messagesResult, eventsResult] = await Promise.allSettled([
1245
1263
  this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
1246
- this.client.getConversationEvents(id)
1264
+ this.client.getConversationEvents(id, jobId)
1247
1265
  ]);
1248
1266
  this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
1249
1267
  if (eventsResult.status === "fulfilled") {
@@ -1292,10 +1310,31 @@ function finalizeThinking(builder) {
1292
1310
  builder.thinkingStartMs = null;
1293
1311
  }
1294
1312
  }
1313
+ function finalizeEditor(builder) {
1314
+ if (builder.activeEditorId) {
1315
+ builder.patchBlock(builder.activeEditorId, {
1316
+ isStreaming: false
1317
+ });
1318
+ builder.activeEditorId = null;
1319
+ }
1320
+ }
1295
1321
  var handleUserMessage = (event, builder) => {
1296
- if (builder.findBlock((b) => b.type === "user")) return;
1297
1322
  const e = event;
1298
- builder.addBlock({ type: "user", id: builder.nextId(), content: e.content });
1323
+ const existing = builder.findBlock((b) => b.type === "user");
1324
+ if (existing) {
1325
+ if (e.createdAt) {
1326
+ builder.patchBlock(existing.id, {
1327
+ createdAt: e.createdAt
1328
+ });
1329
+ }
1330
+ return;
1331
+ }
1332
+ builder.addBlock({
1333
+ type: "user",
1334
+ id: builder.nextId(),
1335
+ content: e.content,
1336
+ createdAt: e.createdAt
1337
+ });
1299
1338
  };
1300
1339
  var handleChunk = (event, builder) => {
1301
1340
  const e = event;
@@ -1337,12 +1376,26 @@ var handleToolCall = (event, builder) => {
1337
1376
  var handleToolExecuting = (event, builder) => {
1338
1377
  const e = event;
1339
1378
  const block = builder.findBlock(
1340
- (b) => b.type === "tool" && b.toolName === e.name && b.status === "calling"
1379
+ (b) => b.type === "tool" && (e.call_id ? b.callId === e.call_id : b.toolName === e.name) && b.status === "calling"
1341
1380
  );
1342
1381
  if (block) {
1343
1382
  builder.patchBlock(block.id, { status: "executing" });
1344
1383
  }
1345
1384
  };
1385
+ var handleToolProgress = (event, builder) => {
1386
+ const e = event;
1387
+ const block = builder.findBlock(
1388
+ (b) => b.type === "tool" && b.callId === e.callId
1389
+ );
1390
+ if (block && block.type === "tool") {
1391
+ const sources = block.sources ? [...block.sources] : [];
1392
+ sources.push(e.item);
1393
+ builder.patchBlock(block.id, {
1394
+ sources,
1395
+ status: "executing"
1396
+ });
1397
+ }
1398
+ };
1346
1399
  var handleToolEnd = (event, builder) => {
1347
1400
  const e = event;
1348
1401
  const callId = e.type === "tool_end" ? e.callId : void 0;
@@ -1455,6 +1508,7 @@ var handleSubagentEnd = (event, builder) => {
1455
1508
  var handleComplete = (_event, builder) => {
1456
1509
  finalizeText(builder);
1457
1510
  finalizeThinking(builder);
1511
+ finalizeEditor(builder);
1458
1512
  for (const b of builder.getBlocks()) {
1459
1513
  if (b.type === "tool" && b.status !== "completed") {
1460
1514
  builder.patchBlock(b.id, { status: "completed" });
@@ -1474,31 +1528,422 @@ var handleError = (event, builder) => {
1474
1528
  var handleDisconnected = (_event, builder) => {
1475
1529
  finalizeText(builder);
1476
1530
  finalizeThinking(builder);
1531
+ finalizeEditor(builder);
1532
+ };
1533
+ var handleCapsuleOutputChunk = (event, builder) => {
1534
+ const e = event;
1535
+ const block = builder.findBlock(
1536
+ (b) => b.type === "capsule" && b.callId === e.callId
1537
+ );
1538
+ if (block && block.type === "capsule") {
1539
+ builder.patchBlock(block.id, {
1540
+ output: block.output + e.chunk
1541
+ });
1542
+ } else {
1543
+ builder.addBlock({
1544
+ type: "capsule",
1545
+ id: builder.nextId(),
1546
+ callId: e.callId,
1547
+ toolName: "",
1548
+ output: e.chunk,
1549
+ isActive: true
1550
+ });
1551
+ }
1552
+ };
1553
+ var handleCapsuleOutput = (event, builder) => {
1554
+ const e = event;
1555
+ const block = builder.findBlock(
1556
+ (b) => b.type === "capsule" && b.callId === (e.callId ?? "")
1557
+ );
1558
+ if (block) {
1559
+ builder.patchBlock(block.id, {
1560
+ output: e.output,
1561
+ command: e.command,
1562
+ toolName: e.toolName,
1563
+ durationMs: e.durationMs,
1564
+ isActive: false
1565
+ });
1566
+ } else {
1567
+ builder.addBlock({
1568
+ type: "capsule",
1569
+ id: builder.nextId(),
1570
+ callId: e.callId ?? "",
1571
+ toolName: e.toolName,
1572
+ command: e.command,
1573
+ output: e.output,
1574
+ durationMs: e.durationMs,
1575
+ isActive: false
1576
+ });
1577
+ }
1578
+ };
1579
+ var handleAssetCreated = (event, builder) => {
1580
+ const e = event;
1581
+ builder.addBlock({
1582
+ type: "asset",
1583
+ id: builder.nextId(),
1584
+ assetId: e.assetId,
1585
+ name: e.name,
1586
+ url: e.url,
1587
+ mediaType: e.mediaType,
1588
+ sizeBytes: e.sizeBytes
1589
+ });
1590
+ };
1591
+ var handleTodoUpdate = (event, builder) => {
1592
+ const e = event;
1593
+ if (builder.activeTodoId) {
1594
+ builder.patchBlock(builder.activeTodoId, {
1595
+ todos: e.todos
1596
+ });
1597
+ } else {
1598
+ const id = builder.nextId();
1599
+ builder.activeTodoId = id;
1600
+ builder.addBlock({
1601
+ type: "todo",
1602
+ id,
1603
+ todos: e.todos
1604
+ });
1605
+ }
1606
+ };
1607
+ var handleEditorContentStart = (event, builder) => {
1608
+ const e = event;
1609
+ const id = builder.nextId();
1610
+ builder.activeEditorId = id;
1611
+ builder.addBlock({
1612
+ type: "editor",
1613
+ id,
1614
+ callId: e.callId,
1615
+ path: e.path,
1616
+ language: e.language,
1617
+ content: "",
1618
+ isStreaming: true
1619
+ });
1620
+ };
1621
+ var handleEditorContentDelta = (event, builder) => {
1622
+ const e = event;
1623
+ const block = builder.findBlock(
1624
+ (b) => b.type === "editor" && b.callId === e.callId
1625
+ );
1626
+ if (block && block.type === "editor") {
1627
+ builder.patchBlock(block.id, {
1628
+ content: block.content + e.delta
1629
+ });
1630
+ }
1631
+ };
1632
+ var handleEditorContentEnd = (event, builder) => {
1633
+ const e = event;
1634
+ const block = builder.findBlock(
1635
+ (b) => b.type === "editor" && b.callId === e.callId
1636
+ );
1637
+ if (block) {
1638
+ builder.patchBlock(block.id, {
1639
+ isStreaming: false
1640
+ });
1641
+ }
1642
+ builder.activeEditorId = null;
1643
+ };
1644
+ var noop = () => {
1477
1645
  };
1478
1646
  var standardHandlers = {
1479
1647
  user_message: handleUserMessage,
1480
1648
  chunk: handleChunk,
1481
1649
  tool_call: handleToolCall,
1482
1650
  tool_executing: handleToolExecuting,
1651
+ tool_progress: handleToolProgress,
1483
1652
  tool_completed: handleToolEnd,
1484
1653
  tool_end: handleToolEnd,
1485
1654
  agent_start: handleAgentStart,
1655
+ agent_end: noop,
1486
1656
  thinking_delta: handleThinkingDelta,
1487
1657
  thinking_complete: handleThinkingComplete,
1488
1658
  subagent_start: handleSubagentStart,
1489
1659
  subagent_chunk: handleSubagentChunk,
1490
1660
  subagent_update: handleSubagentUpdate,
1491
1661
  subagent_end: handleSubagentEnd,
1662
+ subagent_tool_use: noop,
1663
+ capsule_output: handleCapsuleOutput,
1664
+ capsule_output_chunk: handleCapsuleOutputChunk,
1665
+ asset_created: handleAssetCreated,
1666
+ todo_update: handleTodoUpdate,
1667
+ editor_content_start: handleEditorContentStart,
1668
+ editor_content_delta: handleEditorContentDelta,
1669
+ editor_content_end: handleEditorContentEnd,
1670
+ retry: noop,
1492
1671
  complete: handleComplete,
1493
1672
  error: handleError,
1494
1673
  disconnected: handleDisconnected
1495
1674
  };
1675
+
1676
+ // src/types.ts
1677
+ var ChatEventType = {
1678
+ Connected: "connected",
1679
+ BlocksChanged: "blocks_changed",
1680
+ UserMessage: "user_message",
1681
+ TitleGenerated: "title_generated",
1682
+ ModelInfo: "model_info",
1683
+ Chunk: "chunk",
1684
+ ToolCall: "tool_call",
1685
+ ToolExecuting: "tool_executing",
1686
+ ToolProgress: "tool_progress",
1687
+ ToolCompleted: "tool_completed",
1688
+ ToolEnd: "tool_end",
1689
+ AgentStart: "agent_start",
1690
+ AgentEnd: "agent_end",
1691
+ ThinkingDelta: "thinking_delta",
1692
+ ThinkingComplete: "thinking_complete",
1693
+ SubagentStart: "subagent_start",
1694
+ SubagentChunk: "subagent_chunk",
1695
+ SubagentUpdate: "subagent_update",
1696
+ SubagentEnd: "subagent_end",
1697
+ SubagentToolUse: "subagent_tool_use",
1698
+ CapsuleOutput: "capsule_output",
1699
+ CapsuleOutputChunk: "capsule_output_chunk",
1700
+ AssetCreated: "asset_created",
1701
+ TodoUpdate: "todo_update",
1702
+ EditorContentStart: "editor_content_start",
1703
+ EditorContentDelta: "editor_content_delta",
1704
+ EditorContentEnd: "editor_content_end",
1705
+ Complete: "complete",
1706
+ Error: "error",
1707
+ Disconnected: "disconnected",
1708
+ Retry: "retry"
1709
+ };
1710
+
1711
+ // src/stream-manager.ts
1712
+ var StreamManager = class {
1713
+ constructor(session) {
1714
+ this._state = "idle";
1715
+ this._activeConversationId = null;
1716
+ this._backgroundJobs = /* @__PURE__ */ new Map();
1717
+ this.handlers = [];
1718
+ this.unsub = null;
1719
+ this.session = session;
1720
+ this.attach();
1721
+ }
1722
+ // ── Public state ──────────────────────────────────────────────
1723
+ get state() {
1724
+ return this._state;
1725
+ }
1726
+ get activeConversationId() {
1727
+ return this._activeConversationId;
1728
+ }
1729
+ get backgroundJobs() {
1730
+ return this._backgroundJobs;
1731
+ }
1732
+ // ── Event subscription ────────────────────────────────────────
1733
+ on(handler) {
1734
+ this.handlers.push(handler);
1735
+ return () => {
1736
+ this.handlers = this.handlers.filter((h) => h !== handler);
1737
+ };
1738
+ }
1739
+ emit(event) {
1740
+ for (const handler of this.handlers) {
1741
+ try {
1742
+ handler(event);
1743
+ } catch {
1744
+ }
1745
+ }
1746
+ }
1747
+ setState(state) {
1748
+ this._state = state;
1749
+ this.emit({
1750
+ type: "stateChange",
1751
+ state,
1752
+ conversationId: this._activeConversationId
1753
+ });
1754
+ }
1755
+ // ── Session event wiring ──────────────────────────────────────
1756
+ attach() {
1757
+ this.unsub = this.session.on((event) => {
1758
+ this.onSessionEvent(event);
1759
+ });
1760
+ }
1761
+ onSessionEvent(event) {
1762
+ const convId = this.session.conversationId;
1763
+ if (event.type === ChatEventType.BlocksChanged) {
1764
+ if (this._state === "streaming" && convId) {
1765
+ this.emit({
1766
+ type: "blocksChanged",
1767
+ conversationId: convId,
1768
+ blocks: event.blocks
1769
+ });
1770
+ }
1771
+ return;
1772
+ }
1773
+ this.emit({
1774
+ type: "event",
1775
+ conversationId: convId,
1776
+ event
1777
+ });
1778
+ if (event.type === ChatEventType.Complete) {
1779
+ if (this._state === "streaming") {
1780
+ this.setState("idle");
1781
+ }
1782
+ }
1783
+ }
1784
+ // ── Send ──────────────────────────────────────────────────────
1785
+ async send(content, options) {
1786
+ if (this._state === "streaming") return;
1787
+ if (!this._activeConversationId) {
1788
+ const id = await this.session.createNewConversation();
1789
+ this.setActiveConversation(id);
1790
+ }
1791
+ this.prepareUserBlock(content);
1792
+ this.setState("streaming");
1793
+ try {
1794
+ await this.session.send(content, {
1795
+ enableSearch: options?.enableSearch,
1796
+ agentName: options?.agentName,
1797
+ uploadIds: options?.uploadIds
1798
+ });
1799
+ } catch {
1800
+ }
1801
+ this.finalizeStream();
1802
+ }
1803
+ // ── Regenerate ────────────────────────────────────────────────
1804
+ async regenerate() {
1805
+ if (this._state === "streaming") return;
1806
+ const userMsgs = this.session.messages.filter(
1807
+ (m) => m.role === "user"
1808
+ );
1809
+ const lastUserMsg = userMsgs[userMsgs.length - 1];
1810
+ if (!lastUserMsg) return;
1811
+ this.prepareUserBlock(lastUserMsg.content);
1812
+ this.setState("streaming");
1813
+ try {
1814
+ await this.session.resendFromCheckpoint(
1815
+ lastUserMsg.id,
1816
+ lastUserMsg.content
1817
+ );
1818
+ } catch {
1819
+ }
1820
+ this.finalizeStream();
1821
+ }
1822
+ // ── Switch conversation ───────────────────────────────────────
1823
+ async switchTo(conversationId) {
1824
+ if (conversationId === this._activeConversationId) return;
1825
+ if (this._state === "streaming") {
1826
+ const oldConvId = this._activeConversationId;
1827
+ const jobId = this.session.currentJobId;
1828
+ if (oldConvId && jobId) {
1829
+ this._backgroundJobs.set(oldConvId, jobId);
1830
+ this.emit({
1831
+ type: "backgroundJobsChanged",
1832
+ jobs: this._backgroundJobs
1833
+ });
1834
+ }
1835
+ this.session.detach();
1836
+ }
1837
+ if (this._backgroundJobs.has(conversationId)) {
1838
+ this._backgroundJobs.delete(conversationId);
1839
+ this.emit({
1840
+ type: "backgroundJobsChanged",
1841
+ jobs: this._backgroundJobs
1842
+ });
1843
+ }
1844
+ this.setActiveConversation(conversationId);
1845
+ await this.restore(conversationId);
1846
+ }
1847
+ // ── Create / delete conversation ──────────────────────────────
1848
+ async createConversation() {
1849
+ const id = await this.session.createNewConversation();
1850
+ this.setActiveConversation(id);
1851
+ return id;
1852
+ }
1853
+ async deleteConversation(id) {
1854
+ await this.session.deleteConversation(id);
1855
+ this._backgroundJobs.delete(id);
1856
+ if (this._activeConversationId === id) {
1857
+ this._activeConversationId = null;
1858
+ this.emit({ type: "conversationChanged", conversationId: null });
1859
+ }
1860
+ }
1861
+ // ── Stop (explicit cancel) ────────────────────────────────────
1862
+ stop() {
1863
+ this.session.disconnect();
1864
+ this.setState("idle");
1865
+ }
1866
+ // ── Cleanup ───────────────────────────────────────────────────
1867
+ destroy() {
1868
+ if (this.unsub) {
1869
+ this.unsub();
1870
+ this.unsub = null;
1871
+ }
1872
+ this.handlers = [];
1873
+ }
1874
+ // ── Internal: helpers ──────────────────────────────────────────
1875
+ prepareUserBlock(content) {
1876
+ this.session.blockBuilder.reset();
1877
+ this.session.blockBuilder.addBlock({
1878
+ type: "user",
1879
+ id: this.session.blockBuilder.nextId(),
1880
+ content
1881
+ });
1882
+ }
1883
+ finalizeStream() {
1884
+ if (this._state === "streaming") {
1885
+ this.setState("idle");
1886
+ }
1887
+ }
1888
+ // ── Internal: restore ─────────────────────────────────────────
1889
+ async restore(conversationId) {
1890
+ this.setState("restoring");
1891
+ let activeJobId = null;
1892
+ try {
1893
+ const res = await this.session.client.get(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);
1894
+ activeJobId = res.job_id ?? null;
1895
+ } catch {
1896
+ }
1897
+ if (activeJobId) {
1898
+ await this.session.loadConversation(conversationId);
1899
+ this.setState("streaming");
1900
+ try {
1901
+ await this.session.reconnectToJob(activeJobId);
1902
+ } catch {
1903
+ }
1904
+ if (this._state === "streaming") {
1905
+ this.setState("idle");
1906
+ }
1907
+ } else {
1908
+ await this.session.loadConversation(conversationId);
1909
+ try {
1910
+ const jobs = await this.session.client.get(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);
1911
+ const completedJobs = jobs.filter(
1912
+ (j) => j.status === "completed"
1913
+ );
1914
+ for (const job of completedJobs) {
1915
+ await this.session.switchConversation(conversationId, job.job_id);
1916
+ }
1917
+ if (completedJobs.length > 0) {
1918
+ this.emit({
1919
+ type: "blocksChanged",
1920
+ conversationId,
1921
+ blocks: this.session.blockBuilder.getBlocks()
1922
+ });
1923
+ this.emit({
1924
+ type: "versionsReady",
1925
+ conversationId,
1926
+ count: completedJobs.length
1927
+ });
1928
+ }
1929
+ } catch {
1930
+ }
1931
+ this.setState("idle");
1932
+ }
1933
+ }
1934
+ // ── Internal: set active conversation ─────────────────────────
1935
+ setActiveConversation(id) {
1936
+ this._activeConversationId = id;
1937
+ this.emit({ type: "conversationChanged", conversationId: id });
1938
+ }
1939
+ };
1496
1940
  // Annotate the CommonJS export names for ESM import in node:
1497
1941
  0 && (module.exports = {
1498
1942
  AstralformClient,
1499
1943
  AstralformError,
1500
1944
  AuthenticationError,
1501
1945
  BlockBuilder,
1946
+ ChatEventType,
1502
1947
  ChatSession,
1503
1948
  ConnectionError,
1504
1949
  InMemoryStorage,
@@ -1506,6 +1951,7 @@ var standardHandlers = {
1506
1951
  RateLimitError,
1507
1952
  ServerError,
1508
1953
  StreamAbortedError,
1954
+ StreamManager,
1509
1955
  ToolRegistry,
1510
1956
  generateId,
1511
1957
  standardHandlers,