Compare commits

..

2 Commits

Author SHA1 Message Date
benzntech
50082ce21a feat(providers): add Cookie Editor fast-path to web session credential guide
The 'How to get the session credential' instructions in the provider
add-connection modal only described the manual DevTools flow. Add a
fast-path step using the Cookie Editor extension (export as Cookie
header, select all numbered session-token chunks) and demote the
DevTools walkthrough to the manual alternative.

New i18n keys (webSessionGuideStep2Fast, webSessionGuideStep3Manual)
ship in en.json; other locales fall back to English until translated.
2026-08-09 02:22:57 -03:00
diegosouzapw
b1e7e50cc5 fix(deps): bump transitive deps for 6 Dependabot + remaining audit vulns on main
Same overrides as #9464 (ip-address, hono, fast-uri, socket.io-parser, undici)
applied directly to main. Also covers brace-expansion (scoped), js-yaml v4 copies,
and mermaid.

npm audit: 6→0 vulnerabilities.
Closes Dependabot #161-#166.
2026-08-09 02:22:56 -03:00
7 changed files with 215 additions and 525 deletions

399
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.8.50",
"version": "3.8.49",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.8.50",
"version": "3.8.49",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
@@ -31,7 +31,7 @@
"clsx": "^2.1.1",
"commander": "^15.0.0",
"csv-stringify": "^6.7.0",
"dompurify": "^3.4.13",
"dompurify": "^3.4.12",
"express": "^5.2.1",
"fetch-socks": "^1.3.3",
"fflate": "^0.8.3",
@@ -79,7 +79,7 @@
"sqlite-vec": "^0.1.9",
"tailwind-merge": "^3.6.0",
"tsx": "^4.23.0",
"undici": "^8.10.0",
"undici": "^8.3.0",
"update-notifier": "^7.3.1",
"uuid": "^14.0.0",
"ws": "^8.18.0",
@@ -132,7 +132,6 @@
"lint-staged": "^17.0.8",
"lockfile-lint": "^5.0.0",
"node-loader": "^2.1.0",
"opencode-ai": "1.18.8",
"playwright-ctrf-json-reporter": "^0.0.29",
"prettier": "^3.8.3",
"promptfoo": "^0.121.18",
@@ -152,7 +151,7 @@
"@atjsh/llmlingua-2": "2.0.3",
"@huggingface/transformers": "3.5.2",
"@tensorflow/tfjs": "4.22.0",
"better-sqlite3": "^13.0.2",
"better-sqlite3": "^13.0.1",
"js-tiktoken": "^1.0.20",
"keytar": "^7.9.0",
"tls-client-node": "^0.2.0",
@@ -3693,6 +3692,9 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3709,6 +3711,9 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3725,6 +3730,9 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3741,6 +3749,9 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3757,6 +3768,9 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3773,6 +3787,9 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3789,6 +3806,9 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3805,6 +3825,9 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3821,6 +3844,9 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3843,6 +3869,9 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3865,6 +3894,9 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3887,6 +3919,9 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3909,6 +3944,9 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3931,6 +3969,9 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3953,6 +3994,9 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3975,6 +4019,9 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -5346,6 +5393,9 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5362,6 +5412,9 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5378,6 +5431,9 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5394,6 +5450,9 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10611,6 +10670,9 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10628,6 +10690,9 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10645,6 +10710,9 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10662,6 +10730,9 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -12708,6 +12779,9 @@
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12721,6 +12795,9 @@
"arm"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -12734,6 +12811,9 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12747,6 +12827,9 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -12760,6 +12843,9 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12773,6 +12859,9 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -13598,14 +13687,11 @@
}
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
@@ -13670,9 +13756,10 @@
}
},
"node_modules/better-sqlite3": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.2.tgz",
"integrity": "sha512-jW6oufeDhXZaiX9Lw5A+oerVClx4iFrI6uDj1zu7SqUAjak9vbJvA0NEcKLNxHiQHb6kYCoFzzXYV0YOauhV3g==",
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.1.tgz",
"integrity": "sha512-LYpmOXdkpQYf4wmlxkdzW01XGlOXNIbjLg45yNkh0FQ4814VbK9PdOFmhZpYbej+EZtR/i3FDdhEG98HqZdgnA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -13954,16 +14041,14 @@
}
},
"node_modules/brace-expansion": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
@@ -16979,9 +17064,9 @@
"license": "MIT"
},
"node_modules/dompurify": {
"version": "3.4.13",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
"version": "3.4.12",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz",
"integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -24392,25 +24477,6 @@
"node": ">= 14"
}
},
"node_modules/libxmljs2/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/libxmljs2/node_modules/brace-expansion": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/libxmljs2/node_modules/cacache": {
"version": "19.0.1",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz",
@@ -26883,24 +26949,6 @@
"node": "*"
}
},
"node_modules/minimatch/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/minimatch/node_modules/brace-expansion": {
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
@@ -27528,9 +27576,9 @@
"optional": true
},
"node_modules/nanoid": {
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"funding": [
{
"type": "github",
@@ -28739,205 +28787,6 @@
}
}
},
"node_modules/opencode-ai": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-ai/-/opencode-ai-1.18.8.tgz",
"integrity": "sha512-eZvYK0rIc/NUDQ+s3LsO9gyUU3MswsbNOLZz06iPwVhbg/2jF6bkTaroBgiIdFWKwUn5sj+kSMc4TBYxFkMrNQ==",
"cpu": [
"arm64",
"x64"
],
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"os": [
"darwin",
"linux",
"win32"
],
"bin": {
"opencode": "bin/opencode.exe"
},
"optionalDependencies": {
"opencode-darwin-arm64": "1.18.8",
"opencode-darwin-x64": "1.18.8",
"opencode-darwin-x64-baseline": "1.18.8",
"opencode-linux-arm64": "1.18.8",
"opencode-linux-arm64-musl": "1.18.8",
"opencode-linux-x64": "1.18.8",
"opencode-linux-x64-baseline": "1.18.8",
"opencode-linux-x64-baseline-musl": "1.18.8",
"opencode-linux-x64-musl": "1.18.8",
"opencode-windows-arm64": "1.18.8",
"opencode-windows-x64": "1.18.8",
"opencode-windows-x64-baseline": "1.18.8"
}
},
"node_modules/opencode-darwin-arm64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-darwin-arm64/-/opencode-darwin-arm64-1.18.8.tgz",
"integrity": "sha512-ZZCIEgTvHxOHk52Aeqhq59t/R0aqs29bPIgu45XE4rkgjmn/XCkTWalCPtyzJHipdcEbq/g0lqsE1OlJV0oNbA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/opencode-darwin-x64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-darwin-x64/-/opencode-darwin-x64-1.18.8.tgz",
"integrity": "sha512-2EXRMJbRKnFPWI9oDU9tb7jDGmKiPmfjCLtwJMe3EF57h5wfcdEH9sP25bR3Og5NbE2M+PtMcJm0jMeHn2XoLQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/opencode-darwin-x64-baseline": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-darwin-x64-baseline/-/opencode-darwin-x64-baseline-1.18.8.tgz",
"integrity": "sha512-eLXa2tK9LRuZ5e20QG2k4dmWAA5xnLgJ1afRTSD0/ybE6CAeK02i8vFCnFFDaxuBo+gnq+yqO8AkqvN1m64V/Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/opencode-linux-arm64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-arm64/-/opencode-linux-arm64-1.18.8.tgz",
"integrity": "sha512-7kj3c9JEdryHgK+o8zE/N9KzTOdbiDn6KpY8dl+hM9n5Cnmxezx4IAlgJeC9QxpIx8Omop6CYuZ+17KfrKdKLw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-arm64-musl": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-arm64-musl/-/opencode-linux-arm64-musl-1.18.8.tgz",
"integrity": "sha512-tww5TF/LIOv/GoTNyzGYgqDRhbJrhoMu8R+p5yD/SpnXPg3rcfYREw2wRy9yikyPU9sAQksuIIteTsyGerPjlA==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-x64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-x64/-/opencode-linux-x64-1.18.8.tgz",
"integrity": "sha512-Sm4fbQ9BdLI6hgN6FYYX8Nql+Sqe/2EKHJu3iWg0UYs93AXN4ROi0rvOmRbMk+ycYgOchb0hL6Ti2opxLx17sg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-x64-baseline": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-x64-baseline/-/opencode-linux-x64-baseline-1.18.8.tgz",
"integrity": "sha512-egeEF4tk1rK9flIQjjeSVB9cR/X3zUti0pNAHW6ROJkNkj72z2C2FmjK1hZbfjtteCueMXPLptS23JROHGWL1w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-x64-baseline-musl": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-x64-baseline-musl/-/opencode-linux-x64-baseline-musl-1.18.8.tgz",
"integrity": "sha512-S+438BXs48gLeXX/ya4TSNytDy9mliU3sOAf6j9rfFjzGiF/S08LedemSAnHkr0riBtamik1aRPSmTjhQ0dOBg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-x64-musl": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-x64-musl/-/opencode-linux-x64-musl-1.18.8.tgz",
"integrity": "sha512-c+E4Zsp0DYVcuqcDtgxw/4YcFLrVYWdGBR8x4CzpW48ga3RshaH+BlmUiy+GY0yr1x6UR+e2V3w4uzvzm/L9UQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-windows-arm64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-windows-arm64/-/opencode-windows-arm64-1.18.8.tgz",
"integrity": "sha512-7NjdtEIiX28kmsKD9jHbFG4bbwBB5T4dAe2UwdnOqCBb2cl+ETV5eO6kbdC/xWxrgOghgZM1Wtw791T5pQPyag==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/opencode-windows-x64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-windows-x64/-/opencode-windows-x64-1.18.8.tgz",
"integrity": "sha512-G+NEgEMvu/dEYshH5IaqHVTmsHVuGdORBvVmgphFiknT7q/NXPuoZCMtMIdfNlEFbu54BlzRDdJCR3Mqe98gUw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/opencode-windows-x64-baseline": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-windows-x64-baseline/-/opencode-windows-x64-baseline-1.18.8.tgz",
"integrity": "sha512-IGbjFyWoSN9rdGUJX7TWkQ1Yl673Q3dDna54b5NtqeRcZ839p+Z47zzM5m883HKAxrdKCC6Z22HYuDcXLV0laA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
@@ -32174,13 +32023,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/rimraf/node_modules/brace-expansion": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
@@ -35157,9 +34999,9 @@
"license": "MIT"
},
"node_modules/undici": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz",
"integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==",
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
"integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
@@ -36950,7 +36792,12 @@
},
"open-sse": {
"name": "@omniroute/open-sse",
"version": "3.8.50"
"version": "3.8.49",
"dependencies": {
"@toon-format/toon": "^4.1.0",
"safe-regex": "^2.1.1",
"smol-toml": "1.7.1"
}
}
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "omniroute",
"version": "3.8.50",
"description": "Unified AI router with 290 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"version": "3.8.49",
"description": "Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"type": "module",
"bin": {
"omniroute": "bin/omniroute.mjs",
@@ -23,7 +23,6 @@
".env.example",
"scripts/build/postinstall.mjs",
"scripts/build/fixTlsClientNodeBinary.mjs",
"scripts/build/fixPlaywrightAndroid.mjs",
"bin/cli/runtime/",
"scripts/postinstall.mjs",
"scripts/build/postinstallSupport.mjs",
@@ -34,15 +33,11 @@
"scripts/dev/tls-options.mjs",
"scripts/check/check-supported-node-runtime.ts",
"scripts/dev/sync-env.mjs",
"scripts/build/assembleStandalone.mjs",
"scripts/build/backendOnlyPages.mjs",
"scripts/build/build-next-isolated.mjs",
"scripts/build/build-tproxy-native.mjs",
"scripts/build/native-binary-compat.mjs",
"scripts/build/build-next-isolated.mjs",
"scripts/build/runtime-env.mjs",
"README.md",
"LICENSE",
"!**/node_modules/**",
"!**/__tests__/**",
"!**/*.test.ts",
"!**/*.test.tsx",
@@ -115,8 +110,6 @@
"test:unit:ci": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:unit:ci:shard": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD \"tests/unit/serial/**/*.test.ts\"",
"test:unit:fast": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:scoped": "bash scripts/quality/test-scoped.sh",
"test:scoped:staged": "bash scripts/quality/test-scoped.sh --staged",
"test:unit:shard": "concurrently --kill-others-on-fail -n s1,s2 \"npm:test:unit:shard:1\" \"npm:test:unit:shard:2\"",
"test:unit:shard:1": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=1/2 \"tests/unit/serial/**/*.test.ts\"",
"test:unit:shard:2": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=2/2 \"tests/unit/serial/**/*.test.ts\"",
@@ -150,7 +143,6 @@
"check:node-runtime": "node --import tsx scripts/check/check-supported-node-runtime.ts",
"check:pack-artifact": "node --import tsx scripts/build/validate-pack-artifact.ts",
"check:pack-boot": "node scripts/check/check-pack-boot.mjs",
"check:install-upgrade": "node scripts/check/check-install-upgrade.mjs",
"check:pack-policy": "node --import tsx scripts/build/validate-pack-artifact.ts --policy-only",
"check:cli-i18n": "node scripts/check/check-cli-i18n.mjs",
"check:openapi-coverage": "node scripts/check/check-openapi-coverage.mjs",
@@ -169,7 +161,6 @@
"check:test-masking": "node scripts/check/check-test-masking.mjs",
"check:test-runner-api": "node scripts/check/check-test-runner-api.mjs",
"check:changelog-integrity": "node scripts/check/check-changelog-integrity.mjs",
"sweep:stale-fragments": "node scripts/release/sweep-stale-fragments.mjs",
"changelog:aggregate": "node scripts/release/aggregate-changelog.mjs",
"check:agent-skills-sync": "node --import tsx/esm scripts/skills/generate-agent-skills.mjs",
"check:build-scope": "node scripts/check/check-build-scope.mjs",
@@ -193,7 +184,6 @@
"check:bundle-size": "node scripts/check/check-bundle-size.mjs",
"check:circular-deps": "node scripts/check/check-circular-deps.mjs",
"check:mutation-ratchet": "node scripts/check/check-mutation-ratchet.mjs",
"check:rtl-ratchet": "node scripts/check/check-rtl-ratchet.mjs",
"check:licenses": "node scripts/check/check-licenses.mjs",
"check:pr-evidence": "node scripts/check/check-pr-evidence.mjs",
"check:vuln-ratchet": "node scripts/check/check-vuln-ratchet.mjs",
@@ -210,11 +200,9 @@
"typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json",
"typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json",
"check:dashboard-typecheck": "node scripts/check/check-dashboard-typecheck.mjs",
"check:open-sse-typecheck": "node scripts/check/check-open-sse-typecheck.mjs",
"backfill-aggregation": "node --import tsx src/scripts/backfillAggregation.ts",
"env:sync": "node scripts/dev/sync-env.mjs",
"test:integration": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 tests/integration/*.test.ts \"tests/integration/combo-matrix/*.test.ts\"",
"test:integration:ci": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD tests/integration/*.test.ts \"tests/integration/combo-matrix/*.test.ts\"",
"test:combo:matrix": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/integration/combo-matrix/*.test.ts\"",
"test:combo:live": "cross-env RUN_COMBO_LIVE=1 DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/integration/combo-live/*.live.test.ts\"",
"test:combo:live:vps": "node scripts/test/combo-live-vps.mjs",
@@ -244,7 +232,6 @@
"prepare": "husky",
"system-info": "node scripts/dev/system-info.mjs",
"build:cli-api": "node --import tsx/esm scripts/cli/generate-api-commands.mjs",
"postbuild": "node scripts/build/colocate-standalone.mjs",
"release:contributors": "node scripts/release/gen-contributors.mjs",
"release:uncovered": "node scripts/release/list-uncovered-commits.mjs",
"test:coverage:runner": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
@@ -269,7 +256,7 @@
"clsx": "^2.1.1",
"commander": "^15.0.0",
"csv-stringify": "^6.7.0",
"dompurify": "^3.4.13",
"dompurify": "^3.4.12",
"express": "^5.2.1",
"fetch-socks": "^1.3.3",
"fflate": "^0.8.3",
@@ -317,7 +304,7 @@
"sqlite-vec": "^0.1.9",
"tailwind-merge": "^3.6.0",
"tsx": "^4.23.0",
"undici": "^8.10.0",
"undici": "^8.3.0",
"update-notifier": "^7.3.1",
"uuid": "^14.0.0",
"ws": "^8.18.0",
@@ -330,7 +317,7 @@
"@atjsh/llmlingua-2": "2.0.3",
"@huggingface/transformers": "3.5.2",
"@tensorflow/tfjs": "4.22.0",
"better-sqlite3": "^13.0.2",
"better-sqlite3": "^13.0.1",
"js-tiktoken": "^1.0.20",
"keytar": "^7.9.0",
"tls-client-node": "^0.2.0",
@@ -376,7 +363,6 @@
"lint-staged": "^17.0.8",
"lockfile-lint": "^5.0.0",
"node-loader": "^2.1.0",
"opencode-ai": "1.18.8",
"playwright-ctrf-json-reporter": "^0.0.29",
"prettier": "^3.8.3",
"promptfoo": "^0.121.18",
@@ -408,16 +394,8 @@
"sharp"
]
},
"allowScripts": {
"better-sqlite3": true,
"esbuild": true,
"@swc/core": true,
"@parcel/watcher": true,
"keytar": true,
"protobufjs": true,
"unrs-resolver": true
},
"overrides": {
"dompurify": "^3.4.12",
"fast-xml-parser": "^5.10.1",
"sharp": "^0.35.0",
"postcss": "^8.5.18",
@@ -447,14 +425,27 @@
"adm-zip": "^0.6.0",
"promptfoo": {
"js-yaml": "^5.2.2",
"@apidevtools/json-schema-ref-parser": {
"js-yaml": "^4.3.1"
},
"undici": "^7.29.0"
},
"socket.io-parser": "^4.2.7",
"tar": "^7.5.21",
"nanoid": "^3.3.17",
"brace-expansion": "^5.0.9",
"minimatch": {
"brace-expansion": "^1.1.18"
},
"libxmljs2": {
"minimatch": {
"brace-expansion": "^2.1.4"
}
},
"rimraf": {
"minimatch": {
"brace-expansion": "^2.1.4"
}
},
"@apidevtools/json-schema-ref-parser": {
"js-yaml": "^4.3.1"
},
"@eslint/eslintrc": {
"js-yaml": "^4.3.1"
},
@@ -463,12 +454,6 @@
},
"xmlbuilder2": {
"js-yaml": "^4.3.1"
},
"monaco-editor": {
"dompurify": "^3.4.13"
},
"@apidevtools/json-schema-ref-parser": {
"js-yaml": "^4.3.1"
}
}
}

View File

@@ -111,16 +111,16 @@ export default function WebSessionCredentialGuide({
<li>
{providerText(
t,
"webSessionGuideStep2",
"Open the browser developer tools and inspect a request made by the web app."
"webSessionGuideStep2Fast",
"Fast path: install the Cookie Editor extension (chromewebstore.google.com → Cookie Editor), open it on the {provider} tab, find {credential} (select all numbered chunks if split), and click Export → Copy with the export format set to “Cookie header”.",
{ provider: providerName, credential: requirement.credentialName }
)}
</li>
<li>
{providerText(
t,
"webSessionGuideStep3",
"Copy the required credential from the provider's own domain. For cookies, copy only the Cookie header value and omit Cookie:.",
{ credential: requirement.credentialName }
"webSessionGuideStep3Manual",
"Manual path: open the browser developer tools (F12 → Network), refresh the page, open an authenticated request, and copy the Cookie header value from Request Headers — omit the Cookie: prefix."
)}
</li>
<li>

View File

@@ -5999,7 +5999,9 @@
"webTokenRequiredCredential": "Required token: {credential}",
"webSessionGuideStep1": "Sign in to {provider} in your browser.",
"webSessionGuideStep2": "Open the browser developer tools and inspect a request made by the web app.",
"webSessionGuideStep2Fast": "Fast path: install the Cookie Editor extension (chromewebstore.google.com → Cookie Editor), open it on the {provider} tab, find {credential} (select all numbered chunks if split), and click Export → Copy with the export format set to “Cookie header”.",
"webSessionGuideStep3": "Copy the required credential from the provider's own domain. For cookies, copy only the Cookie header value and omit Cookie:.",
"webSessionGuideStep3Manual": "Manual path: open the browser developer tools (F12 → Network), refresh the page, open an authenticated request, and copy the Cookie header value from Request Headers — omit the Cookie: prefix.",
"webSessionGuideStep4": "Paste it here and check the connection. If it stops working, sign in again and replace it with a fresh value.",
"webSessionSecurityHint": "Treat this like a password: it may access your signed-in web account until it expires or is revoked.",
"webNoAuthGuideTitle": "No credential required",

View File

@@ -83,6 +83,7 @@ import { getResource404Bypass } from "./requestResourceHealth";
import * as log from "../utils/logger";
import { fisherYatesShuffle, getNextFromDeckSync } from "@/shared/utils/shuffleDeck";
import { readHeaderValue, type AuthRequestHeaders } from "./headerReader.ts";
type JsonRecord = Record<string, unknown>;
interface RecoverableConnectionState {
connectionId: string;
@@ -93,6 +94,7 @@ interface RecoverableConnectionState {
lastErrorType?: string | null;
lastErrorSource?: string | null;
}
interface CredentialSelectionOptions {
allowSuppressedConnections?: boolean;
allowRateLimitedConnections?: boolean;
@@ -102,12 +104,14 @@ interface CredentialSelectionOptions {
sessionKey?: string | null;
sessionAffinityTtlMs?: number | null;
}
interface CooldownInspectionState {
connection: ProviderConnectionView;
connectionCooldownMs: number | null;
codexScopeCooldownMs: number | null;
retryableModelCooldownMs: number | null;
}
const MIN_QUOTA_THRESHOLD_PERCENT = 1;
const MAX_QUOTA_THRESHOLD_PERCENT = 100;
const NON_RETRYABLE_MODEL_LOCKOUT_REASONS = new Set(["not_found", "not_found_local"]);
@@ -115,20 +119,25 @@ const NON_RETRYABLE_MODEL_LOCKOUT_REASONS = new Set(["not_found", "not_found_loc
// this base. Real upstream Retry-After hints still win — they flow through
// `exactCooldownMs` (usedUpstreamRetryHint), not this base. (#5222)
const ANTIGRAVITY_FAMILY_INFERRED_BASE_COOLDOWN_MS = 30_000;
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function toStringOrNull(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value : null;
}
function toNullableNumber(value: unknown): number | null {
if (value === null || value === undefined) return null;
const parsed = toNumber(value, Number.NaN);
return Number.isFinite(parsed) ? parsed : null;
}
function toBooleanOrDefault(value: unknown, fallback: boolean): boolean {
return typeof value === "boolean" ? value : fallback;
}
function normalizeSessionKey(value: unknown, prefix: string): string | null {
if (typeof value !== "string" || value.trim().length === 0) return null;
const trimmed = value.trim();
@@ -137,6 +146,7 @@ function normalizeSessionKey(value: unknown, prefix: string): string | null {
}
return `${prefix}:sha256:${createHash("sha256").update(trimmed).digest("hex")}`;
}
function extractTextForSessionHash(value: unknown): string | null {
if (typeof value === "string") return value;
if (Array.isArray(value)) {
@@ -154,6 +164,7 @@ function extractTextForSessionHash(value: unknown): string | null {
if (value && typeof value === "object") return JSON.stringify(value);
return null;
}
function getFirstInputText(body: unknown): string | null {
const record = asRecord(body);
if (record.input !== undefined) {
@@ -178,6 +189,7 @@ function getFirstInputText(body: unknown): string | null {
return null;
}
export function extractSessionAffinityKey(
body: unknown,
headers?: Headers | { get?: (name: string) => string | null } | null
@@ -204,6 +216,7 @@ export function extractSessionAffinityKey(
if (!inputText || inputText.trim().length === 0) return null;
return `input:sha256:${createHash("sha256").update(inputText.slice(0, 4096)).digest("hex")}`;
}
function getCodexLimitPolicy(providerSpecificData: JsonRecord): {
use5h: boolean;
useWeekly: boolean;
@@ -214,11 +227,13 @@ function getCodexLimitPolicy(providerSpecificData: JsonRecord): {
useWeekly: toBooleanOrDefault(policy.useWeekly, true),
};
}
interface QuotaLimitPolicy {
enabled: boolean;
thresholdPercent: number;
windows: string[];
}
interface QuotaCacheView {
quotas?: Record<
string,
@@ -228,6 +243,7 @@ interface QuotaCacheView {
}
>;
}
function normalizeQuotaThreshold(
value: unknown,
fallback = DEFAULT_QUOTA_THRESHOLD_PERCENT
@@ -235,14 +251,17 @@ function normalizeQuotaThreshold(
const parsed = toNumber(value, fallback);
return Math.min(MAX_QUOTA_THRESHOLD_PERCENT, Math.max(MIN_QUOTA_THRESHOLD_PERCENT, parsed));
}
function normalizeWindowName(windowName: unknown): string | null {
if (typeof windowName !== "string") return null;
const normalized = windowName.trim().toLowerCase();
return normalized.length > 0 ? normalized : null;
}
function uniqueWindows(windows: string[]): string[] {
return [...new Set(windows)];
}
function normalizeCodexWindowName(windowName: unknown): string | null {
if (typeof windowName !== "string") return null;
const normalized = windowName.trim().toLowerCase();
@@ -254,6 +273,7 @@ function normalizeCodexWindowName(windowName: unknown): string | null {
}
return toCodexBaseQuotaWindowName(normalized);
}
function applyCodexWindowPolicy(rawWindows: string[], providerSpecificData: JsonRecord): string[] {
const codexPolicy = getCodexLimitPolicy(providerSpecificData);
const normalizedRaw = rawWindows.map(normalizeCodexWindowName).filter(Boolean) as string[];
@@ -271,6 +291,7 @@ function applyCodexWindowPolicy(rawWindows: string[], providerSpecificData: Json
return uniqueWindows(windows);
}
function getCodexScopeRateLimitedUntil(
providerSpecificData: JsonRecord,
model: string | null
@@ -281,6 +302,7 @@ function getCodexScopeRateLimitedUntil(
const value = scopeMap[scope];
return typeof value === "string" && value.trim().length > 0 ? value : null;
}
function isCodexScopeUnavailable(
connection: ProviderConnectionView,
model: string | null
@@ -289,6 +311,7 @@ function isCodexScopeUnavailable(
if (!until) return false;
return new Date(until).getTime() > Date.now();
}
function getEarliestCodexScopeRateLimitedUntil(
connections: ProviderConnectionView[],
model: string | null
@@ -309,9 +332,11 @@ function getEarliestCodexScopeRateLimitedUntil(
return earliest;
}
function normalizeStatus(value: string | null): string {
return (value || "").trim().toLowerCase();
}
function isTerminalConnectionStatus(connection: ProviderConnectionView): boolean {
const status = normalizeStatus(connection.testStatus);
return status === "credits_exhausted" || status === "banned" || status === "expired";
@@ -329,6 +354,7 @@ function isRecoverableCookieAuth401(
resolveProviderId(provider) in WEB_COOKIE_PROVIDERS
);
}
function resolveTerminalConnectionStatus(
status: number,
result: { permanent?: boolean; creditsExhausted?: boolean },
@@ -355,6 +381,7 @@ function resolveTerminalConnectionStatus(
}
return null;
}
export function resolveQuotaLimitPolicy(
provider: string,
providerSpecificData: JsonRecord
@@ -380,6 +407,7 @@ export function resolveQuotaLimitPolicy(
windows,
};
}
export function evaluateQuotaLimitPolicy(
provider: string,
connection: ProviderConnectionView,
@@ -412,6 +440,7 @@ export function evaluateQuotaLimitPolicy(
resetAt: getEarliestFutureDate(resetCandidates),
};
}
function parseFutureDateMs(value: string | null): number | null {
if (!value) return null;
// Tolerate numeric-epoch strings (e.g. "1781696905131.0") as well as ISO
@@ -420,6 +449,7 @@ function parseFutureDateMs(value: string | null): number | null {
if (!Number.isFinite(ms) || ms <= Date.now()) return null;
return ms;
}
function getEarliestFutureDate(candidates: Array<string | null>): string | null {
return (
candidates
@@ -431,26 +461,31 @@ function getEarliestFutureDate(candidates: Array<string | null>): string | null
.sort((a, b) => (a.ms as number) - (b.ms as number))[0]?.raw || null
);
}
function getCachedQuotaResetAt(connectionId: string): string | null {
const entry = getQuotaCache(connectionId);
if (!entry?.quotas) return null;
return getEarliestFutureDate(Object.values(entry.quotas).map((quota) => quota.resetAt));
}
function isRetryableModelLockoutReason(reason: unknown): boolean {
return typeof reason === "string" && reason.length > 0
? !NON_RETRYABLE_MODEL_LOCKOUT_REASONS.has(reason)
: false;
}
function pushClampedPercentage(percentages: number[], value: number): void {
if (Number.isFinite(value)) {
percentages.push(Math.max(0, Math.min(100, value)));
}
}
function isResetAtInPast(resetAt: string | null): boolean {
if (!resetAt) return false;
const resetMs = new Date(resetAt).getTime();
return Number.isFinite(resetMs) && resetMs <= Date.now();
}
function collectPolicyQuotaHeadroomPercentages(
provider: string,
connection: ProviderConnectionView,
@@ -473,6 +508,7 @@ function collectPolicyQuotaHeadroomPercentages(
return percentages;
}
function collectCachedQuotaHeadroomPercentages(
provider: string,
connection: ProviderConnectionView,
@@ -492,6 +528,7 @@ function collectCachedQuotaHeadroomPercentages(
return percentages;
}
function getConnectionQuotaHeadroomPercent(
provider: string,
connection: ProviderConnectionView,
@@ -511,6 +548,7 @@ function getConnectionQuotaHeadroomPercent(
return percentages.length > 0 ? Math.min(...percentages) : null;
}
function getConnectionErrorPenalty(connection: ProviderConnectionView): number {
const errorType = normalizeStatus(connection.lastErrorType);
const errorSource = normalizeStatus(connection.lastErrorSource);
@@ -534,6 +572,7 @@ function getConnectionErrorPenalty(connection: ProviderConnectionView): number {
return penalty;
}
function getConnectionRecencyPenalty(connection: ProviderConnectionView): number {
if (!connection.lastUsedAt) return 0;
const ageMs = Date.now() - new Date(connection.lastUsedAt).getTime();
@@ -543,6 +582,7 @@ function getConnectionRecencyPenalty(connection: ProviderConnectionView): number
if (ageMs < 5 * 60_000) return 1;
return 0;
}
function getP2CConnectionScore(
provider: string,
connection: ProviderConnectionView,
@@ -588,6 +628,7 @@ function getP2CConnectionScore(
return { score, quotaHeadroomPercent };
}
function compareP2CConnections(
provider: string,
a: ProviderConnectionView,
@@ -621,10 +662,12 @@ function compareP2CConnections(
* exclude it (#3061), otherwise it gets re-selected forever.
*/
const SYNTHETIC_NOAUTH_CONNECTION_ID = "noauth";
type AnonymousFallbackProviderDefinition = {
anonymousFallback?: boolean;
noAuth?: boolean;
};
function buildSyntheticNoAuthCredentials(providerSpecificData: JsonRecord = {}): {
apiKey: null;
accessToken: null;
@@ -713,6 +756,7 @@ async function loadNoAuthProviderSpecificData(providerId: string): Promise<JsonR
return {};
}
}
function providerCanUseSyntheticNoAuthFallback(providerId: string): boolean {
const providerDef = getProviderById(providerId) as
AnonymousFallbackProviderDefinition | undefined;
@@ -728,6 +772,7 @@ function providerCanUseSyntheticNoAuthFallback(providerId: string): boolean {
webCookieProviderDef?.noAuth === true
);
}
async function maybeSyntheticNoAuthFallback(
providerId: string,
excludedConnectionIds: Set<string>,
@@ -745,6 +790,7 @@ async function maybeSyntheticNoAuthFallback(
const providerSpecificData = await loadNoAuthProviderSpecificData(providerId);
return buildSyntheticNoAuthCredentials(providerSpecificData);
}
function normalizeExcludedConnectionIds(
excludeConnectionId: string | null,
extraExcludedConnectionIds: string[] | null | undefined
@@ -765,6 +811,7 @@ function normalizeExcludedConnectionIds(
return normalized;
}
function formatConnectionPrefixesForLog(ids: Iterable<string>, max = 6): string {
const prefixes = Array.from(ids)
.filter((id) => typeof id === "string" && id.length > 0)
@@ -772,6 +819,7 @@ function formatConnectionPrefixesForLog(ids: Iterable<string>, max = 6): string
.map((id) => `${id.slice(0, 8)}...`);
return prefixes.length > 0 ? prefixes.join(",") : "none";
}
function buildQuotaPreflightRateLimitedResult(
provider: string,
blockedByPreflight: Array<{
@@ -802,10 +850,12 @@ function buildQuotaPreflightRateLimitedResult(
lastErrorCode: 429,
};
}
function quotaPreflightUnavailableUntil(resetAt?: string | null): string {
const resetMs = parseFutureDateMs(resetAt ?? null);
return new Date(resetMs ?? Date.now() + 5 * 60 * 1000).toISOString();
}
async function markQuotaPreflightAccountUnavailable(
provider: string,
connectionId: string,
@@ -834,12 +884,14 @@ async function markQuotaPreflightAccountUnavailable(
// Provider-scoped mutexes prevent race conditions during account selection without
// serializing unrelated providers behind a single global lock.
const selectionMutexes = new Map<string, Promise<void>>();
function getSelectionMutexKey(provider: string, options: CredentialSelectionOptions): string {
return [
resolveProviderId(provider) || provider,
options.forcedConnectionId ? `forced:${options.forcedConnectionId}` : "pool",
].join(":");
}
function createSelectionLock(key: string) {
const currentMutex = selectionMutexes.get(key) ?? Promise.resolve();
let resolveMutex: (() => void) | undefined;
@@ -871,6 +923,7 @@ export { fisherYatesShuffle, getNextFromDeckSync as getNextFromDeck };
// Re-export readHeaderValue and AuthRequestHeaders from headerReader.ts for
// backwards compat with existing imports (e.g. googApiKeyAuth.ts).
export { readHeaderValue, type AuthRequestHeaders } from "./headerReader.ts";
const PROVIDER_SEARCH_PAIRS: string[][] = [
["nvidia", "nvidia_nim"],
["kimi-coding", "kimi-coding-apikey"],
@@ -1650,6 +1703,7 @@ export async function getProviderCredentials(
selectionLock.release();
}
}
export async function getProviderCredentialsWithQuotaPreflight(
provider: string,
excludeConnectionId: string | null = null,
@@ -1951,17 +2005,16 @@ export async function markAccountUnavailable(
const disableCooling = connProviderSpecificData.disableCooling === true;
const isPerModelQuotaProvider = hasPerModelQuota(provider, model, connectionPassthroughModels);
const isNvidiaModelGone = provider === "nvidia" && status === 410;
const modelLockoutOptions = { maxCooldownMs: effectiveProviderProfile?.maxCooldownMs };
if (
isPerModelQuotaProvider &&
provider &&
provider !== "codex" &&
model &&
(status === 404 || isNvidiaModelGone || status === 429 || status >= 500)
(status === 404 || status === 429 || status >= 500)
) {
const reason =
status === 404 || isNvidiaModelGone
status === 404
? "not_found"
: status === 429 && fallbackResult.reason === RateLimitReason.QUOTA_EXHAUSTED
? "quota_exhausted"
@@ -1993,10 +2046,7 @@ export async function markAccountUnavailable(
? "model"
: getQuotaScopeLabelForProvider(provider, model);
const antigravityFamilyInferredBaseCooldownMs =
!usesExactAntigravityLock &&
provider === "antigravity" &&
quotaScope === "family" &&
status === 429
!usesExactAntigravityLock && provider === "antigravity" && quotaScope === "family" && status === 429
? ANTIGRAVITY_FAMILY_INFERRED_BASE_COOLDOWN_MS
: null;
const lockout = recordModelLockoutFailure(
@@ -2005,7 +2055,7 @@ export async function markAccountUnavailable(
model,
reason,
status,
status === 404 || isNvidiaModelGone
status === 404
? (effectiveProviderProfile?.baseCooldownMs ?? COOLDOWN_MS.notFoundLocal)
: (antigravityFamilyInferredBaseCooldownMs ??
fallbackResult.baseCooldownMs ??
@@ -2302,6 +2352,7 @@ export interface RecoveredStateExpectation {
lastErrorAt: string | null;
rateLimitedUntil: string | null;
}
export async function clearRecoveredProviderState(
credentials: Partial<RecoverableConnectionState> | null,
expectedState?: RecoveredStateExpectation
@@ -2322,10 +2373,12 @@ export async function clearRecoveredProviderState(
await clearAccountError(credentials.connectionId, credentials);
return { applied: true };
}
type AuthRequestLike = {
headers?: AuthRequestHeaders | null;
url?: string | null;
};
function readNonEmptyUrlToken(request: AuthRequestLike): string | null {
if (typeof request?.url !== "string" || request.url.trim().length === 0) return null;

View File

@@ -247,7 +247,6 @@
"tests/unit/no-memory-header.test.ts",
"tests/unit/noauth-autocombo-lockout-7623.test.ts",
"tests/unit/non-streaming-sse-terminal-typescan-4459.test.ts",
"tests/unit/nvidia-410-model-scope.test.ts",
"tests/unit/nvidia-passthrough-models-6773.test.ts",
"tests/unit/nvidia-quota-phase1.test.ts",
"tests/unit/oauth-providers-config.test.ts",

View File

@@ -1,196 +0,0 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-nvidia-410-model-scope-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "nvidia-410-model-scope-test-secret";
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const auth = await import("../../src/sse/services/auth.ts");
const fallback = await import("../../open-sse/services/accountFallback.ts");
const DEAD_MODEL = "deepseek-ai/deepseek-v4-pro";
const HEALTHY_MODEL = "z-ai/glm-5.2";
const GONE_BODY = JSON.stringify({
type: "about:blank",
title: "Gone",
status: 410,
detail:
"The model 'deepseek-ai/deepseek-v4-pro' has reached its end of life " +
"and is no longer available.",
});
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
async function seedNvidiaConnection() {
return providersDb.createProviderConnection({
provider: "nvidia",
authType: "apikey",
name: "nvidia-410-model-scope",
apiKey: "sk-nvidia-410-model-scope",
isActive: true,
testStatus: "active",
providerSpecificData: {},
});
}
test.beforeEach(async () => {
await resetStorage();
});
test.after(async () => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("NVIDIA 410 Gone stays model-scoped and leaves the connection usable", async () => {
const connection = await seedNvidiaConnection();
assert.equal(
fallback.hasPerModelQuota("nvidia", DEAD_MODEL),
true,
"NVIDIA must use per-model failure scoping"
);
const result = await auth.markAccountUnavailable(
connection.id,
410,
GONE_BODY,
"nvidia",
DEAD_MODEL
);
assert.equal(result.shouldFallback, true);
const after = await providersDb.getProviderConnectionById(connection.id);
assert.equal(
after?.rateLimitedUntil ?? null,
null,
"410 for one retired NVIDIA model must not apply a connection-wide cooldown"
);
assert.equal(
after?.testStatus,
"active",
"410 for one retired NVIDIA model must leave the NVIDIA connection active"
);
assert.equal(
fallback.isModelLocked("nvidia", connection.id, DEAD_MODEL),
true,
"the retired model itself should be locked"
);
assert.equal(
fallback.isModelLocked("nvidia", connection.id, HEALTHY_MODEL),
false,
"a healthy sibling NVIDIA model must remain unlocked"
);
const healthyCredentials = await auth.getProviderCredentials("nvidia", null, null, HEALTHY_MODEL);
assert.equal(
healthyCredentials?.connectionId,
connection.id,
"the same NVIDIA connection must remain selectable for healthy sibling models"
);
});
test("non-per-model provider keeps 410 connection-scoped", async () => {
assert.equal(
fallback.hasPerModelQuota("openai", DEAD_MODEL),
false,
"plain OpenAI API-key connections are not per-model quota providers"
);
const connection = await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
name: "openai-410-connection-scope",
apiKey: "sk-openai-410-connection-scope",
isActive: true,
testStatus: "active",
providerSpecificData: {},
});
const result = await auth.markAccountUnavailable(
connection.id,
410,
"Gone",
"openai",
DEAD_MODEL
);
assert.equal(result.shouldFallback, true);
const after = await providersDb.getProviderConnectionById(connection.id);
assert.ok(
after?.rateLimitedUntil,
"non-per-model providers should retain the existing connection-level 410 behavior"
);
assert.equal(
after?.testStatus,
"unavailable",
"410 model scoping must not be applied globally to every provider"
);
});
test("other per-model providers retain existing 410 connection scope", async () => {
assert.equal(
fallback.hasPerModelQuota("gemini", DEAD_MODEL),
true,
"Gemini provides a non-NVIDIA per-model control case"
);
const connection = await providersDb.createProviderConnection({
provider: "gemini",
authType: "apikey",
name: "gemini-410-control",
apiKey: "sk-gemini-410-control",
isActive: true,
testStatus: "active",
providerSpecificData: {},
});
const result = await auth.markAccountUnavailable(
connection.id,
410,
"Gone",
"gemini",
DEAD_MODEL
);
assert.equal(result.shouldFallback, true);
const after = await providersDb.getProviderConnectionById(connection.id);
assert.ok(
after?.rateLimitedUntil,
"410 must remain connection-scoped for per-model providers without an explicit 410 contract"
);
assert.equal(
after?.testStatus,
"unavailable",
"the NVIDIA-specific 410 fix must not change other provider semantics"
);
assert.equal(
fallback.isModelLocked("gemini", connection.id, DEAD_MODEL),
false,
"a generic per-model provider must not inherit NVIDIA's 410 model lock"
);
});