@btst/stack 4.3.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 +289 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/compaction.mjs +281 -0
- 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 +285 -89
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +285 -89
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +6 -3
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +6 -3
- 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 +62 -17
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +62 -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/client/context-checkpoint.cjs +30 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/context-checkpoint.mjs +27 -0
- 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/packages/stack/src/plugins/ai-chat/schemas.cjs +2 -0
- package/dist/packages/stack/src/plugins/ai-chat/schemas.mjs +2 -0
- package/dist/packages/ui/src/components/markdown-content.cjs +9 -9
- package/dist/packages/ui/src/components/markdown-content.mjs +9 -9
- 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/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.D2y4gl03.d.mts → stack.94kN3pEK.d.mts} +5 -5
- package/dist/shared/{stack.DBH1Bjko.d.ts → stack.B2EYZyh_.d.ts} +16 -2
- package/dist/shared/{stack.rmWRtSjs.d.cts → stack.BTStHVTy.d.cts} +5 -5
- package/dist/shared/{stack.CZ1h_T4k.d.mts → stack.BggTHKlo.d.mts} +90 -90
- package/dist/shared/{stack.DzJgoNqJ.d.cts → stack.Cei2T2gc.d.cts} +2 -0
- package/dist/shared/{stack.DiYg7Jw1.d.cts → stack.CtrnoQgH.d.cts} +90 -90
- package/dist/shared/{stack.CHrtGKLz.d.mts → stack.DZRZAzcx.d.mts} +2 -0
- package/dist/shared/{stack.CnDMBj4z.d.ts → stack.DfrRYOIl.d.ts} +5 -5
- package/dist/shared/{stack.B7Mj74BQ.d.mts → stack.DivuAnNI.d.mts} +16 -2
- package/dist/shared/{stack.qMp2VzNw.d.ts → stack.byjorQi5.d.ts} +2 -0
- package/dist/shared/{stack.Dgt3MCtW.d.cts → stack.fF273jws.d.cts} +16 -2
- package/dist/shared/{stack.DQpyA3Cq.d.ts → stack.lPKwd_v0.d.ts} +90 -90
- package/package.json +1 -1
- package/src/__tests__/markdown-link-navigation.test.tsx +29 -0
- 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__/compaction.test.ts +272 -0
- package/src/plugins/ai-chat/__tests__/persisted-compaction.test.ts +819 -0
- package/src/plugins/ai-chat/api/compaction.ts +377 -0
- 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 +380 -93
- package/src/plugins/ai-chat/api/plugin.ts +15 -4
- package/src/plugins/ai-chat/api/serializers.ts +1 -0
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +97 -21
- package/src/plugins/ai-chat/client/components/chat-message.tsx +32 -2
- package/src/plugins/ai-chat/client/context-checkpoint.ts +47 -0
- package/src/plugins/ai-chat/client/localization/index.ts +2 -0
- package/src/plugins/ai-chat/db.ts +8 -0
- package/src/plugins/ai-chat/history.ts +68 -0
- package/src/plugins/ai-chat/schemas.ts +2 -0
- package/src/plugins/ai-chat/types.ts +2 -0
- package/dist/shared/{stack.lsbdKRj7.d.mts → stack.B8PG21zX.d.mts} +16 -16
- package/dist/shared/{stack.CGmh-Lpe.d.ts → stack.BuxUQqL8.d.ts} +12 -12
- package/dist/shared/{stack.DtZgEX84.d.cts → stack.CKc_Bais.d.cts} +6 -6
- package/dist/shared/{stack.DtZgEX84.d.mts → stack.CKc_Bais.d.mts} +6 -6
- package/dist/shared/{stack.DtZgEX84.d.ts → stack.CKc_Bais.d.ts} +6 -6
- package/dist/shared/{stack.zch70Loj.d.cts → stack.CX83YNuS.d.cts} +12 -12
- package/dist/shared/{stack.BMlTZnZp.d.ts → stack.CyalJ0j6.d.ts} +16 -16
- package/dist/shared/{stack.BV0uNV4f.d.cts → stack.D6Pg-ufX.d.cts} +16 -16
- package/dist/shared/{stack.Doy2_wgl.d.mts → stack.Egr3RWhA.d.mts} +12 -12
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { persistedHistory } from "../../history";
|
|
4
|
+
|
|
3
5
|
import { useChat } from "@ai-sdk/react";
|
|
4
6
|
import {
|
|
5
7
|
useEffect,
|
|
@@ -46,6 +48,11 @@ import {
|
|
|
46
48
|
import { usePageAIContext } from "../context/page-ai-context";
|
|
47
49
|
import { useChatAnalytics } from "../context/chat-analytics";
|
|
48
50
|
import { navigateAiChatCrossOrigin } from "../navigation";
|
|
51
|
+
import {
|
|
52
|
+
captureContextCheckpoint,
|
|
53
|
+
contextRequest,
|
|
54
|
+
type ContextCheckpoint,
|
|
55
|
+
} from "../context-checkpoint";
|
|
49
56
|
|
|
50
57
|
interface ChatInterfaceProps {
|
|
51
58
|
initialMessages?: UIMessage[];
|
|
@@ -89,30 +96,28 @@ function reconcilePersistedMessageIds(
|
|
|
89
96
|
return messages.map((message) => ({
|
|
90
97
|
...message,
|
|
91
98
|
id: persistedIds.get(message) ?? message.id,
|
|
99
|
+
...(persistedMessages.some(
|
|
100
|
+
(saved) => saved.id === message.id && saved.interrupted,
|
|
101
|
+
)
|
|
102
|
+
? { metadata: { ...(message.metadata as object), interrupted: true } }
|
|
103
|
+
: {}),
|
|
92
104
|
}));
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
function persistedMessagesToUiMessages(
|
|
96
108
|
messages: readonly SerializedMessage[],
|
|
97
109
|
): UIMessage[] {
|
|
98
|
-
return messages
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
id: message.id,
|
|
112
|
-
role: message.role as "user" | "assistant" | "system",
|
|
113
|
-
parts,
|
|
114
|
-
};
|
|
115
|
-
});
|
|
110
|
+
return persistedHistory(messages).map((message) => {
|
|
111
|
+
// Browser tool callbacks do not resume when saved history is restored.
|
|
112
|
+
const abandoned = message.parts.some(
|
|
113
|
+
(part) =>
|
|
114
|
+
"toolCallId" in part &&
|
|
115
|
+
(part.state === "input-streaming" || part.state === "input-available"),
|
|
116
|
+
);
|
|
117
|
+
return abandoned
|
|
118
|
+
? { ...message, metadata: { interrupted: true } }
|
|
119
|
+
: message;
|
|
120
|
+
});
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
function ChatActionCheck({
|
|
@@ -314,6 +319,9 @@ export function ChatInterface({
|
|
|
314
319
|
Array<{ generation: number; identityPartitionKey: string }>
|
|
315
320
|
>([]);
|
|
316
321
|
const latestMessages = useRef<UIMessage[]>([]);
|
|
322
|
+
const contextCheckpoint = useRef<ContextCheckpoint | undefined>(undefined);
|
|
323
|
+
const submittedMessages = useRef<UIMessage[]>([]);
|
|
324
|
+
const [isCompacting, setIsCompacting] = useState(false);
|
|
317
325
|
|
|
318
326
|
const conversationsListQueryKey = useMemo(() => {
|
|
319
327
|
// In public mode, we don't need conversation queries
|
|
@@ -465,6 +473,7 @@ export function ChatInterface({
|
|
|
465
473
|
pendingStreamRequests.current.push(request);
|
|
466
474
|
activeStreamPartitionKey.current = request.identityPartitionKey;
|
|
467
475
|
}
|
|
476
|
+
setIsCompacting(false);
|
|
468
477
|
const currentPageContext = pageAIContextRef.current;
|
|
469
478
|
|
|
470
479
|
// Build page context fields to include in every request
|
|
@@ -483,20 +492,26 @@ export function ChatInterface({
|
|
|
483
492
|
if (newUserMessage) {
|
|
484
493
|
messagesToSend.push(newUserMessage);
|
|
485
494
|
}
|
|
495
|
+
submittedMessages.current = messagesToSend;
|
|
486
496
|
// Clear the ref after use
|
|
487
497
|
editMessagesRef.current = null;
|
|
488
498
|
return {
|
|
489
499
|
body: {
|
|
490
|
-
|
|
500
|
+
...(isPublicMode
|
|
501
|
+
? contextRequest(messagesToSend, contextCheckpoint.current)
|
|
502
|
+
: { messages: messagesToSend }),
|
|
491
503
|
conversationId: conversationIdRef.current,
|
|
492
504
|
...pageContextBody,
|
|
493
505
|
},
|
|
494
506
|
};
|
|
495
507
|
}
|
|
496
|
-
|
|
508
|
+
submittedMessages.current = hookMessages;
|
|
509
|
+
// Retain the full UI history; compact only the transport payload.
|
|
497
510
|
return {
|
|
498
511
|
body: {
|
|
499
|
-
|
|
512
|
+
...(isPublicMode
|
|
513
|
+
? contextRequest(hookMessages, contextCheckpoint.current)
|
|
514
|
+
: { messages: hookMessages }),
|
|
500
515
|
conversationId: conversationIdRef.current,
|
|
501
516
|
...pageContextBody,
|
|
502
517
|
},
|
|
@@ -550,6 +565,33 @@ export function ChatInterface({
|
|
|
550
565
|
} = useChat({
|
|
551
566
|
id: `${chatInstanceId}:${isPublicMode ? "public" : identitySessionVersion}`,
|
|
552
567
|
transport,
|
|
568
|
+
onData: ({ type, data }) => {
|
|
569
|
+
if (!mounted.current || !data || typeof data !== "object") return;
|
|
570
|
+
if (
|
|
571
|
+
!isPublicMode &&
|
|
572
|
+
(identitySessionVersion !== identitySessionGeneration.current ||
|
|
573
|
+
activeStreamPartitionKey.current !==
|
|
574
|
+
latestIdentityPartitionKey.current)
|
|
575
|
+
)
|
|
576
|
+
return;
|
|
577
|
+
if (type === "data-context-status" && "compacting" in data) {
|
|
578
|
+
setIsCompacting(data.compacting === true);
|
|
579
|
+
}
|
|
580
|
+
if (
|
|
581
|
+
isPublicMode &&
|
|
582
|
+
type === "data-context-checkpoint" &&
|
|
583
|
+
"summary" in data &&
|
|
584
|
+
"throughMessageId" in data &&
|
|
585
|
+
typeof data.summary === "string" &&
|
|
586
|
+
typeof data.throughMessageId === "string"
|
|
587
|
+
) {
|
|
588
|
+
contextCheckpoint.current = captureContextCheckpoint(
|
|
589
|
+
submittedMessages.current,
|
|
590
|
+
data.summary,
|
|
591
|
+
data.throughMessageId,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
},
|
|
553
595
|
// Automatically resubmit after all client-side tool results are provided
|
|
554
596
|
sendAutomaticallyWhen: (options) =>
|
|
555
597
|
mounted.current &&
|
|
@@ -630,6 +672,7 @@ export function ChatInterface({
|
|
|
630
672
|
}
|
|
631
673
|
},
|
|
632
674
|
onError: (err) => {
|
|
675
|
+
setIsCompacting(false);
|
|
633
676
|
if (!mounted.current) return;
|
|
634
677
|
console.error("useChat onError:", err);
|
|
635
678
|
if (
|
|
@@ -653,6 +696,7 @@ export function ChatInterface({
|
|
|
653
696
|
// the provider or response stream failed.
|
|
654
697
|
},
|
|
655
698
|
onFinish: async (completion) => {
|
|
699
|
+
setIsCompacting(false);
|
|
656
700
|
if (
|
|
657
701
|
!mounted.current ||
|
|
658
702
|
(!isPublicMode &&
|
|
@@ -684,6 +728,25 @@ export function ChatInterface({
|
|
|
684
728
|
).length,
|
|
685
729
|
});
|
|
686
730
|
}
|
|
731
|
+
if (
|
|
732
|
+
completion?.isAbort ||
|
|
733
|
+
completion?.isError ||
|
|
734
|
+
completion?.isDisconnect
|
|
735
|
+
) {
|
|
736
|
+
setMessages((current) =>
|
|
737
|
+
current.map((message) =>
|
|
738
|
+
message.id === completion.message.id
|
|
739
|
+
? {
|
|
740
|
+
...message,
|
|
741
|
+
metadata: {
|
|
742
|
+
...(message.metadata as object),
|
|
743
|
+
interrupted: true,
|
|
744
|
+
},
|
|
745
|
+
}
|
|
746
|
+
: message,
|
|
747
|
+
),
|
|
748
|
+
);
|
|
749
|
+
}
|
|
687
750
|
// In public mode, skip all persistence-related operations
|
|
688
751
|
if (isPublicMode) {
|
|
689
752
|
activeStreamPartitionKey.current = undefined;
|
|
@@ -1328,7 +1391,20 @@ export function ChatInterface({
|
|
|
1328
1391
|
/>
|
|
1329
1392
|
))
|
|
1330
1393
|
)}
|
|
1394
|
+
{isLoading && isCompacting && (
|
|
1395
|
+
<div
|
|
1396
|
+
role="status"
|
|
1397
|
+
className="text-muted-foreground text-sm py-4"
|
|
1398
|
+
>
|
|
1399
|
+
{tr(
|
|
1400
|
+
"CHAT_COMPACTING",
|
|
1401
|
+
"aiChat.chat.compacting",
|
|
1402
|
+
"Summarizing earlier conversation…",
|
|
1403
|
+
)}
|
|
1404
|
+
</div>
|
|
1405
|
+
)}
|
|
1331
1406
|
{isLoading &&
|
|
1407
|
+
!isCompacting &&
|
|
1332
1408
|
messages[messages.length - 1]?.role !== "assistant" && (
|
|
1333
1409
|
<div className="flex items-center gap-2 text-muted-foreground text-sm py-4">
|
|
1334
1410
|
<div className="animate-pulse">
|
|
@@ -132,6 +132,12 @@ export function ChatMessage({
|
|
|
132
132
|
isRetrying = false,
|
|
133
133
|
}: ChatMessageProps) {
|
|
134
134
|
const isUser = message.role === "user";
|
|
135
|
+
const interrupted = Boolean(
|
|
136
|
+
message.metadata &&
|
|
137
|
+
typeof message.metadata === "object" &&
|
|
138
|
+
"interrupted" in message.metadata &&
|
|
139
|
+
message.metadata.interrupted,
|
|
140
|
+
);
|
|
135
141
|
|
|
136
142
|
const {
|
|
137
143
|
localization: customLocalization,
|
|
@@ -479,10 +485,22 @@ export function ChatMessage({
|
|
|
479
485
|
{toolParts.map((part: any) => {
|
|
480
486
|
const toolName = getToolName(part);
|
|
481
487
|
const toolCallId = part.toolCallId;
|
|
482
|
-
const
|
|
488
|
+
const unfinished =
|
|
489
|
+
interrupted &&
|
|
490
|
+
(part.state === "input-streaming" ||
|
|
491
|
+
part.state === "input-available");
|
|
492
|
+
const state = (
|
|
493
|
+
unfinished ? "output-error" : part.state
|
|
494
|
+
) as ToolCallState;
|
|
483
495
|
const input = part.input;
|
|
484
496
|
const output = part.output;
|
|
485
|
-
const errorText =
|
|
497
|
+
const errorText = unfinished
|
|
498
|
+
? tr(
|
|
499
|
+
"CHAT_INTERRUPTED",
|
|
500
|
+
"aiChat.interrupted",
|
|
501
|
+
"Response interrupted",
|
|
502
|
+
)
|
|
503
|
+
: part.errorText;
|
|
486
504
|
const isLoading =
|
|
487
505
|
state === "input-streaming" ||
|
|
488
506
|
state === "input-available";
|
|
@@ -513,6 +531,18 @@ export function ChatMessage({
|
|
|
513
531
|
</div>
|
|
514
532
|
)}
|
|
515
533
|
{/* Text content */}
|
|
534
|
+
{interrupted && !isStreaming && (
|
|
535
|
+
<p
|
|
536
|
+
className="text-xs text-muted-foreground"
|
|
537
|
+
data-testid="chat-interrupted"
|
|
538
|
+
>
|
|
539
|
+
{tr(
|
|
540
|
+
"CHAT_INTERRUPTED",
|
|
541
|
+
"aiChat.interrupted",
|
|
542
|
+
"Response interrupted",
|
|
543
|
+
)}
|
|
544
|
+
</p>
|
|
545
|
+
)}
|
|
516
546
|
{displayContent ? (
|
|
517
547
|
<MarkdownContent
|
|
518
548
|
markdown={displayContent}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
|
|
3
|
+
/** Browser-only snapshot; never replaces or mutates the visible transcript. */
|
|
4
|
+
export interface ContextCheckpoint {
|
|
5
|
+
summary: string;
|
|
6
|
+
throughMessageId: string;
|
|
7
|
+
prefix: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function captureContextCheckpoint(
|
|
11
|
+
messages: UIMessage[],
|
|
12
|
+
summary: string,
|
|
13
|
+
throughMessageId: string,
|
|
14
|
+
): ContextCheckpoint | undefined {
|
|
15
|
+
const index = messages.findIndex(
|
|
16
|
+
(message) => message.id === throughMessageId,
|
|
17
|
+
);
|
|
18
|
+
if (index < 0) return undefined;
|
|
19
|
+
return {
|
|
20
|
+
summary,
|
|
21
|
+
throughMessageId,
|
|
22
|
+
prefix: JSON.stringify(messages.slice(0, index + 1)),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function contextRequest(
|
|
27
|
+
messages: UIMessage[],
|
|
28
|
+
checkpoint?: ContextCheckpoint,
|
|
29
|
+
) {
|
|
30
|
+
if (checkpoint) {
|
|
31
|
+
const index = messages.findIndex(
|
|
32
|
+
(message) => message.id === checkpoint.throughMessageId,
|
|
33
|
+
);
|
|
34
|
+
if (
|
|
35
|
+
index >= 0 &&
|
|
36
|
+
JSON.stringify(messages.slice(0, index + 1)) === checkpoint.prefix
|
|
37
|
+
) {
|
|
38
|
+
return {
|
|
39
|
+
messages: messages.slice(index + 1),
|
|
40
|
+
contextSummary: checkpoint.summary,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Editing or retrying a summarized turn invalidates the snapshot. Resend
|
|
45
|
+
// the original transcript so the server builds a fresh summary if needed.
|
|
46
|
+
return { messages };
|
|
47
|
+
}
|
|
@@ -21,6 +21,10 @@ export const aiChatSchema = createDbPlugin("aiChat", {
|
|
|
21
21
|
type: "string",
|
|
22
22
|
required: true,
|
|
23
23
|
},
|
|
24
|
+
contextCheckpoint: {
|
|
25
|
+
type: "string",
|
|
26
|
+
required: false,
|
|
27
|
+
},
|
|
24
28
|
createdAt: {
|
|
25
29
|
type: "date",
|
|
26
30
|
defaultValue: () => new Date(),
|
|
@@ -56,6 +60,10 @@ export const aiChatSchema = createDbPlugin("aiChat", {
|
|
|
56
60
|
type: "string",
|
|
57
61
|
required: true,
|
|
58
62
|
},
|
|
63
|
+
interrupted: {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
required: false,
|
|
66
|
+
},
|
|
59
67
|
createdAt: {
|
|
60
68
|
type: "date",
|
|
61
69
|
defaultValue: () => new Date(),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
import type { Message } from "./types";
|
|
3
|
+
|
|
4
|
+
function isMessagePart(value: unknown): boolean {
|
|
5
|
+
if (!value || typeof value !== "object" || !("type" in value)) return false;
|
|
6
|
+
const part = value as Record<string, unknown>;
|
|
7
|
+
if (typeof part.type !== "string") return false;
|
|
8
|
+
switch (part.type) {
|
|
9
|
+
case "text":
|
|
10
|
+
case "reasoning":
|
|
11
|
+
return typeof part.text === "string";
|
|
12
|
+
case "file":
|
|
13
|
+
return typeof part.url === "string" && typeof part.mediaType === "string";
|
|
14
|
+
case "source-url":
|
|
15
|
+
return typeof part.sourceId === "string" && typeof part.url === "string";
|
|
16
|
+
case "source-document":
|
|
17
|
+
return (
|
|
18
|
+
typeof part.sourceId === "string" &&
|
|
19
|
+
typeof part.mediaType === "string" &&
|
|
20
|
+
typeof part.title === "string"
|
|
21
|
+
);
|
|
22
|
+
case "step-start":
|
|
23
|
+
return true;
|
|
24
|
+
default:
|
|
25
|
+
if (part.type.startsWith("data-")) return "data" in part;
|
|
26
|
+
return (
|
|
27
|
+
(part.type.startsWith("tool-") ||
|
|
28
|
+
(part.type === "dynamic-tool" &&
|
|
29
|
+
typeof part.toolName === "string")) &&
|
|
30
|
+
typeof part.toolCallId === "string" &&
|
|
31
|
+
[
|
|
32
|
+
"input-streaming",
|
|
33
|
+
"input-available",
|
|
34
|
+
"output-available",
|
|
35
|
+
"output-error",
|
|
36
|
+
].includes(String(part.state))
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function persistedHistory(
|
|
42
|
+
messages: readonly Pick<Message, "id" | "role" | "content" | "interrupted">[],
|
|
43
|
+
): UIMessage[] {
|
|
44
|
+
return messages
|
|
45
|
+
.filter((message) => message.role !== "data")
|
|
46
|
+
.map((message) => {
|
|
47
|
+
let parts: UIMessage["parts"];
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(message.content);
|
|
50
|
+
// The old completion writer saved [] for empty assistant replies.
|
|
51
|
+
// Empty arrays in legacy user text must remain literal text.
|
|
52
|
+
parts =
|
|
53
|
+
Array.isArray(parsed) &&
|
|
54
|
+
(parsed.length > 0 || message.role === "assistant") &&
|
|
55
|
+
parsed.every(isMessagePart)
|
|
56
|
+
? parsed
|
|
57
|
+
: [{ type: "text", text: message.content }];
|
|
58
|
+
} catch {
|
|
59
|
+
parts = [{ type: "text", text: message.content }];
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
id: message.id,
|
|
63
|
+
role: message.role as UIMessage["role"],
|
|
64
|
+
parts,
|
|
65
|
+
...(message.interrupted ? { metadata: { interrupted: true } } : {}),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -36,6 +36,8 @@ export const chatRequestSchema = z.object({
|
|
|
36
36
|
]),
|
|
37
37
|
),
|
|
38
38
|
conversationId: z.string().optional(),
|
|
39
|
+
/** Untrusted continuation summary for opt-in public context compaction. */
|
|
40
|
+
contextSummary: z.string().max(32_000).optional(),
|
|
39
41
|
model: z.string().optional(),
|
|
40
42
|
/**
|
|
41
43
|
* Description of the current page context, injected into the AI system prompt.
|
|
@@ -95,8 +95,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
95
95
|
readonly id: string;
|
|
96
96
|
readonly slug: string;
|
|
97
97
|
readonly contentTypeId: string;
|
|
98
|
-
readonly data: string;
|
|
99
98
|
readonly authorId?: string | undefined;
|
|
99
|
+
readonly data: string;
|
|
100
100
|
}[];
|
|
101
101
|
readonly total: number;
|
|
102
102
|
}>;
|
|
@@ -128,8 +128,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
128
128
|
readonly id: string;
|
|
129
129
|
readonly slug: string;
|
|
130
130
|
readonly contentTypeId: string;
|
|
131
|
-
readonly data: string;
|
|
132
131
|
readonly authorId?: string | undefined;
|
|
132
|
+
readonly data: string;
|
|
133
133
|
}>;
|
|
134
134
|
readonly createContentItem: better_call.StrictEndpoint<"/content/:typeSlug", {
|
|
135
135
|
params: z.ZodObject<{
|
|
@@ -165,8 +165,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
165
165
|
readonly id: string;
|
|
166
166
|
readonly slug: string;
|
|
167
167
|
readonly contentTypeId: string;
|
|
168
|
-
readonly data: string;
|
|
169
168
|
readonly authorId?: string | undefined;
|
|
169
|
+
readonly data: string;
|
|
170
170
|
}>;
|
|
171
171
|
readonly updateContentItem: better_call.StrictEndpoint<"/content/:typeSlug/:id", {
|
|
172
172
|
params: z.ZodObject<{
|
|
@@ -203,8 +203,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
203
203
|
readonly id: string;
|
|
204
204
|
readonly slug: string;
|
|
205
205
|
readonly contentTypeId: string;
|
|
206
|
-
readonly data: string;
|
|
207
206
|
readonly authorId?: string | undefined;
|
|
207
|
+
readonly data: string;
|
|
208
208
|
}>;
|
|
209
209
|
readonly deleteContentItem: better_call.StrictEndpoint<"/content/:typeSlug/:id", {
|
|
210
210
|
params: z.ZodObject<{
|
|
@@ -248,8 +248,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
248
248
|
readonly id: string;
|
|
249
249
|
readonly slug: string;
|
|
250
250
|
readonly contentTypeId: string;
|
|
251
|
-
readonly data: string;
|
|
252
251
|
readonly authorId?: string | undefined;
|
|
252
|
+
readonly data: string;
|
|
253
253
|
}[];
|
|
254
254
|
};
|
|
255
255
|
readonly contentType?: {
|
|
@@ -271,8 +271,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
271
271
|
readonly id: string;
|
|
272
272
|
readonly slug: string;
|
|
273
273
|
readonly contentTypeId: string;
|
|
274
|
-
readonly data: string;
|
|
275
274
|
readonly authorId?: string | undefined;
|
|
275
|
+
readonly data: string;
|
|
276
276
|
}>;
|
|
277
277
|
readonly listContentByRelation: better_call.StrictEndpoint<"/content/:typeSlug/by-relation", {
|
|
278
278
|
params: z.ZodObject<{
|
|
@@ -314,8 +314,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
314
314
|
readonly id: string;
|
|
315
315
|
readonly slug: string;
|
|
316
316
|
readonly contentTypeId: string;
|
|
317
|
-
readonly data: string;
|
|
318
317
|
readonly authorId?: string | undefined;
|
|
318
|
+
readonly data: string;
|
|
319
319
|
}[];
|
|
320
320
|
readonly total: number;
|
|
321
321
|
readonly limit: number;
|
|
@@ -383,8 +383,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
383
383
|
readonly id: string;
|
|
384
384
|
readonly slug: string;
|
|
385
385
|
readonly contentTypeId: string;
|
|
386
|
-
readonly data: string;
|
|
387
386
|
readonly authorId?: string | undefined;
|
|
387
|
+
readonly data: string;
|
|
388
388
|
}[];
|
|
389
389
|
readonly total: number;
|
|
390
390
|
readonly limit: number;
|
|
@@ -459,8 +459,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
459
459
|
id: string;
|
|
460
460
|
slug: string;
|
|
461
461
|
contentTypeId: string;
|
|
462
|
-
data: string;
|
|
463
462
|
authorId?: string | undefined;
|
|
463
|
+
data: string;
|
|
464
464
|
}[];
|
|
465
465
|
total: number;
|
|
466
466
|
}, false>;
|
|
@@ -493,8 +493,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
493
493
|
id: string;
|
|
494
494
|
slug: string;
|
|
495
495
|
contentTypeId: string;
|
|
496
|
-
data: string;
|
|
497
496
|
authorId?: string | undefined;
|
|
497
|
+
data: string;
|
|
498
498
|
}, false>;
|
|
499
499
|
readonly createContentItem: _btst_stack_plugins_api.Operation<z.ZodObject<{
|
|
500
500
|
typeSlug: z.ZodString;
|
|
@@ -522,8 +522,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
522
522
|
id: string;
|
|
523
523
|
slug: string;
|
|
524
524
|
contentTypeId: string;
|
|
525
|
-
data: string;
|
|
526
525
|
authorId?: string | undefined;
|
|
526
|
+
data: string;
|
|
527
527
|
}, false>;
|
|
528
528
|
readonly updateContentItem: _btst_stack_plugins_api.Operation<z.ZodObject<{
|
|
529
529
|
typeSlug: z.ZodString;
|
|
@@ -554,8 +554,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
554
554
|
id: string;
|
|
555
555
|
slug: string;
|
|
556
556
|
contentTypeId: string;
|
|
557
|
-
data: string;
|
|
558
557
|
authorId?: string | undefined;
|
|
558
|
+
data: string;
|
|
559
559
|
}, false>;
|
|
560
560
|
readonly deleteContentItem: _btst_stack_plugins_api.Operation<z.ZodObject<{
|
|
561
561
|
typeSlug: z.ZodString;
|
|
@@ -598,8 +598,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
598
598
|
id: string;
|
|
599
599
|
slug: string;
|
|
600
600
|
contentTypeId: string;
|
|
601
|
-
data: string;
|
|
602
601
|
authorId?: string | undefined;
|
|
602
|
+
data: string;
|
|
603
603
|
}[];
|
|
604
604
|
};
|
|
605
605
|
contentType?: {
|
|
@@ -619,8 +619,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
619
619
|
id: string;
|
|
620
620
|
slug: string;
|
|
621
621
|
contentTypeId: string;
|
|
622
|
-
data: string;
|
|
623
622
|
authorId?: string | undefined;
|
|
623
|
+
data: string;
|
|
624
624
|
}, false>;
|
|
625
625
|
readonly listContentByRelation: _btst_stack_plugins_api.Operation<z.ZodObject<{
|
|
626
626
|
typeSlug: z.ZodString;
|
|
@@ -657,8 +657,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
657
657
|
id: string;
|
|
658
658
|
slug: string;
|
|
659
659
|
contentTypeId: string;
|
|
660
|
-
data: string;
|
|
661
660
|
authorId?: string | undefined;
|
|
661
|
+
data: string;
|
|
662
662
|
}[];
|
|
663
663
|
total: number;
|
|
664
664
|
limit: number;
|
|
@@ -715,8 +715,8 @@ declare const cmsBackendPlugin: (config: CMSBackendConfig) => _btst_stack_plugin
|
|
|
715
715
|
id: string;
|
|
716
716
|
slug: string;
|
|
717
717
|
contentTypeId: string;
|
|
718
|
-
data: string;
|
|
719
718
|
authorId?: string | undefined;
|
|
719
|
+
data: string;
|
|
720
720
|
}[];
|
|
721
721
|
total: number;
|
|
722
722
|
limit: number;
|
|
@@ -127,11 +127,11 @@ declare const formBuilderBackendPlugin: (config?: FormBuilderBackendConfig) => _
|
|
|
127
127
|
readonly status: Form["status"];
|
|
128
128
|
readonly createdAt: string;
|
|
129
129
|
readonly updatedAt: string;
|
|
130
|
-
readonly id: string;
|
|
131
|
-
readonly name: string;
|
|
132
130
|
readonly schema: string;
|
|
131
|
+
readonly id: string;
|
|
133
132
|
readonly description?: string | undefined;
|
|
134
133
|
readonly slug: string;
|
|
134
|
+
readonly name: string;
|
|
135
135
|
readonly successMessage?: string | undefined;
|
|
136
136
|
readonly redirectUrl?: string | undefined;
|
|
137
137
|
readonly createdBy?: string | undefined;
|
|
@@ -151,11 +151,11 @@ declare const formBuilderBackendPlugin: (config?: FormBuilderBackendConfig) => _
|
|
|
151
151
|
readonly status: Form["status"];
|
|
152
152
|
readonly createdAt: string;
|
|
153
153
|
readonly updatedAt: string;
|
|
154
|
-
readonly id: string;
|
|
155
|
-
readonly name: string;
|
|
156
154
|
readonly schema: string;
|
|
155
|
+
readonly id: string;
|
|
157
156
|
readonly description?: string | undefined;
|
|
158
157
|
readonly slug: string;
|
|
158
|
+
readonly name: string;
|
|
159
159
|
readonly successMessage?: string | undefined;
|
|
160
160
|
readonly redirectUrl?: string | undefined;
|
|
161
161
|
readonly createdBy?: string | undefined;
|
|
@@ -171,11 +171,11 @@ declare const formBuilderBackendPlugin: (config?: FormBuilderBackendConfig) => _
|
|
|
171
171
|
readonly status: Form["status"];
|
|
172
172
|
readonly createdAt: string;
|
|
173
173
|
readonly updatedAt: string;
|
|
174
|
-
readonly id: string;
|
|
175
|
-
readonly name: string;
|
|
176
174
|
readonly schema: string;
|
|
175
|
+
readonly id: string;
|
|
177
176
|
readonly description?: string | undefined;
|
|
178
177
|
readonly slug: string;
|
|
178
|
+
readonly name: string;
|
|
179
179
|
readonly successMessage?: string | undefined;
|
|
180
180
|
readonly redirectUrl?: string | undefined;
|
|
181
181
|
readonly createdBy?: string | undefined;
|
|
@@ -191,11 +191,11 @@ declare const formBuilderBackendPlugin: (config?: FormBuilderBackendConfig) => _
|
|
|
191
191
|
readonly status: Form["status"];
|
|
192
192
|
readonly createdAt: string;
|
|
193
193
|
readonly updatedAt: string;
|
|
194
|
-
readonly id: string;
|
|
195
|
-
readonly name: string;
|
|
196
194
|
readonly schema: string;
|
|
195
|
+
readonly id: string;
|
|
197
196
|
readonly description?: string | undefined;
|
|
198
197
|
readonly slug: string;
|
|
198
|
+
readonly name: string;
|
|
199
199
|
readonly successMessage?: string | undefined;
|
|
200
200
|
readonly redirectUrl?: string | undefined;
|
|
201
201
|
readonly createdBy?: string | undefined;
|
|
@@ -225,11 +225,11 @@ declare const formBuilderBackendPlugin: (config?: FormBuilderBackendConfig) => _
|
|
|
225
225
|
readonly status: Form["status"];
|
|
226
226
|
readonly createdAt: string;
|
|
227
227
|
readonly updatedAt: string;
|
|
228
|
-
readonly id: string;
|
|
229
|
-
readonly name: string;
|
|
230
228
|
readonly schema: string;
|
|
229
|
+
readonly id: string;
|
|
231
230
|
readonly description?: string | undefined;
|
|
232
231
|
readonly slug: string;
|
|
232
|
+
readonly name: string;
|
|
233
233
|
readonly successMessage?: string | undefined;
|
|
234
234
|
readonly redirectUrl?: string | undefined;
|
|
235
235
|
readonly createdBy?: string | undefined;
|
|
@@ -259,11 +259,11 @@ declare const formBuilderBackendPlugin: (config?: FormBuilderBackendConfig) => _
|
|
|
259
259
|
readonly status: Form["status"];
|
|
260
260
|
readonly createdAt: string;
|
|
261
261
|
readonly updatedAt: string;
|
|
262
|
-
readonly id: string;
|
|
263
|
-
readonly name: string;
|
|
264
262
|
readonly schema: string;
|
|
263
|
+
readonly id: string;
|
|
265
264
|
readonly description?: string | undefined;
|
|
266
265
|
readonly slug: string;
|
|
266
|
+
readonly name: string;
|
|
267
267
|
readonly successMessage?: string | undefined;
|
|
268
268
|
readonly redirectUrl?: string | undefined;
|
|
269
269
|
readonly createdBy?: string | undefined;
|