@astralform/js 3.2.0 → 4.1.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 +105 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -16
- package/dist/index.d.ts +81 -16
- package/dist/index.js +105 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -637,8 +637,13 @@ 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;
|
|
641
|
+
/**
|
|
642
|
+
* Start a durable long-horizon goal for this run (goal mode). The backend mints
|
|
643
|
+
* an agent_goal from this text and drives the run under a budget until the
|
|
644
|
+
* objective is genuinely complete. A blank/omitted value runs a normal turn.
|
|
645
|
+
*/
|
|
646
|
+
goal?: string;
|
|
642
647
|
/**
|
|
643
648
|
* Per-request model choice (client-side model selection), wire shape.
|
|
644
649
|
* `provider` and `model` are paired; omit to reuse the thread's last model.
|
|
@@ -723,8 +728,13 @@ interface SendOptions$1 extends ModelChoiceOptions {
|
|
|
723
728
|
enabledClientTools?: string[];
|
|
724
729
|
uploadIds?: string[];
|
|
725
730
|
agentName?: string;
|
|
726
|
-
enableSearch?: boolean;
|
|
727
731
|
planMode?: boolean;
|
|
732
|
+
/**
|
|
733
|
+
* Start a durable long-horizon goal for this run (goal mode). The text becomes
|
|
734
|
+
* the goal's objective; the backend keeps the agent working under a budget until
|
|
735
|
+
* it's complete. Omit for a normal turn.
|
|
736
|
+
*/
|
|
737
|
+
goal?: string;
|
|
728
738
|
}
|
|
729
739
|
/**
|
|
730
740
|
* A selectable model for one of the team's connected providers, from
|
|
@@ -750,6 +760,13 @@ interface ConversationAsset {
|
|
|
750
760
|
workspacePath?: string;
|
|
751
761
|
sourceMessageId?: string;
|
|
752
762
|
agentName?: string;
|
|
763
|
+
/**
|
|
764
|
+
* Freshly-signed download/preview URL. Minted per response by the backend
|
|
765
|
+
* (private `workspaces` bucket), so it reflects the current signature rather
|
|
766
|
+
* than a link baked in when the asset was created. May be absent if signing
|
|
767
|
+
* failed or the asset has no stored object.
|
|
768
|
+
*/
|
|
769
|
+
url?: string;
|
|
753
770
|
createdAt: string;
|
|
754
771
|
}
|
|
755
772
|
|
|
@@ -969,9 +986,7 @@ declare class ChatSession {
|
|
|
969
986
|
private emit;
|
|
970
987
|
connect(): Promise<void>;
|
|
971
988
|
send(content: string, options?: SendOptions$1): Promise<void>;
|
|
972
|
-
resendFromCheckpoint(messageId: string, newContent: string
|
|
973
|
-
enableSearch?: boolean;
|
|
974
|
-
}): Promise<void>;
|
|
989
|
+
resendFromCheckpoint(messageId: string, newContent: string): Promise<void>;
|
|
975
990
|
private resetStreamingState;
|
|
976
991
|
private processStream;
|
|
977
992
|
/** Last received sequence number for resumable reconnection */
|
|
@@ -1002,6 +1017,14 @@ declare class ChatSession {
|
|
|
1002
1017
|
/** POST a client-tool result, retrying transient failures a few times. */
|
|
1003
1018
|
private submitToolResultWithRetry;
|
|
1004
1019
|
private dispatchWireEvent;
|
|
1020
|
+
/**
|
|
1021
|
+
* Synchronous replay of a single stored wire event — the side-effect +
|
|
1022
|
+
* translate + emit core of ``dispatchWireEvent`` without the (live-only)
|
|
1023
|
+
* client-tool round-trip. Called in a tight synchronous loop during history
|
|
1024
|
+
* restore so the consumer's per-event store writes batch into ONE render
|
|
1025
|
+
* instead of re-typing the whole conversation event by event.
|
|
1026
|
+
*/
|
|
1027
|
+
private replayWireEvent;
|
|
1005
1028
|
/**
|
|
1006
1029
|
* State mutations driven by wire events. Kept separate from translation so
|
|
1007
1030
|
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
@@ -1028,16 +1051,33 @@ declare class ChatSession {
|
|
|
1028
1051
|
/** Stop the job and disconnect (explicit user action). */
|
|
1029
1052
|
disconnect(): void;
|
|
1030
1053
|
createNewConversation(): Promise<string>;
|
|
1031
|
-
switchConversation(id: string, jobId?: string,
|
|
1032
1054
|
/**
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1055
|
+
* Replay one completed turn's already-fetched events, synchronously.
|
|
1056
|
+
*
|
|
1057
|
+
* Fetching is the caller's job (``StreamManager.restore`` loads every turn's
|
|
1058
|
+
* events in parallel and the message list once), so this is pure replay: no
|
|
1059
|
+
* awaits, so the whole restore runs in a single synchronous pass and the
|
|
1060
|
+
* consumer batches it into one render.
|
|
1061
|
+
*
|
|
1062
|
+
* ``userMessageContent`` is the prompt that triggered this turn. It's emitted
|
|
1063
|
+
* as a synthetic ``user_message`` BEFORE any of the turn's events: user
|
|
1064
|
+
* prompts aren't persisted in ``job_events``, and some events precede
|
|
1065
|
+
* ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
|
|
1066
|
+
* so leading with the prompt keeps the turn in order.
|
|
1067
|
+
*/
|
|
1068
|
+
replayTurn(id: string, events: ConversationEvent[], userMessageContent?: string): void;
|
|
1069
|
+
/**
|
|
1070
|
+
* Load a conversation's messages and replay its persisted history.
|
|
1071
|
+
*
|
|
1072
|
+
* Convenience for plain-``ChatSession`` consumers (the documented
|
|
1073
|
+
* conversation-management API). ``StreamManager`` drives restore itself —
|
|
1074
|
+
* loading messages once and replaying each turn in parallel — and does NOT
|
|
1075
|
+
* call this; it's kept so direct-Session usage doesn't break.
|
|
1076
|
+
*
|
|
1077
|
+
* Without ``jobId`` it replays the whole conversation; with one, just that
|
|
1078
|
+
* job's events.
|
|
1039
1079
|
*/
|
|
1040
|
-
|
|
1080
|
+
switchConversation(id: string, jobId?: string): Promise<void>;
|
|
1041
1081
|
deleteConversation(id: string): Promise<void>;
|
|
1042
1082
|
toggleClientTool(name: string): boolean;
|
|
1043
1083
|
}
|
|
@@ -1110,10 +1150,14 @@ declare function streamJobSSE(options: StreamJobSSEOptions): AsyncGenerator<Chat
|
|
|
1110
1150
|
|
|
1111
1151
|
type StreamState = "idle" | "streaming" | "restoring" | "detached";
|
|
1112
1152
|
interface SendOptions extends ModelChoiceOptions {
|
|
1113
|
-
enableSearch?: boolean;
|
|
1114
1153
|
agentName?: string;
|
|
1115
1154
|
uploadIds?: string[];
|
|
1116
1155
|
planMode?: boolean;
|
|
1156
|
+
/**
|
|
1157
|
+
* Start a durable long-horizon goal for this run (goal mode) — the text is the
|
|
1158
|
+
* goal objective the backend drives to completion. Omit for a normal turn.
|
|
1159
|
+
*/
|
|
1160
|
+
goal?: string;
|
|
1117
1161
|
}
|
|
1118
1162
|
type StreamManagerEvent = {
|
|
1119
1163
|
type: "stateChange";
|
|
@@ -1153,7 +1197,26 @@ declare class StreamManager {
|
|
|
1153
1197
|
private onSessionEvent;
|
|
1154
1198
|
send(content: string, options?: SendOptions): Promise<void>;
|
|
1155
1199
|
regenerate(): Promise<void>;
|
|
1156
|
-
|
|
1200
|
+
/**
|
|
1201
|
+
* Switch the active conversation.
|
|
1202
|
+
*
|
|
1203
|
+
* ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a
|
|
1204
|
+
* restored conversation's rendered blocks: it moves the active pointer and
|
|
1205
|
+
* loads the message list (needed for send / regenerate context) but skips
|
|
1206
|
+
* the expensive event fetch + replay, and never enters the ``restoring``
|
|
1207
|
+
* state, so a consumer that clears its block view on ``restoring`` keeps
|
|
1208
|
+
* showing the cached history with no flash of a spinner.
|
|
1209
|
+
*
|
|
1210
|
+
* It still confirms there is no live job before skipping: the in-memory
|
|
1211
|
+
* background-job map is empty on a fresh instance (page reload) and blind to
|
|
1212
|
+
* jobs started in another tab/device, so the fast path always asks the server
|
|
1213
|
+
* (``getActiveJob``) and falls through to a full reconnect if one is running.
|
|
1214
|
+
* That one small request is the only cost it doesn't skip, so passing the
|
|
1215
|
+
* flag whenever you hold cached blocks is safe.
|
|
1216
|
+
*/
|
|
1217
|
+
switchTo(conversationId: string, opts?: {
|
|
1218
|
+
skipHistoryReplay?: boolean;
|
|
1219
|
+
}): Promise<void>;
|
|
1157
1220
|
createConversation(): Promise<string>;
|
|
1158
1221
|
deleteConversation(id: string): Promise<void>;
|
|
1159
1222
|
stop(): void;
|
|
@@ -1200,7 +1263,9 @@ declare function mapSseToChat(raw: RawSseEvent): ChatEvent[];
|
|
|
1200
1263
|
* ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so
|
|
1201
1264
|
* gating the user block on ``message_start`` would replay them above the
|
|
1202
1265
|
* user's own message. Keying off ``job_id`` injects the prompt once per job,
|
|
1203
|
-
* before its first event — matching
|
|
1266
|
+
* before its first event — matching how the restore path
|
|
1267
|
+
* (``stream-manager.ts#restore`` → ``session.ts#replayTurn``) leads each turn
|
|
1268
|
+
* with its own prompt.
|
|
1204
1269
|
*/
|
|
1205
1270
|
declare function replayEvents(sseEvents: RawSseEvent[], userMessages: {
|
|
1206
1271
|
role: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -637,8 +637,13 @@ 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;
|
|
641
|
+
/**
|
|
642
|
+
* Start a durable long-horizon goal for this run (goal mode). The backend mints
|
|
643
|
+
* an agent_goal from this text and drives the run under a budget until the
|
|
644
|
+
* objective is genuinely complete. A blank/omitted value runs a normal turn.
|
|
645
|
+
*/
|
|
646
|
+
goal?: string;
|
|
642
647
|
/**
|
|
643
648
|
* Per-request model choice (client-side model selection), wire shape.
|
|
644
649
|
* `provider` and `model` are paired; omit to reuse the thread's last model.
|
|
@@ -723,8 +728,13 @@ interface SendOptions$1 extends ModelChoiceOptions {
|
|
|
723
728
|
enabledClientTools?: string[];
|
|
724
729
|
uploadIds?: string[];
|
|
725
730
|
agentName?: string;
|
|
726
|
-
enableSearch?: boolean;
|
|
727
731
|
planMode?: boolean;
|
|
732
|
+
/**
|
|
733
|
+
* Start a durable long-horizon goal for this run (goal mode). The text becomes
|
|
734
|
+
* the goal's objective; the backend keeps the agent working under a budget until
|
|
735
|
+
* it's complete. Omit for a normal turn.
|
|
736
|
+
*/
|
|
737
|
+
goal?: string;
|
|
728
738
|
}
|
|
729
739
|
/**
|
|
730
740
|
* A selectable model for one of the team's connected providers, from
|
|
@@ -750,6 +760,13 @@ interface ConversationAsset {
|
|
|
750
760
|
workspacePath?: string;
|
|
751
761
|
sourceMessageId?: string;
|
|
752
762
|
agentName?: string;
|
|
763
|
+
/**
|
|
764
|
+
* Freshly-signed download/preview URL. Minted per response by the backend
|
|
765
|
+
* (private `workspaces` bucket), so it reflects the current signature rather
|
|
766
|
+
* than a link baked in when the asset was created. May be absent if signing
|
|
767
|
+
* failed or the asset has no stored object.
|
|
768
|
+
*/
|
|
769
|
+
url?: string;
|
|
753
770
|
createdAt: string;
|
|
754
771
|
}
|
|
755
772
|
|
|
@@ -969,9 +986,7 @@ declare class ChatSession {
|
|
|
969
986
|
private emit;
|
|
970
987
|
connect(): Promise<void>;
|
|
971
988
|
send(content: string, options?: SendOptions$1): Promise<void>;
|
|
972
|
-
resendFromCheckpoint(messageId: string, newContent: string
|
|
973
|
-
enableSearch?: boolean;
|
|
974
|
-
}): Promise<void>;
|
|
989
|
+
resendFromCheckpoint(messageId: string, newContent: string): Promise<void>;
|
|
975
990
|
private resetStreamingState;
|
|
976
991
|
private processStream;
|
|
977
992
|
/** Last received sequence number for resumable reconnection */
|
|
@@ -1002,6 +1017,14 @@ declare class ChatSession {
|
|
|
1002
1017
|
/** POST a client-tool result, retrying transient failures a few times. */
|
|
1003
1018
|
private submitToolResultWithRetry;
|
|
1004
1019
|
private dispatchWireEvent;
|
|
1020
|
+
/**
|
|
1021
|
+
* Synchronous replay of a single stored wire event — the side-effect +
|
|
1022
|
+
* translate + emit core of ``dispatchWireEvent`` without the (live-only)
|
|
1023
|
+
* client-tool round-trip. Called in a tight synchronous loop during history
|
|
1024
|
+
* restore so the consumer's per-event store writes batch into ONE render
|
|
1025
|
+
* instead of re-typing the whole conversation event by event.
|
|
1026
|
+
*/
|
|
1027
|
+
private replayWireEvent;
|
|
1005
1028
|
/**
|
|
1006
1029
|
* State mutations driven by wire events. Kept separate from translation so
|
|
1007
1030
|
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
@@ -1028,16 +1051,33 @@ declare class ChatSession {
|
|
|
1028
1051
|
/** Stop the job and disconnect (explicit user action). */
|
|
1029
1052
|
disconnect(): void;
|
|
1030
1053
|
createNewConversation(): Promise<string>;
|
|
1031
|
-
switchConversation(id: string, jobId?: string,
|
|
1032
1054
|
/**
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1055
|
+
* Replay one completed turn's already-fetched events, synchronously.
|
|
1056
|
+
*
|
|
1057
|
+
* Fetching is the caller's job (``StreamManager.restore`` loads every turn's
|
|
1058
|
+
* events in parallel and the message list once), so this is pure replay: no
|
|
1059
|
+
* awaits, so the whole restore runs in a single synchronous pass and the
|
|
1060
|
+
* consumer batches it into one render.
|
|
1061
|
+
*
|
|
1062
|
+
* ``userMessageContent`` is the prompt that triggered this turn. It's emitted
|
|
1063
|
+
* as a synthetic ``user_message`` BEFORE any of the turn's events: user
|
|
1064
|
+
* prompts aren't persisted in ``job_events``, and some events precede
|
|
1065
|
+
* ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
|
|
1066
|
+
* so leading with the prompt keeps the turn in order.
|
|
1067
|
+
*/
|
|
1068
|
+
replayTurn(id: string, events: ConversationEvent[], userMessageContent?: string): void;
|
|
1069
|
+
/**
|
|
1070
|
+
* Load a conversation's messages and replay its persisted history.
|
|
1071
|
+
*
|
|
1072
|
+
* Convenience for plain-``ChatSession`` consumers (the documented
|
|
1073
|
+
* conversation-management API). ``StreamManager`` drives restore itself —
|
|
1074
|
+
* loading messages once and replaying each turn in parallel — and does NOT
|
|
1075
|
+
* call this; it's kept so direct-Session usage doesn't break.
|
|
1076
|
+
*
|
|
1077
|
+
* Without ``jobId`` it replays the whole conversation; with one, just that
|
|
1078
|
+
* job's events.
|
|
1039
1079
|
*/
|
|
1040
|
-
|
|
1080
|
+
switchConversation(id: string, jobId?: string): Promise<void>;
|
|
1041
1081
|
deleteConversation(id: string): Promise<void>;
|
|
1042
1082
|
toggleClientTool(name: string): boolean;
|
|
1043
1083
|
}
|
|
@@ -1110,10 +1150,14 @@ declare function streamJobSSE(options: StreamJobSSEOptions): AsyncGenerator<Chat
|
|
|
1110
1150
|
|
|
1111
1151
|
type StreamState = "idle" | "streaming" | "restoring" | "detached";
|
|
1112
1152
|
interface SendOptions extends ModelChoiceOptions {
|
|
1113
|
-
enableSearch?: boolean;
|
|
1114
1153
|
agentName?: string;
|
|
1115
1154
|
uploadIds?: string[];
|
|
1116
1155
|
planMode?: boolean;
|
|
1156
|
+
/**
|
|
1157
|
+
* Start a durable long-horizon goal for this run (goal mode) — the text is the
|
|
1158
|
+
* goal objective the backend drives to completion. Omit for a normal turn.
|
|
1159
|
+
*/
|
|
1160
|
+
goal?: string;
|
|
1117
1161
|
}
|
|
1118
1162
|
type StreamManagerEvent = {
|
|
1119
1163
|
type: "stateChange";
|
|
@@ -1153,7 +1197,26 @@ declare class StreamManager {
|
|
|
1153
1197
|
private onSessionEvent;
|
|
1154
1198
|
send(content: string, options?: SendOptions): Promise<void>;
|
|
1155
1199
|
regenerate(): Promise<void>;
|
|
1156
|
-
|
|
1200
|
+
/**
|
|
1201
|
+
* Switch the active conversation.
|
|
1202
|
+
*
|
|
1203
|
+
* ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a
|
|
1204
|
+
* restored conversation's rendered blocks: it moves the active pointer and
|
|
1205
|
+
* loads the message list (needed for send / regenerate context) but skips
|
|
1206
|
+
* the expensive event fetch + replay, and never enters the ``restoring``
|
|
1207
|
+
* state, so a consumer that clears its block view on ``restoring`` keeps
|
|
1208
|
+
* showing the cached history with no flash of a spinner.
|
|
1209
|
+
*
|
|
1210
|
+
* It still confirms there is no live job before skipping: the in-memory
|
|
1211
|
+
* background-job map is empty on a fresh instance (page reload) and blind to
|
|
1212
|
+
* jobs started in another tab/device, so the fast path always asks the server
|
|
1213
|
+
* (``getActiveJob``) and falls through to a full reconnect if one is running.
|
|
1214
|
+
* That one small request is the only cost it doesn't skip, so passing the
|
|
1215
|
+
* flag whenever you hold cached blocks is safe.
|
|
1216
|
+
*/
|
|
1217
|
+
switchTo(conversationId: string, opts?: {
|
|
1218
|
+
skipHistoryReplay?: boolean;
|
|
1219
|
+
}): Promise<void>;
|
|
1157
1220
|
createConversation(): Promise<string>;
|
|
1158
1221
|
deleteConversation(id: string): Promise<void>;
|
|
1159
1222
|
stop(): void;
|
|
@@ -1200,7 +1263,9 @@ declare function mapSseToChat(raw: RawSseEvent): ChatEvent[];
|
|
|
1200
1263
|
* ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so
|
|
1201
1264
|
* gating the user block on ``message_start`` would replay them above the
|
|
1202
1265
|
* user's own message. Keying off ``job_id`` injects the prompt once per job,
|
|
1203
|
-
* before its first event — matching
|
|
1266
|
+
* before its first event — matching how the restore path
|
|
1267
|
+
* (``stream-manager.ts#restore`` → ``session.ts#replayTurn``) leads each turn
|
|
1268
|
+
* with its own prompt.
|
|
1204
1269
|
*/
|
|
1205
1270
|
declare function replayEvents(sseEvents: RawSseEvent[], userMessages: {
|
|
1206
1271
|
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,8 +1301,8 @@ 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,
|
|
1305
|
+
goal: options?.goal,
|
|
1302
1306
|
// Per-request model choice (client-side model selection).
|
|
1303
1307
|
provider: options?.provider,
|
|
1304
1308
|
model: options?.model,
|
|
@@ -1307,15 +1311,14 @@ var ChatSession = class {
|
|
|
1307
1311
|
};
|
|
1308
1312
|
await this.processStream(request);
|
|
1309
1313
|
}
|
|
1310
|
-
async resendFromCheckpoint(messageId, newContent
|
|
1314
|
+
async resendFromCheckpoint(messageId, newContent) {
|
|
1311
1315
|
if (this.isStreaming) return;
|
|
1312
1316
|
const request = {
|
|
1313
1317
|
message: newContent,
|
|
1314
1318
|
conversation_id: this.conversationId ?? void 0,
|
|
1315
1319
|
resend_from: messageId,
|
|
1316
1320
|
mcp_manifest: this.toolRegistry.getManifest(),
|
|
1317
|
-
enabled_mcp: Array.from(this.enabledClientTools)
|
|
1318
|
-
enable_search: options?.enableSearch
|
|
1321
|
+
enabled_mcp: Array.from(this.enabledClientTools)
|
|
1319
1322
|
};
|
|
1320
1323
|
await this.processStream(request);
|
|
1321
1324
|
}
|
|
@@ -1506,6 +1509,20 @@ var ChatSession = class {
|
|
|
1506
1509
|
}
|
|
1507
1510
|
}
|
|
1508
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Synchronous replay of a single stored wire event — the side-effect +
|
|
1514
|
+
* translate + emit core of ``dispatchWireEvent`` without the (live-only)
|
|
1515
|
+
* client-tool round-trip. Called in a tight synchronous loop during history
|
|
1516
|
+
* restore so the consumer's per-event store writes batch into ONE render
|
|
1517
|
+
* instead of re-typing the whole conversation event by event.
|
|
1518
|
+
*/
|
|
1519
|
+
replayWireEvent(wire, conversationId) {
|
|
1520
|
+
this.applyWireSideEffects(wire, conversationId, "");
|
|
1521
|
+
const event = translateWireEvent(wire);
|
|
1522
|
+
if (event) {
|
|
1523
|
+
this.emit(event);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1509
1526
|
/**
|
|
1510
1527
|
* State mutations driven by wire events. Kept separate from translation so
|
|
1511
1528
|
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
@@ -1652,34 +1669,57 @@ var ChatSession = class {
|
|
|
1652
1669
|
this.messages = [];
|
|
1653
1670
|
return id;
|
|
1654
1671
|
}
|
|
1655
|
-
|
|
1672
|
+
/**
|
|
1673
|
+
* Replay one completed turn's already-fetched events, synchronously.
|
|
1674
|
+
*
|
|
1675
|
+
* Fetching is the caller's job (``StreamManager.restore`` loads every turn's
|
|
1676
|
+
* events in parallel and the message list once), so this is pure replay: no
|
|
1677
|
+
* awaits, so the whole restore runs in a single synchronous pass and the
|
|
1678
|
+
* consumer batches it into one render.
|
|
1679
|
+
*
|
|
1680
|
+
* ``userMessageContent`` is the prompt that triggered this turn. It's emitted
|
|
1681
|
+
* as a synthetic ``user_message`` BEFORE any of the turn's events: user
|
|
1682
|
+
* prompts aren't persisted in ``job_events``, and some events precede
|
|
1683
|
+
* ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
|
|
1684
|
+
* so leading with the prompt keeps the turn in order.
|
|
1685
|
+
*/
|
|
1686
|
+
replayTurn(id, events, userMessageContent) {
|
|
1656
1687
|
this.conversationId = id;
|
|
1657
1688
|
this.resetStreamingState();
|
|
1689
|
+
if (userMessageContent) {
|
|
1690
|
+
this.emit({ type: "user_message", content: userMessageContent });
|
|
1691
|
+
}
|
|
1692
|
+
for (const ev of events) {
|
|
1693
|
+
const type = ev.data.type || ev.event;
|
|
1694
|
+
if (!type || type === "done") continue;
|
|
1695
|
+
const wire = { ...ev.data, type };
|
|
1696
|
+
try {
|
|
1697
|
+
this.replayWireEvent(wire, id);
|
|
1698
|
+
} catch {
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Load a conversation's messages and replay its persisted history.
|
|
1704
|
+
*
|
|
1705
|
+
* Convenience for plain-``ChatSession`` consumers (the documented
|
|
1706
|
+
* conversation-management API). ``StreamManager`` drives restore itself —
|
|
1707
|
+
* loading messages once and replaying each turn in parallel — and does NOT
|
|
1708
|
+
* call this; it's kept so direct-Session usage doesn't break.
|
|
1709
|
+
*
|
|
1710
|
+
* Without ``jobId`` it replays the whole conversation; with one, just that
|
|
1711
|
+
* job's events.
|
|
1712
|
+
*/
|
|
1713
|
+
async switchConversation(id, jobId) {
|
|
1658
1714
|
const [messagesResult, eventsResult] = await Promise.allSettled([
|
|
1659
1715
|
this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
|
|
1660
1716
|
this.client.getConversationEvents(id, jobId)
|
|
1661
1717
|
]);
|
|
1662
1718
|
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
|
-
}
|
|
1719
|
+
this.replayTurn(
|
|
1720
|
+
id,
|
|
1721
|
+
eventsResult.status === "fulfilled" ? eventsResult.value : []
|
|
1722
|
+
);
|
|
1683
1723
|
}
|
|
1684
1724
|
async deleteConversation(id) {
|
|
1685
1725
|
try {
|
|
@@ -1823,10 +1863,10 @@ var StreamManager = class {
|
|
|
1823
1863
|
this.setState("streaming");
|
|
1824
1864
|
try {
|
|
1825
1865
|
await this.session.send(content, {
|
|
1826
|
-
enableSearch: options?.enableSearch,
|
|
1827
1866
|
agentName: options?.agentName,
|
|
1828
1867
|
uploadIds: options?.uploadIds,
|
|
1829
1868
|
planMode: options?.planMode,
|
|
1869
|
+
goal: options?.goal,
|
|
1830
1870
|
provider: options?.provider,
|
|
1831
1871
|
model: options?.model,
|
|
1832
1872
|
reasoningEffort: options?.reasoningEffort,
|
|
@@ -1855,8 +1895,26 @@ var StreamManager = class {
|
|
|
1855
1895
|
this.finalizeStream();
|
|
1856
1896
|
}
|
|
1857
1897
|
// ── Switch conversation ───────────────────────────────────────
|
|
1858
|
-
|
|
1898
|
+
/**
|
|
1899
|
+
* Switch the active conversation.
|
|
1900
|
+
*
|
|
1901
|
+
* ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a
|
|
1902
|
+
* restored conversation's rendered blocks: it moves the active pointer and
|
|
1903
|
+
* loads the message list (needed for send / regenerate context) but skips
|
|
1904
|
+
* the expensive event fetch + replay, and never enters the ``restoring``
|
|
1905
|
+
* state, so a consumer that clears its block view on ``restoring`` keeps
|
|
1906
|
+
* showing the cached history with no flash of a spinner.
|
|
1907
|
+
*
|
|
1908
|
+
* It still confirms there is no live job before skipping: the in-memory
|
|
1909
|
+
* background-job map is empty on a fresh instance (page reload) and blind to
|
|
1910
|
+
* jobs started in another tab/device, so the fast path always asks the server
|
|
1911
|
+
* (``getActiveJob``) and falls through to a full reconnect if one is running.
|
|
1912
|
+
* That one small request is the only cost it doesn't skip, so passing the
|
|
1913
|
+
* flag whenever you hold cached blocks is safe.
|
|
1914
|
+
*/
|
|
1915
|
+
async switchTo(conversationId, opts) {
|
|
1859
1916
|
if (conversationId === this._activeConversationId) return;
|
|
1917
|
+
const targetHadBackgroundJob = this._backgroundJobs.has(conversationId);
|
|
1860
1918
|
if (this._state === "streaming") {
|
|
1861
1919
|
const oldConvId = this._activeConversationId;
|
|
1862
1920
|
const jobId = this.session.currentJobId;
|
|
@@ -1877,6 +1935,18 @@ var StreamManager = class {
|
|
|
1877
1935
|
});
|
|
1878
1936
|
}
|
|
1879
1937
|
this.setActiveConversation(conversationId);
|
|
1938
|
+
if (opts?.skipHistoryReplay && !targetHadBackgroundJob) {
|
|
1939
|
+
let activeJobId = null;
|
|
1940
|
+
try {
|
|
1941
|
+
activeJobId = (await this.session.client.getActiveJob(conversationId)).jobId;
|
|
1942
|
+
} catch {
|
|
1943
|
+
}
|
|
1944
|
+
if (!activeJobId) {
|
|
1945
|
+
await this.session.loadConversation(conversationId);
|
|
1946
|
+
this.setState("idle");
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1880
1950
|
await this.restore(conversationId);
|
|
1881
1951
|
}
|
|
1882
1952
|
// ── Create / delete conversation ──────────────────────────────
|
|
@@ -1941,13 +2011,16 @@ var StreamManager = class {
|
|
|
1941
2011
|
const userMessages = this.session.messages.filter(
|
|
1942
2012
|
(m) => m.role === "user"
|
|
1943
2013
|
);
|
|
2014
|
+
const eventLists = await Promise.all(
|
|
2015
|
+
completedJobs.map(
|
|
2016
|
+
(job) => this.session.client.getConversationEvents(conversationId, job.job_id).catch(() => [])
|
|
2017
|
+
)
|
|
2018
|
+
);
|
|
1944
2019
|
for (let i = 0; i < completedJobs.length; i++) {
|
|
1945
|
-
|
|
1946
|
-
const userContent = userMessages[i]?.content;
|
|
1947
|
-
await this.session.switchConversation(
|
|
2020
|
+
this.session.replayTurn(
|
|
1948
2021
|
conversationId,
|
|
1949
|
-
|
|
1950
|
-
|
|
2022
|
+
eventLists[i] ?? [],
|
|
2023
|
+
userMessages[i]?.content
|
|
1951
2024
|
);
|
|
1952
2025
|
}
|
|
1953
2026
|
if (completedJobs.length > 0) {
|