mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-27 02:12:19 +03:00
Compare commits
999 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76a6d8292c | ||
|
|
8f09c444b6 | ||
|
|
9032e6abb8 | ||
|
|
1c070d16a6 | ||
|
|
7837fcc657 | ||
|
|
f9690d40d3 | ||
|
|
5de6cd77dc | ||
|
|
aa5ab55b14 | ||
|
|
9195b18981 | ||
|
|
b812d6efb8 | ||
|
|
231a02eb10 | ||
|
|
6736806361 | ||
|
|
8e17756bf8 | ||
|
|
0b133fe55e | ||
|
|
d01266c642 | ||
|
|
fe3f9c86d5 | ||
|
|
14bf3645d6 | ||
|
|
0f4a7b2405 | ||
|
|
681e49a4cc | ||
|
|
6e9c97fbff | ||
|
|
370070f489 | ||
|
|
7168f4014d | ||
|
|
f0912feefb | ||
|
|
cf36972969 | ||
|
|
40862f26e6 | ||
|
|
3cb34ad827 | ||
|
|
af338d447b | ||
|
|
6fad06f659 | ||
|
|
1d51d8ff27 | ||
|
|
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 | ||
|
|
bf76da3222 | ||
|
|
f171b7de96 | ||
|
|
c0cbf00199 | ||
|
|
0cd6e59fb9 | ||
|
|
11a8adc71c | ||
|
|
b9c7fd879f | ||
|
|
2fc4c7ea33 | ||
|
|
c5003665c3 | ||
|
|
538028c150 | ||
|
|
fb8d187f8d | ||
|
|
1a11301e1a | ||
|
|
4c6cdd5c23 | ||
|
|
30a64b0dd3 | ||
|
|
04de492019 | ||
|
|
07890df6cb | ||
|
|
2f23cfdf1c | ||
|
|
1832946d41 | ||
|
|
6ec8745d2e | ||
|
|
b6bbfe063b | ||
|
|
48182edbd5 | ||
|
|
94a00cb6d6 | ||
|
|
fc24361aa6 | ||
|
|
cec833afc6 | ||
|
|
f1cddba938 | ||
|
|
a0acdfdcb9 | ||
|
|
6637f294df | ||
|
|
ad8a444105 | ||
|
|
877cfa0071 | ||
|
|
e6f0a780b7 | ||
|
|
dd9de2efa9 | ||
|
|
f6b0811f78 | ||
|
|
eba9d854a9 | ||
|
|
437cf9bab0 | ||
|
|
fdaeccf1e5 | ||
|
|
7723e46c26 | ||
|
|
dce355cce6 | ||
|
|
213e7b7093 | ||
|
|
fe7d8f93a1 | ||
|
|
9e2f4216f9 | ||
|
|
a48f7b2222 | ||
|
|
0b85d8a9bc | ||
|
|
58d6938065 | ||
|
|
a536a2b822 | ||
|
|
9ffad1005e | ||
|
|
65edddd62e | ||
|
|
a7cdcd8b3a | ||
|
|
3d6b85ed20 | ||
|
|
7abea2020c | ||
|
|
e16c34f0e3 | ||
|
|
4bfda6a145 | ||
|
|
98470e8551 | ||
|
|
df558ab8d6 | ||
|
|
c07372b58c | ||
|
|
00f59b95ae | ||
|
|
8915a7c2cd | ||
|
|
8595964ab8 | ||
|
|
922dae8546 | ||
|
|
69b3e23400 | ||
|
|
55325773dc | ||
|
|
b84c915b23 | ||
|
|
cfb390936a | ||
|
|
c5f344f333 | ||
|
|
ba4b496306 | ||
|
|
c48554589c | ||
|
|
da0851e21d | ||
|
|
d2d05abac0 | ||
|
|
de3e0423cc | ||
|
|
8d742d7938 | ||
|
|
682fd550fa | ||
|
|
abcf836a0c | ||
|
|
b123fb2cc7 | ||
|
|
0da3621a68 | ||
|
|
8ed452d9ea | ||
|
|
f380d44697 | ||
|
|
86d377a2f0 | ||
|
|
508a6d99f5 | ||
|
|
63e42047e3 | ||
|
|
769be46bf9 | ||
|
|
13829de0d9 | ||
|
|
ad7f570be5 | ||
|
|
9ba4f966db | ||
|
|
ae8d2ac2e1 | ||
|
|
93beb068a3 | ||
|
|
e88d260acd | ||
|
|
8121238872 | ||
|
|
161e377ec1 | ||
|
|
ad4bd800aa | ||
|
|
2fba6f65f4 | ||
|
|
a754ab4f10 | ||
|
|
86cfc468bd | ||
|
|
7df0c1607e | ||
|
|
6acd36e374 | ||
|
|
af51eecbac | ||
|
|
3a23dc8b04 | ||
|
|
ba13e44720 | ||
|
|
e80420f6db | ||
|
|
21ddcfc866 | ||
|
|
20f82cb22c | ||
|
|
7ef75bab23 | ||
|
|
7224e03590 | ||
|
|
cf4f2991a5 | ||
|
|
9eb3c23494 | ||
|
|
c80d8898cc | ||
|
|
bc74dd88e0 | ||
|
|
da87c461ef | ||
|
|
bf2e694f2c | ||
|
|
e5150487c4 | ||
|
|
9ff6353b88 | ||
|
|
926fd8abf4 | ||
|
|
211a7a4cfe | ||
|
|
c1835cd9cc | ||
|
|
5700044393 | ||
|
|
36fbd3d018 | ||
|
|
d1178390a9 | ||
|
|
8182825e92 | ||
|
|
2392006246 | ||
|
|
a6e78cd5dc | ||
|
|
8752790352 | ||
|
|
3976c79e12 | ||
|
|
5c1cf7f4ac | ||
|
|
7e90b8b7be | ||
|
|
912321a030 | ||
|
|
ab0a905499 | ||
|
|
3c6b3c02df | ||
|
|
bcb2e91d97 | ||
|
|
766ef94605 | ||
|
|
e3f016e262 | ||
|
|
65833f1ae0 | ||
|
|
2602cd9ab2 | ||
|
|
8333f3d9de | ||
|
|
dee1d9ba74 | ||
|
|
ed2e0c5080 | ||
|
|
7db810d7d0 | ||
|
|
8dae4e5038 | ||
|
|
b9b28edefe | ||
|
|
58120f435f | ||
|
|
027b8e52da | ||
|
|
aad510a9d5 | ||
|
|
9852a805a1 | ||
|
|
b2cabf0122 | ||
|
|
521ce15f86 | ||
|
|
fb97c11140 | ||
|
|
1c5c62e311 | ||
|
|
77148f7f97 | ||
|
|
a329d2f2bc | ||
|
|
39e9e4446b | ||
|
|
b32de54944 | ||
|
|
071b874e1b | ||
|
|
9ba65d3323 | ||
|
|
890a851bbf | ||
|
|
5f6ca23da4 | ||
|
|
58df1c06ee | ||
|
|
95f8599dc2 | ||
|
|
8a11242d7f | ||
|
|
948513ef5f | ||
|
|
c497a35d21 | ||
|
|
e0a539bc64 | ||
|
|
44b8395ead | ||
|
|
1bc8878490 | ||
|
|
ded2ac493d | ||
|
|
57b3319ac0 | ||
|
|
eba7ba25b8 | ||
|
|
df774892c8 | ||
|
|
f3b4ce6b67 | ||
|
|
bb8545b3e1 | ||
|
|
600149fc2b | ||
|
|
f4de3c8748 | ||
|
|
6e7e04839f | ||
|
|
f62dcc12a0 | ||
|
|
bef591c2e6 | ||
|
|
5907296d36 | ||
|
|
aa2a7d12be | ||
|
|
33fee5dcc5 | ||
|
|
e9ae50be0c | ||
|
|
5886c0fd5e | ||
|
|
ed146fcf07 | ||
|
|
35538e6f77 | ||
|
|
ea924f3bbf | ||
|
|
7bc15a2fc9 | ||
|
|
2bf7db92ee | ||
|
|
95260f56ba | ||
|
|
c5ace0376a | ||
|
|
7ee09388fa | ||
|
|
a15b0ef060 | ||
|
|
57cfd9a315 | ||
|
|
5fb4149c32 | ||
|
|
03d97ba617 | ||
|
|
5205f5f4b4 | ||
|
|
6eda0f4d00 | ||
|
|
9e640cac6b | ||
|
|
061521f87f | ||
|
|
b15eb278e1 | ||
|
|
142ac8eb96 | ||
|
|
88705bb6e9 | ||
|
|
60d4fcfe7e | ||
|
|
038d19ec98 | ||
|
|
e1b98768c7 | ||
|
|
b82af2b849 | ||
|
|
703591d76a | ||
|
|
7142688a77 | ||
|
|
a12622b3d8 | ||
|
|
9248ab4dfd | ||
|
|
5a8c6440f0 | ||
|
|
74b694a4dd | ||
|
|
896b52d5fb | ||
|
|
1429fea27a | ||
|
|
3218563f32 | ||
|
|
d412edbbe1 | ||
|
|
968159a85d | ||
|
|
18a3741fc2 | ||
|
|
f1be3e6bb0 | ||
|
|
b717a02394 | ||
|
|
d68143e63d | ||
|
|
0d306b8b1c | ||
|
|
a655863855 | ||
|
|
58264c80dd | ||
|
|
6f9f1aec65 | ||
|
|
97b1ee5b02 | ||
|
|
fe033cd0b3 | ||
|
|
afbd07c62a | ||
|
|
9b15996545 | ||
|
|
1dbbd7241d | ||
|
|
6c0ef48d45 | ||
|
|
8b57f88ca3 | ||
|
|
3e9fdc777e | ||
|
|
a8ca88797a | ||
|
|
71540b5dc0 | ||
|
|
b5a145d7b3 | ||
|
|
21d6a0a2dd | ||
|
|
80cc7340ac | ||
|
|
45b272ee2f | ||
|
|
f765664580 | ||
|
|
10b44f036d | ||
|
|
1bf4ee3a3c | ||
|
|
5d82ffa503 | ||
|
|
5dc3fd2ec0 | ||
|
|
4562fdda92 | ||
|
|
18258b9b0d | ||
|
|
92e0f242c7 | ||
|
|
428fa9404c | ||
|
|
3cccc480fb | ||
|
|
acb94216c8 | ||
|
|
5fa97841b2 | ||
|
|
4ad66bf7b9 | ||
|
|
64860ed5e5 | ||
|
|
b17faf6e1e | ||
|
|
0ea73bd527 | ||
|
|
b2f0820560 | ||
|
|
7ad5d42982 | ||
|
|
3912734498 | ||
|
|
0fa3f9a057 | ||
|
|
0fbabdcf25 | ||
|
|
67b7ae98a6 | ||
|
|
0f703c95dd | ||
|
|
c34b3f41bd | ||
|
|
e003b17280 | ||
|
|
e003d58c60 | ||
|
|
0546d06c0a | ||
|
|
5337111990 | ||
|
|
bb06f8eb0c | ||
|
|
23e3a1c269 | ||
|
|
e47740e02e | ||
|
|
d9ff0035f5 | ||
|
|
7a7f3be0d2 | ||
|
|
91e45fbe95 | ||
|
|
7d7e9da28c | ||
|
|
24a9739604 | ||
|
|
4fb9687782 | ||
|
|
95ffc21b60 | ||
|
|
f3c5e55b26 | ||
|
|
40183c6a5c | ||
|
|
457c59e38a | ||
|
|
aa93a3f2e2 | ||
|
|
8b9abcb6cc | ||
|
|
1ecc1908c7 | ||
|
|
6a2c7b467d | ||
|
|
0acef57865 | ||
|
|
43046ee649 | ||
|
|
a15fda0c08 | ||
|
|
e5988764ce | ||
|
|
9c9d9b5a8d | ||
|
|
44dc564d85 | ||
|
|
83e367afab | ||
|
|
8b7e7c2669 | ||
|
|
53474021b7 | ||
|
|
da1ed1b5b2 | ||
|
|
e08d661600 | ||
|
|
1aa1bc7a26 | ||
|
|
47634e942e | ||
|
|
15466cbf1a | ||
|
|
2a749db427 | ||
|
|
ecccce86e4 | ||
|
|
bf3f64bea4 | ||
|
|
2f2d6b8535 | ||
|
|
d68c884649 | ||
|
|
8b556de03b | ||
|
|
7229af53c3 | ||
|
|
81b3034c2f | ||
|
|
f0419396b5 | ||
|
|
6b9c2754e8 | ||
|
|
8edb131f8b | ||
|
|
d6f6520a79 | ||
|
|
cc2bb4d719 | ||
|
|
3859f1c9ae | ||
|
|
5f8d774e19 | ||
|
|
538a3e855c | ||
|
|
03f2ef1e2b | ||
|
|
237d0746cf | ||
|
|
33b6c58087 | ||
|
|
e96b023d04 | ||
|
|
7ac1d4621b | ||
|
|
a2d7cbe8fe | ||
|
|
c74ed29739 | ||
|
|
6c8501f122 | ||
|
|
941e945f74 | ||
|
|
f2844d59e4 | ||
|
|
047ff187f6 | ||
|
|
1136c40811 | ||
|
|
5a78dc864f | ||
|
|
15c98c3048 | ||
|
|
0a5b005ce5 | ||
|
|
4d64e64127 | ||
|
|
5470c70cd0 | ||
|
|
47959ee395 | ||
|
|
7c34c178cd | ||
|
|
ac7cb41483 | ||
|
|
0ab388b88e | ||
|
|
54448902f1 | ||
|
|
12107a02fd | ||
|
|
eace06efdc | ||
|
|
ee0afa1eec | ||
|
|
83cdd0dafe | ||
|
|
5be025f1d1 | ||
|
|
c651842ea1 | ||
|
|
423abe6788 | ||
|
|
4003c38fd1 | ||
|
|
3e0c322fd4 | ||
|
|
7fcdd4abdd | ||
|
|
3f3280b2d4 | ||
|
|
aae2399631 | ||
|
|
03bd2b6803 | ||
|
|
48754fd999 | ||
|
|
c496ebdef9 | ||
|
|
c009c40606 | ||
|
|
b29456c8e5 | ||
|
|
38266bf2ff | ||
|
|
c2e51f8948 | ||
|
|
c54a57838e | ||
|
|
64f040bddd | ||
|
|
1a099ea2f2 | ||
|
|
13c45807ef | ||
|
|
dfbb9d5fff | ||
|
|
a7fe369ea0 | ||
|
|
b62e6c5a69 | ||
|
|
92e29a6ad7 | ||
|
|
eeb9c69aa3 | ||
|
|
b7662ed5a1 | ||
|
|
9d6296f610 | ||
|
|
fd2a1320e0 | ||
|
|
8a8a6a4a82 | ||
|
|
8cdc14eec1 | ||
|
|
a1200b2fb5 | ||
|
|
c88c29eddc | ||
|
|
2845c4de98 | ||
|
|
bfa9cd15b7 | ||
|
|
659e2b414d | ||
|
|
7bcb58e3db | ||
|
|
2d7d7776a6 | ||
|
|
c5f429521c | ||
|
|
426d8636bc | ||
|
|
a265c7096e | ||
|
|
1c9953b1ba | ||
|
|
601cc21a44 | ||
|
|
102c42dfe4 | ||
|
|
4953727aa7 | ||
|
|
e6af874b47 | ||
|
|
801b4eef4c | ||
|
|
fe5c20a04e | ||
|
|
246fd05fae | ||
|
|
a09b298127 | ||
|
|
f89f40778f | ||
|
|
3d0c8d8d45 | ||
|
|
0e5e8bf14e | ||
|
|
ce34d329d3 | ||
|
|
eaf4a5805c | ||
|
|
8420e565d4 | ||
|
|
00df10c29a | ||
|
|
1b68deb0f6 | ||
|
|
d1497c9ac8 | ||
|
|
03d4cbf6d5 | ||
|
|
718be831af | ||
|
|
9d5ec523be | ||
|
|
81c43b45fb | ||
|
|
146a491769 | ||
|
|
4c53388579 | ||
|
|
3403ddcc6e | ||
|
|
684b81d835 | ||
|
|
4f32da57fd | ||
|
|
97265e48b3 | ||
|
|
64797158e2 | ||
|
|
8359293dcd | ||
|
|
b2dc53d18b | ||
|
|
edf8dd2a12 | ||
|
|
5a777bd598 | ||
|
|
bd39e01ee1 | ||
|
|
e3ed29aab6 | ||
|
|
896ce9c0e2 | ||
|
|
82934132e9 | ||
|
|
a2012b70de | ||
|
|
bcfeba8a57 | ||
|
|
d3dfd9ce57 | ||
|
|
aa06d5d356 | ||
|
|
448c8a29e1 | ||
|
|
928b7120f4 | ||
|
|
a3deacd718 | ||
|
|
78959fffbd | ||
|
|
1788616e52 | ||
|
|
c61e6d0777 | ||
|
|
41d91d628a | ||
|
|
a3bc7620b1 | ||
|
|
8064c588dc | ||
|
|
564e983c68 | ||
|
|
e1da181740 | ||
|
|
c63209200e | ||
|
|
737808cf53 | ||
|
|
a197bb7736 | ||
|
|
f9dd967bc5 | ||
|
|
44e4d55a66 | ||
|
|
095c84ac16 | ||
|
|
e063eae727 | ||
|
|
f02c5b5c69 | ||
|
|
838f1d645c | ||
|
|
ce2c30c437 | ||
|
|
d56fae0a7b | ||
|
|
e45ef00bef | ||
|
|
e9f31f7394 | ||
|
|
7c10a98eb2 | ||
|
|
f260483101 | ||
|
|
389e6e5c9e | ||
|
|
1cfd5866be | ||
|
|
c7ceac7f41 | ||
|
|
cd6eca0424 | ||
|
|
8c6136fea0 | ||
|
|
9644444028 | ||
|
|
9c4154291d | ||
|
|
533f5f6da6 | ||
|
|
1b8de756cd | ||
|
|
650b415537 | ||
|
|
04b50329fc | ||
|
|
25aab8c55c | ||
|
|
ceda2e70c1 | ||
|
|
2908303d4b | ||
|
|
a9f69711c6 | ||
|
|
a8ab16a720 | ||
|
|
8091b6b508 | ||
|
|
a00ef0fc7e | ||
|
|
5ce6d615a4 | ||
|
|
e06b69cdac | ||
|
|
d261ae7883 | ||
|
|
6fa77a63d7 | ||
|
|
f76c1b32d6 | ||
|
|
0aede2ef63 | ||
|
|
1e3a2e0a27 | ||
|
|
1bdabf43db | ||
|
|
05e568feb0 | ||
|
|
81e2519436 | ||
|
|
ef623c9bb5 | ||
|
|
da581525a6 | ||
|
|
6ff7b6570c | ||
|
|
8b2081837e | ||
|
|
ce978b602a | ||
|
|
9b00f5d550 | ||
|
|
d98ec59c79 | ||
|
|
d79b55be5a | ||
|
|
1f9a402dcd | ||
|
|
f9bcc9418b | ||
|
|
08256a3502 | ||
|
|
9b255e643a | ||
|
|
ca1f918e9e | ||
|
|
bb3fe1cd48 | ||
|
|
5d7772ecb0 | ||
|
|
56ce618eca | ||
|
|
605c3f9be1 | ||
|
|
b0381c7542 | ||
|
|
2f0894c220 | ||
|
|
b328ed5fa9 | ||
|
|
7d72f1711f | ||
|
|
d139b4557f | ||
|
|
cd05e03d63 | ||
|
|
e25029939d | ||
|
|
53de27417d | ||
|
|
74d3374d5c | ||
|
|
3ae00bebe4 | ||
|
|
f9df72c4d7 | ||
|
|
d0fb4576a8 | ||
|
|
0e4b0b3540 | ||
|
|
df1105d0c6 | ||
|
|
44478c36a3 | ||
|
|
fa267274b0 | ||
|
|
0db272946a | ||
|
|
91015b6499 | ||
|
|
2979a36a7c | ||
|
|
72f6d6b7b9 | ||
|
|
d81a7bcedf | ||
|
|
8fbbe8b82b | ||
|
|
271f5f9c64 | ||
|
|
7c992ffd21 | ||
|
|
fc2af8ba87 | ||
|
|
c8a539a6cb | ||
|
|
b7cdaa662a | ||
|
|
0a25930020 | ||
|
|
8643f4015f | ||
|
|
1854711aff | ||
|
|
c905119d82 | ||
|
|
c581ca8339 | ||
|
|
ccf9d9214a | ||
|
|
d37c8b732f | ||
|
|
f707fc1cad | ||
|
|
b1c713de60 | ||
|
|
0f13965391 | ||
|
|
8642e2b721 | ||
|
|
441534853b | ||
|
|
82f42c8664 | ||
|
|
5cd318fa9a | ||
|
|
5506071e9a | ||
|
|
ced98f2da7 | ||
|
|
282ec65e8b | ||
|
|
8e06dc5ace | ||
|
|
bfd3e2c01b | ||
|
|
a1957f0923 | ||
|
|
11a02ba361 | ||
|
|
4643c19abc | ||
|
|
a3369df62f | ||
|
|
4297c42597 | ||
|
|
e06e7157ac | ||
|
|
22f9e6f4c0 | ||
|
|
4b7a9233e7 | ||
|
|
204839f702 | ||
|
|
d15e3109ee | ||
|
|
8b513ee8f8 | ||
|
|
2c1488e65a | ||
|
|
8ebe1cc2d8 | ||
|
|
b0d6c15e63 | ||
|
|
3a3c7a7968 | ||
|
|
783d7ae605 | ||
|
|
bbf7a6b2f8 | ||
|
|
0fe6e24554 | ||
|
|
4bbaf55586 | ||
|
|
cda765a02d | ||
|
|
36856b18db | ||
|
|
66f0a8f994 | ||
|
|
455231170f | ||
|
|
5faeb58ab0 | ||
|
|
056e4a88ff | ||
|
|
8fd944ccf7 | ||
|
|
86105a547c | ||
|
|
9806648c07 | ||
|
|
6186babdb3 | ||
|
|
f2ecefb54a | ||
|
|
43bd529b78 | ||
|
|
9c82b3d4ca | ||
|
|
b19e6a8e87 | ||
|
|
e3a2bd75f3 | ||
|
|
da39e1485f | ||
|
|
88cc53a4b0 | ||
|
|
245243c7e7 | ||
|
|
759ac0df3d | ||
|
|
db8d97b6de | ||
|
|
27d66e4b3e | ||
|
|
ca7854210d | ||
|
|
c009c993c3 | ||
|
|
00188f75ae | ||
|
|
4d086542aa | ||
|
|
1555883633 | ||
|
|
8f2c0acc7e | ||
|
|
0e30d15c01 | ||
|
|
da14390fe0 | ||
|
|
11c0cff4ef | ||
|
|
e322376996 | ||
|
|
4fbe45f30a | ||
|
|
2cd0f60c3c | ||
|
|
1b354be827 | ||
|
|
7db280ee64 | ||
|
|
192c06cadf | ||
|
|
ad7e7abda0 | ||
|
|
02ccb35e80 | ||
|
|
a8a29e17c5 | ||
|
|
75a6d850fc | ||
|
|
b0f5f92f1a | ||
|
|
eaddb6f0fa | ||
|
|
5cff98ea75 | ||
|
|
76127415a4 | ||
|
|
56936fe0e3 | ||
|
|
dfbbbeb1b4 | ||
|
|
7f3ffd935e | ||
|
|
29cf462d8f | ||
|
|
5e1693e1f7 | ||
|
|
45424ca226 | ||
|
|
d976abb5e0 | ||
|
|
92d302aed3 | ||
|
|
1e93ee5c34 | ||
|
|
1b6c502c7f | ||
|
|
4e4532c057 | ||
|
|
1e57ae5923 | ||
|
|
9055fc2129 | ||
|
|
b8fec94b0d | ||
|
|
2b6c88cd26 | ||
|
|
f6c0744d67 | ||
|
|
639b49fc5b | ||
|
|
c0252f7b13 | ||
|
|
a87d64372f | ||
|
|
02b19e63e8 | ||
|
|
dba16363b7 | ||
|
|
d20a2b3e44 | ||
|
|
677f5f8713 | ||
|
|
7da23a90d4 | ||
|
|
8dad2d32b6 | ||
|
|
d07a5f0df7 | ||
|
|
55a9e31932 | ||
|
|
e62be7e6b3 | ||
|
|
7f9ec724ae | ||
|
|
daaa3a8782 | ||
|
|
d1c62420bf | ||
|
|
1c10cfe4bc | ||
|
|
a4252d52ce | ||
|
|
1d7bc5fed7 | ||
|
|
763fdf3135 | ||
|
|
82314562e7 | ||
|
|
69e9bd81e9 | ||
|
|
26f927f798 | ||
|
|
2042dcf991 | ||
|
|
87ffe41d8c | ||
|
|
943a9374b4 | ||
|
|
8956ffef73 | ||
|
|
4383e7d807 | ||
|
|
863055768e | ||
|
|
2c1da9e146 | ||
|
|
845787ab7f | ||
|
|
1db948e9bb | ||
|
|
f0d00bcee5 | ||
|
|
1e9a9adbad | ||
|
|
d87c7c3b8c | ||
|
|
eb3c834609 | ||
|
|
e53c76081f | ||
|
|
134316328c | ||
|
|
4767561f02 | ||
|
|
2d6b31b606 | ||
|
|
a22f0a4e7b | ||
|
|
5a244aa12a | ||
|
|
69d28bec4d | ||
|
|
c859665c6b | ||
|
|
e7b19758f3 | ||
|
|
623c63baf6 | ||
|
|
a3ad7c6c2e | ||
|
|
afc9362ca5 | ||
|
|
f6b125e8c2 | ||
|
|
5df3c22be8 | ||
|
|
11a0df5443 | ||
|
|
e27a2a0d55 | ||
|
|
dc8abe60ee | ||
|
|
afe2ab37e4 | ||
|
|
f7bd99f965 | ||
|
|
f5238944b4 | ||
|
|
c7ae9c30c2 | ||
|
|
82f7a12a46 | ||
|
|
f494a8531b | ||
|
|
36ed0499db | ||
|
|
46cff2200d | ||
|
|
5ea6ad4a9e | ||
|
|
6cad4fae8e | ||
|
|
8df24c855b | ||
|
|
f25882c0e9 | ||
|
|
be6c769192 | ||
|
|
a4276444b5 | ||
|
|
0af27b8d8a | ||
|
|
542eb0e719 | ||
|
|
c658b39270 | ||
|
|
52ef3dfc7e | ||
|
|
57da407693 | ||
|
|
d2d6fc5883 | ||
|
|
6a7a6022d4 | ||
|
|
b53eafa615 | ||
|
|
c949214e99 | ||
|
|
887cf25b65 | ||
|
|
dd6142196f | ||
|
|
902c7244d1 | ||
|
|
4f11762c68 | ||
|
|
8a7f7c1ba0 | ||
|
|
af46f87eed | ||
|
|
fd749d1e0b | ||
|
|
5046f90dfa | ||
|
|
cf13e95610 | ||
|
|
5763609008 | ||
|
|
6d672ab09a | ||
|
|
ac68022233 | ||
|
|
c2b31f6b20 | ||
|
|
54b1d8c8de | ||
|
|
cd1ab696b2 | ||
|
|
d9d0640f6e | ||
|
|
e19046116a | ||
|
|
82a621ec08 | ||
|
|
ce560ebe9d | ||
|
|
f900a81ec9 | ||
|
|
2a620b178d | ||
|
|
5aaaad529b | ||
|
|
4e15ca6bbd | ||
|
|
8555a3a106 | ||
|
|
3a39ae6231 | ||
|
|
00e6b7cf2c | ||
|
|
ebd1e5421b | ||
|
|
2feb2df30d | ||
|
|
ba38dc738b | ||
|
|
9233edb6cd | ||
|
|
f697c56922 | ||
|
|
5ab6a3b431 | ||
|
|
25cd0b3612 | ||
|
|
6e1bf4652d | ||
|
|
3991c96c78 | ||
|
|
70008e67e4 | ||
|
|
0d1e6685f6 | ||
|
|
60d1d13c6a | ||
|
|
ade1e5f345 | ||
|
|
0a59ef4996 | ||
|
|
ace3e47d22 | ||
|
|
9865401636 | ||
|
|
1343768de1 | ||
|
|
743cfcbb3c | ||
|
|
44ee62e391 | ||
|
|
47445ebf5c | ||
|
|
6b0638da30 | ||
|
|
74a0585683 | ||
|
|
9aaebbcd2a | ||
|
|
2348680a2b | ||
|
|
59a8b8db35 | ||
|
|
2ba46a622b | ||
|
|
9013236718 | ||
|
|
eab96cc94b | ||
|
|
7f66e82f16 | ||
|
|
c74054d928 | ||
|
|
5428c9b9a8 | ||
|
|
db0bda2810 | ||
|
|
c4efad3ea7 | ||
|
|
62995b640d | ||
|
|
9a476d310a | ||
|
|
bc09eb64a6 | ||
|
|
8e4873d53e | ||
|
|
5874bd3de6 | ||
|
|
a1dc2270a2 | ||
|
|
1c2f553a7c | ||
|
|
049ddff6fe | ||
|
|
9429d1639e | ||
|
|
af7ca4baf7 | ||
|
|
7906fdfc1d | ||
|
|
9911272898 | ||
|
|
8909ae041e | ||
|
|
c9a15772b0 | ||
|
|
91f3bd4056 | ||
|
|
2306081dab | ||
|
|
8963c62adb | ||
|
|
e4a11bd6d0 | ||
|
|
2f6e63771f | ||
|
|
cbd60c853e | ||
|
|
2d8091340f | ||
|
|
2025c16c82 | ||
|
|
811fb7f9b2 | ||
|
|
a2bd32e76c | ||
|
|
89c07f4e8a | ||
|
|
ac89069671 | ||
|
|
7cb420d8e6 | ||
|
|
d3919d441f | ||
|
|
4b5824babc | ||
|
|
fb87df14fd | ||
|
|
da9e4e929b | ||
|
|
10b23b15ae | ||
|
|
30fba39b35 | ||
|
|
5a75ff67c9 | ||
|
|
358828b617 | ||
|
|
e080c4a16a | ||
|
|
04b7e38baf | ||
|
|
7ee23fbe19 | ||
|
|
c49bdb4ebb | ||
|
|
0f7efed8d5 | ||
|
|
d07bc6dcf3 | ||
|
|
d607d46fa3 | ||
|
|
2225dd14aa | ||
|
|
f6c0e7bbbe | ||
|
|
c4675c5219 | ||
|
|
2d977a3c4d | ||
|
|
9405918258 | ||
|
|
a69d7dd4b5 | ||
|
|
428e6cb53f | ||
|
|
c9a2955d28 | ||
|
|
7aefcd3437 | ||
|
|
79f4f79c46 | ||
|
|
c11c275678 | ||
|
|
bbcd1d3a08 | ||
|
|
3342d5b931 | ||
|
|
f96ee44213 | ||
|
|
bc53fe0cd9 | ||
|
|
97a67b5d3e | ||
|
|
1ffa58be76 | ||
|
|
a5cf51c0b9 | ||
|
|
3d38cbf70f | ||
|
|
196a4e037c | ||
|
|
6a0760a2c5 | ||
|
|
b0819404c7 | ||
|
|
bfe495931f | ||
|
|
11bcdd810a | ||
|
|
228ebf436e | ||
|
|
d2b6624de4 | ||
|
|
0dd6d349b3 | ||
|
|
ecb0774b7b | ||
|
|
6ab32b351f | ||
|
|
e09d4a02a2 | ||
|
|
3de8b4371a | ||
|
|
396ab2bab5 | ||
|
|
305fb56b62 | ||
|
|
0f22f38f7e | ||
|
|
084b206ae6 | ||
|
|
0d3728efa4 | ||
|
|
2b067c5d00 | ||
|
|
21135407af | ||
|
|
c38a58fc98 | ||
|
|
20e4b1b011 | ||
|
|
9691469987 | ||
|
|
63114af08d | ||
|
|
e78ede45b6 | ||
|
|
751ff77b7c | ||
|
|
5a53c17e81 | ||
|
|
9c32c30daf | ||
|
|
baa0208fa9 | ||
|
|
2ec0cd13cd | ||
|
|
0d8f28a4a4 | ||
|
|
33dfbf0177 | ||
|
|
85c6b63c8f | ||
|
|
d19f336286 | ||
|
|
052eb8d330 | ||
|
|
3510d8c0bc | ||
|
|
20860877b8 | ||
|
|
bddec84f4e | ||
|
|
aba12ad5db | ||
|
|
6ea8d094b2 | ||
|
|
b5a3a3d019 | ||
|
|
5ecef5c90c | ||
|
|
fe9d9a5a5c | ||
|
|
e18cfe1d80 | ||
|
|
7eb45b2e19 | ||
|
|
70465ada4d | ||
|
|
8ddea153d3 | ||
|
|
8dca8fba6b | ||
|
|
f21ba7df64 | ||
|
|
ef917e42d1 | ||
|
|
865a1b9b2c | ||
|
|
de8a0836a8 | ||
|
|
b8272c55d7 | ||
|
|
8d93c13f9a | ||
|
|
8152b030bf | ||
|
|
9352ac767f | ||
|
|
5f20029ff7 | ||
|
|
dbd00117c8 | ||
|
|
2902a0fe26 | ||
|
|
7ba57634c1 | ||
|
|
211dde25d0 | ||
|
|
57ff59aef2 | ||
|
|
c39faba2b5 | ||
|
|
212bca2e1e | ||
|
|
f807c56e31 | ||
|
|
5510c25040 | ||
|
|
9d884d2d60 | ||
|
|
f26fa67374 | ||
|
|
ccb314e065 | ||
|
|
0517dcf0b7 | ||
|
|
b7f0665ce9 | ||
|
|
144628755d | ||
|
|
7c5bb2c6b6 | ||
|
|
afadb0fea1 | ||
|
|
b6c9c8a822 | ||
|
|
11f43ca65c | ||
|
|
8dce812a4d | ||
|
|
93047069b6 | ||
|
|
c4f1990aff | ||
|
|
6e2816f08b | ||
|
|
227268024d | ||
|
|
8d5891a382 | ||
|
|
7700fca501 | ||
|
|
527c542d6d | ||
|
|
8fbae5e467 | ||
|
|
4d2a5efd12 | ||
|
|
5ffa14190a | ||
|
|
7820145cbe | ||
|
|
b7a6c563ac | ||
|
|
52221488d0 | ||
|
|
4a1acb1446 | ||
|
|
dc90211222 | ||
|
|
378c9f321d | ||
|
|
e11bcc2848 | ||
|
|
3f10430150 | ||
|
|
e8b72b54b3 | ||
|
|
d902dda4b1 | ||
|
|
2538480b95 | ||
|
|
b9b8c93cb9 | ||
|
|
68b7b35425 | ||
|
|
163c5feccc | ||
|
|
0488f0536e | ||
|
|
09e90ec25b | ||
|
|
d97a11a54f | ||
|
|
4a779dfe3c | ||
|
|
61e09d545f | ||
|
|
3a68d7dabc | ||
|
|
22d318f201 | ||
|
|
afa2cea678 | ||
|
|
6dce45505c | ||
|
|
014732788c | ||
|
|
0e75d838ab | ||
|
|
8383da8a50 | ||
|
|
199d173816 | ||
|
|
f2829441f0 | ||
|
|
21137bd84a | ||
|
|
a05e51a577 | ||
|
|
09a094629c | ||
|
|
90de0fbf68 | ||
|
|
c9cdd5109b | ||
|
|
0e207dc5d2 | ||
|
|
876a5a98f4 | ||
|
|
8e82350d66 | ||
|
|
de75ed1551 | ||
|
|
87266104a3 | ||
|
|
fed8140404 | ||
|
|
e4d83e91bb | ||
|
|
a3153d893a | ||
|
|
be219449f9 | ||
|
|
06d193f0d9 | ||
|
|
4f413615d9 | ||
|
|
2a79b833fb | ||
|
|
52e3d4b37b | ||
|
|
d9b393a308 | ||
|
|
9a3d72c6a2 | ||
|
|
5b9b1cdd44 |
39
.agents/workflows/deploy-vps-akamai.md
Normal file
39
.agents/workflows/deploy-vps-akamai.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35)
|
||||
---
|
||||
|
||||
# Deploy to Akamai VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Akamai VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Akamai VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
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'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
```
|
||||
49
.agents/workflows/deploy-vps-both.md
Normal file
49
.agents/workflows/deploy-vps-both.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to BOTH the Akamai VPS and the Local VPS
|
||||
---
|
||||
|
||||
# Deploy to VPS (Both) Workflow
|
||||
|
||||
Deploy OmniRoute to the production VPSs using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to both VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
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'"
|
||||
```
|
||||
|
||||
```bash
|
||||
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'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
39
.agents/workflows/deploy-vps-local.md
Normal file
39
.agents/workflows/deploy-vps-local.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
|
||||
---
|
||||
|
||||
# Deploy to Local VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Local VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
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'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
256
.agents/workflows/generate-release.md
Normal file
256
.agents/workflows/generate-release.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
description: Create a new release, bump version up to 1.x.10 threshold, update changelog, and manage Pull Requests
|
||||
---
|
||||
|
||||
# Generate Release Workflow
|
||||
|
||||
Bump version, finalize CHANGELOG, commit, open a **PR to main** and wait for user confirmation before tagging, publishing, and deploying.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (2.x.y → 2.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 `2.(x+1).0` — e.g. `2.1.10` → `2.2.0`.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Two-Phase Flow
|
||||
|
||||
```
|
||||
Phase 1 (automated): bump → docs → i18n → commit → push → open PR
|
||||
↕ 🛑 STOP: Notify user, wait for PR confirmation
|
||||
Phase 2 (post-merge): tag → publish → GitHub release → Docker → deploy
|
||||
```
|
||||
|
||||
**NEVER push directly to main or create tags before the user confirms the PR.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Merge
|
||||
|
||||
### 1. Create release branch
|
||||
|
||||
```bash
|
||||
git checkout -b release/v2.x.y
|
||||
```
|
||||
|
||||
### 2. Determine new version
|
||||
|
||||
Check current version in `package.json` and increment the **patch** number only:
|
||||
|
||||
```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`)
|
||||
|
||||
> **⚠️ ATOMIC COMMIT RULE — Version bump MUST happen before committing feature files.**
|
||||
>
|
||||
> **CORRECT order:**
|
||||
>
|
||||
> 1. `npm version patch --no-git-tag-version` ← bump first
|
||||
> 2. implement features / fix bugs
|
||||
> 3. `git add -A && git commit -m "chore(release): v2.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **OR if features are already staged:**
|
||||
>
|
||||
> 1. implement features (do NOT commit yet)
|
||||
> 2. `npm version patch --no-git-tag-version` ← bump before committing
|
||||
> 3. `git add -A && git commit -m "chore(release): v2.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **NEVER do this (creates version mismatch in git history):**
|
||||
>
|
||||
> - ~~commit features → then bump version → commit package.json separately~~
|
||||
>
|
||||
> This ensures that `git show v2.x.y` always contains both code changes and the version bump together.
|
||||
> The GitHub release tag will point to a commit that includes ALL changes for that version.
|
||||
|
||||
### 3. Regenerate lock file (REQUIRED after version bump)
|
||||
|
||||
**Mandatory** — skipping causes `@swc/helpers` lock mismatch and CI failures:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4. Finalize CHANGELOG.md
|
||||
|
||||
Replace `[Unreleased]` header with the new version and date.
|
||||
Keep an empty `## [Unreleased]` section above it.
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [2.x.y] — YYYY-MM-DD
|
||||
```
|
||||
|
||||
### 5. Update openapi.yaml version ⚠️ MANDATORY
|
||||
|
||||
> **CI will fail** if `docs/openapi.yaml` version ≠ `package.json` version (`check:docs-sync` enforces this).
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version") && sed -i "s/ version: .*/ version: $VERSION/" docs/openapi.yaml && echo "✓ openapi.yaml → $VERSION"
|
||||
```
|
||||
|
||||
### 6. Update README.md and i18n docs
|
||||
|
||||
Run `/update-docs` workflow steps to:
|
||||
|
||||
- Update feature table rows in `README.md`
|
||||
- Sync changes to all 29 language `docs/i18n/*/README.md` files
|
||||
- Update `docs/FEATURES.md` if Settings section changed
|
||||
|
||||
### 7. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
All tests must pass before creating the PR.
|
||||
|
||||
### 8. Stage, commit, and push
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore(release): v2.x.y — summary of changes"
|
||||
git push origin release/v2.x.y
|
||||
```
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
```bash
|
||||
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."
|
||||
```
|
||||
|
||||
### 10. 🛑 STOP — Notify User & Await PR Confirmation
|
||||
|
||||
**This is a mandatory stop point.** Use `notify_user` with `BlockedOnUser: true`:
|
||||
|
||||
Inform the user:
|
||||
|
||||
- PR URL
|
||||
- Summary of changes
|
||||
- Test results
|
||||
- List of files changed
|
||||
|
||||
**DO NOT proceed to Phase 2 until the user confirms the PR looks good and merges it.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Post-Merge (only after user confirms)
|
||||
|
||||
> Run these steps only AFTER the user has merged the PR.
|
||||
|
||||
### 11. Pull main and create tag
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git tag -a v2.x.y -m "Release v2.x.y"
|
||||
```
|
||||
|
||||
### 12. Push tag to GitHub
|
||||
|
||||
```bash
|
||||
git push origin --tags
|
||||
```
|
||||
|
||||
### 13. Create GitHub release
|
||||
|
||||
```bash
|
||||
gh release create v2.x.y --title "v2.x.y — summary" --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**:
|
||||
|
||||
```bash
|
||||
# Verify the Docker workflow triggered
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 3
|
||||
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
gh workflow run docker-publish.yml --repo diegosouzapw/OmniRoute --ref v2.x.y
|
||||
```
|
||||
|
||||
### 15. Deploy to BOTH VPS environments (MANDATORY)
|
||||
|
||||
> Always deploy to **both** environments after every release.
|
||||
> See `/deploy-vps` workflow for detailed steps.
|
||||
|
||||
```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"
|
||||
|
||||
# Verify both
|
||||
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
|
||||
curl -s -o /dev/null -w "AKAMAI: HTTP %{http_code}\n" http://69.164.221.35:20128/
|
||||
```
|
||||
|
||||
### 16. Clean up release branch
|
||||
|
||||
```bash
|
||||
git branch -d release/v2.x.y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Always run `/update-docs` BEFORE this workflow (ensures CHANGELOG and README are current)
|
||||
- The `prepublishOnly` script runs `npm run build:cli` automatically during `npm publish`
|
||||
- After npm publish, verify with `npm info omniroute version`
|
||||
- Lock file sync errors are caused by skipping `npm install` after version bump
|
||||
- Use `gh auth switch -u diegosouzapw` if git push fails with wrong account
|
||||
|
||||
## Known CI Pitfalls
|
||||
|
||||
| CI failure | Cause | Fix |
|
||||
| ------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `[docs-sync] FAIL - OpenAPI version differs from package.json` | Skipped step 5 — `docs/openapi.yaml` version not updated | Run step 5 (`sed -i ...`) and commit |
|
||||
| `[docs-sync] FAIL - CHANGELOG.md first section must be "## [Unreleased]"` | `## [Unreleased]` missing or not at top of CHANGELOG | Add `## [Unreleased]\n\n---\n` before the first versioned `## [x.y.z]` |
|
||||
| Electron Linux `.deb` build fails (`FpmTarget` error) | `fpm` Ruby gem not installed on `ubuntu-latest` runner | Already fixed in `electron-release.yml` (`gem install fpm` step) |
|
||||
| Docker Hub `502 error writing layer blob` | Transient Docker Hub network error during ARM64 push | Re-run the Docker publish workflow; no code change needed |
|
||||
50
.agents/workflows/issue-triage.md
Normal file
50
.agents/workflows/issue-triage.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
description: How to respond to GitHub issues with insufficient information
|
||||
---
|
||||
|
||||
# Issue Triage Workflow
|
||||
|
||||
Respond to GitHub issues that need more information before they can be investigated.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify issues needing triage
|
||||
|
||||
```bash
|
||||
gh issue list --state open --limit 20
|
||||
```
|
||||
|
||||
### 2. Evaluate each issue
|
||||
|
||||
Check if the issue has:
|
||||
|
||||
- Clear reproduction steps
|
||||
- Environment details (OS, Node.js version, OmniRoute version)
|
||||
- Error logs/screenshots
|
||||
- Expected vs actual behavior
|
||||
|
||||
### 3. Respond with triage template
|
||||
|
||||
For issues missing information:
|
||||
|
||||
```markdown
|
||||
Thank you for reporting this issue! To help us investigate, please provide:
|
||||
|
||||
1. **OmniRoute version**: (`omniroute --version`)
|
||||
2. **Node.js version**: (`node --version`)
|
||||
3. **Operating system**: (e.g., Ubuntu 24.04, macOS 15, Windows 11)
|
||||
4. **Installation method**: (npm, Docker, source)
|
||||
5. **Steps to reproduce**: (exact commands/actions that trigger the issue)
|
||||
6. **Error logs**: (paste relevant logs from the console)
|
||||
7. **Expected behavior**: (what should happen)
|
||||
|
||||
This will help us debug and resolve your issue faster. 🙏
|
||||
```
|
||||
|
||||
### 4. Label the issue
|
||||
|
||||
Add appropriate labels: `needs-info`, `bug`, `enhancement`, `question`, etc.
|
||||
|
||||
```bash
|
||||
gh issue edit <NUMBER> --add-label "needs-info"
|
||||
```
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description: Fetch all open GitHub issues, analyze bugs, resolve what's possible, triage the rest, then commit and release
|
||||
description: Fetch all open GitHub issues, analyze bugs, resolve what's possible, triage the rest, wait for user validation, then commit and release
|
||||
---
|
||||
|
||||
# /resolve-issues — Automated Issue Resolution Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open issues from the project's GitHub repository, classifies them, analyzes bugs, resolves what can be fixed, triages issues with insufficient information, and generates a release with all fixes.
|
||||
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.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -21,8 +21,8 @@ This workflow fetches all open issues from the project's GitHub repository, clas
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 100 --json number,title,labels,body,comments,createdAt,author`
|
||||
- Parse the JSON output to get a list of all open issues
|
||||
- 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)
|
||||
|
||||
### 3. Classify Each Issue
|
||||
@@ -60,41 +60,61 @@ Call the `/issue-triage` workflow (located at `~/.gemini/antigravity/global_work
|
||||
|
||||
Proceed with resolution:
|
||||
|
||||
1. **Research** — Search the codebase for files related to the issue
|
||||
2. **Root Cause** — Identify the root cause by reading the relevant source files
|
||||
3. **Implement Fix** — Apply the fix following existing code patterns and conventions
|
||||
4. **Test** — Build the project and run tests to verify the fix
|
||||
5. **Commit** — Commit with message format: `fix: <description> (#<issue_number>)`
|
||||
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>)`
|
||||
|
||||
### 5. Commit All Fixes
|
||||
### 5. Generate Report & Wait for Validation
|
||||
|
||||
After processing all issues:
|
||||
Present a summary report to the user via `notify_user` with `BlockedOnUser: true`:
|
||||
|
||||
- Ensure all fixes are committed with proper issue references
|
||||
- Each fix should be its own commit for clean git history
|
||||
| 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 |
|
||||
|
||||
### 6. Close Resolved Issues
|
||||
> **⚠️ 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.
|
||||
|
||||
For each successfully fixed issue:
|
||||
// turbo
|
||||
- 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
|
||||
|
||||
- Close with a comment: `gh issue close <NUMBER> --repo <owner>/<repo> --comment "Fixed in <commit_hash>. The fix will be included in the next release."`
|
||||
### 6. Commit & Push Fix Branch (only after user approval)
|
||||
|
||||
### 7. Generate Report
|
||||
After the user validates:
|
||||
|
||||
Present a summary report to the user via `notify_user`:
|
||||
- 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`
|
||||
|
||||
| Issue | Title | Status | Action |
|
||||
| ----- | ----- | ------------- | --------------------------- |
|
||||
| #N | Title | ✅ Fixed | Commit hash |
|
||||
| #N | Title | ❓ Needs Info | Triage comment posted |
|
||||
| #N | Title | ⏭️ Skipped | Feature request / not a bug |
|
||||
### 7. 🛑 WAIT — Notify User & Await PR Verification
|
||||
|
||||
### 8. Update Docs & Release
|
||||
**This is a mandatory stop point.** Use `notify_user` with `BlockedOnUser: true`:
|
||||
|
||||
If any fixes were committed:
|
||||
- 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**
|
||||
|
||||
1. Run the `/update-docs` workflow (at `~/.gemini/antigravity/global_workflows/update-docs.md`) to update CHANGELOG and README
|
||||
2. Run the `/generate-release` workflow (at `.agents/workflows/generate-release.md`) to bump version, tag, and publish
|
||||
Wait for the user to respond:
|
||||
|
||||
- **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.
|
||||
|
||||
118
.agents/workflows/review-discussions.md
Normal file
118
.agents/workflows/review-discussions.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
description: Read all open GitHub Discussions, summarize them, respond to pending ones, and create issues from actionable feature requests
|
||||
---
|
||||
|
||||
# /review-discussions — GitHub Discussions Review & Response Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow reads all open GitHub Discussions, generates a categorized summary, identifies which ones need a response, drafts and posts replies, and optionally creates issues from actionable feature requests. It follows the same flow used for Issues but adapted for the Discussions forum.
|
||||
|
||||
// turbo-all
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- 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 Discussions
|
||||
|
||||
- Use `read_url_content` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Parse the discussion list to get all discussion titles, IDs, authors, categories, and dates
|
||||
- For each discussion, fetch the individual page to read the full content and all comments/replies
|
||||
|
||||
### 3. Summarize All Discussions
|
||||
|
||||
For each discussion, extract:
|
||||
|
||||
- **Title** and **#Number**
|
||||
- **Author** (GitHub username)
|
||||
- **Category** (Announcements, General, Ideas, Q&A, Show and tell)
|
||||
- **Date** created
|
||||
- **Summary** of the original post (1-2 sentences)
|
||||
- **Comments count** and key participants
|
||||
- **Your previous response** (if any)
|
||||
- **Pending action** — whether a response or follow-up is needed
|
||||
|
||||
### 4. Present Summary Report to User
|
||||
|
||||
Present the full summary to the user organized by category, using a table:
|
||||
|
||||
| # | Category | Title | Author | Date | Status |
|
||||
| --- | -------- | ----- | ------ | ------ | ----------------- |
|
||||
| #N | Ideas | Title | @user | Mar 23 | ⚠️ Needs response |
|
||||
| #N | Q&A | Title | @user | Mar 9 | ✅ Answered |
|
||||
| #N | General | Title | @user | Mar 19 | ⚠️ Needs response |
|
||||
|
||||
Highlight:
|
||||
|
||||
- **⚠️ Needs response** — No reply from maintainer, or a follow-up comment was left unanswered
|
||||
- **✅ Answered** — Maintainer already responded
|
||||
- **🐛 Bug reported** — A bug was mentioned that needs tracking
|
||||
- **💡 Actionable** — Contains a concrete feature request that could become an issue
|
||||
|
||||
### 5. Draft & Post Responses
|
||||
|
||||
For each discussion that needs a response, draft a reply following these guidelines:
|
||||
|
||||
#### Response Style
|
||||
|
||||
- **Friendly and professional** — Start with "Hey @username!"
|
||||
- **Acknowledge the contribution** — Thank the user for their input
|
||||
- **Be specific** — Reference existing features, settings, or dashboard pages if the feature already exists
|
||||
- **Provide workarounds** — If the request isn't implemented yet, suggest current alternatives
|
||||
- **Commit to action** — If the request is valid, state that you'll open an issue or add it to the roadmap
|
||||
- **Keep it concise** — 3-5 paragraphs max
|
||||
|
||||
#### Posting via Browser
|
||||
|
||||
- Use `browser_subagent` to navigate to each discussion and post the comment
|
||||
- **IMPORTANT**: When typing text in GitHub comment boxes via the browser, use only plain ASCII characters:
|
||||
- Use regular hyphens `-` instead of em-dashes
|
||||
- Use `->` instead of arrow symbols
|
||||
- Do NOT use emoji Unicode characters (the browser keyboard may fail on them)
|
||||
- Use `**bold**` and `\`code\`` markdown formatting
|
||||
- Click the green "Comment" button (or "Reply" for threaded replies) after typing
|
||||
- Verify the comment was posted by checking the page shows the new comment
|
||||
|
||||
### 6. Create Issues from Actionable Feature Requests
|
||||
|
||||
For discussions that contain concrete, actionable feature requests:
|
||||
|
||||
1. Ask the user which ones should become issues
|
||||
2. For each approved request, create a GitHub issue via `browser_subagent`:
|
||||
- Navigate to `https://github.com/<owner>/<repo>/issues/new`
|
||||
- **Title**: `<Feature Name> - <Short description>`
|
||||
- **Body** should include:
|
||||
- `## Feature Request` header
|
||||
- `**Source:** Discussion #N by @author`
|
||||
- `## Problem` — What limitation the user hit
|
||||
- `## Proposed Solution` — How it could work
|
||||
- `### Implementation Ideas` — Technical approach
|
||||
- `### Current Workarounds` — What users can do today
|
||||
- `## Additional Context` — Links to related issues/discussions
|
||||
- Add `enhancement` label
|
||||
- Click "Submit new issue" / "Create"
|
||||
3. After creation, go back to the original discussion and post a comment linking to the new issue:
|
||||
- "I've opened Issue #N to track this feature request. Follow along there for updates!"
|
||||
|
||||
### 7. Final Report
|
||||
|
||||
Present a final summary to the user:
|
||||
|
||||
| Discussion | Action Taken |
|
||||
| ---------- | ---------------------------------- |
|
||||
| #N — Title | Responded with workarounds |
|
||||
| #N — Title | Responded + created Issue #N |
|
||||
| #N — Title | Already answered, no action needed |
|
||||
| #N — Title | Responded to follow-up comment |
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow is **interactive** — always present the summary and wait for user approval before posting responses or creating issues
|
||||
- If the user says "pode responder" (or similar approval), proceed with posting all drafted responses
|
||||
- For discussions in non-English languages, respond in the same language as the original post
|
||||
- Always reference specific dashboard paths, config options, or code files when explaining existing features
|
||||
- When a discussion reveals a bug, note it separately from feature requests
|
||||
@@ -6,7 +6,7 @@ description: Analyze open Pull Requests from the project's GitHub repository, ge
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -18,7 +18,11 @@ This workflow fetches all open PRs from the project's GitHub repository, perform
|
||||
|
||||
### 2. Fetch Open Pull Requests
|
||||
|
||||
- Navigate to `https://github.com/<owner>/<repo>/pulls` and scrape all open PRs
|
||||
// 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>`
|
||||
- For each open PR, collect:
|
||||
- PR number, title, author, branch, number of commits, date
|
||||
- PR description/body
|
||||
@@ -60,6 +64,21 @@ This workflow fetches all open PRs from the project's GitHub repository, perform
|
||||
- Are edge cases covered?
|
||||
- Would existing tests break?
|
||||
|
||||
#### 3f. Cross-Layer (Global) Analysis
|
||||
|
||||
Perform a **global impact assessment** to verify whether the PR changes are complete across all layers of the application:
|
||||
|
||||
- **Backend → Frontend check**: If the PR adds or modifies backend-only resources (new endpoints, services, data models), evaluate whether corresponding frontend changes are missing:
|
||||
- Does a new endpoint require a new screen/page in the dashboard?
|
||||
- Should there be a new action button, menu item, or navigation link?
|
||||
- Are there new data fields that should be displayed or editable in the UI?
|
||||
- Does a new feature need a toggle, configuration panel, or status indicator?
|
||||
- **Frontend → Backend check**: If the PR adds frontend elements, verify the backend support exists:
|
||||
- Are the required API endpoints implemented?
|
||||
- Is the data model sufficient for the new UI components?
|
||||
- **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:
|
||||
|
||||
- **PR Summary** — What it does, files affected, commit count
|
||||
@@ -79,18 +98,52 @@ This workflow fetches all open PRs from the project's GitHub repository, perform
|
||||
|
||||
### 6. Implementation (if approved)
|
||||
|
||||
- Checkout the PR branch or apply changes locally
|
||||
- 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
|
||||
// turbo
|
||||
- Run: `npm test` or equivalent test command
|
||||
- Build the project to verify compilation
|
||||
// turbo
|
||||
- Run: `npm run build` or equivalent build command
|
||||
- If all checks pass, prepare the merge
|
||||
- Push the updated branch: `git push origin <branch-name>`
|
||||
|
||||
### 7. Post-Merge (if applicable)
|
||||
### 7. 🛑 WAIT — Notify User & Await PR Verification
|
||||
|
||||
- Update CHANGELOG.md with the new feature
|
||||
- Consider version bump if warranted
|
||||
- Follow the `/generate-release` workflow if a release is needed
|
||||
**This is a mandatory stop point.** Use `notify_user` with `BlockedOnUser: true`:
|
||||
|
||||
- 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**
|
||||
|
||||
Wait for the user to respond:
|
||||
|
||||
- **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
|
||||
|
||||
### 8. Thank the Contributor
|
||||
|
||||
- Post a **thank-you comment** on the PR via the GitHub API
|
||||
- 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!"_
|
||||
|
||||
### 9. Merge & Release (only after user confirms PR)
|
||||
|
||||
After the user confirms the PR:
|
||||
|
||||
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"`
|
||||
|
||||
105
.agents/workflows/update-docs.md
Normal file
105
.agents/workflows/update-docs.md
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
41
.env.example
41
.env.example
@@ -40,6 +40,7 @@ MACHINE_ID_SALT=endpoint-proxy-salt
|
||||
ENABLE_REQUEST_LOGS=false
|
||||
AUTH_COOKIE_SECURE=false
|
||||
REQUIRE_API_KEY=false
|
||||
ALLOW_API_KEY_REVEAL=false
|
||||
|
||||
# Input Sanitizer (FASE-01 — prompt injection & PII protection)
|
||||
# INPUT_SANITIZER_ENABLED=true
|
||||
@@ -130,6 +131,44 @@ GEMINI_CLI_OAUTH_CLIENT_SECRET=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
|
||||
# IFLOW_OAUTH_CLIENT_ID=
|
||||
IFLOW_OAUTH_CLIENT_SECRET=4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Provider User-Agent Overrides (optional — customize per-provider UA headers)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 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
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# CLI Fingerprint Compatibility (optional — match native CLI binary signatures)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 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.
|
||||
#
|
||||
# Enable per-provider:
|
||||
# CLI_COMPAT_CODEX=1
|
||||
# CLI_COMPAT_CLAUDE=1
|
||||
# CLI_COMPAT_GITHUB=1
|
||||
# CLI_COMPAT_ANTIGRAVITY=1
|
||||
# CLI_COMPAT_KIRO=1
|
||||
# CLI_COMPAT_CURSOR=1
|
||||
# CLI_COMPAT_KIMI_CODING=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
|
||||
# DEEPSEEK_API_KEY=
|
||||
@@ -150,6 +189,8 @@ IFLOW_OAUTH_CLIENT_SECRET=4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW
|
||||
# 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=*
|
||||
|
||||
145
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
145
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
name: Bug Report
|
||||
description: Report a bug or unexpected behavior in OmniRoute
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report a bug. Please fill out the sections below so we can reproduce and fix the issue.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: OmniRoute Version
|
||||
description: "Run `omniroute --version` or check the left sidebar in the dashboard."
|
||||
placeholder: "e.g. 3.0.9"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: install-method
|
||||
attributes:
|
||||
label: Installation Method
|
||||
options:
|
||||
- npm (global)
|
||||
- Docker / Docker Compose
|
||||
- Electron desktop app
|
||||
- Built from source
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: OS Version
|
||||
placeholder: "e.g. Windows 11 23H2, macOS 15.3, Ubuntu 24.04"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: node-version
|
||||
attributes:
|
||||
label: Node.js Version
|
||||
description: "Run `node --version`. Skip if using Docker."
|
||||
placeholder: "e.g. 22.12.0"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: provider
|
||||
attributes:
|
||||
label: Provider(s) Involved
|
||||
description: "Which AI provider(s) does this affect?"
|
||||
placeholder: "e.g. Antigravity, OpenRouter, Ollama, Qwen"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: model
|
||||
attributes:
|
||||
label: Model(s) Involved
|
||||
placeholder: "e.g. claude-sonnet-4-20250514, gpt-4o, gemini-2.5-pro"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: client-tool
|
||||
attributes:
|
||||
label: Client Tool
|
||||
description: "Which tool are you using OmniRoute with?"
|
||||
placeholder: "e.g. Claude Code, Cursor, Roo Code, OpenClaw, Gemini CLI, cURL"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: "A clear description of what the bug is."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: "Step-by-step instructions to reproduce the behavior."
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: "What did you expect to happen?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: "What actually happened?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Error Logs / Output
|
||||
description: "Paste any relevant error messages, logs, or terminal output. This will be automatically formatted as code."
|
||||
render: shell
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: "If applicable, add screenshots to help explain the problem. Please also include the text of any error messages above — screenshots alone are not searchable."
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: "Any other context about the problem (e.g. proxy config, number of accounts, network setup)."
|
||||
validations:
|
||||
required: false
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Question / Help
|
||||
url: https://github.com/diegosouzapw/OmniRoute/discussions
|
||||
about: For questions or help with setup, please use GitHub Discussions instead of opening an issue.
|
||||
70
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
70
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature or improvement for OmniRoute
|
||||
title: "[Feature] "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for suggesting a feature! Please describe the problem you're trying to solve and how you'd like it to work.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem / Use Case
|
||||
description: "What problem does this feature solve? Why do you need it?"
|
||||
placeholder: "I'm trying to ... but currently ..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Proposed Solution
|
||||
description: "How would you like this to work?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives Considered
|
||||
description: "Have you considered any workarounds or alternative approaches?"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
description: "Which part of OmniRoute does this relate to?"
|
||||
multiple: true
|
||||
options:
|
||||
- Dashboard / UI
|
||||
- Proxy / Routing
|
||||
- Provider Support
|
||||
- CLI Tools Integration
|
||||
- OAuth / Authentication
|
||||
- Analytics / Usage Tracking
|
||||
- Docker / Deployment
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: provider
|
||||
attributes:
|
||||
label: Related Provider(s)
|
||||
description: "If this relates to specific providers, list them."
|
||||
placeholder: "e.g. Antigravity, OpenRouter, Ollama"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: "Any other context, mockups, or references."
|
||||
validations:
|
||||
required: false
|
||||
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -10,6 +10,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
@@ -22,6 +25,12 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run check:cycles
|
||||
- run: npm run check:route-validation:t06
|
||||
- run: npm run check:any-budget:t11
|
||||
- run: npm run check:docs-sync
|
||||
- run: npm run typecheck:core
|
||||
- run: npm run typecheck:noimplicit:core
|
||||
|
||||
security:
|
||||
name: Security Audit
|
||||
@@ -127,7 +136,6 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run test:integration
|
||||
continue-on-error: true
|
||||
|
||||
test-security:
|
||||
name: Security Tests
|
||||
@@ -144,4 +152,3 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run test:security
|
||||
continue-on-error: true
|
||||
|
||||
2
.github/workflows/deploy-vps.yml
vendored
2
.github/workflows/deploy-vps.yml
vendored
@@ -16,12 +16,14 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy via SSH
|
||||
uses: appleboy/ssh-action@v1
|
||||
continue-on-error: true
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: 22
|
||||
timeout: 30s
|
||||
command_timeout: 5m
|
||||
script: |
|
||||
echo "=== Updating OmniRoute ==="
|
||||
npm install -g omniroute@latest 2>&1
|
||||
|
||||
131
.github/workflows/docker-publish.yml
vendored
131
.github/workflows/docker-publish.yml
vendored
@@ -1,107 +1,82 @@
|
||||
name: Publish to Docker Hub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version tag to build (e.g. 2.6.0)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (${{ matrix.platform }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
platform_pair: linux-amd64
|
||||
runner: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
platform_pair: linux-arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
docker:
|
||||
name: Build and Push Docker (multi-arch)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
IMAGE_NAME: diegosouzapw/omniroute
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }}
|
||||
|
||||
- name: Set up QEMU (for multi-arch builds)
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version from release tag or input
|
||||
id: version
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
VERSION="${{ inputs.version }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
VERSION="${VERSION#v}"
|
||||
fi
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Publishing Docker image: $IMAGE_NAME:$VERSION"
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: runner-base
|
||||
platforms: ${{ matrix.platform }}
|
||||
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=omniroute-runner-base-${{ matrix.platform_pair }}
|
||||
cache-to: type=gha,mode=max,scope=omniroute-runner-base-${{ matrix.platform_pair }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p "${{ runner.temp }}/digests"
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ matrix.platform_pair }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Merge manifest and publish tags
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
env:
|
||||
IMAGE_NAME: diegosouzapw/omniroute
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Extract version from release tag
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
VERSION="${VERSION#v}"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Publishing Docker image version: $VERSION"
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}" \
|
||||
-t "${{ env.IMAGE_NAME }}:latest" \
|
||||
$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
ghcr.io/diegosouzapw/omniroute:${{ steps.version.outputs.version }}
|
||||
ghcr.io/diegosouzapw/omniroute:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
no-cache: false
|
||||
env:
|
||||
DOCKER_BUILDKIT_INLINE_CACHE: 1
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
|
||||
213
.github/workflows/electron-release.yml
vendored
Normal file
213
.github/workflows/electron-release.yml
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
name: Build Electron Desktop App
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Release version (e.g., v1.6.8)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.validate.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate version format
|
||||
id: validate
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
else
|
||||
VERSION="${{ inputs.version }}"
|
||||
fi
|
||||
|
||||
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Invalid version format. Expected: v1.6.8"
|
||||
exit 1
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "✓ Valid version: $VERSION"
|
||||
|
||||
build:
|
||||
name: Build Electron (${{ matrix.platform }})
|
||||
needs: validate
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: windows
|
||||
runner: windows-latest
|
||||
target: win
|
||||
ext: .exe
|
||||
- platform: macos-intel
|
||||
runner: macos-15-intel
|
||||
target: mac-x64
|
||||
ext: .dmg
|
||||
- platform: macos-arm64
|
||||
runner: macos-latest
|
||||
target: mac-arm64
|
||||
ext: -arm64.dmg
|
||||
- platform: linux
|
||||
runner: ubuntu-latest
|
||||
target: linux
|
||||
ext: .AppImage
|
||||
deb_ext: .deb
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Next.js standalone
|
||||
env:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
run: npm run build
|
||||
|
||||
- name: Sync version in electron/package.json
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="${{ needs.validate.outputs.version }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('electron/package.json'));
|
||||
pkg.version = '$VERSION_NO_V';
|
||||
fs.writeFileSync('electron/package.json', JSON.stringify(pkg, null, 2) + '\\n');
|
||||
"
|
||||
echo "✓ electron/package.json version set to $VERSION_NO_V"
|
||||
|
||||
- name: Install fpm (Linux .deb packaging tool)
|
||||
if: matrix.platform == 'linux'
|
||||
run: sudo gem install fpm --no-document
|
||||
|
||||
- name: Install Electron dependencies
|
||||
working-directory: electron
|
||||
run: npm install --no-audit --no-fund
|
||||
|
||||
- name: Build Electron for ${{ matrix.platform }}
|
||||
working-directory: electron
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npm run build:${{ matrix.target }}
|
||||
|
||||
- name: Collect installers
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
cd electron/dist-electron
|
||||
# Copy only installer files for this platform
|
||||
for file in *${{ matrix.ext }}; do
|
||||
[ -f "$file" ] && cp "$file" ../../release-assets/
|
||||
done
|
||||
# Linux: also copy .deb package
|
||||
if [ "${{ matrix.platform }}" = "linux" ]; then
|
||||
for file in *.deb; do
|
||||
[ -f "$file" ] && cp "$file" ../../release-assets/
|
||||
done
|
||||
fi
|
||||
# Windows: also copy portable standalone exe as OmniRoute.exe
|
||||
if [ "${{ matrix.platform }}" = "windows" ]; then
|
||||
for file in *.exe; do
|
||||
# Skip the NSIS installer (contains "Setup")
|
||||
case "$file" in *Setup*) continue ;; esac
|
||||
[ -f "$file" ] && cp "$file" "../../release-assets/OmniRoute.exe" && break
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: electron-${{ matrix.platform }}
|
||||
path: release-assets/
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: [validate, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: release-assets
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create source archives
|
||||
run: |
|
||||
# Create source code archives (excluding dev dependencies and build artifacts)
|
||||
export TARBALL="OmniRoute-${{ needs.validate.outputs.version }}.source.tar.gz"
|
||||
export ZIPBALL="OmniRoute-${{ needs.validate.outputs.version }}.source.zip"
|
||||
|
||||
# Use git archive for clean source export
|
||||
git archive --format=tar.gz --prefix=OmniRoute-${{ needs.validate.outputs.version }}/ HEAD -o "release-assets/$TARBALL"
|
||||
git archive --format=zip --prefix=OmniRoute-${{ needs.validate.outputs.version }}/ HEAD -o "release-assets/$ZIPBALL"
|
||||
|
||||
echo "✓ Created source archives:"
|
||||
ls -lh "release-assets/$TARBALL" "release-assets/$ZIPBALL"
|
||||
|
||||
- name: List release files
|
||||
run: ls -la release-assets/
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.validate.outputs.version }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
generate_release_notes: true
|
||||
fail_on_unmatched_files: false
|
||||
files: |
|
||||
release-assets/*.dmg
|
||||
release-assets/*.exe
|
||||
release-assets/*.AppImage
|
||||
release-assets/*.deb
|
||||
release-assets/*.blockmap
|
||||
release-assets/*.source.tar.gz
|
||||
release-assets/*.source.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish-npm:
|
||||
name: Publish to npm
|
||||
needs: [validate, release]
|
||||
uses: ./.github/workflows/npm-publish.yml
|
||||
with:
|
||||
version: ${{ needs.validate.outputs.version }}
|
||||
tag: latest
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
106
.github/workflows/npm-publish.yml
vendored
106
.github/workflows/npm-publish.yml
vendored
@@ -3,10 +3,39 @@ name: Publish to npm
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to publish (e.g. 2.9.5 or 3.0.0-rc.15)"
|
||||
required: true
|
||||
type: string
|
||||
tag:
|
||||
description: "npm dist-tag (latest / next)"
|
||||
required: false
|
||||
default: "latest"
|
||||
type: choice
|
||||
options:
|
||||
- latest
|
||||
- next
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to publish (without v prefix)"
|
||||
required: true
|
||||
type: string
|
||||
tag:
|
||||
description: "npm dist-tag (latest / next)"
|
||||
required: false
|
||||
default: "latest"
|
||||
type: string
|
||||
secrets:
|
||||
NPM_TOKEN:
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
@@ -22,21 +51,76 @@ jobs:
|
||||
node-version: 22
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install dependencies (skip scripts to avoid heavy build)
|
||||
run: npm install --ignore-scripts --no-audit --no-fund
|
||||
|
||||
- name: Build standalone app
|
||||
run: npm run build:cli
|
||||
|
||||
- name: Sync version from release tag
|
||||
- name: Resolve version and dist-tag
|
||||
id: resolve
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
# Remove 'v' prefix if present (v0.1.0 -> 0.1.0)
|
||||
case "${{ github.event_name }}" in
|
||||
workflow_dispatch|workflow_call)
|
||||
VERSION="${{ inputs.version }}"
|
||||
TAG="${{ inputs.tag }}"
|
||||
;;
|
||||
release)
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
;;
|
||||
esac
|
||||
# Strip v prefix if present
|
||||
VERSION="${VERSION#v}"
|
||||
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||
echo "Publishing version: $VERSION"
|
||||
# Default dist-tag logic
|
||||
if [ -z "$TAG" ]; then
|
||||
if [[ "$VERSION" == *-* ]]; then
|
||||
TAG="next"
|
||||
else
|
||||
TAG="latest"
|
||||
fi
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "📦 Publishing omniroute@$VERSION with tag=$TAG"
|
||||
|
||||
- name: Sync package.json version
|
||||
run: |
|
||||
npm version "${{ steps.resolve.outputs.version }}" --no-git-tag-version --allow-same-version
|
||||
|
||||
- name: Build CLI bundle (standalone app)
|
||||
env:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
run: node scripts/prepublish.mjs
|
||||
|
||||
- name: Publish to npm
|
||||
run: npm publish --access public
|
||||
run: |
|
||||
VERSION="${{ steps.resolve.outputs.version }}"
|
||||
TAG="${{ steps.resolve.outputs.tag }}"
|
||||
# Check if this version is already published — skip instead of failing with E403
|
||||
if npm view "omniroute@${VERSION}" version --silent 2>/dev/null | grep -q "^${VERSION}$"; then
|
||||
echo "⚠️ Version ${VERSION} is already published on npm — skipping."
|
||||
exit 0
|
||||
fi
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
npm publish --access public
|
||||
else
|
||||
npm publish --access public --tag "$TAG"
|
||||
fi
|
||||
echo "✅ Published omniroute@$VERSION (tag: $TAG)"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish to GitHub Packages
|
||||
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
|
||||
npm publish --registry=https://npm.pkg.github.com --tag "$TAG" || echo "⚠️ Version ${VERSION} might already be published on GitHub."
|
||||
fi
|
||||
echo "✅ Action finished for GitHub Packages"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
43
.github/workflows/sync-upstream.yml
vendored
Normal file
43
.github/workflows/sync-upstream.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Sync Upstream
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync with upstream
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Fetch upstream
|
||||
run: |
|
||||
git remote add upstream https://github.com/diegosouzapw/OmniRoute.git || true
|
||||
git fetch upstream
|
||||
git fetch origin
|
||||
|
||||
- name: Sync main branch
|
||||
run: |
|
||||
git checkout main
|
||||
git merge upstream/main --no-edit || {
|
||||
echo "Merge conflict detected. Manual intervention required."
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Push changes
|
||||
run: git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/tombii/OmniRoute.git main
|
||||
38
.gitignore
vendored
38
.gitignore
vendored
@@ -1,5 +1,10 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# project-specific directories
|
||||
.omnivscodeagent/
|
||||
omnirouteCloud/
|
||||
omnirouteSite/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
/.pnp
|
||||
@@ -50,6 +55,8 @@ logs/*
|
||||
# analysis directories (generated, not tracked)
|
||||
.analysis/
|
||||
antigravity-manager-analysis/
|
||||
.sisyphus/
|
||||
.plans/
|
||||
|
||||
# docs (allow specific tracked files)
|
||||
docs/*
|
||||
@@ -63,6 +70,7 @@ docs/*
|
||||
!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
|
||||
@@ -76,6 +84,11 @@ docs/*
|
||||
!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/*
|
||||
@@ -88,20 +101,37 @@ test-results/
|
||||
playwright-report/
|
||||
blob-report/
|
||||
cloud/
|
||||
omnirouteCloud/
|
||||
omnirouteSite/
|
||||
|
||||
# Security Analysis (standalone project with own git)
|
||||
security-analysis/
|
||||
|
||||
# Deploy workflow (contains sensitive VPS credentials)
|
||||
.agent/workflows/deploy.md
|
||||
clipr/
|
||||
app.log
|
||||
*.tgz
|
||||
|
||||
# Backup directories
|
||||
app.__qa_backup/
|
||||
.app-build-backup-*/
|
||||
|
||||
# Electron (subproject dependency lock)
|
||||
# Production standalone build (created by scripts/prepublish.mjs)
|
||||
# Conflicts with Next.js App Router detection in dev (root app/ shadows src/app/)
|
||||
# npm publish still includes it via package.json "files" field
|
||||
/app/
|
||||
|
||||
# Electron (subproject dependency lock and build artifacts)
|
||||
electron/package-lock.json
|
||||
electron/dist-electron/
|
||||
electron/node_modules/
|
||||
icon.iconset/
|
||||
|
||||
# VS Code Extension (independent Git repo)
|
||||
vscode-extension/
|
||||
|
||||
# SQLite residual files
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
*.sqlite-journal
|
||||
|
||||
# Compiled npm-package build artifact (not source, should not be in git)
|
||||
/app
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
npx lint-staged
|
||||
node scripts/check-docs-sync.mjs
|
||||
npm run test:unit
|
||||
|
||||
14
.npmignore
14
.npmignore
@@ -3,6 +3,11 @@ data/
|
||||
**/data/
|
||||
**/db.json
|
||||
|
||||
# VS Code extension test runtime (large binary, not needed in npm package)
|
||||
app/vscode-extension/
|
||||
**/data/
|
||||
**/db.json
|
||||
|
||||
# Source code (pre-built app/ is published instead)
|
||||
src/
|
||||
open-sse/
|
||||
@@ -42,3 +47,12 @@ AGENTS.md
|
||||
# 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/
|
||||
|
||||
60
AGENTS.md
60
AGENTS.md
@@ -4,6 +4,7 @@
|
||||
|
||||
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).
|
||||
|
||||
## Stack
|
||||
|
||||
@@ -13,6 +14,7 @@ Unified AI proxy/router — route any LLM through one endpoint. Multi-provider s
|
||||
- **Streaming**: SSE via `open-sse` internal package
|
||||
- **Styling**: Tailwind CSS v4
|
||||
- **Docker**: Multi-stage Dockerfile, 3 profiles (base / cli / host)
|
||||
- **i18n**: next-intl with 30 languages (`src/i18n/messages/`)
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -47,6 +49,60 @@ but the real logic lives in `src/lib/db/`.
|
||||
|
||||
Translation between provider formats: `open-sse/translator/`
|
||||
|
||||
**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.
|
||||
|
||||
### MCP Server (`open-sse/mcp-server/`)
|
||||
|
||||
16 tools for AI agent control via **3 transport modes**:
|
||||
|
||||
- **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`
|
||||
|
||||
HTTP transports run in-process via `httpTransport.ts` singleton using `WebStandardStreamableHTTPServerTransport`.
|
||||
|
||||
| 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` |
|
||||
|
||||
- Scoped authorization (9 scopes), audit logging, Zod schemas
|
||||
- IDE configs for Claude Desktop, Cursor, VS Code Copilot
|
||||
|
||||
### A2A Server (`src/lib/a2a/`)
|
||||
|
||||
Agent-to-Agent v0.3 protocol:
|
||||
|
||||
- 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
|
||||
|
||||
### Auto-Combo Engine (`open-sse/services/autoCombo/`)
|
||||
|
||||
Self-healing routing optimization:
|
||||
|
||||
- 6-factor scoring, 4 mode packs, bandit exploration
|
||||
- Progressive cooldown, probe-based re-admission
|
||||
|
||||
### Dashboard (`src/app/(dashboard)/`)
|
||||
|
||||
| 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 |
|
||||
|
||||
### OAuth & Tokens (`src/lib/oauth/`)
|
||||
|
||||
18 modules handling OAuth flows, token refresh, and provider credentials.
|
||||
@@ -76,7 +132,7 @@ overridable via env vars or `data/provider-credentials.json`.
|
||||
|
||||
- No hardcoded API keys or secrets in commits
|
||||
- Auth middleware on all API routes
|
||||
- Input validation on user-facing endpoints
|
||||
- Input validation on user-facing endpoints (Zod schemas)
|
||||
- SQLite encryption key must not be logged
|
||||
|
||||
### Architecture
|
||||
@@ -85,6 +141,7 @@ overridable via env vars or `data/provider-credentials.json`.
|
||||
- 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
|
||||
|
||||
@@ -92,6 +149,7 @@ overridable via env vars or `data/provider-credentials.json`.
|
||||
- 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
|
||||
|
||||
|
||||
2954
CHANGELOG.md
2954
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -114,6 +114,7 @@ npm run test:fixes # Fix verification tests
|
||||
|
||||
# With coverage
|
||||
npm run test:coverage
|
||||
npm run coverage:report
|
||||
|
||||
# E2E tests (requires Playwright)
|
||||
npm run test:e2e
|
||||
@@ -123,7 +124,13 @@ npm run lint
|
||||
npm run check
|
||||
```
|
||||
|
||||
Current test status: **368+ unit tests** covering:
|
||||
Coverage notes:
|
||||
|
||||
- `npm run test:coverage` measures source coverage for the main unit test suite, excludes `tests/**`, and includes `open-sse/**`
|
||||
- `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
|
||||
|
||||
Current test status: **968+ unit tests** covering:
|
||||
|
||||
- Provider translators and format conversion
|
||||
- Rate limiting, circuit breaker, and resilience
|
||||
|
||||
166
COVERAGE_PLAN.md
Normal file
166
COVERAGE_PLAN.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# Test Coverage Plan
|
||||
|
||||
Last updated: 2026-03-28
|
||||
|
||||
## Baseline
|
||||
|
||||
There are multiple coverage numbers depending on how the report is computed. For planning, only one of them is useful.
|
||||
|
||||
| Metric | Scope | Statements / Lines | Branches | Functions | Notes |
|
||||
| -------------------- | ----------------------------------------------------- | -----------------: | -------: | --------: | --------------------------------------------------- |
|
||||
| Legacy | Old `npm run test:coverage` | 79.42% | 75.15% | 67.94% | Inflated: counts test files and excludes `open-sse` |
|
||||
| Diagnostic | Source-only, excluding tests and excluding `open-sse` | 68.16% | 63.55% | 64.06% | Useful only to isolate `src/**` |
|
||||
| Recommended baseline | Source-only, excluding tests and including `open-sse` | 56.95% | 66.05% | 57.80% | This is the project-wide baseline to improve |
|
||||
|
||||
The recommended baseline is the number to optimize against.
|
||||
|
||||
## Rules
|
||||
|
||||
- Coverage targets apply to source files, not to `tests/**`.
|
||||
- `open-sse/**` is part of the product and must remain in scope.
|
||||
- New code should not reduce coverage in touched areas.
|
||||
- Prefer testing behavior and branch outcomes over implementation details.
|
||||
- Prefer temp SQLite databases and small fixtures over broad mocks for `src/lib/db/**`.
|
||||
|
||||
## Current command set
|
||||
|
||||
- `npm run test:coverage`
|
||||
- Main source coverage gate for the unit test suite
|
||||
- Generates `text-summary`, `html`, `json-summary`, and `lcov`
|
||||
- `npm run coverage:report`
|
||||
- Detailed file-by-file report from the latest run
|
||||
- `npm run test:coverage:legacy`
|
||||
- Historical comparison only
|
||||
|
||||
## Milestones
|
||||
|
||||
| Phase | Target | Focus |
|
||||
| ------- | ---------------------: | ------------------------------------------------- |
|
||||
| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage |
|
||||
| Phase 2 | 65% statements / lines | DB and route foundations |
|
||||
| Phase 3 | 70% statements / lines | Provider validation and usage analytics |
|
||||
| Phase 4 | 75% statements / lines | `open-sse` translators and helpers |
|
||||
| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches |
|
||||
| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites |
|
||||
| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet |
|
||||
|
||||
Branches and functions should ratchet upward with each phase, but the primary hard target is statements / lines.
|
||||
|
||||
## Priority hotspots
|
||||
|
||||
These files or areas offer the best return for the next phases:
|
||||
|
||||
1. `open-sse/handlers`
|
||||
- `chatCore.ts` at 7.57%
|
||||
- Overall directory at 29.07%
|
||||
2. `open-sse/translator/request`
|
||||
- Overall directory at 36.39%
|
||||
- Many translators are still near single-digit coverage
|
||||
3. `open-sse/translator/response`
|
||||
- Overall directory at 8.07%
|
||||
4. `open-sse/executors`
|
||||
- Overall directory at 36.62%
|
||||
5. `src/lib/db`
|
||||
- `models.ts` at 20.66%
|
||||
- `registeredKeys.ts` at 34.46%
|
||||
- `modelComboMappings.ts` at 36.25%
|
||||
- `settings.ts` at 46.40%
|
||||
- `webhooks.ts` at 33.33%
|
||||
6. `src/lib/usage`
|
||||
- `usageHistory.ts` at 21.12%
|
||||
- `usageStats.ts` at 9.56%
|
||||
- `costCalculator.ts` at 30.00%
|
||||
7. `src/lib/providers`
|
||||
- `validation.ts` at 41.16%
|
||||
8. Low-risk utility and API files for early gains
|
||||
- `src/shared/utils/upstreamError.ts`
|
||||
- `src/shared/utils/apiAuth.ts`
|
||||
- `src/lib/api/errorResponse.ts`
|
||||
- `src/app/api/settings/require-login/route.ts`
|
||||
- `src/app/api/providers/[id]/models/route.ts`
|
||||
|
||||
## Execution checklist
|
||||
|
||||
### Phase 1: 56.95% -> 60%
|
||||
|
||||
- [x] Fix coverage metric so it reflects source code instead of test files
|
||||
- [x] Keep a legacy coverage script for comparison
|
||||
- [x] Record the baseline and hotspots in-repo
|
||||
- [ ] Add focused tests for low-risk utilities:
|
||||
- `src/shared/utils/upstreamError.ts`
|
||||
- `src/shared/utils/fetchTimeout.ts`
|
||||
- `src/lib/api/errorResponse.ts`
|
||||
- `src/shared/utils/apiAuth.ts`
|
||||
- `src/lib/display/names.ts`
|
||||
- [ ] Add route tests for:
|
||||
- `src/app/api/settings/require-login/route.ts`
|
||||
- `src/app/api/providers/[id]/models/route.ts`
|
||||
|
||||
### Phase 2: 60% -> 65%
|
||||
|
||||
- [ ] Add DB-backed tests for:
|
||||
- `src/lib/db/modelComboMappings.ts`
|
||||
- `src/lib/db/settings.ts`
|
||||
- `src/lib/db/registeredKeys.ts`
|
||||
- [ ] Cover branch behavior in:
|
||||
- `src/lib/providers/validation.ts`
|
||||
- `src/app/api/v1/embeddings/route.ts`
|
||||
- `src/app/api/v1/moderations/route.ts`
|
||||
|
||||
### Phase 3: 65% -> 70%
|
||||
|
||||
- [ ] Add usage analytics tests for:
|
||||
- `src/lib/usage/usageHistory.ts`
|
||||
- `src/lib/usage/usageStats.ts`
|
||||
- `src/lib/usage/costCalculator.ts`
|
||||
- [ ] Expand route coverage for proxy management and settings branches
|
||||
|
||||
### Phase 4: 70% -> 75%
|
||||
|
||||
- [ ] Cover translator helpers and central translation paths:
|
||||
- `open-sse/translator/index.ts`
|
||||
- `open-sse/translator/helpers/*`
|
||||
- `open-sse/translator/request/*`
|
||||
- `open-sse/translator/response/*`
|
||||
|
||||
### Phase 5: 75% -> 80%
|
||||
|
||||
- [ ] Add handler-level tests for:
|
||||
- `open-sse/handlers/chatCore.ts`
|
||||
- `open-sse/handlers/responsesHandler.js`
|
||||
- `open-sse/handlers/imageGeneration.js`
|
||||
- `open-sse/handlers/embeddings.js`
|
||||
- [ ] Add executor branch coverage for provider-specific auth, retries, and endpoint overrides
|
||||
|
||||
### Phase 6: 80% -> 85%
|
||||
|
||||
- [ ] Merge more edge-case suites into the main coverage path
|
||||
- [ ] Increase function coverage for DB modules with weak constructor/helper coverage
|
||||
- [ ] Close branch gaps in `settings.ts`, `registeredKeys.ts`, `validation.ts`, and translator helpers
|
||||
|
||||
### Phase 7: 85% -> 90%
|
||||
|
||||
- [ ] Treat the remaining low-coverage files as blockers
|
||||
- [ ] Add regression tests for every uncovered production bug fixed during the push to 90%
|
||||
- [ ] Raise the coverage gate in CI only after the local baseline is stable for at least two consecutive runs
|
||||
|
||||
## Ratchet policy
|
||||
|
||||
Update `npm run test:coverage` thresholds only after the project actually exceeds the next milestone with a comfortable buffer.
|
||||
|
||||
Recommended ratchet sequence:
|
||||
|
||||
1. 55/60/55
|
||||
2. 60/62/58
|
||||
3. 65/64/62
|
||||
4. 70/66/66
|
||||
5. 75/70/72
|
||||
6. 80/75/78
|
||||
7. 85/80/84
|
||||
8. 90/85/88
|
||||
|
||||
Order is `statements-lines / branches / functions`.
|
||||
|
||||
## Known gap
|
||||
|
||||
The current coverage command measures the main Node unit suite and includes source reached from it, including `open-sse`. It does not yet merge Vitest coverage into a single unified report. That merge is worth doing later, but it is not a blocker for starting the 60% -> 80% climb.
|
||||
21
Dockerfile
21
Dockerfile
@@ -1,11 +1,17 @@
|
||||
FROM node:22-bookworm-slim AS builder
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package*.json ./
|
||||
COPY scripts/postinstall.mjs ./scripts/postinstall.mjs
|
||||
COPY scripts/native-binary-compat.mjs ./scripts/native-binary-compat.mjs
|
||||
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
|
||||
RUN mkdir -p /app/data && npm run build -- --webpack
|
||||
|
||||
FROM node:22-bookworm-slim AS runner-base
|
||||
WORKDIR /app
|
||||
@@ -23,13 +29,24 @@ 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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
# Explicitly copy @swc/helpers — not always traced by standalone output but needed at runtime
|
||||
COPY --from=builder /app/node_modules/@swc/helpers ./node_modules/@swc/helpers
|
||||
# Explicitly copy pino transport dependencies — pino spawns a worker that requires
|
||||
# pino-abstract-transport at runtime; Next.js standalone trace does not capture it (#449)
|
||||
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
|
||||
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
|
||||
COPY --from=builder /app/scripts/healthcheck.mjs ./healthcheck.mjs
|
||||
|
||||
EXPOSE 20128
|
||||
@@ -43,7 +60,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/"
|
||||
|
||||
|
||||
1798
README.ar.md
1798
README.ar.md
File diff suppressed because it is too large
Load Diff
1875
README.bg.md
1875
README.bg.md
File diff suppressed because it is too large
Load Diff
2084
README.cs.md
Normal file
2084
README.cs.md
Normal file
File diff suppressed because it is too large
Load Diff
1885
README.da.md
1885
README.da.md
File diff suppressed because it is too large
Load Diff
1024
README.de.md
1024
README.de.md
File diff suppressed because it is too large
Load Diff
1027
README.es.md
1027
README.es.md
File diff suppressed because it is too large
Load Diff
1867
README.fi.md
1867
README.fi.md
File diff suppressed because it is too large
Load Diff
1025
README.fr.md
1025
README.fr.md
File diff suppressed because it is too large
Load Diff
1885
README.he.md
1885
README.he.md
File diff suppressed because it is too large
Load Diff
1873
README.hu.md
1873
README.hu.md
File diff suppressed because it is too large
Load Diff
1865
README.id.md
1865
README.id.md
File diff suppressed because it is too large
Load Diff
2143
README.in.md
2143
README.in.md
File diff suppressed because it is too large
Load Diff
1024
README.it.md
1024
README.it.md
File diff suppressed because it is too large
Load Diff
1796
README.ja.md
1796
README.ja.md
File diff suppressed because it is too large
Load Diff
1893
README.ko.md
1893
README.ko.md
File diff suppressed because it is too large
Load Diff
1871
README.ms.md
1871
README.ms.md
File diff suppressed because it is too large
Load Diff
1875
README.nl.md
1875
README.nl.md
File diff suppressed because it is too large
Load Diff
1873
README.no.md
1873
README.no.md
File diff suppressed because it is too large
Load Diff
1871
README.phi.md
1871
README.phi.md
File diff suppressed because it is too large
Load Diff
1867
README.pl.md
1867
README.pl.md
File diff suppressed because it is too large
Load Diff
1097
README.pt-BR.md
1097
README.pt-BR.md
File diff suppressed because it is too large
Load Diff
1863
README.pt.md
1863
README.pt.md
File diff suppressed because it is too large
Load Diff
1875
README.ro.md
1875
README.ro.md
File diff suppressed because it is too large
Load Diff
1023
README.ru.md
1023
README.ru.md
File diff suppressed because it is too large
Load Diff
1884
README.sk.md
1884
README.sk.md
File diff suppressed because it is too large
Load Diff
1867
README.sv.md
1867
README.sv.md
File diff suppressed because it is too large
Load Diff
1876
README.th.md
1876
README.th.md
File diff suppressed because it is too large
Load Diff
1884
README.uk-UA.md
1884
README.uk-UA.md
File diff suppressed because it is too large
Load Diff
1883
README.vi.md
1883
README.vi.md
File diff suppressed because it is too large
Load Diff
1023
README.zh-CN.md
1023
README.zh-CN.md
File diff suppressed because it is too large
Load Diff
65
bin/mcp-server.mjs
Normal file
65
bin/mcp-server.mjs
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const ROOT = join(__dirname, "..");
|
||||
|
||||
function resolveMcpEntry(rootDir = ROOT) {
|
||||
const candidates = [
|
||||
// Preferred distributable JS entry (npm publish artifact)
|
||||
join(rootDir, "app", "open-sse", "mcp-server", "server.js"),
|
||||
// Local workspace TypeScript source fallback
|
||||
join(rootDir, "open-sse", "mcp-server", "server.ts"),
|
||||
];
|
||||
|
||||
for (const entry of candidates) {
|
||||
if (existsSync(entry)) return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatSpawnError(exitCode, signal) {
|
||||
if (signal) return `MCP server exited by signal ${signal}`;
|
||||
return `MCP server exited with code ${exitCode ?? 1}`;
|
||||
}
|
||||
|
||||
export async function startMcpCli(rootDir = ROOT) {
|
||||
const mcpEntry = resolveMcpEntry(rootDir);
|
||||
if (!mcpEntry) {
|
||||
throw new Error(
|
||||
"MCP server entrypoint not found. Expected app/open-sse/mcp-server/server.js or open-sse/mcp-server/server.ts."
|
||||
);
|
||||
}
|
||||
|
||||
// `tsx` loader is only required for local `.ts` fallback; JS entry works without it.
|
||||
const loaderArgs = mcpEntry.endsWith(".ts") ? ["--import", "tsx/esm"] : [];
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, [...loaderArgs, mcpEntry], {
|
||||
cwd: rootDir,
|
||||
env: process.env,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
child.once("error", reject);
|
||||
child.once("exit", (code, signal) => {
|
||||
if ((code ?? 0) === 0 && !signal) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
reject(new Error(formatSpawnError(code, signal)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
startMcpCli().catch((err) => {
|
||||
console.error("\x1b[31m✖ Failed to start MCP server:\x1b[0m", err?.message || err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
* 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
|
||||
*/
|
||||
@@ -16,6 +17,7 @@ import { existsSync, readFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { homedir, platform } from "node:os";
|
||||
import { isNativeBinaryCompatible } from "../scripts/native-binary-compat.mjs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -86,9 +88,17 @@ if (args.includes("--help") || args.includes("-h")) {
|
||||
omniroute Start the server
|
||||
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 --help Show this help
|
||||
omniroute --version Show version
|
||||
|
||||
\x1b[1mMCP Integration:\x1b[0m
|
||||
The --mcp flag starts an MCP server over stdio, exposing OmniRoute
|
||||
tools for AI agents in VS Code, Cursor, Claude Desktop, and Copilot.
|
||||
|
||||
Available tools: omniroute_get_health, omniroute_list_combos,
|
||||
omniroute_check_quota, omniroute_route_request, and more.
|
||||
|
||||
\x1b[1mConfig:\x1b[0m
|
||||
Loads .env from: ~/.omniroute/.env or ./.env
|
||||
Memory limit: OMNIROUTE_MEMORY_MB (default: 512)
|
||||
@@ -106,16 +116,26 @@ if (args.includes("--help") || args.includes("-h")) {
|
||||
|
||||
if (args.includes("--version") || args.includes("-v")) {
|
||||
try {
|
||||
const pkg = await import(join(ROOT, "package.json"), {
|
||||
with: { type: "json" },
|
||||
});
|
||||
console.log(pkg.default.version);
|
||||
const { version } = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
|
||||
console.log(version);
|
||||
} catch {
|
||||
console.log("unknown");
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// ── MCP Server Mode ───────────────────────────────────────
|
||||
if (args.includes("--mcp")) {
|
||||
try {
|
||||
const { startMcpCli } = await import(join(ROOT, "bin", "mcp-server.mjs"));
|
||||
await startMcpCli(ROOT);
|
||||
} catch (err) {
|
||||
console.error("\x1b[31m✖ Failed to start MCP server:\x1b[0m", err.message || err);
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function parsePort(value, fallback) {
|
||||
const parsed = parseInt(String(value), 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
|
||||
@@ -167,8 +187,50 @@ const serverJs = join(APP_DIR, "server.js");
|
||||
|
||||
if (!existsSync(serverJs)) {
|
||||
console.error("\x1b[31m✖ Server not found at:\x1b[0m", serverJs);
|
||||
console.error(" This usually means the package was not built correctly.");
|
||||
console.error(" Try reinstalling: npm install -g omniroute");
|
||||
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");
|
||||
if (isMise) {
|
||||
console.error(
|
||||
" \x1b[33m⚠ mise detected:\x1b[0m If you installed via `npm install -g omniroute`,"
|
||||
);
|
||||
console.error(" try: \x1b[36mnpx omniroute@latest\x1b[0m (downloads a fresh copy)");
|
||||
console.error(" or: \x1b[36mmise exec -- npx omniroute\x1b[0m");
|
||||
} else if (isNvm) {
|
||||
console.error(
|
||||
" \x1b[33m⚠ nvm detected:\x1b[0m Try reinstalling after loading the correct Node version:"
|
||||
);
|
||||
console.error(" \x1b[36mnvm use --lts && npm install -g omniroute\x1b[0m");
|
||||
} else {
|
||||
console.error(" Try: \x1b[36mnpm install -g omniroute\x1b[0m (reinstall)");
|
||||
console.error(" Or: \x1b[36mnpx omniroute@latest\x1b[0m");
|
||||
}
|
||||
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",
|
||||
"better-sqlite3",
|
||||
"build",
|
||||
"Release",
|
||||
"better_sqlite3.node"
|
||||
);
|
||||
if (existsSync(sqliteBinary) && !isNativeBinaryCompatible(sqliteBinary)) {
|
||||
console.error(
|
||||
"\x1b[31m✖ better-sqlite3 native module is incompatible with this platform.\x1b[0m"
|
||||
);
|
||||
console.error(` Run: cd ${APP_DIR} && npm rebuild better-sqlite3`);
|
||||
if (platform() === "darwin") {
|
||||
console.error(" If build tools are missing: xcode-select --install");
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
container_name: omniroute-prod
|
||||
build:
|
||||
context: .
|
||||
target: runner-base
|
||||
target: runner-cli
|
||||
image: omniroute:prod
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
|
||||
@@ -59,6 +59,11 @@ services:
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-${PORT:-20128}}:${DASHBOARD_PORT:-${PORT:-20128}}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
volumes:
|
||||
- omniroute-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
|
||||
|
||||
|
||||
196
docs/A2A-SERVER.md
Normal file
196
docs/A2A-SERVER.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# OmniRoute A2A Server Documentation
|
||||
|
||||
> Agent-to-Agent Protocol v0.3 — OmniRoute as an intelligent routing agent
|
||||
|
||||
## Agent Discovery
|
||||
|
||||
```bash
|
||||
curl http://localhost:20128/.well-known/agent.json
|
||||
```
|
||||
|
||||
Returns the Agent Card describing OmniRoute's capabilities, skills, and authentication requirements.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
All `/a2a` requests require an API key via the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: Bearer YOUR_OMNIROUTE_API_KEY
|
||||
```
|
||||
|
||||
If no API key is configured on the server, authentication is bypassed.
|
||||
|
||||
---
|
||||
|
||||
## JSON-RPC 2.0 Methods
|
||||
|
||||
### `message/send` — Synchronous Execution
|
||||
|
||||
Sends a message to a skill and waits for the complete response.
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/a2a \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_KEY" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"method": "message/send",
|
||||
"params": {
|
||||
"skill": "smart-routing",
|
||||
"messages": [{"role": "user", "content": "Write a hello world in Python"}],
|
||||
"metadata": {"model": "auto", "combo": "fast-coding"}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"result": {
|
||||
"task": { "id": "uuid", "state": "completed" },
|
||||
"artifacts": [{ "type": "text", "content": "..." }],
|
||||
"metadata": {
|
||||
"routing_explanation": "Selected claude-sonnet via provider \"anthropic\" (latency: 1200ms, cost: $0.003)",
|
||||
"cost_envelope": { "estimated": 0.005, "actual": 0.003, "currency": "USD" },
|
||||
"resilience_trace": [
|
||||
{ "event": "primary_selected", "provider": "anthropic", "timestamp": "..." }
|
||||
],
|
||||
"policy_verdict": { "allowed": true, "reason": "within budget and quota limits" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `message/stream` — SSE Streaming
|
||||
|
||||
Same as `message/send` but returns Server-Sent Events for real-time streaming.
|
||||
|
||||
```bash
|
||||
curl -N -X POST http://localhost:20128/a2a \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_KEY" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"method": "message/stream",
|
||||
"params": {
|
||||
"skill": "smart-routing",
|
||||
"messages": [{"role": "user", "content": "Explain quantum computing"}]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
**SSE Events:**
|
||||
|
||||
```
|
||||
data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"working"},"chunk":{"type":"text","content":"..."}}}
|
||||
|
||||
: heartbeat 2026-03-03T17:00:00Z
|
||||
|
||||
data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"completed"},"metadata":{...}}}
|
||||
```
|
||||
|
||||
### `tasks/get` — Query Task Status
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/a2a \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_KEY" \
|
||||
-d '{"jsonrpc":"2.0","id":"2","method":"tasks/get","params":{"taskId":"TASK_UUID"}}'
|
||||
```
|
||||
|
||||
### `tasks/cancel` — Cancel a Task
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/a2a \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_KEY" \
|
||||
-d '{"jsonrpc":"2.0","id":"3","method":"tasks/cancel","params":{"taskId":"TASK_UUID"}}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Skills
|
||||
|
||||
| Skill | Description |
|
||||
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `smart-routing` | Routes prompts through OmniRoute's intelligent pipeline. Returns response with routing explanation, cost, and resilience trace. |
|
||||
| `quota-management` | Answers natural-language queries about provider quotas, suggests free combos, and provides quota rankings. |
|
||||
|
||||
---
|
||||
|
||||
## Task Lifecycle
|
||||
|
||||
```
|
||||
submitted → working → completed
|
||||
→ failed
|
||||
→ cancelled
|
||||
```
|
||||
|
||||
- Tasks expire after 5 minutes (configurable)
|
||||
- Terminal states: `completed`, `failed`, `cancelled`
|
||||
- Event log tracks every state transition
|
||||
|
||||
---
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Meaning |
|
||||
| :----- | :----------------------------- |
|
||||
| -32700 | Parse error (invalid JSON) |
|
||||
| -32600 | Invalid request / Unauthorized |
|
||||
| -32601 | Method or skill not found |
|
||||
| -32602 | Invalid params |
|
||||
| -32603 | Internal error |
|
||||
|
||||
---
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Python (requests)
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
resp = requests.post("http://localhost:20128/a2a", json={
|
||||
"jsonrpc": "2.0", "id": "1",
|
||||
"method": "message/send",
|
||||
"params": {
|
||||
"skill": "smart-routing",
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
}
|
||||
}, headers={"Authorization": "Bearer YOUR_KEY"})
|
||||
|
||||
result = resp.json()["result"]
|
||||
print(result["artifacts"][0]["content"])
|
||||
print(result["metadata"]["routing_explanation"])
|
||||
```
|
||||
|
||||
### TypeScript (fetch)
|
||||
|
||||
```typescript
|
||||
const resp = await fetch("http://localhost:20128/a2a", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer YOUR_KEY",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: "1",
|
||||
method: "message/send",
|
||||
params: {
|
||||
skill: "smart-routing",
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { result } = await resp.json();
|
||||
console.log(result.metadata.routing_explanation);
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# API Reference
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](API_REFERENCE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/API_REFERENCE.md) | 🇪🇸 [Español](i18n/es/API_REFERENCE.md) | 🇫🇷 [Français](i18n/fr/API_REFERENCE.md) | 🇮🇹 [Italiano](i18n/it/API_REFERENCE.md) | 🇷🇺 [Русский](i18n/ru/API_REFERENCE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/API_REFERENCE.md) | 🇩🇪 [Deutsch](i18n/de/API_REFERENCE.md) | 🇮🇳 [हिन्दी](i18n/in/API_REFERENCE.md) | 🇹🇭 [ไทย](i18n/th/API_REFERENCE.md) | 🇺🇦 [Українська](i18n/uk-UA/API_REFERENCE.md) | 🇸🇦 [العربية](i18n/ar/API_REFERENCE.md) | 🇯🇵 [日本語](i18n/ja/API_REFERENCE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/API_REFERENCE.md) | 🇧🇬 [Български](i18n/bg/API_REFERENCE.md) | 🇩🇰 [Dansk](i18n/da/API_REFERENCE.md) | 🇫🇮 [Suomi](i18n/fi/API_REFERENCE.md) | 🇮🇱 [עברית](i18n/he/API_REFERENCE.md) | 🇭🇺 [Magyar](i18n/hu/API_REFERENCE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/API_REFERENCE.md) | 🇰🇷 [한국어](i18n/ko/API_REFERENCE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/API_REFERENCE.md) | 🇳🇱 [Nederlands](i18n/nl/API_REFERENCE.md) | 🇳🇴 [Norsk](i18n/no/API_REFERENCE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/API_REFERENCE.md) | 🇷🇴 [Română](i18n/ro/API_REFERENCE.md) | 🇵🇱 [Polski](i18n/pl/API_REFERENCE.md) | 🇸🇰 [Slovenčina](i18n/sk/API_REFERENCE.md) | 🇸🇪 [Svenska](i18n/sv/API_REFERENCE.md) | 🇵🇭 [Filipino](i18n/phi/API_REFERENCE.md)
|
||||
🌐 **Languages:** 🇺🇸 [English](API_REFERENCE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/API_REFERENCE.md) | 🇪🇸 [Español](i18n/es/API_REFERENCE.md) | 🇫🇷 [Français](i18n/fr/API_REFERENCE.md) | 🇮🇹 [Italiano](i18n/it/API_REFERENCE.md) | 🇷🇺 [Русский](i18n/ru/API_REFERENCE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/API_REFERENCE.md) | 🇩🇪 [Deutsch](i18n/de/API_REFERENCE.md) | 🇮🇳 [हिन्दी](i18n/in/API_REFERENCE.md) | 🇹🇭 [ไทย](i18n/th/API_REFERENCE.md) | 🇺🇦 [Українська](i18n/uk-UA/API_REFERENCE.md) | 🇸🇦 [العربية](i18n/ar/API_REFERENCE.md) | 🇯🇵 [日本語](i18n/ja/API_REFERENCE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/API_REFERENCE.md) | 🇧🇬 [Български](i18n/bg/API_REFERENCE.md) | 🇩🇰 [Dansk](i18n/da/API_REFERENCE.md) | 🇫🇮 [Suomi](i18n/fi/API_REFERENCE.md) | 🇮🇱 [עברית](i18n/he/API_REFERENCE.md) | 🇭🇺 [Magyar](i18n/hu/API_REFERENCE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/API_REFERENCE.md) | 🇰🇷 [한국어](i18n/ko/API_REFERENCE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/API_REFERENCE.md) | 🇳🇱 [Nederlands](i18n/nl/API_REFERENCE.md) | 🇳🇴 [Norsk](i18n/no/API_REFERENCE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/API_REFERENCE.md) | 🇷🇴 [Română](i18n/ro/API_REFERENCE.md) | 🇵🇱 [Polski](i18n/pl/API_REFERENCE.md) | 🇸🇰 [Slovenčina](i18n/sk/API_REFERENCE.md) | 🇸🇪 [Svenska](i18n/sv/API_REFERENCE.md) | 🇵🇭 [Filipino](i18n/phi/API_REFERENCE.md) | 🇨🇿 [Čeština](i18n/cs/API_REFERENCE.md)
|
||||
|
||||
Complete reference for all OmniRoute API endpoints.
|
||||
|
||||
@@ -38,15 +38,20 @@ Content-Type: application/json
|
||||
|
||||
### Custom Headers
|
||||
|
||||
| Header | Direction | Description |
|
||||
| ------------------------ | --------- | --------------------------------- |
|
||||
| `X-OmniRoute-No-Cache` | Request | Set to `true` to bypass cache |
|
||||
| `X-OmniRoute-Progress` | Request | Set to `true` for progress events |
|
||||
| `Idempotency-Key` | Request | Dedup key (5s window) |
|
||||
| `X-Request-Id` | Request | Alternative dedup key |
|
||||
| `X-OmniRoute-Cache` | Response | `HIT` or `MISS` (non-streaming) |
|
||||
| `X-OmniRoute-Idempotent` | Response | `true` if deduplicated |
|
||||
| `X-OmniRoute-Progress` | Response | `enabled` if progress tracking on |
|
||||
| Header | Direction | Description |
|
||||
| ------------------------ | --------- | ------------------------------------------------ |
|
||||
| `X-OmniRoute-No-Cache` | Request | Set to `true` to bypass cache |
|
||||
| `X-OmniRoute-Progress` | Request | Set to `true` for progress events |
|
||||
| `X-Session-Id` | Request | Sticky session key for external session affinity |
|
||||
| `x_session_id` | Request | Underscore variant also accepted (direct HTTP) |
|
||||
| `Idempotency-Key` | Request | Dedup key (5s window) |
|
||||
| `X-Request-Id` | Request | Alternative dedup key |
|
||||
| `X-OmniRoute-Cache` | Response | `HIT` or `MISS` (non-streaming) |
|
||||
| `X-OmniRoute-Idempotent` | Response | `true` if deduplicated |
|
||||
| `X-OmniRoute-Progress` | Response | `enabled` if progress tracking on |
|
||||
| `X-OmniRoute-Session-Id` | Response | Effective session ID used by OmniRoute |
|
||||
|
||||
> Nginx note: if you rely on underscore headers (for example `x_session_id`), enable `underscores_in_headers on;`.
|
||||
|
||||
---
|
||||
|
||||
@@ -137,10 +142,10 @@ The provider prefix is auto-added if missing. Mismatched models return `400`.
|
||||
|
||||
```bash
|
||||
# Get cache stats
|
||||
GET /api/cache
|
||||
GET /api/cache/stats
|
||||
|
||||
# Clear all caches
|
||||
DELETE /api/cache
|
||||
DELETE /api/cache/stats
|
||||
```
|
||||
|
||||
Response example:
|
||||
@@ -211,23 +216,23 @@ Response example:
|
||||
|
||||
### Settings
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------------- | ------- | ---------------------- |
|
||||
| `/api/settings` | GET/PUT | 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 |
|
||||
| `/api/monitoring/health` | GET | Health check |
|
||||
| `/api/cache` | GET/DELETE | Cache stats / clear |
|
||||
| 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 |
|
||||
|
||||
### Backup & Export/Import
|
||||
|
||||
@@ -248,6 +253,13 @@ 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`) |
|
||||
|
||||
### CLI Tools
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
@@ -260,14 +272,24 @@ Response example:
|
||||
|
||||
CLI responses include: `installed`, `runnable`, `command`, `commandPath`, `runtimeMode`, `reason`.
|
||||
|
||||
### ACP Agents
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ----------------- | ------ | -------------------------------------------------------- |
|
||||
| `/api/acp/agents` | GET | List all detected agents (built-in + custom) with status |
|
||||
| `/api/acp/agents` | POST | Add custom agent or refresh detection cache |
|
||||
| `/api/acp/agents` | DELETE | Remove a custom agent by `id` query param |
|
||||
|
||||
GET response includes `agents[]` (id, name, binary, version, installed, protocol, isCustom) and `summary` (total, installed, notFound, builtIn, custom).
|
||||
|
||||
### Resilience & Rate Limits
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ----------------------- | ------- | ------------------------------- |
|
||||
| `/api/resilience` | GET/PUT | 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 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 |
|
||||
|
||||
### Evals
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# OmniRoute Architecture
|
||||
|
||||
🌐 **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)
|
||||
🌐 **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-02-18_
|
||||
_Last updated: 2026-03-28_
|
||||
|
||||
## Executive Summary
|
||||
|
||||
@@ -65,6 +65,26 @@ Primary runtime model:
|
||||
- Provider SLA/control plane outside local process
|
||||
- External CLI binaries themselves (Claude CLI, Codex CLI, etc.)
|
||||
|
||||
## Dashboard Surface (Current)
|
||||
|
||||
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/costs` — cost aggregation and pricing visibility
|
||||
- `/dashboard/analytics` — usage analytics and evaluations
|
||||
- `/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/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
|
||||
|
||||
## High-Level System Context
|
||||
|
||||
```mermaid
|
||||
@@ -81,8 +101,8 @@ flowchart LR
|
||||
API[V1 Compatibility API\n/v1/*]
|
||||
DASH[Dashboard + Management API\n/api/*]
|
||||
CORE[SSE + Translation Core\nopen-sse + src/sse]
|
||||
DB[(db.json)]
|
||||
UDB[(usage.json + log.txt)]
|
||||
DB[(storage.sqlite)]
|
||||
UDB[(usage tables + log artifacts)]
|
||||
end
|
||||
|
||||
subgraph Upstreams[Upstream Providers]
|
||||
@@ -144,7 +164,7 @@ Management domains:
|
||||
- Providers/connections: `src/app/api/providers*`
|
||||
- Provider nodes: `src/app/api/provider-nodes*`
|
||||
- Custom models: `src/app/api/provider-models` (GET/POST/DELETE)
|
||||
- Model catalog: `src/app/api/models/catalog` (GET)
|
||||
- Model catalog: `src/app/api/models/route.ts` (GET)
|
||||
- Proxy config: `src/app/api/settings/proxy` (GET/PUT/DELETE) + `src/app/api/settings/proxy/test` (POST)
|
||||
- OAuth: `src/app/api/oauth/*`
|
||||
- Keys/aliases/combos/pricing: `src/app/api/keys*`, `src/app/api/models/alias`, `src/app/api/combos*`, `src/app/api/pricing`
|
||||
@@ -225,18 +245,19 @@ OAuth provider modules (12 individual files under `src/lib/oauth/providers/`):
|
||||
|
||||
## 3) Persistence Layer
|
||||
|
||||
Primary state DB:
|
||||
Primary state DB (SQLite):
|
||||
|
||||
- `src/lib/localDb.ts`
|
||||
- file: `${DATA_DIR}/db.json` (or `$XDG_CONFIG_HOME/omniroute/db.json` when set, else `~/.omniroute/db.json`)
|
||||
- entities: providerConnections, providerNodes, modelAliases, combos, apiKeys, settings, pricing, **customModels**, **proxyConfig**, **ipFilter**, **thinkingBudget**, **systemPrompt**
|
||||
- Core infra: `src/lib/db/core.ts` (better-sqlite3, migrations, WAL)
|
||||
- Re-export facade: `src/lib/localDb.ts` (thin compatibility layer for callers)
|
||||
- file: `${DATA_DIR}/storage.sqlite` (or `$XDG_CONFIG_HOME/omniroute/storage.sqlite` when set, else `~/.omniroute/storage.sqlite`)
|
||||
- entities (tables + KV namespaces): providerConnections, providerNodes, modelAliases, combos, apiKeys, settings, pricing, **customModels**, **proxyConfig**, **ipFilter**, **thinkingBudget**, **systemPrompt**
|
||||
|
||||
Usage DB:
|
||||
Usage persistence:
|
||||
|
||||
- `src/lib/usageDb.ts`
|
||||
- files: `${DATA_DIR}/usage.json`, `${DATA_DIR}/log.txt`, `${DATA_DIR}/call_logs/`
|
||||
- follows same base directory policy as `localDb` (`DATA_DIR`, then `XDG_CONFIG_HOME/omniroute` when set)
|
||||
- decomposed into focused sub-modules: `migrations.ts`, `usageHistory.ts`, `costCalculator.ts`, `usageStats.ts`, `callLogs.ts`
|
||||
- facade: `src/lib/usageDb.ts` (decomposed modules in `src/lib/usage/*`)
|
||||
- SQLite tables in `storage.sqlite`: `usage_history`, `call_logs`, `proxy_logs`
|
||||
- optional file artifacts remain for compatibility/debug (`${DATA_DIR}/log.txt`, `${DATA_DIR}/call_logs/`, `<repo>/logs/...`)
|
||||
- legacy JSON files are migrated to SQLite by startup migrations when present
|
||||
|
||||
Domain State DB (SQLite):
|
||||
|
||||
@@ -253,8 +274,9 @@ Domain State DB (SQLite):
|
||||
|
||||
## 5) Cloud Sync
|
||||
|
||||
- Scheduler init: `src/lib/initCloudSync.ts`, `src/shared/services/initializeCloudSync.ts`
|
||||
- Scheduler init: `src/lib/initCloudSync.ts`, `src/shared/services/initializeCloudSync.ts`, `src/shared/services/modelSyncScheduler.ts`
|
||||
- Periodic task: `src/shared/services/cloudSyncScheduler.ts`
|
||||
- Periodic task: `src/shared/services/modelSyncScheduler.ts`
|
||||
- Control route: `src/app/api/sync/cloud/route.ts`
|
||||
|
||||
## Request Lifecycle (`/v1/chat/completions`)
|
||||
@@ -334,7 +356,7 @@ flowchart TD
|
||||
Q -- No --> R[Return all unavailable]
|
||||
```
|
||||
|
||||
Fallback decisions are driven by `open-sse/services/accountFallback.ts` using status codes and error-message heuristics.
|
||||
Fallback decisions are driven by `open-sse/services/accountFallback.ts` using status codes and error-message heuristics. Combo routing adds one extra guard: provider-scoped 400s such as upstream content-block and role-validation failures are treated as model-local failures so later combo targets can still run.
|
||||
|
||||
## OAuth Onboarding and Token Refresh Lifecycle
|
||||
|
||||
@@ -505,9 +527,9 @@ erDiagram
|
||||
|
||||
Physical storage files:
|
||||
|
||||
- main state: `${DATA_DIR}/db.json` (or `$XDG_CONFIG_HOME/omniroute/db.json` when set, else `~/.omniroute/db.json`)
|
||||
- usage stats: `${DATA_DIR}/usage.json`
|
||||
- request log lines: `${DATA_DIR}/log.txt`
|
||||
- primary runtime DB: `${DATA_DIR}/storage.sqlite`
|
||||
- request log lines: `${DATA_DIR}/log.txt` (compat/debug artifact)
|
||||
- structured call payload archives: `${DATA_DIR}/call_logs/`
|
||||
- optional translator/request debug sessions: `<repo>/logs/...`
|
||||
|
||||
## Deployment Topology
|
||||
@@ -522,8 +544,8 @@ flowchart LR
|
||||
subgraph ContainerOrProcess[OmniRoute Runtime]
|
||||
Next[Next.js Server\nPORT=20128]
|
||||
Core[SSE Core + Executors]
|
||||
MainDB[(db.json)]
|
||||
UsageDB[(usage.json/log.txt)]
|
||||
MainDB[(storage.sqlite)]
|
||||
UsageDB[(usage tables + log artifacts)]
|
||||
end
|
||||
|
||||
subgraph External[External Services]
|
||||
@@ -550,7 +572,7 @@ flowchart LR
|
||||
- `src/app/api/providers*`: provider CRUD, validation, testing
|
||||
- `src/app/api/provider-nodes*`: custom compatible node management
|
||||
- `src/app/api/provider-models`: custom model management (CRUD)
|
||||
- `src/app/api/models/catalog`: full model catalog API (all types grouped by provider)
|
||||
- `src/app/api/models/route.ts`: model catalog API (aliases + custom models)
|
||||
- `src/app/api/oauth/*`: OAuth/device-code flows
|
||||
- `src/app/api/keys*`: local API key lifecycle
|
||||
- `src/app/api/models/alias`: alias management
|
||||
@@ -582,8 +604,9 @@ flowchart LR
|
||||
|
||||
### Persistence
|
||||
|
||||
- `src/lib/localDb.ts`: persistent config/state
|
||||
- `src/lib/usageDb.ts`: usage history and rolling request logs
|
||||
- `src/lib/db/*`: persistent config/state and domain persistence on SQLite
|
||||
- `src/lib/localDb.ts`: compatibility re-export for DB modules
|
||||
- `src/lib/usageDb.ts`: usage history/call logs facade on top of SQLite tables
|
||||
|
||||
## Provider Executor Coverage (Strategy Pattern)
|
||||
|
||||
@@ -724,23 +747,31 @@ Files are written to `<repo>/logs/<session>/` for each request session.
|
||||
|
||||
## 5) Data Integrity
|
||||
|
||||
- DB shape migration/repair for missing keys
|
||||
- corrupt JSON reset safeguards for localDb and usageDb
|
||||
- SQLite schema migrations and auto-upgrade hooks at startup
|
||||
- legacy JSON → SQLite migration compatibility path
|
||||
|
||||
## Observability and Operational Signals
|
||||
|
||||
Runtime visibility sources:
|
||||
|
||||
- console logs from `src/sse/utils/logger.ts`
|
||||
- per-request usage aggregates in `usage.json`
|
||||
- textual request status log in `log.txt`
|
||||
- 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`
|
||||
- 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; 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
|
||||
|
||||
- JWT secret (`JWT_SECRET`) secures dashboard session cookie verification/signing
|
||||
- Initial password fallback (`INITIAL_PASSWORD`, default `123456`) must be overridden in real deployments
|
||||
- Initial password bootstrap (`INITIAL_PASSWORD`) should be explicitly configured for first-run provisioning
|
||||
- API key HMAC secret (`API_KEY_SECRET`) secures generated local API key format
|
||||
- Provider secrets (API keys/tokens) are persisted in local DB and should be protected at filesystem level
|
||||
- Cloud sync endpoints rely on API key auth + machine id semantics
|
||||
@@ -762,13 +793,13 @@ Environment variables actively used by code:
|
||||
|
||||
## Known Architectural Notes
|
||||
|
||||
1. `usageDb` and `localDb` now share the same base directory policy (`DATA_DIR` -> `XDG_CONFIG_HOME/omniroute` -> `~/.omniroute`) with legacy file migration.
|
||||
2. `/api/v1/route.ts` returns a static model list and is not the main models source used by `/v1/models`.
|
||||
1. `usageDb` and `localDb` share the same base directory policy (`DATA_DIR` -> `XDG_CONFIG_HOME/omniroute` -> `~/.omniroute`) with legacy file migration.
|
||||
2. `/api/v1/route.ts` delegates to the same unified catalog builder used by `/api/v1/models` (`src/app/api/v1/models/catalog.ts`) to avoid semantic drift.
|
||||
3. Request logger writes full headers/body when enabled; treat log directory as sensitive.
|
||||
4. Cloud behavior depends on correct `NEXT_PUBLIC_BASE_URL` and cloud endpoint reachability.
|
||||
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:plan3`. Source code under `src/` is **TypeScript** (`.ts`/`.tsx`); the `open-sse/` workspace remains JavaScript (`.js`).
|
||||
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).
|
||||
|
||||
## Operational Verification Checklist
|
||||
|
||||
63
docs/AUTO-COMBO.md
Normal file
63
docs/AUTO-COMBO.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# OmniRoute Auto-Combo Engine
|
||||
|
||||
> Self-managing model chains with adaptive scoring
|
||||
|
||||
## How It Works
|
||||
|
||||
The Auto-Combo Engine dynamically selects the best provider/model for each request using a **6-factor scoring function**:
|
||||
|
||||
| Factor | Weight | Description |
|
||||
| :--------- | :----- | :---------------------------------------------- |
|
||||
| Quota | 0.20 | Remaining capacity [0..1] |
|
||||
| Health | 0.25 | Circuit breaker: CLOSED=1.0, HALF=0.5, OPEN=0.0 |
|
||||
| CostInv | 0.20 | Inverse cost (cheaper = higher score) |
|
||||
| LatencyInv | 0.15 | Inverse p95 latency (faster = higher) |
|
||||
| TaskFit | 0.10 | Model × task type fitness score |
|
||||
| Stability | 0.10 | Low variance in latency/errors |
|
||||
|
||||
## Mode Packs
|
||||
|
||||
| Pack | Focus | Key Weight |
|
||||
| :---------------------- | :----------- | :--------------- |
|
||||
| 🚀 **Ship Fast** | Speed | latencyInv: 0.35 |
|
||||
| 💰 **Cost Saver** | Economy | costInv: 0.40 |
|
||||
| 🎯 **Quality First** | Best model | taskFit: 0.40 |
|
||||
| 📡 **Offline Friendly** | Availability | quota: 0.40 |
|
||||
|
||||
## Self-Healing
|
||||
|
||||
- **Temporary exclusion**: Score < 0.2 → excluded for 5 min (progressive backoff, max 30 min)
|
||||
- **Circuit breaker awareness**: OPEN → auto-excluded; HALF_OPEN → probe requests
|
||||
- **Incident mode**: >50% OPEN → disable exploration, maximize stability
|
||||
- **Cooldown recovery**: After exclusion, first request is a "probe" with reduced timeout
|
||||
|
||||
## Bandit Exploration
|
||||
|
||||
5% of requests (configurable) are routed to random providers for exploration. Disabled in incident mode.
|
||||
|
||||
## API
|
||||
|
||||
```bash
|
||||
# Create auto-combo
|
||||
curl -X POST http://localhost:20128/api/combos/auto \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":"my-auto","name":"Auto Coder","candidatePool":["anthropic","google","openai"],"modePack":"ship-fast"}'
|
||||
|
||||
# List auto-combos
|
||||
curl http://localhost:20128/api/combos/auto
|
||||
```
|
||||
|
||||
## Task Fitness
|
||||
|
||||
30+ models scored across 6 task types (`coding`, `review`, `planning`, `analysis`, `debugging`, `documentation`). Supports wildcard patterns (e.g., `*-coder` → high coding score).
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
| :------------------------------------------- | :------------------------------------ |
|
||||
| `open-sse/services/autoCombo/scoring.ts` | Scoring function & pool normalization |
|
||||
| `open-sse/services/autoCombo/taskFitness.ts` | Model × task fitness lookup |
|
||||
| `open-sse/services/autoCombo/engine.ts` | Selection logic, bandit, budget cap |
|
||||
| `open-sse/services/autoCombo/selfHealing.ts` | Exclusion, probes, incident mode |
|
||||
| `open-sse/services/autoCombo/modePacks.ts` | 4 weight profiles |
|
||||
| `src/app/api/combos/auto/route.ts` | REST API |
|
||||
344
docs/CLI-TOOLS.md
Normal file
344
docs/CLI-TOOLS.md
Normal file
@@ -0,0 +1,344 @@
|
||||
# CLI Tools Setup Guide — OmniRoute
|
||||
|
||||
This guide explains how to install and configure all supported AI coding CLI tools
|
||||
to use **OmniRoute** as the unified backend, giving you centralized key management,
|
||||
cost tracking, model switching, and request logging across every tool.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
Claude / Codex / OpenCode / Cline / KiloCode / Continue / Kiro / Cursor / Copilot
|
||||
│
|
||||
▼ (all point to OmniRoute)
|
||||
http://YOUR_SERVER:20128/v1
|
||||
│
|
||||
▼ (OmniRoute routes to the right provider)
|
||||
Anthropic / OpenAI / Gemini / DeepSeek / Groq / Mistral / ...
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- One API key to manage all tools
|
||||
- Cost tracking across all CLIs in the dashboard
|
||||
- Model switching without reconfiguring every tool
|
||||
- Works locally and on remote servers (VPS)
|
||||
|
||||
---
|
||||
|
||||
## Supported Tools (Dashboard Source of Truth)
|
||||
|
||||
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 |
|
||||
|
||||
### 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 |
|
||||
|
||||
Legacy IDs still accepted for compatibility: `copilot`, `kimi-coding`, `qwen`.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Get an OmniRoute API Key
|
||||
|
||||
1. Open the OmniRoute dashboard → **API Manager** (`/dashboard/api-manager`)
|
||||
2. Click **Create API Key**
|
||||
3. Give it a name (e.g. `cli-tools`) and select all permissions
|
||||
4. Copy the key — you'll need it for every CLI below
|
||||
|
||||
> Your key looks like: `sk-xxxxxxxxxxxxxxxx-xxxxxxxxx`
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Install CLI Tools
|
||||
|
||||
All npm-based tools require Node.js 18+:
|
||||
|
||||
```bash
|
||||
# Claude Code (Anthropic)
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
|
||||
# OpenAI Codex
|
||||
npm install -g @openai/codex
|
||||
|
||||
# OpenCode
|
||||
npm install -g opencode-ai
|
||||
|
||||
# Cline
|
||||
npm install -g cline
|
||||
|
||||
# KiloCode
|
||||
npm install -g kilocode
|
||||
|
||||
# Kiro CLI (Amazon — requires curl + unzip)
|
||||
apt-get install -y unzip # on Debian/Ubuntu
|
||||
curl -fsSL https://cli.kiro.dev/install | bash
|
||||
export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc
|
||||
```
|
||||
|
||||
**Verify:**
|
||||
|
||||
```bash
|
||||
claude --version # 2.x.x
|
||||
codex --version # 0.x.x
|
||||
opencode --version # x.x.x
|
||||
cline --version # 2.x.x
|
||||
kilocode --version # x.x.x (or: kilo --version)
|
||||
kiro-cli --version # 1.x.x
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Set Global Environment Variables
|
||||
|
||||
Add to `~/.bashrc` (or `~/.zshrc`), then run `source ~/.bashrc`:
|
||||
|
||||
```bash
|
||||
# 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 GEMINI_BASE_URL="http://localhost:20128/v1"
|
||||
export GEMINI_API_KEY="sk-your-omniroute-key"
|
||||
```
|
||||
|
||||
> For a **remote server** replace `localhost:20128` with the server IP or domain,
|
||||
> e.g. `http://192.168.0.15:20128`.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Configure Each Tool
|
||||
|
||||
### Claude Code
|
||||
|
||||
```bash
|
||||
# Via CLI:
|
||||
claude config set --global api-base-url http://localhost:20128/v1
|
||||
|
||||
# Or create ~/.claude/settings.json:
|
||||
mkdir -p ~/.claude && cat > ~/.claude/settings.json << EOF
|
||||
{
|
||||
"apiBaseUrl": "http://localhost:20128/v1",
|
||||
"apiKey": "sk-your-omniroute-key"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
**Test:** `claude "say hello"`
|
||||
|
||||
---
|
||||
|
||||
### OpenAI Codex
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.codex && cat > ~/.codex/config.yaml << EOF
|
||||
model: auto
|
||||
apiKey: sk-your-omniroute-key
|
||||
apiBaseUrl: http://localhost:20128/v1
|
||||
EOF
|
||||
```
|
||||
|
||||
**Test:** `codex "what is 2+2?"`
|
||||
|
||||
---
|
||||
|
||||
### OpenCode
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode && cat > ~/.config/opencode/config.toml << EOF
|
||||
[provider.openai]
|
||||
base_url = "http://localhost:20128/v1"
|
||||
api_key = "sk-your-omniroute-key"
|
||||
EOF
|
||||
```
|
||||
|
||||
**Test:** `opencode`
|
||||
|
||||
---
|
||||
|
||||
### Cline (CLI or VS Code)
|
||||
|
||||
**CLI mode:**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.cline/data && cat > ~/.cline/data/globalState.json << EOF
|
||||
{
|
||||
"apiProvider": "openai",
|
||||
"openAiBaseUrl": "http://localhost:20128/v1",
|
||||
"openAiApiKey": "sk-your-omniroute-key"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
**VS Code mode:**
|
||||
Cline extension settings → API Provider: `OpenAI Compatible` → Base URL: `http://localhost:20128/v1`
|
||||
|
||||
Or use the OmniRoute dashboard → **CLI Tools → Cline → Apply Config**.
|
||||
|
||||
---
|
||||
|
||||
### KiloCode (CLI or VS Code)
|
||||
|
||||
**CLI mode:**
|
||||
|
||||
```bash
|
||||
kilocode --api-base http://localhost:20128/v1 --api-key sk-your-omniroute-key
|
||||
```
|
||||
|
||||
**VS Code settings:**
|
||||
|
||||
```json
|
||||
{
|
||||
"kilo-code.openAiBaseUrl": "http://localhost:20128/v1",
|
||||
"kilo-code.apiKey": "sk-your-omniroute-key"
|
||||
}
|
||||
```
|
||||
|
||||
Or use the OmniRoute dashboard → **CLI Tools → KiloCode → Apply Config**.
|
||||
|
||||
---
|
||||
|
||||
### Continue (VS Code Extension)
|
||||
|
||||
Edit `~/.continue/config.yaml`:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
- name: OmniRoute
|
||||
provider: openai
|
||||
model: auto
|
||||
apiBase: http://localhost:20128/v1
|
||||
apiKey: sk-your-omniroute-key
|
||||
default: true
|
||||
```
|
||||
|
||||
Restart VS Code after editing.
|
||||
|
||||
---
|
||||
|
||||
### Kiro CLI (Amazon)
|
||||
|
||||
```bash
|
||||
# Login to your AWS/Kiro account:
|
||||
kiro-cli login
|
||||
|
||||
# The CLI uses its own auth — OmniRoute is not needed as backend for Kiro CLI itself.
|
||||
# Use kiro-cli alongside OmniRoute for other tools.
|
||||
kiro-cli status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Cursor (Desktop App)
|
||||
|
||||
> **Note:** Cursor routes requests through its cloud. For OmniRoute integration,
|
||||
> enable **Cloud Endpoint** in OmniRoute Settings and use your public domain URL.
|
||||
|
||||
Via GUI: **Settings → Models → OpenAI API Key**
|
||||
|
||||
- Base URL: `https://your-domain.com/v1`
|
||||
- API Key: your OmniRoute key
|
||||
|
||||
---
|
||||
|
||||
## Dashboard Auto-Configuration
|
||||
|
||||
The OmniRoute dashboard automates configuration for most tools:
|
||||
|
||||
1. Go to `http://localhost:20128/dashboard/cli-tools`
|
||||
2. Expand any tool card
|
||||
3. Select your API key from the dropdown
|
||||
4. Click **Apply Config** (if tool is detected as installed)
|
||||
5. Or copy the generated config snippet manually
|
||||
|
||||
---
|
||||
|
||||
## Built-in Agents: Droid & OpenClaw
|
||||
|
||||
**Droid** and **OpenClaw** are AI agents built directly into OmniRoute — no installation needed.
|
||||
They run as internal routes and use OmniRoute's model routing automatically.
|
||||
|
||||
- Access: `http://localhost:20128/dashboard/agents`
|
||||
- Configure: same combos and providers as all other tools
|
||||
- No API key or CLI install required
|
||||
|
||||
---
|
||||
|
||||
## Available API Endpoints
|
||||
|
||||
| Endpoint | Description | Use For |
|
||||
| -------------------------- | ----------------------------- | --------------------------- |
|
||||
| `/v1/chat/completions` | Standard chat (all providers) | All modern tools |
|
||||
| `/v1/responses` | Responses API (OpenAI format) | Codex, agentic workflows |
|
||||
| `/v1/completions` | Legacy text completions | Older tools using `prompt:` |
|
||||
| `/v1/embeddings` | Text embeddings | RAG, search |
|
||||
| `/v1/images/generations` | Image generation | DALL-E, Flux, etc. |
|
||||
| `/v1/audio/speech` | Text-to-speech | ElevenLabs, OpenAI TTS |
|
||||
| `/v1/audio/transcriptions` | Speech-to-text | Deepgram, AssemblyAI |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Error | Cause | Fix |
|
||||
| ------------------------- | ----------------------- | ------------------------------------------ |
|
||||
| `Connection refused` | OmniRoute not running | `pm2 start omniroute` |
|
||||
| `401 Unauthorized` | Wrong API key | Check in `/dashboard/api-manager` |
|
||||
| `No combo configured` | No active routing combo | Set up in `/dashboard/combos` |
|
||||
| `invalid model` | Model not in catalog | Use `auto` or check `/dashboard/providers` |
|
||||
| CLI shows "not installed" | Binary not in PATH | Check `which <command>` |
|
||||
| `kiro-cli: not found` | Not in PATH | `export PATH="$HOME/.local/bin:$PATH"` |
|
||||
|
||||
---
|
||||
|
||||
## Quick Setup Script (One Command)
|
||||
|
||||
```bash
|
||||
# Install all CLIs and configure for OmniRoute (replace with your key and server URL)
|
||||
OMNIROUTE_URL="http://localhost:20128/v1"
|
||||
OMNIROUTE_KEY="sk-your-omniroute-key"
|
||||
|
||||
npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai cline kilocode
|
||||
|
||||
# Kiro CLI
|
||||
apt-get install -y unzip 2>/dev/null; curl -fsSL https://cli.kiro.dev/install | bash
|
||||
|
||||
# Write configs
|
||||
mkdir -p ~/.claude ~/.codex ~/.config/opencode ~/.continue
|
||||
|
||||
cat > ~/.claude/settings.json <<< "{\"apiBaseUrl\":\"$OMNIROUTE_URL\",\"apiKey\":\"$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"
|
||||
EOF
|
||||
|
||||
source ~/.bashrc
|
||||
echo "✅ All CLIs installed and configured for OmniRoute"
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# omniroute — Codebase Documentation
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](CODEBASE_DOCUMENTATION.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/CODEBASE_DOCUMENTATION.md) | 🇪🇸 [Español](i18n/es/CODEBASE_DOCUMENTATION.md) | 🇫🇷 [Français](i18n/fr/CODEBASE_DOCUMENTATION.md) | 🇮🇹 [Italiano](i18n/it/CODEBASE_DOCUMENTATION.md) | 🇷🇺 [Русский](i18n/ru/CODEBASE_DOCUMENTATION.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/CODEBASE_DOCUMENTATION.md) | 🇩🇪 [Deutsch](i18n/de/CODEBASE_DOCUMENTATION.md) | 🇮🇳 [हिन्दी](i18n/in/CODEBASE_DOCUMENTATION.md) | 🇹🇭 [ไทย](i18n/th/CODEBASE_DOCUMENTATION.md) | 🇺🇦 [Українська](i18n/uk-UA/CODEBASE_DOCUMENTATION.md) | 🇸🇦 [العربية](i18n/ar/CODEBASE_DOCUMENTATION.md) | 🇯🇵 [日本語](i18n/ja/CODEBASE_DOCUMENTATION.md) | 🇻🇳 [Tiếng Việt](i18n/vi/CODEBASE_DOCUMENTATION.md) | 🇧🇬 [Български](i18n/bg/CODEBASE_DOCUMENTATION.md) | 🇩🇰 [Dansk](i18n/da/CODEBASE_DOCUMENTATION.md) | 🇫🇮 [Suomi](i18n/fi/CODEBASE_DOCUMENTATION.md) | 🇮🇱 [עברית](i18n/he/CODEBASE_DOCUMENTATION.md) | 🇭🇺 [Magyar](i18n/hu/CODEBASE_DOCUMENTATION.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/CODEBASE_DOCUMENTATION.md) | 🇰🇷 [한국어](i18n/ko/CODEBASE_DOCUMENTATION.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/CODEBASE_DOCUMENTATION.md) | 🇳🇱 [Nederlands](i18n/nl/CODEBASE_DOCUMENTATION.md) | 🇳🇴 [Norsk](i18n/no/CODEBASE_DOCUMENTATION.md) | 🇵🇹 [Português (Portugal)](i18n/pt/CODEBASE_DOCUMENTATION.md) | 🇷🇴 [Română](i18n/ro/CODEBASE_DOCUMENTATION.md) | 🇵🇱 [Polski](i18n/pl/CODEBASE_DOCUMENTATION.md) | 🇸🇰 [Slovenčina](i18n/sk/CODEBASE_DOCUMENTATION.md) | 🇸🇪 [Svenska](i18n/sv/CODEBASE_DOCUMENTATION.md) | 🇵🇭 [Filipino](i18n/phi/CODEBASE_DOCUMENTATION.md)
|
||||
🌐 **Languages:** 🇺🇸 [English](CODEBASE_DOCUMENTATION.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/CODEBASE_DOCUMENTATION.md) | 🇪🇸 [Español](i18n/es/CODEBASE_DOCUMENTATION.md) | 🇫🇷 [Français](i18n/fr/CODEBASE_DOCUMENTATION.md) | 🇮🇹 [Italiano](i18n/it/CODEBASE_DOCUMENTATION.md) | 🇷🇺 [Русский](i18n/ru/CODEBASE_DOCUMENTATION.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/CODEBASE_DOCUMENTATION.md) | 🇩🇪 [Deutsch](i18n/de/CODEBASE_DOCUMENTATION.md) | 🇮🇳 [हिन्दी](i18n/in/CODEBASE_DOCUMENTATION.md) | 🇹🇭 [ไทย](i18n/th/CODEBASE_DOCUMENTATION.md) | 🇺🇦 [Українська](i18n/uk-UA/CODEBASE_DOCUMENTATION.md) | 🇸🇦 [العربية](i18n/ar/CODEBASE_DOCUMENTATION.md) | 🇯🇵 [日本語](i18n/ja/CODEBASE_DOCUMENTATION.md) | 🇻🇳 [Tiếng Việt](i18n/vi/CODEBASE_DOCUMENTATION.md) | 🇧🇬 [Български](i18n/bg/CODEBASE_DOCUMENTATION.md) | 🇩🇰 [Dansk](i18n/da/CODEBASE_DOCUMENTATION.md) | 🇫🇮 [Suomi](i18n/fi/CODEBASE_DOCUMENTATION.md) | 🇮🇱 [עברית](i18n/he/CODEBASE_DOCUMENTATION.md) | 🇭🇺 [Magyar](i18n/hu/CODEBASE_DOCUMENTATION.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/CODEBASE_DOCUMENTATION.md) | 🇰🇷 [한국어](i18n/ko/CODEBASE_DOCUMENTATION.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/CODEBASE_DOCUMENTATION.md) | 🇳🇱 [Nederlands](i18n/nl/CODEBASE_DOCUMENTATION.md) | 🇳🇴 [Norsk](i18n/no/CODEBASE_DOCUMENTATION.md) | 🇵🇹 [Português (Portugal)](i18n/pt/CODEBASE_DOCUMENTATION.md) | 🇷🇴 [Română](i18n/ro/CODEBASE_DOCUMENTATION.md) | 🇵🇱 [Polski](i18n/pl/CODEBASE_DOCUMENTATION.md) | 🇸🇰 [Slovenčina](i18n/sk/CODEBASE_DOCUMENTATION.md) | 🇸🇪 [Svenska](i18n/sv/CODEBASE_DOCUMENTATION.md) | 🇵🇭 [Filipino](i18n/phi/CODEBASE_DOCUMENTATION.md) | 🇨🇿 [Čeština](i18n/cs/CODEBASE_DOCUMENTATION.md)
|
||||
|
||||
> A comprehensive, beginner-friendly guide to the **omniroute** multi-provider AI proxy router.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# OmniRoute — Dashboard Features Gallery
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](FEATURES.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/FEATURES.md) | 🇪🇸 [Español](i18n/es/FEATURES.md) | 🇫🇷 [Français](i18n/fr/FEATURES.md) | 🇮🇹 [Italiano](i18n/it/FEATURES.md) | 🇷🇺 [Русский](i18n/ru/FEATURES.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/FEATURES.md) | 🇩🇪 [Deutsch](i18n/de/FEATURES.md) | 🇮🇳 [हिन्दी](i18n/in/FEATURES.md) | 🇹🇭 [ไทย](i18n/th/FEATURES.md) | 🇺🇦 [Українська](i18n/uk-UA/FEATURES.md) | 🇸🇦 [العربية](i18n/ar/FEATURES.md) | 🇯🇵 [日本語](i18n/ja/FEATURES.md) | 🇻🇳 [Tiếng Việt](i18n/vi/FEATURES.md) | 🇧🇬 [Български](i18n/bg/FEATURES.md) | 🇩🇰 [Dansk](i18n/da/FEATURES.md) | 🇫🇮 [Suomi](i18n/fi/FEATURES.md) | 🇮🇱 [עברית](i18n/he/FEATURES.md) | 🇭🇺 [Magyar](i18n/hu/FEATURES.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/FEATURES.md) | 🇰🇷 [한국어](i18n/ko/FEATURES.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/FEATURES.md) | 🇳🇱 [Nederlands](i18n/nl/FEATURES.md) | 🇳🇴 [Norsk](i18n/no/FEATURES.md) | 🇵🇹 [Português (Portugal)](i18n/pt/FEATURES.md) | 🇷🇴 [Română](i18n/ro/FEATURES.md) | 🇵🇱 [Polski](i18n/pl/FEATURES.md) | 🇸🇰 [Slovenčina](i18n/sk/FEATURES.md) | 🇸🇪 [Svenska](i18n/sv/FEATURES.md) | 🇵🇭 [Filipino](i18n/phi/FEATURES.md)
|
||||
🌐 **Languages:** 🇺🇸 [English](FEATURES.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/FEATURES.md) | 🇪🇸 [Español](i18n/es/FEATURES.md) | 🇫🇷 [Français](i18n/fr/FEATURES.md) | 🇮🇹 [Italiano](i18n/it/FEATURES.md) | 🇷🇺 [Русский](i18n/ru/FEATURES.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/FEATURES.md) | 🇩🇪 [Deutsch](i18n/de/FEATURES.md) | 🇮🇳 [हिन्दी](i18n/in/FEATURES.md) | 🇹🇭 [ไทย](i18n/th/FEATURES.md) | 🇺🇦 [Українська](i18n/uk-UA/FEATURES.md) | 🇸🇦 [العربية](i18n/ar/FEATURES.md) | 🇯🇵 [日本語](i18n/ja/FEATURES.md) | 🇻🇳 [Tiếng Việt](i18n/vi/FEATURES.md) | 🇧🇬 [Български](i18n/bg/FEATURES.md) | 🇩🇰 [Dansk](i18n/da/FEATURES.md) | 🇫🇮 [Suomi](i18n/fi/FEATURES.md) | 🇮🇱 [עברית](i18n/he/FEATURES.md) | 🇭🇺 [Magyar](i18n/hu/FEATURES.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/FEATURES.md) | 🇰🇷 [한국어](i18n/ko/FEATURES.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/FEATURES.md) | 🇳🇱 [Nederlands](i18n/nl/FEATURES.md) | 🇳🇴 [Norsk](i18n/no/FEATURES.md) | 🇵🇹 [Português (Portugal)](i18n/pt/FEATURES.md) | 🇷🇴 [Română](i18n/ro/FEATURES.md) | 🇵🇱 [Polski](i18n/pl/FEATURES.md) | 🇸🇰 [Slovenčina](i18n/sk/FEATURES.md) | 🇸🇪 [Svenska](i18n/sv/FEATURES.md) | 🇵🇭 [Filipino](i18n/phi/FEATURES.md) | 🇨🇿 [Čeština](i18n/cs/FEATURES.md)
|
||||
|
||||
Visual guide to every section of the OmniRoute dashboard.
|
||||
|
||||
@@ -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).
|
||||
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.
|
||||
|
||||

|
||||
|
||||
@@ -16,7 +16,7 @@ Manage AI provider connections: OAuth providers (Claude Code, Codex, Gemini CLI)
|
||||
|
||||
## 🎨 Combos
|
||||
|
||||
Create model routing combos with 6 strategies: fill-first, round-robin, power-of-two-choices, random, least-used, and cost-optimized. Each combo chains multiple models with automatic fallback.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
@@ -46,9 +46,28 @@ Four modes for debugging API translations: **Playground** (format converter), **
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Model Playground _(v2.0.9+)_
|
||||
|
||||
Test any model directly from the dashboard. Select provider, model, and endpoint, write prompts with Monaco Editor, stream responses in real-time, abort mid-stream, and view timing metrics.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Themes _(v2.0.5+)_
|
||||
|
||||
Customizable color themes for the entire dashboard. Choose from 7 preset colors (Coral, Blue, Red, Green, Violet, Orange, Cyan) or create a custom theme by picking any hex color. Supports light, dark, and system mode.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Settings
|
||||
|
||||
General settings, system storage, backup management (export/import database), appearance (dark/light mode), security (includes API endpoint protection and custom provider blocking), routing, resilience, and advanced configuration.
|
||||
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, sidebar item 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
|
||||
|
||||

|
||||
|
||||
@@ -56,12 +75,29 @@ General settings, system storage, backup management (export/import database), ap
|
||||
|
||||
## 🔧 CLI Tools
|
||||
|
||||
One-click configuration for AI coding tools: Claude Code, Codex CLI, Gemini CLI, OpenClaw, Kilo Code, and Antigravity.
|
||||
One-click configuration for AI coding tools: Claude Code, Codex CLI, Gemini CLI, OpenClaw, Kilo Code, Antigravity, Cline, Continue, Cursor, and Factory Droid. Features automated config apply/reset, connection profiles, and model mapping.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 🤖 CLI Agents _(v2.0.11+)_
|
||||
|
||||
Dashboard for discovering and managing CLI agents. Shows a grid of 14 built-in agents (Codex, Claude, Goose, Gemini CLI, OpenClaw, Aider, OpenCode, Cline, Qwen Code, ForgeCode, Amazon Q, Open Interpreter, Cursor CLI, Warp) with:
|
||||
|
||||
- **Installation status** — Installed / Not Found with version detection
|
||||
- **Protocol badges** — stdio, HTTP, etc.
|
||||
- **Custom agents** — Register any CLI tool via form (name, binary, version command, spawn args)
|
||||
- **CLI Fingerprint Matching** — Per-provider toggle to match native CLI request signatures, reducing ban risk while preserving proxy IP
|
||||
|
||||
---
|
||||
|
||||
## 🖼️ 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.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Request Logs
|
||||
|
||||
Real-time request logging with filtering by provider, model, account, and API key. Shows status codes, token usage, latency, and response details.
|
||||
@@ -72,6 +108,36 @@ Real-time request logging with filtering by provider, model, account, and API ke
|
||||
|
||||
## 🌐 API Endpoint
|
||||
|
||||
Your unified API endpoint with capability breakdown: Chat Completions, Embeddings, Image Generation, Reranking, Audio Transcription, and registered API keys.
|
||||
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 integration and cloud proxy support for remote access.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 🔑 API Key Management
|
||||
|
||||
Create, scope, and revoke API keys. Each key can be restricted to specific models/providers with full access or read-only permissions. Visual key management with usage tracking.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Audit Log
|
||||
|
||||
Administrative action tracking with filtering by action type, actor, target, IP address, and timestamp. Full security event history.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Desktop Application
|
||||
|
||||
Native Electron desktop app for Windows, macOS, and Linux. Run OmniRoute as a standalone application with system tray integration, offline support, auto-update, and one-click install.
|
||||
|
||||
Key features:
|
||||
|
||||
- Server readiness polling (no blank screen on cold start)
|
||||
- System tray with port management
|
||||
- Content Security Policy
|
||||
- Single-instance lock
|
||||
- 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+)
|
||||
|
||||
📖 See [`electron/README.md`](../electron/README.md) for full documentation.
|
||||
|
||||
83
docs/MCP-SERVER.md
Normal file
83
docs/MCP-SERVER.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# OmniRoute MCP Server Documentation
|
||||
|
||||
> Model Context Protocol server with 16 intelligent tools
|
||||
|
||||
## Installation
|
||||
|
||||
OmniRoute MCP is built-in. Start it with:
|
||||
|
||||
```bash
|
||||
omniroute --mcp
|
||||
```
|
||||
|
||||
Or via the open-sse transport:
|
||||
|
||||
```bash
|
||||
# HTTP streamable transport (port 20130)
|
||||
omniroute --dev # MCP auto-starts on /mcp endpoint
|
||||
```
|
||||
|
||||
## IDE Configuration
|
||||
|
||||
See [IDE Configs](integrations/ide-configs.md) for Antigravity, Cursor, Copilot, and Claude Desktop setup.
|
||||
|
||||
---
|
||||
|
||||
## Essential Tools (8)
|
||||
|
||||
| Tool | Description |
|
||||
| :------------------------------ | :--------------------------------------- |
|
||||
| `omniroute_get_health` | Gateway health, circuit breakers, uptime |
|
||||
| `omniroute_list_combos` | All configured combos with models |
|
||||
| `omniroute_get_combo_metrics` | Performance metrics for a specific combo |
|
||||
| `omniroute_switch_combo` | Switch active combo by ID/name |
|
||||
| `omniroute_check_quota` | Quota status per provider or all |
|
||||
| `omniroute_route_request` | Send a chat completion through OmniRoute |
|
||||
| `omniroute_cost_report` | Cost analytics for a time period |
|
||||
| `omniroute_list_models_catalog` | Full model catalog with capabilities |
|
||||
|
||||
## 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 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
|
||||
|
||||
MCP tools are authenticated via API key scopes. Each tool requires specific scopes:
|
||||
|
||||
| Scope | Tools |
|
||||
| :------------- | :----------------------------------------------- |
|
||||
| `read:health` | get_health, get_provider_metrics |
|
||||
| `read:combos` | list_combos, get_combo_metrics |
|
||||
| `write:combos` | switch_combo |
|
||||
| `read:quota` | check_quota |
|
||||
| `write:route` | route_request, simulate_route, test_combo |
|
||||
| `read:usage` | cost_report, get_session_snapshot, explain_route |
|
||||
| `write:config` | set_budget_guard, set_resilience_profile |
|
||||
| `read:models` | list_models_catalog, best_combo_for_task |
|
||||
|
||||
## Audit Logging
|
||||
|
||||
Every tool call is logged to `mcp_tool_audit` with:
|
||||
|
||||
- Tool name, arguments, result
|
||||
- Duration (ms), success/failure
|
||||
- API key hash, timestamp
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
| :------------------------------------------- | :------------------------------------------ |
|
||||
| `open-sse/mcp-server/server.ts` | MCP server creation + 16 tool registrations |
|
||||
| `open-sse/mcp-server/transport.ts` | Stdio + HTTP transport |
|
||||
| `open-sse/mcp-server/auth.ts` | API key + scope validation |
|
||||
| `open-sse/mcp-server/audit.ts` | Tool call audit logging |
|
||||
| `open-sse/mcp-server/tools/advancedTools.ts` | 8 advanced tool handlers |
|
||||
33
docs/RELEASE_CHECKLIST.md
Normal file
33
docs/RELEASE_CHECKLIST.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Release Checklist
|
||||
|
||||
Use this checklist before tagging or publishing a new OmniRoute release.
|
||||
|
||||
## Version and Changelog
|
||||
|
||||
1. Bump `package.json` version (`x.y.z`) in the release branch.
|
||||
2. Move release notes from `## [Unreleased]` in `CHANGELOG.md` to a dated section:
|
||||
- `## [x.y.z] — YYYY-MM-DD`
|
||||
3. Keep `## [Unreleased]` as the first changelog section for upcoming work.
|
||||
4. Ensure the latest semver section in `CHANGELOG.md` equals `package.json` version.
|
||||
|
||||
## API Docs
|
||||
|
||||
1. Update `docs/openapi.yaml`:
|
||||
- `info.version` must equal `package.json` version.
|
||||
2. Validate endpoint examples if API contracts changed.
|
||||
|
||||
## Runtime Docs
|
||||
|
||||
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.
|
||||
|
||||
## Automated Check
|
||||
|
||||
Run the sync guard locally before opening PR:
|
||||
|
||||
```bash
|
||||
npm run check:docs-sync
|
||||
```
|
||||
|
||||
CI also runs this check in `.github/workflows/ci.yml` (lint job).
|
||||
113
docs/TASKS.md
113
docs/TASKS.md
@@ -1,113 +0,0 @@
|
||||
# Rate Limiting & Flow Control Overhaul — Tasks
|
||||
|
||||
> Referência: [Relatório de Análise](../walkthrough.md) · Fase docs em `/docs/phases/`
|
||||
|
||||
---
|
||||
|
||||
## Fase 1 — Error Classification & Provider Profiles
|
||||
|
||||
### Backend Core
|
||||
|
||||
- [x] `constants.ts` — Substituir `COOLDOWN_MS.transient` por `transientInitial` (5s) + `transientMax` (60s)
|
||||
- [x] `constants.ts` — Adicionar `PROVIDER_PROFILES` (oauth / apikey) com cooldowns diferenciados
|
||||
- [x] `constants.ts` — Adicionar `DEFAULT_API_LIMITS` (100 RPM, 200ms minTime)
|
||||
- [x] `providerRegistry.ts` — Criar helper `getProviderCategory(providerId)` → `"oauth"` | `"apikey"`
|
||||
- [x] `accountFallback.ts` — Aceitar `provider` como parâmetro em `checkFallbackError`
|
||||
- [x] `accountFallback.ts` — Implementar backoff exponencial para 502/503/504 transientes
|
||||
- [x] `accountFallback.ts` — Calcular cooldown baseado no perfil do provedor
|
||||
- [x] `accountFallback.ts` — Adicionar helper `getProviderProfile(provider)`
|
||||
|
||||
### Callers (propagar `provider`)
|
||||
|
||||
- [x] `auth.ts` → `markAccountUnavailable` — Passar `provider` para `checkFallbackError`
|
||||
- [x] `combo.ts` → `handleComboChat` / `handleRoundRobinCombo` — Passar `provider` nos erros
|
||||
|
||||
### Testes
|
||||
|
||||
- [x] Atualizar `rate-limit-enhanced.test.mjs` — Teste "transient errors don't increase backoff" → `newBackoffLevel = 1`
|
||||
- [x] Criar `error-classification.test.mjs` — Cooldown exponencial 502, perfis OAuth/API, helper `getProviderCategory`
|
||||
|
||||
---
|
||||
|
||||
## Fase 2 — Circuit Breaker no Combo Pipeline
|
||||
|
||||
### Backend
|
||||
|
||||
- [x] `combo.ts` — Importar `getCircuitBreaker` e `CircuitBreakerOpenError`
|
||||
- [x] `combo.ts` — `handleComboChat` — Verificar `breaker.canExecute()` antes de cada modelo
|
||||
- [x] `combo.ts` — `handleRoundRobinCombo` — Integrar breaker per-model
|
||||
- [x] `combo.ts` — Marcar `semaphore.markRateLimited` para 502/503/504 (não só 429)
|
||||
- [x] `combo.ts` — Implementar early exit quando todos os modelos têm breaker OPEN
|
||||
|
||||
### Testes
|
||||
|
||||
- [x] Criar `combo-circuit-breaker.test.mjs` — Combo skip breaker OPEN, early exit, semáforo 502
|
||||
|
||||
---
|
||||
|
||||
## Fase 3 — Anti-Thundering Herd & Auto Rate Limit
|
||||
|
||||
### Backend
|
||||
|
||||
- [x] `rateLimitManager.ts` — Auto-enable para `apikey` providers com limites elevados
|
||||
- [x] `rateLimitManager.ts` — Criar limiter com defaults (100 RPM) quando não configurado
|
||||
- [x] `auth.ts` — Adicionar mutex na `markAccountUnavailable` para evitar marcação paralela
|
||||
|
||||
### Testes
|
||||
|
||||
- [x] Criar `thundering-herd.test.mjs` — Mutex, auto-enable, limites não restritivos
|
||||
|
||||
---
|
||||
|
||||
## Fase 4 — Frontend Resilience UI
|
||||
|
||||
### Settings Page
|
||||
|
||||
- [x] `settings/page.tsx` — Adicionar tab "Resilience" (icon: `health_and_safety`) entre Routing e Pricing
|
||||
|
||||
### Novos Componentes
|
||||
|
||||
- [x] Criar `ResilienceTab.tsx` — Layout com 4 cards (Provider Profiles → Rate Limiting → Circuit Breakers → Policies)
|
||||
- [x] Criar `ProviderProfilesCard.tsx` — Toggle OAuth/API Key, inputs para cooldowns
|
||||
- [x] Criar `CircuitBreakerCard.tsx` — Status real-time per-provider, auto-refresh 5s, botão reset
|
||||
- [x] Criar `RateLimitOverviewCard.tsx` — Tabela providers × accounts × cooldown — **agora editável com RPM, Min Gap, Max Concurrent**
|
||||
|
||||
### API Routes
|
||||
|
||||
- [x] Criar `api/resilience/route.ts` — GET (estado completo + defaults mesclados) + PATCH (salvar perfis + defaults)
|
||||
- [x] Criar `api/resilience/reset/route.ts` — POST (resetar breakers + cooldowns)
|
||||
|
||||
### Migração
|
||||
|
||||
- [x] `PoliciesPanel.tsx` movido de Security para Resilience tab
|
||||
|
||||
---
|
||||
|
||||
## Fase 5 — Settings Page Restructure (v0.9.0)
|
||||
|
||||
### Tab Reorganization
|
||||
|
||||
- [x] **Security** — Simplificado para Login/Password + IP Access Control
|
||||
- [x] **Routing** — Expandido para 6 estratégias globais com descrições
|
||||
- [x] **Resilience** — Reordenado: Provider Profiles → Rate Limiting (editável) → Circuit Breakers → Policies
|
||||
- [x] **AI** — Thinking Budget + System Prompt + Prompt Cache (movido do Advanced)
|
||||
- [x] **Advanced** — Simplificado para apenas Global Proxy
|
||||
|
||||
### Backend Routing Strategies
|
||||
|
||||
- [x] `auth.ts` — Implementar `random` (Fisher-Yates shuffle)
|
||||
- [x] `auth.ts` — Implementar `least-used` (sorted by lastUsedAt)
|
||||
- [x] `auth.ts` — Implementar `cost-optimized` (sorted by priority)
|
||||
- [x] `auth.ts` — Corrigir `p2c` (power-of-two-choices com health scoring)
|
||||
- [x] `settings.ts` — Expandir tipo `fallbackStrategy` para 6 valores
|
||||
|
||||
---
|
||||
|
||||
## Verificação Final
|
||||
|
||||
- [x] Rodar todos os testes unitários: `node --test tests/unit/*.test.mjs`
|
||||
- [x] Build do Next.js: `npm run build`
|
||||
- [x] Verificar aba Resilience no browser
|
||||
- [x] Testar persistência dos perfis (salvar → reload)
|
||||
- [x] Testar Reset All Breakers
|
||||
- [x] Verificar todas as 5 tabs reestruturadas
|
||||
@@ -1,6 +1,6 @@
|
||||
# Troubleshooting
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](TROUBLESHOOTING.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/TROUBLESHOOTING.md) | 🇪🇸 [Español](i18n/es/TROUBLESHOOTING.md) | 🇫🇷 [Français](i18n/fr/TROUBLESHOOTING.md) | 🇮🇹 [Italiano](i18n/it/TROUBLESHOOTING.md) | 🇷🇺 [Русский](i18n/ru/TROUBLESHOOTING.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/TROUBLESHOOTING.md) | 🇩🇪 [Deutsch](i18n/de/TROUBLESHOOTING.md) | 🇮🇳 [हिन्दी](i18n/in/TROUBLESHOOTING.md) | 🇹🇭 [ไทย](i18n/th/TROUBLESHOOTING.md) | 🇺🇦 [Українська](i18n/uk-UA/TROUBLESHOOTING.md) | 🇸🇦 [العربية](i18n/ar/TROUBLESHOOTING.md) | 🇯🇵 [日本語](i18n/ja/TROUBLESHOOTING.md) | 🇻🇳 [Tiếng Việt](i18n/vi/TROUBLESHOOTING.md) | 🇧🇬 [Български](i18n/bg/TROUBLESHOOTING.md) | 🇩🇰 [Dansk](i18n/da/TROUBLESHOOTING.md) | 🇫🇮 [Suomi](i18n/fi/TROUBLESHOOTING.md) | 🇮🇱 [עברית](i18n/he/TROUBLESHOOTING.md) | 🇭🇺 [Magyar](i18n/hu/TROUBLESHOOTING.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/TROUBLESHOOTING.md) | 🇰🇷 [한국어](i18n/ko/TROUBLESHOOTING.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/TROUBLESHOOTING.md) | 🇳🇱 [Nederlands](i18n/nl/TROUBLESHOOTING.md) | 🇳🇴 [Norsk](i18n/no/TROUBLESHOOTING.md) | 🇵🇹 [Português (Portugal)](i18n/pt/TROUBLESHOOTING.md) | 🇷🇴 [Română](i18n/ro/TROUBLESHOOTING.md) | 🇵🇱 [Polski](i18n/pl/TROUBLESHOOTING.md) | 🇸🇰 [Slovenčina](i18n/sk/TROUBLESHOOTING.md) | 🇸🇪 [Svenska](i18n/sv/TROUBLESHOOTING.md) | 🇵🇭 [Filipino](i18n/phi/TROUBLESHOOTING.md)
|
||||
🌐 **Languages:** 🇺🇸 [English](TROUBLESHOOTING.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/TROUBLESHOOTING.md) | 🇪🇸 [Español](i18n/es/TROUBLESHOOTING.md) | 🇫🇷 [Français](i18n/fr/TROUBLESHOOTING.md) | 🇮🇹 [Italiano](i18n/it/TROUBLESHOOTING.md) | 🇷🇺 [Русский](i18n/ru/TROUBLESHOOTING.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/TROUBLESHOOTING.md) | 🇩🇪 [Deutsch](i18n/de/TROUBLESHOOTING.md) | 🇮🇳 [हिन्दी](i18n/in/TROUBLESHOOTING.md) | 🇹🇭 [ไทย](i18n/th/TROUBLESHOOTING.md) | 🇺🇦 [Українська](i18n/uk-UA/TROUBLESHOOTING.md) | 🇸🇦 [العربية](i18n/ar/TROUBLESHOOTING.md) | 🇯🇵 [日本語](i18n/ja/TROUBLESHOOTING.md) | 🇻🇳 [Tiếng Việt](i18n/vi/TROUBLESHOOTING.md) | 🇧🇬 [Български](i18n/bg/TROUBLESHOOTING.md) | 🇩🇰 [Dansk](i18n/da/TROUBLESHOOTING.md) | 🇫🇮 [Suomi](i18n/fi/TROUBLESHOOTING.md) | 🇮🇱 [עברית](i18n/he/TROUBLESHOOTING.md) | 🇭🇺 [Magyar](i18n/hu/TROUBLESHOOTING.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/TROUBLESHOOTING.md) | 🇰🇷 [한국어](i18n/ko/TROUBLESHOOTING.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/TROUBLESHOOTING.md) | 🇳🇱 [Nederlands](i18n/nl/TROUBLESHOOTING.md) | 🇳🇴 [Norsk](i18n/no/TROUBLESHOOTING.md) | 🇵🇹 [Português (Portugal)](i18n/pt/TROUBLESHOOTING.md) | 🇷🇴 [Română](i18n/ro/TROUBLESHOOTING.md) | 🇵🇱 [Polski](i18n/pl/TROUBLESHOOTING.md) | 🇸🇰 [Slovenčina](i18n/sk/TROUBLESHOOTING.md) | 🇸🇪 [Svenska](i18n/sv/TROUBLESHOOTING.md) | 🇵🇭 [Filipino](i18n/phi/TROUBLESHOOTING.md) | 🇨🇿 [Čeština](i18n/cs/TROUBLESHOOTING.md)
|
||||
|
||||
Common problems and solutions for OmniRoute.
|
||||
|
||||
@@ -10,7 +10,7 @@ Common problems and solutions for OmniRoute.
|
||||
|
||||
| Problem | Solution |
|
||||
| ----------------------------- | ------------------------------------------------------------------ |
|
||||
| First login not working | Check `INITIAL_PASSWORD` in `.env` (default: `123456`) |
|
||||
| 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` |
|
||||
@@ -120,8 +120,8 @@ curl http://localhost:20128/api/monitoring/health
|
||||
|
||||
### Runtime Storage
|
||||
|
||||
- Main state: `${DATA_DIR}/db.json` (providers, combos, aliases, keys, settings)
|
||||
- Usage: `${DATA_DIR}/usage.json`, `${DATA_DIR}/log.txt`, `${DATA_DIR}/call_logs/`
|
||||
- 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`)
|
||||
|
||||
---
|
||||
@@ -210,6 +210,41 @@ When many concurrent requests hit a rate-limited provider, OmniRoute uses mutex
|
||||
|
||||
---
|
||||
|
||||
## Optional RAG / LLM failure taxonomy (16 problems)
|
||||
|
||||
Some OmniRoute users place the gateway in front of RAG or agent stacks. In those setups it is common to see a strange pattern: OmniRoute looks healthy (providers up, routing profiles ok, no rate limit alerts) but the final answer is still wrong.
|
||||
|
||||
In practice these incidents usually come from the downstream RAG pipeline, not from the gateway itself.
|
||||
|
||||
If you want a shared vocabulary to describe those failures you can use the WFGY ProblemMap, an external MIT license text resource that defines sixteen recurring RAG / LLM failure patterns. At a high level it covers:
|
||||
|
||||
- retrieval drift and broken context boundaries
|
||||
- empty or stale indexes and vector stores
|
||||
- embedding versus semantic mismatch
|
||||
- prompt assembly and context window issues
|
||||
- logic collapse and overconfident answers
|
||||
- long chain and agent coordination failures
|
||||
- multi agent memory and role drift
|
||||
- deployment and bootstrap ordering problems
|
||||
|
||||
The idea is simple:
|
||||
|
||||
1. When you investigate a bad response, capture:
|
||||
- user task and request
|
||||
- route or provider combo in OmniRoute
|
||||
- any RAG context used downstream (retrieved documents, tool calls, etc)
|
||||
2. Map the incident to one or two WFGY ProblemMap numbers (`No.1` … `No.16`).
|
||||
3. Store the number in your own dashboard, runbook, or incident tracker next to the OmniRoute logs.
|
||||
4. Use the corresponding WFGY page to decide whether you need to change your RAG stack, retriever, or routing strategy.
|
||||
|
||||
Full text and concrete recipes live here (MIT license, text only):
|
||||
|
||||
[WFGY ProblemMap README](https://github.com/onestardao/WFGY/blob/main/ProblemMap/README.md)
|
||||
|
||||
You can ignore this section if you do not run RAG or agent pipelines behind OmniRoute.
|
||||
|
||||
---
|
||||
|
||||
## Still Stuck?
|
||||
|
||||
- **GitHub Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# User Guide
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](USER_GUIDE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/USER_GUIDE.md) | 🇪🇸 [Español](i18n/es/USER_GUIDE.md) | 🇫🇷 [Français](i18n/fr/USER_GUIDE.md) | 🇮🇹 [Italiano](i18n/it/USER_GUIDE.md) | 🇷🇺 [Русский](i18n/ru/USER_GUIDE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/USER_GUIDE.md) | 🇩🇪 [Deutsch](i18n/de/USER_GUIDE.md) | 🇮🇳 [हिन्दी](i18n/in/USER_GUIDE.md) | 🇹🇭 [ไทย](i18n/th/USER_GUIDE.md) | 🇺🇦 [Українська](i18n/uk-UA/USER_GUIDE.md) | 🇸🇦 [العربية](i18n/ar/USER_GUIDE.md) | 🇯🇵 [日本語](i18n/ja/USER_GUIDE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/USER_GUIDE.md) | 🇧🇬 [Български](i18n/bg/USER_GUIDE.md) | 🇩🇰 [Dansk](i18n/da/USER_GUIDE.md) | 🇫🇮 [Suomi](i18n/fi/USER_GUIDE.md) | 🇮🇱 [עברית](i18n/he/USER_GUIDE.md) | 🇭🇺 [Magyar](i18n/hu/USER_GUIDE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/USER_GUIDE.md) | 🇰🇷 [한국어](i18n/ko/USER_GUIDE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/USER_GUIDE.md) | 🇳🇱 [Nederlands](i18n/nl/USER_GUIDE.md) | 🇳🇴 [Norsk](i18n/no/USER_GUIDE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/USER_GUIDE.md) | 🇷🇴 [Română](i18n/ro/USER_GUIDE.md) | 🇵🇱 [Polski](i18n/pl/USER_GUIDE.md) | 🇸🇰 [Slovenčina](i18n/sk/USER_GUIDE.md) | 🇸🇪 [Svenska](i18n/sv/USER_GUIDE.md) | 🇵🇭 [Filipino](i18n/phi/USER_GUIDE.md)
|
||||
🌐 **Languages:** 🇺🇸 [English](USER_GUIDE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/USER_GUIDE.md) | 🇪🇸 [Español](i18n/es/USER_GUIDE.md) | 🇫🇷 [Français](i18n/fr/USER_GUIDE.md) | 🇮🇹 [Italiano](i18n/it/USER_GUIDE.md) | 🇷🇺 [Русский](i18n/ru/USER_GUIDE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/USER_GUIDE.md) | 🇩🇪 [Deutsch](i18n/de/USER_GUIDE.md) | 🇮🇳 [हिन्दी](i18n/in/USER_GUIDE.md) | 🇹🇭 [ไทย](i18n/th/USER_GUIDE.md) | 🇺🇦 [Українська](i18n/uk-UA/USER_GUIDE.md) | 🇸🇦 [العربية](i18n/ar/USER_GUIDE.md) | 🇯🇵 [日本語](i18n/ja/USER_GUIDE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/USER_GUIDE.md) | 🇧🇬 [Български](i18n/bg/USER_GUIDE.md) | 🇩🇰 [Dansk](i18n/da/USER_GUIDE.md) | 🇫🇮 [Suomi](i18n/fi/USER_GUIDE.md) | 🇮🇱 [עברית](i18n/he/USER_GUIDE.md) | 🇭🇺 [Magyar](i18n/hu/USER_GUIDE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/USER_GUIDE.md) | 🇰🇷 [한국어](i18n/ko/USER_GUIDE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/USER_GUIDE.md) | 🇳🇱 [Nederlands](i18n/nl/USER_GUIDE.md) | 🇳🇴 [Norsk](i18n/no/USER_GUIDE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/USER_GUIDE.md) | 🇷🇴 [Română](i18n/ro/USER_GUIDE.md) | 🇵🇱 [Polski](i18n/pl/USER_GUIDE.md) | 🇸🇰 [Slovenčina](i18n/sk/USER_GUIDE.md) | 🇸🇪 [Svenska](i18n/sv/USER_GUIDE.md) | 🇵🇭 [Filipino](i18n/phi/USER_GUIDE.md) | 🇨🇿 [Čeština](i18n/cs/USER_GUIDE.md)
|
||||
|
||||
Complete guide for configuring providers, creating combos, integrating CLI tools, and deploying OmniRoute.
|
||||
|
||||
@@ -405,25 +405,127 @@ 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 LOG_TO_FILE="${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 |
|
||||
| `ENABLE_REQUEST_LOGS` | `false` | Enables request/response logs |
|
||||
| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) |
|
||||
| `CLOUDFLARED_BIN` | unset | Use an existing `cloudflared` binary instead of managed download |
|
||||
| `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).
|
||||
|
||||
@@ -538,6 +640,14 @@ 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
|
||||
- Tunnel URLs are ephemeral and change every time you stop/start the tunnel
|
||||
- Set `CLOUDFLARED_BIN` if you prefer using a preinstalled `cloudflared` binary instead of the managed download
|
||||
|
||||
### LLM Gateway Intelligence (Phase 9)
|
||||
|
||||
- **Semantic Cache** — Auto-caches non-streaming, temperature=0 responses (bypass with `X-OmniRoute-No-Cache: true`)
|
||||
@@ -578,6 +688,22 @@ Configure via **Dashboard → Settings → Routing**.
|
||||
| **Least Used** | Routes to the account with the oldest `lastUsedAt` timestamp, distributing traffic evenly |
|
||||
| **Cost Optimized** | Routes to the account with the lowest priority value, optimizing for lowest-cost providers |
|
||||
|
||||
#### External Sticky Session Header
|
||||
|
||||
For external session affinity (for example, Claude Code/Codex agents behind reverse proxies), send:
|
||||
|
||||
```http
|
||||
X-Session-Id: your-session-key
|
||||
```
|
||||
|
||||
OmniRoute also accepts `x_session_id` and returns the effective session key in `X-OmniRoute-Session-Id`.
|
||||
|
||||
If you use Nginx and send underscore-form headers, enable:
|
||||
|
||||
```nginx
|
||||
underscores_in_headers on;
|
||||
```
|
||||
|
||||
#### Wildcard Model Aliases
|
||||
|
||||
Create wildcard patterns to remap model names:
|
||||
@@ -667,10 +793,11 @@ 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 |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| **General** | System storage tools, appearance settings, theme controls, and per-item sidebar 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** | Provider profiles, editable rate limits, circuit breaker status, policies & locked identifiers |
|
||||
@@ -755,3 +882,55 @@ Access via **Dashboard → Health**. Real-time system health overview with 6 car
|
||||
| **Latency Telemetry** | p50/p95/p99 latency aggregation per provider |
|
||||
|
||||
**Pro Tip:** The Health page auto-refreshes every 10 seconds. Use the circuit breaker card to identify which providers are experiencing issues.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Desktop Application (Electron)
|
||||
|
||||
OmniRoute is available as a native desktop application for Windows, macOS, and Linux.
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# From the electron directory:
|
||||
cd electron
|
||||
npm install
|
||||
|
||||
# Development mode (connect to running Next.js dev server):
|
||||
npm run dev
|
||||
|
||||
# Production mode (uses standalone build):
|
||||
npm start
|
||||
```
|
||||
|
||||
### Building Installers
|
||||
|
||||
```bash
|
||||
cd electron
|
||||
npm run build # Current platform
|
||||
npm run build:win # Windows (.exe NSIS)
|
||||
npm run build:mac # macOS (.dmg universal)
|
||||
npm run build:linux # Linux (.AppImage)
|
||||
```
|
||||
|
||||
Output → `electron/dist-electron/`
|
||||
|
||||
### Key Features
|
||||
|
||||
| Feature | Description |
|
||||
| --------------------------- | ---------------------------------------------------- |
|
||||
| **Server Readiness** | Polls server before showing window (no blank screen) |
|
||||
| **System Tray** | Minimize to tray, change port, quit from tray menu |
|
||||
| **Port Management** | Change server port from tray (auto-restarts server) |
|
||||
| **Content Security Policy** | Restrictive CSP via session headers |
|
||||
| **Single Instance** | Only one app instance can run at a time |
|
||||
| **Offline Mode** | Bundled Next.js server works without internet |
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
| --------------------- | ------- | -------------------------------- |
|
||||
| `OMNIROUTE_PORT` | `20128` | Server port |
|
||||
| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit (64–16384 MB) |
|
||||
|
||||
📖 Full documentation: [`electron/README.md`](../electron/README.md)
|
||||
|
||||
@@ -1,69 +1,71 @@
|
||||
# OmniRoute — Guia de Deploy em VM com Cloudflare
|
||||
# OmniRoute — Deployment Guide on VM with Cloudflare
|
||||
|
||||
Guia completo para instalar e configurar o OmniRoute em uma VM (VPS) com domínio gerenciado via Cloudflare.
|
||||
🌐 **Languages:** 🇺🇸 [English](VM_DEPLOYMENT_GUIDE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/VM_DEPLOYMENT_GUIDE.md) | 🇪🇸 [Español](i18n/es/VM_DEPLOYMENT_GUIDE.md) | 🇫🇷 [Français](i18n/fr/VM_DEPLOYMENT_GUIDE.md) | 🇮🇹 [Italiano](i18n/it/VM_DEPLOYMENT_GUIDE.md) | 🇷🇺 [Русский](i18n/ru/VM_DEPLOYMENT_GUIDE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/VM_DEPLOYMENT_GUIDE.md) | 🇩🇪 [Deutsch](i18n/de/VM_DEPLOYMENT_GUIDE.md) | 🇮🇳 [हिन्दी](i18n/in/VM_DEPLOYMENT_GUIDE.md) | 🇹🇭 [ไทย](i18n/th/VM_DEPLOYMENT_GUIDE.md) | 🇺🇦 [Українська](i18n/uk-UA/VM_DEPLOYMENT_GUIDE.md) | 🇸🇦 [العربية](i18n/ar/VM_DEPLOYMENT_GUIDE.md) | 🇯🇵 [日本語](i18n/ja/VM_DEPLOYMENT_GUIDE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/VM_DEPLOYMENT_GUIDE.md) | 🇧🇬 [Български](i18n/bg/VM_DEPLOYMENT_GUIDE.md) | 🇩🇰 [Dansk](i18n/da/VM_DEPLOYMENT_GUIDE.md) | 🇫🇮 [Suomi](i18n/fi/VM_DEPLOYMENT_GUIDE.md) | 🇮🇱 [עברית](i18n/he/VM_DEPLOYMENT_GUIDE.md) | 🇭🇺 [Magyar](i18n/hu/VM_DEPLOYMENT_GUIDE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/VM_DEPLOYMENT_GUIDE.md) | 🇰🇷 [한국어](i18n/ko/VM_DEPLOYMENT_GUIDE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/VM_DEPLOYMENT_GUIDE.md) | 🇳🇱 [Nederlands](i18n/nl/VM_DEPLOYMENT_GUIDE.md) | 🇳🇴 [Norsk](i18n/no/VM_DEPLOYMENT_GUIDE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/VM_DEPLOYMENT_GUIDE.md) | 🇷🇴 [Română](i18n/ro/VM_DEPLOYMENT_GUIDE.md) | 🇵🇱 [Polski](i18n/pl/VM_DEPLOYMENT_GUIDE.md) | 🇸🇰 [Slovenčina](i18n/sk/VM_DEPLOYMENT_GUIDE.md) | 🇸🇪 [Svenska](i18n/sv/VM_DEPLOYMENT_GUIDE.md) | 🇵🇭 [Filipino](i18n/phi/VM_DEPLOYMENT_GUIDE.md) | 🇨🇿 [Čeština](i18n/cs/VM_DEPLOYMENT_GUIDE.md)
|
||||
|
||||
Complete guide to install and configure OmniRoute on a VM (VPS) with domain managed via Cloudflare.
|
||||
|
||||
---
|
||||
|
||||
## Pré-Requisitos
|
||||
## Prerequisites
|
||||
|
||||
| Item | Mínimo | Recomendado |
|
||||
| ----------- | ------------------------ | ---------------- |
|
||||
| **CPU** | 1 vCPU | 2 vCPU |
|
||||
| **RAM** | 1 GB | 2 GB |
|
||||
| **Disco** | 10 GB SSD | 25 GB SSD |
|
||||
| **SO** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS |
|
||||
| **Domínio** | Registrado no Cloudflare | — |
|
||||
| **Docker** | Docker Engine 24+ | Docker 27+ |
|
||||
| Item | Minimum | Recommended |
|
||||
| ---------- | ------------------------ | ---------------- |
|
||||
| **CPU** | 1 vCPU | 2 vCPU |
|
||||
| **RAM** | 1 GB | 2 GB |
|
||||
| **Disk** | 10 GB SSD | 25 GB SSD |
|
||||
| **OS** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS |
|
||||
| **Domain** | Registered on Cloudflare | — |
|
||||
| **Docker** | Docker Engine 24+ | Docker 27+ |
|
||||
|
||||
**Providers testados**: Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail.
|
||||
**Tested providers**: Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail.
|
||||
|
||||
---
|
||||
|
||||
## 1. Configurar a VM
|
||||
## 1. Configure the VM
|
||||
|
||||
### 1.1 Criar a instância
|
||||
### 1.1 Create the instance
|
||||
|
||||
No seu provider de VPS preferido:
|
||||
On your preferred VPS provider:
|
||||
|
||||
- Escolha Ubuntu 24.04 LTS
|
||||
- Selecione o plano mínimo (1 vCPU / 1 GB RAM)
|
||||
- Defina uma senha forte para root ou configure SSH key
|
||||
- Anote o **IP público** (ex: `203.0.113.10`)
|
||||
- Choose Ubuntu 24.04 LTS
|
||||
- Select the minimum plan (1 vCPU / 1 GB RAM)
|
||||
- Set a strong root password or configure SSH key
|
||||
- Note the **public IP** (e.g., `203.0.113.10`)
|
||||
|
||||
### 1.2 Conectar via SSH
|
||||
### 1.2 Connect via SSH
|
||||
|
||||
```bash
|
||||
ssh root@203.0.113.10
|
||||
```
|
||||
|
||||
### 1.3 Atualizar o sistema
|
||||
### 1.3 Update the system
|
||||
|
||||
```bash
|
||||
apt update && apt upgrade -y
|
||||
```
|
||||
|
||||
### 1.4 Instalar Docker
|
||||
### 1.4 Install Docker
|
||||
|
||||
```bash
|
||||
# Instalar dependências
|
||||
# Install dependencies
|
||||
apt install -y ca-certificates curl gnupg
|
||||
|
||||
# Adicionar repositório oficial do Docker
|
||||
# Add official Docker repository
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo “$VERSION_CODENAME”) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
apt update
|
||||
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
```
|
||||
|
||||
### 1.5 Instalar nginx
|
||||
### 1.5 Install nginx
|
||||
|
||||
```bash
|
||||
apt install -y nginx
|
||||
```
|
||||
|
||||
### 1.6 Configurar Firewall (UFW)
|
||||
### 1.6 Configure Firewall (UFW)
|
||||
|
||||
```bash
|
||||
ufw default deny incoming
|
||||
@@ -74,29 +76,29 @@ ufw allow 443/tcp # HTTPS
|
||||
ufw enable
|
||||
```
|
||||
|
||||
> **Dica**: Para segurança máxima, restrinja as portas 80 e 443 apenas para IPs da Cloudflare. Veja a seção [Segurança Avançada](#segurança-avançada).
|
||||
> **Tip**: For maximum security, restrict ports 80 and 443 to Cloudflare IPs only. See the [Advanced Security](#advanced-security) section.
|
||||
|
||||
---
|
||||
|
||||
## 2. Instalar o OmniRoute
|
||||
## 2. Install OmniRoute
|
||||
|
||||
### 2.1 Criar diretório de configuração
|
||||
### 2.1 Create configuration directory
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/omniroute
|
||||
```
|
||||
|
||||
### 2.2 Criar arquivo de variáveis de ambiente
|
||||
### 2.2 Create environment variables file
|
||||
|
||||
```bash
|
||||
cat > /opt/omniroute/.env << 'EOF'
|
||||
# === Segurança ===
|
||||
JWT_SECRET=ALTERE-PARA-CHAVE-SECRETA-UNICA-64-CHARS
|
||||
INITIAL_PASSWORD=SuaSenhaSegura123!
|
||||
API_KEY_SECRET=ALTERE-PARA-OUTRA-CHAVE-SECRETA
|
||||
STORAGE_ENCRYPTION_KEY=ALTERE-PARA-TERCEIRA-CHAVE-SECRETA
|
||||
cat > /opt/omniroute/.env << ‘EOF’
|
||||
# === Security ===
|
||||
JWT_SECRET=CHANGE-TO-A-UNIQUE-64-CHAR-SECRET-KEY
|
||||
INITIAL_PASSWORD=YourSecurePassword123!
|
||||
API_KEY_SECRET=REPLACE-WITH-ANOTHER-SECRET-KEY
|
||||
STORAGE_ENCRYPTION_KEY=REPLACE-WITH-THIRD-SECRET-KEY
|
||||
STORAGE_ENCRYPTION_KEY_VERSION=v1
|
||||
MACHINE_ID_SALT=ALTERE-PARA-SALT-UNICO
|
||||
MACHINE_ID_SALT=CHANGE-TO-A-UNIQUE-SALT
|
||||
|
||||
# === App ===
|
||||
PORT=20128
|
||||
@@ -108,19 +110,19 @@ ENABLE_REQUEST_LOGS=true
|
||||
AUTH_COOKIE_SECURE=false
|
||||
REQUIRE_API_KEY=false
|
||||
|
||||
# === Domain (altere para seu domínio) ===
|
||||
# === Domain (change to your domain) ===
|
||||
BASE_URL=https://llms.seudominio.com
|
||||
NEXT_PUBLIC_BASE_URL=https://llms.seudominio.com
|
||||
|
||||
# === Cloud Sync (opcional) ===
|
||||
# === Cloud Sync (optional) ===
|
||||
# CLOUD_URL=https://cloud.omniroute.online
|
||||
# NEXT_PUBLIC_CLOUD_URL=https://cloud.omniroute.online
|
||||
EOF
|
||||
```
|
||||
|
||||
> ⚠️ **IMPORTANTE**: Gere chaves secretas únicas! Use `openssl rand -hex 32` para cada chave.
|
||||
> ⚠️ **IMPORTANT**: Generate unique secret keys! Use `openssl rand -hex 32` for each key.
|
||||
|
||||
### 2.3 Iniciar o container
|
||||
### 2.3 Start the container
|
||||
|
||||
```bash
|
||||
docker pull diegosouzapw/omniroute:latest
|
||||
@@ -134,45 +136,45 @@ docker run -d \
|
||||
diegosouzapw/omniroute:latest
|
||||
```
|
||||
|
||||
### 2.4 Verificar se está rodando
|
||||
### 2.4 Verify that it is running
|
||||
|
||||
```bash
|
||||
docker ps | grep omniroute
|
||||
docker logs omniroute --tail 20
|
||||
```
|
||||
|
||||
Deve exibir: `[DB] SQLite database ready` e `listening on port 20128`.
|
||||
It should display: `[DB] SQLite database ready` and `listening on port 20128`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Configurar nginx (Reverse Proxy)
|
||||
## 3. Configure nginx (Reverse Proxy)
|
||||
|
||||
### 3.1 Gerar certificado SSL (Cloudflare Origin)
|
||||
### 3.1 Generate SSL certificate (Cloudflare Origin)
|
||||
|
||||
No painel da Cloudflare:
|
||||
In the Cloudflare dashboard:
|
||||
|
||||
1. Vá em **SSL/TLS → Origin Server**
|
||||
2. Clique **Create Certificate**
|
||||
3. Deixe os padrões (15 anos, \*.seudominio.com)
|
||||
4. Copie o **Origin Certificate** e a **Private Key**
|
||||
1. Go to **SSL/TLS → Origin Server**
|
||||
2. Click **Create Certificate**
|
||||
3. Keep the defaults (15 years, \*.yourdomain.com)
|
||||
4. Copy the **Origin Certificate** and the **Private Key**
|
||||
|
||||
```bash
|
||||
mkdir -p /etc/nginx/ssl
|
||||
|
||||
# Colar o certificado
|
||||
# Paste the certificate
|
||||
nano /etc/nginx/ssl/origin.crt
|
||||
|
||||
# Colar a chave privada
|
||||
# Paste the private key
|
||||
nano /etc/nginx/ssl/origin.key
|
||||
|
||||
chmod 600 /etc/nginx/ssl/origin.key
|
||||
```
|
||||
|
||||
### 3.2 Configuração do nginx
|
||||
### 3.2 Nginx Configuration
|
||||
|
||||
```bash
|
||||
cat > /etc/nginx/sites-available/omniroute << 'NGINX'
|
||||
# Default server — bloqueia acesso direto por IP
|
||||
cat > /etc/nginx/sites-available/omniroute << ‘NGINX’
|
||||
# Default server — blocks direct access via IP
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
@@ -188,7 +190,7 @@ server {
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name llms.seudominio.com; # Altere para seu domínio
|
||||
server_name llms.yourdomain.com; # Change to your domain
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/origin.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/origin.key;
|
||||
@@ -206,7 +208,7 @@ server {
|
||||
# WebSocket support
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Connection “upgrade”;
|
||||
|
||||
# SSE (Server-Sent Events) — streaming AI responses
|
||||
proxy_buffering off;
|
||||
@@ -220,61 +222,61 @@ server {
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name llms.seudominio.com;
|
||||
server_name llms.yourdomain.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
NGINX
|
||||
```
|
||||
|
||||
### 3.3 Ativar e testar
|
||||
### 3.3 Enable and Test
|
||||
|
||||
```bash
|
||||
# Remover config padrão
|
||||
# Remove default configuration
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
|
||||
# Ativar OmniRoute
|
||||
# Enable OmniRoute
|
||||
ln -sf /etc/nginx/sites-available/omniroute /etc/nginx/sites-enabled/omniroute
|
||||
|
||||
# Testar e recarregar
|
||||
# Test and reload
|
||||
nginx -t && systemctl reload nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Configurar Cloudflare DNS
|
||||
## 4. Configure Cloudflare DNS
|
||||
|
||||
### 4.1 Adicionar registro DNS
|
||||
### 4.1 Add DNS record
|
||||
|
||||
No painel da Cloudflare → DNS:
|
||||
In the Cloudflare dashboard → DNS:
|
||||
|
||||
| Type | Name | Content | Proxy |
|
||||
| ---- | ------ | ------------------------- | ---------- |
|
||||
| A | `llms` | `203.0.113.10` (IP da VM) | ✅ Proxied |
|
||||
| Type | Name | Content | Proxy |
|
||||
| ---- | ------ | ---------------------- | ---------- |
|
||||
| A | `llms` | `203.0.113.10` (VM IP) | ✅ Proxied |
|
||||
|
||||
### 4.2 Configurar SSL
|
||||
### 4.2 Configure SSL
|
||||
|
||||
Em **SSL/TLS → Overview**:
|
||||
Under **SSL/TLS → Overview**:
|
||||
|
||||
- Modo: **Full (Strict)**
|
||||
- Mode: **Full (Strict)**
|
||||
|
||||
Em **SSL/TLS → Edge Certificates**:
|
||||
Under **SSL/TLS → Edge Certificates**:
|
||||
|
||||
- Always Use HTTPS: ✅ On
|
||||
- Minimum TLS Version: TLS 1.2
|
||||
- Automatic HTTPS Rewrites: ✅ On
|
||||
|
||||
### 4.3 Testar
|
||||
### 4.3 Testing
|
||||
|
||||
```bash
|
||||
curl -sI https://llms.seudominio.com/health
|
||||
# Deve retornar HTTP/2 200
|
||||
# Should return HTTP/2 200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Operações e Manutenção
|
||||
## 5. Operations and Maintenance
|
||||
|
||||
### Atualizar para nova versão
|
||||
### Upgrade to a new version
|
||||
|
||||
```bash
|
||||
docker pull diegosouzapw/omniroute:latest
|
||||
@@ -286,42 +288,42 @@ docker run -d --name omniroute --restart unless-stopped \
|
||||
diegosouzapw/omniroute:latest
|
||||
```
|
||||
|
||||
### Ver logs
|
||||
### View logs
|
||||
|
||||
```bash
|
||||
docker logs -f omniroute # Stream em tempo real
|
||||
docker logs omniroute --tail 50 # Últimas 50 linhas
|
||||
docker logs -f omniroute # Real-time stream
|
||||
docker logs omniroute --tail 50 # Last 50 lines
|
||||
```
|
||||
|
||||
### Backup manual do banco
|
||||
### Manual database backup
|
||||
|
||||
```bash
|
||||
# Copiar dados do volume para o host
|
||||
# Copy data from the volume to the host
|
||||
docker cp omniroute:/app/data ./backup-$(date +%F)
|
||||
|
||||
# Ou comprimir todo o volume
|
||||
# Or compress the entire volume
|
||||
docker run --rm -v omniroute-data:/data -v $(pwd):/backup \
|
||||
alpine tar czf /backup/omniroute-data-$(date +%F).tar.gz /data
|
||||
```
|
||||
|
||||
### Restaurar de backup
|
||||
### Restore from backup
|
||||
|
||||
```bash
|
||||
docker stop omniroute
|
||||
docker run --rm -v omniroute-data:/data -v $(pwd):/backup \
|
||||
alpine sh -c "rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /"
|
||||
alpine sh -c “rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /”
|
||||
docker start omniroute
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Segurança Avançada
|
||||
## 6. Advanced Security
|
||||
|
||||
### Restringir nginx para Cloudflare IPs
|
||||
### Restrict nginx to Cloudflare IPs
|
||||
|
||||
```bash
|
||||
cat > /etc/nginx/cloudflare-ips.conf << 'CF'
|
||||
# Cloudflare IPv4 ranges — atualizar periodicamente
|
||||
cat > /etc/nginx/cloudflare-ips.conf << ‘CF’
|
||||
# Cloudflare IPv4 ranges — update periodically
|
||||
# https://www.cloudflare.com/ips-v4/
|
||||
set_real_ip_from 173.245.48.0/20;
|
||||
set_real_ip_from 103.21.244.0/22;
|
||||
@@ -342,7 +344,7 @@ real_ip_header CF-Connecting-IP;
|
||||
CF
|
||||
```
|
||||
|
||||
Adicionar no `nginx.conf` dentro do bloco `http {}`:
|
||||
Add the following to `nginx.conf` inside the `http {}` block:
|
||||
|
||||
```nginx
|
||||
include /etc/nginx/cloudflare-ips.conf;
|
||||
@@ -355,45 +357,45 @@ apt install -y fail2ban
|
||||
systemctl enable fail2ban
|
||||
systemctl start fail2ban
|
||||
|
||||
# Verificar status
|
||||
# Check status
|
||||
fail2ban-client status sshd
|
||||
```
|
||||
|
||||
### Bloquear acesso direto na porta do Docker
|
||||
### Block direct access to the Docker port
|
||||
|
||||
```bash
|
||||
# Impedir acesso externo direto à porta 20128
|
||||
# Prevent direct external access to port 20128
|
||||
iptables -I DOCKER-USER -p tcp --dport 20128 -j DROP
|
||||
iptables -I DOCKER-USER -i lo -p tcp --dport 20128 -j ACCEPT
|
||||
|
||||
# Persistir as regras
|
||||
# Persist the rules
|
||||
apt install -y iptables-persistent
|
||||
netfilter-persistent save
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Deploy do Cloud Worker (Opcional)
|
||||
## 7. Deploy to Cloudflare Workers (Optional)
|
||||
|
||||
Para acesso remoto via Cloudflare Workers (sem expor a VM diretamente):
|
||||
For remote access via Cloudflare Workers (without exposing the VM directly):
|
||||
|
||||
```bash
|
||||
# No repositório local
|
||||
# In the local repository
|
||||
cd omnirouteCloud
|
||||
npm install
|
||||
npx wrangler login
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
Ver documentação completa em [omnirouteCloud/README.md](../omnirouteCloud/README.md).
|
||||
See the full documentation at [omnirouteCloud/README.md](../omnirouteCloud/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Resumo de Portas
|
||||
## Port Summary
|
||||
|
||||
| Porta | Serviço | Acesso |
|
||||
| ----- | ----------- | ----------------------------- |
|
||||
| 22 | SSH | Público (com fail2ban) |
|
||||
| 80 | nginx HTTP | Redirect → HTTPS |
|
||||
| 443 | nginx HTTPS | Via Cloudflare Proxy |
|
||||
| 20128 | OmniRoute | Somente localhost (via nginx) |
|
||||
| Port | Service | Access |
|
||||
| ----- | ----------- | -------------------------- |
|
||||
| 22 | SSH | Public (with fail2ban) |
|
||||
| 80 | nginx HTTP | Redirect → HTTPS |
|
||||
| 443 | nginx HTTPS | Via Cloudflare Proxy |
|
||||
| 20128 | OmniRoute | Localhost only (via nginx) |
|
||||
|
||||
46
docs/adr/0001-proxy-registry-limit-generalization.md
Normal file
46
docs/adr/0001-proxy-registry-limit-generalization.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 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.
|
||||
32
docs/adr/0002-api-error-contract-management-endpoints.md
Normal file
32
docs/adr/0002-api-error-contract-management-endpoints.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 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`.
|
||||
16
docs/adr/0003-security-checklist-proxy-limits.md
Normal file
16
docs/adr/0003-security-checklist-proxy-limits.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# ADR-0003: Security Checklist for Proxy Registry and Usage Controls
|
||||
|
||||
Date: 2026-03-17
|
||||
Status: Accepted
|
||||
|
||||
## Checklist
|
||||
|
||||
- Validate all management payloads with Zod.
|
||||
- Reject malformed scope assignment updates with status 400.
|
||||
- Reject deleting an in-use proxy with status 409 unless forced.
|
||||
- Never expose proxy username/password in list responses by default.
|
||||
- Never log raw credentials or token values.
|
||||
- Keep error responses free from internal stack traces.
|
||||
- Protect management endpoints with existing auth middleware policy.
|
||||
- Audit mutating operations: create/update/delete/assign/migrate.
|
||||
- Ensure resolver fallback to legacy config while migration is in transition.
|
||||
@@ -1,37 +0,0 @@
|
||||
# ADR-001: Next.js as the Foundation for an AI Gateway
|
||||
|
||||
## Status: Accepted
|
||||
|
||||
## Context
|
||||
|
||||
OmniRoute is an AI routing gateway that translates, forwards, and manages requests across 20+ LLM providers. We needed a framework that could serve both the API proxy layer and a management dashboard from a single codebase.
|
||||
|
||||
**Alternatives considered:**
|
||||
|
||||
- **Express.js only** — Simpler proxy, but requires separate frontend tooling
|
||||
- **Fastify** — Fast, but no built-in SSR/dashboard support
|
||||
- **Next.js** — Unified full-stack framework with API routes, SSR, and static pages
|
||||
|
||||
## Decision
|
||||
|
||||
We chose Next.js because:
|
||||
|
||||
1. **Single deployment** — API routes (`/api/*`) and dashboard UI in one process
|
||||
2. **Middleware layer** — Native request interception for auth guards and request tracing
|
||||
3. **File-based routing** — Easy to map provider endpoints to handlers
|
||||
4. **Built-in TypeScript** — Type safety across the entire codebase
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- One `npm run build` produces both API and UI
|
||||
- Middleware provides centralized auth and request tracing
|
||||
- Dashboard gets automatic code splitting and optimization
|
||||
|
||||
**Negative:**
|
||||
|
||||
- Next.js middleware has limitations (no heavy imports, edge runtime constraints)
|
||||
- Serverless deployment model doesn't align with persistent WebSocket/SSE connections
|
||||
- Build times are longer than Express-only setups
|
||||
- The SSE proxy layer (`open-sse/`) operates outside Next.js conventions
|
||||
@@ -1,37 +0,0 @@
|
||||
# ADR-002: Hub-and-Spoke Translation with OpenAI as Intermediate Format
|
||||
|
||||
## Status: Accepted
|
||||
|
||||
## Context
|
||||
|
||||
OmniRoute routes requests across 20+ providers, each with its own API format (OpenAI, Anthropic Messages, Google Gemini, AWS Bedrock, etc.). Direct provider-to-provider translation would require O(n²) translators.
|
||||
|
||||
**Alternatives considered:**
|
||||
|
||||
- **Direct translation** — Each pair needs a dedicated translator (n² complexity)
|
||||
- **Common intermediate format** — Translate to/from a canonical format (2n complexity)
|
||||
- **Protocol buffers** — Strong typing but heavy overhead for a proxy
|
||||
|
||||
## Decision
|
||||
|
||||
We use the **OpenAI Chat Completions format** as the canonical intermediate representation. All incoming requests are normalized to OpenAI format, processed, then translated to the target provider's format.
|
||||
|
||||
```
|
||||
Client → [any format] → OpenAI canonical → [target format] → Provider
|
||||
Provider → [response] → OpenAI canonical → [original format] → Client
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- Only 2 translators per provider (inbound + outbound) instead of n² pairs
|
||||
- OpenAI format is the de facto standard — most clients already use it
|
||||
- Adding a new provider requires only implementing one translator pair
|
||||
- Streaming (SSE) works consistently through the canonical format
|
||||
|
||||
**Negative:**
|
||||
|
||||
- Some provider-specific features may be lost in translation
|
||||
- The double translation adds latency (typically < 5ms)
|
||||
- OpenAI format changes require updating the canonical representation
|
||||
@@ -1,39 +0,0 @@
|
||||
# ADR-003: Dual Storage — SQLite Primary with JSON Migration Path
|
||||
|
||||
## Status: Accepted
|
||||
|
||||
## Context
|
||||
|
||||
OmniRoute originally used LowDB (JSON file) for all persistence. As the project grew, JSON-based storage became a bottleneck for concurrent access, querying, and data integrity.
|
||||
|
||||
**Alternatives considered:**
|
||||
|
||||
- **LowDB only** — Simple but no concurrent access, no ACID, no querying
|
||||
- **SQLite only** — Fast, ACID-compliant, but breaks existing deployments
|
||||
- **PostgreSQL** — Production-grade but requires external dependency
|
||||
- **Dual storage with migration** — SQLite primary + automatic JSON migration
|
||||
|
||||
## Decision
|
||||
|
||||
We migrated to **SQLite as the primary store** with an automatic one-time migration from `db.json`:
|
||||
|
||||
1. On startup, if `db.json` exists and SQLite is empty, auto-migrate all data
|
||||
2. All new reads/writes go through SQLite
|
||||
3. The `db.json` file is preserved but no longer written to
|
||||
|
||||
Settings remain in a hybrid model where LowDB handles simple key-value configuration for backward compatibility.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- ACID transactions for provider connections, API keys, and usage data
|
||||
- Proper SQL queries for analytics and log filtering
|
||||
- Concurrent read/write safety via WAL mode
|
||||
- Zero-downtime migration from JSON — users upgrade transparently
|
||||
|
||||
**Negative:**
|
||||
|
||||
- Two storage engines to maintain (SQLite + LowDB for settings)
|
||||
- Migration code must handle edge cases and partial data
|
||||
- SQLite binary dependency needed in deployment environments
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 01 — Home Page (Dashboard)
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `home`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/page.tsx` | 17 | 0 (wrapper) |
|
||||
| `src/app/(dashboard)/dashboard/HomePageClient.tsx` | 500 | ~25 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
### HomePageClient.tsx
|
||||
| Linha | String EN | Chave i18n | String PT-BR |
|
||||
|-------|-----------|------------|--------------|
|
||||
| 138 | "Quick Start" | `home.quickStart` | "Início Rápido" |
|
||||
| 242 | "Providers Overview" | `home.providersOverview` | "Visão Geral dos Provedores" |
|
||||
| 436 | "No models available for this provider." | `home.noModelsAvailable` | "Nenhum modelo disponível para este provedor." |
|
||||
| — | "Total Requests" | `home.totalRequests` | "Total de Requisições" |
|
||||
| — | "Active Providers" | `home.activeProviders` | "Provedores Ativos" |
|
||||
| — | "Success Rate" | `home.successRate` | "Taxa de Sucesso" |
|
||||
| — | "Avg Latency" | `home.avgLatency` | "Latência Média" |
|
||||
| — | "Configure Endpoint" | `home.configureEndpoint` | "Configurar Endpoint" |
|
||||
| — | "Add Provider" | `home.addProvider` | "Adicionar Provedor" |
|
||||
| — | "View Docs" | `home.viewDocs` | "Ver Documentação" |
|
||||
| — | "Copied!" | `common.copied` | ✅ já existe |
|
||||
| — | "requests" | `home.requests` | "requisições" |
|
||||
| — | "models" | `home.models` | "modelos" |
|
||||
| — | "accounts" | `home.accounts` | "contas" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves no `en.json` (namespace `home`)
|
||||
- [ ] Adicionar traduções no `pt-BR.json`
|
||||
- [ ] Substituir strings por `t()` no `HomePageClient.tsx`
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,25 +0,0 @@
|
||||
# Task 02 — Analytics Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `analytics`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/analytics/page.tsx` | 46 | ~8 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| Linha | String EN | Chave i18n | String PT-BR |
|
||||
|-------|-----------|------------|--------------|
|
||||
| 11 | "Monitor your API usage patterns..." | `analytics.overviewDescription` | "Monitore padrões de uso da API..." |
|
||||
| 13 | "Run evaluation suites to test..." | `analytics.evalsDescription` | "Execute suítes de avaliação..." |
|
||||
| 24 | "Analytics" | `analytics.title` | "Análises" |
|
||||
| 32 | "Overview" | `analytics.overview` | "Visão Geral" |
|
||||
| 33 | "Evals" | `analytics.evals` | "Avaliações" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves no `en.json`
|
||||
- [ ] Adicionar traduções no `pt-BR.json`
|
||||
- [ ] Substituir strings por `t()` em `page.tsx`
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,31 +0,0 @@
|
||||
# Task 03 — API Manager Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `apiManager`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx` | ~400 | ~20 |
|
||||
|
||||
## Strings a Traduzir (levantamento parcial — abrir código para completar)
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "API Keys" | `apiManager.title` | "Chaves de API" |
|
||||
| "Create API Key" | `apiManager.createKey` | "Criar Chave de API" |
|
||||
| "Name" | `apiManager.name` | "Nome" |
|
||||
| "Key" | `apiManager.key` | "Chave" |
|
||||
| "Created" | `apiManager.created` | "Criado em" |
|
||||
| "Last Used" | `apiManager.lastUsed` | "Último Uso" |
|
||||
| "Actions" | `apiManager.actions` | "Ações" |
|
||||
| "Delete" | `common.delete` | ✅ já existe |
|
||||
| "No API keys found" | `apiManager.noKeys` | "Nenhuma chave de API encontrada" |
|
||||
| ~11 strings adicionais | — | Levantar no código |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar todas as strings do `ApiManagerPageClient.tsx`
|
||||
- [ ] Adicionar chaves no `en.json`
|
||||
- [ ] Adicionar traduções no `pt-BR.json`
|
||||
- [ ] Substituir strings por `t()`
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,31 +0,0 @@
|
||||
# Task 04 — Audit Log Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `auditLog`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/audit-log/page.tsx` | 241 | ~12 |
|
||||
| `src/app/(dashboard)/dashboard/logs/AuditLogTab.tsx` | ~100 | ~3 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Audit Log" | `auditLog.title` | "Log de Auditoria" |
|
||||
| "Search actions..." | `auditLog.searchPlaceholder` | "Buscar ações..." |
|
||||
| "Action" | `auditLog.action` | "Ação" |
|
||||
| "Actor" | `auditLog.actor` | "Autor" |
|
||||
| "Target" | `auditLog.target` | "Alvo" |
|
||||
| "Details" | `auditLog.details` | "Detalhes" |
|
||||
| "IP Address" | `auditLog.ipAddress` | "Endereço IP" |
|
||||
| "Timestamp" | `auditLog.timestamp` | "Data/Hora" |
|
||||
| "No audit entries found" | `auditLog.noEntries` | "Nenhum registro de auditoria" |
|
||||
| "Load More" | `auditLog.loadMore` | "Carregar Mais" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves no `en.json`
|
||||
- [ ] Adicionar traduções no `pt-BR.json`
|
||||
- [ ] Substituir strings em `audit-log/page.tsx` e `AuditLogTab.tsx`
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 05 — CLI Tools Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `cliTools`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Strings |
|
||||
|---------|---------|
|
||||
| `cli-tools/components/AntigravityToolCard.tsx` | ~3 |
|
||||
| `cli-tools/components/ClaudeToolCard.tsx` | ~3 |
|
||||
| `cli-tools/components/ClineToolCard.tsx` | ~4 |
|
||||
| `cli-tools/components/CodexToolCard.tsx` | ~3 |
|
||||
| `cli-tools/components/DefaultToolCard.tsx` | ~3 |
|
||||
| `cli-tools/components/DroidToolCard.tsx` | ~2 |
|
||||
| `cli-tools/components/KiloToolCard.tsx` | ~4 |
|
||||
| `cli-tools/components/OpenClawToolCard.tsx` | ~2 |
|
||||
|
||||
## Strings comuns entre Tool Cards
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Status" | `cliTools.status` | "Status" |
|
||||
| "Connected" | `cliTools.connected` | "Conectado" |
|
||||
| "Not Connected" | `cliTools.notConnected` | "Não Conectado" |
|
||||
| "Configure" | `cliTools.configure` | "Configurar" |
|
||||
| "Test Connection" | `cliTools.testConnection` | "Testar Conexão" |
|
||||
| "Models" | `cliTools.models` | "Modelos" |
|
||||
| "Map Models" | `cliTools.mapModels` | "Mapear Modelos" |
|
||||
| "Save" | `common.save` | ✅ já existe |
|
||||
| "Cancel" | `common.cancel` | ✅ já existe |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings de cada ToolCard
|
||||
- [ ] Adicionar chaves no `en.json`
|
||||
- [ ] Adicionar traduções no `pt-BR.json`
|
||||
- [ ] Substituir por `t()` em cada componente
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,34 +0,0 @@
|
||||
# Task 06 — Combos Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `combos`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/combos/page.tsx` | ~1000 | ~20 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| Linha | String EN | Chave i18n | String PT-BR |
|
||||
|-------|-----------|------------|--------------|
|
||||
| 207 | "Combos" | `combos.title` | "Combos" |
|
||||
| 372 | "No models" | `combos.noModels` | "Sem modelos" |
|
||||
| 747 | "Routing Strategy" | `combos.routingStrategy` | "Estratégia de Roteamento" |
|
||||
| 791 | "Models" | `combos.models` | "Modelos" |
|
||||
| 807 | "No models added yet" | `combos.noModelsYet` | "Nenhum modelo adicionado" |
|
||||
| 922 | "Max Retries" | `combos.maxRetries` | "Máximo de Tentativas" |
|
||||
| 959 | "Timeout (ms)" | `combos.timeout` | "Timeout (ms)" |
|
||||
| 977 | "Healthcheck" | `combos.healthcheck` | "Verificação de Saúde" |
|
||||
| — | "Create Combo" | `combos.create` | "Criar Combo" |
|
||||
| — | "Edit Combo" | `combos.edit` | "Editar Combo" |
|
||||
| — | "Delete Combo" | `combos.deleteCombo` | "Excluir Combo" |
|
||||
| — | "Add Model" | `combos.addModel` | "Adicionar Modelo" |
|
||||
| — | "Priority" | `combos.priority` | "Prioridade" |
|
||||
| — | "Fallback" | `combos.fallback` | "Fallback" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves no `en.json`
|
||||
- [ ] Adicionar traduções no `pt-BR.json`
|
||||
- [ ] Substituir strings por `t()` em `combos/page.tsx`
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,23 +0,0 @@
|
||||
# Task 07 — Costs Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `costs`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/costs/page.tsx` | ~200 | ~5 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Costs" | `costs.title` | "Custos" |
|
||||
| "Total Cost" | `costs.totalCost` | "Custo Total" |
|
||||
| "Cost Breakdown" | `costs.breakdown` | "Detalhamento de Custos" |
|
||||
| "No cost data" | `costs.noData` | "Sem dados de custo" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings completas do código
|
||||
- [ ] Adicionar chaves no `en.json` / `pt-BR.json`
|
||||
- [ ] Substituir por `t()` e testar
|
||||
@@ -1,30 +0,0 @@
|
||||
# Task 08 — Endpoint Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `endpoint`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx` | ~750 | ~20 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| Linha | String EN | Chave i18n | String PT-BR |
|
||||
|-------|-----------|------------|--------------|
|
||||
| 320 | "API Endpoint" | `endpoint.title` | "Endpoint da API" |
|
||||
| 403 | "Available Endpoints" | `endpoint.available` | "Endpoints Disponíveis" |
|
||||
| 561 | "Cloud Proxy" | `endpoint.cloudProxy` | "Proxy na Nuvem" |
|
||||
| 632 | "Note" | `endpoint.note` | "Nota" |
|
||||
| 717 | "Warning" | `endpoint.warning` | "Aviso" |
|
||||
| 740 | "Are you sure you want to disable cloud proxy?" | `endpoint.disableConfirm` | "Tem certeza que deseja desativar o proxy na nuvem?" |
|
||||
| — | "Copy" | `common.copy` | ✅ já existe |
|
||||
| — | "Base URL" | `endpoint.baseUrl` | "URL Base" |
|
||||
| — | "Connected" | `endpoint.connected` | "Conectado" |
|
||||
| — | "Enable" | `endpoint.enable` | "Ativar" |
|
||||
| — | "Disable" | `endpoint.disable` | "Desativar" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings restantes
|
||||
- [ ] Adicionar chaves no `en.json` / `pt-BR.json`
|
||||
- [ ] Substituir por `t()` e testar
|
||||
@@ -1,30 +0,0 @@
|
||||
# Task 09 — Health Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `health`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/health/page.tsx` | ~350 | ~15 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "System Health" | `health.title` | "Saúde do Sistema" |
|
||||
| "Healthy" | `health.healthy` | "Saudável" |
|
||||
| "Degraded" | `health.degraded` | "Degradado" |
|
||||
| "Down" | `health.down` | "Offline" |
|
||||
| "Uptime" | `health.uptime` | "Tempo Ativo" |
|
||||
| "Memory" | `health.memory` | "Memória" |
|
||||
| "CPU" | `health.cpu` | "CPU" |
|
||||
| "Database" | `health.database` | "Banco de Dados" |
|
||||
| "Last Check" | `health.lastCheck` | "Última Verificação" |
|
||||
| "Refresh" | `common.refresh` | ✅ já existe |
|
||||
| ~5 strings adicionais | — | Levantar |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings restantes
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` e testar
|
||||
@@ -1,24 +0,0 @@
|
||||
# Task 10 — Limits Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `limits`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/limits/page.tsx` | ~150 | ~5 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Limits & Quotas" | `limits.title` | "Limites e Cotas" |
|
||||
| "Rate Limit" | `limits.rateLimit` | "Limite de Taxa" |
|
||||
| "Provider" | `limits.provider` | "Provedor" |
|
||||
| "Remaining" | `limits.remaining` | "Restante" |
|
||||
| "Reset" | `limits.reset` | "Reiniciar" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings completas
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` e testar
|
||||
@@ -1,24 +0,0 @@
|
||||
# Task 11 — Logs Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `logs`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/logs/` | ~200 | ~5 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Logs" | `logs.title` | "Logs" |
|
||||
| "Request Logs" | `logs.requestLogs` | "Logs de Requisições" |
|
||||
| "Proxy Logs" | `logs.proxyLogs` | "Logs do Proxy" |
|
||||
| "Audit Log" | `logs.auditLog` | "Log de Auditoria" |
|
||||
| "Console" | `logs.console` | "Console" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings completas
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` e testar
|
||||
@@ -1,26 +0,0 @@
|
||||
# Task 12 — Onboarding Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `onboarding`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Linhas | Strings |
|
||||
|---------|--------|---------|
|
||||
| `src/app/(dashboard)/dashboard/onboarding/page.tsx` | ~300 | ~10 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Welcome to OmniRoute" | `onboarding.welcome` | "Bem-vindo ao OmniRoute" |
|
||||
| "Set Password" | `onboarding.setPassword` | "Definir Senha" |
|
||||
| "Add Provider" | `onboarding.addProvider` | "Adicionar Provedor" |
|
||||
| "Get Started" | `onboarding.getStarted` | "Começar" |
|
||||
| "Skip" | `onboarding.skip` | "Pular" |
|
||||
| "Next" | `common.next` | ✅ já existe |
|
||||
| ~4 strings adicionais | — | Levantar |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings completas
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` e testar
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 13 — Providers Pages
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `providers`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Strings |
|
||||
|---------|---------|
|
||||
| `providers/page.tsx` | ~5 |
|
||||
| `providers/[id]/page.tsx` | ~12 |
|
||||
| `providers/new/page.tsx` | ~3 |
|
||||
| `providers/components/ModelAvailabilityPanel.tsx` | ~3 |
|
||||
| `providers/components/ModelAvailabilityBadge.tsx` | ~1 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Providers" | `providers.title` | "Provedores" |
|
||||
| "Add Provider" | `providers.add` | "Adicionar Provedor" |
|
||||
| "Edit Provider" | `providers.edit` | "Editar Provedor" |
|
||||
| "Test Connection" | `providers.testConnection` | "Testar Conexão" |
|
||||
| "Connected" | `providers.connected` | "Conectado" |
|
||||
| "Disconnected" | `providers.disconnected` | "Desconectado" |
|
||||
| "Models" | `providers.models` | "Modelos" |
|
||||
| "Accounts" | `providers.accounts` | "Contas" |
|
||||
| "Delete Provider" | `providers.deleteProvider` | "Excluir Provedor" |
|
||||
| "No providers configured" | `providers.noProviders` | "Nenhum provedor configurado" |
|
||||
| "Model Availability" | `providers.modelAvailability` | "Disponibilidade de Modelos" |
|
||||
| ~9 strings adicionais | — | Levantar |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings completas de todos os arquivos
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` e testar
|
||||
@@ -1,51 +0,0 @@
|
||||
# Task 14 — Settings Page (MAIOR TAREFA)
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `settings`
|
||||
|
||||
## Arquivos (20 componentes!)
|
||||
| Arquivo | Strings |
|
||||
|---------|---------|
|
||||
| `settings/components/AppearanceTab.tsx` | ~4 |
|
||||
| `settings/components/CacheStatsCard.tsx` | ~4 |
|
||||
| `settings/components/ComboDefaultsTab.tsx` | ~8 |
|
||||
| `settings/components/FallbackChainsEditor.tsx` | ~2 |
|
||||
| `settings/components/IPFilterSection.tsx` | ~2 |
|
||||
| `settings/components/PoliciesPanel.tsx` | ~5 |
|
||||
| `settings/components/PricingTab.tsx` | ~8 |
|
||||
| `settings/components/ProxyTab.tsx` | ~2 |
|
||||
| `settings/components/ResilienceTab.tsx` | ~7 |
|
||||
| `settings/components/RoutingTab.tsx` | ~4 |
|
||||
| `settings/components/SecurityTab.tsx` | ~5 |
|
||||
| `settings/components/SessionInfoCard.tsx` | ~5 |
|
||||
| `settings/components/SystemPromptTab.tsx` | ~2 |
|
||||
| `settings/components/SystemStorageTab.tsx` | ~8 |
|
||||
| `settings/components/ThinkingBudgetTab.tsx` | ~5 |
|
||||
| `settings/pricing/page.tsx` | ~17 |
|
||||
|
||||
## Strings a Traduzir (amostra)
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "General" | `settings.general` | "Geral" |
|
||||
| "Security" | `settings.security` | "Segurança" |
|
||||
| "Appearance" | `settings.appearance` | "Aparência" |
|
||||
| "Routing" | `settings.routing` | "Roteamento" |
|
||||
| "Cache" | `settings.cache` | "Cache" |
|
||||
| "Resilience" | `settings.resilience` | "Resiliência" |
|
||||
| "System Prompt" | `settings.systemPrompt` | "Prompt do Sistema" |
|
||||
| "Thinking Budget" | `settings.thinkingBudget` | "Orçamento de Raciocínio" |
|
||||
| "Proxy" | `settings.proxy` | "Proxy" |
|
||||
| "Pricing" | `settings.pricing` | "Preços" |
|
||||
| "Storage" | `settings.storage` | "Armazenamento" |
|
||||
| "Policies" | `settings.policies` | "Políticas" |
|
||||
| "IP Filter" | `settings.ipFilter` | "Filtro de IP" |
|
||||
| "Combo Defaults" | `settings.comboDefaults` | "Padrões de Combo" |
|
||||
| "Fallback Chains" | `settings.fallbackChains` | "Cadeias de Fallback" |
|
||||
| ~40 strings adicionais | — | Levantar em cada tab |
|
||||
|
||||
## Checklist
|
||||
- [ ] Levantar strings de CADA componente (16 arquivos)
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` em todos os 16 arquivos
|
||||
- [ ] Testar cada aba em EN e PT-BR
|
||||
@@ -1,41 +0,0 @@
|
||||
# Task 15 — Translator Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `translator`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Strings |
|
||||
|---------|---------|
|
||||
| `translator/components/LiveMonitorMode.tsx` | ~11 |
|
||||
| `translator/components/PlaygroundMode.tsx` | ~5 |
|
||||
| `translator/components/TestBenchMode.tsx` | ~3 |
|
||||
| `translator/components/ChatTesterMode.tsx` | ~4 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Real-Time Translation Activity" | `translator.realtime` | "Atividade de Tradução em Tempo Real" |
|
||||
| "Chat Tester" | `translator.chatTester` | "Testador de Chat" |
|
||||
| "Test Bench" | `translator.testBench` | "Bancada de Testes" |
|
||||
| "Recent Translations" | `translator.recentTranslations` | "Traduções Recentes" |
|
||||
| "No translations yet" | `translator.noTranslations` | "Nenhuma tradução ainda" |
|
||||
| "Time" | `translator.time` | "Tempo" |
|
||||
| "Source" | `translator.source` | "Origem" |
|
||||
| "Target" | `translator.target` | "Destino" |
|
||||
| "Model" | `translator.model` | "Modelo" |
|
||||
| "Status" | `translator.status` | "Status" |
|
||||
| "Latency" | `translator.latency` | "Latência" |
|
||||
| "Format Converter" | `translator.formatConverter` | "Conversor de Formato" |
|
||||
| "Input" | `translator.input` | "Entrada" |
|
||||
| "Output" | `translator.output` | "Saída" |
|
||||
| "Example Templates" | `translator.exampleTemplates` | "Modelos de Exemplo" |
|
||||
| "Compatibility Tester" | `translator.compatibilityTester` | "Testador de Compatibilidade" |
|
||||
| "Compatibility Report" | `translator.compatibilityReport` | "Relatório de Compatibilidade" |
|
||||
| "Pipeline Debugger" | `translator.pipelineDebugger` | "Depurador de Pipeline" |
|
||||
| "Translation Pipeline" | `translator.translationPipeline` | "Pipeline de Tradução" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` em cada componente
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,57 +0,0 @@
|
||||
# Task 16 — Usage Page
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `usage`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Strings |
|
||||
|---------|---------|
|
||||
| `usage/components/BudgetTab.tsx` | ~4 |
|
||||
| `usage/components/BudgetTelemetryCards.tsx` | ~9 |
|
||||
| `usage/components/EvalsTab.tsx` | ~6 |
|
||||
| `usage/components/RateLimitStatus.tsx` | ~2 |
|
||||
| `usage/components/SessionsTab.tsx` | ~7 |
|
||||
| `usage/components/ProviderLimits/index.tsx` | ~7 |
|
||||
| `usage/components/ProviderLimits/ProviderLimitCard.tsx` | ~1 |
|
||||
| `usage/components/ProviderLimits/QuotaTable.tsx` | ~1 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | Chave i18n | String PT-BR |
|
||||
|-----------|------------|--------------|
|
||||
| "Budget Management" | `usage.budgetManagement` | "Gerenciamento de Orçamento" |
|
||||
| "API Key" | `usage.apiKey` | "Chave de API" |
|
||||
| "This Month" | `usage.thisMonth` | "Este Mês" |
|
||||
| "Set Limits" | `usage.setLimits` | "Definir Limites" |
|
||||
| "Total requests" | `usage.totalRequests` | "Total de requisições" |
|
||||
| "No data yet" | `usage.noData` | "Sem dados ainda" |
|
||||
| "Entries" | `usage.entries` | "Entradas" |
|
||||
| "Hit Rate" | `usage.hitRate` | "Taxa de Acerto" |
|
||||
| "Circuit Breakers" | `usage.circuitBreakers` | "Disjuntores" |
|
||||
| "Locked IPs" | `usage.lockedIPs` | "IPs Bloqueados" |
|
||||
| "How It Works" | `usage.howItWorks` | "Como Funciona" |
|
||||
| "Define" | `usage.define` | "Definir" |
|
||||
| "Run" | `usage.run` | "Executar" |
|
||||
| "Evaluate" | `usage.evaluate` | "Avaliar" |
|
||||
| "Evaluation Suites" | `usage.evalSuites` | "Suítes de Avaliação" |
|
||||
| "Model Evaluations" | `usage.modelEvals` | "Avaliações de Modelos" |
|
||||
| "Model Lockouts" | `usage.modelLockouts` | "Bloqueios de Modelo" |
|
||||
| "No models currently locked" | `usage.noLockouts` | "Nenhum modelo bloqueado" |
|
||||
| "Active Sessions" | `usage.activeSessions` | "Sessões Ativas" |
|
||||
| "No active sessions" | `usage.noSessions` | "Sem sessões ativas" |
|
||||
| "Session" | `usage.session` | "Sessão" |
|
||||
| "Age" | `usage.age` | "Idade" |
|
||||
| "Requests" | `usage.requests` | "Requisições" |
|
||||
| "Connection" | `usage.connection` | "Conexão" |
|
||||
| "Provider Limits" | `usage.providerLimits` | "Limites do Provedor" |
|
||||
| "No Providers Connected" | `usage.noProviders` | "Nenhum Provedor Conectado" |
|
||||
| "Account" | `usage.account` | "Conta" |
|
||||
| "Model Quotas" | `usage.modelQuotas` | "Cotas de Modelo" |
|
||||
| "Last Used" | `usage.lastUsed` | "Último Uso" |
|
||||
| "Actions" | `usage.actions` | "Ações" |
|
||||
| "No quota data" | `usage.noQuota` | "Sem dados de cota" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` em cada componente
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,44 +0,0 @@
|
||||
# Task 17 — Shared Modals
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `modals`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Strings |
|
||||
|---------|---------|
|
||||
| `shared/components/OAuthModal.tsx` | ~6 |
|
||||
| `shared/components/KiroAuthModal.tsx` | ~10 |
|
||||
| `shared/components/KiroSocialOAuthModal.tsx` | ~3 |
|
||||
| `shared/components/CursorAuthModal.tsx` | ~2 |
|
||||
| `shared/components/PricingModal.tsx` | ~10 |
|
||||
| `shared/components/ModelSelectModal.tsx` | ~2 |
|
||||
| `shared/components/ProxyConfigModal.tsx` | ~1 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | String PT-BR |
|
||||
|-----------|--------------|
|
||||
| "Waiting for Authorization" | "Aguardando Autorização" |
|
||||
| "Verification URL" | "URL de Verificação" |
|
||||
| "Your Code" | "Seu Código" |
|
||||
| "Remote access:" | "Acesso remoto:" |
|
||||
| "Connected Successfully!" | "Conectado com Sucesso!" |
|
||||
| "Connection Failed" | "Falha na Conexão" |
|
||||
| "Choose your authentication method:" | "Escolha seu método de autenticação:" |
|
||||
| "AWS Builder ID" | "AWS Builder ID" |
|
||||
| "AWS IAM Identity Center" | "AWS IAM Identity Center" |
|
||||
| "Google Account" | "Conta Google" |
|
||||
| "GitHub Account" | "Conta GitHub" |
|
||||
| "Import Token" | "Importar Token" |
|
||||
| "Auto-detecting tokens..." | "Detectando tokens automaticamente..." |
|
||||
| "Pricing Configuration" | "Configuração de Preços" |
|
||||
| "Loading pricing data..." | "Carregando dados de preços..." |
|
||||
| "Model" / "Input" / "Output" / "Cached" | "Modelo" / "Entrada" / "Saída" / "Em Cache" |
|
||||
| "Combos" | "Combos" |
|
||||
| "No models found" | "Nenhum modelo encontrado" |
|
||||
| "Connected" | "Conectado" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` em cada modal
|
||||
- [ ] Testar cada modal em EN e PT-BR
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 18 — Shared Loggers
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `loggers`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Strings |
|
||||
|---------|---------|
|
||||
| `shared/components/RequestLoggerV2.tsx` | ~12 |
|
||||
| `shared/components/RequestLoggerDetail.tsx` | ~4 |
|
||||
| `shared/components/ProxyLogger.tsx` | ~7 |
|
||||
| `shared/components/ProxyLogDetail.tsx` | ~6 |
|
||||
| `shared/components/ConsoleLogViewer.tsx` | ~2 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | String PT-BR |
|
||||
|-----------|--------------|
|
||||
| "All Providers" | "Todos os Provedores" |
|
||||
| "All Models" | "Todos os Modelos" |
|
||||
| "All Accounts" | "Todas as Contas" |
|
||||
| "All API Keys" | "Todas as Chaves de API" |
|
||||
| "Newest" / "Oldest" | "Mais Recente" / "Mais Antigo" |
|
||||
| "Model A-Z" / "Model Z-A" | "Modelo A-Z" / "Modelo Z-A" |
|
||||
| "Columns" | "Colunas" |
|
||||
| "Loading logs..." | "Carregando logs..." |
|
||||
| "All Types" | "Todos os Tipos" |
|
||||
| "All Levels" | "Todos os Níveis" |
|
||||
| "Proxy Event" | "Evento do Proxy" |
|
||||
| "Time" / "Model" / "Combo" | "Tempo" / "Modelo" / "Combo" |
|
||||
| "No log entries found" | "Nenhuma entrada de log encontrada" |
|
||||
| "No payload data available" | "Nenhum dado de payload disponível" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` em cada logger
|
||||
- [ ] Testar em EN e PT-BR
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 19 — Shared Charts & Stats
|
||||
|
||||
**Status:** `[ ]` Não iniciado
|
||||
**Namespace JSON:** `stats`
|
||||
|
||||
## Arquivos
|
||||
| Arquivo | Strings |
|
||||
|---------|---------|
|
||||
| `shared/components/UsageStats.tsx` | ~6 |
|
||||
| `shared/components/analytics/charts.tsx` | ~15 |
|
||||
| `shared/components/TokenHealthBadge.tsx` | ~6 |
|
||||
| `shared/components/SystemMonitor.tsx` | ~1 |
|
||||
| `shared/components/Footer.tsx` | ~3 |
|
||||
|
||||
## Strings a Traduzir
|
||||
|
||||
| String EN | String PT-BR |
|
||||
|-----------|--------------|
|
||||
| "Usage Overview" | "Visão Geral de Uso" |
|
||||
| "Output Tokens" | "Tokens de Saída" |
|
||||
| "Total Cost" | "Custo Total" |
|
||||
| "Usage by Model" | "Uso por Modelo" |
|
||||
| "Usage by Account" | "Uso por Conta" |
|
||||
| "Failed to load usage statistics." | "Falha ao carregar estatísticas." |
|
||||
| "Token Health" | "Saúde dos Tokens" |
|
||||
| "Total OAuth" | "Total OAuth" |
|
||||
| "Healthy" / "Errored" / "Warning" | "Saudável" / "Com Erro" / "Aviso" |
|
||||
| "Last check" | "Última verificação" |
|
||||
| "No data" / "Share" | "Sem dados" / "Compartilhar" |
|
||||
| "Unable to load system metrics" | "Não foi possível carregar métricas" |
|
||||
| "Product" / "Resources" / "Company" (Footer) | "Produto" / "Recursos" / "Empresa" |
|
||||
|
||||
## Checklist
|
||||
- [ ] Adicionar chaves / traduções
|
||||
- [ ] Substituir por `t()` em cada componente
|
||||
- [ ] Testar em EN e PT-BR
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user