diff --git a/package-lock.json b/package-lock.json index 78ae241..fd5bf18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.5.1", "license": "MIT", "dependencies": { + "@huggingface/transformers": "^3.5.2", "archiver": "^7.0.1", "chokidar": "^4.0.3", "eslint": "^9.27.0", @@ -567,6 +568,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", @@ -1164,6 +1175,27 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@huggingface/jinja": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.4.1.tgz", + "integrity": "sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@huggingface/transformers": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.5.2.tgz", + "integrity": "sha512-mfRXkmcL99+ibpjM++pvZmc2h3po8i1ZgSRI5Rtgh++P15GU0lY8UQteYt/w5V+GQw+Jpao93MoipcePzh3mKg==", + "license": "Apache-2.0", + "dependencies": { + "@huggingface/jinja": "^0.4.1", + "onnxruntime-node": "1.21.0", + "onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4", + "sharp": "^0.34.1" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1225,6 +1257,402 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz", + "integrity": "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz", + "integrity": "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", + "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz", + "integrity": "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz", + "integrity": "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz", + "integrity": "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz", + "integrity": "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz", + "integrity": "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz", + "integrity": "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz", + "integrity": "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz", + "integrity": "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz", + "integrity": "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz", + "integrity": "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1242,6 +1670,18 @@ "node": ">=12" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1844,6 +2284,70 @@ "node": ">=14" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.41.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", @@ -2277,7 +2781,6 @@ "version": "22.15.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2864,6 +3367,13 @@ ], "license": "MIT" }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3103,6 +3613,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -3222,6 +3741,19 @@ "dev": true, "license": "MIT" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3240,6 +3772,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3434,6 +3976,40 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3444,6 +4020,15 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3454,6 +4039,12 @@ "node": ">=8" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -3541,7 +4132,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3551,7 +4141,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3586,6 +4175,12 @@ "node": ">= 0.4" } }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", @@ -4138,6 +4733,12 @@ "node": ">=16" } }, + "node_modules/flatbuffers": { + "version": "25.2.10", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.2.10.tgz", + "integrity": "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw==", + "license": "Apache-2.0" + }, "node_modules/flatted": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", @@ -4358,6 +4959,23 @@ "node": ">=10.13.0" } }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -4370,11 +4988,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4396,6 +5029,12 @@ "dev": true, "license": "MIT" }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4405,6 +5044,18 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -5524,6 +6175,12 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -5692,6 +6349,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5745,6 +6408,18 @@ "tmpl": "1.0.5" } }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5841,6 +6516,33 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mlly": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", @@ -5995,6 +6697,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6021,6 +6732,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/onnxruntime-common": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.21.0.tgz", + "integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==", + "license": "MIT" + }, + "node_modules/onnxruntime-node": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.21.0.tgz", + "integrity": "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==", + "hasInstallScript": true, + "license": "MIT", + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "global-agent": "^3.0.0", + "onnxruntime-common": "1.21.0", + "tar": "^7.0.1" + } + }, + "node_modules/onnxruntime-web": { + "version": "1.22.0-dev.20250409-89f8206ba4", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.22.0-dev.20250409-89f8206ba4.tgz", + "integrity": "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==", + "license": "MIT", + "dependencies": { + "flatbuffers": "^25.1.24", + "guid-typescript": "^1.0.9", + "long": "^5.2.3", + "onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4", + "platform": "^1.3.6", + "protobufjs": "^7.2.4" + } + }, + "node_modules/onnxruntime-web/node_modules/onnxruntime-common": { + "version": "1.22.0-dev.20250409-89f8206ba4", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.22.0-dev.20250409-89f8206ba4.tgz", + "integrity": "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ==", + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6290,6 +7044,12 @@ "pathe": "^2.0.1" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, "node_modules/postcss-load-config": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", @@ -6414,6 +7174,30 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6602,6 +7386,29 @@ "node": ">=0.10.0" } }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/roarr/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/rollup": { "version": "4.41.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", @@ -6690,7 +7497,6 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -6699,6 +7505,80 @@ "node": ">=10" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT" + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sharp": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.2.tgz", + "integrity": "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.2", + "@img/sharp-darwin-x64": "0.34.2", + "@img/sharp-libvips-darwin-arm64": "1.1.0", + "@img/sharp-libvips-darwin-x64": "1.1.0", + "@img/sharp-libvips-linux-arm": "1.1.0", + "@img/sharp-libvips-linux-arm64": "1.1.0", + "@img/sharp-libvips-linux-ppc64": "1.1.0", + "@img/sharp-libvips-linux-s390x": "1.1.0", + "@img/sharp-libvips-linux-x64": "1.1.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", + "@img/sharp-libvips-linuxmusl-x64": "1.1.0", + "@img/sharp-linux-arm": "0.34.2", + "@img/sharp-linux-arm64": "0.34.2", + "@img/sharp-linux-s390x": "0.34.2", + "@img/sharp-linux-x64": "0.34.2", + "@img/sharp-linuxmusl-arm64": "0.34.2", + "@img/sharp-linuxmusl-x64": "0.34.2", + "@img/sharp-wasm32": "0.34.2", + "@img/sharp-win32-arm64": "0.34.2", + "@img/sharp-win32-ia32": "0.34.2", + "@img/sharp-win32-x64": "0.34.2" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6732,6 +7612,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -7035,6 +7930,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tar-stream": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", @@ -7046,6 +7958,15 @@ "streamx": "^2.15.0" } }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -7314,6 +8235,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/tsup": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", @@ -7450,7 +8378,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/update-browserslist-db": { diff --git a/package.json b/package.json index ab4138e..9a1b3b7 100644 --- a/package.json +++ b/package.json @@ -21,15 +21,16 @@ }, "license": "MIT", "dependencies": { + "@huggingface/transformers": "^3.5.2", "archiver": "^7.0.1", "chokidar": "^4.0.3", "eslint": "^9.27.0", "ignore": "^7.0.4", "mime-types": "^3.0.1", + "node-appwrite": "17.0.0", "node-pty": "^1.0.0", "prettier": "^3.5.3", - "ws": "^8.18.2", - "node-appwrite": "17.0.0" + "ws": "^8.18.2" }, "devDependencies": { "@types/archiver": "^6.0.3", diff --git a/src/adapters/embeddings.ts b/src/adapters/embeddings.ts new file mode 100644 index 0000000..7199454 --- /dev/null +++ b/src/adapters/embeddings.ts @@ -0,0 +1,41 @@ +export interface EmbeddingConfig { + [key: string]: any; +} + +export abstract class EmbeddingAdapter { + protected config: EmbeddingConfig; + public isInitializing: boolean = false; + + constructor(config: EmbeddingConfig = {}) { + this.config = config; + } + + /** + * Initialize the embedding model/service + */ + abstract initialize(): Promise; + + /** + * Generate embeddings for the given text + * @param text The text to generate embeddings for + * @returns Promise The embedding vector + */ + abstract generateEmbedding(text: string): Promise; + + /** + * Get the name/identifier of this adapter + */ + abstract getName(): string; + + /** + * Check if the adapter is initialized and ready to use + */ + abstract isInitialized(): boolean; + + /** + * Clean up resources when done + */ + async cleanup(): Promise { + // Default implementation - can be overridden by specific adapters + } +} diff --git a/src/adapters/embeddings/huggingface.ts b/src/adapters/embeddings/huggingface.ts new file mode 100644 index 0000000..12481d4 --- /dev/null +++ b/src/adapters/embeddings/huggingface.ts @@ -0,0 +1,74 @@ +import { + pipeline, + type FeatureExtractionPipeline, +} from "@huggingface/transformers"; +import { EmbeddingAdapter, EmbeddingConfig } from "../embeddings"; + +export interface HuggingFaceConfig extends EmbeddingConfig { + modelName?: string; + pooling?: "mean" | "none" | "cls" | undefined; + normalize?: boolean; +} + +export class HuggingFaceEmbeddingAdapter extends EmbeddingAdapter { + private pipeline: FeatureExtractionPipeline | null = null; + private modelName: string; + private pooling: "mean" | "none" | "cls" | undefined; + private normalize: boolean; + + constructor(config: HuggingFaceConfig = {}) { + super(config); + this.modelName = config.modelName || "jinaai/jina-embeddings-v2-base-code"; + this.pooling = config.pooling || "mean"; + this.normalize = config.normalize !== false; // default to true + } + + async initialize(): Promise { + if (!this.pipeline && !this.isInitializing) { + this.isInitializing = true; + console.log(`[HuggingFace] Initializing model: ${this.modelName}...`); + try { + this.pipeline = await pipeline("feature-extraction", this.modelName); + console.log("[HuggingFace] Model initialized successfully"); + } catch (error) { + console.error(`[HuggingFace] Error initializing model: ${error}`); + throw error; + } + } + this.isInitializing = false; + } + + async generateEmbedding(text: string): Promise { + if (!this.pipeline) { + throw new Error( + "HuggingFace adapter not initialized. Call initialize() first.", + ); + } + + try { + const output = await this.pipeline(text, { + pooling: this.pooling, + normalize: this.normalize, + }); + + // Convert tensor to array if needed + return Array.from(output.data); + } catch (error) { + console.error(`[HuggingFace] Error generating embedding: ${error}`); + throw error; + } + } + + getName(): string { + return `HuggingFace (${this.modelName})`; + } + + isInitialized(): boolean { + return this.pipeline !== null; + } + + async cleanup(): Promise { + this.pipeline = null; + console.log("[HuggingFace] Adapter cleaned up"); + } +} diff --git a/src/adapters/embeddings/openai.ts b/src/adapters/embeddings/openai.ts new file mode 100644 index 0000000..8aaa5b7 --- /dev/null +++ b/src/adapters/embeddings/openai.ts @@ -0,0 +1,95 @@ +import { EmbeddingAdapter, EmbeddingConfig } from "../embeddings"; + +export interface OpenAIConfig extends EmbeddingConfig { + apiKey: string; + model?: string; + baseURL?: string; +} + +export class OpenAIEmbeddingAdapter extends EmbeddingAdapter { + private apiKey: string; + private model: string; + private baseURL: string; + private initialized: boolean = false; + + constructor(config: OpenAIConfig) { + super(config); + if (!config.apiKey) { + throw new Error("OpenAI API key is required"); + } + this.apiKey = config.apiKey; + this.model = config.model || "text-embedding-3-small"; + this.baseURL = config.baseURL || "https://api.openai.com/v1"; + } + + async initialize(): Promise { + console.log(`[OpenAI] Initializing with model: ${this.model}...`); + this.isInitializing = true; + if (!this.apiKey.startsWith("sk-")) { + console.warn( + "[OpenAI] API key doesn't start with 'sk-', this might cause issues", + ); + } + this.initialized = true; + this.isInitializing = false; + console.log("[OpenAI] Adapter initialized successfully"); + } + + async generateEmbedding(text: string): Promise { + if (!this.initialized) { + throw new Error( + "OpenAI adapter not initialized. Call initialize() first.", + ); + } + + if (this.isInitializing) { + throw new Error( + "OpenAI adapter is initializing. Please wait a moment and try again.", + ); + } + + try { + const response = await fetch(`${this.baseURL}/embeddings`, { + method: "POST", + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + input: text, + model: this.model, + }), + }); + + if (!response.ok) { + throw new Error( + `OpenAI API error: ${response.status} ${response.statusText}`, + ); + } + + const data = (await response.json()) as any; + + if (!data.data || !data.data[0] || !data.data[0].embedding) { + throw new Error("Invalid response format from OpenAI API"); + } + + return data.data[0].embedding; + } catch (error) { + console.error(`[OpenAI] Error generating embedding: ${error}`); + throw error; + } + } + + getName(): string { + return `OpenAI (${this.model})`; + } + + isInitialized(): boolean { + return this.initialized; + } + + async cleanup(): Promise { + this.initialized = false; + console.log("[OpenAI] Adapter cleaned up"); + } +} diff --git a/src/adapters/index.ts b/src/adapters/index.ts new file mode 100644 index 0000000..28eec36 --- /dev/null +++ b/src/adapters/index.ts @@ -0,0 +1,6 @@ +export { EmbeddingAdapter } from "./embeddings"; +export type { EmbeddingConfig } from "./embeddings"; +export { HuggingFaceEmbeddingAdapter } from "./embeddings/huggingface"; +export type { HuggingFaceConfig } from "./embeddings/huggingface"; +export { OpenAIEmbeddingAdapter } from "./embeddings/openai"; +export type { OpenAIConfig } from "./embeddings/openai"; diff --git a/src/index.ts b/src/index.ts index 61649f1..5137abb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,8 @@ +export { Appwrite } from "./services/appwrite"; export { Code } from "./services/code"; +export { Embeddings } from "./services/embeddings"; export { Filesystem } from "./services/filesystem"; export { Git } from "./services/git"; export { System } from "./services/system"; export { Terminal } from "./services/terminal"; export { Synapse } from "./synapse"; -export { Appwrite } from "./services/appwrite"; diff --git a/src/services/embeddings.ts b/src/services/embeddings.ts new file mode 100644 index 0000000..33740c2 --- /dev/null +++ b/src/services/embeddings.ts @@ -0,0 +1,484 @@ +import * as chokidar from "chokidar"; +import * as fsSync from "fs"; +import * as fs from "fs/promises"; +import * as path from "path"; +import { EmbeddingAdapter } from "../adapters/embeddings"; +import { Synapse } from "../synapse"; + +export type EmbeddingResult = { + success: boolean; + message: string; +}; + +export type DocumentEmbedding = { + filePath: string; + content: string; + embedding: number[]; + size: number; +}; + +export type RelevantDocument = { + filePath: string; + content: string; + similarity: number; +}; + +export class Embeddings { + private synapse: Synapse; + private workDir: string; + private embeddings: Map = new Map(); + private embeddingAdapter: EmbeddingAdapter; + private gitignorePatterns: string[] | null = null; + private watcher: chokidar.FSWatcher | null = null; + private isWatching: boolean = false; + private processingQueue: Set = new Set(); + + constructor( + synapse: Synapse, + workDir: string, + embeddingAdapter: EmbeddingAdapter, + ) { + this.synapse = synapse; + this.embeddingAdapter = embeddingAdapter; + + if (workDir) { + if (!fsSync.existsSync(workDir)) { + fsSync.mkdirSync(workDir, { recursive: true }); + } + this.workDir = workDir; + } else { + this.workDir = process.cwd(); + } + } + + /** + * Updates the working directory + * @param workDir - The new working directory + */ + updateWorkDir(workDir: string): void { + if (!fsSync.existsSync(workDir)) { + fsSync.mkdirSync(workDir, { recursive: true }); + } + this.workDir = workDir; + } + + private log(message: string): void { + const timestamp = new Date().toISOString(); + console.log(`[Embeddings][${timestamp}] ${message}`); + } + + private parseGitignore(): string[] { + if (this.gitignorePatterns !== null) { + return this.gitignorePatterns; + } + + const gitignorePath = path.join(this.workDir, ".gitignore"); + const patterns: string[] = []; + + try { + if (fsSync.existsSync(gitignorePath)) { + const content = fsSync.readFileSync(gitignorePath, "utf-8"); + const lines = content.split("\n"); + + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + let pattern = trimmed.replace(/\/$/, "").replace(/\*$/, ""); + if (pattern) { + patterns.push(pattern); + } + } + } + + this.log(`Loaded ${patterns.length} patterns from .gitignore`); + } + } catch (error) { + this.log(`Error reading .gitignore: ${error}`); + } + + this.gitignorePatterns = patterns; + return patterns; + } + + private shouldIgnoreFile(filePath: string): boolean { + const fileName = path.basename(filePath); + const relativePath = path.relative(this.workDir, filePath); + + const hardcodedIgnore = [ + "node_modules", + ".git", + "dist", + "build", + "coverage", + ".next", + "tmp", + ".cache", + "huggingface_hub", + "helpers", + ]; + + // Check if any part of the path contains ignored directories + const pathParts = relativePath.split(path.sep); + for (const part of pathParts) { + if (hardcodedIgnore.includes(part)) { + return true; + } + } + + const gitignorePatterns = this.parseGitignore(); + + for (const pattern of gitignorePatterns) { + if ( + fileName === pattern || + relativePath === pattern || + relativePath.startsWith(pattern + "/") || + pathParts.includes(pattern) + ) { + return true; + } + } + + return false; + } + + private isCodeFile(filePath: string): boolean { + const codeExtensions = [ + ".ts", + ".js", + ".tsx", + ".jsx", + ".py", + ".java", + ".cpp", + ".c", + ".cs", + ".go", + ".rs", + ".php", + ".rb", + ".swift", + ".kt", + ".scala", + ".sh", + ".sql", + ".html", + ".css", + ".md", + ".json", + ".xml", + ".yaml", + ".yml", + ]; + + const ext = path.extname(filePath); + return codeExtensions.includes(ext); + } + + public refreshGitignoreCache(): void { + this.gitignorePatterns = null; + this.log("Gitignore cache refreshed"); + } + + private async initializeEmbeddingModel(): Promise { + if ( + !this.embeddingAdapter.isInitialized() && + !this.embeddingAdapter.isInitializing + ) { + this.log( + `Initializing embedding adapter: ${this.embeddingAdapter.getName()}...`, + ); + try { + await this.embeddingAdapter.initialize(); + this.log("Embedding adapter initialized successfully"); + } catch (error) { + this.log(`Error initializing embedding adapter: ${error}`); + throw error; + } + } + } + + private async readFileContent(filePath: string): Promise { + try { + const content = await fs.readFile(filePath, "utf-8"); + + // Limit file size for embedding model processing + return content.length > 7000 + ? content.substring(0, 7000) + "..." + : content; + } catch (error) { + this.log(`Error reading file ${filePath}: ${error}`); + return ""; + } + } + + private cosineSimilarity(a: number[], b: number[]): number { + const dotProduct = a.reduce((sum, ai, i) => sum + ai * b[i], 0); + const magnitudeA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0)); + const magnitudeB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0)); + return dotProduct / (magnitudeA * magnitudeB); + } + + private async embedFile(filePath: string): Promise { + const relativePath = path.relative(this.workDir, filePath); + + // Prevent duplicate processing + if (this.processingQueue.has(relativePath)) { + return; + } + + this.processingQueue.add(relativePath); + + try { + this.log(`Embedding file: ${relativePath}`); + + const content = await this.readFileContent(filePath); + if (!content.trim()) { + this.log(`Skipping empty file: ${relativePath}`); + this.embeddings.delete(relativePath); + return; + } + + // Create a document string that includes file path context + const documentText = `File: ${relativePath}\n\n${content}`; + + const embedding = + await this.embeddingAdapter.generateEmbedding(documentText); + + this.embeddings.set(relativePath, { + filePath: relativePath, + content: content, + embedding: embedding, + size: content.length, + }); + + this.log(`Successfully embedded: ${relativePath}`); + } catch (error) { + this.log(`Error embedding file ${relativePath}: ${error}`); + } finally { + this.processingQueue.delete(relativePath); + } + } + + private async removeFileEmbedding(filePath: string): Promise { + const relativePath = path.relative(this.workDir, filePath); + + if (this.embeddings.has(relativePath)) { + this.embeddings.delete(relativePath); + this.log(`Removed embedding for deleted file: ${relativePath}`); + } + } + + private async onFileChange(filePath: string): Promise { + if (!this.isCodeFile(filePath) || this.shouldIgnoreFile(filePath)) { + return; + } + + await this.embedFile(filePath); + } + + private async onFileDelete(filePath: string): Promise { + await this.removeFileEmbedding(filePath); + } + + public async startWatching(): Promise { + if (this.isWatching) { + return { + success: true, + message: "File watcher is already running", + }; + } + + try { + await this.initializeEmbeddingModel(); + + this.log("Starting file watcher and initial embedding generation..."); + const startTime = performance.now(); + + // Initial scan and embedding of existing files + await this.initialScan(); + + // Set up file watcher + this.watcher = chokidar.watch(this.workDir, { + ignored: [ + "**/node_modules/**", + "**/.git/**", + "**/dist/**", + "**/build/**", + "**/coverage/**", + "**/.next/**", + "**/tmp/**", + "**/.cache/**", + "**/huggingface_hub/**", + "**/helpers/**", + ], + persistent: true, + ignoreInitial: true, // We already did initial scan + }); + + this.watcher + .on("add", (filePath) => this.onFileChange(filePath)) + .on("change", (filePath) => this.onFileChange(filePath)) + .on("unlink", (filePath) => this.onFileDelete(filePath)); + + this.isWatching = true; + + const endTime = performance.now(); + const duration = (endTime - startTime) / 1000; + + this.log( + `File watcher started. Initial embedding completed in ${duration.toFixed(2)}s`, + ); + this.log(`Watching ${this.embeddings.size} files for changes`); + + return { + success: true, + message: `File watcher started successfully. Embedded ${this.embeddings.size} files in ${duration.toFixed(2)}s`, + }; + } catch (error) { + this.log(`Error starting file watcher: ${error}`); + return { + success: false, + message: `Failed to start file watcher: ${error}`, + }; + } + } + + private async initialScan(): Promise { + const files = this.getAllCodeFiles(this.workDir); + this.log(`Found ${files.length} code files for initial embedding`); + + const embedPromises = files.map((filePath) => this.embedFile(filePath)); + await Promise.all(embedPromises); + } + + private getAllCodeFiles(directory: string): string[] { + const files: string[] = []; + + const traverseDirectory = (dir: string): void => { + try { + const entries = fsSync.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + if (!this.shouldIgnoreFile(fullPath)) { + traverseDirectory(fullPath); + } + } else if (entry.isFile()) { + if (this.isCodeFile(fullPath) && !this.shouldIgnoreFile(fullPath)) { + files.push(fullPath); + } + } + } + } catch (error) { + this.log(`Error reading directory ${dir}: ${error}`); + } + }; + + traverseDirectory(directory); + return files; + } + + public async stopWatching(): Promise { + if (this.watcher) { + await this.watcher.close(); + this.watcher = null; + this.isWatching = false; + this.log("File watcher stopped"); + } + } + + public async findDocuments( + query: string, + limit: number = 5, + ): Promise<{ success: boolean; data: RelevantDocument[]; message: string }> { + if (this.embeddingAdapter.isInitializing) { + return { + success: false, + data: [], + message: + "Embedding adapter is initializing. Please wait a moment and try again.", + }; + } + + if (!this.isWatching) { + return { + success: false, + data: [], + message: + "No embeddings available. Please run startWatching() first or create some code files first.", + }; + } + + if (this.embeddings.size === 0) { + return { + success: false, + data: [], + message: + "No embeddings available yet. Try creating some code files first.", + }; + } + + await this.initializeEmbeddingModel(); + + this.log(`Searching for documents relevant to: "${query}"`); + + try { + // Generate embedding for the query + const queryVector = await this.embeddingAdapter.generateEmbedding(query); + + // Calculate similarities + const similarities = Array.from(this.embeddings.values()).map((doc) => ({ + filePath: doc.filePath, + content: doc.content, + similarity: this.cosineSimilarity(queryVector, doc.embedding), + })); + + // Sort by similarity (highest first) and limit results + const results = similarities + .sort((a, b) => b.similarity - a.similarity) + .slice(0, limit); + + this.log(`Found ${results.length} relevant documents`); + results.forEach((result, index) => { + this.log( + `${index + 1}. ${result.filePath} (similarity: ${result.similarity.toFixed(4)})`, + ); + }); + + return { + success: true, + data: results, + message: `Found ${results.length} relevant documents`, + }; + } catch (error) { + this.log(`Error finding relevant documents: ${error}`); + return { + success: false, + data: [], + message: `Error finding relevant documents: ${error}`, + }; + } + } + + public getStats(): { totalFiles: number; totalSize: number } { + const embeddings = Array.from(this.embeddings.values()); + const totalSize = embeddings.reduce((sum, doc) => sum + doc.size, 0); + return { + totalFiles: embeddings.length, + totalSize: totalSize, + }; + } + + public isWatchingFiles(): boolean { + return this.isWatching; + } + + public async dispose(): Promise { + await this.stopWatching(); + this.embeddings.clear(); + this.processingQueue.clear(); + this.embeddingAdapter.cleanup(); + } +} diff --git a/src/services/filesystem.ts b/src/services/filesystem.ts index 64c7283..295573b 100644 --- a/src/services/filesystem.ts +++ b/src/services/filesystem.ts @@ -108,6 +108,10 @@ export class Filesystem { filePath: string, content: string = "", ): Promise { + if (!filePath) { + return { success: false, error: "filePath is required" }; + } + const fullPath = this.resolvePath(filePath); try { @@ -166,6 +170,10 @@ export class Filesystem { * @throws Error if file reading fails */ async getFile(filePath: string): Promise { + if (!filePath) { + return { success: false, error: "filePath is required" }; + } + try { const fullPath = this.resolvePath(filePath); @@ -201,6 +209,10 @@ export class Filesystem { filePath: string, content: string, ): Promise { + if (!filePath) { + return { success: false, error: "filePath is required" }; + } + try { this.log(`Updating file at path: ${filePath}`); const fullPath = this.resolvePath(filePath); @@ -232,6 +244,10 @@ export class Filesystem { filePath: string, content: string, ): Promise { + if (!filePath) { + return { success: false, error: "filePath is required" }; + } + try { const fullPath = this.resolvePath(filePath); await fs.appendFile(fullPath, content); @@ -258,6 +274,10 @@ export class Filesystem { oldPath: string, newPath: string, ): Promise { + if (!oldPath || !newPath) { + return { success: false, error: "oldPath and newPath are required" }; + } + try { this.log(`Moving file from ${oldPath} to ${newPath}`); const fullOldPath = this.resolvePath(oldPath); @@ -284,6 +304,10 @@ export class Filesystem { * @throws Error if file deletion fails */ async deleteFile(filePath: string): Promise { + if (!filePath) { + return { success: false, error: "filePath is required" }; + } + try { this.log(`Deleting file at path: ${filePath}`); const fullPath = this.resolvePath(filePath); @@ -309,6 +333,10 @@ export class Filesystem { * @throws Error if directory creation fails */ async createFolder(dirPath: string): Promise { + if (!dirPath) { + return { success: false, error: "dirPath is required" }; + } + const fullPath = this.resolvePath(dirPath); if (dirPath === "." || dirPath === "" || dirPath === "/") { @@ -340,6 +368,10 @@ export class Filesystem { * @throws Error if directory reading fails */ async getFolder(dirPath: string): Promise { + if (!dirPath) { + return { success: false, error: "dirPath is required" }; + } + try { this.log(`Reading directory at path: ${dirPath}`); const fullPath = this.resolvePath(dirPath); @@ -373,6 +405,10 @@ export class Filesystem { dirPath: string, name: string, ): Promise { + if (!dirPath || !name) { + return { success: false, error: "dirPath and name are required" }; + } + try { this.log(`Renaming folder at ${dirPath} to ${name}`); const fullPath = this.resolvePath(dirPath); @@ -405,6 +441,10 @@ export class Filesystem { oldPath: string, newPath: string, ): Promise { + if (!oldPath || !newPath) { + return { success: false, error: "oldPath and newPath are required" }; + } + try { this.log(`Moving folder from ${oldPath} to ${newPath}`); const fullOldPath = this.resolvePath(oldPath); @@ -431,6 +471,10 @@ export class Filesystem { * @throws Error if deletion fails */ async deleteFolder(dirPath: string): Promise { + if (!dirPath) { + return { success: false, error: "dirPath is required" }; + } + try { this.log(`Deleting folder at path: ${dirPath}`); const fullPath = this.resolvePath(dirPath); @@ -569,7 +613,7 @@ export class Filesystem { */ async searchFiles(term: string): Promise { if (!term || term.trim() === "") { - return { success: false, error: "Search term is required" }; + return { success: false, error: "Search `term` is required" }; } const results: FileSearchMatch[] = []; const workDir = path.resolve(this.workDir); @@ -664,6 +708,10 @@ export class Filesystem { * @returns Promise containing the gzip file as a Buffer */ async createGzipFile(saveAs: string | null = null): Promise { + if (!this.workDir) { + return { success: false, error: "Work directory is not set" }; + } + try { const archive = archiver("tar", { gzip: true, diff --git a/tests/services/embeddings.test.ts b/tests/services/embeddings.test.ts new file mode 100644 index 0000000..bf4e3c2 --- /dev/null +++ b/tests/services/embeddings.test.ts @@ -0,0 +1,279 @@ +const mockChokidar = { + watch: jest.fn().mockReturnValue({ + on: jest.fn().mockReturnThis(), + close: jest.fn().mockResolvedValue(undefined), + }), +}; + +jest.mock("chokidar", () => mockChokidar); + +import * as fsSync from "fs"; +import * as fs from "fs/promises"; +import * as path from "path"; +import { EmbeddingAdapter } from "../../src/adapters/embeddings"; +import { Embeddings } from "../../src/services/embeddings"; +import { Synapse } from "../../src/synapse"; + +jest.mock("fs"); +jest.mock("fs/promises"); +jest.mock("path", () => ({ + ...jest.requireActual("path"), + sep: "/", + join: jest.fn(), + relative: jest.fn(), + extname: jest.fn(), + basename: jest.fn(), +})); + +// Mock embedding adapter +class MockEmbeddingAdapter extends EmbeddingAdapter { + private initialized: boolean = false; + private mockEmbeddingFunction: jest.Mock; + + constructor(mockEmbeddingFunction: jest.Mock) { + super(); + this.mockEmbeddingFunction = mockEmbeddingFunction; + } + + async initialize(): Promise { + this.initialized = true; + } + + async generateEmbedding(text: string): Promise { + if (!this.initialized) { + throw new Error("Adapter not initialized"); + } + const result = await this.mockEmbeddingFunction(text); + return Array.from(result.data); + } + + getName(): string { + return "MockEmbeddingAdapter"; + } + + isInitialized(): boolean { + return this.initialized; + } +} + +describe("Embeddings", () => { + let embeddings: Embeddings; + let mockSynapse: Synapse; + let mockEmbeddingFunction: jest.Mock; + let mockAdapter: MockEmbeddingAdapter; + + const mockWorkDir = "/test/workspace"; + + beforeEach(() => { + jest.clearAllMocks(); + + mockSynapse = new Synapse(); + mockEmbeddingFunction = jest.fn().mockResolvedValue({ + data: new Float32Array([0.1, 0.2, 0.3, 0.4, 0.5]), + }); + mockAdapter = new MockEmbeddingAdapter(mockEmbeddingFunction); + mockAdapter.initialize(); + + // Mock fs operations + (fsSync.existsSync as jest.Mock).mockReturnValue(true); + (fsSync.mkdirSync as jest.Mock).mockReturnValue(undefined); + (fsSync.readdirSync as jest.Mock).mockReturnValue([ + { name: "file1.ts", isDirectory: () => false, isFile: () => true }, + { name: "file2.js", isDirectory: () => false, isFile: () => true }, + { name: "node_modules", isDirectory: () => true, isFile: () => false }, + ]); + (fsSync.readFileSync as jest.Mock).mockReturnValue("const test = 'code';"); + (fs.readFile as jest.Mock).mockResolvedValue("const test = 'code';"); + + // Mock path operations + (path.join as jest.Mock).mockImplementation((...parts: string[]) => + parts.join("/"), + ); + (path.relative as jest.Mock).mockImplementation( + (from: string, to: string) => to.replace(from + "/", ""), + ); + (path.extname as jest.Mock).mockImplementation((filePath: string) => { + const ext = filePath.split(".").pop(); + return ext ? `.${ext}` : ""; + }); + (path.basename as jest.Mock).mockImplementation( + (filePath: string) => filePath.split("/").pop() || "", + ); + + embeddings = new Embeddings(mockSynapse, mockWorkDir, mockAdapter); + }); + + describe("initialization", () => { + it("should create embeddings instance", () => { + expect(embeddings).toBeInstanceOf(Embeddings); + expect(fsSync.existsSync).toHaveBeenCalledWith(mockWorkDir); + }); + + it("should create work directory if it doesn't exist", () => { + (fsSync.existsSync as jest.Mock).mockReturnValue(false); + new Embeddings(mockSynapse, "/new/directory", mockAdapter); + expect(fsSync.mkdirSync).toHaveBeenCalledWith("/new/directory", { + recursive: true, + }); + }); + }); + + describe("startWatching", () => { + it("should successfully start watching and generate initial embeddings", async () => { + const result = await embeddings.startWatching(); + + expect(result.success).toBe(true); + expect(result.message).toContain("started successfully"); + expect(embeddings.isWatchingFiles()).toBe(true); + expect(mockEmbeddingFunction).toHaveBeenCalled(); + }); + + it("should return early if already watching", async () => { + await embeddings.startWatching(); + const result = await embeddings.startWatching(); + + expect(result.success).toBe(true); + expect(result.message).toContain("already running"); + }); + + it("should handle initialization errors", async () => { + const mockError = new Error("Init error"); + jest.spyOn(mockAdapter, "initialize").mockRejectedValue(mockError); + jest.spyOn(mockAdapter, "isInitialized").mockReturnValue(false); + + const result = await embeddings.startWatching(); + + expect(result.success).toBe(false); + expect(result.message).toContain("Failed to start"); + expect(result.message).toContain("Init error"); + }); + + it("should skip empty and non-code files", async () => { + (fsSync.readdirSync as jest.Mock).mockReturnValue([ + { name: "empty.ts", isDirectory: () => false, isFile: () => true }, + { name: "image.png", isDirectory: () => false, isFile: () => true }, + ]); + (fs.readFile as jest.Mock).mockResolvedValue(""); + + await embeddings.startWatching(); + + expect(mockEmbeddingFunction).not.toHaveBeenCalled(); + }); + + it("should truncate large files", async () => { + const largeContent = "a".repeat(8000); + (fs.readFile as jest.Mock).mockResolvedValue(largeContent); + + await embeddings.startWatching(); + + const callArgs = mockEmbeddingFunction.mock.calls[0][0]; + expect(callArgs).toContain("..."); + }); + }); + + describe("findDocuments", () => { + beforeEach(async () => { + await embeddings.startWatching(); + }); + + it("should find relevant documents", async () => { + const result = await embeddings.findDocuments("test query"); + + expect(result.success).toBe(true); + expect(result.data).toHaveLength(2); + expect(result.data[0]).toHaveProperty("similarity"); + }); + + it("should limit results", async () => { + const result = await embeddings.findDocuments("test query", 1); + + expect(result.data).toHaveLength(1); + }); + + it("should return error when no embeddings available", async () => { + const newEmbeddings = new Embeddings( + mockSynapse, + mockWorkDir, + mockAdapter, + ); + const result = await newEmbeddings.findDocuments("test query"); + + expect(result.success).toBe(false); + expect(result.message).toContain("No embeddings available"); + }); + + it("should handle search errors", async () => { + mockEmbeddingFunction.mockRejectedValue(new Error("Search error")); + + const result = await embeddings.findDocuments("test query"); + + expect(result.success).toBe(false); + }); + }); + + describe("file filtering", () => { + it("should process only code files", async () => { + (fsSync.readdirSync as jest.Mock).mockReturnValue([ + { name: "file.ts", isDirectory: () => false, isFile: () => true }, + { name: "file.txt", isDirectory: () => false, isFile: () => true }, + { name: "image.png", isDirectory: () => false, isFile: () => true }, + ]); + + await embeddings.startWatching(); + + expect(mockEmbeddingFunction).toHaveBeenCalledTimes(1); // Only .ts file + }); + + it("should skip ignored directories", async () => { + (fsSync.readdirSync as jest.Mock).mockImplementation((dir: string) => { + if (dir === mockWorkDir) { + return [ + { name: "src", isDirectory: () => true, isFile: () => false }, + { + name: "node_modules", + isDirectory: () => true, + isFile: () => false, + }, + ]; + } + if (dir.includes("src")) { + return [ + { name: "index.ts", isDirectory: () => false, isFile: () => true }, + ]; + } + return []; + }); + + await embeddings.startWatching(); + + expect(mockEmbeddingFunction).toHaveBeenCalledTimes(1); // Only src/index.ts + }); + }); + + describe("utility methods", () => { + it("should return correct stats", async () => { + expect(embeddings.getStats().totalFiles).toBe(0); + + await embeddings.startWatching(); + const stats = embeddings.getStats(); + + expect(stats.totalFiles).toBeGreaterThan(0); + expect(stats.totalSize).toBeGreaterThan(0); + }); + + it("should stop watching", async () => { + await embeddings.startWatching(); + await embeddings.stopWatching(); + + expect(embeddings.isWatchingFiles()).toBe(false); + }); + + it("should dispose properly", async () => { + await embeddings.startWatching(); + await embeddings.dispose(); + + expect(embeddings.isWatchingFiles()).toBe(false); + expect(embeddings.getStats().totalFiles).toBe(0); + }); + }); +});