@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 @@
|
|
|
1
|
+
{"version":3,"file":"getVercelAIMessages.d.ts","mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,oBAAoB;AAC3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI;AAEnC,eAAO,MAAM,mBAAmB,GAAI,UAAU,SAAS,SAAS,GAAG,SAAS,EAC1E,SAAS,aAAa,KAEsB,UAAU,EACvD","names":[],"ignoreList":[],"sources":["../../src/utils/getVercelAIMessages.ts"],"sourcesContent":[null]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getExternalStoreMessages } from "@ulearning/ai-core";
|
|
2
|
+
// src/utils/getVercelAIMessages.ts
|
|
3
|
+
const getVercelAIMessages = (message) => {
|
|
4
|
+
return getExternalStoreMessages(message);
|
|
5
|
+
};
|
|
6
|
+
export { getVercelAIMessages };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=getVercelAIMessages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getVercelAIMessages.js","names":[],"sources":["../../src/utils/getVercelAIMessages.ts"],"sourcesContent":["import {\n getExternalStoreMessages,\n type ThreadMessage,\n} from \"@ulearning/ai-core\";\nimport type { UIMessage } from \"ai\";\n\nexport const getVercelAIMessages = <UI_MESSAGE extends UIMessage = UIMessage>(\n message: ThreadMessage,\n) => {\n return getExternalStoreMessages(message) as UI_MESSAGE[];\n};\n"],"mappings":";;AAMA,MAAa,uBACX,YACG;CACH,OAAO,yBAAyB,OAAO;AACzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sliceMessagesUntil.d.ts","mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI;AAEnC,eAAO,MAAM,kBAAkB,GAAI,UAAU,SAAS,SAAS,GAAG,SAAS,EACzE,UAAU,UAAU,EAAE,EACtB,WAAW,MAAM,GAAG,IAAI,iBAWzB","names":[],"ignoreList":[],"sources":["../../src/utils/sliceMessagesUntil.ts"],"sourcesContent":[null]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/utils/sliceMessagesUntil.ts
|
|
2
|
+
const sliceMessagesUntil = (messages, messageId) => {
|
|
3
|
+
if (messageId == null) return [];
|
|
4
|
+
const messageIdx = messages.findIndex((m) => m.id === messageId);
|
|
5
|
+
if (messageIdx === -1) throw new Error(`sliceMessagesUntil: Message "${messageId}" not found in AI SDK messages. This is likely an internal bug in assistant-ui.`);
|
|
6
|
+
return messages.slice(0, messageIdx + 1);
|
|
7
|
+
};
|
|
8
|
+
export { sliceMessagesUntil };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=sliceMessagesUntil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sliceMessagesUntil.js","names":[],"sources":["../../src/utils/sliceMessagesUntil.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\n\nexport const sliceMessagesUntil = <UI_MESSAGE extends UIMessage = UIMessage>(\n messages: UI_MESSAGE[],\n messageId: string | null,\n) => {\n if (messageId == null) return [];\n\n const messageIdx = messages.findIndex((m) => m.id === messageId);\n if (messageIdx === -1)\n throw new Error(\n `sliceMessagesUntil: Message \"${messageId}\" not found in AI SDK messages. This is likely an internal bug in assistant-ui.`,\n );\n\n return messages.slice(0, messageIdx + 1);\n};\n"],"mappings":";AAEA,MAAa,sBACX,UACA,cACG;CACH,IAAI,aAAa,MAAM,OAAO,CAAC;CAE/B,MAAM,aAAa,SAAS,WAAW,MAAM,EAAE,OAAO,SAAS;CAC/D,IAAI,eAAe,IACjB,MAAM,IAAI,MACR,gCAAgC,UAAU,gFAC5C;CAEF,OAAO,SAAS,MAAM,GAAG,aAAa,CAAC;AACzC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,99 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ulearning/ai-sdk-adapter",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0-next.0",
|
|
4
|
+
"description": "Vercel AI SDK adapter for assistant-ui",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai-sdk",
|
|
7
|
+
"vercel",
|
|
8
|
+
"assistant-ui",
|
|
9
|
+
"vue",
|
|
10
|
+
"ai",
|
|
11
|
+
"chat",
|
|
12
|
+
"openai",
|
|
13
|
+
"streaming"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"imports": {
|
|
18
|
+
"#mcp-stdio": {
|
|
19
|
+
"types": "./src/tools/mcp-stdio.node.ts",
|
|
20
|
+
"react-native": "./dist/tools/mcp-stdio.unsupported.js",
|
|
21
|
+
"edge-light": "./dist/tools/mcp-stdio.unsupported.js",
|
|
22
|
+
"workerd": "./dist/tools/mcp-stdio.unsupported.js",
|
|
23
|
+
"browser": "./dist/tools/mcp-stdio.unsupported.js",
|
|
24
|
+
"node": "./dist/tools/mcp-stdio.node.js",
|
|
25
|
+
"default": "./dist/tools/mcp-stdio.unsupported.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"main": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@ai-sdk/mcp": "2.0.90",
|
|
44
|
+
"@ulearning/ai-core": "^1.0.0-next.0",
|
|
45
|
+
"@ulearning/ai-store": "^1.0.0-next.0",
|
|
46
|
+
"@ulearning/ai-tap": "^1.0.0-next.0",
|
|
47
|
+
"@ulearning/ai-stream": "^0.3.56-next.0",
|
|
48
|
+
"@ai-sdk/vue": "4.0.162"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"ai": "^7.0.162",
|
|
52
|
+
"vue": "^3.5.22"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"vue": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@ulearning/ai-buildutils": "0.0.33-next.0",
|
|
61
|
+
"@ulearning/ai-vue-compat": "0.0.0",
|
|
62
|
+
"@types/json-schema": "^7.0.15",
|
|
63
|
+
"@types/react": "^19.3.0",
|
|
64
|
+
"ai": "7.0.162",
|
|
65
|
+
"jsdom": "^30.1.3",
|
|
66
|
+
"react": "^19.3.1",
|
|
67
|
+
"vitest": "^5.0.5",
|
|
68
|
+
"vue": "3.5.44",
|
|
69
|
+
"vue-tsc": "3.3.14",
|
|
70
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
71
|
+
"@testing-library/dom": "^10.4.2",
|
|
72
|
+
"@ai-sdk/react": "^4.0.157"
|
|
73
|
+
},
|
|
74
|
+
"publishConfig": {
|
|
75
|
+
"access": "public",
|
|
76
|
+
"registry": "https://registry.npmjs.org/",
|
|
77
|
+
"provenance": false
|
|
78
|
+
},
|
|
79
|
+
"homepage": "http://1.92.106.239/frontend-common/ai-sdk",
|
|
80
|
+
"repository": {
|
|
81
|
+
"type": "git",
|
|
82
|
+
"url": "http://1.92.106.239/frontend-common/ai-sdk.git",
|
|
83
|
+
"directory": "packages/ai-sdk"
|
|
84
|
+
},
|
|
85
|
+
"bugs": {
|
|
86
|
+
"url": "http://1.92.106.239/frontend-common/ai-sdk/-/issues"
|
|
87
|
+
},
|
|
88
|
+
"scripts": {
|
|
89
|
+
"build": "aui-build",
|
|
90
|
+
"test": "vitest run",
|
|
91
|
+
"test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=html --coverage.reporter=json-summary",
|
|
92
|
+
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.test.json",
|
|
93
|
+
"test:peer-vue": "vitest run --config vitest.peer-vue.config.ts",
|
|
94
|
+
"test:artifacts": "vitest run --config vitest.artifacts.config.ts",
|
|
95
|
+
"test:standalone": "vitest run --project standalone",
|
|
96
|
+
"test:watch": "vitest",
|
|
97
|
+
"test:types:peer-vue": "aui-vue-compat-types"
|
|
98
|
+
}
|
|
6
99
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
flushTapSync,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
useTapRoot,
|
|
7
|
+
} from "@ulearning/ai-tap";
|
|
8
|
+
import { effectScope } from "vue";
|
|
9
|
+
import { AuiConfig, createAssistantClient } from "@ulearning/ai-store/client";
|
|
10
|
+
import * as aiSdk from "../index";
|
|
11
|
+
import { createControlledTransport } from "../runtime/__tests__/controlled-transport";
|
|
12
|
+
|
|
13
|
+
vi.mock("react", () => {
|
|
14
|
+
throw new Error("The native standalone graph must not import React.");
|
|
15
|
+
});
|
|
16
|
+
vi.mock("react-dom", () => {
|
|
17
|
+
throw new Error("The native standalone graph must not import React DOM.");
|
|
18
|
+
});
|
|
19
|
+
vi.mock("react/jsx-runtime", () => {
|
|
20
|
+
throw new Error("The native standalone graph must not import React JSX.");
|
|
21
|
+
});
|
|
22
|
+
vi.mock("react/jsx-dev-runtime", () => {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"The native standalone graph must not import React development JSX.",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
vi.mock("react-dom/client", () => {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"The native standalone graph must not import the React DOM client.",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
vi.mock("react-dom/server", () => {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"The native standalone graph must not import the React DOM server.",
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("native Vue standalone graph", () => {
|
|
39
|
+
it("owns a real Vue scope and releases its standalone Tap resource exactly once", () => {
|
|
40
|
+
const scope = effectScope();
|
|
41
|
+
const setup = vi.fn();
|
|
42
|
+
const cleanup = vi.fn();
|
|
43
|
+
const root = scope.run(() =>
|
|
44
|
+
useTapRoot(function useNativeStandaloneCounter() {
|
|
45
|
+
const [count, setCount] = useState(0);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setup();
|
|
48
|
+
return cleanup;
|
|
49
|
+
}, []);
|
|
50
|
+
return { count, setCount };
|
|
51
|
+
}),
|
|
52
|
+
)!;
|
|
53
|
+
const values: number[] = [];
|
|
54
|
+
const unsubscribe = root.subscribe(() =>
|
|
55
|
+
values.push(root.getValue().count),
|
|
56
|
+
);
|
|
57
|
+
try {
|
|
58
|
+
expect(root.getValue().count).toBe(0);
|
|
59
|
+
expect(setup).toHaveBeenCalledTimes(1);
|
|
60
|
+
flushTapSync(() => root.getValue().setCount(2));
|
|
61
|
+
expect(root.getValue().count).toBe(2);
|
|
62
|
+
expect(values).toEqual([2]);
|
|
63
|
+
scope.stop();
|
|
64
|
+
scope.stop();
|
|
65
|
+
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
66
|
+
} finally {
|
|
67
|
+
unsubscribe();
|
|
68
|
+
scope.stop();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("loads the whole native barrel without importing React", () => {
|
|
73
|
+
expect(typeof aiSdk.AISDKChat).toBe("function");
|
|
74
|
+
expect(typeof aiSdk.useChatRuntime).toBe("function");
|
|
75
|
+
expect(typeof aiSdk.useAISDKRuntime).toBe("function");
|
|
76
|
+
expect(typeof aiSdk.frontendTools).toBe("function");
|
|
77
|
+
expect(typeof aiSdk.AssistantChatTransport).toBe("function");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("streams a native standalone chat round trip", async () => {
|
|
81
|
+
const { transport, emit, close } = createControlledTransport();
|
|
82
|
+
const handle = createAssistantClient(
|
|
83
|
+
AuiConfig({ threads: aiSdk.AISDKChat({ transport }) }),
|
|
84
|
+
);
|
|
85
|
+
try {
|
|
86
|
+
handle.subscribe(() => {});
|
|
87
|
+
const aui = handle.getClient();
|
|
88
|
+
|
|
89
|
+
flushTapSync(() => aui.composer.setText("hi"));
|
|
90
|
+
flushTapSync(() => aui.composer.send());
|
|
91
|
+
|
|
92
|
+
await vi.waitFor(() => {
|
|
93
|
+
expect(aui.thread.getState().isRunning).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
emit(
|
|
97
|
+
{ type: "start" },
|
|
98
|
+
{ type: "text-start", id: "t1" },
|
|
99
|
+
{ type: "text-delta", id: "t1", delta: "hello " },
|
|
100
|
+
{ type: "text-delta", id: "t1", delta: "world" },
|
|
101
|
+
{ type: "text-end", id: "t1" },
|
|
102
|
+
{ type: "finish" },
|
|
103
|
+
);
|
|
104
|
+
close();
|
|
105
|
+
|
|
106
|
+
await vi.waitFor(() => {
|
|
107
|
+
const state = aui.thread.getState();
|
|
108
|
+
expect(state.isRunning).toBe(false);
|
|
109
|
+
expect(state.messages).toHaveLength(2);
|
|
110
|
+
expect(state.messages.at(-1)?.content).toContainEqual(
|
|
111
|
+
expect.objectContaining({ type: "text", text: "hello world" }),
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
} finally {
|
|
115
|
+
handle.destroy();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { aiSDKV6FormatAdapter } from "./aiSDKFormatAdapter";
|
|
4
|
+
|
|
5
|
+
const assistant = (
|
|
6
|
+
parts: UIMessage["parts"],
|
|
7
|
+
metadata?: Record<string, unknown>,
|
|
8
|
+
id = "a1",
|
|
9
|
+
): UIMessage =>
|
|
10
|
+
({
|
|
11
|
+
id,
|
|
12
|
+
role: "assistant",
|
|
13
|
+
parts,
|
|
14
|
+
...(metadata ? { metadata } : {}),
|
|
15
|
+
}) as UIMessage;
|
|
16
|
+
|
|
17
|
+
describe("aiSDKV6FormatAdapter", () => {
|
|
18
|
+
it("stores a message without its id and restores it from the row", () => {
|
|
19
|
+
const message = assistant([{ type: "text", text: "hi" }], { a: 1 });
|
|
20
|
+
const content = aiSDKV6FormatAdapter.encode({ parentId: null, message });
|
|
21
|
+
expect(content).toEqual({
|
|
22
|
+
role: "assistant",
|
|
23
|
+
parts: [{ type: "text", text: "hi" }],
|
|
24
|
+
metadata: { a: 1 },
|
|
25
|
+
});
|
|
26
|
+
expect(
|
|
27
|
+
aiSDKV6FormatAdapter.decode({
|
|
28
|
+
id: "a1",
|
|
29
|
+
parent_id: "u1",
|
|
30
|
+
format: "ai-sdk/v6",
|
|
31
|
+
content,
|
|
32
|
+
}),
|
|
33
|
+
).toEqual({ parentId: "u1", message });
|
|
34
|
+
expect(aiSDKV6FormatAdapter.getId(message)).toBe("a1");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
import type { MessageFormatAdapter } from "@ulearning/ai-core";
|
|
3
|
+
|
|
4
|
+
export type AISDKStorageFormat = Omit<UIMessage, "id">;
|
|
5
|
+
|
|
6
|
+
/** The stored form of an AI SDK message: the message without its id. */
|
|
7
|
+
export const aiSDKV6FormatAdapter: MessageFormatAdapter<
|
|
8
|
+
UIMessage,
|
|
9
|
+
AISDKStorageFormat
|
|
10
|
+
> = {
|
|
11
|
+
format: "ai-sdk/v6",
|
|
12
|
+
encode: ({ message: { id: _id, ...message } }) => message,
|
|
13
|
+
decode: (stored) => ({
|
|
14
|
+
parentId: stored.parent_id,
|
|
15
|
+
message: { id: stored.id, ...stored.content } as UIMessage,
|
|
16
|
+
}),
|
|
17
|
+
getId: (message) => message.id,
|
|
18
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { vercelAttachmentAdapter } from "./vercelAttachmentAdapter";
|
|
3
|
+
|
|
4
|
+
const originalFileReader = globalThis.FileReader;
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
globalThis.FileReader = originalFileReader;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe("vercelAttachmentAdapter", () => {
|
|
11
|
+
it("sends a file as a data URL via the shared core encoder", async () => {
|
|
12
|
+
globalThis.FileReader = undefined as unknown as typeof FileReader;
|
|
13
|
+
|
|
14
|
+
const file = new File(["hello"], "a.txt", { type: "text/plain" });
|
|
15
|
+
const result = await vercelAttachmentAdapter.send({
|
|
16
|
+
id: "1",
|
|
17
|
+
type: "file",
|
|
18
|
+
name: file.name,
|
|
19
|
+
file,
|
|
20
|
+
contentType: file.type,
|
|
21
|
+
content: [],
|
|
22
|
+
status: { type: "requires-action", reason: "composer-send" },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const part = result.content[0];
|
|
26
|
+
if (part?.type !== "file") throw new Error("expected file content part");
|
|
27
|
+
expect(part.data).toBe("data:text/plain;base64,aGVsbG8=");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("aborts a file send via the shared core encoder", async () => {
|
|
31
|
+
globalThis.FileReader = undefined as unknown as typeof FileReader;
|
|
32
|
+
|
|
33
|
+
const file = new File(["hello"], "a.txt", { type: "text/plain" });
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
controller.abort();
|
|
36
|
+
|
|
37
|
+
await expect(
|
|
38
|
+
vercelAttachmentAdapter.send(
|
|
39
|
+
{
|
|
40
|
+
id: "1",
|
|
41
|
+
type: "file",
|
|
42
|
+
name: file.name,
|
|
43
|
+
file,
|
|
44
|
+
contentType: file.type,
|
|
45
|
+
content: [],
|
|
46
|
+
status: { type: "requires-action", reason: "composer-send" },
|
|
47
|
+
},
|
|
48
|
+
{ signal: controller.signal },
|
|
49
|
+
),
|
|
50
|
+
).rejects.toBe(controller.signal.reason);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AttachmentAdapter } from "@ulearning/ai-core";
|
|
2
|
+
import { getFileDataURL } from "@ulearning/ai-core/internal";
|
|
3
|
+
import { generateId } from "ai";
|
|
4
|
+
|
|
5
|
+
export const vercelAttachmentAdapter: AttachmentAdapter = {
|
|
6
|
+
accept: "*",
|
|
7
|
+
async add({ file }) {
|
|
8
|
+
return {
|
|
9
|
+
id: generateId(),
|
|
10
|
+
type: file.type.startsWith("image/") ? "image" : "file",
|
|
11
|
+
name: file.name,
|
|
12
|
+
file,
|
|
13
|
+
contentType: file.type,
|
|
14
|
+
content: [],
|
|
15
|
+
status: { type: "requires-action", reason: "composer-send" },
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
async send(attachment, options) {
|
|
19
|
+
// noop
|
|
20
|
+
return {
|
|
21
|
+
...attachment,
|
|
22
|
+
status: { type: "complete" },
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "file",
|
|
26
|
+
mimeType: attachment.contentType ?? "",
|
|
27
|
+
filename: attachment.name,
|
|
28
|
+
data: await getFileDataURL(attachment.file, options),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
async remove() {
|
|
34
|
+
// noop
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createRuntimeExtras } from "@ulearning/ai-core/vue";
|
|
2
|
+
import type { AbstractChat, UIMessage } from "ai";
|
|
3
|
+
|
|
4
|
+
/** Framework independent advanced chat surface, preserving the AI SDK 7 methods. */
|
|
5
|
+
export type AISDKChatHelpers<UI_MESSAGE extends UIMessage = UIMessage> = {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
setMessages: (
|
|
8
|
+
messages: UI_MESSAGE[] | ((messages: UI_MESSAGE[]) => UI_MESSAGE[]),
|
|
9
|
+
) => void;
|
|
10
|
+
error: Error | undefined;
|
|
11
|
+
} & Pick<
|
|
12
|
+
AbstractChat<UI_MESSAGE>,
|
|
13
|
+
| "sendMessage"
|
|
14
|
+
| "regenerate"
|
|
15
|
+
| "stop"
|
|
16
|
+
| "resumeStream"
|
|
17
|
+
| "addToolResult"
|
|
18
|
+
| "addToolOutput"
|
|
19
|
+
| "addToolApprovalResponse"
|
|
20
|
+
| "status"
|
|
21
|
+
| "messages"
|
|
22
|
+
| "clearError"
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
export type AISDKRuntimeExtras = {
|
|
26
|
+
chat: AISDKChatHelpers<UIMessage>;
|
|
27
|
+
error: Error | undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const aiSDKExtras =
|
|
31
|
+
createRuntimeExtras<AISDKRuntimeExtras>("useAISDKRuntime");
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { defineComponent, h, shallowRef } from "vue";
|
|
4
|
+
import { render } from "../runtime/__tests__/vue";
|
|
5
|
+
import type { UIMessage } from "ai";
|
|
6
|
+
import type { Unstable_ToolInteractionLog } from "@ulearning/ai-core";
|
|
7
|
+
import { useExternalMessageConverter } from "@ulearning/ai-core/vue";
|
|
8
|
+
import { describe, expect, it, vi } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
AISDKMessageConverter,
|
|
11
|
+
convertAISDKMessage,
|
|
12
|
+
createAISDKMetadataKey,
|
|
13
|
+
type AISDKMessageConverterMetadata,
|
|
14
|
+
} from "./convertMessage";
|
|
15
|
+
|
|
16
|
+
const messages: UIMessage[] = [
|
|
17
|
+
{ id: "u0", role: "user", parts: [{ type: "text", text: "weather?" }] },
|
|
18
|
+
{
|
|
19
|
+
id: "a1",
|
|
20
|
+
role: "assistant",
|
|
21
|
+
parts: [
|
|
22
|
+
{
|
|
23
|
+
type: "tool-weather",
|
|
24
|
+
toolCallId: "call-1",
|
|
25
|
+
state: "output-available",
|
|
26
|
+
input: { city: "NYC" },
|
|
27
|
+
output: { temp: 72 },
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{ id: "u2", role: "user", parts: [{ type: "text", text: "and Paris?" }] },
|
|
32
|
+
{
|
|
33
|
+
id: "a3",
|
|
34
|
+
role: "assistant",
|
|
35
|
+
parts: [
|
|
36
|
+
{
|
|
37
|
+
type: "tool-weather",
|
|
38
|
+
toolCallId: "call-3",
|
|
39
|
+
state: "input-available",
|
|
40
|
+
input: { city: "Paris" },
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{ id: "u4", role: "user", parts: [{ type: "text", text: "deploy" }] },
|
|
45
|
+
{
|
|
46
|
+
id: "a5",
|
|
47
|
+
role: "assistant",
|
|
48
|
+
parts: [
|
|
49
|
+
{
|
|
50
|
+
type: "tool-deploy",
|
|
51
|
+
toolCallId: "call-5",
|
|
52
|
+
state: "approval-requested",
|
|
53
|
+
input: { target: "prod" },
|
|
54
|
+
approval: { id: "approval-5" },
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{ id: "u6", role: "user", parts: [{ type: "text", text: "thanks" }] },
|
|
59
|
+
{ id: "a7", role: "assistant", parts: [{ type: "text", text: "done" }] },
|
|
60
|
+
];
|
|
61
|
+
const ALL_IDS = messages.map((message) => message.id);
|
|
62
|
+
|
|
63
|
+
// Keyed by every metadata field, so adding one fails typecheck until its
|
|
64
|
+
// invalidation is pinned here.
|
|
65
|
+
const fieldChanges: {
|
|
66
|
+
[K in keyof Required<AISDKMessageConverterMetadata>]: [
|
|
67
|
+
value: AISDKMessageConverterMetadata[K],
|
|
68
|
+
reconverts: readonly string[],
|
|
69
|
+
];
|
|
70
|
+
} = {
|
|
71
|
+
toolStatuses: [
|
|
72
|
+
{
|
|
73
|
+
"call-3": {
|
|
74
|
+
type: "interrupt",
|
|
75
|
+
payload: { type: "human", payload: { question: "unit?" } },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
["a3"],
|
|
79
|
+
],
|
|
80
|
+
toolArtifacts: [new Map([["call-3", { chart: true }]]), ["a3"]],
|
|
81
|
+
toolInteractions: [
|
|
82
|
+
new Map([
|
|
83
|
+
[
|
|
84
|
+
"call-3",
|
|
85
|
+
{
|
|
86
|
+
entries: [{ type: "progress", payload: { step: 1 }, timestamp: 1 }],
|
|
87
|
+
} as unknown as Unstable_ToolInteractionLog,
|
|
88
|
+
],
|
|
89
|
+
]),
|
|
90
|
+
["a3"],
|
|
91
|
+
],
|
|
92
|
+
toolApprovalResponses: [
|
|
93
|
+
new Map([["approval-5", { approvalId: "approval-5", approved: true }]]),
|
|
94
|
+
["a5"],
|
|
95
|
+
],
|
|
96
|
+
cancelledToolApprovalIds: [new Set(["approval-5"]), ["a5"]],
|
|
97
|
+
cancelledStatusMessageIds: [new Set(["a3"]), ["a3"]],
|
|
98
|
+
messageTiming: [
|
|
99
|
+
{
|
|
100
|
+
a7: {
|
|
101
|
+
streamStartTime: 1,
|
|
102
|
+
totalStreamTime: 5,
|
|
103
|
+
totalChunks: 3,
|
|
104
|
+
toolCallCount: 1,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
["a7"],
|
|
108
|
+
],
|
|
109
|
+
optimisticMessageId: ["a7", ["a7"]],
|
|
110
|
+
supportsRichToolApprovalResponses: [true, ["a5"]],
|
|
111
|
+
createdAtCache: [new Map(), ALL_IDS],
|
|
112
|
+
toolLastInputCache: [new Map(), ALL_IDS],
|
|
113
|
+
toolArgsKeyOrderCache: [new Map(), ALL_IDS],
|
|
114
|
+
toolArgsTextCache: [new WeakMap(), ALL_IDS],
|
|
115
|
+
mcpAppMetadataCache: [new Map(), ALL_IDS],
|
|
116
|
+
// Core applies these after the callback, so no message re-converts.
|
|
117
|
+
error: ["boom", []],
|
|
118
|
+
cancelledMessageIds: [new Set(["a7"]), []],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const scenarios: {
|
|
122
|
+
name: string;
|
|
123
|
+
patch: Partial<AISDKMessageConverterMetadata>;
|
|
124
|
+
reconverts: readonly string[];
|
|
125
|
+
}[] = [
|
|
126
|
+
...Object.entries(fieldChanges).map(([name, [value, reconverts]]) => ({
|
|
127
|
+
name,
|
|
128
|
+
patch: { [name]: value },
|
|
129
|
+
reconverts,
|
|
130
|
+
})),
|
|
131
|
+
{
|
|
132
|
+
name: "an unrelated tool status",
|
|
133
|
+
patch: { toolStatuses: { "call-unknown": { type: "executing" } } },
|
|
134
|
+
reconverts: [],
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
describe("createAISDKMetadataKey", () => {
|
|
139
|
+
it.each(scenarios)(
|
|
140
|
+
"re-converts only the affected messages when $name changes and reverts",
|
|
141
|
+
({ patch, reconverts }) => {
|
|
142
|
+
const callback = vi.fn(convertAISDKMessage);
|
|
143
|
+
const getMetadataKey = createAISDKMetadataKey();
|
|
144
|
+
// A shared createdAt cache keeps the uncached comparison's dates equal.
|
|
145
|
+
const base: AISDKMessageConverterMetadata = { createdAtCache: new Map() };
|
|
146
|
+
const metadataSource = shallowRef(base);
|
|
147
|
+
let converted!: ReturnType<typeof useExternalMessageConverter<UIMessage>>;
|
|
148
|
+
const view = render(
|
|
149
|
+
h(
|
|
150
|
+
defineComponent({
|
|
151
|
+
setup() {
|
|
152
|
+
converted = useExternalMessageConverter(() => ({
|
|
153
|
+
callback,
|
|
154
|
+
messages,
|
|
155
|
+
isRunning: false,
|
|
156
|
+
metadata: metadataSource.value,
|
|
157
|
+
getMetadataKey,
|
|
158
|
+
}));
|
|
159
|
+
return () => null;
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
void converted.value;
|
|
165
|
+
expect(callback).toHaveBeenCalledTimes(messages.length);
|
|
166
|
+
|
|
167
|
+
for (const metadata of [{ ...base, ...patch }, { ...base }]) {
|
|
168
|
+
callback.mockClear();
|
|
169
|
+
metadataSource.value = metadata;
|
|
170
|
+
const result = converted.value;
|
|
171
|
+
|
|
172
|
+
expect(callback.mock.calls.map(([message]) => message.id)).toEqual(
|
|
173
|
+
reconverts,
|
|
174
|
+
);
|
|
175
|
+
expect([...result]).toEqual(
|
|
176
|
+
AISDKMessageConverter.toThreadMessages(messages, false, metadata),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
view.unmount();
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
});
|