@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.js CHANGED
@@ -332,6 +332,8 @@ var BlockBuilder = class {
332
332
  this.activeTextId = null;
333
333
  this.activeThinkingId = null;
334
334
  this.thinkingStartMs = null;
335
+ this.activeEditorId = null;
336
+ this.activeTodoId = null;
335
337
  }
336
338
  // ── Registration ──────────────────────────────────────────────
337
339
  on(eventType, handler) {
@@ -358,6 +360,8 @@ var BlockBuilder = class {
358
360
  this.activeTextId = null;
359
361
  this.activeThinkingId = null;
360
362
  this.thinkingStartMs = null;
363
+ this.activeEditorId = null;
364
+ this.activeTodoId = null;
361
365
  }
362
366
  setOnChange(fn) {
363
367
  this._onChange = fn;
@@ -583,7 +587,6 @@ var ChatSession = class {
583
587
  this.thinkingContent = "";
584
588
  this.isThinking = false;
585
589
  this.activeSubagents = /* @__PURE__ */ new Map();
586
- this.sources = [];
587
590
  this.capsuleOutputs = [];
588
591
  this.todos = [];
589
592
  this.activeTools = /* @__PURE__ */ new Map();
@@ -687,7 +690,6 @@ var ChatSession = class {
687
690
  this.thinkingContent = "";
688
691
  this.isThinking = false;
689
692
  this.activeSubagents.clear();
690
- this.sources = [];
691
693
  this.capsuleOutputs = [];
692
694
  this.todos = [];
693
695
  this.activeTools.clear();
@@ -699,10 +701,12 @@ var ChatSession = class {
699
701
  try {
700
702
  await this.consumeJobStream(request);
701
703
  } catch (err) {
702
- this.emit({
703
- type: "error",
704
- error: err instanceof Error ? err : new ConnectionError(String(err))
705
- });
704
+ if (!(err instanceof DOMException && err.name === "AbortError")) {
705
+ this.emit({
706
+ type: "error",
707
+ error: err instanceof Error ? err : new ConnectionError(String(err))
708
+ });
709
+ }
706
710
  } finally {
707
711
  this.isStreaming = false;
708
712
  this.executingTool = null;
@@ -855,11 +859,24 @@ var ChatSession = class {
855
859
  applyEvent(event) {
856
860
  switch (event.type) {
857
861
  case "user_message":
858
- this.emit({ type: "user_message", content: event.content });
862
+ this.emit({
863
+ type: "user_message",
864
+ content: event.content,
865
+ createdAt: event.created_at
866
+ });
859
867
  break;
860
- case "title_generated":
868
+ case "title_generated": {
869
+ if (this.conversationId && event.title) {
870
+ const conv = this.conversations.find(
871
+ (c) => c.id === this.conversationId
872
+ );
873
+ if (conv) {
874
+ conv.title = event.title;
875
+ }
876
+ }
861
877
  this.emit({ type: "title_generated", title: event.title });
862
878
  break;
879
+ }
863
880
  case "message_start":
864
881
  if (event.conversation_id && !this.conversationId) {
865
882
  this.conversationId = event.conversation_id;
@@ -882,6 +899,23 @@ var ChatSession = class {
882
899
  this.isThinking = false;
883
900
  this.emit({ type: "thinking_complete" });
884
901
  break;
902
+ case "tool_executing":
903
+ this.emit({
904
+ type: "tool_executing",
905
+ name: event.tool,
906
+ call_id: event.call_id
907
+ });
908
+ break;
909
+ case "tool_progress":
910
+ this.emit({
911
+ type: "tool_progress",
912
+ callId: event.call_id,
913
+ tool: event.tool,
914
+ index: event.index,
915
+ total: event.total,
916
+ item: event.item
917
+ });
918
+ break;
885
919
  case "message_stop":
886
920
  this.emit({
887
921
  type: "complete",
@@ -991,10 +1025,6 @@ var ChatSession = class {
991
1025
  result: event.result
992
1026
  });
993
1027
  break;
994
- case "sources":
995
- this.sources.push(...event.sources);
996
- this.emit({ type: "sources", sources: event.sources });
997
- break;
998
1028
  case "capsule_output": {
999
1029
  const capsule = {
1000
1030
  toolName: event.tool_name,
@@ -1020,6 +1050,40 @@ var ChatSession = class {
1020
1050
  this.todos = event.todos;
1021
1051
  this.emit({ type: "todo_update", todos: event.todos });
1022
1052
  break;
1053
+ case "context_update":
1054
+ this.emit({
1055
+ type: "context_update",
1056
+ context: event.context,
1057
+ phase: event.phase,
1058
+ updatedAt: event.updated_at
1059
+ });
1060
+ break;
1061
+ case "desktop_stream":
1062
+ this.emit({
1063
+ type: "desktop_stream",
1064
+ url: event.url,
1065
+ authKey: event.auth_key,
1066
+ sandboxId: event.sandbox_id
1067
+ });
1068
+ break;
1069
+ case "attachment_staged":
1070
+ this.emit({
1071
+ type: "attachment_staged",
1072
+ files: (event.files || []).map((f) => ({
1073
+ name: f.name,
1074
+ path: f.path,
1075
+ mediaType: f.media_type,
1076
+ sizeBytes: f.size_bytes
1077
+ }))
1078
+ });
1079
+ break;
1080
+ case "workspace_ready":
1081
+ this.emit({
1082
+ type: "workspace_ready",
1083
+ conversationId: event.conversation_id,
1084
+ sandboxId: event.sandbox_id
1085
+ });
1086
+ break;
1023
1087
  case "asset_created":
1024
1088
  this.emit({
1025
1089
  type: "asset_created",
@@ -1030,27 +1094,6 @@ var ChatSession = class {
1030
1094
  sizeBytes: event.size_bytes
1031
1095
  });
1032
1096
  break;
1033
- case "timeline_entry":
1034
- this.emit({
1035
- type: "timeline_entry",
1036
- id: event.id,
1037
- status: event.status,
1038
- kind: event.kind,
1039
- agent_name: event.agent_name,
1040
- tool_name: event.tool_name,
1041
- display_name: event.display_name,
1042
- tool_category: event.tool_category,
1043
- viewer: event.viewer,
1044
- call_id: event.call_id,
1045
- detail: event.detail,
1046
- started_at: event.started_at,
1047
- duration_ms: event.duration_ms,
1048
- output_summary: event.output_summary,
1049
- sources: event.sources,
1050
- parent_id: event.parent_id,
1051
- structured_output: event.structured_output
1052
- });
1053
- break;
1054
1097
  case "editor_content_start":
1055
1098
  this.emit({
1056
1099
  type: "editor_content_start",
@@ -1172,19 +1215,26 @@ var ChatSession = class {
1172
1215
  this.abortController = null;
1173
1216
  }
1174
1217
  }
1175
- disconnect() {
1176
- if (this.currentJobId) {
1177
- this.client.cancelJob(this.currentJobId).catch(() => {
1178
- });
1179
- this.currentJobId = null;
1180
- }
1218
+ /** Detach from the SSE stream without cancelling the job.
1219
+ * The backend job keeps running — caller can reconnect later. */
1220
+ detach() {
1181
1221
  this.abortController?.abort();
1182
1222
  this.abortController = null;
1183
1223
  this.isStreaming = false;
1184
1224
  this.streamingContent = "";
1185
1225
  this.executingTool = null;
1226
+ this.blockBuilder.reset();
1186
1227
  this.emit({ type: "disconnected" });
1187
1228
  }
1229
+ /** Stop the job and disconnect (explicit user action). */
1230
+ disconnect() {
1231
+ if (this.currentJobId) {
1232
+ this.client.cancelJob(this.currentJobId).catch(() => {
1233
+ });
1234
+ }
1235
+ this.detach();
1236
+ this.currentJobId = null;
1237
+ }
1188
1238
  async createNewConversation() {
1189
1239
  const id = generateId();
1190
1240
  const conversation = await this.storage.createConversation(
@@ -1252,10 +1302,31 @@ function finalizeThinking(builder) {
1252
1302
  builder.thinkingStartMs = null;
1253
1303
  }
1254
1304
  }
1305
+ function finalizeEditor(builder) {
1306
+ if (builder.activeEditorId) {
1307
+ builder.patchBlock(builder.activeEditorId, {
1308
+ isStreaming: false
1309
+ });
1310
+ builder.activeEditorId = null;
1311
+ }
1312
+ }
1255
1313
  var handleUserMessage = (event, builder) => {
1256
- if (builder.findBlock((b) => b.type === "user")) return;
1257
1314
  const e = event;
1258
- builder.addBlock({ type: "user", id: builder.nextId(), content: e.content });
1315
+ const existing = builder.findBlock((b) => b.type === "user");
1316
+ if (existing) {
1317
+ if (e.createdAt) {
1318
+ builder.patchBlock(existing.id, {
1319
+ createdAt: e.createdAt
1320
+ });
1321
+ }
1322
+ return;
1323
+ }
1324
+ builder.addBlock({
1325
+ type: "user",
1326
+ id: builder.nextId(),
1327
+ content: e.content,
1328
+ createdAt: e.createdAt
1329
+ });
1259
1330
  };
1260
1331
  var handleChunk = (event, builder) => {
1261
1332
  const e = event;
@@ -1297,12 +1368,26 @@ var handleToolCall = (event, builder) => {
1297
1368
  var handleToolExecuting = (event, builder) => {
1298
1369
  const e = event;
1299
1370
  const block = builder.findBlock(
1300
- (b) => b.type === "tool" && b.toolName === e.name && b.status === "calling"
1371
+ (b) => b.type === "tool" && (e.call_id ? b.callId === e.call_id : b.toolName === e.name) && b.status === "calling"
1301
1372
  );
1302
1373
  if (block) {
1303
1374
  builder.patchBlock(block.id, { status: "executing" });
1304
1375
  }
1305
1376
  };
1377
+ var handleToolProgress = (event, builder) => {
1378
+ const e = event;
1379
+ const block = builder.findBlock(
1380
+ (b) => b.type === "tool" && b.callId === e.callId
1381
+ );
1382
+ if (block && block.type === "tool") {
1383
+ const sources = block.sources ? [...block.sources] : [];
1384
+ sources.push(e.item);
1385
+ builder.patchBlock(block.id, {
1386
+ sources,
1387
+ status: "executing"
1388
+ });
1389
+ }
1390
+ };
1306
1391
  var handleToolEnd = (event, builder) => {
1307
1392
  const e = event;
1308
1393
  const callId = e.type === "tool_end" ? e.callId : void 0;
@@ -1415,6 +1500,7 @@ var handleSubagentEnd = (event, builder) => {
1415
1500
  var handleComplete = (_event, builder) => {
1416
1501
  finalizeText(builder);
1417
1502
  finalizeThinking(builder);
1503
+ finalizeEditor(builder);
1418
1504
  for (const b of builder.getBlocks()) {
1419
1505
  if (b.type === "tool" && b.status !== "completed") {
1420
1506
  builder.patchBlock(b.id, { status: "completed" });
@@ -1434,30 +1520,457 @@ var handleError = (event, builder) => {
1434
1520
  var handleDisconnected = (_event, builder) => {
1435
1521
  finalizeText(builder);
1436
1522
  finalizeThinking(builder);
1523
+ finalizeEditor(builder);
1524
+ };
1525
+ var handleCapsuleOutputChunk = (event, builder) => {
1526
+ const e = event;
1527
+ const block = builder.findBlock(
1528
+ (b) => b.type === "capsule" && b.callId === e.callId
1529
+ );
1530
+ if (block && block.type === "capsule") {
1531
+ builder.patchBlock(block.id, {
1532
+ output: block.output + e.chunk
1533
+ });
1534
+ } else {
1535
+ builder.addBlock({
1536
+ type: "capsule",
1537
+ id: builder.nextId(),
1538
+ callId: e.callId,
1539
+ toolName: "",
1540
+ output: e.chunk,
1541
+ isActive: true
1542
+ });
1543
+ }
1544
+ };
1545
+ var handleCapsuleOutput = (event, builder) => {
1546
+ const e = event;
1547
+ const block = builder.findBlock(
1548
+ (b) => b.type === "capsule" && b.callId === (e.callId ?? "")
1549
+ );
1550
+ if (block) {
1551
+ builder.patchBlock(block.id, {
1552
+ output: e.output,
1553
+ command: e.command,
1554
+ toolName: e.toolName,
1555
+ durationMs: e.durationMs,
1556
+ isActive: false
1557
+ });
1558
+ } else {
1559
+ builder.addBlock({
1560
+ type: "capsule",
1561
+ id: builder.nextId(),
1562
+ callId: e.callId ?? "",
1563
+ toolName: e.toolName,
1564
+ command: e.command,
1565
+ output: e.output,
1566
+ durationMs: e.durationMs,
1567
+ isActive: false
1568
+ });
1569
+ }
1570
+ };
1571
+ var handleAssetCreated = (event, builder) => {
1572
+ const e = event;
1573
+ builder.addBlock({
1574
+ type: "asset",
1575
+ id: builder.nextId(),
1576
+ assetId: e.assetId,
1577
+ name: e.name,
1578
+ url: e.url,
1579
+ mediaType: e.mediaType,
1580
+ sizeBytes: e.sizeBytes
1581
+ });
1582
+ };
1583
+ var handleTodoUpdate = (event, builder) => {
1584
+ const e = event;
1585
+ if (builder.activeTodoId) {
1586
+ builder.patchBlock(builder.activeTodoId, {
1587
+ todos: e.todos
1588
+ });
1589
+ } else {
1590
+ const id = builder.nextId();
1591
+ builder.activeTodoId = id;
1592
+ builder.addBlock({
1593
+ type: "todo",
1594
+ id,
1595
+ todos: e.todos
1596
+ });
1597
+ }
1598
+ };
1599
+ var handleEditorContentStart = (event, builder) => {
1600
+ const e = event;
1601
+ const id = builder.nextId();
1602
+ builder.activeEditorId = id;
1603
+ builder.addBlock({
1604
+ type: "editor",
1605
+ id,
1606
+ callId: e.callId,
1607
+ path: e.path,
1608
+ language: e.language,
1609
+ content: "",
1610
+ isStreaming: true
1611
+ });
1612
+ };
1613
+ var handleEditorContentDelta = (event, builder) => {
1614
+ const e = event;
1615
+ const block = builder.findBlock(
1616
+ (b) => b.type === "editor" && b.callId === e.callId
1617
+ );
1618
+ if (block && block.type === "editor") {
1619
+ builder.patchBlock(block.id, {
1620
+ content: block.content + e.delta
1621
+ });
1622
+ }
1623
+ };
1624
+ var handleEditorContentEnd = (event, builder) => {
1625
+ const e = event;
1626
+ const block = builder.findBlock(
1627
+ (b) => b.type === "editor" && b.callId === e.callId
1628
+ );
1629
+ if (block) {
1630
+ builder.patchBlock(block.id, {
1631
+ isStreaming: false
1632
+ });
1633
+ }
1634
+ builder.activeEditorId = null;
1635
+ };
1636
+ var handleDesktopStream = (event, builder) => {
1637
+ const e = event;
1638
+ if (!e.url) return;
1639
+ const existing = builder.findBlock((b) => b.type === "desktop_stream");
1640
+ if (existing) {
1641
+ builder.patchBlock(existing.id, {
1642
+ url: e.url,
1643
+ authKey: e.authKey,
1644
+ sandboxId: e.sandboxId
1645
+ });
1646
+ } else {
1647
+ builder.addBlock({
1648
+ type: "desktop_stream",
1649
+ id: builder.nextId(),
1650
+ url: e.url,
1651
+ authKey: e.authKey,
1652
+ sandboxId: e.sandboxId
1653
+ });
1654
+ }
1655
+ };
1656
+ var handleAttachmentStaged = (event, builder) => {
1657
+ const e = event;
1658
+ if (!e.files || e.files.length === 0) return;
1659
+ builder.addBlock({
1660
+ type: "attachment",
1661
+ id: builder.nextId(),
1662
+ files: e.files
1663
+ });
1664
+ };
1665
+ var noop = () => {
1437
1666
  };
1438
1667
  var standardHandlers = {
1439
1668
  user_message: handleUserMessage,
1440
1669
  chunk: handleChunk,
1441
1670
  tool_call: handleToolCall,
1442
1671
  tool_executing: handleToolExecuting,
1672
+ tool_progress: handleToolProgress,
1443
1673
  tool_completed: handleToolEnd,
1444
1674
  tool_end: handleToolEnd,
1445
1675
  agent_start: handleAgentStart,
1676
+ agent_end: noop,
1446
1677
  thinking_delta: handleThinkingDelta,
1447
1678
  thinking_complete: handleThinkingComplete,
1448
1679
  subagent_start: handleSubagentStart,
1449
1680
  subagent_chunk: handleSubagentChunk,
1450
1681
  subagent_update: handleSubagentUpdate,
1451
1682
  subagent_end: handleSubagentEnd,
1683
+ subagent_tool_use: noop,
1684
+ capsule_output: handleCapsuleOutput,
1685
+ capsule_output_chunk: handleCapsuleOutputChunk,
1686
+ asset_created: handleAssetCreated,
1687
+ todo_update: handleTodoUpdate,
1688
+ editor_content_start: handleEditorContentStart,
1689
+ editor_content_delta: handleEditorContentDelta,
1690
+ editor_content_end: handleEditorContentEnd,
1691
+ desktop_stream: handleDesktopStream,
1692
+ attachment_staged: handleAttachmentStaged,
1693
+ workspace_ready: noop,
1694
+ retry: noop,
1452
1695
  complete: handleComplete,
1453
1696
  error: handleError,
1454
1697
  disconnected: handleDisconnected
1455
1698
  };
1699
+
1700
+ // src/types.ts
1701
+ var ChatEventType = {
1702
+ Connected: "connected",
1703
+ BlocksChanged: "blocks_changed",
1704
+ UserMessage: "user_message",
1705
+ TitleGenerated: "title_generated",
1706
+ ModelInfo: "model_info",
1707
+ Chunk: "chunk",
1708
+ ToolCall: "tool_call",
1709
+ ToolExecuting: "tool_executing",
1710
+ ToolProgress: "tool_progress",
1711
+ ToolCompleted: "tool_completed",
1712
+ ToolEnd: "tool_end",
1713
+ AgentStart: "agent_start",
1714
+ AgentEnd: "agent_end",
1715
+ ThinkingDelta: "thinking_delta",
1716
+ ThinkingComplete: "thinking_complete",
1717
+ SubagentStart: "subagent_start",
1718
+ SubagentChunk: "subagent_chunk",
1719
+ SubagentUpdate: "subagent_update",
1720
+ SubagentEnd: "subagent_end",
1721
+ SubagentToolUse: "subagent_tool_use",
1722
+ CapsuleOutput: "capsule_output",
1723
+ CapsuleOutputChunk: "capsule_output_chunk",
1724
+ AssetCreated: "asset_created",
1725
+ TodoUpdate: "todo_update",
1726
+ EditorContentStart: "editor_content_start",
1727
+ EditorContentDelta: "editor_content_delta",
1728
+ EditorContentEnd: "editor_content_end",
1729
+ Complete: "complete",
1730
+ Error: "error",
1731
+ Disconnected: "disconnected",
1732
+ Retry: "retry",
1733
+ ContextUpdate: "context_update",
1734
+ DesktopStream: "desktop_stream",
1735
+ AttachmentStaged: "attachment_staged",
1736
+ WorkspaceReady: "workspace_ready"
1737
+ };
1738
+
1739
+ // src/stream-manager.ts
1740
+ var StreamManager = class {
1741
+ constructor(session) {
1742
+ this._state = "idle";
1743
+ this._activeConversationId = null;
1744
+ this._backgroundJobs = /* @__PURE__ */ new Map();
1745
+ this.handlers = [];
1746
+ this.unsub = null;
1747
+ this.session = session;
1748
+ this.attach();
1749
+ }
1750
+ // ── Public state ──────────────────────────────────────────────
1751
+ get state() {
1752
+ return this._state;
1753
+ }
1754
+ get activeConversationId() {
1755
+ return this._activeConversationId;
1756
+ }
1757
+ get backgroundJobs() {
1758
+ return this._backgroundJobs;
1759
+ }
1760
+ // ── Event subscription ────────────────────────────────────────
1761
+ on(handler) {
1762
+ this.handlers.push(handler);
1763
+ return () => {
1764
+ this.handlers = this.handlers.filter((h) => h !== handler);
1765
+ };
1766
+ }
1767
+ emit(event) {
1768
+ for (const handler of this.handlers) {
1769
+ try {
1770
+ handler(event);
1771
+ } catch {
1772
+ }
1773
+ }
1774
+ }
1775
+ setState(state) {
1776
+ this._state = state;
1777
+ this.emit({
1778
+ type: "stateChange",
1779
+ state,
1780
+ conversationId: this._activeConversationId
1781
+ });
1782
+ }
1783
+ // ── Session event wiring ──────────────────────────────────────
1784
+ attach() {
1785
+ this.unsub = this.session.on((event) => {
1786
+ this.onSessionEvent(event);
1787
+ });
1788
+ }
1789
+ onSessionEvent(event) {
1790
+ const convId = this.session.conversationId;
1791
+ if (event.type === ChatEventType.BlocksChanged) {
1792
+ if (this._state === "streaming" && convId) {
1793
+ this.emit({
1794
+ type: "blocksChanged",
1795
+ conversationId: convId,
1796
+ blocks: event.blocks
1797
+ });
1798
+ }
1799
+ return;
1800
+ }
1801
+ this.emit({
1802
+ type: "event",
1803
+ conversationId: convId,
1804
+ event
1805
+ });
1806
+ if (event.type === ChatEventType.Complete) {
1807
+ if (this._state === "streaming") {
1808
+ this.setState("idle");
1809
+ }
1810
+ }
1811
+ }
1812
+ // ── Send ──────────────────────────────────────────────────────
1813
+ async send(content, options) {
1814
+ if (this._state === "streaming") return;
1815
+ if (!this._activeConversationId) {
1816
+ const id = await this.session.createNewConversation();
1817
+ this.setActiveConversation(id);
1818
+ }
1819
+ this.prepareUserBlock(content);
1820
+ this.setState("streaming");
1821
+ try {
1822
+ await this.session.send(content, {
1823
+ enableSearch: options?.enableSearch,
1824
+ agentName: options?.agentName,
1825
+ uploadIds: options?.uploadIds
1826
+ });
1827
+ } catch {
1828
+ }
1829
+ this.finalizeStream();
1830
+ }
1831
+ // ── Regenerate ────────────────────────────────────────────────
1832
+ async regenerate() {
1833
+ if (this._state === "streaming") return;
1834
+ const userMsgs = this.session.messages.filter(
1835
+ (m) => m.role === "user"
1836
+ );
1837
+ const lastUserMsg = userMsgs[userMsgs.length - 1];
1838
+ if (!lastUserMsg) return;
1839
+ this.prepareUserBlock(lastUserMsg.content);
1840
+ this.setState("streaming");
1841
+ try {
1842
+ await this.session.resendFromCheckpoint(
1843
+ lastUserMsg.id,
1844
+ lastUserMsg.content
1845
+ );
1846
+ } catch {
1847
+ }
1848
+ this.finalizeStream();
1849
+ }
1850
+ // ── Switch conversation ───────────────────────────────────────
1851
+ async switchTo(conversationId) {
1852
+ if (conversationId === this._activeConversationId) return;
1853
+ if (this._state === "streaming") {
1854
+ const oldConvId = this._activeConversationId;
1855
+ const jobId = this.session.currentJobId;
1856
+ if (oldConvId && jobId) {
1857
+ this._backgroundJobs.set(oldConvId, jobId);
1858
+ this.emit({
1859
+ type: "backgroundJobsChanged",
1860
+ jobs: this._backgroundJobs
1861
+ });
1862
+ }
1863
+ this.session.detach();
1864
+ }
1865
+ if (this._backgroundJobs.has(conversationId)) {
1866
+ this._backgroundJobs.delete(conversationId);
1867
+ this.emit({
1868
+ type: "backgroundJobsChanged",
1869
+ jobs: this._backgroundJobs
1870
+ });
1871
+ }
1872
+ this.setActiveConversation(conversationId);
1873
+ await this.restore(conversationId);
1874
+ }
1875
+ // ── Create / delete conversation ──────────────────────────────
1876
+ async createConversation() {
1877
+ const id = await this.session.createNewConversation();
1878
+ this.setActiveConversation(id);
1879
+ return id;
1880
+ }
1881
+ async deleteConversation(id) {
1882
+ await this.session.deleteConversation(id);
1883
+ this._backgroundJobs.delete(id);
1884
+ if (this._activeConversationId === id) {
1885
+ this._activeConversationId = null;
1886
+ this.emit({ type: "conversationChanged", conversationId: null });
1887
+ }
1888
+ }
1889
+ // ── Stop (explicit cancel) ────────────────────────────────────
1890
+ stop() {
1891
+ this.session.disconnect();
1892
+ this.setState("idle");
1893
+ }
1894
+ // ── Cleanup ───────────────────────────────────────────────────
1895
+ destroy() {
1896
+ if (this.unsub) {
1897
+ this.unsub();
1898
+ this.unsub = null;
1899
+ }
1900
+ this.handlers = [];
1901
+ }
1902
+ // ── Internal: helpers ──────────────────────────────────────────
1903
+ prepareUserBlock(content) {
1904
+ this.session.blockBuilder.reset();
1905
+ this.session.blockBuilder.addBlock({
1906
+ type: "user",
1907
+ id: this.session.blockBuilder.nextId(),
1908
+ content
1909
+ });
1910
+ }
1911
+ finalizeStream() {
1912
+ if (this._state === "streaming") {
1913
+ this.setState("idle");
1914
+ }
1915
+ }
1916
+ // ── Internal: restore ─────────────────────────────────────────
1917
+ async restore(conversationId) {
1918
+ this.setState("restoring");
1919
+ let activeJobId = null;
1920
+ try {
1921
+ const res = await this.session.client.get(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);
1922
+ activeJobId = res.job_id ?? null;
1923
+ } catch {
1924
+ }
1925
+ if (activeJobId) {
1926
+ await this.session.loadConversation(conversationId);
1927
+ this.setState("streaming");
1928
+ try {
1929
+ await this.session.reconnectToJob(activeJobId);
1930
+ } catch {
1931
+ }
1932
+ if (this._state === "streaming") {
1933
+ this.setState("idle");
1934
+ }
1935
+ } else {
1936
+ await this.session.loadConversation(conversationId);
1937
+ try {
1938
+ const jobs = await this.session.client.get(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);
1939
+ const completedJobs = jobs.filter(
1940
+ (j) => j.status === "completed"
1941
+ );
1942
+ for (const job of completedJobs) {
1943
+ await this.session.switchConversation(conversationId, job.job_id);
1944
+ }
1945
+ if (completedJobs.length > 0) {
1946
+ this.emit({
1947
+ type: "blocksChanged",
1948
+ conversationId,
1949
+ blocks: this.session.blockBuilder.getBlocks()
1950
+ });
1951
+ this.emit({
1952
+ type: "versionsReady",
1953
+ conversationId,
1954
+ count: completedJobs.length
1955
+ });
1956
+ }
1957
+ } catch {
1958
+ }
1959
+ this.setState("idle");
1960
+ }
1961
+ }
1962
+ // ── Internal: set active conversation ─────────────────────────
1963
+ setActiveConversation(id) {
1964
+ this._activeConversationId = id;
1965
+ this.emit({ type: "conversationChanged", conversationId: id });
1966
+ }
1967
+ };
1456
1968
  export {
1457
1969
  AstralformClient,
1458
1970
  AstralformError,
1459
1971
  AuthenticationError,
1460
1972
  BlockBuilder,
1973
+ ChatEventType,
1461
1974
  ChatSession,
1462
1975
  ConnectionError,
1463
1976
  InMemoryStorage,
@@ -1465,6 +1978,7 @@ export {
1465
1978
  RateLimitError,
1466
1979
  ServerError,
1467
1980
  StreamAbortedError,
1981
+ StreamManager,
1468
1982
  ToolRegistry,
1469
1983
  generateId,
1470
1984
  standardHandlers,