@btst/stack 4.4.0 → 4.5.1
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 +2 -2
- package/dist/packages/stack/src/plugins/ai-chat/api/compaction.cjs +166 -39
- package/dist/packages/stack/src/plugins/ai-chat/api/compaction.mjs +167 -40
- package/dist/packages/stack/src/plugins/ai-chat/api/context-checkpoint.cjs +54 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/context-checkpoint.mjs +51 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +2 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +2 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +275 -95
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +276 -96
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +8 -5
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +8 -5
- package/dist/packages/stack/src/plugins/ai-chat/api/serializers.cjs +1 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/serializers.mjs +1 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +3 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +3 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +26 -17
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +26 -17
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.cjs +22 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.mjs +22 -2
- package/dist/packages/stack/src/plugins/ai-chat/db.cjs +8 -0
- package/dist/packages/stack/src/plugins/ai-chat/db.mjs +8 -0
- package/dist/packages/stack/src/plugins/ai-chat/history.cjs +47 -0
- package/dist/packages/stack/src/plugins/ai-chat/history.mjs +45 -0
- package/dist/plugins/ai-chat/api/index.d.cts +2 -2
- package/dist/plugins/ai-chat/api/index.d.mts +2 -2
- package/dist/plugins/ai-chat/api/index.d.ts +2 -2
- package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
- package/dist/plugins/ai-chat/client/index.d.cts +3 -3
- package/dist/plugins/ai-chat/client/index.d.mts +3 -3
- package/dist/plugins/ai-chat/client/index.d.ts +3 -3
- package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
- package/dist/plugins/blog/api/index.d.cts +1 -1
- package/dist/plugins/blog/api/index.d.mts +1 -1
- package/dist/plugins/blog/api/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.d.cts +1 -1
- package/dist/plugins/blog/query-keys.d.mts +1 -1
- package/dist/plugins/blog/query-keys.d.ts +1 -1
- package/dist/plugins/cms/api/index.d.cts +1 -1
- package/dist/plugins/cms/api/index.d.mts +1 -1
- package/dist/plugins/cms/api/index.d.ts +1 -1
- package/dist/plugins/cms/query-keys.d.cts +1 -1
- package/dist/plugins/cms/query-keys.d.mts +1 -1
- package/dist/plugins/cms/query-keys.d.ts +1 -1
- package/dist/plugins/form-builder/api/index.d.cts +1 -1
- package/dist/plugins/form-builder/api/index.d.mts +1 -1
- package/dist/plugins/form-builder/api/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/query-keys.d.cts +1 -1
- package/dist/plugins/form-builder/query-keys.d.mts +1 -1
- package/dist/plugins/form-builder/query-keys.d.ts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
- package/dist/shared/{stack.DDQlE2Pm.d.cts → stack.Cei2T2gc.d.cts} +1 -0
- package/dist/shared/{stack.C4zspX0T.d.ts → stack.DDQTbADz.d.ts} +7 -2
- package/dist/shared/{stack.tAUeRAtd.d.mts → stack.DZRZAzcx.d.mts} +1 -0
- package/dist/shared/{stack.ymWsP4HZ.d.cts → stack.IY-ngxuw.d.cts} +7 -2
- package/dist/shared/{stack.DlehgNZZ.d.ts → stack.byjorQi5.d.ts} +1 -0
- package/dist/shared/{stack.Ps3MDhTC.d.mts → stack.mU9iMF2j.d.mts} +7 -2
- package/package.json +1 -1
- package/src/plugins/ai-chat/__tests__/authorization.test.ts +56 -30
- package/src/plugins/ai-chat/__tests__/chat-input-authorization.test.tsx +42 -0
- package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +25 -0
- package/src/plugins/ai-chat/__tests__/client-sweep.test.tsx +101 -0
- package/src/plugins/ai-chat/__tests__/compaction.test.ts +79 -19
- package/src/plugins/ai-chat/__tests__/persisted-compaction.test.ts +948 -0
- package/src/plugins/ai-chat/api/compaction.ts +243 -52
- package/src/plugins/ai-chat/api/context-checkpoint.ts +66 -0
- package/src/plugins/ai-chat/api/getters.ts +2 -0
- package/src/plugins/ai-chat/api/operations.ts +377 -101
- package/src/plugins/ai-chat/api/plugin.ts +14 -8
- package/src/plugins/ai-chat/api/serializers.ts +1 -0
- package/src/plugins/ai-chat/client/components/chat-input.tsx +3 -3
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +43 -23
- package/src/plugins/ai-chat/client/components/chat-message.tsx +32 -2
- package/src/plugins/ai-chat/client/localization/index.ts +1 -0
- package/src/plugins/ai-chat/db.ts +8 -0
- package/src/plugins/ai-chat/history.ts +68 -0
- package/src/plugins/ai-chat/types.ts +2 -0
- package/dist/shared/{stack.B8PG21zX.d.mts → stack.BLYaUcQa.d.mts} +88 -88
- package/dist/shared/{stack.DNw6Hv5o.d.cts → stack.BOZRaVpu.d.cts} +2 -2
- package/dist/shared/{stack.D6Pg-ufX.d.cts → stack.Bf6BGZNk.d.cts} +88 -88
- package/dist/shared/{stack.CGmh-Lpe.d.ts → stack.BuxUQqL8.d.ts} +12 -12
- package/dist/shared/{stack.zch70Loj.d.cts → stack.CX83YNuS.d.cts} +12 -12
- package/dist/shared/{stack.CyalJ0j6.d.ts → stack.CyltNNZZ.d.ts} +88 -88
- package/dist/shared/{stack.Clil75lW.d.ts → stack.DlBoNqYu.d.ts} +2 -2
- package/dist/shared/{stack.DhZtP2JZ.d.mts → stack.Dvy7FmaA.d.mts} +2 -2
- package/dist/shared/{stack.Doy2_wgl.d.mts → stack.Egr3RWhA.d.mts} +12 -12
|
@@ -301,6 +301,107 @@ function menuItem(text: string) {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
describe("AI Chat permissions", () => {
|
|
304
|
+
it("shows authorized compaction status and ignores browser checkpoints", async () => {
|
|
305
|
+
let chatOptions: any;
|
|
306
|
+
const messages = [
|
|
307
|
+
{ id: "u1", role: "user", parts: [{ type: "text", text: "Hello" }] },
|
|
308
|
+
];
|
|
309
|
+
mocks.useAiChatIdentityPartition.mockReturnValue({
|
|
310
|
+
id: "owner-1",
|
|
311
|
+
role: "user",
|
|
312
|
+
});
|
|
313
|
+
mocks.useChat.mockImplementation((options) => {
|
|
314
|
+
chatOptions = options;
|
|
315
|
+
return {
|
|
316
|
+
messages,
|
|
317
|
+
status: "streaming",
|
|
318
|
+
error: null,
|
|
319
|
+
sendMessage: vi.fn(),
|
|
320
|
+
setMessages: vi.fn(),
|
|
321
|
+
regenerate: vi.fn(),
|
|
322
|
+
addToolOutput: vi.fn(),
|
|
323
|
+
stop: vi.fn(),
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
await render(<ChatInterface id={conversation.id} />);
|
|
327
|
+
await act(async () => {
|
|
328
|
+
chatOptions.transport.prepareSendMessagesRequest({ messages });
|
|
329
|
+
chatOptions.onData({
|
|
330
|
+
type: "data-context-status",
|
|
331
|
+
data: { compacting: true },
|
|
332
|
+
});
|
|
333
|
+
chatOptions.onData({
|
|
334
|
+
type: "data-context-checkpoint",
|
|
335
|
+
data: { summary: "Do not trust this", throughMessageId: "u1" },
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
expect(container.textContent).toContain("Summarizing");
|
|
339
|
+
let next: any;
|
|
340
|
+
await act(async () => {
|
|
341
|
+
next = chatOptions.transport.prepareSendMessagesRequest({ messages });
|
|
342
|
+
});
|
|
343
|
+
expect(next.body.messages).toEqual(messages);
|
|
344
|
+
expect(next.body).not.toHaveProperty("contextSummary");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it.each([
|
|
348
|
+
{ interrupted: true, state: "output-available" },
|
|
349
|
+
{ interrupted: false, state: "input-available" },
|
|
350
|
+
{ interrupted: false, state: "input-streaming" },
|
|
351
|
+
])(
|
|
352
|
+
"restores interrupted or abandoned $state tools without a live callback",
|
|
353
|
+
async ({ interrupted, state }) => {
|
|
354
|
+
const setMessages = vi.fn();
|
|
355
|
+
mocks.useChat.mockReturnValue({
|
|
356
|
+
messages: [],
|
|
357
|
+
status: "ready",
|
|
358
|
+
error: null,
|
|
359
|
+
sendMessage: vi.fn(),
|
|
360
|
+
setMessages,
|
|
361
|
+
regenerate: vi.fn(),
|
|
362
|
+
addToolOutput: vi.fn(),
|
|
363
|
+
stop: vi.fn(),
|
|
364
|
+
});
|
|
365
|
+
const parts = [
|
|
366
|
+
{ type: "text", text: "Partial response" },
|
|
367
|
+
{
|
|
368
|
+
type: "tool-inspect",
|
|
369
|
+
toolCallId: "call",
|
|
370
|
+
state,
|
|
371
|
+
input: {},
|
|
372
|
+
...(state === "output-available" ? { output: "saved result" } : {}),
|
|
373
|
+
},
|
|
374
|
+
];
|
|
375
|
+
mocks.useConversation.mockReturnValue({
|
|
376
|
+
conversation: {
|
|
377
|
+
...conversation,
|
|
378
|
+
messages: [
|
|
379
|
+
{
|
|
380
|
+
id: "answer",
|
|
381
|
+
conversationId: conversation.id,
|
|
382
|
+
role: "assistant",
|
|
383
|
+
content: JSON.stringify(parts),
|
|
384
|
+
interrupted,
|
|
385
|
+
createdAt: conversation.createdAt,
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
isLoading: false,
|
|
390
|
+
error: null,
|
|
391
|
+
refetch: vi.fn(),
|
|
392
|
+
});
|
|
393
|
+
await render(<ChatInterface id={conversation.id} />);
|
|
394
|
+
expect(setMessages).toHaveBeenCalledWith([
|
|
395
|
+
{
|
|
396
|
+
id: "answer",
|
|
397
|
+
role: "assistant",
|
|
398
|
+
parts,
|
|
399
|
+
metadata: { interrupted: true },
|
|
400
|
+
},
|
|
401
|
+
]);
|
|
402
|
+
},
|
|
403
|
+
);
|
|
404
|
+
|
|
304
405
|
it("uses the resolved AI Chat endpoint for the browser stream transport", async () => {
|
|
305
406
|
const observeRuntime = vi.fn();
|
|
306
407
|
const stack = createClientStack({
|
|
@@ -56,6 +56,85 @@ describe("public context compaction", () => {
|
|
|
56
56
|
expect(summarize).toHaveBeenCalledOnce();
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
it("counts image content separately from inline bytes and keeps tool output as text", () => {
|
|
60
|
+
const inline = {
|
|
61
|
+
role: "user",
|
|
62
|
+
parts: [
|
|
63
|
+
{
|
|
64
|
+
type: "file",
|
|
65
|
+
mediaType: "image/png",
|
|
66
|
+
url: "data:image/png;base64," + "A".repeat(100_000),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
const remote = {
|
|
71
|
+
role: "user",
|
|
72
|
+
parts: [
|
|
73
|
+
{
|
|
74
|
+
type: "file",
|
|
75
|
+
mediaType: "image/png",
|
|
76
|
+
url: "https://example.org/image.png",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
expect(estimateContextTokens([inline])).toBe(
|
|
81
|
+
estimateContextTokens([remote]),
|
|
82
|
+
);
|
|
83
|
+
expect(estimateContextTokens([inline])).toBeGreaterThanOrEqual(4096);
|
|
84
|
+
const toolOutput = {
|
|
85
|
+
role: "tool",
|
|
86
|
+
content: [
|
|
87
|
+
{ type: "tool-result", output: { type: "json", value: inline } },
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
expect(estimateContextTokens([toolOutput])).toBeGreaterThan(30_000);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("sends older images as vision input when summarizing instead of serialized bytes", async () => {
|
|
94
|
+
const png =
|
|
95
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jr1sAAAAASUVORK5CYII=";
|
|
96
|
+
const original = [
|
|
97
|
+
message("u1", "user", "Remember the number in this image"),
|
|
98
|
+
];
|
|
99
|
+
original[0]!.parts.push({ type: "file", mediaType: "image/png", url: png });
|
|
100
|
+
const model = new MockLanguageModelV2({
|
|
101
|
+
doGenerate: async ({ prompt }) => ({
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: JSON.stringify(prompt).includes('"type":"file"')
|
|
106
|
+
? "The image shows verification number 917."
|
|
107
|
+
: "The user wants to remember the image number.",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
finishReason: "stop",
|
|
111
|
+
usage: { inputTokens: 100, outputTokens: 20, totalTokens: 120 },
|
|
112
|
+
warnings: [],
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
const before = structuredClone(original);
|
|
116
|
+
const summary = await createSummarizer(
|
|
117
|
+
model,
|
|
118
|
+
16_384,
|
|
119
|
+
)({ messages: original });
|
|
120
|
+
expect(summary).toContain("917");
|
|
121
|
+
expect(
|
|
122
|
+
model.doGenerateCalls.some((call) =>
|
|
123
|
+
call.prompt.some(
|
|
124
|
+
(message) =>
|
|
125
|
+
message.role === "user" &&
|
|
126
|
+
message.content.some(
|
|
127
|
+
(part) => part.type === "file" && part.mediaType === "image/png",
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
).toBe(true);
|
|
132
|
+
expect(JSON.stringify(model.doGenerateCalls)).not.toContain(
|
|
133
|
+
"data:image/png;base64",
|
|
134
|
+
);
|
|
135
|
+
expect(original).toEqual(before);
|
|
136
|
+
});
|
|
137
|
+
|
|
59
138
|
it("reuses a checkpoint only while all summarized messages are unchanged", () => {
|
|
60
139
|
const checkpoint = captureContextCheckpoint(history, "summary", "a1");
|
|
61
140
|
expect(contextRequest(history, checkpoint)).toEqual({
|
|
@@ -219,25 +298,6 @@ describe("public context compaction", () => {
|
|
|
219
298
|
body: JSON.stringify(body),
|
|
220
299
|
}),
|
|
221
300
|
);
|
|
222
|
-
const attachment = await send({
|
|
223
|
-
messages: [
|
|
224
|
-
{
|
|
225
|
-
id: "file",
|
|
226
|
-
role: "user",
|
|
227
|
-
parts: [
|
|
228
|
-
{
|
|
229
|
-
type: "file",
|
|
230
|
-
mediaType: "image/png",
|
|
231
|
-
url: "https://example.org/image.png",
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
});
|
|
237
|
-
expect(attachment.status).toBe(400);
|
|
238
|
-
expect(await attachment.text()).toContain("text-only");
|
|
239
|
-
expect(model.doGenerateCalls).toHaveLength(0);
|
|
240
|
-
expect(model.doStreamCalls).toHaveLength(0);
|
|
241
301
|
const response = await send({ messages: history });
|
|
242
302
|
expect(response.status).toBe(200);
|
|
243
303
|
const text = await response.text();
|