diff --git a/bridge/package.json b/bridge/package.json index 1fb23b9..9e1b42c 100644 --- a/bridge/package.json +++ b/bridge/package.json @@ -7,21 +7,28 @@ "dev": "ts-node-dev --respawn --transpile-only src/index.ts", "start": "node dist/index.cjs", "build": "tsc --project tsconfig.json && esbuild src/index.ts --bundle --platform=node --outfile=dist/index.cjs --format=cjs --packages=external", + "build:bundle-for-pkg": "esbuild src/index.ts --bundle --platform=node --outfile=dist/index.bundled.cjs --format=cjs --external:better-sqlite3 --external:@napi-rs/keyring --external:ssh2 --external:cpu-features --external:pg-native", "copy:native": "node scripts/copy-native.js", "rebuild:native": "npm rebuild better-sqlite3", - "build:pkg:win": "npm run build && npm run rebuild:native && npm run copy:native && npx @yao-pkg/pkg . --target node22-win-x64 --output ../src-tauri/resources/bridge-x86_64-pc-windows-msvc.exe", - "build:pkg:linux": "npm run build && npm run rebuild:native && npm run copy:native && npx @yao-pkg/pkg . --target node22-linux-x64 --output ../src-tauri/resources/bridge-x86_64-unknown-linux-gnu", + "build:pkg:win": "npm run build && npm run build:bundle-for-pkg && npm run rebuild:native && npm run copy:native && npx @yao-pkg/pkg dist/index.bundled.cjs --target node22-win-x64 --output ../src-tauri/resources/bridge-x86_64-pc-windows-msvc.exe", + "build:pkg:linux": "npm run build && npm run build:bundle-for-pkg && npm run rebuild:native && npm run copy:native && npx @yao-pkg/pkg dist/index.bundled.cjs --target node22-linux-x64 --output ../src-tauri/resources/bridge-x86_64-unknown-linux-gnu", "test": "jest", "test:watch": "jest --watchAll --detectOpenHandles" }, "dependencies": { + "@anthropic-ai/sdk": "^0.100.1", + "@google/generative-ai": "^0.24.1", "@jest/globals": "^30.2.0", + "@mistralai/mistralai": "^2.2.5", "@napi-rs/keyring": "^1.2.0", "@types/ssh2": "^1.15.5", "bcryptjs": "^3.0.3", "better-sqlite3": "^11.9.0", "dotenv": "^17.2.3", + "groq-sdk": "^1.2.1", "mysql2": "^3.15.3", + "ollama": "^0.6.3", + "openai": "^6.41.0", "pg": "^8.16.3", "pg-query-stream": "^4.10.3", "pino": "^9.14.0", @@ -53,4 +60,4 @@ "ts-node-dev": "^2.0.0", "typescript": "^5.0.0" } -} +} \ No newline at end of file diff --git a/bridge/pnpm-lock.yaml b/bridge/pnpm-lock.yaml index 9f5d41d..f00a3ba 100644 --- a/bridge/pnpm-lock.yaml +++ b/bridge/pnpm-lock.yaml @@ -8,9 +8,18 @@ importers: .: dependencies: + '@anthropic-ai/sdk': + specifier: ^0.100.1 + version: 0.100.1(zod@4.4.3) + '@google/generative-ai': + specifier: ^0.24.1 + version: 0.24.1 '@jest/globals': specifier: ^30.2.0 version: 30.2.0 + '@mistralai/mistralai': + specifier: ^2.2.5 + version: 2.2.5 '@napi-rs/keyring': specifier: ^1.2.0 version: 1.2.0 @@ -22,19 +31,28 @@ importers: version: 3.0.3 better-sqlite3: specifier: ^11.9.0 - version: 11.9.0 + version: 11.10.0 dotenv: specifier: ^17.2.3 - version: 17.2.3 + version: 17.4.2 + groq-sdk: + specifier: ^1.2.1 + version: 1.2.1 mysql2: specifier: ^3.15.3 version: 3.15.3 + ollama: + specifier: ^0.6.3 + version: 0.6.3 + openai: + specifier: ^6.41.0 + version: 6.41.0(ws@8.20.1)(zod@4.4.3) pg: specifier: ^8.16.3 - version: 8.16.3 + version: 8.21.0 pg-query-stream: specifier: ^4.10.3 - version: 4.10.3(pg@8.16.3) + version: 4.10.3(pg@8.21.0) pino: specifier: ^9.14.0 version: 9.14.0 @@ -46,7 +64,7 @@ importers: version: 8.3.2 ws: specifier: ^8.19.0 - version: 8.19.0 + version: 8.20.1 devDependencies: '@types/better-sqlite3': specifier: ^7.6.13 @@ -56,70 +74,79 @@ importers: version: 30.0.0 '@types/node': specifier: ^20.0.0 - version: 20.19.25 + version: 20.19.41 '@types/pg': specifier: ^8.15.6 - version: 8.15.6 + version: 8.20.0 '@yao-pkg/pkg': specifier: ^5.12.0 version: 5.16.1 esbuild: specifier: ^0.27.0 - version: 0.27.0 + version: 0.27.7 jest: specifier: ^30.2.0 - version: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + version: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) ts-jest: specifier: ^29.4.6 - version: 29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.27.7)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) + version: 10.9.2(@types/node@20.19.41)(typescript@5.9.3) ts-node-dev: specifier: ^2.0.0 - version: 2.0.0(@types/node@20.19.25)(typescript@5.9.3) + version: 2.0.0(@types/node@20.19.41)(typescript@5.9.3) typescript: specifier: ^5.0.0 version: 5.9.3 packages: - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + '@anthropic-ai/sdk@0.100.1': + resolution: {integrity: sha512-RANcEe7LpiLczkKGOwoXOTuFdPhuubS0i4xaAKOMpcqc55YO0mukgxppV7eygx3DXNjxWT6RYOLPyOy0aIAmwg==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.27.1': @@ -134,12 +161,12 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} hasBin: true @@ -180,8 +207,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -228,22 +255,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -253,171 +284,175 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@emnapi/core@1.7.1': - resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/runtime@1.7.1': - resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/wasi-threads@1.1.0': - resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@esbuild/aix-ppc64@0.27.0': - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.0': - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.0': - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.0': - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.0': - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.0': - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.0': - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.0': - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.0': - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.0': - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.0': - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.0': - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.0': - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.0': - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.0': - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.0': - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.0': - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.0': - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.0': - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.0': - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.0': - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.0': - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.0': - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.0': - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.0': - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.0': - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@google/generative-ai@0.24.1': + resolution: {integrity: sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==} + engines: {node: '>=18.0.0'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -531,6 +566,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mistralai/mistralai@2.2.5': + resolution: {integrity: sha512-ATbWzKkNzNAZ+gtw9MI/c/ULTMG80tKUiRNIbQFfg4OP0uEZZpTfXZeBCNfs5Dq0uqMQ/tQWc4o6RRJQtMrpDA==} + '@napi-rs/keyring-darwin-arm64@1.2.0': resolution: {integrity: sha512-CA83rDeyONDADO25JLZsh3eHY8yTEtm/RS6ecPsY+1v+dSawzT9GywBMu2r6uOp1IEhQs/xAfxgybGAFr17lSA==} engines: {node: '>= 10'} @@ -630,6 +668,9 @@ packages: '@sinonjs/fake-timers@13.0.5': resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@tsconfig/node10@1.0.12': resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} @@ -675,11 +716,11 @@ packages: '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@20.19.25': - resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} + '@types/node@20.19.41': + resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} - '@types/pg@8.15.6': - resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/pg@8.20.0': + resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} '@types/ssh2@1.15.5': resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} @@ -809,8 +850,8 @@ packages: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true @@ -894,8 +935,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.5: - resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} + baseline-browser-mapping@2.10.31: + resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==} + engines: {node: '>=6.0.0'} hasBin: true bcrypt-pbkdf@1.0.2: @@ -905,8 +947,8 @@ packages: resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} hasBin: true - better-sqlite3@11.9.0: - resolution: {integrity: sha512-4b9xYnoaskj8eIkke9ZCB42p5bOPabptSku8Rl4Yww70Jf+aHeLvrIjXDJrKQxUEjdppsFb+fdJSjoH4TklROA==} + better-sqlite3@11.10.0: + resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} @@ -918,18 +960,18 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -962,8 +1004,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001760: - resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -980,8 +1022,8 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - ci-info@4.3.1: - resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} engines: {node: '>=8'} cjs-module-lexer@2.1.1: @@ -1041,8 +1083,8 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - dedent@1.7.0: - resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -1069,12 +1111,12 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} - dotenv@17.2.3: - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} dynamic-dedupe@0.3.0: @@ -1083,8 +1125,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.267: - resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + electron-to-chromium@1.5.361: + resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -1102,8 +1144,12 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} hasBin: true @@ -1139,6 +1185,9 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -1220,6 +1269,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + groq-sdk@1.2.1: + resolution: {integrity: sha512-dsDSWJRJf+n2dPiCv7zU3IsJbrh7jfSPqi6vc1q0TTK1oUF6bn+wv4P2VFdynkHpuJ0TTJ57vlpT87judPgVPA==} + hasBin: true + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -1244,8 +1297,8 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - iconv-lite@0.7.0: - resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} ieee754@1.2.1: @@ -1484,6 +1537,10 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1512,12 +1569,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - lru.min@1.1.3: - resolution: {integrity: sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==} + lru.min@1.1.4: + resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} make-dir@4.0.0: @@ -1545,11 +1598,11 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: @@ -1577,9 +1630,9 @@ packages: resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==} engines: {node: '>= 8.0'} - named-placeholders@1.1.3: - resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} - engines: {node: '>=12.0.0'} + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} nan@2.25.0: resolution: {integrity: sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==} @@ -1598,8 +1651,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - node-abi@3.85.0: - resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} + node-abi@3.87.0: + resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} engines: {node: '>=10'} node-fetch@2.7.0: @@ -1614,8 +1667,9 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -1625,6 +1679,9 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + ollama@0.6.3: + resolution: {integrity: sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -1636,6 +1693,17 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + openai@6.41.0: + resolution: {integrity: sha512-IGWPopZq6Rjoynjfb3NSLf/z2MTw7UiOsm9TAjPGAjUESH7Uq41Trg4QWehBEn58p74i+m7uoRPV2vXcpPXhyA==} + peerDependencies: + ws: ^8.18.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + p-is-promise@3.0.0: resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==} engines: {node: '>=8'} @@ -1682,11 +1750,11 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - pg-cloudflare@1.2.7: - resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} - pg-connection-string@2.9.1: - resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + pg-connection-string@2.13.0: + resolution: {integrity: sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==} pg-cursor@2.15.3: resolution: {integrity: sha512-eHw63TsiGtFEfAd7tOTZ+TLy+i/2ePKS20H84qCQ+aQ60pve05Okon9tKMC+YN3j6XyeFoHnaim7Lt9WVafQsA==} @@ -1697,13 +1765,13 @@ packages: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.10.1: - resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} peerDependencies: pg: '>=8.0' - pg-protocol@1.10.3: - resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + pg-protocol@1.14.0: + resolution: {integrity: sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==} pg-query-stream@4.10.3: resolution: {integrity: sha512-h2utrzpOIzeT9JfaqfvBbVuvCfBjH86jNfVrGGTbyepKAIOyTfDew0lAt8bbJjs9n/I5bGDl7S2sx6h5hPyJxw==} @@ -1714,8 +1782,8 @@ packages: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.16.3: - resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + pg@8.21.0: + resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -1729,12 +1797,12 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pino-abstract-transport@2.0.0: @@ -1759,8 +1827,8 @@ packages: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - postgres-bytea@1.0.0: - resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} engines: {node: '>=0.10.0'} postgres-date@1.0.7: @@ -1791,8 +1859,8 @@ packages: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} pure-rand@7.0.1: resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} @@ -1834,8 +1902,8 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true @@ -1861,8 +1929,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} hasBin: true @@ -1926,6 +1994,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + stream-meter@1.0.4: resolution: {integrity: sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==} @@ -1951,8 +2022,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -2005,8 +2076,8 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} tmpl@1.0.5: @@ -2023,6 +2094,9 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + ts-jest@29.4.6: resolution: {integrity: sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} @@ -2118,8 +2192,8 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - update-browserslist-db@1.2.2: - resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2129,6 +2203,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -2144,6 +2219,9 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -2170,8 +2248,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2217,27 +2295,42 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + snapshots: - '@babel/code-frame@7.27.1': + '@anthropic-ai/sdk@0.100.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + standardwebhooks: 1.0.0 + optionalDependencies: + zod: 4.4.3 + + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.29.0': {} - '@babel/core@7.28.5': + '@babel/core@7.29.0': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -2247,41 +2340,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': + '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.2': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.29.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 '@babel/helper-globals@7.28.0': {} - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-plugin-utils@7.28.6': {} '@babel/helper-string-parser@7.27.1': {} @@ -2289,119 +2382,121 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.4': + '@babel/helpers@7.28.6': dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 - '@babel/parser@7.28.5': + '@babel/parser@7.29.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/template@7.27.2': + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 - '@babel/traverse@7.28.5': + '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 @@ -2412,105 +2507,107 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@emnapi/core@1.7.1': + '@emnapi/core@1.10.0': dependencies: - '@emnapi/wasi-threads': 1.1.0 + '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.7.1': + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.1.0': + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.0': + '@esbuild/aix-ppc64@0.27.7': optional: true - '@esbuild/android-arm64@0.27.0': + '@esbuild/android-arm64@0.27.7': optional: true - '@esbuild/android-arm@0.27.0': + '@esbuild/android-arm@0.27.7': optional: true - '@esbuild/android-x64@0.27.0': + '@esbuild/android-x64@0.27.7': optional: true - '@esbuild/darwin-arm64@0.27.0': + '@esbuild/darwin-arm64@0.27.7': optional: true - '@esbuild/darwin-x64@0.27.0': + '@esbuild/darwin-x64@0.27.7': optional: true - '@esbuild/freebsd-arm64@0.27.0': + '@esbuild/freebsd-arm64@0.27.7': optional: true - '@esbuild/freebsd-x64@0.27.0': + '@esbuild/freebsd-x64@0.27.7': optional: true - '@esbuild/linux-arm64@0.27.0': + '@esbuild/linux-arm64@0.27.7': optional: true - '@esbuild/linux-arm@0.27.0': + '@esbuild/linux-arm@0.27.7': optional: true - '@esbuild/linux-ia32@0.27.0': + '@esbuild/linux-ia32@0.27.7': optional: true - '@esbuild/linux-loong64@0.27.0': + '@esbuild/linux-loong64@0.27.7': optional: true - '@esbuild/linux-mips64el@0.27.0': + '@esbuild/linux-mips64el@0.27.7': optional: true - '@esbuild/linux-ppc64@0.27.0': + '@esbuild/linux-ppc64@0.27.7': optional: true - '@esbuild/linux-riscv64@0.27.0': + '@esbuild/linux-riscv64@0.27.7': optional: true - '@esbuild/linux-s390x@0.27.0': + '@esbuild/linux-s390x@0.27.7': optional: true - '@esbuild/linux-x64@0.27.0': + '@esbuild/linux-x64@0.27.7': optional: true - '@esbuild/netbsd-arm64@0.27.0': + '@esbuild/netbsd-arm64@0.27.7': optional: true - '@esbuild/netbsd-x64@0.27.0': + '@esbuild/netbsd-x64@0.27.7': optional: true - '@esbuild/openbsd-arm64@0.27.0': + '@esbuild/openbsd-arm64@0.27.7': optional: true - '@esbuild/openbsd-x64@0.27.0': + '@esbuild/openbsd-x64@0.27.7': optional: true - '@esbuild/openharmony-arm64@0.27.0': + '@esbuild/openharmony-arm64@0.27.7': optional: true - '@esbuild/sunos-x64@0.27.0': + '@esbuild/sunos-x64@0.27.7': optional: true - '@esbuild/win32-arm64@0.27.0': + '@esbuild/win32-arm64@0.27.7': optional: true - '@esbuild/win32-ia32@0.27.0': + '@esbuild/win32-ia32@0.27.7': optional: true - '@esbuild/win32-x64@0.27.0': + '@esbuild/win32-x64@0.27.7': optional: true + '@google/generative-ai@0.24.1': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -2528,13 +2625,13 @@ snapshots: '@jest/console@30.2.0': dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 jest-message-util: 30.2.0 jest-util: 30.2.0 slash: 3.0.0 - '@jest/core@30.2.0(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))': + '@jest/core@30.2.0(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3))': dependencies: '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 @@ -2542,14 +2639,14 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 ansi-escapes: 4.3.2 chalk: 4.1.2 - ci-info: 4.3.1 + ci-info: 4.4.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -2576,7 +2673,7 @@ snapshots: dependencies: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-mock: 30.2.0 '@jest/expect-utils@30.2.0': @@ -2594,7 +2691,7 @@ snapshots: dependencies: '@jest/types': 30.2.0 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-message-util: 30.2.0 jest-mock: 30.2.0 jest-util: 30.2.0 @@ -2612,7 +2709,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-regex-util: 30.0.1 '@jest/reporters@30.2.0': @@ -2623,7 +2720,7 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -2676,7 +2773,7 @@ snapshots: '@jest/transform@30.2.0': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 7.0.1 @@ -2700,7 +2797,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.25 + '@types/node': 20.19.41 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -2728,6 +2825,15 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mistralai/mistralai@2.2.5': + dependencies: + ws: 8.20.1 + zod: 4.4.3 + zod-to-json-schema: 3.25.1(zod@4.4.3) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@napi-rs/keyring-darwin-arm64@1.2.0': optional: true @@ -2781,8 +2887,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.7.1 - '@emnapi/runtime': 1.7.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true @@ -2803,6 +2909,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@stablelib/base64@1.0.1': {} + '@tsconfig/node10@1.0.12': {} '@tsconfig/node12@1.0.11': {} @@ -2818,28 +2926,28 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 20.19.25 + '@types/node': 20.19.41 '@types/istanbul-lib-coverage@2.0.6': {} @@ -2860,14 +2968,14 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.19.25': + '@types/node@20.19.41': dependencies: undici-types: 6.21.0 - '@types/pg@8.15.6': + '@types/pg@8.20.0': dependencies: - '@types/node': 20.19.25 - pg-protocol: 1.10.3 + '@types/node': 20.19.41 + pg-protocol: 1.14.0 pg-types: 2.2.0 '@types/ssh2@1.15.5': @@ -2953,7 +3061,7 @@ snapshots: node-fetch: 2.7.0 picocolors: 1.1.1 progress: 2.0.3 - semver: 7.7.3 + semver: 7.8.1 tar-fs: 2.1.4 yargs: 16.2.0 transitivePeerDependencies: @@ -2962,28 +3070,28 @@ snapshots: '@yao-pkg/pkg@5.16.1': dependencies: - '@babel/generator': 7.28.5 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@yao-pkg/pkg-fetch': 3.5.16 into-stream: 6.0.0 minimist: 1.2.8 multistream: 4.1.0 picocolors: 1.1.1 - picomatch: 4.0.3 + picomatch: 4.0.4 prebuild-install: 7.1.3 - resolve: 1.22.11 + resolve: 1.22.12 stream-meter: 1.0.4 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 transitivePeerDependencies: - encoding - supports-color acorn-walk@8.3.4: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - acorn@8.15.0: {} + acorn@8.16.0: {} agent-base@6.0.2: dependencies: @@ -3010,7 +3118,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 arg@4.1.3: {} @@ -3026,13 +3134,13 @@ snapshots: aws-ssl-profiles@1.1.2: {} - babel-jest@30.2.0(@babel/core@7.28.5): + babel-jest@30.2.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/transform': 30.2.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 7.0.1 - babel-preset-jest: 30.2.0(@babel/core@7.28.5) + babel-preset-jest: 30.2.0(@babel/core@7.29.0) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -3041,7 +3149,7 @@ snapshots: babel-plugin-istanbul@7.0.1: dependencies: - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 6.0.3 @@ -3053,36 +3161,36 @@ snapshots: dependencies: '@types/babel__core': 7.20.5 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.5): - dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.5) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) - - babel-preset-jest@30.2.0(@babel/core@7.28.5): - dependencies: - '@babel/core': 7.28.5 + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@30.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 babel-plugin-jest-hoist: 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) balanced-match@1.0.2: {} base64-js@1.5.1: {} - baseline-browser-mapping@2.9.5: {} + baseline-browser-mapping@2.10.31: {} bcrypt-pbkdf@1.0.2: dependencies: @@ -3090,7 +3198,7 @@ snapshots: bcryptjs@3.0.3: {} - better-sqlite3@11.9.0: + better-sqlite3@11.10.0: dependencies: bindings: 1.5.0 prebuild-install: 7.1.3 @@ -3107,12 +3215,12 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - brace-expansion@1.1.12: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@2.1.0: dependencies: balanced-match: 1.0.2 @@ -3120,13 +3228,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.1: + browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.9.5 - caniuse-lite: 1.0.30001760 - electron-to-chromium: 1.5.267 - node-releases: 2.0.27 - update-browserslist-db: 1.2.2(browserslist@4.28.1) + baseline-browser-mapping: 2.10.31 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.361 + node-releases: 2.0.46 + update-browserslist-db: 1.2.3(browserslist@4.28.2) bs-logger@0.2.6: dependencies: @@ -3152,7 +3260,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001760: {} + caniuse-lite@1.0.30001793: {} chalk@4.1.2: dependencies: @@ -3175,7 +3283,7 @@ snapshots: chownr@1.1.4: {} - ci-info@4.3.1: {} + ci-info@4.4.0: {} cjs-module-lexer@2.1.1: {} @@ -3229,7 +3337,7 @@ snapshots: dependencies: mimic-response: 3.1.0 - dedent@1.7.0: {} + dedent@1.7.2: {} deep-extend@0.6.0: {} @@ -3241,9 +3349,9 @@ snapshots: detect-newline@3.1.0: {} - diff@4.0.2: {} + diff@4.0.4: {} - dotenv@17.2.3: {} + dotenv@17.4.2: {} dynamic-dedupe@0.3.0: dependencies: @@ -3251,7 +3359,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.267: {} + electron-to-chromium@1.5.361: {} emittery@0.13.1: {} @@ -3267,34 +3375,36 @@ snapshots: dependencies: is-arrayish: 0.2.1 - esbuild@0.27.0: + es-errors@1.3.0: {} + + esbuild@0.27.7: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 escalade@3.2.0: {} @@ -3329,13 +3439,15 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-sha256@1.3.0: {} + fb-watchman@2.0.2: dependencies: bser: 2.1.1 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.4 file-uri-to-path@1.0.0: {} @@ -3389,7 +3501,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 + minimatch: 9.0.9 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -3399,12 +3511,14 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 graceful-fs@4.2.11: {} + groq-sdk@1.2.1: {} + handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -3431,7 +3545,7 @@ snapshots: human-signals@2.1.0: {} - iconv-lite@0.7.0: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -3492,11 +3606,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.3 + semver: 7.8.1 transitivePeerDependencies: - supports-color @@ -3537,10 +3651,10 @@ snapshots: '@jest/expect': 30.2.0 '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 co: 4.6.0 - dedent: 1.7.0 + dedent: 1.7.2 is-generator-fn: 2.1.0 jest-each: 30.2.0 jest-matcher-utils: 30.2.0 @@ -3557,15 +3671,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest-cli@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -3576,16 +3690,16 @@ snapshots: - supports-color - ts-node - jest-config@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest-config@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/get-type': 30.1.0 '@jest/pattern': 30.0.1 '@jest/test-sequencer': 30.2.0 '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) + babel-jest: 30.2.0(@babel/core@7.29.0) chalk: 4.1.2 - ci-info: 4.3.1 + ci-info: 4.4.0 deepmerge: 4.3.1 glob: 10.5.0 graceful-fs: 4.2.11 @@ -3603,8 +3717,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.19.25 - ts-node: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) + '@types/node': 20.19.41 + ts-node: 10.9.2(@types/node@20.19.41)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -3633,7 +3747,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-mock: 30.2.0 jest-util: 30.2.0 jest-validate: 30.2.0 @@ -3641,7 +3755,7 @@ snapshots: jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -3667,7 +3781,7 @@ snapshots: jest-message-util@30.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@jest/types': 30.2.0 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -3680,7 +3794,7 @@ snapshots: jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-util: 30.2.0 jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): @@ -3714,7 +3828,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -3743,7 +3857,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 cjs-module-lexer: 2.1.1 collect-v8-coverage: 1.0.3 @@ -3763,17 +3877,17 @@ snapshots: jest-snapshot@30.2.0: dependencies: - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) - '@babel/types': 7.28.5 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 '@jest/expect-utils': 30.2.0 '@jest/get-type': 30.1.0 '@jest/snapshot-utils': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) chalk: 4.1.2 expect: 30.2.0 graceful-fs: 4.2.11 @@ -3782,7 +3896,7 @@ snapshots: jest-message-util: 30.2.0 jest-util: 30.2.0 pretty-format: 30.2.0 - semver: 7.7.3 + semver: 7.8.1 synckit: 0.11.11 transitivePeerDependencies: - supports-color @@ -3790,11 +3904,11 @@ snapshots: jest-util@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 - ci-info: 4.3.1 + ci-info: 4.4.0 graceful-fs: 4.2.11 - picomatch: 4.0.3 + picomatch: 4.0.4 jest-validate@30.2.0: dependencies: @@ -3809,7 +3923,7 @@ snapshots: dependencies: '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3818,18 +3932,18 @@ snapshots: jest-worker@30.2.0: dependencies: - '@types/node': 20.19.25 + '@types/node': 20.19.41 '@ungap/structured-clone': 1.3.0 jest-util: 30.2.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-cli: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -3848,6 +3962,11 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.2 + ts-algebra: 2.0.0 + json5@2.2.3: {} leven@3.1.0: {} @@ -3868,13 +3987,11 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@7.18.3: {} - - lru.min@1.1.3: {} + lru.min@1.1.4: {} make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.8.1 make-error@1.3.6: {} @@ -3887,19 +4004,19 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mimic-fn@2.1.0: {} mimic-response@3.1.0: {} - minimatch@3.1.2: + minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.14 - minimatch@9.0.5: + minimatch@9.0.9: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.1.0 minimist@1.2.8: {} @@ -3921,16 +4038,16 @@ snapshots: aws-ssl-profiles: 1.1.2 denque: 2.1.0 generate-function: 2.3.1 - iconv-lite: 0.7.0 + iconv-lite: 0.7.2 long: 5.3.2 - lru.min: 1.1.3 - named-placeholders: 1.1.3 + lru.min: 1.1.4 + named-placeholders: 1.1.6 seq-queue: 0.0.5 sqlstring: 2.3.3 - named-placeholders@1.1.3: + named-placeholders@1.1.6: dependencies: - lru-cache: 7.18.3 + lru.min: 1.1.4 nan@2.25.0: optional: true @@ -3943,9 +4060,9 @@ snapshots: neo-async@2.6.2: {} - node-abi@3.85.0: + node-abi@3.87.0: dependencies: - semver: 7.7.3 + semver: 7.8.1 node-fetch@2.7.0: dependencies: @@ -3953,7 +4070,7 @@ snapshots: node-int64@0.4.0: {} - node-releases@2.0.27: {} + node-releases@2.0.46: {} normalize-path@3.0.0: {} @@ -3961,6 +4078,10 @@ snapshots: dependencies: path-key: 3.1.1 + ollama@0.6.3: + dependencies: + whatwg-fetch: 3.6.20 + on-exit-leak-free@2.1.2: {} once@1.4.0: @@ -3971,6 +4092,11 @@ snapshots: dependencies: mimic-fn: 2.1.0 + openai@6.41.0(ws@8.20.1)(zod@4.4.3): + optionalDependencies: + ws: 8.20.1 + zod: 4.4.3 + p-is-promise@3.0.0: {} p-limit@2.3.0: @@ -3991,7 +4117,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -4009,45 +4135,45 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - pg-cloudflare@1.2.7: + pg-cloudflare@1.4.0: optional: true - pg-connection-string@2.9.1: {} + pg-connection-string@2.13.0: {} - pg-cursor@2.15.3(pg@8.16.3): + pg-cursor@2.15.3(pg@8.21.0): dependencies: - pg: 8.16.3 + pg: 8.21.0 pg-int8@1.0.1: {} - pg-pool@3.10.1(pg@8.16.3): + pg-pool@3.14.0(pg@8.21.0): dependencies: - pg: 8.16.3 + pg: 8.21.0 - pg-protocol@1.10.3: {} + pg-protocol@1.14.0: {} - pg-query-stream@4.10.3(pg@8.16.3): + pg-query-stream@4.10.3(pg@8.21.0): dependencies: - pg: 8.16.3 - pg-cursor: 2.15.3(pg@8.16.3) + pg: 8.21.0 + pg-cursor: 2.15.3(pg@8.21.0) pg-types@2.2.0: dependencies: pg-int8: 1.0.1 postgres-array: 2.0.0 - postgres-bytea: 1.0.0 + postgres-bytea: 1.0.1 postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.16.3: + pg@8.21.0: dependencies: - pg-connection-string: 2.9.1 - pg-pool: 3.10.1(pg@8.16.3) - pg-protocol: 1.10.3 + pg-connection-string: 2.13.0 + pg-pool: 3.14.0(pg@8.21.0) + pg-protocol: 1.14.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.2.7 + pg-cloudflare: 1.4.0 pgpass@1.0.5: dependencies: @@ -4055,9 +4181,9 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} - picomatch@4.0.3: {} + picomatch@4.0.4: {} pino-abstract-transport@2.0.0: dependencies: @@ -4087,7 +4213,7 @@ snapshots: postgres-array@2.0.0: {} - postgres-bytea@1.0.0: {} + postgres-bytea@1.0.1: {} postgres-date@1.0.7: {} @@ -4103,8 +4229,8 @@ snapshots: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 2.0.0 - node-abi: 3.85.0 - pump: 3.0.3 + node-abi: 3.87.0 + pump: 3.0.4 rc: 1.2.8 simple-get: 4.0.1 tar-fs: 2.1.4 @@ -4122,7 +4248,7 @@ snapshots: progress@2.0.3: {} - pump@3.0.3: + pump@3.0.4: dependencies: end-of-stream: 1.4.5 once: 1.4.0 @@ -4158,7 +4284,7 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.1 + picomatch: 2.3.2 real-require@0.2.0: {} @@ -4170,8 +4296,9 @@ snapshots: resolve-from@5.0.0: {} - resolve@1.22.11: + resolve@1.22.12: dependencies: + es-errors: 1.3.0 is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -4190,7 +4317,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.3: {} + semver@7.8.1: {} seq-queue@0.0.5: {} @@ -4248,6 +4375,11 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + stream-meter@1.0.4: dependencies: readable-stream: 2.3.8 @@ -4267,7 +4399,7 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 string_decoder@1.1.1: dependencies: @@ -4281,7 +4413,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -4313,7 +4445,7 @@ snapshots: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 - pump: 3.0.3 + pump: 3.0.4 tar-stream: 2.2.0 tar-stream@2.2.0: @@ -4328,16 +4460,16 @@ snapshots: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 - minimatch: 3.1.2 + minimatch: 3.1.5 thread-stream@3.1.0: dependencies: real-require: 0.2.0 - tinyglobby@0.2.15: + tinyglobby@0.2.16: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tmpl@1.0.5: {} @@ -4349,38 +4481,40 @@ snapshots: tree-kill@1.2.2: {} - ts-jest@29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3): + ts-algebra@2.0.0: {} + + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.27.7)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.3 + semver: 7.8.1 type-fest: 4.41.0 typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) - esbuild: 0.27.0 + babel-jest: 30.2.0(@babel/core@7.29.0) + esbuild: 0.27.7 jest-util: 30.2.0 - ts-node-dev@2.0.0(@types/node@20.19.25)(typescript@5.9.3): + ts-node-dev@2.0.0(@types/node@20.19.41)(typescript@5.9.3): dependencies: chokidar: 3.6.0 dynamic-dedupe: 0.3.0 minimist: 1.2.8 mkdirp: 1.0.4 - resolve: 1.22.11 + resolve: 1.22.12 rimraf: 2.7.1 source-map-support: 0.5.21 tree-kill: 1.2.2 - ts-node: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@20.19.41)(typescript@5.9.3) tsconfig: 7.0.0 typescript: 5.9.3 transitivePeerDependencies: @@ -4388,19 +4522,19 @@ snapshots: - '@swc/wasm' - '@types/node' - ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3): + ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.25 - acorn: 8.15.0 + '@types/node': 20.19.41 + acorn: 8.16.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 - diff: 4.0.2 + diff: 4.0.4 make-error: 1.3.6 typescript: 5.9.3 v8-compile-cache-lib: 3.0.1 @@ -4461,9 +4595,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.2.2(browserslist@4.28.1): + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -4485,6 +4619,8 @@ snapshots: webidl-conversions@3.0.1: {} + whatwg-fetch@3.6.20: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -4506,7 +4642,7 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrappy@1.0.2: {} @@ -4515,7 +4651,7 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.19.0: {} + ws@8.20.1: {} xtend@4.0.2: {} @@ -4550,3 +4686,9 @@ snapshots: yn@3.1.1: {} yocto-queue@0.1.0: {} + + zod-to-json-schema@3.25.1(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/bridge/src/ai/README.md b/bridge/src/ai/README.md new file mode 100644 index 0000000..0746cdf --- /dev/null +++ b/bridge/src/ai/README.md @@ -0,0 +1,23 @@ +# AI Integration (Bridge) + +This folder contains AI provider implementations and prompt templates used by the bridge. + +Layout +- `providers/` — concrete provider adapters (OpenAI, Anthropic, Gemini, Mistral, Ollama, Groq). Implement the `AIProvider` interface in `providers/types.ts`. +- `prompts/` — prompt builders and parsers for schema analysis, query explanation and chart recommendation. + +What moved +- The public RPC entry points and handler logic live under `src/handlers/aiHandlers.ts`. +- The service factory implementation was moved to `src/services/ai.impl.ts` and a shim `src/services/aiService.ts` exposes `AIService` and `aiService` for consistency with other bridge services. +- Shared AI types were moved to `src/types/ai.ts` and are re-exported from the central `src/types/index.ts`. + +How to add a provider +1. Create a new file under `providers/` implementing the `AIProvider` interface. +2. Add the provider into `src/services/ai.impl.ts` in the `createProvider` switch. +3. Add any provider-specific configuration notes to this README. + +Testing +- No tests currently exist for AI. To exercise the integration locally, use the frontend UI features that call `ai.*` RPC methods or create a test that uses the RPC registrar. + +Notes +- Keep providers small and avoid direct network retries; the bridge surface should translate provider errors into `AIError` using `providers/types.ts`. diff --git a/bridge/src/ai/prompts/chart-recommendation.ts b/bridge/src/ai/prompts/chart-recommendation.ts new file mode 100644 index 0000000..bb5316e --- /dev/null +++ b/bridge/src/ai/prompts/chart-recommendation.ts @@ -0,0 +1,66 @@ +import { ChartRecommendationInput, ChartRecommendation } from "../../types/"; +import { SYSTEM_CONTEXT } from "./shared"; +import { AIError } from "../providers/types"; + +export function buildChartRecommendationPrompt(input: ChartRecommendationInput): { + system: string; + user: string; +} { + const columnList = input.columns + .map((c) => { + const flags: string[] = [c.type]; + if (c.isPrimaryKey) flags.push("PK"); + if (c.sampleValues?.length) flags.push(`samples: ${c.sampleValues.slice(0, 3).join(", ")}`); + return `- ${c.name} (${flags.join(", ")})`; + }) + .join("\n"); + + const user = `Given the table "${input.tableName}" with the following columns, recommend the best chart visualization: + +## Columns +${columnList} + +## Instructions +Respond ONLY with a valid JSON object — no markdown fences, no explanation text. +The JSON must match exactly this shape: +{ + "chartType": "bar" | "line" | "area" | "pie", + "xAxis": "", + "yAxis": "", + "reasoning": "" +} + +Choose the most insightful combination. Prefer grouping a categorical/text column on X and counting a numeric/PK column on Y.`; + + return { system: SYSTEM_CONTEXT, user }; +} + +/** + * Parse the raw LLM text response into a ChartRecommendation. + * The model is instructed to return bare JSON, but defensively strip + * any markdown fences if the model adds them anyway. + */ +export function parseChartRecommendation(raw: string): ChartRecommendation { + // Strip markdown code fences if present + const cleaned = raw + .replace(/```json\s*/gi, "") + .replace(/```\s*/g, "") + .trim(); + + let parsed: any; + try { + parsed = JSON.parse(cleaned); + } catch { + throw new AIError("PARSE_ERROR", "chart-recommendation", `Failed to parse chart recommendation JSON: ${raw.slice(0, 200)}`); + } + + const validTypes = ["bar", "line", "area", "pie"]; + const chartType = validTypes.includes(parsed.chartType) ? parsed.chartType : "bar"; + + return { + chartType: chartType as ChartRecommendation["chartType"], + xAxis: String(parsed.xAxis ?? ""), + yAxis: String(parsed.yAxis ?? ""), + reasoning: String(parsed.reasoning ?? ""), + }; +} diff --git a/bridge/src/ai/prompts/query-explanation.ts b/bridge/src/ai/prompts/query-explanation.ts new file mode 100644 index 0000000..1897456 --- /dev/null +++ b/bridge/src/ai/prompts/query-explanation.ts @@ -0,0 +1,42 @@ +import { QueryExplanationInput } from "../../types/"; +import { SYSTEM_CONTEXT, MARKDOWN_INSTRUCTION } from "./shared"; + +export function buildQueryExplanationPrompt(input: QueryExplanationInput): { + system: string; + user: string; +} { + const schemaContext = + input.schema && input.schema.length > 0 + ? input.schema + .map((t) => { + const cols = t.columns + .map((c) => `${c.name} ${c.type}${c.isPrimaryKey ? " PK" : ""}${c.isForeignKey ? " FK" : ""}`) + .join(", "); + return `- ${t.name}(${cols})`; + }) + .join("\n") + : "Schema not provided."; + + const dbType = input.databaseType ? ` (${input.databaseType})` : ""; + + const user = `Explain the following SQL query${dbType}: + +\`\`\`sql +${input.sql} +\`\`\` + +## Relevant Schema +${schemaContext} + +## What to cover +1. **Query Purpose** — What does this query do in plain English? +2. **Joins** — Explain any joins and the relationships they traverse +3. **Filters** — What data is being filtered and why +4. **Aggregations** — Any GROUP BY, COUNT, SUM, etc., and what they compute +5. **Performance Concerns** — Potential bottlenecks, missing indexes, full table scans +6. **Suggested Improvements** — Rewritten or optimized version if applicable + +${MARKDOWN_INSTRUCTION}`; + + return { system: SYSTEM_CONTEXT, user }; +} diff --git a/bridge/src/ai/prompts/schema-analysis.ts b/bridge/src/ai/prompts/schema-analysis.ts new file mode 100644 index 0000000..a530893 --- /dev/null +++ b/bridge/src/ai/prompts/schema-analysis.ts @@ -0,0 +1,55 @@ +import { SchemaAnalysisInput } from "../../types/"; +import { SYSTEM_CONTEXT, MARKDOWN_INSTRUCTION } from "./shared"; + +export function buildSchemaAnalysisPrompt(input: SchemaAnalysisInput): { + system: string; + user: string; +} { + const tableDescriptions = input.tables + .map((t) => { + const columns = t.columns + .map((c) => { + const flags: string[] = []; + if (c.isPrimaryKey) flags.push("PK"); + if (c.isForeignKey) flags.push("FK"); + if (!c.nullable) flags.push("NOT NULL"); + if (c.references) flags.push(`→ ${c.references.table}.${c.references.column}`); + return ` - ${c.name} (${c.type})${flags.length ? " [" + flags.join(", ") + "]" : ""}`; + }) + .join("\n"); + + const extras: string[] = []; + if (t.indexes?.length) extras.push(`Indexes: ${t.indexes.join(", ")}`); + if (t.foreignKeys?.length) extras.push(`Foreign keys: ${t.foreignKeys.join(", ")}`); + if (t.constraints?.length) extras.push(`Constraints: ${t.constraints.join(", ")}`); + + return [ + `### Table: ${t.schema ? `${t.schema}.` : ""}${t.name}`, + columns, + extras.length ? extras.map((e) => ` * ${e}`).join("\n") : "", + ] + .filter(Boolean) + .join("\n"); + }) + .join("\n\n"); + + const dbType = input.databaseType ? ` (${input.databaseType})` : ""; + + const user = `Analyze the following database schema${dbType} and provide: + +## What to cover +1. **Purpose** — What does this database appear to be for? +2. **Architecture** — Key design decisions and table relationships +3. **Missing Indexes** — Columns that should be indexed but aren't +4. **Schema Smells** — Anti-patterns, naming issues, or poor design choices +5. **Normalization Concerns** — Over/under-normalization issues +6. **Scalability Concerns** — Issues that may cause problems at scale +7. **Suggested Improvements** — Concrete, actionable recommendations + +## Schema +${tableDescriptions || "No tables provided."} + +${MARKDOWN_INSTRUCTION}`; + + return { system: SYSTEM_CONTEXT, user }; +} diff --git a/bridge/src/ai/prompts/shared.ts b/bridge/src/ai/prompts/shared.ts new file mode 100644 index 0000000..2a7bcc6 --- /dev/null +++ b/bridge/src/ai/prompts/shared.ts @@ -0,0 +1,18 @@ +/** + * Shared prompt fragments used across all AI providers. + * Keep these provider-independent — no SDK-specific formatting here. + */ + +export const SYSTEM_CONTEXT = `You are RelWave AI, an expert database assistant embedded in the RelWave desktop application. +RelWave helps developers manage PostgreSQL, MySQL, MariaDB, and SQLite databases. +Always respond in clear, well-structured Markdown unless instructed otherwise. +Be concise, practical, and actionable. Avoid boilerplate preambles.`; + +export const MARKDOWN_INSTRUCTION = `Format your response in Markdown with: +- Level 2 headings (##) for major sections +- Bullet points for lists of items +- Code blocks (\`\`\`sql) for SQL examples +- Bold text for key terms and important warnings +Keep responses focused and under 1000 words unless the complexity demands more.`; + +export const NO_DATA_PROMPT = "No structured data was provided."; diff --git a/bridge/src/ai/providers/anthropic.provider.ts b/bridge/src/ai/providers/anthropic.provider.ts new file mode 100644 index 0000000..a366468 --- /dev/null +++ b/bridge/src/ai/providers/anthropic.provider.ts @@ -0,0 +1,65 @@ +import Anthropic from "@anthropic-ai/sdk"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../../types"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "claude-3-5-haiku-20241022"; + +export class AnthropicProvider implements AIProvider { + private client: Anthropic; + + constructor(apiKey: string) { + this.client = new Anthropic({ apiKey }); + } + + private async complete(system: string, user: string): Promise { + try { + const msg = await this.client.messages.create({ + model: DEFAULT_MODEL, + max_tokens: 2048, + system, + messages: [{ role: "user", content: user }], + }); + const block = msg.content[0]; + return block.type === "text" ? block.text : ""; + } catch (err) { + throw classifyError(err, "anthropic"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + await this.client.messages.create({ + model: DEFAULT_MODEL, + max_tokens: 10, + messages: [{ role: "user", content: "ping" }], + }); + return ""; + } catch (err) { + throw classifyError(err, "anthropic"); + } + } +} diff --git a/bridge/src/ai/providers/gemini.provider.ts b/bridge/src/ai/providers/gemini.provider.ts new file mode 100644 index 0000000..a5f37c9 --- /dev/null +++ b/bridge/src/ai/providers/gemini.provider.ts @@ -0,0 +1,60 @@ +import { GoogleGenerativeAI } from "@google/generative-ai"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../../types/"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "gemini-1.5-flash"; + +export class GeminiProvider implements AIProvider { + private genAI: GoogleGenerativeAI; + + constructor(apiKey: string) { + this.genAI = new GoogleGenerativeAI(apiKey); + } + + private async complete(system: string, user: string): Promise { + try { + const model = this.genAI.getGenerativeModel({ + model: DEFAULT_MODEL, + systemInstruction: system, + }); + const result = await model.generateContent(user); + return result.response.text(); + } catch (err) { + throw classifyError(err, "gemini"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + const model = this.genAI.getGenerativeModel({ model: DEFAULT_MODEL }); + await model.generateContent("ping"); + return ""; + } catch (err) { + throw classifyError(err, "gemini"); + } + } +} diff --git a/bridge/src/ai/providers/groq.provider.ts b/bridge/src/ai/providers/groq.provider.ts new file mode 100644 index 0000000..f433e40 --- /dev/null +++ b/bridge/src/ai/providers/groq.provider.ts @@ -0,0 +1,66 @@ +import Groq from "groq-sdk"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../../types/"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "llama-3.3-70b-versatile"; + +export class GroqProvider implements AIProvider { + private client: Groq; + + constructor(apiKey: string) { + this.client = new Groq({ apiKey }); + } + + private async complete(system: string, user: string): Promise { + try { + const res = await this.client.chat.completions.create({ + model: DEFAULT_MODEL, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + max_tokens: 2048, + }); + return res.choices[0]?.message?.content ?? ""; + } catch (err) { + throw classifyError(err, "groq"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + await this.client.chat.completions.create({ + model: DEFAULT_MODEL, + messages: [{ role: "user", content: "ping" }], + max_tokens: 5, + }); + return ""; + } catch (err) { + throw classifyError(err, "groq"); + } + } +} diff --git a/bridge/src/ai/providers/mistral.provider.ts b/bridge/src/ai/providers/mistral.provider.ts new file mode 100644 index 0000000..2c6d1c7 --- /dev/null +++ b/bridge/src/ai/providers/mistral.provider.ts @@ -0,0 +1,72 @@ +import { Mistral } from "@mistralai/mistralai"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../../types/"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "mistral-small-latest"; + +export class MistralProvider implements AIProvider { + private client: Mistral; + + constructor(apiKey: string) { + this.client = new Mistral({ apiKey }); + } + + private async complete(system: string, user: string): Promise { + try { + const res = await this.client.chat.complete({ + model: DEFAULT_MODEL, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + maxTokens: 2048, + }); + const choice = res.choices?.[0]; + const content = choice?.message?.content; + if (typeof content === "string") return content; + if (Array.isArray(content)) { + return content.map((c: any) => c.text ?? "").join(""); + } + return ""; + } catch (err) { + throw classifyError(err, "mistral"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + await this.client.chat.complete({ + model: DEFAULT_MODEL, + messages: [{ role: "user", content: "ping" }], + maxTokens: 5, + }); + return ""; + } catch (err) { + throw classifyError(err, "mistral"); + } + } +} diff --git a/bridge/src/ai/providers/ollama.provider.ts b/bridge/src/ai/providers/ollama.provider.ts new file mode 100644 index 0000000..b109927 --- /dev/null +++ b/bridge/src/ai/providers/ollama.provider.ts @@ -0,0 +1,68 @@ +import { Ollama } from "ollama"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../../types/"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "llama3.2"; + +export class OllamaProvider implements AIProvider { + private client: Ollama; + private model: string; + + constructor(baseUrl?: string, model?: string) { + this.client = new Ollama({ host: baseUrl ?? "http://localhost:11434" }); + this.model = model?.trim() || DEFAULT_MODEL; + } + + private async complete(system: string, user: string): Promise { + try { + const res = await this.client.chat({ + model: this.model, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + }); + return res.message?.content ?? ""; + } catch (err) { + throw classifyError(err, "ollama"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + // List models to verify Ollama is reachable and the model exists + const list = await this.client.list(); + const available = list.models.map((m: any) => m.name); + if (!available.some((n: string) => n.startsWith(this.model.split(":")[0]))) { + throw new Error(`Model "${this.model}" not found. Available: ${available.join(", ") || "none"}`); + } + return ""; + } catch (err) { + throw classifyError(err, "ollama"); + } + } +} diff --git a/bridge/src/ai/providers/openai.provider.ts b/bridge/src/ai/providers/openai.provider.ts new file mode 100644 index 0000000..c09da72 --- /dev/null +++ b/bridge/src/ai/providers/openai.provider.ts @@ -0,0 +1,66 @@ +import OpenAI from "openai"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../../types"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "gpt-4o-mini"; + +export class OpenAIProvider implements AIProvider { + private client: OpenAI; + + constructor(apiKey: string) { + this.client = new OpenAI({ apiKey }); + } + + private async complete(system: string, user: string): Promise { + try { + const res = await this.client.chat.completions.create({ + model: DEFAULT_MODEL, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + max_tokens: 2048, + }); + return res.choices[0]?.message?.content ?? ""; + } catch (err) { + throw classifyError(err, "openai"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + await this.client.chat.completions.create({ + model: DEFAULT_MODEL, + messages: [{ role: "user", content: "ping" }], + max_tokens: 5, + }); + return ""; + } catch (err) { + throw classifyError(err, "openai"); + } + } +} diff --git a/bridge/src/ai/providers/types.ts b/bridge/src/ai/providers/types.ts new file mode 100644 index 0000000..2d4bdb4 --- /dev/null +++ b/bridge/src/ai/providers/types.ts @@ -0,0 +1,85 @@ +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../../types/ai"; + +// ── Provider interface ──────────────────────────────────────────────────── + +/** + * All AI providers must implement this interface. + * Frontend code never knows which concrete provider is active. + */ +export interface AIProvider { + /** Analyze a database schema and return a markdown report. */ + analyzeSchema(input: SchemaAnalysisInput): Promise; + + /** Explain a SQL query in plain language, returning markdown. */ + explainQuery(input: QueryExplanationInput): Promise; + + /** Recommend a chart type + axes for the given table. */ + recommendChart(input: ChartRecommendationInput): Promise; + + /** + * Verify that the provider is reachable with the supplied credentials. + * Resolves with an empty string on success, a user-facing message on failure. + */ + testConnection(): Promise; +} + +// ── Standardized error type ─────────────────────────────────────────────── + +export type AIErrorCode = + | "MISSING_API_KEY" + | "INVALID_API_KEY" + | "PROVIDER_UNAVAILABLE" + | "INVALID_MODEL" + | "NETWORK_FAILURE" + | "TIMEOUT" + | "RATE_LIMIT" + | "PARSE_ERROR" + | "UNKNOWN"; + +export class AIError extends Error { + readonly code: AIErrorCode; + readonly provider: string; + readonly originalMessage: string; + + constructor(code: AIErrorCode, provider: string, message: string) { + super(`[${provider}] ${message}`); + this.name = "AIError"; + this.code = code; + this.provider = provider; + this.originalMessage = message; + } +} + +/** + * Classify a raw SDK/network error into an AIErrorCode. + */ +export function classifyError(err: unknown, provider: string): AIError { + const msg = err instanceof Error ? err.message : String(err); + const lower = msg.toLowerCase(); + + if (lower.includes("api key") || lower.includes("apikey") || lower.includes("authentication") || lower.includes("unauthorized") || lower.includes("401")) { + return new AIError("INVALID_API_KEY", provider, msg); + } + if (lower.includes("rate limit") || lower.includes("429") || lower.includes("too many requests")) { + return new AIError("RATE_LIMIT", provider, msg); + } + if (lower.includes("timeout") || lower.includes("timed out")) { + return new AIError("TIMEOUT", provider, msg); + } + if (lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("fetch failed") || lower.includes("network")) { + return new AIError("NETWORK_FAILURE", provider, msg); + } + if (lower.includes("model") && (lower.includes("not found") || lower.includes("invalid"))) { + return new AIError("INVALID_MODEL", provider, msg); + } + if (lower.includes("unavailable") || lower.includes("503") || lower.includes("overloaded")) { + return new AIError("PROVIDER_UNAVAILABLE", provider, msg); + } + + return new AIError("UNKNOWN", provider, msg); +} diff --git a/bridge/src/handlers/aiHandlers.ts b/bridge/src/handlers/aiHandlers.ts new file mode 100644 index 0000000..49201b4 --- /dev/null +++ b/bridge/src/handlers/aiHandlers.ts @@ -0,0 +1,222 @@ +import { Logger } from "pino"; +import { Rpc } from "../types"; +import { AIService } from "../services/aiService"; +import { AIError } from "../ai/providers/types"; +import { + AIAnalyzeSchemaParams, + AIExplainQueryParams, + AIRecommendChartParams, + AITestConnectionParams, +} from "../types/ai"; +import { + getOrCall, + hashSchemaAnalysis, + hashQueryExplanation, + hashChartRecommendation, +} from "../services/aiCacheService"; +import { aiHistoryStore } from "../services/aiHistoryStore"; +import { buildSchemaAnalysisPrompt } from "../ai/prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../ai/prompts/query-explanation"; +import { buildChartRecommendationPrompt } from "../ai/prompts/chart-recommendation"; +import { parseChartRecommendation } from "../ai/prompts/chart-recommendation"; + +export class AIHandlers { + private aiService: AIService; + + constructor(private rpc: Rpc, private logger: Logger) { + this.aiService = new AIService(); + } + + async handleTestConnection(params: AITestConnectionParams, id: number | string) { + try { + const provider = this.aiService.resolveProvider(params.settings); + await provider.testConnection(); + this.rpc.sendResponse(id, { ok: true, data: { connected: true } }); + } catch (err) { + this.logger?.warn({ err }, "ai.testConnection failed"); + const msg = err instanceof AIError ? err.originalMessage : String(err); + const code = err instanceof AIError ? err.code : "UNKNOWN"; + this.rpc.sendError(id, { code, message: msg }); + } + } + + async handleAnalyzeSchema(params: AIAnalyzeSchemaParams & { skipCache?: boolean }, id: number | string) { + try { + if (!params?.input?.tables?.length) { + return this.rpc.sendError(id, { code: "BAD_REQUEST", message: "No tables provided." }); + } + + const hash = hashSchemaAnalysis(params.input, params.datasourceName); + const { system, user } = buildSchemaAnalysisPrompt(params.input); + const prompt = `${system}\n\n${user}`; + + const result = await getOrCall({ + feature: "schema-analysis", + hash, + settings: params.settings, + prompt, + callFn: async () => { + const provider = this.aiService.resolveProvider(params.settings); + return provider.analyzeSchema(params.input); + }, + meta: { datasource_id: params.datasourceName, table_name: params.tableName }, + skipCache: params.skipCache, + }); + + this.rpc.sendResponse(id, { + ok: true, + data: { + markdown: result.response, + cached: result.cached, + createdAt: result.createdAt, + }, + }); + } catch (err) { + this.logger?.error({ err }, "ai.analyzeSchema failed"); + const msg = err instanceof AIError ? err.originalMessage : String(err); + const code = err instanceof AIError ? err.code : "UNKNOWN"; + this.rpc.sendError(id, { code, message: msg }); + } + } + + async handleExplainQuery(params: AIExplainQueryParams & { skipCache?: boolean }, id: number | string) { + try { + if (!params?.input?.sql?.trim()) { + return this.rpc.sendError(id, { code: "BAD_REQUEST", message: "No SQL provided." }); + } + + const hash = hashQueryExplanation(params.input, params.datasourceName); + const { system, user } = buildQueryExplanationPrompt(params.input); + const prompt = `${system}\n\n${user}`; + + const result = await getOrCall({ + feature: "query-explanation", + hash, + settings: params.settings, + prompt, + callFn: async () => { + const provider = this.aiService.resolveProvider(params.settings); + return provider.explainQuery(params.input); + }, + meta: { datasource_id: params.datasourceName, table_name: params.tableName }, + skipCache: params.skipCache, + }); + + this.rpc.sendResponse(id, { + ok: true, + data: { + markdown: result.response, + cached: result.cached, + createdAt: result.createdAt, + }, + }); + } catch (err) { + this.logger?.error({ err }, "ai.explainQuery failed"); + const msg = err instanceof AIError ? err.originalMessage : String(err); + const code = err instanceof AIError ? err.code : "UNKNOWN"; + this.rpc.sendError(id, { code, message: msg }); + } + } + + async handleRecommendChart(params: AIRecommendChartParams & { skipCache?: boolean }, id: number | string) { + try { + if (!params?.input?.tableName || !params?.input?.columns?.length) { + return this.rpc.sendError(id, { code: "BAD_REQUEST", message: "Missing tableName or columns." }); + } + + const hash = hashChartRecommendation(params.input, params.datasourceName); + const { system, user } = buildChartRecommendationPrompt(params.input); + const prompt = `${system}\n\n${user}`; + + const result = await getOrCall({ + feature: "chart-recommendation", + hash, + settings: params.settings, + prompt, + callFn: async () => { + const provider = this.aiService.resolveProvider(params.settings); + const rec = await provider.recommendChart(params.input); + // Serialize the recommendation as JSON so it can be stored as a string + return JSON.stringify(rec); + }, + meta: { + datasource_id: params.datasourceName, + table_name: params.tableName ?? params.input.tableName, + }, + skipCache: params.skipCache, + }); + + // Parse the stored JSON response back into a ChartRecommendation + let recommendation; + try { + recommendation = JSON.parse(result.response); + } catch { + recommendation = parseChartRecommendation(result.response); + } + + this.rpc.sendResponse(id, { + ok: true, + data: { + ...recommendation, + cached: result.cached, + createdAt: result.createdAt, + }, + }); + } catch (err) { + this.logger?.error({ err }, "ai.recommendChart failed"); + const msg = err instanceof AIError ? err.originalMessage : String(err); + const code = err instanceof AIError ? err.code : "UNKNOWN"; + this.rpc.sendError(id, { code, message: msg }); + } + } + + // ── History CRUD handlers ───────────────────────────────────────────── + + async handleGetHistory(params: { feature?: string; provider?: string; limit?: number; offset?: number }, id: number | string) { + try { + const result = aiHistoryStore.list({ + feature: params?.feature, + provider: params?.provider, + limit: params?.limit, + offset: params?.offset, + }); + this.rpc.sendResponse(id, { ok: true, data: result }); + } catch (err: any) { + this.logger?.error({ err }, "ai.getHistory failed"); + this.rpc.sendError(id, { code: "HISTORY_ERROR", message: err?.message ?? String(err) }); + } + } + + async handleGetHistoryById(params: { id: number }, id: number | string) { + try { + const record = aiHistoryStore.getById(params.id); + if (!record) { + return this.rpc.sendError(id, { code: "NOT_FOUND", message: "History entry not found." }); + } + this.rpc.sendResponse(id, { ok: true, data: record }); + } catch (err: any) { + this.logger?.error({ err }, "ai.getHistoryById failed"); + this.rpc.sendError(id, { code: "HISTORY_ERROR", message: err?.message ?? String(err) }); + } + } + + async handleDeleteHistory(params: { id: number }, id: number | string) { + try { + const deleted = aiHistoryStore.deleteById(params.id); + this.rpc.sendResponse(id, { ok: true, data: { deleted } }); + } catch (err: any) { + this.logger?.error({ err }, "ai.deleteHistory failed"); + this.rpc.sendError(id, { code: "HISTORY_ERROR", message: err?.message ?? String(err) }); + } + } + + async handleClearHistory(_params: unknown, id: number | string) { + try { + const count = aiHistoryStore.clearAll(); + this.rpc.sendResponse(id, { ok: true, data: { deletedCount: count } }); + } catch (err: any) { + this.logger?.error({ err }, "ai.clearHistory failed"); + this.rpc.sendError(id, { code: "HISTORY_ERROR", message: err?.message ?? String(err) }); + } + } +} diff --git a/bridge/src/jsonRpcHandler.ts b/bridge/src/jsonRpcHandler.ts index 8b90f6c..55babca 100644 --- a/bridge/src/jsonRpcHandler.ts +++ b/bridge/src/jsonRpcHandler.ts @@ -69,6 +69,7 @@ export function registerDbHandlers( const gitHandlers = new GitHandlers(rpc, logger); const gitAdvancedHandlers = new GitAdvancedHandlers(rpc, logger); const monitoringHandlers = new MonitoringHandlers(rpc, logger, dbService, monitoringService); + const aiHandlers = new (require("./handlers/aiHandlers")).AIHandlers(rpc, logger); // ========================================== // SESSION MANAGEMENT HANDLERS @@ -322,6 +323,34 @@ export function registerDbHandlers( } }); + // ========================================== + // AI HANDLERS + // ========================================== + rpcRegister(rpc, "ai.testConnection", (p, id) => + aiHandlers.handleTestConnection(p, id) + ); + rpcRegister(rpc, "ai.analyzeSchema", (p, id) => + aiHandlers.handleAnalyzeSchema(p, id) + ); + rpcRegister(rpc, "ai.explainQuery", (p, id) => + aiHandlers.handleExplainQuery(p, id) + ); + rpcRegister(rpc, "ai.recommendChart", (p, id) => + aiHandlers.handleRecommendChart(p, id) + ); + rpcRegister(rpc, "ai.getHistory", (p, id) => + aiHandlers.handleGetHistory(p, id) + ); + rpcRegister(rpc, "ai.getHistoryById", (p, id) => + aiHandlers.handleGetHistoryById(p, id) + ); + rpcRegister(rpc, "ai.deleteHistory", (p, id) => + aiHandlers.handleDeleteHistory(p, id) + ); + rpcRegister(rpc, "ai.clearHistory", (p, id) => + aiHandlers.handleClearHistory(p, id) + ); + logger?.info("All RPC handlers registered successfully"); } diff --git a/bridge/src/services/ai.impl.ts b/bridge/src/services/ai.impl.ts new file mode 100644 index 0000000..3554216 --- /dev/null +++ b/bridge/src/services/ai.impl.ts @@ -0,0 +1,60 @@ +import { AISettings, AIProviderName } from "../types/ai"; +import { AIProvider, AIError } from "../ai/providers/types"; +import { AnthropicProvider } from "../ai/providers/anthropic.provider"; +import { OpenAIProvider } from "../ai/providers/openai.provider"; +import { GeminiProvider } from "../ai/providers/gemini.provider"; +import { GroqProvider } from "../ai/providers/groq.provider"; +import { MistralProvider } from "../ai/providers/mistral.provider"; +import { OllamaProvider } from "../ai/providers/ollama.provider"; + +/** + * Factory — creates the correct provider from user settings. + * Throws AIError("MISSING_API_KEY") if the required credential is absent. + */ +export class AIServiceImpl { + /** + * Resolve the active provider from the supplied settings. + */ + resolveProvider(settings: AISettings): AIProvider { + const provider = settings.defaultProvider; + return this.createProvider(provider, settings); + } + + private createProvider(name: AIProviderName, settings: AISettings): AIProvider { + switch (name) { + case "anthropic": { + const key = settings.anthropicApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "anthropic", "Anthropic API key is not configured."); + return new AnthropicProvider(key); + } + case "openai": { + const key = settings.openaiApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "openai", "OpenAI API key is not configured."); + return new OpenAIProvider(key); + } + case "gemini": { + const key = settings.geminiApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "gemini", "Gemini API key is not configured."); + return new GeminiProvider(key); + } + case "groq": { + const key = settings.groqApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "groq", "Groq API key is not configured."); + return new GroqProvider(key); + } + case "mistral": { + const key = settings.mistralApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "mistral", "Mistral API key is not configured."); + return new MistralProvider(key); + } + case "ollama": { + return new OllamaProvider(settings.ollamaBaseUrl, settings.ollamaModel); + } + default: { + throw new AIError("PROVIDER_UNAVAILABLE", name as string, `Unknown provider: ${name}`); + } + } + } +} + +export const aiImpl = new AIServiceImpl(); diff --git a/bridge/src/services/aiCacheService.ts b/bridge/src/services/aiCacheService.ts new file mode 100644 index 0000000..6294668 --- /dev/null +++ b/bridge/src/services/aiCacheService.ts @@ -0,0 +1,172 @@ +/** + * AI Cache Service — orchestrates cache-first AI calls. + * + * Before calling an LLM provider, generates a deterministic SHA-256 hash + * of the input and checks the local SQLite history for a cached response. + * If found, returns immediately without an API call. Otherwise, calls the + * provider, stores the result, and returns it. + */ + +import crypto from "crypto"; +import { aiHistoryStore, type AIHistoryInsert } from "./aiHistoryStore"; +import { AIProvider } from "../ai/providers/types"; +import { + AISettings, + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, +} from "../types/ai"; + +// ── Types ───────────────────────────────────────────────────────────────── + +export type AIFeature = "schema-analysis" | "query-explanation" | "chart-recommendation"; + +export interface CachedResult { + response: string; + cached: boolean; + createdAt?: string; +} + +// ── Hashing ─────────────────────────────────────────────────────────────── + +/** + * Generate a deterministic SHA-256 hash for the given feature and input. + * The same input always produces the same hash. + */ +export function generateContentHash(feature: AIFeature, data: unknown): string { + const payload = JSON.stringify({ feature, data }, Object.keys({ feature, data }).sort()); + return crypto.createHash("sha256").update(payload, "utf-8").digest("hex"); +} + +/** Hash for schema analysis: uses datasource + tables, columns, indexes, relationships, constraints. */ +export function hashSchemaAnalysis(input: SchemaAnalysisInput, datasourceName?: string): string { + const normalized = { + datasource: datasourceName ?? null, + databaseType: input.databaseType ?? null, + tables: (input.tables ?? []).map((t) => ({ + name: t.name, + schema: t.schema ?? null, + columns: (t.columns ?? []).map((c) => ({ + name: c.name, + type: c.type, + nullable: c.nullable ?? null, + isPrimaryKey: c.isPrimaryKey ?? null, + isForeignKey: c.isForeignKey ?? null, + references: c.references ?? null, + })), + indexes: t.indexes ?? [], + foreignKeys: t.foreignKeys ?? [], + constraints: t.constraints ?? [], + })), + }; + return generateContentHash("schema-analysis", normalized); +} + +/** Hash for query explanation: uses datasource + sql + schema. */ +export function hashQueryExplanation(input: QueryExplanationInput, datasourceName?: string): string { + const normalized = { + datasource: datasourceName ?? null, + sql: (input.sql ?? "").trim(), + databaseType: input.databaseType ?? null, + schema: (input.schema ?? []).map((t) => ({ + name: t.name, + schema: t.schema ?? null, + columns: (t.columns ?? []).map((c) => ({ + name: c.name, + type: c.type, + })), + })), + }; + return generateContentHash("query-explanation", normalized); +} + +/** Hash for chart recommendation: uses datasource + tableName + columns. */ +export function hashChartRecommendation(input: ChartRecommendationInput, datasourceName?: string): string { + const normalized = { + datasource: datasourceName ?? null, + tableName: input.tableName, + columns: (input.columns ?? []).map((c) => ({ + name: c.name, + type: c.type, + isPrimaryKey: c.isPrimaryKey ?? null, + })), + }; + return generateContentHash("chart-recommendation", normalized); +} + +// ── Cache-first orchestration ───────────────────────────────────────────── + +/** + * Resolve the model name from the settings based on provider. + * This is best-effort — some providers don't expose the model in settings. + */ +function resolveModelName(settings: AISettings): string { + const provider = settings.defaultProvider; + switch (provider) { + case "ollama": + return settings.ollamaModel ?? "ollama-default"; + default: + return provider; // For API-key providers, the model is selected by the SDK + } +} + +/** + * Check cache, and if missed, call the provider and store the result. + * + * @param feature The AI feature name (schema-analysis, query-explanation, chart-recommendation) + * @param hash Content hash for cache lookup + * @param settings User's AI settings (provider, keys) + * @param prompt The prompt text (for logging / history display) + * @param callFn Async function that calls the actual AI provider + * @param meta Optional metadata (datasource_id, table_name) + * @param skipCache If true, bypass cache and force a fresh call + */ +export async function getOrCall(opts: { + feature: AIFeature; + hash: string; + settings: AISettings; + prompt: string; + callFn: () => Promise; + meta?: { datasource_id?: string; table_name?: string }; + skipCache?: boolean; +}): Promise { + const { feature, hash, settings, prompt, callFn, meta, skipCache } = opts; + + // Check cache (unless skipCache is true) + if (!skipCache) { + const cached = aiHistoryStore.findByHash(hash, meta?.datasource_id); + if (cached) { + return { + response: cached.response, + cached: true, + createdAt: cached.created_at, + }; + } + } + + // Cache miss — call the provider + const response = await callFn(); + + // Estimate tokens: ~4 characters per token is a reasonable approximation + // for English text across most LLM tokenizers. + const estimatedTokens = Math.ceil((prompt.length + response.length) / 4); + + // Store in history + const record: AIHistoryInsert = { + feature, + datasource_id: meta?.datasource_id ?? null, + table_name: meta?.table_name ?? null, + content_hash: hash, + provider: settings.defaultProvider, + model: resolveModelName(settings), + prompt, + response, + tokens_used: estimatedTokens, + }; + aiHistoryStore.insert(record); + + return { + response, + cached: false, + }; +} diff --git a/bridge/src/services/aiHistoryStore.ts b/bridge/src/services/aiHistoryStore.ts new file mode 100644 index 0000000..28e7780 --- /dev/null +++ b/bridge/src/services/aiHistoryStore.ts @@ -0,0 +1,260 @@ +/** + * AI History Store — SQLite-backed repository for AI analysis history. + * + * Stores AI responses locally so the user can browse previous analyses, + * and provides the persistence layer for the cache system. + */ + +import Database from "better-sqlite3"; +import path from "path"; +import fs from "fs"; +import { CONFIG_FOLDER } from "../utils/config"; + +// ── Types ───────────────────────────────────────────────────────────────── + +export interface AIHistoryRow { + id: number; + feature: string; + datasource_id: string | null; + table_name: string | null; + content_hash: string | null; + provider: string; + model: string; + prompt: string; + response: string; + tokens_used: number | null; + created_at: string; +} + +/** Subset returned when listing (no prompt/response to keep payloads small). */ +export interface AIHistoryListItem { + id: number; + feature: string; + datasource_id: string | null; + table_name: string | null; + provider: string; + model: string; + tokens_used: number | null; + created_at: string; +} + +export interface AIHistoryInsert { + feature: string; + datasource_id?: string | null; + table_name?: string | null; + content_hash?: string | null; + provider: string; + model: string; + prompt: string; + response: string; + tokens_used?: number | null; +} + +export interface AIHistoryListParams { + feature?: string; + provider?: string; + limit?: number; + offset?: number; +} + +export interface AIHistoryListResult { + items: AIHistoryListItem[]; + total: number; +} + +// ── DDL ─────────────────────────────────────────────────────────────────── + +const CREATE_TABLE_SQL = ` +CREATE TABLE IF NOT EXISTS ai_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + feature TEXT NOT NULL, + datasource_id TEXT, + table_name TEXT, + content_hash TEXT, + provider TEXT NOT NULL, + model TEXT NOT NULL, + prompt TEXT NOT NULL, + response TEXT NOT NULL, + tokens_used INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); +`; + +const CREATE_INDEX_HASH = `CREATE INDEX IF NOT EXISTS idx_ai_history_hash ON ai_history(content_hash);`; +const CREATE_INDEX_HASH_DS = `CREATE INDEX IF NOT EXISTS idx_ai_history_hash_ds ON ai_history(content_hash, datasource_id);`; +const CREATE_INDEX_FEATURE = `CREATE INDEX IF NOT EXISTS idx_ai_history_feature ON ai_history(feature);`; +const CREATE_INDEX_CREATED = `CREATE INDEX IF NOT EXISTS idx_ai_history_created_at ON ai_history(created_at);`; + +// ── Store ───────────────────────────────────────────────────────────────── + +export class AIHistoryStore { + private db: Database.Database | null = null; + private dbPath: string; + + constructor(dbPath?: string) { + this.dbPath = dbPath ?? path.join(CONFIG_FOLDER, "ai_history.db"); + } + + /** Lazily open the database and run migrations. */ + private getDb(): Database.Database { + if (this.db) return this.db; + + // Ensure the config directory exists + const dir = path.dirname(this.dbPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + // Resolve native binding for pkg builds (reuse existing pattern) + let nativeBinding: string | undefined; + try { + const { resolvePkgNativeBindingPath } = require("../connectors/sqlite"); + if (typeof resolvePkgNativeBindingPath === "function") { + nativeBinding = resolvePkgNativeBindingPath() ?? undefined; + } + } catch { + // Not in a pkg environment or function not exported; ignore. + } + + const opts: Database.Options = {}; + if (nativeBinding) opts.nativeBinding = nativeBinding; + + this.db = new Database(this.dbPath, opts); + + // Enable WAL mode for better concurrent read performance + this.db.pragma("journal_mode = WAL"); + + // Run migrations + this.db.exec(CREATE_TABLE_SQL); + this.db.exec(CREATE_INDEX_HASH); + this.db.exec(CREATE_INDEX_HASH_DS); + this.db.exec(CREATE_INDEX_FEATURE); + this.db.exec(CREATE_INDEX_CREATED); + + return this.db; + } + + /** Insert a new history record. Returns the new row ID. */ + insert(record: AIHistoryInsert): number { + const db = this.getDb(); + const stmt = db.prepare(` + INSERT INTO ai_history (feature, datasource_id, table_name, content_hash, provider, model, prompt, response, tokens_used, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + const info = stmt.run( + record.feature, + record.datasource_id ?? null, + record.table_name ?? null, + record.content_hash ?? null, + record.provider, + record.model, + record.prompt, + record.response, + record.tokens_used ?? null, + new Date().toISOString() + ); + return info.lastInsertRowid as number; + } + + /** + * Look up a cached result by content_hash AND datasource_id. + * Both must match — a cached response for Database A won't be + * returned when querying Database B, even with the same schema. + */ + findByHash(contentHash: string, datasourceId?: string | null): AIHistoryRow | null { + const db = this.getDb(); + let row: AIHistoryRow | undefined; + + if (datasourceId) { + // Match both hash and the specific datasource + row = db + .prepare( + `SELECT * FROM ai_history WHERE content_hash = ? AND datasource_id = ? ORDER BY created_at DESC LIMIT 1` + ) + .get(contentHash, datasourceId) as AIHistoryRow | undefined; + } else { + // No datasource specified — only match rows that also have no datasource + row = db + .prepare( + `SELECT * FROM ai_history WHERE content_hash = ? AND datasource_id IS NULL ORDER BY created_at DESC LIMIT 1` + ) + .get(contentHash) as AIHistoryRow | undefined; + } + + return row ?? null; + } + + /** List history entries with optional filters and pagination. */ + list(params: AIHistoryListParams = {}): AIHistoryListResult { + const db = this.getDb(); + const conditions: string[] = []; + const values: unknown[] = []; + + if (params.feature) { + conditions.push("feature = ?"); + values.push(params.feature); + } + if (params.provider) { + conditions.push("provider = ?"); + values.push(params.provider); + } + + const where = conditions.length + ? `WHERE ${conditions.join(" AND ")}` + : ""; + + const limit = params.limit ?? 20; + const offset = params.offset ?? 0; + + // Count + const countRow = db + .prepare(`SELECT COUNT(*) AS total FROM ai_history ${where}`) + .get(...values) as { total: number }; + + // Fetch items (no prompt/response for list view) + const items = db + .prepare( + `SELECT id, feature, datasource_id, table_name, provider, model, tokens_used, created_at + FROM ai_history ${where} + ORDER BY created_at DESC + LIMIT ? OFFSET ?` + ) + .all(...values, limit, offset) as AIHistoryListItem[]; + + return { items, total: countRow.total }; + } + + /** Get a single history entry by ID (full record). */ + getById(id: number): AIHistoryRow | null { + const db = this.getDb(); + const row = db + .prepare(`SELECT * FROM ai_history WHERE id = ?`) + .get(id) as AIHistoryRow | undefined; + return row ?? null; + } + + /** Delete a single history entry. Returns true if deleted. */ + deleteById(id: number): boolean { + const db = this.getDb(); + const info = db.prepare(`DELETE FROM ai_history WHERE id = ?`).run(id); + return info.changes > 0; + } + + /** Clear all history entries. Returns number of rows deleted. */ + clearAll(): number { + const db = this.getDb(); + const info = db.prepare(`DELETE FROM ai_history`).run(); + return info.changes; + } + + /** Close the database connection (for clean shutdown). */ + close(): void { + if (this.db) { + this.db.close(); + this.db = null; + } + } +} + +// Singleton instance +export const aiHistoryStore = new AIHistoryStore(); diff --git a/bridge/src/services/aiService.ts b/bridge/src/services/aiService.ts new file mode 100644 index 0000000..09036ac --- /dev/null +++ b/bridge/src/services/aiService.ts @@ -0,0 +1,7 @@ +import { AIServiceImpl } from "./ai.impl"; + +// Re-export the AIService class for discoverability under services/ +export class AIService extends AIServiceImpl {} + +// Also provide a singleton instance for callers that prefer a shared object +export const aiService = new AIService(); diff --git a/bridge/src/types/ai.ts b/bridge/src/types/ai.ts new file mode 100644 index 0000000..7246326 --- /dev/null +++ b/bridge/src/types/ai.ts @@ -0,0 +1,96 @@ +// ── Shared types for the AI integration layer (moved from src/ai/ai.types.ts) + +export type AIProviderName = + | "anthropic" + | "openai" + | "gemini" + | "groq" + | "mistral" + | "ollama"; + +/** + * User-facing AI settings — stored client-side and passed on every RPC call. + * The bridge is stateless regarding API keys. + */ +export interface AISettings { + defaultProvider: AIProviderName; + anthropicApiKey?: string; + openaiApiKey?: string; + geminiApiKey?: string; + groqApiKey?: string; + mistralApiKey?: string; + ollamaBaseUrl?: string; + ollamaModel?: string; +} + +// ── Feature input/output types ──────────────────────────────────────────── + +export interface SchemaColumn { + name: string; + type: string; + nullable?: boolean; + isPrimaryKey?: boolean; + isForeignKey?: boolean; + references?: { table: string; column: string }; +} + +export interface SchemaTable { + name: string; + schema?: string; + columns: SchemaColumn[]; + indexes?: string[]; + foreignKeys?: string[]; + constraints?: string[]; +} + +export interface SchemaAnalysisInput { + tables: SchemaTable[]; + databaseType?: string; +} + +export interface QueryExplanationInput { + sql: string; + schema?: SchemaTable[]; + databaseType?: string; +} + +export interface ChartRecommendationInput { + tableName: string; + columns: Array<{ + name: string; + type: string; + isPrimaryKey?: boolean; + sampleValues?: string[]; + }>; +} + +export interface ChartRecommendation { + chartType: "bar" | "line" | "area" | "pie"; + xAxis: string; + yAxis: string; + reasoning: string; +} + +// ── RPC param shapes ───────────────────────────────────────────────────── + +export interface AIRequestBase { + settings: AISettings; + /** Human-readable database / datasource name for history display. */ + datasourceName?: string; + /** Table name context (e.g. for chart recommendation). */ + tableName?: string; +} + +export interface AIAnalyzeSchemaParams extends AIRequestBase { + input: SchemaAnalysisInput; +} + +export interface AIExplainQueryParams extends AIRequestBase { + input: QueryExplanationInput; +} + +export interface AIRecommendChartParams extends AIRequestBase { + input: ChartRecommendationInput; +} + +export interface AITestConnectionParams extends AIRequestBase {} diff --git a/bridge/src/types/index.ts b/bridge/src/types/index.ts index 8c5e236..dcfa1e9 100644 --- a/bridge/src/types/index.ts +++ b/bridge/src/types/index.ts @@ -14,6 +14,7 @@ export * from './common'; export * from './mysql'; export * from './postgres'; export * from './sqlite'; +export * from './ai'; export { SSHConfig } from './common'; export enum DBType { diff --git a/src/features/ai/components/AIHistoryDetailDialog.tsx b/src/features/ai/components/AIHistoryDetailDialog.tsx new file mode 100644 index 0000000..f59841d --- /dev/null +++ b/src/features/ai/components/AIHistoryDetailDialog.tsx @@ -0,0 +1,274 @@ +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { + Bot, + Copy, + Check, + Trash2, + ChevronDown, + Clock, + Cpu, + Hash, + FileText, +} from "lucide-react"; +import { MarkdownRenderer } from "./AIResultDialog"; +import type { AIHistoryEntry } from "@/services/bridge/ai"; +import { cn } from "@/lib/utils"; + +interface AIHistoryDetailDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + entry: AIHistoryEntry | null; + onDelete?: (id: number) => void; +} + +const FEATURE_LABELS: Record = { + "schema-analysis": "Schema Analysis", + "query-explanation": "Query Explanation", + "chart-recommendation": "Chart Recommendation", +}; + +const PROVIDER_LABELS: Record = { + anthropic: "Anthropic", + openai: "OpenAI", + gemini: "Gemini", + groq: "Groq", + mistral: "Mistral", + ollama: "Ollama", +}; + +function formatDate(iso: string): string { + try { + return new Date(iso).toLocaleString(undefined, { + dateStyle: "medium", + timeStyle: "short", + }); + } catch { + return iso; + } +} + +export function AIHistoryDetailDialog({ + open, + onOpenChange, + entry, + onDelete, +}: AIHistoryDetailDialogProps) { + const [copiedField, setCopiedField] = useState<"prompt" | "response" | null>(null); + const [promptOpen, setPromptOpen] = useState(false); + + if (!entry) return null; + + const handleCopy = async (text: string, field: "prompt" | "response") => { + try { + await navigator.clipboard.writeText(text); + setCopiedField(field); + setTimeout(() => setCopiedField(null), 2000); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + return ( + + + + +
+ +
+ Analysis Details + + {FEATURE_LABELS[entry.feature] ?? entry.feature} + +
+
+ +
+ {/* Metadata grid */} +
+ } + label="Provider" + value={PROVIDER_LABELS[entry.provider] ?? entry.provider} + /> + } + label="Model" + value={entry.model} + /> + + + +
+ } + label="Created" + value={formatDate(entry.created_at)} + /> +
+
+ + {entry.created_at} + +
+
+ } + label="Tokens" + value={entry.tokens_used != null ? entry.tokens_used.toLocaleString() : "N/A"} + /> +
+ + {/* Prompt (collapsible) */} + +
+ + + + +
+ +
+                {entry.prompt}
+              
+
+
+ + {/* Response */} +
+
+ Response + +
+
+ +
+
+ + {/* Delete action */} + {onDelete && ( +
+ + + + + + + Delete this analysis? + + This will permanently remove this AI analysis from your local history. + This action cannot be undone. + + + + Cancel + { + onDelete(entry.id); + onOpenChange(false); + }} + > + Delete + + + + +
+ )} +
+
+
+ ); +} + +function MetaItem({ + icon, + label, + value, +}: { + icon: React.ReactNode; + label: string; + value: string; +}) { + return ( +
+ {icon} +
+

{label}

+

{value}

+
+
+ ); +} diff --git a/src/features/ai/components/AIHistoryPanel.tsx b/src/features/ai/components/AIHistoryPanel.tsx new file mode 100644 index 0000000..c435ae2 --- /dev/null +++ b/src/features/ai/components/AIHistoryPanel.tsx @@ -0,0 +1,359 @@ +import { useState, useEffect, useCallback } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { History, Trash2, ChevronLeft, ChevronRight, Loader2 } from "lucide-react"; +import { aiService, type AIHistoryListItem, type AIHistoryEntry } from "@/services/bridge/ai"; +import { AIHistoryDetailDialog } from "./AIHistoryDetailDialog"; +import { cn } from "@/lib/utils"; + +const PAGE_SIZE = 10; + +const FEATURE_OPTIONS = [ + { value: "all", label: "All Features" }, + { value: "schema-analysis", label: "Schema Analysis" }, + { value: "query-explanation", label: "Query Explanation" }, + { value: "chart-recommendation", label: "Chart Recommendation" }, +]; + +const PROVIDER_OPTIONS = [ + { value: "all", label: "All Providers" }, + { value: "anthropic", label: "Anthropic" }, + { value: "openai", label: "OpenAI" }, + { value: "gemini", label: "Gemini" }, + { value: "groq", label: "Groq" }, + { value: "mistral", label: "Mistral" }, + { value: "ollama", label: "Ollama" }, +]; + +const FEATURE_LABELS: Record = { + "schema-analysis": "Schema Analysis", + "query-explanation": "Query Explanation", + "chart-recommendation": "Chart Recommendation", +}; + +const FEATURE_COLORS: Record = { + "schema-analysis": "border-violet-500/30 text-violet-600 bg-violet-500/8", + "query-explanation": "border-blue-500/30 text-blue-600 bg-blue-500/8", + "chart-recommendation": "border-amber-500/30 text-amber-600 bg-amber-500/8", +}; + +const PROVIDER_LABELS: Record = { + anthropic: "Anthropic", + openai: "OpenAI", + gemini: "Gemini", + groq: "Groq", + mistral: "Mistral", + ollama: "Ollama", +}; + +function timeAgo(isoDate: string): string { + const diff = Date.now() - new Date(isoDate).getTime(); + const seconds = Math.floor(diff / 1000); + if (seconds < 60) return "just now"; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + if (days < 30) return `${days}d ago`; + const months = Math.floor(days / 30); + return `${months}mo ago`; +} + +export default function AIHistoryPanel() { + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(0); + const [loading, setLoading] = useState(false); + const [featureFilter, setFeatureFilter] = useState("all"); + const [providerFilter, setProviderFilter] = useState("all"); + + // Detail dialog + const [detailOpen, setDetailOpen] = useState(false); + const [detailEntry, setDetailEntry] = useState(null); + const [detailLoading, setDetailLoading] = useState(false); + + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + + const fetchHistory = useCallback(async () => { + setLoading(true); + try { + const result = await aiService.getHistory({ + feature: featureFilter !== "all" ? featureFilter : undefined, + provider: providerFilter !== "all" ? providerFilter : undefined, + limit: PAGE_SIZE, + offset: page * PAGE_SIZE, + }); + setItems(result.items); + setTotal(result.total); + } catch (err) { + console.error("Failed to load AI history:", err); + } finally { + setLoading(false); + } + }, [featureFilter, providerFilter, page]); + + useEffect(() => { + fetchHistory(); + }, [fetchHistory]); + + // Reset page when filters change + useEffect(() => { + setPage(0); + }, [featureFilter, providerFilter]); + + const handleRowClick = async (item: AIHistoryListItem) => { + setDetailLoading(true); + setDetailOpen(true); + try { + const entry = await aiService.getHistoryById(item.id); + setDetailEntry(entry); + } catch (err) { + console.error("Failed to load history detail:", err); + } finally { + setDetailLoading(false); + } + }; + + const handleDelete = async (id: number) => { + try { + await aiService.deleteHistory(id); + fetchHistory(); + } catch (err) { + console.error("Failed to delete history entry:", err); + } + }; + + const handleClearAll = async () => { + try { + await aiService.clearHistory(); + setItems([]); + setTotal(0); + setPage(0); + } catch (err) { + console.error("Failed to clear history:", err); + } + }; + + return ( + <> + + +
+ +
+ +
+ AI History + {total > 0 && ( + + {total} + + )} +
+ + {total > 0 && ( + + + + + + + Clear all AI history? + + This will permanently delete all {total} AI analysis entries from your local history. + Cached results will no longer be available. This action cannot be undone. + + + + Cancel + + Clear All History + + + + + )} +
+ + {/* Filters */} +
+ + +
+
+ + + {loading ? ( +
+ +
+ ) : items.length === 0 ? ( +
+ +

No AI analysis history yet.

+

+ Results will appear here after you use AI features. +

+
+ ) : ( + <> + + + + + Feature + Database + Provider + Tokens + Created + + + + {items.map((item) => ( + handleRowClick(item)} + > + + + {FEATURE_LABELS[item.feature] ?? item.feature} + + + + {item.datasource_id || ( + + )} + + + {PROVIDER_LABELS[item.provider] ?? item.provider} + + + {item.tokens_used != null ? ( + ~{item.tokens_used.toLocaleString()} + ) : ( + + )} + + + {timeAgo(item.created_at)} + + + ))} + +
+
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ + Page {page + 1} of {totalPages} · {total} entries + +
+ + +
+
+ )} + + )} +
+
+ + {/* Detail dialog */} + { + setDetailOpen(next); + if (!next) setDetailEntry(null); + }} + entry={detailLoading ? null : detailEntry} + onDelete={handleDelete} + /> + + ); +} diff --git a/src/features/ai/components/AIResultDialog.tsx b/src/features/ai/components/AIResultDialog.tsx new file mode 100644 index 0000000..f17f247 --- /dev/null +++ b/src/features/ai/components/AIResultDialog.tsx @@ -0,0 +1,289 @@ +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Loader2, Bot, AlertCircle, Sparkles, Copy, Check, RefreshCw } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface AIResultDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description?: string; + markdown?: string; + loading?: boolean; + error?: string | null; + /** Whether the result came from cache. */ + cached?: boolean; + /** ISO timestamp when the cached result was originally generated. */ + createdAt?: string; + /** Callback to force a fresh AI call (skip cache). */ + onReanalyze?: () => void; +} + +/** + * Format a relative time string from an ISO date. + */ +function timeAgo(isoDate: string): string { + const diff = Date.now() - new Date(isoDate).getTime(); + const seconds = Math.floor(diff / 1000); + if (seconds < 60) return "just now"; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + if (days < 30) return `${days}d ago`; + const months = Math.floor(days / 30); + return `${months}mo ago`; +} + +/** + * Reusable dialog that renders AI-generated markdown output. + * Used by Schema Analysis and Query Explanation features. + */ +export function AIResultDialog({ + open, + onOpenChange, + title, + description, + markdown, + loading, + error, + cached, + createdAt, + onReanalyze, +}: AIResultDialogProps) { + return ( + + + + +
+ +
+ {title} + {/* Cached / Fresh badge */} + {markdown && !loading && !error && cached !== undefined && ( + + {cached ? "Cached" : "Fresh"} + + )} +
+ {/* Description row with optional timestamp + re-analyze */} +
+ {description && ( + {description} + )} + {markdown && !loading && cached && createdAt && ( + + Generated {timeAgo(createdAt)} + + )} + {markdown && !loading && cached && onReanalyze && ( + + )} +
+
+ +
+ {loading ? ( +
+
+ +
+

Analyzing with AI…

+
+ ) : error ? ( +
+
+ +
+

Analysis failed

+

+ {error} +

+

+ Make sure your AI provider is configured in{" "} + Settings → AI Settings. +

+
+ ) : markdown ? ( + + ) : ( +
+
+ +
+

No content yet.

+
+ )} +
+
+
+ ); +} + +/** + * Lightweight markdown renderer — no heavy library needed. + * Handles headings, bold, code blocks, bullet lists, and paragraphs. + * + * Exported so the history detail dialog can reuse it. + */ +export function MarkdownRenderer({ content }: { content: string }) { + const lines = content.split("\n"); + const elements: React.ReactNode[] = []; + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + + // Fenced code block + if (line.trim().startsWith("```")) { + const lang = line.trim().slice(3).trim(); + const codeLines: string[] = []; + i++; + while (i < lines.length && !lines[i].trim().startsWith("```")) { + codeLines.push(lines[i]); + i++; + } + elements.push( + + ); + i++; + continue; + } + + // H1–H3 headings + const h3 = line.match(/^###\s+(.+)$/); + const h2 = line.match(/^##\s+(.+)$/); + const h1 = line.match(/^#\s+(.+)$/); + if (h1) { + elements.push(

{renderInline(h1[1])}

); + i++; continue; + } + if (h2) { + elements.push(

{renderInline(h2[1])}

); + i++; continue; + } + if (h3) { + elements.push(
{renderInline(h3[1])}
); + i++; continue; + } + + // Bullet list items + const bullet = line.match(/^[-*]\s+(.+)$/); + if (bullet) { + const items: string[] = [bullet[1]]; + i++; + while (i < lines.length && lines[i].match(/^[-*]\s+(.+)$/)) { + items.push(lines[i].match(/^[-*]\s+(.+)$/)![1]); + i++; + } + elements.push( +
    + {items.map((item, idx) => ( +
  • + {renderInline(item)} +
  • + ))} +
