@astralform/js 3.2.0 → 4.0.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.d.cts CHANGED
@@ -637,7 +637,6 @@ interface ChatStreamRequest {
637
637
  resend_from?: string;
638
638
  upload_ids?: string[];
639
639
  agent_name?: string;
640
- enable_search?: boolean;
641
640
  plan_mode?: boolean;
642
641
  /**
643
642
  * Per-request model choice (client-side model selection), wire shape.
@@ -723,7 +722,6 @@ interface SendOptions$1 extends ModelChoiceOptions {
723
722
  enabledClientTools?: string[];
724
723
  uploadIds?: string[];
725
724
  agentName?: string;
726
- enableSearch?: boolean;
727
725
  planMode?: boolean;
728
726
  }
729
727
  /**
@@ -750,6 +748,13 @@ interface ConversationAsset {
750
748
  workspacePath?: string;
751
749
  sourceMessageId?: string;
752
750
  agentName?: string;
751
+ /**
752
+ * Freshly-signed download/preview URL. Minted per response by the backend
753
+ * (private `workspaces` bucket), so it reflects the current signature rather
754
+ * than a link baked in when the asset was created. May be absent if signing
755
+ * failed or the asset has no stored object.
756
+ */
757
+ url?: string;
753
758
  createdAt: string;
754
759
  }
755
760
 
