@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/README.md +1 -2
- package/dist/index.cjs +103 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -16
- package/dist/index.d.ts +64 -16
- package/dist/index.js +103 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1946
|
-
const userContent = userMessages[i]?.content;
|
|
1947
|
-
await this.session.switchConversation(
|
|
2018
|
+
this.session.replayTurn(
|
|
1948
2019
|
conversationId,
|
|
1949
|
-
|
|
1950
|
-
|
|
2020
|
+
eventLists[i] ?? [],
|
|
2021
|
+
userMessages[i]?.content
|
|
1951
2022
|
);
|
|
1952
2023
|
}
|
|
1953
2024
|
if (completedJobs.length > 0) {
|