diff --git a/CLAUDE.md b/CLAUDE.md index 58b98f91..24a407a6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,6 @@ - When receiving the first user message, you MUST read the following files in full, in parallel: - README.md + - packages/ai/README.md - packages/tui/README.md - packages/agent/README.md - packages/pods/README.md diff --git a/package-lock.json b/package-lock.json index 6035f8b3..696efa0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -654,6 +654,13 @@ } } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@mariozechner/ai": { "resolved": "packages/ai", "link": true @@ -670,6 +677,317 @@ "resolved": "packages/tui", "link": true }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.1.tgz", @@ -679,6 +997,143 @@ "undici-types": "~6.21.0" } }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.4.tgz", + "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.1", + "tinyglobby": "^0.2.14", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "3.2.4" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@xterm/headless": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.5.0.tgz", @@ -726,6 +1181,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -761,6 +1226,33 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", @@ -773,6 +1265,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -790,6 +1292,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -799,6 +1311,13 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.25.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", @@ -841,12 +1360,64 @@ "@esbuild/win32-x64": "0.25.8" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -985,6 +1556,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -1015,12 +1593,58 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.18", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", + "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -1062,51 +1686,70 @@ } } }, - "node_modules/playwright": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", - "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.55.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } + "license": "MIT" }, - "node_modules/playwright-core": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", - "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, "node_modules/resolve-pkg-maps": { @@ -1119,6 +1762,46 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/rollup": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.49.0", + "@rollup/rollup-android-arm64": "4.49.0", + "@rollup/rollup-darwin-arm64": "4.49.0", + "@rollup/rollup-darwin-x64": "4.49.0", + "@rollup/rollup-freebsd-arm64": "4.49.0", + "@rollup/rollup-freebsd-x64": "4.49.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", + "@rollup/rollup-linux-arm64-gnu": "4.49.0", + "@rollup/rollup-linux-arm64-musl": "4.49.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-musl": "4.49.0", + "@rollup/rollup-linux-s390x-gnu": "4.49.0", + "@rollup/rollup-linux-x64-gnu": "4.49.0", + "@rollup/rollup-linux-x64-musl": "4.49.0", + "@rollup/rollup-win32-arm64-msvc": "4.49.0", + "@rollup/rollup-win32-ia32-msvc": "4.49.0", + "@rollup/rollup-win32-x64-msvc": "4.49.0", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1139,6 +1822,13 @@ ], "license": "MIT" }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -1151,6 +1841,45 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -1166,6 +1895,90 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -1225,6 +2038,177 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vite": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", + "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -1241,6 +2225,23 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -1656,7 +2657,8 @@ }, "devDependencies": { "@types/node": "^24.3.0", - "playwright": "^1.55.0" + "@vitest/ui": "^3.2.4", + "vitest": "^3.2.4" }, "engines": { "node": ">=20.0.0" diff --git a/packages/ai/package.json b/packages/ai/package.json index 3fe01899..d173f604 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -15,7 +15,9 @@ "generate-models": "npx tsx scripts/generate-models.ts", "build": "npm run generate-models && tsc -p tsconfig.build.json && cp src/models.json dist/models.json", "check": "biome check --write .", - "test": "npx tsx --test test/providers.test.ts", + "test": "vitest", + "test:ui": "vitest --ui", + "test:old": "npx tsx --test test/providers.test.ts", "extract-models": "npx tsx scripts/extract-openai-models.ts", "prepublishOnly": "npm run clean && npm run models && npm run build" }, @@ -45,6 +47,8 @@ "node": ">=20.0.0" }, "devDependencies": { - "@types/node": "^24.3.0" + "@types/node": "^24.3.0", + "@vitest/ui": "^3.2.4", + "vitest": "^3.2.4" } } diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index 4fc6436b..421eaa72 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -45,8 +45,8 @@ export class OpenAICompletionsLLM implements LLM { stream_options: { include_usage: true }, }; - // Cerebras doesn't like the "store" field - if (!this.client.baseURL?.includes("cerebras.ai")) { + // Cerebras/xAI dont like the "store" field + if (!this.client.baseURL?.includes("cerebras.ai") || this.client.baseURL?.includes("api.x.ai")) { (params as any).store = false; } @@ -66,7 +66,7 @@ export class OpenAICompletionsLLM implements LLM { params.tool_choice = options.toolChoice; } - if (options?.reasoningEffort && this.isReasoningModel()) { + if (options?.reasoningEffort && this.isReasoningModel() && !this.model.toLowerCase().includes("grok")) { params.reasoning_effort = options.reasoningEffort; } @@ -77,14 +77,7 @@ export class OpenAICompletionsLLM implements LLM { let content = ""; let reasoningContent = ""; let reasoningField: "reasoning" | "reasoning_content" | null = null; - const toolCallsMap = new Map< - number, - { - id: string; - name: string; - arguments: string; - } - >(); + const parsedToolCalls: { id: string; name: string; arguments: string }[] = []; let usage: TokenUsage = { input: 0, output: 0, @@ -97,7 +90,9 @@ export class OpenAICompletionsLLM implements LLM { if (chunk.usage) { usage = { input: chunk.usage.prompt_tokens || 0, - output: chunk.usage.completion_tokens || 0, + output: + (chunk.usage.completion_tokens || 0) + + (chunk.usage.completion_tokens_details?.reasoning_tokens || 0), cacheRead: chunk.usage.prompt_tokens_details?.cached_tokens || 0, cacheWrite: 0, }; @@ -122,7 +117,7 @@ export class OpenAICompletionsLLM implements LLM { blockType = "text"; } - // Handle LLAMA.cpp reasoning_content + // Handle reasoning_content field if ( (choice.delta as any).reasoning_content !== null && (choice.delta as any).reasoning_content !== undefined @@ -137,7 +132,7 @@ export class OpenAICompletionsLLM implements LLM { blockType = "thinking"; } - // Handle Ollama reasoning field + // Handle reasoning field if ((choice.delta as any).reasoning !== null && (choice.delta as any).reasoning !== undefined) { if (blockType === "text") { options?.onText?.("", true); @@ -160,21 +155,22 @@ export class OpenAICompletionsLLM implements LLM { blockType = null; } for (const toolCall of choice.delta.tool_calls) { - const index = toolCall.index; - - if (!toolCallsMap.has(index)) { - toolCallsMap.set(index, { + if ( + parsedToolCalls.length === 0 || + (toolCall.id !== undefined && parsedToolCalls[parsedToolCalls.length - 1].id !== toolCall.id) + ) { + parsedToolCalls.push({ id: toolCall.id || "", name: toolCall.function?.name || "", arguments: "", }); } - const existing = toolCallsMap.get(index)!; - if (toolCall.id) existing.id = toolCall.id; - if (toolCall.function?.name) existing.name = toolCall.function.name; + const current = parsedToolCalls[parsedToolCalls.length - 1]; + if (toolCall.id) current.id = toolCall.id; + if (toolCall.function?.name) current.name = toolCall.function.name; if (toolCall.function?.arguments) { - existing.arguments += toolCall.function.arguments; + current.arguments += toolCall.function.arguments; } } } @@ -195,7 +191,7 @@ export class OpenAICompletionsLLM implements LLM { } // Convert tool calls map to array - const toolCalls: ToolCall[] = Array.from(toolCallsMap.values()).map((tc) => ({ + const toolCalls: ToolCall[] = parsedToolCalls.map((tc) => ({ id: tc.id, name: tc.name, arguments: JSON.parse(tc.arguments), @@ -232,8 +228,12 @@ export class OpenAICompletionsLLM implements LLM { // Add system prompt if provided if (systemPrompt) { - // Cerebras doesn't like the "developer" role - const role = this.isReasoningModel() && !this.client.baseURL?.includes("cerebras.ai") ? "developer" : "system"; + // Cerebras/xAi don't like the "developer" role + const useDeveloperRole = + this.isReasoningModel() && + !this.client.baseURL?.includes("cerebras.ai") && + !this.client.baseURL?.includes("api.x.ai"); + const role = useDeveloperRole ? "developer" : "system"; params.push({ role: role, content: systemPrompt }); } diff --git a/packages/ai/test/examples/anthropic.ts b/packages/ai/test/examples/anthropic.ts deleted file mode 100644 index 8017813a..00000000 --- a/packages/ai/test/examples/anthropic.ts +++ /dev/null @@ -1,67 +0,0 @@ -import chalk from "chalk"; -import { readFileSync } from "fs"; -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; -import { AnthropicLLM, AnthropicLLMOptions } from "../../src/providers/anthropic"; -import { Context, Tool } from "../../src/types"; - -// Define a simple calculator tool -const tools: Tool[] = [ - { - name: "calculate", - description: "Perform a mathematical calculation", - parameters: { - type: "object" as const, - properties: { - expression: { - type: "string", - description: "The mathematical expression to evaluate" - } - }, - required: ["expression"] - } - } -]; - -const options: AnthropicLLMOptions = { - onText: (t, complete) => process.stdout.write(t + (complete ? "\n" : "")), - onThinking: (t, complete) => process.stdout.write(chalk.dim(t + (complete ? "\n" : ""))), - thinking: { enabled: true } -}; -const ai = new AnthropicLLM("claude-sonnet-4-0", process.env.ANTHROPIC_OAUTH_TOKEN ?? process.env.ANTHROPIC_API_KEY); -const context: Context = { - systemPrompt: "You are a helpful assistant that can use tools to answer questions.", - messages: [ - { - role: "user", - content: "Think about birds briefly. Then give me a list of 10 birds. Finally, calculate 42 * 17 + 123 and 453 + 434 in parallel using the calculator tool.", - } - ], - tools -} - -let msg = await ai.complete(context, options) -context.messages.push(msg); -console.log(); -console.log(chalk.yellow(JSON.stringify(msg, null, 2))); - -for (const toolCall of msg.toolCalls || []) { - if (toolCall.name === "calculate") { - const expression = toolCall.arguments.expression; - const result = eval(expression); - context.messages.push({ - role: "toolResult", - content: `The result of ${expression} is ${result}.`, - toolCallId: toolCall.id, - isError: false - }); - } -} - -msg = await ai.complete(context, options); -console.log(); -console.log(chalk.yellow(JSON.stringify(msg, null, 2))); - - - - diff --git a/packages/ai/test/examples/cerebras-completions.ts b/packages/ai/test/examples/cerebras-completions.ts deleted file mode 100644 index 9c3d03f9..00000000 --- a/packages/ai/test/examples/cerebras-completions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import chalk from "chalk"; -import { Context, Tool } from "../../src/types"; -import { OpenAICompletionsLLM, OpenAICompletionsLLMOptions } from "../../src/providers/openai-completions"; - -// Define a simple calculator tool -const tools: Tool[] = [ - { - name: "calculate", - description: "Perform a mathematical calculation", - parameters: { - type: "object" as const, - properties: { - expression: { - type: "string", - description: "The mathematical expression to evaluate" - } - }, - required: ["expression"] - } - } -]; - -const options: OpenAICompletionsLLMOptions = { - onText: (t, complete) => process.stdout.write(t + (complete ? "\n" : "")), - onThinking: (t, complete) => process.stdout.write(chalk.dim(t + (complete ? "\n" : ""))), - reasoningEffort: "medium", - toolChoice: "auto" -}; -const ai = new OpenAICompletionsLLM("gpt-oss-120b", process.env.CEREBRAS_API_KEY, "https://api.cerebras.ai/v1"); -const context: Context = { - systemPrompt: "You are a helpful assistant that can use tools to answer questions.", - messages: [ - { - role: "user", - content: "Think about birds briefly. Then give me a list of 10 birds. Finally, calculate 42 * 17 + 123 and 453 + 434 in parallel using the calculator tool. You must use the tool to answer both math questions.", - } - ], - tools -} - -while (true) { - let msg = await ai.complete(context, options) - context.messages.push(msg); - console.log(); - - for (const toolCall of msg.toolCalls || []) { - if (toolCall.name === "calculate") { - const expression = toolCall.arguments.expression; - const result = eval(expression); - context.messages.push({ - role: "toolResult", - content: `The result of ${expression} is ${result}.`, - toolCallId: toolCall.id, - isError: false - }); - } - } - if (msg.stopReason != "toolUse") break; -} -console.log(); -console.log(chalk.yellow(JSON.stringify(context.messages, null, 2))); - - - - diff --git a/packages/ai/test/examples/gemini.ts b/packages/ai/test/examples/gemini.ts deleted file mode 100644 index cd411f4e..00000000 --- a/packages/ai/test/examples/gemini.ts +++ /dev/null @@ -1,65 +0,0 @@ -import chalk from "chalk"; -import { GeminiLLM, GeminiLLMOptions } from "../../src/providers/gemini.js"; -import { Context, Tool } from "../../src/types.js"; - -// Define a simple calculator tool -const tools: Tool[] = [ - { - name: "calculate", - description: "Perform a mathematical calculation", - parameters: { - type: "object" as const, - properties: { - expression: { - type: "string", - description: "The mathematical expression to evaluate" - } - }, - required: ["expression"] - } - } -]; - -const options: GeminiLLMOptions = { - onText: (t, complete) => process.stdout.write(t + (complete ? "\n" : "")), - onThinking: (t, complete) => process.stdout.write(chalk.dim(t + (complete ? "\n" : ""))), - toolChoice: "auto", - thinking: { - enabled: true, - budgetTokens: -1 // Dynamic thinking - } -}; - -const ai = new GeminiLLM("gemini-2.5-flash", process.env.GEMINI_API_KEY); -const context: Context = { - systemPrompt: "You are a helpful assistant that can use tools to answer questions.", - messages: [ - { - role: "user", - content: "Think about birds briefly. Then give me a list of 10 birds. Finally, calculate 42 * 17 + 123 and 453 + 434 in parallel using the calculator tool.", - } - ], - tools -} - -let msg = await ai.complete(context, options) -context.messages.push(msg); -console.log(); -console.log(chalk.yellow(JSON.stringify(msg, null, 2))); - -for (const toolCall of msg.toolCalls || []) { - if (toolCall.name === "calculate") { - const expression = toolCall.arguments.expression; - const result = eval(expression); - context.messages.push({ - role: "toolResult", - content: `The result of ${expression} is ${result}.`, - toolCallId: toolCall.id, - isError: false - }); - } -} - -msg = await ai.complete(context, options); -console.log(); -console.log(chalk.yellow(JSON.stringify(msg, null, 2))); \ No newline at end of file diff --git a/packages/ai/test/examples/groq-completions.ts b/packages/ai/test/examples/groq-completions.ts deleted file mode 100644 index f4e5f29e..00000000 --- a/packages/ai/test/examples/groq-completions.ts +++ /dev/null @@ -1,66 +0,0 @@ -import chalk from "chalk"; -import { Context, Tool } from "../../src/types"; -import { OpenAICompletionsLLM, OpenAICompletionsLLMOptions } from "../../src/providers/openai-completions"; - -// Define a simple calculator tool -const tools: Tool[] = [ - { - name: "calculate", - description: "Perform a mathematical calculation", - parameters: { - type: "object" as const, - properties: { - expression: { - type: "string", - description: "The mathematical expression to evaluate" - } - }, - required: ["expression"] - } - } -]; - -const options: OpenAICompletionsLLMOptions = { - onText: (t, complete) => process.stdout.write(t + (complete ? "\n" : "")), - onThinking: (t, complete) => process.stdout.write(chalk.dim(t + (complete ? "\n" : ""))), - reasoningEffort: "medium", - toolChoice: "auto" -}; -const ai = new OpenAICompletionsLLM("openai/gpt-oss-20b", process.env.GROQ_API_KEY, "https://api.groq.com/openai/v1"); -const context: Context = { - systemPrompt: "You are a helpful assistant that can use tools to answer questions.", - messages: [ - { - role: "user", - content: "Think about birds briefly. Then give me a list of 10 birds. Finally, calculate 42 * 17 + 123 and 453 + 434 in parallel using the calculator tool.", - } - ], - tools -} - -while (true) { - let msg = await ai.complete(context, options) - context.messages.push(msg); - console.log(); - console.log(chalk.yellow(JSON.stringify(msg, null, 2))); - - for (const toolCall of msg.toolCalls || []) { - if (toolCall.name === "calculate") { - const expression = toolCall.arguments.expression; - const result = eval(expression); - context.messages.push({ - role: "toolResult", - content: `The result of ${expression} is ${result}.`, - toolCallId: toolCall.id, - isError: false - }); - } - } - if (msg.stopReason != "toolUse") break; -} -console.log(); -console.log(chalk.yellow(JSON.stringify(context.messages, null, 2))); - - - - diff --git a/packages/ai/test/examples/ollama-completions.ts b/packages/ai/test/examples/ollama-completions.ts deleted file mode 100644 index f58ca356..00000000 --- a/packages/ai/test/examples/ollama-completions.ts +++ /dev/null @@ -1,66 +0,0 @@ -import chalk from "chalk"; -import { Context, Tool } from "../../src/types"; -import { OpenAICompletionsLLM, OpenAICompletionsLLMOptions } from "../../src/providers/openai-completions"; - -// Define a simple calculator tool -const tools: Tool[] = [ - { - name: "calculate", - description: "Perform a mathematical calculation", - parameters: { - type: "object" as const, - properties: { - expression: { - type: "string", - description: "The mathematical expression to evaluate" - } - }, - required: ["expression"] - } - } -]; - -const options: OpenAICompletionsLLMOptions = { - onText: (t, complete) => process.stdout.write(t + (complete ? "\n" : "")), - onThinking: (t, complete) => process.stdout.write(chalk.dim(t + (complete ? "\n" : ""))), - reasoningEffort: "medium", - toolChoice: "auto" -}; -const ai = new OpenAICompletionsLLM("gpt-oss:20b", "dummy", "http://localhost:11434/v1"); -const context: Context = { - systemPrompt: "You are a helpful assistant that can use tools to answer questions.", - messages: [ - { - role: "user", - content: "Think about birds briefly. Then give me a list of 10 birds. Finally, calculate 42 * 17 + 123 and 453 + 434 in parallel using the calculator tool.", - } - ], - tools -} - -while (true) { - let msg = await ai.complete(context, options) - context.messages.push(msg); - console.log(); - console.log(chalk.yellow(JSON.stringify(msg, null, 2))); - - for (const toolCall of msg.toolCalls || []) { - if (toolCall.name === "calculate") { - const expression = toolCall.arguments.expression; - const result = eval(expression); - context.messages.push({ - role: "toolResult", - content: `The result of ${expression} is ${result}.`, - toolCallId: toolCall.id, - isError: false - }); - } - } - if (msg.stopReason == "stop") break; -} -console.log(); -console.log(chalk.yellow(JSON.stringify(context.messages, null, 2))); - - - - diff --git a/packages/ai/test/examples/openai-completions.ts b/packages/ai/test/examples/openai-completions.ts deleted file mode 100644 index 6a60c969..00000000 --- a/packages/ai/test/examples/openai-completions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import chalk from "chalk"; -import { Context, Tool } from "../../src/types"; -import { OpenAICompletionsLLM, OpenAICompletionsLLMOptions } from "../../src/providers/openai-completions"; - -// Define a simple calculator tool -const tools: Tool[] = [ - { - name: "calculate", - description: "Perform a mathematical calculation", - parameters: { - type: "object" as const, - properties: { - expression: { - type: "string", - description: "The mathematical expression to evaluate" - } - }, - required: ["expression"] - } - } -]; - -const options: OpenAICompletionsLLMOptions = { - onText: (t, complete) => process.stdout.write(t + (complete ? "\n" : "")), - onThinking: (t, complete) => process.stdout.write(chalk.dim(t + (complete ? "\n" : ""))), - reasoningEffort: "medium", - toolChoice: "auto" -}; -const ai = new OpenAICompletionsLLM("gpt-5-mini"); -const context: Context = { - systemPrompt: "You are a helpful assistant that can use tools to answer questions.", - messages: [ - { - role: "user", - content: "Think about birds briefly. Then give me a list of 10 birds. Finally, calculate 42 * 17 + 123 and 453 + 434 in parallel using the calculator tool.", - } - ], - tools -} - -let msg = await ai.complete(context, options) -context.messages.push(msg); -console.log(); -console.log(chalk.yellow(JSON.stringify(msg, null, 2))); - -for (const toolCall of msg.toolCalls || []) { - if (toolCall.name === "calculate") { - const expression = toolCall.arguments.expression; - const result = eval(expression); - context.messages.push({ - role: "toolResult", - content: `The result of ${expression} is ${result}.`, - toolCallId: toolCall.id, - isError: false - }); - } -} - -msg = await ai.complete(context, options); -console.log(); -console.log(chalk.yellow(JSON.stringify(msg, null, 2))); - - - - diff --git a/packages/ai/test/examples/openai-responses.ts b/packages/ai/test/examples/openai-responses.ts deleted file mode 100644 index 923a797b..00000000 --- a/packages/ai/test/examples/openai-responses.ts +++ /dev/null @@ -1,60 +0,0 @@ -import chalk from "chalk"; -import { OpenAIResponsesLLMOptions, OpenAIResponsesLLM } from "../../src/providers/openai-responses.js"; -import type { Context, Tool } from "../../src/types.js"; - -// Define a simple calculator tool -const tools: Tool[] = [ - { - name: "calculate", - description: "Perform a mathematical calculation", - parameters: { - type: "object" as const, - properties: { - expression: { - type: "string", - description: "The mathematical expression to evaluate" - } - }, - required: ["expression"] - } - } -]; - -const ai = new OpenAIResponsesLLM("gpt-5"); -const context: Context = { - messages: [ - { - role: "user", - content: "Think about birds briefly. Then give me a list of 10 birds. Finally, calculate 42 * 17 + 123 and 453 + 434 in parallel using the calculator tool.", - } - ], - tools, -} - -const options: OpenAIResponsesLLMOptions = { - onText: (t, complete) => process.stdout.write(t + (complete ? "\n" : "")), - onThinking: (t, complete) => process.stdout.write(chalk.dim(t + (complete ? "\n" : ""))), - reasoningEffort: "low", - reasoningSummary: "auto" -}; -let msg = await ai.complete(context, options) -context.messages.push(msg); -console.log(); -console.log(chalk.yellow(JSON.stringify(msg, null, 2))); - -for (const toolCall of msg.toolCalls || []) { - if (toolCall.name === "calculate") { - const expression = toolCall.arguments.expression; - const result = eval(expression); - context.messages.push({ - role: "toolResult", - content: `The result of ${expression} is ${result}.`, - toolCallId: toolCall.id, - isError: false - }); - } -} - -msg = await ai.complete(context, options); -console.log(); -console.log(chalk.yellow(JSON.stringify(msg, null, 2))); \ No newline at end of file diff --git a/packages/ai/test/examples/openrouter-completions.ts b/packages/ai/test/examples/openrouter-completions.ts deleted file mode 100644 index 9def960a..00000000 --- a/packages/ai/test/examples/openrouter-completions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import chalk from "chalk"; -import { Context, Tool } from "../../src/types"; -import { OpenAICompletionsLLM, OpenAICompletionsLLMOptions } from "../../src/providers/openai-completions"; - -// Define a simple calculator tool -const tools: Tool[] = [ - { - name: "calculate", - description: "Perform a mathematical calculation", - parameters: { - type: "object" as const, - properties: { - expression: { - type: "string", - description: "The mathematical expression to evaluate" - } - }, - required: ["expression"] - } - } -]; - -const options: OpenAICompletionsLLMOptions = { - onText: (t, complete) => process.stdout.write(t + (complete ? "\n" : "")), - onThinking: (t, complete) => process.stdout.write(chalk.dim(t + (complete ? "\n" : ""))), - reasoningEffort: "medium", - toolChoice: "auto" -}; -const ai = new OpenAICompletionsLLM("z-ai/glm-4.5", process.env.OPENROUTER_API_KEY, "https://openrouter.ai/api/v1"); -const context: Context = { - systemPrompt: "You are a helpful assistant that can use tools to answer questions.", - messages: [ - { - role: "user", - content: "Think about birds briefly. Then give me a list of 10 birds. Finally, calculate 42 * 17 + 123 and 453 + 434 in parallel using the calculator tool.", - } - ], - tools -} - -while (true) { - let msg = await ai.complete(context, options) - context.messages.push(msg); - console.log(); - - for (const toolCall of msg.toolCalls || []) { - if (toolCall.name === "calculate") { - const expression = toolCall.arguments.expression; - const result = eval(expression); - context.messages.push({ - role: "toolResult", - content: `The result of ${expression} is ${result}.`, - toolCallId: toolCall.id, - isError: false - }); - } - } - if (msg.stopReason != "toolUse") break; -} -console.log(); -console.log(chalk.yellow(JSON.stringify(context.messages, null, 2))); - - - - diff --git a/packages/ai/test/providers.test.ts b/packages/ai/test/providers.test.ts index aca77e96..b96975a5 100644 --- a/packages/ai/test/providers.test.ts +++ b/packages/ai/test/providers.test.ts @@ -1,11 +1,10 @@ -#!/usr/bin/env node --test -import { describe, it, before } from "node:test"; -import assert from "node:assert"; +import { describe, it, beforeAll, afterAll, expect } from "vitest"; import { GeminiLLM } from "../src/providers/gemini.js"; import { OpenAICompletionsLLM } from "../src/providers/openai-completions.js"; import { OpenAIResponsesLLM } from "../src/providers/openai-responses.js"; import { AnthropicLLM } from "../src/providers/anthropic.js"; import type { LLM, LLMOptions, Context, Tool, AssistantMessage } from "../src/types.js"; +import { spawn, ChildProcess, execSync } from "child_process"; // Calculator tool definition (same as examples) const calculatorTool: Tool = { @@ -36,12 +35,12 @@ async function basicTextGeneration(llm: LLM) { const response = await llm.complete(context); - assert.strictEqual(response.role, "assistant"); - assert.ok(response.content); - assert.ok(response.usage.input > 0); - assert.ok(response.usage.output > 0); - assert.ok(!response.error); - assert.ok(response.content.includes("Hello test successful"), `Response content should match exactly. Got: ${response.content}`); + expect(response.role).toBe("assistant"); + expect(response.content).toBeTruthy(); + expect(response.usage.input).toBeGreaterThan(0); + expect(response.usage.output).toBeGreaterThan(0); + expect(response.error).toBeFalsy(); + expect(response.content).toContain("Hello test successful"); } async function handleToolCall(llm: LLM) { @@ -55,11 +54,12 @@ async function handleToolCall(llm: LLM) { }; const response = await llm.complete(context); - assert.ok(response.stopReason == "toolUse", "Response should indicate tool use"); - assert.ok(response.toolCalls && response.toolCalls.length > 0, "Response should include tool calls"); - const toolCall = response.toolCalls[0]; - assert.strictEqual(toolCall.name, "calculator"); - assert.ok(toolCall.id); + expect(response.stopReason).toBe("toolUse"); + expect(response.toolCalls).toBeTruthy(); + expect(response.toolCalls!.length).toBeGreaterThan(0); + const toolCall = response.toolCalls![0]; + expect(toolCall.name).toBe("calculator"); + expect(toolCall.id).toBeTruthy(); } async function handleStreaming(llm: LLM) { @@ -77,9 +77,9 @@ async function handleStreaming(llm: LLM) { } } as T); - assert.ok(textChunks.length > 0); - assert.ok(textCompleted); - assert.ok(response.content); + expect(textChunks.length).toBeGreaterThan(0); + expect(textCompleted).toBe(true); + expect(response.content).toBeTruthy(); } async function handleThinking(llm: LLM, options: T, requireThinking: boolean = true) { @@ -96,14 +96,11 @@ async function handleThinking(llm: LLM, options: T, req ...options }); - assert.ok(response.content, "Response should have content"); + expect(response.content).toBeTruthy(); // For providers that should always return thinking when enabled if (requireThinking) { - assert.ok( - thinkingChunks.length > 0 || response.thinking, - `LLM MUST return thinking content when thinking is enabled. Got ${thinkingChunks.length} streaming chars, thinking field: ${response.thinking?.length || 0} chars` - ); + expect(thinkingChunks.length > 0 || !!response.thinking).toBe(true); } } @@ -123,17 +120,15 @@ async function multiTurn(llm: LLM, thinkingOptions: T) const firstResponse = await llm.complete(context, thinkingOptions); // Verify we got either thinking content or tool calls (or both) - const hasThinking = firstResponse.thinking; + const hasThinking = firstResponse.thinking !== undefined && firstResponse.thinking.length > 0; const hasToolCalls = firstResponse.toolCalls && firstResponse.toolCalls.length > 0; - assert.ok( - hasThinking || hasToolCalls, - `First turn MUST include either thinking or tool calls. Got thinking: ${hasThinking}, tool calls: ${hasToolCalls}` - ); + expect(hasThinking || hasToolCalls).toBe(true); // If we got tool calls, verify they're correct if (hasToolCalls) { - assert.ok(firstResponse.toolCalls && firstResponse.toolCalls.length > 0, "First turn should include tool calls"); + expect(firstResponse.toolCalls).toBeTruthy(); + expect(firstResponse.toolCalls!.length).toBeGreaterThan(0); } // If we have thinking with tool calls, we should have thinkingSignature for proper multi-turn context @@ -142,7 +137,7 @@ async function multiTurn(llm: LLM, thinkingOptions: T) // For now, we'll just check if it exists when both are present // Some providers may not support thinkingSignature yet if (firstResponse.thinkingSignature !== undefined) { - assert.ok(firstResponse.thinkingSignature, "Response with thinking and tools should include thinkingSignature"); + expect(firstResponse.thinkingSignature).toBeTruthy(); } } @@ -151,9 +146,9 @@ async function multiTurn(llm: LLM, thinkingOptions: T) // Process tool calls and add results for (const toolCall of firstResponse.toolCalls || []) { - assert.strictEqual(toolCall.name, "calculator", "Tool call should be for calculator"); - assert.ok(toolCall.id, "Tool call must have an ID"); - assert.ok(toolCall.arguments, "Tool call must have arguments"); + expect(toolCall.name).toBe("calculator"); + expect(toolCall.id).toBeTruthy(); + expect(toolCall.arguments).toBeTruthy(); const { a, b, operation } = toolCall.arguments; let result: number; @@ -206,22 +201,21 @@ async function multiTurn(llm: LLM, thinkingOptions: T) } } - assert.ok(finalResponse, "Should get a final response with content"); - assert.ok(finalResponse.content, "Final response should have content"); - assert.strictEqual(finalResponse.role, "assistant"); + expect(finalResponse).toBeTruthy(); + expect(finalResponse!.content).toBeTruthy(); + expect(finalResponse!.role).toBe("assistant"); // The final response should reference the calculations - assert.ok( - finalResponse.content.includes("714") || finalResponse.content.includes("887"), - `Final response should include calculation results. Got: ${finalResponse.content}` - ); + expect( + finalResponse!.content!.includes("714") || finalResponse!.content!.includes("887") + ).toBe(true); } describe("AI Providers E2E Tests", () => { - describe("Gemini Provider", { skip: !process.env.GEMINI_API_KEY }, () => { + describe.skipIf(!process.env.GEMINI_API_KEY)("Gemini Provider", () => { let llm: GeminiLLM; - before(() => { + beforeAll(() => { llm = new GeminiLLM("gemini-2.5-flash", process.env.GEMINI_API_KEY!); }); @@ -246,10 +240,10 @@ describe("AI Providers E2E Tests", () => { }); }); - describe("OpenAI Completions Provider", { skip: !process.env.OPENAI_API_KEY }, () => { + describe.skipIf(!process.env.OPENAI_API_KEY)("OpenAI Completions Provider", () => { let llm: OpenAICompletionsLLM; - before(() => { + beforeAll(() => { llm = new OpenAICompletionsLLM("gpt-4o-mini", process.env.OPENAI_API_KEY!); }); @@ -266,10 +260,10 @@ describe("AI Providers E2E Tests", () => { }); }); - describe("OpenAI Responses Provider", { skip: !process.env.OPENAI_API_KEY }, () => { + describe.skipIf(!process.env.OPENAI_API_KEY)("OpenAI Responses Provider", () => { let llm: OpenAIResponsesLLM; - before(() => { + beforeAll(() => { llm = new OpenAIResponsesLLM("gpt-5-mini", process.env.OPENAI_API_KEY!); }); @@ -286,8 +280,6 @@ describe("AI Providers E2E Tests", () => { }); it("should handle thinking mode", async () => { - // OpenAI Responses API may not always return thinking even when requested - // This is model-dependent behavior await handleThinking(llm, {reasoningEffort: "medium"}, false); }); @@ -296,10 +288,10 @@ describe("AI Providers E2E Tests", () => { }); }); - describe("Anthropic Provider", { skip: !process.env.ANTHROPIC_OAUTH_TOKEN }, () => { + describe.skipIf(!process.env.ANTHROPIC_OAUTH_TOKEN)("Anthropic Provider", () => { let llm: AnthropicLLM; - before(() => { + beforeAll(() => { llm = new AnthropicLLM("claude-sonnet-4-0", process.env.ANTHROPIC_OAUTH_TOKEN!); }); @@ -323,4 +315,198 @@ describe("AI Providers E2E Tests", () => { await multiTurn(llm, {thinking: { enabled: true, budgetTokens: 2048 }}); }); }); + + describe.skipIf(!process.env.GROK_API_KEY)("Grok Provider (via OpenAI Completions)", () => { + let llm: OpenAICompletionsLLM; + + beforeAll(() => { + llm = new OpenAICompletionsLLM("grok-code-fast-1", process.env.GROK_API_KEY!, "https://api.x.ai/v1"); + }); + + it("should complete basic text generation", async () => { + await basicTextGeneration(llm); + }); + + it("should handle tool calling", async () => { + await handleToolCall(llm); + }); + + it("should handle streaming", async () => { + await handleStreaming(llm); + }); + + it("should handle thinking mode", async () => { + await handleThinking(llm, {reasoningEffort: "medium"}, false); + }); + + it("should handle multi-turn with thinking and tools", async () => { + await multiTurn(llm, {reasoningEffort: "medium"}); + }); + }); + + describe.skipIf(!process.env.GROQ_API_KEY)("Groq Provider (via OpenAI Completions)", () => { + let llm: OpenAICompletionsLLM; + + beforeAll(() => { + llm = new OpenAICompletionsLLM("openai/gpt-oss-20b", process.env.GROQ_API_KEY!, "https://api.groq.com/openai/v1"); + }); + + it("should complete basic text generation", async () => { + await basicTextGeneration(llm); + }); + + it("should handle tool calling", async () => { + await handleToolCall(llm); + }); + + it("should handle streaming", async () => { + await handleStreaming(llm); + }); + + it("should handle thinking mode", async () => { + await handleThinking(llm, {reasoningEffort: "medium"}, false); + }); + + it("should handle multi-turn with thinking and tools", async () => { + await multiTurn(llm, {reasoningEffort: "medium"}); + }); + }); + + describe.skipIf(!process.env.CEREBRAS_API_KEY)("Cerebras Provider (via OpenAI Completions)", () => { + let llm: OpenAICompletionsLLM; + + beforeAll(() => { + llm = new OpenAICompletionsLLM("gpt-oss-120b", process.env.CEREBRAS_API_KEY!, "https://api.cerebras.ai/v1"); + }); + + it("should complete basic text generation", async () => { + await basicTextGeneration(llm); + }); + + it("should handle tool calling", async () => { + await handleToolCall(llm); + }); + + it("should handle streaming", async () => { + await handleStreaming(llm); + }); + + it("should handle thinking mode", async () => { + await handleThinking(llm, {reasoningEffort: "medium"}, false); + }); + + it("should handle multi-turn with thinking and tools", async () => { + await multiTurn(llm, {reasoningEffort: "medium"}); + }); + }); + + describe.skipIf(!process.env.OPENROUTER_API_KEY)("OpenRouter Provider (via OpenAI Completions)", () => { + let llm: OpenAICompletionsLLM; + + beforeAll(() => { + llm = new OpenAICompletionsLLM("z-ai/glm-4.5", process.env.OPENROUTER_API_KEY!, "https://openrouter.ai/api/v1"); + }); + + it("should complete basic text generation", async () => { + await basicTextGeneration(llm); + }); + + it("should handle tool calling", async () => { + await handleToolCall(llm); + }); + + it("should handle streaming", async () => { + await handleStreaming(llm); + }); + + it("should handle thinking mode", async () => { + await handleThinking(llm, {reasoningEffort: "medium"}, false); + }); + + it("should handle multi-turn with thinking and tools", async () => { + await multiTurn(llm, {reasoningEffort: "medium"}); + }); + }); + + // Check if ollama is installed + let ollamaInstalled = false; + try { + execSync("which ollama", { stdio: "ignore" }); + ollamaInstalled = true; + } catch { + ollamaInstalled = false; + } + + describe.skipIf(!ollamaInstalled)("Ollama Provider (via OpenAI Completions)", () => { + let llm: OpenAICompletionsLLM; + let ollamaProcess: ChildProcess | null = null; + + beforeAll(async () => { + // Check if model is available, if not pull it + try { + execSync("ollama list | grep -q 'gpt-oss:20b'", { stdio: "ignore" }); + } catch { + console.log("Pulling gpt-oss:20b model for Ollama tests..."); + try { + execSync("ollama pull gpt-oss:20b", { stdio: "inherit" }); + } catch (e) { + console.warn("Failed to pull gpt-oss:20b model, tests will be skipped"); + return; + } + } + + // Start ollama server + ollamaProcess = spawn("ollama", ["serve"], { + detached: false, + stdio: "ignore" + }); + + // Wait for server to be ready + await new Promise((resolve) => { + const checkServer = async () => { + try { + const response = await fetch("http://localhost:11434/api/tags"); + if (response.ok) { + resolve(); + } else { + setTimeout(checkServer, 500); + } + } catch { + setTimeout(checkServer, 500); + } + }; + setTimeout(checkServer, 1000); // Initial delay + }); + + llm = new OpenAICompletionsLLM("gpt-oss:20b", "dummy", "http://localhost:11434/v1"); + }, 30000); // 30 second timeout for setup + + afterAll(() => { + // Kill ollama server + if (ollamaProcess) { + ollamaProcess.kill("SIGTERM"); + ollamaProcess = null; + } + }); + + it("should complete basic text generation", async () => { + await basicTextGeneration(llm); + }); + + it("should handle tool calling", async () => { + await handleToolCall(llm); + }); + + it("should handle streaming", async () => { + await handleStreaming(llm); + }); + + it("should handle thinking mode", async () => { + await handleThinking(llm, {reasoningEffort: "medium"}, false); + }); + + it("should handle multi-turn with thinking and tools", async () => { + await multiTurn(llm, {reasoningEffort: "medium"}); + }); + }); }); \ No newline at end of file diff --git a/packages/ai/vitest.config.ts b/packages/ai/vitest.config.ts new file mode 100644 index 00000000..1b07c96f --- /dev/null +++ b/packages/ai/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + testTimeout: 30000, // 30 seconds for API calls + } +}); \ No newline at end of file