@ulearning/ai-sdk-adapter 0.0.0-stage → 1.0.0-next.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/LICENSE +21 -0
- package/README.md +29 -2
- package/dist/adapters/aiSDKFormatAdapter.d.ts +6 -0
- package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
- package/dist/adapters/aiSDKFormatAdapter.js +17 -0
- package/dist/adapters/aiSDKFormatAdapter.js.map +1 -0
- package/dist/adapters/vercelAttachmentAdapter.d.ts +3 -0
- package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -0
- package/dist/adapters/vercelAttachmentAdapter.js +36 -0
- package/dist/adapters/vercelAttachmentAdapter.js.map +1 -0
- package/dist/aiSDKExtras.d.ts +13 -0
- package/dist/aiSDKExtras.d.ts.map +1 -0
- package/dist/aiSDKExtras.js +6 -0
- package/dist/aiSDKExtras.js.map +1 -0
- package/dist/converters/convertMessage.d.ts +44 -0
- package/dist/converters/convertMessage.d.ts.map +1 -0
- package/dist/converters/convertMessage.js +465 -0
- package/dist/converters/convertMessage.js.map +1 -0
- package/dist/converters/toCreateMessage.d.ts +4 -0
- package/dist/converters/toCreateMessage.d.ts.map +1 -0
- package/dist/converters/toCreateMessage.js +65 -0
- package/dist/converters/toCreateMessage.js.map +1 -0
- package/dist/converters/toolApprovalAnswers.d.ts +3 -0
- package/dist/converters/toolApprovalAnswers.d.ts.map +1 -0
- package/dist/converters/toolApprovalAnswers.js +16 -0
- package/dist/converters/toolApprovalAnswers.js.map +1 -0
- package/dist/hooks.d.ts +17 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +19 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/model-context/injectInteractableContext.d.ts +37 -0
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -0
- package/dist/model-context/injectInteractableContext.js +54 -0
- package/dist/model-context/injectInteractableContext.js.map +1 -0
- package/dist/model-context/injectQuoteContext.d.ts +25 -0
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -0
- package/dist/model-context/injectQuoteContext.js +51 -0
- package/dist/model-context/injectQuoteContext.js.map +1 -0
- package/dist/runtime/AISDKChat.d.ts +13 -0
- package/dist/runtime/AISDKChat.d.ts.map +1 -0
- package/dist/runtime/AISDKChat.js +34 -0
- package/dist/runtime/AISDKChat.js.map +1 -0
- package/dist/runtime/AISDKThreads.d.ts +24 -0
- package/dist/runtime/AISDKThreads.d.ts.map +1 -0
- package/dist/runtime/AISDKThreads.js +92 -0
- package/dist/runtime/AISDKThreads.js.map +1 -0
- package/dist/runtime/DynamicChatTransport.d.ts +50 -0
- package/dist/runtime/DynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/DynamicChatTransport.js +153 -0
- package/dist/runtime/DynamicChatTransport.js.map +1 -0
- package/dist/runtime/getResumableAdapter.d.ts +5 -0
- package/dist/runtime/getResumableAdapter.d.ts.map +1 -0
- package/dist/runtime/getResumableAdapter.js +11 -0
- package/dist/runtime/getResumableAdapter.js.map +1 -0
- package/dist/runtime/toolHistoryCodec.d.ts +20 -0
- package/dist/runtime/toolHistoryCodec.d.ts.map +1 -0
- package/dist/runtime/toolHistoryCodec.js +122 -0
- package/dist/runtime/toolHistoryCodec.js.map +1 -0
- package/dist/runtime/useAISDKRuntime.d.ts +95 -0
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -0
- package/dist/runtime/useAISDKRuntime.js +754 -0
- package/dist/runtime/useAISDKRuntime.js.map +1 -0
- package/dist/runtime/useChatRuntime.d.ts +17 -0
- package/dist/runtime/useChatRuntime.d.ts.map +1 -0
- package/dist/runtime/useChatRuntime.js +50 -0
- package/dist/runtime/useChatRuntime.js.map +1 -0
- package/dist/runtime/useChatSnapshot.d.ts +6 -0
- package/dist/runtime/useChatSnapshot.d.ts.map +1 -0
- package/dist/runtime/useChatSnapshot.js +62 -0
- package/dist/runtime/useChatSnapshot.js.map +1 -0
- package/dist/runtime/useChatThread.d.ts +121 -0
- package/dist/runtime/useChatThread.d.ts.map +1 -0
- package/dist/runtime/useChatThread.js +255 -0
- package/dist/runtime/useChatThread.js.map +1 -0
- package/dist/runtime/useDynamicChatTransport.d.ts +4 -0
- package/dist/runtime/useDynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/useDynamicChatTransport.js +26 -0
- package/dist/runtime/useDynamicChatTransport.js.map +1 -0
- package/dist/runtime/useExternalHistory.d.ts +12 -0
- package/dist/runtime/useExternalHistory.d.ts.map +1 -0
- package/dist/runtime/useExternalHistory.js +556 -0
- package/dist/runtime/useExternalHistory.js.map +1 -0
- package/dist/runtime/useMessageQueue.d.ts +14 -0
- package/dist/runtime/useMessageQueue.d.ts.map +1 -0
- package/dist/runtime/useMessageQueue.js +142 -0
- package/dist/runtime/useMessageQueue.js.map +1 -0
- package/dist/runtime/useResourceCleanup.d.ts +2 -0
- package/dist/runtime/useResourceCleanup.d.ts.map +1 -0
- package/dist/runtime/useResourceCleanup.js +47 -0
- package/dist/runtime/useResourceCleanup.js.map +1 -0
- package/dist/runtime/useStreamingTiming.d.ts +12 -0
- package/dist/runtime/useStreamingTiming.d.ts.map +1 -0
- package/dist/runtime/useStreamingTiming.js +36 -0
- package/dist/runtime/useStreamingTiming.js.map +1 -0
- package/dist/tools/generativeTools.d.ts +71 -0
- package/dist/tools/generativeTools.d.ts.map +1 -0
- package/dist/tools/generativeTools.js +258 -0
- package/dist/tools/generativeTools.js.map +1 -0
- package/dist/tools/mcp-stdio.node.d.ts +2 -0
- package/dist/tools/mcp-stdio.node.d.ts.map +1 -0
- package/dist/tools/mcp-stdio.node.js +1 -0
- package/dist/tools/mcp-stdio.unsupported.d.ts +3 -0
- package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -0
- package/dist/tools/mcp-stdio.unsupported.js +10 -0
- package/dist/tools/mcp-stdio.unsupported.js.map +1 -0
- package/dist/transport/AssistantChatTransport.d.ts +20 -0
- package/dist/transport/AssistantChatTransport.d.ts.map +1 -0
- package/dist/transport/AssistantChatTransport.js +151 -0
- package/dist/transport/AssistantChatTransport.js.map +1 -0
- package/dist/transport/resumable.d.ts +36 -0
- package/dist/transport/resumable.d.ts.map +1 -0
- package/dist/transport/resumable.js +112 -0
- package/dist/transport/resumable.js.map +1 -0
- package/dist/usage.d.ts +24 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +121 -0
- package/dist/usage.js.map +1 -0
- package/dist/utils/getVercelAIMessages.d.ts +4 -0
- package/dist/utils/getVercelAIMessages.d.ts.map +1 -0
- package/dist/utils/getVercelAIMessages.js +8 -0
- package/dist/utils/getVercelAIMessages.js.map +1 -0
- package/dist/utils/sliceMessagesUntil.d.ts +3 -0
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -0
- package/dist/utils/sliceMessagesUntil.js +10 -0
- package/dist/utils/sliceMessagesUntil.js.map +1 -0
- package/package.json +96 -3
- package/src/__tests__/standalone.e2e.ts +118 -0
- package/src/adapters/aiSDKFormatAdapter.test.ts +36 -0
- package/src/adapters/aiSDKFormatAdapter.ts +18 -0
- package/src/adapters/vercelAttachmentAdapter.test.ts +52 -0
- package/src/adapters/vercelAttachmentAdapter.ts +36 -0
- package/src/aiSDKExtras.ts +31 -0
- package/src/converters/convertMessage.metadata-key.test.tsx +182 -0
- package/src/converters/convertMessage.test.ts +2428 -0
- package/src/converters/convertMessage.tool-args-status.test.tsx +185 -0
- package/src/converters/convertMessage.ts +919 -0
- package/src/converters/toCreateMessage.test.ts +716 -0
- package/src/converters/toCreateMessage.ts +110 -0
- package/src/converters/toolApprovalAnswers.ts +27 -0
- package/src/hooks.test.ts +168 -0
- package/src/hooks.ts +26 -0
- package/src/index.ts +29 -0
- package/src/model-context/injectInteractableContext.test.ts +167 -0
- package/src/model-context/injectInteractableContext.ts +64 -0
- package/src/model-context/injectQuoteContext.test.ts +33 -0
- package/src/model-context/injectQuoteContext.ts +63 -0
- package/src/runtime/AISDKChat.artifacts.test.ts +48 -0
- package/src/runtime/AISDKChat.integration.test.tsx +148 -0
- package/src/runtime/AISDKChat.test.ts +241 -0
- package/src/runtime/AISDKChat.ts +43 -0
- package/src/runtime/AISDKThreads.test.ts +811 -0
- package/src/runtime/AISDKThreads.ts +194 -0
- package/src/runtime/DynamicChatTransport.test.ts +228 -0
- package/src/runtime/DynamicChatTransport.ts +281 -0
- package/src/runtime/__tests__/controlled-transport.ts +70 -0
- package/src/runtime/__tests__/tap.ts +80 -0
- package/src/runtime/__tests__/vue.ts +46 -0
- package/src/runtime/getResumableAdapter.ts +16 -0
- package/src/runtime/toolHistoryCodec.test.ts +187 -0
- package/src/runtime/toolHistoryCodec.ts +245 -0
- package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +1956 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +580 -0
- package/src/runtime/useAISDKRuntime.denied-tool.test.tsx +122 -0
- package/src/runtime/useAISDKRuntime.fast-refresh.test.tsx +302 -0
- package/src/runtime/useAISDKRuntime.metadata-cache.test.tsx +157 -0
- package/src/runtime/useAISDKRuntime.queue.test.tsx +707 -0
- package/src/runtime/useAISDKRuntime.reload.test.tsx +217 -0
- package/src/runtime/useAISDKRuntime.test.ts +2345 -0
- package/src/runtime/useAISDKRuntime.tool-output.test.tsx +113 -0
- package/src/runtime/useAISDKRuntime.ts +1503 -0
- package/src/runtime/useAISDKRuntime.voice.test.tsx +319 -0
- package/src/runtime/useChatOnTap.test.ts +60 -0
- package/src/runtime/useChatRuntime.fast-refresh.test.tsx +83 -0
- package/src/runtime/useChatRuntime.integration.test.tsx +661 -0
- package/src/runtime/useChatRuntime.local-storage.test.tsx +114 -0
- package/src/runtime/useChatRuntime.native.test.ts +147 -0
- package/src/runtime/useChatRuntime.run-telemetry.test.tsx +226 -0
- package/src/runtime/useChatRuntime.test.ts +619 -0
- package/src/runtime/useChatRuntime.ts +80 -0
- package/src/runtime/useChatSnapshot.test.ts +60 -0
- package/src/runtime/useChatSnapshot.ts +86 -0
- package/src/runtime/useChatThread.binding.test.tsx +140 -0
- package/src/runtime/useChatThread.test.ts +993 -0
- package/src/runtime/useChatThread.transport.test.tsx +149 -0
- package/src/runtime/useChatThread.ts +533 -0
- package/src/runtime/useDynamicChatTransport.ts +26 -0
- package/src/runtime/useExternalHistory.test.ts +4187 -0
- package/src/runtime/useExternalHistory.ts +902 -0
- package/src/runtime/useMessageQueue.effects.test.tsx +88 -0
- package/src/runtime/useMessageQueue.test.tsx +256 -0
- package/src/runtime/useMessageQueue.ts +189 -0
- package/src/runtime/useResourceCleanup.ts +58 -0
- package/src/runtime/useStreamingTiming.test.tsx +96 -0
- package/src/runtime/useStreamingTiming.ts +69 -0
- package/src/tools/generativeTools.test.ts +1058 -0
- package/src/tools/generativeTools.ts +491 -0
- package/src/tools/mcp-stdio.node.ts +1 -0
- package/src/tools/mcp-stdio.unsupported.ts +12 -0
- package/src/transport/AssistantChatTransport.test.ts +631 -0
- package/src/transport/AssistantChatTransport.ts +236 -0
- package/src/transport/resumable.test.ts +262 -0
- package/src/transport/resumable.ts +162 -0
- package/src/usage.test.ts +204 -0
- package/src/usage.ts +179 -0
- package/src/utils/getVercelAIMessages.ts +11 -0
- package/src/utils/sliceMessagesUntil.test.ts +51 -0
- package/src/utils/sliceMessagesUntil.ts +16 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { resource, useResource } from "@ulearning/ai-tap";
|
|
4
|
+
import { useEffect, useMemo, useState } from "@ulearning/ai-tap";
|
|
5
|
+
import type { Chat } from "@ai-sdk/vue";
|
|
6
|
+
import type { UIMessage } from "ai";
|
|
7
|
+
import type { ChatTransport } from "ai";
|
|
8
|
+
import {
|
|
9
|
+
InMemoryThreadList,
|
|
10
|
+
inMemoryThreadListTransformScopes,
|
|
11
|
+
} from "@ulearning/ai-core/store";
|
|
12
|
+
import { ThreadClient } from "@ulearning/ai-core/store/internal";
|
|
13
|
+
import {
|
|
14
|
+
attachTransformScopes,
|
|
15
|
+
useAssistantClientRef,
|
|
16
|
+
useAssistantScopeEffect,
|
|
17
|
+
} from "@ulearning/ai-store/client";
|
|
18
|
+
import { AssistantChatTransport } from "../transport/AssistantChatTransport";
|
|
19
|
+
import {
|
|
20
|
+
createChat,
|
|
21
|
+
splitChatThreadOptions,
|
|
22
|
+
useChatThread,
|
|
23
|
+
type ChatThreadOptions,
|
|
24
|
+
} from "./useChatThread";
|
|
25
|
+
import { MessageRepository } from "@ulearning/ai-core/internal";
|
|
26
|
+
import { useResourceCleanup } from "./useResourceCleanup";
|
|
27
|
+
|
|
28
|
+
export type AISDKThreadsOptions<UI_MESSAGE extends UIMessage = UIMessage> =
|
|
29
|
+
Omit<ChatThreadOptions<UI_MESSAGE>, "id" | "transport" | "messages"> & {
|
|
30
|
+
/**
|
|
31
|
+
* The transport threads send through. A factory is invoked once per
|
|
32
|
+
* thread so each thread owns its instance. A plain
|
|
33
|
+
* `AssistantChatTransport` instance is cloned per thread (its
|
|
34
|
+
* assistant-ui wiring is per thread); any other transport instance is
|
|
35
|
+
* shared as-is. Defaults to one `AssistantChatTransport` per thread.
|
|
36
|
+
*/
|
|
37
|
+
transport?:
|
|
38
|
+
| ChatTransport<UI_MESSAGE>
|
|
39
|
+
| (() => ChatTransport<UI_MESSAGE>)
|
|
40
|
+
| undefined;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type AISDKThreadChatOptions<UI_MESSAGE extends UIMessage = UIMessage> =
|
|
44
|
+
AISDKThreadsOptions<UI_MESSAGE>;
|
|
45
|
+
|
|
46
|
+
type ChatOptionsRef<UI_MESSAGE extends UIMessage> = {
|
|
47
|
+
current: AISDKThreadChatOptions<UI_MESSAGE> | undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type ChatEntry<UI_MESSAGE extends UIMessage> = {
|
|
51
|
+
chat: Chat<UI_MESSAGE>;
|
|
52
|
+
transport: ChatTransport<UI_MESSAGE>;
|
|
53
|
+
repository: MessageRepository;
|
|
54
|
+
optionsRef: ChatOptionsRef<UI_MESSAGE>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const createChatEntry = <UI_MESSAGE extends UIMessage>(
|
|
58
|
+
threadId: string,
|
|
59
|
+
options: AISDKThreadChatOptions<UI_MESSAGE> | undefined,
|
|
60
|
+
): ChatEntry<UI_MESSAGE> => {
|
|
61
|
+
const optionsRef: ChatOptionsRef<UI_MESSAGE> = { current: options };
|
|
62
|
+
const { chatInit } = splitChatThreadOptions(
|
|
63
|
+
options as ChatThreadOptions<UI_MESSAGE> | undefined,
|
|
64
|
+
);
|
|
65
|
+
const transport =
|
|
66
|
+
typeof options?.transport === "function"
|
|
67
|
+
? options.transport()
|
|
68
|
+
: options?.transport === undefined
|
|
69
|
+
? new AssistantChatTransport()
|
|
70
|
+
: options.transport instanceof AssistantChatTransport
|
|
71
|
+
? options.transport.__internal_clone()
|
|
72
|
+
: options.transport;
|
|
73
|
+
return {
|
|
74
|
+
chat: createChat({ ...chatInit, id: threadId, transport }, optionsRef),
|
|
75
|
+
transport,
|
|
76
|
+
repository: new MessageRepository(),
|
|
77
|
+
optionsRef,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const getOrCreateChatEntry = <UI_MESSAGE extends UIMessage>(
|
|
82
|
+
threadId: string,
|
|
83
|
+
options: AISDKThreadChatOptions<UI_MESSAGE> | undefined,
|
|
84
|
+
chats: Map<string, ChatEntry<UI_MESSAGE>>,
|
|
85
|
+
): ChatEntry<UI_MESSAGE> => {
|
|
86
|
+
const existing = chats.get(threadId);
|
|
87
|
+
if (existing) return existing;
|
|
88
|
+
const created = createChatEntry(threadId, options);
|
|
89
|
+
chats.set(threadId, created);
|
|
90
|
+
return created;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const useAISDKChatThread = <UI_MESSAGE extends UIMessage = UIMessage>({
|
|
94
|
+
threadId,
|
|
95
|
+
options,
|
|
96
|
+
chats,
|
|
97
|
+
}: {
|
|
98
|
+
threadId: string;
|
|
99
|
+
options: AISDKThreadChatOptions<UI_MESSAGE> | undefined;
|
|
100
|
+
chats: Map<string, ChatEntry<UI_MESSAGE>>;
|
|
101
|
+
}) => {
|
|
102
|
+
const { chat, transport, repository } = getOrCreateChatEntry(
|
|
103
|
+
threadId,
|
|
104
|
+
options,
|
|
105
|
+
chats,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const fallbackItem = useMemo(
|
|
109
|
+
() => ({
|
|
110
|
+
initialize: async () => ({
|
|
111
|
+
remoteId: threadId,
|
|
112
|
+
externalId: undefined,
|
|
113
|
+
}),
|
|
114
|
+
}),
|
|
115
|
+
[threadId],
|
|
116
|
+
);
|
|
117
|
+
const runtime = useChatThread(
|
|
118
|
+
{ ...options, transport } as ChatThreadOptions<UI_MESSAGE>,
|
|
119
|
+
{
|
|
120
|
+
id: threadId,
|
|
121
|
+
isMainThread: true,
|
|
122
|
+
getThreadListItem: () => fallbackItem,
|
|
123
|
+
chat,
|
|
124
|
+
messageRepositoryInstance: repository,
|
|
125
|
+
stopOnClientDestroy: false,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const clientRef = useAssistantClientRef();
|
|
130
|
+
useAssistantScopeEffect(
|
|
131
|
+
"modelContext",
|
|
132
|
+
() =>
|
|
133
|
+
runtime.registerModelContextProvider(clientRef.current!.modelContext()),
|
|
134
|
+
[runtime],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return useResource(ThreadClient({ runtime: runtime.thread }));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const AISDKChatThread = resource(useAISDKChatThread);
|
|
141
|
+
|
|
142
|
+
const useAISDKThreads = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
143
|
+
options?: AISDKThreadsOptions<UI_MESSAGE>,
|
|
144
|
+
) => {
|
|
145
|
+
const threadOptions = options;
|
|
146
|
+
const [chats] = useState(() => new Map<string, ChatEntry<UI_MESSAGE>>());
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
for (const { optionsRef } of chats.values()) {
|
|
150
|
+
optionsRef.current = threadOptions;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
useResourceCleanup(true, () => {
|
|
155
|
+
for (const { chat } of chats.values()) {
|
|
156
|
+
void chat.stop().catch(() => {});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const thread = (id: string) => {
|
|
161
|
+
const element = AISDKChatThread({
|
|
162
|
+
threadId: id,
|
|
163
|
+
options: threadOptions,
|
|
164
|
+
chats,
|
|
165
|
+
});
|
|
166
|
+
return element;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return useResource(
|
|
170
|
+
InMemoryThreadList({
|
|
171
|
+
thread,
|
|
172
|
+
onDelete: (id) => {
|
|
173
|
+
void chats
|
|
174
|
+
.get(id)
|
|
175
|
+
?.chat.stop()
|
|
176
|
+
.catch(() => {});
|
|
177
|
+
chats.delete(id);
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* `AuiConfig` entry that runs one AI SDK chat per thread. Hosts the same
|
|
185
|
+
* per-thread orchestration as {@link AISDKChat} inside the client's own
|
|
186
|
+
* resource tree, so it works with any `AssistantClient` host, React or not.
|
|
187
|
+
* Threads live in memory for the client's lifetime and keep
|
|
188
|
+
* their history across switches; only the visible thread is mounted, and a
|
|
189
|
+
* switched-away chat keeps streaming into its stored state until it settles
|
|
190
|
+
* or the thread is deleted. Model context is registered on each mounted thread.
|
|
191
|
+
*/
|
|
192
|
+
export const AISDKThreads = resource(useAISDKThreads);
|
|
193
|
+
|
|
194
|
+
attachTransformScopes(useAISDKThreads, inMemoryThreadListTransformScopes);
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { AssistantRuntime } from "@ulearning/ai-core";
|
|
2
|
+
import type { UIMessage } from "@ai-sdk/react";
|
|
3
|
+
import type { ChatTransport, UIMessageChunk } from "ai";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { AssistantChatTransport } from "../transport/AssistantChatTransport";
|
|
6
|
+
import { DynamicChatTransport } from "./DynamicChatTransport";
|
|
7
|
+
|
|
8
|
+
const emptyStreamResponse = () =>
|
|
9
|
+
new Response(
|
|
10
|
+
new ReadableStream({ start: (controller) => controller.close() }),
|
|
11
|
+
{
|
|
12
|
+
headers: { "content-type": "text/event-stream" },
|
|
13
|
+
},
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const sendMessagesOptions = (chatId: string) => ({
|
|
17
|
+
trigger: "submit-message" as const,
|
|
18
|
+
chatId,
|
|
19
|
+
messageId: undefined,
|
|
20
|
+
messages: [
|
|
21
|
+
{
|
|
22
|
+
id: `message-${chatId}`,
|
|
23
|
+
role: "user" as const,
|
|
24
|
+
parts: [{ type: "text" as const, text: "hello" }],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
abortSignal: undefined,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const createRuntime = (system: string) =>
|
|
31
|
+
({
|
|
32
|
+
thread: {
|
|
33
|
+
getModelContext: () => ({ system }),
|
|
34
|
+
},
|
|
35
|
+
}) as AssistantRuntime;
|
|
36
|
+
|
|
37
|
+
describe("DynamicChatTransport", () => {
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.restoreAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("forwards replay behavior through the dynamic transport and thread proxy", () => {
|
|
43
|
+
const createTransport = (
|
|
44
|
+
resumeStreamIsReplay: boolean,
|
|
45
|
+
): ChatTransport<UIMessage> => ({
|
|
46
|
+
resumeStreamIsReplay,
|
|
47
|
+
sendMessages: async () => new ReadableStream<UIMessageChunk>(),
|
|
48
|
+
reconnectToStream: async () => new ReadableStream<UIMessageChunk>(),
|
|
49
|
+
});
|
|
50
|
+
const dynamicTransport = new DynamicChatTransport(createTransport(false));
|
|
51
|
+
const owner = {};
|
|
52
|
+
const proxy = dynamicTransport.createThreadProxy(owner, () => ({
|
|
53
|
+
runtime: createRuntime("system"),
|
|
54
|
+
getThreadListItem: () => undefined,
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
expect(dynamicTransport.resumeStreamIsReplay).toBe(false);
|
|
58
|
+
expect(proxy.resumeStreamIsReplay).toBe(false);
|
|
59
|
+
|
|
60
|
+
dynamicTransport.setTransport(createTransport(true));
|
|
61
|
+
|
|
62
|
+
expect(dynamicTransport.resumeStreamIsReplay).toBe(true);
|
|
63
|
+
expect(proxy.resumeStreamIsReplay).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("rejects sends without a registered thread context", () => {
|
|
67
|
+
const dynamicTransport = new DynamicChatTransport(
|
|
68
|
+
new AssistantChatTransport<UIMessage>(),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(() =>
|
|
72
|
+
dynamicTransport.sendMessages(sendMessagesOptions("missing-thread")),
|
|
73
|
+
).toThrow(
|
|
74
|
+
'DynamicChatTransport has no registered context for chat "missing-thread"',
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("keeps AssistantChatTransport wiring scoped per thread", async () => {
|
|
79
|
+
const createTransport = () => {
|
|
80
|
+
const bodies: Array<{ id: string; system: string }> = [];
|
|
81
|
+
const fetch = vi.fn(async (_input, init) => {
|
|
82
|
+
bodies.push(JSON.parse(String(init?.body)));
|
|
83
|
+
return emptyStreamResponse();
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
bodies,
|
|
87
|
+
fetch,
|
|
88
|
+
transport: new AssistantChatTransport<UIMessage>({
|
|
89
|
+
fetch,
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
const initial = createTransport();
|
|
94
|
+
const dynamicTransport = new DynamicChatTransport(initial.transport);
|
|
95
|
+
const ownerA = {};
|
|
96
|
+
const ownerB = {};
|
|
97
|
+
|
|
98
|
+
dynamicTransport.setThreadContext(
|
|
99
|
+
"thread-a",
|
|
100
|
+
ownerA,
|
|
101
|
+
createRuntime("system-a"),
|
|
102
|
+
() => ({
|
|
103
|
+
initialize: async () => ({
|
|
104
|
+
remoteId: "remote-a",
|
|
105
|
+
externalId: undefined,
|
|
106
|
+
}),
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
dynamicTransport.setThreadContext(
|
|
110
|
+
"thread-b",
|
|
111
|
+
ownerB,
|
|
112
|
+
createRuntime("system-b"),
|
|
113
|
+
() => ({
|
|
114
|
+
initialize: async () => ({
|
|
115
|
+
remoteId: "remote-b",
|
|
116
|
+
externalId: undefined,
|
|
117
|
+
}),
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
await dynamicTransport.sendMessages(sendMessagesOptions("thread-a"));
|
|
122
|
+
await dynamicTransport.sendMessages(sendMessagesOptions("thread-b"));
|
|
123
|
+
|
|
124
|
+
const replacement = createTransport();
|
|
125
|
+
dynamicTransport.setTransport(replacement.transport);
|
|
126
|
+
await dynamicTransport.sendMessages(sendMessagesOptions("thread-a"));
|
|
127
|
+
await dynamicTransport.sendMessages(sendMessagesOptions("thread-b"));
|
|
128
|
+
|
|
129
|
+
expect(initial.fetch).toHaveBeenCalledTimes(2);
|
|
130
|
+
expect(replacement.fetch).toHaveBeenCalledTimes(2);
|
|
131
|
+
expect(initial.bodies).toEqual([
|
|
132
|
+
expect.objectContaining({ id: "remote-a", system: "system-a" }),
|
|
133
|
+
expect.objectContaining({ id: "remote-b", system: "system-b" }),
|
|
134
|
+
]);
|
|
135
|
+
expect(replacement.bodies).toEqual([
|
|
136
|
+
expect.objectContaining({ id: "remote-a", system: "system-a" }),
|
|
137
|
+
expect.objectContaining({ id: "remote-b", system: "system-b" }),
|
|
138
|
+
]);
|
|
139
|
+
expect(dynamicTransport.getCurrentTransport("thread-a")).not.toBe(
|
|
140
|
+
dynamicTransport.getCurrentTransport("thread-b"),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("defers replacement clones until a thread uses the transport", async () => {
|
|
145
|
+
const clone = vi.spyOn(
|
|
146
|
+
AssistantChatTransport.prototype,
|
|
147
|
+
"__internal_clone",
|
|
148
|
+
);
|
|
149
|
+
const dynamicTransport = new DynamicChatTransport(
|
|
150
|
+
new AssistantChatTransport<UIMessage>(),
|
|
151
|
+
);
|
|
152
|
+
const owner = {};
|
|
153
|
+
dynamicTransport.setThreadContext(
|
|
154
|
+
"thread-a",
|
|
155
|
+
owner,
|
|
156
|
+
createRuntime("system-a"),
|
|
157
|
+
() => ({
|
|
158
|
+
initialize: async () => ({
|
|
159
|
+
remoteId: "remote-a",
|
|
160
|
+
externalId: undefined,
|
|
161
|
+
}),
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
clone.mockClear();
|
|
165
|
+
|
|
166
|
+
for (let index = 0; index < 10; index++) {
|
|
167
|
+
dynamicTransport.setTransport(
|
|
168
|
+
new AssistantChatTransport<UIMessage>({
|
|
169
|
+
fetch: async () => emptyStreamResponse(),
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
dynamicTransport.setThreadContext(
|
|
174
|
+
"thread-a",
|
|
175
|
+
owner,
|
|
176
|
+
createRuntime("updated-system-a"),
|
|
177
|
+
() => ({
|
|
178
|
+
initialize: async () => ({
|
|
179
|
+
remoteId: "updated-remote-a",
|
|
180
|
+
externalId: undefined,
|
|
181
|
+
}),
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
expect(clone).not.toHaveBeenCalled();
|
|
186
|
+
|
|
187
|
+
await dynamicTransport.sendMessages(sendMessagesOptions("thread-a"));
|
|
188
|
+
|
|
189
|
+
expect(clone).toHaveBeenCalledOnce();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("retries a replacement clone after cloning throws", async () => {
|
|
193
|
+
const initialFetch = vi.fn(async () => emptyStreamResponse());
|
|
194
|
+
const dynamicTransport = new DynamicChatTransport(
|
|
195
|
+
new AssistantChatTransport<UIMessage>({ fetch: initialFetch }),
|
|
196
|
+
);
|
|
197
|
+
dynamicTransport.setThreadContext(
|
|
198
|
+
"thread-a",
|
|
199
|
+
{},
|
|
200
|
+
createRuntime("system-a"),
|
|
201
|
+
() => ({
|
|
202
|
+
initialize: async () => ({
|
|
203
|
+
remoteId: "remote-a",
|
|
204
|
+
externalId: undefined,
|
|
205
|
+
}),
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
await dynamicTransport.sendMessages(sendMessagesOptions("thread-a"));
|
|
209
|
+
|
|
210
|
+
const replacement = new AssistantChatTransport<UIMessage>();
|
|
211
|
+
const cloneError = new Error("clone failed");
|
|
212
|
+
const clone = vi
|
|
213
|
+
.spyOn(replacement, "__internal_clone")
|
|
214
|
+
.mockImplementation(() => {
|
|
215
|
+
throw cloneError;
|
|
216
|
+
});
|
|
217
|
+
dynamicTransport.setTransport(replacement);
|
|
218
|
+
|
|
219
|
+
expect(() =>
|
|
220
|
+
dynamicTransport.sendMessages(sendMessagesOptions("thread-a")),
|
|
221
|
+
).toThrow(cloneError);
|
|
222
|
+
expect(() =>
|
|
223
|
+
dynamicTransport.sendMessages(sendMessagesOptions("thread-a")),
|
|
224
|
+
).toThrow(cloneError);
|
|
225
|
+
expect(clone).toHaveBeenCalledTimes(2);
|
|
226
|
+
expect(initialFetch).toHaveBeenCalledOnce();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import type { AssistantRuntime } from "@ulearning/ai-core";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
import type { ChatTransport } from "ai";
|
|
4
|
+
import {
|
|
5
|
+
AssistantChatTransport,
|
|
6
|
+
type InitializableThreadListItem,
|
|
7
|
+
} from "../transport/AssistantChatTransport";
|
|
8
|
+
import type { ResumableClientStorage } from "../transport/resumable";
|
|
9
|
+
import { getResumableAdapter } from "./getResumableAdapter";
|
|
10
|
+
|
|
11
|
+
const resumedStreamIdsByStorage = new WeakMap<
|
|
12
|
+
ResumableClientStorage,
|
|
13
|
+
Set<string>
|
|
14
|
+
>();
|
|
15
|
+
|
|
16
|
+
export const getResumedStreamIds = (
|
|
17
|
+
storage: ResumableClientStorage | undefined,
|
|
18
|
+
) => {
|
|
19
|
+
if (!storage) return new Set<string>();
|
|
20
|
+
let resumedStreamIds = resumedStreamIdsByStorage.get(storage);
|
|
21
|
+
if (!resumedStreamIds) {
|
|
22
|
+
resumedStreamIds = new Set();
|
|
23
|
+
resumedStreamIdsByStorage.set(storage, resumedStreamIds);
|
|
24
|
+
}
|
|
25
|
+
return resumedStreamIds;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ResumableStorageSubscription = {
|
|
29
|
+
listener: () => void;
|
|
30
|
+
threadId: string | undefined;
|
|
31
|
+
unsubscribe: (() => void) | undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
class DynamicResumableStorage implements ResumableClientStorage {
|
|
35
|
+
private readonly subscriptions = new Set<ResumableStorageSubscription>();
|
|
36
|
+
private storage: ResumableClientStorage | undefined;
|
|
37
|
+
private resumedStreamIds: Set<string>;
|
|
38
|
+
private hasPendingNotification = false;
|
|
39
|
+
|
|
40
|
+
constructor(storage: ResumableClientStorage | undefined) {
|
|
41
|
+
this.storage = storage;
|
|
42
|
+
this.resumedStreamIds = getResumedStreamIds(storage);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public setStorage(storage: ResumableClientStorage | undefined) {
|
|
46
|
+
if (this.storage === storage) return;
|
|
47
|
+
this.storage = storage;
|
|
48
|
+
const nextResumedStreamIds = getResumedStreamIds(storage);
|
|
49
|
+
for (const streamId of this.resumedStreamIds) {
|
|
50
|
+
nextResumedStreamIds.add(streamId);
|
|
51
|
+
}
|
|
52
|
+
this.resumedStreamIds = nextResumedStreamIds;
|
|
53
|
+
this.hasPendingNotification = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public flushChange() {
|
|
57
|
+
if (!this.hasPendingNotification) return;
|
|
58
|
+
this.hasPendingNotification = false;
|
|
59
|
+
for (const subscription of this.subscriptions) {
|
|
60
|
+
subscription.unsubscribe?.();
|
|
61
|
+
subscription.unsubscribe = this.storage?.subscribe?.(
|
|
62
|
+
subscription.listener,
|
|
63
|
+
subscription.threadId,
|
|
64
|
+
);
|
|
65
|
+
subscription.listener();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public getResumedStreamIds() {
|
|
70
|
+
return this.resumedStreamIds;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public getStreamId(threadId?: string) {
|
|
74
|
+
return this.storage?.getStreamId(threadId) ?? null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public setStreamId(id: string, threadId?: string) {
|
|
78
|
+
this.storage?.setStreamId(id, threadId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public clear(threadId?: string) {
|
|
82
|
+
this.storage?.clear(threadId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public subscribe(listener: () => void, threadId?: string) {
|
|
86
|
+
const subscription: ResumableStorageSubscription = {
|
|
87
|
+
listener,
|
|
88
|
+
threadId,
|
|
89
|
+
unsubscribe: this.storage?.subscribe?.(listener, threadId),
|
|
90
|
+
};
|
|
91
|
+
this.subscriptions.add(subscription);
|
|
92
|
+
return () => {
|
|
93
|
+
this.subscriptions.delete(subscription);
|
|
94
|
+
subscription.unsubscribe?.();
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type ThreadTransportContext<UI_MESSAGE extends UIMessage> = {
|
|
100
|
+
owner: object;
|
|
101
|
+
sourceTransport?: ChatTransport<UI_MESSAGE> | undefined;
|
|
102
|
+
transport?: ChatTransport<UI_MESSAGE> | undefined;
|
|
103
|
+
runtime?: AssistantRuntime | undefined;
|
|
104
|
+
getThreadListItem?:
|
|
105
|
+
| (() => InitializableThreadListItem | undefined)
|
|
106
|
+
| undefined;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
type ThreadTransportBinding = {
|
|
110
|
+
runtime: AssistantRuntime;
|
|
111
|
+
getThreadListItem: () => InitializableThreadListItem | undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export class DynamicChatTransport<
|
|
115
|
+
UI_MESSAGE extends UIMessage,
|
|
116
|
+
> implements ChatTransport<UI_MESSAGE> {
|
|
117
|
+
private readonly threadContexts = new Map<
|
|
118
|
+
string,
|
|
119
|
+
ThreadTransportContext<UI_MESSAGE>
|
|
120
|
+
>();
|
|
121
|
+
private transport: ChatTransport<UI_MESSAGE>;
|
|
122
|
+
private readonly resumableStorage: DynamicResumableStorage;
|
|
123
|
+
|
|
124
|
+
constructor(transport: ChatTransport<UI_MESSAGE>) {
|
|
125
|
+
this.transport = transport;
|
|
126
|
+
this.resumableStorage = new DynamicResumableStorage(
|
|
127
|
+
getResumableAdapter(transport)?.storage,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public readonly sendMessages: ChatTransport<UI_MESSAGE>["sendMessages"] = (
|
|
132
|
+
options,
|
|
133
|
+
) => this.getTransport(options.chatId).sendMessages(options);
|
|
134
|
+
|
|
135
|
+
public readonly reconnectToStream: ChatTransport<UI_MESSAGE>["reconnectToStream"] =
|
|
136
|
+
(options) => this.getTransport(options.chatId).reconnectToStream(options);
|
|
137
|
+
|
|
138
|
+
public get resumeStreamIsReplay() {
|
|
139
|
+
return this.transport.resumeStreamIsReplay ?? false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public readonly getCurrentTransport = (chatId: string) =>
|
|
143
|
+
this.getThreadTransport(this.threadContexts.get(chatId)) ?? this.transport;
|
|
144
|
+
|
|
145
|
+
public readonly getCurrentResumableStorage = () => this.resumableStorage;
|
|
146
|
+
|
|
147
|
+
public readonly getResumedStreamIds = () =>
|
|
148
|
+
this.resumableStorage.getResumedStreamIds();
|
|
149
|
+
|
|
150
|
+
public createThreadProxy(
|
|
151
|
+
owner: object,
|
|
152
|
+
getBinding: () => ThreadTransportBinding,
|
|
153
|
+
): ChatTransport<UI_MESSAGE> {
|
|
154
|
+
const dynamicTransport = this;
|
|
155
|
+
return {
|
|
156
|
+
get resumeStreamIsReplay() {
|
|
157
|
+
return dynamicTransport.resumeStreamIsReplay;
|
|
158
|
+
},
|
|
159
|
+
sendMessages: (options) =>
|
|
160
|
+
this.getOrCreateBoundTransport(
|
|
161
|
+
options.chatId,
|
|
162
|
+
owner,
|
|
163
|
+
getBinding,
|
|
164
|
+
).sendMessages(options),
|
|
165
|
+
reconnectToStream: (options) =>
|
|
166
|
+
this.getOrCreateBoundTransport(
|
|
167
|
+
options.chatId,
|
|
168
|
+
owner,
|
|
169
|
+
getBinding,
|
|
170
|
+
).reconnectToStream(options),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public setTransport(transport: ChatTransport<UI_MESSAGE>) {
|
|
175
|
+
if (this.transport === transport) return;
|
|
176
|
+
this.transport = transport;
|
|
177
|
+
this.resumableStorage.setStorage(getResumableAdapter(transport)?.storage);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public flushTransportChange() {
|
|
181
|
+
this.resumableStorage.flushChange();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public registerThread(chatId: string, owner: object) {
|
|
185
|
+
const existing = this.threadContexts.get(chatId);
|
|
186
|
+
if (existing?.owner === owner) return;
|
|
187
|
+
this.threadContexts.set(chatId, {
|
|
188
|
+
owner,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public setThreadContext(
|
|
193
|
+
chatId: string,
|
|
194
|
+
owner: object,
|
|
195
|
+
runtime: AssistantRuntime,
|
|
196
|
+
getThreadListItem: () => InitializableThreadListItem | undefined,
|
|
197
|
+
) {
|
|
198
|
+
this.registerThread(chatId, owner);
|
|
199
|
+
const context = this.threadContexts.get(chatId)!;
|
|
200
|
+
context.runtime = runtime;
|
|
201
|
+
context.getThreadListItem = getThreadListItem;
|
|
202
|
+
if (context.transport === undefined) {
|
|
203
|
+
this.getThreadTransport(context);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (
|
|
207
|
+
context.sourceTransport === this.transport &&
|
|
208
|
+
context.transport !== undefined
|
|
209
|
+
) {
|
|
210
|
+
this.wireTransport(context, context.transport);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public unregisterThread(chatId: string, owner: object) {
|
|
215
|
+
if (this.threadContexts.get(chatId)?.owner === owner) {
|
|
216
|
+
this.threadContexts.delete(chatId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private getTransport(chatId: string) {
|
|
221
|
+
const context = this.threadContexts.get(chatId);
|
|
222
|
+
if (!context) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`DynamicChatTransport has no registered context for chat "${chatId}"`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return this.getThreadTransport(context)!;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private getBoundTransport(
|
|
231
|
+
chatId: string,
|
|
232
|
+
owner: object,
|
|
233
|
+
binding: ThreadTransportBinding,
|
|
234
|
+
) {
|
|
235
|
+
this.registerThread(chatId, owner);
|
|
236
|
+
const context = this.threadContexts.get(chatId)!;
|
|
237
|
+
context.runtime = binding.runtime;
|
|
238
|
+
context.getThreadListItem = binding.getThreadListItem;
|
|
239
|
+
return this.getThreadTransport(context)!;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private getOrCreateBoundTransport(
|
|
243
|
+
chatId: string,
|
|
244
|
+
owner: object,
|
|
245
|
+
getBinding: () => ThreadTransportBinding,
|
|
246
|
+
) {
|
|
247
|
+
const context = this.threadContexts.get(chatId);
|
|
248
|
+
return context?.owner === owner
|
|
249
|
+
? this.getThreadTransport(context)!
|
|
250
|
+
: this.getBoundTransport(chatId, owner, getBinding());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private getThreadTransport(
|
|
254
|
+
context: ThreadTransportContext<UI_MESSAGE> | undefined,
|
|
255
|
+
) {
|
|
256
|
+
if (!context) return undefined;
|
|
257
|
+
if (context.sourceTransport !== this.transport) {
|
|
258
|
+
context.transport = this.createThreadTransport(this.transport);
|
|
259
|
+
context.sourceTransport = this.transport;
|
|
260
|
+
}
|
|
261
|
+
this.wireTransport(context, context.transport!);
|
|
262
|
+
return context.transport!;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private createThreadTransport(transport: ChatTransport<UI_MESSAGE>) {
|
|
266
|
+
return transport instanceof AssistantChatTransport
|
|
267
|
+
? transport.__internal_clone()
|
|
268
|
+
: transport;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private wireTransport(
|
|
272
|
+
context: ThreadTransportContext<UI_MESSAGE>,
|
|
273
|
+
transport: ChatTransport<UI_MESSAGE>,
|
|
274
|
+
) {
|
|
275
|
+
if (!(transport instanceof AssistantChatTransport)) return;
|
|
276
|
+
if (context.runtime) transport.setRuntime(context.runtime);
|
|
277
|
+
if (context.getThreadListItem) {
|
|
278
|
+
transport.__internal_setGetThreadListItem(context.getThreadListItem);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|