@jskit-ai/assistant-runtime 0.1.215 → 0.1.216
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.
|
@@ -122,6 +122,7 @@ export default defineConfig({
|
|
|
122
122
|
if (denied || !state) return json({ error: "Access denied.", code: "conversation_forbidden" }, 403);
|
|
123
123
|
if (request.method === "GET" && !match[2]) {
|
|
124
124
|
return json(pageLimits.has(id) ? { ...state, ...await transcript.readConversationLogPage(id, {
|
|
125
|
+
pageUnit: "messages",
|
|
125
126
|
beforeTurnId: url.searchParams.get("beforeTurnId") || "",
|
|
126
127
|
limit: url.searchParams.get("limit") || pageLimits.get(id)
|
|
127
128
|
}) } : state);
|
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.216",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"./server": "./src/server/index.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
15
|
+
"@jskit-ai/assistant-core": "0.1.223",
|
|
16
16
|
"json-rest-schema": "^1.0.17"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
@@ -274,7 +274,9 @@ function createConversation(identity, { api, socket, actorKey, placement, reader
|
|
|
274
274
|
// refresh in this same read, rather than dropping that newly loaded page.
|
|
275
275
|
while (valid()) {
|
|
276
276
|
const revision = historyRevision;
|
|
277
|
-
const
|
|
277
|
+
const oldestTurn = olderPages.value[0]?.conversationLog?.[0];
|
|
278
|
+
const oldest = oldestTurn?.turnId;
|
|
279
|
+
const oldestCursor = olderPages.value[0]?.pagination?.nextBeforeTurnId || oldest;
|
|
278
280
|
const latest = await api.readConversation(identity.conversationId);
|
|
279
281
|
if (!valid()) return latest;
|
|
280
282
|
const pages = [];
|
|
@@ -282,7 +284,9 @@ function createConversation(identity, { api, socket, actorKey, placement, reader
|
|
|
282
284
|
const cursors = new Set();
|
|
283
285
|
while (oldest && page.pagination.hasMoreBefore) {
|
|
284
286
|
const cursor = page.pagination.nextBeforeTurnId || page.pagination.oldestTurnId;
|
|
285
|
-
|
|
287
|
+
const reachedFullOldestTurn = page.pagination.oldestTurnId === oldest && !page.conversationLog[0]?.paginationPartial;
|
|
288
|
+
const passedOldestTurn = compareTurnIds(page.pagination.oldestTurnId, oldest) < 0;
|
|
289
|
+
if (cursor && (cursor === oldestCursor || passedOldestTurn || reachedFullOldestTurn)) break;
|
|
286
290
|
if (!cursor || cursors.has(cursor)) throw new Error("Older conversation history could not be refreshed. Reload chat to try again.");
|
|
287
291
|
cursors.add(cursor);
|
|
288
292
|
page = normalizeConversationLogPage(await api.readConversation(identity.conversationId, {
|
|
@@ -291,7 +295,14 @@ function createConversation(identity, { api, socket, actorKey, placement, reader
|
|
|
291
295
|
if (!valid()) return latest;
|
|
292
296
|
// A removed oldest row can make this page reach farther back than the
|
|
293
297
|
// reader loaded. Keep the original visible boundary and page metadata.
|
|
294
|
-
const saved = page.conversationLog.filter(turn => compareTurnIds(turn.turnId, oldest) >= 0)
|
|
298
|
+
const saved = page.conversationLog.filter(turn => compareTurnIds(turn.turnId, oldest) >= 0).map(turn => {
|
|
299
|
+
if (turn.turnId !== oldest || !oldestTurn.paginationPartial) return turn;
|
|
300
|
+
const roles = ["system", "user", "assistant"];
|
|
301
|
+
const excluded = roles.slice(0, roles.findIndex(role => oldestTurn[role]));
|
|
302
|
+
return { ...turn, paginationPartial: true,
|
|
303
|
+
...Object.fromEntries(excluded.map(role => [role, null])),
|
|
304
|
+
messages: turn.messages?.filter(message => !excluded.includes(message.role)) };
|
|
305
|
+
});
|
|
295
306
|
pages.unshift(normalizeConversationLogPage({ ...page, conversationLog: saved, pagination: {
|
|
296
307
|
...page.pagination, count: saved.length, oldestTurnId: saved[0]?.turnId || "",
|
|
297
308
|
hasMoreBefore: page.pagination.hasMoreBefore || saved.length < page.conversationLog.length
|
|
@@ -501,7 +512,8 @@ function createConversation(identity, { api, socket, actorKey, placement, reader
|
|
|
501
512
|
const finish = (result) => {
|
|
502
513
|
if (typeof complete === "function") complete(result);
|
|
503
514
|
};
|
|
504
|
-
const
|
|
515
|
+
const pagination = normalizeConversationLogPagination(oldestLoadedPage.value?.pagination);
|
|
516
|
+
const beforeTurnId = pagination.nextBeforeTurnId || pagination.oldestTurnId ||
|
|
505
517
|
String(turns.value[0]?.turnId || "").trim();
|
|
506
518
|
if (!available.value || !hasMoreBefore.value || loadingMore.value || !beforeTurnId) {
|
|
507
519
|
finish({ changed: false, loaded: false });
|
|
@@ -519,10 +531,13 @@ function createConversation(identity, { api, socket, actorKey, placement, reader
|
|
|
519
531
|
return false;
|
|
520
532
|
}
|
|
521
533
|
const previousOldestTurnId = String(turns.value[0]?.turnId || "").trim();
|
|
534
|
+
const previousMessageCount = turns.value.reduce((count, turn) => count + (turn.messages?.length || 0), 0);
|
|
522
535
|
historyRevision += 1;
|
|
523
536
|
olderPages.value = [normalizeConversationLogPage(page), ...olderPages.value];
|
|
524
537
|
const nextOldestTurnId = String(turns.value[0]?.turnId || "").trim();
|
|
525
|
-
const
|
|
538
|
+
const nextMessageCount = turns.value.reduce((count, turn) => count + (turn.messages?.length || 0), 0);
|
|
539
|
+
const changed = Boolean(nextOldestTurnId && (nextOldestTurnId !== previousOldestTurnId ||
|
|
540
|
+
nextMessageCount !== previousMessageCount));
|
|
526
541
|
finish({ changed, loaded: true });
|
|
527
542
|
return true;
|
|
528
543
|
} catch (failure) {
|
|
@@ -1628,7 +1628,7 @@ test("paged history uses the original page reader and keeps older messages throu
|
|
|
1628
1628
|
user: { role: "user", messageId: `history-${index + 1}`, text: `Saved question ${index + 1}` },
|
|
1629
1629
|
...(index < 4 ? { assistant: { role: "assistant", messageId: `answer-${index + 1}`, text: `Saved answer ${index + 1}` } } : {})
|
|
1630
1630
|
}));
|
|
1631
|
-
await f.command("history", { id: "chat:1", turns, limit:
|
|
1631
|
+
await f.command("history", { id: "chat:1", turns, limit: 4 });
|
|
1632
1632
|
await f.notify("chat:1");
|
|
1633
1633
|
await expect(f.primary.getByText("Saved question 5", { exact: true })).toHaveCount(1);
|
|
1634
1634
|
await expect(f.primary.getByText("Saved question 1", { exact: true })).toHaveCount(0);
|
|
@@ -1639,7 +1639,7 @@ test("paged history uses the original page reader and keeps older messages throu
|
|
|
1639
1639
|
assert.deepEqual(await f.page.evaluate(() => window.historyCompletion), { changed: true, loaded: true });
|
|
1640
1640
|
await expect(f.primary.getByText("Saved question 2", { exact: true })).toHaveCount(1);
|
|
1641
1641
|
const paged = (await f.state()).requests.filter(request => request.query.beforeTurnId);
|
|
1642
|
-
assert.deepEqual(paged.map(request => request.query), [{ beforeTurnId: "
|
|
1642
|
+
assert.deepEqual(paged.map(request => request.query), [{ beforeTurnId: 'message:["000003","assistant"]', limit: "4" }]);
|
|
1643
1643
|
await f.page.evaluate(() => window.conversationFixture.socket.notify("chat:1", {
|
|
1644
1644
|
type: "message", turnId: "000005", messageId: "live-answer", role: "assistant", status: "inProgress", text: "Live answer",
|
|
1645
1645
|
streaming: { revision: 1, messages: [{ turnId: "000005", messageId: "live-answer", role: "assistant", status: "inProgress", text: "Live answer" }] }
|
|
@@ -2230,7 +2230,7 @@ test("canonical incoming messages preserve paged history and the scrolled-up rea
|
|
|
2230
2230
|
user: { messageId: `loaded-user-${index}`, role: "user", text: `Loaded question ${index + 1}. ` + "Keep the earlier discussion. ".repeat(6) },
|
|
2231
2231
|
assistant: { messageId: `loaded-answer-${index}`, role: "assistant", text: `Loaded answer ${index + 1}. ` + "Keep the earlier answer. ".repeat(6) }
|
|
2232
2232
|
}));
|
|
2233
|
-
await f.command("history", { id: "chat:1", turns, limit:
|
|
2233
|
+
await f.command("history", { id: "chat:1", turns, limit: 12 });
|
|
2234
2234
|
await f.notify("chat:1");
|
|
2235
2235
|
await expect.poll(() => f.page.evaluate(() => window.conversationFixture.current().turns.value.length)).toBe(6);
|
|
2236
2236
|
assert.equal(await f.page.evaluate(() => window.conversationFixture.current().loadMore()), true);
|
|
@@ -2246,7 +2246,7 @@ test("canonical incoming messages preserve paged history and the scrolled-up rea
|
|
|
2246
2246
|
await f.input.fill("Keep this unsent draft");
|
|
2247
2247
|
await f.input.evaluate(element => { element.focus({ preventScroll: true }); element.setSelectionRange(2, 7); });
|
|
2248
2248
|
turns.push({ turnId: "000025", user: { messageId: "incoming-user", role: "user", text: "New incoming question" } });
|
|
2249
|
-
await f.command("history", { id: "chat:1", turns, limit:
|
|
2249
|
+
await f.command("history", { id: "chat:1", turns, limit: 12 });
|
|
2250
2250
|
await f.notify("chat:1");
|
|
2251
2251
|
await expect(f.primary.getByText("New incoming question", { exact: true })).toHaveCount(1);
|
|
2252
2252
|
const ids = await f.page.evaluate(() => window.conversationFixture.current().turns.value.map(turn => turn.turnId));
|
|
@@ -2267,7 +2267,7 @@ for (const width of [390, 768, 1280]) {
|
|
|
2267
2267
|
user: { role: "user", messageId: `atomic-user-${index}`, text: `Atomic saved question ${index + 1}. ` + "Retain visible discussion. ".repeat(8) },
|
|
2268
2268
|
assistant: { role: "assistant", messageId: `atomic-answer-${index}`, text: `Atomic saved answer ${index + 1}. ` + "Retain visible answer. ".repeat(8) }
|
|
2269
2269
|
}));
|
|
2270
|
-
await f.command("history", { id: "chat:1", turns, limit:
|
|
2270
|
+
await f.command("history", { id: "chat:1", turns, limit: 12 });
|
|
2271
2271
|
await f.notify("chat:1");
|
|
2272
2272
|
await expect.poll(() => f.page.evaluate(() => window.conversationFixture.current().turns.value.length)).toBe(6);
|
|
2273
2273
|
assert.equal(await f.page.evaluate(() => window.conversationFixture.current().loadMore()), true);
|
|
@@ -2284,7 +2284,7 @@ for (const width of [390, 768, 1280]) {
|
|
|
2284
2284
|
const fresh = turns.filter(turn => !["000007", "000010"].includes(turn.turnId));
|
|
2285
2285
|
fresh[8] = { ...fresh[8], assistant: { ...fresh[8].assistant, text: "Authoritatively replaced saved answer" } };
|
|
2286
2286
|
fresh.push({ turnId: "000025", user: { role: "user", messageId: "atomic-incoming", text: "Atomic new question" } });
|
|
2287
|
-
await f.command("history", { id: "chat:1", turns: fresh, limit:
|
|
2287
|
+
await f.command("history", { id: "chat:1", turns: fresh, limit: 12 });
|
|
2288
2288
|
let release;
|
|
2289
2289
|
let held = false;
|
|
2290
2290
|
const reads = "**/api/assistant/home/conversations/chat%3A1?**";
|
|
@@ -2971,3 +2971,43 @@ test("unknown delivery reports its current result and allows different idle inpu
|
|
|
2971
2971
|
assert.equal(await f.page.evaluate(id => window.conversationFixture.current().delivery.find(id).status,
|
|
2972
2972
|
old.input.messageId), "uncertain");
|
|
2973
2973
|
});
|
|
2974
|
+
|
|
2975
|
+
|
|
2976
|
+
for (const width of [1280, 390]) test(`ten real messages per history load across split turns at ${width}px`, options, async t => {
|
|
2977
|
+
const f = await fixture(t, width);
|
|
2978
|
+
const turns = [];
|
|
2979
|
+
let id = 0;
|
|
2980
|
+
for (let index = 1; index <= 16; index++) {
|
|
2981
|
+
const user = { role: "user", messageId: `ten-user-${index}`, text: `Ten question ${index}` };
|
|
2982
|
+
const assistant = index < 16 ? { role: "assistant", messageId: `ten-answer-${index}`, text: `Ten answer ${index}` } : null;
|
|
2983
|
+
turns.push({ turnId: String(++id).padStart(6, "0"), user, assistant, messages: [user, assistant].filter(Boolean) });
|
|
2984
|
+
for (let reasoning = 0; reasoning < 4; reasoning++) {
|
|
2985
|
+
const thought = { role: "thinking", messageId: `ten-thought-${index}-${reasoning}`, text: `Reasoning ${index} ${reasoning}` };
|
|
2986
|
+
turns.push({ turnId: String(++id).padStart(6, "0"), thinking: [thought], messages: [thought] });
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
await f.command("history", { id: "chat:1", turns, limit: 10 });
|
|
2990
|
+
await f.notify("chat:1");
|
|
2991
|
+
const messageCount = () => f.page.evaluate(() => window.conversationFixture.current().turns.value
|
|
2992
|
+
.reduce((count, turn) => count + [turn.system, turn.user, turn.assistant].filter(Boolean).length, 0));
|
|
2993
|
+
await expect.poll(messageCount).toBe(10);
|
|
2994
|
+
await expect(f.primary.getByText("Ten question 11", { exact: true })).toHaveCount(0);
|
|
2995
|
+
await expect(f.primary.getByText("Ten answer 11", { exact: true })).toHaveCount(1);
|
|
2996
|
+
assert.equal(await f.page.evaluate(() => window.conversationFixture.current().loadMore()), true);
|
|
2997
|
+
await expect.poll(messageCount).toBe(20);
|
|
2998
|
+
await f.page.evaluate(() => { window.beforeTenMessageRefresh = window.conversationFixture.current().snapshot.value; });
|
|
2999
|
+
await f.notify("chat:1");
|
|
3000
|
+
await expect.poll(() => f.page.evaluate(() => window.conversationFixture.current().snapshot.value !== window.beforeTenMessageRefresh)).toBe(true);
|
|
3001
|
+
await expect.poll(messageCount).toBe(20);
|
|
3002
|
+
assert.equal(await f.page.evaluate(() => window.conversationFixture.current().loadMore()), true);
|
|
3003
|
+
await expect.poll(messageCount).toBe(30);
|
|
3004
|
+
await expect(f.primary.getByText("Ten question 1", { exact: true })).toHaveCount(0);
|
|
3005
|
+
assert.equal(await f.page.evaluate(() => window.conversationFixture.current().loadMore({
|
|
3006
|
+
complete: result => { window.tenMessageCompletion = result; }
|
|
3007
|
+
})), true);
|
|
3008
|
+
await expect.poll(messageCount).toBe(31);
|
|
3009
|
+
assert.deepEqual(await f.page.evaluate(() => window.tenMessageCompletion), { changed: true, loaded: true });
|
|
3010
|
+
await expect(f.primary.getByText("Ten question 1", { exact: true })).toHaveCount(1);
|
|
3011
|
+
await expect(f.primary.getByRole("button", { name: "Load older messages", exact: true })).toHaveCount(0);
|
|
3012
|
+
assert.deepEqual(await f.sent(), []);
|
|
3013
|
+
});
|