diff --git a/package-lock.json b/package-lock.json index d7b11ce9..3c5fa7a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@mistralai/mistralai": "^1.5.0", "@modelcontextprotocol/sdk": "^1.9.0", "@parcel/watcher": "2.5.1", + "@types/katex": "^0.16.7", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", @@ -45,10 +46,13 @@ "cross-spawn": "^7.0.6", "diff": "^7.0.0", "eslint-plugin-react": "^7.37.4", + "fast-json-stable-stringify": "^2.1.0", + "google-auth-library": "^9.15.1", "groq-sdk": "^0.15.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", + "katex": "^0.16.22", "kerberos": "2.1.1", "lucide-react": "^0.477.0", "marked": "^15.0.7", @@ -1177,17 +1181,6 @@ "node": ">= 4.0.0" } }, - "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@es-joy/jsdoccomment": { "version": "0.48.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz", @@ -1202,74 +1195,6 @@ "node": ">=16" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", @@ -1287,346 +1212,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1939,29 +1524,6 @@ "@img/sharp-libvips-darwin-arm64": "1.0.4" } }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "dev": true, - "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.0.4" - } - }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", @@ -1979,323 +1541,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "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.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "dev": true, - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "dev": true, - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "dev": true, - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "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.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "dev": true, - "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.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "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.0.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "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.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "dev": true, - "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.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "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.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "dev": true, - "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.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "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.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "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.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "dev": true, - "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", @@ -2597,125 +1842,6 @@ "node": ">= 10" } }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.0.tgz", - "integrity": "sha512-DiU85EqSHogCz80+sgsx90/ecygfCSGl5P3b4XDRVZpgujBm5lp4ts7YaHru7eVTyZMjHInzKr+w0/7+qDrvMA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.0.tgz", - "integrity": "sha512-VnpoMaGukiNWVxeqKHwi8MN47yKGyki5q+7ql/7p/3ifuU2341i/gDwGK1rivk0pVYbdv5D8z63uu9yMw0QhpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.0.tgz", - "integrity": "sha512-ka97/ssYE5nPH4Qs+8bd8RlYeNeUVBhcnsNUmFM6VWEob4jfN9FTr0NBhXVi1XEJpj3cMfgSRW+LdE3SUZbPrw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.0.tgz", - "integrity": "sha512-zY1JduE4B3q0k2ZCE+DAF/1efjTXUsKP+VXRtrt/rJCTgDlUyyryx7aOgYXNc1d8gobys/Lof9P9ze8IyRDn7Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.0.tgz", - "integrity": "sha512-QqvLZpurBD46RhaVaVBepkVQzh8xtlUN00RlG4Iq1sBheNugamUNPuZEH1r9X1YGQo1KqAe1iiShF0acva3jHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.0.tgz", - "integrity": "sha512-ODZ0r9WMyylTHAN6pLtvUtQlGXBL9voljv6ujSlcsjOxhtXPI1Ag6AhZK0SE8hEpR1374WZZ5w33ChpJd5fsjw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.0.tgz", - "integrity": "sha512-8+4Z3Z7xa13NdUuUAcpVNA6o76lNPniBd9Xbo02bwXQXnZgFvEopwY2at5+z7yHl47X9qbZpvwatZ2BRo3EdZw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2927,26 +2053,6 @@ "@parcel/watcher-win32-x64": "2.5.1" } }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@parcel/watcher-darwin-arm64": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", @@ -2967,226 +2073,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@parcel/watcher/node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -3251,34 +2137,6 @@ "node": ">=18" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", - "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", - "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", @@ -3293,230 +2151,6 @@ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", - "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", - "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", - "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", - "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", - "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", - "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", - "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", - "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", - "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", - "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", - "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", - "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", - "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", - "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", - "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", - "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -3875,6 +2509,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "license": "MIT" + }, "node_modules/@types/kerberos": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@types/kerberos/-/kerberos-1.1.2.tgz", @@ -4700,30 +3340,6 @@ "node": ">= 16" } }, - "node_modules/@vscode/windows-ca-certs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@vscode/windows-ca-certs/-/windows-ca-certs-0.3.3.tgz", - "integrity": "sha512-C0Iq5RcH+H31GUZ8bsMORsX3LySVkGAqe4kQfUSVcCqJ0QOhXkhgwUMU7oCiqYLXaQWyXFp6Fj6eMdt05uK7VA==", - "hasInstallScript": true, - "license": "BSD", - "optional": true, - "os": [ - "win32" - ], - "dependencies": { - "node-addon-api": "^8.2.0" - } - }, - "node_modules/@vscode/windows-ca-certs/node_modules/node-addon-api": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.0.tgz", - "integrity": "sha512-qnyuI2ROiCkye42n9Tj5aX1ns7rzj6n7zW1XReSnLSL9v/vbLeR6fJq6PU27YU/ICfYw6W7Ouk/N7cysWu/hlw==", - "license": "MIT", - "optional": true, - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, "node_modules/@vscode/windows-mutex": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@vscode/windows-mutex/-/windows-mutex-0.5.0.tgz", @@ -5991,6 +4607,15 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.2.1.tgz", + "integrity": "sha512-+NzaKgOUvInq9TIUZ1+DRspzf/HApkCwD4btfuasFTdrfnOxqx853TgDpMolp+uv4RpRp7bPcEU2zKr9+fRmyw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6221,6 +4846,12 @@ "node": ">=0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -8068,6 +6699,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/editorconfig": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.2.tgz", @@ -9284,8 +7924,7 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "node_modules/extend-shallow": { "version": "3.0.2", @@ -9429,7 +8068,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -10205,6 +8844,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -10841,6 +9542,32 @@ "node": ">= 0.10" } }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -10913,6 +9640,19 @@ "undici-types": "~5.26.4" } }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", @@ -13873,6 +12613,15 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -13993,6 +12742,52 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/kerberos": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/kerberos/-/kerberos-2.1.1.tgz", diff --git a/package.json b/package.json index 6e65b143..d4e0fc21 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@mistralai/mistralai": "^1.5.0", "@modelcontextprotocol/sdk": "^1.9.0", "@parcel/watcher": "2.5.1", + "@types/katex": "^0.16.7", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", @@ -106,10 +107,13 @@ "cross-spawn": "^7.0.6", "diff": "^7.0.0", "eslint-plugin-react": "^7.37.4", + "fast-json-stable-stringify": "^2.1.0", + "google-auth-library": "^9.15.1", "groq-sdk": "^0.15.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", + "katex": "^0.16.22", "kerberos": "2.1.1", "lucide-react": "^0.477.0", "marked": "^15.0.7", diff --git a/product.json b/product.json index 6861c657..ab61205d 100644 --- a/product.json +++ b/product.json @@ -1,7 +1,7 @@ { "nameShort": "Void", "nameLong": "Void", - "voidVersion": "1.2.5", + "voidVersion": "1.2.6", "applicationName": "void", "dataFolderName": ".void-editor", "win32MutexName": "voideditor", diff --git a/src/vs/workbench/contrib/void/browser/_dummyContrib.ts b/src/vs/workbench/contrib/void/browser/_dummyContrib.ts index 6898845e..be6b00f9 100644 --- a/src/vs/workbench/contrib/void/browser/_dummyContrib.ts +++ b/src/vs/workbench/contrib/void/browser/_dummyContrib.ts @@ -59,6 +59,6 @@ class DummyService extends Disposable implements IWorkbenchContribution, IDummyS // pick one and delete the other: -registerSingleton(IDummyService, DummyService, InstantiationType.Eager); +registerSingleton(IDummyService, DummyService, InstantiationType.Eager); // lazily loaded, even if Eager -registerWorkbenchContribution2(DummyService.ID, DummyService, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(DummyService.ID, DummyService, WorkbenchPhase.BlockRestore); // mounts on start diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 7a206021..96b47e50 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -32,6 +32,9 @@ import { INotificationService, Severity } from '../../../../platform/notificatio import { truncate } from '../../../../base/common/strings.js'; import { THREAD_STORAGE_KEY } from '../common/storageKeys.js'; import { IConvertToLLMMessageService } from './convertToLLMMessageService.js'; +import { timeout } from '../../../../base/common/async.js'; + +const CHAT_RETRIES = 3 export const findStagingSelectionIndex = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem): number | null => { if (!currentSelections) return null @@ -91,7 +94,7 @@ const defaultMessageState: UserMessageState = { // a 'thread' means a chat message history -type ThreadType = { +export type ThreadType = { id: string; // store the id here too createdAt: string; // ISO string lastModified: string; // ISO string @@ -177,6 +180,7 @@ export interface IChatThreadService { getCurrentThread(): ThreadType; openNewThread(): void; + deleteThread(threadId: string): void; switchToThread(threadId: string): void; // exposed getters/setters @@ -564,7 +568,6 @@ class ChatThreadService extends Disposable implements IChatThreadService { let nMessagesSent = 0 let shouldSendAnotherMessage = true let isRunningWhenEnd: IsRunningType = undefined - let aborted = false // before enter loop, call tool if (callThisToolFirst) { @@ -592,69 +595,94 @@ class ChatThreadService extends Disposable implements IChatThreadService { chatMode }) - const llmCancelToken = this._llmMessageService.sendLLMMessage({ - messagesType: 'chatMessages', - chatMode, - messages: messages, - modelSelection, - modelSelectionOptions, - logging: { loggingName: `Chat - ${chatMode}`, loggingExtras: { threadId, nMessagesSent, chatMode } }, - separateSystemMessage: separateSystemMessage, - onText: ({ fullText, fullReasoning, toolCall }) => { - this._setStreamState(threadId, { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall }, 'merge') - }, - onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => { - this._addMessageToThread(threadId, { role: 'assistant', displayContent: fullText, reasoning: fullReasoning, anthropicReasoning }) - this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge') - resMessageIsDonePromise(toolCall) // resolve with tool calls - }, - onError: (error) => { - const messageSoFar = this.streamState[threadId]?.displayContentSoFar ?? '' - const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? '' - // const toolCallSoFar = this.streamState[threadId]?.toolCallSoFar - // add assistant's message to chat history, and clear selection - this._addMessageToThread(threadId, { role: 'assistant', displayContent: messageSoFar, reasoning: reasoningSoFar, anthropicReasoning: null }) - this._setStreamState(threadId, { error }, 'set') - resMessageIsDonePromise() - }, - onAbort: () => { - // stop the loop to free up the promise, but don't modify state (already handled by whatever stopped it) - resMessageIsDonePromise() - this._metricsService.capture('Agent Loop Done (Aborted)', { nMessagesSent, chatMode }) - aborted = true - }, - }) - // should never happen, just for safety - if (llmCancelToken === null) { - this._setStreamState(threadId, { - error: { message: 'There was an unexpected error when sending your chat message.', fullError: null } - }, 'set') - break - } - this._setStreamState(threadId, { streamingToken: llmCancelToken }, 'merge') // new stream token for the new message - const toolCall = await messageIsDonePromise // wait for message to complete - if (aborted) { return } - this._setStreamState(threadId, { streamingToken: undefined }, 'merge') // streaming message is done + let aborted = false - // call tool if there is one - const tool: RawToolCallObj | undefined = toolCall - if (tool) { - const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, tool.name, tool.id, { preapproved: false, unvalidatedToolParams: tool.rawParams }) + let shouldRetry = true + let nAttempts = 0 - // stop if interrupted. we don't have to do this for llmMessage because we have a stream token for it and onAbort gets called, but we don't have the equivalent for tools. - // just detect tool interruption which is the same as chat interruption right now - if (interrupted) { return } + while (shouldRetry) { + shouldRetry = false - if (awaitingUserApproval) { - isRunningWhenEnd = 'awaiting_user' + const llmCancelToken = this._llmMessageService.sendLLMMessage({ + messagesType: 'chatMessages', + chatMode, + messages: messages, + modelSelection, + modelSelectionOptions, + logging: { loggingName: `Chat - ${chatMode}`, loggingExtras: { threadId, nMessagesSent, chatMode } }, + separateSystemMessage: separateSystemMessage, + onText: ({ fullText, fullReasoning, toolCall }) => { + this._setStreamState(threadId, { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall }, 'merge') + }, + onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => { + this._addMessageToThread(threadId, { role: 'assistant', displayContent: fullText, reasoning: fullReasoning, anthropicReasoning }) + this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge') + resMessageIsDonePromise(toolCall) // resolve with tool calls + + }, + onError: (error) => { + const messageSoFar = this.streamState[threadId]?.displayContentSoFar ?? '' + const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? '' + + if (nAttempts < CHAT_RETRIES) { + nAttempts += 1 + shouldRetry = true + this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge') + timeout(2500).then(() => { resMessageIsDonePromise() }) + } + else { + // const toolCallSoFar = this.streamState[threadId]?.toolCallSoFar + // add assistant's message to chat history, and clear selection + this._addMessageToThread(threadId, { role: 'assistant', displayContent: messageSoFar, reasoning: reasoningSoFar, anthropicReasoning: null }) + this._setStreamState(threadId, { error }, 'set') + resMessageIsDonePromise() + } + }, + onAbort: () => { + // stop the loop to free up the promise, but don't modify state (already handled by whatever stopped it) + resMessageIsDonePromise() + this._metricsService.capture('Agent Loop Done (Aborted)', { nMessagesSent, chatMode }) + aborted = true + }, + }) + + // should never happen, just for safety + if (llmCancelToken === null) { + this._setStreamState(threadId, { + error: { message: 'There was an unexpected error when sending your chat message.', fullError: null } + }, 'set') + break } - else { - shouldSendAnotherMessage = true + this._setStreamState(threadId, { streamingToken: llmCancelToken }, 'merge') // new stream token for the new message + const toolCall = await messageIsDonePromise // wait for message to complete + if (shouldRetry) { + continue } - } + if (aborted) { + return + } + this._setStreamState(threadId, { streamingToken: undefined }, 'merge') // streaming message is done - } // end while + // call tool if there is one + const tool: RawToolCallObj | undefined = toolCall + if (tool) { + const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, tool.name, tool.id, { preapproved: false, unvalidatedToolParams: tool.rawParams }) + + // stop if interrupted. we don't have to do this for llmMessage because we have a stream token for it and onAbort gets called, but we don't have the equivalent for tools. + // just detect tool interruption which is the same as chat interruption right now + if (interrupted) { return } + + if (awaitingUserApproval) { + isRunningWhenEnd = 'awaiting_user' + } + else { + shouldSendAnotherMessage = true + } + } + + } // end while (attempts) + } // end while (send message) // if awaiting user approval, keep isRunning true, else end isRunning @@ -1389,6 +1417,19 @@ We only need to do it for files that were edited since `from`, ie files between } + deleteThread(threadId: string): void { + const { allThreads: currentThreads } = this.state + + // delete the thread + const newThreads = { ...currentThreads }; + delete newThreads[threadId]; + + // store the updated threads + this._storeAllThreads(newThreads); + this._setState({ ...this.state, allThreads: newThreads }, true) + } + + private _addMessageToThread(threadId: string, message: ChatMessage) { const { allThreads } = this.state const oldThread = allThreads[threadId] diff --git a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts index 19606415..44cfbd75 100644 --- a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts @@ -438,27 +438,33 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess super() } - // Read .voidinstructions files from workspace folders - private _getVoidInstructionsFileContents(): string { - const workspaceFolders = this.workspaceContextService.getWorkspace().folders; - let voidInstructions = ''; - for (const folder of workspaceFolders) { - const uri = URI.joinPath(folder.uri, '.voidinstructions') - const { model } = this.voidModelService.getModel(uri) - if (!model) continue - voidInstructions += model.getValue() + '\n\n'; + // Read .voidrules files from workspace folders + private _getVoidRulesFileContents(): string { + try { + const workspaceFolders = this.workspaceContextService.getWorkspace().folders; + let voidRules = ''; + for (const folder of workspaceFolders) { + const uri = URI.joinPath(folder.uri, '.voidrules') + const { model } = this.voidModelService.getModel(uri) + if (!model) continue + voidRules += model.getValue() + '\n\n'; + } + return voidRules.trim(); + } + catch (e) { + console.log('Could not read .voidrules, continuing...') + return '' } - return voidInstructions.trim(); } - // Get combined AI instructions from settings and .voidinstructions files + // Get combined AI instructions from settings and .voidrules files private _getCombinedAIInstructions(): string { const globalAIInstructions = this.voidSettingsService.state.globalSettings.aiInstructions; - const voidInstructionsFileContent = this._getVoidInstructionsFileContents(); + const voidRulesFileContent = this._getVoidRulesFileContents(); const ans: string[] = [] if (globalAIInstructions) ans.push(globalAIInstructions) - if (voidInstructionsFileContent) ans.push(voidInstructionsFileContent) + if (voidRulesFileContent) ans.push(voidRulesFileContent) return ans.join('\n\n') } diff --git a/src/vs/workbench/contrib/void/browser/convertToLLMMessageWorkbenchContrib.ts b/src/vs/workbench/contrib/void/browser/convertToLLMMessageWorkbenchContrib.ts index 9e65f9da..f77dde38 100644 --- a/src/vs/workbench/contrib/void/browser/convertToLLMMessageWorkbenchContrib.ts +++ b/src/vs/workbench/contrib/void/browser/convertToLLMMessageWorkbenchContrib.ts @@ -21,8 +21,8 @@ class ConvertContribWorkbenchContribution extends Disposable implements IWorkben const initializeURI = (uri: URI) => { this.workspaceContext.getWorkspace() - const voidInstrsURI = URI.joinPath(uri, '.voidinstructions') - this.voidModelService.initializeModel(voidInstrsURI) + const voidRulesURI = URI.joinPath(uri, '.voidrules') + this.voidModelService.initializeModel(voidRulesURI) } // call diff --git a/src/vs/workbench/contrib/void/browser/directoryStrService.ts b/src/vs/workbench/contrib/void/browser/directoryStrService.ts index 8174f63f..1d62638c 100644 --- a/src/vs/workbench/contrib/void/browser/directoryStrService.ts +++ b/src/vs/workbench/contrib/void/browser/directoryStrService.ts @@ -327,7 +327,7 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService { async getDirectoryStrTool(uri: URI, options?: { maxItemsPerDir?: number }) { const eRoot = this.explorerService.findClosest(uri) - if (!eRoot) throw new Error(`There was a problem reading the URI: ${uri.fsPath}.`) + if (!eRoot) throw new Error(`The folder ${uri.fsPath} does not exist.`) const maxItemsPerDir = options?.maxItemsPerDir ?? START_MAX_ITEMS_PER_DIR; // Use START_MAX_ITEMS_PER_DIR diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 1b1d7e44..a6858032 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -37,7 +37,7 @@ export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: Ico size-[18px] p-[2px] flex items-center justify-center - text-sm bg-void-bg-3 text-void-fg-3 + text-sm text-void-fg-3 hover:brightness-110 disabled:opacity-50 disabled:cursor-not-allowed ${className} diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 91773cf6..36ac3e53 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -5,6 +5,9 @@ import React, { JSX, useMemo, useState } from 'react' import { marked, MarkedToken, Token } from 'marked' +import katex from 'katex' +import 'katex/dist/katex.min.css' +import dompurify from '../../../../../../../base/browser/dompurify/dompurify.js' import { convertToVscodeLang, detectLanguage } from '../../../../common/helpers/languageHelpers.js' import { BlockCodeApplyWrapper } from './ApplyBlockHoverButtons.js' @@ -31,6 +34,63 @@ function isValidUri(s: string): boolean { return s.length > 5 && isAbsolute(s) && !s.includes('//') && !s.includes('/*') // common case that is a false positive is comments like // } +// renders contiguous string of latex eg $e^{i\pi}$ +const LatexRender = ({ latex }: { latex: string }) => { + + try { + let formula = latex; + let displayMode = false; + + // Extract the formula from delimiters + if (latex.startsWith('$') && latex.endsWith('$')) { + // Check if it's display math $$...$$ + if (latex.startsWith('$$') && latex.endsWith('$$')) { + formula = latex.slice(2, -2); + displayMode = true; + } else { + formula = latex.slice(1, -1); + } + } else if (latex.startsWith('\\(') && latex.endsWith('\\)')) { + formula = latex.slice(2, -2); + } else if (latex.startsWith('\\[') && latex.endsWith('\\]')) { + formula = latex.slice(2, -2); + displayMode = true; + } + + // Render LaTeX + const html = katex.renderToString(formula, { + displayMode: displayMode, + throwOnError: false, + output: 'html' + }); + + // Sanitize the HTML output with DOMPurify + const sanitizedHtml = dompurify.sanitize(html, { + RETURN_TRUSTED_TYPE: true, + USE_PROFILES: { html: true, svg: true, mathMl: true } + }); + + // Add proper styling based on mode + const className = displayMode + ? 'katex-block my-2 text-center' + : 'katex-inline'; + + // Use the ref approach to avoid dangerouslySetInnerHTML + const mathRef = React.useRef(null); + + React.useEffect(() => { + if (mathRef.current) { + mathRef.current.innerHTML = sanitizedHtml as unknown as string; + } + }, [sanitizedHtml]); + + return ; + } catch (error) { + console.error('KaTeX rendering error:', error); + return {latex}; + } +} + const Codespan = ({ text, className, onClick }: { text: string, className?: string, onClick?: () => void }) => { // TODO compute this once for efficiency. we should use `labels.ts/shorten` to display duplicates properly @@ -108,6 +168,105 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string } +const paragraphToLatexSegments = (paragraphText: string) => { + + const segments: React.ReactNode[] = []; + + if (paragraphText + && !(paragraphText.includes('#') || paragraphText.includes('`')) // don't process latex if a codespan or header tag + && !/^[\w\s.()[\]{}]+$/.test(paragraphText) // don't process latex if string only contains alphanumeric chars, whitespace, periods, and brackets + ) { + const rawText = paragraphText; + // Regular expressions to match LaTeX delimiters + const displayMathRegex = /\$\$(.*?)\$\$/g; // Display math: $$...$$ + const inlineMathRegex = /\$((?!\$).*?)\$/g; // Inline math: $...$ (but not $$) + + // Check if the paragraph contains any LaTeX expressions + if (displayMathRegex.test(rawText) || inlineMathRegex.test(rawText)) { + // Reset the regex state (since we used .test earlier) + displayMathRegex.lastIndex = 0; + inlineMathRegex.lastIndex = 0; + + // Parse the text into segments of regular text and LaTeX + let lastIndex = 0; + let segmentId = 0; + + // First replace display math ($$...$$) + let match; + while ((match = displayMathRegex.exec(rawText)) !== null) { + const [fullMatch, formula] = match; + const matchIndex = match.index; + + // Add text before the LaTeX expression + if (matchIndex > lastIndex) { + const textBefore = rawText.substring(lastIndex, matchIndex); + segments.push( + + {textBefore} + + ); + } + + // Add the LaTeX expression + segments.push( + + ); + + lastIndex = matchIndex + fullMatch.length; + } + + // Add any remaining text (which might contain inline math) + if (lastIndex < rawText.length) { + const remainingText = rawText.substring(lastIndex); + + // Process inline math in the remaining text + lastIndex = 0; + inlineMathRegex.lastIndex = 0; + const inlineSegments: React.ReactNode[] = []; + + while ((match = inlineMathRegex.exec(remainingText)) !== null) { + const [fullMatch] = match; + const matchIndex = match.index; + + // Add text before the inline LaTeX + if (matchIndex > lastIndex) { + const textBefore = remainingText.substring(lastIndex, matchIndex); + inlineSegments.push( + + {textBefore} + + ); + } + + // Add the inline LaTeX + inlineSegments.push( + + ); + + lastIndex = matchIndex + fullMatch.length; + } + + // Add any remaining text after all inline math + if (lastIndex < remainingText.length) { + inlineSegments.push( + + {remainingText.substring(lastIndex)} + + ); + } + + segments.push(...inlineSegments); + } + + + } + } + + + return segments +} + + export type RenderTokenOptions = { isApplyEnabled?: boolean, isLinkDetectionEnabled?: boolean } const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ...options }: { token: Token | string, inPTag?: boolean, codeURI?: URI, chatMessageLocation?: ChatMessageLocation, tokenIdx: string, } & RenderTokenOptions): React.ReactNode => { const accessor = useAccessor() @@ -189,24 +348,25 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, .. } if (t.type === 'table') { + return (
- {t.header.map((cell: any, index: number) => ( - ))} - {t.rows.map((row: any[], rowIndex: number) => ( - - {row.map((cell: any, cellIndex: number) => ( - + {row.map((r, rIdx: number) => ( + ))} @@ -288,6 +448,17 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, .. } if (t.type === 'paragraph') { + + // check for latex + const latexSegments = paragraphToLatexSegments(t.raw) + if (latexSegments.length !== 0) { + if (inPTag) { + return {latexSegments}; + } + return

{latexSegments}

; + } + + // if no latex, default behavior const contents = <> {t.tokens.map((token, index) => ( ) } - diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 45f59403..9464bf91 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -14,7 +14,7 @@ import { IDisposable } from '../../../../../../../base/common/lifecycle.js'; import { ErrorDisplay } from './ErrorDisplay.js'; import { BlockCode, TextAreaFns, VoidCustomDropdownBox, VoidInputBox2, VoidSlider, VoidSwitch } from '../util/inputs.js'; import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js'; -import { SidebarThreadSelector } from './SidebarThreadSelector.js'; +import { OldSidebarThreadSelector, PastThreadsList } from './SidebarThreadSelector.js'; import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; import { ChatMode, displayInfoOfProviderName, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; @@ -29,6 +29,7 @@ import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg import { ToolName, toolNames } from '../../../../common/prompt/prompts.js'; import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js'; import { MAX_FILE_CHARS_PAGE } from '../../../toolsService.js'; +import jsonStringify from 'fast-json-stable-stringify' import ErrorBoundary from './ErrorBoundary.js'; @@ -143,9 +144,6 @@ export const IconLoading = ({ className = '' }: { className?: string }) => { } -const getChatBubbleId = (threadId: string, messageIdx: number) => `${threadId}-${messageIdx}`; - - // SLIDER ONLY: const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) => { @@ -554,8 +552,7 @@ export const SelectedFiles = ( {allSelections.map((selection, i) => { const isThisSelectionProspective = i > selections.length - 1 - - const thisKey = `${isThisSelectionProspective}-${i}-${selections.length}` + const thisKey = jsonStringify(selection) return
void) | null, } -const ChatBubble = ({ threadId, chatMessage, currCheckpointIdx, isCommitted, messageIdx, chatIsRunning, _scrollToBottom }: ChatBubbleProps) => { +const ChatBubble = (props: ChatBubbleProps) => { + return + <_ChatBubble {...props} /> + +} + +const _ChatBubble = ({ threadId, chatMessage, currCheckpointIdx, isCommitted, messageIdx, chatIsRunning, _scrollToBottom }: ChatBubbleProps) => { const role = chatMessage.role const isCheckpointGhost = messageIdx > (currCheckpointIdx ?? Infinity) && !chatIsRunning // whether to show as gray (if chat is running, for good measure just dont show any ghosts) @@ -2001,33 +2004,6 @@ const ChatBubble = ({ threadId, chatMessage, currCheckpointIdx, isCommitted, mes isCommitted={isCommitted} /> } - // else if (role === 'tool_request') { - // const ToolRequestWrapper = toolNameToComponent[chatMessage.name]?.requestWrapper as RequestWrapper - // const toolRequestState = ( - // chatIsRunning === 'awaiting_user' ? 'awaiting_user' - // : chatIsRunning === 'tool' ? 'running' - // : chatIsRunning === 'message' ? null - // : null - // ) - // if (ToolRequestWrapper && canAcceptReject) { // if it's the last message - // return <> - // {toolRequestState !== null && - //
- // - //
} - // {chatIsRunning === 'awaiting_user' && - //
- // - //
} - // - // } - // return null - // } else if (role === 'tool') { if (chatMessage.type === 'invalid_params') { @@ -2537,8 +2513,8 @@ export const SidebarChat = () => { // const lastMessageIdx = previousMessages.findLastIndex(v => v.role !== 'checkpoint') // tool request shows up as Editing... if in progress return previousMessages.map((message, i) => { - return { chatIsRunning={isRunning} threadId={threadId} _scrollToBottom={() => scrollToBottom(scrollContainerRef)} - /> + /> }) }, [previousMessages, threadId, currCheckpointIdx, isRunning]) const streamingChatIdx = previousMessagesHTML.length const currStreamingMessageHTML = reasoningSoFar || displayContentSoFar || isRunning ? - { threadId={threadId} _scrollToBottom={null} - /> : null + /> : null // the tool currently being generated const generatingTool = toolIsGenerating ? toolCallSoFar.name === 'edit_file' ? : null @@ -2631,61 +2607,106 @@ export const SidebarChat = () => { } }, [onSubmit, onAbort, isRunning]) - const inputForm =
-
- {previousMessages.length > 0 && - - } -
-
- { textAreaRef.current?.focus() }} - > - { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }} - ref={textAreaRef} - fnsRef={textAreaFnsRef} - multiline={true} - /> - + + + const inputChatArea = { textAreaRef.current?.focus() }} + > + { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }} + ref={textAreaRef} + fnsRef={textAreaFnsRef} + multiline={true} + /> + + + + + const isLandingPage = previousMessages.length === 0 + + + const threadPageInput =
+
+ +
+
+ {inputChatArea}
- return ( -
- {/* History selector */} -
- - - -
- -
-
- - {messagesHTML} - -
- - {inputForm} - -
+ const landingPageInput =
+
+ {inputChatArea}
+
+ + const landingPageContent =
+ + {landingPageInput} + + + {Object.values(chatThreadsState.allThreads).length > 0 && // show if there are threads + +
Previous Threads
+ +
+ } +
+ + + // const threadPageContent =
+ // {/* Thread content */} + //
+ //
+ // + // {messagesHTML} + // + //
+ // + // {inputForm} + // + //
+ //
+ const threadPageContent =
+ + + {messagesHTML} + + + {threadPageInput} + +
+ + + return ( + + {isLandingPage ? + landingPageContent + : threadPageContent} + ) } + + + diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index 96909236..2b1d77a8 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -3,35 +3,20 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import React from "react"; +import { useState } from 'react'; +import { IconShell1 } from '../markdown/ApplyBlockHoverButtons.js'; import { useAccessor, useChatThreadsState } from '../util/services.js'; -import { ISidebarStateService } from '../../../sidebarStateService.js'; import { IconX } from './SidebarChat.js'; +import { Check, Trash2, X } from 'lucide-react'; +import { ThreadType } from '../../../chatThreadService.js'; -const truncate = (s: string) => { - let len = s.length - const TRUNC_AFTER = 16 - if (len >= TRUNC_AFTER) - s = s.substring(0, TRUNC_AFTER) + '...' - return s -} +export const OldSidebarThreadSelector = () => { -export const SidebarThreadSelector = () => { - const threadsState = useChatThreadsState() - const accessor = useAccessor() - const chatThreadsService = accessor.get('IChatThreadService') const sidebarStateService = accessor.get('ISidebarStateService') - const { allThreads } = threadsState - - // sorted by most recent to least recent - const sortedThreadIds = Object.keys(allThreads ?? {}) - .sort((threadId1, threadId2) => (allThreads[threadId1]?.lastModified ?? 0) > (allThreads[threadId2]?.lastModified ?? 0) ? -1 : 1) - .filter(threadId => (allThreads![threadId]?.messages.length ?? 0) !== 0) - return (
@@ -52,72 +37,297 @@ export const SidebarThreadSelector = () => {
{/* a list of all the past threads */} -
-
    - - {sortedThreadIds.length === 0 - - ?
    {`There are no chat threads yet.`}
    - - : sortedThreadIds.map((threadId) => { - if (!allThreads) { - return
  • {`Error accessing chat history.`}
  • ; - } - const pastThread = allThreads[threadId]; - if (!pastThread) { - return
  • {`Error accessing chat history.`}
  • ; - } - - - let firstMsg = null; - // let secondMsg = null; - - const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === 'user'); - - if (firstUserMsgIdx !== -1) { - // firstMsg = truncate(pastThread.messages[firstMsgIdx].displayContent ?? ''); - const firsUsertMsgObj = pastThread.messages[firstUserMsgIdx] - firstMsg = firsUsertMsgObj.role === 'user' && firsUsertMsgObj.displayContent || ''; - } else { - firstMsg = '""'; - } - - // const secondMsgIdx = pastThread.messages.findIndex( - // (msg, i) => msg.role !== 'system' && !!msg.displayContent && i > firstMsgIdx - // ); - - // if (secondMsgIdx !== -1) { - // secondMsg = truncate(pastThread.messages[secondMsgIdx].displayContent ?? ''); - // } - - const numMessages = pastThread.messages.filter((msg) => msg.role === 'assistant' || msg.role === 'user').length; - - return ( -
  • - -
  • - ); - }) - } -
-
+ {/* */}
) } + + + + + + +const truncate = (s: string) => { + let len = s.length + const TRUNC_AFTER = 16 + if (len >= TRUNC_AFTER) + s = s.substring(0, TRUNC_AFTER) + '...' + return s +} + + + +const OldPastThreadsList = () => { + + const accessor = useAccessor() + const chatThreadsService = accessor.get('IChatThreadService') + const sidebarStateService = accessor.get('ISidebarStateService') + + const threadsState = useChatThreadsState() + const { allThreads } = threadsState + + // sorted by most recent to least recent + const sortedThreadIds = Object.keys(allThreads ?? {}) + .sort((threadId1, threadId2) => (allThreads[threadId1]?.lastModified ?? 0) > (allThreads[threadId2]?.lastModified ?? 0) ? -1 : 1) + .filter(threadId => (allThreads![threadId]?.messages.length ?? 0) !== 0) + + + return
+
    + + {sortedThreadIds.length === 0 + + ?
    {`There are no chat threads yet.`}
    + + : sortedThreadIds.map((threadId) => { + if (!allThreads) { + return
  • {`Error accessing chat history.`}
  • ; + } + const pastThread = allThreads[threadId]; + if (!pastThread) { + return
  • {`Error accessing chat history.`}
  • ; + } + + + let firstMsg = null; + // let secondMsg = null; + + const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === 'user'); + + if (firstUserMsgIdx !== -1) { + // firstMsg = truncate(pastThread.messages[firstMsgIdx].displayContent ?? ''); + const firsUsertMsgObj = pastThread.messages[firstUserMsgIdx] + firstMsg = firsUsertMsgObj.role === 'user' && firsUsertMsgObj.displayContent || ''; + } else { + firstMsg = '""'; + } + + // const secondMsgIdx = pastThread.messages.findIndex( + // (msg, i) => msg.role !== 'system' && !!msg.displayContent && i > firstMsgIdx + // ); + + // if (secondMsgIdx !== -1) { + // secondMsg = truncate(pastThread.messages[secondMsgIdx].displayContent ?? ''); + // } + + const numMessages = pastThread.messages.filter((msg) => msg.role === 'assistant' || msg.role === 'user').length; + + return ( +
  • + +
  • + ); + }) + } +
