@jskit-ai/assistant-runtime 0.1.143 → 0.1.145
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/package.json +7 -7
- package/src/client/composables/useAssistantRuntime.js +33 -125
- package/src/client/support/assistantRuntimeState.js +140 -0
- package/src/client/support/conversationRestoreSupport.js +75 -0
- package/src/server/services/chatService.js +93 -212
- package/src/shared/assistantResponseText.js +29 -0
- package/test/assistantRuntimeState.test.js +134 -0
- package/test/chatServiceLifecycle.test.js +415 -0
- package/test/conversationRestoreSupport.test.js +110 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.145",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"./server/actionIds": "./src/server/actionIds.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
15
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
14
|
+
"@jskit-ai/assistant-core": "0.1.150",
|
|
15
|
+
"@jskit-ai/database-runtime": "0.1.174",
|
|
16
16
|
"json-rest-schema": "^1.0.17"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
20
|
-
"@jskit-ai/http-web": "0.1.
|
|
21
|
-
"@jskit-ai/kernel": "0.1.
|
|
22
|
-
"@jskit-ai/shell-web": "0.1.
|
|
19
|
+
"@jskit-ai/http-runtime": "0.1.172",
|
|
20
|
+
"@jskit-ai/http-web": "0.1.19",
|
|
21
|
+
"@jskit-ai/kernel": "0.1.174",
|
|
22
|
+
"@jskit-ai/shell-web": "0.1.178",
|
|
23
23
|
"@tanstack/vue-query": "^5.90.5",
|
|
24
24
|
"vue": "^3.5.13",
|
|
25
25
|
"vuetify": "^4.0.0"
|
|
@@ -5,14 +5,12 @@ import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/ker
|
|
|
5
5
|
import { buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
|
|
6
6
|
import {
|
|
7
7
|
ASSISTANT_STREAM_EVENT_TYPES,
|
|
8
|
-
MAX_HISTORY_MESSAGES,
|
|
9
8
|
MAX_INPUT_CHARS,
|
|
10
9
|
assistantConversationMessagesQueryKey,
|
|
11
10
|
assistantConversationsListQueryKey,
|
|
12
11
|
assistantScopeQueryKey,
|
|
13
12
|
normalizeAssistantStreamEventType,
|
|
14
13
|
normalizeConversationStatus as normalizeAssistantConversationStatus,
|
|
15
|
-
parseJsonObject,
|
|
16
14
|
toPositiveInteger
|
|
17
15
|
} from "@jskit-ai/assistant-core/shared";
|
|
18
16
|
import {
|
|
@@ -23,14 +21,23 @@ import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
|
|
|
23
21
|
import { usePagedCollection } from "@jskit-ai/http-web/client/composables/usePagedCollection";
|
|
24
22
|
import { useSurfaceRouteContext } from "@jskit-ai/shell-web/client/navigation/useSurfaceRouteContext";
|
|
25
23
|
import { resolveAssistantSurfaceConfig } from "../../shared/assistantSurfaces.js";
|
|
24
|
+
import {
|
|
25
|
+
buildHistory,
|
|
26
|
+
buildId,
|
|
27
|
+
interruptPendingToolEvents,
|
|
28
|
+
mapTranscriptEntriesToAssistantState,
|
|
29
|
+
normalizeToolName
|
|
30
|
+
} from "../support/assistantRuntimeState.js";
|
|
26
31
|
import { insertTextAtSelection } from "../support/composerInputSupport.js";
|
|
32
|
+
import {
|
|
33
|
+
loadConversationTranscript,
|
|
34
|
+
resolveConversationRestorePolicy
|
|
35
|
+
} from "../support/conversationRestoreSupport.js";
|
|
27
36
|
import { useWorkspaceWebScopeSupport } from "../support/workspaceScopeSupport.js";
|
|
28
37
|
|
|
29
38
|
const DEFAULT_STREAM_TIMEOUT_MS = 120_000;
|
|
30
39
|
const DEFAULT_HISTORY_PAGE_SIZE = 20;
|
|
31
|
-
const DEFAULT_MESSAGES_PAGE_SIZE = 200;
|
|
32
40
|
const DEFAULT_HISTORY_STALE_TIME_MS = 60_000;
|
|
33
|
-
const RESTORE_MESSAGES_PAGE = 1;
|
|
34
41
|
|
|
35
42
|
function toNonNegativeInteger(value, fallback = 0) {
|
|
36
43
|
const parsed = Number(value);
|
|
@@ -78,18 +85,6 @@ function writeStoredActiveConversationId(scope = {}, conversationId) {
|
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
function buildId(prefix = "id") {
|
|
82
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
83
|
-
return `${prefix}_${crypto.randomUUID()}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function normalizeToolName(value) {
|
|
90
|
-
return normalizeText(value) || "tool";
|
|
91
|
-
}
|
|
92
|
-
|
|
93
88
|
function normalizeConversationStatus(value) {
|
|
94
89
|
return normalizeAssistantConversationStatus(value, {
|
|
95
90
|
fallback: "unknown"
|
|
@@ -110,111 +105,19 @@ function formatConversationStartedAt(value) {
|
|
|
110
105
|
return date.toLocaleString();
|
|
111
106
|
}
|
|
112
107
|
|
|
113
|
-
function parseToolResultPayload(value) {
|
|
114
|
-
return parseJsonObject(value);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function buildHistory(messages) {
|
|
118
|
-
const normalizedHistory = (Array.isArray(messages) ? messages : [])
|
|
119
|
-
.filter((message) => {
|
|
120
|
-
if (!message || typeof message !== "object") {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
if (message.kind !== "chat") {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
if (message.role !== "user" && message.role !== "assistant") {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
if (normalizeText(message.status).toLowerCase() !== "done") {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
return Boolean(normalizeText(message.text));
|
|
133
|
-
})
|
|
134
|
-
.map((message) => ({
|
|
135
|
-
role: message.role,
|
|
136
|
-
content: String(message.text || "").slice(0, MAX_INPUT_CHARS)
|
|
137
|
-
}));
|
|
138
|
-
|
|
139
|
-
return normalizedHistory.slice(-MAX_HISTORY_MESSAGES);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function mapTranscriptEntriesToAssistantState(entries) {
|
|
143
|
-
const sourceEntries = Array.isArray(entries) ? entries : [];
|
|
144
|
-
const messages = [];
|
|
145
|
-
const toolEventsById = new Map();
|
|
146
|
-
|
|
147
|
-
function ensureToolEvent(toolCallId, toolName) {
|
|
148
|
-
const key = normalizeText(toolCallId) || buildId("tool_call");
|
|
149
|
-
if (toolEventsById.has(key)) {
|
|
150
|
-
return toolEventsById.get(key);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const next = {
|
|
154
|
-
id: key,
|
|
155
|
-
name: normalizeToolName(toolName),
|
|
156
|
-
arguments: "",
|
|
157
|
-
status: "pending",
|
|
158
|
-
result: null,
|
|
159
|
-
error: null
|
|
160
|
-
};
|
|
161
|
-
toolEventsById.set(key, next);
|
|
162
|
-
return next;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
for (const entry of sourceEntries) {
|
|
166
|
-
const role = normalizeText(entry?.role).toLowerCase();
|
|
167
|
-
const kind = normalizeText(entry?.kind).toLowerCase();
|
|
168
|
-
const metadata = normalizeObject(entry?.metadata);
|
|
169
|
-
const transcriptId = normalizeRecordId(entry?.id, { fallback: null });
|
|
170
|
-
const messageId = transcriptId ? `transcript_${transcriptId}` : buildId("transcript");
|
|
171
|
-
|
|
172
|
-
if (kind === "chat" && (role === "user" || role === "assistant")) {
|
|
173
|
-
messages.push({
|
|
174
|
-
id: messageId,
|
|
175
|
-
role,
|
|
176
|
-
kind: "chat",
|
|
177
|
-
text: entry?.contentText == null ? "" : String(entry.contentText),
|
|
178
|
-
status: "done"
|
|
179
|
-
});
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (kind === "tool_call") {
|
|
184
|
-
const toolCallId = normalizeText(metadata.toolCallId) || `tool_call_${messageId}`;
|
|
185
|
-
const toolName = normalizeToolName(metadata.tool);
|
|
186
|
-
const toolEvent = ensureToolEvent(toolCallId, toolName);
|
|
187
|
-
toolEvent.arguments = String(entry?.contentText || "");
|
|
188
|
-
toolEvent.status = "pending";
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (kind === "tool_result") {
|
|
193
|
-
const parsedResult = parseToolResultPayload(entry?.contentText);
|
|
194
|
-
const toolCallId = normalizeText(metadata.toolCallId || parsedResult.toolCallId) || `tool_result_${messageId}`;
|
|
195
|
-
const toolName = normalizeToolName(metadata.tool || parsedResult.tool);
|
|
196
|
-
const toolEvent = ensureToolEvent(toolCallId, toolName);
|
|
197
|
-
const failed = parsedResult.ok === false || metadata.ok === false;
|
|
198
|
-
toolEvent.status = failed ? "failed" : "done";
|
|
199
|
-
toolEvent.result = failed ? null : parsedResult.result;
|
|
200
|
-
toolEvent.error = failed ? parsedResult.error || metadata.error || null : null;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
messages,
|
|
206
|
-
pendingToolEvents: [...toolEventsById.values()]
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
108
|
function resolveRuntimePolicy() {
|
|
211
109
|
const appConfig = getClientAppConfig();
|
|
212
110
|
const assistantConfig = normalizeObject(appConfig?.assistant);
|
|
111
|
+
const conversationRestorePolicy = resolveConversationRestorePolicy({
|
|
112
|
+
pageSize: assistantConfig.restoreMessagesPageSize,
|
|
113
|
+
maxEntries: assistantConfig.restoreMessagesMaxEntries
|
|
114
|
+
});
|
|
213
115
|
|
|
214
116
|
return Object.freeze({
|
|
215
117
|
timeoutMs: toPositiveInteger(assistantConfig.timeoutMs, DEFAULT_STREAM_TIMEOUT_MS),
|
|
216
118
|
historyPageSize: toPositiveInteger(assistantConfig.historyPageSize, DEFAULT_HISTORY_PAGE_SIZE),
|
|
217
|
-
restoreMessagesPageSize:
|
|
119
|
+
restoreMessagesPageSize: conversationRestorePolicy.pageSize,
|
|
120
|
+
restoreMessagesMaxEntries: conversationRestorePolicy.maxEntries,
|
|
218
121
|
historyStaleTimeMs: toNonNegativeInteger(assistantConfig.historyStaleTimeMs, DEFAULT_HISTORY_STALE_TIME_MS)
|
|
219
122
|
});
|
|
220
123
|
}
|
|
@@ -474,19 +377,23 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
474
377
|
setRuntimeError("");
|
|
475
378
|
|
|
476
379
|
try {
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
380
|
+
const transcript = await loadConversationTranscript({
|
|
381
|
+
pageSize: runtimePolicy.restoreMessagesPageSize,
|
|
382
|
+
maxEntries: runtimePolicy.restoreMessagesMaxEntries,
|
|
383
|
+
fetchPage: (page, pageSize) => queryClient.fetchQuery({
|
|
384
|
+
queryKey: assistantConversationMessagesQueryKey(runtimeScope.value, parsedConversationId, {
|
|
385
|
+
page,
|
|
386
|
+
pageSize
|
|
387
|
+
}),
|
|
388
|
+
queryFn: () => runtimeApi.getConversationMessages(parsedConversationId, {
|
|
389
|
+
page,
|
|
390
|
+
pageSize
|
|
391
|
+
}),
|
|
392
|
+
staleTime: runtimePolicy.historyStaleTimeMs
|
|
393
|
+
})
|
|
487
394
|
});
|
|
488
395
|
|
|
489
|
-
const restored = mapTranscriptEntriesToAssistantState(
|
|
396
|
+
const restored = mapTranscriptEntriesToAssistantState(transcript.entries);
|
|
490
397
|
messages.value = restored.messages;
|
|
491
398
|
pendingToolEvents.value = restored.pendingToolEvents;
|
|
492
399
|
input.value = "";
|
|
@@ -724,6 +631,7 @@ function useAssistantRuntime({ api = null, surfaceId = "" } = {}) {
|
|
|
724
631
|
}
|
|
725
632
|
} finally {
|
|
726
633
|
clearTimeout(streamTimeout);
|
|
634
|
+
pendingToolEvents.value = interruptPendingToolEvents(pendingToolEvents.value);
|
|
727
635
|
abortController.value = null;
|
|
728
636
|
isStreaming.value = false;
|
|
729
637
|
await invalidateConversationScope();
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
import {
|
|
3
|
+
MAX_HISTORY_MESSAGES,
|
|
4
|
+
MAX_INPUT_CHARS,
|
|
5
|
+
parseJsonObject
|
|
6
|
+
} from "@jskit-ai/assistant-core/shared";
|
|
7
|
+
import { isAssistantProgressOnlyText } from "../../shared/assistantResponseText.js";
|
|
8
|
+
|
|
9
|
+
function buildId(prefix = "id") {
|
|
10
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
11
|
+
return `${prefix}_${crypto.randomUUID()}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeToolName(value) {
|
|
18
|
+
return normalizeText(value) || "tool";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildHistory(messages) {
|
|
22
|
+
const normalizedHistory = (Array.isArray(messages) ? messages : [])
|
|
23
|
+
.filter((message) => {
|
|
24
|
+
if (!message || typeof message !== "object") {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (message.kind !== "chat") {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (message.role !== "user" && message.role !== "assistant") {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (normalizeText(message.status).toLowerCase() !== "done") {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const text = normalizeText(message.text);
|
|
37
|
+
if (!text) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return message.role !== "assistant" || !isAssistantProgressOnlyText(text);
|
|
41
|
+
})
|
|
42
|
+
.map((message) => ({
|
|
43
|
+
role: message.role,
|
|
44
|
+
content: String(message.text || "").slice(0, MAX_INPUT_CHARS)
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
return normalizedHistory.slice(-MAX_HISTORY_MESSAGES);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function interruptPendingToolEvents(toolEvents) {
|
|
51
|
+
return (Array.isArray(toolEvents) ? toolEvents : []).map((toolEvent) => {
|
|
52
|
+
if (normalizeText(toolEvent?.status).toLowerCase() !== "pending") {
|
|
53
|
+
return toolEvent;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...toolEvent,
|
|
58
|
+
status: "interrupted"
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function mapTranscriptEntriesToAssistantState(entries) {
|
|
64
|
+
const sourceEntries = Array.isArray(entries) ? entries : [];
|
|
65
|
+
const messages = [];
|
|
66
|
+
const toolEventsById = new Map();
|
|
67
|
+
|
|
68
|
+
function ensureToolEvent(toolCallId, toolName) {
|
|
69
|
+
const key = normalizeText(toolCallId) || buildId("tool_call");
|
|
70
|
+
if (toolEventsById.has(key)) {
|
|
71
|
+
return toolEventsById.get(key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const next = {
|
|
75
|
+
id: key,
|
|
76
|
+
name: normalizeToolName(toolName),
|
|
77
|
+
arguments: "",
|
|
78
|
+
status: "pending",
|
|
79
|
+
result: null,
|
|
80
|
+
error: null
|
|
81
|
+
};
|
|
82
|
+
toolEventsById.set(key, next);
|
|
83
|
+
return next;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const entry of sourceEntries) {
|
|
87
|
+
const role = normalizeText(entry?.role).toLowerCase();
|
|
88
|
+
const kind = normalizeText(entry?.kind).toLowerCase();
|
|
89
|
+
const metadata = normalizeObject(entry?.metadata);
|
|
90
|
+
const transcriptId = normalizeRecordId(entry?.id, { fallback: null });
|
|
91
|
+
const messageId = transcriptId ? `transcript_${transcriptId}` : buildId("transcript");
|
|
92
|
+
|
|
93
|
+
if (kind === "chat" && (role === "user" || role === "assistant")) {
|
|
94
|
+
const text = entry?.contentText == null ? "" : String(entry.contentText);
|
|
95
|
+
if (role === "assistant" && isAssistantProgressOnlyText(text)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
messages.push({
|
|
100
|
+
id: messageId,
|
|
101
|
+
role,
|
|
102
|
+
kind: "chat",
|
|
103
|
+
text,
|
|
104
|
+
status: "done"
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (kind === "tool_call") {
|
|
110
|
+
const toolCallId = normalizeText(metadata.toolCallId) || `tool_call_${messageId}`;
|
|
111
|
+
const toolEvent = ensureToolEvent(toolCallId, metadata.tool);
|
|
112
|
+
toolEvent.arguments = String(entry?.contentText || "");
|
|
113
|
+
toolEvent.status = "pending";
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (kind === "tool_result") {
|
|
118
|
+
const parsedResult = parseJsonObject(entry?.contentText);
|
|
119
|
+
const toolCallId = normalizeText(metadata.toolCallId || parsedResult.toolCallId) || `tool_result_${messageId}`;
|
|
120
|
+
const toolEvent = ensureToolEvent(toolCallId, metadata.tool || parsedResult.tool);
|
|
121
|
+
const failed = parsedResult.ok === false || metadata.ok === false;
|
|
122
|
+
toolEvent.status = failed ? "failed" : "done";
|
|
123
|
+
toolEvent.result = failed ? null : parsedResult.result;
|
|
124
|
+
toolEvent.error = failed ? parsedResult.error || metadata.error || null : null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
messages,
|
|
130
|
+
pendingToolEvents: interruptPendingToolEvents([...toolEventsById.values()])
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
buildHistory,
|
|
136
|
+
buildId,
|
|
137
|
+
interruptPendingToolEvents,
|
|
138
|
+
mapTranscriptEntriesToAssistantState,
|
|
139
|
+
normalizeToolName
|
|
140
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { MAX_MESSAGE_PAGE_SIZE } from "@jskit-ai/assistant-core/shared";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_RESTORE_MESSAGES_PAGE_SIZE = 200;
|
|
4
|
+
const DEFAULT_RESTORE_MESSAGES_MAX_ENTRIES = 2000;
|
|
5
|
+
const MAX_RESTORE_MESSAGES_MAX_ENTRIES = 5000;
|
|
6
|
+
|
|
7
|
+
function normalizeBoundedPositiveInteger(value, fallback, maximum) {
|
|
8
|
+
const parsed = Number(value);
|
|
9
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return Math.min(parsed, maximum);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeTotalPages(value) {
|
|
17
|
+
const parsed = Number(value);
|
|
18
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveConversationRestorePolicy({ pageSize, maxEntries } = {}) {
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
pageSize: normalizeBoundedPositiveInteger(
|
|
24
|
+
pageSize,
|
|
25
|
+
DEFAULT_RESTORE_MESSAGES_PAGE_SIZE,
|
|
26
|
+
MAX_MESSAGE_PAGE_SIZE
|
|
27
|
+
),
|
|
28
|
+
maxEntries: normalizeBoundedPositiveInteger(
|
|
29
|
+
maxEntries,
|
|
30
|
+
DEFAULT_RESTORE_MESSAGES_MAX_ENTRIES,
|
|
31
|
+
MAX_RESTORE_MESSAGES_MAX_ENTRIES
|
|
32
|
+
)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function loadConversationTranscript({
|
|
37
|
+
fetchPage,
|
|
38
|
+
pageSize = DEFAULT_RESTORE_MESSAGES_PAGE_SIZE,
|
|
39
|
+
maxEntries = DEFAULT_RESTORE_MESSAGES_MAX_ENTRIES
|
|
40
|
+
} = {}) {
|
|
41
|
+
if (typeof fetchPage !== "function") {
|
|
42
|
+
throw new TypeError("loadConversationTranscript requires fetchPage().");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const restorePolicy = resolveConversationRestorePolicy({ pageSize, maxEntries });
|
|
46
|
+
const normalizedPageSize = restorePolicy.pageSize;
|
|
47
|
+
const normalizedMaxEntries = restorePolicy.maxEntries;
|
|
48
|
+
const firstResponse = await fetchPage(1, normalizedPageSize);
|
|
49
|
+
const totalPages = normalizeTotalPages(firstResponse?.totalPages);
|
|
50
|
+
const restoredPageCount = Math.max(1, Math.ceil(normalizedMaxEntries / normalizedPageSize));
|
|
51
|
+
const firstRestoredPage = Math.max(1, totalPages - restoredPageCount + 1);
|
|
52
|
+
const entries = firstRestoredPage === 1 && Array.isArray(firstResponse?.entries)
|
|
53
|
+
? [...firstResponse.entries]
|
|
54
|
+
: [];
|
|
55
|
+
|
|
56
|
+
for (let page = Math.max(2, firstRestoredPage); page <= totalPages; page += 1) {
|
|
57
|
+
const response = await fetchPage(page, normalizedPageSize);
|
|
58
|
+
if (Array.isArray(response?.entries)) {
|
|
59
|
+
entries.push(...response.entries);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Object.freeze({
|
|
64
|
+
entries: Object.freeze(entries.slice(-normalizedMaxEntries)),
|
|
65
|
+
firstRestoredPage,
|
|
66
|
+
pageSize: normalizedPageSize,
|
|
67
|
+
totalPages,
|
|
68
|
+
truncated: firstRestoredPage > 1
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
loadConversationTranscript,
|
|
74
|
+
resolveConversationRestorePolicy
|
|
75
|
+
};
|