diff --git a/package-lock.json b/package-lock.json index 80f440b..85acb5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@ai-sdk/google": "^1.0.0", "@ai-sdk/openai": "^1.1.9", + "@google-cloud/storage": "^7.16.0", + "@napi-rs/canvas": "^0.1.68", "@types/jsdom": "^21.1.7", "ai": "^4.1.26", "axios": "^1.7.9", @@ -982,6 +984,78 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.16.0.tgz", + "integrity": "sha512-7/5LRgykyOfQENcm6hDKP8SX/u9XxE5YOiWOkgkwcoO+cG8xT/cyOvp9wwN3IxfdYgpHs8CE7Nq2PKX2lNaEXw==", + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "<4.1.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1550,6 +1624,176 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.68.tgz", + "integrity": "sha512-LQESrePLEBLvhuFkXx9jjBXRC2ClYsO5mqQ1m/puth5z9SOuM3N/B3vDuqnC3RJFktDktyK9khGvo7dTkqO9uQ==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.68", + "@napi-rs/canvas-darwin-arm64": "0.1.68", + "@napi-rs/canvas-darwin-x64": "0.1.68", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.68", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.68", + "@napi-rs/canvas-linux-arm64-musl": "0.1.68", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.68", + "@napi-rs/canvas-linux-x64-gnu": "0.1.68", + "@napi-rs/canvas-linux-x64-musl": "0.1.68", + "@napi-rs/canvas-win32-x64-msvc": "0.1.68" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.68.tgz", + "integrity": "sha512-h1KcSR4LKLfRfzeBH65xMxbWOGa1OtMFQbCMVlxPCkN1Zr+2gK+70pXO5ktojIYcUrP6KDcOwoc8clho5ccM/w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.68.tgz", + "integrity": "sha512-/VURlrAD4gDoxW1GT/b0nP3fRz/fhxmHI/xznTq2FTwkQLPOlLkDLCvTmQ7v6LtGKdc2Ed6rvYpRan+JXThInQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.68.tgz", + "integrity": "sha512-tEpvGR6vCLTo1Tx9wmDnoOKROpw57wiCWwCpDOuVlj/7rqEJOUYr9ixW4aRJgmeGBrZHgevI0EURys2ER6whmg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.68.tgz", + "integrity": "sha512-U9xbJsumPOiAYeAFZMlHf62b9dGs2HJ6Q5xt7xTB0uEyPeurwhgYBWGgabdsEidyj38YuzI/c3LGBbSQB3vagw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.68.tgz", + "integrity": "sha512-KFkn8wEm3mPnWD4l8+OUUkxylSJuN5q9PnJRZJgv15RtCA1bgxIwTkBhI/+xuyVMcHqON9sXq7cDkEJtHm35dg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.68.tgz", + "integrity": "sha512-IQzts91rCdOALXBWQxLZRCEDrfFTGDtNRJMNu+2SKZ1uT8cmPQkPwVk5rycvFpvgAcmiFiOSCp1aRrlfU8KPpQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.68.tgz", + "integrity": "sha512-e9AS5UttoIKqXSmBzKZdd3NErSVyOEYzJfNOCGtafGk1//gibTwQXGlSXmAKuErqMp09pyk9aqQRSYzm1AQfBw==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.68.tgz", + "integrity": "sha512-Pa/I36VE3j57I3Obhrr+J48KGFfkZk2cJN/2NmW/vCgmoF7kCP6aTVq5n+cGdGWLd/cN9CJ9JvNwEoMRDghu0g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.68.tgz", + "integrity": "sha512-9c6rkc5195wNxuUHJdf4/mmnq433OQey9TNvQ9LspJazvHbfSkTij8wtKjASVQsJyPDva4fkWOeV/OQ7cLw0GQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.68.tgz", + "integrity": "sha512-Fc5Dez23u0FoSATurT6/w1oMytiRnKWEinHivdMvXpge6nG4YvhrASrtqMk8dGJMVQpHr8QJYF45rOrx2YU2Aw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1624,6 +1868,14 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -1708,6 +1960,11 @@ "@types/node": "*" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==" + }, "node_modules/@types/commander": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.0.tgz", @@ -1898,6 +2155,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", + "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -2175,6 +2458,17 @@ "dev": true, "license": "ISC" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2378,6 +2672,14 @@ "node": ">=8" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -2392,6 +2694,14 @@ "dev": true, "license": "MIT" }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2560,19 +2870,29 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/bignumber.js": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "license": "MIT", - "optional": true, "engines": { "node": "*" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "optional": true, + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -2703,12 +3023,36 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause", - "optional": true + "license": "BSD-3-Clause" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -2796,6 +3140,21 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.1.0.tgz", + "integrity": "sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2823,6 +3182,13 @@ "node": ">=10" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "optional": true, + "peer": true + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -3136,6 +3502,22 @@ "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "optional": true, + "peer": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -3151,6 +3533,16 @@ } } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "optional": true, + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3205,6 +3597,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3317,12 +3719,22 @@ "node": ">= 0.4" } }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", - "optional": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -3385,6 +3797,14 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -3437,6 +3857,20 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3664,6 +4098,14 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/eventsource-parser": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", @@ -3706,6 +4148,16 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -3788,8 +4240,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -3849,6 +4300,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", @@ -4092,6 +4560,13 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "optional": true, + "peer": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4128,7 +4603,6 @@ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", "license": "Apache-2.0", - "optional": true, "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -4145,7 +4619,6 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", - "optional": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -4166,7 +4639,6 @@ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", "license": "Apache-2.0", - "optional": true, "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", @@ -4256,6 +4728,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "optional": true, + "peer": true + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4357,7 +4836,6 @@ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", "license": "Apache-2.0", - "optional": true, "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", @@ -4375,7 +4853,6 @@ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=14" } @@ -4411,7 +4888,6 @@ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", "license": "MIT", - "optional": true, "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" @@ -4442,6 +4918,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4572,6 +5062,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "peer": true + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4647,6 +5158,13 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "optional": true, + "peer": true + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -4752,7 +5270,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5577,7 +6094,6 @@ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "license": "MIT", - "optional": true, "dependencies": { "bignumber.js": "^9.0.0" } @@ -5663,7 +6179,6 @@ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", "license": "MIT", - "optional": true, "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -5675,7 +6190,6 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", "license": "MIT", - "optional": true, "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" @@ -5915,6 +6429,19 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -5931,6 +6458,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "optional": true, + "peer": true + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5955,6 +6499,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "optional": true, + "peer": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5987,6 +6538,26 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", + "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "optional": true, + "peer": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "optional": true, + "peer": true + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -6104,7 +6675,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -6148,7 +6718,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -6391,6 +6960,33 @@ "node": ">=8" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "optional": true, + "peer": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6462,6 +7058,17 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "optional": true, + "peer": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6560,6 +7167,32 @@ "node": ">=0.10.0" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "optional": true, + "peer": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", @@ -6577,6 +7210,19 @@ "dev": true, "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -6651,6 +7297,27 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -6763,7 +7430,7 @@ "version": "7.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -6943,6 +7610,53 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "peer": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "peer": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -7020,6 +7734,27 @@ "node": ">= 0.8" } }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -7095,6 +7830,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" + }, "node_modules/superagent": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", @@ -7188,6 +7939,106 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/tar-fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "optional": true, + "peer": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "optional": true, + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -7309,8 +8160,7 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/ts-api-utils": { "version": "1.4.3", @@ -7418,6 +8268,19 @@ } } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "optional": true, + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7555,6 +8418,11 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -7573,7 +8441,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "optional": true, "bin": { "uuid": "dist/bin/uuid" } @@ -7655,8 +8522,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "optional": true + "license": "BSD-2-Clause" }, "node_modules/whatwg-encoding": { "version": "3.1.1", @@ -7684,7 +8550,6 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", - "optional": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -7738,7 +8603,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -7851,7 +8715,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 649b528..05c6464 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "dependencies": { "@ai-sdk/google": "^1.0.0", "@ai-sdk/openai": "^1.1.9", + "@google-cloud/storage": "^7.16.0", + "@napi-rs/canvas": "^0.1.68", "@types/jsdom": "^21.1.7", "ai": "^4.1.26", "axios": "^1.7.9", diff --git a/src/agent.ts b/src/agent.ts index 39dfd59..2c71363 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -16,7 +16,9 @@ import { KnowledgeItem, EvaluationType, BoostedSearchSnippet, - SearchSnippet, EvaluationResponse, Reference, SERPQuery, RepeatEvaluationType, UnNormalizedSearchSnippet, WebContent + SearchSnippet, EvaluationResponse, Reference, SERPQuery, RepeatEvaluationType, UnNormalizedSearchSnippet, WebContent, + ImageObject, + ImageReference } from "./types"; import { TrackerContext } from "./types"; import { search } from "./tools/jina-search"; @@ -41,7 +43,7 @@ import { import { MAX_QUERIES_PER_STEP, MAX_REFLECT_PER_STEP, MAX_URLS_PER_STEP, Schemas } from "./utils/schemas"; import { formatDateBasedOnType, formatDateRange } from "./utils/date-tools"; import { reviseAnswer } from "./tools/md-fixer"; -import { buildReferences } from "./tools/build-ref"; +import { buildImageReferences, buildReferences } from "./tools/build-ref"; async function sleep(ms: number) { const seconds = Math.ceil(ms / 1000); @@ -391,8 +393,9 @@ export async function getResponse(question?: string, minRelScore: number = 0.85, languageCode: string | undefined = undefined, searchLanguageCode?: string, - searchProvider?: string -): Promise<{ result: StepAction; context: TrackerContext; visitedURLs: string[], readURLs: string[], allURLs: string[] }> { + searchProvider?: string, + with_images: boolean = false +): Promise<{ result: StepAction; context: TrackerContext; visitedURLs: string[], readURLs: string[], allURLs: string[], allImages?: string[], relatedImages?: string[] }> { let step = 0; let totalStep = 0; @@ -451,6 +454,7 @@ export async function getResponse(question?: string, const allWebContents: Record = {}; const visitedURLs: string[] = []; const badURLs: string[] = []; + const imageObjects: ImageObject[] = []; const evaluationMetrics: Record = {}; // reserve the 10% final budget for the beast mode const regularBudget = tokenBudget * 0.85; @@ -859,9 +863,11 @@ You decided to think out of the box or cut from a completely different angle. allURLs, visitedURLs, badURLs, + imageObjects, SchemaGen, currentQuestion, - allWebContents + allWebContents, + with_images ); diaryContext.push(success @@ -1017,7 +1023,16 @@ But unfortunately, you failed to solve the issue. You need to think out of the b answerStep.mdAnswer = buildMdFromAnswer(answerStep); } - console.log(thisStep) + let imageReferences: ImageReference[] = []; + if(imageObjects.length && with_images) { + try { + imageReferences = await buildImageReferences(answerStep.answer, imageObjects, context, SchemaGen); + console.log('Image references built:', imageReferences); + } catch (error) { + console.error('Error building image references:', error); + imageReferences = []; + } + } // max return 300 urls const returnedURLs = weightedURLs.slice(0, numReturnedURLs).map(r => r.url); @@ -1026,7 +1041,9 @@ But unfortunately, you failed to solve the issue. You need to think out of the b context, visitedURLs: returnedURLs, readURLs: visitedURLs.filter(url => !badURLs.includes(url)), - allURLs: weightedURLs.map(r => r.url) + allURLs: weightedURLs.map(r => r.url), + allImages: with_images ? imageObjects.map(i => i.url) : undefined, + relatedImages: with_images ? imageReferences.map(i => i.url) : undefined, }; } diff --git a/src/app.ts b/src/app.ts index ec20a36..f2d89ae 100644 --- a/src/app.ts +++ b/src/app.ts @@ -7,7 +7,7 @@ import { ChatCompletionResponse, ChatCompletionChunk, AnswerAction, - Model, StepAction, VisitAction + Model, StepAction, VisitAction, } from './types'; import { TokenTracker } from "./utils/token-tracker"; import { ActionTracker } from "./utils/action-tracker"; @@ -522,7 +522,7 @@ app.post('/v1/chat/completions', (async (req: Request, res: Response) => { // Add content to queue for both thinking steps and final answer if (step.action === 'visit') { // emit every url in the visit action in url field - ((step as VisitAction).URLTargets as string[]).forEach((url) => { + ((step as VisitAction).URLTargets as string[])?.forEach((url) => { const chunk: ChatCompletionChunk = { id: requestId, object: 'chat.completion.chunk', @@ -568,7 +568,9 @@ app.post('/v1/chat/completions', (async (req: Request, res: Response) => { result: finalStep, visitedURLs, readURLs, - allURLs + allURLs, + allImages, + relatedImages, } = await getResponse(undefined, tokenBudget, maxBadAttempts, @@ -583,7 +585,8 @@ app.post('/v1/chat/completions', (async (req: Request, res: Response) => { body.min_annotation_relevance, body.language_code, body.search_language_code, - body.search_provider + body.search_provider, + body.with_images ) let finalAnswer = (finalStep as AnswerAction).mdAnswer; @@ -656,7 +659,8 @@ app.post('/v1/chat/completions', (async (req: Request, res: Response) => { usage, visitedURLs, readURLs, - numURLs: allURLs.length + numURLs: allURLs.length, + relatedImages }; res.write(`data: ${JSON.stringify(finalChunk)}\n\n`); res.end(); @@ -682,7 +686,8 @@ app.post('/v1/chat/completions', (async (req: Request, res: Response) => { usage, visitedURLs, readURLs, - numURLs: allURLs.length + numURLs: allURLs.length, + relatedImages, }; // Log final response (excluding full content for brevity) @@ -693,7 +698,9 @@ app.post('/v1/chat/completions', (async (req: Request, res: Response) => { usage: response.usage, visitedURLs: response.visitedURLs, readURLs: response.readURLs, - numURLs: allURLs.length + numURLs: allURLs.length, + allImages: allImages?.length, + relatedImages: relatedImages?.length, }); res.json(response); diff --git a/src/tools/build-ref.ts b/src/tools/build-ref.ts index e84e441..9fa90c4 100644 --- a/src/tools/build-ref.ts +++ b/src/tools/build-ref.ts @@ -1,8 +1,9 @@ import {segmentText} from './segment'; -import {Reference, TrackerContext, WebContent} from "../types"; +import {ImageObject, ImageReference, Reference, TrackerContext, WebContent} from "../types"; import {Schemas} from "../utils/schemas"; import {cosineSimilarity, jaccardRank} from "./cosine"; import {getEmbeddings} from "./embeddings"; +import { dedupImagesWithEmbeddings } from '../utils/image-tools'; import {normalizeHostName} from '../utils/url-tools'; export async function buildReferences( @@ -366,4 +367,184 @@ function buildFinalResult( answer: modifiedAnswer, references }; +} + +export async function buildImageReferences( + answer: string, + imageObjects: ImageObject[], + context: TrackerContext, + schema: Schemas, + minChunkLength: number = 80, + maxRef: number = 10, + minRelScore: number = 0.35 +): Promise> { + console.log(`[buildImageReferences] Starting with maxRef=${maxRef}, minChunkLength=${minChunkLength}, minRelScore=${minRelScore}`); + console.log(`[buildImageReferences] Answer length: ${answer.length} chars, Image sources: ${imageObjects.length}`); + + // Step 1: Chunk the answer + console.log(`[buildImageReferences] Step 1: Chunking answer text`); + const {chunks: answerChunks, chunk_positions: answerChunkPositions} = await segmentText(answer, context); + console.log(`[buildImageReferences] Answer segmented into ${answerChunks.length} chunks`); + + // Step 2: Prepare image content + console.log(`[buildImageReferences] Step 2: Preparing image content`); + const dudupImages = dedupImagesWithEmbeddings(imageObjects, []); + const allImageEmbeddings: number[][] = dudupImages.map(img => img.embedding[0]); // Extract embedding + const imageToSourceMap: any = {}; + const validImageIndices = new Set(); + + dudupImages.forEach((img, index) => { + imageToSourceMap[index] = { + url: img.url, + altText: img.alt, + embedding: img.embedding[0] // Store extracted embedding + }; + validImageIndices.add(index); + }); + + console.log(`[buildImageReferences] Collected ${allImageEmbeddings.length} image embeddings`); + + if (allImageEmbeddings.length === 0) { + console.log(`[buildImageReferences] No image data available, returning empty array`); + return []; + } + + // Step 3: Filter answer chunks by minimum length + console.log(`[buildImageReferences] Step 3: Filtering answer chunks by minimum length`); + const validAnswerChunks: string[] = []; + const validAnswerChunkIndices: number[] = []; + const validAnswerChunkPositions: [number, number][] = []; + + context.actionTracker.trackThink('cross_reference', schema.languageCode); + + for (let i = 0; i < answerChunks.length; i++) { + const answerChunk = answerChunks[i]; + const answerChunkPosition = answerChunkPositions[i]; + + if (!answerChunk.trim() || answerChunk.length < minChunkLength) continue; + + validAnswerChunks.push(answerChunk); + validAnswerChunkIndices.push(i); + validAnswerChunkPositions.push(answerChunkPosition); + } + + console.log(`[buildImageReferences] Found ${validAnswerChunks.length}/${answerChunks.length} valid answer chunks above minimum length`); + + if (validAnswerChunks.length === 0) { + console.log(`[buildImageReferences] No valid answer chunks, returning empty array`); + return []; + } + + // Step 4: Get embeddings for answer chunks + console.log(`[buildImageReferences] Step 4: Getting embeddings for answer chunks`); + const answerEmbeddings: number[][] = []; + + try { + // const embeddingsResult = await getEmbeddings(validAnswerChunks, context.tokenTracker, embeddingOptions); // No embeddingOptions needed here + // answerEmbeddings.push(...embeddingsResult.embeddings); + const embeddingsResult = await getEmbeddings(validAnswerChunks, context.tokenTracker, { + dimensions: 1024, + model: 'jina-clip-v2', + }); + answerEmbeddings.push(...embeddingsResult.embeddings); + + console.log(`[buildImageReferences] Got embeddings for ${answerEmbeddings.length} answer chunks`); + + // Step 5: Compute pairwise cosine similarity + console.log(`[buildImageReferences] Step 5: Computing pairwise cosine similarity between answer and image embeddings`); + const allMatches = []; + + for (let i = 0; i < validAnswerChunks.length; i++) { + const answerChunkIndex = validAnswerChunkIndices[i]; + const answerChunk = validAnswerChunks[i]; + const answerChunkPosition = answerChunkPositions[i]; + const answerEmbedding = answerEmbeddings[i]; + + const matchesForChunk = []; + + for (const imageIndex of validImageIndices) { + const imageEmbedding = allImageEmbeddings[imageIndex]; + + if (imageEmbedding) { + const score = cosineSimilarity(answerEmbedding, imageEmbedding); + + matchesForChunk.push({ + imageIndex, + relevanceScore: score + }); + } + } + + matchesForChunk.sort((a, b) => b.relevanceScore - a.relevanceScore); + + for (const match of matchesForChunk) { + allMatches.push({ + imageIndex: match.imageIndex, + answerChunkIndex: answerChunkIndex, + relevanceScore: match.relevanceScore, + answerChunk: answerChunk, + answerChunkPosition: answerChunkPosition + }); + } + + console.log(`[buildImageReferences] Processed answer chunk ${i + 1}/${validAnswerChunks.length}, top score: ${matchesForChunk[0]?.relevanceScore.toFixed(4)}`); + } + + // Log statistics about relevance scores + if (allMatches.length > 0) { + const relevanceScores = allMatches.map(match => match.relevanceScore); + const minRelevance = Math.min(...relevanceScores); + const maxRelevance = Math.max(...relevanceScores); + const sumRelevance = relevanceScores.reduce((sum, score) => sum + score, 0); + const meanRelevance = sumRelevance / relevanceScores.length; + + console.log('Reference relevance statistics:', { + min: minRelevance.toFixed(4), + max: maxRelevance.toFixed(4), + mean: meanRelevance.toFixed(4), + count: relevanceScores.length + }); + } + + + // Step 6: Sort all matches by relevance + allMatches.sort((a, b) => b.relevanceScore - a.relevanceScore); + console.log(`[buildImageReferences] Step 6: Sorted ${allMatches.length} potential matches by relevance score`); + + // Step 7: Filter matches + console.log(`[buildImageReferences] Step 7: Filtering matches to ensure uniqueness and threshold (min: ${minRelScore})`); + const usedImages = new Set(); + const usedAnswerChunks = new Set(); + const filteredMatches = []; + + for (const match of allMatches) { + if (match.relevanceScore < minRelScore) continue; + + if (!usedImages.has(match.imageIndex) && !usedAnswerChunks.has(match.answerChunkIndex)) { + filteredMatches.push(match); + usedImages.add(match.imageIndex); + usedAnswerChunks.add(match.answerChunkIndex); + + if (filteredMatches.length >= maxRef) break; + } + } + + console.log(`[buildImageReferences] Selected ${filteredMatches.length}/${allMatches.length} references after filtering`); + + const references: ImageReference[] = filteredMatches.map((match) => { + const source = imageToSourceMap[match.imageIndex]; + return { + url: source.url, + relevanceScore: match.relevanceScore, + answerChunk: match.answerChunk, + answerChunkPosition: match.answerChunkPosition + }; + }); + + return references; + + } catch (error) { + console.error('Embedding failed', error); + return []; + } } \ No newline at end of file diff --git a/src/tools/embeddings.ts b/src/tools/embeddings.ts index 249bd0c..ee142ae 100644 --- a/src/tools/embeddings.ts +++ b/src/tools/embeddings.ts @@ -8,13 +8,14 @@ const MAX_RETRIES = 3; // Maximum number of retries for missing embeddings // Modified to support different embedding tasks and dimensions export async function getEmbeddings( - texts: string[], + texts: string[] | Record[], tokenTracker?: any, options: { task?: "text-matching" | "retrieval.passage" | "retrieval.query", dimensions?: number, late_chunking?: boolean, - embedding_type?: string + embedding_type?: string, + model?: string, } = {} ): Promise<{ embeddings: number[][], tokens: number }> { console.log(`[embeddings] Getting embeddings for ${texts.length} texts`); @@ -66,12 +67,13 @@ export async function getEmbeddings( // Helper function to get embeddings for a batch with retry logic for missing indices async function getBatchEmbeddingsWithRetry( - batchTexts: string[], + batchTexts: string[] | Record[], options: { task?: "text-matching" | "retrieval.passage" | "retrieval.query", dimensions?: number, late_chunking?: boolean, - embedding_type?: string + embedding_type?: string, + model?: string, }, currentBatch: number, batchCount: number @@ -89,12 +91,15 @@ async function getBatchEmbeddingsWithRetry( while (textsToProcess.length > 0 && retryCount < MAX_RETRIES) { const request: JinaEmbeddingRequest = { - model: "jina-embeddings-v3", - task: options.task || "text-matching", - input: textsToProcess, - truncate: true, + model: options.model || "jina-embeddings-v3", + input: textsToProcess as any, }; + if (request.model === "jina-embeddings-v3") { + request.task = options.task || "text-matching"; + request.truncate = true; + } + // Add optional parameters if provided if (options.dimensions) request.dimensions = options.dimensions; if (options.late_chunking) request.late_chunking = options.late_chunking; @@ -110,7 +115,7 @@ async function getBatchEmbeddingsWithRetry( "Authorization": `Bearer ${JINA_API_KEY}` } } - ); + ); if (!response.data.data) { console.error('No data returned from Jina API'); @@ -118,7 +123,7 @@ async function getBatchEmbeddingsWithRetry( // On last retry, create placeholder embeddings const dimensionSize = options.dimensions || 1024; const placeholderEmbeddings = textsToProcess.map(text => { - console.error(`Failed to get embedding after all retries: [${text.substring(0, 50)}...]`); + console.error(`Failed to get embedding after all retries: [${truncateInputString(text)}...]`); return new Array(dimensionSize).fill(0); }); @@ -140,7 +145,7 @@ async function getBatchEmbeddingsWithRetry( // Process successful embeddings const successfulEmbeddings: number[][] = []; - const remainingTexts: string[] = []; + const remainingTexts: (string | Record)[] = []; const newIndexMap = new Map(); for (let idx = 0; idx < textsToProcess.length; idx++) { @@ -160,7 +165,7 @@ async function getBatchEmbeddingsWithRetry( const newIndex = remainingTexts.length; newIndexMap.set(newIndex, indexMap.get(idx)!); remainingTexts.push(textsToProcess[idx]); - console.log(`Missing embedding for index ${idx}, will retry: [${textsToProcess[idx].substring(0, 50)}...]`); + console.log(`Missing embedding for index ${idx}, will retry: [${truncateInputString(textsToProcess[idx])}...]`); } } @@ -190,7 +195,7 @@ async function getBatchEmbeddingsWithRetry( const dimensionSize = options.dimensions || 1024; for (let idx = 0; idx < textsToProcess.length; idx++) { const originalIndex = indexMap.get(idx)!; - console.error(`Failed to get embedding after all retries for index ${originalIndex}: [${textsToProcess[idx].substring(0, 50)}...]`); + console.error(`Failed to get embedding after all retries for index ${originalIndex}: [${truncateInputString(textsToProcess[idx])}...]`); while (batchEmbeddings.length <= originalIndex) { batchEmbeddings.push([]); @@ -228,3 +233,11 @@ async function getBatchEmbeddingsWithRetry( return { batchEmbeddings, batchTokens }; } + +function truncateInputString(input: string | Record): string { + if (typeof input === 'string') { + return input.slice(0, 50); + } else { + return Object.values(input)[0].slice(0, 50); + } +} \ No newline at end of file diff --git a/src/tools/jina-dedup.ts b/src/tools/jina-dedup.ts index 27bb825..26f4e73 100644 --- a/src/tools/jina-dedup.ts +++ b/src/tools/jina-dedup.ts @@ -78,4 +78,4 @@ export async function dedupQueries( unique_queries: newQueries, }; } -} +} \ No newline at end of file diff --git a/src/tools/read.ts b/src/tools/read.ts index b1c5923..fe73791 100644 --- a/src/tools/read.ts +++ b/src/tools/read.ts @@ -6,7 +6,8 @@ import axiosClient from "../utils/axios-client"; export async function readUrl( url: string, withAllLinks?: boolean, - tracker?: TokenTracker + tracker?: TokenTracker, + withAllImages?: boolean ): Promise<{ response: ReadResponse }> { if (!url.trim()) { throw new Error('URL cannot be empty'); @@ -20,7 +21,6 @@ export async function readUrl( 'Accept': 'application/json', 'Authorization': `Bearer ${JINA_API_KEY}`, 'Content-Type': 'application/json', - 'X-Retain-Images': 'none', 'X-Md-Link-Style': 'discarded', }; @@ -28,6 +28,12 @@ export async function readUrl( headers['X-With-Links-Summary'] = 'all'; } + if (withAllImages) { + headers['X-With-Images-Summary'] = 'true' + } else { + headers['X-Retain-Images'] = 'none' + } + try { // Use axios which handles encoding properly const { data } = await axiosClient.post( diff --git a/src/types.ts b/src/types.ts index 83c0264..78dc2f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,6 +27,14 @@ export type Reference = { answerChunkPosition?: number[]; } +export type ImageReference = { + url: string; + dateTime?: string; + relevanceScore?: number; + answerChunk?: string; + answerChunkPosition?: number[]; +} + export type AnswerAction = BaseAction & { action: "answer"; answer: string; @@ -53,6 +61,7 @@ export type ReflectAction = BaseAction & { export type VisitAction = BaseAction & { action: "visit"; URLTargets: number[] | string[]; + image?: ImageObject; }; export type CodingAction = BaseAction & { @@ -155,6 +164,7 @@ export interface ReadResponse { content: string; usage: { tokens: number; }; links: Array<[string, string]>; // [anchor, url] + images: Record; // { image: url } }; name?: string; message?: string; @@ -259,6 +269,8 @@ export interface ChatCompletionRequest { max_annotations?: number; min_annotation_relevance?: number; + + with_images?: boolean; language_code?: string; search_language_code?: string; search_provider?: string; @@ -294,6 +306,8 @@ export interface ChatCompletionResponse { visitedURLs?: string[]; readURLs?: string[]; numURLs?: number; + allImages?: string[]; + relatedImages?: string[]; } export interface ChatCompletionChunk { @@ -318,6 +332,8 @@ export interface ChatCompletionChunk { visitedURLs?: string[]; readURLs?: string[]; numURLs?: number; + allImages?: string[]; + relatedImages?: string[]; } // Tracker Types @@ -336,11 +352,11 @@ export interface TrackerContext { // Interface definitions for Jina API export interface JinaEmbeddingRequest { model: string; - task: string; + task?: string; late_chunking?: boolean; dimensions?: number; embedding_type?: string; - input: string[]; + input: string[] | Record[]; truncate?: boolean; } @@ -356,4 +372,10 @@ export interface JinaEmbeddingResponse { index: number; embedding: number[]; }>; +} + +export type ImageObject = { + url: string; + alt?: string; + embedding: number[][]; } \ No newline at end of file diff --git a/src/utils/image-tools.ts b/src/utils/image-tools.ts new file mode 100644 index 0000000..bd2b8c7 --- /dev/null +++ b/src/utils/image-tools.ts @@ -0,0 +1,233 @@ +import canvas from '@napi-rs/canvas'; +import { getEmbeddings } from '../tools/embeddings'; +import { TokenTracker } from './token-tracker'; +import { ImageObject } from '../types'; +import { cosineSimilarity } from '../tools/cosine'; +export type { Canvas, Image } from '@napi-rs/canvas'; +import { Storage } from '@google-cloud/storage'; +import { randomUUID } from 'crypto'; + +export const downloadFile = async (uri: string) => { + const resp = await fetch(uri); + if (!(resp.ok && resp.body)) { + throw new Error(`Unexpected response ${resp.statusText}`); + } + const contentLength = parseInt(resp.headers.get('content-length') || '0'); + if (contentLength > 1024 * 1024 * 100) { + throw new Error('File too large'); + } + const buff = await resp.arrayBuffer(); + + return { buff, contentType: resp.headers.get('content-type') }; +}; + +const _loadImage = async (input: string | Buffer) => { + let buff; + let contentType; + + if (typeof input === 'string') { + if (input.startsWith('data:')) { + const firstComma = input.indexOf(','); + const header = input.slice(0, firstComma); + const data = input.slice(firstComma + 1); + const encoding = header.split(';')[1]; + contentType = header.split(';')[0].split(':')[1]; + if (encoding?.startsWith('base64')) { + buff = Buffer.from(data, 'base64'); + } else { + buff = Buffer.from(decodeURIComponent(data), 'utf-8'); + } + } + if (input.startsWith('http')) { + if (input.endsWith('.svg')) { + throw new Error('Unsupported image type'); + } + const r = await downloadFile(input); + buff = Buffer.from(r.buff); + contentType = r.contentType; + } + } + + if (!buff) { + throw new Error('Invalid input'); + } + + const img = await canvas.loadImage(buff); + Reflect.set(img, 'contentType', contentType); + + return { + img, + buff, + contentType, + }; +} + +export const loadImage = async (uri: string | Buffer) => { + try { + const theImage = await _loadImage(uri); + + return theImage; + } catch (err: any) { + if (err?.message?.includes('Unsupported image type') || err?.message?.includes('unsupported')) { + throw new Error(`Unknown image format for ${uri.slice(0, 128)}`); + } + throw err; + } +} + +export const fitImageToSquareBox = (image: canvas.Image | canvas.Canvas, size: number = 1024) => { + if (image.width <= size && image.height <= size) { + const canvasInstance = canvas.createCanvas(image.width, image.height); + const ctx = canvasInstance.getContext('2d'); + ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvasInstance.width, canvasInstance.height); + + return canvasInstance; + } + + const aspectRatio = image.width / image.height; + + const resizedWidth = Math.round(aspectRatio > 1 ? size : size * aspectRatio); + const resizedHeight = Math.round(aspectRatio > 1 ? size / aspectRatio : size); + + const canvasInstance = canvas.createCanvas(resizedWidth, resizedHeight); + const ctx = canvasInstance.getContext('2d'); + ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, resizedWidth, resizedHeight); + + return canvasInstance; +} + + +export const canvasToDataUrl = (canvas: canvas.Canvas, mimeType?: 'image/png' | 'image/jpeg') => { + return canvas.toDataURLAsync((mimeType || 'image/png') as 'image/png'); +} + +export const canvasToBuffer = (canvas: canvas.Canvas, mimeType?: 'image/png' | 'image/jpeg') => { + return canvas.toBuffer((mimeType || 'image/png') as 'image/png'); +} + +export const processImage = async (url: string, tracker: TokenTracker): Promise => { + try { + const { img, buff, contentType } = await loadImage(url); + if (!img) { + return; + } + + // Check if the image is smaller than 256x256 + if (img.width < 256 || img.height < 256) { + return; + } + + const newUrl = await saveImageToFirebase(buff, contentType); + const canvas = fitImageToSquareBox(img, 512); + const base64Data = (await canvasToDataUrl(canvas)).split(',')[1]; + + const {embeddings} = await getEmbeddings([{ image: base64Data }], tracker, { + dimensions: 1024, + model: 'jina-clip-v2', + }); + + return { + url: newUrl ?? url, + embedding: embeddings, + }; + + } catch (error) { + return; + } +} + +export const dedupImagesWithEmbeddings = ( + newImages: ImageObject[], // New images with embeddings + existingImages: ImageObject[], // Existing images with embeddings + similarityThreshold: number = 0.86, // Default similarity threshold +): ImageObject[] =>{ + try { + // Quick return for single new image with no existing images + if (newImages.length === 1 && existingImages.length === 0) { + return newImages; + } + + const uniqueImages: ImageObject[] = []; + const usedIndices = new Set(); + + // Compare each new image against existing images and already accepted images + for (let i = 0; i < newImages.length; i++) { + let isUnique = true; + + // Check against existing images + for (let j = 0; j < existingImages.length; j++) { + const similarity = cosineSimilarity( + newImages[i].embedding[0], // Use the first embedding for comparison + existingImages[j].embedding[0] + ); + if (similarity >= similarityThreshold) { + isUnique = false; + break; + } + } + + // Check against already accepted images + if (isUnique) { + for (const usedIndex of usedIndices) { + const similarity = cosineSimilarity( + newImages[i].embedding[0], // Use the first embedding for comparison + newImages[usedIndex].embedding[0] + ); + if (similarity >= similarityThreshold) { + isUnique = false; + break; + } + } + } + + // Add to unique images if passed all checks + if (isUnique) { + uniqueImages.push(newImages[i]); + usedIndices.add(i); + } + } + + return uniqueImages; + } catch (error) { + console.error('Error in image deduplication analysis:', error); + + // Return all new images if there is an error + return newImages; + } +} + +export const saveImageToFirebase = async ( + buffer: Buffer, + mimeType?: string | null, +): Promise => { + if (!process.env.GCLOUD_PROJECT) { + console.error('GCLOUD_PROJECT environment variable is not set'); + return; + } + const firebaseDefaultBucket = new Storage().bucket(`${process.env.GCLOUD_PROJECT}.appspot.com`); + + try { + let extension = 'png'; + const finalMimeType = mimeType || 'image/png'; + + if (!finalMimeType.startsWith('image/')) { + return; + } else { + extension = finalMimeType?.split('/')[1] || 'png'; + } + + const fileName = `readImages/${randomUUID()}.${extension}`; + + const file = firebaseDefaultBucket.file(fileName); + + await file.save(buffer, { + contentType: finalMimeType, + public: true, + }); + + return file.publicUrl(); + } catch (error) { + console.error('Error saving image to Firebase Storage:', error); + return; + } +}; \ No newline at end of file diff --git a/src/utils/url-tools.ts b/src/utils/url-tools.ts index a8b8bc4..a5cc220 100644 --- a/src/utils/url-tools.ts +++ b/src/utils/url-tools.ts @@ -1,12 +1,13 @@ -import { BoostedSearchSnippet, KnowledgeItem, SearchSnippet, TrackerContext, VisitAction, WebContent } from "../types"; -import { getI18nText, smartMergeStrings } from "./text-tools"; -import { rerankDocuments } from "../tools/jina-rerank"; -import { readUrl } from "../tools/read"; -import { Schemas } from "./schemas"; -import { cherryPick } from "../tools/jina-latechunk"; -import { formatDateBasedOnType } from "./date-tools"; -import { classifyText } from "../tools/jina-classify-spam"; -import { segmentText } from "../tools/segment"; +import {BoostedSearchSnippet, ImageObject, KnowledgeItem, SearchSnippet, TrackerContext, VisitAction, WebContent} from "../types"; +import {getI18nText, smartMergeStrings} from "./text-tools"; +import {rerankDocuments} from "../tools/jina-rerank"; +import {readUrl} from "../tools/read"; +import {Schemas} from "./schemas"; +import {cherryPick} from "../tools/jina-latechunk"; +import {formatDateBasedOnType} from "./date-tools"; +import {classifyText} from "../tools/jina-classify-spam"; +import { processImage } from "./image-tools"; +import {segmentText} from "../tools/segment"; import axiosClient from "./axios-client"; export function normalizeUrl(urlString: string, debug = false, options = { @@ -460,9 +461,11 @@ export async function processURLs( allURLs: Record, visitedURLs: string[], badURLs: string[], + imageObjects: ImageObject[], schemaGen: Schemas, question: string, - webContents: Record + webContents: Record, + withImages: boolean = false, ): Promise<{ urlResults: any[], success: boolean }> { // Skip if no URLs to process if (urls.length === 0) { @@ -491,8 +494,8 @@ export async function processURLs( // Store normalized URL for consistent reference url = normalizedUrl; - const { response } = await readUrl(url, true, context.tokenTracker); - const { data } = response; + const {response} = await readUrl(url, true, context.tokenTracker, withImages); + const {data} = response; const guessedTime = await getLastModified(url); if (guessedTime) { console.log('Guessed time for', url, guessedTime); @@ -554,7 +557,18 @@ export async function processURLs( } }); - return { url, result: response }; + // Process images + if (withImages && data.images) { + const imageEntries = Object.entries(data.images || {}); + imageEntries.forEach(async ([alt, url]) => { + const imageObject = await processImage(url, context.tokenTracker); + if (imageObject && !imageObjects.find(i => i.url === imageObject.url)) { + imageObjects.push(imageObject); + } + }); + } + + return {url, result: response}; } catch (error: any) { console.error('Error reading URL:', url, error); badURLs.push(url);