Compare commits
18 Commits
feat/provi
...
chore/open
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
384ce49a4d | ||
|
|
d6f315018a | ||
|
|
ce49d969ca | ||
|
|
6b587d0046 | ||
|
|
c1b34db50d | ||
|
|
ebdbd2c67d | ||
|
|
aa35d460dc | ||
|
|
1b97f42ba3 | ||
|
|
f12b87c80b | ||
|
|
c042a51884 | ||
|
|
d7721559a0 | ||
|
|
e8a91173de | ||
|
|
6d6b6027c5 | ||
|
|
600abe68d0 | ||
|
|
d4d2e68a1f | ||
|
|
25bc16d87e | ||
|
|
d857bd053a | ||
|
|
ce55151ca5 |
@@ -7,6 +7,7 @@ fi
|
||||
|
||||
# Cheap, deterministic local gates (re-enabled). Slower checks (i18n drift,
|
||||
# openapi coverage/security-tiers, env-doc sync) run in CI to keep commits fast.
|
||||
sh scripts/check/check-git-identity.sh
|
||||
npx lint-staged
|
||||
node scripts/check/check-docs-sync.mjs
|
||||
npm run check:any-budget:t11
|
||||
|
||||
696
@omniroute/opencode-plugin-v2/package-lock.json
generated
@@ -22,7 +22,7 @@
|
||||
"node": ">=22.22.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opencode-ai/plugin": "*"
|
||||
"@opencode-ai/plugin": ">=1.18.29 <2"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider": {
|
||||
@@ -39,9 +39,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
|
||||
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
|
||||
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -56,9 +56,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
|
||||
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
|
||||
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -73,9 +73,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -90,9 +90,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -107,9 +107,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -124,9 +124,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -141,9 +141,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -158,9 +158,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -175,9 +175,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
|
||||
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
|
||||
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -192,9 +192,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -209,9 +209,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
|
||||
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
|
||||
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -226,9 +226,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
|
||||
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
|
||||
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -243,9 +243,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
|
||||
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
|
||||
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -260,9 +260,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
|
||||
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
|
||||
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -277,9 +277,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
|
||||
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
|
||||
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -294,9 +294,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
|
||||
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
|
||||
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -311,7 +311,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -326,9 +328,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -343,9 +345,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -360,9 +362,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -377,9 +379,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -394,9 +396,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -411,9 +413,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -428,9 +430,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -445,9 +447,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
|
||||
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
|
||||
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -462,9 +464,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1180,7 +1182,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
|
||||
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -1191,32 +1195,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.28.1",
|
||||
"@esbuild/android-arm": "0.28.1",
|
||||
"@esbuild/android-arm64": "0.28.1",
|
||||
"@esbuild/android-x64": "0.28.1",
|
||||
"@esbuild/darwin-arm64": "0.28.1",
|
||||
"@esbuild/darwin-x64": "0.28.1",
|
||||
"@esbuild/freebsd-arm64": "0.28.1",
|
||||
"@esbuild/freebsd-x64": "0.28.1",
|
||||
"@esbuild/linux-arm": "0.28.1",
|
||||
"@esbuild/linux-arm64": "0.28.1",
|
||||
"@esbuild/linux-ia32": "0.28.1",
|
||||
"@esbuild/linux-loong64": "0.28.1",
|
||||
"@esbuild/linux-mips64el": "0.28.1",
|
||||
"@esbuild/linux-ppc64": "0.28.1",
|
||||
"@esbuild/linux-riscv64": "0.28.1",
|
||||
"@esbuild/linux-s390x": "0.28.1",
|
||||
"@esbuild/linux-x64": "0.28.1",
|
||||
"@esbuild/netbsd-arm64": "0.28.1",
|
||||
"@esbuild/netbsd-x64": "0.28.1",
|
||||
"@esbuild/openbsd-arm64": "0.28.1",
|
||||
"@esbuild/openbsd-x64": "0.28.1",
|
||||
"@esbuild/openharmony-arm64": "0.28.1",
|
||||
"@esbuild/sunos-x64": "0.28.1",
|
||||
"@esbuild/win32-arm64": "0.28.1",
|
||||
"@esbuild/win32-ia32": "0.28.1",
|
||||
"@esbuild/win32-x64": "0.28.1"
|
||||
"@esbuild/aix-ppc64": "0.28.2",
|
||||
"@esbuild/android-arm": "0.28.2",
|
||||
"@esbuild/android-arm64": "0.28.2",
|
||||
"@esbuild/android-x64": "0.28.2",
|
||||
"@esbuild/darwin-arm64": "0.28.2",
|
||||
"@esbuild/darwin-x64": "0.28.2",
|
||||
"@esbuild/freebsd-arm64": "0.28.2",
|
||||
"@esbuild/freebsd-x64": "0.28.2",
|
||||
"@esbuild/linux-arm": "0.28.2",
|
||||
"@esbuild/linux-arm64": "0.28.2",
|
||||
"@esbuild/linux-ia32": "0.28.2",
|
||||
"@esbuild/linux-loong64": "0.28.2",
|
||||
"@esbuild/linux-mips64el": "0.28.2",
|
||||
"@esbuild/linux-ppc64": "0.28.2",
|
||||
"@esbuild/linux-riscv64": "0.28.2",
|
||||
"@esbuild/linux-s390x": "0.28.2",
|
||||
"@esbuild/linux-x64": "0.28.2",
|
||||
"@esbuild/netbsd-arm64": "0.28.2",
|
||||
"@esbuild/netbsd-x64": "0.28.2",
|
||||
"@esbuild/openbsd-arm64": "0.28.2",
|
||||
"@esbuild/openbsd-x64": "0.28.2",
|
||||
"@esbuild/openharmony-arm64": "0.28.2",
|
||||
"@esbuild/sunos-x64": "0.28.2",
|
||||
"@esbuild/win32-arm64": "0.28.2",
|
||||
"@esbuild/win32-ia32": "0.28.2",
|
||||
"@esbuild/win32-x64": "0.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
@@ -1786,490 +1790,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
|
||||
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
|
||||
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
|
||||
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
|
||||
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
|
||||
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
|
||||
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
|
||||
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
|
||||
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
|
||||
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsup/node_modules/esbuild": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
||||
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.7",
|
||||
"@esbuild/android-arm": "0.27.7",
|
||||
"@esbuild/android-arm64": "0.27.7",
|
||||
"@esbuild/android-x64": "0.27.7",
|
||||
"@esbuild/darwin-arm64": "0.27.7",
|
||||
"@esbuild/darwin-x64": "0.27.7",
|
||||
"@esbuild/freebsd-arm64": "0.27.7",
|
||||
"@esbuild/freebsd-x64": "0.27.7",
|
||||
"@esbuild/linux-arm": "0.27.7",
|
||||
"@esbuild/linux-arm64": "0.27.7",
|
||||
"@esbuild/linux-ia32": "0.27.7",
|
||||
"@esbuild/linux-loong64": "0.27.7",
|
||||
"@esbuild/linux-mips64el": "0.27.7",
|
||||
"@esbuild/linux-ppc64": "0.27.7",
|
||||
"@esbuild/linux-riscv64": "0.27.7",
|
||||
"@esbuild/linux-s390x": "0.27.7",
|
||||
"@esbuild/linux-x64": "0.27.7",
|
||||
"@esbuild/netbsd-arm64": "0.27.7",
|
||||
"@esbuild/netbsd-x64": "0.27.7",
|
||||
"@esbuild/openbsd-arm64": "0.27.7",
|
||||
"@esbuild/openbsd-x64": "0.27.7",
|
||||
"@esbuild/openharmony-arm64": "0.27.7",
|
||||
"@esbuild/sunos-x64": "0.27.7",
|
||||
"@esbuild/win32-arm64": "0.27.7",
|
||||
"@esbuild/win32-ia32": "0.27.7",
|
||||
"@esbuild/win32-x64": "0.27.7"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.22.3",
|
||||
"dev": true,
|
||||
|
||||
@@ -63,5 +63,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opencode-ai/plugin": ">=1.18.29 <2"
|
||||
},
|
||||
"overrides": {
|
||||
"esbuild": "^0.28.1"
|
||||
}
|
||||
}
|
||||
|
||||
6
@omniroute/opencode-plugin/package-lock.json
generated
@@ -1745,9 +1745,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/toml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz",
|
||||
"integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-4.3.0.tgz",
|
||||
"integrity": "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"overrides": {
|
||||
"esbuild": "^0.28.1"
|
||||
"esbuild": "^0.28.1",
|
||||
"toml": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
|
||||
|
||||
## Project at a Glance
|
||||
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 357 LLM providers, auto-fallback.
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 356 LLM providers, auto-fallback.
|
||||
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
26
README.md
@@ -7,19 +7,19 @@
|
||||
|
||||
# 🚀 OmniRoute — The Free AI Gateway
|
||||
|
||||
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 357 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 15–95% tokens (~89% avg) — never hit limits. 357 AI providers · 150+ free tiers · ~1.62B free tokens/mo · 19 routing strategies · $0 to start."/>
|
||||
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 356 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 15–95% tokens (~89% avg) — never hit limits. 356 AI providers · 150+ free tiers · ~1.47B free tokens/mo · 19 routing strategies · $0 to start."/>
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 💰 ~1.62B Free Tokens / Month
|
||||
## 💰 ~1.47B Free Tokens / Month
|
||||
|
||||
</div>
|
||||
|
||||
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute catalogs **483 free-tier entries across 35 recurring pool keys** and computes the token headline from the **17 pools with a published positive monthly budget plus five per-model Groq caps**, deduplicated by shared pool. Quotas that only open after a regional identity check (today: ModelScope) are shown apart, +~6M behind regional identity verification, and never summed into the headline. The result stays visible on the dashboard (`/dashboard/free-tiers`).
|
||||
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute catalogs **444 free-tier entries across 34 recurring pool keys** and computes the token headline from the **16 pools with a published positive monthly budget plus five per-model Groq caps**, deduplicated by shared pool. Quotas that only open after a regional identity check (today: ModelScope) are shown apart, +~6M behind regional identity verification, and never summed into the headline. The result stays visible on the dashboard (`/dashboard/free-tiers`).
|
||||
|
||||
<img src="./docs/diagrams/free-tier-budget.svg" width="100%" alt="OmniRoute free-tier budget card: ~1.62B free tokens per month steady, up to ~2.25B in the first month with signup credits, from 35 documented recurring pool keys covering 483 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 17 recurring pools with a published positive monthly token budget plus five per-model Groq caps; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, Nara 210M, LLM7 150M, xKiro 150M, Groq 30M (five per-model caps) and smaller pools, plus first-month signup credits and permanently-free no-token-cap providers surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers."/>
|
||||
<img src="./docs/diagrams/free-tier-budget.svg" width="100%" alt="OmniRoute free-tier budget card: ~1.47B free tokens per month steady, up to ~2.10B in the first month with signup credits, from 34 documented recurring pool keys covering 444 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 16 recurring pools with a published positive monthly token budget plus five per-model Groq caps; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, Nara 210M, LLM7 150M, Groq 30M (five per-model caps) and smaller pools, plus first-month signup credits and permanently-free no-token-cap providers surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers."/>
|
||||
|
||||
> Animated summary of the live `/dashboard/free-tiers` page. Full methodology (pool dedupe, credit tiers, provider terms): **[docs/reference/FREE_TIERS.md](docs/reference/FREE_TIERS.md)**.
|
||||
>
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
| | v3.8.49 | **v3.8.50** | `v3.8.51+` |
|
||||
| ------------------------- | :-----: | :-----------------------: | :---------: |
|
||||
| 🌐 Providers | 290 | **357** | more queued |
|
||||
| 🌐 Providers | 290 | **352** | more queued |
|
||||
| 🧠 Unique chat model IDs | 1185 | **1312** | — |
|
||||
| 🖼️ Modality Bridge | — | 🆕 vision + audio + video | — |
|
||||
| 📡 Radar free catalog | — | 🆕 opt-in | — |
|
||||
@@ -101,7 +101,7 @@
|
||||
<tr>
|
||||
<td align="right"><b>⚙️ Features</b></td>
|
||||
<td align="center"><a href="#-combos--the-flagship">🎯 Combos</a></td>
|
||||
<td align="center"><a href="#-357-ai-providers--152-catalog-marked-free">🌐 Providers</a></td>
|
||||
<td align="center"><a href="#-352-ai-providers--154-catalog-marked-free">🌐 Providers</a></td>
|
||||
<td align="center"><a href="#-full-cli--a2a--mcp">🔌 CLI & MCP</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -209,7 +209,7 @@ curl http://localhost:20128/v1/chat/completions \
|
||||
|
||||
</div>
|
||||
|
||||
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 357 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 357 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 53 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
|
||||
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 356 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 356 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 52 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
@@ -462,7 +462,7 @@ All **19** strategies — mix & match per combo step:
|
||||
|
||||
</div>
|
||||
|
||||
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 357 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 42 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
|
||||
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 356 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 42 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
|
||||
|
||||
<sub>📊 Full methodology & per-feature detail vs 9router, OpenRouter, CLIProxyAPI & LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
|
||||
|
||||
@@ -518,9 +518,9 @@ Pix copia-e-cola:
|
||||
|
||||
## 📡 OmniRoute Radar
|
||||
|
||||
The main free-tier headline remains **~1.62B tokens/month** from the documented,
|
||||
The main free-tier headline remains **~1.47B tokens/month** from the documented,
|
||||
pool-deduplicated catalog above. Temporary provider signup credits can separately lift the first
|
||||
month to **~2.25B**. Radar is an optional, signed catalog overlay for people who want fresher
|
||||
month to **~2.10B**. Radar is an optional, signed catalog overlay for people who want fresher
|
||||
free-model availability between OmniRoute releases; the community catalog and every existing free
|
||||
feature remain free.
|
||||
|
||||
@@ -644,11 +644,11 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 🌐 357 AI Providers — 152 Catalog-Marked Free
|
||||
## 🌐 352 AI Providers — 152 Catalog-Marked Free
|
||||
|
||||
</div>
|
||||
|
||||
> **357 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **483 per-model rows**, **35 recurring pools** and **53 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
|
||||
> **352 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **444 per-model rows**, **34 recurring pools** and **52 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -1307,7 +1307,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
|
||||
<tr><td nowrap><b><a href="docs/architecture/RESILIENCE_GUIDE.md">Resilience Guide</a></b></td><td>Circuit breakers, cooldowns, queue, anti-thundering herd, TLS spoofing</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/routing/AUTO-COMBO.md">Auto-Combo Engine</a></b></td><td>16-factor scoring, mode packs, self-healing</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/ops/PROXY_GUIDE.md">Proxy Guide</a></b></td><td>3-level proxy system, 1proxy marketplace, registry CRUD</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/reference/FREE_TIERS.md">Free Tiers</a></b></td><td>Consolidated directory: 35 documented recurring pools / 483 cataloged free-tier entries</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/reference/FREE_TIERS.md">Free Tiers</a></b></td><td>Consolidated directory: 34 documented recurring pools / 444 cataloged free-tier entries</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/guides/FEATURES.md">Features Gallery</a></b></td><td>Visual dashboard tour with screenshots</td></tr>
|
||||
<tr><td nowrap><b><a href="docs/architecture/CODEBASE_DOCUMENTATION.md">Codebase Documentation</a></b></td><td>Beginner-friendly codebase walkthrough</td></tr>
|
||||
</table>
|
||||
|
||||
1
changelog.d/features/12789-quota-weighted-routing.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(combo):** add combo strategy `quota-weighted`: skip exhausted accounts, then weighted-draw by leftover / in-flight load; existing conversations stay pinned until the account is empty ([#12789](https://github.com/diegosouzapw/OmniRoute/pull/12789))
|
||||
2
changelog.d/features/12866-live-catalog-authority.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- **feat(models):** Account live listings become the chat catalog source for Claude, Codex, Copilot, and AGY; public metadata only fills prices on IDs those accounts already list. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866))
|
||||
- **fix(models):** Union `agy` and `antigravity` live catalogs so `agy/gemini-3.8-flash-high` is not rejected after the prefix folds to `antigravity`. ([#12866](https://github.com/diegosouzapw/OmniRoute/pull/12866))
|
||||
1
changelog.d/fixes/0000-claude-extra-usage-preflight.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(claude):** `blockExtraUsage: false` no longer lets 5h quota preflight skip the account; extra usage is billed after the session bar is gone, so the request must reach Anthropic
|
||||
1
changelog.d/fixes/12696-server-owned-tool-loop.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(chat):** Chat Completions no longer return empty `content` after a server-owned memory or skills tool; the first provider send and account/model recovery now share one pipeline so a follow-up round-trip can fill the reply ([#12696](https://github.com/diegosouzapw/OmniRoute/issues/12696)) — thanks @HouMinXi
|
||||
2
changelog.d/fixes/12711-batch-delete-click-event.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711))
|
||||
- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream
|
||||
1
changelog.d/fixes/12868-runtime-quotagroup-nodemap.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868))
|
||||
@@ -0,0 +1 @@
|
||||
- Restricted API keys whose `allowedModels` lists a combo name no longer skip every combo member at pre-dispatch (`ALL_TARGETS_SKIPPED`). Inner-target filtering still applies when the allow-list is a provider prefix or `disableNonPublicModels` is on ([#12899](https://github.com/diegosouzapw/OmniRoute/pull/12899)).
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi
|
||||
1
changelog.d/fixes/12934-custom-models-live-catalog.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(catalog):** Picker-added `customModels` enter the dispatch-time live catalog so combo members and bare inference no longer 400 ([#12934](https://github.com/diegosouzapw/OmniRoute/pull/12934)) — thanks @HouMinXi
|
||||
1
changelog.d/fixes/dashboard-stale-tab-refresh.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(pwa):** do not intercept dashboard navigations so Chrome can fall back from a stale HTTP/3 Alt-Svc advertisement (UDP :20128 is unpublished; F5 on a long-lived tab hung until a new tab opened a fresh TCP connection).
|
||||
1
changelog.d/fixes/glm-sse-transform-arity.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached TransformStream
|
||||
1
changelog.d/fixes/grok-cli-reset-credits-readonly.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(grok-cli):** Provider Limits shows grok-cli banked reset credits from `GetRemainingResets` (including a real zero; a failed RPC omits the row) and the existing View credits button now calls `ConsumerUiSvc/RedeemReset` for grok-cli. Live tokens use nested fields 10/20/30 (id + Timestamp), not compact 1/2/3.
|
||||
1
changelog.d/fixes/moonshot-balance-pct.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** Moonshot/Kimi Open Platform voucher and cash leftover percentages follow the bucket balance, so an empty wallet no longer paints those rows as 100% while Available is 0%
|
||||
@@ -1 +1 @@
|
||||
Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider.
|
||||
- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider.
|
||||
|
||||
1
changelog.d/maintenance/combo-round-robin-extract.md
Normal file
@@ -0,0 +1 @@
|
||||
- **refactor(combo):** move `handleRoundRobinCombo` (and `resolveTargetTokenLimit`) into `open-sse/services/combo/roundRobinCombo.ts`. `combo.ts` drops from 2164 to 1014 lines (`split("\n").length`); the leaf is 1199 (under the 1200 new-file cap). The round-robin call site uses a dynamic `import()` so `releaseStickyPinOnFailure` / `clearStaleLKGP` can stay exported from `combo.ts` without a static cycle. Skip / sticky / semaphore / safety-timer behavior is unchanged.
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"_rebaseline_2026_09_03_12648_xkiro_provider": "PR #12648 (feat/provider-xkiro) own growth: src/shared/constants/providers/apikey/gateways.ts +18 lines on top of #12649 (the xkiro APIKEY_PROVIDERS_GATEWAYS catalog entry with hasFree/freeNote/authHint/apiHint documenting the 5M tokens/day free plan, plus the Prettier reflow of two pre-existing >100-col authHint lines (oneminai, freebuff) that lint-staged enforces on any touch of the file; additive data at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines: #11786 seekai, #10987 logfare, #10531 freebuff). Covered by tests/unit/free-provider-xkiro.test.ts (4/4).",
|
||||
"_rebaseline_2026_09_06_runtime_quotagroup_nodemap": "Own growth: src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx 1201->1222 (+21, check-file-size split-newline). QuotaGroup is a module-level sibling and was reading nodeMap from RuntimePageClient's closure; that identifier is not in scope, so a quota monitor with status error/exhausted/alerting throws ReferenceError. Fix threads nodeMap as a prop (3 call sites + parameter + ProviderNodeEntry import). Prettier wraps the long import and the three QuotaGroup JSX tags. Covered by tests/unit/ui/runtime-page-client.test.tsx (empty monitors stay green; error+exhausted fixtures mount QuotaGroup).",
|
||||
"_rebaseline_2026_09_05_claude_extra_usage_preflight": "Own growth: open-sse/services/combo.ts 4080->4084 (+4). buildAutoCandidates now forwards connection.providerSpecificData into evaluateQuotaCutoff so a Claude account with blockExtraUsage=false is not dropped at the 5h bar. Irreducible at the existing cutoff call site; the helper lives in claudeExtraUsage.ts (under cap). Covered by tests/unit/quota-preflight.test.ts.",
|
||||
"_rebaseline_2026_09_04_12697_combo_pin_allowlist": "PR #12697 own growth: src/sse/handlers/chat.ts 2454->2458 (+4). checkModelAvailable preflight and handleSingleModelChat now call comboPinAllowlist so a pin-only combo step cannot scan the provider pool after 502/429. Helper lives in src/lib/combos/steps.ts under cap. Covered by tests/unit/combo-pin-implicit-allowlist.test.ts (11/11).",
|
||||
"_rebaseline_2026_09_05_quota_weighted": "feat/quota-weighted-routing own growth: src/app/(dashboard)/dashboard/combos/page.tsx 5066->5080 (+14 = STRATEGY_GUIDANCE_FALLBACK + STRATEGY_RECOMMENDATIONS_FALLBACK entries for quota-weighted; copy is the spec-mandated when/avoid/example and tips, irreducible at the existing fallback maps). Rebased onto 9d1a896c6 where #12671 already grew the same file 5018->5066. Covered by tests/unit/combo/quota-weighted-strategy.test.ts + autocombo-unification.test.ts.",
|
||||
"_rebaseline_2026_09_03_combo_execute_target_attempt": "Task 3 of handleComboChat split: new leaf open-sse/services/combo/executeTargetAttempt.ts lands at 1205 (check-file-size split-newline; wc -l 1204) above new-file cap 1200. Lift-as-is from combo.ts:1533-2616 retry loop. Pure classify predicates already extracted to executeTargetClassify.ts (54 LOC). Remaining growth is I/O + side effects (handleSingleModel, quality, pin/LKGP, cooldown, lockout) that cannot leave this file without splitting the retry loop mid-request. Frozen at exact LOC so it can only shrink. Covered by tests/unit/combo/execute-target-attempt.test.ts (7/7).",
|
||||
"_rebaseline_2026_09_06_12696_chat_pipeline_retry_off": "PR #12696 own test growth: tests/integration/chat-pipeline.test.ts 1644->1648 (+4). The upstream-500 structured-error case now sets requestRetry/maxRetryIntervalSec to 0 so the new provider-execution pipeline cannot retry the mock 500 and double-count fetch. Irreducible at the existing seed+fetch mock; covered by the same test.",
|
||||
"_rebaseline_2026_09_03_reset_aware_model_family": "Own growth: open-sse/services/combo.ts 4036->4041 (+5). buildAutoCandidates now keys the reset-aware quota cache by getQuotaFetchScope and spreads requestedModel onto the connection so Gemini windows stay off a Claude-empty Antigravity account. Irreducible wiring at the existing fetchResetAwareQuotaWithCache call site; the family helper itself lives in antigravityQuotaFamily.ts. Covered by tests/unit/reset-aware-request-scope-12600.test.ts.",
|
||||
"_rebaseline_2026_09_03_overloaded_not_provider_breaker": "fix/overloaded-not-provider-breaker own growth: open-sse/services/combo.ts 4036->4075 (check-file-size split-newline, +39). Circuit-open pre-skip now records the breaker retryAfter and, when every target was skipped that way, waits the short reset via resolveCircuitOpenWaitDecision (new leaf in comboCooldownRetry.ts) instead of crystallizing ALL_TARGETS_SKIPPED in ~43ms. skippedForCircuitOpen / earliestCircuitOpenRetryMs reset each setTry so a later iteration cannot inherit a stale retryAfter. Irreducible at the existing ALL_TARGETS_SKIPPED chokepoint (same pattern as #7301/#8213 cooldown-wait). Predicate itself lives in circuitBreaker.ts / comboPredicates.ts / chatPredicates.ts, all under cap. Covered by tests/unit/overloaded-not-provider-breaker.test.ts + combo-cooldown-retry.test.ts.",
|
||||
"_rebaseline_2026_09_03_12649_free_tier_reaudit_gateways": "PR #12649 (fix/free-tier-quota-reaudit) own growth: src/shared/constants/providers/apikey/gateways.ts 1459->1462 (+3 = the nara authHint rewritten for the re-audited 7M/day plan now wraps to two lines, plus the Prettier reflow of two pre-existing >100-col authHint lines (oneminai, freebuff) that lint-staged enforces on any touch of the file; additive text at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines: #11786 seekai, #10987 logfare, #10531 freebuff). Covered by tests/unit/free-tier-reaudit-2026-09.test.ts and tests/unit/free-providers-batch-2026-07.test.ts.",
|
||||
@@ -210,7 +215,7 @@
|
||||
"_rebaseline_2026_08_24_video_bridge_fu01_fu03_fu04_result_cache_tests": "PRs #11362 (FU-01 cache hardening) + #11382 (FU-03 visual dedup policy identity) + #11383 (FU-04 focused analysis mode) own test growth: videoBridgeResultCache.test.ts <1000->1040, +40 (sum of three stacked PRs boarded together in the same merge-batch, each adding its own cache-identity assertions on the shared result-cache seam). Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).",
|
||||
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
|
||||
"_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
|
||||
"tests/integration/chat-pipeline.test.ts": 1644,
|
||||
"tests/integration/chat-pipeline.test.ts": 1648,
|
||||
"tests/unit/account-fallback-service.test.ts": 2056,
|
||||
"tests/unit/batch_api.test.ts": 1345,
|
||||
"tests/unit/cc-compatible-provider.test.ts": 1225,
|
||||
@@ -426,7 +431,9 @@
|
||||
"open-sse/mcp-server/server.ts": 1572,
|
||||
"open-sse/services/accountFallback.ts": 2467,
|
||||
"open-sse/services/adobeFireflyBrowserLogin.ts": 1401,
|
||||
"open-sse/services/combo.ts": 4084,
|
||||
"open-sse/services/combo.ts": 4080,
|
||||
"open-sse/services/combo/executeTargetAttempt.ts": 1205,
|
||||
"open-sse/translator/response/openai-responses.ts": 1466,
|
||||
"open-sse/utils/cursorAgentProtobuf.ts": 1547,
|
||||
"open-sse/utils/proxyFetch.ts": 1271,
|
||||
@@ -435,12 +442,12 @@
|
||||
"open-sse/vendor/codex-chatgpt-web/bridge.ts": 1335,
|
||||
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1344,
|
||||
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3186,
|
||||
"src/app/(dashboard)/dashboard/combos/page.tsx": 5066,
|
||||
"src/app/(dashboard)/dashboard/combos/page.tsx": 5080,
|
||||
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1319,
|
||||
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2491,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631,
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": 2025,
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1222,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1475,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271,
|
||||
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1607,
|
||||
@@ -456,9 +463,9 @@
|
||||
"src/lib/tailscaleTunnel.ts": 1208,
|
||||
"src/lib/tokenHealthCheck.ts": 1218,
|
||||
"src/shared/components/RequestLoggerV2.tsx": 1718,
|
||||
"src/shared/constants/providers/apikey/gateways.ts": 1480,
|
||||
"src/shared/constants/providers/apikey/gateways.ts": 1462,
|
||||
"src/shared/services/cliRuntime.ts": 1296,
|
||||
"src/sse/handlers/chat.ts": 2454,
|
||||
"src/sse/handlers/chat.ts": 2458,
|
||||
"src/sse/services/auth.ts": 3450,
|
||||
"tests/unit/account-fallback-service.test.ts": 2453,
|
||||
"tests/unit/provider-validation-specialty.test.ts": 4656
|
||||
@@ -576,7 +583,7 @@
|
||||
"src/app/(dashboard)/dashboard/health/page.tsx": "1165",
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": "1324",
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": "1944",
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1201",
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1222",
|
||||
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": "1019",
|
||||
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": "1470",
|
||||
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": "1123",
|
||||
|
||||
@@ -34,7 +34,7 @@ inside GitHub's `<img>` sandbox:
|
||||
| [combo-always-on.svg](./combo-always-on.svg) | style reference | Animated priority-combo fallback (4 layers, 16s loop). Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [cli-terminal.svg](./cli-terminal.svg) | README.md (root) | Compact half-height animated terminal (1200×350): 3 real CLI commands cycling with typewriter + scrolling subcommand ticker; first frame = completed providers screen. Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [compression-pipeline.svg](./compression-pipeline.svg) | README.md (root) | Animated 12-engine compression funnel (8s loop). Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [free-tier-budget.svg](./free-tier-budget.svg) | README.md (root) | Animated free-tier budget card (~1.62B/mo quantified headline, 17-pool + Groq-caps budget bar, per-pool grid, signup credits, 10s loop). Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [free-tier-budget.svg](./free-tier-budget.svg) | README.md (root) | Animated free-tier budget card (~1.47B/mo quantified headline, 16-pool + Groq-caps budget bar, per-pool grid, signup credits, 10s loop). Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [readme-hero.svg](./readme-hero.svg) | README.md (root) | Animated hero card (tagline, live provider/free-access headline, full-width compression bar demo, 6 stat chips). Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [promise-pillars.svg](./promise-pillars.svg) | README.md (root) | Animated "The Promise" 6-pillar card (12s border-highlight sweep). Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [why-pain-fix.svg](./why-pain-fix.svg) | README.md (root) | Animated "Why OmniRoute" 10-row pain-vs-fix ledger (15s green row sweep). Edit the SVG directly — there is no `.mmd` source. |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (357 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 86 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
|
||||
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (356 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 86 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
|
||||
<desc>Compact animated terminal cycling three real OmniRoute CLI commands with a typewriter effect and a scrolling subcommand ticker; the first frame shows the completed providers-list screen.</desc>
|
||||
<defs><clipPath id="tickerClip"><rect x="12" y="304" width="1176" height="40"/></clipPath><clipPath id="tw0"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;31;61;92;122;153;184;214;245;245" keyTimes="0;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw1"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;26;51;76;102;128;153;178;204;204" keyTimes="0;0.348;0.351;0.357;0.363;0.369;0.375;0.381;0.387;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw2"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;20;41;61;82;102;122;143;163;163" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/></rect></clipPath></defs>
|
||||
<rect width="1200" height="350" fill="#0d1117"/>
|
||||
@@ -6,7 +6,7 @@
|
||||
<path d="M 0 34 L 1200 34" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<circle cx="24" cy="17" r="6" fill="#ff5f56"/><circle cx="46" cy="17" r="6" fill="#ffbd2e"/><circle cx="68" cy="17" r="6" fill="#27c93f"/>
|
||||
<text x="600" y="22" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="13" fill="#71717a">omniroute — 86 top-level commands</text>
|
||||
<g font-family="Consolas, 'Courier New', monospace" font-size="17"><animate attributeName="opacity" values="1;0;0" keyTimes="0;0.006;1" dur="18s" repeatCount="indefinite"/><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text><text x="40" y="100" font-weight="700" fill="#38bdf8">OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa">1f3a9c2e  anthropic   Claude Max 20x    <tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa">8c2d5b1a  codex       Codex Pro (team)  <tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa">f4e0a97b  glm         GLM Coding Plan   <tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa">03bd6e5f  kimi        Kimi K2 free      <tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a">… 353 more providers</text></g><g opacity="1" font-family="Consolas, 'Courier New', monospace" font-size="17">
|
||||
<g font-family="Consolas, 'Courier New', monospace" font-size="17"><animate attributeName="opacity" values="1;0;0" keyTimes="0;0.006;1" dur="18s" repeatCount="indefinite"/><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text><text x="40" y="100" font-weight="700" fill="#38bdf8">OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa">1f3a9c2e  anthropic   Claude Max 20x    <tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa">8c2d5b1a  codex       Codex Pro (team)  <tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa">f4e0a97b  glm         GLM Coding Plan   <tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa">03bd6e5f  kimi        Kimi K2 free      <tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a">… 348 more providers</text></g><g opacity="1" font-family="Consolas, 'Courier New', monospace" font-size="17">
|
||||
<animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.315;0.33;1" dur="18s" repeatCount="indefinite"/>
|
||||
<text x="40" y="66" fill="#22c55e">$</text>
|
||||
<g clip-path="url(#tw0)"><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text></g>
|
||||
@@ -14,7 +14,7 @@
|
||||
<animate attributeName="x" calcMode="discrete" values="64;95;125;156;186;217;248;278;309;309" keyTimes="0.000;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0;0;1;0.2;1;0.2;1;0;0" keyTimes="0;0.011;0.012;0.022;0.032;0.042;0.052;0.074;1" dur="18s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.045;0.047" dur="18s" repeatCount="indefinite"/>OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.053;0.055" dur="18s" repeatCount="indefinite"/>1f3a9c2e  anthropic   Claude Max 20x    <tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.061;0.063" dur="18s" repeatCount="indefinite"/>8c2d5b1a  codex       Codex Pro (team)  <tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.069;0.07100000000000001" dur="18s" repeatCount="indefinite"/>f4e0a97b  glm         GLM Coding Plan   <tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.077;0.079" dur="18s" repeatCount="indefinite"/>03bd6e5f  kimi        Kimi K2 free      <tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.085;0.08700000000000001" dur="18s" repeatCount="indefinite"/>… 353 more providers</text>
|
||||
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.045;0.047" dur="18s" repeatCount="indefinite"/>OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.053;0.055" dur="18s" repeatCount="indefinite"/>1f3a9c2e  anthropic   Claude Max 20x    <tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.061;0.063" dur="18s" repeatCount="indefinite"/>8c2d5b1a  codex       Codex Pro (team)  <tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.069;0.07100000000000001" dur="18s" repeatCount="indefinite"/>f4e0a97b  glm         GLM Coding Plan   <tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.077;0.079" dur="18s" repeatCount="indefinite"/>03bd6e5f  kimi        Kimi K2 free      <tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.085;0.08700000000000001" dur="18s" repeatCount="indefinite"/>… 348 more providers</text>
|
||||
</g><g opacity="0" font-family="Consolas, 'Courier New', monospace" font-size="17">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.333;0.34800000000000003;0.648;0.663;1" dur="18s" repeatCount="indefinite"/>
|
||||
<text x="40" y="66" fill="#22c55e">$</text>
|
||||
@@ -32,7 +32,7 @@
|
||||
<animate attributeName="x" calcMode="discrete" values="64;84;105;125;146;166;186;207;227;227" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0;0;1;0.2;1;0.2;1;0;0" keyTimes="0;0.677;0.678;0.688;0.698;0.708;0.718;0.74;1" dur="18s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.711;0.713" dur="18s" repeatCount="indefinite"/>OmniRoute Health</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.719;0.721" dur="18s" repeatCount="indefinite"/>  Status: <tspan fill='#22c55e'>healthy</tspan>   Uptime: 4d 12h 33m</text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.727;0.729" dur="18s" repeatCount="indefinite"/>  Requests (24h): 18,412   p95: 412ms</text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.735;0.737" dur="18s" repeatCount="indefinite"/>  Breakers: <tspan fill='#22c55e'>● 24 closed</tspan>  <tspan fill='#f59e0b'>◒ 1 half-open</tspan>  <tspan fill='#ef4444'>○ 0 open</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.743;0.745" dur="18s" repeatCount="indefinite"/>  Providers: 357 registered   150+ free tiers</text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.751;0.753" dur="18s" repeatCount="indefinite"/>… live: /dashboard · omniroute status</text>
|
||||
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.711;0.713" dur="18s" repeatCount="indefinite"/>OmniRoute Health</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.719;0.721" dur="18s" repeatCount="indefinite"/>  Status: <tspan fill='#22c55e'>healthy</tspan>   Uptime: 4d 12h 33m</text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.727;0.729" dur="18s" repeatCount="indefinite"/>  Requests (24h): 18,412   p95: 412ms</text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.735;0.737" dur="18s" repeatCount="indefinite"/>  Breakers: <tspan fill='#22c55e'>● 24 closed</tspan>  <tspan fill='#f59e0b'>◒ 1 half-open</tspan>  <tspan fill='#ef4444'>○ 0 open</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.743;0.745" dur="18s" repeatCount="indefinite"/>  Providers: 350 registered   90+ free tiers</text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.751;0.753" dur="18s" repeatCount="indefinite"/>… live: /dashboard · omniroute status</text>
|
||||
</g>
|
||||
<path d="M 0 300 L 1200 300" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<g clip-path="url(#tickerClip)"><g font-family="Consolas, 'Courier New', monospace" font-size="14" fill="#71717a">
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 357 providers, 150+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
|
||||
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 356 providers, 150+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 42 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
|
||||
<desc>Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses.</desc>
|
||||
<defs>
|
||||
<pattern id="gC" width="32" height="32" patternUnits="userSpaceOnUse"><path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.05" stroke-width="1"/></pattern>
|
||||
@@ -23,7 +23,7 @@
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.15s" fill="freeze"/>
|
||||
<text x="44" y="196" font-size="14.5" fill="#c9d1d9">Providers</text>
|
||||
<text x="440" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">357</text>
|
||||
<text x="440" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">352</text>
|
||||
<text x="604" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">40+</text>
|
||||
<text x="760" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">400+*</text>
|
||||
<text x="916" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">~5</text>
|
||||
@@ -32,7 +32,7 @@
|
||||
<rect x="36" y="212" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.24s" fill="freeze"/>
|
||||
<text x="44" y="238" font-size="14.5" fill="#c9d1d9">Free providers built-in</text>
|
||||
<text x="440" y="238" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">150+</text>
|
||||
<text x="440" y="238" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">90+</text>
|
||||
<use href="#yes" x="604" y="233"/>
|
||||
<use href="#mid" x="760" y="233"/>
|
||||
<use href="#no" x="916" y="233"/>
|
||||
@@ -117,7 +117,7 @@
|
||||
<rect x="36" y="632" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="1.14s" fill="freeze"/>
|
||||
<text x="44" y="658" font-size="14.5" fill="#c9d1d9">i18n UI locales</text>
|
||||
<text x="440" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">43</text>
|
||||
<text x="440" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">42</text>
|
||||
<text x="604" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">6</text>
|
||||
<use href="#no" x="760" y="653"/>
|
||||
<use href="#no" x="916" y="653"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,5 +1,5 @@
|
||||
<svg viewBox="0 0 1200 842" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute free-tier budget: about 1.62 billion free tokens per month steady, up to about 2.25 billion in the first month with signup credits. The catalog contains 483 rows, 476 active and 7 discontinued, grouped into 35 recurring pool keys; 17 pools have a published positive monthly token budget and 18 have a zero, uncapped, or keyless budget. Honest pool-deduped math counts each shared free pool once; 13 providers carry a terms-of-service avoid flag. The 17 quantified pools plus five per-model Groq caps are Mistral 1 billion, Nara 210 million, LLM7 150 million, xKiro 150 million, Groq 30 million across five per-model caps, Cloudflare AI 30 million, API Airforce 24 million, Bluesminds 7.2 million, SambaNova 6 million, Arcee 4.8 million, Navy 4.5 million, BazaarLink 3.6 million, OpenRouter 1.2 million, Cohere 800 thousand, HuggingChat 500 thousand, Morph 400 thousand, Hugging Face 200 thousand, and Kiro 25 thousand. One-time signup credits add about 626 million. Uncapped providers and the OpenRouter top-up boost are shown separately so they do not inflate the headline. A further 6 million behind regional identity verification (ModelScope) is also shown apart. Live usage remains available at /dashboard/free-tiers.">
|
||||
<desc>Pool-deduplicated chart of the 17 recurring free-token pools with positive published budgets (plus Groq's five per-model caps as one segment), plus signup credits and uncapped providers shown separately.</desc>
|
||||
<svg viewBox="0 0 1200 842" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute free-tier budget: about 1.47 billion free tokens per month steady, up to about 2.10 billion in the first month with signup credits. The catalog contains 444 rows, 437 active and 7 discontinued, grouped into 34 recurring pool keys; 16 pools have a published positive monthly token budget and 18 have a zero, uncapped, or keyless budget. Honest pool-deduped math counts each shared free pool once; 13 providers carry a terms-of-service avoid flag. The 16 quantified pools plus five per-model Groq caps are Mistral 1 billion, Nara 210 million, LLM7 150 million, Groq 30 million across five per-model caps, Cloudflare AI 30 million, API Airforce 24 million, Bluesminds 7.2 million, SambaNova 6 million, Arcee 4.8 million, Navy 4.5 million, BazaarLink 3.6 million, OpenRouter 1.2 million, Cohere 800 thousand, HuggingChat 500 thousand, Morph 400 thousand, Hugging Face 200 thousand, and Kiro 25 thousand. One-time signup credits add about 626 million. Uncapped providers and the OpenRouter top-up boost are shown separately so they do not inflate the headline. A further 6 million behind regional identity verification (ModelScope) is also shown apart. Live usage remains available at /dashboard/free-tiers.">
|
||||
<desc>Pool-deduplicated chart of the 16 recurring free-token pools with positive published budgets (plus Groq's five per-model caps as one segment), plus signup credits and uncapped providers shown separately.</desc>
|
||||
<defs>
|
||||
<pattern id="gridPaperF" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||
<path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.06" stroke-width="1"/>
|
||||
@@ -61,10 +61,10 @@
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="2.4s" begin="1.6s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<text x="60" y="228" font-family="Consolas, 'Courier New', monospace" font-size="104" font-weight="800" fill="url(#gradBrandF)">~1.62B</text>
|
||||
<text x="60" y="228" font-family="Consolas, 'Courier New', monospace" font-size="104" font-weight="800" fill="url(#gradBrandF)">~1.47B</text>
|
||||
<text x="62" y="266" font-family="Consolas, 'Courier New', monospace" font-size="15" letter-spacing="3" font-weight="700" fill="#a1a1aa">FREE TOKENS / MONTH · <tspan fill="#22c55e">STEADY</tspan></text>
|
||||
<text x="62" y="298" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16" fill="#F7F6FC">up to <tspan font-weight="800" fill="#22c55e">~2.25B</tspan> in your first month — signup credits</text>
|
||||
<text x="62" y="326" font-family="Consolas, 'Courier New', monospace" font-size="12" fill="#71717a">documented free tiers · <tspan fill="#8b5cf6">35 recurring pools</tspan> · <tspan fill="#8b5cf6">483 catalog entries</tspan> · one endpoint</text>
|
||||
<text x="62" y="298" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16" fill="#F7F6FC">up to <tspan font-weight="800" fill="#22c55e">~2.10B</tspan> in your first month — signup credits</text>
|
||||
<text x="62" y="326" font-family="Consolas, 'Courier New', monospace" font-size="12" fill="#71717a">documented free tiers · <tspan fill="#8b5cf6">34 recurring pools</tspan> · <tspan fill="#8b5cf6">444 catalog entries</tspan> · one endpoint</text>
|
||||
|
||||
<!-- ═══ Panel · The honest math ═══ -->
|
||||
<rect x="680" y="84" width="460" height="216" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
@@ -75,7 +75,7 @@
|
||||
</line>
|
||||
<text x="836" y="156" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#a1a1aa">every rate limit · 24/7</text>
|
||||
<text x="836" y="176" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#ef4444" opacity="0.85">we don't publish that</text>
|
||||
<text x="704" y="240" font-family="Consolas, 'Courier New', monospace" font-size="34" font-weight="800" fill="#22c55e">~1.62B</text>
|
||||
<text x="704" y="240" font-family="Consolas, 'Courier New', monospace" font-size="34" font-weight="800" fill="#22c55e">~1.47B</text>
|
||||
<text x="836" y="224" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#a1a1aa">each shared free pool</text>
|
||||
<text x="836" y="244" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#22c55e">counted once ✓</text>
|
||||
<text x="704" y="280" font-family="Consolas, 'Courier New', monospace" font-size="12" fill="#f59e0b"><tspan font-weight="800">13 providers</tspan> ToS-flagged <tspan fill="#71717a">— we flag it · you decide</tspan></text>
|
||||
@@ -85,23 +85,22 @@
|
||||
<g clip-path="url(#barShapeF)">
|
||||
<rect x="60" y="372" width="1080" height="18" fill="#1c2230"/>
|
||||
<g clip-path="url(#barRevF)">
|
||||
<rect x="60.0" y="372" width="579.0" height="18" fill="#6c5ce7"/>
|
||||
<rect x="640.0" y="372" width="127.6" height="18" fill="#00b894"/>
|
||||
<rect x="768.6" y="372" width="93.3" height="18" fill="#0984e3"/>
|
||||
<rect x="862.9" y="372" width="93.3" height="18" fill="#e17055"/>
|
||||
<rect x="957.2" y="372" width="24.7" height="18" fill="#fdcb6e"/>
|
||||
<rect x="982.9" y="372" width="24.7" height="18" fill="#e84393"/>
|
||||
<rect x="1008.6" y="372" width="21.2" height="18" fill="#00cec9"/>
|
||||
<rect x="1030.8" y="372" width="11.6" height="18" fill="#d63031"/>
|
||||
<rect x="1043.4" y="372" width="10.9" height="18" fill="#a29bfe"/>
|
||||
<rect x="1055.3" y="372" width="10.2" height="18" fill="#55efc4"/>
|
||||
<rect x="1066.5" y="372" width="10.1" height="18" fill="#74b9ff"/>
|
||||
<rect x="1077.6" y="372" width="9.6" height="18" fill="#ffeaa7"/>
|
||||
<rect x="1088.2" y="372" width="8.2" height="18" fill="#fab1a0"/>
|
||||
<rect x="1097.4" y="372" width="8.0" height="18" fill="#81ecec"/>
|
||||
<rect x="1106.4" y="372" width="7.8" height="18" fill="#6c5ce7"/>
|
||||
<rect x="1115.2" y="372" width="7.7" height="18" fill="#00b894"/>
|
||||
<rect x="1123.9" y="372" width="7.6" height="18" fill="#0984e3"/>
|
||||
<rect x="60.0" y="372" width="686.0" height="18" fill="#6c5ce7"/>
|
||||
<rect x="747.0" y="372" width="139.7" height="18" fill="#00b894"/>
|
||||
<rect x="887.7" y="372" width="99.8" height="18" fill="#0984e3"/>
|
||||
<rect x="988.5" y="372" width="20.0" height="18" fill="#e17055"/>
|
||||
<rect x="1009.5" y="372" width="20.0" height="18" fill="#e84393"/>
|
||||
<rect x="1030.5" y="372" width="16.0" height="18" fill="#00cec9"/>
|
||||
<rect x="1047.5" y="372" width="7.5" height="18" fill="#d63031"/>
|
||||
<rect x="1056.0" y="372" width="7.5" height="18" fill="#a29bfe"/>
|
||||
<rect x="1064.5" y="372" width="7.5" height="18" fill="#55efc4"/>
|
||||
<rect x="1073.0" y="372" width="7.5" height="18" fill="#74b9ff"/>
|
||||
<rect x="1081.5" y="372" width="7.5" height="18" fill="#ffeaa7"/>
|
||||
<rect x="1090.0" y="372" width="7.5" height="18" fill="#fab1a0"/>
|
||||
<rect x="1098.5" y="372" width="7.5" height="18" fill="#81ecec"/>
|
||||
<rect x="1107.0" y="372" width="7.5" height="18" fill="#6c5ce7"/>
|
||||
<rect x="1115.5" y="372" width="7.5" height="18" fill="#00b894"/>
|
||||
<rect x="1124.0" y="372" width="7.5" height="18" fill="#0984e3"/>
|
||||
<rect x="1132.5" y="372" width="7.5" height="18" fill="#e17055"/>
|
||||
</g>
|
||||
</g>
|
||||
@@ -116,21 +115,20 @@
|
||||
<circle cx="66" cy="452" r="5" fill="#6c5ce7"/><text x="78" y="456" fill="#c9d1d9">Mistral <tspan fill="#71717a">1.00B</tspan></text>
|
||||
<circle cx="346" cy="452" r="5" fill="#00b894"/><text x="358" y="456" fill="#c9d1d9">Nara <tspan fill="#71717a">210M</tspan></text>
|
||||
<circle cx="626" cy="452" r="5" fill="#0984e3"/><text x="638" y="456" fill="#c9d1d9">LLM7 <tspan fill="#71717a">150M</tspan></text>
|
||||
<circle cx="906" cy="452" r="5" fill="#e17055"/><text x="918" y="456" fill="#c9d1d9">xKiro <tspan fill="#71717a">150M</tspan></text>
|
||||
<circle cx="66" cy="482" r="5" fill="#fdcb6e"/><text x="78" y="486" fill="#c9d1d9">Groq <tspan fill="#71717a">30M · 5 caps</tspan></text>
|
||||
<circle cx="346" cy="482" r="5" fill="#e84393"/><text x="358" y="486" fill="#c9d1d9">Cloudflare AI <tspan fill="#71717a">30M</tspan></text>
|
||||
<circle cx="626" cy="482" r="5" fill="#00cec9"/><text x="638" y="486" fill="#c9d1d9">API Airforce <tspan fill="#71717a">24M</tspan></text>
|
||||
<circle cx="906" cy="482" r="5" fill="#d63031"/><text x="918" y="486" fill="#c9d1d9">Bluesminds <tspan fill="#71717a">7.2M</tspan></text>
|
||||
<circle cx="66" cy="512" r="5" fill="#a29bfe"/><text x="78" y="516" fill="#c9d1d9">SambaNova <tspan fill="#71717a">6M</tspan></text>
|
||||
<circle cx="346" cy="512" r="5" fill="#55efc4"/><text x="358" y="516" fill="#c9d1d9">Arcee <tspan fill="#71717a">4.8M</tspan></text>
|
||||
<circle cx="626" cy="512" r="5" fill="#74b9ff"/><text x="638" y="516" fill="#c9d1d9">Navy <tspan fill="#71717a">4.5M</tspan></text>
|
||||
<circle cx="906" cy="512" r="5" fill="#ffeaa7"/><text x="918" y="516" fill="#c9d1d9">BazaarLink <tspan fill="#71717a">3.6M</tspan></text>
|
||||
<circle cx="66" cy="542" r="5" fill="#fab1a0"/><text x="78" y="546" fill="#c9d1d9">OpenRouter <tspan fill="#71717a">1.2M</tspan></text>
|
||||
<circle cx="346" cy="542" r="5" fill="#81ecec"/><text x="358" y="546" fill="#c9d1d9">Cohere <tspan fill="#71717a">800K</tspan></text>
|
||||
<circle cx="626" cy="542" r="5" fill="#6c5ce7"/><text x="638" y="546" fill="#c9d1d9">HuggingChat <tspan fill="#71717a">500K</tspan></text>
|
||||
<circle cx="906" cy="542" r="5" fill="#00b894"/><text x="918" y="546" fill="#c9d1d9">Morph <tspan fill="#71717a">400K</tspan></text>
|
||||
<circle cx="66" cy="572" r="5" fill="#0984e3"/><text x="78" y="576" fill="#c9d1d9">Hugging Face <tspan fill="#71717a">200K</tspan></text>
|
||||
<circle cx="346" cy="572" r="5" fill="#e17055"/><text x="358" y="576" fill="#c9d1d9">Kiro <tspan fill="#71717a">25K</tspan></text>
|
||||
<circle cx="906" cy="452" r="5" fill="#e17055"/><text x="918" y="456" fill="#c9d1d9">Groq <tspan fill="#71717a">30M · 5 caps</tspan></text>
|
||||
<circle cx="66" cy="482" r="5" fill="#e84393"/><text x="78" y="486" fill="#c9d1d9">Cloudflare AI <tspan fill="#71717a">30M</tspan></text>
|
||||
<circle cx="346" cy="482" r="5" fill="#00cec9"/><text x="358" y="486" fill="#c9d1d9">API Airforce <tspan fill="#71717a">24M</tspan></text>
|
||||
<circle cx="626" cy="482" r="5" fill="#d63031"/><text x="638" y="486" fill="#c9d1d9">Bluesminds <tspan fill="#71717a">7.2M</tspan></text>
|
||||
<circle cx="906" cy="482" r="5" fill="#a29bfe"/><text x="918" y="486" fill="#c9d1d9">SambaNova <tspan fill="#71717a">6M</tspan></text>
|
||||
<circle cx="66" cy="512" r="5" fill="#55efc4"/><text x="78" y="516" fill="#c9d1d9">Arcee <tspan fill="#71717a">4.8M</tspan></text>
|
||||
<circle cx="346" cy="512" r="5" fill="#74b9ff"/><text x="358" y="516" fill="#c9d1d9">Navy <tspan fill="#71717a">4.5M</tspan></text>
|
||||
<circle cx="626" cy="512" r="5" fill="#ffeaa7"/><text x="638" y="516" fill="#c9d1d9">BazaarLink <tspan fill="#71717a">3.6M</tspan></text>
|
||||
<circle cx="906" cy="512" r="5" fill="#fab1a0"/><text x="918" y="516" fill="#c9d1d9">OpenRouter <tspan fill="#71717a">1.2M</tspan></text>
|
||||
<circle cx="66" cy="542" r="5" fill="#81ecec"/><text x="78" y="546" fill="#c9d1d9">Cohere <tspan fill="#71717a">800K</tspan></text>
|
||||
<circle cx="346" cy="542" r="5" fill="#6c5ce7"/><text x="358" y="546" fill="#c9d1d9">HuggingChat <tspan fill="#71717a">500K</tspan></text>
|
||||
<circle cx="626" cy="542" r="5" fill="#00b894"/><text x="638" y="546" fill="#c9d1d9">Morph <tspan fill="#71717a">400K</tspan></text>
|
||||
<circle cx="906" cy="542" r="5" fill="#0984e3"/><text x="918" y="546" fill="#c9d1d9">Hugging Face <tspan fill="#71717a">200K</tspan></text>
|
||||
<circle cx="66" cy="572" r="5" fill="#e17055"/><text x="78" y="576" fill="#c9d1d9">Kiro <tspan fill="#71717a">25K</tspan></text>
|
||||
</g>
|
||||
|
||||
<!-- ═══ First-month signup credits ═══ -->
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 357 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 150+ providers with a free tier and 53 recurring or keyless free-forever providers. Every tool works: 36 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
|
||||
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 356 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 150+ providers with a free tier and 52 recurring or keyless free-forever providers. Every tool works: 36 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
|
||||
<desc>Animated promise card: six pillar tiles fade in in reading order, then a soft colored border highlight sweeps from tile to tile in a continuous cycle.</desc>
|
||||
<defs>
|
||||
<pattern id="gridPaperP" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||
@@ -21,7 +21,7 @@
|
||||
<line x1="150" y1="53" x2="1160" y2="53" stroke="#232b38" stroke-width="1.5"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">357 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
|
||||
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">356 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
|
||||
</g>
|
||||
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
|
||||
@@ -38,7 +38,7 @@
|
||||
<line x1="3.9" y1="3.9" x2="18.1" y2="18.1"/>
|
||||
</g>
|
||||
<text x="102" y="170" font-size="18" font-weight="800" fill="#74b9ff">Never hit limits</text>
|
||||
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 357 providers in</text>
|
||||
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 356 providers in</text>
|
||||
<text x="66" y="226" font-size="13.5" fill="#a1a1aa">milliseconds. Quota out? The next provider</text>
|
||||
<text x="66" y="248" font-size="13.5" fill="#a1a1aa">takes over while a healthy target remains.</text>
|
||||
</g>
|
||||
@@ -73,7 +73,7 @@
|
||||
<circle cx="6.6" cy="6.6" r="1.4" fill="#fdcb6e" stroke="none"/>
|
||||
</g>
|
||||
<text x="862" y="170" font-size="18" font-weight="800" fill="#fdcb6e">$0 to start</text>
|
||||
<text x="826" y="204" font-size="13.5" fill="#a1a1aa">150+ providers with a free tier, 53 free</text>
|
||||
<text x="826" y="204" font-size="13.5" fill="#a1a1aa">150+ providers with a free tier, 52 free</text>
|
||||
<text x="826" y="226" font-size="13.5" fill="#a1a1aa">forever — Qoder, Pollinations, Cloudflare,</text>
|
||||
<text x="826" y="248" font-size="13.5" fill="#a1a1aa">SiliconFlow… No card needed.</text>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 357 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 357 AI providers, 150+ free tiers, about 1.62B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
|
||||
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 356 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 356 AI providers, 150+ free tiers, about 1.47B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
|
||||
<desc>Animated hero card: a pulse travels the divider line and a compression bar demo repeatedly shrinks a prompt by up to 95 percent; all headline content is static and readable on the first frame.</desc>
|
||||
<defs>
|
||||
<pattern id="gridPaperH" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||
@@ -28,7 +28,7 @@
|
||||
<text x="48" y="138" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="60" font-weight="800" fill="#e9edf3">Never stop coding<tspan fill="#a855f7">.</tspan></text>
|
||||
|
||||
<!-- subheadline -->
|
||||
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">357 providers</tspan> — <tspan fill="#7ee787" font-weight="800">150+ free</tspan> — through one endpoint.</text>
|
||||
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">356 providers</tspan> — <tspan fill="#7ee787" font-weight="800">150+ free</tspan> — through one endpoint.</text>
|
||||
|
||||
<!-- plug line -->
|
||||
<text x="48" y="222" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16.5" fill="#a1a1aa">Claude Code · Codex · Cursor · Cline · Copilot · Antigravity  →  <tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>
|
||||
@@ -66,13 +66,13 @@
|
||||
<!-- stat chips -->
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" text-anchor="middle">
|
||||
<rect x="48" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#6c5ce7" stroke-opacity="0.55" stroke-width="1.5"/>
|
||||
<text x="134" y="471" font-size="17" font-weight="800" fill="#a78bfa">357</text>
|
||||
<text x="134" y="471" font-size="17" font-weight="800" fill="#a78bfa">352</text>
|
||||
<text x="134" y="490" font-size="11" fill="#a1a1aa">AI PROVIDERS</text>
|
||||
<rect x="234" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#22c55e" stroke-opacity="0.55" stroke-width="1.5"/>
|
||||
<text x="320" y="471" font-size="17" font-weight="800" fill="#7ee787">150+</text>
|
||||
<text x="320" y="471" font-size="17" font-weight="800" fill="#7ee787">90+</text>
|
||||
<text x="320" y="490" font-size="11" fill="#a1a1aa">FREE TIERS</text>
|
||||
<rect x="420" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#22c55e" stroke-opacity="0.55" stroke-width="1.5"/>
|
||||
<text x="506" y="471" font-size="17" font-weight="800" fill="#7ee787">~1.62B</text>
|
||||
<text x="506" y="471" font-size="17" font-weight="800" fill="#7ee787">~1.47B</text>
|
||||
<text x="506" y="490" font-size="11" fill="#a1a1aa">FREE TOKENS / MO</text>
|
||||
<rect x="606" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#e17055" stroke-opacity="0.55" stroke-width="1.5"/>
|
||||
<text x="692" y="471" font-size="17" font-weight="800" fill="#e17055">15–95%</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
@@ -252,7 +252,7 @@ Supports **19 routing strategies** (see `src/shared/constants/routingStrategies.
|
||||
|
||||
### base.ts (1170 LOC)
|
||||
|
||||
The **abstract executor** that all 107 executors extend. It contains:
|
||||
The **abstract executor** that all 101 executors extend. It contains:
|
||||
|
||||
- `buildUrl()` — default URL construction (subclasses override for custom)
|
||||
- `buildHeaders()` — default headers (auth, content-type)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Free Tiers Guide: Understand and Combine Free AI Access
|
||||
|
||||
> **TL;DR**: OmniRoute registers 357 provider IDs, with **152 provider-catalog entries marked `hasFree`**. The stricter audited free-model catalog covers **35 recurring pool keys / 483 entries** (476 active + 7 discontinued). Connect several suitable providers for broader fallback capacity; every quota, approval rule, privacy policy, and paid-overage condition still applies.
|
||||
> **TL;DR**: OmniRoute registers 352 provider IDs, with **152 provider-catalog entries marked `hasFree`**. The stricter audited free-model catalog covers **34 recurring pool keys / 444 entries** (437 active + 7 discontinued). Connect several suitable providers for broader fallback capacity; every quota, approval rule, privacy policy, and paid-overage condition still applies.
|
||||
|
||||
---
|
||||
|
||||
@@ -161,11 +161,11 @@ The live, pool-deduplicated catalog currently reports:
|
||||
|
||||
| Metric | Current audited value | Interpretation |
|
||||
| ---------------------------------------------------- | -----------------------------------------------: | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Recurring quantified grant | **~1.62B tokens/month** | Shared pools counted once; excludes uncapped providers from the sum |
|
||||
| First month with signup grants | **~2.25B tokens** | Recurring total plus one-time and recurring credits |
|
||||
| Audited free-model inventory | **35 recurring pool keys / 483 catalog entries** | 476 active + 7 discontinued; distinct from the 357-provider catalog |
|
||||
| Recurring/keyless free-forever providers represented | **53** | Unique providers across recurring daily/monthly/credit/uncapped and keyless catalog types, eligibility-gated rows excluded |
|
||||
| Provider catalog entries marked `hasFree` | **152 / 357** | Broader provider metadata; not all have a quantifiable recurring quota |
|
||||
| Recurring quantified grant | **~1.47B tokens/month** | Shared pools counted once; excludes uncapped providers from the sum |
|
||||
| First month with signup grants | **~2.10B tokens** | Recurring total plus one-time and recurring credits |
|
||||
| Audited free-model inventory | **34 recurring pool keys / 444 catalog entries** | 437 active + 7 discontinued; distinct from the 352-provider catalog |
|
||||
| Recurring/keyless free-forever providers represented | **52** | Unique providers across recurring daily/monthly/credit/uncapped and keyless catalog types, eligibility-gated rows excluded |
|
||||
| Provider catalog entries marked `hasFree` | **152 / 352** | Broader provider metadata; not all have a quantifiable recurring quota |
|
||||
|
||||
These values are computed from `open-sse/config/freeModelCatalog.ts`; see the
|
||||
[Free Tiers Reference](../reference/FREE_TIERS.md) for pool deduplication, ToS flags,
|
||||
|
||||
@@ -58,8 +58,8 @@ OmniRoute includes a service worker (`sw.js`) that provides intelligent caching:
|
||||
| **App Shell** | Cache-first | `/`, `/offline`, manifest, and icons are pre-cached on install |
|
||||
| **Static assets** (CSS, JS, images, fonts) | Network-first with cache fallback | Fetches fresh from the network; falls back to cache if offline |
|
||||
| **Next.js bundles** (`/_next/`) | Network-first with cache update | Fetches from network and updates cache; serves cached version if offline |
|
||||
| **Navigation requests** | Network-only with offline fallback | Always fetches from network; shows `/offline` page if network is unavailable |
|
||||
| **API routes** (`/api/`, `/a2a`, `/dashboard/endpoint`) | Bypass (never cached) | Always goes directly to the server — never intercepted by the service worker |
|
||||
| **Navigation requests** | Bypass (never intercepted) | Browser owns HTTP/3→HTTP/2 fallback; a dead QUIC socket must not become `Response.error()` |
|
||||
| **API / dashboard routes** (`/api/`, `/a2a`, `/dashboard`) | Bypass (never cached) | Always goes directly to the server — never intercepted by the service worker |
|
||||
|
||||
### Offline Page
|
||||
|
||||
@@ -118,7 +118,7 @@ A vanilla service worker (no framework dependencies) with:
|
||||
- **Install phase**: Pre-caches the app shell (root, offline page, manifest, icons)
|
||||
- **Activate phase**: Cleans up old cache versions and claims all clients
|
||||
- **Fetch phase**: Intelligent routing based on request type (navigation, static asset, API)
|
||||
- **Cache versioning**: `omniroute-pwa-v2` — bump this to force a fresh cache on update
|
||||
- **Cache versioning**: `omniroute-pwa-v3` — bump this to force a fresh cache on update
|
||||
|
||||
### Layout Metadata (`src/app/layout.tsx`)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -15,14 +15,14 @@ lastUpdated: 2026-09-03
|
||||
|
||||
| Metric | Tokens / month | Meaning |
|
||||
| ------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Documented recurring grant (steady)** | **~1.62B** | Free-tier **pools** (per-model catalog), each shared pool counted **once**. The live source behind `/api/free-tier/summary` and the dashboard's Free-Tier Budget page. **Use this number.** |
|
||||
| **+ first month with signup credits** | **~2.25B** | Steady + one-time signup credits (Together $25, Z.AI 20M, DeepSeek 5M, …), deduped per account. **First month only** — does not recur. |
|
||||
| **Documented recurring grant (steady)** | **~1.47B** | Free-tier **pools** (per-model catalog), each shared pool counted **once**. The live source behind `/api/free-tier/summary` and the dashboard's Free-Tier Budget page. **Use this number.** |
|
||||
| **+ first month with signup credits** | **~2.10B** | Steady + one-time signup credits (Together $25, Z.AI 20M, DeepSeek 5M, …), deduped per account. **First month only** — does not recur. |
|
||||
| **+ permanently free, no published cap** | _un-quantifiable_ | `siliconflow`, `glm-cn` (GLM-4-Flash), `tencent`, `baidu`, `kilo-gateway`, `opencode-zen`, `gemini`, `ollama-cloud` — real recurring access, rate/concurrency-limited, **no token cap to count**. Listed, never summed (counting them at `RPM×24/7` is the inflation we reject). |
|
||||
| **+ deposit-unlock boost** | **+~24M** | A one-time **$10** OpenRouter top-up raises its free pool from 50 → 1000 req/day. Reported separately so it never inflates the steady number. |
|
||||
| **+ behind a regional identity check** | **+~6M** | `modelscope` (Alibaba Cloud binding + mainland-China real-name verification). Real recurring quota, exposed as `gatedRecurringTokens` / `gatedProviders` and on the dashboard. Never summed into the headline: +~6M behind regional identity verification. |
|
||||
| Theoretical ceiling (all rate limits, 24/7) | ~10B | Sum of every provider rate limit extrapolated to non-stop use. **Not a guarantee** — do not headline this. |
|
||||
|
||||
**Honest headline:** _OmniRoute aggregates **~1.62B documented free tokens per month** (up to ~2.25B in your first month with signup credits) across 35 free-tier pools — plus a long tail of permanently-free, no-cap providers — and RTK + Caveman compression (15–95% token savings) stretches that further._
|
||||
**Honest headline:** _OmniRoute aggregates **~1.47B documented free tokens per month** (up to ~2.10B in your first month with signup credits) across 34 free-tier pools — plus a long tail of permanently-free, no-cap providers — and RTK + Caveman compression (15–95% token savings) stretches that further._
|
||||
|
||||
> **Why this dropped from the previous ~1.94B.** The 2026-06-17 refresh is an honesty correction, not a loss: `gemini` is now pool-deduped (was inflated by counting each Flash variant separately, 462M → 60M), `cloudflare-ai` corrected to its real 10k-Neurons/day (122M → 30M), `doubao` reclassified as a one-time signup credit (not recurring), and shut-down tiers removed (`chutes`/`phind`/`kluster` discontinued). Partly offset by `llm7` (correct 5M/day → 150M) and new free providers (Kilo, OpenCode Zen, Z.AI GLM-Flash).
|
||||
>
|
||||
@@ -30,13 +30,11 @@ lastUpdated: 2026-09-03
|
||||
>
|
||||
> **Updated on 2026-08-26 after retiring Felo Web:** Felo Web is excluded while its GPL-derived provenance/licensing remains on HOLD; the source reported 38 pool keys at the time. The pool count is live and CI-gated (`check:docs-counts` fails the build if the numbers above drift from `computeFreeModelTotals()`).
|
||||
>
|
||||
> **Re-audited on 2026-09-02 against the providers' own pages** (sources: the `// evidence:` comments next to each re-audited entry in `open-sse/config/freeModelCatalog.data.ts`): `gemini` and `ollama-cloud` no longer publish a token figure (Google removed the per-model free table on 2025-12-23; Ollama's Free plan is "starter usage credits") and are now listed as **uncapped**, never summed (−80M); `groq` is five **per-model** 200K-TPD caps (6M each, +15M) with three retired IDs dropped; `nara` is one 7M/day bucket (+60M, 210M). `mistral`'s 1B is visible only in the account console — see _Evidence classes_ under Methodology.
|
||||
> **Re-audited on 2026-09-02 against the providers' own pages** (sources: the `// evidence:` comments next to each re-audited entry in `open-sse/config/freeModelCatalog.data.ts`): `gemini` and `ollama-cloud` no longer publish a token figure (Google removed the per-model free table on 2025-12-23; Ollama's Free plan is "starter usage credits") and are now listed as **uncapped**, never summed (−80M); `groq` is five **per-model** 200K-TPD caps (6M each, +15M) with three retired IDs dropped; `nara` is one 7M/day bucket (+60M, 210M). `mistral`'s 1B is visible only in the account console — see _Evidence classes_ under Methodology. The source reported 35 such keys at that point (−3: `gemini` and `ollama-cloud` moved to the uncapped list, and Groq's per-model caps are not a shared pool).
|
||||
>
|
||||
> **Corrected on 2026-09-03 (#11773):** `cerebras` was reclassified from a 30M/mo recurring grant (old no-card 1M tokens/day trial) to a one-time $5 signup credit that requires a payment method. Same honesty rule as LongCat.
|
||||
>
|
||||
> **Plus xKiro (2026-09-03):** the new `xkiro-free` pool (150M/mo) adds a 35th recurring pool key. Felo Web stays excluded while its GPL-derived provenance/licensing remains on HOLD. The source now reports **35 recurring pool keys** and **~1.62B steady** — the live, CI-gated number (`check:docs-counts` fails the build if this drifts from `computeFreeModelTotals()`).
|
||||
> **Corrected to ~1.47B on 2026-09-03 (#11773):** `cerebras` was reclassified from a 30M/mo recurring grant (old no-card 1M tokens/day trial) to a one-time $5 signup credit that requires a payment method. Same honesty rule as LongCat. The source now reports 34 recurring pool keys and ~1.47B steady.
|
||||
|
||||
Biggest **documented** contributors: `mistral` 1.00B, `nara` 210M, `llm7` 150M, `xkiro` 150M, `groq` 30M (five per-model caps), `cloudflare-ai` 30M, `api-airforce` 24M. (`longcat` is excluded — its 10M LongCat-2.0 grant is a one-time, KYC-gated signup credit, not a recurring monthly budget.)
|
||||
Biggest **documented** contributors: `mistral` 1.00B, `nara` 210M, `llm7` 150M, `groq` 30M (five per-model caps), `cloudflare-ai` 30M, `api-airforce` 24M. (`longcat` is excluded — its 10M LongCat-2.0 grant is a one-time, KYC-gated signup credit, not a recurring monthly budget.)
|
||||
|
||||
> ⚠️ The theoretical ceiling (~10B) is inflated by rate-limit-only providers with **no published token cap** (`tencent`, `siliconflow`, `nvidia`, `baidu`, `glm-cn`, `sparkdesk`) whose figures would be `RPM/TPM × 24/7 × 30d` — a theoretical maximum no single account will sustain. They are **excluded** from the defensible number (shown in the "permanently free, no cap" row instead). This is the same inflation that makes competitors' multi-billion claims unreliable.
|
||||
|
||||
@@ -76,7 +74,7 @@ purpose.
|
||||
## Methodology & caveats
|
||||
|
||||
- Numbers are **upper-bound estimates** from each provider's documented free-tier limits as of **2026-06-17**, gathered by web research. Free tiers change constantly — re-verify before relying on a figure.
|
||||
- **What an entry actually vouches for.** No entry carries a per-row confidence rating, and the API serves none — treat every figure above as an estimate of the same, unstated quality. Two facts are different, because they are curated by hand rather than inferred: 44 entries carry an independently documented hard stop (39 of them the xKiro rows, which all share one daily allowance), and 13 entries carry a prompt-training disclosure. `hardStopGuaranteed` is set only when the provider's own terms say that exceeding the free allowance refuses the request rather than silently starting to bill you, with the source in a comment next to the entry; it is never defaulted to `true`, and an entry nobody has verified stays unset. So a missing hard-stop flag means "not established", not "known to bill you".
|
||||
- **What an entry actually vouches for.** No entry carries a per-row confidence rating, and the API serves none — treat every figure above as an estimate of the same, unstated quality. Two facts are different, because they are curated by hand rather than inferred: 5 entries carry an independently documented hard stop, and 13 entries carry a prompt-training disclosure. `hardStopGuaranteed` is set only when the provider's own terms say that exceeding the free allowance refuses the request rather than silently starting to bill you, with the source in a comment next to the entry; it is never defaulted to `true`, and an entry nobody has verified stays unset. So a missing hard-stop flag means "not established", not "known to bill you".
|
||||
- `estMonthlyFreeTokens` = recurring monthly tokens only. **One-time signup credits do not recur** and count as 0. Discontinued tiers are also 0.
|
||||
- Daily token cap → `monthly = daily × 30`. Only RPD documented → `RPD × ~800 output tokens × 30`. Only RPM/TPM (no daily cap) → **uncapped** (see below).
|
||||
- **Permanently free, but no published token cap** (`siliconflow`, `glm-cn`, `tencent`, `baidu`, `kilo-gateway`, `opencode-zen`, `gemini`, `ollama-cloud`): these are real recurring free access, rate/concurrency-limited. We classify them `recurring-uncapped` and **never sum them** — multiplying `RPM × 24/7 × 30d` would produce a fantasy ceiling (the inflation we reject). They are listed so you know they exist.
|
||||
@@ -191,7 +189,6 @@ Most "free tokens per month" figures in this space are sums of per-model labels.
|
||||
| `veoaifree-web` | caution | ToS explicitly bans automated bots or scripts running at "inhuman speeds" and prohibits copying the platform to create … |
|
||||
| `vertex` | caution | Google Cloud Service Terms restrict resale to authorized resellers only (Section 14 requires a Reseller Agreement); a s… |
|
||||
| `voyage-ai` | caution | ToS grants "personal, non-commercial use" for site content and prohibits credential/account sharing with third parties;… |
|
||||
| `xkiro` | caution | ToS (2026-07-30) forbids reselling/redistributing the service and violating the upstream providers' terms; personal pro… |
|
||||
| `360ai` | unknown | ToS for developer API not publicly accessible without registration; access requires application approval which implies … |
|
||||
| `chutes` | unknown | ToS page exists at chutes.ai/terms but content was not accessible via fetch; no explicit proxy/resale clauses found in … |
|
||||
| `freemodel-dev` | unknown | The Terms of Service page (freemodel.dev/terms) returned only a header with no readable content via WebFetch; no clause… |
|
||||
@@ -214,7 +211,6 @@ Most "free tokens per month" figures in this space are sums of per-model labels.
|
||||
| `mistral` | recurring | ~1.00B | — | caution | 5 |
|
||||
| `nara` | recurring | ~210M | — | caution | 8 |
|
||||
| `llm7` | recurring | ~150M | — | caution | 4 |
|
||||
| `xkiro` | recurring | ~150M | — | caution | 39 |
|
||||
| `longcat` | one-time | — | 10M | caution | 1 |
|
||||
| `cerebras` | one-time | — | $5 credit | caution | 2 |
|
||||
| `cloudflare-ai` | recurring | ~30M | — | caution | 9 |
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
title: "Provider Reference"
|
||||
version: 3.8.51
|
||||
lastUpdated: 2026-09-04
|
||||
lastUpdated: 2026-09-03
|
||||
---
|
||||
|
||||
# Provider Reference
|
||||
|
||||
> **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand.
|
||||
> Regenerate with: `npm run gen:provider-reference`
|
||||
> **Last generated:** 2026-09-04
|
||||
> **Last generated:** 2026-09-03
|
||||
|
||||
Total providers: **357**. See category breakdown below.
|
||||
Total providers: **356**. See category breakdown below.
|
||||
|
||||
## Categories
|
||||
|
||||
@@ -118,7 +118,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
|
||||
| `zai-web` | `zw` | Z.ai Web | Web cookie | [link](https://chat.z.ai) | Copy the "token" value from chat.z.ai → DevTools → Application → Local Storage. Do not copy cookies; OmniRoute handles the per-request CAPTCHA through its browser transport. | — |
|
||||
| `zenmux-free` | `zmf` | ZenMux Free (Web) | Web cookie | [link](https://zenmux.ai) | Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days. | — |
|
||||
|
||||
## API Key Providers (paid / paid-with-free-credits) (239)
|
||||
## API Key Providers (paid / paid-with-free-credits) (238)
|
||||
|
||||
| ID | Alias | Name | Tags | Website | Notes |
|
||||
|----|-------|------|------|---------|-------|
|
||||
@@ -354,7 +354,6 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
|
||||
| `xai` | `xai` | xAI (Grok) | API key | [link](https://x.ai) | Use an official xAI API key, or sign in with xAI OAuth. Grok Build JWT sessions remain a separate provider. |
|
||||
| `xiaomi-mimo` | `mimo` | Xiaomi MiMo | API key | [link](https://mimo.mi.com) | — |
|
||||
| `xiaomi-mimo-token-plan` | `mimotp` | Xiaomi MiMo Token Plan | API key | [link](https://mimo.mi.com) | — |
|
||||
| `xkiro` | `xkiro` | xKiro | API key, aggregator | [link](https://xkiro.com) | Create a free account at xkiro.com and paste the key here (Bearer; x-api-key also accepted). |
|
||||
| `yi` | `yi` | Yi (01.AI) | API key | [link](https://01.ai) | Get API key at platform.lingyiwanwu.com |
|
||||
| `yolo-auto` | `yolo-auto` | Yolo-Auto | API key, aggregator | [link](https://yolo-auto.com) | Free API access is request-limited and intended for testing; no numeric daily quota is published and free access is not promised indefinitely. |
|
||||
| `zai` | `zai` | Z.AI | API key | [link](https://open.bigmodel.cn) | — |
|
||||
|
||||
@@ -3,37 +3,36 @@
|
||||
<rect x="16" y="16" width="868" height="564" rx="13" fill="#161b22" stroke="#30363d"/>
|
||||
<text x="868" y="572" fill="#484f58" font-size="10.5" text-anchor="end">OmniRoute · /dashboard/free-tiers · preview mockup</text>
|
||||
<text x="32" y="50" fill="#e6edf3" font-size="18" font-weight="700">Monthly free-token budget</text>
|
||||
<text x="868" y="50" fill="#7d8590" font-size="13" text-anchor="end">22 free pools · 483 models · one endpoint</text>
|
||||
<text x="868" y="50" fill="#7d8590" font-size="13" text-anchor="end">21 free pools · 444 models · one endpoint</text>
|
||||
<text x="32" y="84" fill="#7d8590" font-size="11.5">Steady / month</text>
|
||||
<text x="32" y="114" fill="#e6edf3" font-size="27" font-weight="800">~1.62B</text>
|
||||
<text x="32" y="114" fill="#e6edf3" font-size="27" font-weight="800">~1.47B</text>
|
||||
<text x="330" y="84" fill="#7d8590" font-size="11.5">First month (+ signup credits)</text>
|
||||
<text x="330" y="114" fill="#3fb950" font-size="27" font-weight="800">~2.25B</text>
|
||||
<text x="330" y="114" fill="#3fb950" font-size="27" font-weight="800">~2.10B</text>
|
||||
<text x="700" y="84" fill="#7d8590" font-size="11.5">ToS-flagged (you decide)</text>
|
||||
<text x="700" y="114" fill="#d29922" font-size="27" font-weight="800">13 providers</text>
|
||||
<clipPath id="bar"><rect x="32" y="132" width="836" height="16" rx="8"/></clipPath>
|
||||
<g clip-path="url(#bar)"><rect x="32" y="132" width="836" height="16" fill="#21262d"/>
|
||||
<rect x="32.0" y="132" width="427.8" height="16" fill="#6c5ce7"/>
|
||||
<rect x="459.2" y="132" width="95.8" height="16" fill="#00b894"/>
|
||||
<rect x="554.4" y="132" width="70.6" height="16" fill="#0984e3"/>
|
||||
<rect x="624.4" y="132" width="70.6" height="16" fill="#e17055"/>
|
||||
<rect x="694.4" y="132" width="20.2" height="16" fill="#fdcb6e"/>
|
||||
<rect x="714.0" y="132" width="17.7" height="16" fill="#e84393"/>
|
||||
<rect x="731.1" y="132" width="10.6" height="16" fill="#00cec9"/>
|
||||
<rect x="741.1" y="132" width="10.1" height="16" fill="#d63031"/>
|
||||
<rect x="750.7" y="132" width="10.1" height="16" fill="#a29bfe"/>
|
||||
<rect x="760.2" y="132" width="10.1" height="16" fill="#55efc4"/>
|
||||
<rect x="769.7" y="132" width="10.1" height="16" fill="#74b9ff"/>
|
||||
<rect x="779.2" y="132" width="10.1" height="16" fill="#ffeaa7"/>
|
||||
<rect x="788.7" y="132" width="10.1" height="16" fill="#fab1a0"/>
|
||||
<rect x="798.3" y="132" width="9.6" height="16" fill="#81ecec"/>
|
||||
<rect x="807.3" y="132" width="9.5" height="16" fill="#6c5ce7"/>
|
||||
<rect x="816.2" y="132" width="9.1" height="16" fill="#00b894"/>
|
||||
<rect x="824.7" y="132" width="8.1" height="16" fill="#0984e3"/>
|
||||
<rect x="832.2" y="132" width="7.9" height="16" fill="#e17055"/>
|
||||
<rect x="839.5" y="132" width="7.8" height="16" fill="#fdcb6e"/>
|
||||
<rect x="846.7" y="132" width="7.8" height="16" fill="#e84393"/>
|
||||
<rect x="853.9" y="132" width="7.7" height="16" fill="#00cec9"/>
|
||||
<rect x="861.0" y="132" width="7.6" height="16" fill="#d63031"/>
|
||||
<rect x="32.0" y="132" width="475.3" height="16" fill="#6c5ce7"/>
|
||||
<rect x="506.7" y="132" width="105.8" height="16" fill="#00b894"/>
|
||||
<rect x="611.9" y="132" width="77.8" height="16" fill="#0984e3"/>
|
||||
<rect x="689.0" y="132" width="21.6" height="16" fill="#e17055"/>
|
||||
<rect x="710.1" y="132" width="18.8" height="16" fill="#fdcb6e"/>
|
||||
<rect x="728.3" y="132" width="11.0" height="16" fill="#e84393"/>
|
||||
<rect x="738.7" y="132" width="10.4" height="16" fill="#00cec9"/>
|
||||
<rect x="748.5" y="132" width="10.4" height="16" fill="#d63031"/>
|
||||
<rect x="758.3" y="132" width="10.4" height="16" fill="#a29bfe"/>
|
||||
<rect x="768.1" y="132" width="10.4" height="16" fill="#55efc4"/>
|
||||
<rect x="777.9" y="132" width="10.4" height="16" fill="#74b9ff"/>
|
||||
<rect x="787.7" y="132" width="10.4" height="16" fill="#ffeaa7"/>
|
||||
<rect x="797.5" y="132" width="9.8" height="16" fill="#fab1a0"/>
|
||||
<rect x="806.8" y="132" width="9.7" height="16" fill="#81ecec"/>
|
||||
<rect x="815.9" y="132" width="9.3" height="16" fill="#6c5ce7"/>
|
||||
<rect x="824.5" y="132" width="8.2" height="16" fill="#00b894"/>
|
||||
<rect x="832.1" y="132" width="8.0" height="16" fill="#0984e3"/>
|
||||
<rect x="839.5" y="132" width="7.8" height="16" fill="#e17055"/>
|
||||
<rect x="846.7" y="132" width="7.8" height="16" fill="#fdcb6e"/>
|
||||
<rect x="853.9" y="132" width="7.7" height="16" fill="#e84393"/>
|
||||
<rect x="861.0" y="132" width="7.6" height="16" fill="#00cec9"/>
|
||||
</g>
|
||||
<text x="32" y="172" fill="#7d8590" font-size="12">Each segment = one free pool · widths floored so every provider shows · honest numbers in the grid.</text>
|
||||
<circle cx="37" cy="196" r="5" fill="#6c5ce7"/>
|
||||
@@ -43,43 +42,41 @@
|
||||
<circle cx="463" cy="196" r="5" fill="#0984e3"/>
|
||||
<text x="474" y="200" fill="#c9d1d9" font-size="12.5">GPT-4o mini <tspan fill="#7d8590">150M</tspan></text>
|
||||
<circle cx="676" cy="196" r="5" fill="#e17055"/>
|
||||
<text x="687" y="200" fill="#c9d1d9" font-size="12.5">Qwen3 VL Plus <tspan fill="#7d8590">150M</tspan></text>
|
||||
<text x="687" y="200" fill="#c9d1d9" font-size="12.5">Llama 3.3 70B <tspan fill="#7d8590">30M</tspan></text>
|
||||
<circle cx="37" cy="226" r="5" fill="#fdcb6e"/>
|
||||
<text x="48" y="230" fill="#c9d1d9" font-size="12.5">Llama 3.3 70B <tspan fill="#7d8590">30M</tspan></text>
|
||||
<text x="48" y="230" fill="#c9d1d9" font-size="12.5">Grok-3 <tspan fill="#7d8590">24M</tspan></text>
|
||||
<circle cx="250" cy="226" r="5" fill="#e84393"/>
|
||||
<text x="261" y="230" fill="#c9d1d9" font-size="12.5">Grok-3 <tspan fill="#7d8590">24M</tspan></text>
|
||||
<text x="261" y="230" fill="#c9d1d9" font-size="12.5">GPT-4o <tspan fill="#7d8590">7M</tspan></text>
|
||||
<circle cx="463" cy="226" r="5" fill="#00cec9"/>
|
||||
<text x="474" y="230" fill="#c9d1d9" font-size="12.5">GPT-4o <tspan fill="#7d8590">7M</tspan></text>
|
||||
<text x="474" y="230" fill="#c9d1d9" font-size="12.5">GPT-OSS 120B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<circle cx="676" cy="226" r="5" fill="#d63031"/>
|
||||
<text x="687" y="230" fill="#c9d1d9" font-size="12.5">GPT-OSS 120B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<text x="687" y="230" fill="#c9d1d9" font-size="12.5">GPT-OSS 20B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<circle cx="37" cy="256" r="5" fill="#a29bfe"/>
|
||||
<text x="48" y="260" fill="#c9d1d9" font-size="12.5">GPT-OSS 20B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<text x="48" y="260" fill="#c9d1d9" font-size="12.5">GPT-OSS Safeguard 20B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<circle cx="250" cy="256" r="5" fill="#55efc4"/>
|
||||
<text x="261" y="260" fill="#c9d1d9" font-size="12.5">GPT-OSS Safeguard 20B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<text x="261" y="260" fill="#c9d1d9" font-size="12.5">Qwen3.6 27B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<circle cx="463" cy="256" r="5" fill="#74b9ff"/>
|
||||
<text x="474" y="260" fill="#c9d1d9" font-size="12.5">Qwen3.6 27B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<text x="474" y="260" fill="#c9d1d9" font-size="12.5">Qwen3.8 27B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<circle cx="676" cy="256" r="5" fill="#ffeaa7"/>
|
||||
<text x="687" y="260" fill="#c9d1d9" font-size="12.5">Qwen3.8 27B <tspan fill="#7d8590">6M</tspan></text>
|
||||
<text x="687" y="260" fill="#c9d1d9" font-size="12.5">MiniMax-M2.7 <tspan fill="#7d8590">6M</tspan></text>
|
||||
<circle cx="37" cy="286" r="5" fill="#fab1a0"/>
|
||||
<text x="48" y="290" fill="#c9d1d9" font-size="12.5">MiniMax-M2.7 <tspan fill="#7d8590">6M</tspan></text>
|
||||
<text x="48" y="290" fill="#c9d1d9" font-size="12.5">Arcee Trinity Large Prev <tspan fill="#7d8590">5M</tspan></text>
|
||||
<circle cx="250" cy="286" r="5" fill="#81ecec"/>
|
||||
<text x="261" y="290" fill="#c9d1d9" font-size="12.5">Arcee Trinity Large Prev <tspan fill="#7d8590">5M</tspan></text>
|
||||
<text x="261" y="290" fill="#c9d1d9" font-size="12.5">NavyAI free pool <tspan fill="#7d8590">5M</tspan></text>
|
||||
<circle cx="463" cy="286" r="5" fill="#6c5ce7"/>
|
||||
<text x="474" y="290" fill="#c9d1d9" font-size="12.5">NavyAI free pool <tspan fill="#7d8590">5M</tspan></text>
|
||||
<text x="474" y="290" fill="#c9d1d9" font-size="12.5">Auto Free <tspan fill="#7d8590">4M</tspan></text>
|
||||
<circle cx="676" cy="286" r="5" fill="#00b894"/>
|
||||
<text x="687" y="290" fill="#c9d1d9" font-size="12.5">Auto Free <tspan fill="#7d8590">4M</tspan></text>
|
||||
<text x="687" y="290" fill="#c9d1d9" font-size="12.5">Auto <tspan fill="#7d8590">1M</tspan></text>
|
||||
<circle cx="37" cy="316" r="5" fill="#0984e3"/>
|
||||
<text x="48" y="320" fill="#c9d1d9" font-size="12.5">Auto <tspan fill="#7d8590">1M</tspan></text>
|
||||
<text x="48" y="320" fill="#c9d1d9" font-size="12.5">Command A Reasoning <tspan fill="#7d8590">800K</tspan></text>
|
||||
<circle cx="250" cy="316" r="5" fill="#e17055"/>
|
||||
<text x="261" y="320" fill="#c9d1d9" font-size="12.5">Command A Reasoning <tspan fill="#7d8590">800K</tspan></text>
|
||||
<text x="261" y="320" fill="#c9d1d9" font-size="12.5">ERNIE 4.5 VL 424B A47B B <tspan fill="#7d8590">500K</tspan></text>
|
||||
<circle cx="463" cy="316" r="5" fill="#fdcb6e"/>
|
||||
<text x="474" y="320" fill="#c9d1d9" font-size="12.5">ERNIE 4.5 VL 424B A47B B <tspan fill="#7d8590">500K</tspan></text>
|
||||
<text x="474" y="320" fill="#c9d1d9" font-size="12.5">morph-v3-large <tspan fill="#7d8590">400K</tspan></text>
|
||||
<circle cx="676" cy="316" r="5" fill="#e84393"/>
|
||||
<text x="687" y="320" fill="#c9d1d9" font-size="12.5">morph-v3-large <tspan fill="#7d8590">400K</tspan></text>
|
||||
<text x="687" y="320" fill="#c9d1d9" font-size="12.5">Llama 3.1 8B <tspan fill="#7d8590">200K</tspan></text>
|
||||
<circle cx="37" cy="346" r="5" fill="#00cec9"/>
|
||||
<text x="48" y="350" fill="#c9d1d9" font-size="12.5">Llama 3.1 8B <tspan fill="#7d8590">200K</tspan></text>
|
||||
<circle cx="250" cy="346" r="5" fill="#d63031"/>
|
||||
<text x="261" y="350" fill="#c9d1d9" font-size="12.5">Claude Sonnet 4.5 <tspan fill="#7d8590">25K</tspan></text>
|
||||
<text x="48" y="350" fill="#c9d1d9" font-size="12.5">Claude Sonnet 4.5 <tspan fill="#7d8590">25K</tspan></text>
|
||||
<line x1="32" y1="386" x2="868" y2="386" stroke="#30363d"/>
|
||||
<text x="32" y="412" fill="#3fb950" font-size="13" font-weight="700">+ First month: one-time signup credits (~626M)</text>
|
||||
<rect x="32" y="421" width="90" height="22" rx="11" fill="#13311f" stroke="#238636"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.4 KiB |
4
llm.txt
@@ -1,6 +1,6 @@
|
||||
# OmniRoute
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 357 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 356 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **357 AI providers** with automatic format translation
|
||||
- **356 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
|
||||
@@ -506,52 +506,4 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
|
||||
{ provider: "nara", modelId: "mistral-medium-3-5", displayName: "Mistral Medium 3.5", monthlyTokens: 210000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
|
||||
{ provider: "nara", modelId: "qwen3.8-27b", displayName: "Qwen3.8 27B", monthlyTokens: 210000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
|
||||
{ provider: "nara", modelId: "stepfun-3.7-flash", displayName: "StepFun 3.7 Flash", monthlyTokens: 210000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
|
||||
// evidence: public-page https://xkiro.com/ (2026-09-02) — Free plan: "$0 / month · Free forever · Free tokens
|
||||
// 5M / day · Access to 40+ free models · No credit card"; https://docs.xkiro.com/models/tiers/ — "free — any
|
||||
// account — Callable on every plan, including the free one, within a daily token allowance". One allowance per
|
||||
// account ⇒ single pool: 5M × 30 = 150M. Rows = the 40 `access_tier: "free"` models on the public
|
||||
// GET https://api.xkiro.com/v1/models minus openai/gpt-5.3-codex-spark (undeclared provenance).
|
||||
// hardStopGuaranteed: https://docs.xkiro.com/api/rate-limits/ — "Past it, free-model requests are blocked with a
|
||||
// 429 until the daily reset" + "No credit card" (page header) / "no card required" (Free plan card).
|
||||
// tos: caution — ToS (2026-07-30) forbids reselling the service and violating the upstream providers' terms;
|
||||
// personal proxy use is not addressed.
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3-vl-plus:free", displayName: "Qwen3 VL Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "sensenova/sensenova-6.8-flash-lite", displayName: "SenseNova 6.8 Flash-Lite (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "minimax/minimax-m2.7:free", displayName: "MiniMax M2.7 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "minimax/minimax-m2.7-highspeed:free", displayName: "MiniMax M2.7 Highspeed (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "minimax/minimax-m2.5:free", displayName: "MiniMax M2.5 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "minimax/minimax-m3:free", displayName: "MiniMax M3 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "minimax/minimax-m2.1-highspeed:free", displayName: "MiniMax M2.1 Highspeed (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "minimax/minimax-m2.5-highspeed:free", displayName: "MiniMax M2.5 Highspeed (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "minimax/minimax-m2.1:free", displayName: "MiniMax M2.1 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "minimax/minimax-m2:free", displayName: "MiniMax M2 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.5-flash:free", displayName: "Qwen3.5 Flash (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.6-plus:free", displayName: "Qwen3.6 Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.5-397b-a17b:free", displayName: "Qwen3.5 397B A17B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.5-omni-flash:free", displayName: "Qwen3.5 Omni Flash (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "mistralai/mistral-large-2512", displayName: "Mistral Large 3 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "mistralai/mistral-medium-3.5", displayName: "Mistral Medium 3.5 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "mistralai/mistral-small-2603", displayName: "Mistral Small 4 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "mistralai/codestral-2508", displayName: "Codestral (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "mistralai/devstral-medium", displayName: "Devstral 2 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "mistralai/ministral-8b", displayName: "Ministral 3 8B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "mistralai/ministral-3b", displayName: "Ministral 3 3B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "deepseek/deepseek-v4-pro", displayName: "DeepSeek V4 Pro (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "deepseek/deepseek-v3.2", displayName: "DeepSeek V3.2 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "deepseek/deepseek-chat-v3.1", displayName: "DeepSeek V3.1 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.7-plus:free", displayName: "Qwen3.7 Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "mistralai/ministral-14b", displayName: "Ministral 3 14B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.6-max-preview:free", displayName: "Qwen3.6 Max Preview (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.5-plus:free", displayName: "Qwen3.5 Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.5-omni-plus:free", displayName: "Qwen3.5 Omni Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "deepseek/deepseek-v4-flash", displayName: "DeepSeek V4 Flash (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "sensenova/sensenova-6.7-flash-lite", displayName: "SenseNova 6.7 Flash-Lite (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.8-max:free", displayName: "Qwen3.8 Max (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.7-max:free", displayName: "Qwen3.7 Max (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.6-27b:free", displayName: "Qwen3.6 27B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3.6-35b-a3b:free", displayName: "Qwen3.6 35B A3B (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3-max:free", displayName: "Qwen3 Max (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3-coder-plus:free", displayName: "Qwen3 Coder Plus (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen-plus-2025-07-28:free", displayName: "Qwen Plus 0728 (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
{ provider: "xkiro", modelId: "qwen/qwen3-omni-flash:free", displayName: "Qwen3 Omni Flash (xKiro)", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "xkiro-free", tos: "caution", hardStopGuaranteed: true },
|
||||
];
|
||||
|
||||
@@ -230,7 +230,6 @@ import { x5labProvider } from "./registry/x5lab/index.ts";
|
||||
import { kenariProvider } from "./registry/kenari/index.ts";
|
||||
import { navyProvider } from "./registry/navy/index.ts";
|
||||
import { naraProvider } from "./registry/nara/index.ts";
|
||||
import { xkiroProvider } from "./registry/xkiro/index.ts";
|
||||
import { opperProvider } from "./registry/opper/index.ts";
|
||||
import { requestyProvider } from "./registry/requesty/index.ts";
|
||||
import { sealionProvider } from "./registry/sealion/index.ts";
|
||||
@@ -505,7 +504,6 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
kenari: kenariProvider,
|
||||
navy: navyProvider,
|
||||
nara: naraProvider,
|
||||
xkiro: xkiroProvider,
|
||||
opper: opperProvider,
|
||||
requesty: requestyProvider,
|
||||
sealion: sealionProvider,
|
||||
|
||||
@@ -25,5 +25,5 @@ export const agyProvider: RegistryEntry = {
|
||||
},
|
||||
models: [...AGY_PUBLIC_MODELS],
|
||||
passthroughModels: true,
|
||||
liveCatalogAuthoritative: false,
|
||||
liveCatalogAuthoritative: true,
|
||||
};
|
||||
|
||||
@@ -25,5 +25,5 @@ export const antigravityProvider: RegistryEntry = {
|
||||
},
|
||||
models: [...ANTIGRAVITY_PUBLIC_MODELS],
|
||||
passthroughModels: true,
|
||||
liveCatalogAuthoritative: false,
|
||||
liveCatalogAuthoritative: true,
|
||||
};
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* xKiro — OpenAI-compatible gateway (xkiro.com). Free plan: 5M tokens/day per
|
||||
* account across the models tagged `access_tier: "free"` on the public
|
||||
* `GET https://api.xkiro.com/v1/models` (2026-09-02: 40 rows; context lengths and
|
||||
* capabilities below come from that endpoint). `openai/gpt-5.3-codex-spark` is
|
||||
* listed free upstream but its provenance is undeclared, so it is not pinned.
|
||||
* Bearer auth; `GET /v1/usage` answers 401 without a valid key.
|
||||
*/
|
||||
export const xkiroProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "xkiro",
|
||||
baseUrl: "https://api.xkiro.com/v1/chat/completions",
|
||||
models: [
|
||||
{
|
||||
id: "qwen/qwen3-vl-plus:free",
|
||||
name: "Qwen3 VL Plus",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "sensenova/sensenova-6.8-flash-lite",
|
||||
name: "SenseNova 6.8 Flash-Lite",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "minimax/minimax-m2.7:free",
|
||||
name: "MiniMax M2.7",
|
||||
contextLength: 204800,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "minimax/minimax-m2.7-highspeed:free",
|
||||
name: "MiniMax M2.7 Highspeed",
|
||||
contextLength: 204800,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "minimax/minimax-m2.5:free",
|
||||
name: "MiniMax M2.5",
|
||||
contextLength: 204800,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "minimax/minimax-m3:free",
|
||||
name: "MiniMax M3",
|
||||
contextLength: 1000000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "minimax/minimax-m2.1-highspeed:free",
|
||||
name: "MiniMax M2.1 Highspeed",
|
||||
contextLength: 204800,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "minimax/minimax-m2.5-highspeed:free",
|
||||
name: "MiniMax M2.5 Highspeed",
|
||||
contextLength: 204800,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "minimax/minimax-m2.1:free",
|
||||
name: "MiniMax M2.1",
|
||||
contextLength: 204800,
|
||||
toolCalling: true,
|
||||
},
|
||||
{ id: "minimax/minimax-m2:free", name: "MiniMax M2", contextLength: 204800, toolCalling: true },
|
||||
{
|
||||
id: "qwen/qwen3.5-flash:free",
|
||||
name: "Qwen3.5 Flash",
|
||||
contextLength: 1000000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.6-plus:free",
|
||||
name: "Qwen3.6 Plus",
|
||||
contextLength: 1000000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.5-397b-a17b:free",
|
||||
name: "Qwen3.5 397B A17B",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.5-omni-flash:free",
|
||||
name: "Qwen3.5 Omni Flash",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "mistralai/mistral-large-2512",
|
||||
name: "Mistral Large 3",
|
||||
contextLength: 256000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{
|
||||
id: "mistralai/mistral-medium-3.5",
|
||||
name: "Mistral Medium 3.5",
|
||||
contextLength: 256000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "mistralai/mistral-small-2603",
|
||||
name: "Mistral Small 4",
|
||||
contextLength: 256000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "mistralai/codestral-2508",
|
||||
name: "Codestral",
|
||||
contextLength: 256000,
|
||||
toolCalling: true,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{
|
||||
id: "mistralai/devstral-medium",
|
||||
name: "Devstral 2",
|
||||
contextLength: 256000,
|
||||
toolCalling: true,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{
|
||||
id: "mistralai/ministral-8b",
|
||||
name: "Ministral 3 8B",
|
||||
contextLength: 256000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{
|
||||
id: "mistralai/ministral-3b",
|
||||
name: "Ministral 3 3B",
|
||||
contextLength: 128000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{
|
||||
id: "deepseek/deepseek-v4-pro",
|
||||
name: "DeepSeek V4 Pro",
|
||||
contextLength: 1048576,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "deepseek/deepseek-v3.2",
|
||||
name: "DeepSeek V3.2",
|
||||
contextLength: 131072,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "deepseek/deepseek-chat-v3.1",
|
||||
name: "DeepSeek V3.1",
|
||||
contextLength: 163840,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.7-plus:free",
|
||||
name: "Qwen3.7 Plus",
|
||||
contextLength: 1000000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "mistralai/ministral-14b",
|
||||
name: "Ministral 3 14B",
|
||||
contextLength: 256000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.6-max-preview:free",
|
||||
name: "Qwen3.6 Max Preview",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.5-plus:free",
|
||||
name: "Qwen3.5 Plus",
|
||||
contextLength: 1000000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.5-omni-plus:free",
|
||||
name: "Qwen3.5 Omni Plus",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "deepseek/deepseek-v4-flash",
|
||||
name: "DeepSeek V4 Flash",
|
||||
contextLength: 1048576,
|
||||
toolCalling: true,
|
||||
},
|
||||
{
|
||||
id: "sensenova/sensenova-6.7-flash-lite",
|
||||
name: "SenseNova 6.7 Flash-Lite",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.8-max:free",
|
||||
name: "Qwen3.8 Max",
|
||||
contextLength: 1000000,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{ id: "qwen/qwen3.7-max:free", name: "Qwen3.7 Max", contextLength: 1000000, toolCalling: true },
|
||||
{
|
||||
id: "qwen/qwen3.6-27b:free",
|
||||
name: "Qwen3.6 27B",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3.6-35b-a3b:free",
|
||||
name: "Qwen3.6 35B A3B",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3-max:free",
|
||||
name: "Qwen3 Max",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3-coder-plus:free",
|
||||
name: "Qwen3 Coder Plus",
|
||||
contextLength: 1048576,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen-plus-2025-07-28:free",
|
||||
name: "Qwen Plus 0728",
|
||||
contextLength: 131072,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3-omni-flash:free",
|
||||
name: "Qwen3 Omni Flash",
|
||||
contextLength: 262144,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -223,8 +223,8 @@ export function translateSseResponse(
|
||||
suppressThinkClose: boolean = false
|
||||
): Response {
|
||||
if (!response.body) return response;
|
||||
// GLM is a high-throughput provider — use a larger stream buffer (64KB) to
|
||||
// keep provider → client pacing ahead of the model's token emission rate.
|
||||
// Helper has 15 parameters; a 16th positional (65536) was a TS2554 and
|
||||
// never reached TransformStream. highWaterMark stays at the helper default.
|
||||
const transform = createSSETransformStreamWithLogger(
|
||||
FORMATS.CLAUDE,
|
||||
FORMATS.OPENAI,
|
||||
@@ -238,10 +238,7 @@ export function translateSseResponse(
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
suppressThinkClose,
|
||||
undefined,
|
||||
undefined,
|
||||
65536
|
||||
suppressThinkClose
|
||||
);
|
||||
const headers = cloneHeaders(response.headers);
|
||||
headers.set("content-type", "text/event-stream");
|
||||
|
||||
@@ -23,6 +23,36 @@ export function projectFailureUsageErrorCode(opts: {
|
||||
return errorBody.error.code || String(opts.statusCode);
|
||||
}
|
||||
|
||||
export interface FailureUsageAggregate {
|
||||
prompt_tokens?: number;
|
||||
completion_tokens?: number;
|
||||
cache_read_input_tokens?: number;
|
||||
cache_creation_input_tokens?: number;
|
||||
reasoning_tokens?: number;
|
||||
}
|
||||
|
||||
export function toFailureUsageAggregate(
|
||||
usage:
|
||||
| {
|
||||
prompt_tokens?: number;
|
||||
completion_tokens?: number;
|
||||
cache_read_input_tokens?: number;
|
||||
cache_creation_input_tokens?: number;
|
||||
reasoning_tokens?: number;
|
||||
}
|
||||
| null
|
||||
| undefined
|
||||
): FailureUsageAggregate | undefined {
|
||||
if (!usage) return undefined;
|
||||
return {
|
||||
prompt_tokens: usage.prompt_tokens,
|
||||
completion_tokens: usage.completion_tokens,
|
||||
cache_read_input_tokens: usage.cache_read_input_tokens,
|
||||
cache_creation_input_tokens: usage.cache_creation_input_tokens,
|
||||
reasoning_tokens: usage.reasoning_tokens,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFailureUsageRecord(opts: {
|
||||
provider: string | null | undefined;
|
||||
model: string | null | undefined;
|
||||
@@ -35,11 +65,18 @@ export function buildFailureUsageRecord(opts: {
|
||||
errorCode: string | null | undefined;
|
||||
latencyMs: number;
|
||||
endpoint?: string | null | undefined;
|
||||
aggregate?: FailureUsageAggregate | null;
|
||||
}) {
|
||||
return {
|
||||
provider: opts.provider || "unknown",
|
||||
model: opts.model || "unknown",
|
||||
tokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 },
|
||||
tokens: {
|
||||
input: opts.aggregate?.prompt_tokens ?? 0,
|
||||
output: opts.aggregate?.completion_tokens ?? 0,
|
||||
cacheRead: opts.aggregate?.cache_read_input_tokens ?? 0,
|
||||
cacheCreation: opts.aggregate?.cache_creation_input_tokens ?? 0,
|
||||
reasoning: opts.aggregate?.reasoning_tokens ?? 0,
|
||||
},
|
||||
status: String(opts.statusCode),
|
||||
success: false,
|
||||
latencyMs: opts.latencyMs,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
toMemoryRetrievalConfig,
|
||||
} from "@/lib/memory/settings";
|
||||
import { injectMemory, shouldInjectMemory } from "@/lib/memory/injection";
|
||||
import { injectSkills } from "@/lib/skills/injection";
|
||||
import { injectSkillsWithMetadata } from "@/lib/skills/injection";
|
||||
import { buildMemoryToolsForProvider } from "@/lib/skills/memoryBuiltins";
|
||||
import { skillRegistry } from "@/lib/skills/registry";
|
||||
import { FORMATS } from "../../translator/formats.ts";
|
||||
@@ -13,6 +13,13 @@ import { detectCachingContext } from "../../services/compression/cachingAware.ts
|
||||
|
||||
type MemorySkillsLogger = { debug?: (...args: unknown[]) => void } | null | undefined;
|
||||
|
||||
export interface MemorySkillsInjectionResult {
|
||||
body: Record<string, unknown>;
|
||||
memorySettings: { enabled: boolean; skillsEnabled: boolean; maxTokens: number } | null;
|
||||
builtinToolNames: string[];
|
||||
injectedCustomSkillNames: string[];
|
||||
}
|
||||
|
||||
function getToolName(tool: unknown): string {
|
||||
if (!tool || typeof tool !== "object") return "";
|
||||
const r = tool as Record<string, unknown>;
|
||||
@@ -60,11 +67,14 @@ export async function injectMemoryAndSkills({
|
||||
targetFormat: string;
|
||||
backgroundReason: string | null;
|
||||
log: MemorySkillsLogger;
|
||||
}) {
|
||||
}): Promise<MemorySkillsInjectionResult> {
|
||||
const memorySettings = memoryOwnerId
|
||||
? await getMemorySettings().catch(() => DEFAULT_MEMORY_SETTINGS)
|
||||
: null;
|
||||
|
||||
const builtinOwnerSet: string[] = [];
|
||||
const injectedCustomSkillNames: string[] = [];
|
||||
|
||||
if (
|
||||
memoryOwnerId &&
|
||||
memorySettings &&
|
||||
@@ -178,34 +188,48 @@ export async function injectMemoryAndSkills({
|
||||
return [];
|
||||
})
|
||||
);
|
||||
const memoryTools = buildMemoryToolsForProvider(
|
||||
const newMemoryTools = buildMemoryToolsForProvider(
|
||||
getSkillsProviderForFormat(sourceFormat)
|
||||
).filter((tool) => {
|
||||
const record = tool as Record<string, unknown>;
|
||||
const name = (record.function as Record<string, unknown> | undefined)?.name ?? record.name;
|
||||
return typeof name === "string" && !existingToolNames.has(name);
|
||||
});
|
||||
if (memoryTools.length > 0) {
|
||||
if (newMemoryTools.length > 0) {
|
||||
body = {
|
||||
...body,
|
||||
tools: [...existingTools, ...memoryTools],
|
||||
tools: [...existingTools, ...newMemoryTools],
|
||||
};
|
||||
// Track the names of newly injected memory tools for the owner set.
|
||||
builtinOwnerSet.push(
|
||||
...newMemoryTools
|
||||
.map((tool) => {
|
||||
const record = tool as Record<string, unknown>;
|
||||
const name =
|
||||
(record.function as Record<string, unknown> | undefined)?.name ?? record.name;
|
||||
return typeof name === "string" ? name : "";
|
||||
})
|
||||
.filter(Boolean)
|
||||
);
|
||||
log?.debug?.(
|
||||
"MEMORY",
|
||||
`Injected ${memoryTools.length} memory tool(s) for key=${memoryOwnerId}`
|
||||
`Injected ${newMemoryTools.length} memory tool(s) for key=${memoryOwnerId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (memoryOwnerId && memorySettings?.skillsEnabled) {
|
||||
if (memoryOwnerId && memorySettings?.skillsEnabled && body.stream !== true) {
|
||||
// Ensure the registry cache is warm before listing: on a cold/fresh
|
||||
// process skills that exist only in the DB would be missed (false
|
||||
// negative -> silent skip). loadFromDatabase() is a no-op when the cache
|
||||
// is already warm (TTL = 60 s), so repeated calls are cheap. Mirrors the
|
||||
// pattern in src/lib/skills/interception.ts (#2815).
|
||||
// Memory builtins and registered Skills are only executed by the
|
||||
// non-streaming server-owned tool loop; stream clients execute tools
|
||||
// client-side, so we skip injection for stream requests.
|
||||
await skillRegistry.loadFromDatabase(memoryOwnerId);
|
||||
const existingTools = Array.isArray(body.tools) ? body.tools : [];
|
||||
const mergedTools = injectSkills({
|
||||
const { tools: mergedTools, injectedNames } = injectSkillsWithMetadata({
|
||||
provider: getSkillsProviderForFormat(sourceFormat),
|
||||
existingTools,
|
||||
apiKeyId: memoryOwnerId,
|
||||
@@ -225,6 +249,7 @@ export async function injectMemoryAndSkills({
|
||||
...body,
|
||||
tools: mergedTools,
|
||||
};
|
||||
injectedCustomSkillNames.push(...injectedNames);
|
||||
log?.debug?.("SKILLS", `Injected ${mergedTools.length - existingTools.length} skills`);
|
||||
}
|
||||
}
|
||||
@@ -236,5 +261,45 @@ export async function injectMemoryAndSkills({
|
||||
};
|
||||
}
|
||||
|
||||
return { body, memorySettings };
|
||||
return { body, memorySettings, builtinToolNames: builtinOwnerSet, injectedCustomSkillNames };
|
||||
}
|
||||
|
||||
interface FallbackPlan {
|
||||
enabled: boolean;
|
||||
toolName: string | null;
|
||||
convertedToolCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure helper: merge web-search/web-fetch fallback tool names into the
|
||||
* builtin owner set. Adds a name only when plan.enabled===true,
|
||||
* plan.convertedToolCount>0, plan.toolName is non-null, and that name
|
||||
* did not already exist in the pre-conversion client tools (builtinToolNames)
|
||||
* OR in the original client tool names captured before fallback injection.
|
||||
* Does not mutate its input; returns a new result.
|
||||
*/
|
||||
export function mergeInjectedFallbackOwnerNames(
|
||||
injectionResult: { builtinToolNames: string[] },
|
||||
plans: FallbackPlan[],
|
||||
preConversionClientToolNames?: string[]
|
||||
): { builtinToolNames: string[] } {
|
||||
const existing = new Set(injectionResult.builtinToolNames);
|
||||
if (preConversionClientToolNames) {
|
||||
for (const name of preConversionClientToolNames) {
|
||||
existing.add(name);
|
||||
}
|
||||
}
|
||||
const extraNames: string[] = [];
|
||||
for (const plan of plans) {
|
||||
if (
|
||||
plan.enabled &&
|
||||
plan.convertedToolCount > 0 &&
|
||||
plan.toolName &&
|
||||
!existing.has(plan.toolName)
|
||||
) {
|
||||
extraNames.push(plan.toolName);
|
||||
existing.add(plan.toolName);
|
||||
}
|
||||
}
|
||||
return { builtinToolNames: [...injectionResult.builtinToolNames, ...extraNames] };
|
||||
}
|
||||
|
||||
159
open-sse/handlers/chatCore/nonStreamingClientTranslate.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Client translation for non-streaming responses.
|
||||
* Extracted from chatCore.ts (lines ~5098-5195) by symbol boundaries.
|
||||
*
|
||||
* Handles: translate, tool-name restore, finish-reason normalization, sanitize,
|
||||
* reasoning replay capture, and client usage buffer application.
|
||||
*
|
||||
* Phase distinction:
|
||||
* - "final": applies applyClientUsageBuffer (normalizes visible usage fields)
|
||||
* - "intermediate": skips usage buffer (raw usage preserved for aggregation)
|
||||
*/
|
||||
|
||||
import type {
|
||||
NonStreamingClientTranslateInput,
|
||||
NonStreamingClientTranslateResult,
|
||||
} from "@/lib/skills/toolLoopTypes.ts";
|
||||
import { needsTranslation } from "../../translator/index.ts";
|
||||
import { FORMATS } from "../../translator/formats.ts";
|
||||
import { translateNonStreamingResponse } from "../responseTranslator.ts";
|
||||
import { extractToolSchemaMap } from "../../translator/response/openai-responses/toolSchemas.ts";
|
||||
import { stripMarkdownCodeFence } from "../../utils/aiSdkCompat.ts";
|
||||
import { normalizeOpenAIToolFinishReasons } from "./passthroughToolNames.ts";
|
||||
import {
|
||||
cacheReasoningFromAssistantMessage,
|
||||
requiresReasoningReplay,
|
||||
} from "../../services/reasoningCache.ts";
|
||||
import {
|
||||
sanitizeOpenAIResponse,
|
||||
sanitizeResponsesApiResponse,
|
||||
shouldParseTextualReasoningTags,
|
||||
} from "../responseSanitizer.ts";
|
||||
import { isStripReasoningRequested } from "./headers.ts";
|
||||
import { applyClientUsageBuffer } from "./clientUsageBuffer.ts";
|
||||
|
||||
export type { NonStreamingClientTranslateInput, NonStreamingClientTranslateResult };
|
||||
|
||||
/**
|
||||
* Translate a non-streaming provider response to the client's expected format.
|
||||
*
|
||||
* All of: translate, tool-name identity restore, finish-reason normalization,
|
||||
* and sanitize are applied every round (both intermediate and final).
|
||||
* `applyClientUsageBuffer` is applied only for `phase === "final"`.
|
||||
* Reasoning replay capture runs every round.
|
||||
*/
|
||||
export function translateNonStreamingClientResponse(
|
||||
input: NonStreamingClientTranslateInput
|
||||
): NonStreamingClientTranslateResult {
|
||||
const {
|
||||
responseBody,
|
||||
responsePayloadFormat,
|
||||
clientResponseFormat,
|
||||
sourceFormat,
|
||||
provider,
|
||||
model,
|
||||
requestBody,
|
||||
responseToolNameMap,
|
||||
requestToolIdentityMap,
|
||||
reasoningCacheScope,
|
||||
clientHeaders,
|
||||
isClaudeCodeCompatible,
|
||||
phase,
|
||||
} = input;
|
||||
|
||||
// ── Extract tool schemas for schema-aware translation ──────────────────────
|
||||
const finalBody = requestBody as Record<string, unknown> | null;
|
||||
const responseToolSchemas = extractToolSchemaMap(finalBody || responseBody);
|
||||
|
||||
// ── Translate response to client's expected format ─────────────────────────
|
||||
let translatedResponse = needsTranslation(responsePayloadFormat, clientResponseFormat)
|
||||
? translateNonStreamingResponse(
|
||||
responseBody,
|
||||
responsePayloadFormat,
|
||||
clientResponseFormat,
|
||||
responseToolNameMap,
|
||||
responseToolSchemas
|
||||
)
|
||||
: responseBody;
|
||||
const responseForMemoryExtraction = translatedResponse;
|
||||
|
||||
// ── T26: Strip markdown code blocks if provider format is Claude ───────────
|
||||
if (sourceFormat === "claude") {
|
||||
if (typeof translatedResponse?.choices?.[0]?.message?.content === "string") {
|
||||
translatedResponse.choices[0].message.content = stripMarkdownCodeFence(
|
||||
translatedResponse.choices[0].message.content
|
||||
) as string;
|
||||
}
|
||||
}
|
||||
|
||||
// ── T18: Normalize finish_reason to 'tool_calls' if tool calls present ─────
|
||||
normalizeOpenAIToolFinishReasons(translatedResponse);
|
||||
|
||||
// ── Reasoning Replay Cache (#1628) ────────────────────────────────────────
|
||||
// Capture reasoning_content from non-streaming responses with tool_calls
|
||||
// so it can be replayed on subsequent turns.
|
||||
try {
|
||||
const cacheResponse = translatedResponse?.choices?.[0]
|
||||
? translatedResponse
|
||||
: needsTranslation(responsePayloadFormat, FORMATS.OPENAI)
|
||||
? translateNonStreamingResponse(
|
||||
responseBody,
|
||||
responsePayloadFormat,
|
||||
FORMATS.OPENAI,
|
||||
responseToolNameMap,
|
||||
responseToolSchemas
|
||||
)
|
||||
: responseBody;
|
||||
const firstChoice = cacheResponse?.choices?.[0];
|
||||
const msg = firstChoice?.message;
|
||||
// Prefer explicit historyMessages (parent: translatedBody.messages). Do not
|
||||
// overload requestBody — Responses-shaped finalBody has `input`, not `messages`.
|
||||
const historyMessages = Array.isArray(input.historyMessages)
|
||||
? input.historyMessages
|
||||
: (finalBody as { messages?: unknown[] } | null | undefined)?.messages;
|
||||
if (requiresReasoningReplay({ provider, model })) {
|
||||
cacheReasoningFromAssistantMessage(msg, provider, model, {
|
||||
scope: reasoningCacheScope,
|
||||
historyMessages: Array.isArray(historyMessages) ? historyMessages : [],
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Cache capture is non-critical — never block the response
|
||||
}
|
||||
|
||||
// ── Sanitize response for SDK compatibility ────────────────────────────────
|
||||
if (clientResponseFormat === FORMATS.OPENAI_RESPONSES) {
|
||||
translatedResponse = sanitizeResponsesApiResponse(translatedResponse);
|
||||
// Restore {namespace, name} on function_call items for round-trip closure (#7936)
|
||||
const responseOutput = translatedResponse?.output;
|
||||
if (requestToolIdentityMap && Array.isArray(responseOutput)) {
|
||||
for (const item of responseOutput) {
|
||||
if (item?.type !== "function_call") continue;
|
||||
const identity = requestToolIdentityMap.get(item.name);
|
||||
if (identity) {
|
||||
item.namespace = identity.namespace;
|
||||
item.name = identity.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (clientResponseFormat === FORMATS.OPENAI) {
|
||||
const stripReasoning = isStripReasoningRequested(clientHeaders ?? null);
|
||||
translatedResponse = sanitizeOpenAIResponse(translatedResponse, {
|
||||
stripReasoning,
|
||||
parseTextualReasoningTags: shouldParseTextualReasoningTags(provider, model),
|
||||
});
|
||||
}
|
||||
|
||||
// ── Client usage buffer (#8331) ───────────────────────────────────────────
|
||||
// Only apply for final phase; intermediate preserves raw usage for aggregation.
|
||||
if (phase === "final") {
|
||||
applyClientUsageBuffer(translatedResponse, finalBody || responseBody, clientResponseFormat, {
|
||||
preserveContextBudgetInVisibleUsage: isClaudeCodeCompatible,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
response: translatedResponse,
|
||||
responseForMemoryExtraction,
|
||||
};
|
||||
}
|
||||
133
open-sse/handlers/chatCore/nonStreamingFinalization.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Request-level finalization for non-streaming chat.
|
||||
* Success and failure each write usage/cost/quota/attempt/pending once.
|
||||
*/
|
||||
|
||||
import type {
|
||||
ChatCoreErrorResult,
|
||||
ProviderLegUsage,
|
||||
ServerOwnedToolLoopResult,
|
||||
} from "@/lib/skills/toolLoopTypes.ts";
|
||||
import type { PersistAttemptLogsArgs } from "./attemptLogging.ts";
|
||||
import { type FailureUsageAggregate, toFailureUsageAggregate } from "./failureUsage.ts";
|
||||
|
||||
export type NonStreamingFinalizationPlan =
|
||||
| {
|
||||
kind: "success";
|
||||
usage: ProviderLegUsage | null;
|
||||
totalCostUsd: number;
|
||||
receiptCount: number;
|
||||
}
|
||||
| {
|
||||
kind: "failure";
|
||||
error: ChatCoreErrorResult;
|
||||
usage: ProviderLegUsage | null;
|
||||
totalCostUsd: number;
|
||||
receiptCount: number;
|
||||
};
|
||||
|
||||
export interface NonStreamingFinalizationDeps {
|
||||
writeUsage: (plan: NonStreamingFinalizationPlan) => void | Promise<void>;
|
||||
writeCost: (totalCostUsd: number) => void;
|
||||
scheduleQuota: (plan: NonStreamingFinalizationPlan) => void | Promise<void>;
|
||||
writeAttempt: (plan: NonStreamingFinalizationPlan) => void;
|
||||
finalizePending: (plan: NonStreamingFinalizationPlan) => void;
|
||||
}
|
||||
|
||||
function missingError(): ChatCoreErrorResult {
|
||||
return {
|
||||
success: false,
|
||||
status: 500,
|
||||
response: new Response(null, { status: 500 }),
|
||||
error: "Missing tool-loop error result",
|
||||
errorCode: "internal_error",
|
||||
};
|
||||
}
|
||||
|
||||
export function buildNonStreamingFinalizationPlan(
|
||||
loop: ServerOwnedToolLoopResult
|
||||
): NonStreamingFinalizationPlan {
|
||||
const usage = loop.cumulativeUsage;
|
||||
const totalCostUsd = loop.totalCostUsd;
|
||||
const receiptCount = loop.receipts.length;
|
||||
if (loop.kind === "error") {
|
||||
return {
|
||||
kind: "failure",
|
||||
error: loop.errorResult ?? missingError(),
|
||||
usage,
|
||||
totalCostUsd,
|
||||
receiptCount,
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "success",
|
||||
usage,
|
||||
totalCostUsd,
|
||||
receiptCount,
|
||||
};
|
||||
}
|
||||
|
||||
export async function finalizeNonStreamingRequest(
|
||||
plan: NonStreamingFinalizationPlan,
|
||||
deps: NonStreamingFinalizationDeps
|
||||
): Promise<void> {
|
||||
await deps.writeUsage(plan);
|
||||
deps.writeCost(plan.totalCostUsd);
|
||||
if (plan.kind === "success") {
|
||||
await deps.scheduleQuota(plan);
|
||||
}
|
||||
deps.writeAttempt(plan);
|
||||
deps.finalizePending(plan);
|
||||
}
|
||||
|
||||
export async function finalizeToolLoopError(input: {
|
||||
loop: ServerOwnedToolLoopResult;
|
||||
model: string;
|
||||
provider: string;
|
||||
connectionId?: string;
|
||||
providerRequest?: Record<string, unknown>;
|
||||
persistFailureUsage: (
|
||||
status: number,
|
||||
errorCode: string,
|
||||
usage?: FailureUsageAggregate | null
|
||||
) => void;
|
||||
persistAttemptLogs: (params: PersistAttemptLogsArgs) => void;
|
||||
trackPendingRequest: (
|
||||
model: string,
|
||||
provider: string,
|
||||
connectionId?: string,
|
||||
isPending?: boolean
|
||||
) => void;
|
||||
}): Promise<ChatCoreErrorResult> {
|
||||
const plan = buildNonStreamingFinalizationPlan(input.loop);
|
||||
const err = plan.kind === "failure" ? plan.error : missingError();
|
||||
await finalizeNonStreamingRequest(plan, {
|
||||
writeUsage: () => {
|
||||
input.persistFailureUsage(
|
||||
err.status,
|
||||
err.errorCode || `upstream_${err.status}`,
|
||||
toFailureUsageAggregate(plan.usage)
|
||||
);
|
||||
},
|
||||
writeCost: () => {},
|
||||
scheduleQuota: () => {},
|
||||
writeAttempt: () => {
|
||||
input.persistAttemptLogs({
|
||||
status: err.status,
|
||||
error: err.error || "Provider request failed",
|
||||
providerRequest: input.providerRequest,
|
||||
clientResponse: {
|
||||
error: {
|
||||
message: err.error || "Provider request failed",
|
||||
type: err.errorType || "api_error",
|
||||
},
|
||||
},
|
||||
cacheSource: "upstream",
|
||||
});
|
||||
},
|
||||
finalizePending: () => {
|
||||
input.trackPendingRequest(input.model, input.provider, input.connectionId, false);
|
||||
},
|
||||
});
|
||||
return err;
|
||||
}
|
||||
1144
open-sse/handlers/chatCore/nonStreamingProviderLeg.ts
Normal file
449
open-sse/handlers/chatCore/providerExecutionPipeline.ts
Normal file
@@ -0,0 +1,449 @@
|
||||
import type { ChatCoreErrorResult, ProviderLegUsage } from "@/lib/skills/toolLoopTypes.ts";
|
||||
import type { getProviderCredentials } from "@/sse/services/auth.ts";
|
||||
import type { updateFromHeaders, updateFromResponseBody } from "../../services/rateLimitManager.ts";
|
||||
import type { writeTerminalStatus } from "@/shared/utils/terminalStatus.ts";
|
||||
import type { updateProviderConnection } from "@/lib/db/providers.ts";
|
||||
import type { lockModel, recordCoreOwnedAntigravityQuotaState } from "../../services/accountFallback.ts";
|
||||
import { createErrorResult } from "../../utils/error.ts";
|
||||
import { applyStatusRestatement } from "../../config/upstreamStatusRestatement.ts";
|
||||
import { recoverAnthropicThinkingSignature } from "./thinkingSignatureRecovery.ts";
|
||||
import { isModelUnavailableError, getNextFamilyFallback as defaultGetNextFamilyFallback } from "../../services/modelFamilyFallback.ts";
|
||||
import { COOLDOWN_MS } from "../../config/errorConfig.ts";
|
||||
import { normalizeHeaders } from "../../utils/headers.ts";
|
||||
|
||||
export interface ChatCoreExecutorResult {
|
||||
response: Response;
|
||||
url: string;
|
||||
headers: Record<string, string>;
|
||||
transformedBody: unknown;
|
||||
transport?: string;
|
||||
_executionCredentials?: Record<string, unknown>;
|
||||
_accountSemaphoreRelease?: () => void;
|
||||
}
|
||||
|
||||
export interface ProviderExecutionPolicy {
|
||||
allowAccountRotation: boolean;
|
||||
allowModelFallback: boolean;
|
||||
expectedConnectionId?: string;
|
||||
}
|
||||
|
||||
export type ProviderExecutionOutcome =
|
||||
| {
|
||||
kind: "response";
|
||||
response: Response;
|
||||
url: string;
|
||||
headers: Record<string, string>;
|
||||
transformedBody: unknown;
|
||||
model: string;
|
||||
connectionId: string;
|
||||
}
|
||||
| {
|
||||
kind: "error";
|
||||
result: ChatCoreErrorResult;
|
||||
providerUsage: ProviderLegUsage | null;
|
||||
model: string;
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export interface PipelineTargetContext {
|
||||
provider: string;
|
||||
requestedModel: string;
|
||||
sourceFormat: string;
|
||||
targetFormat: string;
|
||||
stream: boolean;
|
||||
}
|
||||
|
||||
export interface PipelineConnectionContext {
|
||||
initialConnectionId: string;
|
||||
getCurrentConnectionId: () => string | undefined;
|
||||
getCredentials: () => Record<string, unknown>;
|
||||
replaceCredentials: (next: Record<string, unknown>) => void;
|
||||
onCredentialsRefreshed: (next: Record<string, unknown>) => void | Promise<void>;
|
||||
assertManagedLeaseFence: (connectionId: string) => void;
|
||||
getProviderCredentials: typeof getProviderCredentials;
|
||||
refreshCredentials?: (
|
||||
credentials: Record<string, unknown>
|
||||
) => Promise<Record<string, unknown> | null>;
|
||||
}
|
||||
|
||||
export interface PipelineWireState {
|
||||
body: Record<string, unknown>;
|
||||
currentModel: string;
|
||||
triedModels: Set<string>;
|
||||
setBodyAndModel: (body: Record<string, unknown>, model: string) => void;
|
||||
}
|
||||
|
||||
export interface PipelineStateHooks {
|
||||
updatePendingStage: (stage: string, data?: Record<string, unknown>) => void;
|
||||
recordRateLimitHeaders: typeof updateFromHeaders;
|
||||
recordRateLimitBody: typeof updateFromResponseBody;
|
||||
writeTerminalStatus: typeof writeTerminalStatus;
|
||||
persistConnectionPatch: typeof updateProviderConnection;
|
||||
setConnectionRateLimitedUntil: (
|
||||
connectionId: string,
|
||||
untilMs: number | null
|
||||
) => void | Promise<void>;
|
||||
lockModel: typeof lockModel;
|
||||
recordAntigravityQuotaState: typeof recordCoreOwnedAntigravityQuotaState;
|
||||
markAccountSemaphoreBlocked: (connectionId: string) => void;
|
||||
isolateProbeFailures: () => boolean | Promise<boolean>;
|
||||
onCodexScopeRateLimited?: (params: {
|
||||
failedConnectionId: string;
|
||||
model: string | null;
|
||||
rateLimitedUntil: string;
|
||||
credentials?: Record<string, unknown> | null;
|
||||
}) => void | Promise<void>;
|
||||
onClearSessionAffinity?: (params: { failedConnectionId: string }) => void | Promise<void>;
|
||||
onAuditAccountRotation?: (params: {
|
||||
action: "codex.account_rotation";
|
||||
failedConnectionId: string;
|
||||
newConnectionId: string;
|
||||
attempt: number;
|
||||
retryAfterMs: number | null;
|
||||
}) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface ProviderExecutionPipelineInput {
|
||||
policy: Readonly<ProviderExecutionPolicy>;
|
||||
target: PipelineTargetContext;
|
||||
connection: PipelineConnectionContext;
|
||||
wire: PipelineWireState;
|
||||
state: PipelineStateHooks;
|
||||
sendProviderAttempt: (model: string, allowDedup: boolean) => Promise<ChatCoreExecutorResult>;
|
||||
getNextFamilyFallback?: (
|
||||
currentModel: string,
|
||||
triedModels: Set<string>,
|
||||
providerHint?: string | null
|
||||
) => string | null;
|
||||
}
|
||||
|
||||
const LEASE_MISMATCH_STATUS = 409;
|
||||
const LEASE_MISMATCH_CODE = "LEASE_CONNECTION_MISMATCH";
|
||||
|
||||
function currentConnectionId(connection: PipelineConnectionContext): string {
|
||||
return connection.getCurrentConnectionId() ?? connection.initialConnectionId;
|
||||
}
|
||||
|
||||
function retryAfterMsFrom(attempt: ChatCoreExecutorResult): number | null {
|
||||
// attempt.headers is the outbound request bag (BaseExecutor finalHeaders).
|
||||
// Retry-After lives on the upstream Response — same source as the parent
|
||||
// chatCore rotate path. normalizeHeaders lower-cases keys, so "Retry-After"
|
||||
// is looked up as "retry-after"; it does not drop the field.
|
||||
const raw = normalizeHeaders(attempt.response?.headers)["retry-after"];
|
||||
if (raw == null || raw === "") return null;
|
||||
const parsed = Number.parseFloat(String(raw));
|
||||
if (!Number.isFinite(parsed) || parsed < 0) return null;
|
||||
return parsed * 1000;
|
||||
}
|
||||
|
||||
function leaseMismatch(model: string, connectionId: string): ProviderExecutionOutcome {
|
||||
const result = createErrorResult(
|
||||
LEASE_MISMATCH_STATUS,
|
||||
"Managed lease connection mismatch",
|
||||
null,
|
||||
LEASE_MISMATCH_CODE,
|
||||
"lease_error"
|
||||
);
|
||||
return {
|
||||
kind: "error",
|
||||
result: {
|
||||
success: false,
|
||||
status: result.status,
|
||||
response: result.response,
|
||||
error: result.error,
|
||||
errorCode: LEASE_MISMATCH_CODE,
|
||||
errorType: "lease_error",
|
||||
},
|
||||
providerUsage: null,
|
||||
model,
|
||||
connectionId,
|
||||
};
|
||||
}
|
||||
|
||||
async function toOutcome(
|
||||
attempt: ChatCoreExecutorResult,
|
||||
model: string,
|
||||
connectionId: string,
|
||||
provider: string
|
||||
): Promise<ProviderExecutionOutcome> {
|
||||
const status = attempt.response.status;
|
||||
if (status >= 200 && status < 300) {
|
||||
return {
|
||||
kind: "response",
|
||||
response: attempt.response,
|
||||
url: attempt.url,
|
||||
headers: attempt.headers,
|
||||
transformedBody: attempt.transformedBody,
|
||||
model,
|
||||
connectionId,
|
||||
};
|
||||
}
|
||||
let message = attempt.response.statusText || "upstream error";
|
||||
let body: unknown = attempt.transformedBody;
|
||||
try {
|
||||
// clone() is the drain. sendProviderAttempt must not cancel() a streaming
|
||||
// non-2xx body before we get here (BYOP 422 / Codex 429 Retry-After).
|
||||
body = JSON.parse(await attempt.response.clone().text());
|
||||
const err = (body as { error?: { message?: unknown } } | null)?.error;
|
||||
if (err && typeof err.message === "string" && err.message) message = err.message;
|
||||
} catch {
|
||||
// keep statusText
|
||||
}
|
||||
const restatement = applyStatusRestatement({
|
||||
provider,
|
||||
status,
|
||||
message,
|
||||
body,
|
||||
retryAfterMs: null,
|
||||
});
|
||||
const result = createErrorResult(
|
||||
restatement.status,
|
||||
message,
|
||||
restatement.retryAfterMs
|
||||
);
|
||||
return {
|
||||
kind: "error",
|
||||
result: {
|
||||
success: false,
|
||||
status: result.status,
|
||||
response: attempt.response,
|
||||
error: result.error,
|
||||
errorCode: result.errorCode,
|
||||
errorType: result.errorType,
|
||||
},
|
||||
providerUsage: null,
|
||||
model,
|
||||
connectionId,
|
||||
};
|
||||
}
|
||||
|
||||
function assertLease(
|
||||
policy: Readonly<ProviderExecutionPolicy>,
|
||||
connection: PipelineConnectionContext,
|
||||
model: string
|
||||
): ProviderExecutionOutcome | null {
|
||||
const expected = policy.expectedConnectionId;
|
||||
if (!expected) return null;
|
||||
const current = connection.getCurrentConnectionId();
|
||||
if (current && current !== expected) {
|
||||
return leaseMismatch(model, current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function maxAttemptsFor(provider: string): number {
|
||||
return provider === "codex" ? 3 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared first-send + provider recovery. Does not read a successful body.
|
||||
* Account/model retries live here; sendProviderAttempt is one wire send.
|
||||
*/
|
||||
export async function runProviderExecutionPipeline(
|
||||
input: ProviderExecutionPipelineInput
|
||||
): Promise<ProviderExecutionOutcome> {
|
||||
const { policy, target, connection, wire, state, sendProviderAttempt } = input;
|
||||
const maxAttempts = maxAttemptsFor(target.provider);
|
||||
const excludedIds: string[] = [];
|
||||
let attempts = 0;
|
||||
let lastAttempt: ChatCoreExecutorResult | null = null;
|
||||
let antigravityByopRotationPending = false;
|
||||
let authRefreshPending = false;
|
||||
let authRefreshed = false;
|
||||
let modelFallbackPending = false;
|
||||
const resolveFamilyFallback = input.getNextFamilyFallback ?? defaultGetNextFamilyFallback;
|
||||
|
||||
while (
|
||||
attempts < maxAttempts ||
|
||||
antigravityByopRotationPending ||
|
||||
authRefreshPending ||
|
||||
modelFallbackPending
|
||||
) {
|
||||
antigravityByopRotationPending = false;
|
||||
authRefreshPending = false;
|
||||
modelFallbackPending = false;
|
||||
const before = assertLease(policy, connection, wire.currentModel);
|
||||
if (before) return before;
|
||||
|
||||
const attempt = await sendProviderAttempt(wire.currentModel, attempts === 0);
|
||||
lastAttempt = attempt;
|
||||
|
||||
const after = assertLease(policy, connection, wire.currentModel);
|
||||
if (after) return after;
|
||||
|
||||
const status = attempt.response.status;
|
||||
if (status >= 200 && status < 300) {
|
||||
return toOutcome(attempt, wire.currentModel, currentConnectionId(connection), target.provider);
|
||||
}
|
||||
|
||||
const isolateProbe = await state.isolateProbeFailures();
|
||||
const canRotateAccount = policy.allowAccountRotation && !isolateProbe;
|
||||
|
||||
if (
|
||||
canRotateAccount &&
|
||||
target.provider === "codex" &&
|
||||
status === 429 &&
|
||||
attempts < maxAttempts - 1
|
||||
) {
|
||||
const failedId = currentConnectionId(connection);
|
||||
const retryAfterMs = retryAfterMsFrom(attempt);
|
||||
if (failedId && !excludedIds.includes(failedId)) excludedIds.push(failedId);
|
||||
if (failedId) {
|
||||
await state.onCodexScopeRateLimited?.({
|
||||
failedConnectionId: failedId,
|
||||
model: wire.currentModel || target.requestedModel || null,
|
||||
rateLimitedUntil: new Date(Date.now() + (retryAfterMs || 60_000)).toISOString(),
|
||||
credentials: connection.getCredentials(),
|
||||
});
|
||||
await state.onClearSessionAffinity?.({ failedConnectionId: failedId });
|
||||
}
|
||||
const nextCreds = await connection
|
||||
.getProviderCredentials("codex", null, null, wire.currentModel, {
|
||||
excludeConnectionIds: [...excludedIds],
|
||||
})
|
||||
.catch(() => null);
|
||||
if (nextCreds && !nextCreds.allRateLimited && nextCreds.connectionId) {
|
||||
await state.onAuditAccountRotation?.({
|
||||
action: "codex.account_rotation",
|
||||
failedConnectionId: failedId,
|
||||
newConnectionId: String(nextCreds.connectionId),
|
||||
attempt: attempts + 1,
|
||||
retryAfterMs,
|
||||
});
|
||||
connection.replaceCredentials(nextCreds as Record<string, unknown>);
|
||||
attempts += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (canRotateAccount && target.provider === "antigravity" && status === 422) {
|
||||
// Same drain as toOutcome: clone the Response. A prior body.cancel()
|
||||
// makes this throw "Body has already been consumed" and skips rotate.
|
||||
const byopBody = await attempt.response
|
||||
.clone()
|
||||
.text()
|
||||
.catch(() => "");
|
||||
if (byopBody.includes("gcp_project_required")) {
|
||||
const failedId = currentConnectionId(connection);
|
||||
if (failedId && !excludedIds.includes(failedId)) excludedIds.push(failedId);
|
||||
if (failedId) {
|
||||
await state.setConnectionRateLimitedUntil(
|
||||
failedId,
|
||||
Date.now() + (COOLDOWN_MS.gcpProjectRequired ?? 24 * 60 * 60 * 1000)
|
||||
);
|
||||
}
|
||||
const nextCreds = await connection
|
||||
.getProviderCredentials("antigravity", null, null, wire.currentModel, {
|
||||
excludeConnectionIds: [...excludedIds],
|
||||
})
|
||||
.catch(() => null);
|
||||
if (nextCreds && !nextCreds.allRateLimited && nextCreds.connectionId) {
|
||||
connection.replaceCredentials(nextCreds as Record<string, unknown>);
|
||||
antigravityByopRotationPending = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!authRefreshed &&
|
||||
(status === 401 || status === 403) &&
|
||||
typeof connection.refreshCredentials === "function"
|
||||
) {
|
||||
const refreshed = await connection.refreshCredentials(connection.getCredentials());
|
||||
if (refreshed && (refreshed.accessToken || refreshed.copilotToken)) {
|
||||
connection.replaceCredentials({ ...connection.getCredentials(), ...refreshed });
|
||||
await connection.onCredentialsRefreshed(refreshed);
|
||||
authRefreshed = true;
|
||||
authRefreshPending = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let signatureMessage = attempt.response.statusText || "upstream error";
|
||||
try {
|
||||
const parsed = JSON.parse(await attempt.response.clone().text()) as {
|
||||
error?: { message?: unknown };
|
||||
};
|
||||
if (typeof parsed?.error?.message === "string" && parsed.error.message) {
|
||||
signatureMessage = parsed.error.message;
|
||||
}
|
||||
} catch {
|
||||
// keep statusText
|
||||
}
|
||||
const signatureRecovery = await recoverAnthropicThinkingSignature({
|
||||
provider: target.provider,
|
||||
statusCode: status,
|
||||
message: signatureMessage,
|
||||
body: wire.body,
|
||||
execute: async (recoveryBody) => {
|
||||
if (recoveryBody && typeof recoveryBody === "object" && !Array.isArray(recoveryBody)) {
|
||||
wire.setBodyAndModel(recoveryBody as Record<string, unknown>, wire.currentModel);
|
||||
}
|
||||
return sendProviderAttempt(wire.currentModel, false);
|
||||
},
|
||||
parseError: async (response) => {
|
||||
let message = response.statusText || "upstream error";
|
||||
let responseBody: unknown = null;
|
||||
try {
|
||||
responseBody = JSON.parse(await response.clone().text());
|
||||
const err = (responseBody as { error?: { message?: unknown } } | null)?.error;
|
||||
if (typeof err?.message === "string" && err.message) message = err.message;
|
||||
} catch {
|
||||
// keep statusText
|
||||
}
|
||||
return {
|
||||
statusCode: response.status,
|
||||
message,
|
||||
retryAfterMs: null,
|
||||
responseBody,
|
||||
};
|
||||
},
|
||||
});
|
||||
if (signatureRecovery.attempted && signatureRecovery.succeeded && signatureRecovery.execution) {
|
||||
lastAttempt = {
|
||||
response: signatureRecovery.execution.response,
|
||||
url: signatureRecovery.execution.url ?? attempt.url,
|
||||
headers: (signatureRecovery.execution.headers as Record<string, string>) ?? attempt.headers,
|
||||
transformedBody: signatureRecovery.execution.transformedBody ?? attempt.transformedBody,
|
||||
};
|
||||
return toOutcome(
|
||||
lastAttempt,
|
||||
wire.currentModel,
|
||||
currentConnectionId(connection),
|
||||
target.provider
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (policy.allowModelFallback) {
|
||||
let fallbackMessage = attempt.response.statusText || "upstream error";
|
||||
try {
|
||||
const parsed = JSON.parse(await attempt.response.clone().text()) as {
|
||||
error?: { message?: unknown };
|
||||
};
|
||||
if (typeof parsed?.error?.message === "string" && parsed.error.message) {
|
||||
fallbackMessage = parsed.error.message;
|
||||
}
|
||||
} catch {
|
||||
// keep statusText
|
||||
}
|
||||
if (isModelUnavailableError(status, fallbackMessage, target.provider)) {
|
||||
const nextModel = resolveFamilyFallback(wire.currentModel, wire.triedModels, target.provider);
|
||||
if (nextModel) {
|
||||
wire.setBodyAndModel({ ...wire.body, model: nextModel }, nextModel);
|
||||
modelFallbackPending = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toOutcome(attempt, wire.currentModel, currentConnectionId(connection), target.provider);
|
||||
}
|
||||
|
||||
if (lastAttempt) {
|
||||
return toOutcome(lastAttempt, wire.currentModel, currentConnectionId(connection), target.provider);
|
||||
}
|
||||
return leaseMismatch(wire.currentModel, currentConnectionId(connection));
|
||||
}
|
||||
15
open-sse/handlers/chatCore/serverOwnedToolLoopGate.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { FORMATS } from "../../translator/formats.ts";
|
||||
|
||||
export function shouldRunServerOwnedToolLoop(input: {
|
||||
enabled: boolean;
|
||||
stream: boolean;
|
||||
isResponsesEndpoint: boolean;
|
||||
sourceFormat: string;
|
||||
}): boolean {
|
||||
if (!input.enabled) return false;
|
||||
if (input.stream) return false;
|
||||
if (input.isResponsesEndpoint) return false;
|
||||
if (input.sourceFormat === FORMATS.OPENAI) return true;
|
||||
if (input.sourceFormat === FORMATS.CLAUDE) return true;
|
||||
return false;
|
||||
}
|
||||
175
open-sse/handlers/chatCore/serverOwnedToolLoopWire.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import type {
|
||||
ExecutionContext,
|
||||
NonStreamingProviderLegResult,
|
||||
ProviderLegUsage,
|
||||
ServerOwnedToolLoopResult,
|
||||
ToolCall,
|
||||
} from "@/lib/skills/toolLoopTypes.ts";
|
||||
import { executeServerOwned } from "@/lib/skills/interception";
|
||||
import { runServerOwnedToolLoop, LOOP_BUDGET_MS } from "@/lib/skills/serverOwnedToolLoop.ts";
|
||||
import { deriveToolRequestIdentity } from "@/lib/skills/stableJson.ts";
|
||||
import { getIdempotencyKey } from "@/lib/idempotencyLayer";
|
||||
import { runNonStreamingProviderLeg } from "./nonStreamingProviderLeg.ts";
|
||||
import type { ProviderLegInput } from "./nonStreamingProviderLeg.ts";
|
||||
import { shouldRunServerOwnedToolLoop } from "./serverOwnedToolLoopGate.ts";
|
||||
import { FORMATS } from "../../translator/formats.ts";
|
||||
|
||||
export function derivePostInjectionRequestIdentity(input: {
|
||||
apiKeyId: string;
|
||||
headers: unknown;
|
||||
skillRequestId: string;
|
||||
postInjectionBody: Record<string, unknown>;
|
||||
}): string {
|
||||
const stableClientRequestId = getIdempotencyKey(input.headers as never);
|
||||
return deriveToolRequestIdentity({
|
||||
apiKeyId: input.apiKeyId,
|
||||
stableClientRequestId,
|
||||
skillRequestId: input.skillRequestId,
|
||||
postInjectionBody: input.postInjectionBody,
|
||||
});
|
||||
}
|
||||
|
||||
export async function continueServerOwnedToolLoop(input: {
|
||||
initialLeg: NonStreamingProviderLegResult & { kind: "ok" };
|
||||
sourceBody: Record<string, unknown>;
|
||||
sourceFormat: "openai" | "claude";
|
||||
skillsModelId: string;
|
||||
executionContext: ExecutionContext;
|
||||
abortSignal?: AbortSignal;
|
||||
deadlineAtMs: number;
|
||||
expectedConnectionId?: string;
|
||||
followUpLeg: (nextSourceBody: Record<string, unknown>) => Promise<NonStreamingProviderLegResult>;
|
||||
executeServerOwned?: (
|
||||
calls: ToolCall[],
|
||||
context: ExecutionContext
|
||||
) => Promise<import("@/lib/skills/toolLoopTypes.ts").ExecutedToolResult[]>;
|
||||
}): Promise<ServerOwnedToolLoopResult> {
|
||||
const runOwned = input.executeServerOwned ?? executeServerOwned;
|
||||
return runServerOwnedToolLoop({
|
||||
initialLeg: input.initialLeg,
|
||||
sourceBody: input.sourceBody,
|
||||
sourceFormat: input.sourceFormat,
|
||||
skillsModelId: input.skillsModelId,
|
||||
executionContext: input.executionContext,
|
||||
abortSignal: input.abortSignal,
|
||||
deadlineAtMs: input.deadlineAtMs,
|
||||
executeServerOwned: (calls: ToolCall[], context: ExecutionContext) => runOwned(calls, context),
|
||||
resumeUpstream: async (nextSourceBody, expectedConnectionId) => {
|
||||
if (
|
||||
expectedConnectionId &&
|
||||
input.expectedConnectionId &&
|
||||
expectedConnectionId !== input.expectedConnectionId
|
||||
) {
|
||||
return {
|
||||
kind: "error",
|
||||
result: {
|
||||
success: false,
|
||||
status: 409,
|
||||
response: new Response(null, { status: 409 }),
|
||||
error: "Follow-up connection mismatch",
|
||||
errorCode: "LEASE_CONNECTION_MISMATCH",
|
||||
},
|
||||
receipt: input.initialLeg.receipt,
|
||||
usage: null,
|
||||
};
|
||||
}
|
||||
return input.followUpLeg(nextSourceBody);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function followUpLegInput(
|
||||
base: Omit<
|
||||
ProviderLegInput,
|
||||
"phase" | "allowAccountRotation" | "allowModelFallback" | "sourceBody"
|
||||
>,
|
||||
nextSourceBody: Record<string, unknown>,
|
||||
expectedConnectionId?: string
|
||||
): ProviderLegInput {
|
||||
return {
|
||||
...base,
|
||||
phase: "follow-up",
|
||||
sourceBody: nextSourceBody,
|
||||
expectedConnectionId,
|
||||
allowAccountRotation: false,
|
||||
allowModelFallback: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeLoopIntoOkLeg(
|
||||
leg: NonStreamingProviderLegResult & { kind: "ok" },
|
||||
loop: ServerOwnedToolLoopResult
|
||||
): NonStreamingProviderLegResult & { kind: "ok" } {
|
||||
return {
|
||||
...leg,
|
||||
response: loop.response ?? leg.response,
|
||||
responseForMemoryExtraction:
|
||||
loop.responseForMemoryExtraction ?? leg.responseForMemoryExtraction,
|
||||
providerBody: loop.finalProviderBody ?? leg.providerBody,
|
||||
providerRequest: loop.finalProviderRequest ?? leg.providerRequest,
|
||||
usage: loop.cumulativeUsage,
|
||||
};
|
||||
}
|
||||
|
||||
export type ToolLoopApplyResult =
|
||||
| { kind: "skip" }
|
||||
| {
|
||||
kind: "ok";
|
||||
leg: NonStreamingProviderLegResult & { kind: "ok" };
|
||||
usage: ProviderLegUsage | null;
|
||||
loop: ServerOwnedToolLoopResult;
|
||||
}
|
||||
| { kind: "error"; loop: ServerOwnedToolLoopResult };
|
||||
|
||||
export async function applyServerOwnedToolLoopIfNeeded(input: {
|
||||
enabled: boolean;
|
||||
stream: boolean;
|
||||
isResponsesEndpoint: boolean;
|
||||
sourceFormat: string;
|
||||
initialLeg: NonStreamingProviderLegResult;
|
||||
sourceBody: Record<string, unknown>;
|
||||
skillsModelId: string;
|
||||
executionContext: ExecutionContext;
|
||||
abortSignal?: AbortSignal;
|
||||
expectedConnectionId?: string;
|
||||
followUpLeg: (nextSourceBody: Record<string, unknown>) => Promise<NonStreamingProviderLegResult>;
|
||||
logReceipt: (receipt: ServerOwnedToolLoopResult["receipts"][number]) => void;
|
||||
executeServerOwned?: (
|
||||
calls: ToolCall[],
|
||||
context: ExecutionContext
|
||||
) => Promise<import("@/lib/skills/toolLoopTypes.ts").ExecutedToolResult[]>;
|
||||
}): Promise<ToolLoopApplyResult> {
|
||||
if (
|
||||
input.initialLeg.kind !== "ok" ||
|
||||
!shouldRunServerOwnedToolLoop({
|
||||
enabled: input.enabled,
|
||||
stream: input.stream,
|
||||
isResponsesEndpoint: input.isResponsesEndpoint,
|
||||
sourceFormat: input.sourceFormat,
|
||||
})
|
||||
) {
|
||||
return { kind: "skip" };
|
||||
}
|
||||
const loop = await continueServerOwnedToolLoop({
|
||||
initialLeg: input.initialLeg,
|
||||
sourceBody: input.sourceBody,
|
||||
sourceFormat: input.sourceFormat === FORMATS.CLAUDE ? "claude" : "openai",
|
||||
skillsModelId: input.skillsModelId,
|
||||
executionContext: input.executionContext,
|
||||
abortSignal: input.abortSignal,
|
||||
deadlineAtMs: Date.now() + LOOP_BUDGET_MS,
|
||||
expectedConnectionId: input.expectedConnectionId,
|
||||
followUpLeg: input.followUpLeg,
|
||||
executeServerOwned: input.executeServerOwned,
|
||||
});
|
||||
for (const receipt of loop.receipts) input.logReceipt(receipt);
|
||||
if (loop.kind === "error") return { kind: "error", loop };
|
||||
return {
|
||||
kind: "ok",
|
||||
leg: mergeLoopIntoOkLeg(input.initialLeg, loop),
|
||||
usage: loop.cumulativeUsage,
|
||||
loop,
|
||||
};
|
||||
}
|
||||
|
||||
export { LOOP_BUDGET_MS, runNonStreamingProviderLeg };
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "./promptCacheAffinity.ts";
|
||||
import {
|
||||
orderTargetsByHeadroom,
|
||||
orderTargetsByQuotaWeighted,
|
||||
orderTargetsByResetAwareQuota,
|
||||
orderTargetsByResetWindow,
|
||||
} from "./quotaStrategies.ts";
|
||||
@@ -18,17 +19,19 @@ import {
|
||||
sortTargetsByCost,
|
||||
sortTargetsByUsage,
|
||||
} from "./targetSorters.ts";
|
||||
import { decrementInflight } from "./quotaShareInflight.ts";
|
||||
import type { ComboLike, ComboLogger, ResolvedComboTarget } from "./types.ts";
|
||||
|
||||
/**
|
||||
* Result of {@link applyStrategyOrdering}.
|
||||
*
|
||||
* `quotaShareRelease` carries the idempotent release for the in-flight slot that
|
||||
* quota-share ordering reserves for its winner (#11371). It is non-null only when
|
||||
* the `quota-share` strategy ran; every other strategy leaves it null. The caller
|
||||
* MUST invoke it exactly once when the request settles — selection reserves the
|
||||
* slot, so dropping the callback leaks the counter monotonically upward and
|
||||
* degenerates P2C into "fewest lifetime dispatches".
|
||||
* quota-share and quota-weighted reserve for their winner. quota-share reserves
|
||||
* inside selectQuotaShareTarget; quota-weighted reserves inside the orderer so
|
||||
* two in-process draws cannot both see inflight=0. Stickiness may then move [0];
|
||||
* resolveComboTargetPipeline transfers the slot for both strategies. The caller
|
||||
* MUST invoke the callback exactly once when the request settles — dropping it
|
||||
* leaks the counter and degenerates later draws toward whoever looks idle.
|
||||
*/
|
||||
export interface ApplyStrategyOrderingResult {
|
||||
orderedTargets: ResolvedComboTarget[];
|
||||
@@ -239,6 +242,27 @@ export async function applyStrategyOrdering(
|
||||
"COMBO",
|
||||
`Headroom ordering: ${orderedTargets[0]?.modelStr}${orderedTargets[0]?.connectionId ? ` (${orderedTargets[0].connectionId})` : ""} has most free capacity`
|
||||
);
|
||||
} else if (strategy === "quota-weighted") {
|
||||
orderedTargets = await orderTargetsByQuotaWeighted(
|
||||
orderedTargets,
|
||||
combo.name,
|
||||
config,
|
||||
log,
|
||||
apiKeyAllowedConnections
|
||||
);
|
||||
const winnerId = orderedTargets[0]?.connectionId ?? "";
|
||||
if (winnerId) {
|
||||
let released = false;
|
||||
quotaShareRelease = () => {
|
||||
if (released) return;
|
||||
released = true;
|
||||
decrementInflight(winnerId);
|
||||
};
|
||||
}
|
||||
log.info(
|
||||
"COMBO",
|
||||
`Quota-weighted ordering: ${orderedTargets[0]?.modelStr}${orderedTargets[0]?.connectionId ? ` (${orderedTargets[0].connectionId})` : ""} first`
|
||||
);
|
||||
} else if (strategy === "quota-share") {
|
||||
// Internal quota-share combos (qtSd/): delegate to the dedicated module (DRR +
|
||||
// P2C in-flight + per-model bucket gating + per-connection concurrency gating).
|
||||
|
||||
117
open-sse/services/combo/attemptLoopTypes.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Shared types for the handleComboChat attempt-loop split (ROADMAP 3.8.52).
|
||||
*
|
||||
* Mutable loop state lives on AttemptLoopState. Read-only dependencies live on
|
||||
* AttemptLoopDeps. Do not merge the two into ComboContext.
|
||||
*
|
||||
* @internal — not part of the public combo.ts barrel.
|
||||
*/
|
||||
import type { PerTargetAdmissionHook } from "../admission/types.ts";
|
||||
import type { ResilienceSettings } from "../../../src/lib/resilience/settings";
|
||||
import type { ContextRelayConfig, UniversalHandoffConfig } from "../contextHandoff.ts";
|
||||
import type { ComboErrorEntry } from "./comboErrorAggregation.ts";
|
||||
import type { ResetWindowConfig } from "./quotaScoring.ts";
|
||||
import type { ResponseValidationConfig } from "./responseValidation.ts";
|
||||
import type { ApplyStickinessResult } from "./sessionStickiness.ts";
|
||||
import type {
|
||||
ComboLike,
|
||||
ComboLogger,
|
||||
ComboRetryAfter,
|
||||
HandleSingleModel,
|
||||
IsModelAvailable,
|
||||
ResolvedComboTarget,
|
||||
} from "./types.ts";
|
||||
|
||||
export type ExecuteTargetResult = { ok: boolean; response?: Response } | null;
|
||||
|
||||
export type AttemptLoopState = {
|
||||
orderedTargets: ResolvedComboTarget[];
|
||||
fallbackCount: number;
|
||||
recordedAttempts: number;
|
||||
comboErrors: ComboErrorEntry[];
|
||||
lastError: string | null;
|
||||
lastStatus: number | null;
|
||||
earliestRetryAfter: ComboRetryAfter | null;
|
||||
comboExpired: boolean;
|
||||
exhaustedProviders: Set<string>;
|
||||
exhaustedConnections: Set<string>;
|
||||
transientRateLimitedProviders: Set<string>;
|
||||
abortControllers: Map<number, AbortController>;
|
||||
dispatchedTargets: Set<string>;
|
||||
targetFailureTrust: Map<string, { observedFailure: boolean; allObservedFailuresQuota: boolean }>;
|
||||
comboAttemptOrder: Array<{ provider: string; model: string }>;
|
||||
skippedForCircuitOpen: boolean;
|
||||
earliestCircuitOpenRetryMs: number;
|
||||
/** Mutable attempt budget shared with dispatchWithCooldownRetry (Task 4). */
|
||||
globalAttempts: number;
|
||||
/** Quota-trust accumulators; persist across set retries and cooldown re-dispatch. */
|
||||
observedFailure: boolean;
|
||||
allObservedFailuresQuota: boolean;
|
||||
observeFailure(quotaExhausted: boolean, targetExecutionKey?: string): void;
|
||||
};
|
||||
|
||||
export type AttemptLoopDeps = {
|
||||
strategy: string;
|
||||
combo: ComboLike;
|
||||
config: Record<string, unknown> & {
|
||||
zeroLatencyOptimizationsEnabled?: boolean;
|
||||
responseValidation?: ResponseValidationConfig | null;
|
||||
failoverBeforeRetryExplicit?: boolean;
|
||||
failoverBeforeRetry?: boolean;
|
||||
predictiveTtftMs?: number;
|
||||
fallbackCompressionMode?: string;
|
||||
fallbackCompressionThreshold?: number;
|
||||
retryDelayMs?: number;
|
||||
fallbackDelayMs?: number;
|
||||
maxGlobalAttempts?: unknown;
|
||||
hedging?: boolean;
|
||||
hedgeDelayMs?: unknown;
|
||||
};
|
||||
log: ComboLogger;
|
||||
settings: Record<string, unknown> | null;
|
||||
resilienceSettings: ResilienceSettings;
|
||||
sticky: ApplyStickinessResult;
|
||||
effectiveSessionId: string | null;
|
||||
preScreenMap: Map<string, { profile?: unknown }>;
|
||||
quotaCutoffResetWindowConfig: ResetWindowConfig;
|
||||
maxRetries: number;
|
||||
traceInvocationId: string;
|
||||
clientRequestedStream: boolean;
|
||||
handleSingleModelWithTimeout: HandleSingleModel;
|
||||
isModelAvailable?: IsModelAvailable;
|
||||
perTargetAdmission?: PerTargetAdmissionHook | null;
|
||||
signal?: AbortSignal | null;
|
||||
body: Record<string, unknown>;
|
||||
startTime: number;
|
||||
releaseStickyPinOnFailure: (
|
||||
messageHash: string | null | undefined,
|
||||
failedConnectionId: string | null | undefined
|
||||
) => void;
|
||||
clearStaleLKGP: (
|
||||
comboName: string,
|
||||
executionKey: string | undefined,
|
||||
comboId: string | undefined,
|
||||
log: ComboLogger,
|
||||
tag: string
|
||||
) => void;
|
||||
/**
|
||||
* Closed-over setup values from handleComboChatInner. Optional so Task 2
|
||||
* gate tests keep compiling; attempt uses defaults when absent.
|
||||
*/
|
||||
clientManagedResponsesContext?: boolean;
|
||||
reasoningTokenBufferEnabled?: boolean;
|
||||
stickyWeightedLimit?: number;
|
||||
getWeightedStepKeyForTarget?: (target: ResolvedComboTarget) => string | null;
|
||||
universalHandoffConfig?: UniversalHandoffConfig;
|
||||
relayOptions?: { sessionId?: string | null } | null;
|
||||
relayConfig?: ContextRelayConfig | null;
|
||||
};
|
||||
|
||||
export type GateDecision =
|
||||
| { kind: "skip"; result: ExecuteTargetResult }
|
||||
| {
|
||||
kind: "proceed";
|
||||
targetForAttempt: ResolvedComboTarget;
|
||||
profile: unknown;
|
||||
protectedPriorityTarget: boolean;
|
||||
};
|
||||
575
open-sse/services/combo/comboAttemptLoop.ts
Normal file
@@ -0,0 +1,575 @@
|
||||
/**
|
||||
* Set-try + speculative dispatch loop for handleComboChatInner.
|
||||
* Extracted from combo.ts dispatchWithCooldownRetry (#11804 finally lives here).
|
||||
*
|
||||
* @internal — not part of the public combo.ts barrel.
|
||||
*/
|
||||
import { formatRetryAfter, getModelLockoutInfo } from "../accountFallback.ts";
|
||||
import {
|
||||
errorResponse,
|
||||
errorResponseWithComboDiagnostics,
|
||||
unavailableResponse,
|
||||
} from "../../utils/error.ts";
|
||||
import type { ComboDiagnostics } from "../../utils/error.ts";
|
||||
import { COMBO_FAILURE_THRESHOLD, recordComboFailure } from "./failureTracker.ts";
|
||||
import { buildNoUpstreamResponseDiagnostics, buildRecoveryHint } from "./pinRecovery.ts";
|
||||
import { formatExhaustedConnectionKey } from "./comboDiagFormat.ts";
|
||||
import { recordComboRequest } from "../comboMetrics.ts";
|
||||
import { notifyWebhookEvent } from "../../../src/lib/webhookDispatcher.ts";
|
||||
import { parseModel } from "../model.ts";
|
||||
import {
|
||||
formatComboOutcomes,
|
||||
buildRedactedSummary,
|
||||
resolveComboTerminalStatus,
|
||||
} from "./comboErrorAggregation.ts";
|
||||
import {
|
||||
resolveComboCooldownWaitDecision,
|
||||
resolveCircuitOpenWaitDecision,
|
||||
type ResolveComboCooldownDecisionResult,
|
||||
} from "./comboCooldownRetry.ts";
|
||||
import {
|
||||
computeClosestRetryAfter,
|
||||
waitForCooldownAwareRetry,
|
||||
} from "../../../src/sse/services/cooldownAwareRetry.ts";
|
||||
import { toRetryAfterDisplayValue } from "./validateQuality.ts";
|
||||
import { finalizeComboTrace, finishComboTrace } from "./decisionTrace.ts";
|
||||
import { isRetryAfterEligibleStatus } from "./unavailableRetryGate.ts";
|
||||
import { withQuotaExhaustionClassification } from "./quotaExhaustion.ts";
|
||||
import {
|
||||
COMBO_LOOP_SAFETY_TIMEOUT_MS,
|
||||
COMBO_SAFETY_DRAIN_MS,
|
||||
resolveDelayMs,
|
||||
} from "./comboPredicates.ts";
|
||||
import { evaluateExecuteTargetGates } from "./executeTargetGates.ts";
|
||||
import { executeTargetAttempt } from "./executeTargetAttempt.ts";
|
||||
import type { AttemptLoopDeps, AttemptLoopState, ExecuteTargetResult } from "./attemptLoopTypes.ts";
|
||||
|
||||
export type DispatchWithCooldownRetryExtra = {
|
||||
maxSetRetries: number;
|
||||
setRetryDelayMs: number;
|
||||
comboTimeoutMs: number;
|
||||
comboStartTime: number;
|
||||
comboCooldownWaitEnabled: boolean;
|
||||
comboCooldownAttempt: { current: number };
|
||||
comboCooldownBudgetLeftMs: { current: number };
|
||||
evaluateGates: typeof evaluateExecuteTargetGates;
|
||||
executeAttempt: typeof executeTargetAttempt;
|
||||
};
|
||||
|
||||
export async function dispatchWithCooldownRetry(opts: {
|
||||
state: AttemptLoopState;
|
||||
deps: AttemptLoopDeps;
|
||||
extra: DispatchWithCooldownRetryExtra;
|
||||
}): Promise<Response> {
|
||||
const { state, deps, extra } = opts;
|
||||
// #7360: persist lastStatus/earliestRetryAfter across set retries; reset
|
||||
// only on a fresh dispatch (including cooldown-aware re-dispatch).
|
||||
state.lastError = null;
|
||||
state.earliestRetryAfter = null;
|
||||
state.lastStatus = null;
|
||||
state.skippedForCircuitOpen = false;
|
||||
state.earliestCircuitOpenRetryMs = 0;
|
||||
// #11804: the loop-safety timer is armed per setTry iteration but must be
|
||||
// cleared on EVERY exit path, not just the happy one. Hoisted to function
|
||||
// scope so the `finally` at the end of this function always reaches it.
|
||||
let activeLoopSafetyTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
try {
|
||||
for (let setTry = 0; setTry <= extra.maxSetRetries; setTry++) {
|
||||
// #1731: Per-set-iteration set of providers whose quota is fully exhausted.
|
||||
// Reset each retry so providers excluded in a previous attempt get another chance.
|
||||
state.exhaustedProviders = new Set<string>();
|
||||
state.exhaustedConnections = new Set<string>();
|
||||
state.transientRateLimitedProviders = new Set<string>();
|
||||
state.skippedForCircuitOpen = false;
|
||||
state.earliestCircuitOpenRetryMs = 0;
|
||||
if (setTry > 0) {
|
||||
deps.log.info(
|
||||
"COMBO",
|
||||
`All targets failed — retrying set (${setTry}/${extra.maxSetRetries})`
|
||||
);
|
||||
await new Promise((resolve) => {
|
||||
const timer = setTimeout(resolve, extra.setRetryDelayMs);
|
||||
deps.signal?.addEventListener(
|
||||
"abort",
|
||||
() => {
|
||||
clearTimeout(timer);
|
||||
resolve(undefined);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
if (deps.signal?.aborted) {
|
||||
deps.log.info("COMBO", "Client disconnected during set retry delay — aborting");
|
||||
return errorResponse(499, "Client disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
deps.startTime = Date.now();
|
||||
state.fallbackCount = 0;
|
||||
state.recordedAttempts = 0;
|
||||
state.comboErrors = [];
|
||||
|
||||
// QA P0: assemble a sanitized diagnostic trace from the state already in scope
|
||||
// (pool size + this set-try's exhausted providers/connections + attempt order +
|
||||
// a terminal-reason code). Never touches keys/tokens — provider/model ids only.
|
||||
// Silent-stop fix: include a `recovery` hint (action verb + human next-step) so the
|
||||
// OC plugin + non-header-aware clients can render an actionable error instead of an
|
||||
// opaque 5xx. The optional `retryAfterSeconds` carries the upstream Retry-After hint.
|
||||
const buildComboDiag = (
|
||||
terminalReason: string,
|
||||
retryAfterSeconds?: number
|
||||
): ComboDiagnostics => ({
|
||||
poolSize: state.orderedTargets.length,
|
||||
attempted: state.recordedAttempts,
|
||||
excluded: [
|
||||
...[...state.exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })),
|
||||
...[...state.exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))),
|
||||
],
|
||||
attemptOrder: state.comboAttemptOrder,
|
||||
terminalReason,
|
||||
recovery: buildRecoveryHint(terminalReason, retryAfterSeconds),
|
||||
});
|
||||
|
||||
let globalResolve: ((res: Response) => void) | null = null;
|
||||
const globalPromise = new Promise<Response>((res) => {
|
||||
globalResolve = res;
|
||||
});
|
||||
|
||||
// G1 (silent-stop fix): the speculative loop's `Promise.race` waits on
|
||||
// `globalPromise`, which is ONLY resolved from inside a task (success or
|
||||
// fatal error). If a target hangs — e.g. the operator disabled the per-model
|
||||
// timeout (`targetTimeoutMs: 0`) and the upstream never settles — the race
|
||||
// never resolves and the request hangs forever with no response. This safety
|
||||
// promise force-resolves after the combo budget (extra.comboTimeoutMs when set,
|
||||
// otherwise a hard ceiling) so the request ALWAYS terminates with an
|
||||
// actionable 504 instead of dying silently. `state.comboExpired` is flipped so the
|
||||
// target loop stops launching new work; the existing state.comboExpired branch
|
||||
// returns the aggregated 504.
|
||||
const loopSafetyMs =
|
||||
extra.comboTimeoutMs > 0 ? extra.comboTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS;
|
||||
let loopSafetyFired = false;
|
||||
let loopSafetyTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
const loopSafetyPromise = new Promise<Response>((resolve) => {
|
||||
loopSafetyTimer = setTimeout(() => {
|
||||
loopSafetyFired = true;
|
||||
deps.log.warn(
|
||||
"COMBO",
|
||||
`Combo loop safety timeout (${loopSafetyMs}ms) reached without a terminal response — force-terminating`
|
||||
);
|
||||
resolve(
|
||||
errorResponseWithComboDiagnostics(
|
||||
504,
|
||||
`Combo global timeout (${loopSafetyMs}ms) without a terminal response`,
|
||||
buildComboDiag("combo_timeout"),
|
||||
{ code: "COMBO_TIMEOUT", type: "server_error" }
|
||||
)
|
||||
);
|
||||
}, loopSafetyMs);
|
||||
loopSafetyTimer.unref?.();
|
||||
activeLoopSafetyTimer = loopSafetyTimer;
|
||||
});
|
||||
const runningTasks = new Set<Promise<void>>();
|
||||
let anySuccess = false;
|
||||
// #10681: steps already recorded as dispatched (so per-target retries do not
|
||||
// duplicate the decision).
|
||||
state.dispatchedTargets = new Set<string>();
|
||||
// G1: flip state.comboExpired as soon as the safety timer fires so the next loop
|
||||
// iteration breaks instead of launching more targets after the budget, and
|
||||
// abort every in-flight target so a hung upstream actually gets cancelled
|
||||
// (not just "response stops").
|
||||
const markLoopExpiredIfSafetyFired = () => {
|
||||
if (loopSafetyFired) {
|
||||
state.comboExpired = true;
|
||||
for (const [, ac] of state.abortControllers.entries()) ac.abort();
|
||||
}
|
||||
};
|
||||
state.abortControllers = new Map<number, AbortController>();
|
||||
const zeroLatencyOptimizationsEnabled = deps.config.zeroLatencyOptimizationsEnabled === true;
|
||||
const hasProtectedPriorityTarget =
|
||||
deps.strategy === "priority" &&
|
||||
state.orderedTargets.some((target) => target.fallbackOnlyOnQuotaExhaustion === true);
|
||||
|
||||
const executeTarget = async (i: number): Promise<ExecuteTargetResult> => {
|
||||
const gate = await extra.evaluateGates({ index: i, state, deps });
|
||||
if (gate.kind === "skip") return gate.result;
|
||||
return extra.executeAttempt({
|
||||
index: i,
|
||||
state,
|
||||
deps,
|
||||
targetForAttempt: gate.targetForAttempt,
|
||||
profile: gate.profile,
|
||||
protectedPriorityTarget: gate.protectedPriorityTarget,
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i < state.orderedTargets.length; i++) {
|
||||
if (anySuccess || state.comboExpired) break;
|
||||
|
||||
const abortController = new AbortController();
|
||||
state.abortControllers.set(i, abortController);
|
||||
const onClientAbort = () => abortController.abort();
|
||||
deps.signal?.addEventListener("abort", onClientAbort);
|
||||
|
||||
const task = (async () => {
|
||||
try {
|
||||
const res = await executeTarget(i);
|
||||
if (res && !anySuccess) {
|
||||
if (res.ok) {
|
||||
anySuccess = true;
|
||||
globalResolve!(res.response!);
|
||||
for (const [idx, ac] of state.abortControllers.entries()) {
|
||||
if (idx !== i) ac.abort();
|
||||
}
|
||||
} else if (res.response) {
|
||||
// Fatal error, abort combo
|
||||
anySuccess = true;
|
||||
globalResolve!(res.response);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
deps.signal?.removeEventListener("abort", onClientAbort);
|
||||
}
|
||||
})().catch((err) => {
|
||||
const logError = deps.log.error ?? deps.log.warn;
|
||||
logError("COMBO", `Speculative task error for target ${i}`, err);
|
||||
// G2 (silent-stop fix): never leave the speculative loop waiting on an
|
||||
// unresolved globalPromise. If a task throws unexpectedly (outside
|
||||
// executeTarget's error handling) and no other task succeeds, the post-loop
|
||||
// `Promise.race([globalPromise, ...])` would hang forever. Resolve with a
|
||||
// 502 so the request terminates with an actionable error.
|
||||
if (!anySuccess && globalResolve) {
|
||||
anySuccess = true;
|
||||
globalResolve(errorResponse(502, `Combo target ${i} failed with an unexpected error`));
|
||||
}
|
||||
});
|
||||
|
||||
runningTasks.add(task);
|
||||
task.finally(() => runningTasks.delete(task));
|
||||
|
||||
if (
|
||||
zeroLatencyOptimizationsEnabled &&
|
||||
deps.config.hedging &&
|
||||
!hasProtectedPriorityTarget &&
|
||||
i + 1 < state.orderedTargets.length
|
||||
) {
|
||||
const hedgeDelay = resolveDelayMs(deps.config.hedgeDelayMs, 500);
|
||||
const timeoutPromise = new Promise<void>((r) => {
|
||||
setTimeout(r, hedgeDelay);
|
||||
});
|
||||
await Promise.race([task, globalPromise, timeoutPromise, loopSafetyPromise]);
|
||||
} else {
|
||||
await Promise.race([task, globalPromise, loopSafetyPromise]);
|
||||
}
|
||||
markLoopExpiredIfSafetyFired();
|
||||
|
||||
// Global combo timeout check: after each target completes, stop trying
|
||||
// further targets if the total elapsed time exceeds extra.comboTimeoutMs.
|
||||
if (
|
||||
!anySuccess &&
|
||||
extra.comboTimeoutMs > 0 &&
|
||||
Date.now() - extra.comboStartTime >= extra.comboTimeoutMs
|
||||
) {
|
||||
state.comboExpired = true;
|
||||
deps.log.info(
|
||||
"COMBO",
|
||||
`Combo global timeout (${extra.comboTimeoutMs}ms) reached after ` +
|
||||
`${i + 1}/${state.orderedTargets.length} targets (${state.recordedAttempts} attempted) — stopping`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!anySuccess && runningTasks.size > 0) {
|
||||
// G1: include loopSafetyPromise so a hung last task (per-model timeout
|
||||
// disabled) cannot freeze this post-loop race forever.
|
||||
await Promise.race([globalPromise, Promise.all([...runningTasks]), loopSafetyPromise]);
|
||||
markLoopExpiredIfSafetyFired();
|
||||
}
|
||||
|
||||
// G1: if the safety timer won the race (request would otherwise hang), give
|
||||
// in-flight tasks a short drain window to land their per-model errors into
|
||||
// state.comboErrors so the 504 carries the same "tried: a (500)" summary the
|
||||
// regular state.comboExpired branch produces — then return the safety 504.
|
||||
if (loopSafetyFired && !anySuccess) {
|
||||
if (runningTasks.size > 0) {
|
||||
await Promise.race([
|
||||
Promise.allSettled([...runningTasks]),
|
||||
new Promise((resolve) => setTimeout(resolve, COMBO_SAFETY_DRAIN_MS)),
|
||||
]);
|
||||
}
|
||||
const summary = state.comboErrors
|
||||
.slice(0, 5)
|
||||
.map((e) => `${e.model} (${e.status})`)
|
||||
.join(", ");
|
||||
const msg =
|
||||
`Combo global timeout (${loopSafetyMs}ms) after ${state.recordedAttempts}/${state.orderedTargets.length} targets` +
|
||||
(state.comboErrors.length > 0
|
||||
? ` | tried: ${summary}${state.comboErrors.length > 5 ? `... (+${state.comboErrors.length - 5})` : ""}`
|
||||
: "") +
|
||||
" without a terminal response";
|
||||
return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), {
|
||||
code: "COMBO_TIMEOUT",
|
||||
type: "server_error",
|
||||
});
|
||||
}
|
||||
|
||||
// #10681: finalize the decision trace (success).
|
||||
finalizeComboTrace(deps.traceInvocationId, state.orderedTargets);
|
||||
finishComboTrace(deps.traceInvocationId, { status: 200 });
|
||||
if (anySuccess) {
|
||||
// G1: clear the safety timer on the happy path so a successful combo does
|
||||
// not leave a 10-minute timer alive per request.
|
||||
if (loopSafetyTimer) {
|
||||
clearTimeout(loopSafetyTimer);
|
||||
loopSafetyTimer = null;
|
||||
}
|
||||
return await globalPromise;
|
||||
}
|
||||
|
||||
// #10681: finalize the decision trace (global timeout).
|
||||
finalizeComboTrace(deps.traceInvocationId, state.orderedTargets);
|
||||
finishComboTrace(deps.traceInvocationId, { status: 504 });
|
||||
// Global combo timeout: return aggregated error immediately, skipping set retries.
|
||||
if (state.comboExpired) {
|
||||
const summary = buildRedactedSummary(state.comboErrors);
|
||||
const msg =
|
||||
`Combo global timeout (${extra.comboTimeoutMs}ms) after ${state.recordedAttempts}/${state.orderedTargets.length} targets` +
|
||||
(state.comboErrors.length > 0 ? ` | tried: ${summary}` : "");
|
||||
const latencyMs = Date.now() - deps.startTime;
|
||||
if (state.recordedAttempts === 0) {
|
||||
recordComboRequest(deps.combo.name, null, {
|
||||
success: false,
|
||||
latencyMs,
|
||||
fallbackCount: state.fallbackCount,
|
||||
strategy: deps.strategy,
|
||||
});
|
||||
}
|
||||
notifyWebhookEvent("request.failed", {
|
||||
combo: deps.combo.name,
|
||||
reason: "COMBO_TIMEOUT",
|
||||
latencyMs,
|
||||
fallbackCount: state.fallbackCount,
|
||||
});
|
||||
return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), {
|
||||
code: "COMBO_TIMEOUT",
|
||||
type: "server_error",
|
||||
});
|
||||
}
|
||||
|
||||
// All models failed in this set try
|
||||
const latencyMs = Date.now() - deps.startTime;
|
||||
if (state.recordedAttempts === 0) {
|
||||
recordComboRequest(deps.combo.name, null, {
|
||||
success: false,
|
||||
latencyMs,
|
||||
fallbackCount: state.fallbackCount,
|
||||
strategy: deps.strategy,
|
||||
});
|
||||
}
|
||||
|
||||
// Retry the entire set if more attempts remain
|
||||
if (setTry < extra.maxSetRetries) continue;
|
||||
|
||||
if (!state.lastStatus && state.recordedAttempts === 0 && extra.comboCooldownWaitEnabled) {
|
||||
const circuitOpenWait = resolveCircuitOpenWaitDecision({
|
||||
skippedForCircuitOpen: state.skippedForCircuitOpen,
|
||||
retryAfterMs: state.earliestCircuitOpenRetryMs,
|
||||
attempt: extra.comboCooldownAttempt.current,
|
||||
budgetLeftMs: extra.comboCooldownBudgetLeftMs.current,
|
||||
settings: deps.resilienceSettings.comboCooldownWait,
|
||||
});
|
||||
if (circuitOpenWait.wait) {
|
||||
deps.log.info(
|
||||
"COMBO",
|
||||
`${deps.strategy} circuit-open wait: waiting ${Math.ceil(circuitOpenWait.waitMs / 1000)}s (reason=${circuitOpenWait.reason ?? "circuit_open"}) then retrying (attempt ${extra.comboCooldownAttempt.current + 1}/${deps.resilienceSettings.comboCooldownWait.maxAttempts})`
|
||||
);
|
||||
const completed = await waitForCooldownAwareRetry(circuitOpenWait.waitMs, deps.signal);
|
||||
if (!completed) {
|
||||
return errorResponse(499, "Request aborted");
|
||||
}
|
||||
extra.comboCooldownAttempt.current += 1;
|
||||
extra.comboCooldownBudgetLeftMs.current = Math.max(
|
||||
0,
|
||||
extra.comboCooldownBudgetLeftMs.current - circuitOpenWait.waitMs
|
||||
);
|
||||
return dispatchWithCooldownRetry({ state, deps, extra });
|
||||
}
|
||||
}
|
||||
|
||||
// All set retries exhausted — return the final error
|
||||
// #10681: finalize the decision trace (all targets failed or skipped).
|
||||
finalizeComboTrace(deps.traceInvocationId, state.orderedTargets);
|
||||
finishComboTrace(deps.traceInvocationId, { status: 503 });
|
||||
if (!state.lastStatus) {
|
||||
if (state.recordedAttempts === 0) {
|
||||
notifyWebhookEvent("request.failed", {
|
||||
combo: deps.combo.name,
|
||||
reason: "ALL_TARGETS_SKIPPED",
|
||||
latencyMs,
|
||||
fallbackCount: state.fallbackCount,
|
||||
});
|
||||
return withQuotaExhaustionClassification(
|
||||
errorResponseWithComboDiagnostics(
|
||||
503,
|
||||
"Service temporarily unavailable: all targets were skipped by pre-dispatch filters",
|
||||
buildComboDiag("all_targets_skipped"),
|
||||
{ code: "ALL_TARGETS_SKIPPED", type: "service_unavailable" }
|
||||
),
|
||||
state.observedFailure ? state.allObservedFailuresQuota : null
|
||||
);
|
||||
}
|
||||
notifyWebhookEvent("request.failed", {
|
||||
combo: deps.combo.name,
|
||||
reason: "ALL_ACCOUNTS_INACTIVE",
|
||||
latencyMs,
|
||||
fallbackCount: state.fallbackCount,
|
||||
});
|
||||
recordComboFailure(deps.effectiveSessionId, deps.combo.name);
|
||||
return errorResponseWithComboDiagnostics(
|
||||
503,
|
||||
"Service temporarily unavailable: all upstream accounts are inactive",
|
||||
buildComboDiag("all_accounts_inactive"),
|
||||
{ code: "ALL_ACCOUNTS_INACTIVE", type: "service_unavailable" }
|
||||
);
|
||||
}
|
||||
|
||||
// #10501: derive the terminal HTTP status from the structured per-target
|
||||
// outcomes instead of `state.lastStatus` (whichever target happened to fail
|
||||
// LAST). A 4xx is preserved only when the request itself is genuinely
|
||||
// invalid across every eligible target; a heterogeneous mix of failure
|
||||
// classes (e.g. a quality failure + a sibling's 401) normalizes to a
|
||||
// 5xx-class status reflecting an infra/provider problem, not a client
|
||||
// error. See comboErrorAggregation.ts::resolveComboTerminalStatus.
|
||||
const status = resolveComboTerminalStatus(state.comboErrors, state.lastStatus);
|
||||
// #10314: build the terminal message from the structured per-target
|
||||
// outcomes (each distinct class+reason listed separately) instead of
|
||||
// mashing a single state.lastError with raw `[model (status)]` markers. Connection
|
||||
// identifiers are redacted. Falls back to state.lastError when no target recorded
|
||||
// a structured outcome.
|
||||
const msg =
|
||||
formatComboOutcomes(state.comboErrors) || state.lastError || "All combo models unavailable";
|
||||
|
||||
// Cooldown-aware retry: instead of crystallizing a transient failure, wait
|
||||
// out a SHORT cooldown and re-run the whole set loop. Guarded by the helper
|
||||
// (quota_exhausted/auth/not-found excluded, ceiling, attempts, budget).
|
||||
// MAX_GLOBAL_ATTEMPTS still bounds total dispatches. Available to ALL combo
|
||||
// strategies when enabled — entry is driven by earliestRetryAfter + the
|
||||
// real model-lockout reason, NOT by whichever target last overwrote
|
||||
// `status` (a later 403 must not skip the allow-list check for an earlier
|
||||
// 429's retry-after hint). SECURITY (see comboCooldownRetry.ts header): the
|
||||
// allow-list is the PRIMARY barrier and `maxWaitMs` only the SECOND one.
|
||||
// Hardcoding reason:"rate_limit" would drop the primary barrier and leave
|
||||
// only the ceiling — which does NOT cover a quota_exhausted lock carrying a
|
||||
// SHORT upstream retry-after. Model lockouts are recorded for all strategies,
|
||||
// so the real reason is always available.
|
||||
if (extra.comboCooldownWaitEnabled && state.earliestRetryAfter) {
|
||||
const decision: ResolveComboCooldownDecisionResult = resolveComboCooldownWaitDecision({
|
||||
targets: state.orderedTargets,
|
||||
earliestRetryAfter: state.earliestRetryAfter,
|
||||
attempt: extra.comboCooldownAttempt.current,
|
||||
budgetLeftMs: extra.comboCooldownBudgetLeftMs.current,
|
||||
settings: deps.resilienceSettings.comboCooldownWait,
|
||||
// Key each lookup on the TARGET's own model: quota-share combos are
|
||||
// single-model/multi-account (so this is identical to the previous
|
||||
// state.orderedTargets[0] behavior), but heterogeneous combos carry a
|
||||
// different model per target.
|
||||
lookupLock: (provider, connectionId, target) => {
|
||||
const rawModel = parseModel(target?.modelStr ?? "").model || "";
|
||||
if (!rawModel) return null;
|
||||
return getModelLockoutInfo(provider, connectionId, rawModel);
|
||||
},
|
||||
computeWaitMs: (retryAfter) => computeClosestRetryAfter(retryAfter).waitMs,
|
||||
});
|
||||
|
||||
if (decision.wait) {
|
||||
deps.log.info(
|
||||
"COMBO",
|
||||
`${deps.strategy} cooldown wait: ${msg} — waiting ${Math.ceil(
|
||||
decision.waitMs / 1000
|
||||
)}s (reason=${decision.reason ?? "?"}) then retrying (attempt ${
|
||||
extra.comboCooldownAttempt.current + 1
|
||||
}/${deps.resilienceSettings.comboCooldownWait.maxAttempts})`
|
||||
);
|
||||
const completed = await waitForCooldownAwareRetry(decision.waitMs, deps.signal);
|
||||
if (!completed) {
|
||||
deps.log.info("COMBO", `${deps.strategy} cooldown wait aborted by client disconnect`);
|
||||
return errorResponse(499, "Request aborted");
|
||||
}
|
||||
extra.comboCooldownAttempt.current += 1;
|
||||
extra.comboCooldownBudgetLeftMs.current = Math.max(
|
||||
0,
|
||||
extra.comboCooldownBudgetLeftMs.current - decision.waitMs
|
||||
);
|
||||
return dispatchWithCooldownRetry({ state, deps, extra });
|
||||
}
|
||||
}
|
||||
|
||||
// #10681: finalize the decision trace with the aggregated terminal status.
|
||||
finalizeComboTrace(deps.traceInvocationId, state.orderedTargets);
|
||||
finishComboTrace(deps.traceInvocationId, { status });
|
||||
// Retry-after decoration is separate from the wait decision above: only
|
||||
// rate-limit-class final statuses may carry a `(reset after ...)` suffix
|
||||
// (see unavailableRetryGate.ts — do not stitch a peer target's window onto
|
||||
// a config-class status like 403/422).
|
||||
if (state.earliestRetryAfter && isRetryAfterEligibleStatus(status)) {
|
||||
const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(state.earliestRetryAfter));
|
||||
deps.log.warn("COMBO", `All models failed | ${msg} (${retryHuman})`);
|
||||
return withQuotaExhaustionClassification(
|
||||
unavailableResponse(status, msg, state.earliestRetryAfter, retryHuman),
|
||||
state.observedFailure ? state.allObservedFailuresQuota : null
|
||||
);
|
||||
}
|
||||
|
||||
// Silent-stop fix: bump the failure counter (pin clears on 3rd consecutive) and emit
|
||||
// `try-auto` recovery action via buildRecoveryHint so the OC plugin can show "→ Try
|
||||
// model: auto" instead of an opaque 5xx. We pass the upstream retry-after seconds to
|
||||
// the hint so the client can render a precise "wait Ns and retry" message.
|
||||
deps.log.warn("COMBO", `All models failed | ${msg}`);
|
||||
const { pinClearedNow } = recordComboFailure(deps.effectiveSessionId, deps.combo.name);
|
||||
if (pinClearedNow) {
|
||||
deps.log.info(
|
||||
"COMBO",
|
||||
`Auto-cleared session_model_history pin for combo "${deps.combo.name}" after ${COMBO_FAILURE_THRESHOLD} consecutive failures to break the silent-stop loop`
|
||||
);
|
||||
}
|
||||
const retryAfterSeconds = undefined;
|
||||
// #10966: when every observed failure was independently classified as quota/
|
||||
// balance exhaustion (isQuotaExhaustionResponse, tracked via state.observeFailure's
|
||||
// state.allObservedFailuresQuota accumulator), stamp a stable `quota_exhausted`
|
||||
// terminalReason instead of forwarding the raw upstream error string. The raw
|
||||
// string falls through buildRecoveryHint's default branch ("retry" / "failed
|
||||
// transiently"), which is actively misleading for a durable wallet/quota
|
||||
// exhaustion — retrying the same combo will never refill it.
|
||||
const terminalReason =
|
||||
state.observedFailure && state.allObservedFailuresQuota
|
||||
? "quota_exhausted"
|
||||
: (state.lastError ?? "all_models_failed");
|
||||
return withQuotaExhaustionClassification(
|
||||
errorResponseWithComboDiagnostics(
|
||||
status,
|
||||
msg,
|
||||
buildComboDiag(terminalReason, retryAfterSeconds)
|
||||
),
|
||||
state.observedFailure ? state.allObservedFailuresQuota : null
|
||||
);
|
||||
}
|
||||
|
||||
// Final fallback — when the dispatch returned without crystallizing a status (rare).
|
||||
// Surface the recovery hint with a generic retry recommendation so the client at least
|
||||
// gets a non-opaque message instead of "Combo routing completed without an upstream response".
|
||||
recordComboFailure(deps.effectiveSessionId, deps.combo.name);
|
||||
return errorResponseWithComboDiagnostics(
|
||||
503,
|
||||
"Combo routing completed without an upstream response",
|
||||
buildNoUpstreamResponseDiagnostics(state.orderedTargets.length)
|
||||
);
|
||||
} finally {
|
||||
// #11804: always release the loop-safety timer. Covering every exit path by
|
||||
// construction here means a future `return` added to this function cannot
|
||||
// silently reintroduce the leak.
|
||||
if (activeLoopSafetyTimer) {
|
||||
clearTimeout(activeLoopSafetyTimer);
|
||||
activeLoopSafetyTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,11 @@
|
||||
|
||||
import { getModelContextLimit } from "../../../src/lib/modelCapabilities";
|
||||
import { getHiddenModelsByProvider } from "../../../src/lib/db/models";
|
||||
import { getComboModelString, normalizeComboStep } from "../../../src/lib/combos/steps.ts";
|
||||
import {
|
||||
getComboModelString,
|
||||
implicitPinAllowlist,
|
||||
normalizeComboStep,
|
||||
} from "../../../src/lib/combos/steps.ts";
|
||||
import { getProviderByAlias, getProviderById } from "../../../src/shared/constants/providers.ts";
|
||||
import { estimateTokens } from "../contextManager.ts";
|
||||
import { containsMediaKind } from "../../utils/mediaParts.ts";
|
||||
@@ -121,6 +125,9 @@ function normalizeRuntimeStep(
|
||||
const modelStr = getComboModelString(step);
|
||||
if (!modelStr) return null;
|
||||
|
||||
const connectionId = toTrimmedString(step.connectionId);
|
||||
const allowedConnectionIds = implicitPinAllowlist(connectionId, step.allowedConnectionIds);
|
||||
|
||||
return {
|
||||
kind: "model",
|
||||
stepId: step.id,
|
||||
@@ -128,13 +135,13 @@ function normalizeRuntimeStep(
|
||||
modelStr,
|
||||
provider: getTargetProvider(modelStr, step.providerId),
|
||||
providerId: step.providerId || null,
|
||||
connectionId: step.connectionId || null,
|
||||
connectionId,
|
||||
// #3266: a per-step account allowlist scopes round-robin/weighted selection
|
||||
// to a subset of the provider's connections. This is the second writer of
|
||||
// `allowedConnectionIds` (tag routing is the first); both feed the existing
|
||||
// credential-selection filter in auth.ts.
|
||||
...(Array.isArray(step.allowedConnectionIds) && step.allowedConnectionIds.length > 0
|
||||
? { allowedConnectionIds: step.allowedConnectionIds }
|
||||
...(allowedConnectionIds && allowedConnectionIds.length > 0
|
||||
? { allowedConnectionIds }
|
||||
: {}),
|
||||
weight,
|
||||
label,
|
||||
|
||||
@@ -23,6 +23,56 @@
|
||||
*/
|
||||
|
||||
import { getResolvedModelContextOverride } from "../../../src/lib/modelCapabilities";
|
||||
import { parseModel } from "../model.ts";
|
||||
|
||||
/**
|
||||
* Longest-first so `-xhigh` is not eaten by `-high`. Mirrors
|
||||
* `stripKnownEffortSuffix` in modelCapabilities.ts, but that helper's array
|
||||
* order still matches `-high` first (`"…-xhigh".endsWith("-high")`).
|
||||
*/
|
||||
const EFFORT_SUFFIXES_LONGEST_FIRST = [
|
||||
"minimal",
|
||||
"medium",
|
||||
"xhigh",
|
||||
"none",
|
||||
"high",
|
||||
"max",
|
||||
"low",
|
||||
] as const;
|
||||
|
||||
function stripTrailingEffortSuffix(modelId: string): string | null {
|
||||
const normalized = String(modelId || "").trim();
|
||||
if (!normalized) return null;
|
||||
const lowered = normalized.toLowerCase();
|
||||
for (const suffix of EFFORT_SUFFIXES_LONGEST_FIRST) {
|
||||
const token = `-${suffix}`;
|
||||
if (lowered.length > token.length && lowered.endsWith(token)) {
|
||||
return normalized.slice(0, -token.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exact override first; if missing, inherit the base id after stripping a
|
||||
* trailing effort tier (#12475). Combo members are stored as
|
||||
* `provider/GLM-5.3-high` while `model_context_overrides` is keyed on
|
||||
* `GLM-5.3`. Dispatcher already strips the suffix; the compat filter did not.
|
||||
*/
|
||||
function lookupOverrideWithEffortInheritance(modelStr: string): number | null {
|
||||
const exact = getResolvedModelContextOverride(modelStr);
|
||||
if (exact != null) return exact;
|
||||
|
||||
const parsed = parseModel(modelStr);
|
||||
const modelId = typeof parsed.model === "string" ? parsed.model.trim() : "";
|
||||
const base = stripTrailingEffortSuffix(modelId);
|
||||
if (!base || base === modelId) return null;
|
||||
|
||||
if (parsed.provider) {
|
||||
return getResolvedModelContextOverride({ provider: parsed.provider, model: base });
|
||||
}
|
||||
return getResolvedModelContextOverride(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the context-fit verdict from a persisted per-model override, if one
|
||||
@@ -35,7 +85,7 @@ function resolveContextOverrideVerdict(
|
||||
requiredContextTokens: number
|
||||
): boolean | undefined {
|
||||
if (!modelStr) return undefined;
|
||||
const override = getResolvedModelContextOverride(modelStr);
|
||||
const override = lookupOverrideWithEffortInheritance(modelStr);
|
||||
if (override == null) return undefined;
|
||||
return override >= requiredContextTokens;
|
||||
}
|
||||
|
||||
1204
open-sse/services/combo/executeTargetAttempt.ts
Normal file
54
open-sse/services/combo/executeTargetClassify.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Pure classify helpers for executeTarget's retry loop.
|
||||
* Lift-as-is from combo.ts #8375 / #2101 / #4279. No I/O.
|
||||
*
|
||||
* @internal — not part of the public combo.ts barrel.
|
||||
*/
|
||||
import {
|
||||
isContextOverflow400,
|
||||
isInputBoundRequestFailure,
|
||||
isModelScoped400,
|
||||
isParamValidation400,
|
||||
} from "./comboPredicates.ts";
|
||||
|
||||
export function remainderIsHomogeneous(
|
||||
orderedTargets: { modelStr: string }[],
|
||||
index: number,
|
||||
modelStr: string
|
||||
): boolean {
|
||||
return orderedTargets.slice(index + 1).every((nextInPool) => nextInPool.modelStr === modelStr);
|
||||
}
|
||||
|
||||
export function shouldAbortOnInputBoundFailure(opts: {
|
||||
structuredError: unknown;
|
||||
remainderIsHomogeneous: boolean;
|
||||
}): boolean {
|
||||
const structured = opts.structuredError as
|
||||
{ code?: string | null; type?: string | null } | undefined;
|
||||
return isInputBoundRequestFailure(structured) && opts.remainderIsHomogeneous;
|
||||
}
|
||||
|
||||
/**
|
||||
* #2101 / #4279: body-specific 400 must surface via {ok,response}, not null.
|
||||
* Same predicate chain as combo.ts (overflow / param / model-scoped excluded).
|
||||
*/
|
||||
export function shouldSurfaceBodySpecific400(opts: {
|
||||
status: number;
|
||||
errorText: string;
|
||||
shouldFallback: boolean;
|
||||
}): boolean {
|
||||
const errorText = opts.errorText;
|
||||
return (
|
||||
opts.status === 400 &&
|
||||
opts.shouldFallback &&
|
||||
!isContextOverflow400(errorText) &&
|
||||
!isParamValidation400(errorText) &&
|
||||
!isModelScoped400(errorText) &&
|
||||
(errorText.toLowerCase().includes("context") ||
|
||||
errorText.toLowerCase().includes("prompt") ||
|
||||
errorText.toLowerCase().includes("token") ||
|
||||
errorText.toLowerCase().includes("malformed") ||
|
||||
errorText.toLowerCase().includes("invalid") ||
|
||||
errorText.toLowerCase().includes("bad request"))
|
||||
);
|
||||
}
|
||||