mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-27 18:32:16 +03:00
Compare commits
1235 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
476ef48fa5 | ||
|
|
4b9c129e1c | ||
|
|
b75b52eefb | ||
|
|
230f0410f7 | ||
|
|
59f4d42795 | ||
|
|
ebbd7c34c8 | ||
|
|
1e3c08565c | ||
|
|
e4beb49c6d | ||
|
|
8400dbea7d | ||
|
|
c12b7546a8 | ||
|
|
01092fa349 | ||
|
|
41ff569260 | ||
|
|
1b2a4fd659 | ||
|
|
00182afb2f | ||
|
|
a10a4bf491 | ||
|
|
399a911675 | ||
|
|
7ec572ca47 | ||
|
|
66193a2ac8 | ||
|
|
95a43597c9 | ||
|
|
3d78cd6848 | ||
|
|
e21ebaa389 | ||
|
|
d2ce20dc5c | ||
|
|
c766aa5403 | ||
|
|
35d56358b8 | ||
|
|
29c170f83a | ||
|
|
64b8194210 | ||
|
|
f8a23f41a6 | ||
|
|
cf703138f2 | ||
|
|
abe8cae083 | ||
|
|
fa950a8a49 | ||
|
|
a906bda7fc | ||
|
|
8f14452614 | ||
|
|
2666043daf | ||
|
|
20358bc8bf | ||
|
|
b44d1d9b90 | ||
|
|
eadcf43ca0 | ||
|
|
4e79d4708c | ||
|
|
a7df2b0b55 | ||
|
|
7b575f38aa | ||
|
|
f7d45ef31f | ||
|
|
85f2f8d8d8 | ||
|
|
1f9b340d13 | ||
|
|
fb95689601 | ||
|
|
d8e977cb42 | ||
|
|
89886e69a9 | ||
|
|
8ad5f0dbcc | ||
|
|
e9bb874ddc | ||
|
|
9042d5049d | ||
|
|
3a8defed09 | ||
|
|
0428943d84 | ||
|
|
373181dade | ||
|
|
f44fb3d9b7 | ||
|
|
32e0a7cb16 | ||
|
|
4ac29c4d9b | ||
|
|
3d70ad414e | ||
|
|
21f4f8ddce | ||
|
|
f3226b7f8d | ||
|
|
ea9d9e09ba | ||
|
|
ea6f556ed2 | ||
|
|
10b125115e | ||
|
|
ce6706f3e9 | ||
|
|
ec863041f9 | ||
|
|
3ff2f6a7c9 | ||
|
|
983263e45e | ||
|
|
f8c7e7904e | ||
|
|
934c133103 | ||
|
|
8ebddc2ca3 | ||
|
|
9aa89b17a5 | ||
|
|
94ba3b3a23 | ||
|
|
8ca5902a98 | ||
|
|
de4cbe2e3f | ||
|
|
fa0f3cc8be | ||
|
|
eaeab62373 | ||
|
|
7b21e04604 | ||
|
|
dc23c2c0d1 | ||
|
|
5e0d6263ed | ||
|
|
89ef91ca1b | ||
|
|
4597cfd54e | ||
|
|
595b5dc642 | ||
|
|
e0a4f703cb | ||
|
|
16f14b76a9 | ||
|
|
e9d2ebb4f9 | ||
|
|
e929559e9a | ||
|
|
6c172df547 | ||
|
|
902d0b9f41 | ||
|
|
cd27c5cf33 | ||
|
|
1b26018534 | ||
|
|
cc460b36b9 | ||
|
|
d26b92b551 | ||
|
|
3963575529 | ||
|
|
294751d8a3 | ||
|
|
3ec9ca11b1 | ||
|
|
24ffdde03d | ||
|
|
c7b7b847f0 | ||
|
|
e12814fb98 | ||
|
|
90873cd3e2 | ||
|
|
ff80f03953 | ||
|
|
ce3e4ba618 | ||
|
|
71702a086c | ||
|
|
6d6a4abb8a | ||
|
|
9e0d2f6a70 | ||
|
|
0cd388efb8 | ||
|
|
4cdd0dfd1a | ||
|
|
d7cfb626bb | ||
|
|
8053ebe798 | ||
|
|
57e55268e5 | ||
|
|
4f7a0c265d | ||
|
|
1292465a5b | ||
|
|
ae8a7d2dc5 | ||
|
|
2f1c904d74 | ||
|
|
aa535748ef | ||
|
|
42952ee42f | ||
|
|
8a0ca60e4b | ||
|
|
e6e7f8d402 | ||
|
|
edd6941de4 | ||
|
|
6bf7b9601d | ||
|
|
dc103318ab | ||
|
|
1194a1a5a5 | ||
|
|
70d7fc71b3 | ||
|
|
96fa89052b | ||
|
|
97505e200a | ||
|
|
a16e47c593 | ||
|
|
4080eb9312 | ||
|
|
c9538194aa | ||
|
|
73022ff3b3 | ||
|
|
58fb988c52 | ||
|
|
95f7c69e36 | ||
|
|
b3d928c593 | ||
|
|
ea691bfd41 | ||
|
|
00ae48f58d | ||
|
|
d5ea907d69 | ||
|
|
8100b53fd7 | ||
|
|
f23f301547 | ||
|
|
bda1f290ac | ||
|
|
2f905598e8 | ||
|
|
f769ce93cc | ||
|
|
9c9ba8f2bd | ||
|
|
67bce7721b | ||
|
|
84fbfa36c6 | ||
|
|
a46e920148 | ||
|
|
cf959c768d | ||
|
|
34b0cda024 | ||
|
|
e93721162d | ||
|
|
013e33e1a5 | ||
|
|
084f2c53ce | ||
|
|
9e198184a7 | ||
|
|
7f3dccf6dd | ||
|
|
0274af8c9c | ||
|
|
905b7555c2 | ||
|
|
b1974dac12 | ||
|
|
610b6fda3b | ||
|
|
1a5010b2c6 | ||
|
|
3ee0150367 | ||
|
|
4ecddaacd9 | ||
|
|
819c0762b9 | ||
|
|
26edd01ca3 | ||
|
|
a5e32a6d32 | ||
|
|
caeba1fd91 | ||
|
|
cd67c18049 | ||
|
|
c7074761c5 | ||
|
|
f399ece9f9 | ||
|
|
eace1dc44d | ||
|
|
4b475df5b2 | ||
|
|
c0d92f3569 | ||
|
|
88dbefa141 | ||
|
|
74a37bcf78 | ||
|
|
8a8e6ca349 | ||
|
|
ed9a7e5495 | ||
|
|
ccda2fbe44 | ||
|
|
cc07e5f7f6 | ||
|
|
31a0628cf1 | ||
|
|
18a25e4e4c | ||
|
|
778a7170a5 | ||
|
|
1c6d54ef57 | ||
|
|
da4c3660f4 | ||
|
|
665b3e2d5d | ||
|
|
b1e13668f2 | ||
|
|
b5f6bea880 | ||
|
|
5666950929 | ||
|
|
021cfd791f | ||
|
|
cdb271ea23 | ||
|
|
ba6a8602fb | ||
|
|
d4a92830be | ||
|
|
03ec5808ff | ||
|
|
6eb5d663b0 | ||
|
|
6e0b801b6a | ||
|
|
6747e22757 | ||
|
|
6dd883e5f4 | ||
|
|
4671a1eb98 | ||
|
|
845e2b3d01 | ||
|
|
bc91fb9e54 | ||
|
|
9d334c82b9 | ||
|
|
98e70a706e | ||
|
|
eec5fa3feb | ||
|
|
712f1ea3e9 | ||
|
|
388b84de3c | ||
|
|
9881e190bf | ||
|
|
4ac4ac3fd4 | ||
|
|
cf0f947adb | ||
|
|
ae77d1370e | ||
|
|
4d48454c11 | ||
|
|
c9fc36ca14 | ||
|
|
3ff6137767 | ||
|
|
cbe7358dc8 | ||
|
|
3008ba9a13 | ||
|
|
f14679f7a5 | ||
|
|
97912c7d9c | ||
|
|
725ec7f2a9 | ||
|
|
c26c3a46a9 | ||
|
|
19edb8efa4 | ||
|
|
52d5b86e88 | ||
|
|
738e108d06 | ||
|
|
78b845b68c | ||
|
|
56ae1b8246 | ||
|
|
314cad79ba | ||
|
|
4eef082cf3 | ||
|
|
acf6ad4ba0 | ||
|
|
2601e0e49d | ||
|
|
f00e9a1272 | ||
|
|
cabdfb04e7 | ||
|
|
6dcbdfd605 | ||
|
|
d60a5465db | ||
|
|
d2f32090b7 | ||
|
|
0fb535db8e | ||
|
|
f0a750c8a3 | ||
|
|
29c92c38fb | ||
|
|
cb89abc53a | ||
|
|
a71dd1aee6 | ||
|
|
fec0deb758 | ||
|
|
ff28b1a990 | ||
|
|
e30969fecd | ||
|
|
dbd4b0a7d2 | ||
|
|
71745820ed | ||
|
|
abeb78b95c | ||
|
|
4692621e4f | ||
|
|
1c86cd5fec | ||
|
|
fbf129da56 | ||
|
|
0c90c9768b | ||
|
|
dd67b25df1 | ||
|
|
accb58f5b2 | ||
|
|
d08301de8c | ||
|
|
9c9f2b9d45 | ||
|
|
05a50fcf53 | ||
|
|
744a5606e4 | ||
|
|
aa6b2f7567 | ||
|
|
167404f19e | ||
|
|
a691893413 | ||
|
|
213d38cd50 | ||
|
|
d179ace708 | ||
|
|
6df01aeb34 | ||
|
|
e30b706e66 | ||
|
|
6dc5bb9437 | ||
|
|
ead269d30d | ||
|
|
72afe8fe04 | ||
|
|
abaf2e9704 | ||
|
|
3547258282 | ||
|
|
77373ef9af | ||
|
|
a6c96257a5 | ||
|
|
091b1238da | ||
|
|
8a8fcc77a8 | ||
|
|
13495d4d13 | ||
|
|
df76aaef96 | ||
|
|
1e9e6d4349 | ||
|
|
b9c20b2b06 | ||
|
|
d9120c7b56 | ||
|
|
3dba954900 | ||
|
|
7c56918787 | ||
|
|
fa8ca5dcde | ||
|
|
94ae0f1921 | ||
|
|
f9e799d089 | ||
|
|
ce9dc8e851 | ||
|
|
604d55ed42 | ||
|
|
1beb372057 | ||
|
|
9325553ff9 | ||
|
|
17b5a8540e | ||
|
|
1f66b67309 | ||
|
|
cb4ad33703 | ||
|
|
7775c8cc50 | ||
|
|
e766fb1f13 | ||
|
|
a42da2af01 | ||
|
|
99331f777e | ||
|
|
35a1f6a529 | ||
|
|
d403186e6e | ||
|
|
50097b1fb1 | ||
|
|
3478ac6538 | ||
|
|
a5be284f1d | ||
|
|
1dd7ec91fe | ||
|
|
fc80a986f2 | ||
|
|
299793e788 | ||
|
|
8684d43ebd | ||
|
|
e4a27c41a6 | ||
|
|
43d7f0c312 | ||
|
|
9eac8c2efe | ||
|
|
e64dc3b431 | ||
|
|
872e597512 | ||
|
|
f5df0d337d | ||
|
|
eada1321ff | ||
|
|
57b926f739 | ||
|
|
f4d3cf3576 | ||
|
|
1f962a2167 | ||
|
|
0e15988233 | ||
|
|
af5d800759 | ||
|
|
1e60f0ce51 | ||
|
|
c9aef0e595 | ||
|
|
31acf52d0a | ||
|
|
5b5e21a99e | ||
|
|
7ed15c742f | ||
|
|
7d34b6a7e1 | ||
|
|
619d8c937d | ||
|
|
35c29faf99 | ||
|
|
15b4a54454 | ||
|
|
1b84c04dcd | ||
|
|
5b8e41c1b2 | ||
|
|
7073928abb | ||
|
|
76e34f093d | ||
|
|
4f6f97b369 | ||
|
|
a972293151 | ||
|
|
78531fe033 | ||
|
|
b10b724d17 | ||
|
|
80e3858ae4 | ||
|
|
c656123db9 | ||
|
|
3a0bc955be | ||
|
|
e2469b8f6d | ||
|
|
4057fe55a2 | ||
|
|
968cbfeb15 | ||
|
|
01dd72cf1f | ||
|
|
9a8404b733 | ||
|
|
47468636e4 | ||
|
|
05005acabd | ||
|
|
a42ef13b61 | ||
|
|
c5dcf35281 | ||
|
|
34bdd1e8d0 | ||
|
|
1568c53faa | ||
|
|
6b86fb9966 | ||
|
|
2a58543b4d | ||
|
|
0f95330bc1 | ||
|
|
bdfcec7cf7 | ||
|
|
7388623244 | ||
|
|
fff025ca0f | ||
|
|
331e1b7d61 | ||
|
|
16b0499192 | ||
|
|
fe09fe485f | ||
|
|
d52a5af87d | ||
|
|
d539a21f69 | ||
|
|
128f242808 | ||
|
|
66bf8054f9 | ||
|
|
d99cf6350f | ||
|
|
9bd7cb7d00 | ||
|
|
f804a5f443 | ||
|
|
7549a68d6c | ||
|
|
1ef354465a | ||
|
|
a7b6dfb06e | ||
|
|
98c5b250b9 | ||
|
|
098639e146 | ||
|
|
a7d30d9c6f | ||
|
|
0937c5a8a3 | ||
|
|
56608a4654 | ||
|
|
8bb1c83479 | ||
|
|
b709dca2d6 | ||
|
|
b925be2758 | ||
|
|
2e36599d40 | ||
|
|
0d852ab3e0 | ||
|
|
495ca290e0 | ||
|
|
de1bea8227 | ||
|
|
9cd36af3e8 | ||
|
|
3502c5afd1 | ||
|
|
03769eb0ea | ||
|
|
76b0f077a4 | ||
|
|
1dae161823 | ||
|
|
25e22df2cf | ||
|
|
96d0f57558 | ||
|
|
f651a27418 | ||
|
|
3834768b72 | ||
|
|
0d86244c90 | ||
|
|
a389f2699a | ||
|
|
3478dea5de | ||
|
|
0c24ab45af | ||
|
|
cde6f56014 | ||
|
|
9754b04bd6 | ||
|
|
d19b8d83e8 | ||
|
|
496114ae8b | ||
|
|
ceca26ae87 | ||
|
|
20f04574e5 | ||
|
|
1d3d99bb9e | ||
|
|
b38e57d452 | ||
|
|
763d353f5c | ||
|
|
eaa74afb42 | ||
|
|
3245779c11 | ||
|
|
a2a7eb5630 | ||
|
|
bd5dd7cb21 | ||
|
|
9311f2a627 | ||
|
|
bfe64156bf | ||
|
|
333f0b9f2d | ||
|
|
d6cbdc1580 | ||
|
|
77f326e0dd | ||
|
|
881f59354d | ||
|
|
08d0e9f8b4 | ||
|
|
3432dfd280 | ||
|
|
5be86907d7 | ||
|
|
b191842d98 | ||
|
|
293290e12a | ||
|
|
bb1e70acab | ||
|
|
97fe1a1b57 | ||
|
|
860d596c3b | ||
|
|
b909013058 | ||
|
|
f979c606fe | ||
|
|
4a50b2eb6a | ||
|
|
f3ae67473b | ||
|
|
9d32b65a82 | ||
|
|
e57126af4f | ||
|
|
8d1c30ad17 | ||
|
|
ecab0edad1 | ||
|
|
d42842ba25 | ||
|
|
c1659a1c5e | ||
|
|
4a560c0b1c | ||
|
|
891189bbf3 | ||
|
|
01ae037205 | ||
|
|
f53caa93b6 | ||
|
|
c8679b0c79 | ||
|
|
7bc4ac1833 | ||
|
|
c03a5a4443 | ||
|
|
7e1e0e362e | ||
|
|
4a930e7966 | ||
|
|
0307950dc6 | ||
|
|
6d9ba007e5 | ||
|
|
15abfe61ec | ||
|
|
4734d53322 | ||
|
|
71b256aad5 | ||
|
|
e5c4e450c0 | ||
|
|
857b692aac | ||
|
|
738353a0e7 | ||
|
|
4a6e915ebd | ||
|
|
004ed83689 | ||
|
|
4ea123a2c0 | ||
|
|
c8828b8a42 | ||
|
|
3ae6938d1f | ||
|
|
be2ff98f25 | ||
|
|
0357a18cea | ||
|
|
e4e7bdebc6 | ||
|
|
eff2c0beb7 | ||
|
|
fceb9d4145 | ||
|
|
447c13592f | ||
|
|
0afd304949 | ||
|
|
a3d1dc6cf9 | ||
|
|
2fe67ada97 | ||
|
|
949a7a618f | ||
|
|
6034df36b8 | ||
|
|
ea67216bf2 | ||
|
|
38f66917ae | ||
|
|
1e3ac5fff7 | ||
|
|
792a1cb2ab | ||
|
|
5ead25829f | ||
|
|
8cf78ddf00 | ||
|
|
4ae488b25b | ||
|
|
dc6d9e2e4b | ||
|
|
14d18d27b1 | ||
|
|
7b51ccd9e4 | ||
|
|
ce8e9b96ca | ||
|
|
a5982579ac | ||
|
|
46c0a32357 | ||
|
|
21bccce4a1 | ||
|
|
d868124c36 | ||
|
|
bd1ead2237 | ||
|
|
689bf7fbc5 | ||
|
|
25f9a1339f | ||
|
|
bbc0a8d534 | ||
|
|
22492b5707 | ||
|
|
55da8fda74 | ||
|
|
661a63cc45 | ||
|
|
f5700f2b4c | ||
|
|
a5c258ac32 | ||
|
|
a0654b4643 | ||
|
|
adf59ddce7 | ||
|
|
438f25214c | ||
|
|
6902fa34bb | ||
|
|
5cbc08a6a2 | ||
|
|
79c63d1a4f | ||
|
|
01bd0d6760 | ||
|
|
bc7fb96184 | ||
|
|
03b8e21f23 | ||
|
|
68060d636d | ||
|
|
bf04aa3927 | ||
|
|
732a3116ff | ||
|
|
6e9b23c8e2 | ||
|
|
c4570a1387 | ||
|
|
3843751c58 | ||
|
|
ca944f280f | ||
|
|
b140181257 | ||
|
|
4eedf4d1cd | ||
|
|
37cc61e6a3 | ||
|
|
d1ca9c2d51 | ||
|
|
57395ed05c | ||
|
|
8d52a6cc7a | ||
|
|
2206fed7e4 | ||
|
|
67fc68683d | ||
|
|
fb2141382f | ||
|
|
c11c5a866d | ||
|
|
f86754f437 | ||
|
|
b3909b4af5 | ||
|
|
29738cc6f6 | ||
|
|
ee024b34da | ||
|
|
f422493694 | ||
|
|
071fde11d2 | ||
|
|
5c81aba90e | ||
|
|
6007a31380 | ||
|
|
c7a11eea4c | ||
|
|
be6c3ac662 | ||
|
|
9a54e09d15 | ||
|
|
f07c7aea2f | ||
|
|
cba6524926 | ||
|
|
1992516be9 | ||
|
|
cb71fb6907 | ||
|
|
33e7167090 | ||
|
|
f10474548b | ||
|
|
9bfbbd65f5 | ||
|
|
813191beef | ||
|
|
9e45baae58 | ||
|
|
c591922ae6 | ||
|
|
c89d06df18 | ||
|
|
97dca71c2c | ||
|
|
eaec3279ab | ||
|
|
195c313628 | ||
|
|
eb62d6368b | ||
|
|
4655a8504d | ||
|
|
44e4ae2d94 | ||
|
|
5fa5be2136 | ||
|
|
3e83402470 | ||
|
|
e7c2998cf9 | ||
|
|
9ef3dcd581 | ||
|
|
8cfd26c21e | ||
|
|
2d88469761 | ||
|
|
f3e53a108b | ||
|
|
a2436bc50b | ||
|
|
cd56ea7040 | ||
|
|
7efa672976 | ||
|
|
0856854c81 | ||
|
|
488f9653e6 | ||
|
|
48467bc514 | ||
|
|
56f7a5baae | ||
|
|
ee5a1f0e7a | ||
|
|
625bcf105c | ||
|
|
3e3ea37aba | ||
|
|
5049cc6e1d | ||
|
|
b142145a4e | ||
|
|
7eb3056856 | ||
|
|
d8e9c16d83 | ||
|
|
a58db791e1 | ||
|
|
fa2cfe36d4 | ||
|
|
b8c9880a2e | ||
|
|
3b4e7b0e5f | ||
|
|
ed27ef4cee | ||
|
|
1e2b7e728d | ||
|
|
1e9fbd216f | ||
|
|
139cd337a3 | ||
|
|
531d49fa00 | ||
|
|
b17c9a067a | ||
|
|
4d67a4a811 | ||
|
|
c286fdc96a | ||
|
|
b65caf82b4 | ||
|
|
25bd04e400 | ||
|
|
9944d136e4 | ||
|
|
816db26a75 | ||
|
|
25815ae53d | ||
|
|
ea61d00cf7 | ||
|
|
f15576fd98 | ||
|
|
6b64702054 | ||
|
|
0887d544ce | ||
|
|
af57d67320 | ||
|
|
4d460109dd | ||
|
|
10288f87c1 | ||
|
|
05bfe0a7b2 | ||
|
|
6e521af3b3 | ||
|
|
d833cc9c54 | ||
|
|
4e0d926f61 | ||
|
|
9e4ce3ae72 | ||
|
|
7a8e6f8e8c | ||
|
|
e137d63886 | ||
|
|
5e16ef73f9 | ||
|
|
9e4495b85b | ||
|
|
b5e1c8e47b | ||
|
|
36a05831ab | ||
|
|
12b895f94a | ||
|
|
e89015649e | ||
|
|
86fc7841b8 | ||
|
|
76e920fc1a | ||
|
|
99591a2b0f | ||
|
|
770b70a135 | ||
|
|
6845ef6bde | ||
|
|
d4609762bb | ||
|
|
ebf63a75d5 | ||
|
|
3effbe5f06 | ||
|
|
c742433d34 | ||
|
|
bd33b53805 | ||
|
|
1d6739b683 | ||
|
|
e75baed4b6 | ||
|
|
55f73d34e1 | ||
|
|
557157c2d6 | ||
|
|
0bae35d387 | ||
|
|
c7062bc560 | ||
|
|
a344352365 | ||
|
|
33d86ad3b5 | ||
|
|
8bacde0262 | ||
|
|
87c82071c0 | ||
|
|
1f72810649 | ||
|
|
6711095dd6 | ||
|
|
e39a42464e | ||
|
|
515674b6cf | ||
|
|
37cc63e493 | ||
|
|
4cd43f9c93 | ||
|
|
691d83e596 | ||
|
|
4bca25d783 | ||
|
|
74a5bcb3a9 | ||
|
|
9f55159bd5 | ||
|
|
f118082f6b | ||
|
|
586e9f328f | ||
|
|
fb9d52a19b | ||
|
|
815a7b6e1d | ||
|
|
336d889034 | ||
|
|
bba7479688 | ||
|
|
46a532540c | ||
|
|
1442c47bbb | ||
|
|
bb4e0be5f4 | ||
|
|
33aa182757 | ||
|
|
7bd8ace1a2 | ||
|
|
d9f4166418 | ||
|
|
02128618b0 | ||
|
|
f5cd841056 | ||
|
|
804e054bf8 | ||
|
|
3eebfdd349 | ||
|
|
581ff5fc73 | ||
|
|
3c50ffa18e | ||
|
|
4af7a1896c | ||
|
|
e1df1e7350 | ||
|
|
e156cc04c0 | ||
|
|
e0011d8372 | ||
|
|
0da777683f | ||
|
|
39eb5a50ab | ||
|
|
c04a7af39a | ||
|
|
67740a00bd | ||
|
|
6fada51fe8 | ||
|
|
eec2db0590 | ||
|
|
26316d8d76 | ||
|
|
6ea545df05 | ||
|
|
7674059899 | ||
|
|
c1f363fde2 | ||
|
|
ab2d174a0b | ||
|
|
6635540a6d | ||
|
|
a792858793 | ||
|
|
5f6f830d77 | ||
|
|
ba77125052 | ||
|
|
48b44efd67 | ||
|
|
4d9312259c | ||
|
|
7ede1ec4b0 | ||
|
|
b80d97dc38 | ||
|
|
6ee834279d | ||
|
|
c37fb0917f | ||
|
|
9d3986e06e | ||
|
|
dbcc1f4535 | ||
|
|
6c2b37c595 | ||
|
|
b338cc88fb | ||
|
|
58c5f7e373 | ||
|
|
47d188541b | ||
|
|
eb704d6420 | ||
|
|
3d9503658b | ||
|
|
5a1ffbc904 | ||
|
|
14f3a356e8 | ||
|
|
60b75b91bd | ||
|
|
4fa4737982 | ||
|
|
48c777be11 | ||
|
|
84a50beefc | ||
|
|
ab7908e012 | ||
|
|
388e0fe845 | ||
|
|
60b632418f | ||
|
|
89c81bd88b | ||
|
|
99b9b99343 | ||
|
|
0b5dbd6f5c | ||
|
|
726673f926 | ||
|
|
6a522b381c | ||
|
|
1882d263b9 | ||
|
|
b0683f77c2 | ||
|
|
7aceabed07 | ||
|
|
11c51500b3 | ||
|
|
fa886231d3 | ||
|
|
5085dcf96f | ||
|
|
34bcb2b609 | ||
|
|
c608742b84 | ||
|
|
d33c0ed1b5 | ||
|
|
d34618003d | ||
|
|
a24854a3cc | ||
|
|
f1e30dfba2 | ||
|
|
7b75476c4a | ||
|
|
b7bd41942d | ||
|
|
78db90e4bf | ||
|
|
592ca9b5c4 | ||
|
|
9981394557 | ||
|
|
b100325fe0 | ||
|
|
0ddfb48a98 | ||
|
|
d6610b7f8f | ||
|
|
ea540569ed | ||
|
|
e91d19e132 | ||
|
|
bd281e5753 | ||
|
|
7c59f05681 | ||
|
|
6dcde9fcbe | ||
|
|
cc048e55bf | ||
|
|
dd556b44e8 | ||
|
|
c62145af31 | ||
|
|
9148dc9e03 | ||
|
|
606824d282 | ||
|
|
231ca8a935 | ||
|
|
c96221c705 | ||
|
|
19f46eb817 | ||
|
|
185d53da6a | ||
|
|
07c1071c36 | ||
|
|
a9d0453811 | ||
|
|
54ef217de4 | ||
|
|
0ebfa89783 | ||
|
|
2a8b155a16 | ||
|
|
dd814d1591 | ||
|
|
1ba9ff8153 | ||
|
|
1121b81f12 | ||
|
|
f7fbe3946d | ||
|
|
921bfbbe3c | ||
|
|
bca3cb8303 | ||
|
|
fb03687802 | ||
|
|
c0e6a85ffd | ||
|
|
7f723a6bd5 | ||
|
|
02bc2e3ddb | ||
|
|
3c8e448ffb | ||
|
|
7885153933 | ||
|
|
f5161404cb | ||
|
|
a668ac7235 | ||
|
|
2610a286ca | ||
|
|
6daa065b1e | ||
|
|
1b873d3bad | ||
|
|
a0cfae214d | ||
|
|
db0af86085 | ||
|
|
be2f7cb3e5 | ||
|
|
082cb86a23 | ||
|
|
0420144104 | ||
|
|
22656c8699 | ||
|
|
c1c78164d2 | ||
|
|
08f1f05d58 | ||
|
|
c40b67fe77 | ||
|
|
81ebcc9a72 | ||
|
|
9ccc7feb58 | ||
|
|
7706adc444 | ||
|
|
7c76f7443e | ||
|
|
314ef361b6 | ||
|
|
ef401a1a2c | ||
|
|
a0877e484d | ||
|
|
067e0220bd | ||
|
|
da6481f96b | ||
|
|
7dea0441ac | ||
|
|
34c41d5f3d | ||
|
|
77d8dce81c | ||
|
|
d3058cbe07 | ||
|
|
587bab3eb1 | ||
|
|
a36a38bd8d | ||
|
|
629a6936fa | ||
|
|
4e7e50754d | ||
|
|
0288bc681b | ||
|
|
d46e3f07c3 | ||
|
|
d512ab5ddf | ||
|
|
2a052b2db1 | ||
|
|
3ada6d1bff | ||
|
|
86030a0fab | ||
|
|
954c40067e | ||
|
|
6a36606bd5 | ||
|
|
20a72a0f45 | ||
|
|
bcc374eb31 | ||
|
|
f0e1f18c79 | ||
|
|
61b7203062 | ||
|
|
a7e95d00cf | ||
|
|
83c358deb1 | ||
|
|
7dd214f3db | ||
|
|
6698d33f04 | ||
|
|
6dcd5b77aa | ||
|
|
80f2c797c9 | ||
|
|
910471a26f | ||
|
|
ccab588948 | ||
|
|
50683e6600 | ||
|
|
75daf98112 | ||
|
|
9a681a27ad | ||
|
|
25c63c8b10 | ||
|
|
a069df41b8 | ||
|
|
b1183c2c9d | ||
|
|
b777b15ee8 | ||
|
|
35061dfc53 | ||
|
|
a65bca6e49 | ||
|
|
72203f2721 | ||
|
|
03ff03ed52 | ||
|
|
488d50b97e | ||
|
|
88cbd1bd83 | ||
|
|
27fe556bab | ||
|
|
3191b7a991 | ||
|
|
f8d045c275 | ||
|
|
0038fe5ff1 | ||
|
|
3ae810a18e | ||
|
|
afe72c8029 | ||
|
|
2341bba973 | ||
|
|
c0da968af2 | ||
|
|
03fb04f4c5 | ||
|
|
d71c4a0ea7 | ||
|
|
df206d9792 | ||
|
|
49ad44dcaf | ||
|
|
7f785b8fa5 | ||
|
|
b4e674aeb0 | ||
|
|
8ed091703f | ||
|
|
464fd6d4d3 | ||
|
|
a96dfdda67 | ||
|
|
5b140d26c3 | ||
|
|
125fb81fa3 | ||
|
|
ee14372e20 | ||
|
|
5c27e0f9ef | ||
|
|
7607cec7a2 | ||
|
|
5f9c93369e | ||
|
|
9e4132fd3f | ||
|
|
0ae31e0acc | ||
|
|
24721cf2fa | ||
|
|
2ab41a359e | ||
|
|
c87167cac4 | ||
|
|
46311dfaba | ||
|
|
03720bbb81 | ||
|
|
3319fd6a21 | ||
|
|
0f75387f41 | ||
|
|
9fd5d8241e | ||
|
|
0bfee823dd | ||
|
|
668b235b07 | ||
|
|
ea6d0f573d | ||
|
|
d1b8afd3b8 | ||
|
|
0f8b9ca55b | ||
|
|
b5c84a91fb | ||
|
|
cc902db4ab | ||
|
|
faae82eae1 | ||
|
|
49ac0cadfb | ||
|
|
bd5f39e1c6 | ||
|
|
325d048340 | ||
|
|
f1805c8536 | ||
|
|
305545623a | ||
|
|
aac99f6ee2 | ||
|
|
4feea2e721 | ||
|
|
0e73b3b8e1 | ||
|
|
4b0bcf4464 | ||
|
|
57ef0ad41d | ||
|
|
a92d6b75bf | ||
|
|
763da979a8 | ||
|
|
8c58f7a04e | ||
|
|
e1868bdb78 | ||
|
|
f279368531 | ||
|
|
ed72ddc4d3 | ||
|
|
a7fa34c2fc | ||
|
|
51c734e438 | ||
|
|
ad676af3f0 | ||
|
|
1251353694 | ||
|
|
4d97023938 | ||
|
|
adf77053c5 | ||
|
|
9c57888524 | ||
|
|
dd33dc1f9b | ||
|
|
90ed6163f5 | ||
|
|
8f162cd57c | ||
|
|
e7e4bf39fe | ||
|
|
d9341e033b | ||
|
|
bf7d4ebea5 | ||
|
|
8d4a4dc526 | ||
|
|
2a3a0c758a | ||
|
|
94eb7b155e | ||
|
|
50f12869bf | ||
|
|
c81c79ad52 | ||
|
|
f2f6f2f5a8 | ||
|
|
2e2afa616d | ||
|
|
d82a7040f1 | ||
|
|
8fc97a7f91 | ||
|
|
5789c1ae7d | ||
|
|
520a89f5f6 | ||
|
|
15379384dd | ||
|
|
4cc44b37bb | ||
|
|
e121fec599 | ||
|
|
6c669abb23 | ||
|
|
622c4f9f6f | ||
|
|
b7316353f4 | ||
|
|
902a2723ae | ||
|
|
57f3c67e28 | ||
|
|
6a2bc1ef2b | ||
|
|
0b677677d1 | ||
|
|
41f9f96819 | ||
|
|
49f9fee446 | ||
|
|
9588c1ea3e | ||
|
|
c665b01e89 | ||
|
|
5c2db0134f | ||
|
|
de162eb719 | ||
|
|
33297e0226 | ||
|
|
a07e643020 | ||
|
|
304664b318 | ||
|
|
8372a3c7ca | ||
|
|
69bbc0a2a1 | ||
|
|
5bfe881fc8 | ||
|
|
44f691bea4 | ||
|
|
e59db45f5b | ||
|
|
f4087694b1 | ||
|
|
2c63e0fdd6 | ||
|
|
29bb71373e | ||
|
|
ed48858635 | ||
|
|
6f5c8389eb | ||
|
|
52bd72f449 | ||
|
|
b82f26366c | ||
|
|
758057131b | ||
|
|
e37adcd67e | ||
|
|
4e08656422 | ||
|
|
d10e70a77b | ||
|
|
0aa80f1459 | ||
|
|
26732265f0 | ||
|
|
c214c6c120 | ||
|
|
485eb60dde | ||
|
|
7e5e029559 | ||
|
|
116fd7b829 | ||
|
|
1a2b46f00c | ||
|
|
557509ef84 | ||
|
|
56f1c53084 | ||
|
|
afefee2357 | ||
|
|
bfeb1693d6 | ||
|
|
3f82b05f4f | ||
|
|
1fca80e27d | ||
|
|
7928bede1e | ||
|
|
3e62300f9c | ||
|
|
49c9eaa6e1 | ||
|
|
58db8a838a | ||
|
|
5509c65a6f | ||
|
|
fb3ba8bf92 | ||
|
|
667bda6afb | ||
|
|
a381e9aa3b | ||
|
|
32dc3b36ab | ||
|
|
190f02a939 | ||
|
|
aa2027f1b5 | ||
|
|
f665da9348 | ||
|
|
fdc2baab2b | ||
|
|
f4fc0e17da | ||
|
|
b5389cadc8 | ||
|
|
3641869332 | ||
|
|
d570a55ce6 | ||
|
|
fe77e05aff | ||
|
|
906ad8c7c1 | ||
|
|
e5d0a68d70 | ||
|
|
a17adfe366 | ||
|
|
ff158282e7 | ||
|
|
5df8abcddf | ||
|
|
3fad8479ca | ||
|
|
c7da922383 | ||
|
|
c4e2627b43 | ||
|
|
60968a926f | ||
|
|
93001377bf | ||
|
|
b912116a2f | ||
|
|
5899b0f1e4 | ||
|
|
e6e54822f5 | ||
|
|
db5adef813 | ||
|
|
adb8127a30 | ||
|
|
a4d2b8862b | ||
|
|
ad153c226e | ||
|
|
39ce0af4bf | ||
|
|
2e132e47e4 | ||
|
|
8b2cd11e9f | ||
|
|
a69f7c9dfd | ||
|
|
671ac562e7 | ||
|
|
89cb4bbb8c | ||
|
|
a91f8c4d51 | ||
|
|
8ea614266c | ||
|
|
1c0ba24e48 | ||
|
|
3d4b3bd089 | ||
|
|
31783c0d0a | ||
|
|
d244affa6c | ||
|
|
82a999e6e9 | ||
|
|
74fdb728b4 | ||
|
|
be6a53b3eb | ||
|
|
ff00af60ae | ||
|
|
ccabd09742 | ||
|
|
f784729e67 | ||
|
|
9771e956f4 | ||
|
|
5bd209aded | ||
|
|
a9554779ea | ||
|
|
fc90ad5949 | ||
|
|
3f7765fdc8 | ||
|
|
ee58871f65 | ||
|
|
b2b6472222 | ||
|
|
1c8fb4139d | ||
|
|
50057ce9c8 | ||
|
|
51dd7c9abd | ||
|
|
f250cd246c | ||
|
|
0b871b3fa5 | ||
|
|
e00a95bf02 | ||
|
|
2d3b7da4cd | ||
|
|
5083128774 | ||
|
|
e2d1b19216 | ||
|
|
e2287fae58 | ||
|
|
ef519ac5ff | ||
|
|
e7d978e027 | ||
|
|
b6d4442800 | ||
|
|
895e3931bd | ||
|
|
27ff33f93b | ||
|
|
a987425f4a | ||
|
|
971d2dfc31 | ||
|
|
5bb99f941c | ||
|
|
86334452c0 | ||
|
|
8c224878dc | ||
|
|
603db8ce6a | ||
|
|
d4b64ba26b | ||
|
|
0f0a3474fd | ||
|
|
b1de2b1a4a | ||
|
|
87ed178e27 | ||
|
|
5bae4dbf9d | ||
|
|
89eb5b7eb9 | ||
|
|
fbd30dc4ee | ||
|
|
fb9f72fc90 | ||
|
|
30558764ba | ||
|
|
fe2aaa81ca | ||
|
|
b38351a470 | ||
|
|
1d47cadae8 | ||
|
|
47cb9e8e44 | ||
|
|
ac10d25f5f | ||
|
|
597a0f21e0 | ||
|
|
4c15a83e9c | ||
|
|
2df8b234fe | ||
|
|
d852a51672 | ||
|
|
5ec8d943a3 | ||
|
|
9b4a5523cc | ||
|
|
70a4d38d04 | ||
|
|
0ad8576ae5 | ||
|
|
e712883ce1 | ||
|
|
c0f9b33bba | ||
|
|
6de76ea5d1 | ||
|
|
262e72d541 | ||
|
|
2f765529e5 | ||
|
|
bcea92e313 | ||
|
|
56ef849868 | ||
|
|
2a0c4d2b0d | ||
|
|
d4ad9b3778 | ||
|
|
df972b9ae9 | ||
|
|
f695559379 | ||
|
|
3fa7828324 | ||
|
|
dbe17b4b16 | ||
|
|
ee4df2806f | ||
|
|
a405f2e81e | ||
|
|
afc0bc9323 | ||
|
|
ce28dcc630 | ||
|
|
892830e125 | ||
|
|
75425ab1a9 | ||
|
|
2722847a59 | ||
|
|
641f84e9f8 | ||
|
|
1f0a5842f9 | ||
|
|
28c2fb92a8 | ||
|
|
715101cf5e | ||
|
|
ac37a44ffa | ||
|
|
ae3d2bebbe | ||
|
|
f8d4e1a307 | ||
|
|
b98d6984a1 | ||
|
|
8f5c9a3c72 | ||
|
|
e071393eb5 | ||
|
|
6f9fec658f | ||
|
|
9227964cb6 | ||
|
|
cf6056cede | ||
|
|
4397612349 | ||
|
|
cf3719a663 | ||
|
|
77bf35d728 | ||
|
|
e7addec0a1 | ||
|
|
243d61d95f | ||
|
|
028874fd05 | ||
|
|
6d366fe80f | ||
|
|
0924f767e9 | ||
|
|
173b5a1cd1 | ||
|
|
49e1d51be9 | ||
|
|
23e47a74ee | ||
|
|
fce7f6ce47 | ||
|
|
f3b47a16dd | ||
|
|
aa7b754693 | ||
|
|
397b13e2d8 | ||
|
|
b2c203e8c1 | ||
|
|
6afb314d26 | ||
|
|
28123355b4 | ||
|
|
bcb87f5d55 | ||
|
|
981c1c1263 | ||
|
|
67b9a3bc0e | ||
|
|
ab4914ee6a | ||
|
|
e7c73c76dd | ||
|
|
3591a3fe5c | ||
|
|
fbdce049b2 | ||
|
|
9a8520a2de | ||
|
|
a315ab29bc | ||
|
|
5437d691b5 | ||
|
|
f99c90dc85 | ||
|
|
d838388443 | ||
|
|
0b2c488a61 | ||
|
|
e2eb4ef29d | ||
|
|
76e135077b | ||
|
|
6078cd2eab | ||
|
|
3482dade71 | ||
|
|
ff73de5716 | ||
|
|
04d0c350db | ||
|
|
b6a5c91045 | ||
|
|
7a37c79ebc | ||
|
|
ba227c5ec3 | ||
|
|
7ab75dd15a | ||
|
|
df23162e9d | ||
|
|
2c12f18b44 | ||
|
|
eaeb28b4e1 | ||
|
|
d5647eab33 | ||
|
|
89eb8885b1 | ||
|
|
a5dc5687f8 | ||
|
|
6780485051 | ||
|
|
d043e7a242 | ||
|
|
c5d9b5f51d | ||
|
|
35e2892b98 | ||
|
|
b492c5ac1a | ||
|
|
df38b3c62a | ||
|
|
03a860dd6f | ||
|
|
fec585e44b | ||
|
|
11dfdbb7a3 | ||
|
|
ae1a0f411b | ||
|
|
007b5d7f50 | ||
|
|
c6eadc504b | ||
|
|
a864258cb8 | ||
|
|
8a9c15c874 | ||
|
|
7a666526b7 | ||
|
|
3fc1cac015 | ||
|
|
04a0b07bf6 | ||
|
|
59e48ca91a | ||
|
|
8ff562c5af | ||
|
|
b502a93728 | ||
|
|
b6afa6c2c7 | ||
|
|
5887da0229 | ||
|
|
a7d833d96a | ||
|
|
db3753d611 | ||
|
|
f810b13bca | ||
|
|
5ad687c6d8 | ||
|
|
6ad0910790 | ||
|
|
4d8c0546cf | ||
|
|
35f96d4a40 | ||
|
|
ae96fb6f63 | ||
|
|
67592d80aa | ||
|
|
94a5e43e5d | ||
|
|
26958f8f70 | ||
|
|
a427d215e3 | ||
|
|
271cf37b8a | ||
|
|
179c03e79d | ||
|
|
0a1b68639b | ||
|
|
d69e7ec850 | ||
|
|
76a6d8292c | ||
|
|
8f09c444b6 | ||
|
|
9032e6abb8 | ||
|
|
1c070d16a6 | ||
|
|
7837fcc657 | ||
|
|
f9690d40d3 | ||
|
|
5de6cd77dc | ||
|
|
aa5ab55b14 | ||
|
|
9195b18981 | ||
|
|
b812d6efb8 | ||
|
|
231a02eb10 | ||
|
|
6736806361 | ||
|
|
8e17756bf8 | ||
|
|
0b133fe55e | ||
|
|
d01266c642 | ||
|
|
fe3f9c86d5 | ||
|
|
14bf3645d6 | ||
|
|
0f4a7b2405 | ||
|
|
681e49a4cc | ||
|
|
6e9c97fbff | ||
|
|
370070f489 | ||
|
|
7168f4014d | ||
|
|
f0912feefb | ||
|
|
e90c9c171a | ||
|
|
d0c172830c | ||
|
|
d5bf0d1199 | ||
|
|
d3a24446b8 | ||
|
|
aa93276e6e | ||
|
|
cf36972969 | ||
|
|
40862f26e6 | ||
|
|
4083447c3f | ||
|
|
3cb34ad827 | ||
|
|
61d7566ca1 | ||
|
|
af338d447b | ||
|
|
6fad06f659 | ||
|
|
1d51d8ff27 | ||
|
|
82dd4aa403 | ||
|
|
8af9bd1ac3 | ||
|
|
9fc3845d92 | ||
|
|
93bbe8e7a8 | ||
|
|
46acd16999 | ||
|
|
5ad2c6abf6 | ||
|
|
d5781d60bd | ||
|
|
e464a95c5a | ||
|
|
a50ea4bb9e | ||
|
|
aa11bb6d93 | ||
|
|
319018f055 | ||
|
|
394b986ccb | ||
|
|
26f7b36ce4 | ||
|
|
f0daad10ce | ||
|
|
0bc557fb8b | ||
|
|
3571421a0e | ||
|
|
aed80f3e4f | ||
|
|
b84e79362e | ||
|
|
dc077bc309 | ||
|
|
0fd634ef43 | ||
|
|
d352b6b509 | ||
|
|
fcc48cc738 | ||
|
|
ec06a345cc | ||
|
|
7690b364e7 | ||
|
|
b94c0c7d04 | ||
|
|
500bfdf588 | ||
|
|
d23b19c466 | ||
|
|
3a5450039d | ||
|
|
b582ddf090 | ||
|
|
ef8b470e8b | ||
|
|
5a0841a994 | ||
|
|
bd462c4e0b | ||
|
|
f11ec4e142 | ||
|
|
a5393a3ec4 | ||
|
|
bf76da3222 | ||
|
|
f171b7de96 | ||
|
|
c0cbf00199 | ||
|
|
0cd6e59fb9 | ||
|
|
11a8adc71c | ||
|
|
b9c7fd879f | ||
|
|
2fc4c7ea33 | ||
|
|
c5003665c3 | ||
|
|
538028c150 | ||
|
|
94a00cb6d6 | ||
|
|
fdaeccf1e5 | ||
|
|
7723e46c26 | ||
|
|
dce355cce6 | ||
|
|
213e7b7093 | ||
|
|
fe7d8f93a1 | ||
|
|
9e2f4216f9 | ||
|
|
a48f7b2222 | ||
|
|
0b85d8a9bc | ||
|
|
58d6938065 | ||
|
|
a536a2b822 | ||
|
|
b84c915b23 | ||
|
|
769be46bf9 |
49
.agents/workflows/capture-release-evidences.md
Normal file
49
.agents/workflows/capture-release-evidences.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
description: Automatically run the browser_subagent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
---
|
||||
|
||||
# Capture Release Evidences Workflow
|
||||
|
||||
Use this workflow to automatically drive the `browser_subagent` to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OmniRoute must be actively running and accessible (e.g. locally at `http://localhost:20128` or on the Local VPS at `http://192.168.0.15:20128`).
|
||||
- The user must provide the target URL to be tested, or default to `http://192.168.0.15:20128`.
|
||||
|
||||
## Workflow Steps
|
||||
|
||||
### 1. Identify Target Features
|
||||
|
||||
Review the `CHANGELOG.md` for the latest version to map out the new UI elements. For example:
|
||||
|
||||
- **CLI Tools Settings**
|
||||
- **New Provider/Model Listings (e.g., Gemini 3.1, Qoder PAT)**
|
||||
- **New Feature Modals**
|
||||
|
||||
### 2. Run the Browser Subagent
|
||||
|
||||
For each identified feature, invoke the `browser_subagent` using the `default_api:browser_subagent` tool.
|
||||
**Important Task Guidelines for the Subagent:**
|
||||
|
||||
- `TaskName`: Give it a clear name like "Validate CLIProxyAPI Tool Tab".
|
||||
- `TaskSummary`: "Navigate to the CLI Tools tab and verify the new Integration settings."
|
||||
- `Task`: Provide unambiguous instructions for the subagent, such as: "Navigate to http://192.168.0.15:20128/dashboard. Click on the 'Settings' or 'CLI Tools' nav link. Scroll down to find the CLIProxyAPI integration card. Hover over it to trigger UI state. Verify the components render correctly and exit."
|
||||
- `RecordingName`: Ensure it describes the feature (e.g. `v3_4_5_cli_proxy_api`). This is required and strictly automatically saved as a WebP artifacts video by the system.
|
||||
|
||||
_(Note: The `browser_subagent` automatically creates a WebP recording named by the `RecordingName` parameter. No additional tools for screenshots are needed.)_
|
||||
|
||||
### 3. Generate Report Artifact
|
||||
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `write_to_file` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
|
||||
### Example Invocation
|
||||
|
||||
\```json
|
||||
{
|
||||
"TaskName": "Validating Qoder PAT Configuration UI",
|
||||
"TaskSummary": "Validates the Qoder provider configuration modal",
|
||||
"Task": "Go to http://192.168.0.15:20128/dashboard. Click on the 'Providers' tab. Find 'Qoder' in the list. Click 'Add Token' or 'Configure'. Type 'test_token' and submit. Return when done.",
|
||||
"RecordingName": "qoder_pat_ui_validation"
|
||||
}
|
||||
\```
|
||||
@@ -17,7 +17,7 @@ Deploy OmniRoute to the Akamai VPS using `npm pack + scp` + PM2.
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
cd /home/diegosouzapw/dev/proxys/9router && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Akamai VPS and install
|
||||
|
||||
@@ -22,7 +22,7 @@ Deploy OmniRoute to the production VPSs using `npm pack + scp` + PM2.
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
cd /home/diegosouzapw/dev/proxys/9router && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to both VPS and install
|
||||
|
||||
@@ -17,7 +17,7 @@ Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
cd /home/diegosouzapw/dev/proxys/9router && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Local VPS and install
|
||||
|
||||
@@ -11,6 +11,8 @@ Bump version, finalize CHANGELOG, commit, open a **PR to main** and wait for use
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `2.(x+1).0` — e.g. `2.1.10` → `2.2.0`.
|
||||
|
||||
> **🔴 SINGLE BRANCH RULE**: The `release/vX.Y.Z` branch is the **ONLY** development branch for the entire release cycle. ALL work — bug fixes, feature implementations, PR integrations, issue resolutions — MUST be committed directly on this branch. Never create separate `fix/`, `feat/`, or topic branches. When running `/resolve-issues`, `/implement-features`, or `/review-prs`, always work on the current release branch.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Two-Phase Flow
|
||||
@@ -25,6 +27,23 @@ Phase 2 (post-merge): tag → publish → GitHub release → Docker → deploy
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Security Verification (MANDATORY)
|
||||
|
||||
Before creating the release, you must ensure the codebase and supply chain are secure and free of known vulnerabilities.
|
||||
|
||||
1. **Run Local Dependencies Audit:**
|
||||
|
||||
```bash
|
||||
npm audit
|
||||
```
|
||||
|
||||
_Fix any `high` or `critical` vulnerabilities identified._
|
||||
|
||||
2. **Check GitHub CodeQL & Dependabot Alerts:**
|
||||
Navigate to the repository's **Security** tab on GitHub, or use the project's `vulnerability-scanner` skill to analyze active alerts. Ensure all static analysis findings (e.g., prototype pollution, insecure randomness, ReDoS, shell injections) are addressed and logically committed on a target branch.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Merge
|
||||
|
||||
### 1. Create release branch
|
||||
@@ -33,19 +52,19 @@ Phase 2 (post-merge): tag → publish → GitHub release → Docker → deploy
|
||||
git checkout -b release/v2.x.y
|
||||
```
|
||||
|
||||
### 2. Determine new version
|
||||
### 2. Determine and sync version
|
||||
|
||||
Check current version in `package.json` and increment the **patch** number only:
|
||||
Check current version in `package.json`:
|
||||
|
||||
```bash
|
||||
grep '"version"' package.json
|
||||
```
|
||||
|
||||
Version format: `2.x.y` — examples:
|
||||
|
||||
- `2.1.2` → `2.1.3` (patch)
|
||||
- `2.1.9` → `2.1.10` (patch)
|
||||
- `2.1.10` → `2.2.0` (minor threshold — do manually with `sed`)
|
||||
> **🔴 BRANCH-VERSION PARITY RULE**: The logical version in `package.json` MUST exactly match the release branch name. For example, if you are on `release/v3.7.0`, the version in `package.json` MUST be `3.7.0`.
|
||||
>
|
||||
> - If this is the FIRST time generating a release for a new minor/major branch (e.g., bumping from 3.6.9 to 3.7.0), you MUST ensure the version is bumped to match the new branch logic.
|
||||
> - If you are just bumping a patch on the current branch (e.g., 3.6.9 to 3.6.10), use:
|
||||
> `npm version patch --no-git-tag-version`
|
||||
|
||||
> **⚠️ ATOMIC COMMIT RULE — Version bump MUST happen before committing feature files.**
|
||||
>
|
||||
@@ -78,15 +97,29 @@ npm install
|
||||
|
||||
### 4. Finalize CHANGELOG.md
|
||||
|
||||
Replace `[Unreleased]` header with the new version and date.
|
||||
Keep an empty `## [Unreleased]` section above it.
|
||||
> **🔴 NO MIXUPS RULE**: Ensure you do NOT mix the backlog of the previous version with the new one. The new version section must ONLY contain the features and fixes for the current release.
|
||||
|
||||
Replace the `[Unreleased]` header with the new version and date.
|
||||
Keep an empty `## [Unreleased]` section above it, separated by a horizontal rule (`---`).
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [2.x.y] — YYYY-MM-DD
|
||||
## [3.7.0] — 2026-04-19
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- ...
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- ...
|
||||
|
||||
---
|
||||
|
||||
## [3.6.9] — 2026-04-19
|
||||
```
|
||||
|
||||
### 5. Update openapi.yaml version ⚠️ MANDATORY
|
||||
@@ -96,7 +129,18 @@ Keep an empty `## [Unreleased]` section above it.
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version") && sed -i "s/ version: .*/ version: $VERSION/" docs/openapi.yaml && echo "✓ openapi.yaml → $VERSION"
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/openapi.yaml
|
||||
echo "✓ openapi.yaml → $VERSION"
|
||||
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
# Re-run install to assert the workspace lockfile is updated
|
||||
npm install
|
||||
```
|
||||
|
||||
### 6. Update README.md and i18n docs
|
||||
@@ -129,21 +173,29 @@ git push origin release/v2.x.y
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extract the exact changelog entry for this version from the root CHANGELOG.md
|
||||
awk "/^## \\[$VERSION\\]/{flag=1; print; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md > /tmp/changelog_body.txt
|
||||
|
||||
# Append test status and next steps
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### Tests" >> /tmp/changelog_body.txt
|
||||
echo "- All tests pass" >> /tmp/changelog_body.txt
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### ⚠️ After merging: run Phase 2 steps to tag, publish, and deploy." >> /tmp/changelog_body.txt
|
||||
|
||||
gh pr create \
|
||||
--repo diegosouzapw/OmniRoute \
|
||||
--base main \
|
||||
--head release/v2.x.y \
|
||||
--title "chore(release): v2.x.y — summary" \
|
||||
--body "## 🚀 Release v2.x.y
|
||||
|
||||
### Changes
|
||||
...
|
||||
|
||||
### Tests
|
||||
- X/X tests pass
|
||||
|
||||
### ⚠️ After merging: run Phase 2 steps to tag, publish, and deploy."
|
||||
--head release/v$VERSION \
|
||||
--title "Release v$VERSION" \
|
||||
--body-file /tmp/changelog_body.txt
|
||||
```
|
||||
|
||||
### 10. 🛑 STOP — Notify User & Await PR Confirmation
|
||||
@@ -161,35 +213,65 @@ Inform the user:
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Post-Merge (only after user confirms)
|
||||
## Phase 2: Post-Merge Validation (Local VPS)
|
||||
|
||||
> Run these steps only AFTER the user has merged the PR.
|
||||
> Run these steps only AFTER the user has merged the PR into `main` and all CI jobs have passed.
|
||||
|
||||
### 11. Pull main and create tag
|
||||
### 11. Deploy to Local VPS for Final Validation (MANDATORY)
|
||||
|
||||
Before cutting the official git tag and publishing to the world, deploy the `main` branch to the Local VPS for a final homologation test.
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git tag -a v2.x.y -m "Release v2.x.y"
|
||||
|
||||
# Build and pack locally
|
||||
cd /home/diegosouzapw/dev/proxys/9router && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
|
||||
# Deploy to LOCAL VPS (192.168.0.15)
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
|
||||
```
|
||||
|
||||
### 12. Push tag to GitHub
|
||||
### 12. 🛑 STOP — Notify User & Await Final OK
|
||||
|
||||
**This is a mandatory stop point.**
|
||||
Inform the user that the `main` branch is now running on the Local VPS.
|
||||
Wait for the user to manually test and give the **OK**.
|
||||
**DO NOT proceed to Phase 3 until the user confirms the local deploy is stable.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Official Launch
|
||||
|
||||
> Run these steps only AFTER the user gives the final OK from the Phase 2 local validation.
|
||||
|
||||
### 13. Create Git Tag and GitHub Release (MANDATORY)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
git push origin --tags
|
||||
```
|
||||
git checkout main
|
||||
git pull origin main
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
### 13. Create GitHub release
|
||||
# Extracts the changelog section for this version
|
||||
NOTES=$(awk "/^## \\[$VERSION\\]/{flag=1; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||
if [ -z "$NOTES" ]; then NOTES="OmniRoute v$VERSION Release"; fi
|
||||
|
||||
```bash
|
||||
gh release create v2.x.y --title "v2.x.y — summary" --notes "..."
|
||||
git tag -a "v$VERSION" -m "Release v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
gh release create "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES" --target main || gh release edit "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES"
|
||||
```
|
||||
|
||||
### 14. 🐳 Trigger Docker Hub build (MANDATORY — keep npm and Docker in sync)
|
||||
|
||||
> **CRITICAL**: Docker Hub and npm MUST always publish the same version.
|
||||
> The Docker image is built automatically via GitHub Actions when a new tag is pushed.
|
||||
> After pushing the tag in step 11-12, **verify the workflow runs**:
|
||||
> After pushing the tag in step 13, **verify the workflow runs**:
|
||||
|
||||
```bash
|
||||
# Verify the Docker workflow triggered
|
||||
@@ -197,43 +279,66 @@ gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit
|
||||
|
||||
# Wait for the Docker build to complete (usually 5–10 min)
|
||||
gh run watch --repo diegosouzapw/OmniRoute
|
||||
|
||||
# After completion, verify on Docker Hub:
|
||||
# https://hub.docker.com/r/diegosouzapw/omniroute/tags
|
||||
```
|
||||
|
||||
If the Docker build was not triggered automatically, trigger it manually:
|
||||
### 15. Publish to NPM (Optional/Automated)
|
||||
|
||||
Normally handled by CI, but if manual publish is required:
|
||||
|
||||
```bash
|
||||
gh workflow run docker-publish.yml --repo diegosouzapw/OmniRoute --ref v2.x.y
|
||||
npm publish
|
||||
```
|
||||
|
||||
### 15. Deploy to BOTH VPS environments (MANDATORY)
|
||||
### 16. Deploy to AKAMAI VPS (Production)
|
||||
|
||||
> Always deploy to **both** environments after every release.
|
||||
> See `/deploy-vps` workflow for detailed steps.
|
||||
Now that the release is officially cut, deploy it to the Akamai VPS.
|
||||
|
||||
```bash
|
||||
# Build and pack locally
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
|
||||
# Deploy to LOCAL VPS (192.168.0.15)
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && pm2 restart omniroute && pm2 save"
|
||||
|
||||
# Deploy to AKAMAI VPS (69.164.221.35)
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && pm2 restart omniroute && pm2 save"
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
|
||||
# Verify both
|
||||
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "AKAMAI: HTTP %{http_code}\n" http://69.164.221.35:20128/
|
||||
```
|
||||
|
||||
### 16. Clean up release branch
|
||||
## Phase 4: Release Monitoring & Artifact Validation
|
||||
|
||||
> After triggering the official release, actively monitor the CI pipelines until all artifacts are successfully generated. If any pipeline fails, stop and apply the necessary corrections before continuing.
|
||||
|
||||
### 18. Monitor CI Pipelines
|
||||
|
||||
Wait for and verify the successful completion of the following automated jobs:
|
||||
|
||||
1. **Docker Hub Publish**
|
||||
2. **Electron Build**
|
||||
3. **NPM Registry Publish** (Check with `npm info omniroute version`)
|
||||
|
||||
```bash
|
||||
git branch -d release/v2.x.y
|
||||
# Monitor Docker Hub workflow
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Monitor Electron build
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow electron-release.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Verify NPM version
|
||||
npm info omniroute version
|
||||
```
|
||||
|
||||
### 19. Handle Failures (If Any)
|
||||
|
||||
If a workflow fails:
|
||||
|
||||
- Use `gh run view <RUN_ID> --log-failed` to identify the error.
|
||||
- Apply the fix on the `main` branch.
|
||||
- If necessary, re-trigger the workflow using `gh workflow run <workflow_name.yml> --repo diegosouzapw/OmniRoute --ref v2.x.y`
|
||||
|
||||
### 20. Preserve release branch
|
||||
|
||||
```bash
|
||||
# Branch is kept for historical purposes. Do not delete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -2,130 +2,705 @@
|
||||
description: Analyze open feature request issues, implement viable ones on dedicated branches, and respond to authors
|
||||
---
|
||||
|
||||
# /implement-features — Feature Request Implementation Workflow
|
||||
# /implement-features — Feature Request Harvest, Research & Implementation Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
Fetches open feature request issues, analyzes each against the current codebase, implements viable ones on dedicated branches, and responds to authors with results. Does NOT merge to main — leaves branches for author validation.
|
||||
A **5-phase** workflow that systematically harvests feature requests from GitHub issues, creates structured idea files, researches solutions across the internet and Git repositories, presents a consolidated report for user approval, then generates detailed implementation plans and executes them.
|
||||
|
||||
## Steps
|
||||
**Output directory structure:**
|
||||
|
||||
### 1. Identify the Repository
|
||||
```
|
||||
_ideia/
|
||||
├── viable/ # Features approved for implementation
|
||||
│ ├── need_details/ # ❓ Good idea but waiting for author clarification (issues stay OPEN)
|
||||
│ │ └── 1015-warp-terminal-mitm.md
|
||||
│ ├── 1046-native-playground.md # ✅ Ready — researched and planned
|
||||
│ └── 1046-native-playground.requirements.md
|
||||
├── defer/ # ⏭️ Good ideas deferred for future cycles (issues CLOSED)
|
||||
│ └── 1041-smart-auto-combos.md
|
||||
└── notfit/ # ❌ Out of scope / already exists (issues CLOSED)
|
||||
└── 945-telegram-integration.md
|
||||
|
||||
_tasks/features-vX.Y.Z/ # Implementation plans (per-release)
|
||||
└── 1046-native-playground.plan.md
|
||||
```
|
||||
|
||||
> **LIFECYCLE RULE:** `viable/` files are **DELETED** once the feature is implemented — they are not moved. Only unimplemented features live in `viable/` (or `viable/need_details/`). Files in `defer/` and `notfit/` remain as permanent reference.
|
||||
|
||||
> **BRANCH RULE**: All implementation work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `feat/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Harvest: Collect & Catalog Feature Ideas
|
||||
|
||||
### 1.1 Identify the Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract owner/repo
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract owner/repo.
|
||||
|
||||
### 2. Fetch Open Feature Request Issues
|
||||
### 1.2 Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 50 --json number,title,labels,body,comments,createdAt,author`
|
||||
- Filter for issues that are feature requests (label `enhancement`/`feature`, or body describes new functionality, or previously classified as feature request)
|
||||
- Sort by oldest first
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
### 3. Analyze Each Feature Request
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
For each feature request issue, perform a **two-level analysis**:
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
#### Level 1 — Viability Assessment
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
Ask yourself:
|
||||
### 1.3 Fetch ALL Open Feature Requests
|
||||
|
||||
- Does this feature align with the project's goals and architecture?
|
||||
- Is the request technically feasible with the current codebase?
|
||||
- Does it duplicate existing functionality?
|
||||
- Would it introduce breaking changes or security risks?
|
||||
- Is there enough detail to implement it?
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below.
|
||||
|
||||
**Step 1 — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
```bash
|
||||
# Fetch issues with feature/enhancement labels
|
||||
gh issue list --repo <owner>/<repo> --state open -l "enhancement" --limit 500 --json number --jq '.[].number'
|
||||
|
||||
# Also check for [Feature] in title (common pattern when no labels are set)
|
||||
gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,title --jq '.[] | select(.title | test("\\[Feature\\]|\\[feature\\]|feature request"; "i")) | .number'
|
||||
```
|
||||
|
||||
- Merge both lists, deduplicate. Count and confirm the total.
|
||||
|
||||
**Step 2 — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
```bash
|
||||
gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author,assignees
|
||||
```
|
||||
|
||||
- Read the **entire body** — including description, use cases, screenshots, mockups, and any embedded images.
|
||||
- Read **ALL comments** — community discussion, agreements, restrictions, owner responses, and linked PRs.
|
||||
- **Images**: If the body or comments contain image URLs (`` or `https://...png/jpg/gif`), note them — they may contain UI mockups, wireframes, or architecture diagrams that are essential to understanding the request.
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 1.4 Create Idea Files (initially in `_ideia/` root)
|
||||
|
||||
For each feature request, create a structured idea file in `<project_root>/_ideia/`:
|
||||
|
||||
**Filename convention**: `<NUMBER>-<kebab-case-short-title>.md`
|
||||
Example: `1046-native-playground.md`, `1041-smart-auto-combos.md`
|
||||
|
||||
#### 1.4a — If the idea file does NOT exist yet, create it:
|
||||
|
||||
```markdown
|
||||
# Feature: <Title from Issue>
|
||||
|
||||
> GitHub Issue: #<NUMBER> — opened by @<author> on <date>
|
||||
> Status: 📋 Cataloged | Priority: TBD
|
||||
|
||||
## 📝 Original Request
|
||||
|
||||
<Paste the FULL issue body here, preserving all formatting, images, and code blocks>
|
||||
|
||||
## 💬 Community Discussion
|
||||
|
||||
<Summarize ALL comments chronologically, noting who said what and any decisions or objections raised>
|
||||
|
||||
### Participants
|
||||
|
||||
- @<author> — Original requester
|
||||
- @<commenter1> — <brief role/opinion>
|
||||
- ...
|
||||
|
||||
### Key Points
|
||||
|
||||
- <bullet list of the most important discussion points>
|
||||
- <agreements reached>
|
||||
- <objections raised>
|
||||
|
||||
## 🎯 Refined Feature Description
|
||||
|
||||
<YOUR interpretation and enrichment of the feature request. Expand on what was asked, fill in logical gaps, provide concrete examples of how it would work. This section should be MORE detailed and clearer than the original request.>
|
||||
|
||||
### What it solves
|
||||
|
||||
- <problem 1>
|
||||
- <problem 2>
|
||||
|
||||
### How it should work (high level)
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. ...
|
||||
|
||||
### Affected areas
|
||||
|
||||
- <list of codebase areas, modules, files likely affected>
|
||||
|
||||
## 📎 Attachments & References
|
||||
|
||||
- <any image URLs, mockup links, or external references from the issue>
|
||||
|
||||
## 🔗 Related Ideas
|
||||
|
||||
- <links to related \_ideia/ files if any overlap found>
|
||||
```
|
||||
|
||||
#### 1.4b — If the idea file ALREADY exists, update it:
|
||||
|
||||
- Append new comments from the issue to the **Community Discussion** section.
|
||||
- Update the **Refined Feature Description** if new information changes the understanding.
|
||||
- Add any new **Related Ideas** cross-references found.
|
||||
- **Do NOT overwrite** existing content — append and enrich it.
|
||||
|
||||
### 1.5 Cross-Reference & Deduplication
|
||||
|
||||
After processing all issues:
|
||||
|
||||
- Scan all `_ideia/*.md` files for overlapping features.
|
||||
- If two features are substantially the same, add `🔗 Related Ideas` cross-references to both.
|
||||
- If one is a strict subset of another, note it in the smaller file: `> ℹ️ This feature is a subset of #<OTHER_NUMBER>. Consider implementing together.`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Research: Find Solutions & Build Requirements
|
||||
|
||||
For each cataloged idea that is **viable** (aligns with the project's goals):
|
||||
|
||||
### 2.1 Viability Pre-Check
|
||||
|
||||
Before investing in research, quickly assess:
|
||||
|
||||
- [ ] Does this feature align with the project's goals and architecture?
|
||||
- [ ] Is it technically feasible with the current codebase?
|
||||
- [ ] Does it duplicate existing functionality?
|
||||
- [ ] Would it introduce breaking changes or security risks?
|
||||
- [ ] Is there enough detail to understand what's needed?
|
||||
|
||||
**Verdict options:**
|
||||
|
||||
1. ✅ **VIABLE** — Makes sense, enough detail to implement → Go to Level 2
|
||||
2. ❓ **NEEDS MORE INFO** — Good idea but insufficient detail → Post comment asking for specifics
|
||||
3. ❌ **NOT VIABLE** — Doesn't fit the project or is fundamentally flawed → Post comment explaining why, close issue
|
||||
| Verdict | When | Action |
|
||||
| --------------------- | ------------------------------------- | --------------------------- |
|
||||
| ✅ **VIABLE** | Good idea, enough context | Proceed to Research |
|
||||
| ❓ **NEEDS DETAIL** | Good idea, insufficient spec | Skip research, ask author |
|
||||
| ⏭️ **DEFER** | Good idea, too complex for this cycle | Catalog only, skip research |
|
||||
| ❌ **NOT FIT** | Doesn't fit the project | Explain why |
|
||||
| 🔁 **ALREADY EXISTS** | Feature already implemented | Point to existing feature |
|
||||
|
||||
#### Level 2 — Implementation (only for VIABLE features)
|
||||
### 2.2 Internet Research (for VIABLE features)
|
||||
|
||||
1. **Research** — Read all related source files to understand the current architecture
|
||||
2. **Design** — Plan the implementation, filling gaps in the original request
|
||||
3. **Create branch** — Name format: `feat/issue-<NUMBER>-<short-slug>`
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout -b feat/issue-<NUMBER>-<short-slug>
|
||||
```
|
||||
4. **Implement** — Build the complete solution following project patterns
|
||||
5. **Build** — Run `npm run build` to verify compilation
|
||||
6. **Commit** — Commit with: `feat: <description> (#<NUMBER>)`
|
||||
7. **Push** — Push the branch: `git push -u origin feat/issue-<NUMBER>-<short-slug>`
|
||||
8. **Return to main** — `git checkout main`
|
||||
For each viable feature, perform systematic research:
|
||||
|
||||
### 4. Respond to Authors
|
||||
**Step 1 — Web search for similar implementations:**
|
||||
|
||||
#### For VIABLE (implemented) features:
|
||||
```
|
||||
search_web("how to implement <feature description> in <tech stack>")
|
||||
search_web("<feature keyword> implementation nextjs typescript 2025 2026")
|
||||
search_web("<feature keyword> open source library npm")
|
||||
```
|
||||
|
||||
**Step 2 — Find reference Git repositories:**
|
||||
|
||||
```
|
||||
search_web("site:github.com <feature keyword> <tech stack> stars:>100")
|
||||
search_web("github <feature keyword> implementation recently updated 2026")
|
||||
```
|
||||
|
||||
- Find **up to 10 relevant repositories**, sorted by most recently updated.
|
||||
- For each repository:
|
||||
- Note the repo URL, star count, last commit date
|
||||
- Read its README and relevant source files via `read_url_content`
|
||||
- Extract the architectural approach, patterns used, and key code snippets
|
||||
|
||||
**Step 3 — Read API docs and standards:**
|
||||
|
||||
If the feature involves an external API, protocol, or standard:
|
||||
|
||||
- Find and read the official documentation
|
||||
- Note version requirements, authentication patterns, rate limits
|
||||
|
||||
### 2.3 Create Requirements File
|
||||
|
||||
For each researched feature, create a requirements file alongside its idea file:
|
||||
|
||||
**Filename**: `<NUMBER>-<kebab-case-short-title>.requirements.md`
|
||||
|
||||
```markdown
|
||||
# Requirements: <Feature Title>
|
||||
|
||||
> Feature Idea: [#<NUMBER>](./<NUMBER>-<kebab-case-short-title>.md)
|
||||
> Research Date: <YYYY-MM-DD>
|
||||
> Verdict: ✅ VIABLE
|
||||
|
||||
## 🔍 Research Summary
|
||||
|
||||
<Brief summary of what was found during research>
|
||||
|
||||
## 📚 Reference Implementations
|
||||
|
||||
| # | Repository | Stars | Last Updated | Approach | Relevance |
|
||||
| --- | ---------------- | ----- | ------------ | -------- | ------------ |
|
||||
| 1 | [repo/name](url) | ⭐ N | YYYY-MM-DD | <brief> | High/Med/Low |
|
||||
| 2 | ... | | | | |
|
||||
|
||||
### Key Patterns Found
|
||||
|
||||
- <pattern 1 with code snippet or link>
|
||||
- <pattern 2>
|
||||
|
||||
## 📐 Proposed Solution Architecture
|
||||
|
||||
### Approach
|
||||
|
||||
<Describe the chosen approach based on research findings>
|
||||
|
||||
### New Files
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ------------- |
|
||||
| `path/to/new/file.ts` | <description> |
|
||||
|
||||
### Modified Files
|
||||
|
||||
| File | Changes |
|
||||
| -------------------------- | -------------- |
|
||||
| `path/to/existing/file.ts` | <what changes> |
|
||||
|
||||
### Database Changes
|
||||
|
||||
- <migrations needed, if any>
|
||||
|
||||
### API Changes
|
||||
|
||||
- <new/modified endpoints, if any>
|
||||
|
||||
### UI Changes
|
||||
|
||||
- <new/modified pages/components, if any>
|
||||
|
||||
## ⚙️ Implementation Effort
|
||||
|
||||
- **Estimated complexity**: Low / Medium / High / Very High
|
||||
- **Estimated files changed**: ~N
|
||||
- **Dependencies needed**: <new npm packages, if any>
|
||||
- **Breaking changes**: Yes/No — <details>
|
||||
- **i18n impact**: <number of new translation keys>
|
||||
- **Test coverage needed**: <brief description>
|
||||
|
||||
## ⚠️ Open Questions
|
||||
|
||||
- <question 1>
|
||||
- <question 2>
|
||||
|
||||
## 🔗 External References
|
||||
|
||||
- <documentation URLs>
|
||||
- <API references>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2.5 — Organize & Respond: Sort Files and Post GitHub Comments
|
||||
|
||||
### 2.5.1 Create Directory Structure
|
||||
|
||||
// turbo
|
||||
Post a comment on the issue:
|
||||
|
||||
````markdown
|
||||
## ✅ Feature Implemented!
|
||||
|
||||
Hi @<author>! We've analyzed your request and implemented it on a dedicated branch.
|
||||
|
||||
**Branch:** `feat/issue-<NUMBER>-<short-slug>`
|
||||
|
||||
### What was implemented:
|
||||
|
||||
- <bullet list of what was done>
|
||||
|
||||
### How to try it:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git checkout feat/issue-<NUMBER>-<short-slug>
|
||||
mkdir -p <project_root>/_ideia/viable
|
||||
mkdir -p <project_root>/_ideia/viable/need_details
|
||||
mkdir -p <project_root>/_ideia/defer
|
||||
mkdir -p <project_root>/_ideia/notfit
|
||||
```
|
||||
|
||||
### 2.5.2 Move Idea Files to Category Subdirectories
|
||||
|
||||
After classification, move EVERY idea file to its correct subdirectory:
|
||||
|
||||
```bash
|
||||
# ✅ VIABLE — move idea + requirements files
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/
|
||||
mv _ideia/<NUMBER>-*.requirements.md _ideia/viable/
|
||||
|
||||
# ❓ NEEDS DETAIL — viable but waiting for author response
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/need_details/
|
||||
|
||||
# ⏭️ DEFER — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/defer/
|
||||
|
||||
# ❌ NOT FIT & 🔁 ALREADY EXISTS — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/notfit/
|
||||
```
|
||||
|
||||
No files should remain in `_ideia/` root after this step (except subdirectories).
|
||||
|
||||
### 2.5.3 Post GitHub Comments by Category
|
||||
|
||||
**Each category has a specific comment template and action:**
|
||||
|
||||
---
|
||||
|
||||
#### For 🔁 ALREADY EXISTS — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
The feature already exists in the system. Explain WHERE it is and HOW to use it.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
Great news — this functionality **already exists** in OmniRoute:
|
||||
|
||||
**📍 Where to find it:** <exact dashboard path or settings location>
|
||||
|
||||
**🔧 How to use it:**
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. <step 3>
|
||||
|
||||
If you have any trouble finding or using it, feel free to ask in a Discussion. We're always happy to help!
|
||||
|
||||
Closing this as the feature is already available. 🎉
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ⏭️ DEFER — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, explain the idea was cataloged, and that we'll study it before implementing.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for this thoughtful feature request! 🙏
|
||||
|
||||
We really appreciate the detailed proposal. We've **cataloged your idea** and it's now part of our improvement backlog.
|
||||
|
||||
Due to the **significant architectural impact** of this feature, we'll need to conduct thorough use-case studies and architectural analysis before we start development. This ensures we build it right and don't introduce regressions.
|
||||
|
||||
**What happens next:**
|
||||
|
||||
- Your idea is saved in our internal feature backlog
|
||||
- We'll conduct architecture studies when this area is prioritized
|
||||
- We'll notify you here when development begins
|
||||
|
||||
Thank you for contributing to OmniRoute's roadmap! Your input helps shape the product. 🚀
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❌ NOT FIT — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Politely explain why the feature doesn't fit the project scope.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
After careful analysis, we've determined that this feature **falls outside OmniRoute's core scope** as a proxy/router.
|
||||
|
||||
**Reason:** <explain why — e.g., "Telegram integration belongs in the application/orchestrator layer that consumes OmniRoute's API, not inside the router itself.">
|
||||
|
||||
**Alternative:** <suggest an alternative approach if possible>
|
||||
|
||||
We appreciate you thinking of ways to improve OmniRoute! If you'd like to discuss this further, feel free to open a Discussion. 🙏
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❓ NEEDS DETAIL — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Ask for the specific missing details needed.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the feature request — it's an interesting idea and we'd love to explore it further. 🙏
|
||||
|
||||
To move forward, we need a few more details:
|
||||
|
||||
1. <specific question 1>
|
||||
2. <specific question 2>
|
||||
3. <specific question 3>
|
||||
|
||||
If you know of any **open-source projects or repositories** that implement something similar, please share links — it would help us design the best solution.
|
||||
|
||||
Looking forward to your response! 🚀
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ✅ VIABLE — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, confirm we've cataloged their idea, and explain it may be implemented in future versions.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the great feature suggestion! 🙏
|
||||
|
||||
We've analyzed your request and it aligns well with OmniRoute's roadmap. We've **cataloged this feature** and it's in our implementation backlog.
|
||||
|
||||
**Status:** 📋 Cataloged for future implementation
|
||||
|
||||
This feature may be included in upcoming releases. We'll **respond to this issue and tag you** as soon as implementation begins so you can test it.
|
||||
|
||||
Thank you for helping improve OmniRoute! 🚀
|
||||
```
|
||||
|
||||
**⚠️ Do NOT close viable issues — they remain OPEN for tracking.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Report: Present Findings to User
|
||||
|
||||
### 3.1 🛑 MANDATORY STOP — Present Consolidated Report
|
||||
|
||||
After completing Phase 1, Phase 2, and Phase 2.5, **STOP and present the following report** in the chat. Do NOT proceed to implementation.
|
||||
|
||||
Present a structured report containing:
|
||||
|
||||
#### 3.1a — Feature Summary Table
|
||||
|
||||
| # | Issue | Title | Verdict | Location | Action |
|
||||
| --- | ----- | ----- | --------------- | ----------------------------- | ----------------------------- |
|
||||
| 1 | #N | Title | ✅ VIABLE | `_ideia/viable/` | Issue OPEN, comment posted |
|
||||
| 2 | #N | Title | ⏭️ DEFER | `_ideia/defer/` | Issue CLOSED with explanation |
|
||||
| 3 | #N | Title | ❌ NOT FIT | `_ideia/notfit/` | Issue CLOSED with explanation |
|
||||
| 4 | #N | Title | 🔁 EXISTS | `_ideia/notfit/` | Issue CLOSED with guidance |
|
||||
| 5 | #N | Title | ❓ NEEDS DETAIL | `_ideia/viable/need_details/` | Issue OPEN, questions posted |
|
||||
|
||||
#### 3.1b — Viable Features Detail
|
||||
|
||||
For each VIABLE feature, provide a brief paragraph:
|
||||
|
||||
- What was found during research
|
||||
- The proposed approach
|
||||
- Key risks or unknowns
|
||||
- Which reference repositories were most useful
|
||||
|
||||
#### 3.1c — Issues Requiring Author Feedback
|
||||
|
||||
For features marked ❓ NEEDS DETAIL, list:
|
||||
|
||||
- What specific information is missing
|
||||
- What examples or repository references would help
|
||||
|
||||
#### 3.1d — Ask for User Confirmation
|
||||
|
||||
End the report with:
|
||||
|
||||
> **Ready to proceed with implementation?**
|
||||
>
|
||||
> - Reply **"sim"** or **"yes"** to generate full implementation plans for all VIABLE features.
|
||||
> - Reply with specific issue numbers to select only certain features.
|
||||
> - Reply **"não"** or **"no"** to stop here.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Plan: Generate Implementation Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 3.**
|
||||
|
||||
### 4.1 Create Task Directory
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_tasks/features-vX.Y.Z/
|
||||
```
|
||||
|
||||
### 4.2 Generate One Implementation Plan Per Feature
|
||||
|
||||
For each VIABLE feature approved by the user, create:
|
||||
|
||||
**Filename**: `_tasks/features-vX.Y.Z/<NUMBER>-<kebab-case-title>.plan.md`
|
||||
|
||||
```markdown
|
||||
# Implementation Plan: <Feature Title>
|
||||
|
||||
> Issue: #<NUMBER>
|
||||
> Idea: [\_ideia/viable/<NUMBER>-title.md](../../_ideia/viable/<NUMBER>-title.md)
|
||||
> Requirements: [\_ideia/viable/<NUMBER>-title.requirements.md](../../_ideia/viable/<NUMBER>-title.requirements.md)
|
||||
> Branch: `release/vX.Y.Z`
|
||||
|
||||
## Overview
|
||||
|
||||
<Brief description of what will be built>
|
||||
|
||||
## Pre-Implementation Checklist
|
||||
|
||||
- [ ] Read all related source files listed below
|
||||
- [ ] Confirm no conflicts with in-flight PRs
|
||||
- [ ] Verify database migration numbering
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: <Title>
|
||||
|
||||
**Files:**
|
||||
|
||||
- `path/to/file.ts` — <what to change>
|
||||
|
||||
**Details:**
|
||||
<Detailed description of the change, including code patterns to follow, function signatures, etc.>
|
||||
|
||||
### Step 2: <Title>
|
||||
|
||||
...
|
||||
|
||||
### Step N: Tests
|
||||
|
||||
**New test files:**
|
||||
|
||||
- `tests/unit/<test-file>.test.mjs` — <what to test>
|
||||
|
||||
**Test cases:**
|
||||
|
||||
- [ ] <test case 1>
|
||||
- [ ] <test case 2>
|
||||
|
||||
### Step N+1: i18n
|
||||
|
||||
**Translation keys to add:**
|
||||
|
||||
- `<namespace>.<key>` — "<English value>"
|
||||
|
||||
### Step N+2: Documentation
|
||||
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Update relevant docs/ files
|
||||
|
||||
## Verification Plan
|
||||
|
||||
1. Run `npm run build` — must pass
|
||||
2. Run `npm test` — all tests must pass
|
||||
3. Run `npm run lint` — no new errors
|
||||
4. <Manual verification steps>
|
||||
|
||||
## Commit Plan
|
||||
```
|
||||
|
||||
feat: <description> (#<NUMBER>)
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
### 4.3 Present Plans for Final Approval
|
||||
|
||||
Present a summary of all generated plans:
|
||||
|
||||
> **Implementation plans generated:**
|
||||
>
|
||||
> | # | Feature | Plan File | Steps | Effort |
|
||||
> | --- | ------- | ---------------------------------------- | ------- | ------ |
|
||||
> | 1 | <title> | `_tasks/features-vX.Y.Z/N-title.plan.md` | N steps | Medium |
|
||||
>
|
||||
> Reply **"sim"** or **"yes"** to begin implementation of all features.
|
||||
> Reply with specific issue numbers to implement only certain ones.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Execute: Implement the Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 4.**
|
||||
|
||||
### 5.1 Implement Each Feature
|
||||
|
||||
For each approved plan, execute it step by step:
|
||||
|
||||
1. **Follow the plan** — implement exactly as specified in the `.plan.md` file
|
||||
2. **Build** — Run `npm run build` after each feature to verify compilation
|
||||
3. **Test** — Run `npm test` to ensure no regressions
|
||||
4. **Commit** — Commit with: `feat: <description> (#<NUMBER>)`
|
||||
5. **Update the plan** — Mark completed steps with `[x]` in the plan file
|
||||
6. **Continue** — Move to the next feature (do NOT switch branches)
|
||||
|
||||
### 5.2 Respond to Authors (Update Viable Issues)
|
||||
|
||||
For each implemented feature, **close the issue with a final comment**:
|
||||
|
||||
````markdown
|
||||
✅ **Implemented in `release/vX.Y.Z`!**
|
||||
|
||||
Hi @<author>! Great news — your feature request has been implemented! 🎉
|
||||
|
||||
**What was done:**
|
||||
|
||||
- <bullet list of what was built>
|
||||
|
||||
**How to try it:**
|
||||
|
||||
```bash
|
||||
git fetch origin && git checkout release/vX.Y.Z
|
||||
npm install && npm run dev
|
||||
```
|
||||
````
|
||||
|
||||
### Next steps:
|
||||
This will be included in the upcoming **vX.Y.Z** release. Feel free to reopen if you spot any issues! 🚀
|
||||
|
||||
1. **Test it** — Please verify it works as you expected
|
||||
2. **Want to improve it?** — You're welcome to contribute! Just:
|
||||
```bash
|
||||
git checkout feat/issue-<NUMBER>-<short-slug>
|
||||
# Make your improvements
|
||||
git add -A && git commit -m "improve: <your changes>"
|
||||
git push origin feat/issue-<NUMBER>-<short-slug>
|
||||
```
|
||||
Then open a Pull Request from your branch to `main` 🎉
|
||||
3. **Not quite right?** — Let us know in this issue what needs to change
|
||||
````
|
||||
|
||||
Looking forward to your feedback! 🚀
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
````
|
||||
|
||||
Then **DELETE the idea file** — it has served its purpose:
|
||||
|
||||
```bash
|
||||
# ✅ Implemented files are DELETED (not moved)
|
||||
rm _ideia/viable/<NUMBER>-<title>.md
|
||||
rm _ideia/viable/<NUMBER>-<title>.requirements.md # if exists
|
||||
```
|
||||
|
||||
#### For NEEDS MORE INFO:
|
||||
// turbo
|
||||
Post a comment asking for specific missing details needed to implement, e.g.:
|
||||
- "Could you describe the exact behavior when X happens?"
|
||||
- "Which API endpoints should be affected?"
|
||||
- "Should this apply to all providers or only specific ones?"
|
||||
> **Why delete?** `viable/` only holds features that still NEED to be done. Once implemented, the commit history and CHANGELOG are the source of truth. Keeping the file would be confusing.
|
||||
|
||||
Add the context of WHY you need each piece of information.
|
||||
### 5.3 Finalize & Push
|
||||
|
||||
#### For NOT VIABLE:
|
||||
// turbo
|
||||
Post a polite comment explaining why the feature doesn't fit at this time:
|
||||
- If the idea is decent but timing is wrong: "This is an interesting idea, but it doesn't align with our current priorities. Feel free to open a new issue with more details if you'd like us to reconsider."
|
||||
- If fundamentally flawed: Explain the technical or architectural reasons why it won't work, suggest alternatives if possible.
|
||||
- Close the issue after posting the comment.
|
||||
After implementing all approved features:
|
||||
|
||||
### 5. Summary Report
|
||||
Present a summary report to the user via `notify_user`:
|
||||
1. **Update CHANGELOG.md** on the release branch with all new feature entries
|
||||
2. Push the release branch: `git push origin release/vX.Y.Z`
|
||||
3. Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
|
||||
| Issue | Title | Verdict | Branch / Action |
|
||||
|---|---|---|---|
|
||||
| #N | Title | ✅ Implemented | `feat/issue-N-slug` |
|
||||
| #N | Title | ❓ Needs Info | Comment posted |
|
||||
| #N | Title | ❌ Not Viable | Closed with explanation |
|
||||
```
|
||||
### 5.4 Final Summary Report
|
||||
|
||||
Present a final summary report to the user:
|
||||
|
||||
| Issue | Title | Verdict | Action | Commit |
|
||||
| ----- | ----- | --------------- | -------------------------------------------------- | --------- |
|
||||
| #N | Title | ✅ Implemented | Issue closed, idea file deleted | `abc1234` |
|
||||
| #N | Title | ⏭️ Deferred | Issue closed + saved in `_ideia/defer/` | — |
|
||||
| #N | Title | ❌ Not Fit | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | 🔁 Exists | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | ❓ Needs Detail | Issue OPEN, moved to `_ideia/viable/need_details/` | — |
|
||||
|
||||
Include:
|
||||
|
||||
- Total features harvested
|
||||
- Total ideas cataloged (`viable/need_details/` + `defer/` + `notfit/`)
|
||||
- Total features implemented (idea files deleted, issues closed)
|
||||
- Total features deferred
|
||||
- Total issues closed
|
||||
- Total issues left open (needs detail only — viable are closed after implementation)
|
||||
- Test results (pass/fail count)
|
||||
|
||||
@@ -6,7 +6,11 @@ description: Fetch all open GitHub issues, analyze bugs, resolve what's possible
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open issues from the project's GitHub repository, classifies them, analyzes bugs, resolves what can be fixed, and triages issues with insufficient information. **It does NOT merge or release automatically** — it creates a PR and waits for user validation before merging.
|
||||
This workflow fetches all open issues from the project's GitHub repository, classifies them, analyzes bugs, proposes a resolution plan, waits for user validation, and ONLY THEN implements the fixes, commits, and closes the issues on the current release branch (`release/vX.Y.Z`). It does NOT merge or release automatically — the release branch is later merged via PR to main.
|
||||
|
||||
> **BRANCH RULE**: All work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `fix/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
> **⛔ PR PROHIBITION**: If a fix is associated with a contributor's PR, you MUST merge their PR — NEVER close it and re-implement the fix yourself. See `/review-prs` workflow for the full policy. The `gh pr close` command is FORBIDDEN unless the repository owner explicitly requests it.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -17,15 +21,45 @@ This workflow fetches all open issues from the project's GitHub repository, clas
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Fetch All Open Issues
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,title,labels,body,comments,createdAt,author`
|
||||
- Parse the JSON output to get a list of **all** open issues
|
||||
- Sort by oldest first (FIFO)
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
### 3. Classify Each Issue
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch All Open Issues
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below to guarantee **all** issues are fetched.
|
||||
|
||||
**Step 3a — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one issue number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
- For each issue number from step 3a, run:
|
||||
`gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 4. Classify Each Issue
|
||||
|
||||
For each issue, determine its type:
|
||||
|
||||
@@ -36,85 +70,97 @@ For each issue, determine its type:
|
||||
|
||||
Focus ONLY on **Bugs** for resolution. Feature requests and questions should be skipped with a note in the final report.
|
||||
|
||||
### 4. Analyze Each Bug — For each bug issue:
|
||||
### 5. Deep-Read Each Bug Issue (One-by-One Analysis)
|
||||
|
||||
#### 4a. Check Information Sufficiency
|
||||
**IMPORTANT**: Read each bug issue thoroughly, one at a time, before moving to the next. This is NOT a batch process — each issue needs focused attention.
|
||||
|
||||
Verify the issue contains enough information to reproduce and fix:
|
||||
#### 5a. Understand the Problem
|
||||
|
||||
For each bug issue, perform the full analysis:
|
||||
|
||||
1. **Read the entire body** — including Description, Steps to Reproduce, Expected/Actual Behavior, Error Logs, and Screenshots
|
||||
2. **Read ALL comments** — including bot triage comments (Kilo, etc.) and owner/community responses. Pay attention to:
|
||||
- Whether someone already responded with a fix
|
||||
- Whether a community member confirmed the issue is resolved
|
||||
- Whether the issue was marked as duplicate by a bot. **WARNING: DO NOT blindly trust bot duplicate labels (e.g., kilo-duplicate). Bots make mistakes. You MUST read the full conversation and do your own independent analysis to determine if it is truly a duplicate or a distinct bug.**
|
||||
3. **Identify the claimed error** — extract the exact error message, status code, and provider/model involved
|
||||
|
||||
#### 5b. Check Information Sufficiency
|
||||
|
||||
Verify the issue contains enough to act on:
|
||||
|
||||
- [ ] Clear description of the problem
|
||||
- [ ] Steps to reproduce
|
||||
- [ ] Error messages or logs
|
||||
- [ ] Steps to reproduce OR error logs
|
||||
- [ ] Provider/model/version information
|
||||
- [ ] Expected vs actual behavior
|
||||
|
||||
#### 4b. If Information Is INSUFFICIENT
|
||||
#### 5c. Determine Issue Disposition
|
||||
|
||||
Call the `/issue-triage` workflow (located at `~/.gemini/antigravity/global_workflows/issue-triage.md`):
|
||||
// turbo
|
||||
For each bug, classify into one of 5 actions:
|
||||
|
||||
- Post a comment asking for more details using `gh issue comment`
|
||||
- Add `needs-info` label using `gh issue edit`
|
||||
- Mark this issue as **DEFERRED** and move to the next one
|
||||
| Disposition | When to Apply | Action |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **✅ CLOSE — Already Fixed** | Owner responded with fix + no user follow-up, OR community confirmed fix | Close with comment citing which version fixed it |
|
||||
| **✅ CLOSE — Duplicate** | You have independently verified the issue is a duplicate (do NOT rely solely on bot flags) + user provides no new info | Close referencing the original issue |
|
||||
| **✅ CLOSE — Stale** | We requested logs/info > 7 days ago with no reply | Close thanking the user, invite to reopen if needed |
|
||||
| **📝 RESPOND — Needs Info** | Issue is real but missing critical reproduction details | Comment asking for specifics per `/issue-triage` |
|
||||
| **📝 RESPOND — User Config** | Error is caused by unsupported env (Node version, wrong model path, missing API enablement) | Comment explaining the user-side fix |
|
||||
| **🔧 FIX — Code Change** | Root cause is confirmed in the codebase | Research, propose solution in report, wait for approval |
|
||||
|
||||
#### 4c. If Information Is SUFFICIENT
|
||||
#### 5d. For "FIX — Code Change" Issues
|
||||
|
||||
Proceed with resolution:
|
||||
Before coding, perform deep source analysis to formulate a plan:
|
||||
|
||||
1. **Create a fix branch** — `git checkout -b fix/issue-<NUMBER>-<short-description>`
|
||||
2. **Research** — Search the codebase for files related to the issue
|
||||
3. **Root Cause** — Identify the root cause by reading the relevant source files
|
||||
4. **Implement Fix** — Apply the fix following existing code patterns and conventions
|
||||
5. **Test** — Build the project and run tests to verify the fix
|
||||
6. **Commit** — Commit with message format: `fix: <description> (#<issue_number>)`
|
||||
1. **Search the codebase** — `grep_search` for error strings, relevant function names, affected files
|
||||
2. **Search the web** — for upstream API changes, SDK updates, or breaking changes that explain the bug
|
||||
3. **Read the full source file** — don't rely on grep snippets; understand the surrounding logic
|
||||
4. **Verify the root cause** — confirm the bug is reproducible based on the code, not just a user misconfiguration
|
||||
5. **Formulate a proposed solution** — detail the exact files and lines you will change and how you will solve it.
|
||||
6. **Create an Implementation Plan file** — write your proposed solution to `_tasks/features-vX.Y.Z/<ISSUE_NUMBER>-<short-description>.plan.md` (e.g. `_tasks/features-v3.7.6/1810-auto-restore-probe-failed-db.plan.md`) where `vX.Y.Z` is the current branch version. The plan should contain an Overview, Pre-Implementation Checklist, and detailed Implementation Steps (Files, Changes).
|
||||
7. **DO NOT modify the codebase yet** — wait for user approval on your report and plan first.
|
||||
|
||||
### 5. Generate Report & Wait for Validation
|
||||
#### 5e. For "RESPOND" Issues
|
||||
|
||||
Present a summary report to the user via `notify_user` with `BlockedOnUser: true`:
|
||||
Post a substantive comment that:
|
||||
|
||||
| Issue | Title | Status | Action |
|
||||
| ----- | ----- | ------------- | ----------------------------- |
|
||||
| #N | Title | ✅ Ready | Files changed (not committed) |
|
||||
| #N | Title | ❓ Needs Info | Triage comment posted |
|
||||
| #N | Title | ⏭️ Skipped | Feature request / not a bug |
|
||||
- Acknowledges the specific error they reported
|
||||
- Explains the likely root cause
|
||||
- Provides concrete steps to resolve (version upgrade, env var fix, model path correction)
|
||||
- Asks for follow-up info if needed
|
||||
|
||||
> **⚠️ IMPORTANT**: Do NOT commit, close issues, or generate releases at this step.
|
||||
> Wait for the user to review the changes and respond with **OK** before proceeding.
|
||||
**Do NOT post generic template responses.** Every comment should reference the user's specific error messages and environment.
|
||||
|
||||
- If the user says **OK** or approves → Proceed to step 6
|
||||
- If the user requests changes → Apply the requested adjustments first, then present the report again
|
||||
- If the user rejects → Revert the changes and stop
|
||||
### 6. Generate Report & Wait for Validation
|
||||
|
||||
### 6. Commit & Push Fix Branch (only after user approval)
|
||||
Present a summary report to the user detailing your proposed actions. For any bugs that need fixing, explicitly explain your proposed solution (files to change and logic) and point out that it will be implemented on the release branch (`release/vX.Y.Z`) after approval.
|
||||
|
||||
After the user validates:
|
||||
| Issue | Title | Status | Proposed Action / Version |
|
||||
| ----- | ----- | ------------- | ----------------------------------------- |
|
||||
| #N | Title | ✅ Close | Already fixed / duplicate (explain why) |
|
||||
| #N | Title | 🔧 Propose | Explanation of the code fix to be applied |
|
||||
| #N | Title | 📝 Respond | Guidance comment to be posted |
|
||||
| #N | Title | ❓ Needs Info | Triage comment to be posted |
|
||||
| #N | Title | ⏭️ Skip | Feature request / not a bug |
|
||||
|
||||
- Commit each fix individually with message format: `fix: <description> (#<issue_number>)`
|
||||
- Push the fix branch: `git push origin fix/issue-<NUMBER>-<short-description>`
|
||||
- Create a PR: `gh pr create --title "fix: <description> (#<issue_number>)" --body "<details>" --base main`
|
||||
> **⚠️ IMPORTANT**: Do NOT implement code changes, commit, push, or close issues at this step.
|
||||
> Wait for the user to review the proposed fixes and respond with **OK** before proceeding.
|
||||
|
||||
### 7. 🛑 WAIT — Notify User & Await PR Verification
|
||||
- If the user says **OK** or approves → Proceed to step 7
|
||||
- If the user requests changes → Adjust the proposed solution and present the report again
|
||||
- If the user rejects → Revert any accidental changes and stop
|
||||
|
||||
**This is a mandatory stop point.** Use `notify_user` with `BlockedOnUser: true`:
|
||||
### 7. Implement Fixes, Run Tests & Commit (only after user approval)
|
||||
|
||||
- Inform the user that the PR was created and is **awaiting their verification**
|
||||
- Include the PR number, URL, and a summary of what was changed
|
||||
- **DO NOT merge, close issues, generate releases, or deploy until the user confirms**
|
||||
After the user validates and gives the OK:
|
||||
|
||||
Wait for the user to respond:
|
||||
1. **Implement the fixes** — modify the codebase according to the approved plan.
|
||||
2. **Run tests** — `npm run test:all` (or the specific test file) to ensure 100% pass.
|
||||
3. **Update CHANGELOG.md** with all new bug fix entries.
|
||||
4. **Commit** each fix individually on the release branch with message format: `fix: <description> (#<issue_number>)`.
|
||||
5. **Push** the release branch: `git push origin release/vX.Y.Z`.
|
||||
6. **Close resolved issues immediately**. For each issue that was marked as Fixed, run:
|
||||
`gh issue close <NUMBER> --repo <owner>/<repo> --comment "Thank you for reporting! This issue has been fixed and will be included in the next release (vX.Y.Z)."`
|
||||
7. Likewise, close `Duplicate` issues referencing the original, close `Needs Info` if stale, and post the required comments.
|
||||
8. If the project runs automatic releases or needs a PR, proceed to run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user).
|
||||
|
||||
- **User confirms** → Proceed to step 8
|
||||
- **User requests changes** → Apply changes, push to the same branch, notify again
|
||||
- **User rejects** → Close the PR and stop
|
||||
|
||||
### 8. Merge, Close Issues & Release (only after user confirms PR)
|
||||
|
||||
After the user confirms the PR:
|
||||
|
||||
1. **Merge** the PR: `gh pr merge <NUMBER> --merge --repo <owner>/<repo>` or via local merge
|
||||
2. **Close** resolved issues with a comment: `gh issue close <NUMBER> --repo <owner>/<repo> --comment "Fixed in <commit_hash>. The fix will be included in the next release."`
|
||||
3. **Switch to main**: `git checkout main && git pull`
|
||||
4. Run the `/update-docs` workflow (at `~/.gemini/antigravity/global_workflows/update-docs.md`) to update CHANGELOG and README
|
||||
5. Run the `/generate-release` workflow (at `.agents/workflows/generate-release.md`) to bump version, tag, and publish
|
||||
6. Deploy to local VPS: `ssh root@192.168.0.15 "npm install -g omniroute@<VERSION> && pm2 restart omniroute"`
|
||||
|
||||
If NO fixes were committed, skip this step and just present the report.
|
||||
If NO fixes were committed, skip closing and source control steps and just conclude the workflow.
|
||||
|
||||
@@ -4,9 +4,30 @@ description: Analyze open Pull Requests from the project's GitHub repository, ge
|
||||
|
||||
# /review-prs — PR Review & Analysis Workflow
|
||||
|
||||
## ⛔ ABSOLUTE PROHIBITION — Read Before Anything Else
|
||||
|
||||
> **NEVER close a contributor's PR if you intend to use ANY of their code, ideas, or fixes.**
|
||||
>
|
||||
> **NEVER manually integrate contributor code into a release branch and then close their PR.**
|
||||
>
|
||||
> These actions are **STRICTLY FORBIDDEN** under all circumstances:
|
||||
>
|
||||
> 1. ❌ Closing a PR and cherry-picking/copying its code into a release branch
|
||||
> 2. ❌ Closing a PR "because of conflicts" and re-implementing the same fix yourself
|
||||
> 3. ❌ Closing a PR and committing a "similar" solution inspired by it
|
||||
> 4. ❌ Using `gh pr close` on any PR whose content was or will be used
|
||||
>
|
||||
> **Why**: Closing a PR after taking the contributor's work means they get ZERO credit on GitHub — no "Merged" badge, no contribution graph entry, no public record. This is effectively stealing their contribution. An audit found this happened to **37 PRs** in the past.
|
||||
>
|
||||
> **The ONLY acceptable flow**: Resolve conflicts IN the contributor's branch, push fixes TO their branch, then merge THEIR PR via `gh pr merge`. See Step 7 and Step 8 for the exact procedure.
|
||||
>
|
||||
> **When to close a PR**: ONLY when the user (repository owner) explicitly requests it, OR when the PR is clearly spam/malicious, OR when the author themselves asks to close it. In ALL other cases, leave it open.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open PRs from the project's GitHub repository, performs a critical analysis of each one, generates a detailed report, and waits for user approval before proceeding with implementation. **All improvements are committed on top of the PR branch** and the user must verify before merge.
|
||||
This workflow fetches all open PRs from the project's GitHub repository, performs a critical analysis of each one, generates a detailed report, and waits for user approval before proceeding with implementation. **All improvements are committed on the current release branch** (`release/vX.Y.Z`).
|
||||
|
||||
> **BRANCH RULE**: PRs are ALWAYS merged into the current `release/vX.Y.Z` branch, NEVER directly into `main`. The release branch acts as a staging area — only after all PRs are integrated and tests pass does the release branch get merged into `main` via the `/generate-release` workflow.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -16,55 +37,118 @@ This workflow fetches all open PRs from the project's GitHub repository, perform
|
||||
// turbo
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
|
||||
### 2. Fetch Open Pull Requests
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `gh pr list --repo <owner>/<repo> --state open --limit 500 --json number,title,author,headRefName,body,createdAt,additions,deletions,files`
|
||||
- This fetches **all** open PRs without restriction. Get the diff for each with:
|
||||
`gh pr diff <NUMBER> --repo <owner>/<repo>`
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
# Bump patch: e.g. 3.3.11 → 3.3.12
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch Open Pull Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh pr list` can be truncated by the tool, silently hiding PRs. You MUST use the two-step approach below to guarantee **all** PRs are fetched.
|
||||
|
||||
**Step 3a — Get PR numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh pr list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one PR number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each PR** (one call per PR):
|
||||
|
||||
- For each PR number from step 3a, run:
|
||||
`gh pr view <NUMBER> --repo <owner>/<repo> --json number,title,author,headRefName,baseRefName,body,createdAt,additions,deletions,files`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
|
||||
**Step 3c — Fetch diffs for each PR** (one call per PR, saved to /tmp):
|
||||
|
||||
- For each PR number, run:
|
||||
`gh pr diff <NUMBER> --repo <owner>/<repo> > /tmp/pr<NUMBER>.diff`
|
||||
- Then read each diff file with `view_file`.
|
||||
|
||||
- For each open PR, collect:
|
||||
- PR number, title, author, branch, number of commits, date
|
||||
- PR description/body
|
||||
- Files changed (diff)
|
||||
- Existing review comments (from bots or humans)
|
||||
|
||||
### 3. Analyze Each PR — For each open PR, perform the following analysis:
|
||||
**Verification**: Confirm the count of PRs analyzed matches the count from step 3a before proceeding.
|
||||
|
||||
#### 3a. Feature Assessment
|
||||
### 3.5 Redirect PR Base Branches to Release Branch
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: Contributors typically open PRs targeting `main`. Before analyzing or merging, redirect ALL open PRs to target the current release branch instead.
|
||||
|
||||
```bash
|
||||
# Get the current release branch name
|
||||
RELEASE_BRANCH=$(git branch --show-current) # e.g. release/v3.5.4
|
||||
|
||||
# For each open PR that targets main, change its base to the release branch
|
||||
for PR_NUM in $(gh pr list --repo <owner>/<repo> --state open --json number,baseRefName --jq '.[] | select(.baseRefName == "main") | .number'); do
|
||||
echo "Redirecting PR #$PR_NUM → $RELEASE_BRANCH"
|
||||
gh pr edit "$PR_NUM" --repo <owner>/<repo> --base "$RELEASE_BRANCH"
|
||||
done
|
||||
```
|
||||
|
||||
This ensures:
|
||||
|
||||
1. PRs merge into the release branch, not directly into `main`
|
||||
2. Merge conflict detection is accurate against the release branch
|
||||
3. The release branch accumulates all changes before the final merge to `main`
|
||||
4. If the release branch doesn't exist on remote yet, push it first: `git push origin $RELEASE_BRANCH`
|
||||
|
||||
### 4. Analyze Each PR — For each open PR, perform the following analysis:
|
||||
|
||||
#### 4a. Feature Assessment
|
||||
|
||||
- **Does it make sense?** Evaluate if the feature fills a real gap or solves a valid problem
|
||||
- **Alignment** — Check if it aligns with the project's architecture and roadmap
|
||||
- **Complexity** — Assess if the scope is reasonable or if it should be split
|
||||
|
||||
#### 3b. Code Quality Review
|
||||
#### 4b. Code Quality Review
|
||||
|
||||
- Check for code duplication
|
||||
- Evaluate error handling patterns (consistent with existing codebase?)
|
||||
- Check naming conventions and code style
|
||||
- Verify TypeScript types (any `any` usage, missing types?)
|
||||
|
||||
#### 3c. Security Review
|
||||
#### 4c. Security Review
|
||||
|
||||
- Check for missing authentication/authorization on new endpoints
|
||||
- Check for injection vulnerabilities (URL params, SQL, XSS)
|
||||
- Verify input validation on all user-controlled data
|
||||
- Check for hardcoded secrets or credentials
|
||||
|
||||
#### 3d. Architecture Review
|
||||
#### 4d. Architecture Review
|
||||
|
||||
- Does the change follow existing patterns?
|
||||
- Are there any breaking changes to public APIs?
|
||||
- Is the database schema affected? Migration needed?
|
||||
- Impact on performance (N+1 queries, missing indexes?)
|
||||
|
||||
#### 3e. Test Coverage
|
||||
#### 4e. Test Coverage
|
||||
|
||||
- Does the PR include tests?
|
||||
- Are edge cases covered?
|
||||
- Would existing tests break?
|
||||
|
||||
#### 3f. Cross-Layer (Global) Analysis
|
||||
#### 4f. Cross-Layer (Global) Analysis
|
||||
|
||||
Perform a **global impact assessment** to verify whether the PR changes are complete across all layers of the application:
|
||||
|
||||
@@ -79,7 +163,7 @@ Perform a **global impact assessment** to verify whether the PR changes are comp
|
||||
- **Cross-cutting concerns**: Check shared layers (types, DTOs, validation schemas, routes, middleware) for completeness
|
||||
- **Document gaps** — If missing layers are detected, list them as **IMPORTANT** issues in the report with concrete suggestions for what should be added
|
||||
|
||||
### 4. Generate Report — Create a markdown report for each PR including:
|
||||
### 5. Generate Report — Create a markdown report for each PR including:
|
||||
|
||||
- **PR Summary** — What it does, files affected, commit count
|
||||
- **Improvements/Benefits** — Numbered list with impact level (HIGH/MEDIUM/LOW)
|
||||
@@ -88,62 +172,90 @@ Perform a **global impact assessment** to verify whether the PR changes are comp
|
||||
- **Verdict** — Ready to merge? With mandatory vs optional fixes
|
||||
- **Next Steps** — What will happen if approved
|
||||
|
||||
### 5. Present to User
|
||||
### 6. Present to User
|
||||
|
||||
- Show the report via `notify_user` with `BlockedOnUser: true`
|
||||
- Wait for user decision:
|
||||
- **Approved** → Proceed to step 6
|
||||
- **Approved** → Proceed to step 7
|
||||
- **Approved with changes** → Implement the fixes and corrections before merging
|
||||
- **Rejected** → Close the PR or leave a review comment
|
||||
|
||||
### 6. Implementation (if approved)
|
||||
### 7. Pre-Merge Fixes & CI Green-Lighting (if approved)
|
||||
|
||||
- Checkout the PR branch: `gh pr checkout <NUMBER>`
|
||||
- Implement any required fixes identified in the analysis
|
||||
- If the Cross-Layer Analysis (3f) identified missing frontend/backend counterparts, implement them
|
||||
- **Commit improvements on top of the PR branch** with descriptive commit messages
|
||||
- Run the project's test suite to verify nothing breaks
|
||||
> **⚠️ Fixes and Conflict Resolutions MUST be pushed back to the PR branch before merging.** We want the PR itself to be green and fully valid before it integrates.
|
||||
|
||||
- **Sync latest fixes & Resolve Conflicts:** Merge the current `release` branch into the PR branch. If there are merge conflicts, you MUST resolve them inside the author's PR branch. NEVER resolve conflicts by closing their PR and doing the work in a separate branch, as this steals credit from the original author.
|
||||
- **Implement improvements:** Apply the required fixes identified in the analysis directly on the PR branch (e.g., adding missing API routes, fixing SSRF, applying comments from other agents).
|
||||
- **Pushing changes to PR branches:**
|
||||
|
||||
```bash
|
||||
# Checkout the PR locally
|
||||
gh pr checkout <NUMBER>
|
||||
|
||||
# Apply fixes, commit your changes
|
||||
git commit -m "chore: apply review suggestions and missing layers"
|
||||
|
||||
# Attempt to push directly to the PR branch
|
||||
git push
|
||||
```
|
||||
|
||||
- **Fallback (For external forks without maintainer edit access):**
|
||||
If `git push` fails because the PR comes from an external fork without write access, you MUST:
|
||||
1. Create a new branch ending in `-fix` (e.g., `checkout -b fix-pr-<NUMBER>`).
|
||||
2. Push your branch to the main repo (`git push origin fix-pr-<NUMBER>`).
|
||||
3. Create a Pull Request targeting the contributor's repository and branch (use `gh pr create --repo <contributor-repo> --base <contributor-branch> --head diegosouzapw:fix-pr-<NUMBER>`).
|
||||
4. Once they accept our PR into their branch, their original PR to our `main` will automatically update and become green.
|
||||
|
||||
- Run the project's test suite locally to verify nothing breaks:
|
||||
// turbo
|
||||
- Run: `npm test` or equivalent test command
|
||||
- Build the project to verify compilation
|
||||
// turbo
|
||||
- Run: `npm run build` or equivalent build command
|
||||
- Push the updated branch: `git push origin <branch-name>`
|
||||
|
||||
### 7. 🛑 WAIT — Notify User & Await PR Verification
|
||||
### 8. Merge into Release Branch
|
||||
|
||||
**This is a mandatory stop point.** Use `notify_user` with `BlockedOnUser: true`:
|
||||
### 8. Merge into Release Branch (NEVER CLOSE!)
|
||||
|
||||
- Inform the user that the PR has been **improved and pushed**, and is **awaiting their verification**
|
||||
- Include:
|
||||
- PR number and URL
|
||||
- Summary of improvements/fixes applied
|
||||
- Build/test status
|
||||
- List of files changed
|
||||
- **DO NOT merge, generate releases, or deploy until the user confirms**
|
||||
> **⚠️ CRITICAL**: NEVER use `gh pr close` for a PR whose idea or code was accepted. Closing a PR in a contributor's face after taking their idea—or closing it just because it had conflicts—is unacceptable.
|
||||
> You MUST ALWAYS resolve conflicts and apply fixes on the author's PR branch, and then merge the PR using GitHub so the contributor gets the official "Merged" badge and proper credit on their profile.
|
||||
|
||||
Wait for the user to respond:
|
||||
Even if the PR had severe conflicts or required significant architectural adjustments, you MUST:
|
||||
|
||||
- **User confirms** → Proceed to step 8
|
||||
- **User requests more changes** → Apply changes, push to the same branch, notify again
|
||||
- **User rejects** → Leave a review comment and stop
|
||||
1. Resolve any conflicts and apply the fixes directly to their PR branch (as detailed in step 7).
|
||||
2. Once the PR branch is green, conflict-free, and correct, merge it into the release branch using the GitHub CLI.
|
||||
|
||||
### 8. Thank the Contributor
|
||||
```bash
|
||||
# Merge the PR (base is already set to release/vX.Y.Z from step 3.5)
|
||||
gh pr merge <NUMBER> --repo <owner>/<repo> --squash --body "Integrated into release/vX.Y.Z"
|
||||
```
|
||||
|
||||
- Post a **thank-you comment** on the PR via the GitHub API
|
||||
In ALL cases:
|
||||
|
||||
- Post a **thank-you comment** on the PR via the GitHub API before or immediately after merging.
|
||||
- The message should:
|
||||
- Thank the author by name/username for their contribution
|
||||
- Briefly mention what the PR accomplishes and any improvements applied
|
||||
- Be friendly, professional, and encouraging
|
||||
- Example: _"Thanks @author for this great contribution! 🎉 The [feature/fix] is now merged and will be part of the next release. We appreciate your effort!"_
|
||||
- Thank the author by name/username for their contribution.
|
||||
- Explain what was adjusted or improved (if we pushed fixes to their branch).
|
||||
- Note it will be included in the upcoming release.
|
||||
- Be friendly, professional, and encouraging.
|
||||
- Example: _"Thanks @author for this great contribution! 🎉 We've added a few small adjustments to your branch to align with our latest architecture, and it's now officially merged into the release/vX.Y.Z branch. It will be part of the next release. We appreciate your effort!"_
|
||||
|
||||
### 9. Merge & Release (only after user confirms PR)
|
||||
### 9. Sync Local Release Branch
|
||||
|
||||
After the user confirms the PR:
|
||||
After merging PRs, sync the local release branch to include the new changes:
|
||||
|
||||
1. **Merge** the PR into main (local merge with `--no-ff` or via `gh pr merge`)
|
||||
2. **Push** to main: `git push origin main`
|
||||
3. **Clean up** the feature branch: `git branch -d <branch-name>`
|
||||
4. **Update CHANGELOG.md** with the new feature/fix
|
||||
5. Run the `/generate-release` workflow (at `.agents/workflows/generate-release.md`) to bump version, tag, and publish
|
||||
6. Deploy to local VPS: `ssh root@192.168.0.15 "npm install -g omniroute@<VERSION> && pm2 restart omniroute"`
|
||||
```bash
|
||||
git fetch origin
|
||||
git pull origin release/vX.Y.Z
|
||||
```
|
||||
|
||||
### 10. Continue or Finalize
|
||||
|
||||
After processing all approved PRs:
|
||||
|
||||
- If more PRs remain, go back to step 7
|
||||
- When all PRs are processed, **update CHANGELOG.md** on the release branch with all new entries
|
||||
- Run **test coverage** to verify all metrics stay above 85%:
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
- Fix any test regressions introduced by merged PRs
|
||||
- Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
- The `/generate-release` workflow handles the final merge from `release/vX.Y.Z` → `main`
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
---
|
||||
description: How to automatically summarize recent changes and update README and CHANGELOG
|
||||
---
|
||||
|
||||
# Update Documentation Workflow
|
||||
|
||||
Update CHANGELOG.md, README.md, docs/ files, and all multi-language translations whenever features are added or changed.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Summarize recent changes
|
||||
|
||||
Review git log and identify new features, fixes, or changes since the last release tag:
|
||||
|
||||
```bash
|
||||
git log $(git describe --tags --abbrev=0)..HEAD --oneline
|
||||
```
|
||||
|
||||
### 2. Update English CHANGELOG.md
|
||||
|
||||
Add an `[Unreleased]` section (or version header if releasing) with:
|
||||
|
||||
- `### ✨ New Features` — each feature as a bullet point
|
||||
- `### 🐛 Bug Fixes` — if applicable
|
||||
- `### 🧪 Tests` — test count changes
|
||||
- `### 📁 New Files` — table of new files with purpose
|
||||
|
||||
### 3. Update English README.md
|
||||
|
||||
Update the feature tables in these sections:
|
||||
|
||||
- **🧠 Routing & Intelligence** — for routing/model features
|
||||
- **🛡️ Resilience & Security** — for security/resilience features
|
||||
- **📊 Observability & Analytics** — for monitoring features
|
||||
- **☁️ Deploy & Sync** — for deployment features
|
||||
|
||||
### 4. Update docs/ files
|
||||
|
||||
- `docs/FEATURES.md` — update the Settings section description
|
||||
- `docs/API_REFERENCE.md` — add new API routes if any
|
||||
- `docs/ARCHITECTURE.md` — update architecture if structural changes
|
||||
|
||||
### 5. 🌐 Sync Multi-Language Documentation (CRITICAL)
|
||||
|
||||
// turbo-all
|
||||
|
||||
**This step MUST be run after every README or docs update.**
|
||||
|
||||
The project has **30 language versions** of documentation:
|
||||
|
||||
**README files (root directory):**
|
||||
|
||||
```
|
||||
README.md (English - source of truth)
|
||||
README.pt-BR.md README.pt.md README.es.md README.fr.md README.it.md
|
||||
README.de.md README.nl.md README.sv.md README.no.md README.da.md README.fi.md
|
||||
README.ru.md README.uk-UA.md README.bg.md README.sk.md README.pl.md README.ro.md README.hu.md
|
||||
README.ar.md README.he.md README.th.md README.in.md README.id.md README.ms.md README.vi.md
|
||||
README.ja.md README.ko.md README.zh-CN.md README.phi.md README.cs.md
|
||||
```
|
||||
|
||||
**docs/i18n/ directories (29 languages):**
|
||||
|
||||
```
|
||||
docs/i18n/{ar,bg,cs,da,de,es,fi,fr,he,hu,id,in,it,ja,ko,ms,nl,no,phi,pl,pt,pt-BR,ro,ru,sk,sv,th,uk-UA,vi,zh-CN}/
|
||||
Each contains: API_REFERENCE.md, ARCHITECTURE.md, CODEBASE_DOCUMENTATION.md, FEATURES.md, TROUBLESHOOTING.md, USER_GUIDE.md
|
||||
```
|
||||
|
||||
**Sync approach for feature table updates:**
|
||||
|
||||
a. Identify which feature table rows were added to English README.md
|
||||
b. For each translated README, find the corresponding anchor lines:
|
||||
|
||||
- **Routing section:** Find the `💬` (System Prompt) table row — the line before it is always the last routing feature. Insert new routing features before System Prompt.
|
||||
- **Resilience section:** Find the `📊` Rate Limits table row (the one in lines 590-600, NOT the quota tracking one in lines 560-570). Insert new resilience features after it.
|
||||
c. The new feature entries can stay in English for technical features, matching the pattern used in the existing translations.
|
||||
d. Use `sed` or similar tool to batch-insert across all 29 translated READMEs.
|
||||
|
||||
**Verification:**
|
||||
|
||||
```bash
|
||||
# Verify all READMEs have the new features
|
||||
grep -l "NEW_FEATURE_NAME" README.*.md | wc -l
|
||||
# Should return 30 (all language versions)
|
||||
```
|
||||
|
||||
**FEATURES.md sync:**
|
||||
|
||||
```bash
|
||||
# Update Settings description in all docs/i18n/*/FEATURES.md
|
||||
for dir in docs/i18n/*/; do
|
||||
# Update the Settings section description to mention new features
|
||||
# Check FEATURES.md in each directory
|
||||
done
|
||||
```
|
||||
|
||||
### 6. Verify documentation changes
|
||||
|
||||
```bash
|
||||
# Check all modified files
|
||||
git status --short
|
||||
|
||||
# Verify no broken markdown
|
||||
# Optional: run markdownlint if available
|
||||
```
|
||||
327
.agents/workflows/version-bump.md
Normal file
327
.agents/workflows/version-bump.md
Normal file
@@ -0,0 +1,327 @@
|
||||
---
|
||||
description: Bump version, auto-generate CHANGELOG from git commits, update all versioned files, and refresh root + docs/ documentation to reflect the current project state
|
||||
---
|
||||
|
||||
# Version Bump Workflow
|
||||
|
||||
Automatically bump the project version, generate CHANGELOG entries from git history since the last tag, update every file that references the version, and refresh project documentation to reflect the current state.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.4.10` → `3.5.0`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Determine Version
|
||||
|
||||
### 1. Read current version and last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
echo "Last tag: $LAST_TAG"
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
```
|
||||
|
||||
### 2. Calculate new version
|
||||
|
||||
Apply the patch bump rule:
|
||||
|
||||
- If the current patch number is `9`, the new version is `3.(minor+1).0`
|
||||
- Otherwise, increment patch: `3.x.y` → `3.x.(y+1)`
|
||||
|
||||
If the version was ALREADY bumped (e.g. you are on a release branch and package.json already has the new version), **skip the npm version bump** and use the existing version.
|
||||
|
||||
### 3. Bump package.json (if needed)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
# Only if version hasn't been bumped yet
|
||||
npm version patch --no-git-tag-version
|
||||
```
|
||||
|
||||
Or for threshold (y=10):
|
||||
|
||||
```bash
|
||||
# Manual threshold bump
|
||||
VERSION="3.X.0" # compute manually
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Generate CHANGELOG from Git History
|
||||
|
||||
### 4. Collect commits since last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
|
||||
echo "=== Commits since $LAST_TAG ==="
|
||||
git log "$LAST_TAG"..HEAD --pretty=format:"%h %s" --no-merges | head -100
|
||||
echo ""
|
||||
echo "=== Merge commits ==="
|
||||
git log "$LAST_TAG"..HEAD --merges --pretty=format:"%h %s" | head -50
|
||||
```
|
||||
|
||||
### 5. Classify commits and generate CHANGELOG section
|
||||
|
||||
Analyze each commit message and classify into categories based on the conventional-commit prefix and content:
|
||||
|
||||
| Category | Patterns |
|
||||
| ------------------- | ------------------------------------------------ |
|
||||
| ✨ New Features | `feat:`, `feat(*):` |
|
||||
| 🐛 Bug Fixes | `fix:`, `fix(*):` |
|
||||
| ⚠️ Breaking Changes | `BREAKING CHANGE`, `!:` suffix |
|
||||
| 🛠️ Maintenance | `chore:`, `refactor:`, `perf:`, `build:` |
|
||||
| 🧪 Tests | `test:`, `tests:` |
|
||||
| 📝 Documentation | `docs:` |
|
||||
| 🔒 Security | `security:`, CVE references, vulnerability fixes |
|
||||
| 🌍 i18n | translation updates, locale changes |
|
||||
|
||||
For each category with entries, create a markdown section with descriptive bullet points. Use the commit messages but rewrite them to be human-readable and descriptive (not raw commit messages).
|
||||
|
||||
**If a commit references a PR number** (e.g. `#880`, `PR #885`), include it in the description.
|
||||
|
||||
### 6. Update CHANGELOG.md
|
||||
|
||||
Replace the `## [Unreleased]` section content with the generated entries, then add the new versioned section:
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [NEW_VERSION] — YYYY-MM-DD
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Feature name:** Description (#PR)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Fix name:** Description (#PR)
|
||||
|
||||
### 🛠️ Maintenance
|
||||
|
||||
- **Item:** Description
|
||||
|
||||
---
|
||||
|
||||
## [PREVIOUS_VERSION] — YYYY-MM-DD
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
The date must be today's date in `YYYY-MM-DD` format.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Sync Version Across All Files
|
||||
|
||||
### 7. Update workspace package.json files and openapi.yaml
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Update docs/openapi.yaml version
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/openapi.yaml
|
||||
echo "✓ docs/openapi.yaml → $VERSION"
|
||||
|
||||
# Update workspace packages (open-sse, electron)
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✓ All workspace packages synced to $VERSION"
|
||||
```
|
||||
|
||||
### 8. Update llm.txt version references
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
OLD_VERSION_PATTERN='[0-9]\+\.[0-9]\+\.[0-9]\+'
|
||||
|
||||
# Update "Current version:" line
|
||||
sed -i "s/\*\*Current version:\*\* $OLD_VERSION_PATTERN/**Current version:** $VERSION/" llm.txt
|
||||
|
||||
# Update "Key Features (vX.Y.Z)" header
|
||||
sed -i "s/## Key Features (v$OLD_VERSION_PATTERN)/## Key Features (v$VERSION)/" llm.txt
|
||||
|
||||
echo "✓ llm.txt → $VERSION"
|
||||
```
|
||||
|
||||
### 9. Regenerate lock file
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
npm install
|
||||
echo "✓ Lock file regenerated"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Update Root Documentation
|
||||
|
||||
Based on the CHANGELOG entries generated in Phase 2, review and update these root-level files if relevant changes warrant updates:
|
||||
|
||||
### 10. Review and update root documentation files
|
||||
|
||||
For each file below, read the current content and determine if the CHANGELOG entries require any updates. Only modify files where substantive changes have occurred:
|
||||
|
||||
| File | When to update |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `README.md` | New providers, major features, stats changes (test count, provider count), badges, installation instructions, feature table |
|
||||
| `AGENTS.md` | Architecture changes, new modules, new commands, new providers, new services/handlers/executors |
|
||||
| `CONTRIBUTING.md` | Dev workflow changes, new tooling, test infrastructure changes |
|
||||
| `SECURITY.md` | Security fixes, new auth mechanisms, vulnerability disclosures |
|
||||
| `llm.txt` | Provider count changes, new features, architecture changes |
|
||||
|
||||
**Update rules:**
|
||||
|
||||
- **README.md**: Update provider count, test count, feature highlights table, badges if any numbers changed. If a new provider was added, add it to the provider table. If a major feature was added, add it to the features section.
|
||||
- **AGENTS.md**: If new architecture components (handlers, executors, services, DB modules) were added, update the Architecture section. If new commands were added, update the Build/Test table.
|
||||
- **SECURITY.md**: Add new vulnerability fixes or security improvements to the relevant section.
|
||||
- **llm.txt**: Update provider count, feature list, version references.
|
||||
|
||||
### 11. Review and update docs/ files (excluding i18n/)
|
||||
|
||||
For each file in `docs/` (excluding `docs/i18n/`), review if CHANGELOG changes affect it:
|
||||
|
||||
| File | When to update |
|
||||
| -------------------------------- | --------------------------------------------------- |
|
||||
| `docs/API_REFERENCE.md` | New API endpoints, changed request/response formats |
|
||||
| `docs/ARCHITECTURE.md` | New modules, new services, changed data flow |
|
||||
| `docs/CLI-TOOLS.md` | New CLI tool integrations, config format changes |
|
||||
| `docs/FEATURES.md` | New features, removed features, changed settings |
|
||||
| `docs/MCP-SERVER.md` | New MCP tools, changed tool signatures |
|
||||
| `docs/A2A-SERVER.md` | New A2A skills, protocol changes |
|
||||
| `docs/USER_GUIDE.md` | UX changes, new dashboard pages, settings changes |
|
||||
| `docs/VM_DEPLOYMENT_GUIDE.md` | Deployment changes, new env vars |
|
||||
| `docs/TROUBLESHOOTING.md` | New known issues, resolved problems |
|
||||
| `docs/AUTO-COMBO.md` | Routing changes, new strategies |
|
||||
| `docs/CODEBASE_DOCUMENTATION.md` | New files, architectural changes |
|
||||
| `docs/RELEASE_CHECKLIST.md` | Process changes |
|
||||
| `docs/COVERAGE_PLAN.md` | Test changes |
|
||||
| `docs/openapi.yaml` | Already updated in step 7 |
|
||||
|
||||
**Only update files where the CHANGELOG entries directly affect the documented content.** Do NOT update files just to bump a version number — only when the documented behavior, features, or architecture has actually changed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Verify
|
||||
|
||||
### 12. Run lint check
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 13. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
npm test
|
||||
```
|
||||
|
||||
### 14. Verify version sync across all files
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "Expected version: $VERSION"
|
||||
echo ""
|
||||
|
||||
echo "--- package.json ---"
|
||||
grep '"version"' package.json | head -1
|
||||
|
||||
echo "--- open-sse/package.json ---"
|
||||
grep '"version"' open-sse/package.json | head -1
|
||||
|
||||
echo "--- electron/package.json ---"
|
||||
[ -f electron/package.json ] && grep '"version"' electron/package.json | head -1
|
||||
|
||||
echo "--- docs/openapi.yaml ---"
|
||||
grep " version:" docs/openapi.yaml | head -1
|
||||
|
||||
echo "--- llm.txt ---"
|
||||
grep "Current version:" llm.txt
|
||||
|
||||
echo "--- CHANGELOG.md (first versioned entry) ---"
|
||||
grep "^## \[" CHANGELOG.md | head -2
|
||||
```
|
||||
|
||||
### 15. 🛑 STOP — Present Summary to User
|
||||
|
||||
**STOP** and present a summary to the user including:
|
||||
|
||||
- Old version → New version
|
||||
- CHANGELOG entries generated
|
||||
- Files modified
|
||||
- Test results
|
||||
- Any documentation updates made
|
||||
|
||||
**Wait for the user to confirm before committing.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Commit (only after user approval)
|
||||
|
||||
### 16. Stage and commit
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router
|
||||
git add -A
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
git commit -m "chore(release): bump to v$VERSION — changelog, docs, version sync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow does **NOT** create tags, releases, or deploy. Use `/generate-release` for the full release cycle after this.
|
||||
- This workflow does **NOT** update `docs/i18n/` translations. Use `/update-i18n` separately after committing.
|
||||
- The CHANGELOG generation is based on git commits since the last tag. If there are no new commits, the workflow should inform the user and stop.
|
||||
- Always verify the generated CHANGELOG entries make sense — raw commit messages may need rewriting for clarity.
|
||||
- If the version was already bumped (e.g. you're on a `release/vX.Y.Z` branch), skip the `npm version` step and use the existing version.
|
||||
|
||||
## Version Touchpoints Checklist
|
||||
|
||||
| File | Field/Pattern |
|
||||
| ----------------------- | ----------------------------------------------------------- |
|
||||
| `package.json` | `"version": "X.Y.Z"` |
|
||||
| `open-sse/package.json` | `"version": "X.Y.Z"` |
|
||||
| `electron/package.json` | `"version": "X.Y.Z"` |
|
||||
| `docs/openapi.yaml` | `version: X.Y.Z` |
|
||||
| `llm.txt` | `**Current version:** X.Y.Z` and `## Key Features (vX.Y.Z)` |
|
||||
| `CHANGELOG.md` | `## [X.Y.Z] — YYYY-MM-DD` |
|
||||
@@ -30,3 +30,79 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Test suites
|
||||
tests
|
||||
test-results
|
||||
playwright-report
|
||||
blob-report
|
||||
|
||||
# Documentation (not needed in container)
|
||||
docs
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Electron (separate build)
|
||||
electron
|
||||
|
||||
# VS Code extension (separate project)
|
||||
vscode-extension
|
||||
|
||||
# Build artifacts
|
||||
*.tgz
|
||||
*.AppImage
|
||||
*.deb
|
||||
*.rpm
|
||||
|
||||
# Package manager lock (bun)
|
||||
bun.lock
|
||||
|
||||
# Agent config
|
||||
.agents
|
||||
.gemini
|
||||
|
||||
# Misc
|
||||
llm.txt
|
||||
images
|
||||
clipr
|
||||
omnirouteCloud
|
||||
omnirouteSite
|
||||
|
||||
# Temporary/Scratch Folders
|
||||
_*
|
||||
|
||||
# CI/CD and Version Control (that are not actual code)
|
||||
.github
|
||||
.husky
|
||||
.omc
|
||||
|
||||
# Test Configs and Reports
|
||||
playwright.config.ts
|
||||
vitest*.ts
|
||||
audit-report.json
|
||||
sonar-project.properties
|
||||
|
||||
# Deployment Configs
|
||||
docker-compose*.yml
|
||||
fly.toml
|
||||
|
||||
# Consistent with .gitignore
|
||||
.DS_Store
|
||||
.idea/
|
||||
.config/
|
||||
.data/
|
||||
.omnivscodeagent/
|
||||
*.sqlite-*
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
security-analysis/
|
||||
.analysis/
|
||||
antigravity-manager-analysis/
|
||||
.sisyphus/
|
||||
.plans/
|
||||
app.__qa_backup/
|
||||
.app-build-backup-*/
|
||||
.gitnexus
|
||||
.worktrees
|
||||
.next-playwright/
|
||||
cloud/
|
||||
|
||||
807
.env.example
807
.env.example
@@ -1,158 +1,463 @@
|
||||
# OmniRoute environment contract
|
||||
# This file reflects actual runtime usage in the current codebase.
|
||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
# │ OmniRoute — .env Contract │
|
||||
# │ This file documents EVERY environment variable read by the runtime. │
|
||||
# │ Copy to .env and adjust values. Lines starting with # are commented out │
|
||||
# │ (optional / off-by-default). Uncomment only what you need. │
|
||||
# │ Reference: docs/ENVIRONMENT.md for full details and usage scenarios. │
|
||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
# ═══════════════════════════════════════════════════
|
||||
# REQUIRED SECRETS — Generate strong values!
|
||||
# ═══════════════════════════════════════════════════
|
||||
# Generate with: openssl rand -base64 48
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 1. REQUIRED SECRETS — Must be set before first run!
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# These secrets are critical for security. Generate strong, unique values.
|
||||
|
||||
# JWT signing key for dashboard session tokens.
|
||||
# Used by: src/lib/auth — signs/verifies all authenticated session cookies.
|
||||
# Generate: openssl rand -base64 48
|
||||
JWT_SECRET=
|
||||
# Generate with: openssl rand -hex 32
|
||||
|
||||
# Encryption key for API keys stored in the database.
|
||||
# Used by: src/lib/db/apiKeys.ts — encrypts API key values at rest in SQLite.
|
||||
# Generate: openssl rand -hex 32
|
||||
API_KEY_SECRET=
|
||||
|
||||
# Initial admin password — CHANGE THIS before first use!
|
||||
# Initial admin login password — CHANGE THIS before first use!
|
||||
# Used by: bootstrap only — sets the initial dashboard password on first boot.
|
||||
# After first login you can change it from Dashboard → Settings → Security.
|
||||
# Default: CHANGEME (insecure, for local dev only)
|
||||
INITIAL_PASSWORD=CHANGEME
|
||||
DATA_DIR=/var/lib/omniroute
|
||||
|
||||
# Storage (SQLite)
|
||||
STORAGE_DRIVER=sqlite
|
||||
# Generate with: openssl rand -hex 32
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 2. STORAGE & DATABASE
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# OmniRoute uses SQLite for all persistence. These variables control where
|
||||
# data lives, encryption, and cleanup policies.
|
||||
|
||||
# Base directory for all persistent data (SQLite DB, logs, backups).
|
||||
# Used by: src/lib/db/core.ts — resolves the SQLite database file path.
|
||||
# Default: ~/.omniroute/ | Override for Docker or custom installations.
|
||||
# DATA_DIR=/var/lib/omniroute
|
||||
|
||||
# Encryption key for SQLite database encryption at rest.
|
||||
# Used by: src/lib/db/encryption.ts — encrypts the entire SQLite database.
|
||||
# Generate: openssl rand -hex 32 | Leave empty to disable DB encryption.
|
||||
STORAGE_ENCRYPTION_KEY=
|
||||
STORAGE_ENCRYPTION_KEY_VERSION=v1
|
||||
LOG_RETENTION_DAYS=90
|
||||
SQLITE_MAX_SIZE_MB=2048
|
||||
SQLITE_CLEAN_LEGACY_FILES=true
|
||||
|
||||
# Recommended runtime variables
|
||||
# Canonical/base port (keeps backward compatibility)
|
||||
# Version tag for the encryption key — allows future key rotation.
|
||||
# Used by: scripts/bootstrap-env.mjs, electron/main.js — persists key version.
|
||||
# Default: v1 | Increment when rotating STORAGE_ENCRYPTION_KEY.
|
||||
STORAGE_ENCRYPTION_KEY_VERSION=v1
|
||||
|
||||
# Automatic SQLite backup on startup.
|
||||
# Used by: src/lib/db/backup.ts — creates a timestamped backup before migrations.
|
||||
# Default: false (backups enabled) | Set true to skip backup on every restart.
|
||||
DISABLE_SQLITE_AUTO_BACKUP=false
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 3. NETWORK & PORTS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# OmniRoute can run on a single port (default) or split Dashboard/API ports.
|
||||
|
||||
# Canonical port for both Dashboard UI and API (single-port mode).
|
||||
# Used by: src/lib/runtime/ports.ts — base port for the Next.js server.
|
||||
# Default: 20128
|
||||
PORT=20128
|
||||
# Optional split ports:
|
||||
|
||||
# Split-port mode: serve Dashboard and API on separate ports for network isolation.
|
||||
# Used by: src/lib/runtime/ports.ts — overrides PORT for each service.
|
||||
# API_PORT=20129
|
||||
# API_HOST=0.0.0.0
|
||||
# DASHBOARD_PORT=20128
|
||||
# Optional Docker production host publish ports:
|
||||
|
||||
# Use Turbopack in local dev. Next 16.2.4 can fail to compile next/font/google
|
||||
# through the custom dev runner without this on Windows.
|
||||
OMNIROUTE_USE_TURBOPACK=1
|
||||
|
||||
# Docker production port mappings (docker-compose.prod.yml only).
|
||||
# These set the HOST-side published ports. Container ports use PORT/API_PORT.
|
||||
# PROD_DASHBOARD_PORT=20130
|
||||
# PROD_API_PORT=20131
|
||||
NODE_ENV=production
|
||||
INSTANCE_NAME=omniroute
|
||||
|
||||
# Recommended security and ops variables
|
||||
# Runtime override used by Electron and wrapped environments.
|
||||
# OMNIROUTE_PORT takes precedence over PORT when running inside wrappers.
|
||||
# Used by: src/lib/runtime/ports.ts — preserves canonical port in Electron.
|
||||
# OMNIROUTE_PORT=20128
|
||||
|
||||
# Environment mode — affects Next.js behavior, logging verbosity, and caching.
|
||||
# Values: production | development | Default: production
|
||||
NODE_ENV=production
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 4. SECURITY & AUTHENTICATION
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Salt for generating unique machine IDs (fingerprint diversification).
|
||||
# Used by: src/lib/auth — combined with hardware identifiers for machine-id hash.
|
||||
# Default: endpoint-proxy-salt | Change per-deployment for isolation.
|
||||
MACHINE_ID_SALT=endpoint-proxy-salt
|
||||
ENABLE_REQUEST_LOGS=false
|
||||
|
||||
# Set true when running behind HTTPS (reverse proxy with TLS termination).
|
||||
# Used by: src/lib/auth — sets the Secure flag on session cookies.
|
||||
# Default: false | MUST be true in any non-localhost deployment.
|
||||
AUTH_COOKIE_SECURE=false
|
||||
|
||||
# Require an API key for all /v1/* proxy endpoints.
|
||||
# Used by: API middleware — rejects unauthenticated requests to the proxy API.
|
||||
# Default: false | Set true for multi-user/public deployments.
|
||||
REQUIRE_API_KEY=false
|
||||
|
||||
# Input Sanitizer (FASE-01 — prompt injection & PII protection)
|
||||
# Allow revealing full API key values in the Dashboard UI.
|
||||
# Used by: Dashboard providers page — controls show/hide of key values.
|
||||
# Default: false | Security risk if enabled on shared instances.
|
||||
ALLOW_API_KEY_REVEAL=false
|
||||
|
||||
# Shared secret for the internal Codex Responses WebSocket bridge.
|
||||
# Used by: src/app/api/internal/codex-responses-ws/route.ts — authenticates
|
||||
# bridge requests between the Electron/browser WS relay and OmniRoute.
|
||||
# ⚠️ REQUIRED for production — if unset, all WS bridge requests are rejected.
|
||||
# Generate: openssl rand -base64 32
|
||||
# OMNIROUTE_WS_BRIDGE_SECRET=
|
||||
|
||||
# Comma-separated API key IDs that skip request logging (GDPR/compliance).
|
||||
# Used by: src/lib/compliance/index.ts — suppresses logs for specific keys.
|
||||
# NO_LOG_API_KEY_IDS=key_abc123,key_def456
|
||||
|
||||
# Maximum request body size in bytes (rejects larger payloads).
|
||||
# Used by: src/shared/middleware/bodySizeGuard.ts — prevents oversized uploads.
|
||||
# Default: 10485760 (10 MB)
|
||||
# MAX_BODY_SIZE_BYTES=10485760
|
||||
|
||||
# CORS configuration — controls which origins can call the API.
|
||||
# Used by: Next.js middleware — sets Access-Control-Allow-Origin header.
|
||||
# Default: * (all origins) | Restrict for production security.
|
||||
# CORS_ORIGIN=https://your-domain.com
|
||||
|
||||
# Allow provider URLs pointing to private/local networks (localhost, 192.168.x.x, etc.).
|
||||
# REQUIRED for self-hosted providers: LM Studio, Ollama, vLLM, Llamafile, Triton, etc.
|
||||
# Used by: src/shared/network/outboundUrlGuard.ts — disables SSRF guard for provider calls.
|
||||
# Default: false (blocked) | Set true to enable local providers.
|
||||
# OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 5. INPUT SANITIZATION & PII PROTECTION (FASE-01)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Multi-layer defense: request-side injection guard + response-side PII sanitizer.
|
||||
|
||||
# ── Request-Side: Prompt Injection Guard ──
|
||||
# Scans incoming messages for prompt injection patterns before routing.
|
||||
# Used by: src/middleware/promptInjectionGuard.ts
|
||||
# INPUT_SANITIZER_ENABLED=true
|
||||
# INPUT_SANITIZER_MODE=warn # warn | block | redact
|
||||
# INPUT_SANITIZER_MODE=warn # warn = log only | block = reject request | redact = strip patterns
|
||||
|
||||
# Legacy alias for INPUT_SANITIZER_MODE (same effect).
|
||||
# INJECTION_GUARD_MODE=warn
|
||||
|
||||
# PII detection in incoming requests (emails, phone numbers, SSNs, etc.).
|
||||
# Used by: src/middleware/promptInjectionGuard.ts — extends injection guard.
|
||||
# PII_REDACTION_ENABLED=false
|
||||
|
||||
# Cloud sync variables
|
||||
# Must point to this running instance so internal sync jobs can call /api/sync/cloud.
|
||||
# Server-side preferred variables:
|
||||
# ── Response-Side: PII Sanitizer ──
|
||||
# Scans LLM responses for leaked PII before returning to the client.
|
||||
# Used by: src/lib/piiSanitizer.ts
|
||||
# PII_RESPONSE_SANITIZATION=false
|
||||
# PII_RESPONSE_SANITIZATION_MODE=redact # redact = mask PII | warn = log only | block = drop response
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 6. TOOL & ROUTING POLICIES
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Tool policy mode — controls which tools LLMs can invoke via function calling.
|
||||
# Used by: src/lib/toolPolicy.ts — enforces allowlist/denylist on tool_choice.
|
||||
# Values: allowlist | denylist | disabled | Default: disabled
|
||||
# TOOL_POLICY_MODE=disabled
|
||||
|
||||
# Payload manipulation rules JSON file.
|
||||
# Used by: open-sse/services/payloadRules.ts — injects/removes upstream payload fields per model/protocol.
|
||||
# Default: ./config/payloadRules.json
|
||||
# OMNIROUTE_PAYLOAD_RULES_PATH=./config/payloadRules.json
|
||||
|
||||
# Reload interval for payloadRules.json mtime checks in milliseconds.
|
||||
# Used by: open-sse/services/payloadRules.ts — keeps file-based rules hot-reloadable without restart.
|
||||
# Default: 5000 | Minimum: 1000
|
||||
# OMNIROUTE_PAYLOAD_RULES_RELOAD_MS=5000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 7. URLS & CLOUD SYNC
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# URLs used for internal sync jobs, OAuth callbacks, and cloud relay.
|
||||
|
||||
# Internal base URL — used by server-side sync jobs to call /api/sync/cloud.
|
||||
# Used by: src/lib/cloudSync.ts, src/lib/initCloudSync.ts
|
||||
# Default: http://localhost:20128
|
||||
BASE_URL=http://localhost:20128
|
||||
|
||||
# Cloud relay URL — premium feature for remote config sync.
|
||||
# Used by: src/lib/cloudSync.ts — pushes/pulls settings from OmniRoute Cloud.
|
||||
CLOUD_URL=
|
||||
# Backward-compatible/public variables:
|
||||
# NEXT_PUBLIC_BASE_URL is also used as the OAuth redirect_uri origin when running behind a
|
||||
# reverse proxy (e.g., nginx). Set this to your public-facing URL so OAuth callbacks work.
|
||||
# Example: NEXT_PUBLIC_BASE_URL=https://omniroute.example.com
|
||||
|
||||
# Timeout for cloud sync HTTP requests in milliseconds.
|
||||
# Used by: src/lib/cloudSync.ts — fetchWithTimeout wrapper.
|
||||
# Default: 12000 (12 seconds)
|
||||
# CLOUD_SYNC_TIMEOUT_MS=12000
|
||||
|
||||
# Public-facing base URL — CRITICAL for reverse proxy / OAuth callback setups.
|
||||
# Used by: OAuth redirect_uri computation, Dashboard UI links, cloud/model sync.
|
||||
# Set to your public URL when behind nginx/Caddy (e.g., https://omniroute.example.com).
|
||||
# Default: http://localhost:20128
|
||||
NEXT_PUBLIC_BASE_URL=http://localhost:20128
|
||||
|
||||
# Browser-facing OmniRoute origin for generated assets in API responses.
|
||||
# Used by: chatgpt-web image generation cache URLs (/v1/chatgpt-web/image/<id>).
|
||||
# Set this when OpenWebUI or another relay reaches OmniRoute by an internal URL
|
||||
# but the user's browser must fetch images from a LAN, tunnel, or public origin.
|
||||
# Do not include /v1; if included accidentally it will be normalized away.
|
||||
# OMNIROUTE_PUBLIC_BASE_URL=http://192.168.0.15:20128
|
||||
|
||||
# Max wait time for an async chatgpt-web image to land via the celsius
|
||||
# WebSocket, in milliseconds. Default 180000 (3 minutes). Increase during
|
||||
# upstream queue-deep windows ("Lots of people are creating images right now").
|
||||
# OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS=180000
|
||||
|
||||
# Total in-memory byte budget for the chatgpt-web image cache (used to serve
|
||||
# /v1/chatgpt-web/image/<id>), in megabytes. Default 256. Lower this if you
|
||||
# run OmniRoute on a memory-constrained host; raise it if image generation
|
||||
# is heavy and clients are racing the 30-minute TTL.
|
||||
# OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB=256
|
||||
|
||||
# Public cloud URL — client-side mirror of CLOUD_URL.
|
||||
NEXT_PUBLIC_CLOUD_URL=
|
||||
|
||||
# Optional outbound proxy variables for upstream provider calls
|
||||
# Lowercase variants are also supported: http_proxy, https_proxy, all_proxy, no_proxy
|
||||
# SOCKS5 proxy support
|
||||
# Legacy alias — fallback for NEXT_PUBLIC_BASE_URL in sync schedulers.
|
||||
# NEXT_PUBLIC_APP_URL=http://localhost:20128
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 8. OUTBOUND PROXY (Upstream Provider Calls)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Route upstream LLM API calls through an HTTP/SOCKS5 proxy.
|
||||
# Useful for corporate egress, geo-routing, or IP masking.
|
||||
|
||||
# Enable SOCKS5 proxy support in both server and client components.
|
||||
# Used by: open-sse/executors — wraps fetch() calls through the proxy agent.
|
||||
ENABLE_SOCKS5_PROXY=true
|
||||
NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
|
||||
# Standard proxy variables (lowercase variants also supported).
|
||||
# HTTP_PROXY=http://127.0.0.1:7890
|
||||
# HTTPS_PROXY=http://127.0.0.1:7890
|
||||
# ALL_PROXY=socks5://127.0.0.1:7890
|
||||
# NO_PROXY=localhost,127.0.0.1
|
||||
|
||||
# TLS fingerprint spoofing (opt-in) — mimics Chrome 124 TLS handshake via wreq-js
|
||||
# Reduces risk of JA3/JA4 fingerprint-based blocking by providers (e.g., Google)
|
||||
# Requires wreq-js to be installed (included in dependencies)
|
||||
# TLS fingerprint spoofing (opt-in) — mimics Chrome 124 TLS handshake via wreq-js.
|
||||
# Reduces risk of JA3/JA4 fingerprint-based blocking by providers (e.g., Google).
|
||||
# Used by: open-sse/executors — replaces Node.js default TLS fingerprint.
|
||||
# ENABLE_TLS_FINGERPRINT=true
|
||||
|
||||
# Optional CLI runtime overrides (Docker/host integration)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 9. CLI TOOL INTEGRATION
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Control how OmniRoute discovers and launches CLI sidecars (Claude, Codex, etc.).
|
||||
# Used by: src/shared/services/cliRuntime.ts
|
||||
|
||||
# CLI discovery mode: auto = search PATH | manual = use explicit paths below.
|
||||
# CLI_MODE=auto
|
||||
# CLI_EXTRA_PATHS=/host-cli/bin
|
||||
|
||||
# Additional PATH entries for finding CLI binaries (colon-separated).
|
||||
# CLI_EXTRA_PATHS=/host-cli/bin:/usr/local/bin
|
||||
|
||||
# Home directory override for reading CLI config files (~/.claude, etc.).
|
||||
# CLI_CONFIG_HOME=/root
|
||||
|
||||
# Allow OmniRoute to write CLI config files (token refresh, etc.).
|
||||
# CLI_ALLOW_CONFIG_WRITES=true
|
||||
|
||||
# Override binary paths for individual CLI tools.
|
||||
# CLI_CLAUDE_BIN=claude
|
||||
# CLI_CODEX_BIN=codex
|
||||
# CLI_DROID_BIN=droid
|
||||
# CLI_OPENCLAW_BIN=openclaw
|
||||
# CLI_CURSOR_BIN=agent
|
||||
# CLI_CLINE_BIN=cline
|
||||
# CLI_ROO_BIN=roo
|
||||
# CLI_CONTINUE_BIN=cn
|
||||
# CLI_QODER_BIN=qoder
|
||||
# CLI_QWEN_BIN=qwen
|
||||
|
||||
# Provider OAuth Credentials (optional — override hardcoded defaults)
|
||||
# These can also be set via data/provider-credentials.json
|
||||
# CLAUDE_OAUTH_CLIENT_ID=
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ⚠️ GOOGLE OAUTH (Antigravity, Gemini CLI) — IMPORTANT FOR REMOTE SERVERS
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# The built-in Google OAuth credentials ONLY work when OmniRoute runs on
|
||||
# localhost (127.0.0.1 / local network). They are registered with
|
||||
# redirect_uri = http://localhost:PORT/callback and Google will reject any
|
||||
# other redirect URI with: redirect_uri_mismatch.
|
||||
#
|
||||
# If you are hosting OmniRoute on a remote server (VPS, Docker, cloud), you
|
||||
# MUST register your own Google Cloud OAuth 2.0 credentials:
|
||||
#
|
||||
# 1. Go to https://console.cloud.google.com/apis/credentials
|
||||
# 2. Create an OAuth 2.0 Client ID (type: "Web application")
|
||||
# 3. Add your server URL as Authorized redirect URI:
|
||||
# https://your-server.com/callback
|
||||
# 4. Copy the Client ID and Client Secret below.
|
||||
#
|
||||
# See the full tutorial in README.md → "OAuth em Servidor Remoto" section.
|
||||
#
|
||||
# Antigravity (Google Gemini Code Assist):
|
||||
# ANTIGRAVITY_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
||||
# ANTIGRAVITY_OAUTH_CLIENT_SECRET=GOCSPX-your-secret
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 10. INTERNAL AGENT & MCP INTEGRATIONS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Used by MCP server, A2A skills, and CLI sidecars to call the running instance.
|
||||
|
||||
# Explicit base URL for MCP/A2A tools to reach OmniRoute (overrides localhost auto-detect).
|
||||
# For browser-visible generated image URLs, prefer OMNIROUTE_PUBLIC_BASE_URL above.
|
||||
# Used by: open-sse/mcp-server/server.ts, src/lib/a2a/
|
||||
# OMNIROUTE_BASE_URL=http://localhost:20128
|
||||
|
||||
# API key for internal tool calls (MCP tools, A2A skills).
|
||||
# OMNIROUTE_API_KEY=
|
||||
|
||||
# API key ID for MCP audit logging.
|
||||
# Used by: open-sse/mcp-server/audit.ts — tags audit events with a key identity.
|
||||
# OMNIROUTE_API_KEY_ID=
|
||||
|
||||
# Legacy alias for OMNIROUTE_API_KEY.
|
||||
# ROUTER_API_KEY=
|
||||
|
||||
# Enforce scope-based access control on MCP tool calls.
|
||||
# Used by: open-sse/mcp-server/server.ts — rejects calls outside allowed scopes.
|
||||
# OMNIROUTE_MCP_ENFORCE_SCOPES=false
|
||||
|
||||
# Comma-separated scopes granted to this MCP connection.
|
||||
# Full list: admin, combos, health, models, routing, budget, metrics, pricing, memory, skills
|
||||
# OMNIROUTE_MCP_SCOPES=admin,combos,health
|
||||
|
||||
# Model catalog sync interval in hours.
|
||||
# Used by: src/shared/services/modelSyncScheduler.ts — periodic model refresh.
|
||||
# Default: 24
|
||||
# MODEL_SYNC_INTERVAL_HOURS=24
|
||||
|
||||
# Provider limits sync interval in minutes (rate limit windows, quotas).
|
||||
# Used by: src/server-init.ts — polls provider health endpoints.
|
||||
# Default: 70
|
||||
PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70
|
||||
|
||||
# Disable all background services (sync, pricing, model refresh).
|
||||
# Used by: src/instrumentation-node.ts, src/lib/initCloudSync.ts
|
||||
# Useful for: CI builds, test environments, or resource-constrained containers.
|
||||
# OMNIROUTE_DISABLE_BACKGROUND_SERVICES=false
|
||||
|
||||
# Flag set by bootstrap script after initial setup is complete.
|
||||
# Used by: src/app/(dashboard)/dashboard/page.tsx — shows setup wizard vs. dashboard.
|
||||
# OMNIROUTE_BOOTSTRAPPED=false
|
||||
|
||||
# Allow request body to override the Antigravity project field.
|
||||
# Used by: open-sse/executors/antigravity.ts — escape hatch for multi-project setups.
|
||||
# OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE=0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 11. OAUTH PROVIDER CREDENTIALS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Built-in default credentials for localhost development.
|
||||
# For remote/VPS deployments, register your own at each provider's developer console.
|
||||
# The bootstrap-env script auto-populates these in .env if missing.
|
||||
# Can also be overridden via data/provider-credentials.json where supported.
|
||||
|
||||
# ── Claude Code (Anthropic) ──
|
||||
CLAUDE_OAUTH_CLIENT_ID=9d1c250a-e61b-44d9-88ed-5944d1962f5e
|
||||
# Custom redirect URI override for Claude OAuth callback.
|
||||
# CLAUDE_CODE_REDIRECT_URI=https://platform.claude.com/oauth/code/callback
|
||||
|
||||
# ── Codex / OpenAI ──
|
||||
CODEX_OAUTH_CLIENT_ID=app_EMoamEEZ73f0CkXaXp7hrann
|
||||
|
||||
# ── Gemini (Google) ──
|
||||
GEMINI_OAUTH_CLIENT_ID=681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com
|
||||
GEMINI_OAUTH_CLIENT_SECRET=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
|
||||
|
||||
# ── Gemini CLI (Google) ──
|
||||
GEMINI_CLI_OAUTH_CLIENT_ID=681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com
|
||||
GEMINI_CLI_OAUTH_CLIENT_SECRET=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
|
||||
|
||||
# ── Qwen (Alibaba) ──
|
||||
QWEN_OAUTH_CLIENT_ID=f0304373b74a44d2b584a3fb70ca9e56
|
||||
|
||||
# ── Kimi Coding (Moonshot) ──
|
||||
KIMI_CODING_OAUTH_CLIENT_ID=17e5f671-d194-4dfb-9706-5516cb48c098
|
||||
|
||||
# ── Antigravity (Google Cloud Code) ──
|
||||
ANTIGRAVITY_OAUTH_CLIENT_ID=1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com
|
||||
ANTIGRAVITY_OAUTH_CLIENT_SECRET=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
|
||||
|
||||
# Gemini CLI (Google AI):
|
||||
# GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
||||
# GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-secret
|
||||
# GEMINI_CLI_OAUTH_CLIENT_ID=
|
||||
GEMINI_OAUTH_CLIENT_SECRET=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
|
||||
GEMINI_CLI_OAUTH_CLIENT_SECRET=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
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ── GitHub Copilot ──
|
||||
GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
|
||||
|
||||
# CLAUDE_OAUTH_CLIENT_ID=
|
||||
# CODEX_OAUTH_CLIENT_ID=
|
||||
# CODEX_OAUTH_CLIENT_SECRET=
|
||||
# QWEN_OAUTH_CLIENT_ID=
|
||||
# IFLOW_OAUTH_CLIENT_ID=
|
||||
IFLOW_OAUTH_CLIENT_SECRET=4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW
|
||||
# ── Qoder ──
|
||||
QODER_OAUTH_CLIENT_SECRET=4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW
|
||||
|
||||
# ── Qoder Browser OAuth (experimental) ──
|
||||
# OmniRoute only enables the browser OAuth flow when ALL 5 variables below are set:
|
||||
# - QODER_OAUTH_AUTHORIZE_URL
|
||||
# - QODER_OAUTH_TOKEN_URL
|
||||
# - QODER_OAUTH_USERINFO_URL
|
||||
# - QODER_OAUTH_CLIENT_ID
|
||||
# - QODER_OAUTH_CLIENT_SECRET
|
||||
#
|
||||
# Redirect URI to register in the Qoder OAuth app:
|
||||
# - Localhost dev with PORT=20128: http://localhost:20128/callback
|
||||
# - LAN access (example): http://192.168.0.15:20128/callback
|
||||
# - Public domain (recommended): https://omniroute.example.com/callback
|
||||
#
|
||||
# Behind reverse proxy / public domain, also set NEXT_PUBLIC_BASE_URL to the same public origin.
|
||||
# If these values are not available, prefer QODER_PERSONAL_ACCESS_TOKEN below.
|
||||
# QODER_OAUTH_AUTHORIZE_URL=
|
||||
# QODER_OAUTH_TOKEN_URL=
|
||||
# QODER_OAUTH_USERINFO_URL=
|
||||
# QODER_OAUTH_CLIENT_ID=
|
||||
# QODER_OAUTH_CLIENT_SECRET=
|
||||
|
||||
# ── Qoder Personal Access Token (direct API key fallback) ──
|
||||
# Used by: open-sse/executors/qoder.ts — bypasses OAuth when set.
|
||||
# QODER_PERSONAL_ACCESS_TOKEN=
|
||||
# QODER_CLI_WORKSPACE=
|
||||
# OMNIROUTE_QODER_WORKSPACE=
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Provider User-Agent Overrides (optional — customize per-provider UA headers)
|
||||
# ⚠️ GOOGLE OAUTH (Antigravity, Gemini CLI) & OTHER PROVIDERS — REMOTE SERVERS
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# The default Client IDs above ONLY work when OmniRoute runs on localhost.
|
||||
# For remote/VPS hosting (including Docker containers on remote servers):
|
||||
# 1. By default, the browser will attempt OAuth redirects back to localhost, which will fail.
|
||||
# 2. Set NEXT_PUBLIC_BASE_URL=https://your-domain.com to fix the redirect URI.
|
||||
# 3. You MUST create your own OAuth App in each provider's developer console (Google Cloud, etc.)
|
||||
# and set the Authorized redirect URI to your domain (e.g., https://your-domain.com/callback).
|
||||
# 4. Replace the _OAUTH_CLIENT_ID and _SECRET values above with your own credentials.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── OAuth sidecar/CLI bridge (internal) ──
|
||||
# Used by: src/lib/oauth/config/index.ts — internal CLI↔OmniRoute auth bridge.
|
||||
# OMNIROUTE_SERVER=http://localhost:20128
|
||||
# OMNIROUTE_TOKEN=
|
||||
# OMNIROUTE_USER_ID=cli
|
||||
# CLI_TOKEN= # legacy alias for OMNIROUTE_TOKEN
|
||||
# CLI_USER_ID= # legacy alias for OMNIROUTE_USER_ID
|
||||
# SERVER_URL= # legacy alias for OMNIROUTE_SERVER
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 12. PROVIDER USER-AGENT OVERRIDES
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Customize the User-Agent header sent to each upstream provider.
|
||||
# Format: {PROVIDER_ID}_USER_AGENT=custom-value
|
||||
# When set, overrides the default User-Agent header sent to that provider.
|
||||
# Useful when providers update versions or block old user-agents.
|
||||
CLAUDE_USER_AGENT=claude-cli/1.0.83 (external, cli)
|
||||
CODEX_USER_AGENT=codex-cli/0.92.0 (Windows 10.0.26100; x64)
|
||||
GITHUB_USER_AGENT=GitHubCopilotChat/0.26.7
|
||||
ANTIGRAVITY_USER_AGENT=antigravity/1.104.0 darwin/arm64
|
||||
KIRO_USER_AGENT=AWS-SDK-JS/3.0.0 kiro-ide/1.0.0
|
||||
IFLOW_USER_AGENT=iFlow-Cli
|
||||
QWEN_USER_AGENT=QwenCode/0.12.3 (linux; x64)
|
||||
CURSOR_USER_AGENT=connect-es/1.6.1
|
||||
GEMINI_CLI_USER_AGENT=google-api-nodejs-client/9.15.1
|
||||
# Used by: open-sse/executors/base.ts — buildHeaders() dynamic lookup.
|
||||
# Update these when providers release new CLI versions to avoid blocks.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# CLI Fingerprint Compatibility (optional — match native CLI binary signatures)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
CLAUDE_USER_AGENT=claude-cli/2.1.121 (external, cli)
|
||||
CODEX_USER_AGENT=codex-cli/0.125.0 (Windows 10.0.26100; x64)
|
||||
GITHUB_USER_AGENT=GitHubCopilotChat/0.45.1
|
||||
ANTIGRAVITY_USER_AGENT=antigravity/1.107.0 darwin/arm64
|
||||
KIRO_USER_AGENT=AWS-SDK-JS/3.0.0 kiro-ide/1.0.0
|
||||
QODER_USER_AGENT=Qoder-Cli
|
||||
QWEN_USER_AGENT=QwenCode/0.15.3 (linux; x64)
|
||||
CURSOR_USER_AGENT=connect-es/1.6.1
|
||||
GEMINI_CLI_USER_AGENT=google-api-nodejs-client/10.3.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 13. CLI FINGERPRINT COMPATIBILITY (Anti-Detection)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# When enabled, OmniRoute reorders HTTP headers and JSON body fields to match
|
||||
# the exact signature of official CLI tools, reducing account flagging risk.
|
||||
# Your proxy IP is preserved — you get both stealth AND IP masking.
|
||||
#
|
||||
# Used by: open-sse/config/cliFingerprints.ts, open-sse/executors/base.ts
|
||||
|
||||
# Enable per-provider:
|
||||
# CLI_COMPAT_CODEX=1
|
||||
# CLI_COMPAT_CLAUDE=1
|
||||
@@ -164,12 +469,18 @@ GEMINI_CLI_USER_AGENT=google-api-nodejs-client/9.15.1
|
||||
# CLI_COMPAT_KILOCODE=1
|
||||
# CLI_COMPAT_CLINE=1
|
||||
# CLI_COMPAT_QWEN=1
|
||||
#
|
||||
|
||||
# Or enable for all providers at once:
|
||||
# CLI_COMPAT_ALL=1
|
||||
|
||||
# API Key Providers (Phase 1 + Phase 4)
|
||||
# Add via Dashboard → Providers → Add API Key, or set here
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 14. API KEY PROVIDERS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# API keys for direct-authentication providers.
|
||||
# Preferred setup: Dashboard → Providers → Add API Key.
|
||||
# Setting here is an alternative for Docker/headless deployments.
|
||||
|
||||
# DEEPSEEK_API_KEY=
|
||||
# GROQ_API_KEY=
|
||||
# XAI_API_KEY=
|
||||
@@ -183,42 +494,280 @@ GEMINI_CLI_USER_AGENT=google-api-nodejs-client/9.15.1
|
||||
|
||||
# Embedding Providers (optional — used by /v1/embeddings)
|
||||
# NEBIUS_API_KEY=
|
||||
# Provider keys above (openai, mistral, together, fireworks, nvidia) also work for embeddings
|
||||
# Provider keys above (OpenAI, Mistral, Together, Fireworks, NVIDIA) also work for embeddings.
|
||||
|
||||
# Timeout settings
|
||||
# FETCH_TIMEOUT_MS=120000
|
||||
# STREAM_IDLE_TIMEOUT_MS=60000
|
||||
# API bridge timeout for /v1 proxy requests (default: 30000)
|
||||
# API_BRIDGE_PROXY_TIMEOUT_MS=120000
|
||||
|
||||
# CORS configuration (default: * allows all origins)
|
||||
# CORS_ORIGINS=*
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 15. TIMEOUT SETTINGS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# All timeout values are in milliseconds.
|
||||
# Used by: src/shared/utils/runtimeTimeouts.ts — centralized timeout resolution.
|
||||
#
|
||||
# Hierarchy: REQUEST_TIMEOUT_MS acts as a global override.
|
||||
# If set, it becomes the default for FETCH_TIMEOUT_MS and STREAM_IDLE_TIMEOUT_MS.
|
||||
# The fine-grained variables below override their respective defaults only when set.
|
||||
|
||||
# Logging
|
||||
# LOG_LEVEL=info
|
||||
# LOG_FORMAT=text
|
||||
LOG_TO_FILE=true
|
||||
# LOG_FILE_PATH=logs/application/app.log
|
||||
# LOG_MAX_FILE_SIZE=50M
|
||||
# LOG_RETENTION_DAYS=7
|
||||
# ── Global shortcut ──
|
||||
# REQUEST_TIMEOUT_MS=600000 # Overrides both fetch and stream idle defaults
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Memory Optimization (Low-RAM configurations)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Node.js heap limit in MB (default: 256 for Docker, system default for npm)
|
||||
# ── Upstream fetch (provider calls) ──
|
||||
# FETCH_TIMEOUT_MS=600000 # Total request timeout (default: 600000 = 10 min)
|
||||
# # Also drives anthropic-compatible-cc-* X-Stainless-Timeout.
|
||||
# FETCH_HEADERS_TIMEOUT_MS=600000 # Time to receive response headers
|
||||
# FETCH_BODY_TIMEOUT_MS=600000 # Time to receive full response body
|
||||
# FETCH_CONNECT_TIMEOUT_MS=30000 # TCP connection establishment (default: 30s)
|
||||
# FETCH_KEEPALIVE_TIMEOUT_MS=4000 # Keep-alive socket idle timeout (default: 4s)
|
||||
|
||||
# ── Stream idle detection ──
|
||||
# STREAM_IDLE_TIMEOUT_MS=600000 # Max silence between SSE chunks (default: 600000)
|
||||
# # Extended-thinking models rarely pause >90s.
|
||||
|
||||
# ── TLS client (wreq-js fingerprint proxy) ──
|
||||
# TLS_CLIENT_TIMEOUT_MS=600000 # Inherits from FETCH_TIMEOUT_MS by default
|
||||
|
||||
# ── API Bridge (/v1 proxy server) ──
|
||||
# API_BRIDGE_PROXY_TIMEOUT_MS=30000 # Proxy hop timeout (default: 30s)
|
||||
# API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS=300000 # Overall server request timeout
|
||||
# API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS=60000 # Time to send response headers
|
||||
# API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS=5000 # Keep-alive idle timeout
|
||||
# API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS=0 # Raw socket timeout (0 = disabled)
|
||||
|
||||
# ── Graceful shutdown ──
|
||||
# Time to wait for in-flight requests before force-exiting on SIGTERM/SIGINT.
|
||||
# Used by: src/lib/gracefulShutdown.ts
|
||||
# Default: 30000 (30 seconds)
|
||||
# SHUTDOWN_TIMEOUT_MS=30000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 16. LOGGING
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Used by: src/lib/logEnv.ts, src/lib/logRotation.ts, src/shared/utils/logger.ts
|
||||
|
||||
# Application log level — controls console and file log verbosity.
|
||||
# Values: debug | info | warn | error | Default: info
|
||||
# APP_LOG_LEVEL=info
|
||||
|
||||
# Log output format.
|
||||
# Values: text | json | Default: text
|
||||
# APP_LOG_FORMAT=text
|
||||
|
||||
# Write logs to file in addition to stdout.
|
||||
# Default: true | Set false to disable file logging.
|
||||
APP_LOG_TO_FILE=true
|
||||
|
||||
# Path to the application log file.
|
||||
# Default: logs/application/app.log (relative to project root / DATA_DIR)
|
||||
# APP_LOG_FILE_PATH=logs/application/app.log
|
||||
|
||||
# Maximum single log file size before rotation.
|
||||
# Accepts: plain bytes or suffixed (50M, 1G, 512K). Default: 50M
|
||||
# APP_LOG_MAX_FILE_SIZE=50M
|
||||
|
||||
# Days to keep rotated application log files before auto-deletion.
|
||||
# Default: 7
|
||||
# APP_LOG_RETENTION_DAYS=7
|
||||
|
||||
# Maximum number of rotated log file backups to keep.
|
||||
# Default: 20
|
||||
# APP_LOG_MAX_FILES=20
|
||||
|
||||
# How often OmniRoute checks whether the active log file has exceeded
|
||||
# APP_LOG_MAX_FILE_SIZE and triggers a rotation. Set lower for very verbose
|
||||
# services to prevent log files from growing large between checks.
|
||||
# Accepts milliseconds. Default: 60000 (1 minute)
|
||||
# APP_LOG_ROTATION_CHECK_INTERVAL_MS=60000
|
||||
|
||||
# Days to keep request/call log entries in the database before auto-cleanup.
|
||||
# Default: 7
|
||||
# CALL_LOG_RETENTION_DAYS=7
|
||||
|
||||
# Maximum call log entries stored in-memory buffer.
|
||||
# Default: 10000
|
||||
# CALL_LOG_MAX_ENTRIES=10000
|
||||
|
||||
# Maximum rows in the call_logs SQLite table before oldest entries are pruned.
|
||||
# Default: 100000
|
||||
# CALL_LOGS_TABLE_MAX_ROWS=100000
|
||||
|
||||
# Whether call log pipeline capture stores stream chunks when enabled in settings.
|
||||
# Only applies when call_log_pipeline_enabled=true.
|
||||
# Default: true
|
||||
# CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS=true
|
||||
|
||||
# Maximum call log artifact size for pipeline captures, in KB.
|
||||
# Only applies when call_log_pipeline_enabled=true.
|
||||
# Default: 512
|
||||
# CALL_LOG_PIPELINE_MAX_SIZE_KB=512
|
||||
|
||||
# Maximum rows in the proxy_logs SQLite table.
|
||||
# Default: 100000
|
||||
# PROXY_LOGS_TABLE_MAX_ROWS=100000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 17. MEMORY OPTIMIZATION (Low-RAM / Docker)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Node.js V8 heap limit in MB.
|
||||
# Used by: Docker entrypoint — sets --max-old-space-size.
|
||||
# Default: 256 (Docker) | system default (npm)
|
||||
# OMNIROUTE_MEMORY_MB=256
|
||||
|
||||
# Prompt cache settings
|
||||
# PROMPT_CACHE_MAX_SIZE=50
|
||||
# PROMPT_CACHE_MAX_BYTES=2097152
|
||||
# PROMPT_CACHE_TTL_MS=300000
|
||||
# ── Prompt cache (system prompt deduplication) ──
|
||||
# Used by: open-sse/services — caches identical system prompts across requests.
|
||||
# PROMPT_CACHE_MAX_SIZE=50 # Max cached entries (default: 50)
|
||||
# PROMPT_CACHE_MAX_BYTES=2097152 # Max total cache size in bytes (default: 2 MB)
|
||||
# PROMPT_CACHE_TTL_MS=300000 # Cache entry TTL (default: 5 minutes)
|
||||
|
||||
# Semantic cache settings (temperature=0 responses)
|
||||
# SEMANTIC_CACHE_MAX_SIZE=100
|
||||
# SEMANTIC_CACHE_MAX_BYTES=4194304
|
||||
# SEMANTIC_CACHE_TTL_MS=1800000
|
||||
# ── Semantic cache (deterministic response dedup, temperature=0) ──
|
||||
# Used by: open-sse/services — caches identical temperature=0 responses.
|
||||
# SEMANTIC_CACHE_MAX_SIZE=100 # Max cached entries (default: 100)
|
||||
# SEMANTIC_CACHE_MAX_BYTES=4194304 # Max total cache size in bytes (default: 4 MB)
|
||||
# SEMANTIC_CACHE_TTL_MS=1800000 # Cache entry TTL (default: 30 minutes)
|
||||
|
||||
# In-memory log buffers
|
||||
# PROXY_LOG_MAX_ENTRIES=200
|
||||
# CALL_LOGS_MAX=200
|
||||
# ── In-memory log buffers ──
|
||||
# Maximum recent stream events kept in memory for the Dashboard live view.
|
||||
# STREAM_HISTORY_MAX=50
|
||||
|
||||
# ── Context length default ──
|
||||
# Global fallback max context length for models without explicit config.
|
||||
# Used by: open-sse/services/contextManager.ts
|
||||
# CONTEXT_LENGTH_DEFAULT=128000
|
||||
|
||||
# ── Usage token buffer ──
|
||||
# Extra token headroom reserved when tracking usage quotas (prevents over-limit).
|
||||
# Used by: open-sse/utils/usageTracking.ts
|
||||
# USAGE_TOKEN_BUFFER=100
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 18. PRICING SYNC
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Automatic model pricing synchronization from external sources.
|
||||
# Used by: src/lib/pricingSync.ts
|
||||
|
||||
# Enable periodic pricing data sync. Default: false (opt-in only).
|
||||
# PRICING_SYNC_ENABLED=false
|
||||
|
||||
# Sync interval in seconds. Default: 86400 (24 hours).
|
||||
# PRICING_SYNC_INTERVAL=86400
|
||||
|
||||
# Comma-separated data sources. Default: litellm
|
||||
# PRICING_SYNC_SOURCES=litellm
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 19. MODEL SYNC (Dev)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Development-time model catalog sync interval in seconds.
|
||||
# Used by: src/lib/modelsDevSync.ts
|
||||
# Default: 86400 (24 hours)
|
||||
# MODELS_DEV_SYNC_INTERVAL=86400
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 20. PROVIDER-SPECIFIC SETTINGS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# ── OpenRouter ──
|
||||
# OpenRouter model catalog cache TTL in ms.
|
||||
# Used by: src/lib/catalog/openrouterCatalog.ts
|
||||
# Default: 86400000 (24 hours)
|
||||
# OPENROUTER_CATALOG_TTL_MS=86400000
|
||||
|
||||
# ── NanoBanana (Image Generation) ──
|
||||
# Polling config for async image generation jobs.
|
||||
# Used by: open-sse/handlers/imageGeneration.ts
|
||||
# NANOBANANA_POLL_TIMEOUT_MS=120000 # Max wait for job completion (default: 120s)
|
||||
# NANOBANANA_POLL_INTERVAL_MS=2500 # Poll frequency (default: 2.5s)
|
||||
|
||||
# ── Cloudflare Workers AI ──
|
||||
# Account ID override for Cloudflare Workers AI executor.
|
||||
# Used by: open-sse/executors/cloudflare-ai.ts
|
||||
# CLOUDFLARE_ACCOUNT_ID=
|
||||
|
||||
# ── Cloudflare Tunnel (cloudflared) ──
|
||||
# Custom path to cloudflared binary for tunnel management.
|
||||
# Used by: src/lib/cloudflaredTunnel.ts
|
||||
# CLOUDFLARED_BIN=/usr/local/bin/cloudflared
|
||||
|
||||
# ── Search cache ──
|
||||
# TTL for search API response caching (Perplexity, Brave, etc.).
|
||||
# Used by: open-sse/services/searchCache.ts
|
||||
# Default: 300000 (5 minutes)
|
||||
# SEARCH_CACHE_TTL_MS=300000
|
||||
|
||||
# ── OpenAI-compatible multi-connection ──
|
||||
# Allow multiple simultaneous connections per OpenAI-compatible provider node.
|
||||
# Used by: src/app/api/providers/route.ts
|
||||
# ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE=false
|
||||
|
||||
# ── CC-compatible provider (experimental) ──
|
||||
# Enable the Claude Code compatible provider endpoint.
|
||||
# This is only for third-party relays that accept Claude Code clients exclusively.
|
||||
# OmniRoute rewrites requests to pass those relays' Claude Code client validation.
|
||||
# If you only want to use Claude Code CLI, or you are not sure what these relays are,
|
||||
# keep this disabled and add a regular Anthropic-compatible provider instead.
|
||||
# Used by: src/shared/utils/featureFlags.ts
|
||||
# ENABLE_CC_COMPATIBLE_PROVIDER=false
|
||||
|
||||
# ── CLIProxyAPI bridge (legacy) ──
|
||||
# Connection settings for external CLIProxyAPI instances.
|
||||
# Used by: open-sse/executors/cliproxyapi.ts
|
||||
# CLIPROXYAPI_HOST=127.0.0.1
|
||||
# CLIPROXYAPI_PORT=5544
|
||||
# CLIPROXYAPI_CONFIG_DIR=~/.cli-proxy-api
|
||||
|
||||
# ── Local hostnames (Docker networking) ──
|
||||
# Comma-separated additional hostnames treated as "local" for provider routing.
|
||||
# Used by: open-sse/config/providerRegistry.ts — allows Docker service names.
|
||||
# LOCAL_HOSTNAMES=omlx,mlx-audio
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 21. PROXY HEALTH
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Fine-tune proxy health checking behavior.
|
||||
# Used by: src/lib/proxyHealth.ts
|
||||
|
||||
# Timeout for fast-fail health checks (ms). Default: 2000
|
||||
# PROXY_FAST_FAIL_TIMEOUT_MS=2000
|
||||
|
||||
# Health check result cache TTL (ms). Default: 30000 (30s)
|
||||
# PROXY_HEALTH_CACHE_TTL_MS=30000
|
||||
|
||||
# Rate limit maximum wait time before failing a request (ms). Default: 120000 (2 min)
|
||||
# Used by: open-sse/services/rateLimitManager.ts
|
||||
# RATE_LIMIT_MAX_WAIT_MS=120000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 22. DEBUGGING
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# These variables enable verbose debugging output. NEVER enable in production.
|
||||
|
||||
# Dump Cursor protobuf decode/encode details to console.
|
||||
# CURSOR_PROTOBUF_DEBUG=1
|
||||
|
||||
# Dump raw Cursor SSE stream data to console.
|
||||
# CURSOR_STREAM_DEBUG=1
|
||||
|
||||
# Log Responses API SSE-to-JSON translation details.
|
||||
# DEBUG_RESPONSES_SSE_TO_JSON=true
|
||||
|
||||
# Enable E2E test mode — relaxes auth and enables test harness hooks.
|
||||
# NEXT_PUBLIC_OMNIROUTE_E2E_MODE=true
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 23. GITHUB INTEGRATION (Issue Reporting)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Allow users to report issues directly from the Dashboard to GitHub.
|
||||
# Used by: src/app/api/v1/issues/report/route.ts
|
||||
|
||||
# GitHub repository in owner/repo format.
|
||||
# GITHUB_ISSUES_REPO=owner/repo
|
||||
|
||||
# GitHub Personal Access Token with issues:write scope.
|
||||
# GITHUB_ISSUES_TOKEN=ghp_xxxx
|
||||
|
||||
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
* @diegosouzapw
|
||||
|
||||
26
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
26
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -119,6 +119,20 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: test-impact
|
||||
attributes:
|
||||
label: Test Impact
|
||||
description: "What automated test coverage should exist for this bug?"
|
||||
options:
|
||||
- Needs a new unit test
|
||||
- Needs a new integration test
|
||||
- Needs a new e2e test
|
||||
- Existing automated test already fails
|
||||
- Unsure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
@@ -143,3 +157,15 @@ body:
|
||||
description: "Any other context about the problem (e.g. proxy config, number of accounts, network setup)."
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: validation-plan
|
||||
attributes:
|
||||
label: Validation Plan
|
||||
description: "Which commands or tests should prove this bug is fixed?"
|
||||
placeholder: |
|
||||
Example:
|
||||
- node --import tsx/esm --test tests/unit/my-file.test.ts
|
||||
- npm run test:coverage
|
||||
validations:
|
||||
required: false
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,4 @@
|
||||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question / Help
|
||||
url: https://github.com/diegosouzapw/OmniRoute/discussions
|
||||
|
||||
26
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
26
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -33,6 +33,19 @@ body:
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: acceptance
|
||||
attributes:
|
||||
label: Acceptance Criteria
|
||||
description: "Describe the concrete behaviors or outcomes that should be validated before this is considered done."
|
||||
placeholder: |
|
||||
Example:
|
||||
- API route returns 200 with valid payload
|
||||
- Unit coverage added for the new branch
|
||||
- Existing integrations remain green
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
@@ -68,3 +81,16 @@ body:
|
||||
description: "Any other context, mockups, or references."
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: test-plan
|
||||
attributes:
|
||||
label: Expected Test Plan
|
||||
description: "Which automated tests or coverage changes should accompany this work?"
|
||||
placeholder: |
|
||||
Example:
|
||||
- Add unit tests for open-sse/services/combo.ts
|
||||
- Extend integration coverage for /api/v1/models
|
||||
- Keep npm run test:coverage at 60%+
|
||||
validations:
|
||||
required: false
|
||||
|
||||
73
.github/ISSUE_TEMPLATE/test_coverage_task.yml
vendored
Normal file
73
.github/ISSUE_TEMPLATE/test_coverage_task.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: Test Coverage Task
|
||||
description: Create a focused coverage-improvement issue tied to concrete files and targets
|
||||
title: "[Coverage] "
|
||||
labels: ["test", "coverage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template for scoped coverage work. Keep it tied to specific files, measurable targets, and the gate that must stay green.
|
||||
|
||||
- type: input
|
||||
id: baseline
|
||||
attributes:
|
||||
label: Current Coverage Baseline
|
||||
description: "Paste the current overall or file-level coverage number that justifies this task."
|
||||
placeholder: "e.g. Lines 79.00%, Branches 72.85%, open-sse/handlers/chatCore.ts = 67.22%"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: scope
|
||||
attributes:
|
||||
label: Target Files Or Modules
|
||||
description: "List the concrete source files or directories that this task will cover."
|
||||
placeholder: |
|
||||
Example:
|
||||
- open-sse/handlers/chatCore.ts
|
||||
- open-sse/services/combo.ts
|
||||
- tests/integration/chat-pipeline.test.ts
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: scenarios
|
||||
attributes:
|
||||
label: Missing Scenarios
|
||||
description: "Describe the specific behaviors, branches, or failure paths that are currently uncovered."
|
||||
placeholder: |
|
||||
Example:
|
||||
- Upstream timeout path
|
||||
- Empty tool_calls normalization
|
||||
- Fallback route after first provider failure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: target
|
||||
attributes:
|
||||
label: Coverage Target
|
||||
description: "Set the expected target for this task."
|
||||
placeholder: "e.g. Raise open-sse/handlers to 80%+ lines and keep global gate >= 60%"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: validation
|
||||
attributes:
|
||||
label: Validation Commands
|
||||
description: "List the commands that must pass before this issue can be closed."
|
||||
placeholder: |
|
||||
Example:
|
||||
- node --import tsx/esm --test tests/unit/my-suite.test.ts
|
||||
- npm run test:coverage
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: notes
|
||||
attributes:
|
||||
label: Notes
|
||||
description: "Optional context, blockers, or dependencies."
|
||||
validations:
|
||||
required: false
|
||||
15
.github/copilot-instructions.md
vendored
Normal file
15
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# OmniRoute PR and Coverage Instructions
|
||||
|
||||
- Treat `npm run test:coverage` as a required gate for PR work.
|
||||
- The repository minimum is `60%` for statements, lines, functions, and branches.
|
||||
- If a PR changes production code in `src/`, `open-sse/`, `electron/`, or `bin/`, it must include automated tests in the same PR.
|
||||
- When reviewing or updating a PR, if the report shows missing tests or coverage below `60%`, do not stop after reporting the problem. Add or update tests in the PR first, rerun the coverage gate, and only then ask for confirmation.
|
||||
- Prefer the smallest test layer that proves the behavior:
|
||||
- unit tests first
|
||||
- integration tests when multiple modules or DB state are involved
|
||||
- e2e only when the behavior is truly UI or workflow-dependent
|
||||
- For bug issues, try to encode the reproduction as an automated test before or alongside the fix.
|
||||
- In the final PR report, include:
|
||||
- the commands you ran
|
||||
- the changed test files
|
||||
- the final coverage result
|
||||
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@@ -29,3 +29,19 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/electron"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
commit-message:
|
||||
prefix: "deps"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
commit-message:
|
||||
prefix: "deps"
|
||||
|
||||
30
.github/pull_request_template.md
vendored
Normal file
30
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
## Summary
|
||||
|
||||
- Describe the user-facing or operational change.
|
||||
|
||||
## Related Issues
|
||||
|
||||
- Closes #
|
||||
- Related to #
|
||||
|
||||
## Validation
|
||||
|
||||
- [ ] `npm run lint`
|
||||
- [ ] `npm run test:unit`
|
||||
- [ ] `npm run test:coverage`
|
||||
- [ ] Coverage is still `>= 60%` for statements, lines, functions, and branches
|
||||
- [ ] SonarQube PR analysis is green or any remaining issues are explicitly documented below
|
||||
|
||||
## Tests Added Or Updated
|
||||
|
||||
- List every changed or added automated test file.
|
||||
- If no production code changed, state that here.
|
||||
|
||||
## Coverage Notes
|
||||
|
||||
- If this PR changes `src/`, `open-sse/`, `electron/`, or `bin/`, explain which tests cover the change.
|
||||
- If coverage moved down in any touched file, explain why and what follow-up task will recover it.
|
||||
|
||||
## Reviewer Notes
|
||||
|
||||
- Call out any risky areas, migrations, feature flags, or manual validation that reviewers should know about.
|
||||
41
.github/workflows/build-fork.yml
vendored
Normal file
41
.github/workflows/build-fork.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Build Fork Image (ghcr.io)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Push to ghcr.io
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: fix/xiaomi-mimo-provider
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: runner-base
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/gi99lin/omniroute:fix-xiaomi-mimo-provider
|
||||
ghcr.io/gi99lin/omniroute:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
441
.github/workflows/ci.yml
vendored
441
.github/workflows/ci.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -13,6 +15,10 @@ concurrency:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CI_NODE_VERSION: "24"
|
||||
CI_NODE_24_VERSION: "24"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
@@ -21,9 +27,11 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run audit:deps
|
||||
- run: npm run lint
|
||||
- run: npm run check:cycles
|
||||
- run: npm run check:route-validation:t06
|
||||
@@ -32,110 +40,374 @@ jobs:
|
||||
- run: npm run typecheck:core
|
||||
- run: npm run typecheck:noimplicit:core
|
||||
|
||||
security:
|
||||
name: Security Audit
|
||||
i18n-matrix:
|
||||
name: Build language matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
langs: ${{ steps.langs.outputs.langs }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- id: langs
|
||||
run: |
|
||||
LANG_DIR="src/i18n/messages"
|
||||
LANGS=$(ls "$LANG_DIR"/*.json | xargs -n1 basename | sed 's/.json$//' | grep -v '^en$' | jq -R . | jq -s . | jq -c .)
|
||||
echo "langs=${LANGS}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
i18n:
|
||||
name: i18n Validation
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
lang: ${{ fromJson(needs.i18n-matrix.outputs.langs) }}
|
||||
needs: i18n-matrix
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Validate ${{ matrix.lang }}
|
||||
run: |
|
||||
python3 scripts/validate_translation.py quick -l '${{ matrix.lang }}' > result.txt
|
||||
|
||||
- name: Upload result
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: i18n-${{ matrix.lang }}
|
||||
path: result.txt
|
||||
|
||||
pr-test-policy:
|
||||
name: PR Test Policy
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Dependency audit
|
||||
run: npm audit --audit-level=high --omit=dev
|
||||
- name: Check for known vulnerabilities
|
||||
run: npx is-my-node-vulnerable
|
||||
continue-on-error: true
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
- name: Fetch base branch
|
||||
run: git fetch --no-tags origin "${GITHUB_BASE_REF}" --depth=1
|
||||
- name: Validate source changes include tests
|
||||
run: node scripts/check-pr-test-policy.mjs --summary-file .artifacts/pr-test-policy.md
|
||||
- name: Publish PR test policy summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f .artifacts/pr-test-policy.md ]; then
|
||||
cat .artifacts/pr-test-policy.md >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run build
|
||||
|
||||
test-unit:
|
||||
name: Unit Tests
|
||||
package-artifact:
|
||||
name: Package Artifact
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
env:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run build:cli
|
||||
- run: npm run check:pack-artifact
|
||||
|
||||
electron-package-smoke:
|
||||
name: Electron Package Smoke
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
needs: build
|
||||
env:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run build
|
||||
- name: Install Electron dependencies
|
||||
working-directory: electron
|
||||
run: npm install --no-audit --no-fund
|
||||
- name: Pack Electron app
|
||||
working-directory: electron
|
||||
run: npm run pack
|
||||
- name: Smoke packaged Electron app
|
||||
env:
|
||||
ELECTRON_SMOKE_TIMEOUT_MS: 60000
|
||||
run: xvfb-run -a npm run electron:smoke:packaged
|
||||
|
||||
test-unit:
|
||||
name: Unit Tests (${{ matrix.shard }}/2)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
shard: [1, 2]
|
||||
env:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run test:unit
|
||||
- run: npm run check:node-runtime
|
||||
- run: node --import tsx/esm --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
||||
|
||||
node-24-compat:
|
||||
name: Node 24 Compatibility (${{ matrix.shard }}/2)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
env:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_24_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run build
|
||||
- run: node --import tsx/esm --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
||||
|
||||
test-coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: build
|
||||
env:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run test:coverage
|
||||
- name: Check coverage threshold
|
||||
- run: npm run check:node-runtime
|
||||
- name: Run coverage gate
|
||||
run: npm run test:coverage
|
||||
- name: Build coverage summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "Coverage report generated. Check output for threshold compliance."
|
||||
mkdir -p coverage
|
||||
if [ -f coverage/coverage-summary.json ]; then
|
||||
node scripts/test-report-summary.mjs \
|
||||
--input coverage/coverage-summary.json \
|
||||
--output coverage/coverage-report.md \
|
||||
--threshold 60
|
||||
else
|
||||
printf '%s\n' \
|
||||
'# Coverage Report' \
|
||||
'' \
|
||||
'Coverage summary JSON was not generated. Inspect the Coverage job logs.' \
|
||||
> coverage/coverage-report.md
|
||||
fi
|
||||
cat coverage/coverage-report.md >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Upload coverage artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: coverage-report
|
||||
path: |
|
||||
coverage/coverage-summary.json
|
||||
coverage/lcov.info
|
||||
coverage/coverage-report.md
|
||||
if-no-files-found: warn
|
||||
|
||||
sonarqube:
|
||||
name: SonarQube
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-coverage
|
||||
if: ${{ always() && needs.test-coverage.result == 'success' }}
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: coverage-report
|
||||
path: .
|
||||
- name: Explain SonarQube skip
|
||||
if: ${{ env.SONAR_TOKEN == '' || env.SONAR_HOST_URL == '' }}
|
||||
run: |
|
||||
echo "SonarQube scan skipped because SONAR_TOKEN or SONAR_HOST_URL is not configured." >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: SonarQube Scan
|
||||
if: ${{ env.SONAR_TOKEN != '' && env.SONAR_HOST_URL != '' }}
|
||||
uses: SonarSource/sonarqube-scan-action@v8
|
||||
env:
|
||||
SONAR_TOKEN: ${{ env.SONAR_TOKEN }}
|
||||
SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }}
|
||||
|
||||
coverage-pr-comment:
|
||||
name: PR Coverage Comment
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
||||
needs:
|
||||
- pr-test-policy
|
||||
- test-coverage
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Download coverage artifact
|
||||
if: ${{ needs.test-coverage.result != 'cancelled' }}
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: coverage-report
|
||||
path: .
|
||||
- name: Prepare PR coverage comment
|
||||
env:
|
||||
COVERAGE_RESULT: ${{ needs.test-coverage.result }}
|
||||
POLICY_RESULT: ${{ needs.pr-test-policy.result }}
|
||||
run: |
|
||||
mkdir -p .artifacts
|
||||
{
|
||||
echo "<!-- omniroute-coverage-report -->"
|
||||
echo "## CI Coverage Report"
|
||||
echo ""
|
||||
echo "- Coverage job: \`${COVERAGE_RESULT}\`"
|
||||
echo "- PR test policy: \`${POLICY_RESULT}\`"
|
||||
echo ""
|
||||
if [ -f coverage/coverage-report.md ]; then
|
||||
cat coverage/coverage-report.md
|
||||
else
|
||||
echo "Coverage artifact was not available for this run."
|
||||
fi
|
||||
if [ "${POLICY_RESULT}" = "failure" ]; then
|
||||
echo ""
|
||||
echo "## PR Test Policy"
|
||||
echo ""
|
||||
echo "This PR changes production code in \`src/\`, \`open-sse/\`, \`electron/\`, or \`bin/\` without accompanying automated tests."
|
||||
fi
|
||||
} > .artifacts/pr-coverage-comment.md
|
||||
- uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const fs = require("fs");
|
||||
const marker = "<!-- omniroute-coverage-report -->";
|
||||
const body = fs.readFileSync(".artifacts/pr-coverage-comment.md", "utf8");
|
||||
const { owner, repo } = context.repo;
|
||||
const issue_number = context.issue.number;
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const existing = comments.find((comment) => comment.body?.includes(marker));
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
test-e2e:
|
||||
name: E2E Tests
|
||||
name: E2E Tests (${{ matrix.shard }}/6)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5, 6]
|
||||
env:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||
OMNIROUTE_PLAYWRIGHT_SKIP_BUILD: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npx playwright install --with-deps chromium
|
||||
- run: npm run build
|
||||
- run: npm run test:e2e
|
||||
- run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/6
|
||||
|
||||
test-integration:
|
||||
name: Integration Tests
|
||||
name: Integration Tests (${{ matrix.shard }}/2)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
env:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
INITIAL_PASSWORD: ci-test-password-for-integration
|
||||
DATA_DIR: /tmp/omniroute-ci
|
||||
DATA_DIR: /tmp/omniroute-ci-${{ matrix.shard }}
|
||||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run test:integration
|
||||
- run: npm run check:node-runtime
|
||||
- run: node --import tsx/esm --test --test-shard=${{ matrix.shard }}/2 tests/integration/*.test.ts
|
||||
|
||||
test-security:
|
||||
name: Security Tests
|
||||
@@ -144,11 +416,116 @@ jobs:
|
||||
env:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run test:security
|
||||
|
||||
ci-summary:
|
||||
name: CI Dashboard
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
needs:
|
||||
- lint
|
||||
- i18n
|
||||
- pr-test-policy
|
||||
|
||||
- build
|
||||
- package-artifact
|
||||
- electron-package-smoke
|
||||
- test-unit
|
||||
- node-24-compat
|
||||
- test-coverage
|
||||
- sonarqube
|
||||
- coverage-pr-comment
|
||||
- test-e2e
|
||||
- test-integration
|
||||
- test-security
|
||||
steps:
|
||||
- name: Download i18n results
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: i18n-*
|
||||
path: results
|
||||
merge-multiple: true
|
||||
|
||||
- name: Generate dashboard
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
status() {
|
||||
case "$1" in
|
||||
success) echo "🟢 PASS" ;;
|
||||
failure) echo "🔴 FAIL" ;;
|
||||
cancelled) echo "⚫ CANCELLED" ;;
|
||||
skipped) echo "⚪ SKIPPED" ;;
|
||||
*) echo "🟡 UNKNOWN" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
echo "# 🚀 CI Dashboard" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "## 🧱 Core Checks" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Job | Status |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "|-----|--------|" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Lint | $(status '${{ needs.lint.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| PR Test Policy | $(status '${{ needs.pr-test-policy.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "| SonarQube | $(status '${{ needs.sonarqube.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "## 🏗️ Build" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Job | Status |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "|-----|--------|" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Build Matrix | $(status '${{ needs.build.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Package Artifact | $(status '${{ needs.package-artifact.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Electron Package Smoke | $(status '${{ needs.electron-package-smoke.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "## 🧪 Tests" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Suite | Status |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Unit | $(status '${{ needs.test-unit.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Coverage | $(status '${{ needs.test-coverage.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| PR Coverage Comment | $(status '${{ needs.coverage-pr-comment.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| E2E | $(status '${{ needs.test-e2e.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Integration | $(status '${{ needs.test-integration.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Security Tests | $(status '${{ needs.test-security.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "## 🌍 Translations" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
total=0
|
||||
langs=0
|
||||
|
||||
if [ -d results ]; then
|
||||
for file in results/*.txt; do
|
||||
[ -f "$file" ] || continue
|
||||
val=$(sed -r 's/\x1B\[[0-9;]*[mK]//g' "$file" | grep "Untranslated:" | awk '{print $2}')
|
||||
val=${val:-0}
|
||||
total=$((total + val))
|
||||
langs=$((langs + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Metric | Value |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "|--------|------|" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Languages checked | $langs |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Total untranslated | $total |" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if [ "$total" -gt 0 ]; then
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "⚠️ **Translations need attention**" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "✅ **All translations complete**" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
3
.github/workflows/deploy-vps.yml
vendored
3
.github/workflows/deploy-vps.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: >-
|
||||
|
||||
6
.github/workflows/docker-publish.yml
vendored
6
.github/workflows/docker-publish.yml
vendored
@@ -1,6 +1,11 @@
|
||||
name: Publish to Docker Hub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "v*"
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
@@ -12,6 +17,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
|
||||
41
.github/workflows/electron-release.yml
vendored
41
.github/workflows/electron-release.yml
vendored
@@ -13,6 +13,8 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
@@ -69,13 +71,11 @@ jobs:
|
||||
deb_ext: .deb
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
- name: Cache node_modules
|
||||
@@ -88,6 +88,18 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
NPM_CONFIG_LEGACY_PEER_DEPS: true
|
||||
|
||||
- name: Sanitize Windows home directory
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
# The default USERPROFILE contains junction points (Application Data)
|
||||
# that cause EPERM errors during Next.js standalone build glob scans.
|
||||
# Create a clean temp profile directory to avoid this.
|
||||
mkdir -p "$RUNNER_TEMP/home"
|
||||
echo "USERPROFILE=$RUNNER_TEMP/home" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Next.js standalone
|
||||
env:
|
||||
@@ -121,6 +133,23 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npm run build:${{ matrix.target }}
|
||||
|
||||
- name: Smoke packaged Electron app
|
||||
if: matrix.platform != 'linux'
|
||||
# Windows CI: requestSingleInstanceLock() fails due to USERPROFILE
|
||||
# sanitization needed for the build step. Smoke is best-effort there.
|
||||
continue-on-error: ${{ matrix.platform == 'windows' }}
|
||||
env:
|
||||
ELECTRON_SMOKE_TIMEOUT_MS: 60000
|
||||
ELECTRON_SMOKE_STREAM_LOGS: "1"
|
||||
run: npm run electron:smoke:packaged
|
||||
|
||||
- name: Smoke packaged Electron app (Linux)
|
||||
if: matrix.platform == 'linux'
|
||||
env:
|
||||
ELECTRON_SMOKE_TIMEOUT_MS: 60000
|
||||
ELECTRON_SMOKE_STREAM_LOGS: "1"
|
||||
run: xvfb-run -a npm run electron:smoke:packaged
|
||||
|
||||
- name: Collect installers
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -184,7 +213,7 @@ jobs:
|
||||
run: ls -la release-assets/
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ needs.validate.outputs.version }}
|
||||
draft: false
|
||||
|
||||
32
.github/workflows/npm-publish.yml
vendored
32
.github/workflows/npm-publish.yml
vendored
@@ -35,6 +35,10 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
env:
|
||||
NPM_PUBLISH_NODE_VERSION: "24"
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
@@ -47,7 +51,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: ${{ env.NPM_PUBLISH_NODE_VERSION }}
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Install dependencies (skip scripts to avoid heavy build)
|
||||
@@ -56,17 +60,18 @@ jobs:
|
||||
- name: Resolve version and dist-tag
|
||||
id: resolve
|
||||
run: |
|
||||
case "${{ github.event_name }}" in
|
||||
workflow_dispatch|workflow_call)
|
||||
VERSION="${{ inputs.version }}"
|
||||
TAG="${{ inputs.tag }}"
|
||||
;;
|
||||
release)
|
||||
VERSION="${{ inputs.version }}"
|
||||
TAG="${{ inputs.tag }}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
if [ "${{ github.event_name }}" = "release" ]; then
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# Strip v prefix if present
|
||||
VERSION="${VERSION#v}"
|
||||
|
||||
# Default dist-tag logic
|
||||
if [ -z "$TAG" ]; then
|
||||
if [[ "$VERSION" == *-* ]]; then
|
||||
@@ -86,7 +91,10 @@ jobs:
|
||||
- name: Build CLI bundle (standalone app)
|
||||
env:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
run: node scripts/prepublish.mjs
|
||||
run: npm run build:cli
|
||||
|
||||
- name: Validate npm package artifact
|
||||
run: npm run check:pack-artifact
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
@@ -110,11 +118,11 @@ jobs:
|
||||
run: |
|
||||
VERSION="${{ steps.resolve.outputs.version }}"
|
||||
TAG="${{ steps.resolve.outputs.tag }}"
|
||||
|
||||
|
||||
echo "Configuring for GitHub Packages..."
|
||||
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" > .npmrc
|
||||
npm pkg set name="@diegosouzapw/omniroute"
|
||||
|
||||
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
npm publish --registry=https://npm.pkg.github.com || echo "⚠️ Version ${VERSION} might already be published on GitHub."
|
||||
else
|
||||
|
||||
75
.gitignore
vendored
75
.gitignore
vendored
@@ -5,6 +5,12 @@
|
||||
omnirouteCloud/
|
||||
omnirouteSite/
|
||||
|
||||
# Root-level underscore-prefixed directories (private/draft — never commit)
|
||||
/_*/
|
||||
|
||||
# Draft features documentation (internal only)
|
||||
docs/new-features/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
/.pnp
|
||||
@@ -14,9 +20,12 @@ node_modules/
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
.data/
|
||||
.next-playwright/
|
||||
|
||||
# testing
|
||||
coverage/
|
||||
coverage**
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
@@ -50,6 +59,7 @@ next-env.d.ts
|
||||
|
||||
# data and logs
|
||||
data/
|
||||
.data/
|
||||
logs/*
|
||||
|
||||
# analysis directories (generated, not tracked)
|
||||
@@ -58,38 +68,6 @@ antigravity-manager-analysis/
|
||||
.sisyphus/
|
||||
.plans/
|
||||
|
||||
# docs (allow specific tracked files)
|
||||
docs/*
|
||||
!docs/ARCHITECTURE.md
|
||||
!docs/CODEBASE_DOCUMENTATION.md
|
||||
!docs/CONTRIBUTING.md
|
||||
!docs/USER_GUIDE.md
|
||||
!docs/API_REFERENCE.md
|
||||
!docs/TROUBLESHOOTING.md
|
||||
!docs/EXECUTION_CONTEXT_PROVIDER_SYNC.md
|
||||
!docs/TASK_NEBIUS_BACKEND_ENABLEMENT.md
|
||||
!docs/frontend-backend-provider-gap-report.md
|
||||
!docs/openapi.yaml
|
||||
!docs/RELEASE_CHECKLIST.md
|
||||
!docs/PLANO-IMPLANTACAO.md
|
||||
!docs/TASKS.md
|
||||
!docs/FASE-*.md
|
||||
!docs/adr/
|
||||
!docs/cli-tools/
|
||||
!docs/planning/
|
||||
!docs/improvement-plans/
|
||||
!docs/api/
|
||||
!docs/VM_DEPLOYMENT_GUIDE.md
|
||||
!docs/FEATURES.md
|
||||
!docs/screenshots/
|
||||
!docs/i18n/
|
||||
!docs/i18n/**
|
||||
!docs/A2A-SERVER.md
|
||||
!docs/AUTO-COMBO.md
|
||||
!docs/MCP-SERVER.md
|
||||
!docs/CLI-TOOLS.md
|
||||
|
||||
|
||||
# open-sse tests
|
||||
open-sse/test/*
|
||||
|
||||
@@ -101,6 +79,7 @@ test-results/
|
||||
playwright-report/
|
||||
blob-report/
|
||||
cloud/
|
||||
.tmp/
|
||||
|
||||
# Security Analysis (standalone project with own git)
|
||||
security-analysis/
|
||||
@@ -109,6 +88,7 @@ security-analysis/
|
||||
clipr/
|
||||
app.log
|
||||
*.tgz
|
||||
.gh-discussions.json
|
||||
|
||||
# Backup directories
|
||||
app.__qa_backup/
|
||||
@@ -119,8 +99,7 @@ app.__qa_backup/
|
||||
# npm publish still includes it via package.json "files" field
|
||||
/app/
|
||||
|
||||
# Electron (subproject dependency lock and build artifacts)
|
||||
electron/package-lock.json
|
||||
# Electron
|
||||
electron/dist-electron/
|
||||
electron/node_modules/
|
||||
icon.iconset/
|
||||
@@ -135,3 +114,31 @@ vscode-extension/
|
||||
|
||||
# Compiled npm-package build artifact (not source, should not be in git)
|
||||
/app
|
||||
|
||||
# IDEA
|
||||
.idea/
|
||||
|
||||
# Local OpenCode agent config
|
||||
.config/
|
||||
|
||||
# Empty/dangling files
|
||||
typescript
|
||||
|
||||
# Gemini Antigravity agent data
|
||||
.gemini/
|
||||
|
||||
# Superpowers plans/specs (internal tooling, not project code)
|
||||
docs/superpowers/
|
||||
|
||||
# GitNexus local index
|
||||
.gitnexus
|
||||
.worktrees
|
||||
bin/omniroute.mjs
|
||||
|
||||
# Consistent with .dockerignore / .npmignore
|
||||
.omc/
|
||||
audit-report.json
|
||||
bun.lock
|
||||
|
||||
# Private environment variables for .http-client
|
||||
http-client.private.env.json
|
||||
|
||||
9
.husky/pre-commit
Normal file → Executable file
9
.husky/pre-commit
Normal file → Executable file
@@ -1,3 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
echo "⚠️ npx not found in PATH — skipping pre-commit hooks"
|
||||
echo " Run 'npm run lint && npm run check:any-budget:t11' manually before pushing."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
npx lint-staged
|
||||
node scripts/check-docs-sync.mjs
|
||||
npm run test:unit
|
||||
npm run check:any-budget:t11
|
||||
|
||||
8
.husky/pre-push
Executable file
8
.husky/pre-push
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env sh
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
echo "⚠️ npm not found in PATH — skipping pre-push hooks"
|
||||
echo " Run 'npm test' manually before pushing."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
npm run test:unit
|
||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
24
|
||||
55
.npmignore
55
.npmignore
@@ -26,14 +26,21 @@ scripts/
|
||||
.github/
|
||||
.husky/
|
||||
.vscode/
|
||||
.agents/
|
||||
.env*
|
||||
app/.env
|
||||
app/.env*
|
||||
eslint.config.mjs
|
||||
prettier.config.mjs
|
||||
postcss.config.mjs
|
||||
next.config.mjs
|
||||
tsconfig.json
|
||||
tsconfig.typecheck-core.json
|
||||
tsconfig.typecheck-noimplicit-core.json
|
||||
playwright.config.ts
|
||||
vitest.config.ts
|
||||
next-env.d.ts
|
||||
llm.txt
|
||||
|
||||
# Docker
|
||||
docker-compose*.yml
|
||||
@@ -41,9 +48,55 @@ Dockerfile
|
||||
.dockerignore
|
||||
|
||||
# Misc
|
||||
restart.sh
|
||||
AGENTS.md
|
||||
bun.lock
|
||||
|
||||
# Build artifacts (pre-built goes inside app/)
|
||||
.next/
|
||||
node_modules/
|
||||
|
||||
# Ignore large binary files and other build directories
|
||||
*.tgz
|
||||
*.AppImage
|
||||
*.deb
|
||||
*.rpm
|
||||
electron/
|
||||
app/electron/
|
||||
app/vscode-extension/
|
||||
|
||||
# Subprojects
|
||||
clipr/
|
||||
omnirouteCloud/
|
||||
omnirouteSite/
|
||||
vscode-extension/
|
||||
|
||||
# Root-level underscore-prefixed directories (private/draft — never publish)
|
||||
/_*/
|
||||
app/_*/
|
||||
app/coverage/
|
||||
app/logs/
|
||||
app/tests/
|
||||
|
||||
# Consistent with .gitignore and .dockerignore
|
||||
.DS_Store
|
||||
.idea/
|
||||
.config/
|
||||
.data/
|
||||
.omnivscodeagent/
|
||||
.omc/
|
||||
*.sqlite-*
|
||||
*.tsbuildinfo
|
||||
security-analysis/
|
||||
.analysis/
|
||||
antigravity-manager-analysis/
|
||||
.sisyphus/
|
||||
.plans/
|
||||
app.__qa_backup/
|
||||
.app-build-backup-*/
|
||||
.gitnexus
|
||||
.worktrees
|
||||
.next-playwright/
|
||||
test-results/
|
||||
playwright-report/
|
||||
blob-report/
|
||||
coverage/
|
||||
|
||||
4
.npmrc
Normal file
4
.npmrc
Normal file
@@ -0,0 +1,4 @@
|
||||
# @lobehub/icons declares UI peers that are not needed by our deep icon imports.
|
||||
# Keeping peer auto-install disabled prevents npm from pulling @lobehub/ui/mermaid
|
||||
# back into the tree and reopening npm audit findings for unused packages.
|
||||
legacy-peer-deps=true
|
||||
31
.vscode/launch.json
vendored
Normal file
31
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Dev Server",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${env:HOME}/.nvm/versions/node/v22.22.2/bin/node",
|
||||
"program": "${workspaceFolder}/scripts/run-next.mjs",
|
||||
"args": ["dev"],
|
||||
"console": "integratedTerminal",
|
||||
"skipFiles": ["<node_internals>/**", "node_modules/**"]
|
||||
},
|
||||
{
|
||||
"name": "Debug Prod Server",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/scripts/run-next.mjs",
|
||||
"args": ["start"],
|
||||
"console": "integratedTerminal",
|
||||
"skipFiles": ["<node_internals>/**", "node_modules/**"]
|
||||
},
|
||||
{
|
||||
"name": "Attach to Running Server",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"skipFiles": ["<node_internals>/**", "node_modules/**"]
|
||||
}
|
||||
]
|
||||
}
|
||||
29
.vscode/settings.json
vendored
29
.vscode/settings.json
vendored
@@ -16,5 +16,34 @@
|
||||
"javascript:S3776": {
|
||||
"level": "off"
|
||||
}
|
||||
},
|
||||
"git.ignoreLimitWarning": true,
|
||||
// "files.exclude": {
|
||||
// "**/_references": true,
|
||||
// "**/_mono_repo": true,
|
||||
// "**/electron": true,
|
||||
// "**/node_modules": true,
|
||||
// "**/.next": true,
|
||||
// "**/coverage": true,
|
||||
// "**/omniroute-*.tgz": true,
|
||||
// "**/_tasks": true
|
||||
// },
|
||||
"files.watcherExclude": {
|
||||
"**/_references/**": true,
|
||||
"**/_mono_repo/**": true,
|
||||
"**/electron/**": true,
|
||||
"**/node_modules/**": true,
|
||||
"**/.next/**": true,
|
||||
"**/coverage/**": true,
|
||||
"**/_tasks/**": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/_references": true,
|
||||
"**/_mono_repo": true,
|
||||
"**/electron": true,
|
||||
"**/node_modules": true,
|
||||
"**/.next": true,
|
||||
"**/coverage": true,
|
||||
"**/_tasks": true
|
||||
}
|
||||
}
|
||||
|
||||
500
AGENTS.md
500
AGENTS.md
@@ -3,162 +3,436 @@
|
||||
## Project
|
||||
|
||||
Unified AI proxy/router — route any LLM through one endpoint. Multi-provider support
|
||||
(OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Fireworks, Cohere, etc.)
|
||||
with **MCP Server** (16 tools for agent control) and **A2A v0.3 Protocol** (Agent-to-Agent orchestration).
|
||||
with **160+ providers** (OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Fireworks,
|
||||
Cohere, NVIDIA, Cerebras, Pollinations, Puter, Cloudflare AI, HuggingFace, DeepInfra,
|
||||
SambaNova, Meta Llama API, Moonshot AI, AI21 Labs, Databricks, Snowflake, and many more)
|
||||
with **MCP Server** (29 tools), **A2A v0.3 Protocol**, and **Electron desktop app**.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Runtime**: Next.js 16 (App Router), Node.js, ES Modules
|
||||
- **Language**: TypeScript 5.9 (`src/`) + JavaScript (`open-sse/`)
|
||||
- **Runtime**: Next.js 16 (App Router), Node.js `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`, ES Modules (`"type": "module"`)
|
||||
- **Language**: TypeScript 5.9 (`src/`) + JavaScript (`open-sse/`, `electron/`)
|
||||
- **Database**: better-sqlite3 (SQLite) — `DATA_DIR` configurable, default `~/.omniroute/`
|
||||
- **Streaming**: SSE via `open-sse` internal package
|
||||
- **Streaming**: SSE via `open-sse` internal workspace package
|
||||
- **Styling**: Tailwind CSS v4
|
||||
- **Docker**: Multi-stage Dockerfile, 3 profiles (base / cli / host)
|
||||
- **i18n**: next-intl with 30 languages (`src/i18n/messages/`)
|
||||
- **i18n**: next-intl with 40+ languages
|
||||
- **Desktop**: Electron (cross-platform: Windows, macOS, Linux)
|
||||
- **Schemas**: Zod v4 for all API / MCP input validation
|
||||
|
||||
---
|
||||
|
||||
## Build, Lint, and Test Commands
|
||||
|
||||
| Command | Description |
|
||||
| ----------------------------------- | --------------------------------- |
|
||||
| `npm run dev` | Start Next.js dev server |
|
||||
| `npm run build` | Production build (isolated) |
|
||||
| `npm run start` | Run production build |
|
||||
| `npm run build:cli` | Build CLI package |
|
||||
| `npm run lint` | ESLint on all source files |
|
||||
| `npm run typecheck:core` | TypeScript core type checking |
|
||||
| `npm run typecheck:noimplicit:core` | Strict checking (no implicit any) |
|
||||
| `npm run check` | Run lint + test |
|
||||
| `npm run check:cycles` | Check for circular dependencies |
|
||||
| `npm run electron:dev` | Run Electron app in dev mode |
|
||||
| `npm run electron:build` | Build Electron app for current OS |
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# All tests (unit + vitest + ecosystem + e2e)
|
||||
npm run test:all
|
||||
|
||||
# Single test file (Node.js native test runner — most tests use this)
|
||||
node --import tsx/esm --test tests/unit/your-file.test.ts
|
||||
node --import tsx/esm --test tests/unit/plan3-p0.test.ts
|
||||
node --import tsx/esm --test tests/unit/fixes-p1.test.ts
|
||||
node --import tsx/esm --test tests/unit/security-fase01.test.ts
|
||||
|
||||
# Integration tests
|
||||
node --import tsx/esm --test tests/integration/*.test.ts
|
||||
|
||||
# Vitest (MCP server, autoCombo)
|
||||
npm run test:vitest
|
||||
|
||||
# E2E with Playwright
|
||||
npm run test:e2e
|
||||
|
||||
# Protocol clients E2E (MCP transports, A2A)
|
||||
npm run test:protocols:e2e
|
||||
|
||||
# Ecosystem compatibility tests
|
||||
npm run test:ecosystem
|
||||
|
||||
# Coverage (see CONTRIBUTING.md)
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
**For authoritative coverage requirements, test execution, and PR gates, see [`CONTRIBUTING.md`](CONTRIBUTING.md#running-tests).**
|
||||
|
||||
---
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### Formatting (Prettier — enforced via lint-staged)
|
||||
|
||||
2 spaces · semicolons required · double quotes (`"`) · 100 char width · es5 trailing commas.
|
||||
Always run `prettier --write` on changed files.
|
||||
|
||||
### TypeScript
|
||||
|
||||
- **Target**: ES2022 · **Module**: `esnext` · **Resolution**: `bundler`
|
||||
- `strict: false` — prefer explicit types, don't rely on inference
|
||||
- Path aliases: `@/*` → `src/`, `@omniroute/open-sse` → `open-sse/`, `@omniroute/open-sse/*` → `open-sse/*`
|
||||
|
||||
### ESLint Rules
|
||||
|
||||
- **Security (error, everywhere)**: `no-eval`, `no-implied-eval`, `no-new-func`
|
||||
- **Relaxed in `open-sse/` and `tests/`**: `@typescript-eslint/no-explicit-any` = warn
|
||||
- React hooks rules and `@next/next/no-assign-module-variable` disabled in `open-sse/` and `tests/`
|
||||
|
||||
### Naming
|
||||
|
||||
| Element | Convention | Example |
|
||||
| ------------------- | -------------------------------- | ------------------------------------ |
|
||||
| Files | camelCase / kebab-case | `chatCore.ts`, `tokenHealthCheck.ts` |
|
||||
| React components | PascalCase | `Dashboard.tsx`, `ProviderCard.tsx` |
|
||||
| Functions/variables | camelCase | `getHealth()`, `switchCombo()` |
|
||||
| Constants | UPPER_SNAKE | `MAX_RETRIES`, `DEFAULT_TIMEOUT` |
|
||||
| Interfaces | PascalCase (`I` prefix optional) | `ProviderConfig` |
|
||||
| Enums | PascalCase (members too) | `LogLevel.Error` |
|
||||
|
||||
### Imports
|
||||
|
||||
- **Order**: external → internal (`@/`, `@omniroute/open-sse`) → relative (`./`, `../`)
|
||||
- **No barrel imports** from `localDb.ts` — import from the specific `db/` module instead
|
||||
|
||||
### Error Handling
|
||||
|
||||
- try/catch with specific error types; always log with context (pino logger)
|
||||
- Never silently swallow errors in SSE streams — use abort signals for cleanup
|
||||
- Return proper HTTP status codes (4xx client, 5xx server)
|
||||
|
||||
### Security
|
||||
|
||||
- **NEVER** commit API keys, secrets, or credentials
|
||||
- Validate all user inputs with Zod schemas
|
||||
- Auth middleware required on all API routes
|
||||
- Never log SQLite encryption keys
|
||||
- Sanitize user content (dompurify for HTML)
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Data Layer (`src/lib/db/`)
|
||||
|
||||
All persistence uses SQLite through domain-specific modules:
|
||||
`core.ts`, `providers.ts`, `models.ts`, `combos.ts`, `apiKeys.ts`, `settings.ts`,
|
||||
`backup.ts`, `proxies.ts`, `prompts.ts`, `webhooks.ts`, `detailedLogs.ts`,
|
||||
`domainState.ts`, `registeredKeys.ts`, `quotaSnapshots.ts`, `modelComboMappings.ts`,
|
||||
`cliToolState.ts`, `encryption.ts`, `readCache.ts`, `secrets.ts`, `stateReset.ts`,
|
||||
`contextHandoffs.ts`, `compression.ts`.
|
||||
Schema migrations live in `db/migrations/` and run via `migrationRunner.ts`.
|
||||
`src/lib/localDb.ts` is a **re-export layer only** — never add logic there.
|
||||
|
||||
| Module | Responsibility |
|
||||
| -------------- | ------------------------------------------ |
|
||||
| `core.ts` | SQLite engine, migrations, WAL, encryption |
|
||||
| `providers.ts` | Provider connections & nodes |
|
||||
| `models.ts` | Model aliases, MITM aliases, custom models |
|
||||
| `combos.ts` | Combo configurations |
|
||||
| `apiKeys.ts` | API key management & validation |
|
||||
| `settings.ts` | Settings, pricing, proxy config |
|
||||
| `backup.ts` | Backup / restore operations |
|
||||
#### DB Internals
|
||||
|
||||
`src/lib/localDb.ts` is a **re-export layer only** — all 27+ consumers import from it,
|
||||
but the real logic lives in `src/lib/db/`.
|
||||
- **`core.ts`**: `getDbInstance()` returns a singleton `better-sqlite3` instance with WAL
|
||||
journaling. `SCHEMA_SQL` defines 15 base tables. Helpers: `rowToCamel`, `encryptConnectionFields`.
|
||||
- **`migrationRunner.ts`**: Applies versioned SQL files from `db/migrations/` inside transactions.
|
||||
Tracks applied migrations in `_omniroute_migrations` table.
|
||||
- **Migrations**: 22 files (`001_initial_schema.sql` → `022_compression_settings.sql`).
|
||||
Each migration is idempotent and runs in a transaction.
|
||||
- **Domain modules** import `getDbInstance()` from `core.ts` for all CRUD operations.
|
||||
Each module owns a specific table/set of tables (e.g., `providers.ts` → `provider_connections`,
|
||||
`combos.ts` → `combos`). Encryption helpers protect sensitive fields at rest.
|
||||
- **`localDb.ts`** re-exports all domain modules — consumers import from here for convenience.
|
||||
|
||||
### API Route Layer (`src/app/api/v1/`)
|
||||
|
||||
Next.js App Router routes — each follows a consistent pattern:
|
||||
|
||||
```
|
||||
Route → CORS preflight → Body validation (Zod) → Optional auth (extractApiKey/isValidApiKey)
|
||||
→ API key policy enforcement (enforceApiKeyPolicy) → Handler delegation (open-sse)
|
||||
```
|
||||
|
||||
| Route | Handler | Notes |
|
||||
| ------------------------------- | ------------------------- | ----------------------------------------- |
|
||||
| `chat/completions/route.ts` | `handleChat()` | + prompt injection guard (clones request) |
|
||||
| `responses/route.ts` | `handleChat()` (unified) | Responses API format |
|
||||
| `embeddings/route.ts` | `handleEmbedding()` | Model listing + creation |
|
||||
| `images/generations/route.ts` | `handleImageGeneration()` | Model listing + creation |
|
||||
| `audio/transcriptions/route.ts` | audio handler | Multipart form data |
|
||||
| `audio/speech/route.ts` | TTS handler | Binary audio response |
|
||||
| `videos/generations/route.ts` | video handler | ComfyUI/SD WebUI |
|
||||
| `music/generations/route.ts` | music handler | ComfyUI workflows |
|
||||
| `moderations/route.ts` | moderation handler | Content safety |
|
||||
| `rerank/route.ts` | rerank handler | Document relevance |
|
||||
| `search/route.ts` | search handler | Web search (5 providers) |
|
||||
|
||||
**No global Next.js middleware file** — interception is route-specific. Auth is optional
|
||||
(controlled by `REQUIRE_API_KEY` env). Prompt injection guard is unique to chat completions.
|
||||
|
||||
### Request Pipeline (`open-sse/`)
|
||||
|
||||
| Handler | Role |
|
||||
| ----------------------- | ------------------------------------------- |
|
||||
| `chatCore.js` | Main chat completions proxy (SSE / non-SSE) |
|
||||
| `responsesHandler.js` | OpenAI Responses API compat |
|
||||
| `responseTranslator.js` | Format translation for Responses API |
|
||||
| `embeddings.js` | Embedding proxy |
|
||||
| `imageGeneration.js` | Image generation proxy |
|
||||
| `sseParser.js` | SSE stream parser |
|
||||
| `usageExtractor.js` | Token usage extraction from responses |
|
||||
The `open-sse/` workspace is the core streaming engine. Full request flow:
|
||||
|
||||
Translation between provider formats: `open-sse/translator/`
|
||||
```
|
||||
Client Request
|
||||
→ src/app/api/v1/.../route.ts (Next.js route)
|
||||
→ open-sse/handlers/chatCore.ts::handleChatCore()
|
||||
→ Semantic/signature cache check
|
||||
→ Rate limit check (rateLimitManager)
|
||||
→ Combo routing? → open-sse/services/combo.ts::handleComboChat()
|
||||
→ resolveComboTargets() → ordered ResolvedComboTarget[]
|
||||
→ For each target: handleSingleModel() (wraps chatCore)
|
||||
→ translateRequest() (open-sse/translator/)
|
||||
→ Convert source format (e.g., OpenAI) → target format (e.g., Claude)
|
||||
→ getExecutor() → provider-specific executor instance
|
||||
→ executor.execute() (BaseExecutor → DefaultExecutor or provider-specific)
|
||||
→ buildUrl() + buildHeaders() + transformRequest()
|
||||
→ fetch() to upstream provider
|
||||
→ Retry logic with exponential backoff
|
||||
→ Response translation back to client format
|
||||
→ If Responses API: responsesTransformer.ts TransformStream
|
||||
→ SSE stream or JSON response to client
|
||||
```
|
||||
|
||||
**Upstream model extra headers** (`compatByProtocol` / custom models): merged in executors after default auth; **same header name replaces** the executor value (e.g. custom `Authorization` overrides Bearer). In `open-sse/handlers/chatCore.ts`, the primary request merges headers for **both** the client model id and `resolveModelAlias(clientModel)` (resolved id wins on key conflicts). **T5 intra-family fallback** recomputes headers using only the fallback model id and `resolveModelAlias(fallback)` so sibling models do not inherit another model’s headers. Forbidden header names live in `src/shared/constants/upstreamHeaders.ts` — keep sanitize (`models.ts`), Zod (`schemas.ts`), and unit tests aligned when editing that list.
|
||||
**Handlers** (`open-sse/handlers/`): `chatCore.ts`, `responsesHandler.ts`, `embeddings.ts`,
|
||||
`imageGeneration.ts`, `videoGeneration.ts`, `musicGeneration.ts`, `audioSpeech.ts`,
|
||||
`audioTranscription.ts`, `moderations.ts`, `rerank.ts`, `search.ts`.
|
||||
|
||||
**Upstream headers**: merged after default auth; same header name replaces executor value.
|
||||
**T5 intra-family fallback** recomputes headers using only the fallback model id.
|
||||
Forbidden header names: `src/shared/constants/upstreamHeaders.ts` — keep sanitize,
|
||||
Zod schemas, and unit tests aligned when editing.
|
||||
|
||||
### Provider Categories
|
||||
|
||||
- **Free** (4): Qoder AI, Qwen Code, Gemini CLI (deprecated), Kiro AI
|
||||
- **OAuth** (8): Claude Code, Antigravity, Codex, GitHub Copilot, Cursor, Kimi Coding, Kilo Code, Cline
|
||||
- **API Key** (120+): OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Perplexity,
|
||||
Together, Fireworks, Cerebras, Cohere, NVIDIA, Nebius, SiliconFlow, Hyperbolic,
|
||||
HuggingFace, OpenRouter, Vertex AI, Cloudflare AI, Scaleway, AI/ML API, Pollinations,
|
||||
Puter, Longcat, Alibaba, Kimi, Minimax, Blackbox, Synthetic, Kilo Gateway,
|
||||
Z.AI, GLM, Deepgram, AssemblyAI, ElevenLabs, Cartesia, PlayHT, Inworld,
|
||||
NanoBanana, SD WebUI, ComfyUI, Ollama Cloud, Perplexity Search, Serper, Brave, Exa,
|
||||
Tavily, OpenCode Zen/Go, Bailian Coding Plan, DeepInfra, Vercel AI Gateway,
|
||||
Lambda AI, SambaNova, nScale, OVHcloud AI, Baseten, PublicAI, Moonshot AI,
|
||||
Meta Llama API, v0 (Vercel), Morph, Featherless AI, FriendliAI, LlamaGate,
|
||||
Galadriel, Weights & Biases Inference, Volcengine, AI21 Labs, Venice.ai,
|
||||
Codestral, Upstage, Maritalk, Xiaomi MiMo, Inference.net, NanoGPT, Predibase,
|
||||
Bytez, Heroku AI, Databricks, Snowflake Cortex, GigaChat (Sber), CrofAI,
|
||||
AgentRouter, ChatGPT Web, Baidu Qianfan, AWS Polly, RunwayML, GitLab Duo,
|
||||
Amazon Q, Empower, Poe, and many more.
|
||||
- **Self-Hosted** (8+): LM Studio, vLLM, Lemonade, Llamafile, Triton, Docker Model Runner, Xinference, Oobabooga
|
||||
- **Custom**: OpenAI-compatible (`openai-compatible-*`) and Anthropic-compatible (`anthropic-compatible-*`) prefixes
|
||||
|
||||
Providers are registered in `src/shared/constants/providers.ts` with Zod validation at module load.
|
||||
|
||||
### Executors (`open-sse/executors/`)
|
||||
|
||||
Provider-specific request executors: `base.ts`, `default.ts`, `cursor.ts`, `codex.ts`,
|
||||
`antigravity.ts`, `github.ts`, `gemini-cli.ts`, `kiro.ts`, `qoder.ts`, `vertex.ts`,
|
||||
`cloudflare-ai.ts`, `opencode.ts`, `pollinations.ts`, `puter.ts`.
|
||||
|
||||
#### Executor Internals
|
||||
|
||||
- **`base.ts`** (`BaseExecutor`): Abstract base with `buildUrl()`, `buildHeaders()`,
|
||||
`transformRequest()`, retry logic (exponential backoff), and `execute()`. Subclasses
|
||||
override URL/header/transform methods for provider-specific behavior.
|
||||
- **`default.ts`** (`DefaultExecutor extends BaseExecutor`): Handles most OpenAI-compatible
|
||||
providers. Reads provider config from `providerRegistry.ts` to resolve base URL, auth
|
||||
header format, and request transformations.
|
||||
- **`getExecutor()`** (`executors/index.ts`): Factory that returns the correct executor
|
||||
instance based on provider ID. Provider-specific executors (Cursor, Codex, Vertex, etc.)
|
||||
override only what differs from the default.
|
||||
|
||||
### Translator (`open-sse/translator/`)
|
||||
|
||||
Translates between API formats (OpenAI-format ↔ Anthropic, Gemini, etc.).
|
||||
Includes request/response translators with helpers for image handling.
|
||||
|
||||
#### Translator Internals
|
||||
|
||||
- **`translator/index.ts`**: Exports `translateRequest()` and format constants. Called by
|
||||
`chatCore.ts` before executor dispatch.
|
||||
- **Flow**: `translateRequest(body, sourceFormat, targetFormat)` → detects source format
|
||||
(OpenAI, Anthropic, Gemini) → applies the matching translator module → returns
|
||||
transformed body ready for the target provider.
|
||||
- **Response translation** runs in reverse after upstream response, converting back to
|
||||
the client's expected format.
|
||||
|
||||
### Transformer (`open-sse/transformer/`)
|
||||
|
||||
`responsesTransformer.ts` — transforms Responses API format to/from Chat Completions format.
|
||||
|
||||
#### Transformer Internals
|
||||
|
||||
- **`createResponsesApiTransformStream()`**: Returns a `TransformStream` that converts
|
||||
Chat Completions SSE chunks (`data: {"choices":[...]}`) into Responses API SSE events
|
||||
(`response.output_item.added`, `response.output_text.delta`, etc.).
|
||||
- Used when the client sends a Responses API request: the request is internally converted
|
||||
to Chat Completions format, dispatched normally, and the response is piped through this
|
||||
transform stream before reaching the client.
|
||||
|
||||
### Services (`open-sse/services/`)
|
||||
|
||||
36+ service modules including: `combo.ts` (routing engine), `usage.ts`, `tokenRefresh.ts`,
|
||||
`rateLimitManager.ts`, `accountFallback.ts`, `sessionManager.ts`, `wildcardRouter.ts`,
|
||||
`autoCombo/`, `intentClassifier.ts`, `taskAwareRouter.ts`, `thinkingBudget.ts`,
|
||||
`contextManager.ts`, `modelDeprecation.ts`, `modelFamilyFallback.ts`,
|
||||
`emergencyFallback.ts`, `workflowFSM.ts`, `backgroundTaskDetector.ts`, `ipFilter.ts`,
|
||||
`signatureCache.ts`, `volumeDetector.ts`, `contextHandoff.ts`, `compression/` (prompt
|
||||
compression pipeline), and more.
|
||||
|
||||
#### Prompt Compression Pipeline (`compression/`)
|
||||
|
||||
Modular prompt compression that runs proactively before the existing reactive context manager.
|
||||
|
||||
- **`strategySelector.ts`**: Selects compression mode based on config, combo overrides, auto-trigger
|
||||
thresholds. Priority: combo override > auto-trigger > default mode > off.
|
||||
- **`lite.ts`**: 5 lite-mode techniques: `collapseWhitespace`, `dedupSystemPrompt`,
|
||||
`compressToolResults`, `removeRedundantContent`, `replaceImageUrls`. Target: 10-15% savings at
|
||||
<1ms latency.
|
||||
- **`stats.ts`**: Per-request compression stats tracking (original tokens, compressed tokens,
|
||||
savings %, techniques used).
|
||||
- **`types.ts`**: `CompressionMode` (off/lite/standard/aggressive/ultra), `CompressionConfig`,
|
||||
`CompressionStats`, `CompressionResult`.
|
||||
- DB settings in `src/lib/db/compression.ts`, API route at `src/app/api/settings/compression/`.
|
||||
- Phase 1 implements lite mode only; standard/aggressive/ultra are placeholders for Phase 2.
|
||||
|
||||
#### Combo Routing Engine (`combo.ts`)
|
||||
|
||||
- **`handleComboChat()`**: Entry point for combo-routed requests. Receives the combo config
|
||||
and iterates through targets in order until one succeeds or all fail.
|
||||
- **`resolveComboTargets()`**: Expands a combo configuration into an ordered array of
|
||||
`ResolvedComboTarget[]`, each specifying provider + model + account + credentials.
|
||||
- **Strategies** (13): priority, weighted, fill-first, round-robin, P2C, random, least-used,
|
||||
cost-optimized, strict-random, auto, lkgp, context-optimized, context-relay.
|
||||
- Each target calls **`handleSingleModel()`** which wraps `handleChatCore()` with
|
||||
per-target error handling and circuit breaker checks.
|
||||
|
||||
### Domain Layer (`src/domain/`)
|
||||
|
||||
Policy engine modules: `policyEngine.ts`, `comboResolver.ts`, `costRules.ts`,
|
||||
`degradation.ts`, `fallbackPolicy.ts`, `lockoutPolicy.ts`, `modelAvailability.ts`,
|
||||
`providerExpiration.ts`, `quotaCache.ts`, `responses.ts`, `configAudit.ts`.
|
||||
|
||||
### MCP Server (`open-sse/mcp-server/`)
|
||||
|
||||
16 tools for AI agent control via **3 transport modes**:
|
||||
29 tools, 3 transports (stdio / SSE / Streamable HTTP). Scoped auth (10 scopes), Zod schemas.
|
||||
|
||||
- **stdio** — Local IDE integration (Claude Desktop, Cursor, VS Code)
|
||||
- **SSE** — Remote Server-Sent Events at `/api/mcp/sse`
|
||||
- **Streamable HTTP** — Modern bidirectional HTTP at `/api/mcp/stream`
|
||||
**Core tools** (20): get_health, list_combos, get_combo_metrics, switch_combo, check_quota,
|
||||
route_request, cost_report, list_models_catalog, web_search, simulate_route, set_budget_guard,
|
||||
set_routing_strategy, set_resilience_profile, test_combo, get_provider_metrics,
|
||||
best_combo_for_task, explain_route, get_session_snapshot, db_health_check, sync_pricing.
|
||||
|
||||
HTTP transports run in-process via `httpTransport.ts` singleton using `WebStandardStreamableHTTPServerTransport`.
|
||||
**Cache tools** (2): cache_stats, cache_flush.
|
||||
|
||||
| Category | Tools |
|
||||
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Essential | `get_health`, `list_combos`, `get_combo_metrics`, `switch_combo`, `check_quota`, `route_request`, `cost_report`, `list_models_catalog` |
|
||||
| Advanced | `simulate_route`, `set_budget_guard`, `set_resilience_profile`, `test_combo`, `get_provider_metrics`, `best_combo_for_task`, `explain_route`, `get_session_snapshot` |
|
||||
**Memory tools** (3): memory_search, memory_add, memory_clear.
|
||||
|
||||
- Scoped authorization (9 scopes), audit logging, Zod schemas
|
||||
- IDE configs for Claude Desktop, Cursor, VS Code Copilot
|
||||
**Skill tools** (4): skills_list, skills_enable, skills_execute, skills_executions.
|
||||
|
||||
#### MCP Internals
|
||||
|
||||
- **Tool registration**: Each tool is an object with `{ name, description, inputSchema: ZodSchema,
|
||||
handler: async (args) => {...} }`. Zod validates inputs before the handler fires.
|
||||
- **`createMcpServer()`** and **`startMcpStdio()`** exported from `mcp-server/index.ts`.
|
||||
`createMcpServer()` wires all tool sets; `startMcpStdio()` launches the stdio transport.
|
||||
- **Transports**: stdio (CLI `omniroute --mcp`), SSE (`/api/mcp/sse`), Streamable HTTP
|
||||
(`/api/mcp/stream`). All share the same tool/scope engine.
|
||||
- **Scopes** (10): Control which tool categories an API key can access. Enforcement happens
|
||||
before handler dispatch.
|
||||
- **Audit**: Every tool invocation is logged to SQLite (`mcp_audit` table) with tool name,
|
||||
args, success/failure, API key attribution, and timestamp.
|
||||
|
||||
### A2A Server (`src/lib/a2a/`)
|
||||
|
||||
Agent-to-Agent v0.3 protocol:
|
||||
JSON-RPC 2.0, SSE streaming, Task Manager with TTL cleanup.
|
||||
Agent Card at `/.well-known/agent.json`.
|
||||
Skills: `quotaManagement.ts`, `smartRouting.ts`.
|
||||
|
||||
- JSON-RPC 2.0: `message/send`, `message/stream`, `tasks/get`, `tasks/cancel`
|
||||
- Agent Card at `/.well-known/agent.json`
|
||||
- Skills: `smart-routing`, `quota-management`
|
||||
- SSE streaming with 15s heartbeat
|
||||
- Task Manager with state machine and TTL-based cleanup
|
||||
#### A2A Internals
|
||||
|
||||
### Auto-Combo Engine (`open-sse/services/autoCombo/`)
|
||||
- **`taskManager.ts`**: State machine lifecycle for tasks: `submitted → working →
|
||||
completed | failed | canceled`. Tasks have TTL and are cleaned up automatically.
|
||||
- **JSON-RPC methods**: `message/send` (sync), `message/stream` (SSE), `tasks/get`,
|
||||
`tasks/cancel`. Dispatched via `POST /a2a`.
|
||||
- **Skills**: Registered in a DB-backed registry. Each skill receives task context
|
||||
(messages, metadata) and returns structured results. `quotaManagement.ts` summarizes
|
||||
quota; `smartRouting.ts` recommends routing decisions.
|
||||
- **Agent Card**: `/.well-known/agent.json` exposes capabilities, skills, and metadata
|
||||
for client auto-discovery.
|
||||
|
||||
Self-healing routing optimization:
|
||||
### ACP Module (`src/lib/acp/`)
|
||||
|
||||
- 6-factor scoring, 4 mode packs, bandit exploration
|
||||
- Progressive cooldown, probe-based re-admission
|
||||
Agent Communication Protocol registry and manager.
|
||||
|
||||
### Dashboard (`src/app/(dashboard)/`)
|
||||
### Memory System (`src/lib/memory/`)
|
||||
|
||||
| Page | Description |
|
||||
| ------------------------ | --------------------------------------------------------------- |
|
||||
| `/dashboard` | Home with quick start, provider overview |
|
||||
| `/dashboard/endpoint` | **Endpoints** (tabbed): Endpoint Proxy, MCP, A2A, API Endpoints |
|
||||
| `/dashboard/providers` | Provider management and connections |
|
||||
| `/dashboard/combos` | Combo configurations with routing strategies |
|
||||
| `/dashboard/logs` | Request, Proxy, Audit, Console logs (tabbed) |
|
||||
| `/dashboard/analytics` | Usage analytics and evaluations |
|
||||
| `/dashboard/costs` | Cost tracking and breakdown |
|
||||
| `/dashboard/health` | Uptime, circuit breakers, latency |
|
||||
| `/dashboard/cli-tools` | CLI tool integrations (Claude, Codex, Antigravity, etc.) |
|
||||
| `/dashboard/media` | Image, Video, Music generation playground |
|
||||
| `/dashboard/settings` | System settings with multiple tabs |
|
||||
| `/dashboard/api-manager` | API key management with model permissions |
|
||||
Extraction, injection, retrieval, summarization, and store modules for persistent
|
||||
conversational memory across sessions.
|
||||
|
||||
### OAuth & Tokens (`src/lib/oauth/`)
|
||||
### Skills System (`src/lib/skills/`)
|
||||
|
||||
18 modules handling OAuth flows, token refresh, and provider credentials.
|
||||
Default credentials are hardcoded in `src/lib/oauth/constants/oauth.ts`,
|
||||
overridable via env vars or `data/provider-credentials.json`.
|
||||
Extensible skill framework: registry, executor, sandbox, built-in skills,
|
||||
custom skill support, interception, and injection.
|
||||
|
||||
### Supporting Systems
|
||||
#### Skills Internals
|
||||
|
||||
| System | Location |
|
||||
| -------------------------- | ------------------------------------------------- |
|
||||
| Usage tracking & analytics | `src/lib/usageDb.ts`, `src/lib/usageAnalytics.ts` |
|
||||
| Token health checks | `src/lib/tokenHealthCheck.ts` |
|
||||
| Cloud sync | `src/lib/cloudSync.ts` |
|
||||
| Proxy logging | `src/lib/proxyLogger.ts` |
|
||||
| Data paths resolution | `src/lib/dataPaths.ts` |
|
||||
- **`registry.ts`**: DB-backed skill registration and discovery. Skills have metadata
|
||||
(name, description, version, enabled status) stored in SQLite.
|
||||
- **`executor.ts`**: Execution engine with configurable timeout and retry logic.
|
||||
Receives skill name + input, looks up the skill, runs it in the sandbox.
|
||||
- **`sandbox.ts`**: Isolation layer for custom (user-provided) skills. Limits resource
|
||||
access and execution time.
|
||||
- **Built-in skills**: Ship with OmniRoute (e.g., quota management, routing). Located
|
||||
alongside the registry.
|
||||
- **Interception/Injection**: Skills can intercept requests in the pipeline (pre/post
|
||||
processing) or inject context into prompts.
|
||||
|
||||
### Compliance (`src/lib/compliance/`)
|
||||
|
||||
Policy index for compliance enforcement.
|
||||
|
||||
### MITM Proxy (`src/mitm/`)
|
||||
|
||||
MITM proxy capability with certificate management, DNS handling, and target routing.
|
||||
|
||||
### Middleware (`src/middleware/`)
|
||||
|
||||
Request middleware including `promptInjectionGuard.ts`.
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
1. Register in `src/shared/constants/providers.ts`
|
||||
2. Add executor in `open-sse/executors/`
|
||||
3. Add translator rules in `open-sse/translator/` (if non-OpenAI format)
|
||||
2. Add executor in `open-sse/executors/` (if custom logic needed)
|
||||
3. Add translator in `open-sse/translator/` (if non-OpenAI format)
|
||||
4. Add OAuth config in `src/lib/oauth/constants/oauth.ts` (if OAuth-based)
|
||||
5. Add models in `open-sse/config/providerRegistry.ts`
|
||||
|
||||
---
|
||||
|
||||
## Subdirectory AGENTS.md Files
|
||||
|
||||
- **[`open-sse/AGENTS.md`](open-sse/AGENTS.md)** — Streaming engine, request pipeline, handlers, and executors
|
||||
- **[`src/lib/db/AGENTS.md`](src/lib/db/AGENTS.md)** — SQLite persistence, domain modules, migrations
|
||||
- **[`open-sse/services/AGENTS.md`](open-sse/services/AGENTS.md)** — Routing engine, combo resolution, strategy selection
|
||||
|
||||
---
|
||||
|
||||
## Review Focus
|
||||
|
||||
### Security
|
||||
|
||||
- No hardcoded API keys or secrets in commits
|
||||
- Auth middleware on all API routes
|
||||
- Input validation on user-facing endpoints (Zod schemas)
|
||||
- SQLite encryption key must not be logged
|
||||
|
||||
### Architecture
|
||||
|
||||
- DB operations go through `src/lib/db/` modules, never raw SQL in routes
|
||||
- Provider requests flow through `open-sse/handlers/`
|
||||
- Translations use `open-sse/translator/` modules
|
||||
- `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module
|
||||
- MCP and A2A pages are embedded as tabs inside `/dashboard/endpoint`, not standalone routes
|
||||
|
||||
### Code Quality
|
||||
|
||||
- Consistent error handling with try/catch
|
||||
- Proper HTTP status codes
|
||||
- No memory leaks in SSE streams (abort signals, cleanup)
|
||||
- Rate limit headers must be parsed correctly
|
||||
- All API inputs validated with Zod schemas
|
||||
|
||||
### Docker
|
||||
|
||||
- Dockerfile has two targets: `runner-base` and `runner-cli`
|
||||
- `docker-compose.yml` — development (3 profiles)
|
||||
- `docker-compose.prod.yml` — isolated production instance (port 20130)
|
||||
- Data persists in named volumes (`omniroute-data` / `omniroute-prod-data`)
|
||||
|
||||
### Review Mode
|
||||
|
||||
- Provide analysis and suggestions only
|
||||
- Focus on bugs, security, performance, and best practices
|
||||
- **DB ops** go through `src/lib/db/` modules, never raw SQL in routes
|
||||
- **Provider requests** flow through `open-sse/handlers/`
|
||||
- **MCP/A2A pages** are tabs inside `/dashboard/endpoint`, not standalone routes
|
||||
- **No memory leaks** in SSE streams (abort signals, cleanup)
|
||||
- **Rate limit headers** must be parsed correctly
|
||||
- All API inputs validated with **Zod schemas**
|
||||
- **Provider constants** validated at module load via Zod (`src/shared/validation/providerSchema.ts`)
|
||||
- **Pricing data** syncs from LiteLLM via `src/lib/pricingSync.ts`
|
||||
- **Memory/Skills** are cross-cutting: affect MCP tools, request pipeline, and A2A skills
|
||||
- **⛔ NEVER close a contributor's PR** after using their code — always merge via GitHub so they get credit. See `.agents/workflows/review-prs.md` for full policy.
|
||||
|
||||
2905
CHANGELOG.md
2905
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
218
CLAUDE.md
Normal file
218
CLAUDE.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
npm install # Install deps (auto-generates .env from .env.example)
|
||||
npm run dev # Dev server at http://localhost:20128
|
||||
npm run build # Production build (Next.js 16 standalone)
|
||||
npm run lint # ESLint (0 errors expected; warnings are pre-existing)
|
||||
npm run typecheck:core # TypeScript check (should be clean)
|
||||
npm run typecheck:noimplicit:core # Strict check (no implicit any)
|
||||
npm run test:coverage # Unit tests + coverage gate (60% min)
|
||||
npm run check # lint + test combined
|
||||
npm run check:cycles # Detect circular dependencies
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Single test file (Node.js native test runner — most tests)
|
||||
node --import tsx/esm --test tests/unit/your-file.test.ts
|
||||
|
||||
# Vitest (MCP server, autoCombo, cache)
|
||||
npm run test:vitest
|
||||
|
||||
# All suites
|
||||
npm run test:all
|
||||
```
|
||||
|
||||
For full test matrix, see `CONTRIBUTING.md` → "Running Tests". For deep architecture, see `AGENTS.md`.
|
||||
|
||||
---
|
||||
|
||||
## Project at a Glance
|
||||
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 160+ LLM providers, auto-fallback.
|
||||
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------ |
|
||||
| API Routes | `src/app/api/v1/` | Next.js App Router — entry points |
|
||||
| Handlers | `open-sse/handlers/` | Request processing (chat, embeddings, etc) |
|
||||
| Executors | `open-sse/executors/` | Provider-specific HTTP dispatch |
|
||||
| Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) |
|
||||
| Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions |
|
||||
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (22 files) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 29 tools, 3 transports, 10 scopes |
|
||||
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
|
||||
| Skills | `src/lib/skills/` | Extensible skill framework |
|
||||
| Memory | `src/lib/memory/` | Persistent conversational memory |
|
||||
|
||||
Monorepo: `src/` (Next.js 16 app), `open-sse/` (streaming engine workspace), `electron/` (desktop app), `tests/`, `bin/` (CLI entry point).
|
||||
|
||||
---
|
||||
|
||||
## Request Pipeline
|
||||
|
||||
```
|
||||
Client → /v1/chat/completions (Next.js route)
|
||||
→ CORS → Zod validation → auth? → policy check → prompt injection guard
|
||||
→ handleChatCore() [open-sse/handlers/chatCore.ts]
|
||||
→ cache check → rate limit → combo routing?
|
||||
→ resolveComboTargets() → handleSingleModel() per target
|
||||
→ translateRequest() → getExecutor() → executor.execute()
|
||||
→ fetch() upstream → retry w/ backoff
|
||||
→ response translation → SSE stream or JSON
|
||||
→ If Responses API: responsesTransformer.ts TransformStream
|
||||
```
|
||||
|
||||
API routes follow a consistent pattern: `Route → CORS preflight → Zod body validation → Optional auth (extractApiKey/isValidApiKey) → API key policy enforcement → Handler delegation (open-sse)`. No global Next.js middleware — interception is route-specific.
|
||||
|
||||
**Combo routing** (`open-sse/services/combo.ts`): 13 strategies (priority, weighted, fill-first, round-robin, P2C, random, least-used, cost-optimized, strict-random, auto, lkgp, context-optimized, context-relay). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks.
|
||||
|
||||
---
|
||||
|
||||
## Key Conventions
|
||||
|
||||
### Code Style
|
||||
|
||||
- **2 spaces**, semicolons, double quotes, 100 char width, es5 trailing commas (enforced by lint-staged via Prettier)
|
||||
- **Imports**: external → internal (`@/`, `@omniroute/open-sse`) → relative
|
||||
- **Naming**: files=camelCase/kebab, components=PascalCase, constants=UPPER_SNAKE
|
||||
- **ESLint**: `no-eval`, `no-implied-eval`, `no-new-func` = error everywhere; `no-explicit-any` = warn in `open-sse/` and `tests/`
|
||||
- **TypeScript**: `strict: false`, target ES2022, module esnext, resolution bundler. Prefer explicit types.
|
||||
|
||||
### Database
|
||||
|
||||
- **Always** go through `src/lib/db/` domain modules — **never** write raw SQL in routes or handlers
|
||||
- **Never** add logic to `src/lib/localDb.ts` (re-export layer only)
|
||||
- **Never** barrel-import from `localDb.ts` — import specific `db/` modules instead
|
||||
- DB singleton: `getDbInstance()` from `src/lib/db/core.ts` (WAL journaling)
|
||||
- Migrations: `src/lib/db/migrations/` — versioned SQL files, idempotent, run in transactions
|
||||
|
||||
### Error Handling
|
||||
|
||||
- try/catch with specific error types, log with pino context
|
||||
- Never swallow errors in SSE streams — use abort signals for cleanup
|
||||
- Return proper HTTP status codes (4xx/5xx)
|
||||
|
||||
### Security
|
||||
|
||||
- **Never** use `eval()`, `new Function()`, or implied eval
|
||||
- Validate all inputs with Zod schemas
|
||||
- Encrypt credentials at rest (AES-256-GCM)
|
||||
- Upstream header denylist: `src/shared/constants/upstreamHeaders.ts` — keep sanitize, Zod schemas, and unit tests aligned when editing
|
||||
|
||||
---
|
||||
|
||||
## Common Modification Scenarios
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
1. Register in `src/shared/constants/providers.ts` (Zod-validated at load)
|
||||
2. Add executor in `open-sse/executors/` if custom logic needed (extend `BaseExecutor`)
|
||||
3. Add translator in `open-sse/translator/` if non-OpenAI format
|
||||
4. Add OAuth config in `src/lib/oauth/constants/oauth.ts` if OAuth-based
|
||||
5. Register models in `open-sse/config/providerRegistry.ts`
|
||||
6. Write tests in `tests/unit/`
|
||||
|
||||
### Adding a New API Route
|
||||
|
||||
1. Create directory under `src/app/api/v1/your-route/`
|
||||
2. Create `route.ts` with `GET`/`POST` handlers
|
||||
3. Follow pattern: CORS → Zod body validation → optional auth → handler delegation
|
||||
4. Handler goes in `open-sse/handlers/` (import from there, not inline)
|
||||
5. Add tests
|
||||
|
||||
### Adding a New DB Module
|
||||
|
||||
1. Create `src/lib/db/yourModule.ts` — import `getDbInstance` from `./core.ts`
|
||||
2. Export CRUD functions for your domain table(s)
|
||||
3. Add migration in `src/lib/db/migrations/` if new tables needed
|
||||
4. Re-export from `src/lib/localDb.ts` (add to the re-export list only)
|
||||
5. Write tests
|
||||
|
||||
### Adding a New MCP Tool
|
||||
|
||||
1. Add tool definition in `open-sse/mcp-server/tools/` with Zod input schema + async handler
|
||||
2. Register in tool set (wired by `createMcpServer()`)
|
||||
3. Assign to appropriate scope(s)
|
||||
4. Write tests (tool invocation logged to `mcp_audit` table)
|
||||
|
||||
### Adding a New A2A Skill
|
||||
|
||||
1. Create skill in `src/lib/a2a/skills/`
|
||||
2. Skill receives task context (messages, metadata) → returns structured result
|
||||
3. Register in the DB-backed skill registry
|
||||
4. Write tests
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
| What | Command |
|
||||
| ----------------------- | ------------------------------------------------------ |
|
||||
| Unit tests | `npm run test:unit` |
|
||||
| Single file | `node --import tsx/esm --test tests/unit/file.test.ts` |
|
||||
| Vitest (MCP, autoCombo) | `npm run test:vitest` |
|
||||
| E2E (Playwright) | `npm run test:e2e` |
|
||||
| Protocol E2E (MCP+A2A) | `npm run test:protocols:e2e` |
|
||||
| Ecosystem | `npm run test:ecosystem` |
|
||||
| Coverage gate | `npm run test:coverage` (60% min all metrics) |
|
||||
| Coverage report | `npm run coverage:report` |
|
||||
|
||||
**PR rule**: If you change production code in `src/`, `open-sse/`, `electron/`, or `bin/`, you must include or update tests in the same PR.
|
||||
|
||||
**Test layer preference**: unit first → integration (multi-module or DB state) → e2e (UI/workflow only). Encode bug reproductions as automated tests before or alongside the fix.
|
||||
|
||||
**Copilot coverage policy**: When a PR changes production code and coverage is below 60%, do not just report — add or update tests, rerun the coverage gate, then ask for confirmation. Include commands run, changed test files, and final coverage result in the PR report.
|
||||
|
||||
---
|
||||
|
||||
## Git Workflow
|
||||
|
||||
```bash
|
||||
# Never commit directly to main
|
||||
git checkout -b feat/your-feature
|
||||
git commit -m "feat: describe your change"
|
||||
git push -u origin feat/your-feature
|
||||
```
|
||||
|
||||
**Branch prefixes**: `feat/`, `fix/`, `refactor/`, `docs/`, `test/`, `chore/`
|
||||
|
||||
**Commit format** (Conventional Commits): `feat(db): add circuit breaker` — scopes: `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills`
|
||||
|
||||
**Husky hooks**:
|
||||
|
||||
- **pre-commit**: lint-staged + `check-docs-sync` + `check:any-budget:t11`
|
||||
- **pre-push**: `npm run test:unit`
|
||||
|
||||
---
|
||||
|
||||
## Environment
|
||||
|
||||
- **Runtime**: Node.js ≥20.20.2 <21 || ≥22.22.2 <23 || ≥24 <25, ES Modules
|
||||
- **TypeScript**: 5.9+, target ES2022, module esnext, resolution bundler
|
||||
- **Path aliases**: `@/*` → `src/`, `@omniroute/open-sse` → `open-sse/`, `@omniroute/open-sse/*` → `open-sse/*`
|
||||
- **Default port**: 20128 (API + dashboard on same port)
|
||||
- **Data directory**: `DATA_DIR` env var, defaults to `~/.omniroute/`
|
||||
- **Key env vars**: `PORT`, `JWT_SECRET`, `API_KEY_SECRET`, `INITIAL_PASSWORD`, `REQUIRE_API_KEY`, `APP_LOG_LEVEL`
|
||||
- Setup: `cp .env.example .env` then generate `JWT_SECRET` (`openssl rand -base64 48`) and `API_KEY_SECRET` (`openssl rand -hex 32`)
|
||||
|
||||
---
|
||||
|
||||
## Hard Rules
|
||||
|
||||
1. Never commit secrets or credentials
|
||||
2. Never add logic to `localDb.ts`
|
||||
3. Never use `eval()` / `new Function()` / implied eval
|
||||
4. Never commit directly to `main`
|
||||
5. Never write raw SQL in routes — use `src/lib/db/` modules
|
||||
6. Never silently swallow errors in SSE streams
|
||||
7. Always validate inputs with Zod schemas
|
||||
8. Always include tests when changing production code
|
||||
9. Coverage must stay ≥60% (statements, lines, functions, branches)
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
207
CONTRIBUTING.md
207
CONTRIBUTING.md
@@ -8,7 +8,7 @@ Thank you for your interest in contributing! This guide covers everything you ne
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js** 20+ (recommended: 22 LTS)
|
||||
- **Node.js** `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25` (recommended: 24 LTS)
|
||||
- **npm** 10+
|
||||
- **Git**
|
||||
|
||||
@@ -33,13 +33,24 @@ echo "API_KEY_SECRET=$(openssl rand -hex 32)" >> .env
|
||||
|
||||
Key variables for development:
|
||||
|
||||
| Variable | Development Default | Description |
|
||||
| ---------------------- | ----------------------- | ------------------------- |
|
||||
| `PORT` | `3000` | Server port |
|
||||
| `NEXT_PUBLIC_BASE_URL` | `http://localhost:3000` | Base URL for frontend |
|
||||
| `JWT_SECRET` | (generate above) | JWT signing secret |
|
||||
| `INITIAL_PASSWORD` | `123456` | First login password |
|
||||
| `ENABLE_REQUEST_LOGS` | `false` | Enable debug request logs |
|
||||
| Variable | Development Default | Description |
|
||||
| ---------------------- | ------------------------ | --------------------- |
|
||||
| `PORT` | `20128` | Server port |
|
||||
| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | Base URL for frontend |
|
||||
| `JWT_SECRET` | (generate above) | JWT signing secret |
|
||||
| `INITIAL_PASSWORD` | `CHANGEME` | First login password |
|
||||
| `APP_LOG_LEVEL` | `info` | Log verbosity level |
|
||||
|
||||
### Dashboard Settings
|
||||
|
||||
The dashboard provides UI toggles for features that can also be configured via environment variables:
|
||||
|
||||
| Setting Location | Toggle | Description |
|
||||
| ------------------- | ------------------ | ------------------------------ |
|
||||
| Settings → Advanced | Debug Mode | Enable debug request logs (UI) |
|
||||
| Settings → General | Sidebar Visibility | Show/hide sidebar sections |
|
||||
|
||||
These settings are stored in the database and persist across restarts, overriding env var defaults when set.
|
||||
|
||||
### Running Locally
|
||||
|
||||
@@ -57,8 +68,8 @@ PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev
|
||||
|
||||
Default URLs:
|
||||
|
||||
- **Dashboard**: `http://localhost:3000/dashboard`
|
||||
- **API**: `http://localhost:3000/v1`
|
||||
- **Dashboard**: `http://localhost:20128/dashboard`
|
||||
- **API**: `http://localhost:20128/v1`
|
||||
|
||||
---
|
||||
|
||||
@@ -97,28 +108,35 @@ test: add observability unit tests
|
||||
refactor(db): consolidate rate limit tables
|
||||
```
|
||||
|
||||
Scopes: `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`.
|
||||
Scopes: `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills`.
|
||||
|
||||
---
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# All unit tests
|
||||
npm test
|
||||
npm run test:unit
|
||||
# All tests (unit + vitest + ecosystem + e2e)
|
||||
npm run test:all
|
||||
|
||||
# Specific test suites
|
||||
npm run test:security # Security tests
|
||||
npm run test:fixes # Fix verification tests
|
||||
# Single test file (Node.js native test runner — most tests use this)
|
||||
node --import tsx/esm --test tests/unit/your-file.test.ts
|
||||
|
||||
# With coverage
|
||||
npm run test:coverage
|
||||
npm run coverage:report
|
||||
# Vitest (MCP server, autoCombo, cache)
|
||||
npm run test:vitest
|
||||
|
||||
# E2E tests (requires Playwright)
|
||||
npm run test:e2e
|
||||
|
||||
# Protocol clients E2E (MCP transports, A2A)
|
||||
npm run test:protocols:e2e
|
||||
|
||||
# Ecosystem compatibility tests
|
||||
npm run test:ecosystem
|
||||
|
||||
# Coverage (60% min statements/lines/functions/branches)
|
||||
npm run test:coverage
|
||||
npm run coverage:report
|
||||
|
||||
# Lint + format check
|
||||
npm run lint
|
||||
npm run check
|
||||
@@ -127,27 +145,43 @@ npm run check
|
||||
Coverage notes:
|
||||
|
||||
- `npm run test:coverage` measures source coverage for the main unit test suite, excludes `tests/**`, and includes `open-sse/**`
|
||||
- Pull requests must keep the overall coverage gate at **60% or higher** for statements, lines, functions, and branches
|
||||
- If a PR changes production code in `src/`, `open-sse/`, `electron/`, or `bin/`, it must add or update automated tests in the same PR
|
||||
- `npm run coverage:report` prints the detailed file-by-file report from the latest coverage run
|
||||
- `npm run test:coverage:legacy` preserves the older metric for historical comparison
|
||||
- See `docs/COVERAGE_PLAN.md` for the phased coverage improvement roadmap
|
||||
|
||||
Current test status: **968+ unit tests** covering:
|
||||
### Pull Request Requirements
|
||||
|
||||
Before opening or merging a PR:
|
||||
|
||||
- Run `npm run test:unit`
|
||||
- Run `npm run test:coverage`
|
||||
- Ensure the coverage gate stays at **60%+** for all metrics
|
||||
- Include the changed or added test files in the PR description when production code changed
|
||||
- Check the SonarQube result on the PR when the project secrets are configured in CI
|
||||
|
||||
Current test status: **122 unit test files** covering:
|
||||
|
||||
- Provider translators and format conversion
|
||||
- Rate limiting, circuit breaker, and resilience
|
||||
- Semantic cache, idempotency, progress tracking
|
||||
- Database operations and schema
|
||||
- Database operations and schema (21 DB modules)
|
||||
- OAuth flows and authentication
|
||||
- API endpoint validation
|
||||
- API endpoint validation (Zod v4)
|
||||
- MCP server tools and scope enforcement
|
||||
- Memory and Skills systems
|
||||
|
||||
---
|
||||
|
||||
## Code Style
|
||||
|
||||
- **ESLint** — Run `npm run lint` before committing
|
||||
- **Prettier** — Auto-formatted via `lint-staged` on commit
|
||||
- **TypeScript** — All `src/` code uses `.ts`/`.tsx`; document with TSDoc (`@param`, `@returns`, `@throws`)
|
||||
- **Prettier** — Auto-formatted via `lint-staged` on commit (2 spaces, semicolons, double quotes, 100 char width, es5 trailing commas)
|
||||
- **TypeScript** — All `src/` code uses `.ts`/`.tsx`; `open-sse/` uses `.ts`/`.js`; document with TSDoc (`@param`, `@returns`, `@throws`)
|
||||
- **No `eval()`** — ESLint enforces `no-eval`, `no-implied-eval`, `no-new-func`
|
||||
- **Zod validation** — Use Zod schemas for API input validation
|
||||
- **Zod validation** — Use Zod v4 schemas for all API input validation
|
||||
- **Naming**: Files = camelCase/kebab-case, components = PascalCase, constants = UPPER_SNAKE
|
||||
|
||||
---
|
||||
|
||||
@@ -155,40 +189,60 @@ Current test status: **968+ unit tests** covering:
|
||||
|
||||
```
|
||||
src/ # TypeScript (.ts / .tsx)
|
||||
├── app/ # Next.js App Router
|
||||
│ ├── (dashboard)/ # Dashboard pages (.tsx)
|
||||
│ ├── api/ # API routes (.ts)
|
||||
├── app/ # Next.js 16 App Router
|
||||
│ ├── (dashboard)/ # Dashboard pages (23 sections)
|
||||
│ ├── api/ # API routes (51 directories)
|
||||
│ └── login/ # Auth pages (.tsx)
|
||||
├── domain/ # Domain types and response helpers (.ts)
|
||||
├── domain/ # Policy engine (policyEngine, comboResolver, costRules, etc.)
|
||||
├── lib/ # Core business logic (.ts)
|
||||
│ ├── db/ # SQLite database layer
|
||||
│ ├── oauth/ # OAuth services per provider
|
||||
│ ├── cacheLayer.ts # LRU cache
|
||||
│ ├── semanticCache.ts # Semantic response cache
|
||||
│ ├── idempotencyLayer.ts # Request deduplication
|
||||
│ └── localDb.ts # Settings facade (LowDB for config, SQLite for domain data)
|
||||
│ ├── a2a/ # Agent-to-Agent v0.3 protocol server
|
||||
│ ├── acp/ # Agent Communication Protocol registry
|
||||
│ ├── compliance/ # Compliance policy engine
|
||||
│ ├── db/ # SQLite database layer (21 modules + 16 migrations)
|
||||
│ ├── memory/ # Persistent conversational memory
|
||||
│ ├── oauth/ # OAuth providers, services, and utilities
|
||||
│ ├── skills/ # Extensible skill framework
|
||||
│ ├── usage/ # Usage tracking and cost calculation
|
||||
│ └── localDb.ts # Re-export layer only — never add logic here
|
||||
├── middleware/ # Request middleware (promptInjectionGuard)
|
||||
├── mitm/ # MITM proxy (cert, DNS, target routing)
|
||||
├── shared/
|
||||
│ ├── components/ # React components (.tsx)
|
||||
│ ├── middleware/ # Correlation IDs, etc.
|
||||
│ ├── utils/ # Circuit breaker, sanitizer, etc.
|
||||
│ └── validation/ # Zod schemas
|
||||
└── sse/ # SSE chat handlers (.ts)
|
||||
│ ├── constants/ # Provider definitions (60+), MCP scopes, routing strategies
|
||||
│ ├── utils/ # Circuit breaker, sanitizer, auth helpers
|
||||
│ └── validation/ # Zod v4 schemas
|
||||
└── sse/ # SSE proxy pipeline
|
||||
|
||||
open-sse/ # @omniroute/open-sse workspace (JavaScript)
|
||||
├── handlers/ # chatCore.js — main request handler
|
||||
├── services/ # Rate limit, fallback
|
||||
├── translators/ # Format converters (OpenAI ↔ Claude ↔ Gemini)
|
||||
└── utils/ # Progress tracker, stream helpers
|
||||
open-sse/ # @omniroute/open-sse workspace
|
||||
├── executors/ # 14 provider-specific request executors
|
||||
├── handlers/ # 11 request handlers (chat, responses, embeddings, images, etc.)
|
||||
├── mcp-server/ # MCP server (25 tools, 3 transports, 10 scopes)
|
||||
├── services/ # 36+ services (combo, autoCombo, rateLimitManager, etc.)
|
||||
├── translator/ # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama)
|
||||
├── transformer/ # Responses API transformer
|
||||
└── utils/ # 22 utility modules (stream, TLS, proxy, logging)
|
||||
|
||||
electron/ # Electron desktop app (cross-platform)
|
||||
|
||||
tests/
|
||||
├── unit/ # Node.js test runner (.test.mjs)
|
||||
└── e2e/ # Playwright tests
|
||||
├── unit/ # Node.js test runner (122 test files)
|
||||
├── integration/ # Integration tests
|
||||
├── e2e/ # Playwright tests
|
||||
├── security/ # Security tests
|
||||
├── translator/ # Translator-specific tests
|
||||
└── load/ # Load tests
|
||||
|
||||
docs/ # Documentation
|
||||
├── USER_GUIDE.md # Provider setup, CLI integration
|
||||
├── API_REFERENCE.md # All endpoints
|
||||
├── TROUBLESHOOTING.md # Common issues
|
||||
├── ARCHITECTURE.md # System architecture
|
||||
├── API_REFERENCE.md # All endpoints
|
||||
├── USER_GUIDE.md # Provider setup, CLI integration
|
||||
├── TROUBLESHOOTING.md # Common issues
|
||||
├── MCP-SERVER.md # MCP server (25 tools)
|
||||
├── A2A-SERVER.md # A2A agent protocol
|
||||
├── AUTO-COMBO.md # Auto-combo engine
|
||||
├── CLI-TOOLS.md # CLI tools integration
|
||||
├── COVERAGE_PLAN.md # Test coverage improvement plan
|
||||
├── openapi.yaml # OpenAPI specification
|
||||
└── adr/ # Architecture Decision Records
|
||||
```
|
||||
|
||||
@@ -196,50 +250,25 @@ docs/ # Documentation
|
||||
|
||||
## Adding a New Provider
|
||||
|
||||
### Step 1: OAuth Service (if using OAuth)
|
||||
### Step 1: Register Provider Constants
|
||||
|
||||
Create `src/lib/oauth/services/your-provider.ts` extending `OAuthService`:
|
||||
Add to `src/shared/constants/providers.ts` — Zod-validated at module load.
|
||||
|
||||
```typescript
|
||||
import { OAuthService } from "../OAuthService";
|
||||
### Step 2: Add Executor (if custom logic needed)
|
||||
|
||||
export class YourProviderService extends OAuthService {
|
||||
constructor() {
|
||||
super({
|
||||
name: "your-provider",
|
||||
authUrl: "https://provider.com/oauth/authorize",
|
||||
tokenUrl: "https://provider.com/oauth/token",
|
||||
clientId: "...",
|
||||
scopes: ["..."],
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
Create executor in `open-sse/executors/your-provider.ts` extending the base executor.
|
||||
|
||||
### Step 2: Register Provider
|
||||
### Step 3: Add Translator (if non-OpenAI format)
|
||||
|
||||
Add to `src/lib/oauth/providers.ts`:
|
||||
Create request/response translators in `open-sse/translator/`.
|
||||
|
||||
```typescript
|
||||
import { YourProviderService } from "./services/your-provider";
|
||||
// Add to the providers map
|
||||
```
|
||||
### Step 4: Add OAuth Config (if OAuth-based)
|
||||
|
||||
### Step 3: Add Constants
|
||||
Add OAuth credentials in `src/lib/oauth/constants/oauth.ts` and service in `src/lib/oauth/services/`.
|
||||
|
||||
Add provider constants in `src/lib/providerConstants.ts`:
|
||||
### Step 5: Register Models
|
||||
|
||||
- Provider prefix (e.g., `yp/`)
|
||||
- Default models
|
||||
- Pricing info
|
||||
|
||||
### Step 4: Add Translator (if non-OpenAI format)
|
||||
|
||||
Create translator in `open-sse/translators/` if the provider uses a custom API format.
|
||||
|
||||
### Step 5: Add Timeout
|
||||
|
||||
Add request timeout configuration in `src/shared/utils/requestTimeout.ts`.
|
||||
Add model definitions in `open-sse/config/providerRegistry.ts`.
|
||||
|
||||
### Step 6: Add Tests
|
||||
|
||||
@@ -258,6 +287,7 @@ Write unit tests in `tests/unit/` covering at minimum:
|
||||
- [ ] Build succeeds (`npm run build`)
|
||||
- [ ] TypeScript types added for new public functions and interfaces
|
||||
- [ ] No hardcoded secrets or fallback values
|
||||
- [ ] All inputs validated with Zod schemas
|
||||
- [ ] CHANGELOG updated (if user-facing change)
|
||||
- [ ] Documentation updated (if applicable)
|
||||
|
||||
@@ -265,16 +295,13 @@ Write unit tests in `tests/unit/` covering at minimum:
|
||||
|
||||
## Releasing
|
||||
|
||||
When a new GitHub Release is created (e.g. `v0.4.0`), the package is **automatically published to npm** via GitHub Actions:
|
||||
|
||||
```bash
|
||||
gh release create v0.4.0 --title "v0.4.0" --generate-notes
|
||||
```
|
||||
Releases are managed via the `/generate-release` workflow. When a new GitHub Release is created, the package is **automatically published to npm** via GitHub Actions.
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Architecture**: See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
|
||||
- **API Reference**: See [`docs/API_REFERENCE.md`](docs/API_REFERENCE.md)
|
||||
- **Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues)
|
||||
- **ADRs**: See `docs/adr/` for architectural decision records
|
||||
|
||||
17
Dockerfile
17
Dockerfile
@@ -1,19 +1,21 @@
|
||||
FROM node:22-bookworm-slim AS builder
|
||||
FROM node:24.15.0-trixie-slim AS builder
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package*.json ./
|
||||
COPY scripts/postinstall.mjs ./scripts/postinstall.mjs
|
||||
COPY scripts/postinstallSupport.mjs ./scripts/postinstallSupport.mjs
|
||||
COPY scripts/native-binary-compat.mjs ./scripts/native-binary-compat.mjs
|
||||
ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
|
||||
RUN if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; else npm install --no-audit --no-fund; fi
|
||||
|
||||
COPY . ./
|
||||
RUN mkdir -p /app/data && npm run build -- --webpack
|
||||
|
||||
FROM node:22-bookworm-slim AS runner-base
|
||||
FROM node:24.15.0-trixie-slim AS runner-base
|
||||
WORKDIR /app
|
||||
|
||||
LABEL org.opencontainers.image.title="omniroute" \
|
||||
@@ -30,7 +32,7 @@ ENV NODE_OPTIONS="--max-old-space-size=256"
|
||||
# Data directory inside Docker — must match the volume mount in docker-compose.yml
|
||||
ENV DATA_DIR=/app/data
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
@@ -44,6 +46,11 @@ COPY --from=builder /app/node_modules/@swc/helpers ./node_modules/@swc/helpers
|
||||
COPY --from=builder /app/node_modules/pino-abstract-transport ./node_modules/pino-abstract-transport
|
||||
COPY --from=builder /app/node_modules/pino-pretty ./node_modules/pino-pretty
|
||||
COPY --from=builder /app/node_modules/split2 ./node_modules/split2
|
||||
# Migration SQL files are read via fs.readFileSync at runtime and are NOT
|
||||
# traced by Next.js standalone output — copy them explicitly.
|
||||
COPY --from=builder /app/src/lib/db/migrations ./migrations
|
||||
ENV OMNIROUTE_MIGRATIONS_DIR=/app/migrations
|
||||
|
||||
COPY --from=builder /app/scripts/run-standalone.mjs ./run-standalone.mjs
|
||||
COPY --from=builder /app/scripts/runtime-env.mjs ./runtime-env.mjs
|
||||
COPY --from=builder /app/scripts/bootstrap-env.mjs ./bootstrap-env.mjs
|
||||
@@ -60,7 +67,7 @@ FROM runner-base AS runner-cli
|
||||
|
||||
# Install system dependencies required by openclaw (git+ssh references).
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends git ca-certificates \
|
||||
&& apt-get install -y --no-install-recommends git ca-certificates docker.io docker-compose \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& git config --system url."https://github.com/".insteadOf "ssh://git@github.com/"
|
||||
|
||||
|
||||
21
GEMINI.md
Normal file
21
GEMINI.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Security and Cleanliness Rules for AI Assistants
|
||||
|
||||
## 1. File Placement & Organization
|
||||
|
||||
- **Test Files**: ALL unit tests, integration tests, ecosystem tests, or Vitest files MUST strictly be placed within the `tests/` directory (e.g., `tests/unit/`, `tests/integration/`). NEVER create test files in the project root (`/`).
|
||||
- **Scripts and Utilities**: ALL maintenance, debugging, generation, or experimental scripts (`.cjs`, `.mjs`, `.js`, `.ts`) MUST be placed strictly inside the `scripts/` directory or `scripts/scratch/` for temporary one-offs. NEVER dump loose scripts in the project root (`/`).
|
||||
|
||||
**The Project Root MUST ONLY CONTAIN:**
|
||||
|
||||
- Configuration files (`vitest.config.ts`, `next.config.mjs`, `eslint.config.mjs`, etc.)
|
||||
- Dependency files (`package.json`, `package-lock.json`)
|
||||
- Documentation files (`README.md`, `CHANGELOG.md`, `AGENTS.md`)
|
||||
- CI/CD files and ignore definitions (`.gitignore`, `.dockerignore`)
|
||||
|
||||
When creating _any_ validation tests or one-off logic scripts, default to using `scripts/scratch/` or the `tests/unit/` directories according to your goals. Do not pollute the `/` root context.
|
||||
|
||||
## 2. VPS Dashboard Credentials
|
||||
|
||||
| Environment | URL | Password |
|
||||
| ----------- | ------------------------- | -------- |
|
||||
| Local VPS | http://192.168.0.15:20128 | 123456 |
|
||||
2074
README.ar.md
2074
README.ar.md
File diff suppressed because it is too large
Load Diff
2074
README.bg.md
2074
README.bg.md
File diff suppressed because it is too large
Load Diff
2081
README.cs.md
2081
README.cs.md
File diff suppressed because it is too large
Load Diff
2074
README.da.md
2074
README.da.md
File diff suppressed because it is too large
Load Diff
2074
README.fi.md
2074
README.fi.md
File diff suppressed because it is too large
Load Diff
2074
README.he.md
2074
README.he.md
File diff suppressed because it is too large
Load Diff
2074
README.hu.md
2074
README.hu.md
File diff suppressed because it is too large
Load Diff
2074
README.id.md
2074
README.id.md
File diff suppressed because it is too large
Load Diff
1962
README.in.md
1962
README.in.md
File diff suppressed because it is too large
Load Diff
2073
README.ja.md
2073
README.ja.md
File diff suppressed because it is too large
Load Diff
2074
README.ko.md
2074
README.ko.md
File diff suppressed because it is too large
Load Diff
2074
README.ms.md
2074
README.ms.md
File diff suppressed because it is too large
Load Diff
2074
README.nl.md
2074
README.nl.md
File diff suppressed because it is too large
Load Diff
2074
README.no.md
2074
README.no.md
File diff suppressed because it is too large
Load Diff
2074
README.phi.md
2074
README.phi.md
File diff suppressed because it is too large
Load Diff
2074
README.pl.md
2074
README.pl.md
File diff suppressed because it is too large
Load Diff
2074
README.pt.md
2074
README.pt.md
File diff suppressed because it is too large
Load Diff
2074
README.ro.md
2074
README.ro.md
File diff suppressed because it is too large
Load Diff
2080
README.sk.md
2080
README.sk.md
File diff suppressed because it is too large
Load Diff
2074
README.sv.md
2074
README.sv.md
File diff suppressed because it is too large
Load Diff
2074
README.th.md
2074
README.th.md
File diff suppressed because it is too large
Load Diff
2079
README.uk-UA.md
2079
README.uk-UA.md
File diff suppressed because it is too large
Load Diff
2074
README.vi.md
2074
README.vi.md
File diff suppressed because it is too large
Load Diff
24
SECURITY.md
24
SECURITY.md
@@ -20,9 +20,9 @@ If you discover a security vulnerability in OmniRoute, please report it responsi
|
||||
|
||||
| Version | Support Status |
|
||||
| ------- | -------------- |
|
||||
| 1.0.x | ✅ Active |
|
||||
| 0.8.x | ✅ Security |
|
||||
| < 0.8.0 | ❌ Unsupported |
|
||||
| 3.6.x | ✅ Active |
|
||||
| 3.5.x | ✅ Security |
|
||||
| < 3.5.0 | ❌ Unsupported |
|
||||
|
||||
---
|
||||
|
||||
@@ -43,6 +43,7 @@ Request → CORS → API Key Auth → Prompt Injection Guard → Input Sanitizer
|
||||
| **OAuth 2.0 + PKCE** | Secure provider auth (Claude, Codex, Gemini, Cursor, etc.) |
|
||||
| **Token Refresh** | Automatic OAuth token refresh before expiry |
|
||||
| **Secure Cookies** | `AUTH_COOKIE_SECURE=true` for HTTPS environments |
|
||||
| **MCP Scopes** | 10 granular scopes for MCP tool access control |
|
||||
|
||||
### 🛡️ Encryption at Rest
|
||||
|
||||
@@ -98,9 +99,11 @@ PII_REDACTION_ENABLED=true
|
||||
| Feature | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------- |
|
||||
| **CORS** | Configurable origin control (`CORS_ORIGIN` env var, default `*`) |
|
||||
| **IP Filtering** | Whitelist/blacklist IP ranges in dashboard |
|
||||
| **IP Filtering** | Allowlist/blocklist IP ranges in dashboard |
|
||||
| **Rate Limiting** | Per-provider rate limits with automatic backoff |
|
||||
| **Anti-Thundering Herd** | Mutex + per-connection locking prevents cascading 502s |
|
||||
| **TLS Fingerprint** | Browser-like TLS fingerprint spoofing to reduce bot detection |
|
||||
| **CLI Fingerprint** | Per-provider header/body ordering to match native CLI signatures |
|
||||
|
||||
### 🔌 Resilience & Availability
|
||||
|
||||
@@ -113,11 +116,13 @@ PII_REDACTION_ENABLED=true
|
||||
|
||||
### 📋 Compliance
|
||||
|
||||
| Feature | Description |
|
||||
| ------------------ | --------------------------------------------------- |
|
||||
| **Log Retention** | Automatic cleanup after `LOG_RETENTION_DAYS` |
|
||||
| **No-Log Opt-out** | Per API key `noLog` flag disables request logging |
|
||||
| **Audit Log** | Administrative actions tracked in `audit_log` table |
|
||||
| Feature | Description |
|
||||
| ------------------ | ----------------------------------------------------------- |
|
||||
| **Log Retention** | Automatic cleanup after `CALL_LOG_RETENTION_DAYS` |
|
||||
| **No-Log Opt-out** | Per API key `noLog` flag disables request logging |
|
||||
| **Audit Log** | Administrative actions tracked in `audit_log` table |
|
||||
| **MCP Audit** | SQLite-backed audit logging for all MCP tool calls |
|
||||
| **Zod Validation** | All API inputs validated with Zod v4 schemas at module load |
|
||||
|
||||
---
|
||||
|
||||
@@ -167,3 +172,4 @@ docker run -d \
|
||||
- Keep dependencies updated
|
||||
- The project uses `husky` + `lint-staged` for pre-commit checks
|
||||
- CI pipeline runs ESLint security rules on every push
|
||||
- Provider constants validated at module load via Zod (`src/shared/validation/providerSchema.ts`)
|
||||
|
||||
48
audit-report.json
Normal file
48
audit-report.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"auditReportVersion": 2,
|
||||
"vulnerabilities": {
|
||||
"follow-redirects": {
|
||||
"name": "follow-redirects",
|
||||
"severity": "moderate",
|
||||
"isDirect": false,
|
||||
"via": [
|
||||
{
|
||||
"source": 1116560,
|
||||
"name": "follow-redirects",
|
||||
"dependency": "follow-redirects",
|
||||
"title": "follow-redirects leaks Custom Authentication Headers to Cross-Domain Redirect Targets",
|
||||
"url": "https://github.com/advisories/GHSA-r4q5-vmmm-2653",
|
||||
"severity": "moderate",
|
||||
"cwe": ["CWE-200"],
|
||||
"cvss": {
|
||||
"score": 0,
|
||||
"vectorString": null
|
||||
},
|
||||
"range": "<=1.15.11"
|
||||
}
|
||||
],
|
||||
"effects": [],
|
||||
"range": "<=1.15.11",
|
||||
"nodes": ["node_modules/follow-redirects"],
|
||||
"fixAvailable": true
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"vulnerabilities": {
|
||||
"info": 0,
|
||||
"low": 0,
|
||||
"moderate": 1,
|
||||
"high": 0,
|
||||
"critical": 0,
|
||||
"total": 1
|
||||
},
|
||||
"dependencies": {
|
||||
"prod": 421,
|
||||
"dev": 480,
|
||||
"optional": 158,
|
||||
"peer": 480,
|
||||
"peerOptional": 0,
|
||||
"total": 1462
|
||||
}
|
||||
}
|
||||
}
|
||||
83
bin/nodeRuntimeSupport.mjs
Normal file
83
bin/nodeRuntimeSupport.mjs
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
export const SECURE_NODE_LINES = Object.freeze([
|
||||
Object.freeze({ major: 20, minor: 20, patch: 2 }),
|
||||
Object.freeze({ major: 22, minor: 22, patch: 2 }),
|
||||
Object.freeze({ major: 24, minor: 0, patch: 0 }),
|
||||
]);
|
||||
|
||||
export const RECOMMENDED_NODE_VERSION = "24.14.1";
|
||||
export const SUPPORTED_NODE_RANGE = ">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <25";
|
||||
export const SUPPORTED_NODE_DISPLAY =
|
||||
"Node.js 20.20.2+ (20.x LTS), 22.22.2+ (22.x LTS), or 24.0.0+ (24.x LTS)";
|
||||
|
||||
function formatVersion(version) {
|
||||
return `${version.major}.${version.minor}.${version.patch}`;
|
||||
}
|
||||
|
||||
export function parseNodeVersion(version = process.versions.node) {
|
||||
const rawInput = String(version || process.versions.node || "0.0.0").trim();
|
||||
const normalized = rawInput.replace(/^v/i, "");
|
||||
const parts = normalized.split(".");
|
||||
const major = Number.parseInt(parts[0] || "0", 10);
|
||||
const minor = Number.parseInt(parts[1] || "0", 10);
|
||||
const patch = Number.parseInt(parts[2] || "0", 10);
|
||||
|
||||
return {
|
||||
raw: normalized ? `v${normalized}` : "v0.0.0",
|
||||
normalized: normalized || "0.0.0",
|
||||
major: Number.isFinite(major) ? major : 0,
|
||||
minor: Number.isFinite(minor) ? minor : 0,
|
||||
patch: Number.isFinite(patch) ? patch : 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function compareNodeVersions(a, b) {
|
||||
if (a.major !== b.major) return a.major - b.major;
|
||||
if (a.minor !== b.minor) return a.minor - b.minor;
|
||||
return a.patch - b.patch;
|
||||
}
|
||||
|
||||
export function getSecureFloorForMajor(major) {
|
||||
return SECURE_NODE_LINES.find((line) => line.major === major) || null;
|
||||
}
|
||||
|
||||
export function getNodeRuntimeSupport(version = process.versions.node) {
|
||||
const parsed = parseNodeVersion(version);
|
||||
const secureFloor = getSecureFloorForMajor(parsed.major);
|
||||
const nodeCompatible = secureFloor ? compareNodeVersions(parsed, secureFloor) >= 0 : false;
|
||||
|
||||
let reason = "unsupported-major";
|
||||
if (nodeCompatible) {
|
||||
reason = "supported";
|
||||
} else if (secureFloor) {
|
||||
reason = "below-security-floor";
|
||||
} else if (parsed.major >= 25) {
|
||||
reason = "unreleased-major";
|
||||
}
|
||||
|
||||
return {
|
||||
nodeVersion: parsed.raw,
|
||||
nodeCompatible,
|
||||
reason,
|
||||
supportedRange: SUPPORTED_NODE_RANGE,
|
||||
supportedDisplay: SUPPORTED_NODE_DISPLAY,
|
||||
recommendedVersion: `v${RECOMMENDED_NODE_VERSION}`,
|
||||
minimumSecureVersion: secureFloor ? `v${formatVersion(secureFloor)}` : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function getNodeRuntimeWarning(version = process.versions.node) {
|
||||
const support = getNodeRuntimeSupport(version);
|
||||
if (support.nodeCompatible) return null;
|
||||
|
||||
if (support.reason === "below-security-floor" && support.minimumSecureVersion) {
|
||||
return `Node.js ${support.nodeVersion} is below the patched minimum ${support.minimumSecureVersion} for this LTS line.`;
|
||||
}
|
||||
|
||||
if (support.reason === "unreleased-major") {
|
||||
return `Node.js ${support.nodeVersion} is outside the supported LTS lines. OmniRoute currently supports Node.js 20.x, 22.x, and 24.x.`;
|
||||
}
|
||||
|
||||
return `Node.js ${support.nodeVersion} is outside OmniRoute's approved secure runtime policy.`;
|
||||
}
|
||||
169
bin/omniroute.mjs
Executable file → Normal file
169
bin/omniroute.mjs
Executable file → Normal file
@@ -4,36 +4,35 @@
|
||||
* OmniRoute CLI — Smart AI Router with Auto Fallback
|
||||
*
|
||||
* Usage:
|
||||
* omniroute Start the server (default port 20128)
|
||||
* omniroute --port 3000 Start on custom port
|
||||
* omniroute --no-open Start without opening browser
|
||||
* omniroute --mcp Start MCP server (stdio transport for IDEs)
|
||||
* omniroute --help Show help
|
||||
* omniroute --version Show version
|
||||
* omniroute Start the server (default port 20128)
|
||||
* omniroute --port 3000 Start on custom port
|
||||
* omniroute --no-open Start without opening browser
|
||||
* omniroute --mcp Start MCP server (stdio transport for IDEs)
|
||||
* omniroute reset-encrypted-columns Reset broken encrypted credentials
|
||||
* omniroute --help Show help
|
||||
* omniroute --version Show version
|
||||
*/
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { homedir, platform } from "node:os";
|
||||
import { isNativeBinaryCompatible } from "../scripts/native-binary-compat.mjs";
|
||||
import { getNodeRuntimeSupport, getNodeRuntimeWarning } from "./nodeRuntimeSupport.mjs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const ROOT = join(__dirname, "..");
|
||||
const APP_DIR = join(ROOT, "app");
|
||||
|
||||
// ── Load .env file (for global npm install) ─────────────────
|
||||
function loadEnvFile() {
|
||||
const envPaths = [];
|
||||
|
||||
// 1. DATA_DIR/.env if set
|
||||
if (process.env.DATA_DIR) {
|
||||
envPaths.push(join(process.env.DATA_DIR, ".env"));
|
||||
}
|
||||
|
||||
// 2. ~/.omniroute/.env (default data dir)
|
||||
const home = homedir();
|
||||
if (home) {
|
||||
if (platform() === "win32") {
|
||||
@@ -44,7 +43,6 @@ function loadEnvFile() {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. ./.env (current working directory)
|
||||
envPaths.push(join(process.cwd(), ".env"));
|
||||
|
||||
for (const envPath of envPaths) {
|
||||
@@ -53,15 +51,12 @@ function loadEnvFile() {
|
||||
const content = readFileSync(envPath, "utf-8");
|
||||
for (const line of content.split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
// Skip empty lines and comments
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const eqIdx = trimmed.indexOf("=");
|
||||
if (eqIdx > 0) {
|
||||
const key = trimmed.slice(0, eqIdx).trim();
|
||||
const value = trimmed.slice(eqIdx + 1).trim();
|
||||
// Don't override existing env vars
|
||||
if (process.env[key] === undefined) {
|
||||
// Remove surrounding quotes
|
||||
process.env[key] = value.replace(/^["']|["']$/g, "");
|
||||
}
|
||||
}
|
||||
@@ -70,14 +65,13 @@ function loadEnvFile() {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors reading env files
|
||||
// Ignore errors reading env files.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadEnvFile();
|
||||
|
||||
// ── Parse args ─────────────────────────────────────────────
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
@@ -89,6 +83,7 @@ if (args.includes("--help") || args.includes("-h")) {
|
||||
omniroute --port <port> Use custom API port (default: 20128)
|
||||
omniroute --no-open Don't open browser automatically
|
||||
omniroute --mcp Start MCP server (stdio transport for IDEs)
|
||||
omniroute reset-encrypted-columns Reset encrypted credentials (recovery)
|
||||
omniroute --help Show this help
|
||||
omniroute --version Show version
|
||||
|
||||
@@ -124,10 +119,106 @@ if (args.includes("--version") || args.includes("-v")) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// ── MCP Server Mode ───────────────────────────────────────
|
||||
// ── reset-encrypted-columns subcommand ──────────────────────────────────────
|
||||
// Recovery tool for users who lost STORAGE_ENCRYPTION_KEY after upgrade (#1622)
|
||||
if (args.includes("reset-encrypted-columns")) {
|
||||
const dataDir = (() => {
|
||||
const configured = process.env.DATA_DIR?.trim();
|
||||
if (configured) return configured;
|
||||
if (platform() === "win32") {
|
||||
const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
|
||||
return join(appData, "omniroute");
|
||||
}
|
||||
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
||||
if (xdg) return join(xdg, "omniroute");
|
||||
return join(homedir(), ".omniroute");
|
||||
})();
|
||||
|
||||
const dbPath = join(dataDir, "storage.sqlite");
|
||||
|
||||
if (!existsSync(dbPath)) {
|
||||
console.log(`\x1b[33m⚠ No database found at ${dbPath}\x1b[0m`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const force = args.includes("--force");
|
||||
if (!force) {
|
||||
console.log(`
|
||||
\x1b[1m\x1b[33m⚠ WARNING: This will erase all encrypted credentials\x1b[0m
|
||||
|
||||
This command will NULL out the following columns in provider_connections:
|
||||
• api_key
|
||||
• access_token
|
||||
• refresh_token
|
||||
• id_token
|
||||
|
||||
Provider metadata (name, provider_id, settings) will be preserved.
|
||||
You will need to re-authenticate all providers after this operation.
|
||||
|
||||
Database: ${dbPath}
|
||||
|
||||
\x1b[1mTo confirm, run:\x1b[0m
|
||||
omniroute reset-encrypted-columns --force
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
const { createRequire } = await import("node:module");
|
||||
const require = createRequire(import.meta.url);
|
||||
const Database = require("better-sqlite3");
|
||||
const db = new Database(dbPath);
|
||||
|
||||
const countResult = db
|
||||
.prepare(
|
||||
`SELECT COUNT(*) as cnt FROM provider_connections
|
||||
WHERE api_key LIKE 'enc:v1:%'
|
||||
OR access_token LIKE 'enc:v1:%'
|
||||
OR refresh_token LIKE 'enc:v1:%'
|
||||
OR id_token LIKE 'enc:v1:%'`
|
||||
)
|
||||
.get();
|
||||
|
||||
const affected = countResult?.cnt ?? 0;
|
||||
|
||||
if (affected === 0) {
|
||||
console.log("\x1b[32m✔ No encrypted credentials found — nothing to reset.\x1b[0m");
|
||||
db.close();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const result = db
|
||||
.prepare(
|
||||
`UPDATE provider_connections
|
||||
SET api_key = NULL,
|
||||
access_token = NULL,
|
||||
refresh_token = NULL,
|
||||
id_token = NULL
|
||||
WHERE api_key LIKE 'enc:v1:%'
|
||||
OR access_token LIKE 'enc:v1:%'
|
||||
OR refresh_token LIKE 'enc:v1:%'
|
||||
OR id_token LIKE 'enc:v1:%'`
|
||||
)
|
||||
.run();
|
||||
|
||||
db.close();
|
||||
|
||||
console.log(
|
||||
`\x1b[32m✔ Reset ${result.changes} provider connection(s).\x1b[0m\n` +
|
||||
` Re-authenticate your providers in the dashboard or re-add API keys.\n`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`\x1b[31m✖ Failed to reset encrypted columns:\x1b[0m ${err.message || err}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (args.includes("--mcp")) {
|
||||
try {
|
||||
const { startMcpCli } = await import(join(ROOT, "bin", "mcp-server.mjs"));
|
||||
const { startMcpCli } = await import(pathToFileURL(join(ROOT, "bin", "mcp-server.mjs")).href);
|
||||
await startMcpCli(ROOT);
|
||||
} catch (err) {
|
||||
console.error("\x1b[31m✖ Failed to start MCP server:\x1b[0m", err.message || err);
|
||||
@@ -141,7 +232,6 @@ function parsePort(value, fallback) {
|
||||
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
|
||||
}
|
||||
|
||||
// Parse --port (canonical/base port)
|
||||
let port = parsePort(process.env.PORT || "20128", 20128);
|
||||
const portIdx = args.indexOf("--port");
|
||||
if (portIdx !== -1 && args[portIdx + 1]) {
|
||||
@@ -155,41 +245,36 @@ if (portIdx !== -1 && args[portIdx + 1]) {
|
||||
|
||||
const apiPort = parsePort(process.env.API_PORT || String(port), port);
|
||||
const dashboardPort = parsePort(process.env.DASHBOARD_PORT || String(port), port);
|
||||
|
||||
const noOpen = args.includes("--no-open");
|
||||
|
||||
// ── Banner ─────────────────────────────────────────────────
|
||||
console.log(`
|
||||
\x1b[36m ____ _ ____ _
|
||||
/ __ \\ (_) __ \\ | |
|
||||
/ __ \\\\ (_) __ \\\\ | |
|
||||
| | | |_ __ ___ _ __ _| |__) |___ _ _| |_ ___
|
||||
| | | | '_ \` _ \\| '_ \\ | _ // _ \\| | | | __/ _ \\
|
||||
| |__| | | | | | | | | | | | \\ \\ (_) | |_| | || __/
|
||||
\\____/|_| |_| |_|_| |_|_|_| \\_\\___/ \\__,_|\\__\\___|
|
||||
| | | | '_ \` _ \\\\| '_ \\\\ | _ // _ \\\\| | | | __/ _ \\\\
|
||||
| |__| | | | | | | | | | | | \\\\ \\\\ (_) | |_| | || __/
|
||||
\\\\____/|_| |_| |_|_| |_|_|_| \\\\_\\\\___/ \\\\__,_|\\\\__\\\\___|
|
||||
\x1b[0m`);
|
||||
|
||||
// ── Node.js version check ──────────────────────────────────
|
||||
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
||||
if (nodeMajor >= 24) {
|
||||
const nodeSupport = getNodeRuntimeSupport();
|
||||
if (!nodeSupport.nodeCompatible) {
|
||||
const runtimeWarning = getNodeRuntimeWarning() || "Unsupported Node.js runtime detected.";
|
||||
console.warn(`\x1b[33m ⚠ Warning: You are running Node.js ${process.versions.node}.
|
||||
OmniRoute uses better-sqlite3, a native addon that does not yet
|
||||
have compatible prebuilt binaries for Node.js 24+.
|
||||
You may experience errors like "is not a valid Win32 application"
|
||||
or "NODE_MODULE_VERSION mismatch".
|
||||
${runtimeWarning}
|
||||
|
||||
Recommended: use Node.js 22 LTS (or 20 LTS).
|
||||
Supported secure runtimes: ${nodeSupport.supportedDisplay}
|
||||
Recommended: use Node.js ${nodeSupport.recommendedVersion} or newer on the 22.x LTS line.
|
||||
Workaround: npm rebuild better-sqlite3\x1b[0m
|
||||
`);
|
||||
}
|
||||
|
||||
// ── Resolve server entry ───────────────────────────────────
|
||||
const serverJs = join(APP_DIR, "server.js");
|
||||
const serverWsJs = join(APP_DIR, "server-ws.mjs");
|
||||
const serverJs = existsSync(serverWsJs) ? serverWsJs : join(APP_DIR, "server.js");
|
||||
|
||||
if (!existsSync(serverJs)) {
|
||||
console.error("\x1b[31m✖ Server not found at:\x1b[0m", serverJs);
|
||||
console.error(" The package may not have been built correctly.");
|
||||
console.error("");
|
||||
// (#492) Detect common non-standard Node managers that cause this issue
|
||||
const nodeExec = process.execPath || "";
|
||||
const isMise = nodeExec.includes("mise") || nodeExec.includes(".local/share/mise");
|
||||
const isNvm = nodeExec.includes(".nvm") || nodeExec.includes("nvm");
|
||||
@@ -211,10 +296,6 @@ if (!existsSync(serverJs)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// ── Pre-flight: verify better-sqlite3 native binary ───────
|
||||
// Verify the binary's actual target platform/arch before trusting dlopen.
|
||||
// This avoids the macOS false positive where a bundled linux-x64 addon can
|
||||
// appear to load even though the runtime will fail when better-sqlite3 starts.
|
||||
const sqliteBinary = join(
|
||||
APP_DIR,
|
||||
"node_modules",
|
||||
@@ -234,10 +315,8 @@ if (existsSync(sqliteBinary) && !isNativeBinaryCompatible(sqliteBinary)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// ── Start server ───────────────────────────────────────────
|
||||
console.log(` \x1b[2m⏳ Starting server...\x1b[0m\n`);
|
||||
|
||||
// Sanitize memory limit — parseInt to prevent command injection (#150)
|
||||
const rawMemory = parseInt(process.env.OMNIROUTE_MEMORY_MB || "512", 10);
|
||||
const memoryLimit =
|
||||
Number.isFinite(rawMemory) && rawMemory >= 64 && rawMemory <= 16384 ? rawMemory : 512;
|
||||
@@ -265,7 +344,6 @@ server.stdout.on("data", (data) => {
|
||||
const text = data.toString();
|
||||
process.stdout.write(text);
|
||||
|
||||
// Detect server ready
|
||||
if (
|
||||
!started &&
|
||||
(text.includes("Ready") || text.includes("started") || text.includes("listening"))
|
||||
@@ -291,7 +369,6 @@ server.on("exit", (code) => {
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
|
||||
// ── Graceful shutdown ──────────────────────────────────────
|
||||
function shutdown() {
|
||||
console.log("\n\x1b[33m⏹ Shutting down OmniRoute...\x1b[0m");
|
||||
server.kill("SIGTERM");
|
||||
@@ -304,7 +381,6 @@ function shutdown() {
|
||||
process.on("SIGINT", shutdown);
|
||||
process.on("SIGTERM", shutdown);
|
||||
|
||||
// ── On ready ───────────────────────────────────────────────
|
||||
async function onReady() {
|
||||
const dashboardUrl = `http://localhost:${dashboardPort}`;
|
||||
const apiUrl = `http://localhost:${apiPort}`;
|
||||
@@ -326,12 +402,11 @@ async function onReady() {
|
||||
const open = await import("open");
|
||||
await open.default(dashboardUrl);
|
||||
} catch {
|
||||
// open is optional — if not available, just skip
|
||||
// open is optional — if not available, just skip.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if no "Ready" message detected in 15s, assume server is up
|
||||
setTimeout(() => {
|
||||
if (!started) {
|
||||
started = true;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { createInterface } from "node:readline";
|
||||
import { resolve, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { existsSync } from "node:fs";
|
||||
import { createHash } from "node:crypto";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -34,8 +34,10 @@ function ask(question) {
|
||||
return new Promise((resolve) => rl.question(question, resolve));
|
||||
}
|
||||
|
||||
function hashPassword(password) {
|
||||
return createHash("sha256").update(password).digest("hex");
|
||||
function generateSecretDigest(input) {
|
||||
// Use bcrypt with a salt round of 10 to match login/route.ts expectations
|
||||
// and resolve CodeQL js/insufficient-password-hash warning.
|
||||
return bcrypt.hashSync(input, 10);
|
||||
}
|
||||
|
||||
console.log("\n🔑 OmniRoute — Password Reset\n");
|
||||
@@ -68,10 +70,10 @@ async function main() {
|
||||
console.log("ℹ️ No password is currently set.");
|
||||
}
|
||||
|
||||
const password = await ask("Enter new password (min 4 chars): ");
|
||||
const password = await ask("Enter new password (min 8 chars): ");
|
||||
|
||||
if (!password || password.length < 4) {
|
||||
console.error("\n❌ Password must be at least 4 characters.\n");
|
||||
if (!password || password.length < 8) {
|
||||
console.error("\n❌ Password must be at least 8 characters.\n");
|
||||
db.close();
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
@@ -86,7 +88,7 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hashed = hashPassword(password);
|
||||
const hashed = generateSecretDigest(password);
|
||||
|
||||
// Upsert the password
|
||||
const stmt = db.prepare(`
|
||||
|
||||
6
config/payloadRules.json
Normal file
6
config/payloadRules.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"default": [],
|
||||
"default-raw": [],
|
||||
"override": [],
|
||||
"filter": []
|
||||
}
|
||||
@@ -19,6 +19,7 @@ services:
|
||||
target: runner-cli
|
||||
image: omniroute:prod
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 40s
|
||||
env_file: .env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
|
||||
@@ -6,17 +6,21 @@
|
||||
# base → minimal image, no CLI tools
|
||||
# cli → CLIs installed inside the container (portable)
|
||||
# host → runner-base + host-mounted CLI binaries (Linux-first)
|
||||
# cliproxyapi → CLIProxyAPI sidecar on port 8317
|
||||
#
|
||||
# Usage:
|
||||
# docker compose --profile base up -d
|
||||
# docker compose --profile cli up -d
|
||||
# docker compose --profile host up -d
|
||||
# docker compose --profile cliproxyapi up -d
|
||||
# docker compose --profile cli --profile cliproxyapi up -d
|
||||
#
|
||||
# Before first run, copy .env.example → .env and edit your secrets.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
x-common: &common
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 40s
|
||||
env_file: .env
|
||||
environment:
|
||||
- DATA_DIR=/app/data # Must match the volume mount below
|
||||
@@ -25,7 +29,7 @@ x-common: &common
|
||||
- API_PORT=${API_PORT:-20129}
|
||||
- API_HOST=${API_HOST:-0.0.0.0}
|
||||
volumes:
|
||||
- omniroute-data:/app/data
|
||||
- ./data:/app/data
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "healthcheck.mjs"]
|
||||
interval: 30s
|
||||
@@ -59,6 +63,11 @@ services:
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-${PORT:-20128}}:${DASHBOARD_PORT:-${PORT:-20128}}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /usr/libexec/docker/cli-plugins:/usr/libexec/docker/cli-plugins:ro
|
||||
- ${AUTO_UPDATE_HOST_REPO_DIR:-.}:/workspace/omniroute:rw
|
||||
profiles:
|
||||
- cli
|
||||
|
||||
@@ -88,12 +97,12 @@ services:
|
||||
# - CLI_CLINE_BIN=cline
|
||||
# - CLI_CONTINUE_BIN=cn
|
||||
volumes:
|
||||
- omniroute-data:/app/data
|
||||
- ./data:/app/data
|
||||
# ── Host binary mounts (read-only) ──
|
||||
# Adjust paths below to match YOUR host system.
|
||||
- ~/.local/bin:/host-local/bin:ro
|
||||
# Node global binaries (adjust node version path)
|
||||
# - ~/.nvm/versions/node/v22.16.0/bin:/host-node/bin:ro
|
||||
# - ~/.nvm/versions/node/v24.14.1/bin:/host-node/bin:ro
|
||||
# ── Host config mounts (read-write) ──
|
||||
- ~/.codex:/host-home/.codex:rw
|
||||
- ~/.claude:/host-home/.claude:rw
|
||||
@@ -104,6 +113,28 @@ services:
|
||||
profiles:
|
||||
- host
|
||||
|
||||
# ── Profile: cliproxyapi (CLIProxyAPI as sidecar) ─────────────────
|
||||
cliproxyapi:
|
||||
container_name: cliproxyapi
|
||||
image: ghcr.io/router-for-me/cliproxyapi:v6.9.7
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${CLIPROXYAPI_PORT:-8317}:${CLIPROXYAPI_PORT:-8317}"
|
||||
volumes:
|
||||
- cliproxyapi-data:/root/.cli-proxy-api
|
||||
environment:
|
||||
- PORT=${CLIPROXYAPI_PORT:-8317}
|
||||
- HOST=0.0.0.0
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD", "wget", "--spider", "-q", "http://127.0.0.1:${CLIPROXYAPI_PORT:-8317}/v1/models"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
profiles:
|
||||
- cliproxyapi
|
||||
|
||||
volumes:
|
||||
omniroute-data:
|
||||
name: omniroute-data
|
||||
cliproxyapi-data:
|
||||
name: cliproxyapi-data
|
||||
|
||||
@@ -22,6 +22,12 @@ Authorization: Bearer YOUR_OMNIROUTE_API_KEY
|
||||
|
||||
If no API key is configured on the server, authentication is bypassed.
|
||||
|
||||
## Enablement
|
||||
|
||||
A2A is controlled by the **Endpoints → A2A** toggle and is disabled by default. When disabled,
|
||||
`GET /api/a2a/status` reports `status: "disabled"` and `online: false`; JSON-RPC calls to
|
||||
`POST /a2a` return HTTP 503 with JSON-RPC error code `-32000`.
|
||||
|
||||
---
|
||||
|
||||
## JSON-RPC 2.0 Methods
|
||||
@@ -148,6 +154,7 @@ submitted → working → completed
|
||||
| -32601 | Method or skill not found |
|
||||
| -32602 | Invalid params |
|
||||
| -32603 | Internal error |
|
||||
| -32000 | A2A endpoint is disabled |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
Available providers: Nebius, OpenAI, Mistral, Together AI, Fireworks, NVIDIA.
|
||||
Available providers: Nebius, OpenAI, Mistral, Together AI, Fireworks, NVIDIA, **OpenRouter**, **GitHub Models**.
|
||||
|
||||
```bash
|
||||
# List all embedding models
|
||||
@@ -91,7 +91,7 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
Available providers: OpenAI (DALL-E), xAI (Grok Image), Together AI (FLUX), Fireworks AI.
|
||||
Available providers: OpenAI (DALL-E, GPT Image 1), xAI (Grok Image), Together AI (FLUX), Fireworks AI, Nebius (FLUX), Hyperbolic, NanoBanana, **OpenRouter**, SD WebUI (local), ComfyUI (local).
|
||||
|
||||
```bash
|
||||
# List all image models
|
||||
@@ -179,15 +179,15 @@ Response example:
|
||||
|
||||
### Provider Management
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ---------------------------- | --------------- | ------------------------ |
|
||||
| `/api/providers` | GET/POST | List / create providers |
|
||||
| `/api/providers/[id]` | GET/PUT/DELETE | Manage a provider |
|
||||
| `/api/providers/[id]/test` | POST | Test provider connection |
|
||||
| `/api/providers/[id]/models` | GET | List provider models |
|
||||
| `/api/providers/validate` | POST | Validate provider config |
|
||||
| `/api/provider-nodes*` | Various | Provider node management |
|
||||
| `/api/provider-models` | GET/POST/DELETE | Custom models |
|
||||
| Endpoint | Method | Description |
|
||||
| ---------------------------- | --------------------- | ---------------------------------------------- |
|
||||
| `/api/providers` | GET/POST | List / create providers |
|
||||
| `/api/providers/[id]` | GET/PUT/DELETE | Manage a provider |
|
||||
| `/api/providers/[id]/test` | POST | Test provider connection |
|
||||
| `/api/providers/[id]/models` | GET | List provider models |
|
||||
| `/api/providers/validate` | POST | Validate provider config |
|
||||
| `/api/provider-nodes*` | Various | Provider node management |
|
||||
| `/api/provider-models` | GET/POST/PATCH/DELETE | Custom models (add, update, hide/show, delete) |
|
||||
|
||||
### OAuth Flows
|
||||
|
||||
@@ -216,23 +216,23 @@ Response example:
|
||||
|
||||
### Settings
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------------- | ------- | ---------------------- |
|
||||
| `/api/settings` | GET/PUT/PATCH | General settings |
|
||||
| `/api/settings/proxy` | GET/PUT | Network proxy config |
|
||||
| `/api/settings/proxy/test` | POST | Test proxy connection |
|
||||
| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist |
|
||||
| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget |
|
||||
| `/api/settings/system-prompt` | GET/PUT | Global system prompt |
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------------- | ------------- | ---------------------- |
|
||||
| `/api/settings` | GET/PUT/PATCH | General settings |
|
||||
| `/api/settings/proxy` | GET/PUT | Network proxy config |
|
||||
| `/api/settings/proxy/test` | POST | Test proxy connection |
|
||||
| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist |
|
||||
| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget |
|
||||
| `/api/settings/system-prompt` | GET/PUT | Global system prompt |
|
||||
|
||||
### Monitoring
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------ | ---------- | ----------------------- |
|
||||
| `/api/sessions` | GET | Active session tracking |
|
||||
| `/api/rate-limits` | GET | Per-account rate limits |
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------ | ---------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `/api/sessions` | GET | Active session tracking |
|
||||
| `/api/rate-limits` | GET | Per-account rate limits |
|
||||
| `/api/monitoring/health` | GET | Health check + provider summary (`catalogCount`, `configuredCount`, `activeCount`, `monitoredCount`) |
|
||||
| `/api/cache/stats` | GET/DELETE | Cache stats / clear |
|
||||
| `/api/cache/stats` | GET/DELETE | Cache stats / clear |
|
||||
|
||||
### Backup & Export/Import
|
||||
|
||||
@@ -253,6 +253,15 @@ Response example:
|
||||
| `/api/sync/initialize` | POST | Initialize sync |
|
||||
| `/api/cloud/*` | Various | Cloud management |
|
||||
|
||||
### Tunnels
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| -------------------------- | ------ | ----------------------------------------------------------------------- |
|
||||
| `/api/tunnels/cloudflared` | GET | Read Cloudflare Quick Tunnel install/runtime status for the dashboard |
|
||||
| `/api/tunnels/cloudflared` | POST | Enable or disable the Cloudflare Quick Tunnel (`action=enable/disable`) |
|
||||
| `/api/tunnels/ngrok` | GET | Read ngrok Tunnel runtime status for the dashboard |
|
||||
| `/api/tunnels/ngrok` | POST | Enable or disable the ngrok Tunnel (`action=enable/disable`) |
|
||||
|
||||
### CLI Tools
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
@@ -277,12 +286,12 @@ GET response includes `agents[]` (id, name, binary, version, installed, protocol
|
||||
|
||||
### Resilience & Rate Limits
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ----------------------- | ------- | ------------------------------- |
|
||||
| `/api/resilience` | GET/PATCH | Get/update resilience profiles |
|
||||
| `/api/resilience/reset` | POST | Reset circuit breakers |
|
||||
| `/api/rate-limits` | GET | Per-account rate limit status |
|
||||
| `/api/rate-limit` | GET | Global rate limit configuration |
|
||||
| Endpoint | Method | Description |
|
||||
| ----------------------- | --------- | ---------------------------------------------------------------------------------- |
|
||||
| `/api/resilience` | GET/PATCH | Get/update request queue, connection cooldown, provider breaker, and wait settings |
|
||||
| `/api/resilience/reset` | POST | Reset provider circuit breakers |
|
||||
| `/api/rate-limits` | GET | Per-account rate limit status |
|
||||
| `/api/rate-limit` | GET | Global rate limit configuration |
|
||||
|
||||
### Evals
|
||||
|
||||
@@ -313,15 +322,38 @@ These endpoints mirror Gemini's API format for clients that expect native Gemini
|
||||
|
||||
### Internal / System APIs
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| --------------- | ------ | ---------------------------------------------------- |
|
||||
| `/api/init` | GET | Application initialization check (used on first run) |
|
||||
| `/api/tags` | GET | Ollama-compatible model tags (for Ollama clients) |
|
||||
| `/api/restart` | POST | Trigger graceful server restart |
|
||||
| `/api/shutdown` | POST | Trigger graceful server shutdown |
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------ | ------ | ---------------------------------------------------- |
|
||||
| `/api/init` | GET | Application initialization check (used on first run) |
|
||||
| `/api/tags` | GET | Ollama-compatible model tags (for Ollama clients) |
|
||||
| `/api/restart` | POST | Trigger graceful server restart |
|
||||
| `/api/shutdown` | POST | Trigger graceful server shutdown |
|
||||
| `/api/system/env/repair` | POST | Repair OAuth provider environment variables |
|
||||
| `/api/system-info` | GET | Generate system diagnostics report |
|
||||
|
||||
> **Note:** These endpoints are used internally by the system or for Ollama client compatibility. They are not typically called by end users.
|
||||
|
||||
### OAuth Environment Repair _(v3.6.1+)_
|
||||
|
||||
```bash
|
||||
POST /api/system/env/repair
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"provider": "claude-code"
|
||||
}
|
||||
```
|
||||
|
||||
Repairs missing or corrupted OAuth environment variables for a specific provider. Returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"repaired": ["CLAUDE_CODE_OAUTH_CLIENT_ID", "CLAUDE_CODE_OAUTH_CLIENT_SECRET"],
|
||||
"backupPath": "/home/user/.omniroute/backups/env-repair-2026-04-11.bak"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audio Transcription
|
||||
@@ -413,25 +445,6 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Model Availability
|
||||
|
||||
```bash
|
||||
# Get real-time model availability across all providers
|
||||
GET /api/models/availability
|
||||
|
||||
# Check availability for a specific model
|
||||
POST /api/models/availability
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "claude-sonnet-4-5-20250929"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Request Processing
|
||||
|
||||
1. Client sends request to `/v1/*`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](ARCHITECTURE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/ARCHITECTURE.md) | 🇪🇸 [Español](i18n/es/ARCHITECTURE.md) | 🇫🇷 [Français](i18n/fr/ARCHITECTURE.md) | 🇮🇹 [Italiano](i18n/it/ARCHITECTURE.md) | 🇷🇺 [Русский](i18n/ru/ARCHITECTURE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/ARCHITECTURE.md) | 🇩🇪 [Deutsch](i18n/de/ARCHITECTURE.md) | 🇮🇳 [हिन्दी](i18n/in/ARCHITECTURE.md) | 🇹🇭 [ไทย](i18n/th/ARCHITECTURE.md) | 🇺🇦 [Українська](i18n/uk-UA/ARCHITECTURE.md) | 🇸🇦 [العربية](i18n/ar/ARCHITECTURE.md) | 🇯🇵 [日本語](i18n/ja/ARCHITECTURE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/ARCHITECTURE.md) | 🇧🇬 [Български](i18n/bg/ARCHITECTURE.md) | 🇩🇰 [Dansk](i18n/da/ARCHITECTURE.md) | 🇫🇮 [Suomi](i18n/fi/ARCHITECTURE.md) | 🇮🇱 [עברית](i18n/he/ARCHITECTURE.md) | 🇭🇺 [Magyar](i18n/hu/ARCHITECTURE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/ARCHITECTURE.md) | 🇰🇷 [한국어](i18n/ko/ARCHITECTURE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/ARCHITECTURE.md) | 🇳🇱 [Nederlands](i18n/nl/ARCHITECTURE.md) | 🇳🇴 [Norsk](i18n/no/ARCHITECTURE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/ARCHITECTURE.md) | 🇷🇴 [Română](i18n/ro/ARCHITECTURE.md) | 🇵🇱 [Polski](i18n/pl/ARCHITECTURE.md) | 🇸🇰 [Slovenčina](i18n/sk/ARCHITECTURE.md) | 🇸🇪 [Svenska](i18n/sv/ARCHITECTURE.md) | 🇵🇭 [Filipino](i18n/phi/ARCHITECTURE.md) | 🇨🇿 [Čeština](i18n/cs/ARCHITECTURE.md)
|
||||
|
||||
_Last updated: 2026-03-28_
|
||||
_Last updated: 2026-04-15_
|
||||
|
||||
## Executive Summary
|
||||
|
||||
@@ -11,18 +11,27 @@ It provides a single OpenAI-compatible endpoint (`/v1/*`) and routes traffic acr
|
||||
|
||||
Core capabilities:
|
||||
|
||||
- OpenAI-compatible API surface for CLI/tools (28 providers)
|
||||
- OpenAI-compatible API surface for CLI/tools (160+ providers, 16 executors)
|
||||
- Request/response translation across provider formats
|
||||
- Model combo fallback (multi-model sequence)
|
||||
- Structured combo steps (`provider + model + connection`) with runtime ordering by `compositeTiers`
|
||||
- Account-level fallback (multi-account per provider)
|
||||
- OAuth + API-key provider connection management
|
||||
- Quota preflight and quota-aware P2C account selection in the main chat path
|
||||
- OAuth + API-key provider connection management (13 OAuth modules)
|
||||
- Embedding generation via `/v1/embeddings` (6 providers, 9 models)
|
||||
- Image generation via `/v1/images/generations` (4 providers, 9 models)
|
||||
- Image generation via `/v1/images/generations` (10+ providers, 20+ models)
|
||||
- Audio transcription via `/v1/audio/transcriptions` (7 providers)
|
||||
- Text-to-speech via `/v1/audio/speech` (10 providers)
|
||||
- Video generation via `/v1/videos/generations` (ComfyUI + SD WebUI)
|
||||
- Music generation via `/v1/music/generations` (ComfyUI)
|
||||
- Web search via `/v1/search` (5 providers)
|
||||
- Moderations via `/v1/moderations`
|
||||
- Reranking via `/v1/rerank`
|
||||
- Think tag parsing (`<think>...</think>`) for reasoning models
|
||||
- Response sanitization for strict OpenAI SDK compatibility
|
||||
- Role normalization (developer→system, system→user) for cross-provider compatibility
|
||||
- Structured output conversion (json_schema → Gemini responseSchema)
|
||||
- Local persistence for providers, keys, aliases, combos, settings, pricing
|
||||
- Local persistence for providers, keys, aliases, combos, settings, pricing (26 DB modules)
|
||||
- Usage/cost tracking and request logging
|
||||
- Optional cloud sync for multi-device/state sync
|
||||
- IP allowlist/blocklist for API access control
|
||||
@@ -33,15 +42,35 @@ Core capabilities:
|
||||
- Circuit breaker pattern for provider resilience
|
||||
- Anti-thundering herd protection with mutex locking
|
||||
- Signature-based request deduplication cache
|
||||
- Domain layer: model availability, cost rules, fallback policy, lockout policy
|
||||
- Domain layer: cost rules, fallback policy, lockout policy
|
||||
- Context Relay: session handoff summaries for account rotation continuity
|
||||
- Domain state persistence (SQLite write-through cache for fallbacks, budgets, lockouts, circuit breakers)
|
||||
- Policy engine for centralized request evaluation (lockout → budget → fallback)
|
||||
- Request telemetry with p50/p95/p99 latency aggregation
|
||||
- Combo target telemetry and historical combo target health via `combo_execution_key` / `combo_step_id`
|
||||
- Correlation ID (X-Request-Id) for end-to-end tracing
|
||||
- Compliance audit logging with opt-out per API key
|
||||
- Eval framework for LLM quality assurance
|
||||
- Resilience UI dashboard with real-time circuit breaker status
|
||||
- Modular OAuth providers (12 individual modules under `src/lib/oauth/providers/`)
|
||||
- Health dashboard with real-time provider circuit breaker status
|
||||
- MCP Server (29 tools) with 3 transports (stdio/SSE/Streamable HTTP)
|
||||
- A2A Server (JSON-RPC 2.0 + SSE) with skills and task lifecycle
|
||||
- Memory system (extraction, injection, retrieval, summarization)
|
||||
- Skills system (registry, executor, sandbox, built-in skills)
|
||||
- MITM proxy with certificate management and DNS handling
|
||||
- Prompt injection guard middleware
|
||||
- ACP (Agent Communication Protocol) registry
|
||||
- Modular OAuth providers (13 individual modules under `src/lib/oauth/providers/`)
|
||||
- Uninstall/full-uninstall scripts
|
||||
- OAuth environment repair action
|
||||
- WebSocket bridge for OpenAI-compatible WS clients (`/v1/ws`)
|
||||
- Sync token management (issue/revoke, ETag-versioned config bundle download)
|
||||
- GLM Thinking (`glmt`) first-class provider preset
|
||||
- Hybrid token counting (provider-side `/messages/count_tokens` with estimation fallback)
|
||||
- Model alias auto-seeding (30+ cross-proxy dialect normalizations at startup)
|
||||
- Safe outbound fetch with SSRF guard, private URL blocking, and configurable retry
|
||||
- Cooldown-aware chat retries with configurable `requestRetry` and `maxRetryIntervalSec`
|
||||
- Runtime environment validation with Zod at startup
|
||||
- Compliance audit v2 with pagination, provider CRUD events, and SSRF-blocked validation logging
|
||||
|
||||
Primary runtime model:
|
||||
|
||||
@@ -72,15 +101,15 @@ Main pages under `src/app/(dashboard)/dashboard/`:
|
||||
- `/dashboard` — quick start + provider overview
|
||||
- `/dashboard/endpoint` — endpoint proxy + MCP + A2A + API endpoint tabs
|
||||
- `/dashboard/providers` — provider connections and credentials
|
||||
- `/dashboard/combos` — combo strategies, templates, model routing rules
|
||||
- `/dashboard/combos` — combo strategies, templates, step-based builder, model routing rules, manual persisted ordering
|
||||
- `/dashboard/costs` — cost aggregation and pricing visibility
|
||||
- `/dashboard/analytics` — usage analytics and evaluations
|
||||
- `/dashboard/analytics` — usage analytics, evaluations, combo target health
|
||||
- `/dashboard/limits` — quota/rate controls
|
||||
- `/dashboard/cli-tools` — CLI onboarding, runtime detection, config generation
|
||||
- `/dashboard/agents` — detected ACP agents + custom agent registration
|
||||
- `/dashboard/media` — image/video/music playground
|
||||
- `/dashboard/search-tools` — search provider testing and history
|
||||
- `/dashboard/health` — uptime, circuit breakers, rate limits
|
||||
- `/dashboard/health` — uptime, circuit breakers, rate limits, quota-monitored sessions
|
||||
- `/dashboard/logs` — request/proxy/audit/console logs
|
||||
- `/dashboard/settings` — system settings tabs (general, routing, combo defaults, etc.)
|
||||
- `/dashboard/api-manager` — API key lifecycle and model permissions
|
||||
@@ -106,7 +135,7 @@ flowchart LR
|
||||
end
|
||||
|
||||
subgraph Upstreams[Upstream Providers]
|
||||
P1[OAuth Providers\nClaude/Codex/Gemini/Qwen/iFlow/GitHub/Kiro/Cursor/Antigravity]
|
||||
P1[OAuth Providers\nClaude/Codex/Gemini/Qwen/Qoder/GitHub/Kiro/Cursor/Antigravity]
|
||||
P2[API Key Providers\nOpenAI/Anthropic/OpenRouter/GLM/Kimi/MiniMax\nDeepSeek/Groq/xAI/Mistral/Perplexity\nTogether/Fireworks/Cerebras/Cohere/NVIDIA]
|
||||
P3[Compatible Nodes\nOpenAI-compatible / Anthropic-compatible]
|
||||
end
|
||||
@@ -176,16 +205,18 @@ Management domains:
|
||||
- System prompt: `src/app/api/settings/system-prompt` (GET/PUT)
|
||||
- Sessions: `src/app/api/sessions` (GET)
|
||||
- Rate limits: `src/app/api/rate-limits` (GET)
|
||||
- Resilience: `src/app/api/resilience` (GET/PATCH) — provider profiles, circuit breaker, rate limit state
|
||||
- Resilience reset: `src/app/api/resilience/reset` (POST) — reset breakers + cooldowns
|
||||
- Resilience: `src/app/api/resilience` (GET/PATCH) — request queue, connection cooldown, provider breaker, wait-for-cooldown config
|
||||
- Resilience reset: `src/app/api/resilience/reset` (POST) — reset provider breakers
|
||||
- Cache stats: `src/app/api/cache/stats` (GET/DELETE)
|
||||
- Model availability: `src/app/api/models/availability` (GET/POST)
|
||||
- Telemetry: `src/app/api/telemetry/summary` (GET)
|
||||
- Budget: `src/app/api/usage/budget` (GET/POST)
|
||||
- Fallback chains: `src/app/api/fallback/chains` (GET/POST/DELETE)
|
||||
- Compliance audit: `src/app/api/compliance/audit-log` (GET)
|
||||
- Compliance audit: `src/app/api/compliance/audit-log` (GET, with pagination + structured metadata)
|
||||
- Evals: `src/app/api/evals` (GET/POST), `src/app/api/evals/[suiteId]` (GET)
|
||||
- Policies: `src/app/api/policies` (GET/POST)
|
||||
- Sync tokens: `src/app/api/sync/tokens` (GET/POST), `src/app/api/sync/tokens/[id]` (GET/DELETE)
|
||||
- Config bundle: `src/app/api/sync/bundle` (GET, ETag-versioned snapshot of settings/providers/combos/keys)
|
||||
- WebSocket: `src/app/api/v1/ws/route.ts` — Upgrade handler for OpenAI-compatible WS clients
|
||||
|
||||
## 2) SSE + Translation Core
|
||||
|
||||
@@ -220,10 +251,19 @@ Services (business logic):
|
||||
- Wildcard model routing: `open-sse/services/wildcardRouter.ts`
|
||||
- Rate limit management: `open-sse/services/rateLimitManager.ts`
|
||||
- Circuit breaker: `open-sse/services/circuitBreaker.ts`
|
||||
- Context handoff: `open-sse/services/contextHandoff.ts` — handoff summary generation and injection for context-relay strategy
|
||||
- Codex quota fetcher: `open-sse/services/codexQuotaFetcher.ts` — fetches Codex quota for context-relay handoff decisions
|
||||
- Cooldown-aware retry: `src/sse/services/cooldownAwareRetry.ts` — per-model cooldown retries with configurable `requestRetry` / `maxRetryIntervalSec`
|
||||
- Safe outbound fetch: `src/shared/network/safeOutboundFetch.ts` — guarded provider/model fetch with SSRF guard, private-URL blocking, retry, and timeout
|
||||
- Outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — validates provider URLs against private/localhost CIDR ranges
|
||||
- Provider request defaults: `open-sse/services/providerRequestDefaults.ts` — provider-level `maxTokens`, `temperature`, `thinkingBudgetTokens` defaults
|
||||
- GLM provider constants: `open-sse/config/glmProvider.ts` — shared GLM models, quota URLs, GLMT timeout/defaults
|
||||
- Antigravity upstream: `open-sse/config/antigravityUpstream.ts` — base URL and discovery path constants
|
||||
- Codex client constants: `open-sse/config/codexClient.ts` — versioned user-agent and client-version values
|
||||
- Model alias seed: `src/lib/modelAliasSeed.ts` — seeds 30+ cross-proxy dialect aliases at startup
|
||||
|
||||
Domain layer modules:
|
||||
|
||||
- Model availability: `src/lib/domain/modelAvailability.ts`
|
||||
- Cost rules/budgets: `src/lib/domain/costRules.ts`
|
||||
- Fallback policy: `src/lib/domain/fallbackPolicy.ts`
|
||||
- Combo resolver: `src/lib/domain/comboResolver.ts`
|
||||
@@ -237,10 +277,10 @@ Domain layer modules:
|
||||
- Eval runner: `src/lib/domain/evalRunner.ts`
|
||||
- Domain state persistence: `src/lib/db/domainState.ts` — SQLite CRUD for fallback chains, budgets, cost history, lockout state, circuit breakers
|
||||
|
||||
OAuth provider modules (12 individual files under `src/lib/oauth/providers/`):
|
||||
OAuth provider modules (13 individual files under `src/lib/oauth/providers/`):
|
||||
|
||||
- Registry index: `src/lib/oauth/providers/index.ts`
|
||||
- Individual providers: `claude.ts`, `codex.ts`, `gemini.ts`, `antigravity.ts`, `iflow.ts`, `qwen.ts`, `kimi-coding.ts`, `github.ts`, `kiro.ts`, `cursor.ts`, `kilocode.ts`, `cline.ts`
|
||||
- Individual providers: `claude.ts`, `codex.ts`, `gemini.ts`, `antigravity.ts`, `qoder.ts`, `qwen.ts`, `kimi-coding.ts`, `github.ts`, `kiro.ts`, `cursor.ts`, `kilocode.ts`, `cline.ts`
|
||||
- Thin wrapper: `src/lib/oauth/providers.ts` — re-exports from individual modules
|
||||
|
||||
## 3) Persistence Layer
|
||||
@@ -271,6 +311,10 @@ Domain State DB (SQLite):
|
||||
- API key generation/verification: `src/shared/utils/apiKey.ts`
|
||||
- Provider secrets persisted in `providerConnections` entries
|
||||
- Outbound proxy support via `open-sse/utils/proxyFetch.ts` (env vars) and `open-sse/utils/networkProxy.ts` (configurable per-provider or global)
|
||||
- SSRF / outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — blocks private/loopback/link-local ranges for all provider calls
|
||||
- Runtime env validation: `src/lib/env/runtimeEnv.ts` — Zod schema for all environment variables, surfaced as startup errors/warnings
|
||||
- Sync tokens: `src/lib/db/syncTokens.ts` — scoped tokens for config bundle download endpoints; backed by `sync_tokens` SQLite table (migration `024_create_sync_tokens.sql`)
|
||||
- WebSocket handshake auth: `src/lib/ws/handshake.ts` — validates WS upgrade requests via API key or session cookie
|
||||
|
||||
## 5) Cloud Sync
|
||||
|
||||
@@ -588,6 +632,10 @@ flowchart LR
|
||||
- `src/app/api/settings/system-prompt`: global system prompt (GET/PUT)
|
||||
- `src/app/api/sessions`: active session listing (GET)
|
||||
- `src/app/api/rate-limits`: per-account rate limit status (GET)
|
||||
- `src/app/api/sync/tokens`: sync token CRUD (GET/POST)
|
||||
- `src/app/api/sync/tokens/[id]`: sync token get/delete (GET/DELETE)
|
||||
- `src/app/api/sync/bundle`: config bundle download (GET, ETag versioning)
|
||||
- `src/app/api/v1/ws`: WebSocket upgrade handler for OpenAI-compatible WS clients
|
||||
|
||||
### Routing and Execution Core
|
||||
|
||||
@@ -612,15 +660,22 @@ flowchart LR
|
||||
|
||||
Each provider has a specialized executor extending `BaseExecutor` (in `open-sse/executors/base.ts`), which provides URL building, header construction, retry with exponential backoff, credential refresh hooks, and the `execute()` orchestration method.
|
||||
|
||||
| Executor | Provider(s) | Special Handling |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
|
||||
| `DefaultExecutor` | OpenAI, Claude, Gemini, Qwen, iFlow, OpenRouter, GLM, Kimi, MiniMax, DeepSeek, Groq, xAI, Mistral, Perplexity, Together, Fireworks, Cerebras, Cohere, NVIDIA | Dynamic URL/header config per provider |
|
||||
| `AntigravityExecutor` | Google Antigravity | Custom project/session IDs, Retry-After parsing |
|
||||
| `CodexExecutor` | OpenAI Codex | Injects system instructions, forces reasoning effort |
|
||||
| `CursorExecutor` | Cursor IDE | ConnectRPC protocol, Protobuf encoding, request signing via checksum |
|
||||
| `GithubExecutor` | GitHub Copilot | Copilot token refresh, VSCode-mimicking headers |
|
||||
| `KiroExecutor` | AWS CodeWhisperer/Kiro | AWS EventStream binary format → SSE conversion |
|
||||
| `GeminiCLIExecutor` | Gemini CLI | Google OAuth token refresh cycle |
|
||||
| Executor | Provider(s) | Special Handling |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `DefaultExecutor` | OpenAI, Claude, Gemini, Qwen, OpenRouter, GLM, Kimi, MiniMax, DeepSeek, Groq, xAI, Mistral, Perplexity, Together, Fireworks, Cerebras, Cohere, NVIDIA, etc. | Dynamic URL/header config per provider |
|
||||
| `AntigravityExecutor` | Google Antigravity | Custom project/session IDs, Retry-After parsing |
|
||||
| `CliProxyApiExecutor` | CLIProxyAPI-compatible providers | Custom auth and protocol handling |
|
||||
| `CloudflareAiExecutor` | Cloudflare Workers AI | Account ID injection, Neurons-based usage tracking |
|
||||
| `CodexExecutor` | OpenAI Codex | Injects system instructions, forces reasoning effort |
|
||||
| `CursorExecutor` | Cursor IDE | ConnectRPC protocol, Protobuf encoding, request signing via checksum |
|
||||
| `GithubExecutor` | GitHub Copilot | Copilot token refresh, VSCode-mimicking headers |
|
||||
| `GeminiCLIExecutor` | Gemini CLI | Google OAuth token refresh cycle |
|
||||
| `KiroExecutor` | AWS CodeWhisperer/Kiro | AWS EventStream binary format → SSE conversion |
|
||||
| `OpenCodeExecutor` | OpenCode | AI SDK compatible provider setup |
|
||||
| `PollinationsExecutor` | Pollinations AI | No API key required, rate-limited requests |
|
||||
| `PuterExecutor` | Puter | Browser-based provider integration |
|
||||
| `QoderExecutor` | Qoder AI | PAT and OAuth support, multi-model free tier |
|
||||
| `VertexExecutor` | Google Vertex AI | Service account auth, region-based endpoints |
|
||||
|
||||
All other providers (including custom compatible nodes) use the `DefaultExecutor`.
|
||||
|
||||
@@ -638,7 +693,10 @@ All other providers (including custom compatible nodes) use the `DefaultExecutor
|
||||
| Cursor | cursor | Custom checksum | ✅ | ✅ | ❌ | ❌ |
|
||||
| Kiro | kiro | AWS SSO OIDC | ✅ (EventStream) | ❌ | ✅ | ✅ Usage limits |
|
||||
| Qwen | openai | OAuth | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| iFlow | openai | OAuth (Basic) | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| Kilo Code | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| Cline | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| Kimi Coding | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| OpenRouter | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| GLM/Kimi/MiniMax | claude | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| DeepSeek | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
@@ -651,6 +709,17 @@ All other providers (including custom compatible nodes) use the `DefaultExecutor
|
||||
| Cerebras | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Cohere | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| NVIDIA NIM | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Cloudflare AI | openai | API Token + Acct ID | ✅ | ✅ | ❌ | ❌ |
|
||||
| Pollinations | openai | None (no key) | ✅ | ✅ | ❌ | ❌ |
|
||||
| Scaleway AI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| LongCat | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Ollama Cloud | openai | API Key (optional) | ✅ | ✅ | ❌ | ❌ |
|
||||
| HuggingFace | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Nebius | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| SiliconFlow | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Hyperbolic | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Vertex AI | gemini | Service Account | ✅ | ✅ | ✅ | ⚠️ Cloud Console |
|
||||
| Puter | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
|
||||
## Format Translation Coverage
|
||||
|
||||
@@ -686,46 +755,44 @@ Additional processing layers in the translation pipeline:
|
||||
|
||||
## Supported API Endpoints
|
||||
|
||||
| Endpoint | Format | Handler |
|
||||
| -------------------------------------------------- | ------------------ | ---------------------------------------------------- |
|
||||
| `POST /v1/chat/completions` | OpenAI Chat | `src/sse/handlers/chat.ts` |
|
||||
| `POST /v1/messages` | Claude Messages | Same handler (auto-detected) |
|
||||
| `POST /v1/responses` | OpenAI Responses | `open-sse/handlers/responsesHandler.ts` |
|
||||
| `POST /v1/embeddings` | OpenAI Embeddings | `open-sse/handlers/embeddings.ts` |
|
||||
| `GET /v1/embeddings` | Model listing | API route |
|
||||
| `POST /v1/images/generations` | OpenAI Images | `open-sse/handlers/imageGeneration.ts` |
|
||||
| `GET /v1/images/generations` | Model listing | API route |
|
||||
| `POST /v1/providers/{provider}/chat/completions` | OpenAI Chat | Dedicated per-provider with model validation |
|
||||
| `POST /v1/providers/{provider}/embeddings` | OpenAI Embeddings | Dedicated per-provider with model validation |
|
||||
| `POST /v1/providers/{provider}/images/generations` | OpenAI Images | Dedicated per-provider with model validation |
|
||||
| `POST /v1/messages/count_tokens` | Claude Token Count | API route |
|
||||
| `GET /v1/models` | OpenAI Models list | API route (chat + embedding + image + custom models) |
|
||||
| `GET /api/models/catalog` | Catalog | All models grouped by provider + type |
|
||||
| `POST /v1beta/models/*:streamGenerateContent` | Gemini native | API route |
|
||||
| `GET/PUT/DELETE /api/settings/proxy` | Proxy Config | Network proxy configuration |
|
||||
| `POST /api/settings/proxy/test` | Proxy Connectivity | Proxy health/connectivity test endpoint |
|
||||
| `GET/POST/DELETE /api/provider-models` | Custom Models | Custom model management per provider |
|
||||
| Endpoint | Format | Handler |
|
||||
| -------------------------------------------------- | ------------------ | ------------------------------------------------------------------- |
|
||||
| `POST /v1/chat/completions` | OpenAI Chat | `src/sse/handlers/chat.ts` |
|
||||
| `POST /v1/messages` | Claude Messages | Same handler (auto-detected) |
|
||||
| `POST /v1/responses` | OpenAI Responses | `open-sse/handlers/responsesHandler.ts` |
|
||||
| `POST /v1/embeddings` | OpenAI Embeddings | `open-sse/handlers/embeddings.ts` |
|
||||
| `GET /v1/embeddings` | Model listing | API route |
|
||||
| `POST /v1/images/generations` | OpenAI Images | `open-sse/handlers/imageGeneration.ts` |
|
||||
| `GET /v1/images/generations` | Model listing | API route |
|
||||
| `POST /v1/providers/{provider}/chat/completions` | OpenAI Chat | Dedicated per-provider with model validation |
|
||||
| `POST /v1/providers/{provider}/embeddings` | OpenAI Embeddings | Dedicated per-provider with model validation |
|
||||
| `POST /v1/providers/{provider}/images/generations` | OpenAI Images | Dedicated per-provider with model validation |
|
||||
| `POST /v1/messages/count_tokens` | Claude Token Count | API route |
|
||||
| `GET /v1/models` | OpenAI Models list | API route (chat + embedding + image + custom models) |
|
||||
| `GET /api/models/catalog` | Catalog | All models grouped by provider + type |
|
||||
| `POST /v1beta/models/*:streamGenerateContent` | Gemini native | API route |
|
||||
| `GET/PUT/DELETE /api/settings/proxy` | Proxy Config | Network proxy configuration |
|
||||
| `POST /api/settings/proxy/test` | Proxy Connectivity | Proxy health/connectivity test endpoint |
|
||||
| `GET/POST/DELETE /api/provider-models` | Provider Models | Provider model metadata backing custom and managed available models |
|
||||
|
||||
## Bypass Handler
|
||||
|
||||
The bypass handler (`open-sse/utils/bypassHandler.ts`) intercepts known "throwaway" requests from Claude CLI — warmup pings, title extractions, and token counts — and returns a **fake response** without consuming upstream provider tokens. This is triggered only when `User-Agent` contains `claude-cli`.
|
||||
|
||||
## Request Logger Pipeline
|
||||
## Request Logging and Artifacts
|
||||
|
||||
The request logger (`open-sse/utils/requestLogger.ts`) provides a 7-stage debug logging pipeline, disabled by default, enabled via `ENABLE_REQUEST_LOGS=true`:
|
||||
The older file-based request logger (`open-sse/utils/requestLogger.ts`) is retained only for
|
||||
legacy compatibility. The current runtime contract uses:
|
||||
|
||||
```
|
||||
1_req_client.json → 2_req_source.json → 3_req_openai.json → 4_req_target.json
|
||||
→ 5_res_provider.txt → 6_res_openai.txt → 7_res_client.txt
|
||||
```
|
||||
|
||||
Files are written to `<repo>/logs/<session>/` for each request session.
|
||||
- `APP_LOG_TO_FILE=true` for application and audit logs written under `<repo>/logs/`
|
||||
- SQLite-backed call log records in `call_logs`
|
||||
- `${DATA_DIR}/call_logs/YYYY-MM-DD/...` artifacts when the call log pipeline is enabled
|
||||
|
||||
## Failure Modes and Resilience
|
||||
|
||||
## 1) Account/Provider Availability
|
||||
|
||||
- provider account cooldown on transient/rate/auth errors
|
||||
- connection cooldown on retryable upstream failures
|
||||
- account fallback before failing request
|
||||
- combo model fallback when current model/provider path is exhausted
|
||||
|
||||
@@ -750,6 +817,12 @@ Files are written to `<repo>/logs/<session>/` for each request session.
|
||||
- SQLite schema migrations and auto-upgrade hooks at startup
|
||||
- legacy JSON → SQLite migration compatibility path
|
||||
|
||||
## 6) SSRF / Outbound URL Guard
|
||||
|
||||
- `src/shared/network/outboundUrlGuard.ts` blocks all private/loopback/link-local target URLs before they reach provider executors
|
||||
- Provider model discovery and validation routes use `src/shared/network/safeOutboundFetch.ts` which applies the guard before every outbound request
|
||||
- Guard errors surface as `URL_GUARD_BLOCKED` with HTTP 422 and are logged to the compliance audit trail via `providerAudit.ts`
|
||||
|
||||
## Observability and Operational Signals
|
||||
|
||||
Runtime visibility sources:
|
||||
@@ -758,15 +831,16 @@ Runtime visibility sources:
|
||||
- per-request usage aggregates in SQLite (`usage_history`, `call_logs`, `proxy_logs`)
|
||||
- four-stage detailed payload captures in SQLite (`request_detail_logs`) when `settings.detailed_logs_enabled=true`
|
||||
- textual request status log in `log.txt` (optional/compat)
|
||||
- optional deep request/translation logs under `logs/` when `ENABLE_REQUEST_LOGS=true`
|
||||
- optional application log files under `logs/` when `APP_LOG_TO_FILE=true`
|
||||
- optional request artifacts under `${DATA_DIR}/call_logs/` when the call log pipeline is enabled
|
||||
- dashboard usage endpoints (`/api/usage/*`) for UI consumption
|
||||
|
||||
Detailed request payload capture stores up to four JSON payload stages per routed call:
|
||||
|
||||
- raw request received from the client
|
||||
- translated request actually sent upstream
|
||||
- provider response reconstructed as JSON (including streamed event sequences when applicable)
|
||||
- final client response returned by OmniRoute
|
||||
- provider response reconstructed as JSON; streamed responses are compacted to the final summary plus stream metadata
|
||||
- final client response returned by OmniRoute; streamed responses are stored in the same compact summary form
|
||||
|
||||
## Security-Sensitive Boundaries
|
||||
|
||||
@@ -785,7 +859,7 @@ Environment variables actively used by code:
|
||||
- Compatible node behavior: `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE`
|
||||
- Optional storage base override (Linux/macOS when `DATA_DIR` unset): `XDG_CONFIG_HOME`
|
||||
- Security hashing: `API_KEY_SECRET`, `MACHINE_ID_SALT`
|
||||
- Logging: `ENABLE_REQUEST_LOGS`
|
||||
- Logging: `APP_LOG_TO_FILE`, `APP_LOG_RETENTION_DAYS`, `CALL_LOG_RETENTION_DAYS`
|
||||
- Sync/cloud URLing: `NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_CLOUD_URL`
|
||||
- Outbound proxy: `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY` and lowercase variants
|
||||
- SOCKS5 feature flags: `ENABLE_SOCKS5_PROXY`, `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY`
|
||||
@@ -800,7 +874,10 @@ Environment variables actively used by code:
|
||||
5. The `open-sse/` directory is published as the `@omniroute/open-sse` **npm workspace package**. Source code imports it via `@omniroute/open-sse/...` (resolved by Next.js `transpilePackages`). File paths in this document still use the directory name `open-sse/` for consistency.
|
||||
6. Charts in the dashboard use **Recharts** (SVG-based) for accessible, interactive analytics visualizations (model usage bar charts, provider breakdown tables with success rates).
|
||||
7. E2E tests use **Playwright** (`tests/e2e/`), run via `npm run test:e2e`. Unit tests use **Node.js test runner** (`tests/unit/`), run via `npm run test:unit`. Source code under `src/` is **TypeScript** (`.ts`/`.tsx`); the `open-sse/` workspace remains JavaScript (`.js`).
|
||||
8. Settings page is organized into 5 tabs: Security, Routing (6 global strategies: fill-first, round-robin, p2c, random, least-used, cost-optimized), Resilience (editable rate limits, circuit breaker, policies), AI (thinking budget, system prompt, prompt cache), Advanced (proxy).
|
||||
8. Settings page is organized into 7 tabs: General, Appearance, AI, Security, Routing, Resilience, Advanced. The Resilience page only configures request queue, connection cooldown, provider breaker, and wait-for-cooldown behavior; live breaker runtime state is shown on the Health page.
|
||||
9. **Context Relay** strategy (`context-relay`) is split across two layers: `combo.ts` decides if a handoff should be generated, `chat.ts` injects the handoff after account resolution. Handoff data lives in `context_handoffs` SQLite table. This split is intentional because only `chat.ts` knows whether the actual account changed.
|
||||
10. **Proxy enforcement** is now comprehensive: `tokenHealthCheck.ts` resolves proxy per connection, `/api/providers/validate` uses `runWithProxyContext`, and `proxyFetch.ts` uses `undici.fetch()` to maintain dispatcher compatibility on Node 22.
|
||||
11. **Node.js runtime policy detection**: `/api/settings/require-login` returns `nodeVersion` and `nodeCompatible` fields. The login page renders a warning banner when the runtime falls outside the supported secure Node.js lines.
|
||||
|
||||
## Operational Verification Checklist
|
||||
|
||||
|
||||
@@ -32,31 +32,32 @@ Claude / Codex / OpenCode / Cline / KiloCode / Continue / Kiro / Cursor / Copilo
|
||||
The dashboard cards in `/dashboard/cli-tools` are generated from `src/shared/constants/cliTools.ts`.
|
||||
Current list (v3.0.0-rc.16):
|
||||
|
||||
| Tool | ID | Command | Setup Mode | Install Method |
|
||||
| ---------------- | ------------- | ------------ | ---------- | -------------- |
|
||||
| **Claude Code** | `claude` | `claude` | env | npm |
|
||||
| **OpenAI Codex** | `codex` | `codex` | custom | npm |
|
||||
| **Factory Droid**| `droid` | `droid` | custom | bundled/CLI |
|
||||
| **OpenClaw** | `openclaw` | `openclaw` | custom | bundled/CLI |
|
||||
| **Cursor** | `cursor` | app | guide | desktop app |
|
||||
| **Cline** | `cline` | `cline` | custom | npm |
|
||||
| **Kilo Code** | `kilo` | `kilocode` | custom | npm |
|
||||
| **Continue** | `continue` | extension | guide | VS Code |
|
||||
| **Antigravity** | `antigravity` | internal | mitm | OmniRoute |
|
||||
| **GitHub Copilot**| `copilot` | extension | custom | VS Code |
|
||||
| **OpenCode** | `opencode` | `opencode` | guide | npm |
|
||||
| **Kiro AI** | `kiro` | app/cli | mitm | desktop/CLI |
|
||||
| Tool | ID | Command | Setup Mode | Install Method |
|
||||
| ------------------ | ------------- | ---------- | ---------- | -------------- |
|
||||
| **Claude Code** | `claude` | `claude` | env | npm |
|
||||
| **OpenAI Codex** | `codex` | `codex` | custom | npm |
|
||||
| **Factory Droid** | `droid` | `droid` | custom | bundled/CLI |
|
||||
| **OpenClaw** | `openclaw` | `openclaw` | custom | bundled/CLI |
|
||||
| **Cursor** | `cursor` | app | guide | desktop app |
|
||||
| **Cline** | `cline` | `cline` | custom | npm |
|
||||
| **Kilo Code** | `kilo` | `kilocode` | custom | npm |
|
||||
| **Continue** | `continue` | extension | guide | VS Code |
|
||||
| **Antigravity** | `antigravity` | internal | mitm | OmniRoute |
|
||||
| **GitHub Copilot** | `copilot` | extension | custom | VS Code |
|
||||
| **OpenCode** | `opencode` | `opencode` | guide | npm |
|
||||
| **Kiro AI** | `kiro` | app/cli | mitm | desktop/CLI |
|
||||
| **Qwen Code** | `qwen` | `qwen` | custom | npm |
|
||||
|
||||
### CLI fingerprint sync (Agents + Settings)
|
||||
|
||||
`/dashboard/agents` and `Settings > CLI Fingerprint` use `src/shared/constants/cliCompatProviders.ts`.
|
||||
This keeps provider IDs aligned with CLI cards and legacy IDs.
|
||||
|
||||
| CLI ID | Fingerprint Provider ID |
|
||||
| ------ | ----------------------- |
|
||||
| `kilo` | `kilocode` |
|
||||
| `copilot` | `github` |
|
||||
| `claude` / `codex` / `antigravity` / `kiro` / `cursor` / `cline` / `opencode` / `droid` / `openclaw` | same ID |
|
||||
| CLI ID | Fingerprint Provider ID |
|
||||
| ---------------------------------------------------------------------------------------------------- | ----------------------- |
|
||||
| `kilo` | `kilocode` |
|
||||
| `copilot` | `github` |
|
||||
| `claude` / `codex` / `antigravity` / `kiro` / `cursor` / `cline` / `opencode` / `droid` / `openclaw` | same ID |
|
||||
|
||||
Legacy IDs still accepted for compatibility: `copilot`, `kimi-coding`, `qwen`.
|
||||
|
||||
@@ -120,8 +121,8 @@ Add to `~/.bashrc` (or `~/.zshrc`), then run `source ~/.bashrc`:
|
||||
# OmniRoute Universal Endpoint
|
||||
export OPENAI_BASE_URL="http://localhost:20128/v1"
|
||||
export OPENAI_API_KEY="sk-your-omniroute-key"
|
||||
export ANTHROPIC_BASE_URL="http://localhost:20128/v1"
|
||||
export ANTHROPIC_API_KEY="sk-your-omniroute-key"
|
||||
export ANTHROPIC_BASE_URL="http://localhost:20128"
|
||||
export ANTHROPIC_AUTH_TOKEN="sk-your-omniroute-key"
|
||||
export GEMINI_BASE_URL="http://localhost:20128/v1"
|
||||
export GEMINI_API_KEY="sk-your-omniroute-key"
|
||||
```
|
||||
@@ -136,18 +137,19 @@ export GEMINI_API_KEY="sk-your-omniroute-key"
|
||||
### Claude Code
|
||||
|
||||
```bash
|
||||
# Via CLI:
|
||||
claude config set --global api-base-url http://localhost:20128/v1
|
||||
|
||||
# Or create ~/.claude/settings.json:
|
||||
# Create ~/.claude/settings.json:
|
||||
mkdir -p ~/.claude && cat > ~/.claude/settings.json << EOF
|
||||
{
|
||||
"apiBaseUrl": "http://localhost:20128/v1",
|
||||
"apiKey": "sk-your-omniroute-key"
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "http://localhost:20128",
|
||||
"ANTHROPIC_AUTH_TOKEN": "sk-your-omniroute-key"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
Use the unified Anthropic gateway root for Claude Code. Do not append `/v1` here.
|
||||
|
||||
**Test:** `claude "say hello"`
|
||||
|
||||
---
|
||||
@@ -253,6 +255,55 @@ kiro-cli status
|
||||
|
||||
---
|
||||
|
||||
### Qwen Code (Alibaba)
|
||||
|
||||
Qwen Code supports OpenAI-compatible API endpoints via environment variables or `settings.json`.
|
||||
|
||||
**Option 1: Environment variables (`~/.qwen/.env`)**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.qwen && cat > ~/.qwen/.env << EOF
|
||||
OPENAI_API_KEY="sk-your-omniroute-key"
|
||||
OPENAI_BASE_URL="http://localhost:20128/v1"
|
||||
OPENAI_MODEL="auto"
|
||||
EOF
|
||||
```
|
||||
|
||||
**Option 2: `settings.json` with model providers**
|
||||
|
||||
```json
|
||||
// ~/.qwen/settings.json
|
||||
{
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "sk-your-omniroute-key",
|
||||
"OPENAI_BASE_URL": "http://localhost:20128/v1"
|
||||
},
|
||||
"modelProviders": {
|
||||
"openai": [
|
||||
{
|
||||
"id": "omniroute-default",
|
||||
"name": "OmniRoute (Auto)",
|
||||
"envKey": "OPENAI_API_KEY",
|
||||
"baseUrl": "http://localhost:20128/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Option 3: Inline CLI flags**
|
||||
|
||||
```bash
|
||||
OPENAI_BASE_URL="http://localhost:20128/v1" \
|
||||
OPENAI_API_KEY="sk-your-omniroute-key" \
|
||||
OPENAI_MODEL="auto" \
|
||||
qwen
|
||||
```
|
||||
|
||||
> For a **remote server** replace `localhost:20128` with the server IP or domain.
|
||||
|
||||
**Test:** `qwen "say hello"`
|
||||
|
||||
### Cursor (Desktop App)
|
||||
|
||||
> **Note:** Cursor routes requests through its cloud. For OmniRoute integration,
|
||||
@@ -320,9 +371,10 @@ They run as internal routes and use OmniRoute's model routing automatically.
|
||||
```bash
|
||||
# Install all CLIs and configure for OmniRoute (replace with your key and server URL)
|
||||
OMNIROUTE_URL="http://localhost:20128/v1"
|
||||
OMNIROUTE_ANTHROPIC_URL="http://localhost:20128"
|
||||
OMNIROUTE_KEY="sk-your-omniroute-key"
|
||||
|
||||
npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai cline kilocode
|
||||
npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai cline kilocode @qwen-code/qwen-code
|
||||
|
||||
# Kiro CLI
|
||||
apt-get install -y unzip 2>/dev/null; curl -fsSL https://cli.kiro.dev/install | bash
|
||||
@@ -330,13 +382,13 @@ apt-get install -y unzip 2>/dev/null; curl -fsSL https://cli.kiro.dev/install |
|
||||
# Write configs
|
||||
mkdir -p ~/.claude ~/.codex ~/.config/opencode ~/.continue
|
||||
|
||||
cat > ~/.claude/settings.json <<< "{\"apiBaseUrl\":\"$OMNIROUTE_URL\",\"apiKey\":\"$OMNIROUTE_KEY\"}"
|
||||
cat > ~/.claude/settings.json <<< "{\"env\":{\"ANTHROPIC_BASE_URL\":\"$OMNIROUTE_ANTHROPIC_URL\",\"ANTHROPIC_AUTH_TOKEN\":\"$OMNIROUTE_KEY\"}}"
|
||||
cat > ~/.codex/config.yaml <<< "model: auto\napiKey: $OMNIROUTE_KEY\napiBaseUrl: $OMNIROUTE_URL"
|
||||
cat >> ~/.bashrc << EOF
|
||||
export OPENAI_BASE_URL="$OMNIROUTE_URL"
|
||||
export OPENAI_API_KEY="$OMNIROUTE_KEY"
|
||||
export ANTHROPIC_BASE_URL="$OMNIROUTE_URL"
|
||||
export ANTHROPIC_API_KEY="$OMNIROUTE_KEY"
|
||||
export ANTHROPIC_BASE_URL="$OMNIROUTE_ANTHROPIC_URL"
|
||||
export ANTHROPIC_AUTH_TOKEN="$OMNIROUTE_KEY"
|
||||
EOF
|
||||
|
||||
source ~/.bashrc
|
||||
|
||||
@@ -267,7 +267,7 @@ Business logic that supports the handlers and executors.
|
||||
| `provider.ts` | **Format detection** (`detectFormat`): analyzes request body structure to identify Claude/OpenAI/Gemini/Antigravity/Responses formats (includes `max_tokens` heuristic for Claude). Also: URL building, header building, thinking config normalization. Supports `openai-compatible-*` and `anthropic-compatible-*` dynamic providers. |
|
||||
| `model.ts` | Model string parsing (`claude/model-name` → `{provider: "claude", model: "model-name"}`), alias resolution with collision detection, input sanitization (rejects path traversal/control chars), and model info resolution with async alias getter support. |
|
||||
| `accountFallback.ts` | Rate-limit handling: exponential backoff (1s → 2s → 4s → max 2min), account cooldown management, error classification (which errors trigger fallback vs. not). |
|
||||
| `tokenRefresh.ts` | OAuth token refresh for **every provider**: Google (Gemini, Antigravity), Claude, Codex, Qwen, iFlow, GitHub (OAuth + Copilot dual-token), Kiro (AWS SSO OIDC + Social Auth). Includes in-flight promise deduplication cache and retry with exponential backoff. |
|
||||
| `tokenRefresh.ts` | OAuth token refresh for **every provider**: Google (Gemini, Antigravity), Claude, Codex, Qwen, Qoder, GitHub (OAuth + Copilot dual-token), Kiro (AWS SSO OIDC + Social Auth). Includes in-flight promise deduplication cache and retry with exponential backoff. |
|
||||
| `combo.ts` | **Combo models**: chains of fallback models. If model A fails with a fallback-eligible error, try model B, then C, etc. Returns actual upstream status codes. |
|
||||
| `usage.ts` | Fetches quota/usage data from provider APIs (GitHub Copilot quotas, Antigravity model quotas, Codex rate limits, Kiro usage breakdowns, Claude settings). |
|
||||
| `accountSelector.ts` | Smart account selection with scoring algorithm: considers priority, health status, round-robin position, and cooldown state to pick the optimal account for each request. |
|
||||
@@ -403,7 +403,7 @@ import "./request/claude-to-openai.js"; // ← self-registers
|
||||
| `stream.ts` | **SSE Transform Stream** — the core streaming pipeline. Two modes: `TRANSLATE` (full format translation) and `PASSTHROUGH` (normalize + extract usage). Handles chunk buffering, usage estimation, content length tracking. Per-stream encoder/decoder instances avoid shared state. |
|
||||
| `streamHelpers.ts` | Low-level SSE utilities: `parseSSELine` (whitespace-tolerant), `hasValuableContent` (filters empty chunks for OpenAI/Claude/Gemini), `fixInvalidId`, `formatSSE` (format-aware SSE serialization with `perf_metrics` cleanup). |
|
||||
| `usageTracking.ts` | Token usage extraction from any format (Claude/OpenAI/Gemini/Responses), estimation with separate tool/message char-per-token ratios, buffer addition (2000 tokens safety margin), format-specific field filtering, console logging with ANSI colors. |
|
||||
| `requestLogger.ts` | File-based request logging (opt-in via `ENABLE_REQUEST_LOGS=true`). Creates session folders with numbered files: `1_req_client.json` → `7_res_client.txt`. All I/O is async (fire-and-forget). Masks sensitive headers. |
|
||||
| `requestLogger.ts` | Legacy file-based request logging helper kept for compatibility. Current deployments should prefer `APP_LOG_TO_FILE` for application logs and the call log pipeline for persisted request artifacts. |
|
||||
| `bypassHandler.ts` | Intercepts specific patterns from Claude CLI (title extraction, warmup, count) and returns fake responses without calling any provider. Supports both streaming and non-streaming. Intentionally limited to Claude CLI scope. |
|
||||
| `networkProxy.ts` | Resolves outbound proxy URL for a given provider with precedence: provider-specific config → global config → environment variables (`HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY`). Supports `NO_PROXY` exclusions. Caches config for 30s. |
|
||||
|
||||
@@ -539,7 +539,7 @@ A 2000-token buffer is added to reported usage to prevent clients from hitting c
|
||||
| Kiro (AWS) | AWS SSO OIDC or Social | Kiro | Binary EventStream parsing |
|
||||
| Cursor IDE | Checksum auth | Cursor | Protobuf encoding, SHA-256 checksums |
|
||||
| Qwen | OAuth | Default | Standard auth |
|
||||
| iFlow | OAuth (Basic + Bearer) | Default | Dual auth header |
|
||||
| Qoder | OAuth (Basic + Bearer) | Default | Dual auth header |
|
||||
| OpenRouter | API key | Default | Standard Bearer auth |
|
||||
| GLM, Kimi, MiniMax | API key | Default | Claude-compatible, use `x-api-key` |
|
||||
| `openai-compatible-*` | API key | Default | Dynamic: any OpenAI-compatible endpoint |
|
||||
|
||||
183
docs/COMPRESSION_GUIDE.md
Normal file
183
docs/COMPRESSION_GUIDE.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 🗜️ Prompt Compression Guide — OmniRoute
|
||||
|
||||
> Save 15-75% on token costs automatically. For a quick overview, see the [README Compression section](../README.md#%EF%B8%8F-prompt-compression--save-15-75-tokens-automatically).
|
||||
|
||||
## Overview
|
||||
|
||||
OmniRoute implements a modular prompt compression pipeline that runs **proactively** before requests hit upstream providers. This means your token savings happen transparently — no changes needed to your workflow.
|
||||
|
||||
```
|
||||
Client Request
|
||||
→ Compression Strategy Selector
|
||||
→ Combo override? → Use combo setting
|
||||
→ Auto-trigger threshold? → Use auto mode
|
||||
→ Default mode? → Use global setting
|
||||
→ Off? → Skip compression
|
||||
→ Selected Compression Mode
|
||||
→ Off: No compression
|
||||
→ Lite: Safe whitespace/formatting cleanup (~15%)
|
||||
→ Standard: Caveman-speak filler removal (~30%)
|
||||
→ Aggressive: History aging + summarization (~50%)
|
||||
→ Ultra: Heuristic pruning + code-block thinning (~75%)
|
||||
→ Compressed Request → Provider
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compression Modes
|
||||
|
||||
### Off
|
||||
|
||||
No compression applied. All messages pass through unchanged.
|
||||
|
||||
### Lite Mode (~15% savings, <1ms latency)
|
||||
|
||||
The safest mode — zero semantic change, only formatting cleanup:
|
||||
|
||||
| Technique | Description |
|
||||
| ------------------------ | ------------------------------------------------- |
|
||||
| `collapseWhitespace` | Merge consecutive blank lines and trailing spaces |
|
||||
| `dedupSystemPrompt` | Remove duplicate system messages |
|
||||
| `compressToolResults` | Compress verbose tool/function outputs |
|
||||
| `removeRedundantContent` | Strip repeated instructions |
|
||||
| `replaceImageUrls` | Shorten base64 image data URIs |
|
||||
|
||||
**Best for:** Always-on usage, safety-critical workflows.
|
||||
|
||||
### Standard Mode (~30% savings)
|
||||
|
||||
Inspired by [Caveman](https://github.com/JuliusBrussee/caveman) — removes filler words and verbose phrasing while preserving meaning:
|
||||
|
||||
- Removes filler words ("please", "I think", "basically", "actually")
|
||||
- Condenses verbose phrases ("in order to" → "to", "as a result of" → "because")
|
||||
- Strips polite hedging ("Would you mind...", "If you could possibly...")
|
||||
- 30+ regex rules tuned for coding prompts
|
||||
|
||||
**Best for:** Daily coding workflows, cost-conscious teams.
|
||||
|
||||
### Aggressive Mode (~50% savings)
|
||||
|
||||
Smart history management for long sessions:
|
||||
|
||||
- **Message Aging** — older messages get progressively compressed
|
||||
- **Tool Result Summarization** — long tool outputs replaced with summaries
|
||||
- **Structural Integrity Guards** — ensures `tool_use` + `tool_result` pairs stay consistent
|
||||
- **Context Window Awareness** — respects per-model token limits
|
||||
|
||||
**Best for:** Extended debugging sessions, large codebases.
|
||||
|
||||
### Ultra Mode (~75% savings)
|
||||
|
||||
Maximum compression for token-critical scenarios:
|
||||
|
||||
- **Heuristic Pruning** — removes messages below relevance threshold
|
||||
- **Code Block Thinning** — compresses repetitive code examples
|
||||
- **Binary Search Truncation** — finds optimal cut point for context window
|
||||
- All Aggressive mode features included
|
||||
|
||||
**Best for:** When you're hitting context limits repeatedly.
|
||||
|
||||
---
|
||||
|
||||
## Token Savings Visualization
|
||||
|
||||
```
|
||||
Without compression: 47K tokens sent to LLM
|
||||
With Lite: 40K tokens sent (15% saved — safe, always-on)
|
||||
With Standard: 33K tokens sent (30% saved — caveman-speak rules)
|
||||
With Aggressive: 24K tokens sent (50% saved — aging + summarization)
|
||||
With Ultra: 12K tokens sent (75% saved — heuristic pruning)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Dashboard
|
||||
|
||||
Navigate to `Dashboard → Settings → Compression`:
|
||||
|
||||
- **Default Mode** — sets the system-wide compression mode
|
||||
- **Auto-Trigger Threshold** — automatically engage compression when token count exceeds threshold
|
||||
- **Per-Combo Override** — each combo can have its own compression mode
|
||||
|
||||
### Per-Combo Override
|
||||
|
||||
In `Dashboard → Combos → [Your Combo] → Advanced`, set compression mode per combo:
|
||||
|
||||
```txt
|
||||
Combo: "free-forever"
|
||||
Mode: Standard
|
||||
Targets:
|
||||
1. gc/gemini-3-flash
|
||||
2. if/kimi-k2-thinking
|
||||
```
|
||||
|
||||
This lets you use aggressive compression on free providers while keeping lite mode on paid subscriptions.
|
||||
|
||||
### API
|
||||
|
||||
```bash
|
||||
# Get compression settings
|
||||
curl http://localhost:20128/api/settings/compression
|
||||
|
||||
# Update compression settings
|
||||
curl -X PUT http://localhost:20128/api/settings/compression \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"defaultMode":"lite","autoTriggerThreshold":32000}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Gets Protected
|
||||
|
||||
The compression engine **always preserves:**
|
||||
|
||||
- ✅ Code blocks (fenced and inline)
|
||||
- ✅ URLs and file paths
|
||||
- ✅ JSON structures and structured data
|
||||
- ✅ API keys, tokens, and identifiers
|
||||
- ✅ Mathematical expressions
|
||||
- ✅ Tool/function call definitions
|
||||
- ✅ System prompts (in lite mode)
|
||||
|
||||
---
|
||||
|
||||
## Compression Stats
|
||||
|
||||
Every compressed request includes stats in the server logs:
|
||||
|
||||
```json
|
||||
{
|
||||
"originalTokens": 47200,
|
||||
"compressedTokens": 40120,
|
||||
"savingsPercent": 15.0,
|
||||
"techniquesUsed": ["collapseWhitespace", "dedupSystemPrompt"],
|
||||
"mode": "lite",
|
||||
"latencyMs": 0.8
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase Roadmap
|
||||
|
||||
| Phase | Modes | Status |
|
||||
| ------- | ------------------------------------ | ---------- |
|
||||
| Phase 1 | Off, Lite | ✅ Shipped |
|
||||
| Phase 2 | Standard, Aggressive, Ultra | ✅ Shipped |
|
||||
| Phase 3 | Per-model adaptive, ML-based pruning | 🗓️ Planned |
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Standard mode compression rules are inspired by **[Caveman](https://github.com/JuliusBrussee/caveman)** by **[JuliusBrussee](https://github.com/JuliusBrussee)** (⭐ 51K+) — the viral "why use many token when few token do trick" project.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Environment Config](ENVIRONMENT.md) — Compression environment variables
|
||||
- [Architecture Guide](ARCHITECTURE.md) — Compression pipeline internals
|
||||
- [User Guide](USER_GUIDE.md) — Getting started with compression
|
||||
119
docs/DOCKER_GUIDE.md
Normal file
119
docs/DOCKER_GUIDE.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# 🐳 Docker Guide — OmniRoute
|
||||
|
||||
> Complete Docker deployment reference. For a quick start, see the [README Docker section](../README.md#-docker).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Quick Run](#quick-run)
|
||||
- [With Environment File](#with-environment-file)
|
||||
- [Docker Compose](#docker-compose)
|
||||
- [Docker Compose with Caddy (HTTPS)](#docker-compose-with-caddy-https-auto-tls)
|
||||
- [Cloudflare Quick Tunnel](#cloudflare-quick-tunnel)
|
||||
- [Image Tags](#image-tags)
|
||||
- [Important Notes](#important-notes)
|
||||
|
||||
---
|
||||
|
||||
## Quick Run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name omniroute \
|
||||
--restart unless-stopped \
|
||||
--stop-timeout 40 \
|
||||
-p 20128:20128 \
|
||||
-v omniroute-data:/app/data \
|
||||
diegosouzapw/omniroute:latest
|
||||
```
|
||||
|
||||
## With Environment File
|
||||
|
||||
```bash
|
||||
# Copy and edit .env first
|
||||
cp .env.example .env
|
||||
|
||||
docker run -d \
|
||||
--name omniroute \
|
||||
--restart unless-stopped \
|
||||
--stop-timeout 40 \
|
||||
--env-file .env \
|
||||
-p 20128:20128 \
|
||||
-v omniroute-data:/app/data \
|
||||
diegosouzapw/omniroute:latest
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```bash
|
||||
# Base profile (no CLI tools)
|
||||
docker compose --profile base up -d
|
||||
|
||||
# CLI profile (Claude Code, Codex, OpenClaw built-in)
|
||||
docker compose --profile cli up -d
|
||||
```
|
||||
|
||||
## Docker Compose with Caddy (HTTPS Auto-TLS)
|
||||
|
||||
OmniRoute can be securely exposed using Caddy's automatic SSL provisioning. Ensure your domain's DNS A record points to your server's IP.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
omniroute:
|
||||
image: diegosouzapw/omniroute:latest
|
||||
container_name: omniroute
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- omniroute-data:/app/data
|
||||
environment:
|
||||
- PORT=20128
|
||||
- NEXT_PUBLIC_BASE_URL=https://your-domain.com
|
||||
|
||||
caddy:
|
||||
image: caddy:latest
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
command: caddy reverse-proxy --from https://your-domain.com --to http://omniroute:20128
|
||||
|
||||
volumes:
|
||||
omniroute-data:
|
||||
```
|
||||
|
||||
## Cloudflare Quick Tunnel
|
||||
|
||||
Dashboard support for Docker deployments includes a one-click **Cloudflare Quick Tunnel** on `Dashboard → Endpoints`. The first enable downloads `cloudflared` only when needed, starts a temporary tunnel to your current `/v1` endpoint, and shows the generated `https://*.trycloudflare.com/v1` URL directly below your normal public URL.
|
||||
|
||||
Endpoint tunnel panels (Cloudflare, Tailscale, ngrok) can be shown or hidden from `Settings → Appearance` without changing active tunnel state.
|
||||
|
||||
### Tunnel Notes
|
||||
|
||||
- Quick Tunnel URLs are temporary and change after every restart.
|
||||
- Quick Tunnels are not auto-restored after an OmniRoute or container restart. Re-enable them from the dashboard when needed.
|
||||
- Managed install currently supports Linux, macOS, and Windows on `x64` / `arm64`.
|
||||
- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained container environments. Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want a different transport.
|
||||
- Docker images bundle system CA roots and pass them to managed `cloudflared`, which avoids TLS trust failures when the tunnel bootstraps inside the container.
|
||||
- Set `CLOUDFLARED_BIN=/absolute/path/to/cloudflared` if you want OmniRoute to use an existing binary instead of downloading one.
|
||||
|
||||
## Image Tags
|
||||
|
||||
| Image | Tag | Size | Description |
|
||||
| ------------------------ | -------- | ------ | --------------------- |
|
||||
| `diegosouzapw/omniroute` | `latest` | ~250MB | Latest stable release |
|
||||
| `diegosouzapw/omniroute` | `3.7.8` | ~250MB | Current version |
|
||||
|
||||
Multi-platform: AMD64 + ARM64 native (Apple Silicon, AWS Graviton, Raspberry Pi).
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **SQLite WAL Mode:** `docker stop` should be allowed to finish so OmniRoute can checkpoint the latest changes back into `storage.sqlite`. The bundled Compose files already set a 40s stop grace period. If you run the image directly, keep `--stop-timeout 40`.
|
||||
- **`DISABLE_SQLITE_AUTO_BACKUP`:** Set to `true` if backups are managed externally.
|
||||
- **Data Persistence:** Always mount a volume to `/app/data` to persist your database, keys, and configurations across container restarts.
|
||||
- **Port Configuration:** Override `PORT` environment variable to change the default `20128` port.
|
||||
|
||||
## See Also
|
||||
|
||||
- [VM Deployment Guide](VM_DEPLOYMENT_GUIDE.md) — VM + nginx + Cloudflare setup
|
||||
- [Fly.io Deployment Guide](FLY_IO_DEPLOYMENT_GUIDE.md) — Deploy to Fly.io
|
||||
- [Environment Config](ENVIRONMENT.md) — Complete `.env` reference
|
||||
673
docs/ENVIRONMENT.md
Normal file
673
docs/ENVIRONMENT.md
Normal file
@@ -0,0 +1,673 @@
|
||||
# Environment Variables Reference
|
||||
|
||||
> Complete reference for every environment variable recognized by OmniRoute.
|
||||
> For a quick-start template, see [`.env.example`](../.env.example).
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [1. Required Secrets](#1-required-secrets)
|
||||
- [2. Storage & Database](#2-storage--database)
|
||||
- [3. Network & Ports](#3-network--ports)
|
||||
- [4. Security & Authentication](#4-security--authentication)
|
||||
- [5. Input Sanitization & PII Protection](#5-input-sanitization--pii-protection)
|
||||
- [6. Tool & Routing Policies](#6-tool--routing-policies)
|
||||
- [7. URLs & Cloud Sync](#7-urls--cloud-sync)
|
||||
- [8. Outbound Proxy](#8-outbound-proxy)
|
||||
- [9. CLI Tool Integration](#9-cli-tool-integration)
|
||||
- [10. Internal Agent & MCP Integrations](#10-internal-agent--mcp-integrations)
|
||||
- [11. OAuth Provider Credentials](#11-oauth-provider-credentials)
|
||||
- [12. Provider User-Agent Overrides](#12-provider-user-agent-overrides)
|
||||
- [13. CLI Fingerprint Compatibility](#13-cli-fingerprint-compatibility)
|
||||
- [14. API Key Providers](#14-api-key-providers)
|
||||
- [15. Timeout Settings](#15-timeout-settings)
|
||||
- [16. Logging](#16-logging)
|
||||
- [17. Memory Optimization](#17-memory-optimization)
|
||||
- [18. Pricing Sync](#18-pricing-sync)
|
||||
- [19. Model Sync (Dev)](#19-model-sync-dev)
|
||||
- [20. Provider-Specific Settings](#20-provider-specific-settings)
|
||||
- [21. Proxy Health](#21-proxy-health)
|
||||
- [22. Debugging](#22-debugging)
|
||||
- [23. GitHub Integration](#23-github-integration)
|
||||
- [Deployment Scenarios](#deployment-scenarios)
|
||||
- [Audit: Removed / Dead Variables](#audit-removed--dead-variables)
|
||||
|
||||
---
|
||||
|
||||
## 1. Required Secrets
|
||||
|
||||
These **must** be set before the first run. Without them, the application will either refuse to start or operate with insecure defaults.
|
||||
|
||||
| Variable | Required | Default | Source File | Description |
|
||||
| ------------------ | -------- | -------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. |
|
||||
| `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. |
|
||||
| `INITIAL_PASSWORD` | **Yes** | `123456` | Bootstrap script | Sets the initial admin dashboard password. **Change before first use.** After login, change via Dashboard → Settings → Security. |
|
||||
|
||||
### Generation Commands
|
||||
|
||||
```bash
|
||||
# Generate all three secrets at once:
|
||||
echo "JWT_SECRET=$(openssl rand -base64 48)"
|
||||
echo "API_KEY_SECRET=$(openssl rand -hex 32)"
|
||||
echo "INITIAL_PASSWORD=$(openssl rand -base64 16)"
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Never commit `.env` files with real secrets to version control. The `.gitignore` already excludes `.env`, but verify before pushing.
|
||||
|
||||
---
|
||||
|
||||
## 2. Storage & Database
|
||||
|
||||
OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These variables control data location, encryption, and lifecycle.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------------- | -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | Root directory for SQLite DB, backups, and data files. Override for Docker volumes or custom paths. |
|
||||
| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. |
|
||||
| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. |
|
||||
| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | When `true`, skips the automatic database backup that runs before migrations on every startup. |
|
||||
| `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. |
|
||||
| `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. |
|
||||
|
||||
### Scenarios
|
||||
|
||||
| Scenario | Configuration |
|
||||
| --------------------- | -------------------------------------------------------------------------------- |
|
||||
| **Local development** | Leave all defaults. DB lives at `~/.omniroute/omniroute.db`. |
|
||||
| **Docker** | `DATA_DIR=/data` + mount a volume at `/data`. |
|
||||
| **Encrypted at rest** | Set `STORAGE_ENCRYPTION_KEY` + keep backups of the key! Losing it = losing data. |
|
||||
| **CI/Testing** | `DATA_DIR=/tmp/omniroute-test` — ephemeral, no encryption needed. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Network & Ports
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------- | ------------ | -------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
|
||||
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
|
||||
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
|
||||
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
|
||||
| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. |
|
||||
| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. |
|
||||
| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. |
|
||||
| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. |
|
||||
|
||||
### Port Modes
|
||||
|
||||
```
|
||||
┌─────────────────────────── Single Port (default) ──────────────────────────┐
|
||||
│ PORT=20128 │
|
||||
│ → Dashboard: http://localhost:20128 │
|
||||
│ → API: http://localhost:20128/v1/chat/completions │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────── Split Ports ─────────────────────────────────────┐
|
||||
│ DASHBOARD_PORT=20128 │
|
||||
│ API_PORT=20129 │
|
||||
│ API_HOST=0.0.0.0 │
|
||||
│ → Dashboard: http://localhost:20128 │
|
||||
│ → API: http://0.0.0.0:20129/v1/chat/completions │
|
||||
│ Use case: Expose API to LAN while restricting Dashboard to localhost. │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────── Docker Production ──────────────────────────────┐
|
||||
│ PROD_DASHBOARD_PORT=443 PROD_API_PORT=8443 │
|
||||
│ → Maps container ports to host ports in docker-compose.prod.yml. │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Security & Authentication
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------------- | --------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combined with hardware identifiers for machine fingerprinting. Change per-deployment for isolation. |
|
||||
| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sets the `Secure` flag on session cookies. **Must be `true`** when running behind HTTPS. |
|
||||
| `REQUIRE_API_KEY` | `false` | API middleware | When `true`, all `/v1/*` proxy requests must include a valid API key. |
|
||||
| `ALLOW_API_KEY_REVEAL` | `false` | Dashboard providers page | Allows revealing full API key values in the Dashboard UI. Security risk on shared instances. |
|
||||
| `NO_LOG_API_KEY_IDS` | _(empty)_ | `src/lib/compliance/index.ts` | Comma-separated API key IDs that bypass request logging (GDPR compliance). |
|
||||
| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. |
|
||||
| `CORS_ORIGIN` | `*` | Next.js middleware | CORS `Access-Control-Allow-Origin` value. Restrict for production. |
|
||||
| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. |
|
||||
|
||||
### Hardening Checklist
|
||||
|
||||
```bash
|
||||
# Production security minimum:
|
||||
AUTH_COOKIE_SECURE=true # Requires HTTPS
|
||||
REQUIRE_API_KEY=true # Authenticate all proxy calls
|
||||
ALLOW_API_KEY_REVEAL=false # Never expose keys in UI
|
||||
CORS_ORIGIN=https://your.domain.com
|
||||
MAX_BODY_SIZE_BYTES=5242880 # 5 MB limit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Input Sanitization & PII Protection
|
||||
|
||||
OmniRoute provides a two-layer defense: request-side injection scanning and response-side PII stripping.
|
||||
|
||||
### Request-Side: Prompt Injection Guard
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------------- | --------- | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `INPUT_SANITIZER_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | Enable scanning of incoming messages for prompt injection patterns. |
|
||||
| `INPUT_SANITIZER_MODE` | `warn` | `src/middleware/promptInjectionGuard.ts` | `warn` = log only, `block` = reject request with 400, `redact` = strip suspicious patterns. |
|
||||
| `INJECTION_GUARD_MODE` | _(unset)_ | `src/middleware/promptInjectionGuard.ts` | Legacy alias for `INPUT_SANITIZER_MODE` — same behavior. |
|
||||
| `PII_REDACTION_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | Detect PII (emails, phones, SSNs) in incoming requests. |
|
||||
|
||||
### Response-Side: PII Sanitizer
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------------- | -------- | ------------------------- | ----------------------------------------------------------------------- |
|
||||
| `PII_RESPONSE_SANITIZATION` | `false` | `src/lib/piiSanitizer.ts` | Scan LLM responses for leaked PII before returning to client. |
|
||||
| `PII_RESPONSE_SANITIZATION_MODE` | `redact` | `src/lib/piiSanitizer.ts` | `redact` = mask PII, `warn` = log only, `block` = drop entire response. |
|
||||
|
||||
### Scenarios
|
||||
|
||||
| Scenario | Configuration |
|
||||
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Enterprise compliance** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=block`, `PII_REDACTION_ENABLED=true`, `PII_RESPONSE_SANITIZATION=true` |
|
||||
| **Monitoring only** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=warn` — logs but never blocks |
|
||||
| **Personal use** | Leave all disabled — zero overhead |
|
||||
|
||||
---
|
||||
|
||||
## 6. Tool & Routing Policies
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------ | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | Controls LLM tool/function-calling access. `allowlist` = only listed tools, `denylist` = all except listed, `disabled` = no restrictions. |
|
||||
|
||||
---
|
||||
|
||||
## 7. URLs & Cloud Sync
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------- | ------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. |
|
||||
| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). |
|
||||
| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. |
|
||||
| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** |
|
||||
| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. |
|
||||
| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. |
|
||||
|
||||
> [!IMPORTANT]
|
||||
> When deploying behind a reverse proxy (nginx, Caddy), `NEXT_PUBLIC_BASE_URL` **must** be set to your public URL (e.g., `https://omniroute.example.com`). Without this, OAuth callbacks will fail because the redirect_uri won't match.
|
||||
|
||||
---
|
||||
|
||||
## 8. Outbound Proxy
|
||||
|
||||
Route upstream LLM provider calls through an HTTP or SOCKS5 proxy for egress control, geo-routing, or IP masking.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------------------- | --------- | -------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `ENABLE_SOCKS5_PROXY` | `true` | `open-sse/executors` | Enable SOCKS5 proxy agent for upstream calls. |
|
||||
| `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` | `true` | Client-side | Client-side awareness of SOCKS5 availability. |
|
||||
| `HTTP_PROXY` | _(unset)_ | Node.js standard | HTTP proxy for upstream calls. |
|
||||
| `HTTPS_PROXY` | _(unset)_ | Node.js standard | HTTPS proxy for upstream calls. |
|
||||
| `ALL_PROXY` | _(unset)_ | Node.js standard | Universal proxy (supports `socks5://`). |
|
||||
| `NO_PROXY` | _(unset)_ | Node.js standard | Comma-separated hostnames/IPs to bypass the proxy. |
|
||||
| `ENABLE_TLS_FINGERPRINT` | `false` | `open-sse/executors` | Spoof TLS fingerprint using wreq-js (mimics Chrome 124). Counters JA3/JA4 blocking. |
|
||||
|
||||
### Scenarios
|
||||
|
||||
| Scenario | Configuration |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **SOCKS5 through SSH tunnel** | `ALL_PROXY=socks5://127.0.0.1:7890`, `ENABLE_SOCKS5_PROXY=true` |
|
||||
| **Corporate HTTP proxy** | `HTTP_PROXY=http://proxy.corp.com:3128`, `HTTPS_PROXY=http://proxy.corp.com:3128`, `NO_PROXY=localhost,internal.corp.com` |
|
||||
| **Anti-fingerprint** | `ENABLE_TLS_FINGERPRINT=true` — requires `wreq-js` (included) |
|
||||
|
||||
---
|
||||
|
||||
## 9. CLI Tool Integration
|
||||
|
||||
Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex, etc.).
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------------- | ---------- | ----------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = search system PATH; `manual` = use explicit paths only. |
|
||||
| `CLI_EXTRA_PATHS` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Additional PATH entries for CLI binary discovery (colon-separated). |
|
||||
| `CLI_CONFIG_HOME` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Override home directory for reading CLI configs (`~/.claude`, `~/.codex`). |
|
||||
| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | Allow OmniRoute to write CLI config files (token refresh, session data). |
|
||||
| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Custom path to Claude CLI binary. |
|
||||
| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Custom path to Codex CLI binary. |
|
||||
| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Custom path to Droid CLI binary. |
|
||||
| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | Custom path to OpenClaw CLI binary. |
|
||||
| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Custom path to Cursor agent binary. |
|
||||
| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. |
|
||||
| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. |
|
||||
| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. |
|
||||
|
||||
### Docker Example
|
||||
|
||||
```bash
|
||||
# Mount host binaries into the container and tell OmniRoute where they are:
|
||||
CLI_EXTRA_PATHS=/host-cli/bin
|
||||
CLI_CONFIG_HOME=/root
|
||||
CLI_ALLOW_CONFIG_WRITES=true
|
||||
CLI_CLAUDE_BIN=/host-cli/bin/claude
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Internal Agent & MCP Integrations
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------------------------- | ----------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `OMNIROUTE_BASE_URL` | auto-detect | `open-sse/mcp-server/server.ts` | Explicit URL for MCP/A2A tools to reach OmniRoute. Overrides localhost auto-detection. |
|
||||
| `OMNIROUTE_API_KEY` | _(unset)_ | MCP/A2A modules | API key for internal MCP tool and A2A skill calls. |
|
||||
| `OMNIROUTE_API_KEY_ID` | _(unset)_ | `open-sse/mcp-server/audit.ts` | Key ID for MCP audit log attribution. |
|
||||
| `ROUTER_API_KEY` | _(unset)_ | Legacy | Legacy alias for `OMNIROUTE_API_KEY`. |
|
||||
| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `false` | `open-sse/mcp-server/server.ts` | Enforce scope-based access control on MCP tool calls. |
|
||||
| `OMNIROUTE_MCP_SCOPES` | _(all)_ | `open-sse/mcp-server/server.ts` | Comma-separated scopes: `admin`, `combos`, `health`, `models`, `routing`, `budget`, `metrics`, `pricing`, `memory`, `skills`. |
|
||||
| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | Model catalog sync interval in hours. |
|
||||
| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | Provider rate-limit and quota polling interval. |
|
||||
| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | Disable all background services (sync, pricing, model refresh). Useful for CI/test. |
|
||||
| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | Set `true` by bootstrap script after initial setup. Controls setup wizard visibility. |
|
||||
| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | Escape hatch: allow request body to override the Antigravity project field. |
|
||||
|
||||
### OAuth CLI Bridge (Internal)
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------- | ----------- | ------------------------------- | ----------------------------------------- |
|
||||
| `OMNIROUTE_SERVER` | auto-detect | `src/lib/oauth/config/index.ts` | Server URL for CLI↔OmniRoute auth bridge. |
|
||||
| `OMNIROUTE_TOKEN` | _(unset)_ | `src/lib/oauth/config/index.ts` | Auth token for CLI bridge. |
|
||||
| `OMNIROUTE_USER_ID` | `cli` | `src/lib/oauth/config/index.ts` | User ID for CLI bridge sessions. |
|
||||
| `SERVER_URL` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_SERVER`. |
|
||||
| `CLI_TOKEN` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_TOKEN`. |
|
||||
| `CLI_USER_ID` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_USER_ID`. |
|
||||
|
||||
---
|
||||
|
||||
## 11. OAuth Provider Credentials
|
||||
|
||||
Built-in credentials for **localhost development**. For remote deployments, register your own at each provider's developer console.
|
||||
|
||||
| Variable | Provider | Notes |
|
||||
| --------------------------------- | ----------------------- | --------------------------------------------------------------------------------- |
|
||||
| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | Public client — no secret needed. |
|
||||
| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | Override redirect URI. Default: `https://platform.claude.com/oauth/code/callback` |
|
||||
| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. |
|
||||
| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. |
|
||||
| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — |
|
||||
| `GEMINI_CLI_OAUTH_CLIENT_ID` | Gemini CLI | Usually same as Gemini. |
|
||||
| `GEMINI_CLI_OAUTH_CLIENT_SECRET` | Gemini CLI | — |
|
||||
| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. |
|
||||
| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. |
|
||||
| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. |
|
||||
| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — |
|
||||
| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | Public client. |
|
||||
| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — |
|
||||
| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. |
|
||||
| `QODER_OAUTH_TOKEN_URL` | Qoder | — |
|
||||
| `QODER_OAUTH_USERINFO_URL` | Qoder | — |
|
||||
| `QODER_OAUTH_CLIENT_ID` | Qoder | — |
|
||||
| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | Direct API key fallback (bypasses OAuth). |
|
||||
| `QODER_CLI_WORKSPACE` | Qoder | Workspace ID for Qoder CLI. |
|
||||
| `OMNIROUTE_QODER_WORKSPACE` | Qoder | Alias for `QODER_CLI_WORKSPACE`. |
|
||||
|
||||
> [!WARNING]
|
||||
> **Google OAuth** (Antigravity, Gemini CLI) credentials **only work on localhost**. For remote servers:
|
||||
>
|
||||
> 1. Go to [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials)
|
||||
> 2. Create an OAuth 2.0 Client ID (type: "Web application")
|
||||
> 3. Add your server URL as Authorized redirect URI
|
||||
> 4. Replace the credential values in `.env`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Provider User-Agent Overrides
|
||||
|
||||
Override the `User-Agent` header sent to each upstream provider. This is dynamically resolved at runtime by the executor base class:
|
||||
|
||||
```
|
||||
process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
```
|
||||
|
||||
> **Source:** `open-sse/executors/base.ts` → `buildHeaders()`
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.121 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.125.0 (Windows 10.0.26100; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.125.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
| `ANTIGRAVITY_USER_AGENT` | `antigravity/1.107.0 darwin/arm64` | When Antigravity IDE updates |
|
||||
| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates |
|
||||
| `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates |
|
||||
| `QWEN_USER_AGENT` | `QwenCode/0.15.3 (linux; x64)` | When Qwen Code updates |
|
||||
| `CURSOR_USER_AGENT` | `connect-es/1.6.1` | When Cursor updates |
|
||||
| `GEMINI_CLI_USER_AGENT` | `google-api-nodejs-client/10.3.0` | When Google API client updates |
|
||||
|
||||
> [!TIP]
|
||||
> You can add User-Agent overrides for **any** provider using the pattern `{PROVIDER_ID}_USER_AGENT`. The executor dynamically constructs the env var name.
|
||||
|
||||
---
|
||||
|
||||
## 13. CLI Fingerprint Compatibility
|
||||
|
||||
When enabled, OmniRoute reorders HTTP headers and JSON body fields to match the exact signature of official CLI tools. This reduces the risk of account flagging while preserving your proxy IP.
|
||||
|
||||
**Source:** `open-sse/config/cliFingerprints.ts`, `open-sse/executors/base.ts`
|
||||
|
||||
### Per-Provider
|
||||
|
||||
| Variable | Effect |
|
||||
| -------------------------- | --------------------------------------- |
|
||||
| `CLI_COMPAT_CODEX=1` | Mimics Codex CLI request signature |
|
||||
| `CLI_COMPAT_CLAUDE=1` | Mimics Claude Code request signature |
|
||||
| `CLI_COMPAT_GITHUB=1` | Mimics GitHub Copilot request signature |
|
||||
| `CLI_COMPAT_ANTIGRAVITY=1` | Mimics Antigravity request signature |
|
||||
| `CLI_COMPAT_KIRO=1` | Mimics Kiro IDE request signature |
|
||||
| `CLI_COMPAT_CURSOR=1` | Mimics Cursor request signature |
|
||||
| `CLI_COMPAT_KIMI_CODING=1` | Mimics Kimi Coding request signature |
|
||||
| `CLI_COMPAT_KILOCODE=1` | Mimics Kilo Code request signature |
|
||||
| `CLI_COMPAT_CLINE=1` | Mimics Cline request signature |
|
||||
| `CLI_COMPAT_QWEN=1` | Mimics Qwen Code request signature |
|
||||
|
||||
### Global
|
||||
|
||||
| Variable | Effect |
|
||||
| ------------------ | --------------------------------------------------------------- |
|
||||
| `CLI_COMPAT_ALL=1` | Enable fingerprint compatibility for **all** providers at once. |
|
||||
|
||||
> [!NOTE]
|
||||
> This feature works alongside the User-Agent overrides (§12). The fingerprint system handles header ordering and body field ordering, while User-Agent overrides handle the specific UA string. Both can be enabled independently.
|
||||
|
||||
---
|
||||
|
||||
## 14. API Key Providers
|
||||
|
||||
API keys for providers that use direct authentication. **Preferred setup:** Dashboard → Providers → Add API Key.
|
||||
|
||||
Setting via environment variables is an alternative for Docker or headless deployments.
|
||||
|
||||
Recognized pattern: `{PROVIDER_ID}_API_KEY`
|
||||
|
||||
| Variable | Provider |
|
||||
| -------------------- | ------------------- |
|
||||
| `DEEPSEEK_API_KEY` | DeepSeek |
|
||||
| `GROQ_API_KEY` | Groq |
|
||||
| `XAI_API_KEY` | xAI (Grok) |
|
||||
| `MISTRAL_API_KEY` | Mistral AI |
|
||||
| `PERPLEXITY_API_KEY` | Perplexity |
|
||||
| `TOGETHER_API_KEY` | Together AI |
|
||||
| `FIREWORKS_API_KEY` | Fireworks AI |
|
||||
| `CEREBRAS_API_KEY` | Cerebras |
|
||||
| `COHERE_API_KEY` | Cohere |
|
||||
| `NVIDIA_API_KEY` | NVIDIA NIM |
|
||||
| `NEBIUS_API_KEY` | Nebius (embeddings) |
|
||||
| `QIANFAN_API_KEY` | Baidu Qianfan |
|
||||
|
||||
> [!TIP]
|
||||
> Keys set via the Dashboard are stored encrypted in SQLite and take precedence over environment variables.
|
||||
|
||||
---
|
||||
|
||||
## 15. Timeout Settings
|
||||
|
||||
All values are in **milliseconds**. Centralized resolution in `src/shared/utils/runtimeTimeouts.ts`.
|
||||
|
||||
### Timeout Hierarchy
|
||||
|
||||
```
|
||||
REQUEST_TIMEOUT_MS (global override)
|
||||
├─→ FETCH_TIMEOUT_MS (upstream provider calls, default: 600000)
|
||||
│ ├─→ FETCH_HEADERS_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS)
|
||||
│ ├─→ FETCH_BODY_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS)
|
||||
│ ├─→ TLS_CLIENT_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS)
|
||||
│ ├── FETCH_CONNECT_TIMEOUT_MS (independent, default: 30000)
|
||||
│ └── FETCH_KEEPALIVE_TIMEOUT_MS (independent, default: 4000)
|
||||
├─→ STREAM_IDLE_TIMEOUT_MS (inherits from REQUEST_TIMEOUT_MS, default: 600000)
|
||||
└─→ API_BRIDGE_PROXY_TIMEOUT_MS (inherits from REQUEST_TIMEOUT_MS, default: 30000)
|
||||
├─→ API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS (derived, default: 300000)
|
||||
├── API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS (default: 60000)
|
||||
├── API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS (default: 5000)
|
||||
└── API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS (default: 0 = disabled)
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ---------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `REQUEST_TIMEOUT_MS` | _(unset)_ | Global shortcut — overrides both `FETCH_TIMEOUT_MS` and `STREAM_IDLE_TIMEOUT_MS` defaults. |
|
||||
| `FETCH_TIMEOUT_MS` | `600000` | Total HTTP request timeout for upstream provider calls. |
|
||||
| `STREAM_IDLE_TIMEOUT_MS` | `600000` | Max silence between SSE chunks before aborting. Extended-thinking models rarely pause >90s. |
|
||||
| `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive response headers. |
|
||||
| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive the full response body. |
|
||||
| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | TCP connection establishment timeout. |
|
||||
| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive socket idle timeout. |
|
||||
| `TLS_CLIENT_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | TLS fingerprint proxy (wreq-js) timeout. |
|
||||
| `API_BRIDGE_PROXY_TIMEOUT_MS` | `30000` | Proxy hop timeout for `/v1` bridge requests. |
|
||||
| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `300000` | Overall server request timeout for the bridge. |
|
||||
| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Time to send response headers via the bridge. |
|
||||
| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Bridge keep-alive idle timeout. |
|
||||
| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Raw socket timeout (0 = disabled). |
|
||||
| `SHUTDOWN_TIMEOUT_MS` | `30000` | Grace period on SIGTERM/SIGINT before force-exit. |
|
||||
|
||||
### Scenarios
|
||||
|
||||
| Scenario | Configuration |
|
||||
| -------------------------------- | ------------------------------------------------------ |
|
||||
| **Long-running code generation** | `REQUEST_TIMEOUT_MS=900000` (15 min) |
|
||||
| **Fast-fail for production API** | `API_BRIDGE_PROXY_TIMEOUT_MS=10000` |
|
||||
| **Extended thinking models** | `STREAM_IDLE_TIMEOUT_MS=300000` (5 min between chunks) |
|
||||
|
||||
---
|
||||
|
||||
## 16. Logging
|
||||
|
||||
The logging system writes to both stdout and rotated log files. All configuration is read by `src/lib/logEnv.ts`.
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ----------------------------------------- | -------------------------- | -------------------------------------------------------------------------------- |
|
||||
| `APP_LOG_LEVEL` | `info` | Minimum log level: `debug`, `info`, `warn`, `error`. |
|
||||
| `APP_LOG_FORMAT` | `text` | Output format: `text` (human-readable) or `json` (structured). |
|
||||
| `APP_LOG_TO_FILE` | `true` | Write logs to file alongside stdout. |
|
||||
| `APP_LOG_FILE_PATH` | `logs/application/app.log` | Log file path (relative to project root or `DATA_DIR`). |
|
||||
| `APP_LOG_MAX_FILE_SIZE` | `50M` | Max file size before rotation. Accepts: `50M`, `1G`, `512K`, or plain bytes. |
|
||||
| `APP_LOG_RETENTION_DAYS` | `7` | Days to keep rotated application log files. |
|
||||
| `APP_LOG_MAX_FILES` | `20` | Maximum rotated log file backups. |
|
||||
| `CALL_LOG_RETENTION_DAYS` | `7` | Days to keep request/call log entries in the database. |
|
||||
| `CALL_LOG_MAX_ENTRIES` | `10000` | Max call log entries in the in-memory buffer. |
|
||||
| `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `call_logs` SQLite table before pruning. |
|
||||
| `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS` | `true` | Store stream chunks in pipeline artifacts when `call_log_pipeline_enabled=true`. |
|
||||
| `CALL_LOG_PIPELINE_MAX_SIZE_KB` | `512` | Max pipeline call log artifact size in KB when `call_log_pipeline_enabled=true`. |
|
||||
| `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `proxy_logs` SQLite table before pruning. |
|
||||
|
||||
---
|
||||
|
||||
## 17. Memory Optimization
|
||||
|
||||
| Variable | Default | Description |
|
||||
| -------------------------- | ------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `OMNIROUTE_MEMORY_MB` | `256` (Docker) / system default | V8 heap limit. Sets `--max-old-space-size`. |
|
||||
| `PROMPT_CACHE_MAX_SIZE` | `50` | Max cached system prompt entries. |
|
||||
| `PROMPT_CACHE_MAX_BYTES` | `2097152` (2 MB) | Max total prompt cache size. |
|
||||
| `PROMPT_CACHE_TTL_MS` | `300000` (5 min) | Prompt cache entry TTL. |
|
||||
| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max cached temperature=0 responses. |
|
||||
| `SEMANTIC_CACHE_MAX_BYTES` | `4194304` (4 MB) | Max total semantic cache size. |
|
||||
| `SEMANTIC_CACHE_TTL_MS` | `1800000` (30 min) | Semantic cache entry TTL. |
|
||||
| `STREAM_HISTORY_MAX` | `50` | Max recent stream events in the Dashboard live view buffer. |
|
||||
| `CONTEXT_LENGTH_DEFAULT` | `128000` | Global fallback max context length for models without explicit config. |
|
||||
| `USAGE_TOKEN_BUFFER` | `100` | Extra token headroom reserved when tracking usage quotas. |
|
||||
|
||||
### Low-RAM Docker Example
|
||||
|
||||
```bash
|
||||
OMNIROUTE_MEMORY_MB=128
|
||||
PROMPT_CACHE_MAX_SIZE=20
|
||||
PROMPT_CACHE_MAX_BYTES=524288 # 512 KB
|
||||
SEMANTIC_CACHE_MAX_SIZE=25
|
||||
SEMANTIC_CACHE_MAX_BYTES=1048576 # 1 MB
|
||||
STREAM_HISTORY_MAX=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 18. Pricing Sync
|
||||
|
||||
Automatic model pricing data synchronization from external sources.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------- | ------------- | ------------------------ | ----------------------------- |
|
||||
| `PRICING_SYNC_ENABLED` | `false` | `src/lib/pricingSync.ts` | Opt-in periodic pricing sync. |
|
||||
| `PRICING_SYNC_INTERVAL` | `86400` (24h) | `src/lib/pricingSync.ts` | Sync interval in seconds. |
|
||||
| `PRICING_SYNC_SOURCES` | `litellm` | `src/lib/pricingSync.ts` | Comma-separated data sources. |
|
||||
|
||||
---
|
||||
|
||||
## 19. Model Sync (Dev)
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------- | ------------- | -------------------------- | -------------------------------------------------------- |
|
||||
| `MODELS_DEV_SYNC_INTERVAL` | `86400` (24h) | `src/lib/modelsDevSync.ts` | Development-time model catalog sync interval in seconds. |
|
||||
|
||||
---
|
||||
|
||||
## 20. Provider-Specific Settings
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------------------------- | ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| `OPENROUTER_CATALOG_TTL_MS` | `86400000` (24h) | `src/lib/catalog/openrouterCatalog.ts` | OpenRouter model catalog cache TTL. |
|
||||
| `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | Max wait for NanoBanana image generation jobs. |
|
||||
| `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana job polling frequency. |
|
||||
| `CLOUDFLARE_ACCOUNT_ID` | _(unset)_ | `open-sse/executors/cloudflare-ai.ts` | Account ID for Cloudflare Workers AI. |
|
||||
| `CLOUDFLARED_BIN` | auto-detect | `src/lib/cloudflaredTunnel.ts` | Custom path to `cloudflared` binary. |
|
||||
| `SEARCH_CACHE_TTL_MS` | `300000` (5 min) | `open-sse/services/searchCache.ts` | TTL for search API (Perplexity, Brave, etc.) response caching. |
|
||||
| `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` | `false` | `src/app/api/providers/route.ts` | Allow multiple simultaneous connections per OpenAI-compatible provider. |
|
||||
| `ENABLE_CC_COMPATIBLE_PROVIDER` | `false` | `src/shared/utils/featureFlags.ts` | Reveal the experimental CC-compatible provider UI for Claude Code-only relays. |
|
||||
| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge host (legacy integration). |
|
||||
| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge port. |
|
||||
| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI config directory. |
|
||||
| `LOCAL_HOSTNAMES` | _(empty)_ | `open-sse/config/providerRegistry.ts` | Comma-separated additional hostnames treated as "local" (Docker service names, etc.). |
|
||||
|
||||
`ENABLE_CC_COMPATIBLE_PROVIDER` is only for third-party relays that accept Claude Code clients
|
||||
exclusively. OmniRoute rewrites requests so those relays accept them. If you only want to use
|
||||
Claude Code CLI, or you are not sure what these relays are, keep this disabled and add a regular
|
||||
Anthropic-compatible provider instead.
|
||||
|
||||
---
|
||||
|
||||
## 21. Proxy Health
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ---------------------------- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. |
|
||||
| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. |
|
||||
| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. |
|
||||
| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. |
|
||||
| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. |
|
||||
|
||||
---
|
||||
|
||||
## 22. Debugging
|
||||
|
||||
> [!CAUTION]
|
||||
> These variables produce **verbose output** and may leak sensitive data. **Never enable in production.**
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------------- | --------- | ----------------------------------------- | -------------------------------------------------------------- |
|
||||
| `CURSOR_PROTOBUF_DEBUG` | _(unset)_ | `open-sse/utils/cursorProtobuf.ts` | Set `1` to dump Cursor protobuf decode/encode details. |
|
||||
| `CURSOR_STREAM_DEBUG` | _(unset)_ | `open-sse/executors/cursor.ts` | Set `1` to dump raw Cursor SSE stream data. |
|
||||
| `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. |
|
||||
| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). |
|
||||
|
||||
---
|
||||
|
||||
## 23. GitHub Integration
|
||||
|
||||
Allow users to report issues directly from the Dashboard.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------- | --------- | --------------------------------------- | ------------------------------------------------------- |
|
||||
| `GITHUB_ISSUES_REPO` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | Repository in `owner/repo` format. |
|
||||
| `GITHUB_ISSUES_TOKEN` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | GitHub Personal Access Token with `issues:write` scope. |
|
||||
|
||||
---
|
||||
|
||||
## Deployment Scenarios
|
||||
|
||||
### Minimal Local Development
|
||||
|
||||
```bash
|
||||
JWT_SECRET=$(openssl rand -base64 48)
|
||||
API_KEY_SECRET=$(openssl rand -hex 32)
|
||||
INITIAL_PASSWORD=dev123
|
||||
PORT=20128
|
||||
NODE_ENV=development
|
||||
```
|
||||
|
||||
### Docker Production
|
||||
|
||||
```bash
|
||||
JWT_SECRET=<generated>
|
||||
API_KEY_SECRET=<generated>
|
||||
INITIAL_PASSWORD=<generated>
|
||||
STORAGE_ENCRYPTION_KEY=<generated>
|
||||
DATA_DIR=/data
|
||||
PORT=20128
|
||||
API_PORT=20129
|
||||
NODE_ENV=production
|
||||
AUTH_COOKIE_SECURE=true
|
||||
REQUIRE_API_KEY=true
|
||||
NEXT_PUBLIC_BASE_URL=https://omniroute.example.com
|
||||
BASE_URL=http://localhost:20128
|
||||
OMNIROUTE_MEMORY_MB=512
|
||||
CORS_ORIGIN=https://your-frontend.example.com
|
||||
```
|
||||
|
||||
### Air-Gapped / CI
|
||||
|
||||
```bash
|
||||
JWT_SECRET=test-jwt-secret-for-ci
|
||||
API_KEY_SECRET=test-api-key-secret-for-ci
|
||||
INITIAL_PASSWORD=testpass
|
||||
NODE_ENV=production
|
||||
OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true
|
||||
APP_LOG_TO_FILE=false
|
||||
```
|
||||
|
||||
### VPS with Reverse Proxy (nginx + Cloudflare)
|
||||
|
||||
```bash
|
||||
JWT_SECRET=<generated>
|
||||
API_KEY_SECRET=<generated>
|
||||
STORAGE_ENCRYPTION_KEY=<generated>
|
||||
PORT=20128
|
||||
AUTH_COOKIE_SECURE=true
|
||||
REQUIRE_API_KEY=true
|
||||
NEXT_PUBLIC_BASE_URL=https://omniroute.example.com
|
||||
BASE_URL=http://127.0.0.1:20128
|
||||
CORS_ORIGIN=https://omniroute.example.com
|
||||
ENABLE_TLS_FINGERPRINT=true
|
||||
CLI_COMPAT_ALL=1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audit: Removed / Dead Variables
|
||||
|
||||
The following variables appeared in previous versions of `.env.example` but have **no runtime references** in the current codebase. They have been removed:
|
||||
|
||||
| Variable | Reason |
|
||||
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `STORAGE_DRIVER=sqlite` | Never read by any source file. SQLite is the only supported driver — no selection needed. |
|
||||
| `INSTANCE_NAME=omniroute` | Present in old docs/env templates but unused at runtime. May return in a future multi-instance feature. |
|
||||
| `SQLITE_MAX_SIZE_MB=2048` | Not referenced in source code. Database size is not artificially limited. |
|
||||
| `SQLITE_CLEAN_LEGACY_FILES=true` | Not referenced in source code. Legacy cleanup was likely removed. |
|
||||
| `CLI_ROO_BIN` | Not registered in `src/shared/services/cliRuntime.ts`. |
|
||||
| `CLI_KIMI_CODING_BIN` | Not registered in `src/shared/services/cliRuntime.ts` (Kimi Coding uses OAuth, not a CLI binary). |
|
||||
| `IFLOW_OAUTH_CLIENT_ID` / `IFLOW_OAUTH_CLIENT_SECRET` | Not referenced anywhere in source code. |
|
||||
|
||||
### Default Value Corrections
|
||||
|
||||
| Variable | Old `.env.example` Value | Actual Code Default | Fixed |
|
||||
| ------------------------- | ------------------------ | ------------------- | ------------------------------------------------------ |
|
||||
| `APP_LOG_RETENTION_DAYS` | `90` | `7` | ✅ Removed misleading value; documented `7` as default |
|
||||
| `CALL_LOG_RETENTION_DAYS` | `90` | `7` | ✅ Removed misleading value; documented `7` as default |
|
||||
139
docs/FEATURES.md
139
docs/FEATURES.md
@@ -8,7 +8,7 @@ Visual guide to every section of the OmniRoute dashboard.
|
||||
|
||||
## 🔌 Providers
|
||||
|
||||
Manage AI provider connections: OAuth providers (Claude Code, Codex, Gemini CLI), API key providers (Groq, DeepSeek, OpenRouter), and free providers (iFlow, Qwen, Kiro). Kiro accounts include credit balance tracking — remaining credits, total allowance, and renewal date visible in Dashboard → Usage.
|
||||
Manage AI provider connections: OAuth providers (Claude Code, Codex, Gemini CLI), API key providers (Groq, DeepSeek, OpenRouter), and free providers (Qoder, Qwen, Kiro). Kiro accounts include credit balance tracking — remaining credits, total allowance, and renewal date visible in Dashboard → Usage.
|
||||
|
||||

|
||||
|
||||
@@ -16,7 +16,14 @@ Manage AI provider connections: OAuth providers (Claude Code, Codex, Gemini CLI)
|
||||
|
||||
## 🎨 Combos
|
||||
|
||||
Create model routing combos with 6 strategies: priority, weighted, round-robin, random, least-used, and cost-optimized. Each combo chains multiple models with automatic fallback and includes quick templates and readiness checks.
|
||||
Create model routing combos with 13 strategies: priority, weighted, round-robin, random, least-used, cost-optimized, strict-random, auto, fill-first, p2c, lkgp, context-optimized, and **context-relay**. Each combo chains multiple models with automatic fallback and includes quick templates and readiness checks.
|
||||
|
||||
Recent combo improvements:
|
||||
|
||||
- **Structured combo builder** — create each step by selecting provider, model, and exact account/connection
|
||||
- **Repeated provider support** — reuse the same provider many times in one combo as long as the `(provider, model, connection)` tuple is unique
|
||||
- **Combo target health** — analytics and health surfaces now distinguish individual combo targets/steps instead of collapsing everything into model strings
|
||||
- **Composite tier ordering** — `defaultTier -> fallbackTier` now influences runtime execution/fallback order for top-level combo steps
|
||||
|
||||

|
||||
|
||||
@@ -32,7 +39,7 @@ Comprehensive usage analytics with token consumption, cost estimates, activity h
|
||||
|
||||
## 🏥 System Health
|
||||
|
||||
Real-time monitoring: uptime, memory, version, latency percentiles (p50/p95/p99), cache statistics, and provider circuit breaker states.
|
||||
Real-time monitoring: uptime, memory, version, latency percentiles (p50/p95/p99), cache statistics, provider circuit breaker states, active quota-monitored sessions, and combo target health.
|
||||
|
||||

|
||||
|
||||
@@ -63,11 +70,11 @@ Customizable color themes for the entire dashboard. Choose from 7 preset colors
|
||||
Comprehensive settings panel with tabs:
|
||||
|
||||
- **General** — System storage, backup management (export/import database)
|
||||
- **Appearance** — Theme selector (dark/light/system), color theme presets and custom colors, health log visibility
|
||||
- **Appearance** — Theme selector (dark/light/system), color theme presets and custom colors, health log visibility, sidebar item visibility controls, Endpoint tunnel visibility controls
|
||||
- **Security** — API endpoint protection, custom provider blocking, IP filtering, session info
|
||||
- **Routing** — Model aliases, background task degradation
|
||||
- **Resilience** — Rate limit persistence, circuit breaker tuning
|
||||
- **Advanced** — Configuration overrides
|
||||
- **Resilience** — Rate limit persistence, circuit breaker tuning, auto-disable banned accounts, provider expiration monitoring, **Context Relay** handoff threshold and summary model configuration
|
||||
- **Advanced** — Configuration overrides, configuration audit trail, fallback degradation mode
|
||||
|
||||

|
||||
|
||||
@@ -92,6 +99,68 @@ Dashboard for discovering and managing CLI agents. Shows a grid of 14 built-in a
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Context Relay _(v3.5.5+)_
|
||||
|
||||
A combo strategy that preserves session continuity when account rotation happens mid-conversation. Before the active account is exhausted, OmniRoute generates a structured handoff summary in the background. After the next request resolves to a different account, the summary is injected as a system message so the new account continues with full context.
|
||||
|
||||
Configurable via combo-level or global settings:
|
||||
|
||||
- **Handoff Threshold** — Quota usage percentage that triggers summary generation (default 85%)
|
||||
- **Max Messages For Summary** — How much recent history to condense
|
||||
- **Summary Model** — Optional override model for generating the handoff summary
|
||||
|
||||
Currently supports Codex account rotation. See [Context Relay documentation](features/context-relay.md).
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Proxy Hardening _(v3.5.5+)_
|
||||
|
||||
Comprehensive proxy configuration enforcement across the entire request pipeline:
|
||||
|
||||
- **Token Health Check** — Background OAuth refresh now resolves proxy config per connection, preventing failures in proxy-required environments
|
||||
- **API Key Validation** — Provider key validation (`POST /api/providers/validate`) routes through `runWithProxyContext`, honoring provider-level and global proxy settings
|
||||
- **undici Dispatcher Fix** — Proxy dispatchers use undici's own fetch implementation instead of Node's built-in fetch, resolving `invalid onRequestStart method` errors on Node.js 22
|
||||
- **Node.js Version Detection** — Login page proactively detects incompatible Node.js versions (24+) and displays a warning banner with instructions to use Node 22 LTS
|
||||
|
||||
---
|
||||
|
||||
## 📧 Email Privacy Masking _(v3.5.6+)_
|
||||
|
||||
OAuth account emails are now masked in the provider dashboard (e.g. `di*****@g****.com`) to prevent accidental exposure when sharing screenshots or recording demos. The full email address remains accessible via hover tooltip (`title` attribute).
|
||||
|
||||
---
|
||||
|
||||
## 👁️ Model Visibility Toggle _(v3.5.6+)_
|
||||
|
||||
The provider page model list now includes:
|
||||
|
||||
- **Real-time search/filter bar** — Quickly find specific models
|
||||
- **Per-model visibility toggle** (👁 icon) — Hidden models are grayed out and excluded from the `/v1/models` catalog
|
||||
- **Active-count badge** (`N/M active`) — Shows at a glance how many models are enabled vs total
|
||||
|
||||
---
|
||||
|
||||
## 🔧 OAuth Env Repair _(v3.6.1+)_
|
||||
|
||||
One-click "Repair env" action for OAuth providers that restores missing environment variables and fixes broken auth state. Accessible from `Dashboard → Providers → [OAuth Provider] → Repair env`. Automatically detects and repairs:
|
||||
|
||||
- Missing OAuth client credentials
|
||||
- Corrupted env file entries
|
||||
- Backup path sanitization
|
||||
|
||||
---
|
||||
|
||||
## 🗑️ Uninstall / Full Uninstall _(v3.6.2+)_
|
||||
|
||||
Clean removal scripts for all installation methods:
|
||||
|
||||
| Command | Action |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------- |
|
||||
| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. |
|
||||
| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. |
|
||||
|
||||
---
|
||||
|
||||
## 🖼️ Media _(v2.0.3+)_
|
||||
|
||||
Generate images, videos, and music from the dashboard. Supports OpenAI, xAI, Together, Hyperbolic, SD WebUI, ComfyUI, AnimateDiff, Stable Audio Open, and MusicGen.
|
||||
@@ -108,7 +177,7 @@ Real-time request logging with filtering by provider, model, account, and API ke
|
||||
|
||||
## 🌐 API Endpoint
|
||||
|
||||
Your unified API endpoint with capability breakdown: Chat Completions, Responses API, Embeddings, Image Generation, Reranking, Audio Transcription, Text-to-Speech, Moderations, and registered API keys. Cloud proxy support for remote access.
|
||||
Your unified API endpoint with capability breakdown: Chat Completions, Responses API, Embeddings, Image Generation, Reranking, Audio Transcription, Text-to-Speech, Moderations, and registered API keys. Cloudflare Quick Tunnel, Tailscale Funnel, ngrok Tunnel, and cloud proxy support are available for remote access.
|
||||
|
||||

|
||||
|
||||
@@ -139,5 +208,61 @@ Key features:
|
||||
- Auto-update on restart
|
||||
- Platform-conditional UI (macOS traffic lights, Windows/Linux default titlebar)
|
||||
- Hardened Electron build packaging — symlinked `node_modules` in the standalone bundle is detected and rejected before packaging, preventing runtime dependency on the build machine (v2.5.5+)
|
||||
- **Graceful shutdown** — Electron `before-quit` shuts down Next.js cleanly, preventing SQLite WAL database locks (v3.6.2+)
|
||||
|
||||
📖 See [`electron/README.md`](../electron/README.md) for full documentation.
|
||||
|
||||
---
|
||||
|
||||
## 🌐 V1 WebSocket Bridge _(v3.6.6+)_
|
||||
|
||||
OmniRoute now supports **OpenAI-compatible WebSocket clients** via the `/v1/ws` upgrade endpoint. The custom `scripts/v1-ws-bridge.mjs` server wraps Next.js and upgrades WS connections to full bidirectional streaming sessions. Authentication uses the same API key or session cookie as HTTP requests.
|
||||
|
||||
Key behaviours:
|
||||
|
||||
- WS upgrade validated by `src/lib/ws/handshake.ts` before the connection is established
|
||||
- Streams terminated cleanly on session close or upstream error
|
||||
- Works alongside the existing HTTP+SSE streaming path simultaneously
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Sync Tokens & Config Bundle _(v3.6.6+)_
|
||||
|
||||
Multi-device and external operator access is now possible via **scoped sync tokens**:
|
||||
|
||||
- **`POST /api/sync/tokens`** — Issue a new sync token (scoped, with optional expiry)
|
||||
- **`DELETE /api/sync/tokens/:id`** — Revoke a token
|
||||
- **`GET /api/sync/bundle`** — Download a versioned, ETag-keyed JSON snapshot of all non-sensitive settings (passwords redacted)
|
||||
|
||||
The config bundle is built by `src/lib/sync/bundle.ts`. Consumers compare the `ETag` response header to detect changes without re-downloading the full payload.
|
||||
|
||||
---
|
||||
|
||||
## 🧠 GLM Thinking Preset _(v3.6.6+)_
|
||||
|
||||
**GLM Thinking (`glmt`)** is now a registered first-class provider: 65 536 max output tokens, 24 576 thinking budget, 900 s default timeout, Claude-compatible API format, and shared usage sync with the GLM family.
|
||||
|
||||
**Hybrid token counting** also lands in v3.6.6: when a Claude-compatible provider exposes `/messages/count_tokens`, OmniRoute calls it before large requests with graceful estimation fallback.
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Safe Outbound Fetch & SSRF Guard _(v3.6.6+)_
|
||||
|
||||
All provider validation and model discovery calls now go through a two-layer outbound guard:
|
||||
|
||||
1. **URL guard** (`src/shared/network/outboundUrlGuard.ts`) — Blocks private/loopback/link-local IP ranges before the socket is opened.
|
||||
2. **Safe fetch wrapper** (`src/shared/network/safeOutboundFetch.ts`) — Applies the URL guard, normalises timeouts, and retries transient errors with exponential backoff.
|
||||
|
||||
Guard violations surface as HTTP 422 (`URL_GUARD_BLOCKED`) and are written to the compliance audit log via `providerAudit.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Cooldown-Aware Retries _(v3.6.6+)_
|
||||
|
||||
Chat requests now **automatically retry** when an upstream provider returns a model-scoped cooldown. Configurable via `REQUEST_RETRY` (default: 2) and `MAX_RETRY_INTERVAL_SEC` (default: 30 s). Rate-limit header learning improved across `x-ratelimit-reset-requests`, `x-ratelimit-reset-tokens`, and `Retry-After` — per-model cooldown state is visible in the Resilience dashboard.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Compliance Audit v2 _(v3.6.6+)_
|
||||
|
||||
The audit log has been expanded with cursor-based pagination, request context enrichment (request ID, user agent, IP), structured auth events, provider CRUD events with diff context, and SSRF-blocked validation logging. New events emitted by `src/lib/compliance/providerAudit.ts`.
|
||||
|
||||
451
docs/FLY_IO_DEPLOYMENT_GUIDE.md
Normal file
451
docs/FLY_IO_DEPLOYMENT_GUIDE.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# OmniRoute Fly.io 部署指南
|
||||
|
||||
本文档记录 OmniRoute 在 Fly.io 上的实际部署方法,适用于两类场景:
|
||||
|
||||
- 首次把当前项目部署到 Fly.io
|
||||
- 后续代码更新后继续发布
|
||||
- 新项目参考同样流程部署
|
||||
|
||||
本文基于当前项目已经验证通过的配置整理,应用名为 `omniroute`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 部署目标
|
||||
|
||||
- 平台:Fly.io
|
||||
- 部署方式:本地 `flyctl` 直接发布
|
||||
- 运行方式:使用仓库内现有 `Dockerfile` 和 `fly.toml`
|
||||
- 数据持久化:Fly Volume 挂载到 `/data`
|
||||
- 访问地址:`https://omniroute.fly.dev/`
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前项目关键配置
|
||||
|
||||
当前仓库中的 `fly.toml` 已确认包含以下关键项:
|
||||
|
||||
```toml
|
||||
app = 'omniroute'
|
||||
primary_region = 'sin'
|
||||
|
||||
[[mounts]]
|
||||
source = 'data'
|
||||
destination = '/data'
|
||||
|
||||
[processes]
|
||||
app = 'node run-standalone.mjs'
|
||||
|
||||
[http_service]
|
||||
internal_port = 20128
|
||||
|
||||
[env]
|
||||
TZ = "Asia/Shanghai"
|
||||
HOST = "0.0.0.0"
|
||||
HOSTNAME = "0.0.0.0"
|
||||
BIND = "0.0.0.0"
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `app = 'omniroute'` 决定实际部署到哪个 Fly 应用
|
||||
- `destination = '/data'` 决定持久卷挂载目录
|
||||
- 本项目必须让 `DATA_DIR=/data`,否则数据库和密钥会写到容器临时目录
|
||||
|
||||
---
|
||||
|
||||
## 3. 必备工具
|
||||
|
||||
### 3.1 安装 Fly CLI
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex"
|
||||
```
|
||||
|
||||
如果安装脚本在当前环境失败,也可以手动下载 `flyctl` 二进制并放到 `PATH` 中。
|
||||
|
||||
### 3.2 登录 Fly 账号
|
||||
|
||||
```powershell
|
||||
flyctl auth login
|
||||
```
|
||||
|
||||
### 3.3 检查登录状态
|
||||
|
||||
```powershell
|
||||
flyctl auth whoami
|
||||
flyctl version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 首次部署当前项目
|
||||
|
||||
### 4.1 获取代码并进入目录
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/xiaoge1688/OmniRoute.git
|
||||
cd OmniRoute
|
||||
```
|
||||
|
||||
### 4.2 确认应用名
|
||||
|
||||
打开 `fly.toml`,重点看这一行:
|
||||
|
||||
```toml
|
||||
app = 'omniroute'
|
||||
```
|
||||
|
||||
如果你准备部署到自己的新应用,可改成全局唯一名称,例如:
|
||||
|
||||
```toml
|
||||
app = 'omniroute-yourname'
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 控制台里要看的是与 `fly.toml` 里 `app` 一致的应用
|
||||
- 以前如果用过别的名字,例如 `oroute`,不要和 `omniroute` 混淆
|
||||
|
||||
### 4.3 创建应用
|
||||
|
||||
如果该应用尚不存在:
|
||||
|
||||
```powershell
|
||||
flyctl apps create omniroute
|
||||
```
|
||||
|
||||
如果你已经改成别的应用名,把 `omniroute` 替换成你的名字。
|
||||
|
||||
### 4.4 首次部署
|
||||
|
||||
```powershell
|
||||
flyctl deploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 必配参数
|
||||
|
||||
本项目在 Fly.io 上建议至少配置以下参数。
|
||||
|
||||
### 5.1 已验证使用的参数
|
||||
|
||||
这些参数已经在当前 `omniroute` 应用上实际部署:
|
||||
|
||||
- `API_KEY_SECRET`
|
||||
- `DATA_DIR`
|
||||
- `JWT_SECRET`
|
||||
- `MACHINE_ID_SALT`
|
||||
- `NEXT_PUBLIC_BASE_URL`
|
||||
- `STORAGE_ENCRYPTION_KEY`
|
||||
|
||||
### 5.2 关于 `INITIAL_PASSWORD`
|
||||
|
||||
当前项目没有设置 `INITIAL_PASSWORD`,因为本次部署按需求不使用它。
|
||||
|
||||
如果不设置:
|
||||
|
||||
- 启动日志会提示默认密码是 `CHANGEME`
|
||||
- 部署后应尽快在系统设置中修改登录密码
|
||||
|
||||
如果你希望无人值守初始化后台密码,也可以后续补:
|
||||
|
||||
- `INITIAL_PASSWORD`
|
||||
|
||||
---
|
||||
|
||||
## 6. 推荐参数说明
|
||||
|
||||
### 6.1 Secrets 中设置
|
||||
|
||||
建议放入 Fly Secrets:
|
||||
|
||||
| 变量名 | 是否推荐 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `API_KEY_SECRET` | 必需 | API Key 生成与校验使用 |
|
||||
| `JWT_SECRET` | 必需 | 登录态和 JWT 签名使用 |
|
||||
| `STORAGE_ENCRYPTION_KEY` | 强烈推荐 | 加密存储敏感连接信息 |
|
||||
| `MACHINE_ID_SALT` | 推荐 | 生成稳定机器标识 |
|
||||
| `INITIAL_PASSWORD` | 可选 | 首次部署时直接指定后台初始密码 |
|
||||
| OAuth/API 私密凭证 | 按需 | 各类外部平台鉴权配置 |
|
||||
|
||||
### 6.2 当前项目推荐值
|
||||
|
||||
| 变量名 | 推荐值 |
|
||||
| --- | --- |
|
||||
| `DATA_DIR` | `/data` |
|
||||
| `NEXT_PUBLIC_BASE_URL` | `https://omniroute.fly.dev` |
|
||||
|
||||
说明:
|
||||
|
||||
- `DATA_DIR=/data` 非常关键,必须与 Fly Volume 挂载点一致
|
||||
- `NEXT_PUBLIC_BASE_URL` 用于调度器和前端回调等场景
|
||||
|
||||
---
|
||||
|
||||
## 7. 一键设置参数
|
||||
|
||||
下面命令会生成安全随机值,并把当前项目需要的参数一次性写入 Fly Secrets。
|
||||
|
||||
说明:
|
||||
|
||||
- 不包含 `INITIAL_PASSWORD`
|
||||
- 适用于当前项目 `omniroute`
|
||||
|
||||
```powershell
|
||||
$apiKeySecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
|
||||
$jwtSecret = [Convert]::ToHexString((1..64 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
|
||||
$machineIdSalt = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
|
||||
$storageKey = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
|
||||
|
||||
flyctl secrets set `
|
||||
API_KEY_SECRET=$apiKeySecret `
|
||||
JWT_SECRET=$jwtSecret `
|
||||
MACHINE_ID_SALT=$machineIdSalt `
|
||||
STORAGE_ENCRYPTION_KEY=$storageKey `
|
||||
DATA_DIR=/data `
|
||||
NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev `
|
||||
-a omniroute
|
||||
```
|
||||
|
||||
如果你还要加初始密码:
|
||||
|
||||
```powershell
|
||||
flyctl secrets set INITIAL_PASSWORD=你的强密码 -a omniroute
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 查看当前参数
|
||||
|
||||
```powershell
|
||||
flyctl secrets list -a omniroute
|
||||
```
|
||||
|
||||
如果控制台 `Secrets` 页面没有显示你期待的变量,先检查:
|
||||
|
||||
- 看的应用是不是 `omniroute`
|
||||
- `fly.toml` 的 `app` 是否和控制台应用一致
|
||||
|
||||
---
|
||||
|
||||
## 9. 后续更新发布
|
||||
|
||||
代码有更新后,发布步骤很简单:
|
||||
|
||||
```powershell
|
||||
git pull
|
||||
flyctl deploy
|
||||
```
|
||||
|
||||
如果只更新参数,不改代码:
|
||||
|
||||
```powershell
|
||||
flyctl secrets set KEY=value -a omniroute
|
||||
```
|
||||
|
||||
Fly 会自动滚动更新机器。
|
||||
|
||||
### 9.1 跟踪原仓库更新并保留 fork 的 `fly.toml`
|
||||
|
||||
如果当前仓库是 fork,并且你要同步上游 `https://github.com/diegosouzapw/OmniRoute` 的更新,推荐按下面流程执行。
|
||||
|
||||
先确认远程:
|
||||
|
||||
```powershell
|
||||
git remote -v
|
||||
```
|
||||
|
||||
应至少包含:
|
||||
|
||||
- `origin` 指向你自己的 fork
|
||||
- `upstream` 指向原仓库
|
||||
|
||||
如果没有 `upstream`,先添加:
|
||||
|
||||
```powershell
|
||||
git remote add upstream https://github.com/diegosouzapw/OmniRoute.git
|
||||
```
|
||||
|
||||
同步上游前,先抓取最新提交和标签:
|
||||
|
||||
```powershell
|
||||
git fetch upstream --tags
|
||||
```
|
||||
|
||||
查看当前版本和上游标签:
|
||||
|
||||
```powershell
|
||||
git describe --tags --always
|
||||
git show --no-patch --oneline v3.4.7
|
||||
```
|
||||
|
||||
如果你想合并上游最新 `main`,并强制保留 fork 当前的 `fly.toml`,可按下面流程执行:
|
||||
|
||||
```powershell
|
||||
git merge upstream/main
|
||||
git checkout HEAD~1 -- fly.toml
|
||||
git add -- fly.toml
|
||||
git commit -m "chore(deploy): keep fork fly.toml"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `git merge upstream/main` 用于同步原仓库最新代码
|
||||
- `git checkout HEAD~1 -- fly.toml` 用于恢复合并前你 fork 自己的 `fly.toml`
|
||||
- 如果上游没有改 `fly.toml`,这一步不会带来额外差异
|
||||
- 如果上游改了 `fly.toml`,这一步能确保 Fly 应用名、挂载卷、区域等 fork 自定义部署配置不被覆盖
|
||||
|
||||
如果你明确只想对齐某个发布标签,例如 `v3.4.7`,也可以先确认标签是否已经包含在 `upstream/main`:
|
||||
|
||||
```powershell
|
||||
git merge-base --is-ancestor v3.4.7 upstream/main
|
||||
```
|
||||
|
||||
返回成功表示 `upstream/main` 已经包含该版本,直接合并 `upstream/main` 即可。
|
||||
|
||||
### 9.2 同步上游后的标准发布顺序
|
||||
|
||||
同步原仓库完成后,推荐按下面顺序发布:
|
||||
|
||||
1. `git fetch upstream --tags`
|
||||
2. `git merge upstream/main`
|
||||
3. 恢复 fork 的 `fly.toml`
|
||||
4. `git push origin main`
|
||||
5. `flyctl deploy`
|
||||
6. `flyctl status -a omniroute`
|
||||
7. `flyctl logs --no-tail -a omniroute`
|
||||
|
||||
这就是当前项目升级到 `v3.4.7` 时使用的实际流程。
|
||||
|
||||
---
|
||||
|
||||
## 10. 发布后检查
|
||||
|
||||
### 10.1 查看应用状态
|
||||
|
||||
```powershell
|
||||
flyctl status -a omniroute
|
||||
```
|
||||
|
||||
### 10.2 查看启动日志
|
||||
|
||||
```powershell
|
||||
flyctl logs --no-tail -a omniroute
|
||||
```
|
||||
|
||||
### 10.3 检查网站可访问
|
||||
|
||||
```powershell
|
||||
try {
|
||||
(Invoke-WebRequest -Uri "https://omniroute.fly.dev" -MaximumRedirection 5 -UseBasicParsing).StatusCode
|
||||
} catch {
|
||||
if ($_.Exception.Response) {
|
||||
$_.Exception.Response.StatusCode.value__
|
||||
} else {
|
||||
throw
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
返回 `200` 说明站点已正常响应。
|
||||
|
||||
---
|
||||
|
||||
## 11. 成功标志
|
||||
|
||||
部署成功后,日志里应看到类似内容:
|
||||
|
||||
```text
|
||||
[bootstrap] Secrets persisted to: /data/server.env
|
||||
[DB] SQLite database ready: /data/storage.sqlite
|
||||
```
|
||||
|
||||
这两个点很关键:
|
||||
|
||||
- `/data/server.env` 说明运行时密钥落到了持久卷
|
||||
- `/data/storage.sqlite` 说明数据库写入持久卷
|
||||
|
||||
如果你看到的是 `/app/data/...`,说明 `DATA_DIR` 没配对,需要立即修正。
|
||||
|
||||
---
|
||||
|
||||
## 12. 常见问题
|
||||
|
||||
### 12.1 `Secrets` 页面是空的
|
||||
|
||||
通常有两种原因:
|
||||
|
||||
- 你还没执行 `flyctl secrets set`
|
||||
- 你打开的是另一个应用,例如 `oroute`,不是 `omniroute`
|
||||
|
||||
### 12.2 `flyctl deploy` 报 `app not found`
|
||||
|
||||
先创建应用:
|
||||
|
||||
```powershell
|
||||
flyctl apps create omniroute
|
||||
```
|
||||
|
||||
### 12.3 `fly.toml` 解析失败
|
||||
|
||||
重点检查:
|
||||
|
||||
- 注释里是否有乱码字符
|
||||
- TOML 引号和缩进是否正确
|
||||
|
||||
### 12.4 数据没有持久化
|
||||
|
||||
检查以下两点:
|
||||
|
||||
- `fly.toml` 中是否存在 `destination = '/data'`
|
||||
- `DATA_DIR` 是否设置为 `/data`
|
||||
|
||||
### 12.5 不设置 `INITIAL_PASSWORD` 是否能跑
|
||||
|
||||
可以运行,但会回退到默认 `CHANGEME`。生产环境建议尽快修改后台密码。
|
||||
|
||||
---
|
||||
|
||||
## 13. 新项目复用建议
|
||||
|
||||
如果以后是新项目照着这份文档部署,最少改这几项:
|
||||
|
||||
1. 修改 `fly.toml` 里的 `app`
|
||||
2. 修改 `NEXT_PUBLIC_BASE_URL`
|
||||
3. 保持 `DATA_DIR=/data`
|
||||
4. 重新生成 `API_KEY_SECRET`、`JWT_SECRET`、`MACHINE_ID_SALT`、`STORAGE_ENCRYPTION_KEY`
|
||||
5. 首次部署后检查日志是否写入 `/data`
|
||||
|
||||
不要直接复用旧项目的密钥。
|
||||
|
||||
---
|
||||
|
||||
## 14. 当前项目的最小发布清单
|
||||
|
||||
当前项目后续最常用的命令如下:
|
||||
|
||||
```powershell
|
||||
flyctl auth whoami
|
||||
flyctl status -a omniroute
|
||||
flyctl secrets list -a omniroute
|
||||
flyctl deploy
|
||||
flyctl logs --no-tail -a omniroute
|
||||
```
|
||||
|
||||
如果只是正常发版,核心就是:
|
||||
|
||||
```powershell
|
||||
flyctl deploy
|
||||
```
|
||||
|
||||
如果是新环境首次部署,核心就是:
|
||||
|
||||
1. `flyctl auth login`
|
||||
2. `flyctl apps create omniroute`
|
||||
3. `flyctl secrets set ... -a omniroute`
|
||||
4. `flyctl deploy`
|
||||
5. `flyctl logs --no-tail -a omniroute`
|
||||
407
docs/FREE_TIERS.md
Normal file
407
docs/FREE_TIERS.md
Normal file
@@ -0,0 +1,407 @@
|
||||
# 🆓 Free LLM API Providers — Consolidated Directory
|
||||
|
||||
> **The ultimate aggregated reference for all permanently free LLM API providers.**
|
||||
> Consolidated from 6 community repositories. Use with OmniRoute to route through 25+ free providers simultaneously.
|
||||
|
||||
_Last consolidated: May 2026 · Sources: awesome-free-llm-apis, awesome-free-llm-apis2, free-llm-api-resources, Free-LLM-Collection, FREE-LLM-API-Provider, gpt4free_
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Quick Comparison](#quick-comparison)
|
||||
- [Provider APIs (First-Party)](#provider-apis-first-party)
|
||||
- [Inference Providers (Third-Party)](#inference-providers-third-party)
|
||||
- [China-Based Providers](#china-based-providers)
|
||||
- [Trial Credit Providers](#trial-credit-providers)
|
||||
- [Using with OmniRoute](#using-with-omniroute)
|
||||
- [Glossary](#glossary)
|
||||
|
||||
---
|
||||
|
||||
## Quick Comparison
|
||||
|
||||
All free providers at a glance, sorted by generosity of free tier:
|
||||
|
||||
| Provider | Type | Best Free Model | RPM | RPD | Tokens | OpenAI Compat | Speed |
|
||||
| ----------------- | --------- | ---------------------- | ------- | ------------ | ---------------- | --------------- | --------- |
|
||||
| **Groq** | Inference | Llama 3.3 70B | 30 | 14,400 | 6K TPM | ✅ | 🟢 Fast |
|
||||
| **Cerebras** | Inference | Qwen3 235B | 30 | 14,400 | 1M TPD | ✅ | 🟢 Fast |
|
||||
| **Mistral AI** | Provider | Mistral Large 3 | 60 | Unlimited | 1B/month | ✅ | 🟡 Medium |
|
||||
| **Google Gemini** | Provider | Gemini 2.5 Flash | 5–15 | 20–1,500 | 250K TPM | ✅ | 🟢 Fast |
|
||||
| **NVIDIA NIM** | Inference | 129 models | 40 | — | — | ✅ | 🟡 Medium |
|
||||
| **Ollama Cloud** | Inference | 400+ models | — | — | Session limits | ❌ (Ollama API) | 🟡 Medium |
|
||||
| **OpenRouter** | Inference | 35+ free models | 20 | 50–1,000 | — | ✅ | 🟡 Medium |
|
||||
| **GitHub Models** | Inference | GPT-4.1, GPT-5 | 10–15 | 50–150 | 8K in/4K out | ✅ | 🟡 Medium |
|
||||
| **Cloudflare AI** | Inference | 50+ models | — | 10K neurons | — | ⚠️ Partial | 🟡 Medium |
|
||||
| **Hugging Face** | Inference | Thousands | — | — | $0.10/mo credits | ✅ | 🔴 Slow |
|
||||
| **Cohere** | Provider | Command A (111B) | 20 | — | 1K calls/month | ⚠️ Partial | 🟡 Medium |
|
||||
| **Pollinations** | Inference | Text+Image+Video+Audio | — | Hourly reset | — | ✅ | 🟡 Medium |
|
||||
| **Z.AI (Zhipu)** | Provider | GLM-4.7-Flash | — | — | Undocumented | ✅ | 🟡 Medium |
|
||||
| **SiliconFlow** | Inference | Qwen3-8B | 1,000 | — | 50K TPM | ✅ | 🟡 Medium |
|
||||
| **Kilo Code** | Inference | Free auto-router | ~200/hr | — | — | ✅ | 🟡 Medium |
|
||||
| **LLM7.io** | Inference | 30+ models | 15–30 | — | — | ✅ | 🟡 Medium |
|
||||
| **Kluster AI** | Inference | DeepSeek-R1 | — | — | Undocumented | ✅ | 🟡 Medium |
|
||||
| **ModelScope** | Inference | Qwen, DeepSeek | — | 2,000 | ≤500/model/day | ✅ | 🟡 Medium |
|
||||
| **IBM watsonx** | Provider | Granite models | 2/sec | — | 300K/month | ❌ | 🟡 Medium |
|
||||
|
||||
---
|
||||
|
||||
## Provider APIs (First-Party)
|
||||
|
||||
APIs from the companies that train or fine-tune the models.
|
||||
|
||||
### Google Gemini 🇺🇸
|
||||
|
||||
🔗 [Get API Key](https://aistudio.google.com/app/apikey) · Base URL: `https://generativelanguage.googleapis.com/v1beta`
|
||||
|
||||
> ⚠️ Free tier NOT available in EU/UK/Switzerland. Prompts may be used by Google to improve products.
|
||||
|
||||
| Model | Context | Max Output | Modality | RPM | RPD |
|
||||
| --------------------------------- | ------- | ---------- | ---------------------- | --- | ------ |
|
||||
| Gemini 2.5 Flash / Gemini 3 Flash | 1M | 65K | Text+Image+Audio+Video | 5 | 20 |
|
||||
| Gemini 2.5 Flash-Lite | 1M | 65K | Text+Image+Audio+Video | 10 | 20 |
|
||||
| Gemini 3.1 Flash-Lite | 1M | 65K | Text+Image+Audio+Video | 15 | 1,500 |
|
||||
| Gemma 4 26B/31B | — | — | Text | 15 | 1,500 |
|
||||
| Gemma 3 (1B/4B/12B/27B) | — | — | Text | 30 | 14,400 |
|
||||
|
||||
### Mistral AI 🇫🇷
|
||||
|
||||
🔗 [Get API Key](https://console.mistral.ai/api-keys) · Base URL: `https://api.mistral.ai/v1`
|
||||
|
||||
Free "Experiment" plan, no credit card. ~1B tokens/month. Requires phone verification.
|
||||
|
||||
| Model | Context | Max Output | Modality | Rate Limit |
|
||||
| ------------------ | ------- | ---------- | --------------- | --------------- |
|
||||
| Mistral Small 4 | 256K | 256K | Text+Image+Code | 1 RPS, 500K TPM |
|
||||
| Mistral Medium 3 | 128K | 128K | Text | 1 RPS, 500K TPM |
|
||||
| Mistral Large 3 | 256K | 256K | Text | 1 RPS, 500K TPM |
|
||||
| Mistral Nemo (12B) | 128K | 128K | Text | 1 RPS, 500K TPM |
|
||||
| Codestral | 256K | 256K | Code | 30 RPM, 2K RPD |
|
||||
| Pixtral Large | 128K | 128K | Text+Image | 1 RPS, 500K TPM |
|
||||
|
||||
### Cohere 🇨🇦
|
||||
|
||||
🔗 [Get API Key](https://dashboard.cohere.com/api-keys) · Base URL: `https://api.cohere.com/v2`
|
||||
|
||||
Free "Trial" key. 1,000 API calls/month. Non-commercial use only. 20 RPM.
|
||||
|
||||
| Model | Context | Max Output | Modality |
|
||||
| ------------------- | ------- | ---------- | ----------------------- |
|
||||
| Command A (111B) | 256K | 4K | Text |
|
||||
| Command A Reasoning | 256K | 4K | Text (reasoning) |
|
||||
| Command A Vision | 256K | 4K | Text+Image |
|
||||
| Command A Translate | 256K | 4K | Translation |
|
||||
| Command R+ | 128K | 4K | Text |
|
||||
| Command R | 128K | 4K | Text |
|
||||
| Command R7B | 128K | 4K | Text |
|
||||
| Embed 4 | — | — | Embeddings (Text+Image) |
|
||||
| Rerank 3.5 | — | — | Reranking |
|
||||
|
||||
### Z.AI (Zhipu AI) 🇨🇳
|
||||
|
||||
🔗 [Get API Key](https://open.bigmodel.cn/usercenter/apikeys) · Base URL: `https://open.bigmodel.cn/api/paas/v4`
|
||||
|
||||
Permanent free models, no credit card. No published rate limits.
|
||||
|
||||
| Model | Context | Max Output | Modality |
|
||||
| --------------- | ------- | ---------- | ---------- |
|
||||
| GLM-4.7-Flash | 200K | 128K | Text |
|
||||
| GLM-4.5-Flash | 128K | ~8K | Text |
|
||||
| GLM-4.6V-Flash | 128K | ~4K | Text+Image |
|
||||
| GLM-5 / GLM-5.1 | — | — | Text |
|
||||
|
||||
### IBM watsonx 🇺🇸
|
||||
|
||||
🔗 [Pricing](https://www.ibm.com/products/watsonx-ai/pricing)
|
||||
|
||||
Free tier: 2 RPS, 300K tokens/month. Granite foundation models.
|
||||
|
||||
---
|
||||
|
||||
## Inference Providers (Third-Party)
|
||||
|
||||
### Groq 🇺🇸
|
||||
|
||||
🔗 [Get API Key](https://console.groq.com/keys) · Base URL: `https://api.groq.com/openai/v1`
|
||||
|
||||
Ultra-fast LPU inference (~300–500 tok/s). No credit card required.
|
||||
|
||||
| Model | RPM | RPD | TPM | Modality |
|
||||
| ---------------------------------- | --- | ------ | --- | ---------------- |
|
||||
| llama-3.3-70b-versatile | 30 | 1,000 | 12K | Text |
|
||||
| llama-3.1-8b-instant | 30 | 14,400 | 6K | Text |
|
||||
| llama-4-scout-17b-16e-instruct | 30 | 1,000 | 30K | Text+Vision |
|
||||
| llama-4-maverick-17b-128e-instruct | 30 | 1,000 | 6K | Text+Vision |
|
||||
| qwen3-32b | 60 | 1,000 | 6K | Text |
|
||||
| kimi-k2-instruct | 60 | 1,000 | 10K | Text |
|
||||
| gpt-oss-120b / gpt-oss-20b | 30 | 1,000 | 8K | Text |
|
||||
| deepseek-r1-distill-70b | 30 | 14,400 | — | Text (reasoning) |
|
||||
| whisper-large-v3 / v3-turbo | 20 | 2,000 | — | Audio→Text |
|
||||
|
||||
### Cerebras 🇺🇸
|
||||
|
||||
🔗 [Get API Key](https://cloud.cerebras.ai/) · Base URL: `https://api.cerebras.ai/v1`
|
||||
|
||||
Wafer-scale chip inference (~2,600 tok/s). 1M tokens/day cap.
|
||||
|
||||
| Model | RPM | RPH | RPD | TPM | TPD |
|
||||
| ------------------------------ | --- | --- | ------ | --- | --- |
|
||||
| gpt-oss-120b | 30 | 900 | 14,400 | 64K | 1M |
|
||||
| llama3.1-8b | 30 | 900 | 14,400 | 60K | 1M |
|
||||
| qwen-3-235b-a22b-instruct-2507 | 30 | 900 | 14,400 | 60K | 1M |
|
||||
| zai-glm-4.7 | 10 | 100 | 100 | 60K | 1M |
|
||||
|
||||
### NVIDIA NIM 🇺🇸
|
||||
|
||||
🔗 [Explore Models](https://build.nvidia.com/explore/discover) · Base URL: `https://integrate.api.nvidia.com/v1`
|
||||
|
||||
Free with NVIDIA Developer Program. **129 models**, 40 RPM. Phone verification required.
|
||||
|
||||
**Notable models:** DeepSeek-R1, DeepSeek-V3.2, Nemotron Ultra 253B, Llama 3.1 405B, Qwen3 Coder 480B, Mistral Large 3, Kimi K2, GLM-5.1, MiniMax M2.7, Gemma 4 31B, + 100 more.
|
||||
|
||||
### OpenRouter 🇺🇸
|
||||
|
||||
🔗 [Get API Key](https://openrouter.ai/keys) · Base URL: `https://openrouter.ai/api/v1`
|
||||
|
||||
35+ free models (suffix `:free`). 20 RPM.
|
||||
|
||||
| Credits Purchased | RPD |
|
||||
| ----------------- | ----- |
|
||||
| < $10 | 50 |
|
||||
| ≥ $10 (one-time) | 1,000 |
|
||||
|
||||
**Notable free models:** DeepSeek R1, DeepSeek V3, Qwen3 Coder 480B, Llama 4 Scout/Maverick, GPT-OSS 120B, Nemotron 3 Super 120B, MiniMax M2.5, Gemma 4 31B, Devstral, + 23 more.
|
||||
|
||||
### GitHub Models 🇺🇸
|
||||
|
||||
🔗 [Marketplace](https://github.com/marketplace/models) · Base URL: `https://models.inference.ai.azure.com`
|
||||
|
||||
Free for all GitHub users. 45+ models including frontier models.
|
||||
|
||||
| Tier | RPM | RPD | Tokens/Request |
|
||||
| ----------------------- | --- | --- | -------------- |
|
||||
| Low tier models | 15 | 150 | 8K in / 4K out |
|
||||
| High tier models | 10 | 50 | 8K in / 4K out |
|
||||
| DeepSeek-R1 / MAI-DS-R1 | 1 | 8 | 4K in / 4K out |
|
||||
| Grok-3 | 1 | 15 | 4K in / 4K out |
|
||||
|
||||
**Notable models:** GPT-4.1, GPT-4o, GPT-5, GPT-5-mini, o3-mini, o4-mini, DeepSeek-R1, Llama 4 Scout/Maverick, Codestral, Mistral Medium 3, Phi-4, Grok-3.
|
||||
|
||||
### Cloudflare Workers AI 🇺🇸
|
||||
|
||||
🔗 [Get Token](https://dash.cloudflare.com/profile/api-tokens) · 10,000 Neurons/day free. 50+ models.
|
||||
|
||||
**Notable models:** Llama 3.3 70B, Llama 4 Scout, Qwen3 30B-A3B, QwQ 32B, DeepSeek R1 Distill, Gemma 4 26B, GLM 4.7 Flash, Nemotron 3 120B, Kimi K2.5/K2.6, Mistral Small 3.1, GPT-OSS 120B/20B, + 40 more.
|
||||
|
||||
### Hugging Face 🇺🇸
|
||||
|
||||
🔗 [Get Token](https://huggingface.co/settings/tokens) · Base URL: `https://api-inference.huggingface.co/v1`
|
||||
|
||||
$0.10/month free credits (auto-replenished). Thousands of models. Serverless limited to <10GB models.
|
||||
|
||||
### Ollama Cloud 🇺🇸
|
||||
|
||||
🔗 [Get Key](https://ollama.com/settings/keys) · Base URL: `https://api.ollama.com`
|
||||
|
||||
400+ models. Session/weekly limits (unpublished). NOT OpenAI SDK-compatible.
|
||||
|
||||
**Notable models:** GPT-OSS 120B, DeepSeek V3.2/V4, Kimi K2/K2.5/K2.6, GLM-5/5.1, Qwen3 Coder 480B, Gemini 3 Flash, MiniMax M2.7, Cogito 2.1 671B, Nemotron 3 Super 120B.
|
||||
|
||||
### Pollinations AI 🇩🇪
|
||||
|
||||
🔗 [Get Key](https://enter.pollinations.ai) · Base URL: `https://gen.pollinations.ai/v1`
|
||||
|
||||
No sign-up required for basic use. Unique: **text + image + video + audio** all free.
|
||||
|
||||
**Text models:** openai, openai-large, openai-reasoning, gemini, mistral, llama.
|
||||
**Image models:** flux, gpt-image, seedream, kontext.
|
||||
**Video:** wan-fast. **Audio:** tts-1, 30+ ElevenLabs voices.
|
||||
|
||||
### SiliconFlow 🇨🇳
|
||||
|
||||
🔗 [Get Key](https://cloud.siliconflow.cn/account/ak) · Base URL: `https://api.siliconflow.cn/v1`
|
||||
|
||||
14 CNY signup credits. Permanently free models: 1,000 RPM, 50K TPM.
|
||||
|
||||
| Model | Context | Modality |
|
||||
| --------------------------- | ------- | ---------------- |
|
||||
| Qwen/Qwen3-8B | 131K | Text |
|
||||
| DeepSeek-R1-0528-Qwen3-8B | ~33K | Text (reasoning) |
|
||||
| DeepSeek-R1-Distill-Qwen-7B | 131K | Text (reasoning) |
|
||||
| THUDM/glm-4-9b-chat | 32K | Text |
|
||||
| THUDM/GLM-4.1V-9B-Thinking | 66K | Vision+Text |
|
||||
| DeepSeek-OCR | — | Vision (OCR) |
|
||||
|
||||
### Kilo Code 🇺🇸
|
||||
|
||||
🔗 [Get Key](https://kilo.ai) · Base URL: `https://api.kilo.ai/api/gateway`
|
||||
|
||||
Free models, no credit card. ~200 req/hr. Auto-router `kilo-auto/free`.
|
||||
|
||||
### LLM7.io 🇬🇧
|
||||
|
||||
🔗 [Get Token](https://token.llm7.io) · Base URL: `https://api.llm7.io/v1`
|
||||
|
||||
30+ models. 15 RPM (30 RPM with free token). No registration for basic access.
|
||||
|
||||
### Kluster AI 🇺🇸
|
||||
|
||||
🔗 [Get Key](https://platform.kluster.ai/apikeys) · DeepSeek-R1, Llama 4 Maverick, Qwen3-235B + more.
|
||||
|
||||
### OpenCode Zen
|
||||
|
||||
🔗 [Docs](https://opencode.ai/docs/zen/) · Free models (Big Pickle Stealth, MiniMax M2.5 Free, Arcee Large).
|
||||
|
||||
### Vercel AI Gateway
|
||||
|
||||
🔗 [Docs](https://vercel.com/docs/ai-gateway) · $5/month free credits. Routes to various providers.
|
||||
|
||||
---
|
||||
|
||||
## China-Based Providers
|
||||
|
||||
### ModelScope (魔搭社区) 🇨🇳
|
||||
|
||||
🔗 [Get Token](https://modelscope.cn/my/myaccesstoken) · Base URL: `https://api-inference.modelscope.cn/v1`
|
||||
|
||||
2,000 req/day total, ≤500/model/day. Requires Alibaba Cloud account + real-name verification.
|
||||
|
||||
**Models:** DeepSeek V4 Pro/Flash, DeepSeek V3.2, GLM-5/5.1, MiniMax M2.5, Qwen3-235B, Qwen3 Coder 480B, Ling-2.6-1T.
|
||||
|
||||
### Tencent Hunyuan (腾讯混元)
|
||||
|
||||
Hunyuan-Lite: free. Other models: 100M tokens free (1-year expiry).
|
||||
|
||||
### Volcengine (火山引擎)
|
||||
|
||||
500 resource points/day. Tongyi Qwen free (100 calls/day). Doubao models with tiered pricing.
|
||||
|
||||
### ChatAnywhere
|
||||
|
||||
🔗 Base URL: `https://api.chatanywhere.tech` · GPT-5.4-mini, DeepSeek-V4, and more.
|
||||
|
||||
### InternAI (书生)
|
||||
|
||||
🔗 Base URL: `https://chat.intern-ai.org.cn/api/v1` · 10 RPM. Keys valid 6 months.
|
||||
|
||||
**Models:** intern-latest, intern-s1-pro, internvl3.5-241b-a28b.
|
||||
|
||||
### Bigmodel (智谱)
|
||||
|
||||
🔗 Base URL: `https://open.bigmodel.cn/api/paas/v4/` · 30 concurrent requests.
|
||||
|
||||
**Models:** GLM-4-Flash, GLM-4V-Flash, GLM-4.1V-Thinking-Flash, GLM-4.6V-Flash, GLM-4.7-Flash.
|
||||
|
||||
---
|
||||
|
||||
## Trial Credit Providers
|
||||
|
||||
These offer one-time or time-limited credits (not permanent free tiers):
|
||||
|
||||
| Provider | Credits | Expiry | Notable Models |
|
||||
| ---------------------------------------------------------- | ---------------- | -------- | ----------------------------- |
|
||||
| [Baseten](https://app.baseten.co/) | $30 | — | Any model (pay by compute) |
|
||||
| [NLP Cloud](https://nlpcloud.com) | $15 | — | Various open models |
|
||||
| [AI21](https://studio.ai21.com/) | $10 | 3 months | Jamba family |
|
||||
| [Upstage](https://console.upstage.ai/) | $10 | 3 months | Solar Pro/Mini |
|
||||
| [Modal](https://modal.com) | $5/mo | Monthly | Any model (compute time) |
|
||||
| [SambaNova](https://cloud.sambanova.ai/) | $5 | 3 months | Llama 3.3, Qwen3, DeepSeek R1 |
|
||||
| [Scaleway](https://console.scaleway.com/generative-api) | 1M tokens | One-time | Llama 3.3, Gemma 3, GPT-OSS |
|
||||
| [Alibaba Cloud](https://bailian.console.alibabacloud.com/) | 1M tokens/model | — | Qwen family |
|
||||
| [Fireworks](https://fireworks.ai/) | $1 | — | Various open models |
|
||||
| [Nebius](https://tokenfactory.nebius.com/) | $1 | — | Various open models |
|
||||
| [Inference.net](https://inference.net) | $1 (+$25 survey) | — | Various open models |
|
||||
| [Hyperbolic](https://app.hyperbolic.ai/) | $1 | — | DeepSeek V3, Llama 3.3 |
|
||||
| [Novita](https://novita.ai/) | $0.50 | 1 year | Various open models |
|
||||
|
||||
---
|
||||
|
||||
## Using with OmniRoute
|
||||
|
||||
OmniRoute supports **all providers listed above** as connections. Here's how to maximize free usage:
|
||||
|
||||
### 1. Add Multiple Free Providers
|
||||
|
||||
```
|
||||
Dashboard → Providers → Add Connection
|
||||
```
|
||||
|
||||
Add API keys for Groq, Cerebras, Mistral, Google Gemini, OpenRouter, GitHub Models, etc.
|
||||
|
||||
### 2. Create a Free-Tier Combo
|
||||
|
||||
```
|
||||
Dashboard → Combos → Create Combo → Add all free providers as targets
|
||||
```
|
||||
|
||||
Use the **"priority"** or **"round-robin"** strategy to distribute load across free tiers.
|
||||
|
||||
### 3. Recommended Free Combo Strategy
|
||||
|
||||
| Priority | Provider | Why |
|
||||
| -------- | ----------------- | --------------------------------------------- |
|
||||
| 1 | **Groq** | Fastest inference, 14,400 RPD on small models |
|
||||
| 2 | **Cerebras** | 1M TPD, fast wafer-scale chips |
|
||||
| 3 | **Mistral** | 1B tokens/month, large model selection |
|
||||
| 4 | **Google Gemini** | 1M context, multimodal |
|
||||
| 5 | **NVIDIA NIM** | 129 models, 40 RPM |
|
||||
| 6 | **OpenRouter** | 35+ free models as final fallback |
|
||||
|
||||
### 4. Environment Variables
|
||||
|
||||
```bash
|
||||
# These providers work out of the box with OmniRoute:
|
||||
GROQ_API_KEY=your-key
|
||||
CEREBRAS_API_KEY=your-key
|
||||
MISTRAL_API_KEY=your-key
|
||||
GOOGLE_AI_API_KEY=your-key
|
||||
NVIDIA_API_KEY=your-key
|
||||
OPENROUTER_API_KEY=your-key
|
||||
GITHUB_TOKEN=your-token
|
||||
CLOUDFLARE_API_TOKEN=your-token
|
||||
COHERE_API_KEY=your-key
|
||||
SILICONFLOW_API_KEY=your-key
|
||||
```
|
||||
|
||||
### 5. Estimated Free Capacity
|
||||
|
||||
With all top-6 providers combined in a combo:
|
||||
|
||||
| Metric | Combined Free Capacity |
|
||||
| -------------------- | ---------------------- |
|
||||
| **Requests/Day** | ~31,000+ RPD |
|
||||
| **Tokens/Month** | ~32B+ tokens |
|
||||
| **Models Available** | 200+ unique models |
|
||||
| **Cost** | $0.00 |
|
||||
|
||||
---
|
||||
|
||||
## Glossary
|
||||
|
||||
| Term | Meaning |
|
||||
| ----------- | ------------------------------------------- |
|
||||
| **RPM** | Requests per minute |
|
||||
| **RPD** | Requests per day |
|
||||
| **RPH** | Requests per hour |
|
||||
| **RPS** | Requests per second |
|
||||
| **TPM** | Tokens per minute |
|
||||
| **TPD** | Tokens per day |
|
||||
| **Neurons** | Cloudflare's compute unit (~1 output token) |
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
This document consolidates data from 6 community repositories:
|
||||
|
||||
| Repository | Focus |
|
||||
| -------------------------------------------------------------------------- | ------------------------------------------------ |
|
||||
| [awesome-free-llm-apis](https://github.com/mnfst/awesome-free-llm-apis) | Curated list with detailed model tables |
|
||||
| [awesome-free-llm-apis2](https://github.com/) | Extended list with speed tiers and code snippets |
|
||||
| [free-llm-api-resources](https://github.com/) | Auto-generated model lists with trial credits |
|
||||
| [Free-LLM-Collection](https://github.com/for-the-zero/Free-LLM-Collection) | Chinese + global providers with rate limits |
|
||||
| [FREE-LLM-API-Provider](https://github.com/CYBIRD-D/FREE-LLM-API-Provider) | Deep provider analysis with CN platforms |
|
||||
| [gpt4free](https://github.com/xtekky/gpt4free) | Config-based routing with quota awareness |
|
||||
|
||||
> ⚠️ **Disclaimer:** Rate limits change frequently. Always verify with the provider's official documentation before relying on specific limits. Trial credits and time-limited promotions are separated from permanent free tiers.
|
||||
409
docs/I18N.md
Normal file
409
docs/I18N.md
Normal file
@@ -0,0 +1,409 @@
|
||||
# i18n — Internationalization Guide
|
||||
|
||||
OmniRoute supports **30 languages** with full dashboard UI translation, translated documentation, and RTL support for Arabic and Hebrew.
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](../I18N.md) | 🇧🇷 [Português (Brasil)](./pt-BR/I18N.md) | 🇪🇸 [Español](./es/I18N.md) | 🇫🇷 [Français](./fr/I18N.md) | 🇩🇪 [Deutsch](./de/I18N.md) | 🇮🇹 [Italiano](./it/I18N.md) | 🇷🇺 [Русский](./ru/I18N.md) | 🇨🇳 [中文 (简体)](./zh-CN/I18N.md) | 🇯🇵 [日本語](./ja/I18N.md) | 🇰🇷 [한국어](./ko/I18N.md) | 🇸🇦 [العربية](./ar/I18N.md) | 🇮🇳 [हिन्दी](./hi/I18N.md) | 🇹🇭 [ไทย](./th/I18N.md) | 🇹🇷 [Türkçe](./tr/I18N.md) | 🇺🇦 [Українська](./uk-UA/I18N.md) | 🇻🇳 [Tiếng Việt](./vi/I18N.md) | 🇧🇬 [Български](./bg/I18N.md) | 🇩🇰 [Dansk](./da/I18N.md) | 🇫🇮 [Suomi](./fi/I18N.md) | 🇮🇱 [עברית](./he/I18N.md) | 🇭🇺 [Magyar](./hu/I18N.md) | 🇮🇩 [Bahasa Indonesia](./id/I18N.md) | 🇲🇾 [Bahasa Melayu](./ms/I18N.md) | 🇳🇱 [Nederlands](./nl/I18N.md) | 🇳🇴 [Norsk](./no/I18N.md) | 🇵🇹 [Português (Portugal)](./pt/I18N.md) | 🇷🇴 [Română](./ro/I18N.md) | 🇵🇱 [Polski](./pl/I18N.md) | 🇸🇰 [Slovenčina](./sk/I18N.md) | 🇸🇪 [Svenska](./sv/I18N.md) | 🇵🇭 [Filipino](./phi/I18N.md) | 🇨🇿 [Čeština](./cs/I18N.md)
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Generate translations | `node scripts/i18n/generate-multilang.mjs messages` |
|
||||
| Translate docs (LLM) | `python3 scripts/i18n_autotranslate.py --api-url <url> --api-key <key> --model <model>` |
|
||||
| Validate a locale | `python3 scripts/validate_translation.py quick -l cs` |
|
||||
| Check code keys | `python3 scripts/check_translations.py` |
|
||||
| Generate QA report | `node scripts/i18n/generate-qa-checklist.mjs` |
|
||||
| Visual QA (Playwright) | `node scripts/i18n/run-visual-qa.mjs` |
|
||||
|
||||
## Architecture
|
||||
|
||||
### Source of Truth
|
||||
- **UI strings**: `src/i18n/messages/en.json` (English source, ~2800 keys)
|
||||
- **Locale files**: `src/i18n/messages/{locale}.json` (30 translations)
|
||||
- **Framework**: `next-intl` with cookie-based locale resolution
|
||||
- **Config**: `src/i18n/config.ts` — defines all 30 locales, language names, flags
|
||||
|
||||
### Runtime Flow
|
||||
1. User selects language → `NEXT_LOCALE` cookie set
|
||||
2. `src/i18n/request.ts` resolves locale: cookie → `Accept-Language` header → fallback `en`
|
||||
3. Dynamic import loads `messages/{locale}.json`
|
||||
4. Components use `useTranslations("namespace")` and `t("key")`
|
||||
|
||||
### Supported Locales
|
||||
|
||||
| Code | Language | RTL | Google Translate Code |
|
||||
|------|----------|-----|----------------------|
|
||||
| `ar` | العربية | Yes | `ar` |
|
||||
| `bg` | Български | No | `bg` |
|
||||
| `cs` | Čeština | No | `cs` |
|
||||
| `da` | Dansk | No | `da` |
|
||||
| `de` | Deutsch | No | `de` |
|
||||
| `es` | Español | No | `es` |
|
||||
| `fi` | Suomi | No | `fi` |
|
||||
| `fr` | Français | No | `fr` |
|
||||
| `he` | עברית | Yes | `iw` |
|
||||
| `hi` | हिन्दी | No | `hi` |
|
||||
| `hu` | Magyar | No | `hu` |
|
||||
| `id` | Bahasa Indonesia | No | `id` |
|
||||
| `it` | Italiano | No | `it` |
|
||||
| `ja` | 日本語 | No | `ja` |
|
||||
| `ko` | 한국어 | No | `ko` |
|
||||
| `ms` | Bahasa Melayu | No | `ms` |
|
||||
| `nl` | Nederlands | No | `nl` |
|
||||
| `no` | Norsk | No | `no` |
|
||||
| `phi` | Filipino | No | `tl` |
|
||||
| `pl` | Polski | No | `pl` |
|
||||
| `pt` | Português (Portugal) | No | `pt` |
|
||||
| `pt-BR` | Português (Brasil) | No | `pt` |
|
||||
| `ro` | Română | No | `ro` |
|
||||
| `ru` | Русский | No | `ru` |
|
||||
| `sk` | Slovenčina | No | `sk` |
|
||||
| `sv` | Svenska | No | `sv` |
|
||||
| `th` | ไทย | No | `th` |
|
||||
| `tr` | Türkçe | No | `tr` |
|
||||
| `uk-UA` | Українська | No | `uk` |
|
||||
| `vi` | Tiếng Việt | No | `vi` |
|
||||
| `zh-CN` | 中文 (简体) | No | `zh-CN` |
|
||||
|
||||
## Adding a New Language
|
||||
|
||||
### 1. Register the Locale
|
||||
Edit `src/i18n/config.ts`:
|
||||
```ts
|
||||
// Add to LOCALES array
|
||||
"xx",
|
||||
// Add to LANGUAGES array
|
||||
{ code: "xx", label: "XX", name: "Language Name", flag: "🏳️" },
|
||||
```
|
||||
|
||||
### 2. Add to Generator
|
||||
Edit `scripts/i18n/generate-multilang.mjs` — add entry to `LOCALE_SPECS`:
|
||||
```js
|
||||
{
|
||||
code: "xx",
|
||||
googleTl: "xx",
|
||||
label: "XX",
|
||||
flag: "🏳️",
|
||||
languageName: "Language Name",
|
||||
readmeName: "Language Name",
|
||||
docsName: "Language Name",
|
||||
},
|
||||
```
|
||||
|
||||
### 3. Generate Initial Translation
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs messages
|
||||
```
|
||||
This creates `src/i18n/messages/xx.json` auto-translated from `en.json` via Google Translate.
|
||||
|
||||
### 4. Review & Fix Auto-Translations
|
||||
Auto-translations are a starting point. Review manually for:
|
||||
- Technical accuracy
|
||||
- Context-appropriate terminology
|
||||
- Proper handling of placeholders (`{count}`, `{value}`, etc.)
|
||||
|
||||
### 5. Validate
|
||||
```bash
|
||||
python3 scripts/validate_translation.py quick -l xx
|
||||
python3 scripts/validate_translation.py diff common -l xx
|
||||
```
|
||||
|
||||
### 6. Generate Translated Documentation
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs docs
|
||||
```
|
||||
|
||||
## Auto-Translation Pipeline
|
||||
|
||||
### generate-multilang.mjs (Google Translate)
|
||||
|
||||
**Primary auto-translation engine** — uses Google Translate free API to generate translations for UI strings, READMEs, and documentation.
|
||||
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs [messages|readme|docs|all]
|
||||
```
|
||||
|
||||
| Mode | What it does |
|
||||
|------|-------------|
|
||||
| `messages` | Translates missing keys in `src/i18n/messages/{locale}.json` from `en.json` |
|
||||
| `readme` | Translates `README.md` into all locales as `README.{code}.md` in project root |
|
||||
| `docs` | Translates `DOC_SOURCE_FILES` into `docs/i18n/{locale}/{docName}` |
|
||||
| `all` | Runs all three modes |
|
||||
|
||||
**Features:**
|
||||
- **Text protection**: Masks code blocks (```` ``` ````), inline code (`` ` ``), markdown links/images (`[text](url)`), HTML tags, tables, and ICU placeholders (`{count}`, `{value}`, `{total}`, etc.) before translation, then restores them
|
||||
- **Chunked batching**: Joins multiple strings with `__OMNIROUTE_I18N_SEPARATOR__` delimiters to minimize API calls (max 1800 chars per request)
|
||||
- **In-memory cache**: Avoids redundant API calls for repeated strings within a session
|
||||
- **Retry logic**: Exponential backoff (up to 5 attempts with 300ms × attempt delay) for 429/5xx errors
|
||||
- **Timeout**: 20 seconds per request
|
||||
- **Skip existing**: If target file already exists, it is NOT overwritten
|
||||
|
||||
**Important behaviors:**
|
||||
- `docs/i18n/README.md` is **regenerated** each run — it's an auto-generated index of all docs
|
||||
- Root `README.{code}.md` files are only created if they don't exist (skips locales in `EXISTING_README_CODES`)
|
||||
- Language bars (`🌐 **Languages:** ...`) are automatically inserted/updated in all translated docs
|
||||
|
||||
### i18n_autotranslate.py (LLM-based)
|
||||
|
||||
**Secondary translator** — uses any OpenAI-compatible LLM API (including OmniRoute itself) to translate existing `docs/i18n/` markdown files. Best for polishing or re-translating docs with better quality than Google Translate.
|
||||
|
||||
```bash
|
||||
python3 scripts/i18n_autotranslate.py \
|
||||
--api-url http://localhost:20128/v1 \
|
||||
--api-key sk-your-key \
|
||||
--model gpt-4o
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Scans `docs/i18n/` markdown files for English paragraphs
|
||||
- Skips code blocks, tables, and already-translated content
|
||||
- Sends paragraphs to LLM with technical translation system prompt
|
||||
- Supports all 30 languages
|
||||
|
||||
## Validation & QA
|
||||
|
||||
### validate_translation.py
|
||||
|
||||
**Translation validator** — compares any locale JSON against `en.json` and reports issues.
|
||||
|
||||
```bash
|
||||
# Quick check (counts only)
|
||||
python3 scripts/validate_translation.py quick -l cs
|
||||
# Output:
|
||||
# Missing: 0
|
||||
# Untranslated: 0
|
||||
# Ignored (UNTRANSLATABLE_KEYS): 236
|
||||
|
||||
# Detailed diff by category
|
||||
python3 scripts/validate_translation.py diff common -l cs
|
||||
python3 scripts/validate_translation.py diff settings -l cs
|
||||
|
||||
# Export to CSV
|
||||
python3 scripts/validate_translation.py csv -l cs > report.csv
|
||||
|
||||
# Export to Markdown
|
||||
python3 scripts/validate_translation.py md -l cs > report.md
|
||||
|
||||
# Full report (default)
|
||||
python3 scripts/validate_translation.py -l cs
|
||||
```
|
||||
|
||||
**Detects:**
|
||||
- **Missing keys** — keys in `en.json` but not in locale file
|
||||
- **Extra keys** — keys in locale file but not in `en.json`
|
||||
- **Untranslated keys** — keys where locale value equals English source (excluding allowlist)
|
||||
- **Placeholder mismatches** — ICU placeholders that don't match between source and translation
|
||||
|
||||
**Exit codes:**
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | OK |
|
||||
| 1 | Generic error |
|
||||
| 2 | Missing strings (hard error) |
|
||||
| 3 | Untranslated warning (soft) |
|
||||
|
||||
**Environment:** Set `TRANSLATION_LANG=cs` or use `-l cs` flag.
|
||||
|
||||
### check_translations.py
|
||||
|
||||
**Code-to-JSON key checker** — scans `src/**/*.tsx` and `src/**/*.ts` for `useTranslations()` calls and verifies all referenced keys exist in `en.json`.
|
||||
|
||||
```bash
|
||||
# Basic check
|
||||
python3 scripts/check_translations.py
|
||||
|
||||
# Verbose output
|
||||
python3 scripts/check_translations.py --verbose
|
||||
|
||||
# Auto-fix (adds missing keys to en.json)
|
||||
python3 scripts/check_translations.py --fix
|
||||
```
|
||||
|
||||
### generate-qa-checklist.mjs
|
||||
|
||||
**Static analysis QA** — scans Next.js page files for i18n risk metrics and generates a Markdown report.
|
||||
|
||||
```bash
|
||||
node scripts/i18n/generate-qa-checklist.mjs
|
||||
```
|
||||
|
||||
**Checks:**
|
||||
- Fixed-width class usage (overflow risk)
|
||||
- Directional left/right classes (RTL risk)
|
||||
- Clipping-prone patterns
|
||||
- Locale parity (missing/extra keys vs `en.json`)
|
||||
- README language selector bars in priority locales (`es`, `fr`, `de`, `ja`, `ar`)
|
||||
|
||||
**Output:** `docs/reports/i18n-qa-checklist-{date}.md`
|
||||
|
||||
### run-visual-qa.mjs
|
||||
|
||||
**Visual QA via Playwright** — takes screenshots of all dashboard routes in multiple locales and viewports, then evaluates page health.
|
||||
|
||||
```bash
|
||||
# Default: es, fr, de, ja, ar on localhost:20128
|
||||
node scripts/i18n/run-visual-qa.mjs
|
||||
|
||||
# Custom base URL and locales
|
||||
QA_BASE_URL=http://staging.example.com QA_LOCALES=de,fr node scripts/i18n/run-visual-qa.mjs
|
||||
|
||||
# Custom routes
|
||||
QA_ROUTES=/dashboard/settings,/dashboard/providers node scripts/i18n/run-visual-qa.mjs
|
||||
```
|
||||
|
||||
**Detects:**
|
||||
- Text overflow
|
||||
- Element clipping
|
||||
- RTL layout mismatches
|
||||
|
||||
**Output:** `docs/reports/i18n-visual-qa-{date}.md` + JSON report
|
||||
|
||||
## Managing Untranslatable Keys
|
||||
|
||||
### untranslatable-keys.json
|
||||
|
||||
**File:** `scripts/i18n/untranslatable-keys.json`
|
||||
|
||||
Allowlist of keys that should remain identical to English source. Used by `validate_translation.py` to avoid false-positive "untranslated" warnings.
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Keys that should remain untranslated...",
|
||||
"keys": [
|
||||
"common.model",
|
||||
"common.oauth",
|
||||
"health.cpu",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**What belongs here:**
|
||||
- Brand/product names: `landing.brandName`, `common.social-github`
|
||||
- Technical terms/acronyms: `health.cpu`, `mcpDashboard.pid`, `settings.ai`
|
||||
- ICU/format strings: `apiManager.modelsCount`, `health.millisecondsShort`
|
||||
- Placeholder values: `providers.openaiBaseUrlPlaceholder`, `cliTools.baseUrlPlaceholder`
|
||||
- Protocol names: `common.http`, `common.oauth`, `providers.oauth2Label`
|
||||
- Navigation sections: `sidebar.primarySection`, `sidebar.cliSection`
|
||||
|
||||
**To add a key:** Edit the `keys` array in `scripts/i18n/untranslatable-keys.json` and re-run validation.
|
||||
|
||||
## CI Integration
|
||||
|
||||
### GitHub Actions (`.github/workflows/ci.yml`)
|
||||
|
||||
The CI pipeline validates all locales on every push and PR:
|
||||
|
||||
1. **`i18n-matrix` job** — dynamically discovers all locale files (excluding `en.json`)
|
||||
2. **`i18n` job** — runs `validate_translation.py quick -l '<lang>'` for each locale in parallel
|
||||
3. **`ci-summary` job** — aggregates results into a dashboard summary
|
||||
|
||||
```yaml
|
||||
# i18n-matrix: discovers languages
|
||||
LANGS=$(ls src/i18n/messages/*.json | xargs -n1 basename | sed 's/.json$//' | grep -v '^en$')
|
||||
|
||||
# i18n: validates each language
|
||||
python3 scripts/validate_translation.py quick -l '${{ matrix.lang }}'
|
||||
```
|
||||
|
||||
**Dashboard output:**
|
||||
```
|
||||
## 🌍 Translations
|
||||
| Metric | Value |
|
||||
|--------|------|
|
||||
| Languages checked | 30 |
|
||||
| Total untranslated | 0 |
|
||||
|
||||
✅ All translations complete
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/i18n/
|
||||
├── config.ts # Locale definitions (30 locales, RTL config)
|
||||
├── request.ts # Runtime locale resolution
|
||||
└── messages/
|
||||
├── en.json # Source of truth (~2800 keys)
|
||||
├── cs.json # Czech translation
|
||||
├── de.json # German translation
|
||||
└── ... # 30 locale files total
|
||||
|
||||
scripts/
|
||||
├── i18n/
|
||||
│ ├── generate-multilang.mjs # Auto-translation engine (Google Translate, 888 lines)
|
||||
│ ├── generate-qa-checklist.mjs # Static analysis QA
|
||||
│ ├── run-visual-qa.mjs # Playwright visual QA
|
||||
│ └── untranslatable-keys.json # Allowlist for validation (236 keys)
|
||||
├── validate_translation.py # Translation validator
|
||||
├── check_translations.py # Code-to-JSON key checker
|
||||
└── i18n_autotranslate.py # LLM-based doc translator
|
||||
|
||||
.github/workflows/
|
||||
└── ci.yml # i18n validation in CI matrix
|
||||
|
||||
docs/
|
||||
├── I18N.md # This file — i18n toolchain documentation
|
||||
├── i18n/
|
||||
│ ├── README.md # Auto-generated language index
|
||||
│ ├── cs/ # Czech docs
|
||||
│ │ └── docs/
|
||||
│ │ ├── I18N.md # Czech translation of this file
|
||||
│ │ └── ...
|
||||
│ ├── de/ # German docs
|
||||
│ └── ... # 30 locale directories
|
||||
└── reports/
|
||||
├── i18n-qa-checklist-*.md # Static analysis reports
|
||||
└── i18n-visual-qa-*.md # Visual QA reports
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When Editing Translations
|
||||
1. **Always edit `en.json` first** — it's the source of truth
|
||||
2. **Run `generate-multilang.mjs messages`** to propagate new keys to all locales
|
||||
3. **Review auto-translations** — Google Translate is a starting point, not final
|
||||
4. **Validate before committing** — `python3 scripts/validate_translation.py quick -l <lang>`
|
||||
5. **Update `untranslatable-keys.json`** if a key should remain in English
|
||||
|
||||
### Placeholder Safety
|
||||
- ICU placeholders (`{count}`, `{value}`, `{total}`, `{seconds}`) must be preserved exactly
|
||||
- Plural formats (`{count, plural, one {# model} other {# models}}`) must maintain structure
|
||||
- The validator detects placeholder mismatches automatically
|
||||
|
||||
### Adding New Translation Keys in Code
|
||||
```tsx
|
||||
// Use namespaced keys
|
||||
const t = useTranslations("settings");
|
||||
t("cacheSettings"); // maps to settings.cacheSettings in JSON
|
||||
|
||||
// Run check_translations.py to verify keys exist
|
||||
python3 scripts/check_translations.py --verbose
|
||||
```
|
||||
|
||||
### RTL Considerations
|
||||
- Arabic (`ar`) and Hebrew (`he`) are RTL locales
|
||||
- Avoid hardcoded `left`/`right` CSS — use `start`/`end` logical properties
|
||||
- Visual QA catches RTL layout mismatches via `run-visual-qa.mjs`
|
||||
|
||||
## Known Issues & History
|
||||
|
||||
### `in.json` → `hi.json` Fix
|
||||
The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This created an orphaned `in.json` duplicate of `hi.json`. Fixed by changing `code: "in"` to `code: "hi"` in `generate-multilang.mjs` and removing the orphaned file.
|
||||
|
||||
### `docs/i18n/README.md` Is Auto-Generated
|
||||
The `docs/i18n/README.md` file is completely regenerated by `generate-multilang.mjs docs`. Any manual edits will be lost. Use `docs/I18N.md` (this file) for hand-written documentation that should persist.
|
||||
|
||||
### External Untranslatable Keys List
|
||||
The `untranslatable-keys.json` allowlist was moved from an inline Python set in `validate_translation.py` to an external JSON file for easier maintenance. The validator loads it at runtime.
|
||||
|
||||
### `generate-multilang.mjs` Hindi Code Fix
|
||||
The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This was introduced in upstream commit `952b0b22c` by `diegosouzapw`. Fixed by changing `code: "in"` to `code: "hi"` in the `LOCALE_SPECS` array and removing the orphaned `in.json` file.
|
||||
|
||||
### `validate_translation.py` Ignored Count Output
|
||||
The `quick` check now displays the count of ignored keys from `untranslatable-keys.json`:
|
||||
```
|
||||
Missing: 0
|
||||
Untranslated: 0
|
||||
Ignored (UNTRANSLATABLE_KEYS): 236
|
||||
```
|
||||
@@ -38,16 +38,16 @@ See [IDE Configs](integrations/ide-configs.md) for Antigravity, Cursor, Copilot,
|
||||
|
||||
## Advanced Tools (8)
|
||||
|
||||
| Tool | Description |
|
||||
| :--------------------------------- | :---------------------------------------------- |
|
||||
| `omniroute_simulate_route` | Dry-run routing simulation with fallback tree |
|
||||
| `omniroute_set_budget_guard` | Session budget with degrade/block/alert actions |
|
||||
| `omniroute_set_resilience_profile` | Apply conservative/balanced/aggressive preset |
|
||||
| `omniroute_test_combo` | Live-test all models in a combo |
|
||||
| `omniroute_get_provider_metrics` | Detailed metrics for one provider |
|
||||
| `omniroute_best_combo_for_task` | Task-fitness recommendation with alternatives |
|
||||
| `omniroute_explain_route` | Explain a past routing decision |
|
||||
| `omniroute_get_session_snapshot` | Full session state: costs, tokens, errors |
|
||||
| Tool | Description |
|
||||
| :--------------------------------- | :---------------------------------------------------------- |
|
||||
| `omniroute_simulate_route` | Dry-run routing simulation with fallback tree |
|
||||
| `omniroute_set_budget_guard` | Session budget with degrade/block/alert actions |
|
||||
| `omniroute_set_resilience_profile` | Apply conservative/balanced/aggressive preset |
|
||||
| `omniroute_test_combo` | Live-test all models in a combo via a real upstream request |
|
||||
| `omniroute_get_provider_metrics` | Detailed metrics for one provider |
|
||||
| `omniroute_best_combo_for_task` | Task-fitness recommendation with alternatives |
|
||||
| `omniroute_explain_route` | Explain a past routing decision |
|
||||
| `omniroute_get_session_snapshot` | Full session state: costs, tokens, errors |
|
||||
|
||||
## Authentication
|
||||
|
||||
|
||||
596
docs/PROXY_GUIDE.md
Normal file
596
docs/PROXY_GUIDE.md
Normal file
@@ -0,0 +1,596 @@
|
||||
# 🌐 OmniRoute Proxy Guide
|
||||
|
||||
> **Bypass geographic blocks, protect your identity, and route AI traffic through any proxy — with zero configuration complexity.**
|
||||
|
||||
OmniRoute includes a full-featured proxy management system that lets you route upstream AI provider traffic through HTTP, HTTPS, or SOCKS5 proxies. Whether you're in a blocked region, need IP rotation, or want stealth fingerprinting — this guide covers everything.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Why Use Proxies?](#why-use-proxies)
|
||||
- [Architecture Overview](#architecture-overview)
|
||||
- [3-Level Proxy System](#3-level-proxy-system)
|
||||
- [Proxy Registry (CRUD)](#proxy-registry-crud)
|
||||
- [1proxy Free Marketplace](#1proxy-free-proxy-marketplace)
|
||||
- [Proxy Rotation](#proxy-rotation)
|
||||
- [Anti-Detection & Stealth](#anti-detection--stealth)
|
||||
- [Upstream Proxy Modes](#upstream-proxy-modes)
|
||||
- [Dashboard UI](#dashboard-ui)
|
||||
- [API Reference](#api-reference)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Why Use Proxies?
|
||||
|
||||
Many AI providers restrict access by geographic region. Developers in **Russia, China, Iran, Cuba, Turkey**, and other countries encounter errors like:
|
||||
|
||||
```
|
||||
unsupported_country_region_territory
|
||||
```
|
||||
|
||||
Even outside blocked regions, proxies are useful for:
|
||||
|
||||
| Use Case | Description |
|
||||
| --------------------- | --------------------------------------------------------------- |
|
||||
| **Geographic bypass** | Access OpenAI, Anthropic, Codex, Copilot from blocked countries |
|
||||
| **IP rotation** | Distribute requests across multiple IPs to avoid rate limiting |
|
||||
| **Privacy** | Hide your real IP from upstream providers |
|
||||
| **Compliance** | Route traffic through specific jurisdictions |
|
||||
| **Testing** | Simulate requests from different regions |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ OmniRoute Server │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ Proxy │ │ Proxy │ │ Proxy │ │
|
||||
│ │ Registry │───▶│ Dispatcher │───▶│ Fetch (undici) │ │
|
||||
│ │ (SQLite) │ │ (cached) │ │ │ │
|
||||
│ └─────────────┘ └──────────────┘ └────────┬─────────┘ │
|
||||
│ ▲ │ │
|
||||
│ │ ▼ │
|
||||
│ ┌──────┴──────┐ ┌──────────────────┐ │
|
||||
│ │ 1proxy Sync │ │ Upstream │ │
|
||||
│ │ (free pool) │ │ Provider API │ │
|
||||
│ └─────────────┘ └──────────────────┘ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
| Component | File | Role |
|
||||
| -------------------- | -------------------------------------------- | ---------------------------------------------------------- |
|
||||
| **Proxy Registry** | `src/lib/db/proxies.ts` | CRUD for proxy entries + scope assignments |
|
||||
| **Proxy Dispatcher** | `open-sse/utils/proxyDispatcher.ts` | Creates `undici` ProxyAgent/SOCKS dispatchers with caching |
|
||||
| **Proxy Fetch** | `open-sse/utils/proxyFetch.ts` | Wraps `fetch()` with proxy dispatcher injection |
|
||||
| **Settings Route** | `src/app/api/settings/proxy/route.ts` | Legacy proxy config API (GET/PUT/DELETE) |
|
||||
| **Management Route** | `src/app/api/v1/management/proxies/route.ts` | Registry CRUD API (GET/POST/PATCH/DELETE) |
|
||||
| **1proxy DB** | `src/lib/db/oneproxy.ts` | Free proxy marketplace persistence |
|
||||
| **1proxy Sync** | `src/lib/oneproxySync.ts` | Fetches proxies from 1proxy API |
|
||||
| **1proxy Rotator** | `src/lib/oneproxyRotator.ts` | Rotation strategies (quality/random/sequential) |
|
||||
|
||||
---
|
||||
|
||||
## 3-Level Proxy System
|
||||
|
||||
OmniRoute supports proxy configuration at **four independent scopes**, resolved in priority order:
|
||||
|
||||
```
|
||||
Priority Resolution Order (highest → lowest):
|
||||
|
||||
1. 🔵 Account/Connection Proxy → per API key / OAuth connection
|
||||
2. 🟡 Provider Proxy → per provider (e.g., all OpenAI traffic)
|
||||
3. 🟠 Combo Proxy → per combo/routing configuration
|
||||
4. 🟢 Global Proxy → all traffic, all providers
|
||||
```
|
||||
|
||||
### How Resolution Works
|
||||
|
||||
When OmniRoute sends a request to an upstream provider, it calls `resolveProxyForConnectionFromRegistry()` which checks each level in order:
|
||||
|
||||
1. **Account-level** — Is there a proxy assigned to this specific connection ID?
|
||||
2. **Provider-level** — Is there a proxy assigned to this provider (e.g., `openai`)?
|
||||
3. **Global-level** — Is there a global proxy configured?
|
||||
4. **No proxy** — Direct connection to the provider.
|
||||
|
||||
The first match wins. This means you can set a global proxy as a fallback but override it for specific providers or connections.
|
||||
|
||||
### What Gets Proxied
|
||||
|
||||
| Traffic Type | Proxied? | Notes |
|
||||
| -------------------- | -------- | --------------------------------------------- |
|
||||
| Chat completions | ✅ | All `/v1/chat/completions` requests |
|
||||
| Embeddings | ✅ | `/v1/embeddings` |
|
||||
| Image generation | ✅ | `/v1/images/generations` |
|
||||
| Audio (TTS/STT) | ✅ | `/v1/audio/*` |
|
||||
| OAuth token exchange | ✅ | Solves `unsupported_country_region_territory` |
|
||||
| Connection tests | ✅ | "Test Connection" button uses proxy |
|
||||
| Token refresh | ✅ | Background OAuth renewal |
|
||||
| Model sync | ✅ | Model listing and discovery |
|
||||
|
||||
---
|
||||
|
||||
## Proxy Registry (CRUD)
|
||||
|
||||
The proxy registry is a SQLite table (`proxy_registry`) that stores all your proxies. Each proxy has:
|
||||
|
||||
| Field | Type | Description |
|
||||
| ---------- | ------- | ----------------------------------- |
|
||||
| `id` | UUID | Unique identifier |
|
||||
| `name` | String | Human-readable label |
|
||||
| `type` | String | Protocol: `http`, `https`, `socks5` |
|
||||
| `host` | String | Proxy hostname or IP |
|
||||
| `port` | Integer | Port number |
|
||||
| `username` | String | Auth username (encrypted at rest) |
|
||||
| `password` | String | Auth password (encrypted at rest) |
|
||||
| `region` | String | Geographic region label |
|
||||
| `notes` | String | Free-text notes |
|
||||
| `status` | String | `active` or `inactive` |
|
||||
| `source` | String | `manual` or `oneproxy` |
|
||||
|
||||
### Creating a Proxy
|
||||
|
||||
**Via Dashboard:**
|
||||
|
||||
1. Go to **Settings → Proxy**
|
||||
2. Click **Add Proxy**
|
||||
3. Fill in the type, host, port, and optional auth credentials
|
||||
4. Save
|
||||
|
||||
**Via API:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/api/v1/management/proxies \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "US Proxy",
|
||||
"type": "http",
|
||||
"host": "proxy.example.com",
|
||||
"port": 8080,
|
||||
"username": "user",
|
||||
"password": "pass",
|
||||
"region": "US"
|
||||
}'
|
||||
```
|
||||
|
||||
### Updating a Proxy
|
||||
|
||||
```bash
|
||||
curl -X PATCH http://localhost:20128/api/v1/management/proxies \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"id": "proxy-uuid-here",
|
||||
"host": "new-proxy.example.com",
|
||||
"port": 9090
|
||||
}'
|
||||
```
|
||||
|
||||
> **Note:** Credentials are preserved unless you explicitly send non-empty replacements. Sending empty strings for `username`/`password` will keep the stored values.
|
||||
|
||||
### Deleting a Proxy
|
||||
|
||||
```bash
|
||||
# Fails if proxy is assigned to any scope
|
||||
curl -X DELETE "http://localhost:20128/api/v1/management/proxies?id=proxy-uuid"
|
||||
|
||||
# Force delete (removes assignments too)
|
||||
curl -X DELETE "http://localhost:20128/api/v1/management/proxies?id=proxy-uuid&force=1"
|
||||
```
|
||||
|
||||
### Listing Proxies
|
||||
|
||||
```bash
|
||||
curl "http://localhost:20128/api/v1/management/proxies?limit=50&offset=0"
|
||||
```
|
||||
|
||||
### Assigning Proxies to Scopes
|
||||
|
||||
```bash
|
||||
# Assign to global scope
|
||||
curl -X PUT http://localhost:20128/api/settings/proxy \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"level": "global", "proxy": {"type":"http","host":"proxy.example.com","port":8080}}'
|
||||
|
||||
# Assign to a specific provider
|
||||
curl -X PUT http://localhost:20128/api/settings/proxy \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"level": "provider", "id": "openai", "proxy": {"type":"socks5","host":"socks.example.com","port":1080}}'
|
||||
|
||||
# Assign to a specific connection/key
|
||||
curl -X PUT http://localhost:20128/api/settings/proxy \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"level": "key", "id": "connection-uuid", "proxy": {"type":"http","host":"key-proxy.com","port":3128}}'
|
||||
```
|
||||
|
||||
### Resolving Effective Proxy
|
||||
|
||||
Check which proxy would be used for a given connection:
|
||||
|
||||
```bash
|
||||
curl "http://localhost:20128/api/settings/proxy?resolve=connection-uuid"
|
||||
```
|
||||
|
||||
Returns the resolved proxy with its level (`account`, `provider`, or `global`) and source.
|
||||
|
||||
### Bulk Assignment
|
||||
|
||||
Assign one proxy to multiple providers or connections at once:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/api/v1/management/proxies/bulk-assign \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"scope": "provider",
|
||||
"scopeIds": ["openai", "anthropic", "codex"],
|
||||
"proxyId": "proxy-uuid"
|
||||
}'
|
||||
```
|
||||
|
||||
### Import/Export
|
||||
|
||||
Proxies are included in the **Backup/Restore** system. When you export your OmniRoute configuration:
|
||||
|
||||
1. Go to **Dashboard → Settings → Backup**
|
||||
2. Click **Export** — proxy registry and assignments are included
|
||||
3. To restore, click **Import** and upload the backup file
|
||||
|
||||
The proxy registry also supports **upsert by host+port** — if you import a proxy that already exists (same host and port), it updates instead of creating a duplicate.
|
||||
|
||||
### Legacy Migration
|
||||
|
||||
If you configured proxies in an older version (pre-registry), OmniRoute automatically migrates them:
|
||||
|
||||
```
|
||||
Legacy key_value store → proxy_registry + proxy_assignments
|
||||
```
|
||||
|
||||
This happens once on first startup after upgrade. Use `migrateLegacyProxyConfigToRegistry({ force: true })` to re-run.
|
||||
|
||||
---
|
||||
|
||||
## 1proxy Free Proxy Marketplace
|
||||
|
||||
> 🆕 **Contributed by [@oyi77](https://github.com/oyi77)** — PR [#1847](https://github.com/diegosouzapw/OmniRoute/pull/1847) (Issue [#1788](https://github.com/diegosouzapw/OmniRoute/issues/1788))
|
||||
|
||||
OmniRoute integrates with the **[1proxy](https://1proxy-api.aitradepulse.com)** community platform to provide access to **hundreds of free, validated proxies** from around the world. This is perfect for users who don't have their own proxy infrastructure.
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
┌─────────────┐ Sync ┌─────────────────┐ Rotate ┌──────────┐
|
||||
│ 1proxy API │ ────────────▶ │ proxy_registry │ ────────────▶ │ Provider │
|
||||
│ (external) │ up to 500 │ source=oneproxy │ by quality │ API │
|
||||
└─────────────┘ proxies └─────────────────┘ └──────────┘
|
||||
```
|
||||
|
||||
1. **Sync** — OmniRoute fetches validated proxies from the 1proxy API
|
||||
2. **Store** — Proxies are saved in the same `proxy_registry` table with `source = 'oneproxy'`
|
||||
3. **Filter** — Filter by protocol, country, quality score
|
||||
4. **Rotate** — Pick the best proxy using quality, random, or sequential strategies
|
||||
5. **Auto-degrade** — Failed proxies get their quality score reduced; below threshold → marked inactive
|
||||
|
||||
### Syncing Proxies
|
||||
|
||||
**Via Dashboard:**
|
||||
|
||||
1. Go to **Settings → 1proxy** tab
|
||||
2. Click **"Sync Now"**
|
||||
3. View stats: total proxies, active count, average quality, by-country breakdown
|
||||
|
||||
**Via API:**
|
||||
|
||||
```bash
|
||||
# Trigger sync
|
||||
curl -X POST http://localhost:20128/api/settings/oneproxy \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
|
||||
# Response:
|
||||
# { "success": true, "added": 127, "updated": 45, "failed": 2, "total": 172 }
|
||||
```
|
||||
|
||||
### Filtering Proxies
|
||||
|
||||
```bash
|
||||
# Filter by protocol
|
||||
curl "http://localhost:20128/api/settings/oneproxy?protocol=socks5"
|
||||
|
||||
# Filter by country
|
||||
curl "http://localhost:20128/api/settings/oneproxy?countryCode=US"
|
||||
|
||||
# Filter by minimum quality score
|
||||
curl "http://localhost:20128/api/settings/oneproxy?minQuality=80"
|
||||
|
||||
# Combine filters
|
||||
curl "http://localhost:20128/api/settings/oneproxy?protocol=http&countryCode=DE&minQuality=70"
|
||||
```
|
||||
|
||||
### Proxy Quality Scores
|
||||
|
||||
Each 1proxy proxy comes with metadata:
|
||||
|
||||
| Field | Description |
|
||||
| --------------- | -------------------------------------------- |
|
||||
| `qualityScore` | 0-100 rating from 1proxy validation |
|
||||
| `latencyMs` | Measured network latency |
|
||||
| `anonymity` | `transparent`, `anonymous`, or `elite` |
|
||||
| `googleAccess` | Whether the proxy can access Google services |
|
||||
| `countryCode` | Two-letter ISO country code |
|
||||
| `lastValidated` | Timestamp of last validation |
|
||||
|
||||
Quality scores are dynamically adjusted:
|
||||
|
||||
- **Failed requests** reduce the score by 10 points
|
||||
- **Score drops to ≤10** → proxy is marked `inactive`
|
||||
- Inactive proxies are excluded from rotation
|
||||
|
||||
### Rotation Strategies
|
||||
|
||||
```bash
|
||||
# Rotate by quality (best proxy first) — default
|
||||
curl -X POST http://localhost:20128/api/settings/oneproxy/rotate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"strategy": "quality"}'
|
||||
|
||||
# Random rotation
|
||||
curl -X POST http://localhost:20128/api/settings/oneproxy/rotate \
|
||||
-d '{"strategy": "random"}'
|
||||
|
||||
# Sequential (least recently validated first)
|
||||
curl -X POST http://localhost:20128/api/settings/oneproxy/rotate \
|
||||
-d '{"strategy": "sequential"}'
|
||||
```
|
||||
|
||||
### Circuit Breaker
|
||||
|
||||
The 1proxy sync has a built-in circuit breaker:
|
||||
|
||||
- After **5 consecutive sync failures**, further sync attempts are blocked
|
||||
- Reset with: `resetOneproxyCircuitBreaker()` or restart the server
|
||||
- Sync status is available at `GET /api/settings/oneproxy?action=status`
|
||||
|
||||
### Clearing 1proxy Proxies
|
||||
|
||||
```bash
|
||||
# Delete a single 1proxy proxy
|
||||
curl -X DELETE "http://localhost:20128/api/settings/oneproxy?id=proxy-uuid"
|
||||
|
||||
# Clear ALL 1proxy proxies (manual proxies are untouched)
|
||||
curl -X DELETE "http://localhost:20128/api/settings/oneproxy?clearAll=1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anti-Detection & Stealth
|
||||
|
||||
OmniRoute doesn't just route traffic through a proxy — it makes the traffic look legitimate:
|
||||
|
||||
### TLS Fingerprint Spoofing
|
||||
|
||||
Uses `wreq-js` to generate browser-like TLS fingerprints, bypassing bot detection systems that flag non-browser TLS handshakes.
|
||||
|
||||
### CLI Fingerprint Matching
|
||||
|
||||
The **CLI Fingerprint Toggle** (`Settings → Security`) reorders HTTP headers and JSON body fields to match the exact signature of native CLI binaries (Claude Code, Codex, etc.). This works **on top of** the proxy:
|
||||
|
||||
```
|
||||
Your IP (blocked) → Proxy IP (US) → Provider API
|
||||
+ TLS spoof
|
||||
+ CLI fingerprint
|
||||
```
|
||||
|
||||
You get both **IP masking** and **request authenticity** simultaneously.
|
||||
|
||||
### Proxy IP Preservation
|
||||
|
||||
Color-coded badges in the dashboard show which proxy level is active:
|
||||
|
||||
| Badge | Level | Meaning |
|
||||
| ----- | ---------- | ----------------------------------------- |
|
||||
| 🟢 | Global | All traffic goes through this proxy |
|
||||
| 🟡 | Provider | Only this provider's traffic is proxied |
|
||||
| 🔵 | Connection | This specific key/account uses this proxy |
|
||||
|
||||
The badge also shows the resolved proxy IP for verification.
|
||||
|
||||
---
|
||||
|
||||
## Upstream Proxy Modes
|
||||
|
||||
For providers that use the CLIProxyAPI pattern, OmniRoute supports three upstream proxy modes:
|
||||
|
||||
| Mode | Description |
|
||||
| ------------- | -------------------------------------------------- |
|
||||
| `native` | OmniRoute handles proxy routing directly (default) |
|
||||
| `cliproxyapi` | Delegates to an external CLIProxyAPI instance |
|
||||
| `fallback` | Tries native first, falls back to CLIProxyAPI |
|
||||
|
||||
Configure per-provider:
|
||||
|
||||
```bash
|
||||
curl -X PUT "http://localhost:20128/api/upstream-proxy/openai" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"mode": "native", "enabled": true}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dashboard UI
|
||||
|
||||
### Settings → Proxy Tab
|
||||
|
||||
- **Global proxy** configuration (set once for all traffic)
|
||||
- **Per-provider proxy** overrides
|
||||
- **Per-connection proxy** assignments
|
||||
- **Connection test** through configured proxy
|
||||
- **Color-coded badges** showing active proxy level
|
||||
|
||||
### Settings → 1proxy Tab
|
||||
|
||||
- **Sync Now** button to fetch free proxies
|
||||
- **Stats cards**: Total, Active, Avg Quality, Last Sync
|
||||
- **Filters**: Protocol, Country Code, Min Quality
|
||||
- **Proxy table** with host, protocol, country, quality score, latency, anonymity, Google access
|
||||
- **Sync status** panel with success/failure tracking and consecutive failure count
|
||||
- **Clear All** to remove all 1proxy entries
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Proxy Settings API
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| -------- | ---------------------------------------------- | ----------------------- |
|
||||
| `GET` | `/api/settings/proxy` | Get full proxy config |
|
||||
| `GET` | `/api/settings/proxy?level=global` | Get global proxy |
|
||||
| `GET` | `/api/settings/proxy?level=provider&id=openai` | Get provider proxy |
|
||||
| `GET` | `/api/settings/proxy?resolve=connectionId` | Resolve effective proxy |
|
||||
| `PUT` | `/api/settings/proxy` | Update proxy config |
|
||||
| `DELETE` | `/api/settings/proxy?level=provider&id=openai` | Remove proxy at level |
|
||||
|
||||
### Proxy Registry API
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| -------- | ------------------------------------------------- | --------------------- |
|
||||
| `GET` | `/api/v1/management/proxies` | List all proxies |
|
||||
| `GET` | `/api/v1/management/proxies?id=uuid` | Get proxy by ID |
|
||||
| `GET` | `/api/v1/management/proxies?id=uuid&where_used=1` | Get proxy assignments |
|
||||
| `POST` | `/api/v1/management/proxies` | Create proxy |
|
||||
| `PATCH` | `/api/v1/management/proxies` | Update proxy |
|
||||
| `DELETE` | `/api/v1/management/proxies?id=uuid` | Delete proxy |
|
||||
| `DELETE` | `/api/v1/management/proxies?id=uuid&force=1` | Force delete |
|
||||
| `POST` | `/api/v1/management/proxies/bulk-assign` | Bulk assign |
|
||||
| `GET` | `/api/v1/management/proxies/assignments` | List assignments |
|
||||
| `GET` | `/api/v1/management/proxies/health` | Proxy health stats |
|
||||
|
||||
### 1proxy API
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| -------- | -------------------------------------- | ----------------------- |
|
||||
| `GET` | `/api/settings/oneproxy` | List 1proxy proxies |
|
||||
| `GET` | `/api/settings/oneproxy?action=stats` | Get stats + sync status |
|
||||
| `GET` | `/api/settings/oneproxy?action=status` | Get sync status only |
|
||||
| `POST` | `/api/settings/oneproxy` | Trigger sync |
|
||||
| `POST` | `/api/settings/oneproxy/rotate` | Rotate to next proxy |
|
||||
| `DELETE` | `/api/settings/oneproxy?id=uuid` | Delete one |
|
||||
| `DELETE` | `/api/settings/oneproxy?clearAll=1` | Clear all |
|
||||
|
||||
### Upstream Proxy API
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| -------- | --------------------------------- | ---------------------------- |
|
||||
| `GET` | `/api/upstream-proxy/:providerId` | Get upstream proxy config |
|
||||
| `PUT` | `/api/upstream-proxy/:providerId` | Set upstream proxy mode |
|
||||
| `DELETE` | `/api/upstream-proxy/:providerId` | Remove upstream proxy config |
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
| -------------------------------- | ------------------------------------- | ------------------------------- |
|
||||
| `ENABLE_SOCKS5_PROXY` | `false` | Enable SOCKS5 proxy support |
|
||||
| `ONEPROXY_ENABLED` | `true` | Enable 1proxy integration |
|
||||
| `ONEPROXY_API_URL` | `https://1proxy-api.aitradepulse.com` | 1proxy API endpoint |
|
||||
| `ONEPROXY_MAX_PROXIES` | `500` | Maximum proxies to sync |
|
||||
| `ONEPROXY_MIN_QUALITY_THRESHOLD` | `50` | Minimum quality score to import |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "SOCKS5 proxy is disabled"
|
||||
|
||||
Set `ENABLE_SOCKS5_PROXY=true` in your `.env` file and restart.
|
||||
|
||||
### "socket hang up" errors through proxy
|
||||
|
||||
This is normal with cheap proxies that drop idle connections. OmniRoute already handles this by:
|
||||
|
||||
- Disabling keep-alive on proxy connections (`keepAliveTimeout: 1`)
|
||||
- Disabling pipelining (`pipelining: 0`)
|
||||
- Caching dispatchers to avoid repeated handshakes
|
||||
|
||||
If it persists, try a different proxy or use the 1proxy rotation feature.
|
||||
|
||||
### "unsupported_country_region_territory" during OAuth
|
||||
|
||||
Make sure the proxy is configured **before** starting the OAuth flow. OmniRoute routes OAuth token exchange through the configured proxy. Set a global or provider-level proxy first, then connect.
|
||||
|
||||
### Proxy not being used
|
||||
|
||||
Check the resolution order:
|
||||
|
||||
1. Verify with `GET /api/settings/proxy?resolve=your-connection-id`
|
||||
2. Check if the proxy `status` is `active` (not `inactive`)
|
||||
3. Ensure the proxy assignment scope matches your connection
|
||||
|
||||
### 1proxy sync failing
|
||||
|
||||
Check the sync status:
|
||||
|
||||
```bash
|
||||
curl "http://localhost:20128/api/settings/oneproxy?action=status"
|
||||
```
|
||||
|
||||
If `consecutiveFailures >= 5`, the circuit breaker has tripped. Restart the server to reset, or wait for manual reset.
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### `proxy_registry` Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE proxy_registry (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'http',
|
||||
host TEXT NOT NULL,
|
||||
port INTEGER NOT NULL,
|
||||
username TEXT DEFAULT '',
|
||||
password TEXT DEFAULT '',
|
||||
region TEXT,
|
||||
notes TEXT,
|
||||
status TEXT DEFAULT 'active',
|
||||
source TEXT NOT NULL DEFAULT 'manual', -- 'manual' or 'oneproxy'
|
||||
quality_score INTEGER, -- 0-100 (1proxy only)
|
||||
latency_ms INTEGER, -- milliseconds (1proxy only)
|
||||
anonymity TEXT, -- transparent/anonymous/elite
|
||||
google_access INTEGER DEFAULT 0, -- can access Google? (1proxy)
|
||||
last_validated TEXT, -- ISO timestamp (1proxy)
|
||||
country_code TEXT, -- ISO 2-letter code (1proxy)
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
### `proxy_assignments` Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE proxy_assignments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
proxy_id TEXT NOT NULL REFERENCES proxy_registry(id),
|
||||
scope TEXT NOT NULL, -- 'global', 'provider', 'account', 'combo'
|
||||
scope_id TEXT, -- provider ID, connection ID, or combo ID
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
UNIQUE(scope, scope_id)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 📖 **Related documentation:**
|
||||
>
|
||||
> - [User Guide](USER_GUIDE.md) — General setup and configuration
|
||||
> - [API Reference](API_REFERENCE.md) — Full API documentation
|
||||
> - [Environment Config](ENVIRONMENT.md) — All environment variables
|
||||
186
docs/PWA_GUIDE.md
Normal file
186
docs/PWA_GUIDE.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# Progressive Web App (PWA) Guide
|
||||
|
||||
OmniRoute ships as a fully installable Progressive Web App. When you access the dashboard from any mobile browser — Android (Chrome) or iOS (Safari) — you can "Add to Home Screen" and get a native app-like experience with no app store required.
|
||||
|
||||
## What Is a PWA?
|
||||
|
||||
A Progressive Web App turns the OmniRoute web dashboard into something that looks and feels like a native mobile app. Once installed, it:
|
||||
|
||||
- Launches from your home screen with its own icon
|
||||
- Opens fullscreen — no browser address bar or tab UI
|
||||
- Works offline with a dedicated connectivity page
|
||||
- Caches static assets for faster loading
|
||||
- Supports both portrait and landscape orientations
|
||||
|
||||
## Installation
|
||||
|
||||
### Android (Chrome)
|
||||
|
||||
1. Open the OmniRoute dashboard in Chrome: `http://YOUR_IP:20128`
|
||||
2. Chrome will show an **"Add OmniRoute to Home screen"** banner automatically, or:
|
||||
- Tap the **⋮** menu (three dots) → **"Add to Home screen"** or **"Install app"**
|
||||
3. Confirm the prompt
|
||||
4. OmniRoute appears on your home screen as a standalone app
|
||||
|
||||
### iOS (Safari)
|
||||
|
||||
1. Open the OmniRoute dashboard in Safari: `http://YOUR_IP:20128`
|
||||
2. Tap the **Share** button (box with arrow)
|
||||
3. Scroll down and tap **"Add to Home Screen"**
|
||||
4. Name it (defaults to "OmniRoute") and tap **Add**
|
||||
5. OmniRoute appears on your home screen with the app icon
|
||||
|
||||
### Desktop (Chrome / Edge)
|
||||
|
||||
1. Open the OmniRoute dashboard
|
||||
2. Click the **install icon** in the address bar (or ⋮ → "Install OmniRoute...")
|
||||
3. Confirm the prompt
|
||||
4. OmniRoute opens as a standalone window — no tabs, no address bar
|
||||
|
||||
## Features
|
||||
|
||||
### Fullscreen Experience
|
||||
|
||||
The manifest is configured with `display: "fullscreen"`, which means the installed app uses the entire screen — no browser chrome, no status bar overlap. This makes the dashboard feel truly native.
|
||||
|
||||
### Offline Support
|
||||
|
||||
OmniRoute includes a service worker (`sw.js`) that provides intelligent caching:
|
||||
|
||||
| Asset Type | Strategy | Behavior |
|
||||
| ------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
### Offline Page
|
||||
|
||||
When the network is unavailable and a user navigates to a new page, the service worker serves a dedicated `/offline` page that:
|
||||
|
||||
- Displays a clear **"Connectivity Issue"** message
|
||||
- Shows a live **online/offline status indicator** that updates in real time
|
||||
- Provides a **"Retry Connection"** button to reload when connectivity returns
|
||||
- Links to the **Status Page** for diagnostics
|
||||
|
||||
### App Icons
|
||||
|
||||
OmniRoute provides icons optimized for each platform:
|
||||
|
||||
| File | Size | Used By |
|
||||
| ---------------------- | ---------------- | ------------------------------------- |
|
||||
| `icon-512.png` | 512×512 | Android install prompt, splash screen |
|
||||
| `apple-touch-icon.png` | 180×180 | iOS home screen icon |
|
||||
| `icon-192.svg` | 192×192 (vector) | Android adaptive icon |
|
||||
| `apple-touch-icon.svg` | 180×180 (vector) | Apple fallback |
|
||||
| `favicon.svg` | Vector | Browser tabs |
|
||||
| `favicon.ico` | Multi-size | Legacy browsers |
|
||||
|
||||
### Automatic Registration
|
||||
|
||||
The service worker is registered automatically via the `<PwaRegister />` component in the root layout. No user action is needed — the app becomes installable as soon as the browser detects the valid manifest and service worker.
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### Web App Manifest (`manifest.webmanifest`)
|
||||
|
||||
Generated by Next.js via `src/app/manifest.ts`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "OmniRoute",
|
||||
"short_name": "OmniRoute",
|
||||
"description": "OmniRoute is an AI gateway for multi-provider LLMs. One endpoint for all your AI providers.",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "fullscreen",
|
||||
"orientation": "any",
|
||||
"background_color": "#0b0f1a",
|
||||
"theme_color": "#0b0f1a",
|
||||
"icons": [
|
||||
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" },
|
||||
{ "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Service Worker (`public/sw.js`)
|
||||
|
||||
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
|
||||
|
||||
### Layout Metadata (`src/app/layout.tsx`)
|
||||
|
||||
The root layout provides all the meta tags required for PWA compliance:
|
||||
|
||||
- `manifest` link to `/manifest.webmanifest`
|
||||
- `apple-web-app-capable: true` for iOS standalone mode
|
||||
- `apple-web-app-status-bar-style: black-translucent`
|
||||
- `mobile-web-app-capable: yes` for Android Chrome
|
||||
- `theme-color: #0b0f1a`
|
||||
- `viewport-fit: cover` for edge-to-edge rendering
|
||||
|
||||
### Component: `PwaRegister`
|
||||
|
||||
Located at `src/shared/components/PwaRegister.tsx`, this client component:
|
||||
|
||||
1. Runs on mount (client-side only)
|
||||
2. Checks for `serviceWorker` support in the browser
|
||||
3. Registers `/sw.js` silently (errors are swallowed to avoid blocking the app)
|
||||
4. Renders nothing (`return null`) — it's a side-effect-only component
|
||||
|
||||
## Use With Termux (Android)
|
||||
|
||||
When running OmniRoute on Android via Termux, the PWA works seamlessly:
|
||||
|
||||
1. Start OmniRoute in Termux: `npx omniroute`
|
||||
2. Open Chrome on the same phone: `http://localhost:20128`
|
||||
3. Install the PWA via "Add to Home Screen"
|
||||
4. The PWA connects to the local Termux server — everything runs on-device
|
||||
|
||||
This combination means your Android phone is both the **server** (Termux) and the **client** (PWA) — a complete self-contained AI gateway.
|
||||
|
||||
## Use From Other Devices
|
||||
|
||||
Install the PWA on any device that has browser access to your OmniRoute server:
|
||||
|
||||
- **Another phone/tablet**: Navigate to `http://PHONE_IP:20128` and install the PWA
|
||||
- **Laptop**: Open Chrome/Edge and install it as a desktop PWA
|
||||
- **Smart TV with browser**: Access the dashboard fullscreen
|
||||
|
||||
## Customization
|
||||
|
||||
### Instance Name
|
||||
|
||||
The PWA title respects the **Instance Name** setting from `Dashboard → Settings`. If you rename your instance to "My AI Gateway", the installed PWA will show that name.
|
||||
|
||||
### Custom Favicon
|
||||
|
||||
If you upload a custom favicon via `Dashboard → Settings`, the PWA icon on desktop will reflect the custom icon. Mobile home screen icons use the pre-built `icon-512.png` and `apple-touch-icon.png` files.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **No push notifications** — The service worker does not implement the Push API. Notifications are handled by the Electron app instead.
|
||||
- **No background sync** — Offline actions are not queued for replay. The PWA is primarily a dashboard viewer.
|
||||
- **iOS restrictions** — Safari on iOS does not support all PWA features (e.g., install prompts are manual, and background service workers are limited).
|
||||
- **Cache size** — The service worker caches static assets only. Large response payloads from `/api/` routes are never cached.
|
||||
- **Custom icons on mobile** — Changing the favicon in settings does not update the home screen icon on mobile (this requires regenerating the PWA icons).
|
||||
|
||||
## Files Reference
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------------------------- | ---------------------------------------------------------------- |
|
||||
| `src/app/manifest.ts` | Next.js manifest route (generates `manifest.webmanifest`) |
|
||||
| `public/sw.js` | Service worker with caching logic |
|
||||
| `src/shared/components/PwaRegister.tsx` | Client component that registers the service worker |
|
||||
| `src/app/offline/page.tsx` | Offline fallback page with live status indicator |
|
||||
| `src/app/layout.tsx` | Root layout with PWA metadata (apple-web-app, theme-color, etc.) |
|
||||
| `public/icon-512.png` | 512×512 PNG icon (Android, splash screen) |
|
||||
| `public/apple-touch-icon.png` | 180×180 PNG icon (iOS home screen) |
|
||||
| `public/icon-192.svg` | 192×192 SVG icon (Android adaptive) |
|
||||
| `public/apple-touch-icon.svg` | 180×180 SVG icon (Apple fallback) |
|
||||
@@ -20,7 +20,14 @@ Use this checklist before tagging or publishing a new OmniRoute release.
|
||||
|
||||
1. Review `docs/ARCHITECTURE.md` for storage/runtime drift.
|
||||
2. Review `docs/TROUBLESHOOTING.md` for env var and operational drift.
|
||||
3. Update localized docs if source docs changed significantly.
|
||||
3. Verify the release/runtime Node.js version still satisfies the supported secure floor:
|
||||
- `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`
|
||||
- `npm run check:node-runtime`
|
||||
4. Validate the npm publish artifact after building the standalone package:
|
||||
- `npm run build:cli`
|
||||
- `npm run check:pack-artifact`
|
||||
- confirm no `app.__qa_backup`, `scripts/scratch`, `package-lock.json`, or other local residue
|
||||
5. Update localized docs if source docs changed significantly.
|
||||
|
||||
## Automated Check
|
||||
|
||||
|
||||
145
docs/RESILIENCE_GUIDE.md
Normal file
145
docs/RESILIENCE_GUIDE.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# 🛡️ Resilience Guide — OmniRoute
|
||||
|
||||
> How OmniRoute keeps your AI coding workflow running when providers fail.
|
||||
|
||||
## Overview
|
||||
|
||||
OmniRoute implements a multi-layered resilience system that ensures zero downtime:
|
||||
|
||||
```
|
||||
Client Request
|
||||
→ Rate Limit Check (per-IP, per-connection)
|
||||
→ Combo Routing (13 strategies)
|
||||
→ Connection Selection (P2C, round-robin, etc.)
|
||||
→ Request Queue & Pacing
|
||||
→ Execute (provider-specific executor)
|
||||
→ On Failure:
|
||||
→ Connection Cooldown (exponential backoff)
|
||||
→ Circuit Breaker (provider-level)
|
||||
→ Wait For Cooldown (auto-retry)
|
||||
→ Next Combo Target (fallback chain)
|
||||
→ Response
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Request Queue & Pacing
|
||||
|
||||
Per-connection request buckets smooth bursts before they hit upstream rate caps.
|
||||
|
||||
Configure in `Dashboard → Settings → Resilience`:
|
||||
|
||||
| Setting | Default | Description |
|
||||
| --------------- | ------- | ------------------------------------ |
|
||||
| Queue Size | `10` | Max queued requests per connection |
|
||||
| Pacing Interval | `0ms` | Minimum gap between requests |
|
||||
| Max Concurrent | `5` | Simultaneous requests per connection |
|
||||
|
||||
---
|
||||
|
||||
## Connection Cooldown
|
||||
|
||||
A single connection cools down after retryable failures. Features:
|
||||
|
||||
- **Exponential Backoff** — progressively longer cooldowns after each failure
|
||||
- **`Retry-After` Header Support** — respects upstream hints
|
||||
- **Configurable Base/Max** — tune cooldown duration per use case
|
||||
- **Auto-Recovery** — connection automatically becomes available after cooldown expires
|
||||
|
||||
---
|
||||
|
||||
## Circuit Breaker
|
||||
|
||||
Provider-level protection against cascading failures:
|
||||
|
||||
1. **Connection-scoped `429` rate limits** stay in Connection Cooldown (don't trip the breaker)
|
||||
2. **Provider-wide transient errors** (5xx, network timeouts) increment the failure counter
|
||||
3. **Breaker trips** only after fallback is exhausted AND the provider still fails
|
||||
4. **Recovery** — breaker automatically moves to half-open state after timeout, tests with probe request
|
||||
|
||||
Configure thresholds in `Dashboard → Settings → Resilience`.
|
||||
|
||||
---
|
||||
|
||||
## Wait For Cooldown
|
||||
|
||||
Instead of immediately failing when all connections are in cooldown, OmniRoute can wait for the earliest connection to expire and retry:
|
||||
|
||||
- **Automatic** — server waits for the earliest cooldown to expire
|
||||
- **Transparent** — client sees a slightly delayed response instead of an error
|
||||
- **Configurable** — enable/disable per combo or globally
|
||||
|
||||
---
|
||||
|
||||
## Anti-Thundering Herd
|
||||
|
||||
When multiple concurrent requests hit a failing provider simultaneously:
|
||||
|
||||
- **Mutex Protection** — only one retry attempt at a time per connection
|
||||
- **Semaphore** — limits concurrent retry storms across connections
|
||||
- **Deduplication** — identical requests within 5s window are deduplicated
|
||||
|
||||
---
|
||||
|
||||
## Combo Fallback Chains
|
||||
|
||||
The primary resilience mechanism. Configure in `Dashboard → Combos`:
|
||||
|
||||
```txt
|
||||
Combo: "always-on"
|
||||
1. cc/claude-opus-4-7 ← Primary (subscription)
|
||||
2. cx/gpt-5.2-codex ← Secondary (subscription)
|
||||
3. glm/glm-4.7 ← Cheap backup ($0.5/1M)
|
||||
4. if/kimi-k2-thinking ← Free fallback (unlimited)
|
||||
```
|
||||
|
||||
When provider #1 fails (quota, rate, or health), OmniRoute automatically routes to #2, then #3, then #4 — with zero manual intervention.
|
||||
|
||||
### 13 Routing Strategies
|
||||
|
||||
| Strategy | Description |
|
||||
| ------------------- | ---------------------------------- |
|
||||
| `priority` | First available in order |
|
||||
| `weighted` | Weighted distribution |
|
||||
| `fill-first` | Fill primary before moving |
|
||||
| `round-robin` | Rotate through all targets |
|
||||
| `p2c` | Power-of-two choices (quota-aware) |
|
||||
| `random` | Random selection |
|
||||
| `least-used` | Least recently used |
|
||||
| `cost-optimized` | Cheapest available |
|
||||
| `strict-random` | True random (no tracking) |
|
||||
| `auto` | OmniRoute selects based on context |
|
||||
| `lkgp` | Last Known Good Provider |
|
||||
| `context-optimized` | Best for current context window |
|
||||
| `context-relay` | Session handoff during rotation |
|
||||
|
||||
---
|
||||
|
||||
## TLS Fingerprint Spoofing
|
||||
|
||||
OmniRoute makes proxied traffic look like legitimate browser/CLI requests:
|
||||
|
||||
- **Browser-like TLS** via `wreq-js` — prevents bot detection
|
||||
- **CLI Fingerprint Matching** — reorders headers and body fields to match native CLI binary signatures (Claude Code, Codex, etc.)
|
||||
- **Proxy IP Preservation** — stealth features work on top of proxy IP masking
|
||||
|
||||
---
|
||||
|
||||
## Health Dashboard
|
||||
|
||||
Monitor all resilience components in real-time at `Dashboard → Health`:
|
||||
|
||||
- **Uptime** — server uptime and last restart
|
||||
- **Provider Breaker States** — open/closed/half-open per provider
|
||||
- **Connection Cooldowns** — active cooldowns with expiry times
|
||||
- **Cache Stats** — signature + semantic cache hit rates
|
||||
- **Lockouts** — API key lockouts and IP bans
|
||||
- **Latency** — p50/p95/p99 percentiles
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Architecture Guide](ARCHITECTURE.md) — System architecture and internals
|
||||
- [User Guide](USER_GUIDE.md) — Providers, combos, CLI integration
|
||||
- [Auto-Combo Engine](AUTO-COMBO.md) — 6-factor scoring, mode packs
|
||||
311
docs/SETUP_GUIDE.md
Normal file
311
docs/SETUP_GUIDE.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# 📖 Setup Guide — OmniRoute
|
||||
|
||||
> Complete setup reference for OmniRoute. For the quick version, see the [Quick Start in README](../README.md#-quick-start).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Install Methods](#install-methods)
|
||||
- [CLI Tool Configuration](#cli-tool-configuration)
|
||||
- [Protocol Setup (MCP + A2A)](#protocol-setup-mcp--a2a)
|
||||
- [Timeout Configuration](#timeout-configuration)
|
||||
- [Split-Port Mode](#split-port-mode)
|
||||
- [Void Linux (xbps-src)](#void-linux-xbps-src-template)
|
||||
- [Uninstalling](#uninstalling)
|
||||
|
||||
---
|
||||
|
||||
## Install Methods
|
||||
|
||||
### npm (recommended)
|
||||
|
||||
```bash
|
||||
npm install -g omniroute
|
||||
omniroute
|
||||
```
|
||||
|
||||
Dashboard opens at `http://localhost:20128` and API base URL is `http://localhost:20128/v1`.
|
||||
|
||||
### pnpm
|
||||
|
||||
```bash
|
||||
pnpm install -g omniroute
|
||||
pnpm approve-builds -g # Select all packages → approve
|
||||
omniroute
|
||||
```
|
||||
|
||||
> **pnpm users:** `pnpm approve-builds -g` is required to enable native build scripts for `better-sqlite3` and `@swc/core`.
|
||||
|
||||
### Arch Linux (AUR)
|
||||
|
||||
```bash
|
||||
yay -S omniroute-bin
|
||||
systemctl --user enable --now omniroute.service
|
||||
```
|
||||
|
||||
The [AUR package](https://aur.archlinux.org/packages/omniroute-bin) installs OmniRoute and provides a systemd user service.
|
||||
|
||||
### From Source
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
npm install
|
||||
PORT=20128 DASHBOARD_PORT=20129 NEXT_PUBLIC_BASE_URL=http://localhost:20129 npm run dev
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
See the [Docker Guide](DOCKER_GUIDE.md) for complete Docker setup including Compose profiles and Caddy HTTPS.
|
||||
|
||||
### CLI Options
|
||||
|
||||
| Command | Description |
|
||||
| ----------------------- | ----------------------------------------------------------- |
|
||||
| `omniroute` | Start server (`PORT=20128`, API and dashboard on same port) |
|
||||
| `omniroute --port 3000` | Set canonical/API port to 3000 |
|
||||
| `omniroute --mcp` | Start MCP server (stdio transport) |
|
||||
| `omniroute --no-open` | Don't auto-open browser |
|
||||
| `omniroute --help` | Show help |
|
||||
|
||||
---
|
||||
|
||||
## CLI Tool Configuration
|
||||
|
||||
### 1) Connect Providers and Create API Key
|
||||
|
||||
1. Open Dashboard → `Providers` and connect at least one provider (OAuth or API key).
|
||||
2. Open Dashboard → `Endpoints` and create an API key.
|
||||
3. (Optional) Open Dashboard → `Combos` and set your fallback chain.
|
||||
|
||||
### 2) Point Your Coding Tool
|
||||
|
||||
```txt
|
||||
Base URL: http://localhost:20128/v1
|
||||
API Key: [copy from Endpoint page]
|
||||
Model: if/kimi-k2-thinking (or any provider/model prefix)
|
||||
```
|
||||
|
||||
Works with Claude Code, Codex CLI, Gemini CLI, Cursor, Cline, OpenClaw, OpenCode, and OpenAI-compatible SDKs.
|
||||
|
||||
For detailed per-tool configuration (Claude Code, Codex CLI, Cursor, Cline, OpenClaw, Kilo Code, Copilot, and more), see the dedicated **[CLI Tools Guide](CLI-TOOLS.md)**.
|
||||
|
||||
---
|
||||
|
||||
## Protocol Setup (MCP + A2A)
|
||||
|
||||
### MCP Setup (Model Context Protocol)
|
||||
|
||||
Start MCP transport in stdio mode:
|
||||
|
||||
```bash
|
||||
omniroute --mcp
|
||||
```
|
||||
|
||||
Recommended validation flow:
|
||||
|
||||
```bash
|
||||
# 1. Start MCP server
|
||||
omniroute --mcp
|
||||
|
||||
# 2. From your MCP client, call:
|
||||
omniroute_get_health # Should return system health
|
||||
omniroute_list_combos # Should return active combos
|
||||
|
||||
# 3. Or run the full E2E suite:
|
||||
npm run test:protocols:e2e
|
||||
```
|
||||
|
||||
#### MCP Client Configuration
|
||||
|
||||
**Claude Code:**
|
||||
|
||||
```bash
|
||||
claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp/stream
|
||||
```
|
||||
|
||||
**Cursor / Cline:**
|
||||
|
||||
Add to your MCP settings:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"omniroute": {
|
||||
"command": "omniroute",
|
||||
"args": ["--mcp"],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Full MCP documentation:** [MCP Server README](../open-sse/mcp-server/README.md) — 29 tools, IDE configs, Python/TS/Go clients.
|
||||
|
||||
### A2A Setup (Agent-to-Agent Protocol)
|
||||
|
||||
Verify the Agent Card:
|
||||
|
||||
```bash
|
||||
curl http://localhost:20128/.well-known/agent.json
|
||||
```
|
||||
|
||||
Send a task:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/a2a \
|
||||
-H 'content-type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":"quickstart","method":"message/send","params":{"skill":"quota-management","messages":[{"role":"user","content":"Give me a short quota summary."}]}}'
|
||||
```
|
||||
|
||||
**Full A2A documentation:** [A2A Server README](../src/lib/a2a/README.md) — JSON-RPC 2.0, skills, streaming, task lifecycle.
|
||||
|
||||
---
|
||||
|
||||
## Timeout Configuration
|
||||
|
||||
### Basic Timeouts
|
||||
|
||||
For most deployments, you only need these two variables:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| ------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `REQUEST_TIMEOUT_MS` | `600000` | Shared baseline for upstream response-start timeout, hidden Undici timeouts, TLS fingerprint requests, and API bridge request/proxy timeouts |
|
||||
| `STREAM_IDLE_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` | Maximum gap between streaming chunks before OmniRoute aborts the SSE stream |
|
||||
|
||||
Backward compatibility is preserved: existing `FETCH_TIMEOUT_MS`, `API_BRIDGE_PROXY_TIMEOUT_MS`, and other per-layer timeout vars still work and override the shared baseline.
|
||||
|
||||
### Provider-Specific Notes
|
||||
|
||||
For Claude Code-compatible upstreams (`anthropic-compatible-cc-*`), OmniRoute derives the outbound `X-Stainless-Timeout` header from the resolved fetch timeout so provider-side read timeouts stay aligned with your env configuration.
|
||||
|
||||
For third-party Claude Code-compatible reverse proxies, OmniRoute keeps the default `anthropic-beta` set conservative and, when `Client Cache Control` is left on `Auto`, only forwards client-provided `cache_control` markers.
|
||||
|
||||
### Advanced Timeout Overrides
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| ---------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------- |
|
||||
| `FETCH_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` | Upstream response-start timeout used until response headers arrive |
|
||||
| `FETCH_HEADERS_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Undici time limit for receiving upstream response headers |
|
||||
| `FETCH_BODY_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Undici time limit between upstream body chunks (`0` disables it) |
|
||||
| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | Undici TCP connect timeout |
|
||||
| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Undici idle keep-alive socket timeout |
|
||||
| `TLS_CLIENT_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Timeout for TLS fingerprint requests made through `wreq-js` |
|
||||
| `API_BRIDGE_PROXY_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` or `30000` | Timeout for `/v1` proxy forwarding from API port to dashboard port |
|
||||
| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `max(API_BRIDGE_PROXY_TIMEOUT_MS, 300000)` | Incoming request timeout on the API bridge server |
|
||||
| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Incoming header timeout on the API bridge server |
|
||||
| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Keep-alive timeout on the API bridge server |
|
||||
| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Socket inactivity timeout on the API bridge server (`0` disables it) |
|
||||
|
||||
> **Note:** For streaming requests, `FETCH_TIMEOUT_MS` only covers connection setup / waiting for the first upstream response. Once the stream is active, OmniRoute will only abort on an actual stall (`STREAM_IDLE_TIMEOUT_MS`) or Undici body inactivity (`FETCH_BODY_TIMEOUT_MS`).
|
||||
|
||||
### Reverse Proxy Compatibility
|
||||
|
||||
If you run OmniRoute behind Nginx, Caddy, Cloudflare, or another reverse proxy, make sure the proxy timeouts are also higher than your OmniRoute stream/fetch timeouts.
|
||||
|
||||
---
|
||||
|
||||
## Split-Port Mode
|
||||
|
||||
Run API and Dashboard on separate ports for advanced scenarios (reverse proxy, container networking):
|
||||
|
||||
```bash
|
||||
PORT=20128 DASHBOARD_PORT=20129 omniroute
|
||||
# API: http://localhost:20128/v1
|
||||
# Dashboard: http://localhost:20129
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Void Linux (xbps-src) Template
|
||||
|
||||
For Void Linux users, you can build a native package using `xbps-src`. Save this block as `srcpkgs/omniroute/template`:
|
||||
|
||||
```bash
|
||||
# Template file for 'omniroute'
|
||||
pkgname=omniroute
|
||||
version=3.4.1
|
||||
revision=1
|
||||
hostmakedepends="nodejs python3 make"
|
||||
depends="openssl"
|
||||
short_desc="Universal AI gateway with smart routing for multiple LLM providers"
|
||||
maintainer="zenobit <zenobit@disroot.org>"
|
||||
license="MIT"
|
||||
homepage="https://github.com/diegosouzapw/OmniRoute"
|
||||
distfiles="https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz"
|
||||
checksum=009400afee90a9f32599d8fe734145cfd84098140b7287990183dde45ae2245b
|
||||
system_accounts="_omniroute"
|
||||
omniroute_homedir="/var/lib/omniroute"
|
||||
export NODE_ENV=production
|
||||
export npm_config_engine_strict=false
|
||||
export npm_config_loglevel=error
|
||||
export npm_config_fund=false
|
||||
export npm_config_audit=false
|
||||
|
||||
do_build() {
|
||||
local _gyp_arch
|
||||
case "$XBPS_TARGET_MACHINE" in
|
||||
aarch64*) _gyp_arch=arm64 ;;
|
||||
armv7*|armv6*) _gyp_arch=arm ;;
|
||||
i686*) _gyp_arch=ia32 ;;
|
||||
*) _gyp_arch=x64 ;;
|
||||
esac
|
||||
|
||||
NODE_ENV=development npm ci --ignore-scripts
|
||||
npm run build
|
||||
cp -r .next/static .next/standalone/.next/static
|
||||
[ -d public ] && cp -r public .next/standalone/public || true
|
||||
|
||||
local _node_gyp=/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js
|
||||
(cd node_modules/better-sqlite3 && node "$_node_gyp" rebuild --arch="$_gyp_arch")
|
||||
|
||||
local _bs3_release=.next/standalone/node_modules/better-sqlite3/build/Release
|
||||
mkdir -p "$_bs3_release"
|
||||
cp node_modules/better-sqlite3/build/Release/better_sqlite3.node "$_bs3_release/"
|
||||
|
||||
rm -rf .next/standalone/node_modules/@img
|
||||
|
||||
for _mod in pino-abstract-transport split2 process-warning; do
|
||||
cp -r "node_modules/$_mod" .next/standalone/node_modules/
|
||||
done
|
||||
}
|
||||
|
||||
do_check() {
|
||||
npm run test:unit
|
||||
}
|
||||
|
||||
do_install() {
|
||||
vmkdir usr/lib/omniroute/.next
|
||||
vcopy .next/standalone/. usr/lib/omniroute/.next/standalone
|
||||
|
||||
for _d in \
|
||||
.next/standalone/.next/server/app/dashboard \
|
||||
.next/standalone/.next/server/app/dashboard/settings \
|
||||
.next/standalone/.next/server/app/dashboard/providers; do
|
||||
touch "${DESTDIR}/usr/lib/omniroute/${_d}/.keep"
|
||||
done
|
||||
|
||||
cat > "${WRKDIR}/omniroute" <<'EOF'
|
||||
#!/bin/sh
|
||||
export PORT="${PORT:-20128}"
|
||||
export DATA_DIR="${DATA_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/omniroute}"
|
||||
export APP_LOG_TO_FILE="${APP_LOG_TO_FILE:-false}"
|
||||
mkdir -p "${DATA_DIR}"
|
||||
exec node /usr/lib/omniroute/.next/standalone/server.js "$@"
|
||||
EOF
|
||||
vbin "${WRKDIR}/omniroute"
|
||||
}
|
||||
|
||||
post_install() {
|
||||
vlicense LICENSE
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Uninstalling
|
||||
|
||||
| Command | Action |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------- |
|
||||
| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. |
|
||||
| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. |
|
||||
|
||||
> For detailed uninstall instructions across all methods, see [UNINSTALL.md](UNINSTALL.md).
|
||||
160
docs/TERMUX_GUIDE.md
Normal file
160
docs/TERMUX_GUIDE.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Termux Headless Setup
|
||||
|
||||
OmniRoute can run as a headless server on Android through Termux. The Electron desktop app is not supported in Termux, but the web dashboard and OpenAI-compatible API work from the local browser or from other devices on the same network.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install Termux from F-Droid or GitHub releases, then update packages and install the build tools required by native dependencies such as `better-sqlite3`.
|
||||
|
||||
```bash
|
||||
pkg update
|
||||
pkg upgrade
|
||||
pkg install nodejs-lts python build-essential git
|
||||
```
|
||||
|
||||
If native package compilation fails, rerun the `pkg install` command above and then retry the OmniRoute install.
|
||||
|
||||
## Install
|
||||
|
||||
Run the latest published package directly:
|
||||
|
||||
```bash
|
||||
npx -y omniroute@latest
|
||||
```
|
||||
|
||||
You can also install it globally:
|
||||
|
||||
```bash
|
||||
npm install -g omniroute
|
||||
omniroute
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
Start OmniRoute in headless server mode:
|
||||
|
||||
```bash
|
||||
omniroute
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
npx omniroute
|
||||
```
|
||||
|
||||
The dashboard listens on:
|
||||
|
||||
```text
|
||||
http://localhost:20128
|
||||
```
|
||||
|
||||
Open that URL in the Android browser. If you run clients inside Termux, use the same host and port as the OpenAI-compatible base URL.
|
||||
|
||||
## Background Execution
|
||||
|
||||
For a simple background process:
|
||||
|
||||
```bash
|
||||
nohup omniroute > omniroute.log 2>&1 &
|
||||
```
|
||||
|
||||
To stop it:
|
||||
|
||||
```bash
|
||||
pkill -f omniroute
|
||||
```
|
||||
|
||||
For automatic startup after device boot, install the Termux:Boot add-on and create a boot script:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.termux/boot
|
||||
cat > ~/.termux/boot/omniroute.sh <<'EOF'
|
||||
#!/data/data/com.termux/files/usr/bin/sh
|
||||
cd "$HOME"
|
||||
nohup omniroute > "$HOME/omniroute.log" 2>&1 &
|
||||
EOF
|
||||
chmod +x ~/.termux/boot/omniroute.sh
|
||||
```
|
||||
|
||||
Android battery optimization can stop long-running background processes. Disable battery optimization for Termux if the server is expected to stay online.
|
||||
|
||||
## Access From Other Devices
|
||||
|
||||
Find the phone IP address on the WiFi network:
|
||||
|
||||
```bash
|
||||
ip addr show wlan0
|
||||
```
|
||||
|
||||
Then open the dashboard from another device:
|
||||
|
||||
```text
|
||||
http://PHONE_IP:20128
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```text
|
||||
http://192.168.1.50:20128
|
||||
```
|
||||
|
||||
Keep the phone and client on the same trusted network. If you expose OmniRoute outside the phone, enable API keys and dashboard authentication.
|
||||
|
||||
## Data Directory
|
||||
|
||||
By default OmniRoute stores data under the Termux home directory, following the same server-side data path behavior used on Linux. To place the database somewhere explicit:
|
||||
|
||||
```bash
|
||||
export DATA_DIR="$HOME/.omniroute"
|
||||
omniroute
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Electron does not run in Termux.
|
||||
- There is no system tray or desktop integration.
|
||||
- This setup is server-only: use the browser dashboard.
|
||||
- Native dependencies may need local compilation.
|
||||
- Low-memory Android devices may need fewer concurrent requests.
|
||||
- MITM/system certificate features may require Android-level trust-store work outside Termux.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### better-sqlite3 Build Errors
|
||||
|
||||
Install the Termux build toolchain:
|
||||
|
||||
```bash
|
||||
pkg install nodejs-lts python build-essential
|
||||
```
|
||||
|
||||
Then rerun:
|
||||
|
||||
```bash
|
||||
npx -y omniroute@latest
|
||||
```
|
||||
|
||||
### Port Already In Use
|
||||
|
||||
Check what is listening on the default port:
|
||||
|
||||
```bash
|
||||
ss -ltnp | grep 20128
|
||||
```
|
||||
|
||||
Stop the old process:
|
||||
|
||||
```bash
|
||||
pkill -f omniroute
|
||||
```
|
||||
|
||||
### Dashboard Not Reachable From Another Device
|
||||
|
||||
Verify both devices are on the same WiFi network, then test from Termux:
|
||||
|
||||
```bash
|
||||
curl http://localhost:20128
|
||||
```
|
||||
|
||||
If local access works but LAN access does not, check Android hotspot/WiFi isolation and any firewall or VPN profile on the phone.
|
||||
@@ -8,13 +8,95 @@ Common problems and solutions for OmniRoute.
|
||||
|
||||
## Quick Fixes
|
||||
|
||||
| Problem | Solution |
|
||||
| ----------------------------- | ------------------------------------------------------------------ |
|
||||
| First login not working | Set `INITIAL_PASSWORD` in `.env` (no hardcoded default) |
|
||||
| Dashboard opens on wrong port | Set `PORT=20128` and `NEXT_PUBLIC_BASE_URL=http://localhost:20128` |
|
||||
| No request logs under `logs/` | Set `ENABLE_REQUEST_LOGS=true` |
|
||||
| EACCES: permission denied | Set `DATA_DIR=/path/to/writable/dir` to override `~/.omniroute` |
|
||||
| Routing strategy not saving | Update to v1.4.11+ (Zod schema fix for settings persistence) |
|
||||
| Problem | Solution |
|
||||
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| First login not working | Set `INITIAL_PASSWORD` in `.env` (no hardcoded default) |
|
||||
| Dashboard opens on wrong port | Set `PORT=20128` and `NEXT_PUBLIC_BASE_URL=http://localhost:20128` |
|
||||
| No logs written to disk | Set `APP_LOG_TO_FILE=true` and verify call log capture is enabled |
|
||||
| EACCES: permission denied | Set `DATA_DIR=/path/to/writable/dir` to override `~/.omniroute` |
|
||||
| Routing strategy not saving | Update to v1.4.11+ (Zod schema fix for settings persistence) |
|
||||
| Login crash / blank page | Check Node.js version — see [Node.js Compatibility](#nodejs-compatibility) below |
|
||||
| `dlopen` / `slice is not valid mach-o file` (macOS) | Run `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` — see [macOS native module rebuild](#macos-native-module-rebuild) below |
|
||||
| Proxy "fetch failed" | Ensure proxy config is set at the correct level — see [Proxy Issues](#proxy-issues) below |
|
||||
|
||||
---
|
||||
|
||||
## Node.js Compatibility
|
||||
|
||||
<a name="nodejs-compatibility"></a>
|
||||
|
||||
### Login page crashes or shows "Module self-registration" error
|
||||
|
||||
**Cause:** You are running a Node.js version outside OmniRoute's approved secure runtime floor. The most common case is running an older Node 20, 22, or 24 patch level that falls below the patched security floor OmniRoute requires.
|
||||
|
||||
**Symptoms:**
|
||||
|
||||
- Login page shows a blank screen or a server error
|
||||
- Console shows `Error: Module did not self-register` or similar native binding errors
|
||||
- The login page shows an **orange warning banner** with your Node version if the runtime is outside the supported secure policy
|
||||
|
||||
**Fix:**
|
||||
|
||||
1. Install a supported Node.js LTS release (recommended: Node.js 24.x):
|
||||
```bash
|
||||
nvm install 24
|
||||
nvm use 24
|
||||
```
|
||||
2. Verify your version: `node --version` should show `v24.0.0` or newer on the 24.x LTS line
|
||||
3. Reinstall OmniRoute: `npm install -g omniroute`
|
||||
4. Restart: `omniroute`
|
||||
|
||||
> **Supported secure versions:** `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`. Node.js 24.x LTS (Krypton) is fully supported.
|
||||
|
||||
### macOS: `dlopen` / "slice is not valid mach-o file"
|
||||
|
||||
<a name="macos-native-module-rebuild"></a>
|
||||
|
||||
**Cause:** After a global `npm install -g omniroute`, the `better-sqlite3` native binary inside the package may have been compiled for a different architecture or Node.js ABI than what is running locally. This is common on macOS (both Apple Silicon and Intel) when the pre-built binary does not match your environment.
|
||||
|
||||
**Symptoms:**
|
||||
|
||||
- Server fails immediately on startup with a `dlopen` error
|
||||
- Error contains `slice is not valid mach-o file`
|
||||
- Full example:
|
||||
|
||||
```
|
||||
dlopen(/Users/<user>/.nvm/versions/node/v24.14.1/lib/node_modules/omniroute/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node, 0x0001): tried: '...' (slice is not valid mach-o file)
|
||||
```
|
||||
|
||||
**Fix — rebuild for your local environment (no Node.js downgrade required):**
|
||||
|
||||
```bash
|
||||
cd $(npm root -g)/omniroute/app
|
||||
npm rebuild better-sqlite3
|
||||
omniroute
|
||||
```
|
||||
|
||||
> **Note:** This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported range is **`>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`** (`engines` field in `package.json`). Node.js 24.x LTS (Krypton) is fully supported with `better-sqlite3` v12.x.
|
||||
|
||||
---
|
||||
|
||||
## Proxy Issues
|
||||
|
||||
<a name="proxy-issues"></a>
|
||||
|
||||
### Provider validation shows "fetch failed"
|
||||
|
||||
**Cause:** The API key validation endpoint (`POST /api/providers/validate`) was previously bypassing proxy configuration, causing failures in environments that require proxy routing.
|
||||
|
||||
**Fix (v3.5.5+):** This is now fixed. Provider validation routes through `runWithProxyContext`, honoring provider-level and global proxy settings automatically.
|
||||
|
||||
### Token health check fails with "fetch failed"
|
||||
|
||||
**Cause:** Background OAuth token refresh was not resolving proxy configuration per connection.
|
||||
|
||||
**Fix (v3.5.5+):** The token health check scheduler now resolves proxy config per connection before attempting refresh. Update to v3.5.5+.
|
||||
|
||||
### SOCKS5 proxy returns "invalid onRequestStart method"
|
||||
|
||||
**Cause:** On Node.js 22, the undici@8 dispatcher is incompatible with Node's built-in `fetch()` implementation.
|
||||
|
||||
**Fix (v3.5.5+):** OmniRoute now uses undici's own `fetch()` function when a proxy dispatcher is active, ensuring consistent behavior. Update to v3.5.5+.
|
||||
|
||||
---
|
||||
|
||||
@@ -97,16 +179,20 @@ curl -s http://localhost:20128/api/cli-tools/openclaw-settings | jq '{installed,
|
||||
|
||||
1. Check usage stats in Dashboard → Usage
|
||||
2. Switch primary model to GLM/MiniMax
|
||||
3. Use free tier (Gemini CLI, iFlow) for non-critical tasks
|
||||
3. Use free tier (Gemini CLI, Qoder) for non-critical tasks
|
||||
4. Set cost budgets per API key: Dashboard → API Keys → Budget
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### Enable Request Logs
|
||||
### Enable Log Files
|
||||
|
||||
Set `ENABLE_REQUEST_LOGS=true` in your `.env` file. Logs appear under `logs/` directory.
|
||||
Set `APP_LOG_TO_FILE=true` in your `.env` file. Application logs are written under `logs/`.
|
||||
Request artifacts are stored under `${DATA_DIR}/call_logs/` when the call log pipeline is
|
||||
enabled in settings.
|
||||
When pipeline capture is enabled, set `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS=false` to omit
|
||||
stream chunk payloads, or tune `CALL_LOG_PIPELINE_MAX_SIZE_KB` to change the artifact cap in KB.
|
||||
|
||||
### Check Provider Health
|
||||
|
||||
@@ -121,8 +207,9 @@ curl http://localhost:20128/api/monitoring/health
|
||||
### Runtime Storage
|
||||
|
||||
- Main state: `${DATA_DIR}/storage.sqlite` (providers, combos, aliases, keys, settings)
|
||||
- Usage: SQLite tables in `storage.sqlite` (`usage_history`, `call_logs`, `proxy_logs`) + optional `${DATA_DIR}/log.txt` and `${DATA_DIR}/call_logs/`
|
||||
- Request logs: `<repo>/logs/...` (when `ENABLE_REQUEST_LOGS=true`)
|
||||
- Usage: SQLite tables in `storage.sqlite` (`usage_history`, `call_logs`, `proxy_logs`) + optional `${DATA_DIR}/call_logs/`
|
||||
- Application logs: `<repo>/logs/...` (when `APP_LOG_TO_FILE=true`)
|
||||
- Call log artifacts: `${DATA_DIR}/call_logs/YYYY-MM-DD/...` when the call log pipeline is enabled
|
||||
|
||||
---
|
||||
|
||||
|
||||
155
docs/UNINSTALL.md
Normal file
155
docs/UNINSTALL.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# OmniRoute — Uninstall Guide
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](UNINSTALL.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/UNINSTALL.md) | 🇪🇸 [Español](i18n/es/UNINSTALL.md) | 🇫🇷 [Français](i18n/fr/UNINSTALL.md) | 🇮🇹 [Italiano](i18n/it/UNINSTALL.md) | 🇷🇺 [Русский](i18n/ru/UNINSTALL.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/UNINSTALL.md) | 🇩🇪 [Deutsch](i18n/de/UNINSTALL.md) | 🇮🇳 [हिन्दी](i18n/in/UNINSTALL.md) | 🇹🇭 [ไทย](i18n/th/UNINSTALL.md) | 🇺🇦 [Українська](i18n/uk-UA/UNINSTALL.md) | 🇸🇦 [العربية](i18n/ar/UNINSTALL.md) | 🇯🇵 [日本語](i18n/ja/UNINSTALL.md) | 🇻🇳 [Tiếng Việt](i18n/vi/UNINSTALL.md) | 🇧🇬 [Български](i18n/bg/UNINSTALL.md) | 🇩🇰 [Dansk](i18n/da/UNINSTALL.md) | 🇫🇮 [Suomi](i18n/fi/UNINSTALL.md) | 🇮🇱 [עברית](i18n/he/UNINSTALL.md) | 🇭🇺 [Magyar](i18n/hu/UNINSTALL.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/UNINSTALL.md) | 🇰🇷 [한국어](i18n/ko/UNINSTALL.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/UNINSTALL.md) | 🇳🇱 [Nederlands](i18n/nl/UNINSTALL.md) | 🇳🇴 [Norsk](i18n/no/UNINSTALL.md) | 🇵🇹 [Português (Portugal)](i18n/pt/UNINSTALL.md) | 🇷🇴 [Română](i18n/ro/UNINSTALL.md) | 🇵🇱 [Polski](i18n/pl/UNINSTALL.md) | 🇸🇰 [Slovenčina](i18n/sk/UNINSTALL.md) | 🇸🇪 [Svenska](i18n/sv/UNINSTALL.md) | 🇵🇭 [Filipino](i18n/phi/UNINSTALL.md) | 🇨🇿 [Čeština](i18n/cs/UNINSTALL.md)
|
||||
|
||||
This guide covers how to cleanly remove OmniRoute from your system.
|
||||
|
||||
---
|
||||
|
||||
## Quick Uninstall (v3.6.2+)
|
||||
|
||||
OmniRoute provides two built-in scripts for clean removal:
|
||||
|
||||
### Keep Your Data
|
||||
|
||||
```bash
|
||||
npm run uninstall
|
||||
```
|
||||
|
||||
This removes the OmniRoute application but **preserves** your database, configurations, API keys, and provider settings in `~/.omniroute/`. Use this if you plan to reinstall later and want to keep your setup.
|
||||
|
||||
### Full Removal
|
||||
|
||||
```bash
|
||||
npm run uninstall:full
|
||||
```
|
||||
|
||||
This removes the application **and permanently erases** all data:
|
||||
|
||||
- Database (`storage.sqlite`)
|
||||
- Provider configurations and API keys
|
||||
- Backup files
|
||||
- Log files
|
||||
- All files in the `~/.omniroute/` directory
|
||||
|
||||
> ⚠️ **Warning:** `npm run uninstall:full` is irreversible. All your provider connections, combos, API keys, and usage history will be permanently deleted.
|
||||
|
||||
---
|
||||
|
||||
## Manual Uninstall
|
||||
|
||||
### NPM Global Install
|
||||
|
||||
```bash
|
||||
# Remove the global package
|
||||
npm uninstall -g omniroute
|
||||
|
||||
# (Optional) Remove data directory
|
||||
rm -rf ~/.omniroute
|
||||
```
|
||||
|
||||
### pnpm Global Install
|
||||
|
||||
```bash
|
||||
pnpm uninstall -g omniroute
|
||||
rm -rf ~/.omniroute
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Stop and remove the container
|
||||
docker stop omniroute
|
||||
docker rm omniroute
|
||||
|
||||
# Remove the volume (deletes all data)
|
||||
docker volume rm omniroute-data
|
||||
|
||||
# (Optional) Remove the image
|
||||
docker rmi diegosouzapw/omniroute:latest
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```bash
|
||||
# Stop and remove containers
|
||||
docker compose down
|
||||
|
||||
# Also remove volumes (deletes all data)
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
### Electron Desktop App
|
||||
|
||||
**Windows:**
|
||||
|
||||
- Open `Settings → Apps → OmniRoute → Uninstall`
|
||||
- Or run the NSIS uninstaller from the install directory
|
||||
|
||||
**macOS:**
|
||||
|
||||
- Drag `OmniRoute.app` from `/Applications` to Trash
|
||||
- Remove data: `rm -rf ~/Library/Application Support/omniroute`
|
||||
|
||||
**Linux:**
|
||||
|
||||
- Remove the AppImage file
|
||||
- Remove data: `rm -rf ~/.omniroute`
|
||||
|
||||
### Source Install (git clone)
|
||||
|
||||
```bash
|
||||
# Remove the cloned directory
|
||||
rm -rf /path/to/omniroute
|
||||
|
||||
# (Optional) Remove data directory
|
||||
rm -rf ~/.omniroute
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Directories
|
||||
|
||||
OmniRoute stores data in the following locations by default:
|
||||
|
||||
| Platform | Default Path | Override |
|
||||
| ------------- | ----------------------------- | ------------------------- |
|
||||
| Linux | `~/.omniroute/` | `DATA_DIR` env var |
|
||||
| macOS | `~/.omniroute/` | `DATA_DIR` env var |
|
||||
| Windows | `%APPDATA%/omniroute/` | `DATA_DIR` env var |
|
||||
| Docker | `/app/data/` (mounted volume) | `DATA_DIR` env var |
|
||||
| XDG-compliant | `$XDG_CONFIG_HOME/omniroute/` | `XDG_CONFIG_HOME` env var |
|
||||
|
||||
### Files in the data directory
|
||||
|
||||
| File/Directory | Description |
|
||||
| -------------------- | ------------------------------------------------- |
|
||||
| `storage.sqlite` | Main database (providers, combos, settings, keys) |
|
||||
| `storage.sqlite-wal` | SQLite write-ahead log (temporary) |
|
||||
| `storage.sqlite-shm` | SQLite shared memory (temporary) |
|
||||
| `call_logs/` | Request payload archives |
|
||||
| `backups/` | Automatic database backups |
|
||||
| `log.txt` | Legacy request log (optional) |
|
||||
|
||||
---
|
||||
|
||||
## Verify Complete Removal
|
||||
|
||||
After uninstalling, verify there are no remaining files:
|
||||
|
||||
```bash
|
||||
# Check for global npm package
|
||||
npm list -g omniroute 2>/dev/null
|
||||
|
||||
# Check for data directory
|
||||
ls -la ~/.omniroute/ 2>/dev/null
|
||||
|
||||
# Check for running processes
|
||||
pgrep -f omniroute
|
||||
```
|
||||
|
||||
If any process is still running, stop it:
|
||||
|
||||
```bash
|
||||
pkill -f omniroute
|
||||
```
|
||||
@@ -36,14 +36,15 @@ Complete guide for configuring providers, creating combos, integrating CLI tools
|
||||
| | Cerebras | Pay per use | None | Wafer-scale speed |
|
||||
| | Cohere | Pay per use | None | Command R+ RAG |
|
||||
| | NVIDIA NIM | Pay per use | None | Enterprise models |
|
||||
| | Baidu Qianfan | Pay per use | None | ERNIE models |
|
||||
| **💰 CHEAP** | GLM-4.7 | $0.6/1M | Daily 10AM | Budget backup |
|
||||
| | MiniMax M2.1 | $0.2/1M | 5-hour rolling | Cheapest option |
|
||||
| | Kimi K2 | $9/mo flat | 10M tokens/mo | Predictable cost |
|
||||
| **🆓 FREE** | iFlow | $0 | Unlimited | 8 models free |
|
||||
| **🆓 FREE** | Qoder | $0 | Unlimited | 8 models free |
|
||||
| | Qwen | $0 | Unlimited | 3 models free |
|
||||
| | Kiro | $0 | Unlimited | Claude free |
|
||||
|
||||
**💡 Pro Tip:** Start with Gemini CLI (180K free/month) + iFlow (unlimited free) combo = $0 cost!
|
||||
**💡 Pro Tip:** Start with Gemini CLI (180K free/month) + Qoder (unlimited free) combo = $0 cost!
|
||||
|
||||
---
|
||||
|
||||
@@ -55,7 +56,7 @@ Complete guide for configuring providers, creating combos, integrating CLI tools
|
||||
|
||||
```
|
||||
Combo: "maximize-claude"
|
||||
1. cc/claude-opus-4-6 (use subscription fully)
|
||||
1. cc/claude-opus-4-7 (use subscription fully)
|
||||
2. glm/glm-4.7 (cheap backup when quota out)
|
||||
3. if/kimi-k2-thinking (free emergency fallback)
|
||||
|
||||
@@ -83,7 +84,7 @@ Quality: Production-ready models
|
||||
|
||||
```
|
||||
Combo: "always-on"
|
||||
1. cc/claude-opus-4-6 (best quality)
|
||||
1. cc/claude-opus-4-7 (best quality)
|
||||
2. cx/gpt-5.2-codex (second subscription)
|
||||
3. glm/glm-4.7 (cheap, resets daily)
|
||||
4. minimax/MiniMax-M2.1 (cheapest, 5h reset)
|
||||
@@ -121,7 +122,7 @@ Dashboard → Providers → Connect Claude Code
|
||||
→ 5-hour + weekly quota tracking
|
||||
|
||||
Models:
|
||||
cc/claude-opus-4-6
|
||||
cc/claude-opus-4-7
|
||||
cc/claude-sonnet-4-5-20250929
|
||||
cc/claude-haiku-4-5-20251001
|
||||
```
|
||||
@@ -164,7 +165,7 @@ Dashboard → Providers → Connect GitHub
|
||||
Models:
|
||||
gh/gpt-5
|
||||
gh/claude-4.5-sonnet
|
||||
gh/gemini-3-pro
|
||||
gh/gemini-3.1-pro-preview
|
||||
```
|
||||
|
||||
### 💰 Cheap Providers
|
||||
@@ -191,12 +192,19 @@ Models:
|
||||
|
||||
**Use:** `kimi/kimi-latest` — **Pro Tip:** Fixed $9/month for 10M tokens = $0.90/1M effective cost!
|
||||
|
||||
#### Baidu Qianfan / ERNIE
|
||||
|
||||
1. Sign up: [Baidu AI Cloud Qianfan](https://cloud.baidu.com/product/wenxinworkshop)
|
||||
2. Create a Qianfan API key → Dashboard → Add API Key: Provider: `qianfan`
|
||||
|
||||
**Use:** `qianfan/ernie-4.5-turbo-128k`, `qianfan/ernie-x1-turbo-32k`, or another Qianfan OpenAI-compatible model ID.
|
||||
|
||||
### 🆓 FREE Providers
|
||||
|
||||
#### iFlow (8 FREE models)
|
||||
#### Qoder (8 FREE models)
|
||||
|
||||
```bash
|
||||
Dashboard → Connect iFlow → OAuth login → Unlimited usage
|
||||
Dashboard → Connect Qoder → OAuth login → Unlimited usage
|
||||
|
||||
Models: if/kimi-k2-thinking, if/qwen3-coder-plus, if/glm-4.7, if/minimax-m2, if/deepseek-r1
|
||||
```
|
||||
@@ -221,6 +229,8 @@ Models: kr/claude-sonnet-4.5, kr/claude-haiku-4.5
|
||||
|
||||
## 🎨 Combos
|
||||
|
||||
You can reorder combo cards directly in **Dashboard → Combos** by dragging the handle on each card. The order is stored in SQLite and restored on reload.
|
||||
|
||||
### Example 1: Maximize Subscription → Cheap Backup
|
||||
|
||||
```
|
||||
@@ -228,7 +238,7 @@ Dashboard → Combos → Create New
|
||||
|
||||
Name: premium-coding
|
||||
Models:
|
||||
1. cc/claude-opus-4-6 (Subscription primary)
|
||||
1. cc/claude-opus-4-7 (Subscription primary)
|
||||
2. glm/glm-4.7 (Cheap backup, $0.6/1M)
|
||||
3. minimax/MiniMax-M2.1 (Cheapest fallback, $0.20/1M)
|
||||
|
||||
@@ -257,20 +267,24 @@ Cost: $0 forever!
|
||||
Settings → Models → Advanced:
|
||||
OpenAI API Base URL: http://localhost:20128/v1
|
||||
OpenAI API Key: [from omniroute dashboard]
|
||||
Model: cc/claude-opus-4-6
|
||||
Model: cc/claude-opus-4-7
|
||||
```
|
||||
|
||||
### Claude Code
|
||||
|
||||
Edit `~/.claude/config.json`:
|
||||
Edit `~/.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"anthropic_api_base": "http://localhost:20128/v1",
|
||||
"anthropic_api_key": "your-omniroute-api-key"
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "http://localhost:20128",
|
||||
"ANTHROPIC_AUTH_TOKEN": "your-omniroute-api-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use the Claude-compatible root endpoint here. Do not append `/v1` to `ANTHROPIC_BASE_URL`.
|
||||
|
||||
### Codex CLI
|
||||
|
||||
```bash
|
||||
@@ -311,7 +325,7 @@ Edit `~/.openclaw/openclaw.json`:
|
||||
Provider: OpenAI Compatible
|
||||
Base URL: http://localhost:20128/v1
|
||||
API Key: [from dashboard]
|
||||
Model: cc/claude-opus-4-6
|
||||
Model: cc/claude-opus-4-7
|
||||
```
|
||||
|
||||
---
|
||||
@@ -337,6 +351,17 @@ omniroute --port 3000
|
||||
|
||||
The CLI automatically loads `.env` from `~/.omniroute/.env` or `./.env`.
|
||||
|
||||
### Uninstalling
|
||||
|
||||
When you no longer need OmniRoute, we provide two quick scripts for a clean removal:
|
||||
|
||||
| Command | Action |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------- |
|
||||
| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. |
|
||||
| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. |
|
||||
|
||||
> Note: To run these commands, navigate to the OmniRoute project folder (if you cloned it) and run them. Alternatively, if globally installed, you can simply run `npm uninstall -g omniroute`.
|
||||
|
||||
### VPS Deployment
|
||||
|
||||
```bash
|
||||
@@ -405,25 +430,130 @@ docker run -d --name omniroute -p 20128:20128 --env-file ./.env -v omniroute-dat
|
||||
|
||||
For host-integrated mode with CLI binaries, see the Docker section in the main docs.
|
||||
|
||||
### Void Linux (xbps-src)
|
||||
|
||||
Void Linux users can package and install OmniRoute natively using the `xbps-src` cross-compilation framework. This automates the Node.js standalone build along with the required `better-sqlite3` native bindings.
|
||||
|
||||
<details>
|
||||
<summary><b>View xbps-src template</b></summary>
|
||||
|
||||
```bash
|
||||
# Template file for 'omniroute'
|
||||
pkgname=omniroute
|
||||
version=3.2.4
|
||||
revision=1
|
||||
hostmakedepends="nodejs python3 make"
|
||||
depends="openssl"
|
||||
short_desc="Universal AI gateway with smart routing for multiple LLM providers"
|
||||
maintainer="zenobit <zenobit@disroot.org>"
|
||||
license="MIT"
|
||||
homepage="https://github.com/diegosouzapw/OmniRoute"
|
||||
distfiles="https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz"
|
||||
checksum=009400afee90a9f32599d8fe734145cfd84098140b7287990183dde45ae2245b
|
||||
system_accounts="_omniroute"
|
||||
omniroute_homedir="/var/lib/omniroute"
|
||||
export NODE_ENV=production
|
||||
export npm_config_engine_strict=false
|
||||
export npm_config_loglevel=error
|
||||
export npm_config_fund=false
|
||||
export npm_config_audit=false
|
||||
|
||||
do_build() {
|
||||
# Determine target CPU arch for node-gyp
|
||||
local _gyp_arch
|
||||
case "$XBPS_TARGET_MACHINE" in
|
||||
aarch64*) _gyp_arch=arm64 ;;
|
||||
armv7*|armv6*) _gyp_arch=arm ;;
|
||||
i686*) _gyp_arch=ia32 ;;
|
||||
*) _gyp_arch=x64 ;;
|
||||
esac
|
||||
|
||||
# 1) Install all deps – skip scripts
|
||||
NODE_ENV=development npm ci --ignore-scripts
|
||||
|
||||
# 2) Build the Next.js standalone bundle
|
||||
npm run build
|
||||
|
||||
# 3) Copy static assets into standalone
|
||||
cp -r .next/static .next/standalone/.next/static
|
||||
[ -d public ] && cp -r public .next/standalone/public || true
|
||||
|
||||
# 4) Compile better-sqlite3 native binding
|
||||
local _node_gyp=/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js
|
||||
(cd node_modules/better-sqlite3 && node "$_node_gyp" rebuild --arch="$_gyp_arch")
|
||||
|
||||
# 5) Place the compiled binding into the standalone bundle
|
||||
local _bs3_release=.next/standalone/node_modules/better-sqlite3/build/Release
|
||||
mkdir -p "$_bs3_release"
|
||||
cp node_modules/better-sqlite3/build/Release/better_sqlite3.node "$_bs3_release/"
|
||||
|
||||
# 6) Remove arch-specific sharp bundles
|
||||
rm -rf .next/standalone/node_modules/@img
|
||||
|
||||
# 7) Copy pino runtime deps omitted by Next.js static analysis:
|
||||
for _mod in pino-abstract-transport split2 process-warning; do
|
||||
cp -r "node_modules/$_mod" .next/standalone/node_modules/
|
||||
done
|
||||
}
|
||||
|
||||
do_check() {
|
||||
npm run test:unit
|
||||
}
|
||||
|
||||
do_install() {
|
||||
vmkdir usr/lib/omniroute/.next
|
||||
vcopy .next/standalone/. usr/lib/omniroute/.next/standalone
|
||||
|
||||
# Prevent removal of empty Next.js app router dirs by the post-install hook
|
||||
for _d in \
|
||||
.next/standalone/.next/server/app/dashboard \
|
||||
.next/standalone/.next/server/app/dashboard/settings \
|
||||
.next/standalone/.next/server/app/dashboard/providers; do
|
||||
touch "${DESTDIR}/usr/lib/omniroute/${_d}/.keep"
|
||||
done
|
||||
|
||||
cat > "${WRKDIR}/omniroute" <<'EOF'
|
||||
#!/bin/sh
|
||||
export PORT="${PORT:-20128}"
|
||||
export DATA_DIR="${DATA_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/omniroute}"
|
||||
export APP_LOG_TO_FILE="${APP_LOG_TO_FILE:-false}"
|
||||
mkdir -p "${DATA_DIR}"
|
||||
exec node /usr/lib/omniroute/.next/standalone/server.js "$@"
|
||||
EOF
|
||||
vbin "${WRKDIR}/omniroute"
|
||||
}
|
||||
|
||||
post_install() {
|
||||
vlicense LICENSE
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ------------------------- | ------------------------------------ | ------------------------------------------------------- |
|
||||
| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT signing secret (**change in production**) |
|
||||
| `INITIAL_PASSWORD` | `123456` | First login password |
|
||||
| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) |
|
||||
| `PORT` | framework default | Service port (`20128` in examples) |
|
||||
| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) |
|
||||
| `NODE_ENV` | runtime default | Set `production` for deploy |
|
||||
| `BASE_URL` | `http://localhost:20128` | Server-side internal base URL |
|
||||
| `CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL |
|
||||
| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | HMAC secret for generated API keys |
|
||||
| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` |
|
||||
| `ENABLE_REQUEST_LOGS` | `false` | Enables request/response logs |
|
||||
| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) |
|
||||
| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB |
|
||||
| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries |
|
||||
| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries |
|
||||
| Variable | Default | Description |
|
||||
| --------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
||||
| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT signing secret (**change in production**) |
|
||||
| `INITIAL_PASSWORD` | `123456` | First login password |
|
||||
| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) |
|
||||
| `PORT` | framework default | Service port (`20128` in examples) |
|
||||
| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) |
|
||||
| `NODE_ENV` | runtime default | Set `production` for deploy |
|
||||
| `BASE_URL` | `http://localhost:20128` | Server-side internal base URL |
|
||||
| `CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL |
|
||||
| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | HMAC secret for generated API keys |
|
||||
| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` |
|
||||
| `ALLOW_API_KEY_REVEAL` | `false` | Allow Api Manager to copy full API keys on demand |
|
||||
| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | Server-side refresh cadence for cached Provider Limits data; UI refresh buttons still trigger manual sync |
|
||||
| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | Disable automatic SQLite snapshots before writes/import/restore; manual backups still work |
|
||||
| `APP_LOG_TO_FILE` | `true` | Enables application and audit log output to disk |
|
||||
| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) |
|
||||
| `CLOUDFLARED_BIN` | unset | Use an existing `cloudflared` binary instead of managed download |
|
||||
| `CLOUDFLARED_PROTOCOL` | `http2` | Transport for managed Quick Tunnels (`http2`, `quic`, or `auto`) |
|
||||
| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB |
|
||||
| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries |
|
||||
| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries |
|
||||
|
||||
For the full environment variable reference, see the [README](../README.md).
|
||||
|
||||
@@ -434,7 +564,7 @@ For the full environment variable reference, see the [README](../README.md).
|
||||
<details>
|
||||
<summary><b>View all available models</b></summary>
|
||||
|
||||
**Claude Code (`cc/`)** — Pro/Max: `cc/claude-opus-4-6`, `cc/claude-sonnet-4-5-20250929`, `cc/claude-haiku-4-5-20251001`
|
||||
**Claude Code (`cc/`)** — Pro/Max: `cc/claude-opus-4-7`, `cc/claude-sonnet-4-5-20250929`, `cc/claude-haiku-4-5-20251001`
|
||||
|
||||
**Codex (`cx/`)** — Plus/Pro: `cx/gpt-5.2-codex`, `cx/gpt-5.1-codex-max`
|
||||
|
||||
@@ -446,7 +576,7 @@ For the full environment variable reference, see the [README](../README.md).
|
||||
|
||||
**MiniMax (`minimax/`)** — $0.2/1M: `minimax/MiniMax-M2.1`
|
||||
|
||||
**iFlow (`if/`)** — FREE: `if/kimi-k2-thinking`, `if/qwen3-coder-plus`, `if/deepseek-r1`
|
||||
**Qoder (`if/`)** — FREE: `if/kimi-k2-thinking`, `if/qwen3-coder-plus`, `if/deepseek-r1`
|
||||
|
||||
**Qwen (`qw/`)** — FREE: `qw/qwen3-coder-plus`, `qw/qwen3-coder-flash`
|
||||
|
||||
@@ -472,6 +602,8 @@ For the full environment variable reference, see the [README](../README.md).
|
||||
|
||||
**NVIDIA NIM (`nvidia/`)**: `nvidia/nvidia/llama-3.3-70b-instruct`
|
||||
|
||||
**Baidu Qianfan (`qianfan/`)**: `qianfan/ernie-4.5-turbo-128k`, `qianfan/ernie-x1-turbo-32k`
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
@@ -494,6 +626,11 @@ curl -X POST http://localhost:20128/api/provider-models \
|
||||
|
||||
Or use Dashboard: **Providers → [Provider] → Custom Models**.
|
||||
|
||||
Notes:
|
||||
|
||||
- OpenRouter and OpenAI/Anthropic-compatible providers are managed from **Available Models** only. Manual add, import, and auto-sync all land in the same available-model list, so there is no separate Custom Models section for those providers.
|
||||
- The **Custom Models** section is intended for providers that do not expose managed available-model imports.
|
||||
|
||||
### Dedicated Provider Routes
|
||||
|
||||
Route requests directly to a specific provider with model validation:
|
||||
@@ -538,6 +675,18 @@ Returns models grouped by provider with types (`chat`, `embedding`, `image`).
|
||||
- Automatic background sync with timeout + fail-fast
|
||||
- Prefer server-side `BASE_URL`/`CLOUD_URL` in production
|
||||
|
||||
### Cloudflare Quick Tunnel
|
||||
|
||||
- Available in **Dashboard → Endpoints** for Docker and other self-hosted deployments
|
||||
- Creates a temporary `https://*.trycloudflare.com` URL that forwards to your current OpenAI-compatible `/v1` endpoint
|
||||
- First enable installs `cloudflared` only when needed; later restarts reuse the same managed binary
|
||||
- Quick Tunnels are not auto-restored after an OmniRoute or container restart; re-enable them from the dashboard when needed
|
||||
- Tunnel URLs are ephemeral and change every time you stop/start the tunnel
|
||||
- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained containers
|
||||
- Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want to override the managed transport choice
|
||||
- Set `CLOUDFLARED_BIN` if you prefer using a preinstalled `cloudflared` binary instead of the managed download
|
||||
- Cloudflare Quick Tunnel, Tailscale Funnel, and ngrok Tunnel panels can be shown or hidden in **Settings → Appearance**. Hiding a panel does not stop a running tunnel.
|
||||
|
||||
### LLM Gateway Intelligence (Phase 9)
|
||||
|
||||
- **Semantic Cache** — Auto-caches non-streaming, temperature=0 responses (bypass with `X-OmniRoute-No-Cache: true`)
|
||||
@@ -611,7 +760,7 @@ Define global fallback chains that apply across all requests:
|
||||
|
||||
```
|
||||
Chain: production-fallback
|
||||
1. cc/claude-opus-4-6
|
||||
1. cc/claude-opus-4-7
|
||||
2. gh/gpt-5.1-codex
|
||||
3. glm/glm-4.7
|
||||
```
|
||||
@@ -622,30 +771,34 @@ Chain: production-fallback
|
||||
|
||||
Configure via **Dashboard → Settings → Resilience**.
|
||||
|
||||
OmniRoute implements provider-level resilience with four components:
|
||||
OmniRoute implements provider-level resilience with five components:
|
||||
|
||||
1. **Provider Profiles** — Per-provider configuration for:
|
||||
- Failure threshold (how many failures before opening)
|
||||
- Cooldown duration
|
||||
- Rate limit detection sensitivity
|
||||
- Exponential backoff parameters
|
||||
|
||||
2. **Editable Rate Limits** — System-level defaults configurable in the dashboard:
|
||||
1. **Request Queue & Pacing** — System-level request shaping:
|
||||
- **Requests Per Minute (RPM)** — Maximum requests per minute per account
|
||||
- **Min Time Between Requests** — Minimum gap in milliseconds between requests
|
||||
- **Max Concurrent Requests** — Maximum simultaneous requests per account
|
||||
- Click **Edit** to modify, then **Save** or **Cancel**. Values persist via the resilience API.
|
||||
|
||||
3. **Circuit Breaker** — Tracks failures per provider and automatically opens the circuit when a threshold is reached:
|
||||
2. **Connection Cooldown** — Per-auth-type configuration for a single connection after retryable failures:
|
||||
- **Base Cooldown** — Default cooldown window for retryable upstream failures
|
||||
- **Use Upstream Retry Hints** — Honors authoritative `Retry-After` or reset hints when provided
|
||||
- **Max Backoff Steps** — Maximum exponential backoff level for repeated failures
|
||||
|
||||
3. **Provider Circuit Breaker** — Tracks end-to-end provider failures and automatically opens the breaker when the configured threshold is reached:
|
||||
- **Failure Threshold** — Consecutive provider failures before opening the breaker
|
||||
- **Reset Timeout** — Time window before the provider is tested again
|
||||
- **CLOSED** (Healthy) — Requests flow normally
|
||||
- **OPEN** — Provider is temporarily blocked after repeated failures
|
||||
- **HALF_OPEN** — Testing if provider has recovered
|
||||
|
||||
4. **Policies & Locked Identifiers** — Shows circuit breaker status and locked identifiers with force-unlock capability.
|
||||
Connection-scoped `429` rate limits stay in **Connection Cooldown** and do not count toward the provider breaker.
|
||||
|
||||
5. **Rate Limit Auto-Detection** — Monitors `429` and `Retry-After` headers to proactively avoid hitting provider rate limits.
|
||||
The provider breaker runtime state is shown on **Dashboard → Health** only.
|
||||
|
||||
**Pro Tip:** Use **Reset All** button to clear all circuit breakers and cooldowns when a provider recovers from an outage.
|
||||
4. **Wait For Cooldown** — If every candidate connection is already cooling down, OmniRoute can wait for the earliest cooldown and retry the same client request automatically.
|
||||
|
||||
5. **Rate Limit Auto-Detection** — When upstream providers return explicit wait windows, those hints override the local connection cooldown when the setting is enabled.
|
||||
|
||||
**Pro Tip:** Use the **Health** page to inspect and reset live provider breakers after an outage. The Resilience page only changes configuration.
|
||||
|
||||
---
|
||||
|
||||
@@ -653,11 +806,11 @@ OmniRoute implements provider-level resilience with four components:
|
||||
|
||||
Manage database backups in **Dashboard → Settings → System & Storage**.
|
||||
|
||||
| Action | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Export Database** | Downloads the current SQLite database as a `.sqlite` file |
|
||||
| **Export All (.tar.gz)** | Downloads a full backup archive including: database, settings, combos, provider connections (no credentials), API key metadata |
|
||||
| **Import Database** | Upload a `.sqlite` file to replace the current database. A pre-import backup is automatically created |
|
||||
| Action | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Export Database** | Downloads the current SQLite database as a `.sqlite` file |
|
||||
| **Export All (.tar.gz)** | Downloads a full backup archive including: database, settings, combos, provider connections (no credentials), API key metadata |
|
||||
| **Import Database** | Upload a `.sqlite` file to replace the current database. A pre-import backup is automatically created unless `DISABLE_SQLITE_AUTO_BACKUP=true` |
|
||||
|
||||
```bash
|
||||
# API: Export database
|
||||
@@ -683,15 +836,16 @@ curl -X POST http://localhost:20128/api/db-backups/import \
|
||||
|
||||
### Settings Dashboard
|
||||
|
||||
The settings page is organized into 5 tabs for easy navigation:
|
||||
The settings page is organized into 6 tabs for easy navigation:
|
||||
|
||||
| Tab | Contents |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| **Security** | Login/Password settings, IP Access Control, API auth for `/models`, and Provider Blocking |
|
||||
| **Routing** | Global routing strategy (6 options), wildcard model aliases, fallback chains, combo defaults |
|
||||
| **Resilience** | Provider profiles, editable rate limits, circuit breaker status, policies & locked identifiers |
|
||||
| **AI** | Thinking budget configuration, global system prompt injection, prompt cache stats |
|
||||
| **Advanced** | Global proxy configuration (HTTP/SOCKS5) |
|
||||
| Tab | Contents |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| **General** | System storage tools, appearance settings, theme controls, sidebar visibility, and Endpoint tunnel visibility |
|
||||
| **Security** | Login/Password settings, IP Access Control, API auth for `/models`, and Provider Blocking |
|
||||
| **Routing** | Global routing strategy (6 options), wildcard model aliases, fallback chains, combo defaults |
|
||||
| **Resilience** | Request queue, connection cooldown, provider breaker config, and wait-for-cooldown behavior |
|
||||
| **AI** | Thinking budget configuration, global system prompt injection, prompt cache stats |
|
||||
| **Advanced** | Global proxy configuration (HTTP/SOCKS5) |
|
||||
|
||||
---
|
||||
|
||||
@@ -764,9 +918,9 @@ Access via **Dashboard → Health**. Real-time system health overview with 6 car
|
||||
| Card | What It Shows |
|
||||
| --------------------- | ----------------------------------------------------------- |
|
||||
| **System Status** | Uptime, version, memory usage, data directory |
|
||||
| **Provider Health** | Per-provider circuit breaker state (Closed/Open/Half-Open) |
|
||||
| **Rate Limits** | Active rate limit cooldowns per account with remaining time |
|
||||
| **Active Lockouts** | Providers temporarily blocked by the lockout policy |
|
||||
| **Provider Health** | Global provider circuit breaker runtime state |
|
||||
| **Rate Limits** | Active connection cooldowns per account with remaining time |
|
||||
| **Active Lockouts** | Active model-scoped lockouts and temporary exclusions |
|
||||
| **Signature Cache** | Deduplication cache stats (active keys, hit rate) |
|
||||
| **Latency Telemetry** | p50/p95/p99 latency aggregation per provider |
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ NODE_ENV=production
|
||||
HOSTNAME=0.0.0.0
|
||||
DATA_DIR=/app/data
|
||||
STORAGE_DRIVER=sqlite
|
||||
ENABLE_REQUEST_LOGS=true
|
||||
APP_LOG_TO_FILE=true
|
||||
AUTH_COOKIE_SECURE=false
|
||||
REQUIRE_API_KEY=false
|
||||
|
||||
@@ -213,8 +213,8 @@ server {
|
||||
# SSE (Server-Sent Events) — streaming AI responses
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,10 @@ server {
|
||||
NGINX
|
||||
```
|
||||
|
||||
Keep reverse-proxy stream timeouts aligned with your OmniRoute timeout env vars. If you raise
|
||||
`FETCH_TIMEOUT_MS` / `STREAM_IDLE_TIMEOUT_MS`, raise `proxy_read_timeout` / `proxy_send_timeout`
|
||||
above the same threshold.
|
||||
|
||||
### 3.3 Enable and Test
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# ADR-0001: Proxy Registry + Usage Control Generalization
|
||||
|
||||
Date: 2026-03-17
|
||||
Status: Accepted
|
||||
|
||||
## Context
|
||||
|
||||
OmniRoute sudah punya:
|
||||
|
||||
- Proxy assignment berbasis config-map (`global`, `providers`, `combos`, `keys`).
|
||||
- Quota-aware selection khusus provider tertentu (notably `codex`).
|
||||
|
||||
Gap utama:
|
||||
|
||||
- Proxy belum menjadi aset reusable yang bisa di-manage sebagai entitas (metadata, where-used, safe delete).
|
||||
- Usage policy belum konsisten lintas provider.
|
||||
- Error contract API belum seragam untuk endpoint manajemen.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Tambah **Proxy Registry** sebagai domain baru di DB (`proxy_registry`, `proxy_assignments`).
|
||||
2. Pertahankan kompatibilitas assignment lama (fallback ke `proxyConfig` lama).
|
||||
3. Resolver runtime pakai prioritas:
|
||||
- account -> provider -> global (registry)
|
||||
- fallback ke legacy resolver jika registry belum ada assignment
|
||||
4. Wajib redaction kredensial di output list registry default.
|
||||
5. Standarkan error JSON untuk endpoint manajemen proxy agar konsisten dan punya `requestId`.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positif:
|
||||
|
||||
- Proxy reusable dan bisa dilacak pemakaiannya.
|
||||
- Safe delete bisa ditegakkan (409 saat masih dipakai).
|
||||
- Migrasi bertahap tanpa breaking change runtime.
|
||||
|
||||
Negatif:
|
||||
|
||||
- Ada dual-source sementara (registry + legacy config) sampai migrasi selesai.
|
||||
- Butuh endpoint assignment tambahan dan pemetaan scope yang konsisten.
|
||||
|
||||
## Follow-up
|
||||
|
||||
- Migrasi UI provider/account dari input raw proxy ke selector registry.
|
||||
- Tambah health telemetry per proxy dan alerting.
|
||||
- Generalisasi usage control ke provider lain melalui interface policy yang sama.
|
||||
@@ -1,32 +0,0 @@
|
||||
# ADR-0002: Error Contract for Management Endpoints
|
||||
|
||||
Date: 2026-03-17
|
||||
Status: Accepted
|
||||
|
||||
## Decision
|
||||
|
||||
Management endpoints (proxy config, proxy registry, and proxy assignments) return a uniform error body:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"message": "Human-readable summary",
|
||||
"type": "invalid_request | not_found | conflict | server_error",
|
||||
"details": {}
|
||||
},
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## Status Mapping
|
||||
|
||||
- 400: invalid request / validation failure
|
||||
- 404: resource not found
|
||||
- 409: resource conflict (for example, proxy still assigned)
|
||||
- 500: unexpected server error
|
||||
|
||||
## Notes
|
||||
|
||||
- `requestId` is mandatory for log correlation.
|
||||
- `details` is optional and only used for safe validation details.
|
||||
- Sensitive secrets (proxy credentials, tokens) must never appear in `message` or `details`.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user