@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.
Files changed (103) hide show
  1. package/README.md +2 -2
  2. package/dist/packages/stack/src/plugins/ai-chat/api/compaction.cjs +166 -39
  3. package/dist/packages/stack/src/plugins/ai-chat/api/compaction.mjs +167 -40
  4. package/dist/packages/stack/src/plugins/ai-chat/api/context-checkpoint.cjs +54 -0
  5. package/dist/packages/stack/src/plugins/ai-chat/api/context-checkpoint.mjs +51 -0
  6. package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +2 -0
  7. package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +2 -0
  8. package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +275 -95
  9. package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +276 -96
  10. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +8 -5
  11. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +8 -5
  12. package/dist/packages/stack/src/plugins/ai-chat/api/serializers.cjs +1 -0
  13. package/dist/packages/stack/src/plugins/ai-chat/api/serializers.mjs +1 -0
  14. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +3 -2
  15. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +3 -2
  16. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +26 -17
  17. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +26 -17
  18. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.cjs +22 -2
  19. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.mjs +22 -2
  20. package/dist/packages/stack/src/plugins/ai-chat/db.cjs +8 -0
  21. package/dist/packages/stack/src/plugins/ai-chat/db.mjs +8 -0
  22. package/dist/packages/stack/src/plugins/ai-chat/history.cjs +47 -0
  23. package/dist/packages/stack/src/plugins/ai-chat/history.mjs +45 -0
  24. package/dist/plugins/ai-chat/api/index.d.cts +2 -2
  25. package/dist/plugins/ai-chat/api/index.d.mts +2 -2
  26. package/dist/plugins/ai-chat/api/index.d.ts +2 -2
  27. package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
  28. package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
  29. package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
  30. package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
  31. package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
  32. package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
  33. package/dist/plugins/ai-chat/client/index.d.cts +3 -3
  34. package/dist/plugins/ai-chat/client/index.d.mts +3 -3
  35. package/dist/plugins/ai-chat/client/index.d.ts +3 -3
  36. package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
  37. package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
  38. package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
  39. package/dist/plugins/blog/api/index.d.cts +1 -1
  40. package/dist/plugins/blog/api/index.d.mts +1 -1
  41. package/dist/plugins/blog/api/index.d.ts +1 -1
  42. package/dist/plugins/blog/query-keys.d.cts +1 -1
  43. package/dist/plugins/blog/query-keys.d.mts +1 -1
  44. package/dist/plugins/blog/query-keys.d.ts +1 -1
  45. package/dist/plugins/cms/api/index.d.cts +1 -1
  46. package/dist/plugins/cms/api/index.d.mts +1 -1
  47. package/dist/plugins/cms/api/index.d.ts +1 -1
  48. package/dist/plugins/cms/query-keys.d.cts +1 -1
  49. package/dist/plugins/cms/query-keys.d.mts +1 -1
  50. package/dist/plugins/cms/query-keys.d.ts +1 -1
  51. package/dist/plugins/form-builder/api/index.d.cts +1 -1
  52. package/dist/plugins/form-builder/api/index.d.mts +1 -1
  53. package/dist/plugins/form-builder/api/index.d.ts +1 -1
  54. package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
  55. package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
  56. package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
  57. package/dist/plugins/form-builder/query-keys.d.cts +1 -1
  58. package/dist/plugins/form-builder/query-keys.d.mts +1 -1
  59. package/dist/plugins/form-builder/query-keys.d.ts +1 -1
  60. package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
  61. package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
  62. package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
  63. package/dist/plugins/ui-builder/client/index.d.cts +1 -1
  64. package/dist/plugins/ui-builder/client/index.d.mts +1 -1
  65. package/dist/plugins/ui-builder/client/index.d.ts +1 -1
  66. package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
  67. package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
  68. package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
  69. package/dist/shared/{stack.DDQlE2Pm.d.cts → stack.Cei2T2gc.d.cts} +1 -0
  70. package/dist/shared/{stack.C4zspX0T.d.ts → stack.DDQTbADz.d.ts} +7 -2
  71. package/dist/shared/{stack.tAUeRAtd.d.mts → stack.DZRZAzcx.d.mts} +1 -0
  72. package/dist/shared/{stack.ymWsP4HZ.d.cts → stack.IY-ngxuw.d.cts} +7 -2
  73. package/dist/shared/{stack.DlehgNZZ.d.ts → stack.byjorQi5.d.ts} +1 -0
  74. package/dist/shared/{stack.Ps3MDhTC.d.mts → stack.mU9iMF2j.d.mts} +7 -2
  75. package/package.json +1 -1
  76. package/src/plugins/ai-chat/__tests__/authorization.test.ts +56 -30
  77. package/src/plugins/ai-chat/__tests__/chat-input-authorization.test.tsx +42 -0
  78. package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +25 -0
  79. package/src/plugins/ai-chat/__tests__/client-sweep.test.tsx +101 -0
  80. package/src/plugins/ai-chat/__tests__/compaction.test.ts +79 -19
  81. package/src/plugins/ai-chat/__tests__/persisted-compaction.test.ts +948 -0
  82. package/src/plugins/ai-chat/api/compaction.ts +243 -52
  83. package/src/plugins/ai-chat/api/context-checkpoint.ts +66 -0
  84. package/src/plugins/ai-chat/api/getters.ts +2 -0
  85. package/src/plugins/ai-chat/api/operations.ts +377 -101
  86. package/src/plugins/ai-chat/api/plugin.ts +14 -8
  87. package/src/plugins/ai-chat/api/serializers.ts +1 -0
  88. package/src/plugins/ai-chat/client/components/chat-input.tsx +3 -3
  89. package/src/plugins/ai-chat/client/components/chat-interface.tsx +43 -23
  90. package/src/plugins/ai-chat/client/components/chat-message.tsx +32 -2
  91. package/src/plugins/ai-chat/client/localization/index.ts +1 -0
  92. package/src/plugins/ai-chat/db.ts +8 -0
  93. package/src/plugins/ai-chat/history.ts +68 -0
  94. package/src/plugins/ai-chat/types.ts +2 -0
  95. package/dist/shared/{stack.B8PG21zX.d.mts → stack.BLYaUcQa.d.mts} +88 -88
  96. package/dist/shared/{stack.DNw6Hv5o.d.cts → stack.BOZRaVpu.d.cts} +2 -2
  97. package/dist/shared/{stack.D6Pg-ufX.d.cts → stack.Bf6BGZNk.d.cts} +88 -88
  98. package/dist/shared/{stack.CGmh-Lpe.d.ts → stack.BuxUQqL8.d.ts} +12 -12
  99. package/dist/shared/{stack.zch70Loj.d.cts → stack.CX83YNuS.d.cts} +12 -12
  100. package/dist/shared/{stack.CyalJ0j6.d.ts → stack.CyltNNZZ.d.ts} +88 -88
  101. package/dist/shared/{stack.Clil75lW.d.ts → stack.DlBoNqYu.d.ts} +2 -2
  102. package/dist/shared/{stack.DhZtP2JZ.d.mts → stack.Dvy7FmaA.d.mts} +2 -2
  103. 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();