-
![]()
-
+
+
-
-
-
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/pages/agent/[id]/topic/[topicId].vue b/app/pages/agent/[id]/topic/[topicId].vue
index 26f1556..e47b0de 100644
--- a/app/pages/agent/[id]/topic/[topicId].vue
+++ b/app/pages/agent/[id]/topic/[topicId].vue
@@ -54,6 +54,27 @@ const submitMessage = async (message: BaseMessage, model: ModelWithProvider | nu
startGeneration(model);
};
+const handleAddMessage = async (message: BaseMessage, role: 'user' | 'assistant', _model: ModelWithProvider | null) => {
+ inputValue.value = { content: '', fileIds: [] };
+
+ const res = await sendMessage(message, async () => {
+ await nextTick();
+ setTimeout(() => {
+ scrollToBottom('instant');
+ });
+ }, role);
+ if (!res.ok) {
+ console.error('Failed to add message:', res.error);
+ const chatInput = document.getElementById('chat') as HTMLInputElement | null;
+ if (chatInput) {
+ inputValue.value = message;
+ nextTick(() => {
+ chatInput.focus();
+ });
+ }
+ }
+};
+
const handleRegenerate = async (message: Message) => {
if (!agent.value!.defaultModelId) {
console.error('No model selected');
@@ -292,8 +313,8 @@ console.log("full page render took", Date.now() - rootStart);
diff --git a/bun.lock b/bun.lock
index 1035329..d345fb9 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "veridian",
@@ -14,17 +15,17 @@
"@ai-sdk/openai-compatible": "^2.0.41",
"@aws-sdk/client-s3": "^3.1028.0",
"@aws-sdk/s3-request-presigner": "^3.1028.0",
- "@better-auth/drizzle-adapter": "https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@6913",
+ "@better-auth/drizzle-adapter": "https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@9489",
"@floating-ui/vue": "^1.1.11",
"@iconify-json/mynaui": "^1.2.17",
"@nuxt/fonts": "0.14.0",
"@nuxt/hints": "1.0.0-alpha.5",
"@nuxt/icon": "2.2.0",
- "@openrouter/ai-sdk-provider": "^2.5.1",
+ "@openrouter/ai-sdk-provider": "^2.9.0",
"@pydantic/monty": "^0.0.17",
"@sentry/nuxt": "^10.48.0",
"@tanstack/vue-virtual": "^3.13.23",
- "ai": "^6.0.156",
+ "ai": "^6.0.182",
"ai-sdk-ollama": "^3.8.3",
"better-auth": "^1.6.2",
"comlink": "^4.4.2",
@@ -76,7 +77,7 @@
"@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.30", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-j3fe/6lUUkHPD/51OgMXN9UD7p1QSQEAlroIinmb3MhJ1s+O0MnqdRa30IM7dRHafNp0FQ9X4YpobY85iMknUQ=="],
- "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="],
+ "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.114", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MqkZ5sd+qiq6RgIxELkoFQXg2/JwK+WCMaot7U+rtrZpWJl3fSyYvc28SC03b256o4F7OXjQtdjTqs81B2w+dA=="],
"@ai-sdk/google": ["@ai-sdk/google@3.0.61", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jEKU1Mjcy5CoicejdJQIzM0ntYwyXR8vtYgAZYriKaOuLAiAhiiU538++fGU3CC9HJH/mL1OfsCwMM3gFiCNsw=="],
@@ -264,9 +265,9 @@
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
- "@better-auth/core": ["@better-auth/core@1.4.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.3.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.8", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-q+awYgC7nkLEBdx2sW0iJjkzgSHlIxGnOpsN1r/O1+a4m7osJNHtfK2mKJSL1I+GfNyIlxJF8WvD/NLuYMpmcg=="],
+ "@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
- "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@6913", { "peerDependencies": { "@better-auth/core": "1.5.0-beta.13", "@better-auth/utils": "^0.3.0", "drizzle-orm": ">=0.41.0", "prettier": "^3.7.4" } }],
+ "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@9489", { "peerDependencies": { "@better-auth/core": "^1.7.0-beta.3", "@better-auth/utils": "0.4.0", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "optionalPeers": ["drizzle-orm"] }],
"@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "kysely": "^0.27.0 || ^0.28.0" }, "optionalPeers": ["kysely"] }, "sha512-YMMm75jek/MNCAFWTAaq/U3VPmFnrwZW4NhBjjAwruHQJEIrSZZaOaUEXuUpFRRBhWqg7OOltQcHMwU/45CkuA=="],
@@ -278,7 +279,7 @@
"@better-auth/telemetry": ["@better-auth/telemetry@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21" } }, "sha512-o4gHKXqizUxVUUYChZZTowLEzdsz3ViBE/fKFzfHqNFUnF+aVt8QsbLSfipq1WpTIXyJVT/SnH0hgSdWxdssbQ=="],
- "@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="],
+ "@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
@@ -456,9 +457,9 @@
"@nuxt/vite-builder": ["@nuxt/vite-builder@4.4.2", "", { "dependencies": { "@nuxt/kit": "4.4.2", "@rollup/plugin-replace": "^6.0.3", "@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue-jsx": "^5.1.4", "autoprefixer": "^10.4.27", "consola": "^3.4.2", "cssnano": "^7.1.3", "defu": "^6.1.4", "escape-string-regexp": "^5.0.0", "exsolve": "^1.0.8", "get-port-please": "^3.2.0", "jiti": "^2.6.1", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.1", "mocked-exports": "^0.1.1", "nypm": "^0.6.5", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "postcss": "^8.5.8", "seroval": "^1.5.1", "std-env": "^4.0.0", "ufo": "^1.6.3", "unenv": "^2.0.0-rc.24", "vite": "^7.3.1", "vite-node": "^5.3.0", "vite-plugin-checker": "^0.12.0", "vue-bundle-renderer": "^2.2.0" }, "peerDependencies": { "@babel/plugin-proposal-decorators": "^7.25.0", "@babel/plugin-syntax-jsx": "^7.25.0", "nuxt": "4.4.2", "rolldown": "^1.0.0-beta.38", "rollup-plugin-visualizer": "^6.0.0 || ^7.0.1", "vue": "^3.3.4" }, "optionalPeers": ["@babel/plugin-proposal-decorators", "@babel/plugin-syntax-jsx", "rolldown", "rollup-plugin-visualizer"] }, "sha512-fJaIwMA8ID6BU5EqmoDvnhq4qYDJeWjdHk4jfqy8D3Nm7CoUW0BvX7Ee92XoO05rtUiClGlk/NQ1Ii8hs3ZIbw=="],
- "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="],
+ "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.9.0", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Seva+NCa0WUQnJIUE5GzHsUv1WTIeyqwz0ELl2VtS6NP+eF+77yCXGFVOMbvoCM7QMjlnhv7931e89R+8pJdcQ=="],
- "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
+ "@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="],
@@ -1076,7 +1077,7 @@
"@vercel/nft": ["@vercel/nft@0.27.10", "", { "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0-rc.0", "@rollup/pluginutils": "^5.1.3", "acorn": "^8.6.0", "acorn-import-attributes": "^1.9.5", "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", "resolve-from": "^5.0.0" }, "bin": { "nft": "out/cli.js" } }, "sha512-zbaF9Wp/NsZtKLE4uVmL3FyfFwlpDyuymQM1kPbeT0mVOHKDQQNjnnfslB3REg3oZprmNFJuh3pkHBk2qAaizg=="],
- "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
+ "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
@@ -1170,7 +1171,7 @@
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
- "ai": ["ai@6.0.156", "", { "dependencies": { "@ai-sdk/gateway": "3.0.95", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uyi/5LYbugHQxZsR2PeAFOZEL4WqKkzZw4pv0nQvvdgxgVOsM7snOmGrYkp5fShxH/vnd08SXvHCVTX7oUW7xQ=="],
+ "ai": ["ai@6.0.182", "", { "dependencies": { "@ai-sdk/gateway": "3.0.114", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ooJdziFjYrYRcsCx107roqA8gDTI3P82nUfroNWIhVvwrkYzEN3W1l50YK+XNqkUew8AiimaW0/SLBewRXMuHQ=="],
"ai-sdk-ollama": ["ai-sdk-ollama@3.8.3", "", { "dependencies": { "@ai-sdk/provider": "^3.0.8", "@ai-sdk/provider-utils": "^4.0.23", "jsonrepair": "^3.13.3", "ollama": "^0.6.3" }, "peerDependencies": { "ai": "^6.0.154" } }, "sha512-KId/S++eb0CgTPFTtHzCGCrO73kXZLK+hyyZx5k8LVqU2XOEHYKVbIwDiQ+hm3okHjnsGehn4zR4QNm14SUM3Q=="],
@@ -2140,8 +2141,6 @@
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
- "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
-
"pretty-bytes": ["pretty-bytes@7.1.0", "", {}, "sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw=="],
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
@@ -2566,6 +2565,10 @@
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
+ "@ai-sdk/gateway/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
+
+ "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
+
"@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="],
"@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="],
@@ -2602,28 +2605,6 @@
"@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
- "@better-auth/core/better-call": ["better-call@1.1.8", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw=="],
-
- "@better-auth/kysely-adapter/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
-
- "@better-auth/kysely-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
-
- "@better-auth/memory-adapter/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
-
- "@better-auth/memory-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
-
- "@better-auth/mongo-adapter/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
-
- "@better-auth/mongo-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
-
- "@better-auth/prisma-adapter/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
-
- "@better-auth/prisma-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
-
- "@better-auth/telemetry/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
-
- "@better-auth/telemetry/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
-
"@dxup/nuxt/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"@fastify/otel/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "import-in-the-middle": "^2.0.6", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg=="],
@@ -2680,8 +2661,6 @@
"@nuxt/vite-builder/std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
- "@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
-
"@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="],
"@opentelemetry/sql-common/@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="],
@@ -2712,10 +2691,6 @@
"@sentry/cli/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
- "@sentry/cloudflare/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
-
- "@sentry/node/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
-
"@sentry/nuxt/@nuxt/kit": ["@nuxt/kit@3.21.1", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg=="],
"@types/connect/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
@@ -2786,6 +2761,10 @@
"@vue/server-renderer/@vue/shared": ["@vue/shared@3.6.0-beta.10", "", {}, "sha512-13JUfIAd06F+IBnObE8mExDAMOknPIBjPBUN2JeemmuQwj5i20GduCLbHLVbxSkpFD0RGH4z2mOxUUdD+8M/Aw=="],
+ "ai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
+
+ "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
+
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
@@ -2798,14 +2777,8 @@
"ast-walker-scope/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
- "better-auth/@better-auth/core": ["@better-auth/core@1.6.2", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nBftDp+eN1fwXor1O4KQorCXa0tJNDgpab7O1z4NcWUU+3faDpdzqLn5mbXZer2E8ZD4VhjqOfYZ041xnBF5NA=="],
-
"better-auth/@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.2", "", { "peerDependencies": { "@better-auth/core": "^1.6.2", "@better-auth/utils": "0.4.0", "drizzle-orm": ">=0.41.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-KawrNNuhgmpcc5PgLs6HesMckxCscz5J+BQ99iRmU1cLzG/A87IcydrmYtep+K8WHPN0HmZ/i4z/nOBCtxE2qA=="],
- "better-auth/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
-
- "better-call/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="],
-
"better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
"browserslist/caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
@@ -2992,6 +2965,8 @@
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "@ai-sdk/gateway/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
+
"@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
"@aws-crypto/crc32c/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
@@ -3012,10 +2987,6 @@
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
- "@better-auth/core/better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
-
- "@better-auth/core/better-call/set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
-
"@dxup/nuxt/tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"@fastify/otel/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.212.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg=="],
@@ -3168,6 +3139,8 @@
"@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
+ "ai/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
+
"archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
@@ -3496,8 +3469,6 @@
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
- "@fastify/otel/@opentelemetry/instrumentation/@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
-
"@nuxt/kit/mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"@nuxt/kit/mlly/pkg-types/mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
@@ -3522,6 +3493,8 @@
"@opentelemetry/instrumentation-pg/@types/pg/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+ "@prisma/instrumentation/@opentelemetry/instrumentation/@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
+
"@types/pg-pool/@types/pg/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"@unocss/transformer-attributify-jsx/oxc-parser/@oxc-parser/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
diff --git a/package.json b/package.json
index d669c72..f0886ee 100644
--- a/package.json
+++ b/package.json
@@ -20,17 +20,17 @@
"@ai-sdk/openai-compatible": "^2.0.41",
"@aws-sdk/client-s3": "^3.1028.0",
"@aws-sdk/s3-request-presigner": "^3.1028.0",
- "@better-auth/drizzle-adapter": "https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@6913",
+ "@better-auth/drizzle-adapter": "https://pkg.pr.new/better-auth/better-auth/@better-auth/drizzle-adapter@9489",
"@floating-ui/vue": "^1.1.11",
"@iconify-json/mynaui": "^1.2.17",
"@nuxt/fonts": "0.14.0",
"@nuxt/hints": "1.0.0-alpha.5",
"@nuxt/icon": "2.2.0",
- "@openrouter/ai-sdk-provider": "^2.5.1",
+ "@openrouter/ai-sdk-provider": "^2.9.0",
"@pydantic/monty": "^0.0.17",
"@sentry/nuxt": "^10.48.0",
"@tanstack/vue-virtual": "^3.13.23",
- "ai": "^6.0.156",
+ "ai": "^6.0.182",
"ai-sdk-ollama": "^3.8.3",
"better-auth": "^1.6.2",
"comlink": "^4.4.2",
@@ -75,4 +75,4 @@
"@vercel/nft": "^0.27.4",
"vite": "8.0.0-beta.15"
}
-}
\ No newline at end of file
+}
diff --git a/server/api/files/[...key].get.ts b/server/api/files/[...key].get.ts
index 2571d3a..6b1a972 100644
--- a/server/api/files/[...key].get.ts
+++ b/server/api/files/[...key].get.ts
@@ -1,4 +1,6 @@
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
+import { auth } from "~~/lib/auth";
+import { verifyFileToken } from "~~/server/utils/file-token";
export default defineEventHandler(async (event) => {
const key = getRouterParam(event, 'key');
@@ -6,6 +8,34 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 400, statusMessage: 'Missing file key' });
}
+ const query = getQuery(event);
+ const exp = query.exp ? Number(query.exp) : undefined;
+ const sig = query.sig as string | undefined;
+
+ let authorized = false;
+
+ // Path 1: HMAC token (for AI model access)
+ if (exp && sig && process.env.BETTER_AUTH_SECRET) {
+ authorized = verifyFileToken(key, exp, sig, process.env.BETTER_AUTH_SECRET);
+ }
+
+ // Path 2: Session auth (for client-side access)
+ if (!authorized) {
+ try {
+ const sessionData = await auth.api.getSession(event);
+ if (sessionData) {
+ event.context.user = sessionData.user;
+ authorized = true;
+ }
+ } catch {
+ // No valid session
+ }
+ }
+
+ if (!authorized) {
+ throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
+ }
+
const config = useRuntimeConfig();
const s3 = new S3Client({
@@ -24,14 +54,12 @@ export default defineEventHandler(async (event) => {
Key: key,
}));
- // 3. Set the correct headers so the browser knows what it's receiving
setHeaders(event, {
'Content-Type': response.ContentType || 'application/octet-stream',
'Content-Length': response.ContentLength?.toString() || '',
- 'Cache-Control': 'public, max-age=3600', // Optional: cache for 1 hour
+ 'Cache-Control': 'public, max-age=3600',
});
- // 4. Return the body as a stream directly to the client
return response.Body;
} catch (error: any) {
if (error.name === 'NoSuchKey') {
diff --git a/server/api/topic/[topicId]/auto-rename/index.post.ts b/server/api/topic/[topicId]/auto-rename/index.post.ts
index 632c013..d3f7e7c 100644
--- a/server/api/topic/[topicId]/auto-rename/index.post.ts
+++ b/server/api/topic/[topicId]/auto-rename/index.post.ts
@@ -44,6 +44,11 @@ export default defineEventHandler(async (event) => {
},
with: {
parts: true,
+ attachments: {
+ with: {
+ file: true,
+ }
+ },
},
});
@@ -160,8 +165,18 @@ export default defineEventHandler(async (event) => {
});
}
+ let prompt = firstMessage.content!;
+
+ if (firstMessage.attachments.length > 0) {
+ for (const attachment of firstMessage.attachments) {
+ if (attachment.file.mimeType.startsWith('image/')) {
+ prompt += `\n`;
+ }
+ }
+ }
+
const abortController = addPendingRename(topicId);
- event.waitUntil(autoRename(topicId, abortController, { gateway: gateway.gateway, model }, gateway.textTransformer, firstMessage.content!, userId));
+ event.waitUntil(autoRename(topicId, abortController, { gateway: gateway.gateway, model }, gateway.textTransformer, prompt, userId));
return {
ok: true,
diff --git a/server/api/topic/[topicId]/chat/index.post.ts b/server/api/topic/[topicId]/chat/index.post.ts
index 44c0574..c26a042 100644
--- a/server/api/topic/[topicId]/chat/index.post.ts
+++ b/server/api/topic/[topicId]/chat/index.post.ts
@@ -3,7 +3,7 @@ import * as z from 'zod';
import { type MessageEntity } from '~/composables/useChat';
import { promises as fs } from 'fs';
import { glob } from 'glob';
-import { isLoopFinished, type ModelMessage, rerank, type RerankingModel, streamText, type StreamTextTransform, Tool, tool } from "ai";
+import { isLoopFinished, type ModelMessage, rerank, type RerankingModel, streamText, type StreamTextTransform, type Tool, tool } from "ai";
import { generations, messageParts, messages, toolCalls, ToolCallType } from "~~/drizzle/schema";
import { topicEvents } from "~~/server/utils/events";
import { nanoid } from "nanoid";
@@ -13,6 +13,7 @@ import { eq } from "drizzle-orm";
import { buildFocusedMessageTree, buildMessageTree, marshallMessages } from "~~/utils/message";
import path from "path";
import { isRerankingProvider } from "~~/server/utils/ai-provider";
+import { generateFileToken } from "~~/server/utils/file-token";
export default defineEventHandler(async (event) => {
await protectRoute(event);
@@ -237,7 +238,7 @@ export default defineEventHandler(async (event) => {
}
agentmessage.generationId = generation.id;
- // @ts-ignore
+ // @ts-ignore - doesnt exist on the type but yeah it does now
agentmessage.generation = generation;
return agentmessage;
@@ -311,7 +312,13 @@ export default defineEventHandler(async (event) => {
}
}
- const topicMessages = marshallMessages(topic.agent, buildFocusedMessageTree(topicMessageTree));
+ const fileTokenSecret = process.env.BETTER_AUTH_SECRET!;
+ const topicMessages = marshallMessages(topic.agent, buildFocusedMessageTree(topicMessageTree), {
+ signFileUrl: (fileKey) => {
+ const { exp, sig } = generateFileToken(fileKey, fileTokenSecret);
+ return `exp=${exp}&sig=${sig}`;
+ },
+ });
if (topicMessages.ok === false) {
throw createError({
statusCode: 500,
@@ -353,7 +360,7 @@ const formatPartId = (partType: string, existingId: string) => {
};
const formatToolCallId = (nativeId: string) => {
- return `veridian__tool-${nativeId}-${nanoid()}`;
+ return `veridian__tool-${nativeId.slice(0, 16)}-${nanoid()}`;
};
const evalPython = async (code: string) => {
@@ -528,10 +535,20 @@ const { listDirectoryTool, globTool, readFileTool, readFilesTool, fetchUrlTool,
content: z.string(),
}),
execute: async ({ url }) => {
- const response = await fetch(url);
- const content = await response.text();
+ const response = await fetch('https://api.firecrawl.dev/v2/scrape', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + process.env.FIRECRAWL_API_KEY,
+ },
+ body: JSON.stringify({
+ url,
+ }),
+ });
+ const content = await response.json();
+ console.log(content);
return {
- content,
+ content: content.data.markdown,
};
},
}),
@@ -629,7 +646,7 @@ async function generateResponse(
glob: globTool,
readFile: readFileTool,
readFiles: readFilesTool,
- // fetchUrl: fetchUrlTool,
+ fetchUrl: fetchUrlTool,
python: pythonTool,
bash: bashTool,
};
@@ -657,13 +674,14 @@ async function generateResponse(
const response = streamText({
model: model.gateway(model.model.externalId),
messages,
+ allowSystemInMessages: true,
providerOptions: {
openrouter: {
debug: {
echo_upstream_body: true,
},
user: userId,
- }
+ },
},
experimental_transform: streamTransoforms,
stopWhen: isLoopFinished(),
@@ -959,7 +977,7 @@ async function generateResponse(
throw new Error('Failed to insert message part');
}
- // @ts-ignore
+ // @ts-ignore - doesnt exist on the type but yeah it does now
part.toolCall = toolCall;
await topicEvents.emit(topicId, {
@@ -1005,6 +1023,11 @@ async function generateResponse(
},
})
.where(eq(toolCalls.id, dbToolCallId));
+ if (token.providerMetadata) await db.update(messageParts)
+ .set({
+ providerOptions: token.providerMetadata,
+ })
+ .where(eq(messageParts.toolCallId, dbToolCallId));
await topicEvents.emit(topicId, {
type: 'tool-call-delta',
@@ -1049,6 +1072,7 @@ async function generateResponse(
topicId,
messageId: message.id,
toolCallId: dbToolCallId,
+ providerOptions: token.providerMetadata,
type: 'tool-call',
content: null,
finished: false,
@@ -1060,7 +1084,7 @@ async function generateResponse(
throw new Error('Failed to insert message part');
}
- // @ts-ignore
+ // @ts-ignore - doesnt exist on the type but yeah it does now
part.toolCall = toolCall;
await topicEvents.emit(topicId, {
@@ -1097,6 +1121,11 @@ async function generateResponse(
status: 'failed',
error: { type: ToolCallType.Text, value: 'Tool returned invalid output' }
}).where(eq(toolCalls.id, dbToolCallId));
+ if (token.providerMetadata) await db.update(messageParts)
+ .set({
+ providerOptions: token.providerMetadata,
+ })
+ .where(eq(messageParts.toolCallId, dbToolCallId));
await topicEvents.emit(topicId, {
type: 'tool-call-delta',
@@ -1125,6 +1154,11 @@ async function generateResponse(
value: outputValue,
},
}).where(eq(toolCalls.id, dbToolCallId));
+ if (token.providerMetadata) await db.update(messageParts)
+ .set({
+ providerOptions: token.providerMetadata,
+ })
+ .where(eq(messageParts.toolCallId, dbToolCallId));
await topicEvents.emit(topicId, {
type: 'tool-call-delta',
@@ -1176,6 +1210,11 @@ async function generateResponse(
value: outputValue as string,
}
}).where(eq(toolCalls.id, existingDbToolCallId));
+ if (token.providerMetadata) await db.update(messageParts)
+ .set({
+ providerOptions: token.providerMetadata,
+ })
+ .where(eq(messageParts.toolCallId, existingDbToolCallId));
await topicEvents.emit(topicId, {
type: 'tool-call-delta',
@@ -1220,6 +1259,7 @@ async function generateResponse(
topicId,
messageId: message.id,
toolCallId: dbToolCallId,
+ providerOptions: token.providerMetadata,
type: 'tool-call',
content: null,
finished: false,
@@ -1250,12 +1290,8 @@ async function generateResponse(
case 'finish': {
let tps;
- if (ttft !== undefined && token.totalUsage.outputTokens !== undefined) {
- const tokenStreamStart = requestStart! + ttft;
- // this is the *real* request duration, excluding the
- // TTFT
- const requestDuration = performance.now() - tokenStreamStart;
-
+ if (requestStart !== undefined && token.totalUsage.outputTokens !== undefined) {
+ const requestDuration = performance.now() - requestStart;
tps = token.totalUsage.outputTokens / (requestDuration / 1000);
}
diff --git a/server/api/topic/[topicId]/message.post.ts b/server/api/topic/[topicId]/message.post.ts
index 052ec07..5454192 100644
--- a/server/api/topic/[topicId]/message.post.ts
+++ b/server/api/topic/[topicId]/message.post.ts
@@ -1,6 +1,6 @@
import { modelMessageSchema } from 'ai';
import * as z from 'zod';
-import { attachments, messages } from '~~/drizzle/schema';
+import { attachments, messageParts, messages } from '~~/drizzle/schema';
import { db } from '~~/server/lib/db';
export default defineEventHandler(async (event) => {
@@ -9,6 +9,21 @@ export default defineEventHandler(async (event) => {
const topicId = getRouterParam(event, 'topicId')!;
const userId = event.context.user!.id;
+ const topic = await db.query.topics.findFirst({
+ where: {
+ id: topicId,
+ userId,
+ },
+ });
+
+ if (!topic) {
+ throw createError({
+ statusCode: 404,
+ statusMessage: 'Not Found',
+ message: 'Topic not found',
+ });
+ }
+
const result = await readValidatedBody(event, z.object({
message: z.intersection(
z.object({
@@ -19,6 +34,7 @@ export default defineEventHandler(async (event) => {
),
}).safeParse);
if (!result.success) {
+ console.log(result.error);
throw createError({
statusCode: 400,
statusMessage: 'Bad Request',
@@ -28,49 +44,110 @@ export default defineEventHandler(async (event) => {
const { message } = result.data;
- await db.insert(messages).values({
- // @ts-ignore - drizzle bug
- id: message.id,
- userId,
- topicId,
- parentMessageId: null,
- generationId: null,
- role: message.role,
- content: message.content,
- });
-
- for (const fileId of message.fileIds || []) {
- await db.insert(attachments).values({
- userId,
- topicId,
- messageId: message.id,
- fileId,
- });
- }
-
- const usermessage = await db.query.messages.findFirst({
- where: {
- id: message.id,
- },
- with: {
- attachments: {
- with: {
- file: true,
- }
- }
- },
- });
-
- if (!usermessage) {
+ if (['user', 'assistant'].includes(message.role) === false) {
throw createError({
- statusCode: 500,
- statusMessage: 'Failed to insert message',
- message: 'Failed to insert message',
+ statusCode: 400,
+ statusMessage: 'Bad Request',
+ message: 'Invalid role',
});
}
- topicEvents.emit(topicId, { type: 'MESSAGE_CREATED', payload: usermessage });
- return {
- ok: true
- };
+ switch (message.role) {
+ case 'user': {
+ await db.insert(messages).values({
+ // @ts-ignore - drizzle bug
+ id: message.id,
+ userId,
+ topicId,
+ parentMessageId: null,
+ generationId: null,
+ role: message.role,
+ content: message.content,
+ });
+
+ for (const fileId of message.fileIds || []) {
+ await db.insert(attachments).values({
+ userId,
+ topicId,
+ messageId: message.id,
+ fileId,
+ });
+ }
+
+ const usermessage = await db.query.messages.findFirst({
+ where: {
+ id: message.id,
+ },
+ with: {
+ attachments: {
+ with: {
+ file: true,
+ }
+ }
+ },
+ });
+
+ if (!usermessage) {
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Failed to insert message',
+ message: 'Failed to insert message',
+ });
+ }
+ topicEvents.emit(topicId, { type: 'MESSAGE_CREATED', payload: usermessage });
+
+ return {
+ ok: true
+ };
+ }
+ case 'assistant': {
+ const [dbmessage] = await db.insert(messages).values({
+ // @ts-ignore - drizzle bug
+ id: message.id,
+ userId,
+ topicId,
+ parentMessageId: null,
+ generationId: null,
+ role: message.role,
+ content: message.content,
+ }).returning();
+
+ if (!dbmessage) {
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Failed to insert message',
+ message: 'Failed to insert message',
+ });
+ }
+
+ const [part] = await db.insert(messageParts).values({
+ userId,
+ topicId,
+ messageId: dbmessage.id,
+ type: 'text',
+ content: message.content,
+ providerOptions: null,
+ finished: true,
+ createdAt: new Date(),
+ lastUpdatedAt: new Date(),
+ }).returning();
+
+ // TODO: transaction
+ if (!part) {
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Failed to insert message part',
+ message: 'Failed to insert message part',
+ });
+ }
+
+ topicEvents.emit(topicId, { type: 'MESSAGE_CREATED', payload: dbmessage });
+ topicEvents.emit(topicId, { type: 'text-start', payload: { messageId: dbmessage.id, part } });
+ topicEvents.emit(topicId, { type: 'text-end', payload: { messageId: dbmessage.id, partId: part.id, lastUpdatedAt: new Date(), content: message.content } });
+
+ return {
+ ok: true
+ };
+ }
+ }
})
\ No newline at end of file
diff --git a/server/api/upload/presigned.post.ts b/server/api/upload/presigned.post.ts
index 7b085f5..e5b1927 100644
--- a/server/api/upload/presigned.post.ts
+++ b/server/api/upload/presigned.post.ts
@@ -39,7 +39,22 @@ export default defineEventHandler(async (event) => {
responseChecksumValidation: 'WHEN_REQUIRED',
});
- const key = `veridian__uploads/${Date.now()}-${file.name.replace(/[^a-zA-Z0-9.]/g, '_')}-${event.context.user!.id}`
+ const fileParts = file.name.split('.');
+ if (fileParts.length < 2) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'Invalid file name',
+ data: {
+ code: 'INVALID_FILE_NAME',
+ ok: false,
+ }
+ });
+ }
+
+ const fileExt = fileParts.pop()!;
+ const fileName = fileParts.join('.').replace(/[^a-zA-Z0-9.]/g, '_');
+
+ const key = `veridian__uploads/${Date.now()}-${fileName}-${event.context.user!.id}.${fileExt}`;
const command = new PutObjectCommand({
ACL: 'public-read',
diff --git a/server/utils/file-token.ts b/server/utils/file-token.ts
new file mode 100644
index 0000000..54e8612
--- /dev/null
+++ b/server/utils/file-token.ts
@@ -0,0 +1,37 @@
+import { createHmac, timingSafeEqual } from 'crypto';
+
+const DEFAULT_EXPIRY_MS = 30 * 60 * 1000; // 30 minutes
+
+export function generateFileToken(
+ fileKey: string,
+ secret: string,
+ expiresInMs = DEFAULT_EXPIRY_MS,
+): { exp: number; sig: string } {
+ const exp = Date.now() + expiresInMs;
+ const payload = `${fileKey}:${exp}`;
+ const sig = createHmac('sha256', secret).update(payload).digest('hex');
+ return { exp, sig };
+}
+
+export function verifyFileToken(
+ fileKey: string,
+ exp: number,
+ sig: string,
+ secret: string,
+): boolean {
+ if (Date.now() > exp) {
+ return false;
+ }
+
+ const payload = `${fileKey}:${exp}`;
+ const expected = createHmac('sha256', secret).update(payload).digest('hex');
+
+ const sigBuffer = Buffer.from(sig, 'hex');
+ const expectedBuffer = Buffer.from(expected, 'hex');
+
+ if (sigBuffer.length !== expectedBuffer.length) {
+ return false;
+ }
+
+ return timingSafeEqual(sigBuffer, expectedBuffer);
+}
diff --git a/server/utils/longcat.ts b/server/utils/longcat.ts
deleted file mode 100644
index 398a6d2..0000000
--- a/server/utils/longcat.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import { type ToolSet, type TextStreamPart, type ToolCallPart } from 'ai';
-
-export function createLongcatTransformer
(): (options: {
- tools: TOOLS;
- stopStream: () => void;
-}) => TransformStream, TextStreamPart> {
- let buffer = '';
- let hasToolCallInStep = false;
- let lastChunkId: string | undefined;
- let lastChunkType: 'text' | 'reasoning' | undefined;
- let step = 0;
-
- return (_opts) => {
- return new TransformStream, TextStreamPart>({
- transform(chunk, controller) {
- if (chunk.type === 'finish-step' || chunk.type === 'finish') {
- step++;
-
- if (hasToolCallInStep) {
- // We clone the chunk and overwrite the finishReason.
- // This tricks the SDK into thinking the model requested a tool natively.
- const modifiedChunk = {
- ...chunk,
- finishReason: 'tool-calls' as const,
- };
-
- // Reset for the next potential step
- if (chunk.type === 'finish-step') {
- hasToolCallInStep = false;
- }
-
- controller.enqueue(modifiedChunk);
- return;
- }
- }
-
- if (chunk.type === 'text-start' || chunk.type === 'reasoning-start') {
- lastChunkId = chunk.id;
- lastChunkType = chunk.type.split('-')[1] as 'text' | 'reasoning';
- }
-
- // We only care about text chunks
- if (chunk.type !== 'text-delta' && chunk.type !== 'reasoning-delta') {
- controller.enqueue(chunk);
- return;
- }
-
- buffer += chunk.text;
-
- // Check if we have a full tool call in the buffer
- const pattern = /([\s\S]*?)<\/longcat_tool_call>/g;
- let lastIndex = 0;
- let match;
-
- while ((match = pattern.exec(buffer)) !== null) {
- console.log("longcat tool call found at index", match.index);
-
- // 1. Enqueue any text that appeared BEFORE the tool call
- const textBefore = buffer.substring(lastIndex, match.index);
- if (textBefore) {
- controller.enqueue({ type: chunk.type, text: textBefore, id: lastChunkId ?? chunk.type.includes('reasoning') ? `reasoning-${step}` : `text-${step}` });
- }
-
- // 2. Parse the XML content
- const content = match[1]!.trim();
- const toolNameMatch = content.match(/^([^\s<]+)/);
-
- if (toolNameMatch) {
- hasToolCallInStep = true;
-
- const toolName = toolNameMatch[1];
- const args: Record = {};
- const argRegex = /(.*?)<\/longcat_arg_key>\s*(.*?)<\/longcat_arg_value>/gs;
-
- let argMatch;
- while ((argMatch = argRegex.exec(content)) !== null) {
- args[argMatch[1]!.trim()] = argMatch[2]!.trim();
- }
-
- // 3. EMIT A TOOL CALL PART
- // This is the "magic" - the SDK will see this and act as if the LLM
- // called a native tool.
- const toolCallId = `lc-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
- controller.enqueue({
- type: 'tool-call',
- // @ts-ignore
- id: toolCallId,
- toolCallId,
- toolName,
- input: args,
- dynamic: true,
- });
- }
-
- lastIndex = pattern.lastIndex;
- }
-
- // Keep the remaining buffer (unclosed tags) for the next chunk
- buffer = buffer.substring(lastIndex);
-
- // If there's no open tag starting, we can flush the buffer as text
- if (!buffer.includes('')) {
- if (buffer) {
- controller.enqueue({ type: chunk.type, text: buffer, id: lastChunkId ?? chunk.type.includes('reasoning') ? `reasoning-${step}` : `text-${step}` });
- buffer = '';
- }
- }
- },
- flush(controller) {
- if (buffer && lastChunkId && lastChunkType) {
- controller.enqueue({ type: `${lastChunkType}-delta`, text: buffer, id: lastChunkId });
- }
- }
- });
- };
-}
\ No newline at end of file
diff --git a/server/utils/providers/closedrouter.ts b/server/utils/providers/closedrouter.ts
index 469aead..dbad1d6 100644
--- a/server/utils/providers/closedrouter.ts
+++ b/server/utils/providers/closedrouter.ts
@@ -14,7 +14,11 @@ export default {
}
return Ok({
- gateway: createOpenAI({ name: 'ClosedRouter', apiKey, baseURL }),
+ gateway: createOpenAI({
+ name: 'ClosedRouter',
+ apiKey,
+ baseURL
+ }),
streamTransformer: undefined,
textTransformer: undefined,
});
diff --git a/utils/message.ts b/utils/message.ts
index 6b5d77b..f4297db 100644
--- a/utils/message.ts
+++ b/utils/message.ts
@@ -1,4 +1,4 @@
-import type { FilePart, ImagePart, ModelMessage } from "ai";
+import type { AssistantContent, FilePart, ImagePart, JSONValue, ModelMessage, ToolContent } from "ai";
import { ToolCallType } from "~~/drizzle/schema";
import { Err, Ok, type Result } from "~~/types/result";
import type { Message, MessageEntity } from "~/composables/useChat";
@@ -37,7 +37,11 @@ export const buildFocusedMessageTree = (messages: Readonly): MessageE
return focusedMessageTree;
}
-export const marshallMessages = (agent: Agent, messages: Readonly): Result => {
+export interface MarshallOptions {
+ signFileUrl?: (fileKey: string) => string;
+}
+
+export const marshallMessages = (agent: Agent, messages: Readonly, opts?: MarshallOptions): Result => {
const marshalledMessages: ModelMessage[] = [];
if (agent && agent.systemPrompt) {
@@ -51,47 +55,72 @@ export const marshallMessages = (agent: Agent, messages: Readonly {
- const url = resolveFileUrl(attachment.file.url);
+ let url = resolveFileUrl(attachment.file.url);
+
+ if (opts?.signFileUrl && attachment.file.url.startsWith('/api/files/')) {
+ const fileKey = attachment.file.url.slice('/api/files/'.length);
+ const token = opts.signFileUrl(fileKey);
+ url = `${url}?${token}`;
+ }
if (attachment.file.mimeType.startsWith('image/')) {
return {
type: 'image',
- image: url,
+ image: new URL(url),
};
}
return {
type: 'file',
- data: url,
+ data: new URL(url),
filename: attachment.file.name,
mediaType: attachment.file.mimeType,
};
}) as (FilePart | ImagePart)[];
let messageDate = new Date(message.createdAt);
+ const prompt = `[${messageDate.toDateString()} ${messageDate.toLocaleTimeString()}]: ${message.content!}`;
+
marshalledMessages.push({
role: 'user',
- content: [
+ content: attachments ? [
{
type: 'text',
text: `[${messageDate.toDateString()} ${messageDate.toLocaleTimeString()}]: ${message.content!}`
},
...attachments,
- ],
+ ] : prompt,
});
break;
}
case 'assistant':
+ let assistantPart: AssistantContent = [];
+ let toolParts: ToolContent = [];
+
for (const part of (message.parts || [])) {
if (!part) return Err('Part is undefined');
switch (part.type) {
case 'text':
case 'reasoning': {
- marshalledMessages.push({
- role: 'assistant',
- content: part.content!,
- });
+ if (toolParts.length > 0) {
+ marshalledMessages.push({
+ role: 'assistant',
+ content: assistantPart,
+ });
+ marshalledMessages.push({
+ role: 'tool',
+ content: toolParts,
+ });
+ toolParts = [];
+ assistantPart = [];
+ }
+
+ if (part.providerOptions || part.content) assistantPart.push({
+ type: part.type,
+ text: part.content || '',
+ providerOptions: part.providerOptions ? part.providerOptions as Record : undefined,
+ })
break;
}
case 'tool-call': {
@@ -101,33 +130,32 @@ export const marshallMessages = (agent: Agent, messages: Readonly : undefined,
- });
+ })
if (part.toolCall.status === 'failed') {
let failureType: 'error-text' | 'error-json';
- let failureValue: string;
+ let failureValue: string | JSONValue;
if (part.toolCall.error === null || part.toolCall.error === undefined) {
failureType = 'error-text';
@@ -140,27 +168,32 @@ export const marshallMessages = (agent: Agent, messages: Readonly : undefined,
});
break;
@@ -177,23 +210,22 @@ export const marshallMessages = (agent: Agent, messages: Readonly : undefined,
});
break;
@@ -203,6 +235,15 @@ export const marshallMessages = (agent: Agent, messages: Readonly 0) marshalledMessages.push({
+ role: 'assistant',
+ content: assistantPart,
+ });
+ if (toolParts.length > 0) marshalledMessages.push({
+ role: 'tool',
+ content: toolParts,
+ });
break;
default:
return Err(`Unknown message role: ${message.role}`);