+
+} + + +const numInitialThreads = 3 + +export const PastThreadsList = ({ className = '' }: { className?: string }) => { + const [showAll, setShowAll] = useState(false); + + const [hoveredIdx, setHoveredIdx] = useState(null) + + const threadsState = useChatThreadsState() + const { allThreads } = threadsState + + if (!allThreads) { + return
{`Error accessing chat history.`}
; + } + + // sorted by most recent to least recent + const sortedThreadIds = Object.keys(allThreads ?? {}) + .sort((threadId1, threadId2) => (allThreads[threadId1]?.lastModified ?? 0) > (allThreads[threadId2]?.lastModified ?? 0) ? -1 : 1) + .filter(threadId => (allThreads![threadId]?.messages.length ?? 0) !== 0) + + // Get only first 5 threads if not showing all + const hasMoreThreads = sortedThreadIds.length > numInitialThreads; + const displayThreads = showAll ? sortedThreadIds : sortedThreadIds.slice(0, numInitialThreads); + + return ( +
+ {displayThreads.length === 0 + ? <> // No chats yet... Suggestion: Tell me about my codebase Suggestion: Create a new .voidrules file in the root of my repo + : displayThreads.map((threadId, i) => { + const pastThread = allThreads[threadId]; + if (!pastThread) { + return
{`Error accessing chat history.`}
; + } + + return ( + + ); + }) + } + + {hasMoreThreads && !showAll && ( +
setShowAll(true)} + > + Show {sortedThreadIds.length - numInitialThreads} more... +
+ )} + {hasMoreThreads && showAll && ( +
setShowAll(false)} + > + Show less +
+ )} +
+ ); +}; + + + + + +// Format date to display as today, yesterday, or date +const formatDate = (date: Date) => { + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const yesterday = new Date(today); + yesterday.setDate(yesterday.getDate() - 1); + + if (date >= today) { + return 'Today'; + } else if (date >= yesterday) { + return 'Yesterday'; + } else { + return `${date.toLocaleString('default', { month: 'short' })} ${date.getDate()}`; + } +}; + +// Format time to 12-hour format +const formatTime = (date: Date) => { + return date.toLocaleString('en-US', { + hour: 'numeric', + minute: '2-digit', + hour12: true + }); +}; + + +const TrashButton = ({ threadId }: { threadId: string }) => { + + const accessor = useAccessor() + const chatThreadsService = accessor.get('IChatThreadService') + + + const [isTrashPressed, setIsTrashPressed] = useState(false) + + return (isTrashPressed ? +
+ { setIsTrashPressed(false); }} + data-tooltip-id='void-tooltip' + data-tooltip-place='top' + data-tooltip-content='Cancel' + /> + { chatThreadsService.deleteThread(threadId); setIsTrashPressed(false); }} + data-tooltip-id='void-tooltip' + data-tooltip-place='top' + data-tooltip-content='Confirm' + /> +
+ : { setIsTrashPressed(true); }} + data-tooltip-id='void-tooltip' + data-tooltip-place='top' + data-tooltip-content='Delete thread?' + /> + ) +} + +const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx }: { pastThread: ThreadType, idx: number, hoveredIdx: number | null, setHoveredIdx: (idx: number | null) => void }) => { + + + const accessor = useAccessor() + const chatThreadsService = accessor.get('IChatThreadService') + const sidebarStateService = accessor.get('ISidebarStateService') + + let firstMsg = null; + const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === 'user'); + + if (firstUserMsgIdx !== -1) { + const firsUsertMsgObj = pastThread.messages[firstUserMsgIdx]; + firstMsg = firsUsertMsgObj.role === 'user' && firsUsertMsgObj.displayContent || ''; + } else { + firstMsg = '""'; + } + + const numMessages = pastThread.messages.filter((msg) => msg.role === 'assistant' || msg.role === 'user').length; + + const detailsHTML = + {/* {numMessages} */} + {formatDate(new Date(pastThread.lastModified))} + + + return
{ + chatThreadsService.switchToThread(pastThread.id); + sidebarStateService.setState({ isHistoryOpen: false }); + }} + onMouseEnter={() => setHoveredIdx(idx)} + onMouseLeave={() => setHoveredIdx(null)} + > +
+ + {firstMsg} + + +
+ {idx === hoveredIdx ? + + : detailsHTML + } +
+
+
+} diff --git a/src/vs/workbench/contrib/void/browser/react/src/styles.css b/src/vs/workbench/contrib/void/browser/react/src/styles.css index 58ddedab..f195c334 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/styles.css +++ b/src/vs/workbench/contrib/void/browser/react/src/styles.css @@ -12,7 +12,7 @@ --void-bg-1-alt: var(--vscode-badge-background); --void-bg-2: var(--vscode-sideBar-background); --void-bg-2-alt: color-mix(in srgb, var(--vscode-editor-background) 30%, var(--vscode-sideBar-background) 70%); - --void-bg-2-hover: color-mix(in srgb, var(--vscode-editor-foreground) 5%, var(--vscode-sideBar-background) 95%); + --void-bg-2-hover: color-mix(in srgb, var(--vscode-editor-foreground) 2%, var(--vscode-sideBar-background) 98%); --void-bg-3: var(--vscode-editor-background); --void-fg-0: color-mix(in srgb, var(--vscode-tab-activeForeground) 90%, black 10%); diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index d9a266a7..1b8df220 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -664,7 +664,7 @@ export const VoidCustomDropdownBox = >({ {isOpen && (
>({ key={optionName} className={`flex items-center px-2 py-1 pr-4 cursor-pointer whitespace-nowrap transition-all duration-100 - ${thisOptionIsSelected ? 'bg-void-bg-2-hover' : 'bg-void-bg-2 hover:bg-void-bg-2-hover'} + ${thisOptionIsSelected ? 'bg-void-bg-2-hover' : 'bg-void-bg-2-alt hover:bg-void-bg-2-hover'} `} onClick={() => { onChangeOption(option); @@ -709,7 +709,7 @@ export const VoidCustomDropdownBox = >({ )}
- + {optionName} {optionDetail} diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx index 40ee767c..1feed1e0 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx @@ -307,7 +307,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName voidSettingsState.settingsOfProvider[providerName].models.forEach(m => { infoOfModelName[m.modelName] = { - showAsDefault: m.isDefault, + showAsDefault: m.type === 'default', isDownloaded: true } }) @@ -367,7 +367,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName return ( -
+
- {cell.raw} + {t.header.map((h, hIdx: number) => ( + + {h.text}
- {cell.raw} + {t.rows.map((row, rowIdx: number) => ( +
+ {r.text}
{!showAsDefault && removeModelButton} {modelName} @@ -497,7 +497,7 @@ const VoidOnboardingContent = () => { const providerNamesOfWantToUseOption: { [wantToUseOption in WantToUseOption]: ProviderName[] } = { smart: ['anthropic', 'openAI', 'gemini', 'openRouter'], - private: ['ollama', 'vLLM', 'openAICompatible'], + private: ['ollama', 'vLLM', 'openAICompatible', 'lmStudio'], cheap: ['gemini', 'deepseek', 'openRouter', 'ollama', 'vLLM'], all: providerNames, } diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 2c386760..8966ccf7 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------*/ import React, { useCallback, useEffect, useMemo, useState } from 'react' -import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames } from '../../../../common/voidSettingsTypes.js' +import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames, subTextMdOfProviderName } from '../../../../common/voidSettingsTypes.js' import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js' import { VoidButtonBgDarken, VoidCustomDropdownBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js' import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js' @@ -286,7 +286,7 @@ export const ModelDump = () => { return
{modelDump.map((m, i) => { - const { isHidden, isDefault, isAutodetected, modelName, providerName, providerEnabled } = m + const { isHidden, type, modelName, providerName, providerEnabled } = m const isNewProviderName = (i > 0 ? modelDump[i - 1] : undefined)?.providerName !== providerName @@ -318,7 +318,7 @@ export const ModelDump = () => { // : (isHidden ? `'${modelName}' won't appear in dropdowns` : ``) // } > - {isAutodetected ? '(detected locally)' : isDefault ? '' : '(custom model)'} + {type === 'autodetected' ? '(detected locally)' : type === 'default' ? '' : '(custom model)'} { />
- {isDefault ? null : } + {type === 'default' || type === 'autodetected' ? null : }
@@ -344,9 +344,9 @@ export const ModelDump = () => { // providers -const ProviderSetting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => { +const ProviderSetting = ({ providerName, settingName, subTextMd }: { providerName: ProviderName, settingName: SettingName, subTextMd: React.ReactNode }) => { - const { title: settingTitle, placeholder, isPasswordField, subTextMd } = displayInfoOfSettingName(providerName, settingName) + const { title: settingTitle, placeholder, isPasswordField } = displayInfoOfSettingName(providerName, settingName) const accessor = useAccessor() const voidSettingsService = accessor.get('IVoidSettingsService') @@ -370,10 +370,9 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider passwordBlur={isPasswordField} compact={true} /> - {subTextMd === undefined ? null :
- + {!subTextMd ? null :
+ {subTextMd}
} -
} @@ -456,7 +455,14 @@ export const SettingsForProvider = ({ providerName, showProviderTitle, showProvi
{/* settings besides models (e.g. api key) */} {settingNames.map((settingName, i) => { - return + + return } + /> })} {showProviderSuggestions && needsModel ? @@ -1025,11 +1031,11 @@ export const Settings = () => {

AI Instructions

- -

+ diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index ba257dc4..d6c85982 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -202,7 +202,19 @@ registerAction2(class extends Action2 { }) +const openNewThreadAndFireFocus = (accessor: ServicesAccessor) => { + const stateService = accessor.get(ISidebarStateService) + stateService.setState({ isHistoryOpen: false, currentTab: 'chat' }) + const chatThreadService = accessor.get(IChatThreadService) + chatThreadService.openNewThread() + + // focus + stateService.fireFocusChat() + const window = getActiveWindow() + window.requestAnimationFrame(() => stateService.fireFocusChat()) + +} // New chat menu button @@ -213,6 +225,25 @@ registerAction2(class extends Action2 { title: 'New Chat', icon: { id: 'add' }, menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }], + + }); + } + async run(accessor: ServicesAccessor): Promise { + + const metricsService = accessor.get(IMetricsService) + metricsService.capture('Chat Navigation', { type: 'New Chat' }) + + openNewThreadAndFireFocus(accessor) + + } +}) + +// New chat keybind +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'void.newChatKeybindAction', + title: 'New Chat Keybind', keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL, weight: KeybindingWeight.VoidExtension, @@ -220,19 +251,16 @@ registerAction2(class extends Action2 { }); } async run(accessor: ServicesAccessor): Promise { - const stateService = accessor.get(ISidebarStateService) + const metricsService = accessor.get(IMetricsService) + const commandService = accessor.get(ICommandService) + metricsService.capture('Chat Navigation', { type: 'New Chat Keybind' }) - metricsService.capture('Chat Navigation', { type: 'New Chat' }) + openNewThreadAndFireFocus(accessor) - stateService.setState({ isHistoryOpen: false, currentTab: 'chat' }) - const chatThreadService = accessor.get(IChatThreadService) - chatThreadService.openNewThread() + // add user's selection to chat + await commandService.executeCommand(VOID_CTRL_L_ACTION_ID) - // focus - stateService.fireFocusChat() - const window = getActiveWindow() - window.requestAnimationFrame(() => stateService.fireFocusChat()) } }) @@ -247,13 +275,27 @@ registerAction2(class extends Action2 { }); } async run(accessor: ServicesAccessor): Promise { + + // do not do anything if there are no messages (without this it clears all of the user's selections if the button is pressed) + // TODO the history button should be disabled in this case so we can remove this logic + const thread = accessor.get(IChatThreadService).getCurrentThread() + if (thread.messages.length === 0) { + return; + } + const stateService = accessor.get(ISidebarStateService) const metricsService = accessor.get(IMetricsService) + metricsService.capture('Chat Navigation', { type: 'History' }) + openNewThreadAndFireFocus(accessor) + + // doesnt do anything right now stateService.setState({ isHistoryOpen: !stateService.state.isHistoryOpen, currentTab: 'chat' }) stateService.fireBlurChat() + + } }) diff --git a/src/vs/workbench/contrib/void/browser/sidebarStateService.ts b/src/vs/workbench/contrib/void/browser/sidebarStateService.ts index 799ae14d..bd56657e 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarStateService.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarStateService.ts @@ -13,7 +13,7 @@ import { VOID_OPEN_SIDEBAR_ACTION_ID } from './sidebarPane.js'; // service that manages sidebar's state export type VoidSidebarState = { - isHistoryOpen: boolean; + isHistoryOpen: boolean; // this isn't doing anything right now currentTab: 'chat'; } diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index f3695343..82f1819c 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -263,7 +263,7 @@ export class ToolsService implements IToolsService { read_file: async ({ uri, startLine, endLine, pageNumber }) => { await voidModelService.initializeModel(uri) const { model } = await voidModelService.getModelSafe(uri) - if (model === null) { throw new Error(`Contents were empty. There may have been an error, or the file may not exist.`) } + if (model === null) { throw new Error(`No contents; File does not exist.`) } let contents: string if (startLine === null && endLine === null) { diff --git a/src/vs/workbench/contrib/void/common/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/void/common/helpers/extractCodeFromResult.ts index 09e57f04..de6402c2 100644 --- a/src/vs/workbench/contrib/void/common/helpers/extractCodeFromResult.ts +++ b/src/vs/workbench/contrib/void/common/helpers/extractCodeFromResult.ts @@ -153,6 +153,7 @@ export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { te const foundMid = pm.removePrefix(`<${midTag}>`) if (foundMid) { + pm.removeSuffix(`\n`) // sometimes outputs \n pm.removeSuffix(``) } const s = pm.value() diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index f75d5549..71459066 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -43,7 +43,22 @@ export const defaultProviderSettings = { }, mistral: { apiKey: '', - } + }, + lmStudio: { + endpoint: 'http://localhost:1234', + }, + liteLLM: { // https://docs.litellm.ai/docs/providers/openai_compatible + endpoint: '', + }, + googleVertex: { // google https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library + region: 'us-west2', + project: '', + }, + microsoftAzure: { // microsoft Azure Foundry + project: '', // really 'resource' + apiKey: '', + azureApiVersion: '2024-05-01-preview', + }, } as const @@ -84,6 +99,8 @@ export const defaultModelsOfProvider = { ], vLLM: [ // autodetected ], + lmStudio: [], // autodetected + openRouter: [ // https://openrouter.ai/models // 'anthropic/claude-3.7-sonnet:thinking', 'anthropic/claude-3.7-sonnet', @@ -112,6 +129,11 @@ export const defaultModelsOfProvider = { 'ministral-8b-latest', ], openAICompatible: [], // fallback + googleVertex: [], + microsoftAzure: [], + liteLLM: [], + + } as const satisfies Record @@ -168,7 +190,7 @@ type VoidStaticProviderInfo = { // doesn't change (not stateful) const modelOptionsDefaults: VoidStaticModelInfo = { - contextWindow: 32_000, + contextWindow: 16_000, maxOutputTokens: 4_096, cost: { input: 0, output: 0 }, downloadable: false, @@ -806,6 +828,25 @@ const groqSettings: VoidStaticProviderInfo = { modelOptionsFallback: (modelName) => { return null } } + +// ---------------- GOOGLE VERTEX ---------------- +const googleVertexModelOptions = { +} as const satisfies Record +const googleVertexSettings: VoidStaticProviderInfo = { + modelOptions: googleVertexModelOptions, + modelOptionsFallback: (modelName) => { return null } +} + +// ---------------- MICROSOFT AZURE ---------------- +const microsoftAzureModelOptions = { +} as const satisfies Record +const microsoftAzureSettings: VoidStaticProviderInfo = { + modelOptions: microsoftAzureModelOptions, + modelOptionsFallback: (modelName) => { return null } +} + + +// ---------------- VLLM, OLLAMA, OPENAICOMPAT (self-hosted / local) ---------------- const ollamaModelOptions = { 'qwen2.5-coder:1.5b': { contextWindow: 32_000, @@ -858,9 +899,6 @@ const ollamaModelOptions = { export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq', 'deepseek-r1'] as const satisfies (keyof typeof ollamaModelOptions)[] - -// ---------------- VLLM, OLLAMA, OPENAICOMPAT (self-hosted / local) ---------------- - const vLLMSettings: VoidStaticProviderInfo = { // reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://docs.vllm.ai/en/stable/features/reasoning_outputs.html#streaming-chat-completions providerReasoningIOSettings: { output: { nameOfFieldInDelta: 'reasoning_content' }, }, @@ -868,6 +906,12 @@ const vLLMSettings: VoidStaticProviderInfo = { modelOptions: {}, // TODO } +const lmStudioSettings: VoidStaticProviderInfo = { + providerReasoningIOSettings: { output: { needsManualParse: true }, }, + modelOptionsFallback: (modelName) => extensiveModelFallback(modelName, { downloadable: { sizeGb: 'not-known' } }), + modelOptions: {}, // TODO +} + const ollamaSettings: VoidStaticProviderInfo = { // reasoning: we need to filter out reasoning tags manually providerReasoningIOSettings: { output: { needsManualParse: true }, }, @@ -881,6 +925,12 @@ const openaiCompatible: VoidStaticProviderInfo = { modelOptions: {}, } +const liteLLMSettings: VoidStaticProviderInfo = { // https://docs.litellm.ai/docs/reasoning_content + providerReasoningIOSettings: { output: { nameOfFieldInDelta: 'reasoning_content' } }, + modelOptionsFallback: (modelName) => extensiveModelFallback(modelName, { downloadable: { sizeGb: 'not-known' } }), + modelOptions: {}, // TODO +} + // ---------------- OPENROUTER ---------------- const openRouterModelOptions_assumingOpenAICompat = { @@ -1027,9 +1077,12 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: VoidStaticProvi ollama: ollamaSettings, openAICompatible: openaiCompatible, mistral: mistralSettings, - // googleVertex: {}, - // microsoftAzure: {}, - // openHands: {}, + + liteLLM: liteLLMSettings, + lmStudio: lmStudioSettings, + + googleVertex: googleVertexSettings, + microsoftAzure: microsoftAzureSettings, } as const diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 964ad598..2a0ddbeb 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -330,6 +330,7 @@ Here's an example of a good edit suggestion: ${fileNameEditExample}.`) } + details.push(`NEVER write the FULL PATH of a file when speaking with the user. Just write the file name ONLY.`) details.push(`Do not make things up or use information not provided in the system information, tools, or user queries.`) details.push(`Today's date is ${new Date().toDateString()}.`) @@ -446,9 +447,10 @@ export const DIVIDER = `=======` export const FINAL = `>>>>>>> UPDATED` export const searchReplace_systemMessage = `\ -You are a coding assistant that generates SEARCH/REPLACE code blocks that will be used to edit a file. +You are a coding assistant that takes in a diff describing of a change to make, and outputs SEARCH/REPLACE code blocks which implement the change. +The diff will be labeled \`DIFF\` and the original file will be labeled \`ORIGINAL_FILE\`. -A SEARCH/REPLACE block describes the code before and after a change. Here is the format: +Format your SEARCH/REPLACE blocks as follows: ${tripleTick[0]} ${ORIGINAL} // ... original code goes here @@ -457,23 +459,28 @@ ${DIVIDER} ${FINAL} ${tripleTick[1]} -You will be given the original file \`ORIGINAL_FILE\` and a diff to apply to the file, \`CHANGE\`. -Output SEARCH/REPLACE blocks to edit the file according to the desired change. You may output multiple SEARCH/REPLACE blocks. -Be sure to output a change for every single item that changed from the original file to the given change, including comments. +1. Every single item written in \`CHANGE\` should show up in the final result, except for comments explicitly saying things like "// ... existing code". Make sure to include ALL other comments (even descriptive ones), code, whitespace, etc. in the final result. -Directions: -1. Your OUTPUT should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this. -2. The "ORIGINAL" code in each SEARCH/REPLACE block must EXACTLY match lines in the original file. The original code must NOT includes any new whitespace, comments, or any other modifications from the original code. -3. The "ORIGINAL" code in each SEARCH/REPLACE block must include enough text to uniquely identify the change in the file, but please bias towards writing as little as possible. -4. The "ORIGINAL" code in each SEARCH/REPLACE block must be disjoint from all other blocks. +2. Your SEARCH/REPLACE block(s) must implement the change EXACTLY. You should use comments like "// ... existing code" as reference points, and everything else in the change should be written verbatim. -The SEARCH/REPLACE blocks you generate will be applied immediately, and so they **MUST** produce a file that the user can run IMMEDIATELY. -- Make sure you add all necessary imports. -- Make sure the "UPDATED" code is ready for production as-is, and fix any relevant lint errors. +3. You are allowed to output multiple SEARCH/REPLACE blocks. -Follow coding conventions of the user (spaces, semilcolons, comments, etc). If the user spaces or formats things a certain way, CONTINUE formatting it that way, even if you prefer otherwise. +4. Your output should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this. + +5. The ORIGINAL code in each SEARCH/REPLACE block must EXACTLY match lines in the original file. Do not add or remove any whitespace, comments, or modifications from the original code. + +6. Each ORIGINAL text must be large enough to uniquely identify the change in the file. However; bias towards writing as little as possible. + +7. Each ORIGINAL text must be DISJOINT from all other ORIGINAL text. ## EXAMPLE 1 +DIFF +${tripleTick[0]} +// ... existing code +let x = 6.5 +// ... existing code +${tripleTick[1]} + ORIGINAL_FILE ${tripleTick[0]} let w = 5 @@ -482,15 +489,6 @@ let y = 7 let z = 8 ${tripleTick[1]} -CHANGE -Make x equal to 6.5, not 6. -${tripleTick[0]} -// ... existing code -let x = 6.5 -// ... existing code -${tripleTick[1]} - - ## ACCEPTED OUTPUT ${tripleTick[0]} ${ORIGINAL} @@ -502,11 +500,14 @@ ${tripleTick[1]} ` export const searchReplace_userMessage = ({ originalCode, applyStr }: { originalCode: string, applyStr: string }) => `\ -ORIGINAL_FILE -${originalCode} +DIFF +${applyStr} -CHANGE -${applyStr}` +ORIGINAL_FILE +${tripleTick[0]} +${originalCode} +${tripleTick[1]} +` diff --git a/src/vs/workbench/contrib/void/common/refreshModelService.ts b/src/vs/workbench/contrib/void/common/refreshModelService.ts index 8123441d..c4bfe115 100644 --- a/src/vs/workbench/contrib/void/common/refreshModelService.ts +++ b/src/vs/workbench/contrib/void/common/refreshModelService.ts @@ -8,7 +8,7 @@ import { ILLMMessageService } from './sendLLMMessageService.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; import { RefreshableProviderName, refreshableProviderNames, SettingsOfProvider } from './voidSettingsTypes.js'; -import { OllamaModelResponse, VLLMModelResponse } from './sendLLMMessageTypes.js'; +import { OllamaModelResponse, OpenaiCompatibleModelResponse } from './sendLLMMessageTypes.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; @@ -46,6 +46,7 @@ export type RefreshModelStateOfProvider = Record { } + : this.llmMessageService.openAICompatibleList listFn({ + providerName, onSuccess: ({ models }) => { - // set the models to the detected models this.voidSettingsService.setAutodetectedModels( providerName, models.map(model => { if (providerName === 'ollama') return (model as OllamaModelResponse).name; - else if (providerName === 'vLLM') return (model as VLLMModelResponse).id; + else if (providerName === 'vLLM') return (model as OpenaiCompatibleModelResponse).id; + else if (providerName === 'lmStudio') return (model as OpenaiCompatibleModelResponse).id; else throw new Error('refreshMode fn: unknown provider', providerName); }), { enableProviderOnSuccess: options.enableProviderOnSuccess, hideRefresh: options.doNotFire } diff --git a/src/vs/workbench/contrib/void/common/sendLLMMessageService.ts b/src/vs/workbench/contrib/void/common/sendLLMMessageService.ts index e6a30360..7579cfec 100644 --- a/src/vs/workbench/contrib/void/common/sendLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/common/sendLLMMessageService.ts @@ -3,7 +3,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainSendLLMMessageParams, MainLLMMessageAbortParams, ServiceModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, MainModelListParams, OllamaModelResponse, VLLMModelResponse, } from './sendLLMMessageTypes.js'; +import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainSendLLMMessageParams, MainLLMMessageAbortParams, ServiceModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, MainModelListParams, OllamaModelResponse, OpenaiCompatibleModelResponse, } from './sendLLMMessageTypes.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; @@ -22,7 +22,7 @@ export interface ILLMMessageService { sendLLMMessage: (params: ServiceSendLLMMessageParams) => string | null; abort: (requestId: string) => void; ollamaList: (params: ServiceModelListParams) => void; - vLLMList: (params: ServiceModelListParams) => void; + openAICompatibleList: (params: ServiceModelListParams) => void; } @@ -46,12 +46,12 @@ export class LLMMessageService extends Disposable implements ILLMMessageService success: {} as { [eventId: string]: ((params: EventModelListOnSuccessParams) => void) }, error: {} as { [eventId: string]: ((params: EventModelListOnErrorParams) => void) }, }, - vLLM: { - success: {} as { [eventId: string]: ((params: EventModelListOnSuccessParams) => void) }, - error: {} as { [eventId: string]: ((params: EventModelListOnErrorParams) => void) }, + openAICompat: { + success: {} as { [eventId: string]: ((params: EventModelListOnSuccessParams) => void) }, + error: {} as { [eventId: string]: ((params: EventModelListOnErrorParams) => void) }, } } satisfies { - [providerName: string]: { + [providerName in 'ollama' | 'openAICompat']: { success: { [eventId: string]: ((params: EventModelListOnSuccessParams) => void) }, error: { [eventId: string]: ((params: EventModelListOnErrorParams) => void) }, } @@ -70,14 +70,31 @@ export class LLMMessageService extends Disposable implements ILLMMessageService // .listen sets up an IPC channel and takes a few ms, so we set up listeners immediately and add hooks to them instead // llm - this._register((this.channel.listen('onText_sendLLMMessage') satisfies Event)(e => { this.llmMessageHooks.onText[e.requestId]?.(e) })) - this._register((this.channel.listen('onFinalMessage_sendLLMMessage') satisfies Event)(e => { this.llmMessageHooks.onFinalMessage[e.requestId]?.(e); this._clearChannelHooks(e.requestId) })) - this._register((this.channel.listen('onError_sendLLMMessage') satisfies Event)(e => { this.llmMessageHooks.onError[e.requestId]?.(e); this._clearChannelHooks(e.requestId); console.error('Error in LLMMessageService:', JSON.stringify(e)) })) - // ollama .list() - this._register((this.channel.listen('onSuccess_list_ollama') satisfies Event>)(e => { this.listHooks.ollama.success[e.requestId]?.(e) })) - this._register((this.channel.listen('onError_list_ollama') satisfies Event>)(e => { this.listHooks.ollama.error[e.requestId]?.(e) })) - this._register((this.channel.listen('onSuccess_list_vLLM') satisfies Event>)(e => { this.listHooks.vLLM.success[e.requestId]?.(e) })) - this._register((this.channel.listen('onError_list_vLLM') satisfies Event>)(e => { this.listHooks.vLLM.error[e.requestId]?.(e) })) + this._register((this.channel.listen('onText_sendLLMMessage') satisfies Event)(e => { + this.llmMessageHooks.onText[e.requestId]?.(e) + })) + this._register((this.channel.listen('onFinalMessage_sendLLMMessage') satisfies Event)(e => { + this.llmMessageHooks.onFinalMessage[e.requestId]?.(e); + this._clearChannelHooks(e.requestId) + })) + this._register((this.channel.listen('onError_sendLLMMessage') satisfies Event)(e => { + this.llmMessageHooks.onError[e.requestId]?.(e); + this._clearChannelHooks(e.requestId); + console.error('Error in LLMMessageService:', JSON.stringify(e)) + })) + // .list() + this._register((this.channel.listen('onSuccess_list_ollama') satisfies Event>)(e => { + this.listHooks.ollama.success[e.requestId]?.(e) + })) + this._register((this.channel.listen('onError_list_ollama') satisfies Event>)(e => { + this.listHooks.ollama.error[e.requestId]?.(e) + })) + this._register((this.channel.listen('onSuccess_list_openAICompatible') satisfies Event>)(e => { + this.listHooks.openAICompat.success[e.requestId]?.(e) + })) + this._register((this.channel.listen('onError_list_openAICompatible') satisfies Event>)(e => { + this.listHooks.openAICompat.error[e.requestId]?.(e) + })) } @@ -143,25 +160,24 @@ export class LLMMessageService extends Disposable implements ILLMMessageService } - vLLMList = (params: ServiceModelListParams) => { + openAICompatibleList = (params: ServiceModelListParams) => { const { onSuccess, onError, ...proxyParams } = params const { settingsOfProvider } = this.voidSettingsService.state // add state for request id const requestId_ = generateUuid(); - this.listHooks.vLLM.success[requestId_] = onSuccess - this.listHooks.vLLM.error[requestId_] = onError + this.listHooks.openAICompat.success[requestId_] = onSuccess + this.listHooks.openAICompat.error[requestId_] = onError - this.channel.call('vLLMList', { + this.channel.call('openAICompatibleList', { ...proxyParams, settingsOfProvider, - providerName: 'vLLM', requestId: requestId_, - } satisfies MainModelListParams) + } satisfies MainModelListParams) } - _clearChannelHooks(requestId: string) { + private _clearChannelHooks(requestId: string) { delete this.llmMessageHooks.onText[requestId] delete this.llmMessageHooks.onFinalMessage[requestId] delete this.llmMessageHooks.onError[requestId] @@ -169,8 +185,8 @@ export class LLMMessageService extends Disposable implements ILLMMessageService delete this.listHooks.ollama.success[requestId] delete this.listHooks.ollama.error[requestId] - delete this.listHooks.vLLM.success[requestId] - delete this.listHooks.vLLM.error[requestId] + delete this.listHooks.openAICompat.success[requestId] + delete this.listHooks.openAICompat.error[requestId] } } diff --git a/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts b/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts index a345fde0..6f08096f 100644 --- a/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------*/ import { ToolName, ToolParamName } from './prompt/prompts.js' -import { ChatMode, ModelSelection, ModelSelectionOptions, ProviderName, SettingsOfProvider } from './voidSettingsTypes.js' +import { ChatMode, ModelSelection, ModelSelectionOptions, ProviderName, RefreshableProviderName, SettingsOfProvider } from './voidSettingsTypes.js' export const errorDetails = (fullError: Error | null): string | null => { @@ -162,15 +162,13 @@ export type OllamaModelResponse = { size_vram: number; } -type OpenaiCompatibleModelResponse = { +export type OpenaiCompatibleModelResponse = { id: string; created: number; object: 'model'; owned_by: string; } -export type VLLMModelResponse = OpenaiCompatibleModelResponse - // params to the true list fn @@ -183,12 +181,13 @@ export type ModelListParams = { // params to the service export type ServiceModelListParams = { + providerName: RefreshableProviderName; onSuccess: (param: { models: modelResponse[] }) => void; onError: (param: { error: any }) => void; } type BlockedMainModelListParams = 'onSuccess' | 'onError' -export type MainModelListParams = Omit, BlockedMainModelListParams> & { requestId: string } +export type MainModelListParams = Omit, BlockedMainModelListParams> & { providerName: RefreshableProviderName, requestId: string } export type EventModelListOnSuccessParams = Parameters['onSuccess']>[0] & { requestId: string } export type EventModelListOnErrorParams = Parameters['onError']>[0] & { requestId: string } diff --git a/src/vs/workbench/contrib/void/common/voidModelService.ts b/src/vs/workbench/contrib/void/common/voidModelService.ts index 0c7389cf..6e30218f 100644 --- a/src/vs/workbench/contrib/void/common/voidModelService.ts +++ b/src/vs/workbench/contrib/void/common/voidModelService.ts @@ -42,10 +42,15 @@ class VoidModelService extends Disposable implements IVoidModelService { } initializeModel = async (uri: URI) => { - if (uri.fsPath in this._modelRefOfURI) return; - const editorModelRef = await this._textModelService.createModelReference(uri); - // Keep a strong reference to prevent disposal - this._modelRefOfURI[uri.fsPath] = editorModelRef; + try { + if (uri.fsPath in this._modelRefOfURI) return; + const editorModelRef = await this._textModelService.createModelReference(uri); + // Keep a strong reference to prevent disposal + this._modelRefOfURI[uri.fsPath] = editorModelRef; + } + catch (e) { + console.log('InitializeModel error:', e) + } }; getModelFromFsPath = (fsPath: string): VoidModelType => { diff --git a/src/vs/workbench/contrib/void/common/voidSettingsService.ts b/src/vs/workbench/contrib/void/common/voidSettingsService.ts index 819a01b7..b8b3fd10 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsService.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsService.ts @@ -71,24 +71,22 @@ export interface IVoidSettingsService { -const _updatedModelsAfterDefaultModelsChange = (defaultModelNames: string[], options: { existingModels: VoidStatefulModelInfo[], didAutoDetect: boolean }) => { - const { existingModels, didAutoDetect } = options +const _modelsWithSwappedInNewModels = (options: { existingModels: VoidStatefulModelInfo[], models: string[], type: 'autodetected' | 'default' }) => { + const { existingModels, models, type } = options const existingModelsMap: Record = {} for (const existingModel of existingModels) { existingModelsMap[existingModel.modelName] = existingModel } - const newDefaultModels = defaultModelNames.map((modelName, i) => ({ - modelName, - isDefault: true, - isAutodetected: didAutoDetect, - isHidden: !!existingModelsMap[modelName]?.isHidden, - })) + const newDefaultModels = models.map((modelName, i) => ({ modelName, type, isHidden: !!existingModelsMap[modelName]?.isHidden, })) return [ - ...newDefaultModels, // swap out all the default models for the new default models - ...existingModels.filter(m => !m.isDefault), // keep any non-default (custom) models + ...newDefaultModels, // swap out all the models of this type for the new models of this type + ...existingModels.filter(m => { + const keep = m.type !== type + return keep + }) ] } @@ -101,7 +99,7 @@ export const modelFilterOfFeatureName: { [featureName in FeatureName]: { filter: } -const _stateWithUpdatedDefaultModels = (state: VoidSettingsState): VoidSettingsState => { +const _stateWithMergedDefaultModels = (state: VoidSettingsState): VoidSettingsState => { let newSettingsOfProvider = state.settingsOfProvider // recompute default models @@ -109,7 +107,7 @@ const _stateWithUpdatedDefaultModels = (state: VoidSettingsState): VoidSettingsS const defaultModels = defaultSettingsOfProvider[providerName]?.models ?? [] const currentModels = newSettingsOfProvider[providerName]?.models ?? [] const defaultModelNames = defaultModels.map(m => m.modelName) - const newModels = _updatedModelsAfterDefaultModelsChange(defaultModelNames, { existingModels: currentModels, didAutoDetect: false }) + const newModels = _modelsWithSwappedInNewModels({ existingModels: currentModels, models: defaultModelNames, type: 'default' }) newSettingsOfProvider = { ...newSettingsOfProvider, [providerName]: { @@ -245,24 +243,45 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { } // the stored data structure might be outdated, so we need to update it here - readS = { - ...readS, - settingsOfProvider: { + try { + readS = { + ...readS, ...defaultSettingsOfProvider, ...readS.settingsOfProvider, - mistral: { // we added mistral - ...defaultSettingsOfProvider.mistral, - ...readS.settingsOfProvider.mistral, - }, - } // we added mistral + } + + for (const providerName of providerNames) { + readS.settingsOfProvider[providerName] = { + ...defaultSettingsOfProvider[providerName], + ...readS.settingsOfProvider[providerName], + } as any + + // conversion from 1.0.3 to 1.2.5 (can remove this when enough people update) + for (const m of readS.settingsOfProvider[providerName].models) { + if (!m.type) { + const old = (m as { isAutodetected?: boolean; isDefault?: boolean }) + if (old.isAutodetected) + m.type = 'autodetected' + else if (old.isDefault) + m.type = 'default' + else m.type = 'custom' + } + } + } } + + catch (e) { + readS = defaultState() + } + this.state = readS - this.state = _stateWithUpdatedDefaultModels(this.state) + this.state = _stateWithMergedDefaultModels(this.state) this.state = _validatedModelState(this.state); this._resolver(); this._onDidChangeState.fire(); + } @@ -389,7 +408,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { const { models } = this.state.settingsOfProvider[providerName] const oldModelNames = models.map(m => m.modelName) - const newModels = _updatedModelsAfterDefaultModelsChange(autodetectedModelNames, { existingModels: models, didAutoDetect: true }) + const newModels = _modelsWithSwappedInNewModels({ existingModels: models, models: autodetectedModelNames, type: 'autodetected' }) this.setSettingOfProvider(providerName, 'models', newModels) // if the models changed, log it @@ -423,7 +442,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { if (existingIdx !== -1) return // if exists, do nothing const newModels = [ ...models, - { modelName, isDefault: false, isHidden: false } + { modelName, type: 'custom', isHidden: false } as const ] this.setSettingOfProvider(providerName, 'models', newModels) diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index 3977f31b..eea91932 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -15,7 +15,7 @@ type UnionOfKeys = T extends T ? keyof T : never; export type ProviderName = keyof typeof defaultProviderSettings export const providerNames = Object.keys(defaultProviderSettings) as ProviderName[] -export const localProviderNames = ['ollama', 'vLLM'] satisfies ProviderName[] // all local names +export const localProviderNames = ['ollama', 'vLLM', 'lmStudio'] satisfies ProviderName[] // all local names export const nonlocalProviderNames = providerNames.filter((name) => !(localProviderNames as string[]).includes(name)) // all non-local names type CustomSettingName = UnionOfKeys @@ -30,9 +30,8 @@ export const customSettingNamesOfProvider = (providerName: ProviderName) => { export type VoidStatefulModelInfo = { // <-- STATEFUL modelName: string, - isDefault: boolean, // whether or not it's a default for its provider + type: 'default' | 'autodetected' | 'custom'; isHidden: boolean, // whether or not the user is hiding it (switched off) - isAutodetected?: boolean, // whether the model was autodetected by polling } // TODO!!! eventually we'd want to let the user change supportsFIM, etc on the model themselves @@ -59,74 +58,78 @@ type DisplayInfoForProviderName = { export const displayInfoOfProviderName = (providerName: ProviderName): DisplayInfoForProviderName => { if (providerName === 'anthropic') { - return { - title: 'Anthropic', - } + return { title: 'Anthropic', } } else if (providerName === 'openAI') { - return { - title: 'OpenAI', - } + return { title: 'OpenAI', } } else if (providerName === 'deepseek') { - return { - // title: 'DeepSeek.com API', - title: 'DeepSeek', - } + return { title: 'DeepSeek', } } else if (providerName === 'openRouter') { - return { - title: 'OpenRouter', - } + return { title: 'OpenRouter', } } else if (providerName === 'ollama') { - return { - title: 'Ollama', - } + return { title: 'Ollama', } } else if (providerName === 'vLLM') { - return { - title: 'vLLM', - } + return { title: 'vLLM', } + } + else if (providerName === 'liteLLM') { + return { title: 'LiteLLM', } + } + else if (providerName === 'lmStudio') { + return { title: 'LM Studio', } } else if (providerName === 'openAICompatible') { - return { - title: 'OpenAI-Compatible', - } + return { title: 'OpenAI-Compatible', } } else if (providerName === 'gemini') { - return { - // title: 'Gemini API', - title: 'Gemini', - } + return { title: 'Gemini', } } else if (providerName === 'groq') { - return { - // title: 'Groq.com API', - title: 'Groq', - } + return { title: 'Groq', } } else if (providerName === 'xAI') { - return { - // title: 'Grok (xAI)', - title: 'xAI', - } + return { title: 'xAI', } } else if (providerName === 'mistral') { - return { - // title: 'Mistral API', - title: 'Mistral', - } + return { title: 'Mistral', } + } + else if (providerName === 'googleVertex') { + return { title: 'Google Vertex AI', } + } + else if (providerName === 'microsoftAzure') { + return { title: 'Microsoft Azure OpenAI', } } - throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`) } +export const subTextMdOfProviderName = (providerName: ProviderName): string => { + + if (providerName === 'anthropic') return 'Get your [API Key here](https://console.anthropic.com/settings/keys).' + if (providerName === 'openAI') return 'Get your [API Key here](https://platform.openai.com/api-keys).' + if (providerName === 'deepseek') return 'Get your [API Key here](https://platform.deepseek.com/api_keys).' + if (providerName === 'openRouter') return 'Get your [API Key here](https://openrouter.ai/settings/keys).' + if (providerName === 'gemini') return 'Get your [API Key here](https://aistudio.google.com/apikey).' + if (providerName === 'groq') return 'Get your [API Key here](https://console.groq.com/keys).' + if (providerName === 'xAI') return 'Get your [API Key here](https://console.x.ai).' + if (providerName === 'mistral') return 'Get your [API Key here](https://console.mistral.ai/api-keys).' + if (providerName === 'openAICompatible') return `Use any OpenAI-compatible endpoint (LM Studio, LiteLM, etc).` + if (providerName === 'googleVertex') return 'You must authenticate before using Vertex with Void. Read more about endpoints [here](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library), and regions [here](https://cloud.google.com/vertex-ai/docs/general/locations#available-regions).' + if (providerName === 'microsoftAzure') return 'Read more about endpoints [here](https://learn.microsoft.com/en-us/rest/api/aifoundry/model-inference/get-chat-completions/get-chat-completions?view=rest-aifoundry-model-inference-2024-05-01-preview&tabs=HTTP), and get your API key [here](https://learn.microsoft.com/en-us/azure/search/search-security-api-keys?tabs=rest-use%2Cportal-find%2Cportal-query#find-existing-keys).' + if (providerName === 'ollama') return 'If you would like to change this endpoint, please read more about [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).' + if (providerName === 'vLLM') return 'If you would like to change this endpoint, please read more about [Endpoints here](https://docs.vllm.ai/en/latest/getting_started/quickstart.html#openai-compatible-server).' + if (providerName === 'lmStudio') return 'If you would like to change this endpoint, please more about [Endpoints here](https://lmstudio.ai/docs/app/api/endpoints/openai).' + if (providerName === 'liteLLM') return 'Read more about endpoints [here](https://docs.litellm.ai/docs/providers/openai_compatible).' + + throw new Error(`subTextMdOfProviderName: Unknown provider name: "${providerName}"`) +} + type DisplayInfo = { title: string; placeholder: string; - subTextMd?: string; isPasswordField?: boolean; } export const displayInfoOfSettingName = (providerName: ProviderName, settingName: SettingName): DisplayInfo => { @@ -140,23 +143,15 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName providerName === 'openAI' ? 'sk-proj-key...' : providerName === 'deepseek' ? 'sk-key...' : providerName === 'openRouter' ? 'sk-or-key...' : // sk-or-v1-key - providerName === 'gemini' ? 'key...' : + providerName === 'gemini' ? 'AIzaSy...' : providerName === 'groq' ? 'gsk_key...' : providerName === 'openAICompatible' ? 'sk-key...' : providerName === 'xAI' ? 'xai-key...' : providerName === 'mistral' ? 'api-key...' : - '', + providerName === 'googleVertex' ? 'AIzaSy...' : + providerName === 'microsoftAzure' ? 'key-...' : + '', - subTextMd: providerName === 'anthropic' ? 'Get your [API Key here](https://console.anthropic.com/settings/keys).' : - providerName === 'openAI' ? 'Get your [API Key here](https://platform.openai.com/api-keys).' : - providerName === 'deepseek' ? 'Get your [API Key here](https://platform.deepseek.com/api_keys).' : - providerName === 'openRouter' ? 'Get your [API Key here](https://openrouter.ai/settings/keys).' : - providerName === 'gemini' ? 'Get your [API Key here](https://aistudio.google.com/apikey).' : - providerName === 'groq' ? 'Get your [API Key here](https://console.groq.com/keys).' : - providerName === 'xAI' ? 'Get your [API Key here](https://console.x.ai).' : - providerName === 'mistral' ? 'Get your [API Key here](https://console.mistral.ai/api-keys).' : - providerName === 'openAICompatible' ? `Use any OpenAI-compatible endpoint (LM Studio, LiteLM, etc).` : - '', isPasswordField: true, } } @@ -164,19 +159,51 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName return { title: providerName === 'ollama' ? 'Endpoint' : providerName === 'vLLM' ? 'Endpoint' : - providerName === 'openAICompatible' ? 'baseURL' : // (do not include /chat/completions) - '(never)', + providerName === 'lmStudio' ? 'Endpoint' : + providerName === 'openAICompatible' ? 'baseURL' : // (do not include /chat/completions) + providerName === 'googleVertex' ? 'baseURL' : + providerName === 'microsoftAzure' ? 'baseURL' : + providerName === 'liteLLM' ? 'baseURL' : + '(never)', placeholder: providerName === 'ollama' ? defaultProviderSettings.ollama.endpoint : providerName === 'vLLM' ? defaultProviderSettings.vLLM.endpoint : providerName === 'openAICompatible' ? 'https://my-website.com/v1' - : '(never)', + : providerName === 'lmStudio' ? defaultProviderSettings.lmStudio.endpoint + : providerName === 'liteLLM' ? 'http://localhost:4000' + : '(never)', + - subTextMd: providerName === 'ollama' ? 'If you would like to change this endpoint, please read more about [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).' : - providerName === 'vLLM' ? 'If you would like to change this endpoint, please read more about [Endpoints here](https://docs.vllm.ai/en/latest/getting_started/quickstart.html#openai-compatible-server).' : - undefined, } } + else if (settingName === 'region') { + // vertex only + return { + title: 'Region', + placeholder: providerName === 'googleVertex' ? defaultProviderSettings.googleVertex.region + : '' + } + } + else if (settingName === 'azureApiVersion') { + // azure only + return { + title: 'API Version', + placeholder: providerName === 'microsoftAzure' ? defaultProviderSettings.microsoftAzure.azureApiVersion + : '' + } + } + else if (settingName === 'project') { + return { + title: providerName === 'googleVertex' ? 'Project' + : providerName === 'microsoftAzure' ? 'Resource' + : '', + placeholder: providerName === 'googleVertex' ? 'my-project' + : providerName === 'microsoftAzure' ? 'my-resource' + : '' + + } + + } else if (settingName === '_didFillInProviderSettings') { return { title: '(never)', @@ -200,6 +227,9 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName const defaultCustomSettings: Record = { apiKey: undefined, endpoint: undefined, + region: undefined, + project: undefined, + azureApiVersion: undefined, } @@ -207,8 +237,7 @@ const modelInfoOfDefaultModelNames = (defaultModelNames: string[]): { models: Vo return { models: defaultModelNames.map((modelName, i) => ({ modelName, - isDefault: true, - isAutodetected: false, + type: 'default', isHidden: defaultModelNames.length >= 10, // hide all models if there are a ton of them, and make user enable them individually })) } @@ -252,6 +281,18 @@ export const defaultSettingsOfProvider: SettingsOfProvider = { ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.mistral), _didFillInProviderSettings: undefined, }, + liteLLM: { + ...defaultCustomSettings, + ...defaultProviderSettings.liteLLM, + ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.liteLLM), + _didFillInProviderSettings: undefined, + }, + lmStudio: { + ...defaultCustomSettings, + ...defaultProviderSettings.lmStudio, + ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.lmStudio), + _didFillInProviderSettings: undefined, + }, groq: { // aggregator (serves models from multiple providers) ...defaultCustomSettings, ...defaultProviderSettings.groq, @@ -282,6 +323,18 @@ export const defaultSettingsOfProvider: SettingsOfProvider = { ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.vLLM), _didFillInProviderSettings: undefined, }, + googleVertex: { // aggregator (serves models from multiple providers) + ...defaultCustomSettings, + ...defaultProviderSettings.googleVertex, + ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.googleVertex), + _didFillInProviderSettings: undefined, + }, + microsoftAzure: { // aggregator (serves models from multiple providers) + ...defaultCustomSettings, + ...defaultProviderSettings.microsoftAzure, + ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.microsoftAzure), + _didFillInProviderSettings: undefined, + }, } diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 1645cdac..6cf66205 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -10,6 +10,7 @@ import { Ollama } from 'ollama'; import OpenAI, { ClientOptions } from 'openai'; import { MistralCore } from '@mistralai/mistralai/core.js'; import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js'; +import { GoogleAuth } from 'google-auth-library' /* eslint-enable */ import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js'; @@ -19,6 +20,8 @@ import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGramma import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js'; + + type InternalCommonMessageParams = { onText: OnText; onFinalMessage: OnFinalMessage; @@ -39,7 +42,16 @@ const invalidApiKeyMessage = (providerName: ProviderName) => `Invalid ${displayI // ------------ OPENAI-COMPATIBLE (HELPERS) ------------ -const newOpenAICompatibleSDK = ({ settingsOfProvider, providerName, includeInPayload }: { settingsOfProvider: SettingsOfProvider, providerName: ProviderName, includeInPayload?: { [s: string]: any } }) => { +const getGoogleApiKey = async () => { + // module‑level singleton + const auth = new GoogleAuth({ scopes: `https://www.googleapis.com/auth/cloud-platform` }); + const key = await auth.getAccessToken() + if (!key) throw new Error(`Google API failed to generate a key.`) + return key +} + + +const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includeInPayload }: { settingsOfProvider: SettingsOfProvider, providerName: ProviderName, includeInPayload?: { [s: string]: any } }) => { const commonPayloadOpts: ClientOptions = { dangerouslyAllowBrowser: true, ...includeInPayload, @@ -56,6 +68,14 @@ const newOpenAICompatibleSDK = ({ settingsOfProvider, providerName, includeInPay const thisConfig = settingsOfProvider[providerName] return new OpenAI({ baseURL: `${thisConfig.endpoint}/v1`, apiKey: 'noop', ...commonPayloadOpts }) } + else if (providerName === 'liteLLM') { + const thisConfig = settingsOfProvider[providerName] + return new OpenAI({ baseURL: `${thisConfig.endpoint}/v1`, apiKey: 'noop', ...commonPayloadOpts }) + } + else if (providerName === 'lmStudio') { + const thisConfig = settingsOfProvider[providerName] + return new OpenAI({ baseURL: `${thisConfig.endpoint}/v1`, apiKey: 'noop', ...commonPayloadOpts }) + } else if (providerName === 'openRouter') { const thisConfig = settingsOfProvider[providerName] return new OpenAI({ @@ -70,8 +90,22 @@ const newOpenAICompatibleSDK = ({ settingsOfProvider, providerName, includeInPay } else if (providerName === 'gemini') { const thisConfig = settingsOfProvider[providerName] - return new OpenAI({ baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/', apiKey: thisConfig.apiKey, ...commonPayloadOpts }) + return new OpenAI({ baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai', apiKey: thisConfig.apiKey, ...commonPayloadOpts }) } + else if (providerName === 'googleVertex') { + // https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library + const apiKey = await getGoogleApiKey() + const thisConfig = settingsOfProvider[providerName] + const baseURL = `https://${thisConfig.region}-aiplatform.googleapis.com/v1/projects/${thisConfig.project}/locations/${thisConfig.region}/endpoints/${'openapi'}` + return new OpenAI({ baseURL: baseURL, apiKey: apiKey, ...commonPayloadOpts }) + } + else if (providerName === 'microsoftAzure') { + // https://learn.microsoft.com/en-us/rest/api/aifoundry/model-inference/get-chat-completions/get-chat-completions?view=rest-aifoundry-model-inference-2024-05-01-preview&tabs=HTTP + const thisConfig = settingsOfProvider[providerName] + const baseURL = `https://${thisConfig.project}.services.ai.azure.com/api/models/chat/completions??api-version=${thisConfig.azureApiVersion}` + return new OpenAI({ baseURL: baseURL, apiKey: thisConfig.apiKey, ...commonPayloadOpts }) + } + else if (providerName === 'deepseek') { const thisConfig = settingsOfProvider[providerName] return new OpenAI({ baseURL: 'https://api.deepseek.com/v1', apiKey: thisConfig.apiKey, ...commonPayloadOpts }) @@ -97,7 +131,7 @@ const newOpenAICompatibleSDK = ({ settingsOfProvider, providerName, includeInPay } -const _sendOpenAICompatibleFIM = ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, }: SendFIMParams_Internal) => { +const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, }: SendFIMParams_Internal) => { const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_) if (!supportsFIM) { if (modelName === modelName_) @@ -107,7 +141,7 @@ const _sendOpenAICompatibleFIM = ({ messages: { prefix, suffix, stopTokens }, on return } - const openai = newOpenAICompatibleSDK({ providerName, settingsOfProvider }) + const openai = await newOpenAICompatibleSDK({ providerName, settingsOfProvider }) openai.completions .create({ model: modelName, @@ -178,7 +212,7 @@ const openAIToolToRawToolCallObj = (name: string, toolParamsStr: string, id: str // ------------ OPENAI-COMPATIBLE ------------ -const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage }: SendChatParams_Internal) => { +const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage }: SendChatParams_Internal) => { const { modelName, specialToolFormat, @@ -199,7 +233,7 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError, : {} // instance - const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload }) + const openai: OpenAI = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload }) const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelName, messages: messages as any, @@ -300,7 +334,7 @@ const _openaiCompatibleList = async ({ onSuccess: onSuccess_, onError: onError_, onError_({ error }) } try { - const openai = newOpenAICompatibleSDK({ providerName, settingsOfProvider }) + const openai = await newOpenAICompatibleSDK({ providerName, settingsOfProvider }) openai.models.list() .then(async (response) => { const models: OpenAIModel[] = [] @@ -360,7 +394,7 @@ const anthropicToolToRawToolCallObj = (toolBlock: Anthropic.Messages.ToolUseBloc } // ------------ ANTHROPIC ------------ -const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => { +const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => { const { modelName, specialToolFormat, @@ -505,6 +539,7 @@ const sendMistralFIM = ({ messages, onFinalMessage, onError, settingsOfProvider, stop: messages.stopTokens, }) .then(async response => { + // unfortunately, _setAborter() does not exist let content = response?.ok ? response.value.choices?.[0]?.message?.content ?? '' : ''; const fullText = typeof content === 'string' ? content : content.map(chunk => (chunk.type === 'text' ? chunk.text : '')).join('') @@ -584,7 +619,7 @@ const sendOllamaFIM = ({ messages, onFinalMessage, onError, settingsOfProvider, type CallFnOfProvider = { [providerName in ProviderName]: { - sendChat: (params: SendChatParams_Internal) => void; + sendChat: (params: SendChatParams_Internal) => Promise; sendFIM: ((params: SendFIMParams_Internal) => void) | null; list: ((params: ListParams_Internal) => void) | null; } @@ -646,6 +681,27 @@ export const sendLLMMessageToProviderImplementation = { sendFIM: null, list: null, }, + + lmStudio: { + sendChat: (params) => _sendOpenAICompatibleChat(params), + sendFIM: null, // lmStudio has no suffix parameter in /completions + list: (params) => _openaiCompatibleList(params), + }, + liteLLM: { + sendChat: (params) => _sendOpenAICompatibleChat(params), + sendFIM: null, + list: null, + }, + googleVertex: { + sendChat: (params) => _sendOpenAICompatibleChat(params), + sendFIM: null, + list: null, + }, + microsoftAzure: { + sendChat: (params) => _sendOpenAICompatibleChat(params), + sendFIM: null, + list: null, + }, } satisfies CallFnOfProvider diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts index 1554aeeb..a7c440d4 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts @@ -9,7 +9,7 @@ import { displayInfoOfProviderName } from '../../common/voidSettingsTypes.js'; import { sendLLMMessageToProviderImplementation } from './sendLLMMessage.impl.js'; -export const sendLLMMessage = ({ +export const sendLLMMessage = async ({ messagesType, messages: messages_, onText: onText_, @@ -108,12 +108,12 @@ export const sendLLMMessage = ({ } const { sendFIM, sendChat } = implementation if (messagesType === 'chatMessages') { - sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage, chatMode }) + await sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage, chatMode }) return } if (messagesType === 'FIMMessage') { if (sendFIM) { - sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage }) + await sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage }) return } onError({ message: `Error: This provider does not support Autocomplete yet.`, fullError: null }) diff --git a/src/vs/workbench/contrib/void/electron-main/sendLLMMessageChannel.ts b/src/vs/workbench/contrib/void/electron-main/sendLLMMessageChannel.ts index 182ce580..bff528ce 100644 --- a/src/vs/workbench/contrib/void/electron-main/sendLLMMessageChannel.ts +++ b/src/vs/workbench/contrib/void/electron-main/sendLLMMessageChannel.ts @@ -8,7 +8,7 @@ import { IServerChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, MainSendLLMMessageParams, AbortRef, SendLLMMessageParams, MainLLMMessageAbortParams, ModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, OllamaModelResponse, VLLMModelResponse, MainModelListParams, } from '../common/sendLLMMessageTypes.js'; +import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, MainSendLLMMessageParams, AbortRef, SendLLMMessageParams, MainLLMMessageAbortParams, ModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, OllamaModelResponse, OpenaiCompatibleModelResponse, MainModelListParams, } from '../common/sendLLMMessageTypes.js'; import { sendLLMMessage } from './llmMessage/sendLLMMessage.js' import { IMetricsService } from '../common/metricsService.js'; import { sendLLMMessageToProviderImplementation } from './llmMessage/sendLLMMessage.impl.js'; @@ -25,7 +25,7 @@ export class LLMMessageChannel implements IServerChannel { } // aborters for above - private readonly abortRefOfRequestId: Record = {} + private readonly _infoOfRunningRequest: Record | undefined, abortRef: AbortRef }> = {} // list @@ -34,12 +34,12 @@ export class LLMMessageChannel implements IServerChannel { success: new Emitter>(), error: new Emitter>(), }, - vLLM: { - success: new Emitter>(), - error: new Emitter>(), - } + openaiCompat: { + success: new Emitter>(), + error: new Emitter>(), + }, } satisfies { - [providerName: string]: { + [providerName in 'ollama' | 'openaiCompat']: { success: Emitter>, error: Emitter>, } @@ -59,8 +59,8 @@ export class LLMMessageChannel implements IServerChannel { // list else if (event === 'onSuccess_list_ollama') return this.listEmitters.ollama.success.event; else if (event === 'onError_list_ollama') return this.listEmitters.ollama.error.event; - else if (event === 'onSuccess_list_vLLM') return this.listEmitters.vLLM.success.event; - else if (event === 'onError_list_vLLM') return this.listEmitters.vLLM.error.event; + else if (event === 'onSuccess_list_openAICompatible') return this.listEmitters.openaiCompat.success.event; + else if (event === 'onError_list_openAICompatible') return this.listEmitters.openaiCompat.error.event; else throw new Error(`Event not found: ${event}`); } @@ -72,13 +72,13 @@ export class LLMMessageChannel implements IServerChannel { this._callSendLLMMessage(params) } else if (command === 'abort') { - this._callAbort(params) + await this._callAbort(params) } else if (command === 'ollamaList') { this._callOllamaList(params) } - else if (command === 'vLLMList') { - this._callVLLMList(params) + else if (command === 'openAICompatibleList') { + this._callOpenAICompatibleList(params) } else { throw new Error(`Void sendLLM: command "${command}" not recognized.`) @@ -90,27 +90,37 @@ export class LLMMessageChannel implements IServerChannel { } // the only place sendLLMMessage is actually called - private async _callSendLLMMessage(params: MainSendLLMMessageParams) { + private _callSendLLMMessage(params: MainSendLLMMessageParams) { const { requestId } = params; - if (!(requestId in this.abortRefOfRequestId)) - this.abortRefOfRequestId[requestId] = { current: null } + if (!(requestId in this._infoOfRunningRequest)) + this._infoOfRunningRequest[requestId] = { waitForSend: undefined, abortRef: { current: null } } const mainThreadParams: SendLLMMessageParams = { ...params, - onText: (p) => { this.llmMessageEmitters.onText.fire({ requestId, ...p }); }, - onFinalMessage: (p) => { this.llmMessageEmitters.onFinalMessage.fire({ requestId, ...p }); }, - onError: (p) => { console.log('sendLLM: firing err'); this.llmMessageEmitters.onError.fire({ requestId, ...p }); }, - abortRef: this.abortRefOfRequestId[requestId], + onText: (p) => { + this.llmMessageEmitters.onText.fire({ requestId, ...p }); + }, + onFinalMessage: (p) => { + this.llmMessageEmitters.onFinalMessage.fire({ requestId, ...p }); + }, + onError: (p) => { + console.log('sendLLM: firing err'); + this.llmMessageEmitters.onError.fire({ requestId, ...p }); + }, + abortRef: this._infoOfRunningRequest[requestId].abortRef, } - sendLLMMessage(mainThreadParams, this.metricsService); + const p = sendLLMMessage(mainThreadParams, this.metricsService); + this._infoOfRunningRequest[requestId].waitForSend = p } - private _callAbort(params: MainLLMMessageAbortParams) { + private async _callAbort(params: MainLLMMessageAbortParams) { const { requestId } = params; - if (!(requestId in this.abortRefOfRequestId)) return - this.abortRefOfRequestId[requestId].current?.() - delete this.abortRefOfRequestId[requestId] + if (!(requestId in this._infoOfRunningRequest)) return + const { waitForSend, abortRef } = this._infoOfRunningRequest[requestId] + await waitForSend // wait for the send to finish so we know abortRef was set + abortRef?.current?.() + delete this._infoOfRunningRequest[requestId] } @@ -128,15 +138,15 @@ export class LLMMessageChannel implements IServerChannel { sendLLMMessageToProviderImplementation.ollama.list(mainThreadParams) } - _callVLLMList = (params: MainModelListParams) => { - const { requestId } = params - const emitters = this.listEmitters.vLLM - const mainThreadParams: ModelListParams = { + _callOpenAICompatibleList = (params: MainModelListParams) => { + const { requestId, providerName } = params + const emitters = this.listEmitters.openaiCompat + const mainThreadParams: ModelListParams = { ...params, onSuccess: (p) => { emitters.success.fire({ requestId, ...p }); }, onError: (p) => { emitters.error.fire({ requestId, ...p }); }, } - sendLLMMessageToProviderImplementation.vLLM.list(mainThreadParams) + sendLLMMessageToProviderImplementation[providerName].list(mainThreadParams) }