@assistant-ui/react-ai-sdk 0.0.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/.turbo/turbo-build.log +20 -0
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/index.cjs +494 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +464 -0
- package/dist/index.js.map +1 -0
- package/package.json +34 -0
- package/src/core/VercelModelAdapter.tsx +53 -0
- package/src/core/index.ts +1 -0
- package/src/index.ts +3 -0
- package/src/rsc/VercelRSCAdapter.tsx +18 -0
- package/src/rsc/VercelRSCMessage.tsx +9 -0
- package/src/rsc/VercelRSCRuntime.tsx +101 -0
- package/src/rsc/getVercelRSCMessage.tsx +11 -0
- package/src/rsc/index.ts +4 -0
- package/src/rsc/useVercelRSCRuntime.tsx +20 -0
- package/src/rsc/useVercelRSCSync.tsx +50 -0
- package/src/ui/VercelAIRuntime.tsx +162 -0
- package/src/ui/getVercelAIMessage.tsx +12 -0
- package/src/ui/index.ts +3 -0
- package/src/ui/use-assistant/useVercelUseAssistantRuntime.tsx +18 -0
- package/src/ui/use-chat/useVercelUseChatRuntime.tsx +16 -0
- package/src/ui/utils/VercelHelpers.tsx +3 -0
- package/src/ui/utils/sliceMessagesUntil.tsx +20 -0
- package/src/ui/utils/useVercelAIComposerSync.tsx +15 -0
- package/src/ui/utils/useVercelAIThreadSync.tsx +145 -0
- package/src/utils/ThreadMessageConverter.ts +24 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TextContentPart,
|
|
3
|
+
ThreadMessage,
|
|
4
|
+
unstable_ToolCallContentPart as ToolCallContentPart,
|
|
5
|
+
} from "@assistant-ui/react";
|
|
6
|
+
import type { Message } from "ai";
|
|
7
|
+
import { useEffect, useMemo } from "react";
|
|
8
|
+
import {
|
|
9
|
+
type ConverterCallback,
|
|
10
|
+
ThreadMessageConverter,
|
|
11
|
+
} from "../../utils/ThreadMessageConverter";
|
|
12
|
+
import {
|
|
13
|
+
type VercelAIThreadMessage,
|
|
14
|
+
symbolInnerAIMessage,
|
|
15
|
+
} from "../getVercelAIMessage";
|
|
16
|
+
import type { VercelHelpers } from "./VercelHelpers";
|
|
17
|
+
|
|
18
|
+
const getIsRunning = (vercel: VercelHelpers) => {
|
|
19
|
+
if ("isLoading" in vercel) return vercel.isLoading;
|
|
20
|
+
return vercel.status === "in_progress";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const vercelToThreadMessage = (
|
|
24
|
+
messages: Message[],
|
|
25
|
+
status: "in_progress" | "done" | "error",
|
|
26
|
+
): VercelAIThreadMessage => {
|
|
27
|
+
const firstMessage = messages[0];
|
|
28
|
+
if (!firstMessage) throw new Error("No messages found");
|
|
29
|
+
|
|
30
|
+
const common = {
|
|
31
|
+
id: firstMessage.id,
|
|
32
|
+
createdAt: firstMessage.createdAt ?? new Date(),
|
|
33
|
+
[symbolInnerAIMessage]: messages,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
switch (firstMessage.role) {
|
|
37
|
+
case "user":
|
|
38
|
+
if (messages.length > 1) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"Multiple user messages found. This is likely an internal bug in assistant-ui.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
...common,
|
|
46
|
+
role: "user",
|
|
47
|
+
content: [{ type: "text", text: firstMessage.content }],
|
|
48
|
+
};
|
|
49
|
+
case "assistant":
|
|
50
|
+
return {
|
|
51
|
+
...common,
|
|
52
|
+
role: "assistant",
|
|
53
|
+
content: messages.flatMap((message) => [
|
|
54
|
+
...(message.content
|
|
55
|
+
? [{ type: "text", text: message.content } as TextContentPart]
|
|
56
|
+
: []),
|
|
57
|
+
...(message.toolInvocations?.map(
|
|
58
|
+
(t) =>
|
|
59
|
+
({
|
|
60
|
+
type: "tool-call",
|
|
61
|
+
name: t.toolName,
|
|
62
|
+
args: t.args,
|
|
63
|
+
result: "result" in t ? t.result : undefined,
|
|
64
|
+
}) as ToolCallContentPart,
|
|
65
|
+
) ?? []),
|
|
66
|
+
]),
|
|
67
|
+
status,
|
|
68
|
+
};
|
|
69
|
+
default:
|
|
70
|
+
throw new Error(
|
|
71
|
+
`You have a message with an unsupported role. The role ${firstMessage.role} is not supported.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type Chunk = [Message, ...Message[]];
|
|
77
|
+
const hasItems = (messages: Message[]): messages is Chunk =>
|
|
78
|
+
messages.length > 0;
|
|
79
|
+
|
|
80
|
+
const chunkedMessages = (messages: Message[]): Chunk[] => {
|
|
81
|
+
const chunks: Chunk[] = [];
|
|
82
|
+
let currentChunk: Message[] = [];
|
|
83
|
+
|
|
84
|
+
for (const message of messages) {
|
|
85
|
+
if (message.role === "assistant") {
|
|
86
|
+
currentChunk.push(message);
|
|
87
|
+
} else {
|
|
88
|
+
if (hasItems(currentChunk)) {
|
|
89
|
+
chunks.push(currentChunk);
|
|
90
|
+
currentChunk = [];
|
|
91
|
+
}
|
|
92
|
+
chunks.push([message]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (hasItems(currentChunk)) {
|
|
97
|
+
chunks.push(currentChunk);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return chunks;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const shallowArrayEqual = (a: unknown[], b: unknown[]) => {
|
|
104
|
+
if (a.length !== b.length) return false;
|
|
105
|
+
for (let i = 0; i < a.length; i++) {
|
|
106
|
+
if (a[i] !== b[i]) return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type UpdateDataCallback = (isRunning: boolean, vm: ThreadMessage[]) => void;
|
|
112
|
+
|
|
113
|
+
export const useVercelAIThreadSync = (
|
|
114
|
+
vercel: VercelHelpers,
|
|
115
|
+
updateData: UpdateDataCallback,
|
|
116
|
+
) => {
|
|
117
|
+
const isRunning = getIsRunning(vercel);
|
|
118
|
+
|
|
119
|
+
const converter = useMemo(() => new ThreadMessageConverter(), []);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
const lastMessageId = vercel.messages.at(-1)?.id;
|
|
123
|
+
const convertCallback: ConverterCallback<Chunk> = (messages, cache) => {
|
|
124
|
+
const status =
|
|
125
|
+
lastMessageId === messages[0].id && isRunning ? "in_progress" : "done";
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
cache &&
|
|
129
|
+
shallowArrayEqual(cache.content, messages) &&
|
|
130
|
+
(cache.role === "user" || cache.status === status)
|
|
131
|
+
)
|
|
132
|
+
return cache;
|
|
133
|
+
|
|
134
|
+
return vercelToThreadMessage(messages, status);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const messages = converter.convertMessages(
|
|
138
|
+
chunkedMessages(vercel.messages),
|
|
139
|
+
convertCallback,
|
|
140
|
+
(m) => m[0],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
updateData(isRunning, messages);
|
|
144
|
+
}, [updateData, isRunning, vercel.messages, converter]);
|
|
145
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ThreadMessage } from "@assistant-ui/react";
|
|
2
|
+
|
|
3
|
+
export type ConverterCallback<TIn> = (
|
|
4
|
+
message: TIn,
|
|
5
|
+
cache: ThreadMessage | undefined,
|
|
6
|
+
) => ThreadMessage;
|
|
7
|
+
|
|
8
|
+
export class ThreadMessageConverter {
|
|
9
|
+
private readonly cache = new WeakMap<WeakKey, ThreadMessage>();
|
|
10
|
+
|
|
11
|
+
convertMessages<TIn extends WeakKey>(
|
|
12
|
+
messages: TIn[],
|
|
13
|
+
converter: ConverterCallback<TIn>,
|
|
14
|
+
keyMapper: (m: TIn) => WeakKey = (key) => key,
|
|
15
|
+
): ThreadMessage[] {
|
|
16
|
+
return messages.map((m) => {
|
|
17
|
+
const key = keyMapper(m);
|
|
18
|
+
const cached = this.cache.get(key);
|
|
19
|
+
const newMessage = converter(m, cached);
|
|
20
|
+
this.cache.set(key, newMessage);
|
|
21
|
+
return newMessage;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|