@astralform/js 0.2.1 → 0.2.3

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,
@@ -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,
@@ -1060,6 +1092,40 @@ var ChatSession = class {
1060
1092
  this.todos = event.todos;
1061
1093
  this.emit({ type: "todo_update", todos: event.todos });
1062
1094
  break;
1095
+ case "context_update":
1096
+ this.emit({
1097
+ type: "context_update",
1098
+ context: event.context,
1099
+ phase: event.phase,
1100
+ updatedAt: event.updated_at
1101
+ });
1102
+ break;
1103
+ case "desktop_stream":
1104
+ this.emit({
1105
+ type: "desktop_stream",
1106
+ url: event.url,
1107
+ authKey: event.auth_key,
1108
+ sandboxId: event.sandbox_id
1109
+ });
1110
+ break;
1111
+ case "attachment_staged":
1112
+ this.emit({
1113
+ type: "attachment_staged",
1114
+ files: (event.files || []).map((f) => ({
1115
+ name: f.name,
1116
+ path: f.path,
1117
+ mediaType: f.media_type,
1118
+ sizeBytes: f.size_bytes
1119
+ }))
1120
+ });
1121
+ break;
1122
+ case "workspace_ready":
1123
+ this.emit({
1124
+ type: "workspace_ready",
1125
+ conversationId: event.conversation_id,
1126
+ sandboxId: event.sandbox_id
1127
+ });
1128
+ break;
1063
1129
  case "asset_created":
1064
1130
  this.emit({
1065
1131
  type: "asset_created",
@@ -1070,27 +1136,6 @@ var ChatSession = class {
1070
1136
  sizeBytes: event.size_bytes
1071
1137
  });
1072
1138
  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
1139
  case "editor_content_start":
1095
1140
  this.emit({
1096
1141
  type: "editor_content_start",
@@ -1212,19 +1257,26 @@ var ChatSession = class {
1212
1257
  this.abortController = null;
1213
1258
  }
1214
1259
  }
1215
- disconnect() {
1216
- if (this.currentJobId) {
1217
- this.client.cancelJob(this.currentJobId).catch(() => {
1218
- });
1219
- this.currentJobId = null;
1220
- }
1260
+ /** Detach from the SSE stream without cancelling the job.
1261
+ * The backend job keeps running — caller can reconnect later. */
1262
+ detach() {
1221
1263
  this.abortController?.abort();
1222
1264
  this.abortController = null;
1223
1265
  this.isStreaming = false;
1224
1266
  this.streamingContent = "";
1225
1267
  this.executingTool = null;
1268
+ this.blockBuilder.reset();
1226
1269
  this.emit({ type: "disconnected" });
1227
1270
  }
1271
+ /** Stop the job and disconnect (explicit user action). */
1272
+ disconnect() {
1273
+ if (this.currentJobId) {
1274
+ this.client.cancelJob(this.currentJobId).catch(() => {
1275
+ });
1276
+ }
1277
+ this.detach();
1278
+ this.currentJobId = null;
1279
+ }
1228
1280
  async createNewConversation() {
1229
1281
  const id = generateId();
1230
1282
  const conversation = await this.storage.createConversation(
@@ -1292,10 +1344,31 @@ function finalizeThinking(builder) {
1292
1344
  builder.thinkingStartMs = null;
1293
1345
  }
1294
1346
  }
1347
+ function finalizeEditor(builder) {
1348
+ if (builder.activeEditorId) {
1349
+ builder.patchBlock(builder.activeEditorId, {
1350
+ isStreaming: false
1351
+ });
1352
+ builder.activeEditorId = null;
1353
+ }
1354
+ }
1295
1355
  var handleUserMessage = (event, builder) => {
1296
- if (builder.findBlock((b) => b.type === "user")) return;
1297
1356
  const e = event;
1298
- builder.addBlock({ type: "user", id: builder.nextId(), content: e.content });
1357
+ const existing = builder.findBlock((b) => b.type === "user");
1358
+ if (existing) {
1359
+ if (e.createdAt) {
1360
+ builder.patchBlock(existing.id, {
1361
+ createdAt: e.createdAt
1362
+ });
1363
+ }
1364
+ return;
1365
+ }
1366
+ builder.addBlock({
1367
+ type: "user",
1368
+ id: builder.nextId(),
1369
+ content: e.content,
1370
+ createdAt: e.createdAt
1371
+ });
1299
1372
  };
1300
1373
  var handleChunk = (event, builder) => {
1301
1374
  const e = event;
@@ -1337,12 +1410,26 @@ var handleToolCall = (event, builder) => {
1337
1410
  var handleToolExecuting = (event, builder) => {
1338
1411
  const e = event;
1339
1412
  const block = builder.findBlock(
1340
- (b) => b.type === "tool" && b.toolName === e.name && b.status === "calling"
1413
+ (b) => b.type === "tool" && (e.call_id ? b.callId === e.call_id : b.toolName === e.name) && b.status === "calling"
1341
1414
  );
1342
1415
  if (block) {
1343
1416
  builder.patchBlock(block.id, { status: "executing" });
1344
1417
  }
1345
1418
  };
1419
+ var handleToolProgress = (event, builder) => {
1420
+ const e = event;
1421
+ const block = builder.findBlock(
1422
+ (b) => b.type === "tool" && b.callId === e.callId
1423
+ );
1424
+ if (block && block.type === "tool") {
1425
+ const sources = block.sources ? [...block.sources] : [];
1426
+ sources.push(e.item);
1427
+ builder.patchBlock(block.id, {
1428
+ sources,
1429
+ status: "executing"
1430
+ });
1431
+ }
1432
+ };
1346
1433
  var handleToolEnd = (event, builder) => {
1347
1434
  const e = event;
1348
1435
  const callId = e.type === "tool_end" ? e.callId : void 0;
@@ -1455,6 +1542,7 @@ var handleSubagentEnd = (event, builder) => {
1455
1542
  var handleComplete = (_event, builder) => {
1456
1543
  finalizeText(builder);
1457
1544
  finalizeThinking(builder);
1545
+ finalizeEditor(builder);
1458
1546
  for (const b of builder.getBlocks()) {
1459
1547
  if (b.type === "tool" && b.status !== "completed") {
1460
1548
  builder.patchBlock(b.id, { status: "completed" });
@@ -1474,31 +1562,458 @@ var handleError = (event, builder) => {
1474
1562
  var handleDisconnected = (_event, builder) => {
1475
1563
  finalizeText(builder);
1476
1564
  finalizeThinking(builder);
1565
+ finalizeEditor(builder);
1566
+ };
1567
+ var handleCapsuleOutputChunk = (event, builder) => {
1568
+ const e = event;
1569
+ const block = builder.findBlock(
1570
+ (b) => b.type === "capsule" && b.callId === e.callId
1571
+ );
1572
+ if (block && block.type === "capsule") {
1573
+ builder.patchBlock(block.id, {
1574
+ output: block.output + e.chunk
1575
+ });
1576
+ } else {
1577
+ builder.addBlock({
1578
+ type: "capsule",
1579
+ id: builder.nextId(),
1580
+ callId: e.callId,
1581
+ toolName: "",
1582
+ output: e.chunk,
1583
+ isActive: true
1584
+ });
1585
+ }
1586
+ };
1587
+ var handleCapsuleOutput = (event, builder) => {
1588
+ const e = event;
1589
+ const block = builder.findBlock(
1590
+ (b) => b.type === "capsule" && b.callId === (e.callId ?? "")
1591
+ );
1592
+ if (block) {
1593
+ builder.patchBlock(block.id, {
1594
+ output: e.output,
1595
+ command: e.command,
1596
+ toolName: e.toolName,
1597
+ durationMs: e.durationMs,
1598
+ isActive: false
1599
+ });
1600
+ } else {
1601
+ builder.addBlock({
1602
+ type: "capsule",
1603
+ id: builder.nextId(),
1604
+ callId: e.callId ?? "",
1605
+ toolName: e.toolName,
1606
+ command: e.command,
1607
+ output: e.output,
1608
+ durationMs: e.durationMs,
1609
+ isActive: false
1610
+ });
1611
+ }
1612
+ };
1613
+ var handleAssetCreated = (event, builder) => {
1614
+ const e = event;
1615
+ builder.addBlock({
1616
+ type: "asset",
1617
+ id: builder.nextId(),
1618
+ assetId: e.assetId,
1619
+ name: e.name,
1620
+ url: e.url,
1621
+ mediaType: e.mediaType,
1622
+ sizeBytes: e.sizeBytes
1623
+ });
1624
+ };
1625
+ var handleTodoUpdate = (event, builder) => {
1626
+ const e = event;
1627
+ if (builder.activeTodoId) {
1628
+ builder.patchBlock(builder.activeTodoId, {
1629
+ todos: e.todos
1630
+ });
1631
+ } else {
1632
+ const id = builder.nextId();
1633
+ builder.activeTodoId = id;
1634
+ builder.addBlock({
1635
+ type: "todo",
1636
+ id,
1637
+ todos: e.todos
1638
+ });
1639
+ }
1640
+ };
1641
+ var handleEditorContentStart = (event, builder) => {
1642
+ const e = event;
1643
+ const id = builder.nextId();
1644
+ builder.activeEditorId = id;
1645
+ builder.addBlock({
1646
+ type: "editor",
1647
+ id,
1648
+ callId: e.callId,
1649
+ path: e.path,
1650
+ language: e.language,
1651
+ content: "",
1652
+ isStreaming: true
1653
+ });
1654
+ };
1655
+ var handleEditorContentDelta = (event, builder) => {
1656
+ const e = event;
1657
+ const block = builder.findBlock(
1658
+ (b) => b.type === "editor" && b.callId === e.callId
1659
+ );
1660
+ if (block && block.type === "editor") {
1661
+ builder.patchBlock(block.id, {
1662
+ content: block.content + e.delta
1663
+ });
1664
+ }
1665
+ };
1666
+ var handleEditorContentEnd = (event, builder) => {
1667
+ const e = event;
1668
+ const block = builder.findBlock(
1669
+ (b) => b.type === "editor" && b.callId === e.callId
1670
+ );
1671
+ if (block) {
1672
+ builder.patchBlock(block.id, {
1673
+ isStreaming: false
1674
+ });
1675
+ }
1676
+ builder.activeEditorId = null;
1677
+ };
1678
+ var handleDesktopStream = (event, builder) => {
1679
+ const e = event;
1680
+ if (!e.url) return;
1681
+ const existing = builder.findBlock((b) => b.type === "desktop_stream");
1682
+ if (existing) {
1683
+ builder.patchBlock(existing.id, {
1684
+ url: e.url,
1685
+ authKey: e.authKey,
1686
+ sandboxId: e.sandboxId
1687
+ });
1688
+ } else {
1689
+ builder.addBlock({
1690
+ type: "desktop_stream",
1691
+ id: builder.nextId(),
1692
+ url: e.url,
1693
+ authKey: e.authKey,
1694
+ sandboxId: e.sandboxId
1695
+ });
1696
+ }
1697
+ };
1698
+ var handleAttachmentStaged = (event, builder) => {
1699
+ const e = event;
1700
+ if (!e.files || e.files.length === 0) return;
1701
+ builder.addBlock({
1702
+ type: "attachment",
1703
+ id: builder.nextId(),
1704
+ files: e.files
1705
+ });
1706
+ };
1707
+ var noop = () => {
1477
1708
  };
1478
1709
  var standardHandlers = {
1479
1710
  user_message: handleUserMessage,
1480
1711
  chunk: handleChunk,
1481
1712
  tool_call: handleToolCall,
1482
1713
  tool_executing: handleToolExecuting,
1714
+ tool_progress: handleToolProgress,
1483
1715
  tool_completed: handleToolEnd,
1484
1716
  tool_end: handleToolEnd,
1485
1717
  agent_start: handleAgentStart,
1718
+ agent_end: noop,
1486
1719
  thinking_delta: handleThinkingDelta,
1487
1720
  thinking_complete: handleThinkingComplete,
1488
1721
  subagent_start: handleSubagentStart,
1489
1722
  subagent_chunk: handleSubagentChunk,
1490
1723
  subagent_update: handleSubagentUpdate,
1491
1724
  subagent_end: handleSubagentEnd,
1725
+ subagent_tool_use: noop,
1726
+ capsule_output: handleCapsuleOutput,
1727
+ capsule_output_chunk: handleCapsuleOutputChunk,
1728
+ asset_created: handleAssetCreated,
1729
+ todo_update: handleTodoUpdate,
1730
+ editor_content_start: handleEditorContentStart,
1731
+ editor_content_delta: handleEditorContentDelta,
1732
+ editor_content_end: handleEditorContentEnd,
1733
+ desktop_stream: handleDesktopStream,
1734
+ attachment_staged: handleAttachmentStaged,
1735
+ workspace_ready: noop,
1736
+ retry: noop,
1492
1737
  complete: handleComplete,
1493
1738
  error: handleError,
1494
1739
  disconnected: handleDisconnected
1495
1740
  };
1741
+
1742
+ // src/types.ts
1743
+ var ChatEventType = {
1744
+ Connected: "connected",
1745
+ BlocksChanged: "blocks_changed",
1746
+ UserMessage: "user_message",
1747
+ TitleGenerated: "title_generated",
1748
+ ModelInfo: "model_info",
1749
+ Chunk: "chunk",
1750
+ ToolCall: "tool_call",
1751
+ ToolExecuting: "tool_executing",
1752
+ ToolProgress: "tool_progress",
1753
+ ToolCompleted: "tool_completed",
1754
+ ToolEnd: "tool_end",
1755
+ AgentStart: "agent_start",
1756
+ AgentEnd: "agent_end",
1757
+ ThinkingDelta: "thinking_delta",
1758
+ ThinkingComplete: "thinking_complete",
1759
+ SubagentStart: "subagent_start",
1760
+ SubagentChunk: "subagent_chunk",
1761
+ SubagentUpdate: "subagent_update",
1762
+ SubagentEnd: "subagent_end",
1763
+ SubagentToolUse: "subagent_tool_use",
1764
+ CapsuleOutput: "capsule_output",
1765
+ CapsuleOutputChunk: "capsule_output_chunk",
1766
+ AssetCreated: "asset_created",
1767
+ TodoUpdate: "todo_update",
1768
+ EditorContentStart: "editor_content_start",
1769
+ EditorContentDelta: "editor_content_delta",
1770
+ EditorContentEnd: "editor_content_end",
1771
+ Complete: "complete",
1772
+ Error: "error",
1773
+ Disconnected: "disconnected",
1774
+ Retry: "retry",
1775
+ ContextUpdate: "context_update",
1776
+ DesktopStream: "desktop_stream",
1777
+ AttachmentStaged: "attachment_staged",
1778
+ WorkspaceReady: "workspace_ready"
1779
+ };
1780
+
1781
+ // src/stream-manager.ts
1782
+ var StreamManager = class {
1783
+ constructor(session) {
1784
+ this._state = "idle";
1785
+ this._activeConversationId = null;
1786
+ this._backgroundJobs = /* @__PURE__ */ new Map();
1787
+ this.handlers = [];
1788
+ this.unsub = null;
1789
+ this.session = session;
1790
+ this.attach();
1791
+ }
1792
+ // ── Public state ──────────────────────────────────────────────
1793
+ get state() {
1794
+ return this._state;
1795
+ }
1796
+ get activeConversationId() {
1797
+ return this._activeConversationId;
1798
+ }
1799
+ get backgroundJobs() {
1800
+ return this._backgroundJobs;
1801
+ }
1802
+ // ── Event subscription ────────────────────────────────────────
1803
+ on(handler) {
1804
+ this.handlers.push(handler);
1805
+ return () => {
1806
+ this.handlers = this.handlers.filter((h) => h !== handler);
1807
+ };
1808
+ }
1809
+ emit(event) {
1810
+ for (const handler of this.handlers) {
1811
+ try {
1812
+ handler(event);
1813
+ } catch {
1814
+ }
1815
+ }
1816
+ }
1817
+ setState(state) {
1818
+ this._state = state;
1819
+ this.emit({
1820
+ type: "stateChange",
1821
+ state,
1822
+ conversationId: this._activeConversationId
1823
+ });
1824
+ }
1825
+ // ── Session event wiring ──────────────────────────────────────
1826
+ attach() {
1827
+ this.unsub = this.session.on((event) => {
1828
+ this.onSessionEvent(event);
1829
+ });
1830
+ }
1831
+ onSessionEvent(event) {
1832
+ const convId = this.session.conversationId;
1833
+ if (event.type === ChatEventType.BlocksChanged) {
1834
+ if (this._state === "streaming" && convId) {
1835
+ this.emit({
1836
+ type: "blocksChanged",
1837
+ conversationId: convId,
1838
+ blocks: event.blocks
1839
+ });
1840
+ }
1841
+ return;
1842
+ }
1843
+ this.emit({
1844
+ type: "event",
1845
+ conversationId: convId,
1846
+ event
1847
+ });
1848
+ if (event.type === ChatEventType.Complete) {
1849
+ if (this._state === "streaming") {
1850
+ this.setState("idle");
1851
+ }
1852
+ }
1853
+ }
1854
+ // ── Send ──────────────────────────────────────────────────────
1855
+ async send(content, options) {
1856
+ if (this._state === "streaming") return;
1857
+ if (!this._activeConversationId) {
1858
+ const id = await this.session.createNewConversation();
1859
+ this.setActiveConversation(id);
1860
+ }
1861
+ this.prepareUserBlock(content);
1862
+ this.setState("streaming");
1863
+ try {
1864
+ await this.session.send(content, {
1865
+ enableSearch: options?.enableSearch,
1866
+ agentName: options?.agentName,
1867
+ uploadIds: options?.uploadIds
1868
+ });
1869
+ } catch {
1870
+ }
1871
+ this.finalizeStream();
1872
+ }
1873
+ // ── Regenerate ────────────────────────────────────────────────
1874
+ async regenerate() {
1875
+ if (this._state === "streaming") return;
1876
+ const userMsgs = this.session.messages.filter(
1877
+ (m) => m.role === "user"
1878
+ );
1879
+ const lastUserMsg = userMsgs[userMsgs.length - 1];
1880
+ if (!lastUserMsg) return;
1881
+ this.prepareUserBlock(lastUserMsg.content);
1882
+ this.setState("streaming");
1883
+ try {
1884
+ await this.session.resendFromCheckpoint(
1885
+ lastUserMsg.id,
1886
+ lastUserMsg.content
1887
+ );
1888
+ } catch {
1889
+ }
1890
+ this.finalizeStream();
1891
+ }
1892
+ // ── Switch conversation ───────────────────────────────────────
1893
+ async switchTo(conversationId) {
1894
+ if (conversationId === this._activeConversationId) return;
1895
+ if (this._state === "streaming") {
1896
+ const oldConvId = this._activeConversationId;
1897
+ const jobId = this.session.currentJobId;
1898
+ if (oldConvId && jobId) {
1899
+ this._backgroundJobs.set(oldConvId, jobId);
1900
+ this.emit({
1901
+ type: "backgroundJobsChanged",
1902
+ jobs: this._backgroundJobs
1903
+ });
1904
+ }
1905
+ this.session.detach();
1906
+ }
1907
+ if (this._backgroundJobs.has(conversationId)) {
1908
+ this._backgroundJobs.delete(conversationId);
1909
+ this.emit({
1910
+ type: "backgroundJobsChanged",
1911
+ jobs: this._backgroundJobs
1912
+ });
1913
+ }
1914
+ this.setActiveConversation(conversationId);
1915
+ await this.restore(conversationId);
1916
+ }
1917
+ // ── Create / delete conversation ──────────────────────────────
1918
+ async createConversation() {
1919
+ const id = await this.session.createNewConversation();
1920
+ this.setActiveConversation(id);
1921
+ return id;
1922
+ }
1923
+ async deleteConversation(id) {
1924
+ await this.session.deleteConversation(id);
1925
+ this._backgroundJobs.delete(id);
1926
+ if (this._activeConversationId === id) {
1927
+ this._activeConversationId = null;
1928
+ this.emit({ type: "conversationChanged", conversationId: null });
1929
+ }
1930
+ }
1931
+ // ── Stop (explicit cancel) ────────────────────────────────────
1932
+ stop() {
1933
+ this.session.disconnect();
1934
+ this.setState("idle");
1935
+ }
1936
+ // ── Cleanup ───────────────────────────────────────────────────
1937
+ destroy() {
1938
+ if (this.unsub) {
1939
+ this.unsub();
1940
+ this.unsub = null;
1941
+ }
1942
+ this.handlers = [];
1943
+ }
1944
+ // ── Internal: helpers ──────────────────────────────────────────
1945
+ prepareUserBlock(content) {
1946
+ this.session.blockBuilder.reset();
1947
+ this.session.blockBuilder.addBlock({
1948
+ type: "user",
1949
+ id: this.session.blockBuilder.nextId(),
1950
+ content
1951
+ });
1952
+ }
1953
+ finalizeStream() {
1954
+ if (this._state === "streaming") {
1955
+ this.setState("idle");
1956
+ }
1957
+ }
1958
+ // ── Internal: restore ─────────────────────────────────────────
1959
+ async restore(conversationId) {
1960
+ this.setState("restoring");
1961
+ let activeJobId = null;
1962
+ try {
1963
+ const res = await this.session.client.get(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);
1964
+ activeJobId = res.job_id ?? null;
1965
+ } catch {
1966
+ }
1967
+ if (activeJobId) {
1968
+ await this.session.loadConversation(conversationId);
1969
+ this.setState("streaming");
1970
+ try {
1971
+ await this.session.reconnectToJob(activeJobId);
1972
+ } catch {
1973
+ }
1974
+ if (this._state === "streaming") {
1975
+ this.setState("idle");
1976
+ }
1977
+ } else {
1978
+ await this.session.loadConversation(conversationId);
1979
+ try {
1980
+ const jobs = await this.session.client.get(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);
1981
+ const completedJobs = jobs.filter(
1982
+ (j) => j.status === "completed"
1983
+ );
1984
+ for (const job of completedJobs) {
1985
+ await this.session.switchConversation(conversationId, job.job_id);
1986
+ }
1987
+ if (completedJobs.length > 0) {
1988
+ this.emit({
1989
+ type: "blocksChanged",
1990
+ conversationId,
1991
+ blocks: this.session.blockBuilder.getBlocks()
1992
+ });
1993
+ this.emit({
1994
+ type: "versionsReady",
1995
+ conversationId,
1996
+ count: completedJobs.length
1997
+ });
1998
+ }
1999
+ } catch {
2000
+ }
2001
+ this.setState("idle");
2002
+ }
2003
+ }
2004
+ // ── Internal: set active conversation ─────────────────────────
2005
+ setActiveConversation(id) {
2006
+ this._activeConversationId = id;
2007
+ this.emit({ type: "conversationChanged", conversationId: id });
2008
+ }
2009
+ };
1496
2010
  // Annotate the CommonJS export names for ESM import in node:
1497
2011
  0 && (module.exports = {
1498
2012
  AstralformClient,
1499
2013
  AstralformError,
1500
2014
  AuthenticationError,
1501
2015
  BlockBuilder,
2016
+ ChatEventType,
1502
2017
  ChatSession,
1503
2018
  ConnectionError,
1504
2019
  InMemoryStorage,
@@ -1506,6 +2021,7 @@ var standardHandlers = {
1506
2021
  RateLimitError,
1507
2022
  ServerError,
1508
2023
  StreamAbortedError,
2024
+ StreamManager,
1509
2025
  ToolRegistry,
1510
2026
  generateId,
1511
2027
  standardHandlers,