+ ); + continue; + } + + // Blank line + if (!line.trim()) { + i++; + continue; + } + + // Paragraph + elements.push( +

+ {renderInline(line)} +

+ ); + i++; + } + + return
{elements}
; +} + +/** Render bold (**text**) and inline code (`code`) within a text node. */ +function renderInline(text: string): React.ReactNode { + const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g); + return parts.map((part, i) => { + if (part.startsWith("**") && part.endsWith("**")) { + return {part.slice(2, -2)}; + } + if (part.startsWith("`") && part.endsWith("`")) { + return {part.slice(1, -1)}; + } + return part; + }); +} + +function CodeBlock({ code, lang }: { code: string; lang: string }) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy code: ", err); + } + }; + + return ( +
+
+        {code}
+      
+ +
+ ); +} diff --git a/src/features/ai/hooks/useAISettings.ts b/src/features/ai/hooks/useAISettings.ts new file mode 100644 index 0000000..92045d3 --- /dev/null +++ b/src/features/ai/hooks/useAISettings.ts @@ -0,0 +1,22 @@ +import { loadAISettings, type AISettings } from "@/services/bridge/ai"; +import { useEffect, useState } from "react"; + +/** + * Reads AISettings from localStorage and stays in sync when + * the user updates them in the Settings page during the same session. + */ +export function useAISettings(): AISettings { + const [settings, setSettings] = useState(loadAISettings); + + useEffect(() => { + const onStorage = (e: StorageEvent) => { + if (e.key === "relwave:ai-settings") { + setSettings(loadAISettings()); + } + }; + window.addEventListener("storage", onStorage); + return () => window.removeEventListener("storage", onStorage); + }, []); + + return settings; +} diff --git a/src/features/chart/components/ChartVisualization.tsx b/src/features/chart/components/ChartVisualization.tsx index 8f1245d..f470f4f 100644 --- a/src/features/chart/components/ChartVisualization.tsx +++ b/src/features/chart/components/ChartVisualization.tsx @@ -6,6 +6,7 @@ import { ChevronDown, Sparkles, AlertCircle, + Bot, } from "lucide-react"; import { DropdownMenu, @@ -19,6 +20,10 @@ import ChartRenderer from "./ChartRenderer"; import { useChartVisualization } from "../hooks/useChartVisualization"; import { SelectedTable } from "@/features/database/types"; import { cn } from "@/lib/utils"; +import { useState } from "react"; +import { useAISettings } from "@/features/ai/hooks/useAISettings"; +import { aiService } from "@/services/bridge/ai"; +import { toast } from "sonner"; interface ChartVisualizationProps { selectedTable: SelectedTable; @@ -29,6 +34,9 @@ export const ChartVisualization = ({ selectedTable, dbId, }: ChartVisualizationProps) => { + const aiSettings = useAISettings(); + const [aiLoading, setAiLoading] = useState(false); + const { handleExport, chartType, @@ -45,6 +53,29 @@ export const ChartVisualization = ({ rowData, } = useChartVisualization(selectedTable, dbId); + const handleAISuggest = async () => { + if (!columnData.length) return; + setAiLoading(true); + try { + const rec = await aiService.recommendChart(aiSettings, { + tableName: selectedTable?.name ?? "table", + columns: columnData.map((c) => ({ + name: c.name, + type: c.type, + isPrimaryKey: c.isPrimaryKey, + })), + }); + setChartType(rec.chartType); + setXAxis(rec.xAxis); + setYAxis(rec.yAxis); + toast.success("AI suggestion applied", { description: rec.reasoning }); + } catch (err: any) { + toast.error("AI suggestion failed", { description: err?.message ?? String(err) }); + } finally { + setAiLoading(false); + } + }; + const hasData = rowData.length > 0; const isReady = !isExecuting && !errorMessage && hasData; @@ -72,6 +103,22 @@ export const ChartVisualization = ({ )} + {/* AI Suggest button */} + + + + + + ); +} diff --git a/src/features/schema-explorer/components/SchemaExplorerHeader.tsx b/src/features/schema-explorer/components/SchemaExplorerHeader.tsx index 19f72ff..9146abc 100644 --- a/src/features/schema-explorer/components/SchemaExplorerHeader.tsx +++ b/src/features/schema-explorer/components/SchemaExplorerHeader.tsx @@ -6,11 +6,19 @@ import CreateTableDialog from './CreateTableDialog' import AddIndexesDialog from './AddIndexesDialog' import DropTableDialog from './DropTableDialog' import AlterTableDialog from './AlterTableDialog' +import { AnalyzeSchemaButton } from './AnalyzeSchemaButton' interface SchemaExplorerHeaderProps { dbId: string; database: { name: string; + schemas?: Array<{ + name?: string; + tables: Array<{ + name: string; + columns: Array<{ name: string; type: string; nullable?: boolean; isPrimaryKey?: boolean; isForeignKey?: boolean }>; + }>; + }>; }; onTableCreated?: () => void; selectedTable?: { schema: string; name: string; columns: string[] } | null; @@ -39,6 +47,13 @@ const SchemaExplorerHeader = ({ dbId, database, onTableCreated, selectedTable }: {/* Action Buttons */}
+ {/* AI Analyze Schema */} + {database.schemas && database.schemas.length > 0 && ( + + )} + {/* Table Actions - only show if table is selected */} {selectedTable && ( <> diff --git a/src/features/settings/components/AISettings.tsx b/src/features/settings/components/AISettings.tsx new file mode 100644 index 0000000..db3b460 --- /dev/null +++ b/src/features/settings/components/AISettings.tsx @@ -0,0 +1,335 @@ +import { useState, useEffect } from "react"; +import { Bot, Eye, EyeOff, CheckCircle2, XCircle, Loader2, ChevronDown } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { cn } from "@/lib/utils"; +import { + AIProviderName, + type AISettings as AISettingsData, + loadAISettings, + saveAISettings, + aiService, +} from "@/services/bridge/ai"; + +// ── Provider metadata ───────────────────────────────────────────────────── + +interface ProviderMeta { + name: AIProviderName; + label: string; + description: string; + requiresKey: boolean; + keyField?: keyof AISettingsData; + keyPlaceholder?: string; + extraFields?: Array<{ + field: keyof AISettingsData; + label: string; + placeholder: string; + type?: string; + }>; +} + +const PROVIDERS: ProviderMeta[] = [ + { + name: "anthropic", + label: "Claude (Anthropic)", + description: "claude-3-5-haiku-20241022", + requiresKey: true, + keyField: "anthropicApiKey", + keyPlaceholder: "sk-ant-api03-…", + }, + { + name: "openai", + label: "OpenAI", + description: "gpt-4o-mini", + requiresKey: true, + keyField: "openaiApiKey", + keyPlaceholder: "sk-proj-…", + }, + { + name: "gemini", + label: "Gemini (Google)", + description: "gemini-1.5-flash", + requiresKey: true, + keyField: "geminiApiKey", + keyPlaceholder: "AIzaSy…", + }, + { + name: "groq", + label: "Groq", + description: "llama-3.3-70b-versatile", + requiresKey: true, + keyField: "groqApiKey", + keyPlaceholder: "gsk_…", + }, + { + name: "mistral", + label: "Mistral", + description: "mistral-small-latest", + requiresKey: true, + keyField: "mistralApiKey", + keyPlaceholder: "…", + }, + { + name: "ollama", + label: "Ollama (Local)", + description: "Runs entirely on your machine", + requiresKey: false, + extraFields: [ + { + field: "ollamaBaseUrl", + label: "Base URL", + placeholder: "http://localhost:11434", + }, + { + field: "ollamaModel", + label: "Model", + placeholder: "llama3.2", + }, + ], + }, +]; + +// ── Connection status indicator ─────────────────────────────────────────── + +type ConnectionStatus = "idle" | "testing" | "ok" | "error"; + +function StatusBadge({ status, message }: { status: ConnectionStatus; message?: string }) { + if (status === "idle") return null; + return ( +
+ {status === "testing" && } + {status === "ok" && } + {status === "error" && } + {status === "testing" ? "Testing…" : status === "ok" ? "Connected" : (message ?? "Failed")} +
+ ); +} + +// ── Password field with show/hide ───────────────────────────────────────── + +function SecretInput({ + value, + onChange, + placeholder, + id, +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + id: string; +}) { + const [show, setShow] = useState(false); + return ( +
+ onChange(e.target.value)} + placeholder={placeholder} + className="pr-9 h-8 text-xs font-mono border-border/40" + autoComplete="off" + spellCheck={false} + /> + +
+ ); +} + +// ── Main component ──────────────────────────────────────────────────────── + +export default function AISettings() { + const [settings, setSettings] = useState(loadAISettings); + const [dirty, setDirty] = useState(false); + const [status, setStatus] = useState("idle"); + const [statusMessage, setStatusMessage] = useState(); + + // Load from storage on mount + useEffect(() => { + setSettings(loadAISettings()); + }, []); + + const activeProvider = PROVIDERS.find((p) => p.name === settings.defaultProvider) ?? PROVIDERS[0]; + + const update = (patch: Partial) => { + setSettings((prev) => ({ ...prev, ...patch })); + setDirty(true); + setStatus("idle"); + }; + + const handleSave = () => { + saveAISettings(settings); + setDirty(false); + setStatus("idle"); + }; + + const handleTest = async () => { + // Save first so the bridge gets the latest values + saveAISettings(settings); + setDirty(false); + setStatus("testing"); + setStatusMessage(undefined); + const result = await aiService.testConnection(settings); + if (result.connected) { + setStatus("ok"); + } else { + setStatus("error"); + setStatusMessage(result.message); + } + }; + + return ( +
+ {/* Section header */} +
+
+ +
+
+

AI Settings

+

+ Configure your AI provider. Keys stay on your machine. +

+
+
+ +
+ {/* Provider selector */} +
+
+ +

{activeProvider.description}

+
+ + + + + + {PROVIDERS.map((p) => ( + update({ defaultProvider: p.name })} + > + {p.label} + {p.description} + + ))} + + +
+ + {/* Credential fields for the active provider */} +
+ {activeProvider.requiresKey && activeProvider.keyField && ( +
+ + update({ [activeProvider.keyField!]: v })} + placeholder={activeProvider.keyPlaceholder} + /> +
+ )} + + {activeProvider.extraFields?.map((field) => ( +
+ + update({ [field.field]: e.target.value })} + placeholder={field.placeholder} + className="h-8 text-xs border-border/40" + spellCheck={false} + /> +
+ ))} +
+ + {/* Actions */} +
+ +
+ + +
+
+
+ + {/* All providers key summary (collapsed) */} +
+ + + Configure other providers + +
+ {PROVIDERS.filter((p) => p.name !== settings.defaultProvider && p.requiresKey).map((provider) => ( +
+ + update({ [provider.keyField!]: v })} + placeholder={provider.keyPlaceholder} + /> +
+ ))} +
+
+
+ ); +} diff --git a/src/features/settings/components/index.tsx b/src/features/settings/components/index.tsx index 70ae1b9..9b243f2 100644 --- a/src/features/settings/components/index.tsx +++ b/src/features/settings/components/index.tsx @@ -4,4 +4,6 @@ export { default as ColorVariant } from './ColorVariant' export {default as DeveloperMode} from './DeveloperMode' export {default as CheckForUpdates} from './CheckForUpdates' export {default as Version} from './Version' -export {default as Preview} from './Preview' \ No newline at end of file +export {default as Preview} from './Preview' +export {default as AISettings} from './AISettings' +export { default as AIHistoryPanel } from '../../ai/components/AIHistoryPanel' \ No newline at end of file diff --git a/src/features/workspace/components/ExplainQueryButton.tsx b/src/features/workspace/components/ExplainQueryButton.tsx new file mode 100644 index 0000000..717c03d --- /dev/null +++ b/src/features/workspace/components/ExplainQueryButton.tsx @@ -0,0 +1,85 @@ +import { useState } from "react"; +import { Bot, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { AIResultDialog } from "@/features/ai/components/AIResultDialog"; +import { useAISettings } from "@/features/ai/hooks/useAISettings"; +import { aiService } from "@/services/bridge/ai"; + +interface ExplainQueryButtonProps { + sql: string; + disabled?: boolean; + databaseName?: string; +} + +export function ExplainQueryButton({ sql, disabled, databaseName }: ExplainQueryButtonProps) { + const settings = useAISettings(); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [markdown, setMarkdown] = useState(); + const [error, setError] = useState(null); + const [cached, setCached] = useState(); + const [createdAt, setCreatedAt] = useState(); + + const doExplain = async (skipCache = false) => { + setOpen(true); + setMarkdown(undefined); + setError(null); + setLoading(true); + + try { + const result = await aiService.explainQuery(settings, { + sql: sql.trim(), + }, { skipCache, datasourceName: databaseName }); + setMarkdown(result.markdown); + setCached(result.cached); + setCreatedAt(result.createdAt); + } catch (err: any) { + setError(err?.message ?? String(err)); + } finally { + setLoading(false); + } + }; + + const handleReanalyze = () => { + setCached(undefined); + setCreatedAt(undefined); + doExplain(true); + }; + + const shortSQL = + sql.trim().length > 60 + ? sql.trim().slice(0, 60).replace(/\s+/g, " ") + "…" + : sql.trim().replace(/\s+/g, " "); + + return ( + <> + + + + + ); +} diff --git a/src/features/workspace/components/SQLWorkspacePanel.tsx b/src/features/workspace/components/SQLWorkspacePanel.tsx index 38c1019..5e9a3f8 100644 --- a/src/features/workspace/components/SQLWorkspacePanel.tsx +++ b/src/features/workspace/components/SQLWorkspacePanel.tsx @@ -167,6 +167,7 @@ const SQLWorkspacePanel = ({ dbId }: SQLWorkspacePanelProps) => { isExecuting={isExecuting} queryProgress={queryProgress} canExecute={!!activeTab?.query.trim()} + activeQuery={activeTab?.query} onExecute={handleExecuteQuery} onCancel={handleCancelQuery} /> diff --git a/src/features/workspace/components/WorkspaceHeader.tsx b/src/features/workspace/components/WorkspaceHeader.tsx index 8b032b8..12b859a 100644 --- a/src/features/workspace/components/WorkspaceHeader.tsx +++ b/src/features/workspace/components/WorkspaceHeader.tsx @@ -1,11 +1,13 @@ import { Play, Square, Database, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { ExplainQueryButton } from "./ExplainQueryButton"; interface WorkspaceHeaderProps { databaseName: string; isExecuting: boolean; queryProgress: { rows: number; elapsed: number } | null; canExecute: boolean; + activeQuery?: string; onExecute: () => void; onCancel: () => void; } @@ -15,6 +17,7 @@ export function WorkspaceHeader({ isExecuting, queryProgress, canExecute, + activeQuery, onExecute, onCancel, }: WorkspaceHeaderProps) { @@ -38,6 +41,13 @@ export function WorkspaceHeader({
)} + {/* AI Explain Query */} + + {/* Run/Stop buttons */} {isExecuting ? (