@@ -969,9 +974,7 @@ declare class ChatSession {
969
974
  private emit;
970
975
  connect(): Promise<void>;
971
976
  send(content: string, options?: SendOptions$1): Promise<void>;
972
- resendFromCheckpoint(messageId: string, newContent: string, options?: {
973
- enableSearch?: boolean;
974
- }): Promise<void>;
977
+ resendFromCheckpoint(messageId: string, newContent: string): Promise<void>;
975
978
  private resetStreamingState;
976
979
  private processStream;
977
980
  /** Last received sequence number for resumable reconnection */
@@ -1002,6 +1005,14 @@ declare class ChatSession {
1002
1005
  /** POST a client-tool result, retrying transient failures a few times. */
1003
1006
  private submitToolResultWithRetry;
1004
1007
  private dispatchWireEvent;
1008
+ /**
1009
+ * Synchronous replay of a single stored wire event — the side-effect +
1010
+ * translate + emit core of ``dispatchWireEvent`` without the (live-only)
1011
+ * client-tool round-trip. Called in a tight synchronous loop during history
1012
+ * restore so the consumer's per-event store writes batch into ONE render
1013
+ * instead of re-typing the whole conversation event by event.
1014
+ */
1015
+ private replayWireEvent;
1005
1016
  /**
1006
1017
  * State mutations driven by wire events. Kept separate from translation so
1007
1018
  * the pure wire → ChatEvent mapping can live in translate.ts and be reused
@@ -1028,16 +1039,33 @@ declare class ChatSession {
1028
1039
  /** Stop the job and disconnect (explicit user action). */
1029
1040
  disconnect(): void;
1030
1041
  createNewConversation(): Promise<string>;
1031
- switchConversation(id: string, jobId?: string,
1032
1042
  /**
1033
- * User prompt that triggered this job, if known. Emitted as a
1034
- * synthetic ``user_message`` ChatEvent at the START of the replay —
1035
- * a completed job maps to exactly one user turn, so every event in it
1036
- * belongs beneath that prompt. User messages aren't persisted in
1037
- * ``job_events``, so without this the restored conversation would show
1038
- * the agent response with no visible prompt above it.
1043
+ * Replay one completed turn's already-fetched events, synchronously.
1044
+ *
1045
+ * Fetching is the caller's job (``StreamManager.restore`` loads every turn's
1046
+ * events in parallel and the message list once), so this is pure replay: no
1047
+ * awaits, so the whole restore runs in a single synchronous pass and the
1048
+ * consumer batches it into one render.
1049
+ *
1050
+ * ``userMessageContent`` is the prompt that triggered this turn. It's emitted
1051
+ * as a synthetic ``user_message`` BEFORE any of the turn's events: user
1052
+ * prompts aren't persisted in ``job_events``, and some events precede
1053
+ * ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
1054
+ * so leading with the prompt keeps the turn in order.
1055
+ */
1056
+ replayTurn(id: string, events: ConversationEvent[], userMessageContent?: string): void;
1057
+ /**
1058
+ * Load a conversation's messages and replay its persisted history.
1059
+ *
1060
+ * Convenience for plain-``ChatSession`` consumers (the documented
1061
+ * conversation-management API). ``StreamManager`` drives restore itself —
1062
+ * loading messages once and replaying each turn in parallel — and does NOT
1063
+ * call this; it's kept so direct-Session usage doesn't break.
1064
+ *
1065
+ * Without ``jobId`` it replays the whole conversation; with one, just that
1066
+ * job's events.
1039
1067
  */
1040
- userMessageContent?: string): Promise<void>;
1068
+ switchConversation(id: string, jobId?: string): Promise<void>;
1041
1069
  deleteConversation(id: string): Promise<void>;
1042
1070
  toggleClientTool(name: string): boolean;
1043
1071
  }
@@ -1110,7 +1138,6 @@ declare function streamJobSSE(options: StreamJobSSEOptions): AsyncGenerator<Chat
1110
1138
 
1111
1139
  type StreamState = "idle" | "streaming" | "restoring" | "detached";
1112
1140
  interface SendOptions extends ModelChoiceOptions {
1113
- enableSearch?: boolean;
1114
1141
  agentName?: string;
1115
1142
  uploadIds?: string[];
1116
1143
  planMode?: boolean;
@@ -1153,7 +1180,26 @@ declare class StreamManager {
1153
1180
  private onSessionEvent;
1154
1181
  send(content: string, options?: SendOptions): Promise<void>;
1155
1182
  regenerate(): Promise<void>;
1156
- switchTo(conversationId: string): Promise<void>;
1183
+ /**
1184
+ * Switch the active conversation.
1185
+ *
1186
+ * ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a
1187
+ * restored conversation's rendered blocks: it moves the active pointer and
1188
+ * loads the message list (needed for send / regenerate context) but skips
1189
+ * the expensive event fetch + replay, and never enters the ``restoring``
1190
+ * state, so a consumer that clears its block view on ``restoring`` keeps
1191
+ * showing the cached history with no flash of a spinner.
1192
+ *
1193
+ * It still confirms there is no live job before skipping: the in-memory
1194
+ * background-job map is empty on a fresh instance (page reload) and blind to
1195
+ * jobs started in another tab/device, so the fast path always asks the server
1196
+ * (``getActiveJob``) and falls through to a full reconnect if one is running.
1197
+ * That one small request is the only cost it doesn't skip, so passing the
1198
+ * flag whenever you hold cached blocks is safe.
1199
+ */
1200
+ switchTo(conversationId: string, opts?: {
1201
+ skipHistoryReplay?: boolean;
1202
+ }): Promise<void>;
1157
1203
  createConversation(): Promise<string>;
1158
1204
  deleteConversation(id: string): Promise<void>;
1159
1205
  stop(): void;
@@ -1200,7 +1246,9 @@ declare function mapSseToChat(raw: RawSseEvent): ChatEvent[];
1200
1246
  * ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so
1201
1247
  * gating the user block on ``message_start`` would replay them above the
1202
1248
  * user's own message. Keying off ``job_id`` injects the prompt once per job,
1203
- * before its first event — matching ``session.ts#switchConversation``.
1249
+ * before its first event — matching how the restore path
1250
+ * (``stream-manager.ts#restore`` → ``session.ts#replayTurn``) leads each turn
1251
+ * with its own prompt.
1204
1252
  */
1205
1253
  declare function replayEvents(sseEvents: RawSseEvent[], userMessages: {
1206
1254
  role: string;
package/dist/index.d.ts CHANGED
@@ -637,7 +637,6 @@ interface ChatStreamRequest {
637
637
  resend_from?: string;
638
638
  upload_ids?: string[];
639
639
  agent_name?: string;
640
- enable_search?: boolean;
641
640
  plan_mode?: boolean;
642
641
  /**
643
642
  * Per-request model choice (client-side model selection), wire shape.
@@ -723,7 +722,6 @@ interface SendOptions$1 extends ModelChoiceOptions {
723
722
  enabledClientTools?: string[];
724
723
  uploadIds?: string[];
725
724
  agentName?: string;
726
- enableSearch?: boolean;
727
725
  planMode?: boolean;
728
726
  }
729
727
  /**
@@ -750,6 +748,13 @@ interface ConversationAsset {
750
748
  workspacePath?: string;
751
749
  sourceMessageId?: string;
752
750
  agentName?: string;
751
+ /**
752
+ * Freshly-signed download/preview URL. Minted per response by the backend
753
+ * (private `workspaces` bucket), so it reflects the current signature rather
754
+ * than a link baked in when the asset was created. May be absent if signing
755
+ * failed or the asset has no stored object.
756
+ */
757
+ url?: string;
753
758
  createdAt: string;
754
759
  }
755
760
 
@@ -969,9 +974,7 @@ declare class ChatSession {
969
974
  private emit;
970
975
  connect(): Promise<void>;
971
976
  send(content: string, options?: SendOptions$1): Promise<void>;
972
- resendFromCheckpoint(messageId: string, newContent: string, options?: {
973
- enableSearch?: boolean;
974
- }): Promise<void>;
977
+ resendFromCheckpoint(messageId: string, newContent: string): Promise<void>;
975
978
  private resetStreamingState;
976
979
  private processStream;
977
980
  /** Last received sequence number for resumable reconnection */
@@ -1002,6 +1005,14 @@ declare class ChatSession {
1002
1005
  /** POST a client-tool result, retrying transient failures a few times. */
1003
1006
  private submitToolResultWithRetry;
1004
1007
  private dispatchWireEvent;
1008
+ /**
1009
+ * Synchronous replay of a single stored wire event — the side-effect +
1010
+ * translate + emit core of ``dispatchWireEvent`` without the (live-only)
1011
+ * client-tool round-trip. Called in a tight synchronous loop during history
1012
+ * restore so the consumer's per-event store writes batch into ONE render
1013
+ * instead of re-typing the whole conversation event by event.
1014
+ */
1015
+ private replayWireEvent;
1005
1016
  /**
1006
1017
  * State mutations driven by wire events. Kept separate from translation so
1007
1018
  * the pure wire → ChatEvent mapping can live in translate.ts and be reused
@@ -1028,16 +1039,33 @@ declare class ChatSession {
1028
1039
  /** Stop the job and disconnect (explicit user action). */
1029
1040
  disconnect(): void;
1030
1041
  createNewConversation(): Promise<string>;
1031
- switchConversation(id: string, jobId?: string,
1032
1042
  /**
1033
- * User prompt that triggered this job, if known. Emitted as a
1034
- * synthetic ``user_message`` ChatEvent at the START of the replay —
1035
- * a completed job maps to exactly one user turn, so every event in it
1036
- * belongs beneath that prompt. User messages aren't persisted in
1037
- * ``job_events``, so without this the restored conversation would show
1038
- * the agent response with no visible prompt above it.
1043
+ * Replay one completed turn's already-fetched events, synchronously.
1044
+ *
1045
+ * Fetching is the caller's job (``StreamManager.restore`` loads every turn's
1046
+ * events in parallel and the message list once), so this is pure replay: no
1047
+ * awaits, so the whole restore runs in a single synchronous pass and the
1048
+ * consumer batches it into one render.
1049
+ *
1050
+ * ``userMessageContent`` is the prompt that triggered this turn. It's emitted
1051
+ * as a synthetic ``user_message`` BEFORE any of the turn's events: user
1052
+ * prompts aren't persisted in ``job_events``, and some events precede
1053
+ * ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
1054
+ * so leading with the prompt keeps the turn in order.
1055
+ */
1056
+ replayTurn(id: string, events: ConversationEvent[], userMessageContent?: string): void;
1057
+ /**
1058
+ * Load a conversation's messages and replay its persisted history.
1059
+ *
1060
+ * Convenience for plain-``ChatSession`` consumers (the documented
1061
+ * conversation-management API). ``StreamManager`` drives restore itself —
1062
+ * loading messages once and replaying each turn in parallel — and does NOT
1063
+ * call this; it's kept so direct-Session usage doesn't break.
1064
+ *
1065
+ * Without ``jobId`` it replays the whole conversation; with one, just that
1066
+ * job's events.
1039
1067
  */
1040
- userMessageContent?: string): Promise<void>;
1068
+ switchConversation(id: string, jobId?: string): Promise<void>;
1041
1069
  deleteConversation(id: string): Promise<void>;
1042
1070
  toggleClientTool(name: string): boolean;
1043
1071
  }
@@ -1110,7 +1138,6 @@ declare function streamJobSSE(options: StreamJobSSEOptions): AsyncGenerator<Chat
1110
1138
 
1111
1139
  type StreamState = "idle" | "streaming" | "restoring" | "detached";
1112
1140
  interface SendOptions extends ModelChoiceOptions {
1113
- enableSearch?: boolean;
1114
1141
  agentName?: string;
1115
1142
  uploadIds?: string[];
1116
1143
  planMode?: boolean;
@@ -1153,7 +1180,26 @@ declare class StreamManager {
1153
1180
  private onSessionEvent;
1154
1181
  send(content: string, options?: SendOptions): Promise<void>;
1155
1182
  regenerate(): Promise<void>;
1156
- switchTo(conversationId: string): Promise<void>;
1183
+ /**
1184
+ * Switch the active conversation.
1185
+ *
1186
+ * ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a
1187
+ * restored conversation's rendered blocks: it moves the active pointer and
1188
+ * loads the message list (needed for send / regenerate context) but skips
1189
+ * the expensive event fetch + replay, and never enters the ``restoring``
1190
+ * state, so a consumer that clears its block view on ``restoring`` keeps
1191
+ * showing the cached history with no flash of a spinner.
1192
+ *
1193
+ * It still confirms there is no live job before skipping: the in-memory
1194
+ * background-job map is empty on a fresh instance (page reload) and blind to
1195
+ * jobs started in another tab/device, so the fast path always asks the server
1196
+ * (``getActiveJob``) and falls through to a full reconnect if one is running.
1197
+ * That one small request is the only cost it doesn't skip, so passing the
1198
+ * flag whenever you hold cached blocks is safe.
1199
+ */
1200
+ switchTo(conversationId: string, opts?: {
1201
+ skipHistoryReplay?: boolean;
1202
+ }): Promise<void>;
1157
1203
  createConversation(): Promise<string>;
1158
1204
  deleteConversation(id: string): Promise<void>;
1159
1205
  stop(): void;
@@ -1200,7 +1246,9 @@ declare function mapSseToChat(raw: RawSseEvent): ChatEvent[];
1200
1246
  * ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so
1201
1247
  * gating the user block on ``message_start`` would replay them above the
1202
1248
  * user's own message. Keying off ``job_id`` injects the prompt once per job,
1203
- * before its first event — matching ``session.ts#switchConversation``.
1249
+ * before its first event — matching how the restore path
1250
+ * (``stream-manager.ts#restore`` → ``session.ts#replayTurn``) leads each turn
1251
+ * with its own prompt.
1204
1252
  */
1205
1253
  declare function replayEvents(sseEvents: RawSseEvent[], userMessages: {
1206
1254
  role: string;
package/dist/index.js CHANGED
@@ -592,6 +592,10 @@ var AstralformClient = class {
592
592
  workspacePath: raw.workspace_path,
593
593
  sourceMessageId: raw.source_message_id,
594
594
  agentName: raw.agent_name,
595
+ // The API serializes an unsigned asset as `url: null`; normalize to
596
+ // undefined so it matches the `url?: string` type and consumers that
597
+ // check `!== undefined` never receive a null.
598
+ url: raw.url ?? void 0,
595
599
  createdAt: raw.created_at
596
600
  };
597
601
  }
@@ -1297,7 +1301,6 @@ var ChatSession = class {
1297
1301
  ),
1298
1302
  upload_ids: options?.uploadIds,
1299
1303
  agent_name: options?.agentName,
1300
- enable_search: options?.enableSearch,
1301
1304
  plan_mode: options?.planMode,
1302
1305
  // Per-request model choice (client-side model selection).
1303
1306
  provider: options?.provider,
@@ -1307,15 +1310,14 @@ var ChatSession = class {
1307
1310
  };
1308
1311
  await this.processStream(request);
1309
1312
  }
1310
- async resendFromCheckpoint(messageId, newContent, options) {
1313
+ async resendFromCheckpoint(messageId, newContent) {
1311
1314
  if (this.isStreaming) return;
1312
1315
  const request = {
1313
1316
  message: newContent,
1314
1317
  conversation_id: this.conversationId ?? void 0,
1315
1318
  resend_from: messageId,
1316
1319
  mcp_manifest: this.toolRegistry.getManifest(),
1317
- enabled_mcp: Array.from(this.enabledClientTools),
1318
- enable_search: options?.enableSearch
1320
+ enabled_mcp: Array.from(this.enabledClientTools)
1319
1321
  };
1320
1322
  await this.processStream(request);
1321
1323
  }
@@ -1506,6 +1508,20 @@ var ChatSession = class {
1506
1508
  }
1507
1509
  }
1508
1510
  }
1511
+ /**
1512
+ * Synchronous replay of a single stored wire event — the side-effect +
1513
+ * translate + emit core of ``dispatchWireEvent`` without the (live-only)
1514
+ * client-tool round-trip. Called in a tight synchronous loop during history
1515
+ * restore so the consumer's per-event store writes batch into ONE render
1516
+ * instead of re-typing the whole conversation event by event.
1517
+ */
1518
+ replayWireEvent(wire, conversationId) {
1519
+ this.applyWireSideEffects(wire, conversationId, "");
1520
+ const event = translateWireEvent(wire);
1521
+ if (event) {
1522
+ this.emit(event);
1523
+ }
1524
+ }
1509
1525
  /**
1510
1526
  * State mutations driven by wire events. Kept separate from translation so
1511
1527
  * the pure wire → ChatEvent mapping can live in translate.ts and be reused
@@ -1652,34 +1668,57 @@ var ChatSession = class {
1652
1668
  this.messages = [];
1653
1669
  return id;
1654
1670
  }
1655
- async switchConversation(id, jobId, userMessageContent) {
1671
+ /**
1672
+ * Replay one completed turn's already-fetched events, synchronously.
1673
+ *
1674
+ * Fetching is the caller's job (``StreamManager.restore`` loads every turn's
1675
+ * events in parallel and the message list once), so this is pure replay: no
1676
+ * awaits, so the whole restore runs in a single synchronous pass and the
1677
+ * consumer batches it into one render.
1678
+ *
1679
+ * ``userMessageContent`` is the prompt that triggered this turn. It's emitted
1680
+ * as a synthetic ``user_message`` BEFORE any of the turn's events: user
1681
+ * prompts aren't persisted in ``job_events``, and some events precede
1682
+ * ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
1683
+ * so leading with the prompt keeps the turn in order.
1684
+ */
1685
+ replayTurn(id, events, userMessageContent) {
1656
1686
  this.conversationId = id;
1657
1687
  this.resetStreamingState();
1688
+ if (userMessageContent) {
1689
+ this.emit({ type: "user_message", content: userMessageContent });
1690
+ }
1691
+ for (const ev of events) {
1692
+ const type = ev.data.type || ev.event;
1693
+ if (!type || type === "done") continue;
1694
+ const wire = { ...ev.data, type };
1695
+ try {
1696
+ this.replayWireEvent(wire, id);
1697
+ } catch {
1698
+ }
1699
+ }
1700
+ }
1701
+ /**
1702
+ * Load a conversation's messages and replay its persisted history.
1703
+ *
1704
+ * Convenience for plain-``ChatSession`` consumers (the documented
1705
+ * conversation-management API). ``StreamManager`` drives restore itself —
1706
+ * loading messages once and replaying each turn in parallel — and does NOT
1707
+ * call this; it's kept so direct-Session usage doesn't break.
1708
+ *
1709
+ * Without ``jobId`` it replays the whole conversation; with one, just that
1710
+ * job's events.
1711
+ */
1712
+ async switchConversation(id, jobId) {
1658
1713
  const [messagesResult, eventsResult] = await Promise.allSettled([
1659
1714
  this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
1660
1715
  this.client.getConversationEvents(id, jobId)
1661
1716
  ]);
1662
1717
  this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
1663
- if (eventsResult.status === "fulfilled") {
1664
- if (userMessageContent) {
1665
- this.emit({ type: "user_message", content: userMessageContent });
1666
- }
1667
- for (const ev of eventsResult.value) {
1668
- const type = ev.data.type || ev.event;
1669
- if (!type || type === "done") continue;
1670
- const wire = { ...ev.data, type };
1671
- try {
1672
- await this.dispatchWireEvent(
1673
- wire,
1674
- id,
1675
- "",
1676
- false
1677
- // don't execute client tools on replay
1678
- );
1679
- } catch {
1680
- }
1681
- }
1682
- }
1718
+ this.replayTurn(
1719
+ id,
1720
+ eventsResult.status === "fulfilled" ? eventsResult.value : []
1721
+ );
1683
1722
  }
1684
1723
  async deleteConversation(id) {
1685
1724
  try {
@@ -1823,7 +1862,6 @@ var StreamManager = class {
1823
1862
  this.setState("streaming");
1824
1863
  try {
1825
1864
  await this.session.send(content, {
1826
- enableSearch: options?.enableSearch,
1827
1865
  agentName: options?.agentName,
1828
1866
  uploadIds: options?.uploadIds,
1829
1867
  planMode: options?.planMode,
@@ -1855,8 +1893,26 @@ var StreamManager = class {
1855
1893
  this.finalizeStream();
1856
1894
  }
1857
1895
  // ── Switch conversation ───────────────────────────────────────
1858
- async switchTo(conversationId) {
1896
+ /**
1897
+ * Switch the active conversation.
1898
+ *
1899
+ * ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a
1900
+ * restored conversation's rendered blocks: it moves the active pointer and
1901
+ * loads the message list (needed for send / regenerate context) but skips
1902
+ * the expensive event fetch + replay, and never enters the ``restoring``
1903
+ * state, so a consumer that clears its block view on ``restoring`` keeps
1904
+ * showing the cached history with no flash of a spinner.
1905
+ *
1906
+ * It still confirms there is no live job before skipping: the in-memory
1907
+ * background-job map is empty on a fresh instance (page reload) and blind to
1908
+ * jobs started in another tab/device, so the fast path always asks the server
1909
+ * (``getActiveJob``) and falls through to a full reconnect if one is running.
1910
+ * That one small request is the only cost it doesn't skip, so passing the
1911
+ * flag whenever you hold cached blocks is safe.
1912
+ */
1913
+ async switchTo(conversationId, opts) {
1859
1914
  if (conversationId === this._activeConversationId) return;
1915
+ const targetHadBackgroundJob = this._backgroundJobs.has(conversationId);
1860
1916
  if (this._state === "streaming") {
1861
1917
  const oldConvId = this._activeConversationId;
1862
1918
  const jobId = this.session.currentJobId;
@@ -1877,6 +1933,18 @@ var StreamManager = class {
1877
1933
  });
1878
1934
  }
1879
1935
  this.setActiveConversation(conversationId);
1936
+ if (opts?.skipHistoryReplay && !targetHadBackgroundJob) {
1937
+ let activeJobId = null;
1938
+ try {
1939
+ activeJobId = (await this.session.client.getActiveJob(conversationId)).jobId;
1940
+ } catch {
1941
+ }
1942
+ if (!activeJobId) {
1943
+ await this.session.loadConversation(conversationId);
1944
+ this.setState("idle");
1945
+ return;
1946
+ }
1947
+ }
1880
1948
  await this.restore(conversationId);
1881
1949
  }
1882
1950
  // ── Create / delete conversation ──────────────────────────────
@@ -1941,13 +2009,16 @@ var StreamManager = class {
1941
2009
  const userMessages = this.session.messages.filter(
1942
2010
  (m) => m.role === "user"
1943
2011
  );
2012
+ const eventLists = await Promise.all(
2013
+ completedJobs.map(
2014
+ (job) => this.session.client.getConversationEvents(conversationId, job.job_id).catch(() => [])
2015
+ )
2016
+ );
1944
2017
  for (let i = 0; i < completedJobs.length; i++) {
1945
- const job = completedJobs[i];
1946
- const userContent = userMessages[i]?.content;
1947
- await this.session.switchConversation(
2018
+ this.session.replayTurn(
1948
2019
  conversationId,
1949
- job.job_id,
1950
- userContent
2020
+ eventLists[i] ?? [],
2021
+ userMessages[i]?.content
1951
2022
  );
1952
2023
  }
1953
2024
  if (completedJobs.length > 0) {