@astralform/js 3.1.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 +109 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -16
- package/dist/index.d.ts +66 -16
- package/dist/index.js +109 -33
- 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
|
/**
|
|
@@ -738,6 +736,8 @@ interface ModelOption {
|
|
|
738
736
|
tools: boolean;
|
|
739
737
|
vision: boolean;
|
|
740
738
|
thinkingMode: string;
|
|
739
|
+
/** whether the model accepts a configurable reasoning effort (low/medium/high); false for always-on-thinking / think-tags models where effort is a no-op */
|
|
740
|
+
supportsEffort: boolean;
|
|
741
741
|
}
|
|
742
742
|
interface ConversationAsset {
|
|
743
743
|
id: string;
|
|
@@ -748,6 +748,13 @@ interface ConversationAsset {
|
|
|
748
748
|
workspacePath?: string;
|
|
749
749
|
sourceMessageId?: string;
|
|
750
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;
|
|
751
758
|
createdAt: string;
|
|
752
759
|
}
|
|
753
760
|
|
|
@@ -967,9 +974,7 @@ declare class ChatSession {
|
|
|
967
974
|
private emit;
|
|
968
975
|
connect(): Promise<void>;
|
|
969
976
|
send(content: string, options?: SendOptions$1): Promise<void>;
|
|
970
|
-
resendFromCheckpoint(messageId: string, newContent: string
|
|
971
|
-
enableSearch?: boolean;
|
|
972
|
-
}): Promise<void>;
|
|
977
|
+
resendFromCheckpoint(messageId: string, newContent: string): Promise<void>;
|
|
973
978
|
private resetStreamingState;
|
|
974
979
|
private processStream;
|
|
975
980
|
/** Last received sequence number for resumable reconnection */
|
|
@@ -1000,6 +1005,14 @@ declare class ChatSession {
|
|
|
1000
1005
|
/** POST a client-tool result, retrying transient failures a few times. */
|
|
1001
1006
|
private submitToolResultWithRetry;
|
|
1002
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;
|
|
1003
1016
|
/**
|
|
1004
1017
|
* State mutations driven by wire events. Kept separate from translation so
|
|
1005
1018
|
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
@@ -1026,16 +1039,33 @@ declare class ChatSession {
|
|
|
1026
1039
|
/** Stop the job and disconnect (explicit user action). */
|
|
1027
1040
|
disconnect(): void;
|
|
1028
1041
|
createNewConversation(): Promise<string>;
|
|
1029
|
-
switchConversation(id: string, jobId?: string,
|
|
1030
1042
|
/**
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
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.
|
|
1037
1067
|
*/
|
|
1038
|
-
|
|
1068
|
+
switchConversation(id: string, jobId?: string): Promise<void>;
|
|
1039
1069
|
deleteConversation(id: string): Promise<void>;
|
|
1040
1070
|
toggleClientTool(name: string): boolean;
|
|
1041
1071
|
}
|
|
@@ -1108,7 +1138,6 @@ declare function streamJobSSE(options: StreamJobSSEOptions): AsyncGenerator<Chat
|
|
|
1108
1138
|
|
|
1109
1139
|
type StreamState = "idle" | "streaming" | "restoring" | "detached";
|
|
1110
1140
|
interface SendOptions extends ModelChoiceOptions {
|
|
1111
|
-
enableSearch?: boolean;
|
|
1112
1141
|
agentName?: string;
|
|
1113
1142
|
uploadIds?: string[];
|
|
1114
1143
|
planMode?: boolean;
|
|
@@ -1151,7 +1180,26 @@ declare class StreamManager {
|
|
|
1151
1180
|
private onSessionEvent;
|
|
1152
1181
|
send(content: string, options?: SendOptions): Promise<void>;
|
|
1153
1182
|
regenerate(): Promise<void>;
|
|
1154
|
-
|
|
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>;
|
|
1155
1203
|
createConversation(): Promise<string>;
|
|
1156
1204
|
deleteConversation(id: string): Promise<void>;
|
|
1157
1205
|
stop(): void;
|
|
@@ -1198,7 +1246,9 @@ declare function mapSseToChat(raw: RawSseEvent): ChatEvent[];
|
|
|
1198
1246
|
* ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so
|
|
1199
1247
|
* gating the user block on ``message_start`` would replay them above the
|
|
1200
1248
|
* user's own message. Keying off ``job_id`` injects the prompt once per job,
|
|
1201
|
-
* 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.
|
|
1202
1252
|
*/
|
|
1203
1253
|
declare function replayEvents(sseEvents: RawSseEvent[], userMessages: {
|
|
1204
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
|
/**
|
|
@@ -738,6 +736,8 @@ interface ModelOption {
|
|
|
738
736
|
tools: boolean;
|
|
739
737
|
vision: boolean;
|
|
740
738
|
thinkingMode: string;
|
|
739
|
+
/** whether the model accepts a configurable reasoning effort (low/medium/high); false for always-on-thinking / think-tags models where effort is a no-op */
|
|
740
|
+
supportsEffort: boolean;
|
|
741
741
|
}
|
|
742
742
|
interface ConversationAsset {
|
|
743
743
|
id: string;
|
|
@@ -748,6 +748,13 @@ interface ConversationAsset {
|
|
|
748
748
|
workspacePath?: string;
|
|
749
749
|
sourceMessageId?: string;
|
|
750
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;
|
|
751
758
|
createdAt: string;
|
|
752
759
|
}
|
|
753
760
|
|
|
@@ -967,9 +974,7 @@ declare class ChatSession {
|
|
|
967
974
|
private emit;
|
|
968
975
|
connect(): Promise<void>;
|
|
969
976
|
send(content: string, options?: SendOptions$1): Promise<void>;
|
|
970
|
-
resendFromCheckpoint(messageId: string, newContent: string
|
|
971
|
-
enableSearch?: boolean;
|
|
972
|
-
}): Promise<void>;
|
|
977
|
+
resendFromCheckpoint(messageId: string, newContent: string): Promise<void>;
|
|
973
978
|
private resetStreamingState;
|
|
974
979
|
private processStream;
|
|
975
980
|
/** Last received sequence number for resumable reconnection */
|
|
@@ -1000,6 +1005,14 @@ declare class ChatSession {
|
|
|
1000
1005
|
/** POST a client-tool result, retrying transient failures a few times. */
|
|
1001
1006
|
private submitToolResultWithRetry;
|
|
1002
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;
|
|
1003
1016
|
/**
|
|
1004
1017
|
* State mutations driven by wire events. Kept separate from translation so
|
|
1005
1018
|
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
@@ -1026,16 +1039,33 @@ declare class ChatSession {
|
|
|
1026
1039
|
/** Stop the job and disconnect (explicit user action). */
|
|
1027
1040
|
disconnect(): void;
|
|
1028
1041
|
createNewConversation(): Promise<string>;
|
|
1029
|
-
switchConversation(id: string, jobId?: string,
|
|
1030
1042
|
/**
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
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.
|
|
1037
1067
|
*/
|
|
1038
|
-
|
|
1068
|
+
switchConversation(id: string, jobId?: string): Promise<void>;
|
|
1039
1069
|
deleteConversation(id: string): Promise<void>;
|
|
1040
1070
|
toggleClientTool(name: string): boolean;
|
|
1041
1071
|
}
|
|
@@ -1108,7 +1138,6 @@ declare function streamJobSSE(options: StreamJobSSEOptions): AsyncGenerator<Chat
|
|
|
1108
1138
|
|
|
1109
1139
|
type StreamState = "idle" | "streaming" | "restoring" | "detached";
|
|
1110
1140
|
interface SendOptions extends ModelChoiceOptions {
|
|
1111
|
-
enableSearch?: boolean;
|
|
1112
1141
|
agentName?: string;
|
|
1113
1142
|
uploadIds?: string[];
|
|
1114
1143
|
planMode?: boolean;
|
|
@@ -1151,7 +1180,26 @@ declare class StreamManager {
|
|
|
1151
1180
|
private onSessionEvent;
|
|
1152
1181
|
send(content: string, options?: SendOptions): Promise<void>;
|
|
1153
1182
|
regenerate(): Promise<void>;
|
|
1154
|
-
|
|
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>;
|
|
1155
1203
|
createConversation(): Promise<string>;
|
|
1156
1204
|
deleteConversation(id: string): Promise<void>;
|
|
1157
1205
|
stop(): void;
|
|
@@ -1198,7 +1246,9 @@ declare function mapSseToChat(raw: RawSseEvent): ChatEvent[];
|
|
|
1198
1246
|
* ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so
|
|
1199
1247
|
* gating the user block on ``message_start`` would replay them above the
|
|
1200
1248
|
* user's own message. Keying off ``job_id`` injects the prompt once per job,
|
|
1201
|
-
* 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.
|
|
1202
1252
|
*/
|
|
1203
1253
|
declare function replayEvents(sseEvents: RawSseEvent[], userMessages: {
|
|
1204
1254
|
role: string;
|
package/dist/index.js
CHANGED
|
@@ -510,7 +510,12 @@ var AstralformClient = class {
|
|
|
510
510
|
thinking: m.thinking,
|
|
511
511
|
tools: m.tools,
|
|
512
512
|
vision: m.vision,
|
|
513
|
-
thinkingMode: m.thinking_mode
|
|
513
|
+
thinkingMode: m.thinking_mode,
|
|
514
|
+
// Coerce so the non-optional `supportsEffort: boolean` stays honest even
|
|
515
|
+
// against an older backend that omits `supports_effort` (→ false = safe:
|
|
516
|
+
// the effort control is hidden). Unlike the always-present siblings above,
|
|
517
|
+
// this field can be absent, so it's the one that needs coercion.
|
|
518
|
+
supportsEffort: Boolean(m.supports_effort)
|
|
514
519
|
}));
|
|
515
520
|
}
|
|
516
521
|
async getSkills() {
|
|
@@ -587,6 +592,10 @@ var AstralformClient = class {
|
|
|
587
592
|
workspacePath: raw.workspace_path,
|
|
588
593
|
sourceMessageId: raw.source_message_id,
|
|
589
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,
|
|
590
599
|
createdAt: raw.created_at
|
|
591
600
|
};
|
|
592
601
|
}
|
|
@@ -1292,7 +1301,6 @@ var ChatSession = class {
|
|
|
1292
1301
|
),
|
|
1293
1302
|
upload_ids: options?.uploadIds,
|
|
1294
1303
|
agent_name: options?.agentName,
|
|
1295
|
-
enable_search: options?.enableSearch,
|
|
1296
1304
|
plan_mode: options?.planMode,
|
|
1297
1305
|
// Per-request model choice (client-side model selection).
|
|
1298
1306
|
provider: options?.provider,
|
|
@@ -1302,15 +1310,14 @@ var ChatSession = class {
|
|
|
1302
1310
|
};
|
|
1303
1311
|
await this.processStream(request);
|
|
1304
1312
|
}
|
|
1305
|
-
async resendFromCheckpoint(messageId, newContent
|
|
1313
|
+
async resendFromCheckpoint(messageId, newContent) {
|
|
1306
1314
|
if (this.isStreaming) return;
|
|
1307
1315
|
const request = {
|
|
1308
1316
|
message: newContent,
|
|
1309
1317
|
conversation_id: this.conversationId ?? void 0,
|
|
1310
1318
|
resend_from: messageId,
|
|
1311
1319
|
mcp_manifest: this.toolRegistry.getManifest(),
|
|
1312
|
-
enabled_mcp: Array.from(this.enabledClientTools)
|
|
1313
|
-
enable_search: options?.enableSearch
|
|
1320
|
+
enabled_mcp: Array.from(this.enabledClientTools)
|
|
1314
1321
|
};
|
|
1315
1322
|
await this.processStream(request);
|
|
1316
1323
|
}
|
|
@@ -1501,6 +1508,20 @@ var ChatSession = class {
|
|
|
1501
1508
|
}
|
|
1502
1509
|
}
|
|
1503
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
|
+
}
|
|
1504
1525
|
/**
|
|
1505
1526
|
* State mutations driven by wire events. Kept separate from translation so
|
|
1506
1527
|
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
@@ -1647,34 +1668,57 @@ var ChatSession = class {
|
|
|
1647
1668
|
this.messages = [];
|
|
1648
1669
|
return id;
|
|
1649
1670
|
}
|
|
1650
|
-
|
|
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) {
|
|
1651
1686
|
this.conversationId = id;
|
|
1652
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) {
|
|
1653
1713
|
const [messagesResult, eventsResult] = await Promise.allSettled([
|
|
1654
1714
|
this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
|
|
1655
1715
|
this.client.getConversationEvents(id, jobId)
|
|
1656
1716
|
]);
|
|
1657
1717
|
this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
for (const ev of eventsResult.value) {
|
|
1663
|
-
const type = ev.data.type || ev.event;
|
|
1664
|
-
if (!type || type === "done") continue;
|
|
1665
|
-
const wire = { ...ev.data, type };
|
|
1666
|
-
try {
|
|
1667
|
-
await this.dispatchWireEvent(
|
|
1668
|
-
wire,
|
|
1669
|
-
id,
|
|
1670
|
-
"",
|
|
1671
|
-
false
|
|
1672
|
-
// don't execute client tools on replay
|
|
1673
|
-
);
|
|
1674
|
-
} catch {
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1718
|
+
this.replayTurn(
|
|
1719
|
+
id,
|
|
1720
|
+
eventsResult.status === "fulfilled" ? eventsResult.value : []
|
|
1721
|
+
);
|
|
1678
1722
|
}
|
|
1679
1723
|
async deleteConversation(id) {
|
|
1680
1724
|
try {
|
|
@@ -1818,7 +1862,6 @@ var StreamManager = class {
|
|
|
1818
1862
|
this.setState("streaming");
|
|
1819
1863
|
try {
|
|
1820
1864
|
await this.session.send(content, {
|
|
1821
|
-
enableSearch: options?.enableSearch,
|
|
1822
1865
|
agentName: options?.agentName,
|
|
1823
1866
|
uploadIds: options?.uploadIds,
|
|
1824
1867
|
planMode: options?.planMode,
|
|
@@ -1850,8 +1893,26 @@ var StreamManager = class {
|
|
|
1850
1893
|
this.finalizeStream();
|
|
1851
1894
|
}
|
|
1852
1895
|
// ── Switch conversation ───────────────────────────────────────
|
|
1853
|
-
|
|
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) {
|
|
1854
1914
|
if (conversationId === this._activeConversationId) return;
|
|
1915
|
+
const targetHadBackgroundJob = this._backgroundJobs.has(conversationId);
|
|
1855
1916
|
if (this._state === "streaming") {
|
|
1856
1917
|
const oldConvId = this._activeConversationId;
|
|
1857
1918
|
const jobId = this.session.currentJobId;
|
|
@@ -1872,6 +1933,18 @@ var StreamManager = class {
|
|
|
1872
1933
|
});
|
|
1873
1934
|
}
|
|
1874
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
|
+
}
|
|
1875
1948
|
await this.restore(conversationId);
|
|
1876
1949
|
}
|
|
1877
1950
|
// ── Create / delete conversation ──────────────────────────────
|
|
@@ -1936,13 +2009,16 @@ var StreamManager = class {
|
|
|
1936
2009
|
const userMessages = this.session.messages.filter(
|
|
1937
2010
|
(m) => m.role === "user"
|
|
1938
2011
|
);
|
|
2012
|
+
const eventLists = await Promise.all(
|
|
2013
|
+
completedJobs.map(
|
|
2014
|
+
(job) => this.session.client.getConversationEvents(conversationId, job.job_id).catch(() => [])
|
|
2015
|
+
)
|
|
2016
|
+
);
|
|
1939
2017
|
for (let i = 0; i < completedJobs.length; i++) {
|
|
1940
|
-
|
|
1941
|
-
const userContent = userMessages[i]?.content;
|
|
1942
|
-
await this.session.switchConversation(
|
|
2018
|
+
this.session.replayTurn(
|
|
1943
2019
|
conversationId,
|
|
1944
|
-
|
|
1945
|
-
|
|
2020
|
+
eventLists[i] ?? [],
|
|
2021
|
+
userMessages[i]?.content
|
|
1946
2022
|
);
|
|
1947
2023
|
}
|
|
1948
2024
|
if (completedJobs.length > 0) {
|