@btst/stack 4.4.0 → 4.5.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 +2 -2
- package/dist/packages/stack/src/plugins/ai-chat/api/compaction.cjs +64 -24
- package/dist/packages/stack/src/plugins/ai-chat/api/compaction.mjs +65 -25
- 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 +269 -93
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +270 -94
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +3 -5
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +3 -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-interface.cjs +26 -16
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +26 -16
- 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 +2 -2
- package/dist/plugins/blog/api/index.d.mts +2 -2
- package/dist/plugins/blog/api/index.d.ts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +2 -2
- package/dist/plugins/blog/client/index.d.mts +2 -2
- package/dist/plugins/blog/client/index.d.ts +2 -2
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- 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/shared/{stack.CjCSDfWy.d.mts → stack.94kN3pEK.d.mts} +5 -5
- package/dist/shared/{stack.C4zspX0T.d.ts → stack.B2EYZyh_.d.ts} +5 -2
- package/dist/shared/{stack.BGzgFx3R.d.cts → stack.BTStHVTy.d.cts} +5 -5
- package/dist/shared/{stack.DhZtP2JZ.d.mts → stack.BggTHKlo.d.mts} +37 -37
- package/dist/shared/{stack.DDQlE2Pm.d.cts → stack.Cei2T2gc.d.cts} +1 -0
- package/dist/shared/{stack.DNw6Hv5o.d.cts → stack.CtrnoQgH.d.cts} +37 -37
- package/dist/shared/{stack.tAUeRAtd.d.mts → stack.DZRZAzcx.d.mts} +1 -0
- package/dist/shared/{stack.Dpg_JjIo.d.ts → stack.DfrRYOIl.d.ts} +5 -5
- package/dist/shared/{stack.Ps3MDhTC.d.mts → stack.DivuAnNI.d.mts} +5 -2
- package/dist/shared/{stack.DlehgNZZ.d.ts → stack.byjorQi5.d.ts} +1 -0
- package/dist/shared/{stack.ymWsP4HZ.d.cts → stack.fF273jws.d.cts} +5 -2
- package/dist/shared/{stack.Clil75lW.d.ts → stack.lPKwd_v0.d.ts} +37 -37
- package/package.json +1 -1
- package/src/plugins/ai-chat/__tests__/authorization.test.ts +56 -30
- 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__/persisted-compaction.test.ts +819 -0
- package/src/plugins/ai-chat/api/compaction.ts +107 -40
- 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 +361 -99
- package/src/plugins/ai-chat/api/plugin.ts +5 -8
- package/src/plugins/ai-chat/api/serializers.ts +1 -0
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +43 -22
- 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.CGmh-Lpe.d.ts → stack.BuxUQqL8.d.ts} +12 -12
- package/dist/shared/{stack.Csi-4Mgz.d.cts → stack.CKc_Bais.d.cts} +7 -7
- package/dist/shared/{stack.Csi-4Mgz.d.mts → stack.CKc_Bais.d.mts} +7 -7
- package/dist/shared/{stack.Csi-4Mgz.d.ts → stack.CKc_Bais.d.ts} +7 -7
- package/dist/shared/{stack.zch70Loj.d.cts → stack.CX83YNuS.d.cts} +12 -12
- package/dist/shared/{stack.Doy2_wgl.d.mts → stack.Egr3RWhA.d.mts} +12 -12
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
convertToModelMessages,
|
|
3
|
+
consumeStream,
|
|
3
4
|
createUIMessageStream,
|
|
4
5
|
createUIMessageStreamResponse,
|
|
5
6
|
generateText,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
type UIMessage,
|
|
12
13
|
} from "ai";
|
|
13
14
|
|
|
14
|
-
/** Opt-in context compaction for
|
|
15
|
+
/** Opt-in context compaction for text-only conversations. */
|
|
15
16
|
export interface AiChatCompactionConfig {
|
|
16
17
|
/** Actual context window. Configure the model's output limit at or below 20% of this. */
|
|
17
18
|
contextWindowTokens: number;
|
|
@@ -210,7 +211,10 @@ export function createStepCompactor({
|
|
|
210
211
|
};
|
|
211
212
|
}
|
|
212
213
|
|
|
213
|
-
export
|
|
214
|
+
export type ChatCompletion = { message: UIMessage; interrupted: boolean };
|
|
215
|
+
|
|
216
|
+
/** Stream structured responses, optionally compacting their working context. */
|
|
217
|
+
export function chatResponse({
|
|
214
218
|
model,
|
|
215
219
|
messages,
|
|
216
220
|
summary,
|
|
@@ -219,17 +223,30 @@ export function compactingChatResponse({
|
|
|
219
223
|
config,
|
|
220
224
|
abortSignal,
|
|
221
225
|
onError,
|
|
226
|
+
originalMessages = messages,
|
|
227
|
+
onCheckpoint,
|
|
228
|
+
onFinish,
|
|
229
|
+
generateMessageId,
|
|
222
230
|
}: {
|
|
223
231
|
model: LanguageModel;
|
|
224
232
|
messages: UIMessage[];
|
|
225
233
|
summary?: string;
|
|
226
234
|
system?: string;
|
|
227
235
|
tools?: Record<string, Tool>;
|
|
228
|
-
config
|
|
236
|
+
config?: AiChatCompactionConfig;
|
|
229
237
|
abortSignal?: AbortSignal;
|
|
230
238
|
onError: (error: unknown) => Promise<void>;
|
|
239
|
+
originalMessages?: UIMessage[];
|
|
240
|
+
onCheckpoint?: (checkpoint: {
|
|
241
|
+
summary: string;
|
|
242
|
+
throughMessageId: string;
|
|
243
|
+
}) => Promise<void>;
|
|
244
|
+
onFinish?: (completion: ChatCompletion) => Promise<void>;
|
|
245
|
+
generateMessageId?: () => string;
|
|
231
246
|
}) {
|
|
232
|
-
const budget =
|
|
247
|
+
const budget = config
|
|
248
|
+
? Math.floor(config.contextWindowTokens * 0.8)
|
|
249
|
+
: Infinity;
|
|
233
250
|
const overhead = [
|
|
234
251
|
system,
|
|
235
252
|
Object.entries(tools ?? {}).map(([name, value]) => [
|
|
@@ -238,9 +255,34 @@ export function compactingChatResponse({
|
|
|
238
255
|
value.inputSchema,
|
|
239
256
|
]),
|
|
240
257
|
];
|
|
258
|
+
let failed = false;
|
|
259
|
+
let generationStarted = false;
|
|
260
|
+
const reportError = async (error: unknown) => {
|
|
261
|
+
if (failed) return;
|
|
262
|
+
failed = true;
|
|
263
|
+
await onError(error);
|
|
264
|
+
};
|
|
241
265
|
const stream = createUIMessageStream({
|
|
242
|
-
originalMessages
|
|
243
|
-
|
|
266
|
+
originalMessages,
|
|
267
|
+
generateId: generateMessageId,
|
|
268
|
+
onError: (error) => {
|
|
269
|
+
void reportError(error);
|
|
270
|
+
return streamErrorMessage(error);
|
|
271
|
+
},
|
|
272
|
+
onFinish: onFinish
|
|
273
|
+
? async ({ responseMessage, isAborted, finishReason }) => {
|
|
274
|
+
if (!generationStarted) return;
|
|
275
|
+
await onFinish({
|
|
276
|
+
message: responseMessage,
|
|
277
|
+
interrupted:
|
|
278
|
+
failed ||
|
|
279
|
+
isAborted ||
|
|
280
|
+
Boolean(abortSignal?.aborted) ||
|
|
281
|
+
!finishReason ||
|
|
282
|
+
finishReason === "error",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
: undefined,
|
|
244
286
|
execute: async ({ writer }) => {
|
|
245
287
|
const onCompacting = (active: boolean) =>
|
|
246
288
|
writer.write({
|
|
@@ -250,50 +292,65 @@ export function compactingChatResponse({
|
|
|
250
292
|
});
|
|
251
293
|
const summarize = createSummarizer(
|
|
252
294
|
model,
|
|
253
|
-
config
|
|
295
|
+
config?.contextWindowTokens ?? 4096,
|
|
254
296
|
abortSignal,
|
|
255
297
|
);
|
|
256
298
|
try {
|
|
257
|
-
const compacted =
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
299
|
+
const compacted = config
|
|
300
|
+
? await compactConversation({
|
|
301
|
+
messages,
|
|
302
|
+
summary,
|
|
303
|
+
budget,
|
|
304
|
+
overhead,
|
|
305
|
+
summarize: async (history) => {
|
|
306
|
+
onCompacting(true);
|
|
307
|
+
try {
|
|
308
|
+
return await summarize(history);
|
|
309
|
+
} finally {
|
|
310
|
+
onCompacting(false);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
})
|
|
314
|
+
: { messages, summary, throughMessageId: undefined };
|
|
315
|
+
if (compacted.throughMessageId && compacted.summary) {
|
|
316
|
+
const checkpoint = {
|
|
317
|
+
summary: compacted.summary,
|
|
318
|
+
throughMessageId: compacted.throughMessageId,
|
|
319
|
+
};
|
|
320
|
+
if (onCheckpoint) await onCheckpoint(checkpoint);
|
|
321
|
+
else
|
|
322
|
+
writer.write({
|
|
323
|
+
type: "data-context-checkpoint",
|
|
324
|
+
data: {
|
|
325
|
+
summary: compacted.summary,
|
|
326
|
+
throughMessageId: compacted.throughMessageId,
|
|
327
|
+
},
|
|
328
|
+
transient: true,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
abortSignal?.throwIfAborted();
|
|
332
|
+
generationStarted = true;
|
|
280
333
|
const result = streamText({
|
|
281
334
|
model,
|
|
282
335
|
messages: [
|
|
283
336
|
...(system ? [{ role: "system" as const, content: system }] : []),
|
|
284
337
|
...(compacted.summary ? [summaryMessage(compacted.summary)] : []),
|
|
285
|
-
...(await convertToModelMessages(compacted.messages
|
|
338
|
+
...(await convertToModelMessages(compacted.messages, {
|
|
339
|
+
ignoreIncompleteToolCalls: true,
|
|
340
|
+
})),
|
|
286
341
|
],
|
|
287
342
|
tools,
|
|
288
343
|
...(tools ? { stopWhen: stepCountIs(5) } : {}),
|
|
289
|
-
prepareStep:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
344
|
+
prepareStep: config
|
|
345
|
+
? createStepCompactor({
|
|
346
|
+
budget,
|
|
347
|
+
overhead,
|
|
348
|
+
summarize,
|
|
349
|
+
onCompacting,
|
|
350
|
+
})
|
|
351
|
+
: undefined,
|
|
295
352
|
abortSignal,
|
|
296
|
-
onError: ({ error }) =>
|
|
353
|
+
onError: ({ error }) => reportError(error),
|
|
297
354
|
});
|
|
298
355
|
writer.merge(
|
|
299
356
|
result.toUIMessageStream({
|
|
@@ -301,10 +358,20 @@ export function compactingChatResponse({
|
|
|
301
358
|
}),
|
|
302
359
|
);
|
|
303
360
|
} catch (error) {
|
|
304
|
-
await
|
|
361
|
+
await reportError(error);
|
|
305
362
|
throw error;
|
|
306
363
|
}
|
|
307
364
|
},
|
|
308
365
|
});
|
|
309
|
-
return createUIMessageStreamResponse({
|
|
366
|
+
return createUIMessageStreamResponse({
|
|
367
|
+
stream,
|
|
368
|
+
// Keep draining after a browser disconnect so abort/error completion can
|
|
369
|
+
// save the structured partial response. The request signal stops the model.
|
|
370
|
+
...(onFinish
|
|
371
|
+
? {
|
|
372
|
+
consumeSseStream: ({ stream }: { stream: ReadableStream<string> }) =>
|
|
373
|
+
consumeStream({ stream }),
|
|
374
|
+
}
|
|
375
|
+
: {}),
|
|
376
|
+
});
|
|
310
377
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { Message } from "../types";
|
|
4
|
+
|
|
5
|
+
const checkpointSchema = z.object({
|
|
6
|
+
version: z.literal(1),
|
|
7
|
+
summary: z.string().min(1).max(32_000),
|
|
8
|
+
throughMessageId: z.string(),
|
|
9
|
+
prefixHash: z.string(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function prefixHash(messages: readonly Message[]) {
|
|
13
|
+
return createHash("sha256")
|
|
14
|
+
.update(
|
|
15
|
+
JSON.stringify(
|
|
16
|
+
messages.map(({ id, role, content, interrupted }) => ({
|
|
17
|
+
id,
|
|
18
|
+
role,
|
|
19
|
+
content,
|
|
20
|
+
interrupted: Boolean(interrupted),
|
|
21
|
+
})),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
.digest("hex");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Checkpoints cover committed database messages, never browser-supplied history. */
|
|
28
|
+
export function createContextCheckpoint(
|
|
29
|
+
messages: readonly Message[],
|
|
30
|
+
summary: string,
|
|
31
|
+
throughMessageId: string,
|
|
32
|
+
): string {
|
|
33
|
+
const boundary = messages.findIndex(
|
|
34
|
+
(message) => message.id === throughMessageId,
|
|
35
|
+
);
|
|
36
|
+
if (boundary < 0) throw new Error("Checkpoint boundary is not persisted.");
|
|
37
|
+
return JSON.stringify(
|
|
38
|
+
checkpointSchema.parse({
|
|
39
|
+
version: 1,
|
|
40
|
+
summary,
|
|
41
|
+
throughMessageId,
|
|
42
|
+
prefixHash: prefixHash(messages.slice(0, boundary + 1)),
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readContextCheckpoint(
|
|
48
|
+
value: string | null | undefined,
|
|
49
|
+
messages: readonly Message[],
|
|
50
|
+
) {
|
|
51
|
+
if (!value) return undefined;
|
|
52
|
+
try {
|
|
53
|
+
const checkpoint = checkpointSchema.parse(JSON.parse(value));
|
|
54
|
+
const boundary = messages.findIndex(
|
|
55
|
+
(message) => message.id === checkpoint.throughMessageId,
|
|
56
|
+
);
|
|
57
|
+
if (
|
|
58
|
+
boundary < 0 ||
|
|
59
|
+
prefixHash(messages.slice(0, boundary + 1)) !== checkpoint.prefixHash
|
|
60
|
+
)
|
|
61
|
+
return undefined;
|
|
62
|
+
return { ...checkpoint, boundary };
|
|
63
|
+
} catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -28,6 +28,7 @@ export async function getAllConversations(
|
|
|
28
28
|
|
|
29
29
|
return adapter.findMany<Conversation>({
|
|
30
30
|
model: "conversation",
|
|
31
|
+
select: ["id", "userId", "title", "createdAt", "updatedAt"],
|
|
31
32
|
where: whereConditions.length > 0 ? whereConditions : undefined,
|
|
32
33
|
sortBy: { field: "updatedAt", direction: "desc" },
|
|
33
34
|
});
|
|
@@ -47,6 +48,7 @@ export async function getConversationById(
|
|
|
47
48
|
): Promise<(Conversation & { messages: Message[] }) | null> {
|
|
48
49
|
const conversations = await adapter.findMany<ConversationWithMessages>({
|
|
49
50
|
model: "conversation",
|
|
51
|
+
select: ["id", "userId", "title", "createdAt", "updatedAt"],
|
|
50
52
|
where: [{ field: "id", value: id, operator: "eq" as const }],
|
|
51
53
|
limit: 1,
|
|
52
54
|
join: {
|