mirror of
https://github.com/Aider-AI/aider
synced 2026-05-05 22:52:16 +02:00
Compare commits
2438 Commits
v0.74.2.de
...
v0.85.4.de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c23ebfe688 | ||
|
|
9d778bfdac | ||
|
|
70f2bbb796 | ||
|
|
6c7870dbcf | ||
|
|
ece9803fdc | ||
|
|
ad39fdb2d1 | ||
|
|
8904e2966d | ||
|
|
3c9e180b54 | ||
|
|
1af0e59149 | ||
|
|
3402b151f7 | ||
|
|
f38200c511 | ||
|
|
89ad2ba2cb | ||
|
|
9d6ddcd0fc | ||
|
|
915ebffc8e | ||
|
|
b336dee9b0 | ||
|
|
853532c48c | ||
|
|
1a0ef64011 | ||
|
|
fe3f77176e | ||
|
|
2a18a186b4 | ||
|
|
102f6ef284 | ||
|
|
90dffa9eae | ||
|
|
63d3dbcc9b | ||
|
|
c3f0bdd391 | ||
|
|
c24c2c862d | ||
|
|
7bc2e4e911 | ||
|
|
f7870b6d03 | ||
|
|
bd78b9fe9d | ||
|
|
eab51242bb | ||
|
|
6a28864c22 | ||
|
|
ef59ecbcd8 | ||
|
|
6c16498de1 | ||
|
|
f22fbf9b3a | ||
|
|
1a57730884 | ||
|
|
0967024304 | ||
|
|
456db697f0 | ||
|
|
3db4d378eb | ||
|
|
02c27732af | ||
|
|
966cf2b9fb | ||
|
|
ac46e14ce4 | ||
|
|
9c9c6fe0b8 | ||
|
|
59a5190267 | ||
|
|
302b0cb5f9 | ||
|
|
f4605b2a86 | ||
|
|
3fec90340b | ||
|
|
531838096b | ||
|
|
540b27b577 | ||
|
|
4f4f00f37c | ||
|
|
a544112ff3 | ||
|
|
e0e2cb109a | ||
|
|
66cdfdefd5 | ||
|
|
d5785b57a4 | ||
|
|
ae539fb1f5 | ||
|
|
f5a512ba65 | ||
|
|
c48fea64a1 | ||
|
|
68f05f5b4f | ||
|
|
323910be11 | ||
|
|
19a7864168 | ||
|
|
320ee06cc3 | ||
|
|
8fe52d7b0d | ||
|
|
338cfb46e4 | ||
|
|
63c92771f2 | ||
|
|
a0ffc5761c | ||
|
|
d47cb40518 | ||
|
|
d9e3ede000 | ||
|
|
ba97c83be6 | ||
|
|
22cdacc8e2 | ||
|
|
15806aa6ab | ||
|
|
74ee340101 | ||
|
|
63a7d261ae | ||
|
|
e2d3fc4594 | ||
|
|
14af218ea2 | ||
|
|
5b317e5ec0 | ||
|
|
75f1a33292 | ||
|
|
32cdb7cfad | ||
|
|
d022f4ac63 | ||
|
|
b787e17924 | ||
|
|
5e9daa3c56 | ||
|
|
d5ae9eff88 | ||
|
|
856d94c1dc | ||
|
|
fb4d2f90c1 | ||
|
|
d85078a610 | ||
|
|
f695e71398 | ||
|
|
d90936662b | ||
|
|
6a00d8ff5f | ||
|
|
c4b9f14b90 | ||
|
|
a785b0f463 | ||
|
|
90ecde4da9 | ||
|
|
37b7a7b44f | ||
|
|
89356e897e | ||
|
|
05ca9e5c24 | ||
|
|
7c9cff2f6e | ||
|
|
f9fc2c6a44 | ||
|
|
20429b6852 | ||
|
|
52d04430db | ||
|
|
f16110717b | ||
|
|
a2d345fe3d | ||
|
|
1bdd4f0269 | ||
|
|
9188cedc72 | ||
|
|
fdb49e18cd | ||
|
|
ae927d85f0 | ||
|
|
a1c2eeb88a | ||
|
|
caf212c8d1 | ||
|
|
a29ae3ef37 | ||
|
|
90f9c813a0 | ||
|
|
9fdc6d4a44 | ||
|
|
bb1b9e8e2d | ||
|
|
0c480b7ea4 | ||
|
|
3cb120e0a9 | ||
|
|
1677db3ca7 | ||
|
|
ae94521242 | ||
|
|
f8855ebc58 | ||
|
|
262117d124 | ||
|
|
72c23800a9 | ||
|
|
e91efda8fe | ||
|
|
1daeb01ff0 | ||
|
|
2df4beb6e9 | ||
|
|
e54ac087cb | ||
|
|
17d40a62c9 | ||
|
|
67e190c8d1 | ||
|
|
5562caae0c | ||
|
|
d55beb5f24 | ||
|
|
df5b780d6c | ||
|
|
3e07d068ad | ||
|
|
47ddce3e1b | ||
|
|
e256ffd2c6 | ||
|
|
7a83f038d8 | ||
|
|
990a0566bb | ||
|
|
0ac6068e1d | ||
|
|
1953c9815a | ||
|
|
cc8be1453f | ||
|
|
789aab8417 | ||
|
|
226e23f06d | ||
|
|
150711d7e0 | ||
|
|
8d48def24d | ||
|
|
4c50fc6d69 | ||
|
|
e67c9327fd | ||
|
|
837b8a93e9 | ||
|
|
4c161f9e12 | ||
|
|
f827f22f7a | ||
|
|
3064477cb8 | ||
|
|
a7ccdaf279 | ||
|
|
2bc71cfd71 | ||
|
|
6b7a0565e9 | ||
|
|
8c1ae95f87 | ||
|
|
c0509add21 | ||
|
|
77472e5913 | ||
|
|
836aaece4f | ||
|
|
c4fcc5ad70 | ||
|
|
b259226770 | ||
|
|
db0f7d158d | ||
|
|
b9a9b4cf61 | ||
|
|
29874f1222 | ||
|
|
7897d027d4 | ||
|
|
09b2d49f11 | ||
|
|
295122fc97 | ||
|
|
fa0aa9459b | ||
|
|
c67f6905a5 | ||
|
|
3266eaca91 | ||
|
|
bfaad12cac | ||
|
|
395188043b | ||
|
|
484b8a3603 | ||
|
|
6eaf75f760 | ||
|
|
91f34e37f7 | ||
|
|
7ffd9c1859 | ||
|
|
0bb0f169d2 | ||
|
|
45ad3cdf47 | ||
|
|
fc30409f74 | ||
|
|
6d872b6dc0 | ||
|
|
6fdc956b9e | ||
|
|
196721d27d | ||
|
|
e331a967a6 | ||
|
|
48376e59c2 | ||
|
|
52510c7da5 | ||
|
|
c24798c44f | ||
|
|
6085be5883 | ||
|
|
05c56fe904 | ||
|
|
a7afbd0708 | ||
|
|
3f2c403cf0 | ||
|
|
d7504bed21 | ||
|
|
119a44debe | ||
|
|
87dee0a5f2 | ||
|
|
1d0e463d83 | ||
|
|
8304029b92 | ||
|
|
ef2986a231 | ||
|
|
b79a777936 | ||
|
|
9c9eedd9c5 | ||
|
|
ebaad9d865 | ||
|
|
d922023815 | ||
|
|
acebc11237 | ||
|
|
214b811ef9 | ||
|
|
de9df51b47 | ||
|
|
3194a35230 | ||
|
|
a8568c3c4f | ||
|
|
114ec42563 | ||
|
|
f7df96d224 | ||
|
|
79edb0e1e0 | ||
|
|
5a0951caaf | ||
|
|
6b2bcf651e | ||
|
|
fea0ff189f | ||
|
|
803a8db60c | ||
|
|
414b4e3882 | ||
|
|
a17599152f | ||
|
|
7b9d8e6ba7 | ||
|
|
9ef3211365 | ||
|
|
d9bf69041c | ||
|
|
e3cb907767 | ||
|
|
ef3f8bb301 | ||
|
|
03a489ea35 | ||
|
|
81389b87d7 | ||
|
|
0d8ff295d6 | ||
|
|
6176a8dee3 | ||
|
|
299e6ae7a2 | ||
|
|
0b1d49d630 | ||
|
|
037a36edba | ||
|
|
66bc9cf292 | ||
|
|
2b9e669930 | ||
|
|
cb88b7e62a | ||
|
|
4e9943f2aa | ||
|
|
999c292482 | ||
|
|
9f5018e89e | ||
|
|
59dbce8575 | ||
|
|
3caab85931 | ||
|
|
756372809e | ||
|
|
6aa05ab11c | ||
|
|
9cf373039e | ||
|
|
bc1272f029 | ||
|
|
0049e78250 | ||
|
|
56b45ce1d3 | ||
|
|
bdd67eb229 | ||
|
|
57020a2d5e | ||
|
|
6b9045a2a2 | ||
|
|
5f24a0013a | ||
|
|
b79052501d | ||
|
|
9e0d7d9c46 | ||
|
|
a53ab7d937 | ||
|
|
c055602c6f | ||
|
|
170e8fc9a1 | ||
|
|
ee177054b8 | ||
|
|
f018b5fab5 | ||
|
|
5a29ba03dc | ||
|
|
035d99d3d3 | ||
|
|
702eff1033 | ||
|
|
97f3885357 | ||
|
|
f8653613bc | ||
|
|
b1d47c47d9 | ||
|
|
2c4a126093 | ||
|
|
cdd1546243 | ||
|
|
6a3bb0f4ec | ||
|
|
24c0fbd326 | ||
|
|
7b9eae117f | ||
|
|
512b4d891b | ||
|
|
a6b0f43dce | ||
|
|
e8d9ae9a1f | ||
|
|
2ab0074915 | ||
|
|
225e01717c | ||
|
|
4d39b88110 | ||
|
|
5052150e2e | ||
|
|
d8fbd9cbd3 | ||
|
|
53cda2cc10 | ||
|
|
543e5570ae | ||
|
|
62c7e15a36 | ||
|
|
17a2773a22 | ||
|
|
b8758ca791 | ||
|
|
bf9522a2fb | ||
|
|
ddc8621d6e | ||
|
|
7875de078a | ||
|
|
ea1189b8ec | ||
|
|
1127b8b559 | ||
|
|
64f218a06e | ||
|
|
efde8e867e | ||
|
|
f815f0377e | ||
|
|
883aa9e03d | ||
|
|
2a410fab81 | ||
|
|
34409311a3 | ||
|
|
97379aa02f | ||
|
|
ee4e9c9711 | ||
|
|
7d3c817664 | ||
|
|
8c755bf032 | ||
|
|
0b112e948f | ||
|
|
c11d21a230 | ||
|
|
a9cb1a9d61 | ||
|
|
43cd0164e0 | ||
|
|
49b3f85cc5 | ||
|
|
3daf7d4df3 | ||
|
|
3dcb23c193 | ||
|
|
cad31b638b | ||
|
|
7fbe0d25f5 | ||
|
|
637a31e083 | ||
|
|
f928ffc3fc | ||
|
|
23cb604e6e | ||
|
|
09880ee8f4 | ||
|
|
425fb6d7a8 | ||
|
|
28d87767cd | ||
|
|
ed262b8b06 | ||
|
|
7f30320566 | ||
|
|
9d74e8c730 | ||
|
|
1b2eeaff56 | ||
|
|
0632c7a90f | ||
|
|
c806f18698 | ||
|
|
91d7fbd659 | ||
|
|
fcc85a7ae6 | ||
|
|
dbfba029af | ||
|
|
88fba5f20b | ||
|
|
f7a073961c | ||
|
|
f8c154edce | ||
|
|
c6ad5c8cd2 | ||
|
|
af9ae849bd | ||
|
|
64b4d13880 | ||
|
|
6620141420 | ||
|
|
d79bc2c05b | ||
|
|
9978f6c51e | ||
|
|
5be642fbec | ||
|
|
9f1ef3f49f | ||
|
|
a3562d1d62 | ||
|
|
4e608dbd77 | ||
|
|
3f49acf390 | ||
|
|
77deb35022 | ||
|
|
1a7960810c | ||
|
|
766a41d5de | ||
|
|
df967e4b41 | ||
|
|
781ed90653 | ||
|
|
b9885bb76d | ||
|
|
11480f6110 | ||
|
|
2bc9386876 | ||
|
|
04cbe87caa | ||
|
|
4c959f4542 | ||
|
|
8652fcf86e | ||
|
|
23714d7db6 | ||
|
|
81b86441fd | ||
|
|
edb3bf84cc | ||
|
|
4d5852a30e | ||
|
|
7a5877ea50 | ||
|
|
52ae22bcf8 | ||
|
|
4fb2d78011 | ||
|
|
c93c22ec98 | ||
|
|
a26a3145ba | ||
|
|
055a3d795a | ||
|
|
2d34b738bc | ||
|
|
292aa9bded | ||
|
|
4e86a82a08 | ||
|
|
784ac79da1 | ||
|
|
647f556582 | ||
|
|
aad6838e15 | ||
|
|
95cc362c07 | ||
|
|
9ef506dc25 | ||
|
|
b236e0c801 | ||
|
|
c706663841 | ||
|
|
d7e091f315 | ||
|
|
37601eb4b7 | ||
|
|
a22772b388 | ||
|
|
befff1f22e | ||
|
|
0864a7ca76 | ||
|
|
01592afac3 | ||
|
|
3a5a46253d | ||
|
|
5bb891b2bb | ||
|
|
18f702b95a | ||
|
|
e6a35be5b7 | ||
|
|
6351964bcd | ||
|
|
ede3061fe0 | ||
|
|
f1121e3b7c | ||
|
|
a1cb86dca3 | ||
|
|
cf1d58745e | ||
|
|
98dc8e5d57 | ||
|
|
21a05ead4e | ||
|
|
80f78ee85d | ||
|
|
540b2519c2 | ||
|
|
d3931f67ca | ||
|
|
b6a32d8682 | ||
|
|
023e939798 | ||
|
|
38e7f04e60 | ||
|
|
b40baaceea | ||
|
|
ff549cf9ba | ||
|
|
2c1685bb36 | ||
|
|
2a61494442 | ||
|
|
0af5563e77 | ||
|
|
c147571b18 | ||
|
|
311981f4e5 | ||
|
|
79923c954b | ||
|
|
0b4430f228 | ||
|
|
ee9ad75509 | ||
|
|
920b20b17d | ||
|
|
9297ee982d | ||
|
|
1d5c3c3a2b | ||
|
|
217b45ae88 | ||
|
|
1f6f480864 | ||
|
|
40a5a88d56 | ||
|
|
30097ab859 | ||
|
|
09acfc8147 | ||
|
|
a2ecc5883b | ||
|
|
d127d45669 | ||
|
|
2ebb2103b8 | ||
|
|
c3d4fdb4c1 | ||
|
|
e1ab9cc0ab | ||
|
|
15317a9f4b | ||
|
|
62dc55dd77 | ||
|
|
20faadcbd9 | ||
|
|
8f0fa6684d | ||
|
|
7a3805d39f | ||
|
|
4709a539c6 | ||
|
|
8172125931 | ||
|
|
b8f9d459fb | ||
|
|
96bc57167f | ||
|
|
606e27a337 | ||
|
|
1d7c56b8c5 | ||
|
|
6e1327f66d | ||
|
|
82f33c1220 | ||
|
|
cd7567fcf6 | ||
|
|
e4274aa4f6 | ||
|
|
acd7309b78 | ||
|
|
d5ea078f24 | ||
|
|
8776830306 | ||
|
|
43dd9ef8a5 | ||
|
|
f047b2928b | ||
|
|
d89d500eab | ||
|
|
35fe1df499 | ||
|
|
d32d0b7909 | ||
|
|
0a5c1960b3 | ||
|
|
eef0051b93 | ||
|
|
b5cde63b37 | ||
|
|
043c42b2b4 | ||
|
|
758fa6f67e | ||
|
|
c2fce2699e | ||
|
|
328584e5f4 | ||
|
|
f12395f4d3 | ||
|
|
024c3ed46e | ||
|
|
3ed897c665 | ||
|
|
bfcff84b28 | ||
|
|
4124cee722 | ||
|
|
d18a9f32bc | ||
|
|
aef3863c4a | ||
|
|
f31128706d | ||
|
|
1307215b8f | ||
|
|
cb380b423e | ||
|
|
86d338c811 | ||
|
|
dd3ef07881 | ||
|
|
69f14ace01 | ||
|
|
08220f598c | ||
|
|
9badb711ff | ||
|
|
90b5f897f9 | ||
|
|
4a14aeb7d9 | ||
|
|
fef0f1fa3a | ||
|
|
a39cec8e1d | ||
|
|
c89ac40f56 | ||
|
|
114a0e5ab9 | ||
|
|
371c82e5bb | ||
|
|
71338a679e | ||
|
|
aeaf259021 | ||
|
|
bdec02e290 | ||
|
|
5090f28151 | ||
|
|
a98b531bcc | ||
|
|
8727ffbe68 | ||
|
|
e7de5382fb | ||
|
|
8956eef339 | ||
|
|
0c236d0035 | ||
|
|
aaacee5d4d | ||
|
|
da00455388 | ||
|
|
03acee1ed2 | ||
|
|
4ab8faf21e | ||
|
|
2f45023f59 | ||
|
|
1d2818a064 | ||
|
|
582da0ee44 | ||
|
|
592dea0f8c | ||
|
|
dd8db78680 | ||
|
|
23ce877bd2 | ||
|
|
8bb971c15d | ||
|
|
fe20e528b0 | ||
|
|
8dd8fb52f4 | ||
|
|
af9fcdcfa8 | ||
|
|
9990965e82 | ||
|
|
5b52063446 | ||
|
|
b2e3d47d14 | ||
|
|
67cbda3bd5 | ||
|
|
84d6cf937b | ||
|
|
765ac2a14d | ||
|
|
1167700a53 | ||
|
|
c6954f9972 | ||
|
|
c72e5fcc5e | ||
|
|
4ec075d290 | ||
|
|
60a1a3a8c8 | ||
|
|
bf38754846 | ||
|
|
94197cb25d | ||
|
|
cbaaf96324 | ||
|
|
96899a140b | ||
|
|
c756b080e8 | ||
|
|
a61fb1e23b | ||
|
|
9660d95ceb | ||
|
|
eabc98b64a | ||
|
|
5ff3d1a0c5 | ||
|
|
b6587de389 | ||
|
|
4d9f4e0202 | ||
|
|
e9d2f527a1 | ||
|
|
98e6939c48 | ||
|
|
e3911f8621 | ||
|
|
efd5f79368 | ||
|
|
8e84b5c0b1 | ||
|
|
c1dc473ed8 | ||
|
|
3b08327792 | ||
|
|
8b08c5a5f3 | ||
|
|
eedea62ac1 | ||
|
|
146f62abcc | ||
|
|
1c854f2e83 | ||
|
|
d27bb56cf3 | ||
|
|
28aeb17cbe | ||
|
|
b3cf318c5e | ||
|
|
4acf65fcfb | ||
|
|
4c871c6f50 | ||
|
|
d56ce3ae56 | ||
|
|
5225d7f50c | ||
|
|
41392a1c6e | ||
|
|
ca714157b8 | ||
|
|
9dd2d2a3b1 | ||
|
|
e53f2f7674 | ||
|
|
edbfec0ce4 | ||
|
|
d294e8cd49 | ||
|
|
2229bb9817 | ||
|
|
7ef7b6e042 | ||
|
|
8159cbf7d3 | ||
|
|
c23e609902 | ||
|
|
2d9ea25273 | ||
|
|
7773bbc908 | ||
|
|
72476f0967 | ||
|
|
a9883ccc25 | ||
|
|
3b9b93a8a4 | ||
|
|
f90b7bfb09 | ||
|
|
edc941eb9e | ||
|
|
5e7ef6c50e | ||
|
|
fdc7be1318 | ||
|
|
f00c1bf61b | ||
|
|
09030de0b5 | ||
|
|
bdba0ca1c5 | ||
|
|
e17c7d938c | ||
|
|
433f2908a0 | ||
|
|
9fa5f5ace1 | ||
|
|
849a379a8c | ||
|
|
98ee78edf0 | ||
|
|
e205629a94 | ||
|
|
9351f37935 | ||
|
|
7d185bb710 | ||
|
|
07759813ed | ||
|
|
591d294052 | ||
|
|
df1a0c5b8d | ||
|
|
e743394537 | ||
|
|
22f140ac05 | ||
|
|
25a303935c | ||
|
|
3bf20d4f7a | ||
|
|
45413ce815 | ||
|
|
8ffe466257 | ||
|
|
d9aa3cb2d4 | ||
|
|
5251a2452c | ||
|
|
6df2c1595f | ||
|
|
c56e4a08d3 | ||
|
|
80515b69c1 | ||
|
|
303645cffa | ||
|
|
b3d32f65d3 | ||
|
|
7c0aac7454 | ||
|
|
7719eae023 | ||
|
|
5e210c700d | ||
|
|
c6ce871700 | ||
|
|
f28504a2eb | ||
|
|
48733a315b | ||
|
|
16fbff8de1 | ||
|
|
bbab0cea5e | ||
|
|
19de93ae39 | ||
|
|
230e5065c1 | ||
|
|
c94340d493 | ||
|
|
ac1ff231e0 | ||
|
|
5423ffe518 | ||
|
|
ba4d613cbc | ||
|
|
ab11118c8a | ||
|
|
3ca3f39f1d | ||
|
|
8c3f167e8c | ||
|
|
1a4d3927e7 | ||
|
|
20a29e5cd1 | ||
|
|
51e0fff822 | ||
|
|
13b3e75d0e | ||
|
|
de28178369 | ||
|
|
2f38cd184c | ||
|
|
d8caa76bc8 | ||
|
|
506c3c928e | ||
|
|
48ac1de8d3 | ||
|
|
ebfce5b0f2 | ||
|
|
58f4db4e52 | ||
|
|
ba2c4d1eb7 | ||
|
|
6656b5d973 | ||
|
|
b4673fdc85 | ||
|
|
ce1266be68 | ||
|
|
226108d05d | ||
|
|
b2d541f1eb | ||
|
|
758020c574 | ||
|
|
876569613b | ||
|
|
82b26daf37 | ||
|
|
be44b65095 | ||
|
|
8596f0d4a3 | ||
|
|
19a94e5f15 | ||
|
|
7bde345b83 | ||
|
|
d45a5747ea | ||
|
|
e560ab61b6 | ||
|
|
84c3ac93ef | ||
|
|
7a50b7779a | ||
|
|
328a3c3178 | ||
|
|
21fa54d792 | ||
|
|
ec7ac60cfc | ||
|
|
c2d8d5dc82 | ||
|
|
20a7e3552c | ||
|
|
888168f044 | ||
|
|
851642a1bd | ||
|
|
f7bdebfba9 | ||
|
|
a4d3222108 | ||
|
|
f1caab9de0 | ||
|
|
c08336fdb0 | ||
|
|
541b496d09 | ||
|
|
622bf349c5 | ||
|
|
05eaf82b36 | ||
|
|
5c8150fd16 | ||
|
|
ec9327dcb4 | ||
|
|
8e689d35af | ||
|
|
50fd544070 | ||
|
|
4f8bd2e06d | ||
|
|
6f1b6f5f31 | ||
|
|
bdfda399cb | ||
|
|
a08ffc3513 | ||
|
|
21beee2fe1 | ||
|
|
a564f94bf3 | ||
|
|
9e54898866 | ||
|
|
739e01da95 | ||
|
|
3e0af2cc84 | ||
|
|
9ff13740f2 | ||
|
|
00e5c33444 | ||
|
|
57abaf7500 | ||
|
|
ed14be4e70 | ||
|
|
80909e17c7 | ||
|
|
52697ea884 | ||
|
|
9f01c8d0d6 | ||
|
|
e91d7e74ae | ||
|
|
20ca0463ea | ||
|
|
5e40f469bf | ||
|
|
7f28d63c33 | ||
|
|
bb1fa24971 | ||
|
|
ffbbaa06d7 | ||
|
|
14e1b96f05 | ||
|
|
d8c781b66b | ||
|
|
2fbec8545c | ||
|
|
b66901fc75 | ||
|
|
d569bca520 | ||
|
|
efbefc669f | ||
|
|
24805ff85d | ||
|
|
8b917d5716 | ||
|
|
3502f335ec | ||
|
|
758979e4f3 | ||
|
|
8b5fc801da | ||
|
|
f5c4214c93 | ||
|
|
f106993cd1 | ||
|
|
270e84287a | ||
|
|
daec7cf3f4 | ||
|
|
bb42d1e9a5 | ||
|
|
23f182aab3 | ||
|
|
119fbc995c | ||
|
|
3081f49179 | ||
|
|
8cf1874453 | ||
|
|
31b4bd5bcf | ||
|
|
71d1591cc1 | ||
|
|
134a2d60fe | ||
|
|
152b8912ae | ||
|
|
36f23c101d | ||
|
|
0e40510295 | ||
|
|
db0d0768d7 | ||
|
|
c68cade9f2 | ||
|
|
14928727eb | ||
|
|
67b9345929 | ||
|
|
dae1a376a2 | ||
|
|
1e359f1dcf | ||
|
|
1c54857422 | ||
|
|
0f78a0ac5c | ||
|
|
4e1e77890b | ||
|
|
5573cdfba1 | ||
|
|
14028d3758 | ||
|
|
3ab673b398 | ||
|
|
861f51f6c3 | ||
|
|
64f5d0d388 | ||
|
|
9059af8d5f | ||
|
|
c3a543b99d | ||
|
|
c85cd783e5 | ||
|
|
af2d241c99 | ||
|
|
30839a5273 | ||
|
|
8baa99b7ef | ||
|
|
d1e5572343 | ||
|
|
96aa648e17 | ||
|
|
1ae5f23dc8 | ||
|
|
f565f72679 | ||
|
|
78e76648d0 | ||
|
|
8e1e2210dd | ||
|
|
e8c43c36d7 | ||
|
|
97e2a7bae0 | ||
|
|
6b75a578ac | ||
|
|
8b9238ebc9 | ||
|
|
8cc8027b40 | ||
|
|
ffb743e108 | ||
|
|
0f805752d3 | ||
|
|
4e9de4d51b | ||
|
|
a4e9539040 | ||
|
|
0c383dfb11 | ||
|
|
11d2b7ca98 | ||
|
|
e38be2f280 | ||
|
|
febdd3c0d0 | ||
|
|
0b08ca64a8 | ||
|
|
0f8e7fbd34 | ||
|
|
1a080ba71c | ||
|
|
1622531d85 | ||
|
|
7d0a9c7233 | ||
|
|
53a64c88ad | ||
|
|
27b51d51d8 | ||
|
|
bec35e0538 | ||
|
|
f65e6a3bb1 | ||
|
|
fd94f1a5f9 | ||
|
|
09fc037d4d | ||
|
|
cf0e6dac61 | ||
|
|
3b10e3bcb5 | ||
|
|
4c17784444 | ||
|
|
6616f0886d | ||
|
|
dcafab2764 | ||
|
|
3b6146301f | ||
|
|
42e09b3c7f | ||
|
|
73da42bee6 | ||
|
|
415b1cf5f0 | ||
|
|
c011285904 | ||
|
|
4314b4fefb | ||
|
|
d686f6844d | ||
|
|
65a0e5f771 | ||
|
|
5ca6d8ce67 | ||
|
|
688c2b9ee5 | ||
|
|
271f39505c | ||
|
|
3e8367ea3b | ||
|
|
67a1e52259 | ||
|
|
7561687b7b | ||
|
|
93fc7acbe3 | ||
|
|
72dc67950f | ||
|
|
e2bebd1d51 | ||
|
|
03560d3386 | ||
|
|
a3a3303a83 | ||
|
|
232a6f87d2 | ||
|
|
ab71ea0a65 | ||
|
|
1302224f39 | ||
|
|
733bf0dcdf | ||
|
|
4ed48178a9 | ||
|
|
8cffb975d9 | ||
|
|
97b18797a4 | ||
|
|
579794b265 | ||
|
|
bea746595e | ||
|
|
87711b048a | ||
|
|
0b468ebd85 | ||
|
|
aefc250e30 | ||
|
|
4a86fea86b | ||
|
|
fe6e2e1ea7 | ||
|
|
09d90b9b70 | ||
|
|
14eb7b46a2 | ||
|
|
66077fe3a4 | ||
|
|
d50cf806db | ||
|
|
95edae9bd1 | ||
|
|
a6c35305ed | ||
|
|
b382005a4c | ||
|
|
a71b90bdd6 | ||
|
|
d4a68c80bc | ||
|
|
fcf44cbebe | ||
|
|
51d8cb063a | ||
|
|
cdc86565cc | ||
|
|
1c54907b30 | ||
|
|
b6d4246e18 | ||
|
|
cc1a984c7e | ||
|
|
52d39657ab | ||
|
|
363ec82a48 | ||
|
|
f164b0e3eb | ||
|
|
3aaf7a69ec | ||
|
|
6d2828bc3c | ||
|
|
dd6e2051a8 | ||
|
|
ef440972bb | ||
|
|
da96888669 | ||
|
|
75639059e1 | ||
|
|
0a15dd311a | ||
|
|
434a1c6710 | ||
|
|
f961eecab6 | ||
|
|
d33a571f7d | ||
|
|
ea1239efef | ||
|
|
19c7c7a9dc | ||
|
|
49e4af4fab | ||
|
|
3e27c1bb17 | ||
|
|
0f8d196741 | ||
|
|
4c45f0e44b | ||
|
|
e39eef1ed7 | ||
|
|
c9c7aea1c4 | ||
|
|
18ff9eb2b4 | ||
|
|
b2f3d2cd84 | ||
|
|
5e0832cb8b | ||
|
|
a14c0ccac6 | ||
|
|
278f90acdd | ||
|
|
8e8b18e9a9 | ||
|
|
a277d74869 | ||
|
|
7ca3b6455d | ||
|
|
5ec6f69037 | ||
|
|
39962ba5eb | ||
|
|
51fa1f9abd | ||
|
|
47af5d463c | ||
|
|
33f0b0b41c | ||
|
|
48038b1f5e | ||
|
|
323698d387 | ||
|
|
1f702beb74 | ||
|
|
7d34c28af1 | ||
|
|
d26be77010 | ||
|
|
3b96d1bd57 | ||
|
|
48fd0e71d5 | ||
|
|
bcb35ccf44 | ||
|
|
a663ff7fa8 | ||
|
|
813d34a0e9 | ||
|
|
a4074a13c4 | ||
|
|
249f329b07 | ||
|
|
cf160a8f84 | ||
|
|
4db963182d | ||
|
|
199b59fdb9 | ||
|
|
2d09bfa0f3 | ||
|
|
729285e8a2 | ||
|
|
afd17bd96a | ||
|
|
380d8570dc | ||
|
|
e711eaa810 | ||
|
|
7dfdc2094e | ||
|
|
838646ac5b | ||
|
|
507f07575b | ||
|
|
f5e8808770 | ||
|
|
ae5b6e88a5 | ||
|
|
b45186dde0 | ||
|
|
38be8aa0da | ||
|
|
816d4ba206 | ||
|
|
ede59e4d2a | ||
|
|
ce0931a3c8 | ||
|
|
a44e148818 | ||
|
|
71115c6558 | ||
|
|
8ae837e98b | ||
|
|
9518193d0a | ||
|
|
60a2b799e6 | ||
|
|
1d42690824 | ||
|
|
3f94fd5e4e | ||
|
|
165e237be7 | ||
|
|
38dfd6f4f9 | ||
|
|
5851d66174 | ||
|
|
6a970c3515 | ||
|
|
9e91e8f1b2 | ||
|
|
3e1bc77bf2 | ||
|
|
d991cb6721 | ||
|
|
37a252748a | ||
|
|
5664b5b195 | ||
|
|
278a596bfa | ||
|
|
ea74f31b3e | ||
|
|
9d7dc00f25 | ||
|
|
882e7b6716 | ||
|
|
8ba29ee8e6 | ||
|
|
dd4b61da20 | ||
|
|
c56e836d22 | ||
|
|
427f9c5b00 | ||
|
|
aa07e16f18 | ||
|
|
7b8c7edfd5 | ||
|
|
cf7b35f90d | ||
|
|
02bc9a85c0 | ||
|
|
e1820522db | ||
|
|
0a59c38f31 | ||
|
|
66fdeceb3b | ||
|
|
3f67c41759 | ||
|
|
7fbeafa1cf | ||
|
|
316d8f8e9b | ||
|
|
15d623f2c0 | ||
|
|
d1437b7666 | ||
|
|
ff8e9850ba | ||
|
|
f648a018a2 | ||
|
|
072bd30443 | ||
|
|
48f89f226f | ||
|
|
d5671c2879 | ||
|
|
80114e7a24 | ||
|
|
dede701423 | ||
|
|
43cb4d68f7 | ||
|
|
4783ad3a73 | ||
|
|
482e0c2d0b | ||
|
|
e951164399 | ||
|
|
c73b987cd0 | ||
|
|
b22c9b8542 | ||
|
|
a5327af5e9 | ||
|
|
192f8bec26 | ||
|
|
eb28e22891 | ||
|
|
b6b8f30378 | ||
|
|
67bb4f9552 | ||
|
|
028257480b | ||
|
|
e42a0c45b6 | ||
|
|
1e7f8549ff | ||
|
|
668de71f9d | ||
|
|
067245b810 | ||
|
|
8f236c69e1 | ||
|
|
8ee33da114 | ||
|
|
2fedc2e699 | ||
|
|
1961543e2f | ||
|
|
b4f65734a5 | ||
|
|
0eb80553f6 | ||
|
|
110c63ae95 | ||
|
|
57304536bf | ||
|
|
a9ca5da139 | ||
|
|
947aebfbe0 | ||
|
|
fafc9268d4 | ||
|
|
65a5d55436 | ||
|
|
96b350400f | ||
|
|
7983b4caf2 | ||
|
|
e44122f1be | ||
|
|
42618d7ec6 | ||
|
|
1d0167bbf4 | ||
|
|
43d4b21b23 | ||
|
|
562171c548 | ||
|
|
8dccecdd9f | ||
|
|
940ae364d7 | ||
|
|
532bc454c5 | ||
|
|
14ffe7782c | ||
|
|
2dd40fce44 | ||
|
|
0c8bc46e28 | ||
|
|
23c9d9c34d | ||
|
|
188e9e1114 | ||
|
|
7d0dd29937 | ||
|
|
349cd77821 | ||
|
|
dc2d7b1dfe | ||
|
|
be30329288 | ||
|
|
71446d9f3c | ||
|
|
c9d4c8d09b | ||
|
|
c580ffdb70 | ||
|
|
f46deb4eb7 | ||
|
|
b3215bed48 | ||
|
|
2a9ab02753 | ||
|
|
0da586154d | ||
|
|
26d736551d | ||
|
|
9445a3118b | ||
|
|
a2c46c7436 | ||
|
|
8df7a0960e | ||
|
|
e7f35e7a35 | ||
|
|
088e80e38b | ||
|
|
2d65c7f387 | ||
|
|
94db758eb7 | ||
|
|
2bfb615d68 | ||
|
|
87275140f9 | ||
|
|
0672a68ba4 | ||
|
|
246e3ccfad | ||
|
|
b275ee919f | ||
|
|
eda796d5e0 | ||
|
|
d1b3917309 | ||
|
|
ffee2b971f | ||
|
|
b9a80f9c8c | ||
|
|
980f673ce2 | ||
|
|
55767a0003 | ||
|
|
fb44bebe40 | ||
|
|
b79f072499 | ||
|
|
d65a2e8b51 | ||
|
|
e0b42d51db | ||
|
|
c057dc9466 | ||
|
|
fff53a94d3 | ||
|
|
12beedd0a6 | ||
|
|
80f60a7394 | ||
|
|
2359348505 | ||
|
|
63e3e06a8c | ||
|
|
dca92b580c | ||
|
|
24e2960092 | ||
|
|
be1a52c5c1 | ||
|
|
8a34a6c8f4 | ||
|
|
7924ea9bb9 | ||
|
|
a3a17ae792 | ||
|
|
f8801d811b | ||
|
|
425284ac62 | ||
|
|
4872cdf905 | ||
|
|
88cd81c692 | ||
|
|
d45ecd0800 | ||
|
|
4bfcef60f4 | ||
|
|
e9b7e933f5 | ||
|
|
e5301cef49 | ||
|
|
01ca552174 | ||
|
|
4529d73bf3 | ||
|
|
0798906a51 | ||
|
|
8547c24dac | ||
|
|
0e1e1aae2e | ||
|
|
9cc31e4087 | ||
|
|
e9c7555bb9 | ||
|
|
6f897fec59 | ||
|
|
8c3d77f4c7 | ||
|
|
f9b60d83ac | ||
|
|
3992681b84 | ||
|
|
340bd78259 | ||
|
|
12a46275a2 | ||
|
|
b56234f1c9 | ||
|
|
60859ec2b9 | ||
|
|
0a840860f1 | ||
|
|
cebae18dd6 | ||
|
|
9c9c6b6591 | ||
|
|
ca0ffc66d1 | ||
|
|
b0623f04fe | ||
|
|
2dec862ea6 | ||
|
|
f18fe53a9a | ||
|
|
73348de2b4 | ||
|
|
f4a418bfcd | ||
|
|
50588800f5 | ||
|
|
2762215d66 | ||
|
|
4e53797aac | ||
|
|
b24ac4b3a2 | ||
|
|
88ab6afd3e | ||
|
|
5c5db0a961 | ||
|
|
587186d96c | ||
|
|
d9ddf93f83 | ||
|
|
d3882d3513 | ||
|
|
a458215bbb | ||
|
|
7ae0fa3775 | ||
|
|
f1695f8b15 | ||
|
|
4c08bbb9e5 | ||
|
|
9b55ff8c4c | ||
|
|
2096d2b786 | ||
|
|
70196cd6fd | ||
|
|
c2cba97722 | ||
|
|
7534ebd145 | ||
|
|
6b2331340b | ||
|
|
da7b5005fe | ||
|
|
9210e12316 | ||
|
|
2c47a79c38 | ||
|
|
48cebef974 | ||
|
|
52952efd33 | ||
|
|
30dfd28ac4 | ||
|
|
b5a04f05f3 | ||
|
|
d5a34dcbc5 | ||
|
|
fc6a05ced6 | ||
|
|
2d3162a90b | ||
|
|
83c599e741 | ||
|
|
c7f1671d5a | ||
|
|
9f2d945691 | ||
|
|
36ff099145 | ||
|
|
120e010e48 | ||
|
|
2887816cf0 | ||
|
|
9848479306 | ||
|
|
b662e6b9eb | ||
|
|
258f1f0848 | ||
|
|
a07f312089 | ||
|
|
605d8fe59a | ||
|
|
1c7db4da0d | ||
|
|
b0acc95b01 | ||
|
|
5bcad73515 | ||
|
|
db05754d29 | ||
|
|
dfe3457906 | ||
|
|
7dbb1a2aa8 | ||
|
|
83dac4aae2 | ||
|
|
75b79fa002 | ||
|
|
27c1fd0262 | ||
|
|
8069e06f43 | ||
|
|
8cd106fc8a | ||
|
|
a9c9877580 | ||
|
|
19e1201c8a | ||
|
|
912f98e6eb | ||
|
|
b6808e3700 | ||
|
|
a4f78b60e0 | ||
|
|
6c9906c639 | ||
|
|
8a90af6779 | ||
|
|
9831a13284 | ||
|
|
d2386bc1f6 | ||
|
|
5b10af7b1a | ||
|
|
eacf3cc4ed | ||
|
|
87090139f6 | ||
|
|
650c4cf948 | ||
|
|
24c074eeaa | ||
|
|
b54629addb | ||
|
|
cd67d11ecf | ||
|
|
16bb0c93e7 | ||
|
|
7c40c3a61c | ||
|
|
b8e8b7496d | ||
|
|
d29d5e3a47 | ||
|
|
e980973621 | ||
|
|
db261d0fa4 | ||
|
|
b7f6b847d6 | ||
|
|
5311a842a5 | ||
|
|
e288f59da7 | ||
|
|
c62ceb5db1 | ||
|
|
35decf122d | ||
|
|
2adfe1507b | ||
|
|
5516e6b279 | ||
|
|
8b811c610a | ||
|
|
23348f8e65 | ||
|
|
e1c3a2f8cf | ||
|
|
0b0493fa21 | ||
|
|
14d1742869 | ||
|
|
96aa77288b | ||
|
|
a4c9c10029 | ||
|
|
c9d561e7ad | ||
|
|
87ba63c14c | ||
|
|
0decbad7d0 | ||
|
|
0e647dbc0e | ||
|
|
2540d28b34 | ||
|
|
19a5e5bb00 | ||
|
|
f22afc6458 | ||
|
|
ab00415ca1 | ||
|
|
8df5406986 | ||
|
|
7f05159f0f | ||
|
|
61147dfecf | ||
|
|
06da133aac | ||
|
|
ff1d047048 | ||
|
|
4a8b17cb84 | ||
|
|
fbafc09e6a | ||
|
|
c3c960383e | ||
|
|
9e3adf0bf8 | ||
|
|
2bc0aa1777 | ||
|
|
3bc4064b61 | ||
|
|
b4f9258f3c | ||
|
|
ad844cce5c | ||
|
|
c73b064133 | ||
|
|
bd9b63a1aa | ||
|
|
2d87431aeb | ||
|
|
3f3b1fb657 | ||
|
|
477f9eb4ec | ||
|
|
91497dc2ee | ||
|
|
928b78d9f6 | ||
|
|
51825663b9 | ||
|
|
01fdbda728 | ||
|
|
fa3c68fccd | ||
|
|
189977e4c7 | ||
|
|
290fd99b6d | ||
|
|
15cec5bd50 | ||
|
|
f53db636e1 | ||
|
|
47d3802ffe | ||
|
|
e98ffb5ae0 | ||
|
|
5d77eb1314 | ||
|
|
f124cdbb6f | ||
|
|
1649d084d2 | ||
|
|
36ca790c3d | ||
|
|
a91a8216b7 | ||
|
|
8cae7b20e7 | ||
|
|
a537119f3d | ||
|
|
15fe0afe62 | ||
|
|
1b2a4db1ed | ||
|
|
88a02723fa | ||
|
|
7d013f35e2 | ||
|
|
e881d33bea | ||
|
|
38da91becd | ||
|
|
c99d96a700 | ||
|
|
fa89a6950b | ||
|
|
cde08da282 | ||
|
|
8619bd4e84 | ||
|
|
f49449b520 | ||
|
|
2fe79ac6a3 | ||
|
|
d8830c43c5 | ||
|
|
4ac945da70 | ||
|
|
ee0019e25f | ||
|
|
f37b814570 | ||
|
|
e559bc8694 | ||
|
|
f7618440e7 | ||
|
|
7bc62cb674 | ||
|
|
db77e2e9b9 | ||
|
|
083b49f3c4 | ||
|
|
f3f0416d31 | ||
|
|
775a9f86a1 | ||
|
|
e2bfdc444a | ||
|
|
3c7783585e | ||
|
|
761a297903 | ||
|
|
6d30094a93 | ||
|
|
d8e1816774 | ||
|
|
ae371cb362 | ||
|
|
73c46e8e24 | ||
|
|
ef4c40c692 | ||
|
|
04b3ada7f7 | ||
|
|
424b43b3d3 | ||
|
|
9a9255d6f9 | ||
|
|
d9e52e41ff | ||
|
|
a038bc002a | ||
|
|
fa256eb1a7 | ||
|
|
6689f001cf | ||
|
|
cc043bab9c | ||
|
|
5af73b1dcf | ||
|
|
85925a2dc6 | ||
|
|
fb23b6c26f | ||
|
|
d5cec5f71e | ||
|
|
13b62e3d06 | ||
|
|
779f07f072 | ||
|
|
b923d63700 | ||
|
|
7e2dd9bc04 | ||
|
|
ef1f869b73 | ||
|
|
959d6334db | ||
|
|
d7b00b93c7 | ||
|
|
eec084c842 | ||
|
|
87b504a58f | ||
|
|
243d4d0727 | ||
|
|
673acf4308 | ||
|
|
fd180ebff5 | ||
|
|
61705ce7fc | ||
|
|
6e1dd4474b | ||
|
|
7924657584 | ||
|
|
4f5ed8ace0 | ||
|
|
8737220fb6 | ||
|
|
bcb01e8c1b | ||
|
|
41f669bb89 | ||
|
|
983bc199b3 | ||
|
|
8f15269bd0 | ||
|
|
6ffe3e7067 | ||
|
|
51bf6035f7 | ||
|
|
249a6fc9b1 | ||
|
|
c6d4337855 | ||
|
|
9fa3636c57 | ||
|
|
a417e6e644 | ||
|
|
999eb86d7a | ||
|
|
7b97f93051 | ||
|
|
02bc926d75 | ||
|
|
48ee3cdf98 | ||
|
|
2556a912d3 | ||
|
|
487674b1c5 | ||
|
|
347fbf6471 | ||
|
|
3eff70a3bc | ||
|
|
ad7c708039 | ||
|
|
f993c1f22c | ||
|
|
75b714a1ad | ||
|
|
0636d40909 | ||
|
|
cbb3660a17 | ||
|
|
42363beb72 | ||
|
|
efa36a7196 | ||
|
|
fab713a6a8 | ||
|
|
7d5f1143af | ||
|
|
f05f8df44c | ||
|
|
c89a1a8021 | ||
|
|
db7c679e74 | ||
|
|
71cbbf545b | ||
|
|
6809a7ec3e | ||
|
|
70847a74c2 | ||
|
|
6933bc8add | ||
|
|
a6cbaad5a2 | ||
|
|
899972e22f | ||
|
|
820b925b78 | ||
|
|
5457b43a89 | ||
|
|
0adbc9678f | ||
|
|
8d6a2ecf0e | ||
|
|
6acbd80cee | ||
|
|
2331224157 | ||
|
|
5d2aea434c | ||
|
|
798f6983e4 | ||
|
|
a428fdc951 | ||
|
|
ccacc09ff0 | ||
|
|
40cc155aad | ||
|
|
645e0de971 | ||
|
|
325eb5968d | ||
|
|
65a7583731 | ||
|
|
6d8e4e8fb1 | ||
|
|
4716cce208 | ||
|
|
c67bbe6c00 | ||
|
|
1272e21603 | ||
|
|
7302280417 | ||
|
|
871c6d56dc | ||
|
|
eb0389938c | ||
|
|
e6c191bdc6 | ||
|
|
2727eb6dd7 | ||
|
|
821087bcce | ||
|
|
f1955577bc | ||
|
|
966686cd5d | ||
|
|
79d3d50de6 | ||
|
|
da59f0a0db | ||
|
|
bdc00e5dd4 | ||
|
|
9854a4f92e | ||
|
|
33413ecfe9 | ||
|
|
c7e8d297a4 | ||
|
|
3619953f83 | ||
|
|
b20753c3ac | ||
|
|
4fd6b7a608 | ||
|
|
1de8e477b9 | ||
|
|
6c50645213 | ||
|
|
5aeea0c228 | ||
|
|
86ceeb554d | ||
|
|
ca121e0e28 | ||
|
|
d1fef7fd17 | ||
|
|
25d157afdc | ||
|
|
97afe3cd0b | ||
|
|
2923304bdb | ||
|
|
10d741b6df | ||
|
|
12cd115ae4 | ||
|
|
d1def13cd1 | ||
|
|
127c305b1a | ||
|
|
fd06db18a0 | ||
|
|
387b392c18 | ||
|
|
964022f7b5 | ||
|
|
9edc346c2c | ||
|
|
2fe671744b | ||
|
|
fb8daa5607 | ||
|
|
65e0da72b8 | ||
|
|
3d5924e2f5 | ||
|
|
5b2e2d630b | ||
|
|
6a96cb6ba7 | ||
|
|
43ece9c644 | ||
|
|
46cef723b7 | ||
|
|
9b19dac569 | ||
|
|
54eb642726 | ||
|
|
5bb664657c | ||
|
|
dea4d16e87 | ||
|
|
059cd23543 | ||
|
|
502b8630a2 | ||
|
|
b2444b43a6 | ||
|
|
be87ff0193 | ||
|
|
c7fe86021c | ||
|
|
99b0209c89 | ||
|
|
4d61b596ff | ||
|
|
0549e7079d | ||
|
|
b591b64d3f | ||
|
|
3447f06208 | ||
|
|
a3377686fa | ||
|
|
d23bba5d9f | ||
|
|
5e8fc3e4c8 | ||
|
|
04e76ad6ff | ||
|
|
3a18a9296b | ||
|
|
3e9a6ffbca | ||
|
|
0b06e56182 | ||
|
|
fb5a32f429 | ||
|
|
75dfd4505b | ||
|
|
5661d1428e | ||
|
|
f543c1ee1c | ||
|
|
74254cdbd5 | ||
|
|
a5c8c534c1 | ||
|
|
24159dda58 | ||
|
|
ad0a2b3260 | ||
|
|
7e51c68fde | ||
|
|
3b376a15b7 | ||
|
|
8e5f311708 | ||
|
|
02c48fa8c3 | ||
|
|
51fa2eb103 | ||
|
|
0fd08fe667 | ||
|
|
0da65c6169 | ||
|
|
d2c6db0680 | ||
|
|
5b73938e29 | ||
|
|
2540933921 | ||
|
|
e75e2272f7 | ||
|
|
64b8fc80e8 | ||
|
|
033c149737 | ||
|
|
a6bce6d5f1 | ||
|
|
94016c87fd | ||
|
|
4b6424f631 | ||
|
|
9bf70d8641 | ||
|
|
0eb26fffe1 | ||
|
|
279e7e1456 | ||
|
|
099fc7b31e | ||
|
|
f1e070cc1e | ||
|
|
36f2458f2e | ||
|
|
29db4231ee | ||
|
|
74926578a2 | ||
|
|
5b8ae368bd | ||
|
|
b3e368237e | ||
|
|
3ee5dc131d | ||
|
|
2765d2fe66 | ||
|
|
f50fee407a | ||
|
|
f3295d7c1d | ||
|
|
dd02803f1f | ||
|
|
d4e44d7555 | ||
|
|
f2b8d36d9e | ||
|
|
8d43d4ee21 | ||
|
|
b54af5dbe3 | ||
|
|
4980e901a0 | ||
|
|
cd32311c1f | ||
|
|
b97997b009 | ||
|
|
4aad9fbdd4 | ||
|
|
ac2439e25b | ||
|
|
ddc1556ae0 | ||
|
|
0d75c4d0e3 | ||
|
|
f3a042dcdf | ||
|
|
f9e0a99064 | ||
|
|
d6906fb100 | ||
|
|
821662abcb | ||
|
|
cf496abec0 | ||
|
|
e245d39216 | ||
|
|
44f87c2c17 | ||
|
|
a6fd0de762 | ||
|
|
072ecba4c5 | ||
|
|
3ad5d75bee | ||
|
|
8679c425e0 | ||
|
|
1ec257278e | ||
|
|
ffe89362ab | ||
|
|
a82c0f9e49 | ||
|
|
413271e82a | ||
|
|
60b926b698 | ||
|
|
98be67442e | ||
|
|
8e9c75b6f8 | ||
|
|
8c65604b29 | ||
|
|
930923c211 | ||
|
|
710d540dcd | ||
|
|
ddef8c2499 | ||
|
|
c6289c2bea | ||
|
|
b624bb6fb8 | ||
|
|
4e03f4165d | ||
|
|
2f6ea2f499 | ||
|
|
8c47d1f9c0 | ||
|
|
e31be8d3c9 | ||
|
|
2069ad62d1 | ||
|
|
2993e285c8 | ||
|
|
4ee9cee52a | ||
|
|
0e555699bd | ||
|
|
aad6c63206 | ||
|
|
8c78e09f03 | ||
|
|
25d1eac0f5 | ||
|
|
26daf2cd31 | ||
|
|
280bd44ba3 | ||
|
|
c9d87fef71 | ||
|
|
649742f02a | ||
|
|
245ee84c2d | ||
|
|
76c09205d0 | ||
|
|
d365b5248c | ||
|
|
3dd91bc6f2 | ||
|
|
3dc5a48fcc | ||
|
|
bff077f855 | ||
|
|
6022b09437 | ||
|
|
62b52a78fe | ||
|
|
e33dc9355d | ||
|
|
834da07736 | ||
|
|
fe795cc2d5 | ||
|
|
593de47438 | ||
|
|
ac231e43ad | ||
|
|
aca64071c8 | ||
|
|
5c205cd753 | ||
|
|
57ce2c48b8 | ||
|
|
0937b34983 | ||
|
|
291c4eb258 | ||
|
|
a8744708a0 | ||
|
|
3010b94a64 | ||
|
|
9c4d173b1f | ||
|
|
cca155c5c6 | ||
|
|
a93e7108e6 | ||
|
|
d26df6c178 | ||
|
|
e915964d81 | ||
|
|
1304ca2425 | ||
|
|
894f6b6d1d | ||
|
|
6605a9b22a | ||
|
|
b5d963d151 | ||
|
|
8f840ae0b1 | ||
|
|
7942ca9206 | ||
|
|
a5614bfe40 | ||
|
|
eaac00ad3f | ||
|
|
6687cc9465 | ||
|
|
5b9464ed31 | ||
|
|
4c7d99b26c | ||
|
|
5bf248164a | ||
|
|
4eeae967db | ||
|
|
0ac2a85a3e | ||
|
|
71884e58d4 | ||
|
|
37b2f9617c | ||
|
|
01af14693c | ||
|
|
90e2929fc5 | ||
|
|
8f260aa544 | ||
|
|
ee72a09278 | ||
|
|
e6a422b6c2 | ||
|
|
d29aff919b | ||
|
|
5023bbe8c0 | ||
|
|
c08e3daf29 | ||
|
|
9cf194b52b | ||
|
|
1b0ba71ef0 | ||
|
|
de5ab102b7 | ||
|
|
4e2c7c3329 | ||
|
|
b62840c347 | ||
|
|
674379fe30 | ||
|
|
004ffb9f60 | ||
|
|
bed836b15a | ||
|
|
bbf219ecd2 | ||
|
|
022bf4bdcc | ||
|
|
d8ed90a2c0 | ||
|
|
e5d9d91b01 | ||
|
|
c8f6576cd7 | ||
|
|
14f140fdc5 | ||
|
|
fdbe169423 | ||
|
|
e4ec370e2a | ||
|
|
dfbdb43c31 | ||
|
|
9b8606535d | ||
|
|
0b79fd0a8e | ||
|
|
f032606f32 | ||
|
|
b652a8416d | ||
|
|
8f7b50f3df | ||
|
|
2adafad4e2 | ||
|
|
99a5862bc9 | ||
|
|
6dda54f431 | ||
|
|
8e187a913f | ||
|
|
8935c87d7a | ||
|
|
c6e544750b | ||
|
|
c14d406f59 | ||
|
|
4edfe39449 | ||
|
|
1239d77c88 | ||
|
|
7d4b0b20e8 | ||
|
|
1f42cdd762 | ||
|
|
f182b81e0f | ||
|
|
3f2256d4c4 | ||
|
|
e4b1b12b26 | ||
|
|
35477e0a5a | ||
|
|
f752c9c8a9 | ||
|
|
d5dd7b0a96 | ||
|
|
4e16eba6b0 | ||
|
|
4f129e1c9d | ||
|
|
ffbbb5539d | ||
|
|
30e5b48461 | ||
|
|
0dc31011f5 | ||
|
|
479df00a3b | ||
|
|
06f3e15ce9 | ||
|
|
2f7fde163a | ||
|
|
71d26c2ec5 | ||
|
|
acf8b40a28 | ||
|
|
318027d9e1 | ||
|
|
3116e833a7 | ||
|
|
1cb71b33fa | ||
|
|
7e76b8d0c3 | ||
|
|
e0e78cd879 | ||
|
|
82e4dcf40f | ||
|
|
22b77ad14f | ||
|
|
9b23975f09 | ||
|
|
584c3a65f5 | ||
|
|
db4d6c4419 | ||
|
|
92cb1c60d1 | ||
|
|
ca72d0ca5b | ||
|
|
ba7f245d88 | ||
|
|
ef67ba5cf4 | ||
|
|
1f630a03b9 | ||
|
|
5f71cdc497 | ||
|
|
b85226b8a5 | ||
|
|
af4acccf9f | ||
|
|
3a3bc9898d | ||
|
|
5349c99c74 | ||
|
|
68d175f1c5 | ||
|
|
352f1f31d2 | ||
|
|
c51ef714ad | ||
|
|
5ae68a3c48 | ||
|
|
d2c7e51fac | ||
|
|
d9aa3fb973 | ||
|
|
e1b79ddd69 | ||
|
|
3270a8737c | ||
|
|
cbe8d320fa | ||
|
|
78ce63e71e | ||
|
|
1bb396bea5 | ||
|
|
16304beeab | ||
|
|
d2cfd541ed | ||
|
|
8b1aaf690c | ||
|
|
f1ca8f594c | ||
|
|
78d2e0aa25 | ||
|
|
4a3d239082 | ||
|
|
94eaee8d39 | ||
|
|
471f03ab4f | ||
|
|
9df9af822c | ||
|
|
7b49623b31 | ||
|
|
12bda2b8d0 | ||
|
|
2022f214aa | ||
|
|
786738ba81 | ||
|
|
e4f1b59475 | ||
|
|
7cb302f571 | ||
|
|
8bae91cf6e | ||
|
|
44a145fc86 | ||
|
|
a1fa62adef | ||
|
|
fe247a8f6a | ||
|
|
54e70eff13 | ||
|
|
5453f4a85d | ||
|
|
f2654390e7 | ||
|
|
777903f7da | ||
|
|
2f7892561f | ||
|
|
6d79000d6c | ||
|
|
284911cd6b | ||
|
|
70b6da108c | ||
|
|
6de3b2b262 | ||
|
|
e7554c5413 | ||
|
|
2facd2ab16 | ||
|
|
20ba252c56 | ||
|
|
eba123e381 | ||
|
|
3bb57af331 | ||
|
|
aeb991fdf3 | ||
|
|
75a14380ef | ||
|
|
0aeecf4d9a | ||
|
|
447347c760 | ||
|
|
79619de045 | ||
|
|
1af423e2df | ||
|
|
92fcf9a159 | ||
|
|
d1b1cc5c14 | ||
|
|
b9514ad82a | ||
|
|
7b3411b236 | ||
|
|
915e87e88e | ||
|
|
face505f0d | ||
|
|
dada307e79 | ||
|
|
5124c512c9 | ||
|
|
89f579c1ba | ||
|
|
0035f28035 | ||
|
|
4d556d9235 | ||
|
|
dd5ef29355 | ||
|
|
ca9922ef0a | ||
|
|
b674a0a5e8 | ||
|
|
541d0e6f5c | ||
|
|
fc96e980fb | ||
|
|
1425e3e6c7 | ||
|
|
b9d3c11032 | ||
|
|
81b65e0de3 | ||
|
|
0f80f6af27 | ||
|
|
5eedf98aae | ||
|
|
8f45a2ef3d | ||
|
|
9d51d4a572 | ||
|
|
9978c8ea48 | ||
|
|
e779358429 | ||
|
|
8d663cc3d6 | ||
|
|
a98ca30438 | ||
|
|
589cb2ac79 | ||
|
|
d973be8fea | ||
|
|
41c3b59755 | ||
|
|
8bf168da47 | ||
|
|
0d85c06be2 | ||
|
|
fadede7305 | ||
|
|
a24d31fbba | ||
|
|
c9d463443a | ||
|
|
0e3d486658 | ||
|
|
574ab6bdda | ||
|
|
a172d15463 | ||
|
|
a1362b946a | ||
|
|
6ecb4c5ece | ||
|
|
63c1320f36 | ||
|
|
bf484ce4c4 | ||
|
|
1700212f30 | ||
|
|
751ca3bf75 | ||
|
|
01be8bc64e | ||
|
|
a9e8005b14 | ||
|
|
871cff6232 | ||
|
|
1de8f5ceda | ||
|
|
dd4d2420df | ||
|
|
e9b1a98314 | ||
|
|
f0f48e8e30 | ||
|
|
89b008e1eb | ||
|
|
fb3f8439e2 | ||
|
|
f0e66d2bfb | ||
|
|
65ec254c1b | ||
|
|
47c0d95bd4 | ||
|
|
55d7bed563 | ||
|
|
9a4f3b8d8e | ||
|
|
d9c5ce15f3 | ||
|
|
292908288b | ||
|
|
7fec2661fc | ||
|
|
0cee8bc6ac | ||
|
|
37d867e47e | ||
|
|
c1338fe92f | ||
|
|
1e90a679c0 | ||
|
|
ddb7b9cb8b | ||
|
|
31e1604d99 | ||
|
|
e11c32bca5 | ||
|
|
16309dc077 | ||
|
|
8a1b496cd5 | ||
|
|
99cf99e014 | ||
|
|
f2e9b06dbd | ||
|
|
6cce7c34c2 | ||
|
|
dd2efac3ae | ||
|
|
14c0e50721 | ||
|
|
68e218d002 | ||
|
|
0a026e71b7 | ||
|
|
5175a8c7ca | ||
|
|
661e0cfc71 | ||
|
|
9ff6f35330 | ||
|
|
ab5a066780 | ||
|
|
c2d649e655 | ||
|
|
f82bb65810 | ||
|
|
e76c54b18e | ||
|
|
43658055d3 | ||
|
|
0b71fded7f | ||
|
|
03d1bada2a | ||
|
|
ad76355299 | ||
|
|
c98d409f0a | ||
|
|
9084673fd7 | ||
|
|
8d7300a522 | ||
|
|
7c10600044 | ||
|
|
277da37047 | ||
|
|
f6d697ed2b | ||
|
|
f247927244 | ||
|
|
7097e62582 | ||
|
|
e577bab263 | ||
|
|
274c40793f | ||
|
|
afbe9266e7 | ||
|
|
195ed3e6b6 | ||
|
|
ed8ace7884 | ||
|
|
14761ebec2 | ||
|
|
50d8a19397 | ||
|
|
719324981d | ||
|
|
182c06107f | ||
|
|
d4d4852a0c | ||
|
|
f352fed313 | ||
|
|
5dfce2d199 | ||
|
|
6aafac544b | ||
|
|
2589c29b1c | ||
|
|
076c38526a | ||
|
|
9fd5cfa777 | ||
|
|
c01db8783d | ||
|
|
77101a96a1 | ||
|
|
46863b470c | ||
|
|
736c1f7e1e | ||
|
|
4cf9679334 | ||
|
|
89c9290602 | ||
|
|
3ba75c5a62 | ||
|
|
4ceaed7183 | ||
|
|
f310497d47 | ||
|
|
b3d9e0d1b0 | ||
|
|
7c3d96d0e7 | ||
|
|
cdd730e627 | ||
|
|
21cca34392 | ||
|
|
d64427d726 | ||
|
|
87ccacb99f | ||
|
|
b37773c630 | ||
|
|
4765a90f97 | ||
|
|
29587cd07c | ||
|
|
2651d99676 | ||
|
|
44e5525e6f | ||
|
|
5e48f6898d | ||
|
|
08d48f42ad | ||
|
|
4600dbcda5 | ||
|
|
c1b2ff20de | ||
|
|
94bcaa71e6 | ||
|
|
67bdccbda6 | ||
|
|
f89dabbd0a | ||
|
|
76eee60ad5 | ||
|
|
ac4e4959eb | ||
|
|
7316635aba | ||
|
|
feb277049f | ||
|
|
35d5febd7d | ||
|
|
97a3bfca4e | ||
|
|
cf58133e06 | ||
|
|
0d1811a4ae | ||
|
|
2a8bb715f0 | ||
|
|
cf93869d3f | ||
|
|
77976a5253 | ||
|
|
5af46aaa2e | ||
|
|
679c634459 | ||
|
|
cc33fc2822 | ||
|
|
4f4b10fd86 | ||
|
|
26789115b6 | ||
|
|
87a28dcb9f | ||
|
|
1ad99f39e2 | ||
|
|
dfbeec199e | ||
|
|
41a99ec29d | ||
|
|
7cee3aa1f1 | ||
|
|
b86d8099f1 | ||
|
|
9c4a0043dd | ||
|
|
5cde755976 | ||
|
|
51aab7b656 | ||
|
|
791dc213fa | ||
|
|
8404165db3 | ||
|
|
aee52c01a3 | ||
|
|
58e16b0c48 | ||
|
|
3225ac88c0 | ||
|
|
38b8b85ec2 | ||
|
|
278f748c1c | ||
|
|
874df40303 | ||
|
|
0465d8ce80 | ||
|
|
e9b3f5fd43 | ||
|
|
c49bc2418b | ||
|
|
fd21f5195d | ||
|
|
a1aa63fa06 | ||
|
|
b0a619c714 | ||
|
|
ec648f2c6f | ||
|
|
7ac7c72e03 | ||
|
|
f1d00dbd7f | ||
|
|
f644aba7a8 | ||
|
|
ed75287c8c | ||
|
|
a035c73c41 | ||
|
|
781a619262 | ||
|
|
e08b63cc9f | ||
|
|
97ed57a252 | ||
|
|
71f1779c8c | ||
|
|
116f44cade | ||
|
|
2ebdd689ab | ||
|
|
beecc1a718 | ||
|
|
557ba2adc1 | ||
|
|
89780c1283 | ||
|
|
411e7f86c1 | ||
|
|
610fce67e1 | ||
|
|
d9350cd3ed | ||
|
|
54d6643a1f | ||
|
|
f664420628 | ||
|
|
d4d4c6de68 | ||
|
|
b4313599f8 | ||
|
|
6866f8f0a9 | ||
|
|
3cb478214b | ||
|
|
8d7b4d6446 | ||
|
|
cc6b0bcd72 | ||
|
|
831564cf48 | ||
|
|
4e59b62026 | ||
|
|
f345b9b0ff | ||
|
|
d4fb88a8c4 | ||
|
|
2cb1b6be46 | ||
|
|
feda315c2b | ||
|
|
41219a7d85 | ||
|
|
813a201b6a | ||
|
|
1bc40d48fe | ||
|
|
ddb4e51938 | ||
|
|
9ee67c343d | ||
|
|
bb816eae83 | ||
|
|
92bd446d09 | ||
|
|
a7526fa9c4 | ||
|
|
9b6ff487da | ||
|
|
9d61490743 | ||
|
|
9cce6e41fa | ||
|
|
a56dbdf502 | ||
|
|
7d5f27fa34 | ||
|
|
a61ba0db22 | ||
|
|
7f02a889e2 | ||
|
|
88d388a574 | ||
|
|
4d5a659e1e | ||
|
|
ecfcf1071d | ||
|
|
ec385d45e9 | ||
|
|
985107bb4b | ||
|
|
eb340c74ac | ||
|
|
9a16b33f00 | ||
|
|
0acebc5916 | ||
|
|
8442d9fe5f | ||
|
|
5318dd1a80 | ||
|
|
d916180ec8 | ||
|
|
6e5aa08ee0 | ||
|
|
83e115cde5 | ||
|
|
a3a92cd5dd | ||
|
|
16fc10fb0f | ||
|
|
6bba8b57d4 | ||
|
|
f45c75a137 | ||
|
|
3348df0652 | ||
|
|
3b6a01b63d | ||
|
|
a3554a95c5 | ||
|
|
6fa9af20c0 | ||
|
|
e23437dfa4 | ||
|
|
dcf9eaad77 | ||
|
|
768df05692 | ||
|
|
e0d5d35e32 | ||
|
|
a718a05414 | ||
|
|
953391d9d0 | ||
|
|
4ca229fd42 | ||
|
|
693a43efc8 | ||
|
|
f3d4c931f5 | ||
|
|
ece21315b1 | ||
|
|
318cc57ffe | ||
|
|
ba17924174 | ||
|
|
6bc9daa6ee | ||
|
|
2d8bc95bae | ||
|
|
520eb4a932 | ||
|
|
03733516cc | ||
|
|
e561130336 | ||
|
|
169fa2e7b7 | ||
|
|
6d6db996fb | ||
|
|
f8642bfd94 | ||
|
|
dfdd6bf533 | ||
|
|
91cef71048 | ||
|
|
3daf632384 | ||
|
|
dba5fb9dfa | ||
|
|
70d10a0bb2 | ||
|
|
97291b806a | ||
|
|
c583f008e9 | ||
|
|
b182eba56f | ||
|
|
de5da0e7ce | ||
|
|
f2a893c0d4 | ||
|
|
371a03c794 | ||
|
|
88e3d48be5 | ||
|
|
e8a8681a75 | ||
|
|
b541eec3b8 | ||
|
|
ce3f732645 | ||
|
|
1b3cae1ed5 | ||
|
|
024b9130a0 | ||
|
|
61a8b6020f | ||
|
|
d25877aace | ||
|
|
c3c2d4dc22 | ||
|
|
7454154599 | ||
|
|
04ecea614b | ||
|
|
084a14b640 | ||
|
|
a477759a49 | ||
|
|
d4df207612 | ||
|
|
57d492d4b8 | ||
|
|
fb7413436c | ||
|
|
2cc8105e68 | ||
|
|
a83d5ff123 | ||
|
|
a709d650df | ||
|
|
570e8eae31 | ||
|
|
22f1703bee | ||
|
|
14037eaeb8 | ||
|
|
189d64dc3d | ||
|
|
315f8093c6 | ||
|
|
a776d70e0d | ||
|
|
1ab6c70ac7 | ||
|
|
73eb8701dd | ||
|
|
a503f291e7 | ||
|
|
b54d800024 | ||
|
|
d74068464d | ||
|
|
86a5e8dbe1 | ||
|
|
24d2b683c8 | ||
|
|
9451f0abe4 | ||
|
|
544d58ddbd | ||
|
|
7c1d2d75e0 | ||
|
|
0b949f47d9 | ||
|
|
393b45dd21 | ||
|
|
6bb43555dc | ||
|
|
ba03b07602 | ||
|
|
0ac4c0b97d | ||
|
|
41e93a4d94 | ||
|
|
a0b5b19d38 | ||
|
|
65fad5ae30 | ||
|
|
a689f2116c | ||
|
|
68b5c90d95 | ||
|
|
44eb9af7bc | ||
|
|
9e824e6070 | ||
|
|
ba6bb527a7 | ||
|
|
3525eeae54 | ||
|
|
e535e01e83 | ||
|
|
3b1c81e50e | ||
|
|
89406e5b7d | ||
|
|
d5cc855c0f | ||
|
|
2ed61eaf92 | ||
|
|
865f71e2cc | ||
|
|
08a75808df | ||
|
|
c9dd37db8e | ||
|
|
7e86c8a90c | ||
|
|
b87a5496e9 | ||
|
|
9a88363437 | ||
|
|
c0b9665cfc | ||
|
|
70284ce1c2 | ||
|
|
48621dadaa | ||
|
|
e69bad57e4 | ||
|
|
f55099e969 | ||
|
|
76994facec | ||
|
|
2d843f6e79 | ||
|
|
e15518dd29 | ||
|
|
011e0fd109 | ||
|
|
5f125c1812 | ||
|
|
a6ebed8d16 | ||
|
|
849e02cbfb | ||
|
|
1ab5238405 | ||
|
|
ae6192111d | ||
|
|
59af4114dd | ||
|
|
4491b88763 | ||
|
|
92377fc390 | ||
|
|
79b8e50412 | ||
|
|
b5cd39cc50 | ||
|
|
63c2a98f3c | ||
|
|
c168f78a13 | ||
|
|
42d45b4037 | ||
|
|
c41df63629 | ||
|
|
330bb81206 | ||
|
|
d84c755ee8 | ||
|
|
a24ff28031 | ||
|
|
3a837c472e | ||
|
|
f4880e2ef3 | ||
|
|
a2e4022e31 | ||
|
|
5668b41daa | ||
|
|
c052270048 | ||
|
|
70547171ca | ||
|
|
881868bf17 | ||
|
|
e90eb39a9b | ||
|
|
9513d307a1 | ||
|
|
533e5ec03f | ||
|
|
55f63395c7 | ||
|
|
4253d98a73 | ||
|
|
0c4af58866 | ||
|
|
a5919bd27d | ||
|
|
5e40974fdd | ||
|
|
a27f4d0e04 | ||
|
|
1bb3041298 | ||
|
|
dc6040adda | ||
|
|
864725ff3d | ||
|
|
0fcbea03e5 | ||
|
|
2ce63e6ad3 | ||
|
|
d45af94cee | ||
|
|
1f874b654d | ||
|
|
a7a21757bc | ||
|
|
1bed4e8972 | ||
|
|
efcda12dda | ||
|
|
d7b4079ab5 | ||
|
|
2eb1513612 | ||
|
|
87bcbe0420 | ||
|
|
26b0c6e6da | ||
|
|
8cfbc9b827 | ||
|
|
46eee9e642 | ||
|
|
7d902d2f3e | ||
|
|
3d05007024 | ||
|
|
a2bf2e2910 | ||
|
|
9cf4286cee | ||
|
|
c9ddca3a16 | ||
|
|
06370cb096 | ||
|
|
342586519d | ||
|
|
89174bb524 | ||
|
|
1fa3bc4018 | ||
|
|
d68e2b33fb | ||
|
|
1313cd8216 | ||
|
|
7afc8c760c | ||
|
|
79f714ab16 | ||
|
|
e21bab2d17 | ||
|
|
4288cf2a39 | ||
|
|
9d570a9cb1 | ||
|
|
333ddfb37a | ||
|
|
10a5250527 | ||
|
|
fd57eccdca | ||
|
|
1432be9be6 | ||
|
|
1773bbf759 | ||
|
|
5c94624186 | ||
|
|
dc06c2fab3 | ||
|
|
f37799b39c | ||
|
|
cc84f590fe | ||
|
|
afcf3e77b5 | ||
|
|
0406dda2a6 | ||
|
|
935227f7e7 | ||
|
|
67ebb2566d | ||
|
|
1c736161c5 | ||
|
|
a348c2d013 | ||
|
|
444a95bc6c | ||
|
|
c38cdef220 | ||
|
|
5608db0892 | ||
|
|
98b3722a02 | ||
|
|
2d623ff196 | ||
|
|
703e124277 | ||
|
|
68c27f885f | ||
|
|
58cd190ca9 | ||
|
|
2c5c2b2f67 | ||
|
|
e10fe50c6f | ||
|
|
b8ad0b15e8 | ||
|
|
1b81fb0fdf | ||
|
|
703cb8849d | ||
|
|
55f856b23c | ||
|
|
93c284a67d | ||
|
|
38fd715247 | ||
|
|
987d024847 | ||
|
|
a3c0d628a1 | ||
|
|
3cb6ec9ddb | ||
|
|
c21619608e | ||
|
|
76a8789bc1 | ||
|
|
bbf538e06c | ||
|
|
d94ab3395b | ||
|
|
6d8457a61f | ||
|
|
303f8e1bc9 | ||
|
|
e638116a2f | ||
|
|
5bac9133e6 | ||
|
|
6fde4041ba | ||
|
|
d0c8b38ffc | ||
|
|
be7888ab18 | ||
|
|
11b71fa28c | ||
|
|
c838f9bfd5 | ||
|
|
8d073ce221 | ||
|
|
0be5d39453 | ||
|
|
10d599f26a | ||
|
|
6b76ed8098 | ||
|
|
74ecdf2d3f | ||
|
|
20eacfab0f | ||
|
|
0396e15a3b | ||
|
|
3432a936ea | ||
|
|
87cd2b5dfe | ||
|
|
313b91edbe | ||
|
|
a1f104cb4d | ||
|
|
eadb8d5d0a | ||
|
|
41ae947885 | ||
|
|
84f610c0e9 | ||
|
|
0df959cf68 | ||
|
|
a15d10ea1e | ||
|
|
a37d6e86df | ||
|
|
a405063385 | ||
|
|
f5a5b85e9d | ||
|
|
ba7d941e5b | ||
|
|
804a2d1af9 | ||
|
|
c1bc6e161e | ||
|
|
af1b728b90 | ||
|
|
14e37a82ab | ||
|
|
f8a7854efa | ||
|
|
072ce87051 | ||
|
|
cac9b4460e | ||
|
|
67bf90a149 | ||
|
|
af8558b19e | ||
|
|
1903542f11 | ||
|
|
3ed16fb796 | ||
|
|
6f99392eda | ||
|
|
680dbfbf77 | ||
|
|
51a72b497b | ||
|
|
d6e57dd194 | ||
|
|
e92ab55da6 | ||
|
|
c78b3e0204 | ||
|
|
ac1c05389a | ||
|
|
95583fe2cd | ||
|
|
ddedda9233 | ||
|
|
d30b9d1513 | ||
|
|
4c35f88ea0 | ||
|
|
e6623ae0a8 | ||
|
|
4755578822 | ||
|
|
319d543ac2 | ||
|
|
9e668cda7f | ||
|
|
5447483da2 | ||
|
|
8e22a8d107 | ||
|
|
18d27ab4e4 | ||
|
|
fe60832492 | ||
|
|
6bf683409f | ||
|
|
634bfb1eae | ||
|
|
c9d597d2b1 | ||
|
|
92c616f717 | ||
|
|
a1286d0d4d | ||
|
|
eef3a3afeb | ||
|
|
b1e8d29ae0 | ||
|
|
e0cef55fcd | ||
|
|
9aacf5c7db | ||
|
|
4858749a20 | ||
|
|
b53c0b982a | ||
|
|
2aac9ff9c5 | ||
|
|
bdaa70ada5 | ||
|
|
ca6abdfc61 | ||
|
|
e18593fe88 | ||
|
|
08401aff26 | ||
|
|
cddc67ad69 | ||
|
|
586af2a435 | ||
|
|
97091fab60 | ||
|
|
5f2cf75be8 | ||
|
|
37c7b81c95 | ||
|
|
779a266713 | ||
|
|
ebaedc6f05 | ||
|
|
08a392787a | ||
|
|
883bf74bad | ||
|
|
80de3335b7 | ||
|
|
794072bdf8 | ||
|
|
e28fdb9cb1 | ||
|
|
7873d1c6b3 | ||
|
|
f8c069132e | ||
|
|
c53833072f | ||
|
|
16d7cf7a52 | ||
|
|
3b9c2b9729 | ||
|
|
b230fea66f | ||
|
|
f9b6501af1 | ||
|
|
0cd8e3701d | ||
|
|
8545672839 | ||
|
|
4a6c4b95f1 | ||
|
|
c893bc21ab | ||
|
|
c8c5cbf8cc | ||
|
|
54ef8a1e19 | ||
|
|
82df218bcb | ||
|
|
f613ad6c05 | ||
|
|
4e732d0379 | ||
|
|
ad8b5c9d29 | ||
|
|
1ab4bf14dc | ||
|
|
068a0b4576 | ||
|
|
5f694f228f | ||
|
|
2ffe49130d | ||
|
|
f7d18ef976 | ||
|
|
8233eb6007 | ||
|
|
de4693cdf3 | ||
|
|
8fb235c3f5 | ||
|
|
6feb00dcd9 | ||
|
|
4fc1847a70 | ||
|
|
e7f16f07f7 | ||
|
|
d9551b3106 | ||
|
|
854428795b | ||
|
|
5c3b4bd987 | ||
|
|
9d686d3e52 | ||
|
|
b62e00b935 | ||
|
|
634745c818 | ||
|
|
490c6d9a28 | ||
|
|
e6dd9978cb | ||
|
|
240a5613a5 | ||
|
|
fb96cbcaaf | ||
|
|
b58f879db7 | ||
|
|
1585c6095e | ||
|
|
30a630412d | ||
|
|
e5ca79cd51 | ||
|
|
148353aca4 | ||
|
|
7a098ce740 | ||
|
|
f3b9831a0c | ||
|
|
01454674c8 | ||
|
|
5093b18ecc | ||
|
|
c16cfd0668 | ||
|
|
1674cd5db9 | ||
|
|
f111ab48fb | ||
|
|
65309854ac | ||
|
|
cf0aff8c40 | ||
|
|
16b768485a | ||
|
|
c2e7b533d3 | ||
|
|
539859f1ab | ||
|
|
d1d40a9a76 | ||
|
|
52162a5604 | ||
|
|
84e84207a5 | ||
|
|
a4e1745eca | ||
|
|
5931979b74 | ||
|
|
a412a65315 | ||
|
|
25da0674bb | ||
|
|
c823bf4fbb | ||
|
|
cfd0e67a6b | ||
|
|
94f3af57f1 | ||
|
|
0050a3fe6c | ||
|
|
0e65ddee37 | ||
|
|
101f7de889 | ||
|
|
a21c1ff92d | ||
|
|
f9bb2e498e | ||
|
|
f6bb803be5 | ||
|
|
204a88c171 | ||
|
|
012afc0708 | ||
|
|
cf089abb64 | ||
|
|
40e463cdc1 | ||
|
|
6a1284a5ca | ||
|
|
60522ee474 | ||
|
|
0045641db7 | ||
|
|
97b5b1b669 | ||
|
|
448de8519a | ||
|
|
95e1fe0446 | ||
|
|
47254be254 | ||
|
|
3da15bfd19 | ||
|
|
c79db2581b | ||
|
|
93b86a8800 | ||
|
|
56ba7ef411 | ||
|
|
e2117fd8a9 | ||
|
|
e817c76e38 | ||
|
|
65e059a7d2 | ||
|
|
f661025acc | ||
|
|
2fe1b1e16e | ||
|
|
665ffe3984 | ||
|
|
c3401047e0 | ||
|
|
996177ceaf | ||
|
|
09e998523f | ||
|
|
38e8d27416 | ||
|
|
813de04596 | ||
|
|
3c0eae4180 | ||
|
|
99424a9f53 | ||
|
|
51d118fdb5 | ||
|
|
a26509a1fd | ||
|
|
0db70379e8 | ||
|
|
c612b5d17b | ||
|
|
1b469cce49 | ||
|
|
c62cbd2d77 | ||
|
|
da1bc19052 | ||
|
|
8e2246ec5c | ||
|
|
5cf6945bcb | ||
|
|
7132ae47d7 | ||
|
|
96bde4ad03 | ||
|
|
85b9bdd8f4 | ||
|
|
e5a85108d7 | ||
|
|
aaa3a8ebda | ||
|
|
9ceb766a67 | ||
|
|
f894240fbb | ||
|
|
4bac8e2ebe | ||
|
|
d3ad1fd384 | ||
|
|
ed0e4189e4 | ||
|
|
5f147242be | ||
|
|
c7b4c22b94 | ||
|
|
667bacf81e | ||
|
|
e896b0ea96 | ||
|
|
3b0a5a8b41 | ||
|
|
81d39e9bde | ||
|
|
59eabf03a6 | ||
|
|
4fc4987c43 | ||
|
|
dbf5bb149a | ||
|
|
38acbf6970 | ||
|
|
ad4bd91751 | ||
|
|
1a6f290979 | ||
|
|
c6e02a620a | ||
|
|
90efaa41c2 | ||
|
|
51a73ad8b5 | ||
|
|
93f2387d1b | ||
|
|
207a631a65 | ||
|
|
74e60e98b7 | ||
|
|
6ca6bf7457 | ||
|
|
ea49cdeb17 | ||
|
|
bcc8b1917a | ||
|
|
67b12d4416 | ||
|
|
dc02daecee | ||
|
|
6212b38ea6 | ||
|
|
ce7e5726e7 | ||
|
|
1156b3f22e | ||
|
|
66097f3507 | ||
|
|
146f02d314 | ||
|
|
77e5882ce7 | ||
|
|
d44850a4f3 | ||
|
|
e6e692dc43 | ||
|
|
dc65770ae3 | ||
|
|
8c15802277 | ||
|
|
3d666d9929 | ||
|
|
c0c960ec2e | ||
|
|
2bb4db127c | ||
|
|
dd1a5d4f58 | ||
|
|
961fdf7029 | ||
|
|
ff3d2b006f | ||
|
|
d7efbad3df | ||
|
|
d70995bb1a | ||
|
|
1b58e95dce | ||
|
|
780f70d5c6 | ||
|
|
a01e1f96fa | ||
|
|
3adb443ca5 | ||
|
|
dc9ff3a004 | ||
|
|
f879f4f432 | ||
|
|
183f831a7e | ||
|
|
3c361be621 | ||
|
|
5764d44faf | ||
|
|
ce86677faa | ||
|
|
17d93b39d5 | ||
|
|
1357b85a3d | ||
|
|
c67cb5c604 | ||
|
|
6ffb0df6cb | ||
|
|
032b40c78d | ||
|
|
742aea115b | ||
|
|
0f16cd46f9 | ||
|
|
eea64cf272 | ||
|
|
3d5c5f8054 | ||
|
|
748099a324 | ||
|
|
9c1d050d8b | ||
|
|
4ef834e295 | ||
|
|
50bead172b | ||
|
|
ee4508af03 | ||
|
|
6638959d66 | ||
|
|
f266a9d25d | ||
|
|
6cb8e1a518 | ||
|
|
85375359ed | ||
|
|
17c9ba2c68 | ||
|
|
34334ad8b8 | ||
|
|
4527714094 | ||
|
|
b43d74dbb7 | ||
|
|
0c4140ff02 | ||
|
|
b074c02fa2 | ||
|
|
7636c97f9f | ||
|
|
4211ab28b0 | ||
|
|
cecfbc7e20 | ||
|
|
31a6aff932 | ||
|
|
c4a67c4356 | ||
|
|
9f5765134b | ||
|
|
0c5b51d2ac | ||
|
|
31c4198cee | ||
|
|
a94c4b4ce4 | ||
|
|
088dd99ec1 | ||
|
|
4f9b907b4d | ||
|
|
e7dc3e6062 | ||
|
|
53055e78eb | ||
|
|
9a9c34aa18 | ||
|
|
2f1384840c | ||
|
|
b462e55799 | ||
|
|
263ec60ba6 | ||
|
|
8d44a57200 | ||
|
|
976722c129 | ||
|
|
4a9447d344 | ||
|
|
ac2ed9aa87 | ||
|
|
51cf241dae | ||
|
|
f239b8e26d | ||
|
|
ab9f4161ea | ||
|
|
1d10e649b7 | ||
|
|
a95b40aac6 | ||
|
|
1b5777821f | ||
|
|
587d469193 | ||
|
|
6c3e30f3ea | ||
|
|
91dbcae9e2 | ||
|
|
fb5db4f6b7 | ||
|
|
a1e029a825 | ||
|
|
54dbf9b6f2 | ||
|
|
b6344951fe | ||
|
|
ea972118b5 | ||
|
|
c980fd0e77 | ||
|
|
2fccd4799d | ||
|
|
a3937e4d0d | ||
|
|
acb022d5d5 | ||
|
|
b6e46d6101 | ||
|
|
347f75f804 | ||
|
|
4005ced505 | ||
|
|
c748c35b37 | ||
|
|
a73836ca43 | ||
|
|
b357fab326 | ||
|
|
16a3000451 | ||
|
|
c4fac2d179 | ||
|
|
60d11a6eba | ||
|
|
93edbda984 | ||
|
|
75bd94d757 | ||
|
|
a5cf0b6ef5 | ||
|
|
506280d645 | ||
|
|
2f79b4fde7 | ||
|
|
846f98628d | ||
|
|
eed9be5a9e | ||
|
|
27c77afafc | ||
|
|
c156b2f817 | ||
|
|
96fcc5df6b | ||
|
|
3c775fd5de | ||
|
|
75e9ee6528 | ||
|
|
ea0ee96398 | ||
|
|
3fd4a2841a | ||
|
|
6ecf44c87a | ||
|
|
031e8cea6e | ||
|
|
757fbb0124 | ||
|
|
d65e3f73df | ||
|
|
5b13105d58 | ||
|
|
c8745afb37 | ||
|
|
85189c0bde | ||
|
|
a8635bade2 | ||
|
|
4560572ff2 | ||
|
|
c7fa57fd14 | ||
|
|
54965fdf2e | ||
|
|
30361aa685 | ||
|
|
8be93b72c4 | ||
|
|
c7e9d645e5 | ||
|
|
fe6a3c89f3 | ||
|
|
686a32cbc0 | ||
|
|
55d7397ff5 | ||
|
|
3714d554df | ||
|
|
0415de853b | ||
|
|
0ba1e8f904 | ||
|
|
58bfcb0953 | ||
|
|
fa281d89d2 | ||
|
|
908b10dae0 | ||
|
|
ea03f9def0 | ||
|
|
3510799fca | ||
|
|
1f4a63d6db | ||
|
|
dd94a444d2 | ||
|
|
50fafc9ff6 | ||
|
|
47fc6a689d | ||
|
|
86175a1827 | ||
|
|
6d6e25df4e | ||
|
|
5402ed112c | ||
|
|
235b83d02e | ||
|
|
6ffbec969a | ||
|
|
185ea71646 | ||
|
|
69fcc3acd7 | ||
|
|
da94cf4aab | ||
|
|
8799cf95b4 | ||
|
|
108ce18d51 | ||
|
|
f67ea5d010 | ||
|
|
dd857aeccf | ||
|
|
44b1acd385 | ||
|
|
b2f6018e05 | ||
|
|
bca6507f11 | ||
|
|
30332c2ba5 | ||
|
|
17919d7503 | ||
|
|
42237ced80 | ||
|
|
737021ccdf | ||
|
|
22ed9d8d7c | ||
|
|
49dcd11813 | ||
|
|
7c30086d78 | ||
|
|
e2dbfdc537 | ||
|
|
3e8f9aa31c | ||
|
|
674eb109c2 | ||
|
|
927b5bc8cc | ||
|
|
f7dd0fc582 | ||
|
|
35f30bde04 | ||
|
|
a682b50fd4 | ||
|
|
3b5024749f | ||
|
|
2a56d892d7 | ||
|
|
e3d5eaf388 | ||
|
|
5d1f50117b | ||
|
|
f6a2ec15d7 | ||
|
|
64a8d56725 | ||
|
|
71caea32e7 | ||
|
|
17993ef9ff | ||
|
|
b0aa4ef4c8 | ||
|
|
5c4aaa27d9 | ||
|
|
53586d95d0 | ||
|
|
3877ab1f00 | ||
|
|
2425322e8d | ||
|
|
3f80a113d1 | ||
|
|
9ad20849d3 | ||
|
|
c8c58280d8 | ||
|
|
d40505cd16 | ||
|
|
25c5f84090 | ||
|
|
a58293f04b | ||
|
|
1408fb41b8 | ||
|
|
cb7cb8e527 | ||
|
|
d750dbc703 | ||
|
|
91b417138a | ||
|
|
db5eabd927 | ||
|
|
cbcc0fde04 | ||
|
|
afebfe5f4f | ||
|
|
ee837889db | ||
|
|
a5f4cba72f |
86
.github/workflows/check_pypi_version.yml
vendored
Normal file
86
.github/workflows/check_pypi_version.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
name: Check PyPI Version
|
||||||
|
|
||||||
|
# Check to be sure `pip install aider-chat` installs the most recently published version.
|
||||||
|
# If dependencies get yanked, it may render the latest version uninstallable.
|
||||||
|
# See https://github.com/Aider-AI/aider/issues/3699 for example.
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run once a day at midnight UTC
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
workflow_dispatch: # Allows manual triggering
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install aider-chat
|
||||||
|
run: pip install aider-chat
|
||||||
|
|
||||||
|
- name: Get installed aider version
|
||||||
|
id: installed_version
|
||||||
|
run: |
|
||||||
|
set -x # Enable debugging output
|
||||||
|
aider_version_output=$(aider --version)
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: 'aider --version' command failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Raw aider --version output: $aider_version_output"
|
||||||
|
|
||||||
|
# Extract version number (format X.Y.Z)
|
||||||
|
version_num=$(echo "$aider_version_output" | grep -oP '\d+\.\d+\.\d+')
|
||||||
|
|
||||||
|
# Check if grep found anything
|
||||||
|
if [ -z "$version_num" ]; then
|
||||||
|
echo "Error: Could not extract version number using grep -oP '\d+\.\d+\.\d+' from output: $aider_version_output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Extracted version number: $version_num"
|
||||||
|
echo "version=$version_num" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Fetch all history for all tags
|
||||||
|
|
||||||
|
- name: Get latest tag
|
||||||
|
id: latest_tag
|
||||||
|
run: |
|
||||||
|
set -x # Enable debugging output
|
||||||
|
# Fetch all tags from remote just in case
|
||||||
|
git fetch --tags origin main
|
||||||
|
# Get the latest tag that strictly matches vX.Y.Z (no suffixes like .dev)
|
||||||
|
# List all tags, sort by version descending, filter for exact pattern, take the first one
|
||||||
|
latest_tag=$(git tag --sort=-v:refname | grep -P '^v\d+\.\d+\.\d+$' | head -n 1)
|
||||||
|
|
||||||
|
if [ -z "$latest_tag" ]; then
|
||||||
|
echo "Error: Could not find any tags matching the pattern '^v\d+\.\d+\.\d+$'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Latest non-dev tag: $latest_tag"
|
||||||
|
# Remove 'v' prefix for comparison
|
||||||
|
tag_num=${latest_tag#v}
|
||||||
|
echo "Extracted tag number: $tag_num"
|
||||||
|
echo "tag=$tag_num" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Compare versions
|
||||||
|
run: |
|
||||||
|
echo "Installed version: ${{ steps.installed_version.outputs.version }}"
|
||||||
|
echo "Latest tag version: ${{ steps.latest_tag.outputs.tag }}"
|
||||||
|
if [ "${{ steps.installed_version.outputs.version }}" != "${{ steps.latest_tag.outputs.tag }}" ]; then
|
||||||
|
echo "Error: Installed aider version (${{ steps.installed_version.outputs.version }}) does not match the latest tag (${{ steps.latest_tag.outputs.tag }})."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Versions match."
|
||||||
48
.github/workflows/pre-commit.yml
vendored
Normal file
48
.github/workflows/pre-commit.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: pre-commit
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
pre-commit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
RAW_LOG: pre-commit.log
|
||||||
|
CS_XML: pre-commit.xml
|
||||||
|
steps:
|
||||||
|
- run: sudo apt-get update && sudo apt-get install cppcheck uncrustify
|
||||||
|
if: false
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: python -m pip install pre-commit
|
||||||
|
- uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pre-commit/
|
||||||
|
key: pre-commit-4|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
|
- name: Run pre-commit hooks
|
||||||
|
env:
|
||||||
|
SKIP: no-commit-to-branch
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
pre-commit gc
|
||||||
|
pre-commit run --show-diff-on-failure --color=always --all-files | tee ${RAW_LOG}
|
||||||
|
- name: Convert Raw Log to Checkstyle format (launch action)
|
||||||
|
uses: mdeweerd/logToCheckStyle@v2025.1.1
|
||||||
|
if: ${{ failure() }}
|
||||||
|
with:
|
||||||
|
in: ${{ env.RAW_LOG }}
|
||||||
|
# out: ${{ env.CS_XML }}
|
||||||
|
- uses: actions/cache/save@v4
|
||||||
|
if: ${{ ! cancelled() }}
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pre-commit/
|
||||||
|
key: pre-commit-4|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
|
- name: Provide log as artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ ! cancelled() }}
|
||||||
|
with:
|
||||||
|
name: precommit-logs
|
||||||
|
path: |
|
||||||
|
${{ env.RAW_LOG }}
|
||||||
|
${{ env.CS_XML }}
|
||||||
|
retention-days: 2
|
||||||
2
.github/workflows/ubuntu-tests.yml
vendored
2
.github/workflows/ubuntu-tests.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
|
|||||||
2
.github/workflows/windows-tests.yml
vendored
2
.github/workflows/windows-tests.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
|
|||||||
90
.github/workflows/windows_check_pypi_version.yml
vendored
Normal file
90
.github/workflows/windows_check_pypi_version.yml
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
name: Windows Check PyPI Version
|
||||||
|
|
||||||
|
# Check to be sure `pip install aider-chat` installs the most recently published version on Windows.
|
||||||
|
# If dependencies get yanked, it may render the latest version uninstallable.
|
||||||
|
# See https://github.com/Aider-AI/aider/issues/3699 for example.
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run once a day at 1 AM UTC (offset from Ubuntu check)
|
||||||
|
- cron: '0 1 * * *'
|
||||||
|
workflow_dispatch: # Allows manual triggering
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_version:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: pwsh # Use PowerShell for all run steps
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install aider-chat
|
||||||
|
run: pip install aider-chat
|
||||||
|
|
||||||
|
- name: Get installed aider version
|
||||||
|
id: installed_version
|
||||||
|
run: |
|
||||||
|
Write-Host "Running 'aider --version'..."
|
||||||
|
$aider_version_output = aider --version
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Error: 'aider --version' command failed."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "Raw aider --version output: $aider_version_output"
|
||||||
|
|
||||||
|
# Extract version number (format X.Y.Z) using PowerShell regex
|
||||||
|
$match = [regex]::Match($aider_version_output, '\d+\.\d+\.\d+')
|
||||||
|
|
||||||
|
if (-not $match.Success) {
|
||||||
|
Write-Error "Error: Could not extract version number using regex '\d+\.\d+\.\d+' from output: $aider_version_output"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$version_num = $match.Value
|
||||||
|
|
||||||
|
Write-Host "Extracted version number: $version_num"
|
||||||
|
echo "version=$version_num" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Fetch all history for all tags
|
||||||
|
|
||||||
|
- name: Get latest tag
|
||||||
|
id: latest_tag
|
||||||
|
run: |
|
||||||
|
Write-Host "Fetching tags..."
|
||||||
|
# Fetch all tags from remote just in case
|
||||||
|
git fetch --tags origin main
|
||||||
|
Write-Host "Getting latest non-dev tag..."
|
||||||
|
# Get the latest tag that strictly matches vX.Y.Z (no suffixes like .dev)
|
||||||
|
# List all tags, sort by version descending, filter for exact pattern, take the first one
|
||||||
|
$latest_tag = (git tag --sort=-v:refname | Select-String -Pattern '^v\d+\.\d+\.\d+$' | Select-Object -First 1).Line
|
||||||
|
|
||||||
|
if (-not $latest_tag) {
|
||||||
|
Write-Error "Error: Could not find any tags matching the pattern '^v\d+\.\d+\.\d+$'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Latest non-dev tag: $latest_tag"
|
||||||
|
# Remove 'v' prefix for comparison
|
||||||
|
$tag_num = $latest_tag.Substring(1)
|
||||||
|
Write-Host "Extracted tag number: $tag_num"
|
||||||
|
echo "tag=$tag_num" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Compare versions
|
||||||
|
run: |
|
||||||
|
Write-Host "Installed version: ${{ steps.installed_version.outputs.version }}"
|
||||||
|
Write-Host "Latest tag version: ${{ steps.latest_tag.outputs.tag }}"
|
||||||
|
if ("${{ steps.installed_version.outputs.version }}" -ne "${{ steps.latest_tag.outputs.tag }}") {
|
||||||
|
Write-Error "Error: Installed aider version (${{ steps.installed_version.outputs.version }}) does not match the latest tag (${{ steps.latest_tag.outputs.tag }})."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "Versions match."
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ aider/_version.py
|
|||||||
.#*
|
.#*
|
||||||
.gitattributes
|
.gitattributes
|
||||||
tmp.benchmarks/
|
tmp.benchmarks/
|
||||||
|
.docker_bash_history
|
||||||
@@ -18,5 +18,6 @@ repos:
|
|||||||
rev: v2.2.6
|
rev: v2.2.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
|
args: ["--skip", "aider/website/docs/languages.md"]
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- tomli
|
- tomli
|
||||||
|
|||||||
359
HISTORY.md
359
HISTORY.md
@@ -1,6 +1,355 @@
|
|||||||
# Release history
|
# Release history
|
||||||
|
|
||||||
### main branch
|
### Aider v0.85.3
|
||||||
|
|
||||||
|
- Bumped dependencies to pick up latest litellm==1.75.0.
|
||||||
|
|
||||||
|
### Aider v0.85.2
|
||||||
|
|
||||||
|
- Added support for Grok-4 via `xai/grok-4` and `openrouter/x-ai/grok-4` model names.
|
||||||
|
- Added support for `gemini/gemini-2.5-flash-lite-preview-06-17` model, by Tamir Zahavi-Brunner.
|
||||||
|
- `/clear` now prints “All chat history cleared.” so you know it worked, by Zexin Yuan.
|
||||||
|
- `/undo` output now shows only the first line of each commit message, making it easier to read.
|
||||||
|
- Fixed an issue where new settings for an existing model didn't replace the old ones, by Andrew Grigorev.
|
||||||
|
- Added support for `openrouter/moonshotai/kimi-k2` model, by Jack Harrington.
|
||||||
|
|
||||||
|
### Aider v0.85.1
|
||||||
|
|
||||||
|
- Display model announcements with no-arg `/model` command.
|
||||||
|
|
||||||
|
### Aider v0.85.0
|
||||||
|
|
||||||
|
- Support for Responses API models like o1-pro, o3-pro.
|
||||||
|
- Updated pricing for o3.
|
||||||
|
- Added support for new Gemini models including `gemini-2.5-pro`, `gemini-2.5-flash`, and `gemini-2.5-pro-preview-06-05` with thinking tokens support.
|
||||||
|
- Updated model aliases: `flash` now points to `gemini-2.5-flash` and `gemini` now points to `gemini-2.5-pro`.
|
||||||
|
- Added `--add-gitignore-files` flag to enable adding files listed in .gitignore to Aider's editing scope, by omarcinkonis.
|
||||||
|
- Added `--commit-language` option to specify the language for commit messages, by Kyosuke Takayama.
|
||||||
|
- Enhanced thinking tokens support: can now be disabled by setting to 0, and improved help text with examples.
|
||||||
|
- Added MATLAB language support for repository maps, by Matthew Tofano.
|
||||||
|
- Added support for OpenAI o3-pro model across multiple providers.
|
||||||
|
- Improved GitHub Copilot token handling with better validation and error messages, by Vincent Taverna and Sebastian Estrella.
|
||||||
|
- Fixed encoding issues in git diff output and LLM history logging.
|
||||||
|
- Enhanced commit message generation to use system prompt prefixes, by Luke Reeves.
|
||||||
|
- Improved inline code rendering in Rich markdown output, by Vamsi Talupula.
|
||||||
|
- Fixed Vertex AI model name prefixes in settings, by Wietse Venema.
|
||||||
|
- Improved `/read-only` command to resolve literal paths correctly, by Matteo Landi.
|
||||||
|
- Skip expensive file tracking operations when `--skip-sanity-check-repo` is enabled for better performance, by Makar Ivashko.
|
||||||
|
- Ensure pip is available before package installation.
|
||||||
|
- Auto-create parent directories for chat history files to prevent startup errors, by contributor.
|
||||||
|
- Fixed search block regex to accept optional closing tags when working with HTML content, by Mathis Beer.
|
||||||
|
- Co-authored-by attribution is now enabled by default for commit messages.
|
||||||
|
- Added Clojure language support for repository maps, by Garrett Hopper.
|
||||||
|
- Added custom PostHog analytics configuration options with `--analytics-posthog-host` and `--analytics-posthog-project-api-key` flags, by Vasil Markoukin.
|
||||||
|
- Optimized chat history summarization performance, by jayeshthk.
|
||||||
|
- Improved kebab-case identifier recognition in repository maps for better code analysis.
|
||||||
|
- Increased max tokens for Deepseek models to 65536 for better performance.
|
||||||
|
- Aider wrote 21% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.84.0
|
||||||
|
|
||||||
|
- Added support for new Claude models including the Sonnet 4 and Opus 4 series (e.g., `claude-sonnet-4-20250514`,
|
||||||
|
`claude-opus-4-20250514`) across various providers. The default `sonnet` and `opus` aliases were updated to these newer
|
||||||
|
versions.
|
||||||
|
- Added support for the `vertex_ai/gemini-2.5-flash-preview-05-20` model.
|
||||||
|
- Fixed OpenRouter token cost calculation for improved accuracy.
|
||||||
|
- Updated default OpenRouter models during onboarding to `deepseek/deepseek-r1:free` for the free tier and
|
||||||
|
`anthropic/claude-sonnet-4` for paid tiers.
|
||||||
|
- Automatically refresh GitHub Copilot tokens when used as OpenAI API keys, by Lih Chen.
|
||||||
|
- Aider wrote 79% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.83.2
|
||||||
|
|
||||||
|
- Bumped configargparse to 1.7.1 as 1.7 was pulled.
|
||||||
|
- Added shell tab completion for file path arguments (by saviour) and for `--edit-format`/`--editor-edit-format` options.
|
||||||
|
- Improved OpenRouter model metadata handling by introducing a local cache, increasing reliability and performance.
|
||||||
|
- The `/settings` command now displays detailed metadata for active main, editor, and weak models.
|
||||||
|
- Fixed an issue where files explicitly added via the command line were not correctly ignored if listed in `.gitignore`.
|
||||||
|
- Improved automatic commit messages by providing more context during their generation, by wangboxue.
|
||||||
|
|
||||||
|
### Aider v0.83.1
|
||||||
|
|
||||||
|
- Improved user language detection by correctly normalizing hyphenated language codes (e.g., `en-US` to `en`) and enhancing the validation of locale results.
|
||||||
|
- Prevented Aider from instructing the LLM to reply in 'C' or 'POSIX' when these are detected as the system locale.
|
||||||
|
- Displayed a spinner with the model name when generating commit messages.
|
||||||
|
|
||||||
|
### Aider v0.83.0
|
||||||
|
|
||||||
|
- Added support for `gemini-2.5-pro-preview-05-06` models.
|
||||||
|
- Added support for `qwen3-235b` models.
|
||||||
|
- Added repo-map support for OCaml and OCaml interface files, by Andrey Popp.
|
||||||
|
- Added a spinner animation while waiting for the LLM to start streaming its response.
|
||||||
|
- Updated the spinner animation to a Knight Rider style.
|
||||||
|
- Introduced `--attribute-co-authored-by` option to add co-author trailer to commit messages, by Andrew Grigorev.
|
||||||
|
- Updated Gemini model aliases (e.g., `gemini`, `gemini-2.5-pro`) to point to the `05-06` preview versions.
|
||||||
|
- Marked Gemini 2.5 Pro preview models as `overeager` by default.
|
||||||
|
- Commit message prompt specifies the user's language.
|
||||||
|
- Updated the default weak model for Gemini 2.5 Pro models to `gemini/gemini-2.5-flash-preview-04-17`.
|
||||||
|
- Corrected `gemini-2.5-pro-exp-03-25` model settings to reflect its lack of support for `thinking_budget`.
|
||||||
|
- Ensured model-specific system prompt prefixes are placed on a new line before the main system prompt.
|
||||||
|
- Added tracking of total tokens sent and received, now included in benchmark statistics.
|
||||||
|
- Automatically fetch model parameters (context window, pricing) for OpenRouter models directly from their website, by Stefan Hladnik.
|
||||||
|
- Enabled support for `thinking_tokens` and `reasoning_effort` parameters for OpenRouter models.
|
||||||
|
- Improved cost calculation using `litellm.completion_cost` where available.
|
||||||
|
- Added model settings for `openrouter/google/gemini-2.5-pro-preview-03-25`.
|
||||||
|
- Added `--disable-playwright` flag to prevent Playwright installation prompts and usage, by Andrew Grigorev.
|
||||||
|
- The `aider scrape` command-line tool will now use Playwright for web scraping if it is available, by Jon Keys.
|
||||||
|
- Fixed linter command execution on Windows by adopting `oslex` for argument quoting, by Titusz Pan.
|
||||||
|
- Improved cross-platform display of shell commands by using `oslex` for robust argument quoting, by Titusz Pan.
|
||||||
|
- Improved `/ask` mode to instruct the LLM to elide unchanging code in its responses.
|
||||||
|
- Ensured web scraping in the GUI also respects Playwright availability and the `--disable-playwright` flag.
|
||||||
|
- Improved display of filenames in the prompt header using rich Text formatting.
|
||||||
|
- Enabled `reasoning_effort` for Gemini 2.5 Flash models.
|
||||||
|
- Added a `--shell-completions` argument to generate shell completion scripts (e.g., for bash, zsh).
|
||||||
|
- Explicit `--attribute-author` or `--attribute-committer` flags now override the default behavior when `--attribute-co-authored-by` is used, allowing finer control over commit attribution, by Andrew Grigorev.
|
||||||
|
- Fixed an issue where read-only status of files might not be preserved correctly by some commands (e.g. `/drop` after adding a read-only file).
|
||||||
|
- The `aider-args` utility (or `python -m aider.args`) now defaults to printing a sample YAML configuration if no arguments are provided.
|
||||||
|
- Displayed token count progress and the name of the file or identifier being processed during repo map updates.
|
||||||
|
- Extended the waiting spinner to also show for non-streaming responses and further enhanced its animation with console width clipping, cursor hiding, and a more continuous appearance.
|
||||||
|
- Dropped support for Python 3.9.
|
||||||
|
- Aider wrote 55% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.82.3
|
||||||
|
|
||||||
|
- Add support for `gemini-2.5-flash-preview-04-17` models.
|
||||||
|
- Improved robustness of edit block parsing when filenames start with backticks or fences.
|
||||||
|
- Add new `udiff-simple` edit format, for Gemini 2.5 Pro.
|
||||||
|
- Update default weak/editor models for Gemini 2.5 Pro models to use `gemini-2.5-flash-preview-04-17`.
|
||||||
|
- Instruct models to reply in the user's detected system language.
|
||||||
|
- Fix parsing of diffs for newly created files (`--- /dev/null`).
|
||||||
|
- Add markdown syntax highlighting support when editing multi-line commit messages via `/commit`, by Kay Gosho.
|
||||||
|
- Set Gemini 2.5 Pro models to use the `overeager` prompt setting by default.
|
||||||
|
- Add common file types (`.svg`, `.pdf`) to the default list of ignored files for AI comment scanning (`--watch`).
|
||||||
|
- Skip scanning files larger than 1MB for AI comments (`--watch`).
|
||||||
|
|
||||||
|
### Aider v0.82.2
|
||||||
|
|
||||||
|
- Fix editing shell files with diff-fenced, by zjy1412.
|
||||||
|
- Improve robustness of patch application by allowing multiple update/delete actions for the same file within a single response.
|
||||||
|
- Update prompts to instruct LLMs to consolidate all edits for a given file into a single block within the patch.
|
||||||
|
|
||||||
|
### Aider v0.82.1
|
||||||
|
|
||||||
|
- Added support for `o3` and `o4-mini` including provider-specific versions for OpenAI, OpenRouter, and Azure.
|
||||||
|
- Added support for Azure specific `gpt-4.1` and `gpt-4.1-mini` models.
|
||||||
|
- Disabled streaming for `o3` models since you need identity verification to stream.
|
||||||
|
- Fixed handling of file paths in unified diffs, especially those generated by git.
|
||||||
|
|
||||||
|
### Aider v0.82.0
|
||||||
|
|
||||||
|
- Support for GPT 4.1, mini and nano.
|
||||||
|
- Added new `patch` edit format for OpenAI's GPT-4.1 model.
|
||||||
|
- Improved support for using architect mode with Gemini 2.5 Pro.
|
||||||
|
- Added new `editor-diff`, `editor-whole`, and `editor-diff-fenced` edit formats.
|
||||||
|
- Bugfix for automatically selecting the best edit format to use in architect mode.
|
||||||
|
- Added support for `grok-3-fast-beta` and `grok-3-mini-fast-beta` models.
|
||||||
|
- Aider wrote 92% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.81.3
|
||||||
|
|
||||||
|
- Commit messages generated by aider are no longer forced to be entirely lowercase, by Peter Hadlaw.
|
||||||
|
- Updated default settings for Grok models.
|
||||||
|
|
||||||
|
### Aider v0.81.2
|
||||||
|
|
||||||
|
- Add support for `xai/grok-3-beta`, `xai/grok-3-mini-beta`, `openrouter/x-ai/grok-3-beta`, `openrouter/x-ai/grok-3-mini-beta`, and `openrouter/openrouter/optimus-alpha` models.
|
||||||
|
- Add alias "grok3" for `xai/grok-3-beta`.
|
||||||
|
- Add alias "optimus" for `openrouter/openrouter/optimus-alpha`.
|
||||||
|
- Fix URL extraction from error messages.
|
||||||
|
- Allow adding files by full path even if a file with the same basename is already in the chat.
|
||||||
|
- Fix quoting of values containing '#' in the sample `aider.conf.yml`.
|
||||||
|
- Add support for Fireworks AI model 'deepseek-v3-0324', by Felix Lisczyk.
|
||||||
|
- Commit messages generated by aider are now lowercase, by Anton Ödman.
|
||||||
|
|
||||||
|
### Aider v0.81.1
|
||||||
|
|
||||||
|
- Added support for the `gemini/gemini-2.5-pro-preview-03-25` model.
|
||||||
|
- Updated the `gemini` alias to point to `gemini/gemini-2.5-pro-preview-03-25`.
|
||||||
|
- Added the `gemini-exp` alias for `gemini/gemini-2.5-pro-exp-03-25`.
|
||||||
|
|
||||||
|
### Aider v0.81.0
|
||||||
|
|
||||||
|
- Added support for the `openrouter/openrouter/quasar-alpha` model.
|
||||||
|
- Run with `aider --model quasar`
|
||||||
|
- Offer OpenRouter OAuth authentication if an OpenRouter model is specified but the API key is missing.
|
||||||
|
- Prevent retrying API calls when the provider reports insufficient credits.
|
||||||
|
- Improve URL detection to exclude trailing double quotes.
|
||||||
|
- Aider wrote 86% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.80.4
|
||||||
|
|
||||||
|
- Bumped deps to pickup litellm change to properly display the root cause of OpenRouter "choices" errors.
|
||||||
|
|
||||||
|
### Aider v0.80.3
|
||||||
|
|
||||||
|
- Improve error message for OpenRouter API connection issues to mention potential rate limiting or upstream provider issues.
|
||||||
|
- Configure weak models (`gemini/gemini-2.0-flash` and `openrouter/google/gemini-2.0-flash-exp:free`) for Gemini 2.5 Pro models.
|
||||||
|
- Add model metadata for `openrouter/google/gemini-2.0-flash-exp:free`.
|
||||||
|
|
||||||
|
### Aider v0.80.2
|
||||||
|
|
||||||
|
- Bumped deps.
|
||||||
|
|
||||||
|
### Aider v0.80.1
|
||||||
|
|
||||||
|
- Updated deps for yanked fsspec and aiohttp packages #3699
|
||||||
|
- Removed redundant dependency check during OpenRouter OAuth flow, by Claudia Pellegrino.
|
||||||
|
|
||||||
|
### Aider v0.80.0
|
||||||
|
|
||||||
|
- OpenRouter OAuth integration:
|
||||||
|
- Offer to OAuth against OpenRouter if no model and keys are provided.
|
||||||
|
- Select OpenRouter default model based on free/paid tier status if `OPENROUTER_API_KEY` is set and no model is specified.
|
||||||
|
- Prioritize `gemini/gemini-2.5-pro-exp-03-25` if `GEMINI_API_KEY` is set, and `vertex_ai/gemini-2.5-pro-exp-03-25` if `VERTEXAI_PROJECT` is set, when no model is specified.
|
||||||
|
- Validate user-configured color settings on startup and warn/disable invalid ones.
|
||||||
|
- Warn at startup if `--stream` and `--cache-prompts` are used together, as cost estimates may be inaccurate.
|
||||||
|
- Boost repomap ranking for files whose path components match identifiers mentioned in the chat.
|
||||||
|
- Change web scraping timeout from an error to a warning, allowing scraping to continue with potentially incomplete content.
|
||||||
|
- Left-align markdown headings in the terminal output, by Peter Schilling.
|
||||||
|
- Update edit format to the new model's default when switching models with `/model`, if the user was using the old model's default format.
|
||||||
|
- Add `Ctrl-X Ctrl-E` keybinding to edit the current input buffer in an external editor, by Matteo Landi.
|
||||||
|
- Fix linting errors for filepaths containing shell metacharacters, by Mir Adnan ALI.
|
||||||
|
- Add the `openrouter/deepseek-chat-v3-0324:free` model.
|
||||||
|
- Add repomap support for the Scala language, by Vasil Markoukin.
|
||||||
|
- Fixed bug in `/run` that was preventing auto-testing.
|
||||||
|
- Fix bug preventing `UnboundLocalError` during git tree traversal.
|
||||||
|
- Handle `GitCommandNotFound` error if git is not installed or not in PATH.
|
||||||
|
- Handle `FileNotFoundError` if the current working directory is deleted while aider is running.
|
||||||
|
- Fix completion menu current item color styling, by Andrey Ivanov.
|
||||||
|
- Aider wrote 87% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.79.2
|
||||||
|
|
||||||
|
- Added 'gemini' alias for gemini-2.5-pro model.
|
||||||
|
- Updated Gemini 2.5 Pro max output tokens to 64k.
|
||||||
|
- Added support for Lisp-style semicolon comments in file watcher, by Matteo Landi.
|
||||||
|
- Added OpenRouter API error detection and retries.
|
||||||
|
- Added openrouter/deepseek-chat-v3-0324 model.
|
||||||
|
- Aider wrote 93% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.79.1
|
||||||
|
|
||||||
|
- Improved model listing to include all models in fuzzy matching, including those provided by aider (not litellm).
|
||||||
|
|
||||||
|
### Aider v0.79.0
|
||||||
|
|
||||||
|
- Added support for Gemini 2.5 Pro models.
|
||||||
|
- Added support for DeepSeek V3 0324 model.
|
||||||
|
- Added a new `/context` command that automatically identifies which files need to be edited for a given request.
|
||||||
|
- Added `/edit` as an alias for the `/editor` command.
|
||||||
|
- Added "overeager" mode for Claude 3.7 Sonnet models to try and keep it working within the requested scope.
|
||||||
|
- Aider wrote 65% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.78.0
|
||||||
|
|
||||||
|
- Added support for thinking tokens for OpenRouter Sonnet 3.7.
|
||||||
|
- Added commands to switch between model types: `/editor-model` for Editor Model, and `/weak-model` for Weak Model, by csala.
|
||||||
|
- Added model setting validation to ignore `--reasoning-effort` and `--thinking-tokens` if the model doesn't support them.
|
||||||
|
- Added `--check-model-accepts-settings` flag (default: true) to force unsupported model settings.
|
||||||
|
- Annotated which models support reasoning_effort and thinking_tokens settings in the model settings data.
|
||||||
|
- Improved code block rendering in markdown output with better padding using NoInsetMarkdown.
|
||||||
|
- Added `--git-commit-verify` flag (default: False) to control whether git commit hooks are bypassed.
|
||||||
|
- Fixed autocompletion for `/ask`, `/code`, and `/architect` commands, by shladnik.
|
||||||
|
- Added vi-like behavior when pressing enter in multiline-mode while in vi normal/navigation-mode, by Marco Mayer.
|
||||||
|
- Added AWS_PROFILE support for Bedrock models, allowing use of AWS profiles instead of explicit credentials, by lentil32.
|
||||||
|
- Enhanced `--aiderignore` argument to resolve both absolute and relative paths, by mopemope.
|
||||||
|
- Improved platform information handling to gracefully handle retrieval errors.
|
||||||
|
- Aider wrote 92% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.77.1
|
||||||
|
|
||||||
|
- Bumped dependencies to pickup litellm fix for Ollama.
|
||||||
|
- Added support for `openrouter/google/gemma-3-27b-it` model.
|
||||||
|
- Updated exclude patterns for help documentation.
|
||||||
|
|
||||||
|
### Aider v0.77.0
|
||||||
|
|
||||||
|
- Big upgrade in [programming languages supported](https://aider.chat/docs/languages.html) by adopting [tree-sitter-language-pack](https://github.com/Goldziher/tree-sitter-language-pack/).
|
||||||
|
- 130 new languages with linter support.
|
||||||
|
- 20 new languages with repo-map support.
|
||||||
|
- Added `/think-tokens` command to set thinking token budget with support for human-readable formats (8k, 10.5k, 0.5M).
|
||||||
|
- Added `/reasoning-effort` command to control model reasoning level.
|
||||||
|
- The `/think-tokens` and `/reasoning-effort` commands display current settings when called without arguments.
|
||||||
|
- Display of thinking token budget and reasoning effort in model information.
|
||||||
|
- Changed `--thinking-tokens` argument to accept string values with human-readable formats.
|
||||||
|
- Added `--auto-accept-architect` flag (default: true) to automatically accept changes from architect coder format without confirmation.
|
||||||
|
- Added support for `cohere_chat/command-a-03-2025` and `gemini/gemma-3-27b-it`
|
||||||
|
- The bare `/drop` command now preserves original read-only files provided via args.read.
|
||||||
|
- Fixed a bug where default model would be set by deprecated `--shortcut` switches even when already specified in the command line.
|
||||||
|
- Improved AutoCompleter to require 3 characters for autocompletion to reduce noise.
|
||||||
|
- Aider wrote 72% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.76.2
|
||||||
|
|
||||||
|
- Fixed handling of JSONDecodeError when loading model cache file.
|
||||||
|
- Fixed handling of GitCommandError when retrieving git user configuration.
|
||||||
|
- Aider wrote 75% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.76.1
|
||||||
|
|
||||||
|
- Added ignore_permission_denied option to file watcher to prevent errors when accessing restricted files, by Yutaka Matsubara.
|
||||||
|
- Aider wrote 0% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.76.0
|
||||||
|
|
||||||
|
- Improved support for thinking/reasoningmodels:
|
||||||
|
- Added `--thinking-tokens` CLI option to control token budget for models that support thinking.
|
||||||
|
- Display thinking/reasoning content from LLMs which return it.
|
||||||
|
- Enhanced handling of reasoning tags to better clean up model responses.
|
||||||
|
- Added deprecation warning for `remove_reasoning` setting, now replaced by `reasoning_tag`.
|
||||||
|
- Aider will notify you when it's completed the last request and needs your input:
|
||||||
|
- Added [notifications when LLM responses are ready](https://aider.chat/docs/usage/notifications.html) with `--notifications` flag.
|
||||||
|
- Specify desktop notification command with `--notifications-command`.
|
||||||
|
- Added support for QWQ 32B.
|
||||||
|
- Switch to `tree-sitter-language-pack` for tree sitter support.
|
||||||
|
- Improved error handling for EOF (Ctrl+D) in user input prompts.
|
||||||
|
- Added helper function to ensure hex color values have a # prefix.
|
||||||
|
- Fixed handling of Git errors when reading staged files.
|
||||||
|
- Improved SSL verification control for model information requests.
|
||||||
|
- Improved empty LLM response handling with clearer warning messages.
|
||||||
|
- Fixed Git identity retrieval to respect global configuration, by Akira Komamura.
|
||||||
|
- Offer to install dependencies for Bedrock and Vertex AI models.
|
||||||
|
- Deprecated model shortcut args (like --4o, --opus) in favor of the --model flag.
|
||||||
|
- Aider wrote 85% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.75.3
|
||||||
|
|
||||||
|
- Support for V3 free on OpenRouter: `--model openrouter/deepseek/deepseek-chat:free`.
|
||||||
|
|
||||||
|
### Aider v0.75.2
|
||||||
|
|
||||||
|
- Added support for Claude 3.7 Sonnet models on OpenRouter, Bedrock and Vertex AI.
|
||||||
|
- Updated default model to Claude 3.7 Sonnet on OpenRouter.
|
||||||
|
- Added support for GPT-4.5-preview model.
|
||||||
|
- Added support for Claude 3.7 Sonnet:beta on OpenRouter.
|
||||||
|
- Fixed weak_model_name patterns to match main model name patterns for some models.
|
||||||
|
|
||||||
|
### Aider v0.75.1
|
||||||
|
|
||||||
|
- Added support for `openrouter/anthropic/claude-3.7-sonnet`
|
||||||
|
|
||||||
|
### Aider v0.75.0
|
||||||
|
|
||||||
|
- Basic support for Claude 3.7 Sonnet
|
||||||
|
- Use `--model sonnet` to use the new 3.7
|
||||||
|
- Thinking support coming soon.
|
||||||
|
- Bugfix to `/editor` command.
|
||||||
|
- Aider wrote 46% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.74.3
|
||||||
|
|
||||||
|
- Downgrade streamlit dependency to avoid threading bug.
|
||||||
|
- Added support for tree-sitter language pack.
|
||||||
|
- Added openrouter/o3-mini-high model configuration.
|
||||||
|
- Added build.gradle.kts to special files for Kotlin project support, by Lucas Shadler.
|
||||||
|
|
||||||
|
### Aider v0.74.2
|
||||||
|
|
||||||
|
- Prevent more than one cache warming thread from becoming active.
|
||||||
|
- Fixed continuation prompt ". " for multiline input.
|
||||||
|
- Added HCL (Terraform) syntax support, by Warren Krewenki.
|
||||||
|
|
||||||
|
### Aider v0.74.1
|
||||||
|
|
||||||
- Have o1 & o3-mini generate markdown by sending the magic "Formatting re-enabled." string.
|
- Have o1 & o3-mini generate markdown by sending the magic "Formatting re-enabled." string.
|
||||||
- Bugfix for multi-line inputs, which should not include the ". " continuation prompt.
|
- Bugfix for multi-line inputs, which should not include the ". " continuation prompt.
|
||||||
@@ -128,7 +477,7 @@
|
|||||||
- [Aider works with LLM web chat UIs](https://aider.chat/docs/usage/copypaste.html).
|
- [Aider works with LLM web chat UIs](https://aider.chat/docs/usage/copypaste.html).
|
||||||
- New `--copy-paste` mode.
|
- New `--copy-paste` mode.
|
||||||
- New `/copy-context` command.
|
- New `/copy-context` command.
|
||||||
- [Set API keys and other environment variables for all providers from command line or yaml conf file](https://aider.chat/docs/config/aider_conf.html#storing-llm-keys).
|
- [Set API keys and other environment variables for all providers from command line or YAML conf file](https://aider.chat/docs/config/aider_conf.html#storing-llm-keys).
|
||||||
- New `--api-key provider=key` setting.
|
- New `--api-key provider=key` setting.
|
||||||
- New `--set-env VAR=value` setting.
|
- New `--set-env VAR=value` setting.
|
||||||
- Added bash and zsh support to `--watch-files`.
|
- Added bash and zsh support to `--watch-files`.
|
||||||
@@ -296,7 +645,7 @@
|
|||||||
|
|
||||||
### Aider v0.59.1
|
### Aider v0.59.1
|
||||||
|
|
||||||
- Check for obsolete `yes: true` in yaml config, show helpful error.
|
- Check for obsolete `yes: true` in YAML config, show helpful error.
|
||||||
- Model settings for openrouter/anthropic/claude-3.5-sonnet:beta
|
- Model settings for openrouter/anthropic/claude-3.5-sonnet:beta
|
||||||
|
|
||||||
### Aider v0.59.0
|
### Aider v0.59.0
|
||||||
@@ -306,7 +655,7 @@
|
|||||||
- Still auto-completes the full paths of the repo files like `/add`.
|
- Still auto-completes the full paths of the repo files like `/add`.
|
||||||
- Now supports globs like `src/**/*.py`
|
- Now supports globs like `src/**/*.py`
|
||||||
- Renamed `--yes` to `--yes-always`.
|
- Renamed `--yes` to `--yes-always`.
|
||||||
- Now uses `AIDER_YES_ALWAYS` env var and `yes-always:` yaml key.
|
- Now uses `AIDER_YES_ALWAYS` env var and `yes-always:` YAML key.
|
||||||
- Existing YAML and .env files will need to be updated.
|
- Existing YAML and .env files will need to be updated.
|
||||||
- Can still abbreviate to `--yes` on the command line.
|
- Can still abbreviate to `--yes` on the command line.
|
||||||
- Config file now uses standard YAML list syntax with ` - list entries`, one per line.
|
- Config file now uses standard YAML list syntax with ` - list entries`, one per line.
|
||||||
@@ -513,7 +862,7 @@
|
|||||||
- Use `--map-refresh <always|files|manual|auto>` to configure.
|
- Use `--map-refresh <always|files|manual|auto>` to configure.
|
||||||
- Improved cost estimate logic for caching.
|
- Improved cost estimate logic for caching.
|
||||||
- Improved editing performance on Jupyter Notebook `.ipynb` files.
|
- Improved editing performance on Jupyter Notebook `.ipynb` files.
|
||||||
- Show which config yaml file is loaded with `--verbose`.
|
- Show which config YAML file is loaded with `--verbose`.
|
||||||
- Bumped dependency versions.
|
- Bumped dependency versions.
|
||||||
- Bugfix: properly load `.aider.models.metadata.json` data.
|
- Bugfix: properly load `.aider.models.metadata.json` data.
|
||||||
- Bugfix: Using `--msg /ask ...` caused an exception.
|
- Bugfix: Using `--msg /ask ...` caused an exception.
|
||||||
|
|||||||
257
README.md
257
README.md
@@ -1,145 +1,180 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://aider.chat/"><img src="https://aider.chat/assets/logo.svg" alt="Aider Logo" width="300"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Edit README.md, not index.md -->
|
<h1 align="center">
|
||||||
|
AI Pair Programming in Your Terminal
|
||||||
# Aider is AI pair programming in your terminal
|
</h1>
|
||||||
|
|
||||||
Aider lets you pair program with LLMs,
|
|
||||||
to edit code in your local git repository.
|
|
||||||
Start a new project or work with an existing code base.
|
|
||||||
Aider works best with Claude 3.5 Sonnet, DeepSeek V3, o1 & GPT-4o and can [connect to almost any LLM](https://aider.chat/docs/llms.html).
|
|
||||||
|
|
||||||
|
|
||||||
<!-- SCREENCAST START -->
|
<p align="center">
|
||||||
|
Aider lets you pair program with LLMs to start a new project or build on your existing codebase.
|
||||||
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img
|
<img
|
||||||
src="https://aider.chat/assets/screencast.svg"
|
src="https://aider.chat/assets/screencast.svg"
|
||||||
alt="aider screencast"
|
alt="aider screencast"
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<!-- SCREENCAST END -->
|
|
||||||
|
|
||||||
<!-- VIDEO START
|
|
||||||
<p align="center">
|
|
||||||
<video style="max-width: 100%; height: auto;" autoplay loop muted playsinline>
|
|
||||||
<source src="/assets/shell-cmds-small.mp4" type="video/mp4">
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
</p>
|
|
||||||
VIDEO END -->
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://discord.gg/Tv2uQnR88V">
|
|
||||||
<img src="https://img.shields.io/badge/Join-Discord-blue.svg"/>
|
|
||||||
</a>
|
|
||||||
<a href="https://aider.chat/docs/install.html">
|
|
||||||
<img src="https://img.shields.io/badge/Read-Docs-green.svg"/>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
<!--[[[cog
|
<!--[[[cog
|
||||||
# We can't "include" here.
|
from scripts.homepage import get_badges_md
|
||||||
# Because this page is rendered by GitHub as the repo README
|
text = get_badges_md()
|
||||||
cog.out(open("aider/website/_includes/get-started.md").read())
|
cog.out(text)
|
||||||
]]]-->
|
]]]-->
|
||||||
|
<a href="https://github.com/Aider-AI/aider/stargazers"><img alt="GitHub Stars" title="Total number of GitHub stars the Aider project has received"
|
||||||
|
src="https://img.shields.io/github/stars/Aider-AI/aider?style=flat-square&logo=github&color=f1c40f&labelColor=555555"/></a>
|
||||||
|
<a href="https://pypi.org/project/aider-chat/"><img alt="PyPI Downloads" title="Total number of installations via pip from PyPI"
|
||||||
|
src="https://img.shields.io/badge/📦%20Installs-3.0M-2ecc71?style=flat-square&labelColor=555555"/></a>
|
||||||
|
<img alt="Tokens per week" title="Number of tokens processed weekly by Aider users"
|
||||||
|
src="https://img.shields.io/badge/📈%20Tokens%2Fweek-15B-3498db?style=flat-square&labelColor=555555"/>
|
||||||
|
<a href="https://openrouter.ai/#options-menu"><img alt="OpenRouter Ranking" title="Aider's ranking among applications on the OpenRouter platform"
|
||||||
|
src="https://img.shields.io/badge/🏆%20OpenRouter-Top%2020-9b59b6?style=flat-square&labelColor=555555"/></a>
|
||||||
|
<a href="https://aider.chat/HISTORY.html"><img alt="Singularity" title="Percentage of the new code in Aider's last release written by Aider itself"
|
||||||
|
src="https://img.shields.io/badge/🔄%20Singularity-21%25-e74c3c?style=flat-square&labelColor=555555"/></a>
|
||||||
|
<!--[[[end]]]-->
|
||||||
|
</p>
|
||||||
|
|
||||||
If you already have python 3.8-3.13 installed, you can get started quickly like this:
|
## Features
|
||||||
|
|
||||||
|
### [Cloud and local LLMs](https://aider.chat/docs/llms.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/llms.html"><img src="https://aider.chat/assets/icons/brain.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Aider works best with Claude 3.7 Sonnet, DeepSeek R1 & Chat V3, OpenAI o1, o3-mini & GPT-4o, but can connect to almost any LLM, including local models.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### [Maps your codebase](https://aider.chat/docs/repomap.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/repomap.html"><img src="https://aider.chat/assets/icons/map-outline.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Aider makes a map of your entire codebase, which helps it work well in larger projects.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### [100+ code languages](https://aider.chat/docs/languages.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/languages.html"><img src="https://aider.chat/assets/icons/code-tags.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Aider works with most popular programming languages: python, javascript, rust, ruby, go, cpp, php, html, css, and dozens more.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### [Git integration](https://aider.chat/docs/git.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/git.html"><img src="https://aider.chat/assets/icons/source-branch.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Aider automatically commits changes with sensible commit messages. Use familiar git tools to easily diff, manage and undo AI changes.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### [Use in your IDE](https://aider.chat/docs/usage/watch.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/usage/watch.html"><img src="https://aider.chat/assets/icons/monitor.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Use aider from within your favorite IDE or editor. Ask for changes by adding comments to your code and aider will get to work.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### [Images & web pages](https://aider.chat/docs/usage/images-urls.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/usage/images-urls.html"><img src="https://aider.chat/assets/icons/image-multiple.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Add images and web pages to the chat to provide visual context, screenshots, reference docs, etc.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### [Voice-to-code](https://aider.chat/docs/usage/voice.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/usage/voice.html"><img src="https://aider.chat/assets/icons/microphone.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Speak with aider about your code! Request new features, test cases or bug fixes using your voice and let aider implement the changes.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### [Linting & testing](https://aider.chat/docs/usage/lint-test.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/usage/lint-test.html"><img src="https://aider.chat/assets/icons/check-all.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Automatically lint and test your code every time aider makes changes. Aider can fix problems detected by your linters and test suites.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### [Copy/paste to web chat](https://aider.chat/docs/usage/copypaste.html)
|
||||||
|
|
||||||
|
<a href="https://aider.chat/docs/usage/copypaste.html"><img src="https://aider.chat/assets/icons/content-copy.svg" width="32" height="32" align="left" valign="middle" style="margin-right:10px"></a>
|
||||||
|
Work with any LLM via its web chat interface. Aider streamlines copy/pasting code context and edits back and forth with a browser.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m pip install aider-install
|
python -m pip install aider-install
|
||||||
aider-install
|
aider-install
|
||||||
|
|
||||||
# Change directory into your code base
|
# Change directory into your codebase
|
||||||
cd /to/your/project
|
cd /to/your/project
|
||||||
|
|
||||||
# Work with DeepSeek via DeepSeek's API
|
# DeepSeek
|
||||||
aider --model deepseek --api-key deepseek=your-key-goes-here
|
aider --model deepseek --api-key deepseek=<key>
|
||||||
|
|
||||||
# Work with Claude 3.5 Sonnet via Anthropic's API
|
# Claude 3.7 Sonnet
|
||||||
aider --model sonnet --api-key anthropic=your-key-goes-here
|
aider --model sonnet --api-key anthropic=<key>
|
||||||
|
|
||||||
# Work with GPT-4o via OpenAI's API
|
# o3-mini
|
||||||
aider --model gpt-4o --api-key openai=your-key-goes-here
|
aider --model o3-mini --api-key openai=<key>
|
||||||
|
|
||||||
# Work with Sonnet via OpenRouter's API
|
|
||||||
aider --model openrouter/anthropic/claude-3.5-sonnet --api-key openrouter=your-key-goes-here
|
|
||||||
|
|
||||||
# Work with DeepSeek via OpenRouter's API
|
|
||||||
aider --model openrouter/deepseek/deepseek-chat --api-key openrouter=your-key-goes-here
|
|
||||||
```
|
```
|
||||||
<!--[[[end]]]-->
|
|
||||||
|
|
||||||
See the
|
See the [installation instructions](https://aider.chat/docs/install.html) and [usage documentation](https://aider.chat/docs/usage.html) for more details.
|
||||||
[installation instructions](https://aider.chat/docs/install.html)
|
|
||||||
and
|
|
||||||
[usage documentation](https://aider.chat/docs/usage.html)
|
|
||||||
for more details.
|
|
||||||
|
|
||||||
## Features
|
## More Information
|
||||||
|
|
||||||
- Run aider with the files you want to edit: `aider <file1> <file2> ...`
|
### Documentation
|
||||||
- Ask for changes:
|
- [Installation Guide](https://aider.chat/docs/install.html)
|
||||||
- Add new features or test cases.
|
- [Usage Guide](https://aider.chat/docs/usage.html)
|
||||||
- Describe a bug.
|
- [Tutorial Videos](https://aider.chat/docs/usage/tutorials.html)
|
||||||
- Paste in an error message or GitHub issue URL.
|
|
||||||
- Refactor code.
|
|
||||||
- Update docs.
|
|
||||||
- Aider will edit your files to complete your request.
|
|
||||||
- Aider [automatically git commits](https://aider.chat/docs/git.html) changes with a sensible commit message.
|
|
||||||
- [Use aider inside your favorite editor or IDE](https://aider.chat/docs/usage/watch.html).
|
|
||||||
- Aider works with [most popular languages](https://aider.chat/docs/languages.html): python, javascript, typescript, php, html, css, and more...
|
|
||||||
- Aider can edit multiple files at once for complex requests.
|
|
||||||
- Aider uses a [map of your entire git repo](https://aider.chat/docs/repomap.html), which helps it work well in larger codebases.
|
|
||||||
- Edit files in your editor or IDE while chatting with aider,
|
|
||||||
and it will always use the latest version.
|
|
||||||
Pair program with AI.
|
|
||||||
- [Add images to the chat](https://aider.chat/docs/usage/images-urls.html) (GPT-4o, Claude 3.5 Sonnet, etc).
|
|
||||||
- [Add URLs to the chat](https://aider.chat/docs/usage/images-urls.html) and aider will read their content.
|
|
||||||
- [Code with your voice](https://aider.chat/docs/usage/voice.html).
|
|
||||||
- Aider works best with Claude 3.5 Sonnet, DeepSeek V3, o1 & GPT-4o and can [connect to almost any LLM](https://aider.chat/docs/llms.html).
|
|
||||||
|
|
||||||
|
|
||||||
## Top tier performance
|
|
||||||
|
|
||||||
[Aider has one of the top scores on SWE Bench](https://aider.chat/2024/06/02/main-swe-bench.html).
|
|
||||||
SWE Bench is a challenging software engineering benchmark where aider
|
|
||||||
solved *real* GitHub issues from popular open source
|
|
||||||
projects like django, scikitlearn, matplotlib, etc.
|
|
||||||
|
|
||||||
## More info
|
|
||||||
|
|
||||||
- [Documentation](https://aider.chat/)
|
|
||||||
- [Installation](https://aider.chat/docs/install.html)
|
|
||||||
- [Usage](https://aider.chat/docs/usage.html)
|
|
||||||
- [Tutorial videos](https://aider.chat/docs/usage/tutorials.html)
|
|
||||||
- [Connecting to LLMs](https://aider.chat/docs/llms.html)
|
- [Connecting to LLMs](https://aider.chat/docs/llms.html)
|
||||||
- [Configuration](https://aider.chat/docs/config.html)
|
- [Configuration Options](https://aider.chat/docs/config.html)
|
||||||
- [Troubleshooting](https://aider.chat/docs/troubleshooting.html)
|
- [Troubleshooting](https://aider.chat/docs/troubleshooting.html)
|
||||||
|
- [FAQ](https://aider.chat/docs/faq.html)
|
||||||
|
|
||||||
|
### Community & Resources
|
||||||
- [LLM Leaderboards](https://aider.chat/docs/leaderboards/)
|
- [LLM Leaderboards](https://aider.chat/docs/leaderboards/)
|
||||||
- [GitHub](https://github.com/Aider-AI/aider)
|
- [GitHub Repository](https://github.com/Aider-AI/aider)
|
||||||
- [Discord](https://discord.gg/Tv2uQnR88V)
|
- [Discord Community](https://discord.gg/Y7X7bhMQFV)
|
||||||
|
- [Release notes](https://aider.chat/HISTORY.html)
|
||||||
- [Blog](https://aider.chat/blog/)
|
- [Blog](https://aider.chat/blog/)
|
||||||
|
|
||||||
|
## Kind Words From Users
|
||||||
|
|
||||||
## Kind words from users
|
- *"My life has changed... Aider... It's going to rock your world."* — [Eric S. Raymond on X](https://x.com/esrtweet/status/1910809356381413593)
|
||||||
|
- *"The best free open source AI coding assistant."* — [IndyDevDan on YouTube](https://youtu.be/YALpX8oOn78)
|
||||||
|
- *"The best AI coding assistant so far."* — [Matthew Berman on YouTube](https://www.youtube.com/watch?v=df8afeb1FY8)
|
||||||
|
- *"Aider ... has easily quadrupled my coding productivity."* — [SOLAR_FIELDS on Hacker News](https://news.ycombinator.com/item?id=36212100)
|
||||||
|
- *"It's a cool workflow... Aider's ergonomics are perfect for me."* — [qup on Hacker News](https://news.ycombinator.com/item?id=38185326)
|
||||||
|
- *"It's really like having your senior developer live right in your Git repo - truly amazing!"* — [rappster on GitHub](https://github.com/Aider-AI/aider/issues/124)
|
||||||
|
- *"What an amazing tool. It's incredible."* — [valyagolev on GitHub](https://github.com/Aider-AI/aider/issues/6#issue-1722897858)
|
||||||
|
- *"Aider is such an astounding thing!"* — [cgrothaus on GitHub](https://github.com/Aider-AI/aider/issues/82#issuecomment-1631876700)
|
||||||
|
- *"It was WAY faster than I would be getting off the ground and making the first few working versions."* — [Daniel Feldman on X](https://twitter.com/d_feldman/status/1662295077387923456)
|
||||||
|
- *"THANK YOU for Aider! It really feels like a glimpse into the future of coding."* — [derwiki on Hacker News](https://news.ycombinator.com/item?id=38205643)
|
||||||
|
- *"It's just amazing. It is freeing me to do things I felt were out my comfort zone before."* — [Dougie on Discord](https://discord.com/channels/1131200896827654144/1174002618058678323/1174084556257775656)
|
||||||
|
- *"This project is stellar."* — [funkytaco on GitHub](https://github.com/Aider-AI/aider/issues/112#issuecomment-1637429008)
|
||||||
|
- *"Amazing project, definitely the best AI coding assistant I've used."* — [joshuavial on GitHub](https://github.com/Aider-AI/aider/issues/84)
|
||||||
|
- *"I absolutely love using Aider ... It makes software development feel so much lighter as an experience."* — [principalideal0 on Discord](https://discord.com/channels/1131200896827654144/1133421607499595858/1229689636012691468)
|
||||||
|
- *"I have been recovering from ... surgeries ... aider ... has allowed me to continue productivity."* — [codeninja on Reddit](https://www.reddit.com/r/OpenAI/s/nmNwkHy1zG)
|
||||||
|
- *"I am an aider addict. I'm getting so much more work done, but in less time."* — [dandandan on Discord](https://discord.com/channels/1131200896827654144/1131200896827654149/1135913253483069470)
|
||||||
|
- *"Aider... blows everything else out of the water hands down, there's no competition whatsoever."* — [SystemSculpt on Discord](https://discord.com/channels/1131200896827654144/1131200896827654149/1178736602797846548)
|
||||||
|
- *"Aider is amazing, coupled with Sonnet 3.5 it's quite mind blowing."* — [Josh Dingus on Discord](https://discord.com/channels/1131200896827654144/1133060684540813372/1262374225298198548)
|
||||||
|
- *"Hands down, this is the best AI coding assistant tool so far."* — [IndyDevDan on YouTube](https://www.youtube.com/watch?v=MPYFPvxfGZs)
|
||||||
|
- *"[Aider] changed my daily coding workflows. It's mind-blowing how ...(it)... can change your life."* — [maledorak on Discord](https://discord.com/channels/1131200896827654144/1131200896827654149/1258453375620747264)
|
||||||
|
- *"Best agent for actual dev work in existing codebases."* — [Nick Dobos on X](https://twitter.com/NickADobos/status/1690408967963652097?s=20)
|
||||||
|
- *"One of my favorite pieces of software. Blazing trails on new paradigms!"* — [Chris Wall on X](https://x.com/chris65536/status/1905053299251798432)
|
||||||
|
- *"Aider has been revolutionary for me and my work."* — [Starry Hope on X](https://x.com/starryhopeblog/status/1904985812137132056)
|
||||||
|
- *"Try aider! One of the best ways to vibe code."* — [Chris Wall on X](https://x.com/Chris65536/status/1905053418961391929)
|
||||||
|
- *"Freaking love Aider."* — [hztar on Hacker News](https://news.ycombinator.com/item?id=44035015)
|
||||||
|
- *"Aider is hands down the best. And it's free and opensource."* — [AriyaSavakaLurker on Reddit](https://www.reddit.com/r/ChatGPTCoding/comments/1ik16y6/whats_your_take_on_aider/mbip39n/)
|
||||||
|
- *"Aider is also my best friend."* — [jzn21 on Reddit](https://www.reddit.com/r/ChatGPTCoding/comments/1heuvuo/aider_vs_cline_vs_windsurf_vs_cursor/m27dcnb/)
|
||||||
|
- *"Try Aider, it's worth it."* — [jorgejhms on Reddit](https://www.reddit.com/r/ChatGPTCoding/comments/1heuvuo/aider_vs_cline_vs_windsurf_vs_cursor/m27cp99/)
|
||||||
|
- *"I like aider :)"* — [Chenwei Cui on X](https://x.com/ccui42/status/1904965344999145698)
|
||||||
|
- *"Aider is the precision tool of LLM code gen... Minimal, thoughtful and capable of surgical changes ... while keeping the developer in control."* — [Reilly Sweetland on X](https://x.com/rsweetland/status/1904963807237259586)
|
||||||
|
- *"Cannot believe aider vibe coded a 650 LOC feature across service and cli today in 1 shot."* - [autopoietist on Discord](https://discord.com/channels/1131200896827654144/1131200896827654149/1355675042259796101)
|
||||||
|
- *"Oh no the secret is out! Yes, Aider is the best coding tool around. I highly, highly recommend it to anyone."* — [Joshua D Vander Hook on X](https://x.com/jodavaho/status/1911154899057795218)
|
||||||
|
- *"thanks to aider, i have started and finished three personal projects within the last two days"* — [joseph stalzyn on X](https://x.com/anitaheeder/status/1908338609645904160)
|
||||||
|
- *"Been using aider as my daily driver for over a year ... I absolutely love the tool, like beyond words."* — [koleok on Discord](https://discord.com/channels/1131200896827654144/1273248471394291754/1356727448372252783)
|
||||||
|
- *"Aider ... is the tool to benchmark against."* — [BeetleB on Hacker News](https://news.ycombinator.com/item?id=43930201)
|
||||||
|
- *"aider is really cool"* — [kache on X](https://x.com/yacineMTB/status/1911224442430124387)
|
||||||
|
|
||||||
- *The best free open source AI coding assistant.* -- [IndyDevDan](https://youtu.be/YALpX8oOn78)
|
|
||||||
- *The best AI coding assistant so far.* -- [Matthew Berman](https://www.youtube.com/watch?v=df8afeb1FY8)
|
|
||||||
- *Aider ... has easily quadrupled my coding productivity.* -- [SOLAR_FIELDS](https://news.ycombinator.com/item?id=36212100)
|
|
||||||
- *It's a cool workflow... Aider's ergonomics are perfect for me.* -- [qup](https://news.ycombinator.com/item?id=38185326)
|
|
||||||
- *It's really like having your senior developer live right in your Git repo - truly amazing!* -- [rappster](https://github.com/Aider-AI/aider/issues/124)
|
|
||||||
- *What an amazing tool. It's incredible.* -- [valyagolev](https://github.com/Aider-AI/aider/issues/6#issue-1722897858)
|
|
||||||
- *Aider is such an astounding thing!* -- [cgrothaus](https://github.com/Aider-AI/aider/issues/82#issuecomment-1631876700)
|
|
||||||
- *It was WAY faster than I would be getting off the ground and making the first few working versions.* -- [Daniel Feldman](https://twitter.com/d_feldman/status/1662295077387923456)
|
|
||||||
- *THANK YOU for Aider! It really feels like a glimpse into the future of coding.* -- [derwiki](https://news.ycombinator.com/item?id=38205643)
|
|
||||||
- *It's just amazing. It is freeing me to do things I felt were out my comfort zone before.* -- [Dougie](https://discord.com/channels/1131200896827654144/1174002618058678323/1174084556257775656)
|
|
||||||
- *This project is stellar.* -- [funkytaco](https://github.com/Aider-AI/aider/issues/112#issuecomment-1637429008)
|
|
||||||
- *Amazing project, definitely the best AI coding assistant I've used.* -- [joshuavial](https://github.com/Aider-AI/aider/issues/84)
|
|
||||||
- *I absolutely love using Aider ... It makes software development feel so much lighter as an experience.* -- [principalideal0](https://discord.com/channels/1131200896827654144/1133421607499595858/1229689636012691468)
|
|
||||||
- *I have been recovering from multiple shoulder surgeries ... and have used aider extensively. It has allowed me to continue productivity.* -- [codeninja](https://www.reddit.com/r/OpenAI/s/nmNwkHy1zG)
|
|
||||||
- *I am an aider addict. I'm getting so much more work done, but in less time.* -- [dandandan](https://discord.com/channels/1131200896827654144/1131200896827654149/1135913253483069470)
|
|
||||||
- *After wasting $100 on tokens trying to find something better, I'm back to Aider. It blows everything else out of the water hands down, there's no competition whatsoever.* -- [SystemSculpt](https://discord.com/channels/1131200896827654144/1131200896827654149/1178736602797846548)
|
|
||||||
- *Aider is amazing, coupled with Sonnet 3.5 it’s quite mind blowing.* -- [Josh Dingus](https://discord.com/channels/1131200896827654144/1133060684540813372/1262374225298198548)
|
|
||||||
- *Hands down, this is the best AI coding assistant tool so far.* -- [IndyDevDan](https://www.youtube.com/watch?v=MPYFPvxfGZs)
|
|
||||||
- *[Aider] changed my daily coding workflows. It's mind-blowing how a single Python application can change your life.* -- [maledorak](https://discord.com/channels/1131200896827654144/1131200896827654149/1258453375620747264)
|
|
||||||
- *Best agent for actual dev work in existing codebases.* -- [Nick Dobos](https://twitter.com/NickADobos/status/1690408967963652097?s=20)
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
__version__ = "0.74.2.dev"
|
__version__ = "0.85.4.dev"
|
||||||
safe_version = __version__
|
safe_version = __version__
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -70,9 +70,17 @@ class Analytics:
|
|||||||
# ephemeral
|
# ephemeral
|
||||||
logfile = None
|
logfile = None
|
||||||
|
|
||||||
def __init__(self, logfile=None, permanently_disable=False):
|
def __init__(
|
||||||
|
self,
|
||||||
|
logfile=None,
|
||||||
|
permanently_disable=False,
|
||||||
|
posthog_host=None,
|
||||||
|
posthog_project_api_key=None,
|
||||||
|
):
|
||||||
self.logfile = logfile
|
self.logfile = logfile
|
||||||
self.get_or_create_uuid()
|
self.get_or_create_uuid()
|
||||||
|
self.custom_posthog_host = posthog_host
|
||||||
|
self.custom_posthog_project_api_key = posthog_project_api_key
|
||||||
|
|
||||||
if self.permanently_disable or permanently_disable or not self.asked_opt_in:
|
if self.permanently_disable or permanently_disable or not self.asked_opt_in:
|
||||||
self.disable(permanently_disable)
|
self.disable(permanently_disable)
|
||||||
@@ -92,8 +100,8 @@ class Analytics:
|
|||||||
|
|
||||||
# self.mp = Mixpanel(mixpanel_project_token)
|
# self.mp = Mixpanel(mixpanel_project_token)
|
||||||
self.ph = Posthog(
|
self.ph = Posthog(
|
||||||
project_api_key=posthog_project_api_key,
|
project_api_key=self.custom_posthog_project_api_key or posthog_project_api_key,
|
||||||
host=posthog_host,
|
host=self.custom_posthog_host or posthog_host,
|
||||||
on_error=self.posthog_error,
|
on_error=self.posthog_error,
|
||||||
enable_exception_autocapture=True,
|
enable_exception_autocapture=True,
|
||||||
super_properties=self.get_system_info(), # Add system info to all events
|
super_properties=self.get_system_info(), # Add system info to all events
|
||||||
|
|||||||
297
aider/args.py
297
aider/args.py
@@ -3,8 +3,10 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import configargparse
|
import configargparse
|
||||||
|
import shtab
|
||||||
|
|
||||||
from aider import __version__
|
from aider import __version__
|
||||||
from aider.args_formatter import (
|
from aider.args_formatter import (
|
||||||
@@ -12,10 +14,20 @@ from aider.args_formatter import (
|
|||||||
MarkdownHelpFormatter,
|
MarkdownHelpFormatter,
|
||||||
YamlHelpFormatter,
|
YamlHelpFormatter,
|
||||||
)
|
)
|
||||||
|
from aider.deprecated import add_deprecated_model_args
|
||||||
|
|
||||||
from .dump import dump # noqa: F401
|
from .dump import dump # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_aiderignore_path(path_str, git_root=None):
|
||||||
|
path = Path(path_str)
|
||||||
|
if path.is_absolute():
|
||||||
|
return str(path)
|
||||||
|
elif git_root:
|
||||||
|
return str(Path(git_root) / path)
|
||||||
|
return str(path)
|
||||||
|
|
||||||
|
|
||||||
def default_env_file(git_root):
|
def default_env_file(git_root):
|
||||||
return os.path.join(git_root, ".env") if git_root else ".env"
|
return os.path.join(git_root, ".env") if git_root else ".env"
|
||||||
|
|
||||||
@@ -28,108 +40,28 @@ def get_parser(default_config_files, git_root):
|
|||||||
config_file_parser_class=configargparse.YAMLConfigFileParser,
|
config_file_parser_class=configargparse.YAMLConfigFileParser,
|
||||||
auto_env_var_prefix="AIDER_",
|
auto_env_var_prefix="AIDER_",
|
||||||
)
|
)
|
||||||
|
# List of valid edit formats for argparse validation & shtab completion.
|
||||||
|
# Dynamically gather them from the registered coder classes so the list
|
||||||
|
# stays in sync if new formats are added.
|
||||||
|
from aider import coders as _aider_coders
|
||||||
|
|
||||||
|
edit_format_choices = sorted(
|
||||||
|
{
|
||||||
|
c.edit_format
|
||||||
|
for c in _aider_coders.__all__
|
||||||
|
if hasattr(c, "edit_format") and c.edit_format is not None
|
||||||
|
}
|
||||||
|
)
|
||||||
group = parser.add_argument_group("Main model")
|
group = parser.add_argument_group("Main model")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"files", metavar="FILE", nargs="*", help="files to edit with an LLM (optional)"
|
"files", metavar="FILE", nargs="*", help="files to edit with an LLM (optional)"
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--model",
|
"--model",
|
||||||
metavar="MODEL",
|
metavar="MODEL",
|
||||||
default=None,
|
default=None,
|
||||||
help="Specify the model to use for the main chat",
|
help="Specify the model to use for the main chat",
|
||||||
)
|
)
|
||||||
opus_model = "claude-3-opus-20240229"
|
|
||||||
group.add_argument(
|
|
||||||
"--opus",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=opus_model,
|
|
||||||
help=f"Use {opus_model} model for the main chat",
|
|
||||||
)
|
|
||||||
sonnet_model = "claude-3-5-sonnet-20241022"
|
|
||||||
group.add_argument(
|
|
||||||
"--sonnet",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=sonnet_model,
|
|
||||||
help=f"Use {sonnet_model} model for the main chat",
|
|
||||||
)
|
|
||||||
haiku_model = "claude-3-5-haiku-20241022"
|
|
||||||
group.add_argument(
|
|
||||||
"--haiku",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=haiku_model,
|
|
||||||
help=f"Use {haiku_model} model for the main chat",
|
|
||||||
)
|
|
||||||
gpt_4_model = "gpt-4-0613"
|
|
||||||
group.add_argument(
|
|
||||||
"--4",
|
|
||||||
"-4",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=gpt_4_model,
|
|
||||||
help=f"Use {gpt_4_model} model for the main chat",
|
|
||||||
)
|
|
||||||
gpt_4o_model = "gpt-4o"
|
|
||||||
group.add_argument(
|
|
||||||
"--4o",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=gpt_4o_model,
|
|
||||||
help=f"Use {gpt_4o_model} model for the main chat",
|
|
||||||
)
|
|
||||||
gpt_4o_mini_model = "gpt-4o-mini"
|
|
||||||
group.add_argument(
|
|
||||||
"--mini",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=gpt_4o_mini_model,
|
|
||||||
help=f"Use {gpt_4o_mini_model} model for the main chat",
|
|
||||||
)
|
|
||||||
gpt_4_turbo_model = "gpt-4-1106-preview"
|
|
||||||
group.add_argument(
|
|
||||||
"--4-turbo",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=gpt_4_turbo_model,
|
|
||||||
help=f"Use {gpt_4_turbo_model} model for the main chat",
|
|
||||||
)
|
|
||||||
gpt_3_model_name = "gpt-3.5-turbo"
|
|
||||||
group.add_argument(
|
|
||||||
"--35turbo",
|
|
||||||
"--35-turbo",
|
|
||||||
"--3",
|
|
||||||
"-3",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=gpt_3_model_name,
|
|
||||||
help=f"Use {gpt_3_model_name} model for the main chat",
|
|
||||||
)
|
|
||||||
deepseek_model = "deepseek/deepseek-chat"
|
|
||||||
group.add_argument(
|
|
||||||
"--deepseek",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=deepseek_model,
|
|
||||||
help=f"Use {deepseek_model} model for the main chat",
|
|
||||||
)
|
|
||||||
o1_mini_model = "o1-mini"
|
|
||||||
group.add_argument(
|
|
||||||
"--o1-mini",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=o1_mini_model,
|
|
||||||
help=f"Use {o1_mini_model} model for the main chat",
|
|
||||||
)
|
|
||||||
o1_preview_model = "o1-preview"
|
|
||||||
group.add_argument(
|
|
||||||
"--o1-preview",
|
|
||||||
action="store_const",
|
|
||||||
dest="model",
|
|
||||||
const=o1_preview_model,
|
|
||||||
help=f"Use {o1_preview_model} model for the main chat",
|
|
||||||
)
|
|
||||||
|
|
||||||
##########
|
##########
|
||||||
group = parser.add_argument_group("API Keys and settings")
|
group = parser.add_argument_group("API Keys and settings")
|
||||||
@@ -190,13 +122,13 @@ def get_parser(default_config_files, git_root):
|
|||||||
metavar="MODEL_SETTINGS_FILE",
|
metavar="MODEL_SETTINGS_FILE",
|
||||||
default=".aider.model.settings.yml",
|
default=".aider.model.settings.yml",
|
||||||
help="Specify a file with aider model settings for unknown models",
|
help="Specify a file with aider model settings for unknown models",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--model-metadata-file",
|
"--model-metadata-file",
|
||||||
metavar="MODEL_METADATA_FILE",
|
metavar="MODEL_METADATA_FILE",
|
||||||
default=".aider.model.metadata.json",
|
default=".aider.model.metadata.json",
|
||||||
help="Specify a file with context window and costs for unknown models",
|
help="Specify a file with context window and costs for unknown models",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--alias",
|
"--alias",
|
||||||
action="append",
|
action="append",
|
||||||
@@ -208,6 +140,14 @@ def get_parser(default_config_files, git_root):
|
|||||||
type=str,
|
type=str,
|
||||||
help="Set the reasoning_effort API parameter (default: not set)",
|
help="Set the reasoning_effort API parameter (default: not set)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--thinking-tokens",
|
||||||
|
type=str,
|
||||||
|
help=(
|
||||||
|
"Set the thinking token budget for models that support it. Use 0 to disable. (default:"
|
||||||
|
" not set)"
|
||||||
|
),
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--verify-ssl",
|
"--verify-ssl",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
@@ -224,6 +164,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
"--edit-format",
|
"--edit-format",
|
||||||
"--chat-mode",
|
"--chat-mode",
|
||||||
metavar="EDIT_FORMAT",
|
metavar="EDIT_FORMAT",
|
||||||
|
choices=edit_format_choices,
|
||||||
default=None,
|
default=None,
|
||||||
help="Specify what edit format the LLM should use (default depends on model)",
|
help="Specify what edit format the LLM should use (default depends on model)",
|
||||||
)
|
)
|
||||||
@@ -234,6 +175,12 @@ def get_parser(default_config_files, git_root):
|
|||||||
const="architect",
|
const="architect",
|
||||||
help="Use architect edit format for the main chat",
|
help="Use architect edit format for the main chat",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--auto-accept-architect",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=True,
|
||||||
|
help="Enable/disable automatic acceptance of architect changes (default: True)",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--weak-model",
|
"--weak-model",
|
||||||
metavar="WEAK_MODEL",
|
metavar="WEAK_MODEL",
|
||||||
@@ -252,6 +199,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--editor-edit-format",
|
"--editor-edit-format",
|
||||||
metavar="EDITOR_EDIT_FORMAT",
|
metavar="EDITOR_EDIT_FORMAT",
|
||||||
|
choices=edit_format_choices,
|
||||||
default=None,
|
default=None,
|
||||||
help="Specify the edit format for the editor model (default: depends on editor model)",
|
help="Specify the edit format for the editor model (default: depends on editor model)",
|
||||||
)
|
)
|
||||||
@@ -261,6 +209,14 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=True,
|
default=True,
|
||||||
help="Only work with models that have meta-data available (default: True)",
|
help="Only work with models that have meta-data available (default: True)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--check-model-accepts-settings",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=True,
|
||||||
|
help=(
|
||||||
|
"Check if model accepts settings like reasoning_effort/thinking_tokens (default: True)"
|
||||||
|
),
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--max-chat-history-tokens",
|
"--max-chat-history-tokens",
|
||||||
type=int,
|
type=int,
|
||||||
@@ -323,13 +279,13 @@ def get_parser(default_config_files, git_root):
|
|||||||
metavar="INPUT_HISTORY_FILE",
|
metavar="INPUT_HISTORY_FILE",
|
||||||
default=default_input_history_file,
|
default=default_input_history_file,
|
||||||
help=f"Specify the chat input history file (default: {default_input_history_file})",
|
help=f"Specify the chat input history file (default: {default_input_history_file})",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--chat-history-file",
|
"--chat-history-file",
|
||||||
metavar="CHAT_HISTORY_FILE",
|
metavar="CHAT_HISTORY_FILE",
|
||||||
default=default_chat_history_file,
|
default=default_chat_history_file,
|
||||||
help=f"Specify the chat history file (default: {default_chat_history_file})",
|
help=f"Specify the chat history file (default: {default_chat_history_file})",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--restore-chat-history",
|
"--restore-chat-history",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
@@ -341,7 +297,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
metavar="LLM_HISTORY_FILE",
|
metavar="LLM_HISTORY_FILE",
|
||||||
default=None,
|
default=None,
|
||||||
help="Log the conversation with the LLM to this file (for example, .aider.llm.history)",
|
help="Log the conversation with the LLM to this file (for example, .aider.llm.history)",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
|
|
||||||
##########
|
##########
|
||||||
group = parser.add_argument_group("Output settings")
|
group = parser.add_argument_group("Output settings")
|
||||||
@@ -457,15 +413,23 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=True,
|
default=True,
|
||||||
help="Enable/disable adding .aider* to .gitignore (default: True)",
|
help="Enable/disable adding .aider* to .gitignore (default: True)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--add-gitignore-files",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=False,
|
||||||
|
help="Enable/disable the addition of files listed in .gitignore to Aider's editing scope.",
|
||||||
|
)
|
||||||
default_aiderignore_file = (
|
default_aiderignore_file = (
|
||||||
os.path.join(git_root, ".aiderignore") if git_root else ".aiderignore"
|
os.path.join(git_root, ".aiderignore") if git_root else ".aiderignore"
|
||||||
)
|
)
|
||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--aiderignore",
|
"--aiderignore",
|
||||||
metavar="AIDERIGNORE",
|
metavar="AIDERIGNORE",
|
||||||
|
type=lambda path_str: resolve_aiderignore_path(path_str, git_root),
|
||||||
default=default_aiderignore_file,
|
default=default_aiderignore_file,
|
||||||
help="Specify the aider ignore file (default: .aiderignore in git root)",
|
help="Specify the aider ignore file (default: .aiderignore in git root)",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--subtree-only",
|
"--subtree-only",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -487,14 +451,20 @@ def get_parser(default_config_files, git_root):
|
|||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--attribute-author",
|
"--attribute-author",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
default=True,
|
default=None,
|
||||||
help="Attribute aider code changes in the git author name (default: True)",
|
help=(
|
||||||
|
"Attribute aider code changes in the git author name (default: True). If explicitly set"
|
||||||
|
" to True, overrides --attribute-co-authored-by precedence."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--attribute-committer",
|
"--attribute-committer",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
default=True,
|
default=None,
|
||||||
help="Attribute aider commits in the git committer name (default: True)",
|
help=(
|
||||||
|
"Attribute aider commits in the git committer name (default: True). If explicitly set"
|
||||||
|
" to True, overrides --attribute-co-authored-by precedence for aider edits."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--attribute-commit-message-author",
|
"--attribute-commit-message-author",
|
||||||
@@ -508,6 +478,22 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=False,
|
default=False,
|
||||||
help="Prefix all commit messages with 'aider: ' (default: False)",
|
help="Prefix all commit messages with 'aider: ' (default: False)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--attribute-co-authored-by",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=True,
|
||||||
|
help=(
|
||||||
|
"Attribute aider edits using the Co-authored-by trailer in the commit message"
|
||||||
|
" (default: True). If True, this takes precedence over default --attribute-author and"
|
||||||
|
" --attribute-committer behavior unless they are explicitly set to True."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--git-commit-verify",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=False,
|
||||||
|
help="Enable/disable git pre-commit hooks with --no-verify (default: False)",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--commit",
|
"--commit",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -589,13 +575,23 @@ def get_parser(default_config_files, git_root):
|
|||||||
"--analytics-log",
|
"--analytics-log",
|
||||||
metavar="ANALYTICS_LOG_FILE",
|
metavar="ANALYTICS_LOG_FILE",
|
||||||
help="Specify a file to log analytics events",
|
help="Specify a file to log analytics events",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--analytics-disable",
|
"--analytics-disable",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Permanently disable analytics",
|
help="Permanently disable analytics",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--analytics-posthog-host",
|
||||||
|
metavar="ANALYTICS_POSTHOG_HOST",
|
||||||
|
help="Send analytics to custom PostHog instance",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--analytics-posthog-project-api-key",
|
||||||
|
metavar="ANALYTICS_POSTHOG_PROJECT_API_KEY",
|
||||||
|
help="Send analytics to custom PostHog project",
|
||||||
|
)
|
||||||
|
|
||||||
#########
|
#########
|
||||||
group = parser.add_argument_group("Upgrading")
|
group = parser.add_argument_group("Upgrading")
|
||||||
@@ -656,7 +652,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
"Specify a file containing the message to send the LLM, process reply, then exit"
|
"Specify a file containing the message to send the LLM, process reply, then exit"
|
||||||
" (disables chat mode)"
|
" (disables chat mode)"
|
||||||
),
|
),
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--gui",
|
"--gui",
|
||||||
"--browser",
|
"--browser",
|
||||||
@@ -674,7 +670,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
"--apply",
|
"--apply",
|
||||||
metavar="FILE",
|
metavar="FILE",
|
||||||
help="Apply the changes from the given file instead of running the chat (debug)",
|
help="Apply the changes from the given file instead of running the chat (debug)",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--apply-clipboard-edits",
|
"--apply-clipboard-edits",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -724,18 +720,24 @@ def get_parser(default_config_files, git_root):
|
|||||||
|
|
||||||
######
|
######
|
||||||
group = parser.add_argument_group("Other settings")
|
group = parser.add_argument_group("Other settings")
|
||||||
|
group.add_argument(
|
||||||
|
"--disable-playwright",
|
||||||
|
action="store_true",
|
||||||
|
help="Never prompt for or attempt to install Playwright for web scraping (default: False).",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--file",
|
"--file",
|
||||||
action="append",
|
action="append",
|
||||||
metavar="FILE",
|
metavar="FILE",
|
||||||
help="specify a file to edit (can be used multiple times)",
|
help="specify a file to edit (can be used multiple times)",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--read",
|
"--read",
|
||||||
action="append",
|
action="append",
|
||||||
metavar="FILE",
|
metavar="FILE",
|
||||||
help="specify a read-only file (can be used multiple times)",
|
help="specify a read-only file (can be used multiple times)",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--vim",
|
"--vim",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -748,6 +750,12 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=None,
|
default=None,
|
||||||
help="Specify the language to use in the chat (default: None, uses system settings)",
|
help="Specify the language to use in the chat (default: None, uses system settings)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--commit-language",
|
||||||
|
metavar="COMMIT_LANGUAGE",
|
||||||
|
default=None,
|
||||||
|
help="Specify the language to use in the commit message (default: None, user language)",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--yes-always",
|
"--yes-always",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -765,7 +773,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
"--load",
|
"--load",
|
||||||
metavar="LOAD_FILE",
|
metavar="LOAD_FILE",
|
||||||
help="Load and execute /commands from a file on launch",
|
help="Load and execute /commands from a file on launch",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--encoding",
|
"--encoding",
|
||||||
default="utf-8",
|
default="utf-8",
|
||||||
@@ -786,7 +794,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
"Specify the config file (default: search for .aider.conf.yml in git root, cwd"
|
"Specify the config file (default: search for .aider.conf.yml in git root, cwd"
|
||||||
" or home directory)"
|
" or home directory)"
|
||||||
),
|
),
|
||||||
)
|
).complete = shtab.FILE
|
||||||
# This is a duplicate of the argument in the preparser and is a no-op by this time of
|
# This is a duplicate of the argument in the preparser and is a no-op by this time of
|
||||||
# argument parsing, but it's here so that the help is displayed as expected.
|
# argument parsing, but it's here so that the help is displayed as expected.
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
@@ -794,7 +802,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
metavar="ENV_FILE",
|
metavar="ENV_FILE",
|
||||||
default=default_env_file(git_root),
|
default=default_env_file(git_root),
|
||||||
help="Specify the .env file to load (default: .env in git root)",
|
help="Specify the .env file to load (default: .env in git root)",
|
||||||
)
|
).complete = shtab.FILE
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--suggest-shell-commands",
|
"--suggest-shell-commands",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
@@ -813,6 +821,24 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=False,
|
default=False,
|
||||||
help="Enable/disable multi-line input mode with Meta-Enter to submit (default: False)",
|
help="Enable/disable multi-line input mode with Meta-Enter to submit (default: False)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--notifications",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=False,
|
||||||
|
help=(
|
||||||
|
"Enable/disable terminal bell notifications when LLM responses are ready (default:"
|
||||||
|
" False)"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--notifications-command",
|
||||||
|
metavar="COMMAND",
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
"Specify a command to run for notifications instead of the terminal bell. If not"
|
||||||
|
" specified, a default command for your OS may be used."
|
||||||
|
),
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--detect-urls",
|
"--detect-urls",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
@@ -824,6 +850,22 @@ def get_parser(default_config_files, git_root):
|
|||||||
help="Specify which editor to use for the /editor command",
|
help="Specify which editor to use for the /editor command",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
supported_shells_list = sorted(list(shtab.SUPPORTED_SHELLS))
|
||||||
|
group.add_argument(
|
||||||
|
"--shell-completions",
|
||||||
|
metavar="SHELL",
|
||||||
|
choices=supported_shells_list,
|
||||||
|
help=(
|
||||||
|
"Print shell completion script for the specified SHELL and exit. Supported shells:"
|
||||||
|
f" {', '.join(supported_shells_list)}. Example: aider --shell-completions bash"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
##########
|
||||||
|
group = parser.add_argument_group("Deprecated model settings")
|
||||||
|
# Add deprecated model shortcut arguments
|
||||||
|
add_deprecated_model_args(parser, group)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -867,13 +909,34 @@ def get_sample_dotenv():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
arg = sys.argv[1] if len(sys.argv[1:]) else None
|
if len(sys.argv) > 1:
|
||||||
|
command = sys.argv[1]
|
||||||
if arg == "md":
|
|
||||||
print(get_md_help())
|
|
||||||
elif arg == "dotenv":
|
|
||||||
print(get_sample_dotenv())
|
|
||||||
else:
|
else:
|
||||||
|
command = "yaml" # Default to yaml if no command is given
|
||||||
|
|
||||||
|
if command == "md":
|
||||||
|
print(get_md_help())
|
||||||
|
elif command == "dotenv":
|
||||||
|
print(get_sample_dotenv())
|
||||||
|
elif command == "yaml":
|
||||||
|
print(get_sample_yaml())
|
||||||
|
elif command == "completion":
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
shell = sys.argv[2]
|
||||||
|
if shell not in shtab.SUPPORTED_SHELLS:
|
||||||
|
print(f"Error: Unsupported shell '{shell}'.", file=sys.stderr)
|
||||||
|
print(f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
parser = get_parser([], None)
|
||||||
|
parser.prog = "aider" # Set the program name on the parser
|
||||||
|
print(shtab.complete(parser, shell=shell))
|
||||||
|
else:
|
||||||
|
print("Error: Please specify a shell for completion.", file=sys.stderr)
|
||||||
|
print(f"Usage: python {sys.argv[0]} completion <shell_name>", file=sys.stderr)
|
||||||
|
print(f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
# Default to YAML for any other unrecognized argument, or if 'yaml' was explicitly passed
|
||||||
print(get_sample_yaml())
|
print(get_sample_yaml())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class YamlHelpFormatter(argparse.HelpFormatter):
|
|||||||
# Place in your home dir, or at the root of your git repo.
|
# Place in your home dir, or at the root of your git repo.
|
||||||
##########################################################
|
##########################################################
|
||||||
|
|
||||||
# Note: You can only put OpenAI and Anthropic API keys in the yaml
|
# Note: You can only put OpenAI and Anthropic API keys in the YAML
|
||||||
# config file. Keys for all APIs can be stored in a .env file
|
# config file. Keys for all APIs can be stored in a .env file
|
||||||
# https://aider.chat/docs/config/dotenv.html
|
# https://aider.chat/docs/config/dotenv.html
|
||||||
|
|
||||||
@@ -143,14 +143,20 @@ class YamlHelpFormatter(argparse.HelpFormatter):
|
|||||||
default = "true"
|
default = "true"
|
||||||
|
|
||||||
if default:
|
if default:
|
||||||
|
if "#" in default:
|
||||||
|
parts.append(f'#{switch}: "{default}"\n')
|
||||||
|
else:
|
||||||
parts.append(f"#{switch}: {default}\n")
|
parts.append(f"#{switch}: {default}\n")
|
||||||
elif action.nargs in ("*", "+") or isinstance(action, argparse._AppendAction):
|
elif action.nargs in ("*", "+") or isinstance(action, argparse._AppendAction):
|
||||||
parts.append(f"#{switch}: xxx")
|
parts.append(f"#{switch}: xxx")
|
||||||
parts.append("## Specify multiple values like this:")
|
parts.append("## Specify multiple values like this:")
|
||||||
parts.append(f"#{switch}:")
|
parts.append(f"#{switch}:")
|
||||||
parts.append(f"# - xxx")
|
parts.append("# - xxx")
|
||||||
parts.append(f"# - yyy")
|
parts.append("# - yyy")
|
||||||
parts.append(f"# - zzz")
|
parts.append("# - zzz")
|
||||||
|
else:
|
||||||
|
if switch.endswith("color"):
|
||||||
|
parts.append(f'#{switch}: "xxx"\n')
|
||||||
else:
|
else:
|
||||||
parts.append(f"#{switch}: xxx\n")
|
parts.append(f"#{switch}: xxx\n")
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
from .architect_coder import ArchitectCoder
|
from .architect_coder import ArchitectCoder
|
||||||
from .ask_coder import AskCoder
|
from .ask_coder import AskCoder
|
||||||
from .base_coder import Coder
|
from .base_coder import Coder
|
||||||
|
from .context_coder import ContextCoder
|
||||||
from .editblock_coder import EditBlockCoder
|
from .editblock_coder import EditBlockCoder
|
||||||
from .editblock_fenced_coder import EditBlockFencedCoder
|
from .editblock_fenced_coder import EditBlockFencedCoder
|
||||||
|
from .editor_diff_fenced_coder import EditorDiffFencedCoder
|
||||||
from .editor_editblock_coder import EditorEditBlockCoder
|
from .editor_editblock_coder import EditorEditBlockCoder
|
||||||
from .editor_whole_coder import EditorWholeFileCoder
|
from .editor_whole_coder import EditorWholeFileCoder
|
||||||
from .help_coder import HelpCoder
|
from .help_coder import HelpCoder
|
||||||
|
from .patch_coder import PatchCoder
|
||||||
from .udiff_coder import UnifiedDiffCoder
|
from .udiff_coder import UnifiedDiffCoder
|
||||||
|
from .udiff_simple import UnifiedDiffSimpleCoder
|
||||||
from .wholefile_coder import WholeFileCoder
|
from .wholefile_coder import WholeFileCoder
|
||||||
|
|
||||||
# from .single_wholefile_func_coder import SingleWholeFileFunctionCoder
|
# from .single_wholefile_func_coder import SingleWholeFileFunctionCoder
|
||||||
@@ -18,9 +22,13 @@ __all__ = [
|
|||||||
EditBlockCoder,
|
EditBlockCoder,
|
||||||
EditBlockFencedCoder,
|
EditBlockFencedCoder,
|
||||||
WholeFileCoder,
|
WholeFileCoder,
|
||||||
|
PatchCoder,
|
||||||
UnifiedDiffCoder,
|
UnifiedDiffCoder,
|
||||||
|
UnifiedDiffSimpleCoder,
|
||||||
# SingleWholeFileFunctionCoder,
|
# SingleWholeFileFunctionCoder,
|
||||||
ArchitectCoder,
|
ArchitectCoder,
|
||||||
EditorEditBlockCoder,
|
EditorEditBlockCoder,
|
||||||
EditorWholeFileCoder,
|
EditorWholeFileCoder,
|
||||||
|
EditorDiffFencedCoder,
|
||||||
|
ContextCoder,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from .base_coder import Coder
|
|||||||
class ArchitectCoder(AskCoder):
|
class ArchitectCoder(AskCoder):
|
||||||
edit_format = "architect"
|
edit_format = "architect"
|
||||||
gpt_prompts = ArchitectPrompts()
|
gpt_prompts = ArchitectPrompts()
|
||||||
|
auto_accept_architect = False
|
||||||
|
|
||||||
def reply_completed(self):
|
def reply_completed(self):
|
||||||
content = self.partial_response_content
|
content = self.partial_response_content
|
||||||
@@ -13,7 +14,7 @@ class ArchitectCoder(AskCoder):
|
|||||||
if not content or not content.strip():
|
if not content or not content.strip():
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.io.confirm_ask("Edit the files?"):
|
if not self.auto_accept_architect and not self.io.confirm_ask("Edit the files?"):
|
||||||
return
|
return
|
||||||
|
|
||||||
kwargs = dict()
|
kwargs = dict()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class AskPrompts(CoderPrompts):
|
|||||||
Answer questions about the supplied code.
|
Answer questions about the supplied code.
|
||||||
Always reply to the user in {language}.
|
Always reply to the user in {language}.
|
||||||
|
|
||||||
Describe code changes however you like. Don't use SEARCH/REPLACE blocks!
|
If you need to describe code changes, do so *briefly*.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
example_messages = []
|
example_messages = []
|
||||||
@@ -32,4 +32,4 @@ Here are summaries of some files present in my git repo.
|
|||||||
If you need to see the full contents of any files to answer my questions, ask me to *add them to the chat*.
|
If you need to see the full contents of any files to answer my questions, ask me to *add them to the chat*.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
system_reminder = ""
|
system_reminder = "{final_reminders}"
|
||||||
|
|||||||
@@ -15,10 +15,19 @@ import time
|
|||||||
import traceback
|
import traceback
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Optional dependency: used to convert locale codes (eg ``en_US``)
|
||||||
|
# into human-readable language names (eg ``English``).
|
||||||
|
try:
|
||||||
|
from babel import Locale # type: ignore
|
||||||
|
except ImportError: # Babel not installed – we will fall back to a small mapping
|
||||||
|
Locale = None
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
from aider import __version__, models, prompts, urls, utils
|
from aider import __version__, models, prompts, urls, utils
|
||||||
from aider.analytics import Analytics
|
from aider.analytics import Analytics
|
||||||
from aider.commands import Commands
|
from aider.commands import Commands
|
||||||
@@ -28,10 +37,17 @@ from aider.io import ConfirmGroup, InputOutput
|
|||||||
from aider.linter import Linter
|
from aider.linter import Linter
|
||||||
from aider.llm import litellm
|
from aider.llm import litellm
|
||||||
from aider.models import RETRY_TIMEOUT
|
from aider.models import RETRY_TIMEOUT
|
||||||
|
from aider.reasoning_tags import (
|
||||||
|
REASONING_TAG,
|
||||||
|
format_reasoning_content,
|
||||||
|
remove_reasoning_content,
|
||||||
|
replace_reasoning_tags,
|
||||||
|
)
|
||||||
from aider.repo import ANY_GIT_ERROR, GitRepo
|
from aider.repo import ANY_GIT_ERROR, GitRepo
|
||||||
from aider.repomap import RepoMap
|
from aider.repomap import RepoMap
|
||||||
from aider.run_cmd import run_cmd
|
from aider.run_cmd import run_cmd
|
||||||
from aider.utils import format_content, format_messages, format_tokens, is_image_file
|
from aider.utils import format_content, format_messages, format_tokens, is_image_file
|
||||||
|
from aider.waiting import WaitingSpinner
|
||||||
|
|
||||||
from ..dump import dump # noqa: F401
|
from ..dump import dump # noqa: F401
|
||||||
from .chat_chunks import ChatChunks
|
from .chat_chunks import ChatChunks
|
||||||
@@ -95,8 +111,6 @@ class Coder:
|
|||||||
partial_response_content = ""
|
partial_response_content = ""
|
||||||
commit_before_message = []
|
commit_before_message = []
|
||||||
message_cost = 0.0
|
message_cost = 0.0
|
||||||
message_tokens_sent = 0
|
|
||||||
message_tokens_received = 0
|
|
||||||
add_cache_headers = False
|
add_cache_headers = False
|
||||||
cache_warming_thread = None
|
cache_warming_thread = None
|
||||||
num_cache_warming_pings = 0
|
num_cache_warming_pings = 0
|
||||||
@@ -104,6 +118,7 @@ class Coder:
|
|||||||
detect_urls = True
|
detect_urls = True
|
||||||
ignore_mentions = None
|
ignore_mentions = None
|
||||||
chat_language = None
|
chat_language = None
|
||||||
|
commit_language = None
|
||||||
file_watcher = None
|
file_watcher = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -162,12 +177,15 @@ class Coder:
|
|||||||
commands=from_coder.commands.clone(),
|
commands=from_coder.commands.clone(),
|
||||||
total_cost=from_coder.total_cost,
|
total_cost=from_coder.total_cost,
|
||||||
ignore_mentions=from_coder.ignore_mentions,
|
ignore_mentions=from_coder.ignore_mentions,
|
||||||
|
total_tokens_sent=from_coder.total_tokens_sent,
|
||||||
|
total_tokens_received=from_coder.total_tokens_received,
|
||||||
file_watcher=from_coder.file_watcher,
|
file_watcher=from_coder.file_watcher,
|
||||||
)
|
)
|
||||||
use_kwargs.update(update) # override to complete the switch
|
use_kwargs.update(update) # override to complete the switch
|
||||||
use_kwargs.update(kwargs) # override passed kwargs
|
use_kwargs.update(kwargs) # override passed kwargs
|
||||||
|
|
||||||
kwargs = use_kwargs
|
kwargs = use_kwargs
|
||||||
|
from_coder.ok_to_warm_cache = False
|
||||||
|
|
||||||
for coder in coders.__all__:
|
for coder in coders.__all__:
|
||||||
if hasattr(coder, "edit_format") and coder.edit_format == edit_format:
|
if hasattr(coder, "edit_format") and coder.edit_format == edit_format:
|
||||||
@@ -200,10 +218,22 @@ class Coder:
|
|||||||
prefix = "Model"
|
prefix = "Model"
|
||||||
|
|
||||||
output = f"{prefix}: {main_model.name} with {self.edit_format} edit format"
|
output = f"{prefix}: {main_model.name} with {self.edit_format} edit format"
|
||||||
|
|
||||||
|
# Check for thinking token budget
|
||||||
|
thinking_tokens = main_model.get_thinking_tokens()
|
||||||
|
if thinking_tokens:
|
||||||
|
output += f", {thinking_tokens} think tokens"
|
||||||
|
|
||||||
|
# Check for reasoning effort
|
||||||
|
reasoning_effort = main_model.get_reasoning_effort()
|
||||||
|
if reasoning_effort:
|
||||||
|
output += f", reasoning {reasoning_effort}"
|
||||||
|
|
||||||
if self.add_cache_headers or main_model.caches_by_default:
|
if self.add_cache_headers or main_model.caches_by_default:
|
||||||
output += ", prompt cache"
|
output += ", prompt cache"
|
||||||
if main_model.info.get("supports_assistant_prefill"):
|
if main_model.info.get("supports_assistant_prefill"):
|
||||||
output += ", infinite output"
|
output += ", infinite output"
|
||||||
|
|
||||||
lines.append(output)
|
lines.append(output)
|
||||||
|
|
||||||
if self.edit_format == "architect":
|
if self.edit_format == "architect":
|
||||||
@@ -264,12 +294,15 @@ class Coder:
|
|||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
ok_to_warm_cache = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
main_model,
|
main_model,
|
||||||
io,
|
io,
|
||||||
repo=None,
|
repo=None,
|
||||||
fnames=None,
|
fnames=None,
|
||||||
|
add_gitignore_files=False,
|
||||||
read_only_fnames=None,
|
read_only_fnames=None,
|
||||||
show_diffs=False,
|
show_diffs=False,
|
||||||
auto_commits=True,
|
auto_commits=True,
|
||||||
@@ -297,22 +330,28 @@ class Coder:
|
|||||||
num_cache_warming_pings=0,
|
num_cache_warming_pings=0,
|
||||||
suggest_shell_commands=True,
|
suggest_shell_commands=True,
|
||||||
chat_language=None,
|
chat_language=None,
|
||||||
|
commit_language=None,
|
||||||
detect_urls=True,
|
detect_urls=True,
|
||||||
ignore_mentions=None,
|
ignore_mentions=None,
|
||||||
|
total_tokens_sent=0,
|
||||||
|
total_tokens_received=0,
|
||||||
file_watcher=None,
|
file_watcher=None,
|
||||||
auto_copy_context=False,
|
auto_copy_context=False,
|
||||||
|
auto_accept_architect=True,
|
||||||
):
|
):
|
||||||
# Fill in a dummy Analytics if needed, but it is never .enable()'d
|
# Fill in a dummy Analytics if needed, but it is never .enable()'d
|
||||||
self.analytics = analytics if analytics is not None else Analytics()
|
self.analytics = analytics if analytics is not None else Analytics()
|
||||||
|
|
||||||
self.event = self.analytics.event
|
self.event = self.analytics.event
|
||||||
self.chat_language = chat_language
|
self.chat_language = chat_language
|
||||||
|
self.commit_language = commit_language
|
||||||
self.commit_before_message = []
|
self.commit_before_message = []
|
||||||
self.aider_commit_hashes = set()
|
self.aider_commit_hashes = set()
|
||||||
self.rejected_urls = set()
|
self.rejected_urls = set()
|
||||||
self.abs_root_path_cache = {}
|
self.abs_root_path_cache = {}
|
||||||
|
|
||||||
self.auto_copy_context = auto_copy_context
|
self.auto_copy_context = auto_copy_context
|
||||||
|
self.auto_accept_architect = auto_accept_architect
|
||||||
|
|
||||||
self.ignore_mentions = ignore_mentions
|
self.ignore_mentions = ignore_mentions
|
||||||
if not self.ignore_mentions:
|
if not self.ignore_mentions:
|
||||||
@@ -343,10 +382,15 @@ class Coder:
|
|||||||
self.need_commit_before_edits = set()
|
self.need_commit_before_edits = set()
|
||||||
|
|
||||||
self.total_cost = total_cost
|
self.total_cost = total_cost
|
||||||
|
self.total_tokens_sent = total_tokens_sent
|
||||||
|
self.total_tokens_received = total_tokens_received
|
||||||
|
self.message_tokens_sent = 0
|
||||||
|
self.message_tokens_received = 0
|
||||||
|
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
self.abs_fnames = set()
|
self.abs_fnames = set()
|
||||||
self.abs_read_only_fnames = set()
|
self.abs_read_only_fnames = set()
|
||||||
|
self.add_gitignore_files = add_gitignore_files
|
||||||
|
|
||||||
if cur_messages:
|
if cur_messages:
|
||||||
self.cur_messages = cur_messages
|
self.cur_messages = cur_messages
|
||||||
@@ -372,6 +416,10 @@ class Coder:
|
|||||||
self.pretty = self.io.pretty
|
self.pretty = self.io.pretty
|
||||||
|
|
||||||
self.main_model = main_model
|
self.main_model = main_model
|
||||||
|
# Set the reasoning tag name based on model settings or default
|
||||||
|
self.reasoning_tag_name = (
|
||||||
|
self.main_model.reasoning_tag if self.main_model.reasoning_tag else REASONING_TAG
|
||||||
|
)
|
||||||
|
|
||||||
self.stream = stream and main_model.streaming
|
self.stream = stream and main_model.streaming
|
||||||
|
|
||||||
@@ -400,8 +448,9 @@ class Coder:
|
|||||||
|
|
||||||
for fname in fnames:
|
for fname in fnames:
|
||||||
fname = Path(fname)
|
fname = Path(fname)
|
||||||
if self.repo and self.repo.git_ignored_file(fname):
|
if self.repo and self.repo.git_ignored_file(fname) and not self.add_gitignore_files:
|
||||||
self.io.tool_warning(f"Skipping {fname} that matches gitignore spec.")
|
self.io.tool_warning(f"Skipping {fname} that matches gitignore spec.")
|
||||||
|
continue
|
||||||
|
|
||||||
if self.repo and self.repo.ignored_file(fname):
|
if self.repo and self.repo.ignored_file(fname):
|
||||||
self.io.tool_warning(f"Skipping {fname} that matches aiderignore spec.")
|
self.io.tool_warning(f"Skipping {fname} that matches aiderignore spec.")
|
||||||
@@ -537,6 +586,15 @@ class Coder:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _stop_waiting_spinner(self):
|
||||||
|
"""Stop and clear the waiting spinner if it is running."""
|
||||||
|
spinner = getattr(self, "waiting_spinner", None)
|
||||||
|
if spinner:
|
||||||
|
try:
|
||||||
|
spinner.stop()
|
||||||
|
finally:
|
||||||
|
self.waiting_spinner = None
|
||||||
|
|
||||||
def get_abs_fnames_content(self):
|
def get_abs_fnames_content(self):
|
||||||
for fname in list(self.abs_fnames):
|
for fname in list(self.abs_fnames):
|
||||||
content = self.io.read_text(fname)
|
content = self.io.read_text(fname)
|
||||||
@@ -895,10 +953,11 @@ class Coder:
|
|||||||
else:
|
else:
|
||||||
self.io.tool_error(text)
|
self.io.tool_error(text)
|
||||||
|
|
||||||
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*)")
|
# Exclude double quotes from the matched URL characters
|
||||||
|
url_pattern = re.compile(r'(https?://[^\s/$.?#].[^\s"]*)')
|
||||||
urls = list(set(url_pattern.findall(text))) # Use set to remove duplicates
|
urls = list(set(url_pattern.findall(text))) # Use set to remove duplicates
|
||||||
for url in urls:
|
for url in urls:
|
||||||
url = url.rstrip(".',\"")
|
url = url.rstrip(".',\"}") # Added } to the characters to strip
|
||||||
self.io.offer_url(url)
|
self.io.offer_url(url)
|
||||||
return urls
|
return urls
|
||||||
|
|
||||||
@@ -907,7 +966,8 @@ class Coder:
|
|||||||
if not self.detect_urls:
|
if not self.detect_urls:
|
||||||
return inp
|
return inp
|
||||||
|
|
||||||
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*[^\s,.])")
|
# Exclude double quotes from the matched URL characters
|
||||||
|
url_pattern = re.compile(r'(https?://[^\s/$.?#].[^\s"]*[^\s,.])')
|
||||||
urls = list(set(url_pattern.findall(inp))) # Use set to remove duplicates
|
urls = list(set(url_pattern.findall(inp))) # Use set to remove duplicates
|
||||||
group = ConfirmGroup(urls)
|
group = ConfirmGroup(urls)
|
||||||
for url in urls:
|
for url in urls:
|
||||||
@@ -924,6 +984,9 @@ class Coder:
|
|||||||
return inp
|
return inp
|
||||||
|
|
||||||
def keyboard_interrupt(self):
|
def keyboard_interrupt(self):
|
||||||
|
# Ensure cursor is visible on exit
|
||||||
|
Console().show_cursor(True)
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
thresh = 2 # seconds
|
thresh = 2 # seconds
|
||||||
@@ -982,28 +1045,93 @@ class Coder:
|
|||||||
]
|
]
|
||||||
self.cur_messages = []
|
self.cur_messages = []
|
||||||
|
|
||||||
def get_user_language(self):
|
def normalize_language(self, lang_code):
|
||||||
if self.chat_language:
|
"""
|
||||||
return self.chat_language
|
Convert a locale code such as ``en_US`` or ``fr`` into a readable
|
||||||
|
language name (e.g. ``English`` or ``French``). If Babel is
|
||||||
|
available it is used for reliable conversion; otherwise a small
|
||||||
|
built-in fallback map handles common languages.
|
||||||
|
"""
|
||||||
|
if not lang_code:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if lang_code.upper() in ("C", "POSIX"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Probably already a language name
|
||||||
|
if (
|
||||||
|
len(lang_code) > 3
|
||||||
|
and "_" not in lang_code
|
||||||
|
and "-" not in lang_code
|
||||||
|
and lang_code[0].isupper()
|
||||||
|
):
|
||||||
|
return lang_code
|
||||||
|
|
||||||
|
# Preferred: Babel
|
||||||
|
if Locale is not None:
|
||||||
|
try:
|
||||||
|
loc = Locale.parse(lang_code.replace("-", "_"))
|
||||||
|
return loc.get_display_name("en").capitalize()
|
||||||
|
except Exception:
|
||||||
|
pass # Fall back to manual mapping
|
||||||
|
|
||||||
|
# Simple fallback for common languages
|
||||||
|
fallback = {
|
||||||
|
"en": "English",
|
||||||
|
"fr": "French",
|
||||||
|
"es": "Spanish",
|
||||||
|
"de": "German",
|
||||||
|
"it": "Italian",
|
||||||
|
"pt": "Portuguese",
|
||||||
|
"zh": "Chinese",
|
||||||
|
"ja": "Japanese",
|
||||||
|
"ko": "Korean",
|
||||||
|
"ru": "Russian",
|
||||||
|
}
|
||||||
|
primary_lang_code = lang_code.replace("-", "_").split("_")[0].lower()
|
||||||
|
return fallback.get(primary_lang_code, lang_code)
|
||||||
|
|
||||||
|
def get_user_language(self):
|
||||||
|
"""
|
||||||
|
Detect the user's language preference and return a human-readable
|
||||||
|
language name such as ``English``. Detection order:
|
||||||
|
|
||||||
|
1. ``self.chat_language`` if explicitly set
|
||||||
|
2. ``locale.getlocale()``
|
||||||
|
3. ``LANG`` / ``LANGUAGE`` / ``LC_ALL`` / ``LC_MESSAGES`` environment variables
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Explicit override
|
||||||
|
if self.chat_language:
|
||||||
|
return self.normalize_language(self.chat_language)
|
||||||
|
|
||||||
|
# System locale
|
||||||
try:
|
try:
|
||||||
lang = locale.getlocale()[0]
|
lang = locale.getlocale()[0]
|
||||||
if lang:
|
if lang:
|
||||||
return lang # Return the full language code, including country
|
lang = self.normalize_language(lang)
|
||||||
|
if lang:
|
||||||
|
return lang
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for env_var in ["LANG", "LANGUAGE", "LC_ALL", "LC_MESSAGES"]:
|
# Environment variables
|
||||||
|
for env_var in ("LANG", "LANGUAGE", "LC_ALL", "LC_MESSAGES"):
|
||||||
lang = os.environ.get(env_var)
|
lang = os.environ.get(env_var)
|
||||||
if lang:
|
if lang:
|
||||||
return lang.split(".")[
|
lang = lang.split(".")[0] # Strip encoding if present
|
||||||
0
|
return self.normalize_language(lang)
|
||||||
] # Return language and country, but remove encoding if present
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_platform_info(self):
|
def get_platform_info(self):
|
||||||
|
platform_text = ""
|
||||||
|
try:
|
||||||
platform_text = f"- Platform: {platform.platform()}\n"
|
platform_text = f"- Platform: {platform.platform()}\n"
|
||||||
|
except KeyError:
|
||||||
|
# Skip platform info if it can't be retrieved
|
||||||
|
platform_text = "- Platform information unavailable\n"
|
||||||
|
|
||||||
shell_var = "COMSPEC" if os.name == "nt" else "SHELL"
|
shell_var = "COMSPEC" if os.name == "nt" else "SHELL"
|
||||||
shell_val = os.getenv(shell_var)
|
shell_val = os.getenv(shell_var)
|
||||||
platform_text += f"- Shell: {shell_var}={shell_val}\n"
|
platform_text += f"- Shell: {shell_var}={shell_val}\n"
|
||||||
@@ -1044,22 +1172,33 @@ class Coder:
|
|||||||
return platform_text
|
return platform_text
|
||||||
|
|
||||||
def fmt_system_prompt(self, prompt):
|
def fmt_system_prompt(self, prompt):
|
||||||
lazy_prompt = self.gpt_prompts.lazy_prompt if self.main_model.lazy else ""
|
final_reminders = []
|
||||||
|
if self.main_model.lazy:
|
||||||
|
final_reminders.append(self.gpt_prompts.lazy_prompt)
|
||||||
|
if self.main_model.overeager:
|
||||||
|
final_reminders.append(self.gpt_prompts.overeager_prompt)
|
||||||
|
|
||||||
|
user_lang = self.get_user_language()
|
||||||
|
if user_lang:
|
||||||
|
final_reminders.append(f"Reply in {user_lang}.\n")
|
||||||
|
|
||||||
platform_text = self.get_platform_info()
|
platform_text = self.get_platform_info()
|
||||||
|
|
||||||
if self.suggest_shell_commands:
|
if self.suggest_shell_commands:
|
||||||
shell_cmd_prompt = self.gpt_prompts.shell_cmd_prompt.format(platform=platform_text)
|
shell_cmd_prompt = self.gpt_prompts.shell_cmd_prompt.format(platform=platform_text)
|
||||||
shell_cmd_reminder = self.gpt_prompts.shell_cmd_reminder.format(platform=platform_text)
|
shell_cmd_reminder = self.gpt_prompts.shell_cmd_reminder.format(platform=platform_text)
|
||||||
|
rename_with_shell = self.gpt_prompts.rename_with_shell
|
||||||
else:
|
else:
|
||||||
shell_cmd_prompt = self.gpt_prompts.no_shell_cmd_prompt.format(platform=platform_text)
|
shell_cmd_prompt = self.gpt_prompts.no_shell_cmd_prompt.format(platform=platform_text)
|
||||||
shell_cmd_reminder = self.gpt_prompts.no_shell_cmd_reminder.format(
|
shell_cmd_reminder = self.gpt_prompts.no_shell_cmd_reminder.format(
|
||||||
platform=platform_text
|
platform=platform_text
|
||||||
)
|
)
|
||||||
|
rename_with_shell = ""
|
||||||
|
|
||||||
if self.chat_language:
|
if user_lang: # user_lang is the result of self.get_user_language()
|
||||||
language = self.chat_language
|
language = user_lang
|
||||||
else:
|
else:
|
||||||
language = "the same language they are using"
|
language = "the same language they are using" # Default if no specific lang detected
|
||||||
|
|
||||||
if self.fence[0] == "`" * 4:
|
if self.fence[0] == "`" * 4:
|
||||||
quad_backtick_reminder = (
|
quad_backtick_reminder = (
|
||||||
@@ -1068,24 +1207,27 @@ class Coder:
|
|||||||
else:
|
else:
|
||||||
quad_backtick_reminder = ""
|
quad_backtick_reminder = ""
|
||||||
|
|
||||||
|
final_reminders = "\n\n".join(final_reminders)
|
||||||
|
|
||||||
prompt = prompt.format(
|
prompt = prompt.format(
|
||||||
fence=self.fence,
|
fence=self.fence,
|
||||||
quad_backtick_reminder=quad_backtick_reminder,
|
quad_backtick_reminder=quad_backtick_reminder,
|
||||||
lazy_prompt=lazy_prompt,
|
final_reminders=final_reminders,
|
||||||
platform=platform_text,
|
platform=platform_text,
|
||||||
shell_cmd_prompt=shell_cmd_prompt,
|
shell_cmd_prompt=shell_cmd_prompt,
|
||||||
|
rename_with_shell=rename_with_shell,
|
||||||
shell_cmd_reminder=shell_cmd_reminder,
|
shell_cmd_reminder=shell_cmd_reminder,
|
||||||
|
go_ahead_tip=self.gpt_prompts.go_ahead_tip,
|
||||||
language=language,
|
language=language,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.main_model.system_prompt_prefix:
|
|
||||||
prompt = self.main_model.system_prompt_prefix + prompt
|
|
||||||
|
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
def format_chat_chunks(self):
|
def format_chat_chunks(self):
|
||||||
self.choose_fence()
|
self.choose_fence()
|
||||||
main_sys = self.fmt_system_prompt(self.gpt_prompts.main_system)
|
main_sys = self.fmt_system_prompt(self.gpt_prompts.main_system)
|
||||||
|
if self.main_model.system_prompt_prefix:
|
||||||
|
main_sys = self.main_model.system_prompt_prefix + "\n" + main_sys
|
||||||
|
|
||||||
example_messages = []
|
example_messages = []
|
||||||
if self.main_model.examples_as_sys_msg:
|
if self.main_model.examples_as_sys_msg:
|
||||||
@@ -1200,8 +1342,11 @@ class Coder:
|
|||||||
return
|
return
|
||||||
if not self.num_cache_warming_pings:
|
if not self.num_cache_warming_pings:
|
||||||
return
|
return
|
||||||
|
if not self.ok_to_warm_cache:
|
||||||
|
return
|
||||||
|
|
||||||
delay = 5 * 60 - 5
|
delay = 5 * 60 - 5
|
||||||
|
delay = float(os.environ.get("AIDER_CACHE_KEEPALIVE_DELAY", delay))
|
||||||
self.next_cache_warm = time.time() + delay
|
self.next_cache_warm = time.time() + delay
|
||||||
self.warming_pings_left = self.num_cache_warming_pings
|
self.warming_pings_left = self.num_cache_warming_pings
|
||||||
self.cache_warming_chunks = chunks
|
self.cache_warming_chunks = chunks
|
||||||
@@ -1210,7 +1355,7 @@ class Coder:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def warm_cache_worker():
|
def warm_cache_worker():
|
||||||
while True:
|
while self.ok_to_warm_cache:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if self.warming_pings_left <= 0:
|
if self.warming_pings_left <= 0:
|
||||||
continue
|
continue
|
||||||
@@ -1274,6 +1419,9 @@ class Coder:
|
|||||||
def send_message(self, inp):
|
def send_message(self, inp):
|
||||||
self.event("message_send_starting")
|
self.event("message_send_starting")
|
||||||
|
|
||||||
|
# Notify IO that LLM processing is starting
|
||||||
|
self.io.llm_started()
|
||||||
|
|
||||||
self.cur_messages += [
|
self.cur_messages += [
|
||||||
dict(role="user", content=inp),
|
dict(role="user", content=inp),
|
||||||
]
|
]
|
||||||
@@ -1288,10 +1436,15 @@ class Coder:
|
|||||||
utils.show_messages(messages, functions=self.functions)
|
utils.show_messages(messages, functions=self.functions)
|
||||||
|
|
||||||
self.multi_response_content = ""
|
self.multi_response_content = ""
|
||||||
if self.show_pretty() and self.stream:
|
if self.show_pretty():
|
||||||
|
self.waiting_spinner = WaitingSpinner("Waiting for " + self.main_model.name)
|
||||||
|
self.waiting_spinner.start()
|
||||||
|
if self.stream:
|
||||||
self.mdstream = self.io.get_assistant_mdstream()
|
self.mdstream = self.io.get_assistant_mdstream()
|
||||||
else:
|
else:
|
||||||
self.mdstream = None
|
self.mdstream = None
|
||||||
|
else:
|
||||||
|
self.mdstream = None
|
||||||
|
|
||||||
retry_delay = 0.125
|
retry_delay = 0.125
|
||||||
|
|
||||||
@@ -1362,12 +1515,18 @@ class Coder:
|
|||||||
self.live_incremental_response(True)
|
self.live_incremental_response(True)
|
||||||
self.mdstream = None
|
self.mdstream = None
|
||||||
|
|
||||||
|
# Ensure any waiting spinner is stopped
|
||||||
|
self._stop_waiting_spinner()
|
||||||
|
|
||||||
self.partial_response_content = self.get_multi_response_content_in_progress(True)
|
self.partial_response_content = self.get_multi_response_content_in_progress(True)
|
||||||
self.partial_response_content = self.main_model.remove_reasoning_content(
|
self.remove_reasoning_content()
|
||||||
self.partial_response_content
|
|
||||||
)
|
|
||||||
self.multi_response_content = ""
|
self.multi_response_content = ""
|
||||||
|
|
||||||
|
###
|
||||||
|
# print()
|
||||||
|
# print("=" * 20)
|
||||||
|
# dump(self.partial_response_content)
|
||||||
|
|
||||||
self.io.tool_output()
|
self.io.tool_output()
|
||||||
|
|
||||||
self.show_usage_report()
|
self.show_usage_report()
|
||||||
@@ -1408,7 +1567,8 @@ class Coder:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.reply_completed()
|
if self.reply_completed():
|
||||||
|
return
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
interrupted = True
|
interrupted = True
|
||||||
|
|
||||||
@@ -1535,6 +1695,10 @@ class Coder:
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Cleanup when the Coder object is destroyed."""
|
||||||
|
self.ok_to_warm_cache = False
|
||||||
|
|
||||||
def add_assistant_reply_to_cur_messages(self):
|
def add_assistant_reply_to_cur_messages(self):
|
||||||
if self.partial_response_content:
|
if self.partial_response_content:
|
||||||
self.cur_messages += [dict(role="assistant", content=self.partial_response_content)]
|
self.cur_messages += [dict(role="assistant", content=self.partial_response_content)]
|
||||||
@@ -1547,16 +1711,20 @@ class Coder:
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_file_mentions(self, content):
|
def get_file_mentions(self, content, ignore_current=False):
|
||||||
words = set(word for word in content.split())
|
words = set(word for word in content.split())
|
||||||
|
|
||||||
# drop sentence punctuation from the end
|
# drop sentence punctuation from the end
|
||||||
words = set(word.rstrip(",.!;:?") for word in words)
|
words = set(word.rstrip(",.!;:?") for word in words)
|
||||||
|
|
||||||
# strip away all kinds of quotes
|
# strip away all kinds of quotes
|
||||||
quotes = "".join(['"', "'", "`"])
|
quotes = "\"'`*_"
|
||||||
words = set(word.strip(quotes) for word in words)
|
words = set(word.strip(quotes) for word in words)
|
||||||
|
|
||||||
|
if ignore_current:
|
||||||
|
addable_rel_fnames = self.get_all_relative_files()
|
||||||
|
existing_basenames = {}
|
||||||
|
else:
|
||||||
addable_rel_fnames = self.get_addable_relative_files()
|
addable_rel_fnames = self.get_addable_relative_files()
|
||||||
|
|
||||||
# Get basenames of files already in chat or read-only
|
# Get basenames of files already in chat or read-only
|
||||||
@@ -1567,10 +1735,6 @@ class Coder:
|
|||||||
mentioned_rel_fnames = set()
|
mentioned_rel_fnames = set()
|
||||||
fname_to_rel_fnames = {}
|
fname_to_rel_fnames = {}
|
||||||
for rel_fname in addable_rel_fnames:
|
for rel_fname in addable_rel_fnames:
|
||||||
# Skip files that share a basename with files already in chat
|
|
||||||
if os.path.basename(rel_fname) in existing_basenames:
|
|
||||||
continue
|
|
||||||
|
|
||||||
normalized_rel_fname = rel_fname.replace("\\", "/")
|
normalized_rel_fname = rel_fname.replace("\\", "/")
|
||||||
normalized_words = set(word.replace("\\", "/") for word in words)
|
normalized_words = set(word.replace("\\", "/") for word in words)
|
||||||
if normalized_rel_fname in normalized_words:
|
if normalized_rel_fname in normalized_words:
|
||||||
@@ -1585,6 +1749,10 @@ class Coder:
|
|||||||
fname_to_rel_fnames[fname].append(rel_fname)
|
fname_to_rel_fnames[fname].append(rel_fname)
|
||||||
|
|
||||||
for fname, rel_fnames in fname_to_rel_fnames.items():
|
for fname, rel_fnames in fname_to_rel_fnames.items():
|
||||||
|
# If the basename is already in chat, don't add based on a basename mention
|
||||||
|
if fname in existing_basenames:
|
||||||
|
continue
|
||||||
|
# If the basename mention is unique among addable files and present in the text
|
||||||
if len(rel_fnames) == 1 and fname in words:
|
if len(rel_fnames) == 1 and fname in words:
|
||||||
mentioned_rel_fnames.add(rel_fnames[0])
|
mentioned_rel_fnames.add(rel_fnames[0])
|
||||||
|
|
||||||
@@ -1613,6 +1781,9 @@ class Coder:
|
|||||||
return prompts.added_files.format(fnames=", ".join(added_fnames))
|
return prompts.added_files.format(fnames=", ".join(added_fnames))
|
||||||
|
|
||||||
def send(self, messages, model=None, functions=None):
|
def send(self, messages, model=None, functions=None):
|
||||||
|
self.got_reasoning_content = False
|
||||||
|
self.ended_reasoning_content = False
|
||||||
|
|
||||||
if not model:
|
if not model:
|
||||||
model = self.main_model
|
model = self.main_model
|
||||||
|
|
||||||
@@ -1663,6 +1834,9 @@ class Coder:
|
|||||||
self.io.ai_output(json.dumps(args, indent=4))
|
self.io.ai_output(json.dumps(args, indent=4))
|
||||||
|
|
||||||
def show_send_output(self, completion):
|
def show_send_output(self, completion):
|
||||||
|
# Stop spinner once we have a response
|
||||||
|
self._stop_waiting_spinner()
|
||||||
|
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(completion)
|
print(completion)
|
||||||
|
|
||||||
@@ -1680,6 +1854,14 @@ class Coder:
|
|||||||
except AttributeError as func_err:
|
except AttributeError as func_err:
|
||||||
show_func_err = func_err
|
show_func_err = func_err
|
||||||
|
|
||||||
|
try:
|
||||||
|
reasoning_content = completion.choices[0].message.reasoning_content
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
reasoning_content = completion.choices[0].message.reasoning
|
||||||
|
except AttributeError:
|
||||||
|
reasoning_content = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.partial_response_content = completion.choices[0].message.content or ""
|
self.partial_response_content = completion.choices[0].message.content or ""
|
||||||
except AttributeError as content_err:
|
except AttributeError as content_err:
|
||||||
@@ -1698,6 +1880,15 @@ class Coder:
|
|||||||
raise Exception("No data found in LLM response!")
|
raise Exception("No data found in LLM response!")
|
||||||
|
|
||||||
show_resp = self.render_incremental_response(True)
|
show_resp = self.render_incremental_response(True)
|
||||||
|
|
||||||
|
if reasoning_content:
|
||||||
|
formatted_reasoning = format_reasoning_content(
|
||||||
|
reasoning_content, self.reasoning_tag_name
|
||||||
|
)
|
||||||
|
show_resp = formatted_reasoning + show_resp
|
||||||
|
|
||||||
|
show_resp = replace_reasoning_tags(show_resp, self.reasoning_tag_name)
|
||||||
|
|
||||||
self.io.assistant_output(show_resp, pretty=self.show_pretty())
|
self.io.assistant_output(show_resp, pretty=self.show_pretty())
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -1707,6 +1898,8 @@ class Coder:
|
|||||||
raise FinishReasonLength()
|
raise FinishReasonLength()
|
||||||
|
|
||||||
def show_send_output_stream(self, completion):
|
def show_send_output_stream(self, completion):
|
||||||
|
received_content = False
|
||||||
|
|
||||||
for chunk in completion:
|
for chunk in completion:
|
||||||
if len(chunk.choices) == 0:
|
if len(chunk.choices) == 0:
|
||||||
continue
|
continue
|
||||||
@@ -1725,19 +1918,48 @@ class Coder:
|
|||||||
self.partial_response_function_call[k] += v
|
self.partial_response_function_call[k] += v
|
||||||
else:
|
else:
|
||||||
self.partial_response_function_call[k] = v
|
self.partial_response_function_call[k] = v
|
||||||
|
received_content = True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
text = ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
text = chunk.choices[0].delta.content
|
reasoning_content = chunk.choices[0].delta.reasoning_content
|
||||||
if text:
|
|
||||||
self.partial_response_content += text
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
text = None
|
try:
|
||||||
|
reasoning_content = chunk.choices[0].delta.reasoning
|
||||||
|
except AttributeError:
|
||||||
|
reasoning_content = None
|
||||||
|
|
||||||
|
if reasoning_content:
|
||||||
|
if not self.got_reasoning_content:
|
||||||
|
text += f"<{REASONING_TAG}>\n\n"
|
||||||
|
text += reasoning_content
|
||||||
|
self.got_reasoning_content = True
|
||||||
|
received_content = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = chunk.choices[0].delta.content
|
||||||
|
if content:
|
||||||
|
if self.got_reasoning_content and not self.ended_reasoning_content:
|
||||||
|
text += f"\n\n</{self.reasoning_tag_name}>\n\n"
|
||||||
|
self.ended_reasoning_content = True
|
||||||
|
|
||||||
|
text += content
|
||||||
|
received_content = True
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if received_content:
|
||||||
|
self._stop_waiting_spinner()
|
||||||
|
self.partial_response_content += text
|
||||||
|
|
||||||
if self.show_pretty():
|
if self.show_pretty():
|
||||||
self.live_incremental_response(False)
|
self.live_incremental_response(False)
|
||||||
elif text:
|
elif text:
|
||||||
|
# Apply reasoning tag formatting
|
||||||
|
text = replace_reasoning_tags(text, self.reasoning_tag_name)
|
||||||
try:
|
try:
|
||||||
sys.stdout.write(text)
|
sys.stdout.write(text)
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
@@ -1749,13 +1971,26 @@ class Coder:
|
|||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
yield text
|
yield text
|
||||||
|
|
||||||
|
if not received_content:
|
||||||
|
self.io.tool_warning("Empty response received from LLM. Check your provider account?")
|
||||||
|
|
||||||
def live_incremental_response(self, final):
|
def live_incremental_response(self, final):
|
||||||
show_resp = self.render_incremental_response(final)
|
show_resp = self.render_incremental_response(final)
|
||||||
|
# Apply any reasoning tag formatting
|
||||||
|
show_resp = replace_reasoning_tags(show_resp, self.reasoning_tag_name)
|
||||||
self.mdstream.update(show_resp, final=final)
|
self.mdstream.update(show_resp, final=final)
|
||||||
|
|
||||||
def render_incremental_response(self, final):
|
def render_incremental_response(self, final):
|
||||||
return self.get_multi_response_content_in_progress()
|
return self.get_multi_response_content_in_progress()
|
||||||
|
|
||||||
|
def remove_reasoning_content(self):
|
||||||
|
"""Remove reasoning content from the model's response."""
|
||||||
|
|
||||||
|
self.partial_response_content = remove_reasoning_content(
|
||||||
|
self.partial_response_content,
|
||||||
|
self.reasoning_tag_name,
|
||||||
|
)
|
||||||
|
|
||||||
def calculate_and_show_tokens_and_cost(self, messages, completion=None):
|
def calculate_and_show_tokens_and_cost(self, messages, completion=None):
|
||||||
prompt_tokens = 0
|
prompt_tokens = 0
|
||||||
completion_tokens = 0
|
completion_tokens = 0
|
||||||
@@ -1797,6 +2032,44 @@ class Coder:
|
|||||||
self.usage_report = tokens_report
|
self.usage_report = tokens_report
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try and use litellm's built in cost calculator. Seems to work for non-streaming only?
|
||||||
|
cost = litellm.completion_cost(completion_response=completion)
|
||||||
|
except Exception:
|
||||||
|
cost = 0
|
||||||
|
|
||||||
|
if not cost:
|
||||||
|
cost = self.compute_costs_from_tokens(
|
||||||
|
prompt_tokens, completion_tokens, cache_write_tokens, cache_hit_tokens
|
||||||
|
)
|
||||||
|
|
||||||
|
self.total_cost += cost
|
||||||
|
self.message_cost += cost
|
||||||
|
|
||||||
|
def format_cost(value):
|
||||||
|
if value == 0:
|
||||||
|
return "0.00"
|
||||||
|
magnitude = abs(value)
|
||||||
|
if magnitude >= 0.01:
|
||||||
|
return f"{value:.2f}"
|
||||||
|
else:
|
||||||
|
return f"{value:.{max(2, 2 - int(math.log10(magnitude)))}f}"
|
||||||
|
|
||||||
|
cost_report = (
|
||||||
|
f"Cost: ${format_cost(self.message_cost)} message,"
|
||||||
|
f" ${format_cost(self.total_cost)} session."
|
||||||
|
)
|
||||||
|
|
||||||
|
if cache_hit_tokens and cache_write_tokens:
|
||||||
|
sep = "\n"
|
||||||
|
else:
|
||||||
|
sep = " "
|
||||||
|
|
||||||
|
self.usage_report = tokens_report + sep + cost_report
|
||||||
|
|
||||||
|
def compute_costs_from_tokens(
|
||||||
|
self, prompt_tokens, completion_tokens, cache_write_tokens, cache_hit_tokens
|
||||||
|
):
|
||||||
cost = 0
|
cost = 0
|
||||||
|
|
||||||
input_cost_per_token = self.main_model.info.get("input_cost_per_token") or 0
|
input_cost_per_token = self.main_model.info.get("input_cost_per_token") or 0
|
||||||
@@ -1824,40 +2097,15 @@ class Coder:
|
|||||||
cost += prompt_tokens * input_cost_per_token
|
cost += prompt_tokens * input_cost_per_token
|
||||||
|
|
||||||
cost += completion_tokens * output_cost_per_token
|
cost += completion_tokens * output_cost_per_token
|
||||||
|
return cost
|
||||||
self.total_cost += cost
|
|
||||||
self.message_cost += cost
|
|
||||||
|
|
||||||
def format_cost(value):
|
|
||||||
if value == 0:
|
|
||||||
return "0.00"
|
|
||||||
magnitude = abs(value)
|
|
||||||
if magnitude >= 0.01:
|
|
||||||
return f"{value:.2f}"
|
|
||||||
else:
|
|
||||||
return f"{value:.{max(2, 2 - int(math.log10(magnitude)))}f}"
|
|
||||||
|
|
||||||
cost_report = (
|
|
||||||
f"Cost: ${format_cost(self.message_cost)} message,"
|
|
||||||
f" ${format_cost(self.total_cost)} session."
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.add_cache_headers and self.stream:
|
|
||||||
warning = " Use --no-stream for accurate caching costs."
|
|
||||||
self.usage_report = tokens_report + "\n" + cost_report + warning
|
|
||||||
return
|
|
||||||
|
|
||||||
if cache_hit_tokens and cache_write_tokens:
|
|
||||||
sep = "\n"
|
|
||||||
else:
|
|
||||||
sep = " "
|
|
||||||
|
|
||||||
self.usage_report = tokens_report + sep + cost_report
|
|
||||||
|
|
||||||
def show_usage_report(self):
|
def show_usage_report(self):
|
||||||
if not self.usage_report:
|
if not self.usage_report:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.total_tokens_sent += self.message_tokens_sent
|
||||||
|
self.total_tokens_received += self.message_tokens_received
|
||||||
|
|
||||||
self.io.tool_output(self.usage_report)
|
self.io.tool_output(self.usage_report)
|
||||||
|
|
||||||
prompt_tokens = self.message_tokens_sent
|
prompt_tokens = self.message_tokens_sent
|
||||||
@@ -2132,7 +2380,7 @@ class Coder:
|
|||||||
context = self.get_context_from_history(self.cur_messages)
|
context = self.get_context_from_history(self.cur_messages)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = self.repo.commit(fnames=edited, context=context, aider_edits=True)
|
res = self.repo.commit(fnames=edited, context=context, aider_edits=True, coder=self)
|
||||||
if res:
|
if res:
|
||||||
self.show_auto_commit_outcome(res)
|
self.show_auto_commit_outcome(res)
|
||||||
commit_hash, commit_message = res
|
commit_hash, commit_message = res
|
||||||
@@ -2168,7 +2416,7 @@ class Coder:
|
|||||||
if not self.repo:
|
if not self.repo:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.repo.commit(fnames=self.need_commit_before_edits)
|
self.repo.commit(fnames=self.need_commit_before_edits, coder=self)
|
||||||
|
|
||||||
# files changed, move cur messages back behind the files messages
|
# files changed, move cur messages back behind the files messages
|
||||||
# self.move_back_cur_messages(self.gpt_prompts.files_content_local_edits)
|
# self.move_back_cur_messages(self.gpt_prompts.files_content_local_edits)
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ class CoderPrompts:
|
|||||||
lazy_prompt = """You are diligent and tireless!
|
lazy_prompt = """You are diligent and tireless!
|
||||||
You NEVER leave comments describing code without implementing it!
|
You NEVER leave comments describing code without implementing it!
|
||||||
You always COMPLETELY IMPLEMENT the needed code!
|
You always COMPLETELY IMPLEMENT the needed code!
|
||||||
|
"""
|
||||||
|
|
||||||
|
overeager_prompt = """Pay careful attention to the scope of the user's request.
|
||||||
|
Do what they ask, but no more.
|
||||||
|
Do not improve, comment, fix or modify unrelated parts of the code in any way!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
example_messages = []
|
example_messages = []
|
||||||
@@ -50,3 +55,6 @@ Do not edit these files!
|
|||||||
shell_cmd_reminder = ""
|
shell_cmd_reminder = ""
|
||||||
no_shell_cmd_prompt = ""
|
no_shell_cmd_prompt = ""
|
||||||
no_shell_cmd_reminder = ""
|
no_shell_cmd_reminder = ""
|
||||||
|
|
||||||
|
rename_with_shell = ""
|
||||||
|
go_ahead_tip = ""
|
||||||
|
|||||||
53
aider/coders/context_coder.py
Normal file
53
aider/coders/context_coder.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
from .base_coder import Coder
|
||||||
|
from .context_prompts import ContextPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class ContextCoder(Coder):
|
||||||
|
"""Identify which files need to be edited for a given request."""
|
||||||
|
|
||||||
|
edit_format = "context"
|
||||||
|
gpt_prompts = ContextPrompts()
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if not self.repo_map:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.repo_map.refresh = "always"
|
||||||
|
self.repo_map.max_map_tokens *= self.repo_map.map_mul_no_files
|
||||||
|
self.repo_map.map_mul_no_files = 1.0
|
||||||
|
|
||||||
|
def reply_completed(self):
|
||||||
|
content = self.partial_response_content
|
||||||
|
if not content or not content.strip():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# dump(repr(content))
|
||||||
|
current_rel_fnames = set(self.get_inchat_relative_files())
|
||||||
|
mentioned_rel_fnames = set(self.get_file_mentions(content, ignore_current=True))
|
||||||
|
|
||||||
|
# dump(current_rel_fnames)
|
||||||
|
# dump(mentioned_rel_fnames)
|
||||||
|
# dump(current_rel_fnames == mentioned_rel_fnames)
|
||||||
|
|
||||||
|
if mentioned_rel_fnames == current_rel_fnames:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self.num_reflections >= self.max_reflections - 1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.abs_fnames = set()
|
||||||
|
for fname in mentioned_rel_fnames:
|
||||||
|
self.add_rel_fname(fname)
|
||||||
|
# dump(self.get_inchat_relative_files())
|
||||||
|
|
||||||
|
self.reflected_message = self.gpt_prompts.try_again
|
||||||
|
|
||||||
|
# mentioned_idents = self.get_ident_mentions(cur_msg_text)
|
||||||
|
# if mentioned_idents:
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_for_file_mentions(self, content):
|
||||||
|
pass
|
||||||
75
aider/coders/context_prompts.py
Normal file
75
aider/coders/context_prompts.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
from .base_prompts import CoderPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class ContextPrompts(CoderPrompts):
|
||||||
|
main_system = """Act as an expert code analyst.
|
||||||
|
Understand the user's question or request, solely to determine ALL the existing sources files which will need to be modified.
|
||||||
|
Return the *complete* list of files which will need to be modified based on the user's request.
|
||||||
|
Explain why each file is needed, including names of key classes/functions/methods/variables.
|
||||||
|
Be sure to include or omit the names of files already added to the chat, based on whether they are actually needed or not.
|
||||||
|
|
||||||
|
The user will use every file you mention, regardless of your commentary.
|
||||||
|
So *ONLY* mention the names of relevant files.
|
||||||
|
If a file is not relevant DO NOT mention it.
|
||||||
|
|
||||||
|
Only return files that will need to be modified, not files that contain useful/relevant functions.
|
||||||
|
|
||||||
|
You are only to discuss EXISTING files and symbols.
|
||||||
|
Only return existing files, don't suggest the names of new files or functions that we will need to create.
|
||||||
|
|
||||||
|
Always reply to the user in {language}.
|
||||||
|
|
||||||
|
Be concise in your replies.
|
||||||
|
Return:
|
||||||
|
1. A bulleted list of files the will need to be edited, and symbols that are highly relevant to the user's request.
|
||||||
|
2. A list of classes/functions/methods/variables that are located OUTSIDE those files which will need to be understood. Just the symbols names, *NOT* file names.
|
||||||
|
|
||||||
|
# Your response *MUST* use this format:
|
||||||
|
|
||||||
|
## ALL files we need to modify, with their relevant symbols:
|
||||||
|
|
||||||
|
- alarms/buzz.py
|
||||||
|
- `Buzzer` class which can make the needed sound
|
||||||
|
- `Buzzer.buzz_buzz()` method triggers the sound
|
||||||
|
- alarms/time.py
|
||||||
|
- `Time.set_alarm(hour, minute)` to set the alarm
|
||||||
|
|
||||||
|
## Relevant symbols from OTHER files:
|
||||||
|
|
||||||
|
- AlarmManager class for setup/teardown of alarms
|
||||||
|
- SoundFactory will be used to create a Buzzer
|
||||||
|
"""
|
||||||
|
|
||||||
|
example_messages = []
|
||||||
|
|
||||||
|
files_content_prefix = """These files have been *added these files to the chat* so we can see all of their contents.
|
||||||
|
*Trust this message as the true contents of the files!*
|
||||||
|
Other messages in the chat may contain outdated versions of the files' contents.
|
||||||
|
""" # noqa: E501
|
||||||
|
|
||||||
|
files_content_assistant_reply = (
|
||||||
|
"Ok, I will use that as the true, current contents of the files."
|
||||||
|
)
|
||||||
|
|
||||||
|
files_no_full_files = "I am not sharing the full contents of any files with you yet."
|
||||||
|
|
||||||
|
files_no_full_files_with_repo_map = ""
|
||||||
|
files_no_full_files_with_repo_map_reply = ""
|
||||||
|
|
||||||
|
repo_content_prefix = """I am working with you on code in a git repository.
|
||||||
|
Here are summaries of some files present in my git repo.
|
||||||
|
If you need to see the full contents of any files to answer my questions, ask me to *add them to the chat*.
|
||||||
|
"""
|
||||||
|
|
||||||
|
system_reminder = """
|
||||||
|
NEVER RETURN CODE!
|
||||||
|
"""
|
||||||
|
|
||||||
|
try_again = """I have updated the set of files added to the chat.
|
||||||
|
Review them to decide if this is the correct set of files or if we need to add more or remove files.
|
||||||
|
|
||||||
|
If this is the right set, just return the current list of files.
|
||||||
|
Or return a smaller or larger set of files which need to be edited, with symbols that are highly relevant to the user's request.
|
||||||
|
"""
|
||||||
@@ -383,7 +383,7 @@ def do_replace(fname, content, before_text, after_text, fence=None):
|
|||||||
return new_content
|
return new_content
|
||||||
|
|
||||||
|
|
||||||
HEAD = r"^<{5,9} SEARCH\s*$"
|
HEAD = r"^<{5,9} SEARCH>?\s*$"
|
||||||
DIVIDER = r"^={5,9}\s*$"
|
DIVIDER = r"^={5,9}\s*$"
|
||||||
UPDATED = r"^>{5,9} REPLACE\s*$"
|
UPDATED = r"^>{5,9} REPLACE\s*$"
|
||||||
|
|
||||||
@@ -412,7 +412,16 @@ def strip_filename(filename, fence):
|
|||||||
return
|
return
|
||||||
|
|
||||||
start_fence = fence[0]
|
start_fence = fence[0]
|
||||||
if filename.startswith(start_fence) or filename.startswith(triple_backticks):
|
if filename.startswith(start_fence):
|
||||||
|
candidate = filename[len(start_fence) :]
|
||||||
|
if candidate and ("." in candidate or "/" in candidate):
|
||||||
|
return candidate
|
||||||
|
return
|
||||||
|
|
||||||
|
if filename.startswith(triple_backticks):
|
||||||
|
candidate = filename[len(triple_backticks) :]
|
||||||
|
if candidate and ("." in candidate or "/" in candidate):
|
||||||
|
return candidate
|
||||||
return
|
return
|
||||||
|
|
||||||
filename = filename.rstrip(":")
|
filename = filename.rstrip(":")
|
||||||
@@ -454,7 +463,14 @@ def find_original_update_blocks(content, fence=DEFAULT_FENCE, valid_fnames=None)
|
|||||||
"```csh",
|
"```csh",
|
||||||
"```tcsh",
|
"```tcsh",
|
||||||
]
|
]
|
||||||
next_is_editblock = i + 1 < len(lines) and head_pattern.match(lines[i + 1].strip())
|
|
||||||
|
# Check if the next line or the one after that is an editblock
|
||||||
|
next_is_editblock = (
|
||||||
|
i + 1 < len(lines)
|
||||||
|
and head_pattern.match(lines[i + 1].strip())
|
||||||
|
or i + 2 < len(lines)
|
||||||
|
and head_pattern.match(lines[i + 2].strip())
|
||||||
|
)
|
||||||
|
|
||||||
if any(line.strip().startswith(start) for start in shell_starts) and not next_is_editblock:
|
if any(line.strip().startswith(start) for start in shell_starts) and not next_is_editblock:
|
||||||
shell_content = []
|
shell_content = []
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ from .editblock_fenced_prompts import EditBlockFencedPrompts
|
|||||||
|
|
||||||
class EditBlockFencedCoder(EditBlockCoder):
|
class EditBlockFencedCoder(EditBlockCoder):
|
||||||
"""A coder that uses fenced search/replace blocks for code modifications."""
|
"""A coder that uses fenced search/replace blocks for code modifications."""
|
||||||
|
|
||||||
edit_format = "diff-fenced"
|
edit_format = "diff-fenced"
|
||||||
gpt_prompts = EditBlockFencedPrompts()
|
gpt_prompts = EditBlockFencedPrompts()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class EditBlockFencedPrompts(EditBlockPrompts):
|
|||||||
|
|
||||||
Here are the *SEARCH/REPLACE* blocks:
|
Here are the *SEARCH/REPLACE* blocks:
|
||||||
|
|
||||||
{fence[0]}
|
{fence[0]}python
|
||||||
mathweb/flask/app.py
|
mathweb/flask/app.py
|
||||||
<<<<<<< SEARCH
|
<<<<<<< SEARCH
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
@@ -29,7 +29,7 @@ from flask import Flask
|
|||||||
>>>>>>> REPLACE
|
>>>>>>> REPLACE
|
||||||
{fence[1]}
|
{fence[1]}
|
||||||
|
|
||||||
{fence[0]}
|
{fence[0]}python
|
||||||
mathweb/flask/app.py
|
mathweb/flask/app.py
|
||||||
<<<<<<< SEARCH
|
<<<<<<< SEARCH
|
||||||
def factorial(n):
|
def factorial(n):
|
||||||
@@ -44,7 +44,7 @@ def factorial(n):
|
|||||||
>>>>>>> REPLACE
|
>>>>>>> REPLACE
|
||||||
{fence[1]}
|
{fence[1]}
|
||||||
|
|
||||||
{fence[0]}
|
{fence[0]}python
|
||||||
mathweb/flask/app.py
|
mathweb/flask/app.py
|
||||||
<<<<<<< SEARCH
|
<<<<<<< SEARCH
|
||||||
return str(factorial(n))
|
return str(factorial(n))
|
||||||
@@ -68,7 +68,7 @@ mathweb/flask/app.py
|
|||||||
|
|
||||||
Here are the *SEARCH/REPLACE* blocks:
|
Here are the *SEARCH/REPLACE* blocks:
|
||||||
|
|
||||||
{fence[0]}
|
{fence[0]}python
|
||||||
hello.py
|
hello.py
|
||||||
<<<<<<< SEARCH
|
<<<<<<< SEARCH
|
||||||
=======
|
=======
|
||||||
@@ -79,7 +79,7 @@ def hello():
|
|||||||
>>>>>>> REPLACE
|
>>>>>>> REPLACE
|
||||||
{fence[1]}
|
{fence[1]}
|
||||||
|
|
||||||
{fence[0]}
|
{fence[0]}python
|
||||||
main.py
|
main.py
|
||||||
<<<<<<< SEARCH
|
<<<<<<< SEARCH
|
||||||
def hello():
|
def hello():
|
||||||
@@ -93,3 +93,51 @@ from hello import hello
|
|||||||
""",
|
""",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
system_reminder = """
|
||||||
|
# *SEARCH/REPLACE block* Rules:
|
||||||
|
|
||||||
|
Every *SEARCH/REPLACE block* must use this format:
|
||||||
|
1. The opening fence and code language, eg: {fence[0]}python
|
||||||
|
2. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
|
||||||
|
3. The start of search block: <<<<<<< SEARCH
|
||||||
|
4. A contiguous chunk of lines to search for in the existing source code
|
||||||
|
5. The dividing line: =======
|
||||||
|
6. The lines to replace into the source code
|
||||||
|
7. The end of the replace block: >>>>>>> REPLACE
|
||||||
|
8. The closing fence: {fence[1]}
|
||||||
|
|
||||||
|
Use the *FULL* file path, as shown to you by the user.
|
||||||
|
{quad_backtick_reminder}
|
||||||
|
Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
|
||||||
|
If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.
|
||||||
|
|
||||||
|
*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
|
||||||
|
Including multiple unique *SEARCH/REPLACE* blocks if needed.
|
||||||
|
Include enough lines in each SEARCH section to uniquely match each set of lines that need to change.
|
||||||
|
|
||||||
|
Keep *SEARCH/REPLACE* blocks concise.
|
||||||
|
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
|
||||||
|
Include just the changing lines, and a few surrounding lines if needed for uniqueness.
|
||||||
|
Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
|
||||||
|
|
||||||
|
Only create *SEARCH/REPLACE* blocks for files that the user has added to the chat!
|
||||||
|
|
||||||
|
To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location.
|
||||||
|
|
||||||
|
Pay attention to which filenames the user wants you to edit, especially if they are asking you to create a new file.
|
||||||
|
|
||||||
|
If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
||||||
|
- A new file path, including dir name if needed
|
||||||
|
- An empty `SEARCH` section
|
||||||
|
- The new file's contents in the `REPLACE` section
|
||||||
|
|
||||||
|
To rename files which have been added to the chat, use shell commands at the end of your response.
|
||||||
|
|
||||||
|
If the user just says something like "ok" or "go ahead" or "do that" they probably want you to make SEARCH/REPLACE blocks for the code changes you just proposed.
|
||||||
|
The user will say when they've applied your edits. If they haven't explicitly confirmed the edits have been applied, they probably want proper SEARCH/REPLACE blocks.
|
||||||
|
|
||||||
|
{final_reminders}
|
||||||
|
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||||
|
{shell_cmd_reminder}
|
||||||
|
"""
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# flake8: noqa: E501
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
from . import shell
|
||||||
from .base_prompts import CoderPrompts
|
from .base_prompts import CoderPrompts
|
||||||
|
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@ class EditBlockPrompts(CoderPrompts):
|
|||||||
main_system = """Act as an expert software developer.
|
main_system = """Act as an expert software developer.
|
||||||
Always use best practices when coding.
|
Always use best practices when coding.
|
||||||
Respect and use existing conventions, libraries, etc that are already present in the code base.
|
Respect and use existing conventions, libraries, etc that are already present in the code base.
|
||||||
{lazy_prompt}
|
{final_reminders}
|
||||||
Take requests for changes to the supplied code.
|
Take requests for changes to the supplied code.
|
||||||
If the request is ambiguous, ask questions.
|
If the request is ambiguous, ask questions.
|
||||||
|
|
||||||
@@ -28,32 +29,6 @@ You can keep asking if you then decide you need to edit more files.
|
|||||||
All changes to files must use this *SEARCH/REPLACE block* format.
|
All changes to files must use this *SEARCH/REPLACE block* format.
|
||||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||||
{shell_cmd_prompt}
|
{shell_cmd_prompt}
|
||||||
"""
|
|
||||||
|
|
||||||
shell_cmd_prompt = """
|
|
||||||
4. *Concisely* suggest any shell commands the user might want to run in ```bash blocks.
|
|
||||||
|
|
||||||
Just suggest shell commands this way, not example code.
|
|
||||||
Only suggest complete shell commands that are ready to execute, without placeholders.
|
|
||||||
Only suggest at most a few shell commands at a time, not more than 1-3, one per line.
|
|
||||||
Do not suggest multi-line shell commands.
|
|
||||||
All shell commands will run from the root directory of the user's project.
|
|
||||||
|
|
||||||
Use the appropriate shell based on the user's system info:
|
|
||||||
{platform}
|
|
||||||
Examples of when to suggest shell commands:
|
|
||||||
|
|
||||||
- If you changed a self-contained html file, suggest an OS-appropriate command to open a browser to view it to see the updated content.
|
|
||||||
- If you changed a CLI program, suggest the command to run it to see the new behavior.
|
|
||||||
- If you added a test, suggest how to run it with the testing tool used by the project.
|
|
||||||
- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations.
|
|
||||||
- If your code changes add new dependencies, suggest the command to install them.
|
|
||||||
- Etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
no_shell_cmd_prompt = """
|
|
||||||
Keep in mind these details about the user's platform and environment:
|
|
||||||
{platform}
|
|
||||||
"""
|
"""
|
||||||
example_messages = [
|
example_messages = [
|
||||||
dict(
|
dict(
|
||||||
@@ -181,23 +156,19 @@ If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
|||||||
- An empty `SEARCH` section
|
- An empty `SEARCH` section
|
||||||
- The new file's contents in the `REPLACE` section
|
- The new file's contents in the `REPLACE` section
|
||||||
|
|
||||||
To rename files which have been added to the chat, use shell commands at the end of your response.
|
{rename_with_shell}{go_ahead_tip}{final_reminders}ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||||
|
|
||||||
If the user just says something like "ok" or "go ahead" or "do that" they probably want you to make SEARCH/REPLACE blocks for the code changes you just proposed.
|
|
||||||
The user will say when they've applied your edits. If they haven't explicitly confirmed the edits have been applied, they probably want proper SEARCH/REPLACE blocks.
|
|
||||||
|
|
||||||
{lazy_prompt}
|
|
||||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
|
||||||
{shell_cmd_reminder}
|
{shell_cmd_reminder}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
shell_cmd_reminder = """
|
rename_with_shell = """To rename files which have been added to the chat, use shell commands at the end of your response.
|
||||||
Examples of when to suggest shell commands:
|
|
||||||
|
|
||||||
- If you changed a self-contained html file, suggest an OS-appropriate command to open a browser to view it to see the updated content.
|
|
||||||
- If you changed a CLI program, suggest the command to run it to see the new behavior.
|
|
||||||
- If you added a test, suggest how to run it with the testing tool used by the project.
|
|
||||||
- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations.
|
|
||||||
- If your code changes add new dependencies, suggest the command to install them.
|
|
||||||
- Etc.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
go_ahead_tip = """If the user just says something like "ok" or "go ahead" or "do that" they probably want you to make SEARCH/REPLACE blocks for the code changes you just proposed.
|
||||||
|
The user will say when they've applied your edits. If they haven't explicitly confirmed the edits have been applied, they probably want proper SEARCH/REPLACE blocks.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
shell_cmd_prompt = shell.shell_cmd_prompt
|
||||||
|
no_shell_cmd_prompt = shell.no_shell_cmd_prompt
|
||||||
|
shell_cmd_reminder = shell.shell_cmd_reminder
|
||||||
|
|||||||
9
aider/coders/editor_diff_fenced_coder.py
Normal file
9
aider/coders/editor_diff_fenced_coder.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from .editblock_fenced_coder import EditBlockFencedCoder
|
||||||
|
from .editor_diff_fenced_prompts import EditorDiffFencedPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class EditorDiffFencedCoder(EditBlockFencedCoder):
|
||||||
|
"A coder that uses search/replace blocks, focused purely on editing files."
|
||||||
|
|
||||||
|
edit_format = "editor-diff-fenced"
|
||||||
|
gpt_prompts = EditorDiffFencedPrompts()
|
||||||
11
aider/coders/editor_diff_fenced_prompts.py
Normal file
11
aider/coders/editor_diff_fenced_prompts.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
from .editblock_fenced_prompts import EditBlockFencedPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class EditorDiffFencedPrompts(EditBlockFencedPrompts):
|
||||||
|
shell_cmd_prompt = ""
|
||||||
|
no_shell_cmd_prompt = ""
|
||||||
|
shell_cmd_reminder = ""
|
||||||
|
go_ahead_tip = ""
|
||||||
|
rename_with_shell = ""
|
||||||
@@ -5,7 +5,7 @@ from .editblock_prompts import EditBlockPrompts
|
|||||||
|
|
||||||
class EditorEditBlockPrompts(EditBlockPrompts):
|
class EditorEditBlockPrompts(EditBlockPrompts):
|
||||||
main_system = """Act as an expert software developer who edits source code.
|
main_system = """Act as an expert software developer who edits source code.
|
||||||
{lazy_prompt}
|
{final_reminders}
|
||||||
Describe each change with a *SEARCH/REPLACE block* per the examples below.
|
Describe each change with a *SEARCH/REPLACE block* per the examples below.
|
||||||
All changes to files must use this *SEARCH/REPLACE block* format.
|
All changes to files must use this *SEARCH/REPLACE block* format.
|
||||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||||
@@ -14,3 +14,5 @@ ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
|||||||
shell_cmd_prompt = ""
|
shell_cmd_prompt = ""
|
||||||
no_shell_cmd_prompt = ""
|
no_shell_cmd_prompt = ""
|
||||||
shell_cmd_reminder = ""
|
shell_cmd_reminder = ""
|
||||||
|
go_ahead_tip = ""
|
||||||
|
rename_with_shell = ""
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ from .wholefile_prompts import WholeFilePrompts
|
|||||||
|
|
||||||
class EditorWholeFilePrompts(WholeFilePrompts):
|
class EditorWholeFilePrompts(WholeFilePrompts):
|
||||||
main_system = """Act as an expert software developer and make changes to source code.
|
main_system = """Act as an expert software developer and make changes to source code.
|
||||||
{lazy_prompt}
|
{final_reminders}
|
||||||
Output a copy of each file that needs changes.
|
Output a copy of each file that needs changes.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from .help_prompts import HelpPrompts
|
|||||||
|
|
||||||
class HelpCoder(Coder):
|
class HelpCoder(Coder):
|
||||||
"""Interactive help and documentation about aider."""
|
"""Interactive help and documentation about aider."""
|
||||||
|
|
||||||
edit_format = "help"
|
edit_format = "help"
|
||||||
gpt_prompts = HelpPrompts()
|
gpt_prompts = HelpPrompts()
|
||||||
|
|
||||||
|
|||||||
706
aider/coders/patch_coder.py
Normal file
706
aider/coders/patch_coder.py
Normal file
@@ -0,0 +1,706 @@
|
|||||||
|
import pathlib
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from .base_coder import Coder
|
||||||
|
from .patch_prompts import PatchPrompts
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Domain objects & Exceptions (Adapted from apply_patch.py)
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
class DiffError(ValueError):
|
||||||
|
"""Any problem detected while parsing or applying a patch."""
|
||||||
|
|
||||||
|
|
||||||
|
class ActionType(str, Enum):
|
||||||
|
ADD = "Add"
|
||||||
|
DELETE = "Delete"
|
||||||
|
UPDATE = "Update"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Chunk:
|
||||||
|
orig_index: int = -1 # Line number in the *original* file block where the change starts
|
||||||
|
del_lines: List[str] = field(default_factory=list)
|
||||||
|
ins_lines: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PatchAction:
|
||||||
|
type: ActionType
|
||||||
|
path: str
|
||||||
|
# For ADD:
|
||||||
|
new_content: Optional[str] = None
|
||||||
|
# For UPDATE:
|
||||||
|
chunks: List[Chunk] = field(default_factory=list)
|
||||||
|
move_path: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# Type alias for the return type of get_edits
|
||||||
|
EditResult = Tuple[str, PatchAction]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Patch:
|
||||||
|
actions: Dict[str, PatchAction] = field(default_factory=dict)
|
||||||
|
fuzz: int = 0 # Track fuzziness used during parsing
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Helper functions (Adapted from apply_patch.py)
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
def _norm(line: str) -> str:
|
||||||
|
"""Strip CR so comparisons work for both LF and CRLF input."""
|
||||||
|
return line.rstrip("\r")
|
||||||
|
|
||||||
|
|
||||||
|
def find_context_core(lines: List[str], context: List[str], start: int) -> Tuple[int, int]:
|
||||||
|
"""Finds context block, returns start index and fuzz level."""
|
||||||
|
if not context:
|
||||||
|
return start, 0
|
||||||
|
|
||||||
|
# Exact match
|
||||||
|
for i in range(start, len(lines) - len(context) + 1):
|
||||||
|
if lines[i : i + len(context)] == context:
|
||||||
|
return i, 0
|
||||||
|
# Rstrip match
|
||||||
|
norm_context = [s.rstrip() for s in context]
|
||||||
|
for i in range(start, len(lines) - len(context) + 1):
|
||||||
|
if [s.rstrip() for s in lines[i : i + len(context)]] == norm_context:
|
||||||
|
return i, 1 # Fuzz level 1
|
||||||
|
# Strip match
|
||||||
|
norm_context_strip = [s.strip() for s in context]
|
||||||
|
for i in range(start, len(lines) - len(context) + 1):
|
||||||
|
if [s.strip() for s in lines[i : i + len(context)]] == norm_context_strip:
|
||||||
|
return i, 100 # Fuzz level 100
|
||||||
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
|
def find_context(lines: List[str], context: List[str], start: int, eof: bool) -> Tuple[int, int]:
|
||||||
|
"""Finds context, handling EOF marker."""
|
||||||
|
if eof:
|
||||||
|
# If EOF marker, first try matching at the very end
|
||||||
|
if len(lines) >= len(context):
|
||||||
|
new_index, fuzz = find_context_core(lines, context, len(lines) - len(context))
|
||||||
|
if new_index != -1:
|
||||||
|
return new_index, fuzz
|
||||||
|
# If not found at end, search from `start` as fallback
|
||||||
|
new_index, fuzz = find_context_core(lines, context, start)
|
||||||
|
return new_index, fuzz + 10_000 # Add large fuzz penalty if EOF wasn't at end
|
||||||
|
# Normal case: search from `start`
|
||||||
|
return find_context_core(lines, context, start)
|
||||||
|
|
||||||
|
|
||||||
|
def peek_next_section(lines: List[str], index: int) -> Tuple[List[str], List[Chunk], int, bool]:
|
||||||
|
"""
|
||||||
|
Parses one section (context, -, + lines) of an Update block.
|
||||||
|
Returns: (context_lines, chunks_in_section, next_index, is_eof)
|
||||||
|
"""
|
||||||
|
context_lines: List[str] = []
|
||||||
|
del_lines: List[str] = []
|
||||||
|
ins_lines: List[str] = []
|
||||||
|
chunks: List[Chunk] = []
|
||||||
|
mode = "keep" # Start by expecting context lines
|
||||||
|
start_index = index
|
||||||
|
|
||||||
|
while index < len(lines):
|
||||||
|
line = lines[index]
|
||||||
|
norm_line = _norm(line)
|
||||||
|
|
||||||
|
# Check for section terminators
|
||||||
|
if norm_line.startswith(
|
||||||
|
(
|
||||||
|
"@@",
|
||||||
|
"*** End Patch",
|
||||||
|
"*** Update File:",
|
||||||
|
"*** Delete File:",
|
||||||
|
"*** Add File:",
|
||||||
|
"*** End of File", # Special terminator
|
||||||
|
)
|
||||||
|
):
|
||||||
|
break
|
||||||
|
if norm_line == "***": # Legacy/alternative terminator? Handle just in case.
|
||||||
|
break
|
||||||
|
if norm_line.startswith("***"): # Invalid line
|
||||||
|
raise DiffError(f"Invalid patch line found in update section: {line}")
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
last_mode = mode
|
||||||
|
|
||||||
|
# Determine line type and strip prefix
|
||||||
|
if line.startswith("+"):
|
||||||
|
mode = "add"
|
||||||
|
line_content = line[1:]
|
||||||
|
elif line.startswith("-"):
|
||||||
|
mode = "delete"
|
||||||
|
line_content = line[1:]
|
||||||
|
elif line.startswith(" "):
|
||||||
|
mode = "keep"
|
||||||
|
line_content = line[1:]
|
||||||
|
elif line.strip() == "": # Treat blank lines in patch as context ' '
|
||||||
|
mode = "keep"
|
||||||
|
line_content = "" # Keep it as a blank line
|
||||||
|
else:
|
||||||
|
# Assume lines without prefix are context if format is loose,
|
||||||
|
# but strict format requires ' '. Raise error for strictness.
|
||||||
|
raise DiffError(f"Invalid line prefix in update section: {line}")
|
||||||
|
|
||||||
|
# If mode changes from add/delete back to keep, finalize the previous chunk
|
||||||
|
if mode == "keep" and last_mode != "keep":
|
||||||
|
if del_lines or ins_lines:
|
||||||
|
chunks.append(
|
||||||
|
Chunk(
|
||||||
|
# orig_index is relative to the start of the *context* block found
|
||||||
|
orig_index=len(context_lines) - len(del_lines),
|
||||||
|
del_lines=del_lines,
|
||||||
|
ins_lines=ins_lines,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
del_lines, ins_lines = [], []
|
||||||
|
|
||||||
|
# Collect lines based on mode
|
||||||
|
if mode == "delete":
|
||||||
|
del_lines.append(line_content)
|
||||||
|
context_lines.append(line_content) # Deleted lines are part of the original context
|
||||||
|
elif mode == "add":
|
||||||
|
ins_lines.append(line_content)
|
||||||
|
elif mode == "keep":
|
||||||
|
context_lines.append(line_content)
|
||||||
|
|
||||||
|
# Finalize any pending chunk at the end of the section
|
||||||
|
if del_lines or ins_lines:
|
||||||
|
chunks.append(
|
||||||
|
Chunk(
|
||||||
|
orig_index=len(context_lines) - len(del_lines),
|
||||||
|
del_lines=del_lines,
|
||||||
|
ins_lines=ins_lines,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for EOF marker
|
||||||
|
is_eof = False
|
||||||
|
if index < len(lines) and _norm(lines[index]) == "*** End of File":
|
||||||
|
index += 1
|
||||||
|
is_eof = True
|
||||||
|
|
||||||
|
if index == start_index and not is_eof: # Should not happen if patch is well-formed
|
||||||
|
raise DiffError("Empty patch section found.")
|
||||||
|
|
||||||
|
return context_lines, chunks, index, is_eof
|
||||||
|
|
||||||
|
|
||||||
|
def identify_files_needed(text: str) -> List[str]:
|
||||||
|
"""Extracts file paths from Update and Delete actions."""
|
||||||
|
lines = text.splitlines()
|
||||||
|
paths = set()
|
||||||
|
for line in lines:
|
||||||
|
norm_line = _norm(line)
|
||||||
|
if norm_line.startswith("*** Update File: "):
|
||||||
|
paths.add(norm_line[len("*** Update File: ") :].strip())
|
||||||
|
elif norm_line.startswith("*** Delete File: "):
|
||||||
|
paths.add(norm_line[len("*** Delete File: ") :].strip())
|
||||||
|
return list(paths)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# PatchCoder Class Implementation
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
class PatchCoder(Coder):
|
||||||
|
"""
|
||||||
|
A coder that uses a custom patch format for code modifications,
|
||||||
|
inspired by the format described in tmp.gpt41edits.txt.
|
||||||
|
Applies patches using logic adapted from the reference apply_patch.py script.
|
||||||
|
"""
|
||||||
|
|
||||||
|
edit_format = "patch"
|
||||||
|
gpt_prompts = PatchPrompts()
|
||||||
|
|
||||||
|
def get_edits(self) -> List[EditResult]:
|
||||||
|
"""
|
||||||
|
Parses the LLM response content (containing the patch) into a list of
|
||||||
|
tuples, where each tuple contains the file path and the PatchAction object.
|
||||||
|
"""
|
||||||
|
content = self.partial_response_content
|
||||||
|
if not content or not content.strip():
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Check for patch sentinels
|
||||||
|
lines = content.splitlines()
|
||||||
|
if (
|
||||||
|
len(lines) < 2
|
||||||
|
or not _norm(lines[0]).startswith("*** Begin Patch")
|
||||||
|
# Allow flexible end, might be EOF or just end of stream
|
||||||
|
# or _norm(lines[-1]) != "*** End Patch"
|
||||||
|
):
|
||||||
|
# Tolerate missing sentinels if content looks like a patch action
|
||||||
|
is_patch_like = any(
|
||||||
|
_norm(line).startswith(
|
||||||
|
("@@", "*** Update File:", "*** Add File:", "*** Delete File:")
|
||||||
|
)
|
||||||
|
for line in lines
|
||||||
|
)
|
||||||
|
if not is_patch_like:
|
||||||
|
# If it doesn't even look like a patch, return empty
|
||||||
|
self.io.tool_warning("Response does not appear to be in patch format.")
|
||||||
|
return []
|
||||||
|
# If it looks like a patch but lacks sentinels, try parsing anyway but warn.
|
||||||
|
self.io.tool_warning(
|
||||||
|
"Patch format warning: Missing '*** Begin Patch'/'*** End Patch' sentinels."
|
||||||
|
)
|
||||||
|
start_index = 0
|
||||||
|
else:
|
||||||
|
start_index = 1 # Skip "*** Begin Patch"
|
||||||
|
|
||||||
|
# Identify files needed for context lookups during parsing
|
||||||
|
needed_paths = identify_files_needed(content)
|
||||||
|
current_files: Dict[str, str] = {}
|
||||||
|
for rel_path in needed_paths:
|
||||||
|
abs_path = self.abs_root_path(rel_path)
|
||||||
|
try:
|
||||||
|
# Use io.read_text to handle potential errors/encodings
|
||||||
|
file_content = self.io.read_text(abs_path)
|
||||||
|
if file_content is None:
|
||||||
|
raise DiffError(
|
||||||
|
f"File referenced in patch not found or could not be read: {rel_path}"
|
||||||
|
)
|
||||||
|
current_files[rel_path] = file_content
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise DiffError(f"File referenced in patch not found: {rel_path}")
|
||||||
|
except IOError as e:
|
||||||
|
raise DiffError(f"Error reading file {rel_path}: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Parse the patch text using adapted logic
|
||||||
|
patch_obj = self._parse_patch_text(lines, start_index, current_files)
|
||||||
|
# Convert Patch object actions dict to a list of tuples (path, action)
|
||||||
|
# for compatibility with the base Coder's prepare_to_edit method.
|
||||||
|
results = []
|
||||||
|
for path, action in patch_obj.actions.items():
|
||||||
|
results.append((path, action))
|
||||||
|
return results
|
||||||
|
except DiffError as e:
|
||||||
|
# Raise as ValueError for consistency with other coders' error handling
|
||||||
|
raise ValueError(f"Error parsing patch content: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
# Catch unexpected errors during parsing
|
||||||
|
raise ValueError(f"Unexpected error parsing patch: {e}")
|
||||||
|
|
||||||
|
def _parse_patch_text(
|
||||||
|
self, lines: List[str], start_index: int, current_files: Dict[str, str]
|
||||||
|
) -> Patch:
|
||||||
|
"""
|
||||||
|
Parses patch content lines into a Patch object.
|
||||||
|
Adapted from the Parser class in apply_patch.py.
|
||||||
|
"""
|
||||||
|
patch = Patch()
|
||||||
|
index = start_index
|
||||||
|
fuzz_accumulator = 0
|
||||||
|
|
||||||
|
while index < len(lines):
|
||||||
|
line = lines[index]
|
||||||
|
norm_line = _norm(line)
|
||||||
|
|
||||||
|
if norm_line == "*** End Patch":
|
||||||
|
index += 1
|
||||||
|
break # Successfully reached end
|
||||||
|
|
||||||
|
# ---------- UPDATE ---------- #
|
||||||
|
if norm_line.startswith("*** Update File: "):
|
||||||
|
path = norm_line[len("*** Update File: ") :].strip()
|
||||||
|
index += 1
|
||||||
|
if not path:
|
||||||
|
raise DiffError("Update File action missing path.")
|
||||||
|
|
||||||
|
# Optional move target
|
||||||
|
move_to = None
|
||||||
|
if index < len(lines) and _norm(lines[index]).startswith("*** Move to: "):
|
||||||
|
move_to = _norm(lines[index])[len("*** Move to: ") :].strip()
|
||||||
|
index += 1
|
||||||
|
if not move_to:
|
||||||
|
raise DiffError("Move to action missing path.")
|
||||||
|
|
||||||
|
if path not in current_files:
|
||||||
|
raise DiffError(f"Update File Error - missing file content for: {path}")
|
||||||
|
|
||||||
|
file_content = current_files[path]
|
||||||
|
|
||||||
|
existing_action = patch.actions.get(path)
|
||||||
|
if existing_action is not None:
|
||||||
|
# Merge additional UPDATE block into the existing one
|
||||||
|
if existing_action.type != ActionType.UPDATE:
|
||||||
|
raise DiffError(f"Conflicting actions for file: {path}")
|
||||||
|
|
||||||
|
new_action, index, fuzz = self._parse_update_file_sections(
|
||||||
|
lines, index, file_content
|
||||||
|
)
|
||||||
|
existing_action.chunks.extend(new_action.chunks)
|
||||||
|
|
||||||
|
if move_to:
|
||||||
|
if existing_action.move_path and existing_action.move_path != move_to:
|
||||||
|
raise DiffError(f"Conflicting move targets for file: {path}")
|
||||||
|
existing_action.move_path = move_to
|
||||||
|
fuzz_accumulator += fuzz
|
||||||
|
else:
|
||||||
|
# First UPDATE block for this file
|
||||||
|
action, index, fuzz = self._parse_update_file_sections(
|
||||||
|
lines, index, file_content
|
||||||
|
)
|
||||||
|
action.path = path
|
||||||
|
action.move_path = move_to
|
||||||
|
patch.actions[path] = action
|
||||||
|
fuzz_accumulator += fuzz
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ---------- DELETE ---------- #
|
||||||
|
elif norm_line.startswith("*** Delete File: "):
|
||||||
|
path = norm_line[len("*** Delete File: ") :].strip()
|
||||||
|
index += 1
|
||||||
|
if not path:
|
||||||
|
raise DiffError("Delete File action missing path.")
|
||||||
|
existing_action = patch.actions.get(path)
|
||||||
|
if existing_action:
|
||||||
|
if existing_action.type == ActionType.DELETE:
|
||||||
|
# Duplicate delete – ignore the extra block
|
||||||
|
self.io.tool_warning(f"Duplicate delete action for file: {path} ignored.")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise DiffError(f"Conflicting actions for file: {path}")
|
||||||
|
if path not in current_files:
|
||||||
|
raise DiffError(
|
||||||
|
f"Delete File Error - file not found: {path}"
|
||||||
|
) # Check against known files
|
||||||
|
|
||||||
|
patch.actions[path] = PatchAction(type=ActionType.DELETE, path=path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ---------- ADD ---------- #
|
||||||
|
elif norm_line.startswith("*** Add File: "):
|
||||||
|
path = norm_line[len("*** Add File: ") :].strip()
|
||||||
|
index += 1
|
||||||
|
if not path:
|
||||||
|
raise DiffError("Add File action missing path.")
|
||||||
|
if path in patch.actions:
|
||||||
|
raise DiffError(f"Duplicate action for file: {path}")
|
||||||
|
# Check if file exists in the context provided (should not for Add).
|
||||||
|
# Note: We only have needed files, a full check requires FS access.
|
||||||
|
# if path in current_files:
|
||||||
|
# raise DiffError(f"Add File Error - file already exists: {path}")
|
||||||
|
|
||||||
|
action, index = self._parse_add_file_content(lines, index)
|
||||||
|
action.path = path # Ensure path is set
|
||||||
|
patch.actions[path] = action
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we are here, the line is unexpected
|
||||||
|
# Allow blank lines between actions
|
||||||
|
if not norm_line.strip():
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise DiffError(f"Unknown or misplaced line while parsing patch: {line}")
|
||||||
|
|
||||||
|
# Check if we consumed the whole input or stopped early
|
||||||
|
# Tolerate missing "*** End Patch" if we processed actions
|
||||||
|
# if index < len(lines) and _norm(lines[index-1]) != "*** End Patch":
|
||||||
|
# raise DiffError("Patch parsing finished unexpectedly before end of input.")
|
||||||
|
|
||||||
|
patch.fuzz = fuzz_accumulator
|
||||||
|
return patch
|
||||||
|
|
||||||
|
def _parse_update_file_sections(
|
||||||
|
self, lines: List[str], index: int, file_content: str
|
||||||
|
) -> Tuple[PatchAction, int, int]:
|
||||||
|
"""Parses all sections (@@, context, -, +) for a single Update File action."""
|
||||||
|
action = PatchAction(type=ActionType.UPDATE, path="") # Path set by caller
|
||||||
|
orig_lines = file_content.splitlines() # Use splitlines for consistency
|
||||||
|
current_file_index = 0 # Track position in original file content
|
||||||
|
total_fuzz = 0
|
||||||
|
|
||||||
|
while index < len(lines):
|
||||||
|
norm_line = _norm(lines[index])
|
||||||
|
# Check for terminators for *this* file update
|
||||||
|
if norm_line.startswith(
|
||||||
|
(
|
||||||
|
"*** End Patch",
|
||||||
|
"*** Update File:",
|
||||||
|
"*** Delete File:",
|
||||||
|
"*** Add File:",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
break # End of this file's update section
|
||||||
|
|
||||||
|
# Handle @@ scope lines (optional)
|
||||||
|
scope_lines = []
|
||||||
|
while index < len(lines) and _norm(lines[index]).startswith("@@"):
|
||||||
|
scope_line_content = lines[index][len("@@") :].strip()
|
||||||
|
if scope_line_content: # Ignore empty @@ lines?
|
||||||
|
scope_lines.append(scope_line_content)
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
# Find the scope in the original file if specified
|
||||||
|
if scope_lines:
|
||||||
|
# Simple scope finding: search from current position
|
||||||
|
# A more robust finder could handle nested scopes like the reference @@ @@
|
||||||
|
found_scope = False
|
||||||
|
temp_index = current_file_index
|
||||||
|
while temp_index < len(orig_lines):
|
||||||
|
# Check if all scope lines match sequentially from temp_index
|
||||||
|
match = True
|
||||||
|
for i, scope in enumerate(scope_lines):
|
||||||
|
if (
|
||||||
|
temp_index + i >= len(orig_lines)
|
||||||
|
or _norm(orig_lines[temp_index + i]).strip() != scope
|
||||||
|
):
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if match:
|
||||||
|
current_file_index = temp_index + len(scope_lines)
|
||||||
|
found_scope = True
|
||||||
|
break
|
||||||
|
temp_index += 1
|
||||||
|
|
||||||
|
if not found_scope:
|
||||||
|
# Try fuzzy scope matching (strip whitespace)
|
||||||
|
temp_index = current_file_index
|
||||||
|
while temp_index < len(orig_lines):
|
||||||
|
match = True
|
||||||
|
for i, scope in enumerate(scope_lines):
|
||||||
|
if (
|
||||||
|
temp_index + i >= len(orig_lines)
|
||||||
|
or _norm(orig_lines[temp_index + i]).strip() != scope.strip()
|
||||||
|
):
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if match:
|
||||||
|
current_file_index = temp_index + len(scope_lines)
|
||||||
|
found_scope = True
|
||||||
|
total_fuzz += 1 # Add fuzz for scope match difference
|
||||||
|
break
|
||||||
|
temp_index += 1
|
||||||
|
|
||||||
|
if not found_scope:
|
||||||
|
scope_txt = "\n".join(scope_lines)
|
||||||
|
raise DiffError(f"Could not find scope context:\n{scope_txt}")
|
||||||
|
|
||||||
|
# Peek and parse the next context/change section
|
||||||
|
context_block, chunks_in_section, next_index, is_eof = peek_next_section(lines, index)
|
||||||
|
|
||||||
|
# Find where this context block appears in the original file
|
||||||
|
found_index, fuzz = find_context(orig_lines, context_block, current_file_index, is_eof)
|
||||||
|
total_fuzz += fuzz
|
||||||
|
|
||||||
|
if found_index == -1:
|
||||||
|
ctx_txt = "\n".join(context_block)
|
||||||
|
marker = "*** End of File" if is_eof else ""
|
||||||
|
raise DiffError(
|
||||||
|
f"Could not find patch context {marker} starting near line"
|
||||||
|
f" {current_file_index}:\n{ctx_txt}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Adjust chunk original indices to be absolute within the file
|
||||||
|
for chunk in chunks_in_section:
|
||||||
|
# chunk.orig_index from peek is relative to context_block start
|
||||||
|
# We need it relative to the file start
|
||||||
|
chunk.orig_index += found_index
|
||||||
|
action.chunks.append(chunk)
|
||||||
|
|
||||||
|
# Advance file index past the matched context block
|
||||||
|
current_file_index = found_index + len(context_block)
|
||||||
|
# Advance line index past the processed section in the patch
|
||||||
|
index = next_index
|
||||||
|
|
||||||
|
return action, index, total_fuzz
|
||||||
|
|
||||||
|
def _parse_add_file_content(self, lines: List[str], index: int) -> Tuple[PatchAction, int]:
|
||||||
|
"""Parses the content (+) lines for an Add File action."""
|
||||||
|
added_lines: List[str] = []
|
||||||
|
while index < len(lines):
|
||||||
|
line = lines[index]
|
||||||
|
norm_line = _norm(line)
|
||||||
|
# Stop if we hit another action or end marker
|
||||||
|
if norm_line.startswith(
|
||||||
|
(
|
||||||
|
"*** End Patch",
|
||||||
|
"*** Update File:",
|
||||||
|
"*** Delete File:",
|
||||||
|
"*** Add File:",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Expect lines to start with '+'
|
||||||
|
if not line.startswith("+"):
|
||||||
|
# Tolerate blank lines? Or require '+'? Reference implies '+' required.
|
||||||
|
if norm_line.strip() == "":
|
||||||
|
# Treat blank line as adding a blank line
|
||||||
|
added_lines.append("")
|
||||||
|
else:
|
||||||
|
raise DiffError(f"Invalid Add File line (missing '+'): {line}")
|
||||||
|
else:
|
||||||
|
added_lines.append(line[1:]) # Strip leading '+'
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
action = PatchAction(type=ActionType.ADD, path="", new_content="\n".join(added_lines))
|
||||||
|
return action, index
|
||||||
|
|
||||||
|
def apply_edits(self, edits: List[PatchAction]):
|
||||||
|
"""
|
||||||
|
Applies the parsed PatchActions to the corresponding files.
|
||||||
|
"""
|
||||||
|
if not edits:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Group edits by original path? Not strictly needed if processed sequentially.
|
||||||
|
|
||||||
|
# Edits are now List[Tuple[str, PatchAction]]
|
||||||
|
for _path_tuple_element, action in edits:
|
||||||
|
# action is the PatchAction object
|
||||||
|
# action.path is the canonical path within the action logic
|
||||||
|
full_path = self.abs_root_path(action.path)
|
||||||
|
path_obj = pathlib.Path(full_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if action.type == ActionType.ADD:
|
||||||
|
# Check existence *before* writing
|
||||||
|
if path_obj.exists():
|
||||||
|
raise DiffError(f"ADD Error: File already exists: {action.path}")
|
||||||
|
if action.new_content is None:
|
||||||
|
# Parser should ensure this doesn't happen
|
||||||
|
raise DiffError(f"ADD change for {action.path} has no content")
|
||||||
|
|
||||||
|
self.io.tool_output(f"Adding {action.path}")
|
||||||
|
path_obj.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
# Ensure single trailing newline, matching reference behavior
|
||||||
|
content_to_write = action.new_content
|
||||||
|
if not content_to_write.endswith("\n"):
|
||||||
|
content_to_write += "\n"
|
||||||
|
self.io.write_text(full_path, content_to_write)
|
||||||
|
|
||||||
|
elif action.type == ActionType.DELETE:
|
||||||
|
self.io.tool_output(f"Deleting {action.path}")
|
||||||
|
if not path_obj.exists():
|
||||||
|
self.io.tool_warning(
|
||||||
|
f"DELETE Warning: File not found, skipping: {action.path}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
path_obj.unlink()
|
||||||
|
|
||||||
|
elif action.type == ActionType.UPDATE:
|
||||||
|
if not path_obj.exists():
|
||||||
|
raise DiffError(f"UPDATE Error: File does not exist: {action.path}")
|
||||||
|
|
||||||
|
current_content = self.io.read_text(full_path)
|
||||||
|
if current_content is None:
|
||||||
|
# Should have been caught during parsing if file was needed
|
||||||
|
raise DiffError(f"Could not read file for UPDATE: {action.path}")
|
||||||
|
|
||||||
|
# Apply the update logic using the parsed chunks
|
||||||
|
new_content = self._apply_update(current_content, action, action.path)
|
||||||
|
|
||||||
|
target_full_path = (
|
||||||
|
self.abs_root_path(action.move_path) if action.move_path else full_path
|
||||||
|
)
|
||||||
|
target_path_obj = pathlib.Path(target_full_path)
|
||||||
|
|
||||||
|
if action.move_path:
|
||||||
|
self.io.tool_output(
|
||||||
|
f"Updating and moving {action.path} to {action.move_path}"
|
||||||
|
)
|
||||||
|
# Check if target exists before overwriting/moving
|
||||||
|
if target_path_obj.exists() and full_path != target_full_path:
|
||||||
|
self.io.tool_warning(
|
||||||
|
"UPDATE Warning: Target file for move already exists, overwriting:"
|
||||||
|
f" {action.move_path}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.io.tool_output(f"Updating {action.path}")
|
||||||
|
|
||||||
|
# Ensure parent directory exists for target
|
||||||
|
target_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.io.write_text(target_full_path, new_content)
|
||||||
|
|
||||||
|
# Remove original file *after* successful write to new location if moved
|
||||||
|
if action.move_path and full_path != target_full_path:
|
||||||
|
path_obj.unlink()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Should not happen
|
||||||
|
raise DiffError(f"Unknown action type encountered: {action.type}")
|
||||||
|
|
||||||
|
except (DiffError, FileNotFoundError, IOError, OSError) as e:
|
||||||
|
# Raise a ValueError to signal failure, consistent with other coders.
|
||||||
|
raise ValueError(f"Error applying action '{action.type}' to {action.path}: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
# Catch unexpected errors during application
|
||||||
|
raise ValueError(
|
||||||
|
f"Unexpected error applying action '{action.type}' to {action.path}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _apply_update(self, text: str, action: PatchAction, path: str) -> str:
|
||||||
|
"""
|
||||||
|
Applies UPDATE chunks to the given text content.
|
||||||
|
Adapted from _get_updated_file in apply_patch.py.
|
||||||
|
"""
|
||||||
|
if action.type is not ActionType.UPDATE:
|
||||||
|
# Should not be called otherwise, but check for safety
|
||||||
|
raise DiffError("_apply_update called with non-update action")
|
||||||
|
|
||||||
|
orig_lines = text.splitlines() # Use splitlines to handle endings consistently
|
||||||
|
dest_lines: List[str] = []
|
||||||
|
current_orig_line_idx = 0 # Tracks index in orig_lines processed so far
|
||||||
|
|
||||||
|
# Sort chunks by their original index to apply them sequentially
|
||||||
|
sorted_chunks = sorted(action.chunks, key=lambda c: c.orig_index)
|
||||||
|
|
||||||
|
for chunk in sorted_chunks:
|
||||||
|
# chunk.orig_index is the absolute line number where the change starts
|
||||||
|
# (where the first deleted line was, or where inserted lines go if no deletes)
|
||||||
|
chunk_start_index = chunk.orig_index
|
||||||
|
|
||||||
|
if chunk_start_index < current_orig_line_idx:
|
||||||
|
# This indicates overlapping chunks or incorrect indices from parsing
|
||||||
|
raise DiffError(
|
||||||
|
f"{path}: Overlapping or out-of-order chunk detected."
|
||||||
|
f" Current index {current_orig_line_idx}, chunk starts at {chunk_start_index}."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add lines from original file between the last chunk and this one
|
||||||
|
dest_lines.extend(orig_lines[current_orig_line_idx:chunk_start_index])
|
||||||
|
|
||||||
|
# Verify that the lines to be deleted actually match the original file content
|
||||||
|
# (The parser should have used find_context, but double-check here)
|
||||||
|
num_del = len(chunk.del_lines)
|
||||||
|
actual_deleted_lines = orig_lines[chunk_start_index : chunk_start_index + num_del]
|
||||||
|
|
||||||
|
# Use the same normalization as find_context_core for comparison robustness
|
||||||
|
norm_chunk_del = [_norm(s).strip() for s in chunk.del_lines]
|
||||||
|
norm_actual_del = [_norm(s).strip() for s in actual_deleted_lines]
|
||||||
|
|
||||||
|
if norm_chunk_del != norm_actual_del:
|
||||||
|
# This indicates the context matching failed or the file changed since parsing
|
||||||
|
# Provide detailed error message
|
||||||
|
expected_str = "\n".join(f"- {s}" for s in chunk.del_lines)
|
||||||
|
actual_str = "\n".join(f" {s}" for s in actual_deleted_lines)
|
||||||
|
raise DiffError(
|
||||||
|
f"{path}: Mismatch applying patch near line {chunk_start_index + 1}.\n"
|
||||||
|
f"Expected lines to remove:\n{expected_str}\n"
|
||||||
|
f"Found lines in file:\n{actual_str}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the inserted lines from the chunk
|
||||||
|
dest_lines.extend(chunk.ins_lines)
|
||||||
|
|
||||||
|
# Advance the original line index past the lines processed (deleted lines)
|
||||||
|
current_orig_line_idx = chunk_start_index + num_del
|
||||||
|
|
||||||
|
# Add any remaining lines from the original file after the last chunk
|
||||||
|
dest_lines.extend(orig_lines[current_orig_line_idx:])
|
||||||
|
|
||||||
|
# Join lines and ensure a single trailing newline
|
||||||
|
result = "\n".join(dest_lines)
|
||||||
|
if result or orig_lines: # Add newline unless result is empty and original was empty
|
||||||
|
result += "\n"
|
||||||
|
return result
|
||||||
161
aider/coders/patch_prompts.py
Normal file
161
aider/coders/patch_prompts.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
from .base_prompts import CoderPrompts
|
||||||
|
from .editblock_prompts import EditBlockPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class PatchPrompts(EditBlockPrompts):
|
||||||
|
# --------------------------------------------------------------------- #
|
||||||
|
# SYSTEM PROMPT
|
||||||
|
# --------------------------------------------------------------------- #
|
||||||
|
main_system = """Act as an expert software developer.
|
||||||
|
Always use best practices when coding.
|
||||||
|
Respect and use existing conventions, libraries, etc that are already present in the code base.
|
||||||
|
{final_reminders}
|
||||||
|
Take requests for changes to the supplied code.
|
||||||
|
If the request is ambiguous, ask questions.
|
||||||
|
|
||||||
|
Always reply to the user in {language}.
|
||||||
|
|
||||||
|
Once you understand the request you MUST:
|
||||||
|
|
||||||
|
1. Decide if you need to propose edits to any files that haven't been added to the chat. You can create new files without asking!
|
||||||
|
|
||||||
|
• If you need to propose edits to existing files not already added to the chat, you *MUST* tell the user their full path names and ask them to *add the files to the chat*.
|
||||||
|
• End your reply and wait for their approval.
|
||||||
|
• You can keep asking if you then decide you need to edit more files.
|
||||||
|
|
||||||
|
2. Think step‑by‑step and explain the needed changes in a few short sentences.
|
||||||
|
|
||||||
|
3. Describe the changes using the V4A diff format, enclosed within `*** Begin Patch` and `*** End Patch` markers.
|
||||||
|
|
||||||
|
IMPORTANT: Each file MUST appear only once in the patch.
|
||||||
|
Consolidate **all** edits for a given file into a single `*** [ACTION] File:` block.
|
||||||
|
{shell_cmd_prompt}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------- #
|
||||||
|
# EXAMPLE MESSAGES
|
||||||
|
# --------------------------------------------------------------------- #
|
||||||
|
example_messages = [
|
||||||
|
# --- Example 1 ---------------------------------------------------- #
|
||||||
|
dict(
|
||||||
|
role="user",
|
||||||
|
content="Change get_factorial() to use math.factorial",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
role="assistant",
|
||||||
|
content="""To make this change we need to modify `mathweb/flask/app.py`:
|
||||||
|
|
||||||
|
1. Import the `math` module.
|
||||||
|
2. Replace the custom recursive `factorial()` helper with `math.factorial`.
|
||||||
|
3. Update `get_factorial()` to call `math.factorial`.
|
||||||
|
|
||||||
|
Here is the patch:
|
||||||
|
|
||||||
|
*** Begin Patch
|
||||||
|
*** Update File: mathweb/flask/app.py
|
||||||
|
@@
|
||||||
|
-from flask import Flask
|
||||||
|
-
|
||||||
|
-from flask import Flask
|
||||||
|
+from flask import Flask
|
||||||
|
+import math
|
||||||
|
@@
|
||||||
|
-def factorial(n):
|
||||||
|
- "compute factorial"
|
||||||
|
-
|
||||||
|
- if n == 0:
|
||||||
|
- return 1
|
||||||
|
- else:
|
||||||
|
- return n * factorial(n-1)
|
||||||
|
+def factorial(n):
|
||||||
|
+ "compute factorial"
|
||||||
|
+
|
||||||
|
+ return math.factorial(n)
|
||||||
|
@@ def get_factorial(self, n):
|
||||||
|
- return str(factorial(n))
|
||||||
|
+ return str(math.factorial(n))
|
||||||
|
*** End Patch
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
# --- Example 2 ---------------------------------------------------- #
|
||||||
|
dict(
|
||||||
|
role="user",
|
||||||
|
content="Refactor hello() into its own file.",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
role="assistant",
|
||||||
|
content="""To make this change we need to modify `main.py` and create a new file `hello.py`:
|
||||||
|
|
||||||
|
1. Create `hello.py` with the `hello()` function.
|
||||||
|
2. Remove `hello()` from `main.py` and import it instead.
|
||||||
|
|
||||||
|
Here is the patch:
|
||||||
|
|
||||||
|
*** Begin Patch
|
||||||
|
*** Add File: hello.py
|
||||||
|
+# A simple function
|
||||||
|
+def hello():
|
||||||
|
+ "print a greeting"
|
||||||
|
+
|
||||||
|
+ print("hello")
|
||||||
|
*** Update File: main.py
|
||||||
|
@@
|
||||||
|
-def hello():
|
||||||
|
- "print a greeting"
|
||||||
|
-
|
||||||
|
- print("hello")
|
||||||
|
+from hello import hello
|
||||||
|
*** End Patch
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------- #
|
||||||
|
# SYSTEM REMINDER
|
||||||
|
# --------------------------------------------------------------------- #
|
||||||
|
system_reminder = """# V4A Diff Format Rules:
|
||||||
|
|
||||||
|
Your entire response containing the patch MUST start with `*** Begin Patch` on a line by itself.
|
||||||
|
Your entire response containing the patch MUST end with `*** End Patch` on a line by itself.
|
||||||
|
|
||||||
|
Use the *FULL* file path, as shown to you by the user.
|
||||||
|
{quad_backtick_reminder}
|
||||||
|
|
||||||
|
For each file you need to modify, start with a marker line:
|
||||||
|
|
||||||
|
*** [ACTION] File: [path/to/file]
|
||||||
|
|
||||||
|
Where `[ACTION]` is one of `Add`, `Update`, or `Delete`.
|
||||||
|
|
||||||
|
⇨ **Each file MUST appear only once in the patch.**
|
||||||
|
Consolidate all changes for that file into the same block.
|
||||||
|
If you are moving code within a file, include both the deletions and the
|
||||||
|
insertions as separate hunks inside this single `*** Update File:` block
|
||||||
|
(do *not* open a second block for the same file).
|
||||||
|
|
||||||
|
For `Update` actions, describe each snippet of code that needs to be changed using the following format:
|
||||||
|
1. Context lines: Include 3 lines of context *before* the change. These lines MUST start with a single space ` `.
|
||||||
|
2. Lines to remove: Precede each line to be removed with a minus sign `-`.
|
||||||
|
3. Lines to add: Precede each line to be added with a plus sign `+`.
|
||||||
|
4. Context lines: Include 3 lines of context *after* the change. These lines MUST start with a single space ` `.
|
||||||
|
|
||||||
|
Context lines MUST exactly match the existing file content, character for character, including indentation.
|
||||||
|
If a change is near the beginning or end of the file, include fewer than 3 context lines as appropriate.
|
||||||
|
If 3 lines of context is insufficient to uniquely identify the snippet, use `@@ [CLASS_OR_FUNCTION_NAME]` markers on their own lines *before* the context lines to specify the scope. You can use multiple `@@` markers if needed.
|
||||||
|
Do not include line numbers.
|
||||||
|
|
||||||
|
Only create patches for files that the user has added to the chat!
|
||||||
|
|
||||||
|
When moving code *within* a single file, keep everything inside one
|
||||||
|
`*** Update File:` block. Provide one hunk that deletes the code from its
|
||||||
|
original location and another hunk that inserts it at the new location.
|
||||||
|
|
||||||
|
For `Add` actions, use the `*** Add File: [path/to/new/file]` marker, followed by the lines of the new file, each preceded by a plus sign `+`.
|
||||||
|
|
||||||
|
For `Delete` actions, use the `*** Delete File: [path/to/file]` marker. No other lines are needed for the deletion.
|
||||||
|
|
||||||
|
{rename_with_shell}{go_ahead_tip}{final_reminders}ONLY EVER RETURN CODE IN THE SPECIFIED V4A DIFF FORMAT!
|
||||||
|
{shell_cmd_reminder}
|
||||||
|
"""
|
||||||
@@ -109,7 +109,7 @@ class RelativeIndenter:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self.marker in text:
|
if self.marker in text:
|
||||||
raise ValueError("Text already contains the outdent marker: {self.marker}")
|
raise ValueError(f"Text already contains the outdent marker: {self.marker}")
|
||||||
|
|
||||||
lines = text.splitlines(keepends=True)
|
lines = text.splitlines(keepends=True)
|
||||||
|
|
||||||
@@ -235,20 +235,6 @@ Left
|
|||||||
Left
|
Left
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
|
||||||
ri = RelativeIndenter([example])
|
|
||||||
dump(example)
|
|
||||||
|
|
||||||
rel_example = ri.make_relative(example)
|
|
||||||
dump(repr(rel_example))
|
|
||||||
|
|
||||||
abs_example = ri.make_absolute(rel_example)
|
|
||||||
dump(abs_example)
|
|
||||||
|
|
||||||
|
|
||||||
sys.exit()
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def relative_indent(texts):
|
def relative_indent(texts):
|
||||||
ri = RelativeIndenter(texts)
|
ri = RelativeIndenter(texts)
|
||||||
@@ -349,7 +335,7 @@ def lines_to_chars(lines, mapping):
|
|||||||
return new_text
|
return new_text
|
||||||
|
|
||||||
|
|
||||||
def dmp_lines_apply(texts, remap=True):
|
def dmp_lines_apply(texts):
|
||||||
debug = False
|
debug = False
|
||||||
# debug = True
|
# debug = True
|
||||||
|
|
||||||
@@ -655,8 +641,6 @@ def proc(dname):
|
|||||||
(dmp_lines_apply, all_preprocs),
|
(dmp_lines_apply, all_preprocs),
|
||||||
]
|
]
|
||||||
|
|
||||||
_strategies = editblock_strategies # noqa: F841
|
|
||||||
|
|
||||||
short_names = dict(
|
short_names = dict(
|
||||||
search_and_replace="sr",
|
search_and_replace="sr",
|
||||||
git_cherry_pick_osr_onto_o="cp_o",
|
git_cherry_pick_osr_onto_o="cp_o",
|
||||||
|
|||||||
37
aider/coders/shell.py
Normal file
37
aider/coders/shell.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
shell_cmd_prompt = """
|
||||||
|
4. *Concisely* suggest any shell commands the user might want to run in ```bash blocks.
|
||||||
|
|
||||||
|
Just suggest shell commands this way, not example code.
|
||||||
|
Only suggest complete shell commands that are ready to execute, without placeholders.
|
||||||
|
Only suggest at most a few shell commands at a time, not more than 1-3, one per line.
|
||||||
|
Do not suggest multi-line shell commands.
|
||||||
|
All shell commands will run from the root directory of the user's project.
|
||||||
|
|
||||||
|
Use the appropriate shell based on the user's system info:
|
||||||
|
{platform}
|
||||||
|
Examples of when to suggest shell commands:
|
||||||
|
|
||||||
|
- If you changed a self-contained html file, suggest an OS-appropriate command to open a browser to view it to see the updated content.
|
||||||
|
- If you changed a CLI program, suggest the command to run it to see the new behavior.
|
||||||
|
- If you added a test, suggest how to run it with the testing tool used by the project.
|
||||||
|
- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations.
|
||||||
|
- If your code changes add new dependencies, suggest the command to install them.
|
||||||
|
- Etc.
|
||||||
|
""" # noqa
|
||||||
|
|
||||||
|
no_shell_cmd_prompt = """
|
||||||
|
Keep in mind these details about the user's platform and environment:
|
||||||
|
{platform}
|
||||||
|
""" # noqa
|
||||||
|
|
||||||
|
shell_cmd_reminder = """
|
||||||
|
Examples of when to suggest shell commands:
|
||||||
|
|
||||||
|
- If you changed a self-contained html file, suggest an OS-appropriate command to open a browser to view it to see the updated content.
|
||||||
|
- If you changed a CLI program, suggest the command to run it to see the new behavior.
|
||||||
|
- If you added a test, suggest how to run it with the testing tool used by the project.
|
||||||
|
- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations.
|
||||||
|
- If your code changes add new dependencies, suggest the command to install them.
|
||||||
|
- Etc.
|
||||||
|
|
||||||
|
""" # noqa
|
||||||
@@ -45,6 +45,7 @@ other_hunks_applied = (
|
|||||||
|
|
||||||
class UnifiedDiffCoder(Coder):
|
class UnifiedDiffCoder(Coder):
|
||||||
"""A coder that uses unified diff format for code modifications."""
|
"""A coder that uses unified diff format for code modifications."""
|
||||||
|
|
||||||
edit_format = "udiff"
|
edit_format = "udiff"
|
||||||
gpt_prompts = UnifiedDiffPrompts()
|
gpt_prompts = UnifiedDiffPrompts()
|
||||||
|
|
||||||
@@ -344,7 +345,16 @@ def process_fenced_block(lines, start_line_num):
|
|||||||
|
|
||||||
if block[0].startswith("--- ") and block[1].startswith("+++ "):
|
if block[0].startswith("--- ") and block[1].startswith("+++ "):
|
||||||
# Extract the file path, considering that it might contain spaces
|
# Extract the file path, considering that it might contain spaces
|
||||||
fname = block[1][4:].strip()
|
a_fname = block[0][4:].strip()
|
||||||
|
b_fname = block[1][4:].strip()
|
||||||
|
|
||||||
|
# Check if standard git diff prefixes are present (or /dev/null) and strip them
|
||||||
|
if (a_fname.startswith("a/") or a_fname == "/dev/null") and b_fname.startswith("b/"):
|
||||||
|
fname = b_fname[2:]
|
||||||
|
else:
|
||||||
|
# Otherwise, assume the path is as intended
|
||||||
|
fname = b_fname
|
||||||
|
|
||||||
block = block[2:]
|
block = block[2:]
|
||||||
else:
|
else:
|
||||||
fname = None
|
fname = None
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# flake8: noqa: E501
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
from . import shell
|
||||||
from .base_prompts import CoderPrompts
|
from .base_prompts import CoderPrompts
|
||||||
|
|
||||||
|
|
||||||
class UnifiedDiffPrompts(CoderPrompts):
|
class UnifiedDiffPrompts(CoderPrompts):
|
||||||
main_system = """Act as an expert software developer.
|
main_system = """Act as an expert software developer.
|
||||||
{lazy_prompt}
|
{final_reminders}
|
||||||
Always use best practices when coding.
|
Always use best practices when coding.
|
||||||
Respect and use existing conventions, libraries, etc that are already present in the code base.
|
Respect and use existing conventions, libraries, etc that are already present in the code base.
|
||||||
|
|
||||||
@@ -106,5 +107,9 @@ To move code within a file, use 2 hunks: 1 to delete it from its current locatio
|
|||||||
|
|
||||||
To make a new file, show a diff from `--- /dev/null` to `+++ path/to/new/file.ext`.
|
To make a new file, show a diff from `--- /dev/null` to `+++ path/to/new/file.ext`.
|
||||||
|
|
||||||
{lazy_prompt}
|
{final_reminders}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
shell_cmd_prompt = shell.shell_cmd_prompt
|
||||||
|
no_shell_cmd_prompt = shell.no_shell_cmd_prompt
|
||||||
|
shell_cmd_reminder = shell.shell_cmd_reminder
|
||||||
|
|||||||
14
aider/coders/udiff_simple.py
Normal file
14
aider/coders/udiff_simple.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from .udiff_coder import UnifiedDiffCoder
|
||||||
|
from .udiff_simple_prompts import UnifiedDiffSimplePrompts
|
||||||
|
|
||||||
|
|
||||||
|
class UnifiedDiffSimpleCoder(UnifiedDiffCoder):
|
||||||
|
"""
|
||||||
|
A coder that uses unified diff format for code modifications.
|
||||||
|
This variant uses a simpler prompt that doesn't mention specific
|
||||||
|
diff rules like using `@@ ... @@` lines or avoiding line numbers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
edit_format = "udiff-simple"
|
||||||
|
|
||||||
|
gpt_prompts = UnifiedDiffSimplePrompts()
|
||||||
25
aider/coders/udiff_simple_prompts.py
Normal file
25
aider/coders/udiff_simple_prompts.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from .udiff_prompts import UnifiedDiffPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class UnifiedDiffSimplePrompts(UnifiedDiffPrompts):
|
||||||
|
"""
|
||||||
|
Prompts for the UnifiedDiffSimpleCoder.
|
||||||
|
Inherits from UnifiedDiffPrompts and can override specific prompts
|
||||||
|
if a simpler wording is desired for this edit format.
|
||||||
|
"""
|
||||||
|
|
||||||
|
example_messages = []
|
||||||
|
|
||||||
|
system_reminder = """# File editing rules:
|
||||||
|
|
||||||
|
Return edits similar to unified diffs that `diff -U0` would produce.
|
||||||
|
|
||||||
|
The user's patch tool needs CORRECT patches that apply cleanly against the current contents of the file!
|
||||||
|
Think carefully and make sure you include and mark all lines that need to be removed or changed as `-` lines.
|
||||||
|
Make sure you mark all new or modified lines with `+`.
|
||||||
|
Don't leave out any lines or the diff patch won't apply correctly.
|
||||||
|
|
||||||
|
To make a new file, show a diff from `--- /dev/null` to `+++ path/to/new/file.ext`.
|
||||||
|
|
||||||
|
{final_reminders}
|
||||||
|
""" # noqa
|
||||||
@@ -10,7 +10,7 @@ If the request is ambiguous, ask questions.
|
|||||||
|
|
||||||
Always reply to the user in {language}.
|
Always reply to the user in {language}.
|
||||||
|
|
||||||
{lazy_prompt}
|
{final_reminders}
|
||||||
Once you understand the request you MUST:
|
Once you understand the request you MUST:
|
||||||
1. Determine if any code changes are needed.
|
1. Determine if any code changes are needed.
|
||||||
2. Explain any needed changes.
|
2. Explain any needed changes.
|
||||||
@@ -61,7 +61,7 @@ To suggest changes to a file you MUST return a *file listing* that contains the
|
|||||||
*NEVER* skip, omit or elide content from a *file listing* using "..." or by adding comments like "... rest of code..."!
|
*NEVER* skip, omit or elide content from a *file listing* using "..." or by adding comments like "... rest of code..."!
|
||||||
Create a new file you MUST return a *file listing* which includes an appropriate filename, including any appropriate path.
|
Create a new file you MUST return a *file listing* which includes an appropriate filename, including any appropriate path.
|
||||||
|
|
||||||
{lazy_prompt}
|
{final_reminders}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
redacted_edit_message = "No changes are needed."
|
redacted_edit_message = "No changes are needed."
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from aider import models, prompts, voice
|
|||||||
from aider.editor import pipe_editor
|
from aider.editor import pipe_editor
|
||||||
from aider.format_settings import format_settings
|
from aider.format_settings import format_settings
|
||||||
from aider.help import Help, install_help_extra
|
from aider.help import Help, install_help_extra
|
||||||
|
from aider.io import CommandCompletionException
|
||||||
from aider.llm import litellm
|
from aider.llm import litellm
|
||||||
from aider.repo import ANY_GIT_ERROR
|
from aider.repo import ANY_GIT_ERROR
|
||||||
from aider.run_cmd import run_cmd
|
from aider.run_cmd import run_cmd
|
||||||
@@ -27,8 +28,9 @@ from .dump import dump # noqa: F401
|
|||||||
|
|
||||||
|
|
||||||
class SwitchCoder(Exception):
|
class SwitchCoder(Exception):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, placeholder=None, **kwargs):
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
self.placeholder = placeholder
|
||||||
|
|
||||||
|
|
||||||
class Commands:
|
class Commands:
|
||||||
@@ -45,6 +47,7 @@ class Commands:
|
|||||||
parser=self.parser,
|
parser=self.parser,
|
||||||
verbose=self.verbose,
|
verbose=self.verbose,
|
||||||
editor=self.editor,
|
editor=self.editor,
|
||||||
|
original_read_only_fnames=self.original_read_only_fnames,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -59,6 +62,7 @@ class Commands:
|
|||||||
parser=None,
|
parser=None,
|
||||||
verbose=False,
|
verbose=False,
|
||||||
editor=None,
|
editor=None,
|
||||||
|
original_read_only_fnames=None,
|
||||||
):
|
):
|
||||||
self.io = io
|
self.io = io
|
||||||
self.coder = coder
|
self.coder = coder
|
||||||
@@ -77,11 +81,57 @@ class Commands:
|
|||||||
self.help = None
|
self.help = None
|
||||||
self.editor = editor
|
self.editor = editor
|
||||||
|
|
||||||
|
# Store the original read-only filenames provided via args.read
|
||||||
|
self.original_read_only_fnames = set(original_read_only_fnames or [])
|
||||||
|
|
||||||
def cmd_model(self, args):
|
def cmd_model(self, args):
|
||||||
"Switch to a new LLM"
|
"Switch the Main Model to a new LLM"
|
||||||
|
|
||||||
model_name = args.strip()
|
model_name = args.strip()
|
||||||
model = models.Model(model_name, weak_model=self.coder.main_model.weak_model.name)
|
if not model_name:
|
||||||
|
announcements = "\n".join(self.coder.get_announcements())
|
||||||
|
self.io.tool_output(announcements)
|
||||||
|
return
|
||||||
|
|
||||||
|
model = models.Model(
|
||||||
|
model_name,
|
||||||
|
editor_model=self.coder.main_model.editor_model.name,
|
||||||
|
weak_model=self.coder.main_model.weak_model.name,
|
||||||
|
)
|
||||||
|
models.sanity_check_models(self.io, model)
|
||||||
|
|
||||||
|
# Check if the current edit format is the default for the old model
|
||||||
|
old_model_edit_format = self.coder.main_model.edit_format
|
||||||
|
current_edit_format = self.coder.edit_format
|
||||||
|
|
||||||
|
new_edit_format = current_edit_format
|
||||||
|
if current_edit_format == old_model_edit_format:
|
||||||
|
# If the user was using the old model's default, switch to the new model's default
|
||||||
|
new_edit_format = model.edit_format
|
||||||
|
|
||||||
|
raise SwitchCoder(main_model=model, edit_format=new_edit_format)
|
||||||
|
|
||||||
|
def cmd_editor_model(self, args):
|
||||||
|
"Switch the Editor Model to a new LLM"
|
||||||
|
|
||||||
|
model_name = args.strip()
|
||||||
|
model = models.Model(
|
||||||
|
self.coder.main_model.name,
|
||||||
|
editor_model=model_name,
|
||||||
|
weak_model=self.coder.main_model.weak_model.name,
|
||||||
|
)
|
||||||
|
models.sanity_check_models(self.io, model)
|
||||||
|
raise SwitchCoder(main_model=model)
|
||||||
|
|
||||||
|
def cmd_weak_model(self, args):
|
||||||
|
"Switch the Weak Model to a new LLM"
|
||||||
|
|
||||||
|
model_name = args.strip()
|
||||||
|
model = models.Model(
|
||||||
|
self.coder.main_model.name,
|
||||||
|
editor_model=self.coder.main_model.editor_model.name,
|
||||||
|
weak_model=model_name,
|
||||||
|
)
|
||||||
models.sanity_check_models(self.io, model)
|
models.sanity_check_models(self.io, model)
|
||||||
raise SwitchCoder(main_model=model)
|
raise SwitchCoder(main_model=model)
|
||||||
|
|
||||||
@@ -114,6 +164,10 @@ class Commands:
|
|||||||
" them."
|
" them."
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"context",
|
||||||
|
"Automatically identify which files will need to be edited.",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -172,12 +226,18 @@ class Commands:
|
|||||||
|
|
||||||
self.io.tool_output(f"Scraping {url}...")
|
self.io.tool_output(f"Scraping {url}...")
|
||||||
if not self.scraper:
|
if not self.scraper:
|
||||||
|
disable_playwright = getattr(self.args, "disable_playwright", False)
|
||||||
|
if disable_playwright:
|
||||||
|
res = False
|
||||||
|
else:
|
||||||
res = install_playwright(self.io)
|
res = install_playwright(self.io)
|
||||||
if not res:
|
if not res:
|
||||||
self.io.tool_warning("Unable to initialize playwright.")
|
self.io.tool_warning("Unable to initialize playwright.")
|
||||||
|
|
||||||
self.scraper = Scraper(
|
self.scraper = Scraper(
|
||||||
print_error=self.io.tool_error, playwright_available=res, verify_ssl=self.verify_ssl
|
print_error=self.io.tool_error,
|
||||||
|
playwright_available=res,
|
||||||
|
verify_ssl=self.verify_ssl,
|
||||||
)
|
)
|
||||||
|
|
||||||
content = self.scraper.scrape(url) or ""
|
content = self.scraper.scrape(url) or ""
|
||||||
@@ -291,7 +351,7 @@ class Commands:
|
|||||||
return
|
return
|
||||||
|
|
||||||
commit_message = args.strip() if args else None
|
commit_message = args.strip() if args else None
|
||||||
self.coder.repo.commit(message=commit_message)
|
self.coder.repo.commit(message=commit_message, coder=self.coder)
|
||||||
|
|
||||||
def cmd_lint(self, args="", fnames=None):
|
def cmd_lint(self, args="", fnames=None):
|
||||||
"Lint and fix in-chat files or all dirty files if none in chat"
|
"Lint and fix in-chat files or all dirty files if none in chat"
|
||||||
@@ -352,9 +412,24 @@ class Commands:
|
|||||||
"Clear the chat history"
|
"Clear the chat history"
|
||||||
|
|
||||||
self._clear_chat_history()
|
self._clear_chat_history()
|
||||||
|
self.io.tool_output("All chat history cleared.")
|
||||||
|
|
||||||
def _drop_all_files(self):
|
def _drop_all_files(self):
|
||||||
self.coder.abs_fnames = set()
|
self.coder.abs_fnames = set()
|
||||||
|
|
||||||
|
# When dropping all files, keep those that were originally provided via args.read
|
||||||
|
if self.original_read_only_fnames:
|
||||||
|
# Keep only the original read-only files
|
||||||
|
to_keep = set()
|
||||||
|
for abs_fname in self.coder.abs_read_only_fnames:
|
||||||
|
rel_fname = self.coder.get_rel_fname(abs_fname)
|
||||||
|
if (
|
||||||
|
abs_fname in self.original_read_only_fnames
|
||||||
|
or rel_fname in self.original_read_only_fnames
|
||||||
|
):
|
||||||
|
to_keep.add(abs_fname)
|
||||||
|
self.coder.abs_read_only_fnames = to_keep
|
||||||
|
else:
|
||||||
self.coder.abs_read_only_fnames = set()
|
self.coder.abs_read_only_fnames = set()
|
||||||
|
|
||||||
def _clear_chat_history(self):
|
def _clear_chat_history(self):
|
||||||
@@ -404,6 +479,7 @@ class Commands:
|
|||||||
|
|
||||||
fence = "`" * 3
|
fence = "`" * 3
|
||||||
|
|
||||||
|
file_res = []
|
||||||
# files
|
# files
|
||||||
for fname in self.coder.abs_fnames:
|
for fname in self.coder.abs_fnames:
|
||||||
relative_fname = self.coder.get_rel_fname(fname)
|
relative_fname = self.coder.get_rel_fname(fname)
|
||||||
@@ -414,7 +490,7 @@ class Commands:
|
|||||||
# approximate
|
# approximate
|
||||||
content = f"{relative_fname}\n{fence}\n" + content + "{fence}\n"
|
content = f"{relative_fname}\n{fence}\n" + content + "{fence}\n"
|
||||||
tokens = self.coder.main_model.token_count(content)
|
tokens = self.coder.main_model.token_count(content)
|
||||||
res.append((tokens, f"{relative_fname}", "/drop to remove"))
|
file_res.append((tokens, f"{relative_fname}", "/drop to remove"))
|
||||||
|
|
||||||
# read-only files
|
# read-only files
|
||||||
for fname in self.coder.abs_read_only_fnames:
|
for fname in self.coder.abs_read_only_fnames:
|
||||||
@@ -424,7 +500,10 @@ class Commands:
|
|||||||
# approximate
|
# approximate
|
||||||
content = f"{relative_fname}\n{fence}\n" + content + "{fence}\n"
|
content = f"{relative_fname}\n{fence}\n" + content + "{fence}\n"
|
||||||
tokens = self.coder.main_model.token_count(content)
|
tokens = self.coder.main_model.token_count(content)
|
||||||
res.append((tokens, f"{relative_fname} (read-only)", "/drop to remove"))
|
file_res.append((tokens, f"{relative_fname} (read-only)", "/drop to remove"))
|
||||||
|
|
||||||
|
file_res.sort()
|
||||||
|
res.extend(file_res)
|
||||||
|
|
||||||
self.io.tool_output(
|
self.io.tool_output(
|
||||||
f"Approximate context window usage for {self.coder.main_model.name}, in tokens:"
|
f"Approximate context window usage for {self.coder.main_model.name}, in tokens:"
|
||||||
@@ -490,6 +569,7 @@ class Commands:
|
|||||||
|
|
||||||
last_commit_hash = self.coder.repo.get_head_commit_sha(short=True)
|
last_commit_hash = self.coder.repo.get_head_commit_sha(short=True)
|
||||||
last_commit_message = self.coder.repo.get_head_commit_message("(unknown)").strip()
|
last_commit_message = self.coder.repo.get_head_commit_message("(unknown)").strip()
|
||||||
|
last_commit_message = (last_commit_message.splitlines() or [""])[0]
|
||||||
if last_commit_hash not in self.coder.aider_commit_hashes:
|
if last_commit_hash not in self.coder.aider_commit_hashes:
|
||||||
self.io.tool_error("The last commit was not made by aider in this chat session.")
|
self.io.tool_error("The last commit was not made by aider in this chat session.")
|
||||||
self.io.tool_output(
|
self.io.tool_output(
|
||||||
@@ -568,6 +648,7 @@ class Commands:
|
|||||||
# Get the current HEAD after undo
|
# Get the current HEAD after undo
|
||||||
current_head_hash = self.coder.repo.get_head_commit_sha(short=True)
|
current_head_hash = self.coder.repo.get_head_commit_sha(short=True)
|
||||||
current_head_message = self.coder.repo.get_head_commit_message("(unknown)").strip()
|
current_head_message = self.coder.repo.get_head_commit_message("(unknown)").strip()
|
||||||
|
current_head_message = (current_head_message.splitlines() or [""])[0]
|
||||||
self.io.tool_output(f"Now at: {current_head_hash} {current_head_message}")
|
self.io.tool_output(f"Now at: {current_head_hash} {current_head_message}")
|
||||||
|
|
||||||
if self.coder.main_model.send_undo_reply:
|
if self.coder.main_model.send_undo_reply:
|
||||||
@@ -771,7 +852,11 @@ class Commands:
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.coder.repo and self.coder.repo.git_ignored_file(matched_file):
|
if (
|
||||||
|
self.coder.repo
|
||||||
|
and self.coder.repo.git_ignored_file(matched_file)
|
||||||
|
and not self.coder.add_gitignore_files
|
||||||
|
):
|
||||||
self.io.tool_error(f"Can't add {matched_file} which is in gitignore")
|
self.io.tool_error(f"Can't add {matched_file} which is in gitignore")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -818,6 +903,11 @@ class Commands:
|
|||||||
"Remove files from the chat session to free up context space"
|
"Remove files from the chat session to free up context space"
|
||||||
|
|
||||||
if not args.strip():
|
if not args.strip():
|
||||||
|
if self.original_read_only_fnames:
|
||||||
|
self.io.tool_output(
|
||||||
|
"Dropping all files from the chat session except originally read-only files."
|
||||||
|
)
|
||||||
|
else:
|
||||||
self.io.tool_output("Dropping all files from the chat session.")
|
self.io.tool_output("Dropping all files from the chat session.")
|
||||||
self._drop_all_files()
|
self._drop_all_files()
|
||||||
return
|
return
|
||||||
@@ -943,9 +1033,15 @@ class Commands:
|
|||||||
dict(role="assistant", content="Ok."),
|
dict(role="assistant", content="Ok."),
|
||||||
]
|
]
|
||||||
|
|
||||||
if add and exit_status != 0:
|
if add_on_nonzero_exit and exit_status != 0:
|
||||||
|
# Return the formatted output message for test failures
|
||||||
|
return msg
|
||||||
|
elif add and exit_status != 0:
|
||||||
self.io.placeholder = "What's wrong? Fix"
|
self.io.placeholder = "What's wrong? Fix"
|
||||||
|
|
||||||
|
# Return None if output wasn't added or command succeeded
|
||||||
|
return None
|
||||||
|
|
||||||
def cmd_exit(self, args):
|
def cmd_exit(self, args):
|
||||||
"Exit the application"
|
"Exit the application"
|
||||||
self.coder.event("exit", reason="/exit")
|
self.coder.event("exit", reason="/exit")
|
||||||
@@ -1061,6 +1157,18 @@ class Commands:
|
|||||||
show_announcements=False,
|
show_announcements=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def completions_ask(self):
|
||||||
|
raise CommandCompletionException()
|
||||||
|
|
||||||
|
def completions_code(self):
|
||||||
|
raise CommandCompletionException()
|
||||||
|
|
||||||
|
def completions_architect(self):
|
||||||
|
raise CommandCompletionException()
|
||||||
|
|
||||||
|
def completions_context(self):
|
||||||
|
raise CommandCompletionException()
|
||||||
|
|
||||||
def cmd_ask(self, args):
|
def cmd_ask(self, args):
|
||||||
"""Ask questions about the code base without editing any files. If no prompt provided, switches to ask mode.""" # noqa
|
"""Ask questions about the code base without editing any files. If no prompt provided, switches to ask mode.""" # noqa
|
||||||
return self._generic_chat_command(args, "ask")
|
return self._generic_chat_command(args, "ask")
|
||||||
@@ -1073,7 +1181,11 @@ class Commands:
|
|||||||
"""Enter architect/editor mode using 2 different models. If no prompt provided, switches to architect/editor mode.""" # noqa
|
"""Enter architect/editor mode using 2 different models. If no prompt provided, switches to architect/editor mode.""" # noqa
|
||||||
return self._generic_chat_command(args, "architect")
|
return self._generic_chat_command(args, "architect")
|
||||||
|
|
||||||
def _generic_chat_command(self, args, edit_format):
|
def cmd_context(self, args):
|
||||||
|
"""Enter context mode to see surrounding code context. If no prompt provided, switches to context mode.""" # noqa
|
||||||
|
return self._generic_chat_command(args, "context", placeholder=args.strip() or None)
|
||||||
|
|
||||||
|
def _generic_chat_command(self, args, edit_format, placeholder=None):
|
||||||
if not args.strip():
|
if not args.strip():
|
||||||
# Switch to the corresponding chat mode if no args provided
|
# Switch to the corresponding chat mode if no args provided
|
||||||
return self.cmd_chat_mode(edit_format)
|
return self.cmd_chat_mode(edit_format)
|
||||||
@@ -1090,11 +1202,13 @@ class Commands:
|
|||||||
user_msg = args
|
user_msg = args
|
||||||
coder.run(user_msg)
|
coder.run(user_msg)
|
||||||
|
|
||||||
|
# Use the provided placeholder if any
|
||||||
raise SwitchCoder(
|
raise SwitchCoder(
|
||||||
edit_format=self.coder.edit_format,
|
edit_format=self.coder.edit_format,
|
||||||
summarize_from_coder=False,
|
summarize_from_coder=False,
|
||||||
from_coder=coder,
|
from_coder=coder,
|
||||||
show_announcements=False,
|
show_announcements=False,
|
||||||
|
placeholder=placeholder,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_help_md(self):
|
def get_help_md(self):
|
||||||
@@ -1210,9 +1324,20 @@ class Commands:
|
|||||||
# First collect all expanded paths
|
# First collect all expanded paths
|
||||||
for pattern in filenames:
|
for pattern in filenames:
|
||||||
expanded_pattern = expanduser(pattern)
|
expanded_pattern = expanduser(pattern)
|
||||||
if os.path.isabs(expanded_pattern):
|
path_obj = Path(expanded_pattern)
|
||||||
|
is_abs = path_obj.is_absolute()
|
||||||
|
if not is_abs:
|
||||||
|
path_obj = Path(self.coder.root) / path_obj
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
# Check for literal path existence first
|
||||||
|
if path_obj.exists():
|
||||||
|
matches = [path_obj]
|
||||||
|
else:
|
||||||
|
# If literal path doesn't exist, try globbing
|
||||||
|
if is_abs:
|
||||||
# For absolute paths, glob it
|
# For absolute paths, glob it
|
||||||
matches = list(glob.glob(expanded_pattern))
|
matches = [Path(p) for p in glob.glob(expanded_pattern)]
|
||||||
else:
|
else:
|
||||||
# For relative paths and globs, use glob from the root directory
|
# For relative paths and globs, use glob from the root directory
|
||||||
matches = list(Path(self.coder.root).glob(expanded_pattern))
|
matches = list(Path(self.coder.root).glob(expanded_pattern))
|
||||||
@@ -1290,7 +1415,30 @@ class Commands:
|
|||||||
"Print out the current settings"
|
"Print out the current settings"
|
||||||
settings = format_settings(self.parser, self.args)
|
settings = format_settings(self.parser, self.args)
|
||||||
announcements = "\n".join(self.coder.get_announcements())
|
announcements = "\n".join(self.coder.get_announcements())
|
||||||
|
|
||||||
|
# Build metadata for the active models (main, editor, weak)
|
||||||
|
model_sections = []
|
||||||
|
active_models = [
|
||||||
|
("Main model", self.coder.main_model),
|
||||||
|
("Editor model", getattr(self.coder.main_model, "editor_model", None)),
|
||||||
|
("Weak model", getattr(self.coder.main_model, "weak_model", None)),
|
||||||
|
]
|
||||||
|
for label, model in active_models:
|
||||||
|
if not model:
|
||||||
|
continue
|
||||||
|
info = getattr(model, "info", {}) or {}
|
||||||
|
if not info:
|
||||||
|
continue
|
||||||
|
model_sections.append(f"{label} ({model.name}):")
|
||||||
|
for k, v in sorted(info.items()):
|
||||||
|
model_sections.append(f" {k}: {v}")
|
||||||
|
model_sections.append("") # blank line between models
|
||||||
|
|
||||||
|
model_metadata = "\n".join(model_sections)
|
||||||
|
|
||||||
output = f"{announcements}\n{settings}"
|
output = f"{announcements}\n{settings}"
|
||||||
|
if model_metadata:
|
||||||
|
output += "\n" + model_metadata
|
||||||
self.io.tool_output(output)
|
self.io.tool_output(output)
|
||||||
|
|
||||||
def completions_raw_load(self, document, complete_event):
|
def completions_raw_load(self, document, complete_event):
|
||||||
@@ -1407,6 +1555,68 @@ class Commands:
|
|||||||
if user_input.strip():
|
if user_input.strip():
|
||||||
self.io.set_placeholder(user_input.rstrip())
|
self.io.set_placeholder(user_input.rstrip())
|
||||||
|
|
||||||
|
def cmd_edit(self, args=""):
|
||||||
|
"Alias for /editor: Open an editor to write a prompt"
|
||||||
|
return self.cmd_editor(args)
|
||||||
|
|
||||||
|
def cmd_think_tokens(self, args):
|
||||||
|
"""Set the thinking token budget, eg: 8096, 8k, 10.5k, 0.5M, or 0 to disable."""
|
||||||
|
model = self.coder.main_model
|
||||||
|
|
||||||
|
if not args.strip():
|
||||||
|
# Display current value if no args are provided
|
||||||
|
formatted_budget = model.get_thinking_tokens()
|
||||||
|
if formatted_budget is None:
|
||||||
|
self.io.tool_output("Thinking tokens are not currently set.")
|
||||||
|
else:
|
||||||
|
budget = model.get_raw_thinking_tokens()
|
||||||
|
self.io.tool_output(
|
||||||
|
f"Current thinking token budget: {budget:,} tokens ({formatted_budget})."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
value = args.strip()
|
||||||
|
model.set_thinking_tokens(value)
|
||||||
|
|
||||||
|
# Handle the special case of 0 to disable thinking tokens
|
||||||
|
if value == "0":
|
||||||
|
self.io.tool_output("Thinking tokens disabled.")
|
||||||
|
else:
|
||||||
|
formatted_budget = model.get_thinking_tokens()
|
||||||
|
budget = model.get_raw_thinking_tokens()
|
||||||
|
self.io.tool_output(
|
||||||
|
f"Set thinking token budget to {budget:,} tokens ({formatted_budget})."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.io.tool_output()
|
||||||
|
|
||||||
|
# Output announcements
|
||||||
|
announcements = "\n".join(self.coder.get_announcements())
|
||||||
|
self.io.tool_output(announcements)
|
||||||
|
|
||||||
|
def cmd_reasoning_effort(self, args):
|
||||||
|
"Set the reasoning effort level (values: number or low/medium/high depending on model)"
|
||||||
|
model = self.coder.main_model
|
||||||
|
|
||||||
|
if not args.strip():
|
||||||
|
# Display current value if no args are provided
|
||||||
|
reasoning_value = model.get_reasoning_effort()
|
||||||
|
if reasoning_value is None:
|
||||||
|
self.io.tool_output("Reasoning effort is not currently set.")
|
||||||
|
else:
|
||||||
|
self.io.tool_output(f"Current reasoning effort: {reasoning_value}")
|
||||||
|
return
|
||||||
|
|
||||||
|
value = args.strip()
|
||||||
|
model.set_reasoning_effort(value)
|
||||||
|
reasoning_value = model.get_reasoning_effort()
|
||||||
|
self.io.tool_output(f"Set reasoning effort to {reasoning_value}")
|
||||||
|
self.io.tool_output()
|
||||||
|
|
||||||
|
# Output announcements
|
||||||
|
announcements = "\n".join(self.coder.get_announcements())
|
||||||
|
self.io.tool_output(announcements)
|
||||||
|
|
||||||
def cmd_copy_context(self, args=None):
|
def cmd_copy_context(self, args=None):
|
||||||
"""Copy the current chat context as markdown, suitable to paste into a web UI"""
|
"""Copy the current chat context as markdown, suitable to paste into a web UI"""
|
||||||
|
|
||||||
|
|||||||
126
aider/deprecated.py
Normal file
126
aider/deprecated.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
def add_deprecated_model_args(parser, group):
|
||||||
|
"""Add deprecated model shortcut arguments to the argparse parser."""
|
||||||
|
opus_model = "claude-3-opus-20240229"
|
||||||
|
group.add_argument(
|
||||||
|
"--opus",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {opus_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
sonnet_model = "anthropic/claude-3-7-sonnet-20250219"
|
||||||
|
group.add_argument(
|
||||||
|
"--sonnet",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {sonnet_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
haiku_model = "claude-3-5-haiku-20241022"
|
||||||
|
group.add_argument(
|
||||||
|
"--haiku",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {haiku_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
gpt_4_model = "gpt-4-0613"
|
||||||
|
group.add_argument(
|
||||||
|
"--4",
|
||||||
|
"-4",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {gpt_4_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
gpt_4o_model = "gpt-4o"
|
||||||
|
group.add_argument(
|
||||||
|
"--4o",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {gpt_4o_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
gpt_4o_mini_model = "gpt-4o-mini"
|
||||||
|
group.add_argument(
|
||||||
|
"--mini",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {gpt_4o_mini_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
gpt_4_turbo_model = "gpt-4-1106-preview"
|
||||||
|
group.add_argument(
|
||||||
|
"--4-turbo",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {gpt_4_turbo_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
gpt_3_model_name = "gpt-3.5-turbo"
|
||||||
|
group.add_argument(
|
||||||
|
"--35turbo",
|
||||||
|
"--35-turbo",
|
||||||
|
"--3",
|
||||||
|
"-3",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {gpt_3_model_name} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
deepseek_model = "deepseek/deepseek-chat"
|
||||||
|
group.add_argument(
|
||||||
|
"--deepseek",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {deepseek_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
o1_mini_model = "o1-mini"
|
||||||
|
group.add_argument(
|
||||||
|
"--o1-mini",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {o1_mini_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
o1_preview_model = "o1-preview"
|
||||||
|
group.add_argument(
|
||||||
|
"--o1-preview",
|
||||||
|
action="store_true",
|
||||||
|
help=f"Use {o1_preview_model} model for the main chat (deprecated, use --model)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_deprecated_model_args(args, io):
|
||||||
|
"""Handle deprecated model shortcut arguments and provide appropriate warnings."""
|
||||||
|
# Define model mapping
|
||||||
|
model_map = {
|
||||||
|
"opus": "claude-3-opus-20240229",
|
||||||
|
"sonnet": "anthropic/claude-3-7-sonnet-20250219",
|
||||||
|
"haiku": "claude-3-5-haiku-20241022",
|
||||||
|
"4": "gpt-4-0613",
|
||||||
|
"4o": "gpt-4o",
|
||||||
|
"mini": "gpt-4o-mini",
|
||||||
|
"4_turbo": "gpt-4-1106-preview",
|
||||||
|
"35turbo": "gpt-3.5-turbo",
|
||||||
|
"deepseek": "deepseek/deepseek-chat",
|
||||||
|
"o1_mini": "o1-mini",
|
||||||
|
"o1_preview": "o1-preview",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if any deprecated args are used
|
||||||
|
for arg_name, model_name in model_map.items():
|
||||||
|
arg_name_clean = arg_name.replace("-", "_")
|
||||||
|
if hasattr(args, arg_name_clean) and getattr(args, arg_name_clean):
|
||||||
|
# Find preferred name to display in warning
|
||||||
|
from aider.models import MODEL_ALIASES
|
||||||
|
|
||||||
|
display_name = model_name
|
||||||
|
# Check if there's a shorter alias for this model
|
||||||
|
for alias, full_name in MODEL_ALIASES.items():
|
||||||
|
if full_name == model_name:
|
||||||
|
display_name = alias
|
||||||
|
break
|
||||||
|
|
||||||
|
# Show the warning
|
||||||
|
io.tool_warning(
|
||||||
|
f"The --{arg_name.replace('_', '-')} flag is deprecated and will be removed in a"
|
||||||
|
f" future version. Please use --model {display_name} instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set the model
|
||||||
|
if not args.model:
|
||||||
|
args.model = model_name
|
||||||
|
break
|
||||||
@@ -10,12 +10,13 @@ This module provides functionality to:
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
|
from aider.dump import dump # noqa
|
||||||
|
|
||||||
DEFAULT_EDITOR_NIX = "vi"
|
DEFAULT_EDITOR_NIX = "vi"
|
||||||
DEFAULT_EDITOR_OS_X = "vim"
|
DEFAULT_EDITOR_OS_X = "vim"
|
||||||
DEFAULT_EDITOR_WINDOWS = "notepad"
|
DEFAULT_EDITOR_WINDOWS = "notepad"
|
||||||
@@ -87,13 +88,13 @@ def get_environment_editor(default=None):
|
|||||||
|
|
||||||
def discover_editor(editor_override=None):
|
def discover_editor(editor_override=None):
|
||||||
"""
|
"""
|
||||||
Discovers and returns the appropriate editor command as a list of arguments.
|
Discovers and returns the appropriate editor command.
|
||||||
|
|
||||||
Handles cases where the editor command includes arguments, including quoted arguments
|
Handles cases where the editor command includes arguments, including quoted arguments
|
||||||
with spaces (e.g. 'vim -c "set noswapfile"').
|
with spaces (e.g. 'vim -c "set noswapfile"').
|
||||||
|
|
||||||
:return: A list of command parts ready for subprocess execution
|
:return: The editor command as a string
|
||||||
:rtype: list[str]
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
if system == "Windows":
|
if system == "Windows":
|
||||||
@@ -102,14 +103,13 @@ def discover_editor(editor_override=None):
|
|||||||
default_editor = DEFAULT_EDITOR_OS_X
|
default_editor = DEFAULT_EDITOR_OS_X
|
||||||
else:
|
else:
|
||||||
default_editor = DEFAULT_EDITOR_NIX
|
default_editor = DEFAULT_EDITOR_NIX
|
||||||
|
|
||||||
if editor_override:
|
if editor_override:
|
||||||
editor = editor_override
|
editor = editor_override
|
||||||
else:
|
else:
|
||||||
editor = get_environment_editor(default_editor)
|
editor = get_environment_editor(default_editor)
|
||||||
try:
|
|
||||||
return shlex.split(editor)
|
return editor
|
||||||
except ValueError as e:
|
|
||||||
raise RuntimeError(f"Invalid editor command format '{editor}': {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def pipe_editor(input_data="", suffix=None, editor=None):
|
def pipe_editor(input_data="", suffix=None, editor=None):
|
||||||
@@ -128,9 +128,10 @@ def pipe_editor(input_data="", suffix=None, editor=None):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
filepath = write_temp_file(input_data, suffix)
|
filepath = write_temp_file(input_data, suffix)
|
||||||
command_parts = discover_editor(editor)
|
command_str = discover_editor(editor)
|
||||||
command_parts.append(filepath)
|
command_str += " " + filepath
|
||||||
subprocess.call(command_parts)
|
|
||||||
|
subprocess.call(command_str, shell=True)
|
||||||
with open(filepath, "r") as f:
|
with open(filepath, "r") as f:
|
||||||
output_data = f.read()
|
output_data = f.read()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -83,4 +83,25 @@ class LiteLLMExceptions:
|
|||||||
)
|
)
|
||||||
if "boto3" in str(ex):
|
if "boto3" in str(ex):
|
||||||
return ExInfo("APIConnectionError", False, "You need to: pip install boto3")
|
return ExInfo("APIConnectionError", False, "You need to: pip install boto3")
|
||||||
|
if "OpenrouterException" in str(ex) and "'choices'" in str(ex):
|
||||||
|
return ExInfo(
|
||||||
|
"APIConnectionError",
|
||||||
|
True,
|
||||||
|
(
|
||||||
|
"OpenRouter or the upstream API provider is down, overloaded or rate"
|
||||||
|
" limiting your requests."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for specific non-retryable APIError cases like insufficient credits
|
||||||
|
if ex.__class__ is litellm.APIError:
|
||||||
|
err_str = str(ex).lower()
|
||||||
|
if "insufficient credits" in err_str and '"code":402' in err_str:
|
||||||
|
return ExInfo(
|
||||||
|
"APIError",
|
||||||
|
False,
|
||||||
|
"Insufficient credits with the API provider. Please add credits.",
|
||||||
|
)
|
||||||
|
# Fall through to default APIError handling if not the specific credits error
|
||||||
|
|
||||||
return self.exceptions.get(ex.__class__, ExInfo(None, None, None))
|
return self.exceptions.get(ex.__class__, ExInfo(None, None, None))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from aider.coders import Coder
|
|||||||
from aider.dump import dump # noqa: F401
|
from aider.dump import dump # noqa: F401
|
||||||
from aider.io import InputOutput
|
from aider.io import InputOutput
|
||||||
from aider.main import main as cli_main
|
from aider.main import main as cli_main
|
||||||
from aider.scrape import Scraper
|
from aider.scrape import Scraper, has_playwright
|
||||||
|
|
||||||
|
|
||||||
class CaptureIO(InputOutput):
|
class CaptureIO(InputOutput):
|
||||||
@@ -484,7 +484,7 @@ class GUI:
|
|||||||
url = self.web_content
|
url = self.web_content
|
||||||
|
|
||||||
if not self.state.scraper:
|
if not self.state.scraper:
|
||||||
self.scraper = Scraper(print_error=self.info)
|
self.scraper = Scraper(print_error=self.info, playwright_available=has_playwright())
|
||||||
|
|
||||||
content = self.scraper.scrape(url) or ""
|
content = self.scraper.scrape(url) or ""
|
||||||
if content.strip():
|
if content.strip():
|
||||||
|
|||||||
@@ -10,4 +10,10 @@ exclude_website_pats = [
|
|||||||
"docs/unified-diffs.md",
|
"docs/unified-diffs.md",
|
||||||
"docs/leaderboards/index.md",
|
"docs/leaderboards/index.md",
|
||||||
"assets/**",
|
"assets/**",
|
||||||
|
".jekyll-metadata",
|
||||||
|
"Gemfile.lock",
|
||||||
|
"Gemfile",
|
||||||
|
"_config.yml",
|
||||||
|
"**/OLD/**",
|
||||||
|
"OLD/**",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -63,37 +63,37 @@ class ChatSummary:
|
|||||||
if split_index <= min_split:
|
if split_index <= min_split:
|
||||||
return self.summarize_all(messages)
|
return self.summarize_all(messages)
|
||||||
|
|
||||||
head = messages[:split_index]
|
# Split head and tail
|
||||||
tail = messages[split_index:]
|
tail = messages[split_index:]
|
||||||
|
|
||||||
sized = sized[:split_index]
|
# Only size the head once
|
||||||
head.reverse()
|
sized_head = sized[:split_index]
|
||||||
sized.reverse()
|
|
||||||
|
# Precompute token limit (fallback to 4096 if undefined)
|
||||||
|
model_max_input_tokens = self.models[0].info.get("max_input_tokens") or 4096
|
||||||
|
model_max_input_tokens -= 512 # reserve buffer for safety
|
||||||
|
|
||||||
keep = []
|
keep = []
|
||||||
total = 0
|
total = 0
|
||||||
|
|
||||||
# These sometimes come set with value = None
|
# Iterate in original order, summing tokens until limit
|
||||||
model_max_input_tokens = self.models[0].info.get("max_input_tokens") or 4096
|
for tokens, msg in sized_head:
|
||||||
model_max_input_tokens -= 512
|
total += tokens
|
||||||
|
|
||||||
for i in range(split_index):
|
|
||||||
total += sized[i][0]
|
|
||||||
if total > model_max_input_tokens:
|
if total > model_max_input_tokens:
|
||||||
break
|
break
|
||||||
keep.append(head[i])
|
keep.append(msg)
|
||||||
|
# No need to reverse lists back and forth
|
||||||
keep.reverse()
|
|
||||||
|
|
||||||
summary = self.summarize_all(keep)
|
summary = self.summarize_all(keep)
|
||||||
|
|
||||||
tail_tokens = sum(tokens for tokens, msg in sized[split_index:])
|
# If the combined summary and tail still fits, return directly
|
||||||
summary_tokens = self.token_count(summary)
|
summary_tokens = self.token_count(summary)
|
||||||
|
tail_tokens = sum(tokens for tokens, _ in sized[split_index:])
|
||||||
result = summary + tail
|
|
||||||
if summary_tokens + tail_tokens < self.max_tokens:
|
if summary_tokens + tail_tokens < self.max_tokens:
|
||||||
return result
|
return summary + tail
|
||||||
|
|
||||||
return self.summarize_real(result, depth + 1)
|
# Otherwise recurse with increased depth
|
||||||
|
return self.summarize_real(summary + tail, depth + 1)
|
||||||
|
|
||||||
def summarize_all(self, messages):
|
def summarize_all(self, messages):
|
||||||
content = ""
|
content = ""
|
||||||
|
|||||||
252
aider/io.py
252
aider/io.py
@@ -1,7 +1,9 @@
|
|||||||
import base64
|
import base64
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@@ -16,6 +18,7 @@ from prompt_toolkit.enums import EditingMode
|
|||||||
from prompt_toolkit.filters import Condition, is_searching
|
from prompt_toolkit.filters import Condition, is_searching
|
||||||
from prompt_toolkit.history import FileHistory
|
from prompt_toolkit.history import FileHistory
|
||||||
from prompt_toolkit.key_binding import KeyBindings
|
from prompt_toolkit.key_binding import KeyBindings
|
||||||
|
from prompt_toolkit.key_binding.vi_state import InputMode
|
||||||
from prompt_toolkit.keys import Keys
|
from prompt_toolkit.keys import Keys
|
||||||
from prompt_toolkit.lexers import PygmentsLexer
|
from prompt_toolkit.lexers import PygmentsLexer
|
||||||
from prompt_toolkit.output.vt100 import is_dumb_terminal
|
from prompt_toolkit.output.vt100 import is_dumb_terminal
|
||||||
@@ -23,6 +26,7 @@ from prompt_toolkit.shortcuts import CompleteStyle, PromptSession
|
|||||||
from prompt_toolkit.styles import Style
|
from prompt_toolkit.styles import Style
|
||||||
from pygments.lexers import MarkdownLexer, guess_lexer_for_filename
|
from pygments.lexers import MarkdownLexer, guess_lexer_for_filename
|
||||||
from pygments.token import Token
|
from pygments.token import Token
|
||||||
|
from rich.color import ColorParseError
|
||||||
from rich.columns import Columns
|
from rich.columns import Columns
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
@@ -32,8 +36,23 @@ from rich.text import Text
|
|||||||
from aider.mdstream import MarkdownStream
|
from aider.mdstream import MarkdownStream
|
||||||
|
|
||||||
from .dump import dump # noqa: F401
|
from .dump import dump # noqa: F401
|
||||||
|
from .editor import pipe_editor
|
||||||
from .utils import is_image_file
|
from .utils import is_image_file
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
NOTIFICATION_MESSAGE = "Aider is waiting for your input"
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_hash_prefix(color):
|
||||||
|
"""Ensure hex color values have a # prefix."""
|
||||||
|
if not color:
|
||||||
|
return color
|
||||||
|
if isinstance(color, str) and color.strip() and not color.startswith("#"):
|
||||||
|
# Check if it's a valid hex color (3 or 6 hex digits)
|
||||||
|
if all(c in "0123456789ABCDEFabcdef" for c in color) and len(color) in (3, 6):
|
||||||
|
return f"#{color}"
|
||||||
|
return color
|
||||||
|
|
||||||
|
|
||||||
def restore_multiline(func):
|
def restore_multiline(func):
|
||||||
"""Decorator to restore multiline mode after function execution"""
|
"""Decorator to restore multiline mode after function execution"""
|
||||||
@@ -52,6 +71,13 @@ def restore_multiline(func):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class CommandCompletionException(Exception):
|
||||||
|
"""Raised when a command should use the normal autocompleter instead of
|
||||||
|
command-specific completion."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ConfirmGroup:
|
class ConfirmGroup:
|
||||||
preference: str = None
|
preference: str = None
|
||||||
@@ -170,14 +196,23 @@ class AutoCompleter(Completer):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if text[0] == "/":
|
if text[0] == "/":
|
||||||
|
try:
|
||||||
yield from self.get_command_completions(document, complete_event, text, words)
|
yield from self.get_command_completions(document, complete_event, text, words)
|
||||||
return
|
return
|
||||||
|
except CommandCompletionException:
|
||||||
|
# Fall through to normal completion
|
||||||
|
pass
|
||||||
|
|
||||||
candidates = self.words
|
candidates = self.words
|
||||||
candidates.update(set(self.fname_to_rel_fnames))
|
candidates.update(set(self.fname_to_rel_fnames))
|
||||||
candidates = [word if type(word) is tuple else (word, word) for word in candidates]
|
candidates = [word if type(word) is tuple else (word, word) for word in candidates]
|
||||||
|
|
||||||
last_word = words[-1]
|
last_word = words[-1]
|
||||||
|
|
||||||
|
# Only provide completions if the user has typed at least 3 characters
|
||||||
|
if len(last_word) < 3:
|
||||||
|
return
|
||||||
|
|
||||||
completions = []
|
completions = []
|
||||||
for word_match, word_insert in candidates:
|
for word_match, word_insert in candidates:
|
||||||
if word_match.lower().startswith(last_word.lower()):
|
if word_match.lower().startswith(last_word.lower()):
|
||||||
@@ -196,6 +231,8 @@ class InputOutput:
|
|||||||
num_error_outputs = 0
|
num_error_outputs = 0
|
||||||
num_user_asks = 0
|
num_user_asks = 0
|
||||||
clipboard_watcher = None
|
clipboard_watcher = None
|
||||||
|
bell_on_next_input = False
|
||||||
|
notifications_command = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -224,25 +261,40 @@ class InputOutput:
|
|||||||
file_watcher=None,
|
file_watcher=None,
|
||||||
multiline_mode=False,
|
multiline_mode=False,
|
||||||
root=".",
|
root=".",
|
||||||
|
notifications=False,
|
||||||
|
notifications_command=None,
|
||||||
):
|
):
|
||||||
self.placeholder = None
|
self.placeholder = None
|
||||||
self.interrupted = False
|
self.interrupted = False
|
||||||
self.never_prompts = set()
|
self.never_prompts = set()
|
||||||
self.editingmode = editingmode
|
self.editingmode = editingmode
|
||||||
self.multiline_mode = multiline_mode
|
self.multiline_mode = multiline_mode
|
||||||
|
self.bell_on_next_input = False
|
||||||
|
self.notifications = notifications
|
||||||
|
if notifications and notifications_command is None:
|
||||||
|
self.notifications_command = self.get_default_notification_command()
|
||||||
|
else:
|
||||||
|
self.notifications_command = notifications_command
|
||||||
|
|
||||||
no_color = os.environ.get("NO_COLOR")
|
no_color = os.environ.get("NO_COLOR")
|
||||||
if no_color is not None and no_color != "":
|
if no_color is not None and no_color != "":
|
||||||
pretty = False
|
pretty = False
|
||||||
|
|
||||||
self.user_input_color = user_input_color if pretty else None
|
self.user_input_color = ensure_hash_prefix(user_input_color) if pretty else None
|
||||||
self.tool_output_color = tool_output_color if pretty else None
|
self.tool_output_color = ensure_hash_prefix(tool_output_color) if pretty else None
|
||||||
self.tool_error_color = tool_error_color if pretty else None
|
self.tool_error_color = ensure_hash_prefix(tool_error_color) if pretty else None
|
||||||
self.tool_warning_color = tool_warning_color if pretty else None
|
self.tool_warning_color = ensure_hash_prefix(tool_warning_color) if pretty else None
|
||||||
self.assistant_output_color = assistant_output_color
|
self.assistant_output_color = ensure_hash_prefix(assistant_output_color)
|
||||||
self.completion_menu_color = completion_menu_color if pretty else None
|
self.completion_menu_color = ensure_hash_prefix(completion_menu_color) if pretty else None
|
||||||
self.completion_menu_bg_color = completion_menu_bg_color if pretty else None
|
self.completion_menu_bg_color = (
|
||||||
self.completion_menu_current_color = completion_menu_current_color if pretty else None
|
ensure_hash_prefix(completion_menu_bg_color) if pretty else None
|
||||||
self.completion_menu_current_bg_color = completion_menu_current_bg_color if pretty else None
|
)
|
||||||
|
self.completion_menu_current_color = (
|
||||||
|
ensure_hash_prefix(completion_menu_current_color) if pretty else None
|
||||||
|
)
|
||||||
|
self.completion_menu_current_bg_color = (
|
||||||
|
ensure_hash_prefix(completion_menu_current_bg_color) if pretty else None
|
||||||
|
)
|
||||||
|
|
||||||
self.code_theme = code_theme
|
self.code_theme = code_theme
|
||||||
|
|
||||||
@@ -256,6 +308,12 @@ class InputOutput:
|
|||||||
self.yes = yes
|
self.yes = yes
|
||||||
|
|
||||||
self.input_history_file = input_history_file
|
self.input_history_file = input_history_file
|
||||||
|
if self.input_history_file:
|
||||||
|
try:
|
||||||
|
Path(self.input_history_file).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
except (PermissionError, OSError) as e:
|
||||||
|
self.tool_warning(f"Could not create directory for input history: {e}")
|
||||||
|
self.input_history_file = None
|
||||||
self.llm_history_file = llm_history_file
|
self.llm_history_file = llm_history_file
|
||||||
if chat_history_file is not None:
|
if chat_history_file is not None:
|
||||||
self.chat_history_file = Path(chat_history_file)
|
self.chat_history_file = Path(chat_history_file)
|
||||||
@@ -310,6 +368,35 @@ class InputOutput:
|
|||||||
self.file_watcher = file_watcher
|
self.file_watcher = file_watcher
|
||||||
self.root = root
|
self.root = root
|
||||||
|
|
||||||
|
# Validate color settings after console is initialized
|
||||||
|
self._validate_color_settings()
|
||||||
|
|
||||||
|
def _validate_color_settings(self):
|
||||||
|
"""Validate configured color strings and reset invalid ones."""
|
||||||
|
color_attributes = [
|
||||||
|
"user_input_color",
|
||||||
|
"tool_output_color",
|
||||||
|
"tool_error_color",
|
||||||
|
"tool_warning_color",
|
||||||
|
"assistant_output_color",
|
||||||
|
"completion_menu_color",
|
||||||
|
"completion_menu_bg_color",
|
||||||
|
"completion_menu_current_color",
|
||||||
|
"completion_menu_current_bg_color",
|
||||||
|
]
|
||||||
|
for attr_name in color_attributes:
|
||||||
|
color_value = getattr(self, attr_name, None)
|
||||||
|
if color_value:
|
||||||
|
try:
|
||||||
|
# Try creating a style to validate the color
|
||||||
|
RichStyle(color=color_value)
|
||||||
|
except ColorParseError as e:
|
||||||
|
self.console.print(
|
||||||
|
"[bold red]Warning:[/bold red] Invalid configuration for"
|
||||||
|
f" {attr_name}: '{color_value}'. {e}. Disabling this color."
|
||||||
|
)
|
||||||
|
setattr(self, attr_name, None) # Reset invalid color to None
|
||||||
|
|
||||||
def _get_style(self):
|
def _get_style(self):
|
||||||
style_dict = {}
|
style_dict = {}
|
||||||
if not self.pretty:
|
if not self.pretty:
|
||||||
@@ -335,9 +422,9 @@ class InputOutput:
|
|||||||
# Conditionally add 'completion-menu.completion.current' style
|
# Conditionally add 'completion-menu.completion.current' style
|
||||||
completion_menu_current_style = []
|
completion_menu_current_style = []
|
||||||
if self.completion_menu_current_bg_color:
|
if self.completion_menu_current_bg_color:
|
||||||
completion_menu_current_style.append(f"bg:{self.completion_menu_current_bg_color}")
|
completion_menu_current_style.append(self.completion_menu_current_bg_color)
|
||||||
if self.completion_menu_current_color:
|
if self.completion_menu_current_color:
|
||||||
completion_menu_current_style.append(self.completion_menu_current_color)
|
completion_menu_current_style.append(f"bg:{self.completion_menu_current_color}")
|
||||||
if completion_menu_current_style:
|
if completion_menu_current_style:
|
||||||
style_dict["completion-menu.completion.current"] = " ".join(
|
style_dict["completion-menu.completion.current"] = " ".join(
|
||||||
completion_menu_current_style
|
completion_menu_current_style
|
||||||
@@ -444,6 +531,9 @@ class InputOutput:
|
|||||||
):
|
):
|
||||||
self.rule()
|
self.rule()
|
||||||
|
|
||||||
|
# Ring the bell if needed
|
||||||
|
self.ring_bell()
|
||||||
|
|
||||||
rel_fnames = list(rel_fnames)
|
rel_fnames = list(rel_fnames)
|
||||||
show = ""
|
show = ""
|
||||||
if rel_fnames:
|
if rel_fnames:
|
||||||
@@ -451,11 +541,16 @@ class InputOutput:
|
|||||||
get_rel_fname(fname, root) for fname in (abs_read_only_fnames or [])
|
get_rel_fname(fname, root) for fname in (abs_read_only_fnames or [])
|
||||||
]
|
]
|
||||||
show = self.format_files_for_input(rel_fnames, rel_read_only_fnames)
|
show = self.format_files_for_input(rel_fnames, rel_read_only_fnames)
|
||||||
|
|
||||||
|
prompt_prefix = ""
|
||||||
if edit_format:
|
if edit_format:
|
||||||
show += edit_format
|
prompt_prefix += edit_format
|
||||||
if self.multiline_mode:
|
if self.multiline_mode:
|
||||||
show += (" " if edit_format else "") + "multi"
|
prompt_prefix += (" " if edit_format else "") + "multi"
|
||||||
show += "> "
|
prompt_prefix += "> "
|
||||||
|
|
||||||
|
show += prompt_prefix
|
||||||
|
self.prompt_prefix = prompt_prefix
|
||||||
|
|
||||||
inp = ""
|
inp = ""
|
||||||
multiline_input = False
|
multiline_input = False
|
||||||
@@ -499,11 +594,30 @@ class InputOutput:
|
|||||||
"Navigate forward through history"
|
"Navigate forward through history"
|
||||||
event.current_buffer.history_forward()
|
event.current_buffer.history_forward()
|
||||||
|
|
||||||
|
@kb.add("c-x", "c-e")
|
||||||
|
def _(event):
|
||||||
|
"Edit current input in external editor (like Bash)"
|
||||||
|
buffer = event.current_buffer
|
||||||
|
current_text = buffer.text
|
||||||
|
|
||||||
|
# Open the editor with the current text
|
||||||
|
edited_text = pipe_editor(input_data=current_text, suffix="md")
|
||||||
|
|
||||||
|
# Replace the buffer with the edited text, strip any trailing newlines
|
||||||
|
buffer.text = edited_text.rstrip("\n")
|
||||||
|
|
||||||
|
# Move cursor to the end of the text
|
||||||
|
buffer.cursor_position = len(buffer.text)
|
||||||
|
|
||||||
@kb.add("enter", eager=True, filter=~is_searching)
|
@kb.add("enter", eager=True, filter=~is_searching)
|
||||||
def _(event):
|
def _(event):
|
||||||
"Handle Enter key press"
|
"Handle Enter key press"
|
||||||
if self.multiline_mode:
|
if self.multiline_mode and not (
|
||||||
# In multiline mode, Enter adds a newline
|
self.editingmode == EditingMode.VI
|
||||||
|
and event.app.vi_state.input_mode == InputMode.NAVIGATION
|
||||||
|
):
|
||||||
|
# In multiline mode and if not in vi-mode or vi navigation/normal mode,
|
||||||
|
# Enter adds a newline
|
||||||
event.current_buffer.insert_text("\n")
|
event.current_buffer.insert_text("\n")
|
||||||
else:
|
else:
|
||||||
# In normal mode, Enter submits
|
# In normal mode, Enter submits
|
||||||
@@ -521,7 +635,7 @@ class InputOutput:
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
if multiline_input:
|
if multiline_input:
|
||||||
show = ". "
|
show = self.prompt_prefix
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.prompt_session:
|
if self.prompt_session:
|
||||||
@@ -537,7 +651,7 @@ class InputOutput:
|
|||||||
self.clipboard_watcher.start()
|
self.clipboard_watcher.start()
|
||||||
|
|
||||||
def get_continuation(width, line_number, is_soft_wrap):
|
def get_continuation(width, line_number, is_soft_wrap):
|
||||||
return ". "
|
return self.prompt_prefix
|
||||||
|
|
||||||
line = self.prompt_session.prompt(
|
line = self.prompt_session.prompt(
|
||||||
show,
|
show,
|
||||||
@@ -641,9 +755,14 @@ class InputOutput:
|
|||||||
if not self.llm_history_file:
|
if not self.llm_history_file:
|
||||||
return
|
return
|
||||||
timestamp = datetime.now().isoformat(timespec="seconds")
|
timestamp = datetime.now().isoformat(timespec="seconds")
|
||||||
with open(self.llm_history_file, "a", encoding=self.encoding) as log_file:
|
try:
|
||||||
|
Path(self.llm_history_file).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(self.llm_history_file, "a", encoding="utf-8") as log_file:
|
||||||
log_file.write(f"{role.upper()} {timestamp}\n")
|
log_file.write(f"{role.upper()} {timestamp}\n")
|
||||||
log_file.write(content + "\n")
|
log_file.write(content + "\n")
|
||||||
|
except (PermissionError, OSError) as err:
|
||||||
|
self.tool_warning(f"Unable to write to llm history file {self.llm_history_file}: {err}")
|
||||||
|
self.llm_history_file = None
|
||||||
|
|
||||||
def display_user_input(self, inp):
|
def display_user_input(self, inp):
|
||||||
if self.pretty and self.user_input_color:
|
if self.pretty and self.user_input_color:
|
||||||
@@ -696,6 +815,9 @@ class InputOutput:
|
|||||||
):
|
):
|
||||||
self.num_user_asks += 1
|
self.num_user_asks += 1
|
||||||
|
|
||||||
|
# Ring the bell if needed
|
||||||
|
self.ring_bell()
|
||||||
|
|
||||||
question_id = (question, subject)
|
question_id = (question, subject)
|
||||||
|
|
||||||
if question_id in self.never_prompts:
|
if question_id in self.never_prompts:
|
||||||
@@ -750,6 +872,7 @@ class InputOutput:
|
|||||||
self.user_input(f"{question}{res}", log_only=False)
|
self.user_input(f"{question}{res}", log_only=False)
|
||||||
else:
|
else:
|
||||||
while True:
|
while True:
|
||||||
|
try:
|
||||||
if self.prompt_session:
|
if self.prompt_session:
|
||||||
res = self.prompt_session.prompt(
|
res = self.prompt_session.prompt(
|
||||||
question,
|
question,
|
||||||
@@ -758,6 +881,10 @@ class InputOutput:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
res = input(question)
|
res = input(question)
|
||||||
|
except EOFError:
|
||||||
|
# Treat EOF (Ctrl+D) as if the user pressed Enter
|
||||||
|
res = default
|
||||||
|
break
|
||||||
|
|
||||||
if not res:
|
if not res:
|
||||||
res = default
|
res = default
|
||||||
@@ -801,6 +928,9 @@ class InputOutput:
|
|||||||
def prompt_ask(self, question, default="", subject=None):
|
def prompt_ask(self, question, default="", subject=None):
|
||||||
self.num_user_asks += 1
|
self.num_user_asks += 1
|
||||||
|
|
||||||
|
# Ring the bell if needed
|
||||||
|
self.ring_bell()
|
||||||
|
|
||||||
if subject:
|
if subject:
|
||||||
self.tool_output()
|
self.tool_output()
|
||||||
self.tool_output(subject, bold=True)
|
self.tool_output(subject, bold=True)
|
||||||
@@ -812,6 +942,7 @@ class InputOutput:
|
|||||||
elif self.yes is False:
|
elif self.yes is False:
|
||||||
res = "no"
|
res = "no"
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
if self.prompt_session:
|
if self.prompt_session:
|
||||||
res = self.prompt_session.prompt(
|
res = self.prompt_session.prompt(
|
||||||
question + " ",
|
question + " ",
|
||||||
@@ -821,6 +952,9 @@ class InputOutput:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
res = input(question + " ")
|
res = input(question + " ")
|
||||||
|
except EOFError:
|
||||||
|
# Treat EOF (Ctrl+D) as if the user pressed Enter
|
||||||
|
res = default
|
||||||
|
|
||||||
hist = f"{question.strip()} {res.strip()}"
|
hist = f"{question.strip()} {res.strip()}"
|
||||||
self.append_chat_history(hist, linebreak=True, blockquote=True)
|
self.append_chat_history(hist, linebreak=True, blockquote=True)
|
||||||
@@ -840,6 +974,7 @@ class InputOutput:
|
|||||||
|
|
||||||
if not isinstance(message, Text):
|
if not isinstance(message, Text):
|
||||||
message = Text(message)
|
message = Text(message)
|
||||||
|
color = ensure_hash_prefix(color) if color else None
|
||||||
style = dict(style=color) if self.pretty and color else dict()
|
style = dict(style=color) if self.pretty and color else dict()
|
||||||
try:
|
try:
|
||||||
self.console.print(message, **style)
|
self.console.print(message, **style)
|
||||||
@@ -870,18 +1005,26 @@ class InputOutput:
|
|||||||
style = dict()
|
style = dict()
|
||||||
if self.pretty:
|
if self.pretty:
|
||||||
if self.tool_output_color:
|
if self.tool_output_color:
|
||||||
style["color"] = self.tool_output_color
|
style["color"] = ensure_hash_prefix(self.tool_output_color)
|
||||||
style["reverse"] = bold
|
style["reverse"] = bold
|
||||||
|
|
||||||
style = RichStyle(**style)
|
style = RichStyle(**style)
|
||||||
self.console.print(*messages, style=style)
|
self.console.print(*messages, style=style)
|
||||||
|
|
||||||
def get_assistant_mdstream(self):
|
def get_assistant_mdstream(self):
|
||||||
mdargs = dict(style=self.assistant_output_color, code_theme=self.code_theme)
|
mdargs = dict(
|
||||||
|
style=self.assistant_output_color,
|
||||||
|
code_theme=self.code_theme,
|
||||||
|
inline_code_lexer="text",
|
||||||
|
)
|
||||||
mdStream = MarkdownStream(mdargs=mdargs)
|
mdStream = MarkdownStream(mdargs=mdargs)
|
||||||
return mdStream
|
return mdStream
|
||||||
|
|
||||||
def assistant_output(self, message, pretty=None):
|
def assistant_output(self, message, pretty=None):
|
||||||
|
if not message:
|
||||||
|
self.tool_warning("Empty response received from LLM. Check your provider account?")
|
||||||
|
return
|
||||||
|
|
||||||
show_resp = message
|
show_resp = message
|
||||||
|
|
||||||
# Coder will force pretty off if fence is not triple-backticks
|
# Coder will force pretty off if fence is not triple-backticks
|
||||||
@@ -893,7 +1036,7 @@ class InputOutput:
|
|||||||
message, style=self.assistant_output_color, code_theme=self.code_theme
|
message, style=self.assistant_output_color, code_theme=self.code_theme
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
show_resp = Text(message or "<no response>")
|
show_resp = Text(message or "(empty response)")
|
||||||
|
|
||||||
self.console.print(show_resp)
|
self.console.print(show_resp)
|
||||||
|
|
||||||
@@ -904,6 +1047,61 @@ class InputOutput:
|
|||||||
def print(self, message=""):
|
def print(self, message=""):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
|
def llm_started(self):
|
||||||
|
"""Mark that the LLM has started processing, so we should ring the bell on next input"""
|
||||||
|
self.bell_on_next_input = True
|
||||||
|
|
||||||
|
def get_default_notification_command(self):
|
||||||
|
"""Return a default notification command based on the operating system."""
|
||||||
|
import platform
|
||||||
|
|
||||||
|
system = platform.system()
|
||||||
|
|
||||||
|
if system == "Darwin": # macOS
|
||||||
|
# Check for terminal-notifier first
|
||||||
|
if shutil.which("terminal-notifier"):
|
||||||
|
return f"terminal-notifier -title 'Aider' -message '{NOTIFICATION_MESSAGE}'"
|
||||||
|
# Fall back to osascript
|
||||||
|
return (
|
||||||
|
f'osascript -e \'display notification "{NOTIFICATION_MESSAGE}" with title "Aider"\''
|
||||||
|
)
|
||||||
|
elif system == "Linux":
|
||||||
|
# Check for common Linux notification tools
|
||||||
|
for cmd in ["notify-send", "zenity"]:
|
||||||
|
if shutil.which(cmd):
|
||||||
|
if cmd == "notify-send":
|
||||||
|
return f"notify-send 'Aider' '{NOTIFICATION_MESSAGE}'"
|
||||||
|
elif cmd == "zenity":
|
||||||
|
return f"zenity --notification --text='{NOTIFICATION_MESSAGE}'"
|
||||||
|
return None # No known notification tool found
|
||||||
|
elif system == "Windows":
|
||||||
|
# PowerShell notification
|
||||||
|
return (
|
||||||
|
"powershell -command"
|
||||||
|
" \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');"
|
||||||
|
f" [System.Windows.Forms.MessageBox]::Show('{NOTIFICATION_MESSAGE}',"
|
||||||
|
" 'Aider')\""
|
||||||
|
)
|
||||||
|
|
||||||
|
return None # Unknown system
|
||||||
|
|
||||||
|
def ring_bell(self):
|
||||||
|
"""Ring the terminal bell if needed and clear the flag"""
|
||||||
|
if self.bell_on_next_input and self.notifications:
|
||||||
|
if self.notifications_command:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
self.notifications_command, shell=True, capture_output=True
|
||||||
|
)
|
||||||
|
if result.returncode != 0 and result.stderr:
|
||||||
|
error_msg = result.stderr.decode("utf-8", errors="replace")
|
||||||
|
self.tool_warning(f"Failed to run notifications command: {error_msg}")
|
||||||
|
except Exception as e:
|
||||||
|
self.tool_warning(f"Failed to run notifications command: {e}")
|
||||||
|
else:
|
||||||
|
print("\a", end="", flush=True) # Ring the bell
|
||||||
|
self.bell_on_next_input = False # Clear the flag
|
||||||
|
|
||||||
def toggle_multiline_mode(self):
|
def toggle_multiline_mode(self):
|
||||||
"""Toggle between normal and multiline input modes"""
|
"""Toggle between normal and multiline input modes"""
|
||||||
self.multiline_mode = not self.multiline_mode
|
self.multiline_mode = not self.multiline_mode
|
||||||
@@ -929,6 +1127,7 @@ class InputOutput:
|
|||||||
text += "\n"
|
text += "\n"
|
||||||
if self.chat_history_file is not None:
|
if self.chat_history_file is not None:
|
||||||
try:
|
try:
|
||||||
|
self.chat_history_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with self.chat_history_file.open("a", encoding=self.encoding, errors="ignore") as f:
|
with self.chat_history_file.open("a", encoding=self.encoding, errors="ignore") as f:
|
||||||
f.write(text)
|
f.write(text)
|
||||||
except (PermissionError, OSError) as err:
|
except (PermissionError, OSError) as err:
|
||||||
@@ -961,18 +1160,19 @@ class InputOutput:
|
|||||||
ro_paths = []
|
ro_paths = []
|
||||||
for rel_path in read_only_files:
|
for rel_path in read_only_files:
|
||||||
abs_path = os.path.abspath(os.path.join(self.root, rel_path))
|
abs_path = os.path.abspath(os.path.join(self.root, rel_path))
|
||||||
ro_paths.append(abs_path if len(abs_path) < len(rel_path) else rel_path)
|
ro_paths.append(Text(abs_path if len(abs_path) < len(rel_path) else rel_path))
|
||||||
|
|
||||||
files_with_label = ["Readonly:"] + ro_paths
|
files_with_label = [Text("Readonly:")] + ro_paths
|
||||||
read_only_output = StringIO()
|
read_only_output = StringIO()
|
||||||
Console(file=read_only_output, force_terminal=False).print(Columns(files_with_label))
|
Console(file=read_only_output, force_terminal=False).print(Columns(files_with_label))
|
||||||
read_only_lines = read_only_output.getvalue().splitlines()
|
read_only_lines = read_only_output.getvalue().splitlines()
|
||||||
console.print(Columns(files_with_label))
|
console.print(Columns(files_with_label))
|
||||||
|
|
||||||
if editable_files:
|
if editable_files:
|
||||||
files_with_label = editable_files
|
text_editable_files = [Text(f) for f in editable_files]
|
||||||
|
files_with_label = text_editable_files
|
||||||
if read_only_files:
|
if read_only_files:
|
||||||
files_with_label = ["Editable:"] + editable_files
|
files_with_label = [Text("Editable:")] + text_editable_files
|
||||||
editable_output = StringIO()
|
editable_output = StringIO()
|
||||||
Console(file=editable_output, force_terminal=False).print(Columns(files_with_label))
|
Console(file=editable_output, force_terminal=False).print(Columns(files_with_label))
|
||||||
editable_lines = editable_output.getvalue().splitlines()
|
editable_lines = editable_output.getvalue().splitlines()
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import warnings
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import oslex
|
||||||
from grep_ast import TreeContext, filename_to_lang
|
from grep_ast import TreeContext, filename_to_lang
|
||||||
from tree_sitter_languages import get_parser # noqa: E402
|
from grep_ast.tsl import get_parser # noqa: E402
|
||||||
|
|
||||||
from aider.dump import dump # noqa: F401
|
from aider.dump import dump # noqa: F401
|
||||||
from aider.run_cmd import run_cmd_subprocess # noqa: F401
|
from aider.run_cmd import run_cmd_subprocess # noqa: F401
|
||||||
@@ -44,7 +45,7 @@ class Linter:
|
|||||||
return fname
|
return fname
|
||||||
|
|
||||||
def run_cmd(self, cmd, rel_fname, code):
|
def run_cmd(self, cmd, rel_fname, code):
|
||||||
cmd += " " + rel_fname
|
cmd += " " + oslex.quote(rel_fname)
|
||||||
|
|
||||||
returncode = 0
|
returncode = 0
|
||||||
stdout = ""
|
stdout = ""
|
||||||
|
|||||||
189
aider/main.py
189
aider/main.py
@@ -1,4 +1,3 @@
|
|||||||
import configparser
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -15,6 +14,7 @@ except ImportError:
|
|||||||
git = None
|
git = None
|
||||||
|
|
||||||
import importlib_resources
|
import importlib_resources
|
||||||
|
import shtab
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from prompt_toolkit.enums import EditingMode
|
from prompt_toolkit.enums import EditingMode
|
||||||
|
|
||||||
@@ -25,11 +25,13 @@ from aider.coders import Coder
|
|||||||
from aider.coders.base_coder import UnknownEditFormat
|
from aider.coders.base_coder import UnknownEditFormat
|
||||||
from aider.commands import Commands, SwitchCoder
|
from aider.commands import Commands, SwitchCoder
|
||||||
from aider.copypaste import ClipboardWatcher
|
from aider.copypaste import ClipboardWatcher
|
||||||
|
from aider.deprecated import handle_deprecated_model_args
|
||||||
from aider.format_settings import format_settings, scrub_sensitive_info
|
from aider.format_settings import format_settings, scrub_sensitive_info
|
||||||
from aider.history import ChatSummary
|
from aider.history import ChatSummary
|
||||||
from aider.io import InputOutput
|
from aider.io import InputOutput
|
||||||
from aider.llm import litellm # noqa: F401; properly init litellm on launch
|
from aider.llm import litellm # noqa: F401; properly init litellm on launch
|
||||||
from aider.models import ModelSettings
|
from aider.models import ModelSettings
|
||||||
|
from aider.onboarding import offer_openrouter_oauth, select_default_model
|
||||||
from aider.repo import ANY_GIT_ERROR, GitRepo
|
from aider.repo import ANY_GIT_ERROR, GitRepo
|
||||||
from aider.report import report_uncaught_exceptions
|
from aider.report import report_uncaught_exceptions
|
||||||
from aider.versioncheck import check_version, install_from_main_branch, install_upgrade
|
from aider.versioncheck import check_version, install_from_main_branch, install_upgrade
|
||||||
@@ -126,17 +128,15 @@ def setup_git(git_root, io):
|
|||||||
if not repo:
|
if not repo:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_name = repo.git.config("--get", "user.name") or None
|
||||||
|
except git.exc.GitCommandError:
|
||||||
user_name = None
|
user_name = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_email = repo.git.config("--get", "user.email") or None
|
||||||
|
except git.exc.GitCommandError:
|
||||||
user_email = None
|
user_email = None
|
||||||
with repo.config_reader() as config:
|
|
||||||
try:
|
|
||||||
user_name = config.get_value("user", "name", None)
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
user_email = config.get_value("user", "email", None)
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
if user_name and user_email:
|
if user_name and user_email:
|
||||||
return repo.working_tree_dir
|
return repo.working_tree_dir
|
||||||
@@ -359,11 +359,21 @@ def register_models(git_root, model_settings_fname, io, verbose=False):
|
|||||||
|
|
||||||
|
|
||||||
def load_dotenv_files(git_root, dotenv_fname, encoding="utf-8"):
|
def load_dotenv_files(git_root, dotenv_fname, encoding="utf-8"):
|
||||||
|
# Standard .env file search path
|
||||||
dotenv_files = generate_search_path_list(
|
dotenv_files = generate_search_path_list(
|
||||||
".env",
|
".env",
|
||||||
git_root,
|
git_root,
|
||||||
dotenv_fname,
|
dotenv_fname,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Explicitly add the OAuth keys file to the beginning of the list
|
||||||
|
oauth_keys_file = Path.home() / ".aider" / "oauth-keys.env"
|
||||||
|
if oauth_keys_file.exists():
|
||||||
|
# Insert at the beginning so it's loaded first (and potentially overridden)
|
||||||
|
dotenv_files.insert(0, str(oauth_keys_file.resolve()))
|
||||||
|
# Remove duplicates if it somehow got included by generate_search_path_list
|
||||||
|
dotenv_files = list(dict.fromkeys(dotenv_files))
|
||||||
|
|
||||||
loaded = []
|
loaded = []
|
||||||
for fname in dotenv_files:
|
for fname in dotenv_files:
|
||||||
try:
|
try:
|
||||||
@@ -493,6 +503,12 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
# Parse again to include any arguments that might have been defined in .env
|
# Parse again to include any arguments that might have been defined in .env
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
if args.shell_completions:
|
||||||
|
# Ensure parser.prog is set for shtab, though it should be by default
|
||||||
|
parser.prog = "aider"
|
||||||
|
print(shtab.complete(parser, shell=args.shell_completions))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if git is None:
|
if git is None:
|
||||||
args.git = False
|
args.git = False
|
||||||
|
|
||||||
@@ -507,6 +523,8 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
litellm._load_litellm()
|
litellm._load_litellm()
|
||||||
litellm._lazy_module.client_session = httpx.Client(verify=False)
|
litellm._lazy_module.client_session = httpx.Client(verify=False)
|
||||||
litellm._lazy_module.aclient_session = httpx.AsyncClient(verify=False)
|
litellm._lazy_module.aclient_session = httpx.AsyncClient(verify=False)
|
||||||
|
# Set verify_ssl on the model_info_manager
|
||||||
|
models.model_info_manager.set_verify_ssl(False)
|
||||||
|
|
||||||
if args.timeout:
|
if args.timeout:
|
||||||
models.request_timeout = args.timeout
|
models.request_timeout = args.timeout
|
||||||
@@ -555,6 +573,8 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
editingmode=editing_mode,
|
editingmode=editing_mode,
|
||||||
fancy_input=args.fancy_input,
|
fancy_input=args.fancy_input,
|
||||||
multiline_mode=args.multiline,
|
multiline_mode=args.multiline,
|
||||||
|
notifications=args.notifications,
|
||||||
|
notifications_command=args.notifications_command,
|
||||||
)
|
)
|
||||||
|
|
||||||
io = get_io(args.pretty)
|
io = get_io(args.pretty)
|
||||||
@@ -594,6 +614,9 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
|
|
||||||
if args.openai_api_key:
|
if args.openai_api_key:
|
||||||
os.environ["OPENAI_API_KEY"] = args.openai_api_key
|
os.environ["OPENAI_API_KEY"] = args.openai_api_key
|
||||||
|
|
||||||
|
# Handle deprecated model shortcut args
|
||||||
|
handle_deprecated_model_args(args, io)
|
||||||
if args.openai_api_base:
|
if args.openai_api_base:
|
||||||
os.environ["OPENAI_API_BASE"] = args.openai_api_base
|
os.environ["OPENAI_API_BASE"] = args.openai_api_base
|
||||||
if args.openai_api_version:
|
if args.openai_api_version:
|
||||||
@@ -610,7 +633,12 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
)
|
)
|
||||||
os.environ["OPENAI_ORGANIZATION"] = args.openai_organization_id
|
os.environ["OPENAI_ORGANIZATION"] = args.openai_organization_id
|
||||||
|
|
||||||
analytics = Analytics(logfile=args.analytics_log, permanently_disable=args.analytics_disable)
|
analytics = Analytics(
|
||||||
|
logfile=args.analytics_log,
|
||||||
|
permanently_disable=args.analytics_disable,
|
||||||
|
posthog_host=args.analytics_posthog_host,
|
||||||
|
posthog_project_api_key=args.analytics_posthog_project_api_key,
|
||||||
|
)
|
||||||
if args.analytics is not False:
|
if args.analytics is not False:
|
||||||
if analytics.need_to_ask(args.analytics):
|
if analytics.need_to_ask(args.analytics):
|
||||||
io.tool_output(
|
io.tool_output(
|
||||||
@@ -709,11 +737,6 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
if args.check_update:
|
if args.check_update:
|
||||||
check_version(io, verbose=args.verbose)
|
check_version(io, verbose=args.verbose)
|
||||||
|
|
||||||
if args.list_models:
|
|
||||||
models.print_matching_models(io, args.list_models)
|
|
||||||
analytics.event("exit", reason="Listed models")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if args.git:
|
if args.git:
|
||||||
git_root = setup_git(git_root, io)
|
git_root = setup_git(git_root, io)
|
||||||
if args.gitignore:
|
if args.gitignore:
|
||||||
@@ -733,6 +756,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
register_models(git_root, args.model_settings_file, io, verbose=args.verbose)
|
register_models(git_root, args.model_settings_file, io, verbose=args.verbose)
|
||||||
register_litellm_models(git_root, args.model_metadata_file, io, verbose=args.verbose)
|
register_litellm_models(git_root, args.model_metadata_file, io, verbose=args.verbose)
|
||||||
|
|
||||||
|
if args.list_models:
|
||||||
|
models.print_matching_models(io, args.list_models)
|
||||||
|
analytics.event("exit", reason="Listed models")
|
||||||
|
return 0
|
||||||
|
|
||||||
# Process any command line aliases
|
# Process any command line aliases
|
||||||
if args.alias:
|
if args.alias:
|
||||||
for alias_def in args.alias:
|
for alias_def in args.alias:
|
||||||
@@ -746,26 +774,49 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
alias, model = parts
|
alias, model = parts
|
||||||
models.MODEL_ALIASES[alias.strip()] = model.strip()
|
models.MODEL_ALIASES[alias.strip()] = model.strip()
|
||||||
|
|
||||||
if not args.model:
|
selected_model_name = select_default_model(args, io, analytics)
|
||||||
# Select model based on available API keys
|
if not selected_model_name:
|
||||||
model_key_pairs = [
|
# Error message and analytics event are handled within select_default_model
|
||||||
("ANTHROPIC_API_KEY", "sonnet"),
|
# It might have already offered OAuth if no model/keys were found.
|
||||||
("DEEPSEEK_API_KEY", "deepseek"),
|
# If it failed here, we exit.
|
||||||
("OPENROUTER_API_KEY", "openrouter/anthropic/claude-3.5-sonnet"),
|
return 1
|
||||||
("OPENAI_API_KEY", "gpt-4o"),
|
args.model = selected_model_name # Update args with the selected model
|
||||||
("GEMINI_API_KEY", "flash"),
|
|
||||||
]
|
|
||||||
|
|
||||||
for env_key, model_name in model_key_pairs:
|
# Check if an OpenRouter model was selected/specified but the key is missing
|
||||||
if os.environ.get(env_key):
|
if args.model.startswith("openrouter/") and not os.environ.get("OPENROUTER_API_KEY"):
|
||||||
args.model = model_name
|
|
||||||
io.tool_warning(
|
io.tool_warning(
|
||||||
f"Found {env_key} so using {model_name} since no --model was specified."
|
f"The specified model '{args.model}' requires an OpenRouter API key, which was not"
|
||||||
|
" found."
|
||||||
|
)
|
||||||
|
# Attempt OAuth flow because the specific model needs it
|
||||||
|
if offer_openrouter_oauth(io, analytics):
|
||||||
|
# OAuth succeeded, the key should now be in os.environ.
|
||||||
|
# Check if the key is now present after the flow.
|
||||||
|
if os.environ.get("OPENROUTER_API_KEY"):
|
||||||
|
io.tool_output(
|
||||||
|
"OpenRouter successfully connected."
|
||||||
|
) # Inform user connection worked
|
||||||
|
else:
|
||||||
|
# This case should ideally not happen if offer_openrouter_oauth succeeded
|
||||||
|
# but check defensively.
|
||||||
|
io.tool_error(
|
||||||
|
"OpenRouter authentication seemed successful, but the key is still missing."
|
||||||
|
)
|
||||||
|
analytics.event(
|
||||||
|
"exit",
|
||||||
|
reason="OpenRouter key missing after successful OAuth for specified model",
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
# OAuth failed or was declined by the user
|
||||||
|
io.tool_error(
|
||||||
|
f"Unable to proceed without an OpenRouter API key for model '{args.model}'."
|
||||||
|
)
|
||||||
|
io.offer_url(urls.models_and_keys, "Open documentation URL for more info?")
|
||||||
|
analytics.event(
|
||||||
|
"exit",
|
||||||
|
reason="OpenRouter key missing for specified model and OAuth failed/declined",
|
||||||
)
|
)
|
||||||
break
|
|
||||||
if not args.model:
|
|
||||||
io.tool_error("You need to specify a --model and an --api-key to use.")
|
|
||||||
io.offer_url(urls.models_and_keys, "Open documentation url for more info?")
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
main_model = models.Model(
|
main_model = models.Model(
|
||||||
@@ -773,18 +824,52 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
weak_model=args.weak_model,
|
weak_model=args.weak_model,
|
||||||
editor_model=args.editor_model,
|
editor_model=args.editor_model,
|
||||||
editor_edit_format=args.editor_edit_format,
|
editor_edit_format=args.editor_edit_format,
|
||||||
|
verbose=args.verbose,
|
||||||
)
|
)
|
||||||
|
|
||||||
# add --reasoning-effort cli param
|
# Check if deprecated remove_reasoning is set
|
||||||
|
if main_model.remove_reasoning is not None:
|
||||||
|
io.tool_warning(
|
||||||
|
"Model setting 'remove_reasoning' is deprecated, please use 'reasoning_tag' instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set reasoning effort and thinking tokens if specified
|
||||||
if args.reasoning_effort is not None:
|
if args.reasoning_effort is not None:
|
||||||
if not getattr(main_model, "extra_params", None):
|
# Apply if check is disabled or model explicitly supports it
|
||||||
main_model.extra_params = {}
|
if not args.check_model_accepts_settings or (
|
||||||
if "extra_body" not in main_model.extra_params:
|
main_model.accepts_settings and "reasoning_effort" in main_model.accepts_settings
|
||||||
main_model.extra_params["extra_body"] = {}
|
):
|
||||||
main_model.extra_params["extra_body"]["reasoning_effort"] = args.reasoning_effort
|
main_model.set_reasoning_effort(args.reasoning_effort)
|
||||||
|
|
||||||
|
if args.thinking_tokens is not None:
|
||||||
|
# Apply if check is disabled or model explicitly supports it
|
||||||
|
if not args.check_model_accepts_settings or (
|
||||||
|
main_model.accepts_settings and "thinking_tokens" in main_model.accepts_settings
|
||||||
|
):
|
||||||
|
main_model.set_thinking_tokens(args.thinking_tokens)
|
||||||
|
|
||||||
|
# Show warnings about unsupported settings that are being ignored
|
||||||
|
if args.check_model_accepts_settings:
|
||||||
|
settings_to_check = [
|
||||||
|
{"arg": args.reasoning_effort, "name": "reasoning_effort"},
|
||||||
|
{"arg": args.thinking_tokens, "name": "thinking_tokens"},
|
||||||
|
]
|
||||||
|
|
||||||
|
for setting in settings_to_check:
|
||||||
|
if setting["arg"] is not None and (
|
||||||
|
not main_model.accepts_settings
|
||||||
|
or setting["name"] not in main_model.accepts_settings
|
||||||
|
):
|
||||||
|
io.tool_warning(
|
||||||
|
f"Warning: {main_model.name} does not support '{setting['name']}', ignoring."
|
||||||
|
)
|
||||||
|
io.tool_output(
|
||||||
|
f"Use --no-check-model-accepts-settings to force the '{setting['name']}'"
|
||||||
|
" setting."
|
||||||
|
)
|
||||||
|
|
||||||
if args.copy_paste and args.edit_format is None:
|
if args.copy_paste and args.edit_format is None:
|
||||||
if main_model.edit_format in ("diff", "whole"):
|
if main_model.edit_format in ("diff", "whole", "diff-fenced"):
|
||||||
main_model.edit_format = "editor-" + main_model.edit_format
|
main_model.edit_format = "editor-" + main_model.edit_format
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
@@ -830,6 +915,8 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
attribute_commit_message_committer=args.attribute_commit_message_committer,
|
attribute_commit_message_committer=args.attribute_commit_message_committer,
|
||||||
commit_prompt=args.commit_prompt,
|
commit_prompt=args.commit_prompt,
|
||||||
subtree_only=args.subtree_only,
|
subtree_only=args.subtree_only,
|
||||||
|
git_commit_verify=args.git_commit_verify,
|
||||||
|
attribute_co_authored_by=args.attribute_co_authored_by, # Pass the arg
|
||||||
)
|
)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
@@ -839,8 +926,9 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
analytics.event("exit", reason="Repository sanity check failed")
|
analytics.event("exit", reason="Repository sanity check failed")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if repo:
|
if repo and not args.skip_sanity_check_repo:
|
||||||
analytics.event("repo", num_files=len(repo.get_tracked_files()))
|
num_files = len(repo.get_tracked_files())
|
||||||
|
analytics.event("repo", num_files=num_files)
|
||||||
else:
|
else:
|
||||||
analytics.event("no-repo")
|
analytics.event("no-repo")
|
||||||
|
|
||||||
@@ -855,6 +943,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
parser=parser,
|
parser=parser,
|
||||||
verbose=args.verbose,
|
verbose=args.verbose,
|
||||||
editor=args.editor,
|
editor=args.editor,
|
||||||
|
original_read_only_fnames=read_only_fnames,
|
||||||
)
|
)
|
||||||
|
|
||||||
summarizer = ChatSummary(
|
summarizer = ChatSummary(
|
||||||
@@ -877,6 +966,9 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
else:
|
else:
|
||||||
map_tokens = args.map_tokens
|
map_tokens = args.map_tokens
|
||||||
|
|
||||||
|
# Track auto-commits configuration
|
||||||
|
analytics.event("auto_commits", enabled=bool(args.auto_commits))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
coder = Coder.create(
|
coder = Coder.create(
|
||||||
main_model=main_model,
|
main_model=main_model,
|
||||||
@@ -907,8 +999,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
num_cache_warming_pings=args.cache_keepalive_pings,
|
num_cache_warming_pings=args.cache_keepalive_pings,
|
||||||
suggest_shell_commands=args.suggest_shell_commands,
|
suggest_shell_commands=args.suggest_shell_commands,
|
||||||
chat_language=args.chat_language,
|
chat_language=args.chat_language,
|
||||||
|
commit_language=args.commit_language,
|
||||||
detect_urls=args.detect_urls,
|
detect_urls=args.detect_urls,
|
||||||
auto_copy_context=args.copy_paste,
|
auto_copy_context=args.copy_paste,
|
||||||
|
auto_accept_architect=args.auto_accept_architect,
|
||||||
|
add_gitignore_files=args.add_gitignore_files,
|
||||||
)
|
)
|
||||||
except UnknownEditFormat as err:
|
except UnknownEditFormat as err:
|
||||||
io.tool_error(str(err))
|
io.tool_error(str(err))
|
||||||
@@ -1022,6 +1117,9 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
io.tool_output(f"Cur working dir: {Path.cwd()}")
|
io.tool_output(f"Cur working dir: {Path.cwd()}")
|
||||||
io.tool_output(f"Git working dir: {git_root}")
|
io.tool_output(f"Git working dir: {git_root}")
|
||||||
|
|
||||||
|
if args.stream and args.cache_prompts:
|
||||||
|
io.tool_warning("Cost estimates may be inaccurate when using streaming and caching.")
|
||||||
|
|
||||||
if args.load:
|
if args.load:
|
||||||
commands.cmd_load(args.load)
|
commands.cmd_load(args.load)
|
||||||
|
|
||||||
@@ -1060,10 +1158,17 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
coder.ok_to_warm_cache = bool(args.cache_keepalive_pings)
|
||||||
coder.run()
|
coder.run()
|
||||||
analytics.event("exit", reason="Completed main CLI coder.run")
|
analytics.event("exit", reason="Completed main CLI coder.run")
|
||||||
return
|
return
|
||||||
except SwitchCoder as switch:
|
except SwitchCoder as switch:
|
||||||
|
coder.ok_to_warm_cache = False
|
||||||
|
|
||||||
|
# Set the placeholder if provided
|
||||||
|
if hasattr(switch, "placeholder") and switch.placeholder is not None:
|
||||||
|
io.placeholder = switch.placeholder
|
||||||
|
|
||||||
kwargs = dict(io=io, from_coder=coder)
|
kwargs = dict(io=io, from_coder=coder)
|
||||||
kwargs.update(switch.kwargs)
|
kwargs.update(switch.kwargs)
|
||||||
if "show_announcements" in kwargs:
|
if "show_announcements" in kwargs:
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
import io
|
import io
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from rich import box
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.live import Live
|
from rich.live import Live
|
||||||
from rich.markdown import Markdown
|
from rich.markdown import CodeBlock, Heading, Markdown
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.syntax import Syntax
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from aider.dump import dump # noqa: F401
|
from aider.dump import dump # noqa: F401
|
||||||
@@ -46,6 +49,46 @@ The end.
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
|
|
||||||
|
|
||||||
|
class NoInsetCodeBlock(CodeBlock):
|
||||||
|
"""A code block with syntax highlighting and no padding."""
|
||||||
|
|
||||||
|
def __rich_console__(self, console, options):
|
||||||
|
code = str(self.text).rstrip()
|
||||||
|
syntax = Syntax(code, self.lexer_name, theme=self.theme, word_wrap=True, padding=(1, 0))
|
||||||
|
yield syntax
|
||||||
|
|
||||||
|
|
||||||
|
class LeftHeading(Heading):
|
||||||
|
"""A heading class that renders left-justified."""
|
||||||
|
|
||||||
|
def __rich_console__(self, console, options):
|
||||||
|
text = self.text
|
||||||
|
text.justify = "left" # Override justification
|
||||||
|
if self.tag == "h1":
|
||||||
|
# Draw a border around h1s, but keep text left-aligned
|
||||||
|
yield Panel(
|
||||||
|
text,
|
||||||
|
box=box.HEAVY,
|
||||||
|
style="markdown.h1.border",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Styled text for h2 and beyond
|
||||||
|
if self.tag == "h2":
|
||||||
|
yield Text("") # Keep the blank line before h2
|
||||||
|
yield text
|
||||||
|
|
||||||
|
|
||||||
|
class NoInsetMarkdown(Markdown):
|
||||||
|
"""Markdown with code blocks that have no padding and left-justified headings."""
|
||||||
|
|
||||||
|
elements = {
|
||||||
|
**Markdown.elements,
|
||||||
|
"fence": NoInsetCodeBlock,
|
||||||
|
"code_block": NoInsetCodeBlock,
|
||||||
|
"heading_open": LeftHeading,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class MarkdownStream:
|
class MarkdownStream:
|
||||||
"""Streaming markdown renderer that progressively displays content with a live updating window.
|
"""Streaming markdown renderer that progressively displays content with a live updating window.
|
||||||
|
|
||||||
@@ -72,9 +115,9 @@ class MarkdownStream:
|
|||||||
else:
|
else:
|
||||||
self.mdargs = dict()
|
self.mdargs = dict()
|
||||||
|
|
||||||
# Initialize rich Live display with empty text
|
# Defer Live creation until the first update.
|
||||||
self.live = Live(Text(""), refresh_per_second=1.0 / self.min_delay)
|
self.live = None
|
||||||
self.live.start()
|
self._live_started = False
|
||||||
|
|
||||||
def _render_markdown_to_lines(self, text):
|
def _render_markdown_to_lines(self, text):
|
||||||
"""Render markdown text to a list of lines.
|
"""Render markdown text to a list of lines.
|
||||||
@@ -88,7 +131,7 @@ class MarkdownStream:
|
|||||||
# Render the markdown to a string buffer
|
# Render the markdown to a string buffer
|
||||||
string_io = io.StringIO()
|
string_io = io.StringIO()
|
||||||
console = Console(file=string_io, force_terminal=True)
|
console = Console(file=string_io, force_terminal=True)
|
||||||
markdown = Markdown(text, **self.mdargs)
|
markdown = NoInsetMarkdown(text, **self.mdargs)
|
||||||
console.print(markdown)
|
console.print(markdown)
|
||||||
output = string_io.getvalue()
|
output = string_io.getvalue()
|
||||||
|
|
||||||
@@ -120,6 +163,12 @@ class MarkdownStream:
|
|||||||
Markdown going to the console works better in terminal scrollback buffers.
|
Markdown going to the console works better in terminal scrollback buffers.
|
||||||
The live window doesn't play nice with terminal scrollback.
|
The live window doesn't play nice with terminal scrollback.
|
||||||
"""
|
"""
|
||||||
|
# On the first call, stop the spinner and start the Live renderer
|
||||||
|
if not getattr(self, "_live_started", False):
|
||||||
|
self.live = Live(Text(""), refresh_per_second=1.0 / self.min_delay)
|
||||||
|
self.live.start()
|
||||||
|
self._live_started = True
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
# Throttle updates to maintain smooth rendering
|
# Throttle updates to maintain smooth rendering
|
||||||
if not final and now - self.when < self.min_delay:
|
if not final and now - self.when < self.min_delay:
|
||||||
@@ -186,6 +235,7 @@ if __name__ == "__main__":
|
|||||||
_text = _text * 10
|
_text = _text * 10
|
||||||
|
|
||||||
pm = MarkdownStream()
|
pm = MarkdownStream()
|
||||||
|
print("Using NoInsetMarkdown for code blocks with padding=0")
|
||||||
for i in range(6, len(_text), 5):
|
for i in range(6, len(_text), 5):
|
||||||
pm.update(_text[:i])
|
pm.update(_text[:i])
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|||||||
459
aider/models.py
459
aider/models.py
@@ -5,10 +5,10 @@ import json
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass, fields
|
from dataclasses import dataclass, fields
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
@@ -16,9 +16,12 @@ import json5
|
|||||||
import yaml
|
import yaml
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from aider import __version__
|
||||||
from aider.dump import dump # noqa: F401
|
from aider.dump import dump # noqa: F401
|
||||||
from aider.llm import litellm
|
from aider.llm import litellm
|
||||||
|
from aider.openrouter import OpenRouterModelManager
|
||||||
from aider.sendchat import ensure_alternating_roles, sanity_check_messages
|
from aider.sendchat import ensure_alternating_roles, sanity_check_messages
|
||||||
|
from aider.utils import check_pip_install_extra
|
||||||
|
|
||||||
RETRY_TIMEOUT = 60
|
RETRY_TIMEOUT = 60
|
||||||
|
|
||||||
@@ -69,6 +72,8 @@ claude-3-opus-20240229
|
|||||||
claude-3-sonnet-20240229
|
claude-3-sonnet-20240229
|
||||||
claude-3-5-sonnet-20240620
|
claude-3-5-sonnet-20240620
|
||||||
claude-3-5-sonnet-20241022
|
claude-3-5-sonnet-20241022
|
||||||
|
claude-sonnet-4-20250514
|
||||||
|
claude-opus-4-20250514
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ANTHROPIC_MODELS = [ln.strip() for ln in ANTHROPIC_MODELS.splitlines() if ln.strip()]
|
ANTHROPIC_MODELS = [ln.strip() for ln in ANTHROPIC_MODELS.splitlines() if ln.strip()]
|
||||||
@@ -76,9 +81,9 @@ ANTHROPIC_MODELS = [ln.strip() for ln in ANTHROPIC_MODELS.splitlines() if ln.str
|
|||||||
# Mapping of model aliases to their canonical names
|
# Mapping of model aliases to their canonical names
|
||||||
MODEL_ALIASES = {
|
MODEL_ALIASES = {
|
||||||
# Claude models
|
# Claude models
|
||||||
"sonnet": "claude-3-5-sonnet-20241022",
|
"sonnet": "anthropic/claude-sonnet-4-20250514",
|
||||||
"haiku": "claude-3-5-haiku-20241022",
|
"haiku": "claude-3-5-haiku-20241022",
|
||||||
"opus": "claude-3-opus-20240229",
|
"opus": "claude-opus-4-20250514",
|
||||||
# GPT models
|
# GPT models
|
||||||
"4": "gpt-4-0613",
|
"4": "gpt-4-0613",
|
||||||
"4o": "gpt-4o",
|
"4o": "gpt-4o",
|
||||||
@@ -88,8 +93,14 @@ MODEL_ALIASES = {
|
|||||||
"3": "gpt-3.5-turbo",
|
"3": "gpt-3.5-turbo",
|
||||||
# Other models
|
# Other models
|
||||||
"deepseek": "deepseek/deepseek-chat",
|
"deepseek": "deepseek/deepseek-chat",
|
||||||
|
"flash": "gemini/gemini-2.5-flash",
|
||||||
|
"quasar": "openrouter/openrouter/quasar-alpha",
|
||||||
"r1": "deepseek/deepseek-reasoner",
|
"r1": "deepseek/deepseek-reasoner",
|
||||||
"flash": "gemini/gemini-2.0-flash-exp",
|
"gemini-2.5-pro": "gemini/gemini-2.5-pro",
|
||||||
|
"gemini": "gemini/gemini-2.5-pro",
|
||||||
|
"gemini-exp": "gemini/gemini-2.5-pro-exp-03-25",
|
||||||
|
"grok3": "xai/grok-3-beta",
|
||||||
|
"optimus": "openrouter/openrouter/optimus-alpha",
|
||||||
}
|
}
|
||||||
# Model metadata loaded from resources and user's files.
|
# Model metadata loaded from resources and user's files.
|
||||||
|
|
||||||
@@ -103,6 +114,7 @@ class ModelSettings:
|
|||||||
use_repo_map: bool = False
|
use_repo_map: bool = False
|
||||||
send_undo_reply: bool = False
|
send_undo_reply: bool = False
|
||||||
lazy: bool = False
|
lazy: bool = False
|
||||||
|
overeager: bool = False
|
||||||
reminder: str = "user"
|
reminder: str = "user"
|
||||||
examples_as_sys_msg: bool = False
|
examples_as_sys_msg: bool = False
|
||||||
extra_params: Optional[dict] = None
|
extra_params: Optional[dict] = None
|
||||||
@@ -113,8 +125,10 @@ class ModelSettings:
|
|||||||
streaming: bool = True
|
streaming: bool = True
|
||||||
editor_model_name: Optional[str] = None
|
editor_model_name: Optional[str] = None
|
||||||
editor_edit_format: Optional[str] = None
|
editor_edit_format: Optional[str] = None
|
||||||
remove_reasoning: Optional[str] = None
|
reasoning_tag: Optional[str] = None
|
||||||
|
remove_reasoning: Optional[str] = None # Deprecated alias for reasoning_tag
|
||||||
system_prompt_prefix: Optional[str] = None
|
system_prompt_prefix: Optional[str] = None
|
||||||
|
accepts_settings: Optional[list] = None
|
||||||
|
|
||||||
|
|
||||||
# Load model settings from package resource
|
# Load model settings from package resource
|
||||||
@@ -137,23 +151,42 @@ class ModelInfoManager:
|
|||||||
self.cache_file = self.cache_dir / "model_prices_and_context_window.json"
|
self.cache_file = self.cache_dir / "model_prices_and_context_window.json"
|
||||||
self.content = None
|
self.content = None
|
||||||
self.local_model_metadata = {}
|
self.local_model_metadata = {}
|
||||||
self._load_cache()
|
self.verify_ssl = True
|
||||||
|
self._cache_loaded = False
|
||||||
|
|
||||||
|
# Manager for the cached OpenRouter model database
|
||||||
|
self.openrouter_manager = OpenRouterModelManager()
|
||||||
|
|
||||||
|
def set_verify_ssl(self, verify_ssl):
|
||||||
|
self.verify_ssl = verify_ssl
|
||||||
|
if hasattr(self, "openrouter_manager"):
|
||||||
|
self.openrouter_manager.set_verify_ssl(verify_ssl)
|
||||||
|
|
||||||
def _load_cache(self):
|
def _load_cache(self):
|
||||||
|
if self._cache_loaded:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
if self.cache_file.exists():
|
if self.cache_file.exists():
|
||||||
cache_age = time.time() - self.cache_file.stat().st_mtime
|
cache_age = time.time() - self.cache_file.stat().st_mtime
|
||||||
if cache_age < self.CACHE_TTL:
|
if cache_age < self.CACHE_TTL:
|
||||||
|
try:
|
||||||
self.content = json.loads(self.cache_file.read_text())
|
self.content = json.loads(self.cache_file.read_text())
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# If the cache file is corrupted, treat it as missing
|
||||||
|
self.content = None
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
self._cache_loaded = True
|
||||||
|
|
||||||
def _update_cache(self):
|
def _update_cache(self):
|
||||||
try:
|
try:
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
response = requests.get(self.MODEL_INFO_URL, timeout=5)
|
# Respect the --no-verify-ssl switch
|
||||||
|
response = requests.get(self.MODEL_INFO_URL, timeout=5, verify=self.verify_ssl)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
self.content = response.json()
|
self.content = response.json()
|
||||||
try:
|
try:
|
||||||
@@ -173,6 +206,9 @@ class ModelInfoManager:
|
|||||||
if data:
|
if data:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
# Ensure cache is loaded before checking content
|
||||||
|
self._load_cache()
|
||||||
|
|
||||||
if not self.content:
|
if not self.content:
|
||||||
self._update_cache()
|
self._update_cache()
|
||||||
|
|
||||||
@@ -205,18 +241,81 @@ class ModelInfoManager:
|
|||||||
if litellm_info:
|
if litellm_info:
|
||||||
return litellm_info
|
return litellm_info
|
||||||
|
|
||||||
|
if not cached_info and model.startswith("openrouter/"):
|
||||||
|
# First try using the locally cached OpenRouter model database
|
||||||
|
openrouter_info = self.openrouter_manager.get_model_info(model)
|
||||||
|
if openrouter_info:
|
||||||
|
return openrouter_info
|
||||||
|
|
||||||
|
# Fallback to legacy web-scraping if the API cache does not contain the model
|
||||||
|
openrouter_info = self.fetch_openrouter_model_info(model)
|
||||||
|
if openrouter_info:
|
||||||
|
return openrouter_info
|
||||||
|
|
||||||
return cached_info
|
return cached_info
|
||||||
|
|
||||||
|
def fetch_openrouter_model_info(self, model):
|
||||||
|
"""
|
||||||
|
Fetch model info by scraping the openrouter model page.
|
||||||
|
Expected URL: https://openrouter.ai/<model_route>
|
||||||
|
Example: openrouter/qwen/qwen-2.5-72b-instruct:free
|
||||||
|
Returns a dict with keys: max_tokens, max_input_tokens, max_output_tokens,
|
||||||
|
input_cost_per_token, output_cost_per_token.
|
||||||
|
"""
|
||||||
|
url_part = model[len("openrouter/") :]
|
||||||
|
url = "https://openrouter.ai/" + url_part
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
|
||||||
|
response = requests.get(url, timeout=5, verify=self.verify_ssl)
|
||||||
|
if response.status_code != 200:
|
||||||
|
return {}
|
||||||
|
html = response.text
|
||||||
|
import re
|
||||||
|
|
||||||
|
if re.search(
|
||||||
|
rf"The model\s*.*{re.escape(url_part)}.* is not available", html, re.IGNORECASE
|
||||||
|
):
|
||||||
|
print(f"\033[91mError: Model '{url_part}' is not available\033[0m")
|
||||||
|
return {}
|
||||||
|
text = re.sub(r"<[^>]+>", " ", html)
|
||||||
|
context_match = re.search(r"([\d,]+)\s*context", text)
|
||||||
|
if context_match:
|
||||||
|
context_str = context_match.group(1).replace(",", "")
|
||||||
|
context_size = int(context_str)
|
||||||
|
else:
|
||||||
|
context_size = None
|
||||||
|
input_cost_match = re.search(r"\$\s*([\d.]+)\s*/M input tokens", text, re.IGNORECASE)
|
||||||
|
output_cost_match = re.search(r"\$\s*([\d.]+)\s*/M output tokens", text, re.IGNORECASE)
|
||||||
|
input_cost = float(input_cost_match.group(1)) / 1000000 if input_cost_match else None
|
||||||
|
output_cost = float(output_cost_match.group(1)) / 1000000 if output_cost_match else None
|
||||||
|
if context_size is None or input_cost is None or output_cost is None:
|
||||||
|
return {}
|
||||||
|
params = {
|
||||||
|
"max_input_tokens": context_size,
|
||||||
|
"max_tokens": context_size,
|
||||||
|
"max_output_tokens": context_size,
|
||||||
|
"input_cost_per_token": input_cost,
|
||||||
|
"output_cost_per_token": output_cost,
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
except Exception as e:
|
||||||
|
print("Error fetching openrouter info:", str(e))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
model_info_manager = ModelInfoManager()
|
model_info_manager = ModelInfoManager()
|
||||||
|
|
||||||
|
|
||||||
class Model(ModelSettings):
|
class Model(ModelSettings):
|
||||||
def __init__(self, model, weak_model=None, editor_model=None, editor_edit_format=None):
|
def __init__(
|
||||||
|
self, model, weak_model=None, editor_model=None, editor_edit_format=None, verbose=False
|
||||||
|
):
|
||||||
# Map any alias to its canonical name
|
# Map any alias to its canonical name
|
||||||
model = MODEL_ALIASES.get(model, model)
|
model = MODEL_ALIASES.get(model, model)
|
||||||
|
|
||||||
self.name = model
|
self.name = model
|
||||||
|
self.verbose = verbose
|
||||||
|
|
||||||
self.max_chat_history_tokens = 1024
|
self.max_chat_history_tokens = 1024
|
||||||
self.weak_model = None
|
self.weak_model = None
|
||||||
@@ -259,6 +358,11 @@ class Model(ModelSettings):
|
|||||||
val = getattr(source, field.name)
|
val = getattr(source, field.name)
|
||||||
setattr(self, field.name, val)
|
setattr(self, field.name, val)
|
||||||
|
|
||||||
|
# Handle backward compatibility: if remove_reasoning is set but reasoning_tag isn't,
|
||||||
|
# use remove_reasoning's value for reasoning_tag
|
||||||
|
if self.reasoning_tag is None and self.remove_reasoning is not None:
|
||||||
|
self.reasoning_tag = self.remove_reasoning
|
||||||
|
|
||||||
def configure_model_settings(self, model):
|
def configure_model_settings(self, model):
|
||||||
# Look for exact model match
|
# Look for exact model match
|
||||||
exact_match = False
|
exact_match = False
|
||||||
@@ -269,6 +373,10 @@ class Model(ModelSettings):
|
|||||||
exact_match = True
|
exact_match = True
|
||||||
break # Continue to apply overrides
|
break # Continue to apply overrides
|
||||||
|
|
||||||
|
# Initialize accepts_settings if it's None
|
||||||
|
if self.accepts_settings is None:
|
||||||
|
self.accepts_settings = []
|
||||||
|
|
||||||
model = model.lower()
|
model = model.lower()
|
||||||
|
|
||||||
# If no exact match, try generic settings
|
# If no exact match, try generic settings
|
||||||
@@ -276,7 +384,11 @@ class Model(ModelSettings):
|
|||||||
self.apply_generic_model_settings(model)
|
self.apply_generic_model_settings(model)
|
||||||
|
|
||||||
# Apply override settings last if they exist
|
# Apply override settings last if they exist
|
||||||
if self.extra_model_settings and self.extra_model_settings.extra_params:
|
if (
|
||||||
|
self.extra_model_settings
|
||||||
|
and self.extra_model_settings.extra_params
|
||||||
|
and self.extra_model_settings.name == "aider/extra_params"
|
||||||
|
):
|
||||||
# Initialize extra_params if it doesn't exist
|
# Initialize extra_params if it doesn't exist
|
||||||
if not self.extra_params:
|
if not self.extra_params:
|
||||||
self.extra_params = {}
|
self.extra_params = {}
|
||||||
@@ -290,12 +402,38 @@ class Model(ModelSettings):
|
|||||||
# For non-dict values, simply update
|
# For non-dict values, simply update
|
||||||
self.extra_params[key] = value
|
self.extra_params[key] = value
|
||||||
|
|
||||||
|
# Ensure OpenRouter models accept thinking_tokens and reasoning_effort
|
||||||
|
if self.name.startswith("openrouter/"):
|
||||||
|
if self.accepts_settings is None:
|
||||||
|
self.accepts_settings = []
|
||||||
|
if "thinking_tokens" not in self.accepts_settings:
|
||||||
|
self.accepts_settings.append("thinking_tokens")
|
||||||
|
if "reasoning_effort" not in self.accepts_settings:
|
||||||
|
self.accepts_settings.append("reasoning_effort")
|
||||||
|
|
||||||
def apply_generic_model_settings(self, model):
|
def apply_generic_model_settings(self, model):
|
||||||
if "/o3-mini" in model:
|
if "/o3-mini" in model:
|
||||||
self.edit_format = "diff"
|
self.edit_format = "diff"
|
||||||
self.use_repo_map = True
|
self.use_repo_map = True
|
||||||
self.use_temperature = False
|
self.use_temperature = False
|
||||||
self.system_prompt_prefix = "Formatting re-enabled. "
|
self.system_prompt_prefix = "Formatting re-enabled. "
|
||||||
|
self.system_prompt_prefix = "Formatting re-enabled. "
|
||||||
|
if "reasoning_effort" not in self.accepts_settings:
|
||||||
|
self.accepts_settings.append("reasoning_effort")
|
||||||
|
return # <--
|
||||||
|
|
||||||
|
if "gpt-4.1-mini" in model:
|
||||||
|
self.edit_format = "diff"
|
||||||
|
self.use_repo_map = True
|
||||||
|
self.reminder = "sys"
|
||||||
|
self.examples_as_sys_msg = False
|
||||||
|
return # <--
|
||||||
|
|
||||||
|
if "gpt-4.1" in model:
|
||||||
|
self.edit_format = "diff"
|
||||||
|
self.use_repo_map = True
|
||||||
|
self.reminder = "sys"
|
||||||
|
self.examples_as_sys_msg = False
|
||||||
return # <--
|
return # <--
|
||||||
|
|
||||||
if "/o1-mini" in model:
|
if "/o1-mini" in model:
|
||||||
@@ -317,6 +455,8 @@ class Model(ModelSettings):
|
|||||||
self.use_temperature = False
|
self.use_temperature = False
|
||||||
self.streaming = False
|
self.streaming = False
|
||||||
self.system_prompt_prefix = "Formatting re-enabled. "
|
self.system_prompt_prefix = "Formatting re-enabled. "
|
||||||
|
if "reasoning_effort" not in self.accepts_settings:
|
||||||
|
self.accepts_settings.append("reasoning_effort")
|
||||||
return # <--
|
return # <--
|
||||||
|
|
||||||
if "deepseek" in model and "v3" in model:
|
if "deepseek" in model and "v3" in model:
|
||||||
@@ -331,7 +471,7 @@ class Model(ModelSettings):
|
|||||||
self.use_repo_map = True
|
self.use_repo_map = True
|
||||||
self.examples_as_sys_msg = True
|
self.examples_as_sys_msg = True
|
||||||
self.use_temperature = False
|
self.use_temperature = False
|
||||||
self.remove_reasoning = "think"
|
self.reasoning_tag = "think"
|
||||||
return # <--
|
return # <--
|
||||||
|
|
||||||
if ("llama3" in model or "llama-3" in model) and "70b" in model:
|
if ("llama3" in model or "llama-3" in model) and "70b" in model:
|
||||||
@@ -357,6 +497,15 @@ class Model(ModelSettings):
|
|||||||
self.reminder = "sys"
|
self.reminder = "sys"
|
||||||
return # <--
|
return # <--
|
||||||
|
|
||||||
|
if "3-7-sonnet" in model:
|
||||||
|
self.edit_format = "diff"
|
||||||
|
self.use_repo_map = True
|
||||||
|
self.examples_as_sys_msg = True
|
||||||
|
self.reminder = "user"
|
||||||
|
if "thinking_tokens" not in self.accepts_settings:
|
||||||
|
self.accepts_settings.append("thinking_tokens")
|
||||||
|
return # <--
|
||||||
|
|
||||||
if "3.5-sonnet" in model or "3-5-sonnet" in model:
|
if "3.5-sonnet" in model or "3-5-sonnet" in model:
|
||||||
self.edit_format = "diff"
|
self.edit_format = "diff"
|
||||||
self.use_repo_map = True
|
self.use_repo_map = True
|
||||||
@@ -380,6 +529,24 @@ class Model(ModelSettings):
|
|||||||
self.use_repo_map = True
|
self.use_repo_map = True
|
||||||
return # <--
|
return # <--
|
||||||
|
|
||||||
|
if "qwq" in model and "32b" in model and "preview" not in model:
|
||||||
|
self.edit_format = "diff"
|
||||||
|
self.editor_edit_format = "editor-diff"
|
||||||
|
self.use_repo_map = True
|
||||||
|
self.reasoning_tag = "think"
|
||||||
|
self.examples_as_sys_msg = True
|
||||||
|
self.use_temperature = 0.6
|
||||||
|
self.extra_params = dict(top_p=0.95)
|
||||||
|
return # <--
|
||||||
|
|
||||||
|
if "qwen3" in model and "235b" in model:
|
||||||
|
self.edit_format = "diff"
|
||||||
|
self.use_repo_map = True
|
||||||
|
self.system_prompt_prefix = "/no_think"
|
||||||
|
self.use_temperature = 0.7
|
||||||
|
self.extra_params = {"top_p": 0.8, "top_k": 20, "min_p": 0.0}
|
||||||
|
return # <--
|
||||||
|
|
||||||
# use the defaults
|
# use the defaults
|
||||||
if self.edit_format == "diff":
|
if self.edit_format == "diff":
|
||||||
self.use_repo_map = True
|
self.use_repo_map = True
|
||||||
@@ -427,6 +594,8 @@ class Model(ModelSettings):
|
|||||||
|
|
||||||
if not self.editor_edit_format:
|
if not self.editor_edit_format:
|
||||||
self.editor_edit_format = self.editor_model.edit_format
|
self.editor_edit_format = self.editor_model.edit_format
|
||||||
|
if self.editor_edit_format in ("diff", "whole", "diff-fenced"):
|
||||||
|
self.editor_edit_format = "editor-" + self.editor_edit_format
|
||||||
|
|
||||||
return self.editor_model
|
return self.editor_model
|
||||||
|
|
||||||
@@ -535,6 +704,21 @@ class Model(ModelSettings):
|
|||||||
|
|
||||||
model = self.name
|
model = self.name
|
||||||
res = litellm.validate_environment(model)
|
res = litellm.validate_environment(model)
|
||||||
|
|
||||||
|
# If missing AWS credential keys but AWS_PROFILE is set, consider AWS credentials valid
|
||||||
|
if res["missing_keys"] and any(
|
||||||
|
key in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"] for key in res["missing_keys"]
|
||||||
|
):
|
||||||
|
if model.startswith("bedrock/") or model.startswith("us.anthropic."):
|
||||||
|
if os.environ.get("AWS_PROFILE"):
|
||||||
|
res["missing_keys"] = [
|
||||||
|
k
|
||||||
|
for k in res["missing_keys"]
|
||||||
|
if k not in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
|
||||||
|
]
|
||||||
|
if not res["missing_keys"]:
|
||||||
|
res["keys_in_environment"] = True
|
||||||
|
|
||||||
if res["keys_in_environment"]:
|
if res["keys_in_environment"]:
|
||||||
return res
|
return res
|
||||||
if res["missing_keys"]:
|
if res["missing_keys"]:
|
||||||
@@ -559,6 +743,140 @@ class Model(ModelSettings):
|
|||||||
map_tokens = max(map_tokens, 1024)
|
map_tokens = max(map_tokens, 1024)
|
||||||
return map_tokens
|
return map_tokens
|
||||||
|
|
||||||
|
def set_reasoning_effort(self, effort):
|
||||||
|
"""Set the reasoning effort parameter for models that support it"""
|
||||||
|
if effort is not None:
|
||||||
|
if self.name.startswith("openrouter/"):
|
||||||
|
if not self.extra_params:
|
||||||
|
self.extra_params = {}
|
||||||
|
if "extra_body" not in self.extra_params:
|
||||||
|
self.extra_params["extra_body"] = {}
|
||||||
|
self.extra_params["extra_body"]["reasoning"] = {"effort": effort}
|
||||||
|
else:
|
||||||
|
if not self.extra_params:
|
||||||
|
self.extra_params = {}
|
||||||
|
if "extra_body" not in self.extra_params:
|
||||||
|
self.extra_params["extra_body"] = {}
|
||||||
|
self.extra_params["extra_body"]["reasoning_effort"] = effort
|
||||||
|
|
||||||
|
def parse_token_value(self, value):
|
||||||
|
"""
|
||||||
|
Parse a token value string into an integer.
|
||||||
|
Accepts formats: 8096, "8k", "10.5k", "0.5M", "10K", etc.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: String or int token value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Integer token value
|
||||||
|
"""
|
||||||
|
if isinstance(value, int):
|
||||||
|
return value
|
||||||
|
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return int(value) # Try to convert to int
|
||||||
|
|
||||||
|
value = value.strip().upper()
|
||||||
|
|
||||||
|
if value.endswith("K"):
|
||||||
|
multiplier = 1024
|
||||||
|
value = value[:-1]
|
||||||
|
elif value.endswith("M"):
|
||||||
|
multiplier = 1024 * 1024
|
||||||
|
value = value[:-1]
|
||||||
|
else:
|
||||||
|
multiplier = 1
|
||||||
|
|
||||||
|
# Convert to float first to handle decimal values like "10.5k"
|
||||||
|
return int(float(value) * multiplier)
|
||||||
|
|
||||||
|
def set_thinking_tokens(self, value):
|
||||||
|
"""
|
||||||
|
Set the thinking token budget for models that support it.
|
||||||
|
Accepts formats: 8096, "8k", "10.5k", "0.5M", "10K", etc.
|
||||||
|
Pass "0" to disable thinking tokens.
|
||||||
|
"""
|
||||||
|
if value is not None:
|
||||||
|
num_tokens = self.parse_token_value(value)
|
||||||
|
self.use_temperature = False
|
||||||
|
if not self.extra_params:
|
||||||
|
self.extra_params = {}
|
||||||
|
|
||||||
|
# OpenRouter models use 'reasoning' instead of 'thinking'
|
||||||
|
if self.name.startswith("openrouter/"):
|
||||||
|
if "extra_body" not in self.extra_params:
|
||||||
|
self.extra_params["extra_body"] = {}
|
||||||
|
if num_tokens > 0:
|
||||||
|
self.extra_params["extra_body"]["reasoning"] = {"max_tokens": num_tokens}
|
||||||
|
else:
|
||||||
|
if "reasoning" in self.extra_params["extra_body"]:
|
||||||
|
del self.extra_params["extra_body"]["reasoning"]
|
||||||
|
else:
|
||||||
|
if num_tokens > 0:
|
||||||
|
self.extra_params["thinking"] = {"type": "enabled", "budget_tokens": num_tokens}
|
||||||
|
else:
|
||||||
|
if "thinking" in self.extra_params:
|
||||||
|
del self.extra_params["thinking"]
|
||||||
|
|
||||||
|
def get_raw_thinking_tokens(self):
|
||||||
|
"""Get formatted thinking token budget if available"""
|
||||||
|
budget = None
|
||||||
|
|
||||||
|
if self.extra_params:
|
||||||
|
# Check for OpenRouter reasoning format
|
||||||
|
if self.name.startswith("openrouter/"):
|
||||||
|
if (
|
||||||
|
"extra_body" in self.extra_params
|
||||||
|
and "reasoning" in self.extra_params["extra_body"]
|
||||||
|
and "max_tokens" in self.extra_params["extra_body"]["reasoning"]
|
||||||
|
):
|
||||||
|
budget = self.extra_params["extra_body"]["reasoning"]["max_tokens"]
|
||||||
|
# Check for standard thinking format
|
||||||
|
elif (
|
||||||
|
"thinking" in self.extra_params and "budget_tokens" in self.extra_params["thinking"]
|
||||||
|
):
|
||||||
|
budget = self.extra_params["thinking"]["budget_tokens"]
|
||||||
|
|
||||||
|
return budget
|
||||||
|
|
||||||
|
def get_thinking_tokens(self):
|
||||||
|
budget = self.get_raw_thinking_tokens()
|
||||||
|
|
||||||
|
if budget is not None:
|
||||||
|
# Format as xx.yK for thousands, xx.yM for millions
|
||||||
|
if budget >= 1024 * 1024:
|
||||||
|
value = budget / (1024 * 1024)
|
||||||
|
if value == int(value):
|
||||||
|
return f"{int(value)}M"
|
||||||
|
else:
|
||||||
|
return f"{value:.1f}M"
|
||||||
|
else:
|
||||||
|
value = budget / 1024
|
||||||
|
if value == int(value):
|
||||||
|
return f"{int(value)}k"
|
||||||
|
else:
|
||||||
|
return f"{value:.1f}k"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_reasoning_effort(self):
|
||||||
|
"""Get reasoning effort value if available"""
|
||||||
|
if self.extra_params:
|
||||||
|
# Check for OpenRouter reasoning format
|
||||||
|
if self.name.startswith("openrouter/"):
|
||||||
|
if (
|
||||||
|
"extra_body" in self.extra_params
|
||||||
|
and "reasoning" in self.extra_params["extra_body"]
|
||||||
|
and "effort" in self.extra_params["extra_body"]["reasoning"]
|
||||||
|
):
|
||||||
|
return self.extra_params["extra_body"]["reasoning"]["effort"]
|
||||||
|
# Check for standard reasoning_effort format (e.g. in extra_body)
|
||||||
|
elif (
|
||||||
|
"extra_body" in self.extra_params
|
||||||
|
and "reasoning_effort" in self.extra_params["extra_body"]
|
||||||
|
):
|
||||||
|
return self.extra_params["extra_body"]["reasoning_effort"]
|
||||||
|
return None
|
||||||
|
|
||||||
def is_deepseek_r1(self):
|
def is_deepseek_r1(self):
|
||||||
name = self.name.lower()
|
name = self.name.lower()
|
||||||
if "deepseek" not in name:
|
if "deepseek" not in name:
|
||||||
@@ -568,6 +886,57 @@ class Model(ModelSettings):
|
|||||||
def is_ollama(self):
|
def is_ollama(self):
|
||||||
return self.name.startswith("ollama/") or self.name.startswith("ollama_chat/")
|
return self.name.startswith("ollama/") or self.name.startswith("ollama_chat/")
|
||||||
|
|
||||||
|
def github_copilot_token_to_open_ai_key(self, extra_headers):
|
||||||
|
# check to see if there's an openai api key
|
||||||
|
# If so, check to see if it's expire
|
||||||
|
openai_api_key = "OPENAI_API_KEY"
|
||||||
|
|
||||||
|
if openai_api_key not in os.environ or (
|
||||||
|
int(dict(x.split("=") for x in os.environ[openai_api_key].split(";"))["exp"])
|
||||||
|
< int(datetime.now().timestamp())
|
||||||
|
):
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class GitHubCopilotTokenError(Exception):
|
||||||
|
"""Custom exception for GitHub Copilot token-related errors."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Validate GitHub Copilot token exists
|
||||||
|
if "GITHUB_COPILOT_TOKEN" not in os.environ:
|
||||||
|
raise KeyError("GITHUB_COPILOT_TOKEN environment variable not found")
|
||||||
|
|
||||||
|
github_token = os.environ["GITHUB_COPILOT_TOKEN"]
|
||||||
|
if not github_token.strip():
|
||||||
|
raise KeyError("GITHUB_COPILOT_TOKEN environment variable is empty")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {os.environ['GITHUB_COPILOT_TOKEN']}",
|
||||||
|
"Editor-Version": extra_headers["Editor-Version"],
|
||||||
|
"Copilot-Integration-Id": extra_headers["Copilot-Integration-Id"],
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
url = "https://api.github.com/copilot_internal/v2/token"
|
||||||
|
res = requests.get(url, headers=headers)
|
||||||
|
if res.status_code != 200:
|
||||||
|
safe_headers = {k: v for k, v in headers.items() if k != "Authorization"}
|
||||||
|
token_preview = github_token[:5] + "..." if len(github_token) >= 5 else github_token
|
||||||
|
safe_headers["Authorization"] = f"Bearer {token_preview}"
|
||||||
|
raise GitHubCopilotTokenError(
|
||||||
|
f"GitHub Copilot API request failed (Status: {res.status_code})\n"
|
||||||
|
f"URL: {url}\n"
|
||||||
|
f"Headers: {json.dumps(safe_headers, indent=2)}\n"
|
||||||
|
f"JSON: {res.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
response_data = res.json()
|
||||||
|
token = response_data.get("token")
|
||||||
|
if not token:
|
||||||
|
raise GitHubCopilotTokenError("Response missing 'token' field")
|
||||||
|
|
||||||
|
os.environ[openai_api_key] = token
|
||||||
|
|
||||||
def send_completion(self, messages, functions, stream, temperature=None):
|
def send_completion(self, messages, functions, stream, temperature=None):
|
||||||
if os.environ.get("AIDER_SANITY_CHECK_TURNS"):
|
if os.environ.get("AIDER_SANITY_CHECK_TURNS"):
|
||||||
sanity_check_messages(messages)
|
sanity_check_messages(messages)
|
||||||
@@ -577,7 +946,6 @@ class Model(ModelSettings):
|
|||||||
|
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
model=self.name,
|
model=self.name,
|
||||||
messages=messages,
|
|
||||||
stream=stream,
|
stream=stream,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -606,17 +974,23 @@ class Model(ModelSettings):
|
|||||||
hash_object = hashlib.sha1(key)
|
hash_object = hashlib.sha1(key)
|
||||||
if "timeout" not in kwargs:
|
if "timeout" not in kwargs:
|
||||||
kwargs["timeout"] = request_timeout
|
kwargs["timeout"] = request_timeout
|
||||||
|
if self.verbose:
|
||||||
|
dump(kwargs)
|
||||||
|
kwargs["messages"] = messages
|
||||||
|
|
||||||
|
# Are we using github copilot?
|
||||||
|
if "GITHUB_COPILOT_TOKEN" in os.environ:
|
||||||
|
if "extra_headers" not in kwargs:
|
||||||
|
kwargs["extra_headers"] = {
|
||||||
|
"Editor-Version": f"aider/{__version__}",
|
||||||
|
"Copilot-Integration-Id": "vscode-chat",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.github_copilot_token_to_open_ai_key(kwargs["extra_headers"])
|
||||||
|
|
||||||
res = litellm.completion(**kwargs)
|
res = litellm.completion(**kwargs)
|
||||||
return hash_object, res
|
return hash_object, res
|
||||||
|
|
||||||
def remove_reasoning_content(self, res):
|
|
||||||
if not self.remove_reasoning:
|
|
||||||
return res
|
|
||||||
|
|
||||||
pattern = f"<{self.remove_reasoning}>.*?</{self.remove_reasoning}>"
|
|
||||||
res = re.sub(pattern, "", res, flags=re.DOTALL).strip()
|
|
||||||
return res
|
|
||||||
|
|
||||||
def simple_send_with_retries(self, messages):
|
def simple_send_with_retries(self, messages):
|
||||||
from aider.exceptions import LiteLLMExceptions
|
from aider.exceptions import LiteLLMExceptions
|
||||||
|
|
||||||
@@ -625,6 +999,9 @@ class Model(ModelSettings):
|
|||||||
messages = ensure_alternating_roles(messages)
|
messages = ensure_alternating_roles(messages)
|
||||||
retry_delay = 0.125
|
retry_delay = 0.125
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
dump(messages)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
kwargs = {
|
kwargs = {
|
||||||
@@ -637,7 +1014,9 @@ class Model(ModelSettings):
|
|||||||
if not response or not hasattr(response, "choices") or not response.choices:
|
if not response or not hasattr(response, "choices") or not response.choices:
|
||||||
return None
|
return None
|
||||||
res = response.choices[0].message.content
|
res = response.choices[0].message.content
|
||||||
return self.remove_reasoning_content(res)
|
from aider.reasoning_tags import remove_reasoning_content
|
||||||
|
|
||||||
|
return remove_reasoning_content(res, self.reasoning_tag)
|
||||||
|
|
||||||
except litellm_ex.exceptions_tuple() as err:
|
except litellm_ex.exceptions_tuple() as err:
|
||||||
ex_info = litellm_ex.get_ex_info(err)
|
ex_info = litellm_ex.get_ex_info(err)
|
||||||
@@ -673,12 +1052,10 @@ def register_models(model_settings_fnames):
|
|||||||
|
|
||||||
for model_settings_dict in model_settings_list:
|
for model_settings_dict in model_settings_list:
|
||||||
model_settings = ModelSettings(**model_settings_dict)
|
model_settings = ModelSettings(**model_settings_dict)
|
||||||
existing_model_settings = next(
|
|
||||||
(ms for ms in MODEL_SETTINGS if ms.name == model_settings.name), None
|
|
||||||
)
|
|
||||||
|
|
||||||
if existing_model_settings:
|
# Remove all existing settings for this model name
|
||||||
MODEL_SETTINGS.remove(existing_model_settings)
|
MODEL_SETTINGS[:] = [ms for ms in MODEL_SETTINGS if ms.name != model_settings.name]
|
||||||
|
# Add the new settings
|
||||||
MODEL_SETTINGS.append(model_settings)
|
MODEL_SETTINGS.append(model_settings)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Error loading model settings from {model_settings_fname}: {e}")
|
raise Exception(f"Error loading model settings from {model_settings_fname}: {e}")
|
||||||
@@ -760,6 +1137,9 @@ def sanity_check_model(io, model):
|
|||||||
show = True
|
show = True
|
||||||
io.tool_warning(f"Warning for {model}: Unknown which environment variables are required.")
|
io.tool_warning(f"Warning for {model}: Unknown which environment variables are required.")
|
||||||
|
|
||||||
|
# Check for model-specific dependencies
|
||||||
|
check_for_dependencies(io, model.name)
|
||||||
|
|
||||||
if not model.info:
|
if not model.info:
|
||||||
show = True
|
show = True
|
||||||
io.tool_warning(
|
io.tool_warning(
|
||||||
@@ -775,11 +1155,38 @@ def sanity_check_model(io, model):
|
|||||||
return show
|
return show
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_dependencies(io, model_name):
|
||||||
|
"""
|
||||||
|
Check for model-specific dependencies and install them if needed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
io: The IO object for user interaction
|
||||||
|
model_name: The name of the model to check dependencies for
|
||||||
|
"""
|
||||||
|
# Check if this is a Bedrock model and ensure boto3 is installed
|
||||||
|
if model_name.startswith("bedrock/"):
|
||||||
|
check_pip_install_extra(
|
||||||
|
io, "boto3", "AWS Bedrock models require the boto3 package.", ["boto3"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if this is a Vertex AI model and ensure google-cloud-aiplatform is installed
|
||||||
|
elif model_name.startswith("vertex_ai/"):
|
||||||
|
check_pip_install_extra(
|
||||||
|
io,
|
||||||
|
"google.cloud.aiplatform",
|
||||||
|
"Google Vertex AI models require the google-cloud-aiplatform package.",
|
||||||
|
["google-cloud-aiplatform"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def fuzzy_match_models(name):
|
def fuzzy_match_models(name):
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
|
|
||||||
chat_models = set()
|
chat_models = set()
|
||||||
for orig_model, attrs in litellm.model_cost.items():
|
model_metadata = list(litellm.model_cost.items())
|
||||||
|
model_metadata += list(model_info_manager.local_model_metadata.items())
|
||||||
|
|
||||||
|
for orig_model, attrs in model_metadata:
|
||||||
model = orig_model.lower()
|
model = orig_model.lower()
|
||||||
if attrs.get("mode") != "chat":
|
if attrs.get("mode") != "chat":
|
||||||
continue
|
continue
|
||||||
|
|||||||
428
aider/onboarding.py
Normal file
428
aider/onboarding.py
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import http.server
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
import socketserver
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import webbrowser
|
||||||
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from aider import urls
|
||||||
|
from aider.io import InputOutput
|
||||||
|
|
||||||
|
|
||||||
|
def check_openrouter_tier(api_key):
|
||||||
|
"""
|
||||||
|
Checks if the user is on a free tier for OpenRouter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key: The OpenRouter API key to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A boolean indicating if the user is on a free tier (True) or paid tier (False).
|
||||||
|
Returns True if the check fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
"https://openrouter.ai/api/v1/auth/key",
|
||||||
|
headers={"Authorization": f"Bearer {api_key}"},
|
||||||
|
timeout=5, # Add a reasonable timeout
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
# According to the documentation, 'is_free_tier' will be true if the user has never paid
|
||||||
|
return data.get("data", {}).get("is_free_tier", True) # Default to True if not found
|
||||||
|
except Exception:
|
||||||
|
# If there's any error, we'll default to assuming free tier
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def try_to_select_default_model():
|
||||||
|
"""
|
||||||
|
Attempts to select a default model based on available API keys.
|
||||||
|
Checks OpenRouter tier status to select appropriate model.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The name of the selected model, or None if no suitable default is found.
|
||||||
|
"""
|
||||||
|
# Special handling for OpenRouter
|
||||||
|
openrouter_key = os.environ.get("OPENROUTER_API_KEY")
|
||||||
|
if openrouter_key:
|
||||||
|
# Check if the user is on a free tier
|
||||||
|
is_free_tier = check_openrouter_tier(openrouter_key)
|
||||||
|
if is_free_tier:
|
||||||
|
return "openrouter/deepseek/deepseek-r1:free"
|
||||||
|
else:
|
||||||
|
return "openrouter/anthropic/claude-sonnet-4"
|
||||||
|
|
||||||
|
# Select model based on other available API keys
|
||||||
|
model_key_pairs = [
|
||||||
|
("ANTHROPIC_API_KEY", "sonnet"),
|
||||||
|
("DEEPSEEK_API_KEY", "deepseek"),
|
||||||
|
("OPENAI_API_KEY", "gpt-4o"),
|
||||||
|
("GEMINI_API_KEY", "gemini/gemini-2.5-pro-exp-03-25"),
|
||||||
|
("VERTEXAI_PROJECT", "vertex_ai/gemini-2.5-pro-exp-03-25"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for env_key, model_name in model_key_pairs:
|
||||||
|
api_key_value = os.environ.get(env_key)
|
||||||
|
if api_key_value:
|
||||||
|
return model_name
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def offer_openrouter_oauth(io, analytics):
|
||||||
|
"""
|
||||||
|
Offers OpenRouter OAuth flow to the user if no API keys are found.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
io: The InputOutput object for user interaction.
|
||||||
|
analytics: The Analytics object for tracking events.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if authentication was successful, False otherwise.
|
||||||
|
"""
|
||||||
|
# No API keys found - Offer OpenRouter OAuth
|
||||||
|
io.tool_output("OpenRouter provides free and paid access to many LLMs.")
|
||||||
|
# Use confirm_ask which handles non-interactive cases
|
||||||
|
if io.confirm_ask(
|
||||||
|
"Login to OpenRouter or create a free account?",
|
||||||
|
default="y",
|
||||||
|
):
|
||||||
|
analytics.event("oauth_flow_initiated", provider="openrouter")
|
||||||
|
openrouter_key = start_openrouter_oauth_flow(io, analytics)
|
||||||
|
if openrouter_key:
|
||||||
|
# Successfully got key via OAuth, use the default OpenRouter model
|
||||||
|
# Ensure OPENROUTER_API_KEY is now set in the environment for later use
|
||||||
|
os.environ["OPENROUTER_API_KEY"] = openrouter_key
|
||||||
|
# Track OAuth success leading to model selection
|
||||||
|
analytics.event("oauth_flow_success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# OAuth failed or was cancelled by user implicitly (e.g., closing browser)
|
||||||
|
# Error messages are handled within start_openrouter_oauth_flow
|
||||||
|
analytics.event("oauth_flow_failure")
|
||||||
|
io.tool_error("OpenRouter authentication did not complete successfully.")
|
||||||
|
# Fall through to the final error message
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def select_default_model(args, io, analytics):
|
||||||
|
"""
|
||||||
|
Selects a default model based on available API keys if no model is specified.
|
||||||
|
Offers OAuth flow for OpenRouter if no keys are found.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: The command line arguments object.
|
||||||
|
io: The InputOutput object for user interaction.
|
||||||
|
analytics: The Analytics object for tracking events.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The name of the selected model, or None if no suitable default is found.
|
||||||
|
"""
|
||||||
|
if args.model:
|
||||||
|
return args.model # Model already specified
|
||||||
|
|
||||||
|
model = try_to_select_default_model()
|
||||||
|
if model:
|
||||||
|
io.tool_warning(f"Using {model} model with API key from environment.")
|
||||||
|
analytics.event("auto_model_selection", model=model)
|
||||||
|
return model
|
||||||
|
|
||||||
|
no_model_msg = "No LLM model was specified and no API keys were provided."
|
||||||
|
io.tool_warning(no_model_msg)
|
||||||
|
|
||||||
|
# Try OAuth if no model was detected
|
||||||
|
offer_openrouter_oauth(io, analytics)
|
||||||
|
|
||||||
|
# Check again after potential OAuth success
|
||||||
|
model = try_to_select_default_model()
|
||||||
|
if model:
|
||||||
|
return model
|
||||||
|
|
||||||
|
io.offer_url(urls.models_and_keys, "Open documentation URL for more info?")
|
||||||
|
|
||||||
|
|
||||||
|
# Helper function to find an available port
|
||||||
|
def find_available_port(start_port=8484, end_port=8584):
|
||||||
|
for port in range(start_port, end_port + 1):
|
||||||
|
try:
|
||||||
|
# Check if the port is available by trying to bind to it
|
||||||
|
with socketserver.TCPServer(("localhost", port), None):
|
||||||
|
return port
|
||||||
|
except OSError:
|
||||||
|
# Port is likely already in use
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# PKCE code generation
|
||||||
|
def generate_pkce_codes():
|
||||||
|
code_verifier = secrets.token_urlsafe(64)
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
hasher.update(code_verifier.encode("utf-8"))
|
||||||
|
code_challenge = base64.urlsafe_b64encode(hasher.digest()).rstrip(b"=").decode("utf-8")
|
||||||
|
return code_verifier, code_challenge
|
||||||
|
|
||||||
|
|
||||||
|
# Function to exchange the authorization code for an API key
|
||||||
|
def exchange_code_for_key(code, code_verifier, io):
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
"https://openrouter.ai/api/v1/auth/keys",
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
json={
|
||||||
|
"code": code,
|
||||||
|
"code_verifier": code_verifier,
|
||||||
|
"code_challenge_method": "S256",
|
||||||
|
},
|
||||||
|
timeout=30, # Add a timeout
|
||||||
|
)
|
||||||
|
response.raise_for_status() # Raise exception for bad status codes (4xx or 5xx)
|
||||||
|
data = response.json()
|
||||||
|
api_key = data.get("key")
|
||||||
|
if not api_key:
|
||||||
|
io.tool_error("Error: 'key' not found in OpenRouter response.")
|
||||||
|
io.tool_error(f"Response: {response.text}")
|
||||||
|
return None
|
||||||
|
return api_key
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
io.tool_error("Error: Request to OpenRouter timed out during code exchange.")
|
||||||
|
return None
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
io.tool_error(
|
||||||
|
"Error exchanging code for OpenRouter key:"
|
||||||
|
f" {e.response.status_code} {e.response.reason}"
|
||||||
|
)
|
||||||
|
io.tool_error(f"Response: {e.response.text}")
|
||||||
|
return None
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
io.tool_error(f"Error exchanging code for OpenRouter key: {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
io.tool_error(f"Unexpected error during code exchange: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Function to start the OAuth flow
|
||||||
|
def start_openrouter_oauth_flow(io, analytics):
|
||||||
|
"""Initiates the OpenRouter OAuth PKCE flow using a local server."""
|
||||||
|
|
||||||
|
port = find_available_port()
|
||||||
|
if not port:
|
||||||
|
io.tool_error("Could not find an available port between 8484 and 8584.")
|
||||||
|
io.tool_error("Please ensure a port in this range is free, or configure manually.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
callback_url = f"http://localhost:{port}/callback/aider"
|
||||||
|
auth_code = None
|
||||||
|
server_error = None
|
||||||
|
server_started = threading.Event()
|
||||||
|
shutdown_server = threading.Event()
|
||||||
|
|
||||||
|
class OAuthCallbackHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
nonlocal auth_code, server_error
|
||||||
|
parsed_path = urlparse(self.path)
|
||||||
|
if parsed_path.path == "/callback/aider":
|
||||||
|
query_params = parse_qs(parsed_path.query)
|
||||||
|
if "code" in query_params:
|
||||||
|
auth_code = query_params["code"][0]
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", "text/html")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(
|
||||||
|
b"<html><body><h1>Success!</h1>"
|
||||||
|
b"<p>Aider has received the authentication code. "
|
||||||
|
b"You can close this browser tab.</p></body></html>"
|
||||||
|
)
|
||||||
|
# Signal the main thread to shut down the server
|
||||||
|
# Signal the main thread to shut down the server
|
||||||
|
shutdown_server.set()
|
||||||
|
else:
|
||||||
|
# Redirect to aider website if 'code' is missing (e.g., user visited manually)
|
||||||
|
self.send_response(302) # Found (temporary redirect)
|
||||||
|
self.send_header("Location", urls.website)
|
||||||
|
self.end_headers()
|
||||||
|
# No need to set server_error, just redirect.
|
||||||
|
# Do NOT shut down the server here; wait for timeout or success.
|
||||||
|
else:
|
||||||
|
# Redirect anything else (e.g., favicon.ico) to the main website as well
|
||||||
|
self.send_response(302)
|
||||||
|
self.send_header("Location", urls.website)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"Not Found")
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
# Suppress server logging to keep terminal clean
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run_server():
|
||||||
|
nonlocal server_error
|
||||||
|
try:
|
||||||
|
with socketserver.TCPServer(("localhost", port), OAuthCallbackHandler) as httpd:
|
||||||
|
io.tool_output(f"Temporary server listening on {callback_url}", log_only=True)
|
||||||
|
server_started.set() # Signal that the server is ready
|
||||||
|
# Wait until shutdown is requested or timeout occurs (handled by main thread)
|
||||||
|
while not shutdown_server.is_set():
|
||||||
|
httpd.handle_request() # Handle one request at a time
|
||||||
|
# Add a small sleep to prevent busy-waiting if needed,
|
||||||
|
# though handle_request should block appropriately.
|
||||||
|
time.sleep(0.1)
|
||||||
|
io.tool_output("Shutting down temporary server.", log_only=True)
|
||||||
|
except Exception as e:
|
||||||
|
server_error = f"Failed to start or run temporary server: {e}"
|
||||||
|
server_started.set() # Signal even if failed, error will be checked
|
||||||
|
shutdown_server.set() # Ensure shutdown logic proceeds
|
||||||
|
|
||||||
|
server_thread = threading.Thread(target=run_server, daemon=True)
|
||||||
|
server_thread.start()
|
||||||
|
|
||||||
|
# Wait briefly for the server to start, or for an error
|
||||||
|
if not server_started.wait(timeout=5):
|
||||||
|
io.tool_error("Temporary authentication server failed to start in time.")
|
||||||
|
shutdown_server.set() # Ensure thread exits if it eventually starts
|
||||||
|
server_thread.join(timeout=1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Check if server failed during startup
|
||||||
|
if server_error:
|
||||||
|
io.tool_error(server_error)
|
||||||
|
shutdown_server.set() # Ensure thread exits
|
||||||
|
server_thread.join(timeout=1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Generate codes and URL
|
||||||
|
code_verifier, code_challenge = generate_pkce_codes()
|
||||||
|
auth_url_base = "https://openrouter.ai/auth"
|
||||||
|
auth_params = {
|
||||||
|
"callback_url": callback_url,
|
||||||
|
"code_challenge": code_challenge,
|
||||||
|
"code_challenge_method": "S256",
|
||||||
|
}
|
||||||
|
auth_url = f"{auth_url_base}?{'&'.join(f'{k}={v}' for k, v in auth_params.items())}"
|
||||||
|
|
||||||
|
io.tool_output("\nPlease open this URL in your browser to connect Aider with OpenRouter:")
|
||||||
|
io.tool_output()
|
||||||
|
print(auth_url)
|
||||||
|
|
||||||
|
MINUTES = 5
|
||||||
|
io.tool_output(f"\nWaiting up to {MINUTES} minutes for you to finish in the browser...")
|
||||||
|
io.tool_output("Use Control-C to interrupt.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
webbrowser.open(auth_url)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Wait for the callback to set the auth_code or for timeout/error
|
||||||
|
interrupted = False
|
||||||
|
try:
|
||||||
|
shutdown_server.wait(timeout=MINUTES * 60) # Convert minutes to seconds
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
io.tool_warning("\nOAuth flow interrupted.")
|
||||||
|
analytics.event("oauth_flow_failed", provider="openrouter", reason="user_interrupt")
|
||||||
|
interrupted = True
|
||||||
|
# Ensure the server thread is signaled to shut down
|
||||||
|
shutdown_server.set()
|
||||||
|
|
||||||
|
# Join the server thread to ensure it's cleaned up
|
||||||
|
server_thread.join(timeout=1)
|
||||||
|
|
||||||
|
if interrupted:
|
||||||
|
return None # Return None if interrupted by user
|
||||||
|
|
||||||
|
if server_error:
|
||||||
|
io.tool_error(f"Authentication failed: {server_error}")
|
||||||
|
analytics.event("oauth_flow_failed", provider="openrouter", reason=server_error)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not auth_code:
|
||||||
|
io.tool_error("Authentication with OpenRouter failed.")
|
||||||
|
analytics.event("oauth_flow_failed", provider="openrouter")
|
||||||
|
return None
|
||||||
|
|
||||||
|
io.tool_output("Completing authentication...")
|
||||||
|
analytics.event("oauth_flow_code_received", provider="openrouter")
|
||||||
|
|
||||||
|
# Exchange code for key
|
||||||
|
api_key = exchange_code_for_key(auth_code, code_verifier, io)
|
||||||
|
|
||||||
|
if api_key:
|
||||||
|
# Set env var for the current session immediately
|
||||||
|
os.environ["OPENROUTER_API_KEY"] = api_key
|
||||||
|
|
||||||
|
# Save the key to the oauth-keys.env file
|
||||||
|
try:
|
||||||
|
config_dir = os.path.expanduser("~/.aider")
|
||||||
|
os.makedirs(config_dir, exist_ok=True)
|
||||||
|
key_file = os.path.join(config_dir, "oauth-keys.env")
|
||||||
|
with open(key_file, "a", encoding="utf-8") as f:
|
||||||
|
f.write(f'OPENROUTER_API_KEY="{api_key}"\n')
|
||||||
|
|
||||||
|
io.tool_warning("Aider will load the OpenRouter key automatically in future sessions.")
|
||||||
|
io.tool_output()
|
||||||
|
|
||||||
|
analytics.event("oauth_flow_success", provider="openrouter")
|
||||||
|
return api_key
|
||||||
|
except Exception as e:
|
||||||
|
io.tool_error(f"Successfully obtained key, but failed to save it to file: {e}")
|
||||||
|
io.tool_warning("Set OPENROUTER_API_KEY environment variable for this session only.")
|
||||||
|
# Still return the key for the current session even if saving failed
|
||||||
|
analytics.event("oauth_flow_save_failed", provider="openrouter", reason=str(e))
|
||||||
|
return api_key
|
||||||
|
else:
|
||||||
|
io.tool_error("Authentication with OpenRouter failed.")
|
||||||
|
analytics.event("oauth_flow_failed", provider="openrouter", reason="code_exchange_failed")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Dummy Analytics class for testing
|
||||||
|
class DummyAnalytics:
|
||||||
|
def event(self, *args, **kwargs):
|
||||||
|
# print(f"Analytics Event: {args} {kwargs}") # Optional: print events
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function to test the OpenRouter OAuth flow."""
|
||||||
|
print("Starting OpenRouter OAuth flow test...")
|
||||||
|
|
||||||
|
# Use a real IO object for interaction
|
||||||
|
io = InputOutput(
|
||||||
|
pretty=True,
|
||||||
|
yes=False,
|
||||||
|
input_history_file=None,
|
||||||
|
chat_history_file=None,
|
||||||
|
tool_output_color="BLUE",
|
||||||
|
tool_error_color="RED",
|
||||||
|
)
|
||||||
|
# Use a dummy analytics object
|
||||||
|
analytics = DummyAnalytics()
|
||||||
|
|
||||||
|
# Ensure OPENROUTER_API_KEY is not set, to trigger the flow naturally
|
||||||
|
# (though start_openrouter_oauth_flow doesn't check this itself)
|
||||||
|
if "OPENROUTER_API_KEY" in os.environ:
|
||||||
|
print("Warning: OPENROUTER_API_KEY is already set in environment.")
|
||||||
|
# del os.environ["OPENROUTER_API_KEY"] # Optionally unset it for testing
|
||||||
|
|
||||||
|
api_key = start_openrouter_oauth_flow(io, analytics)
|
||||||
|
|
||||||
|
if api_key:
|
||||||
|
print("\nOAuth flow completed successfully!")
|
||||||
|
print(f"Obtained API Key (first 5 chars): {api_key[:5]}...")
|
||||||
|
# Be careful printing the key, even partially
|
||||||
|
else:
|
||||||
|
print("\nOAuth flow failed or was cancelled.")
|
||||||
|
|
||||||
|
print("\nOpenRouter OAuth flow test finished.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
128
aider/openrouter.py
Normal file
128
aider/openrouter.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
"""
|
||||||
|
OpenRouter model metadata caching and lookup.
|
||||||
|
|
||||||
|
This module keeps a local cached copy of the OpenRouter model list
|
||||||
|
(downloaded from ``https://openrouter.ai/api/v1/models``) and exposes a
|
||||||
|
helper class that returns metadata for a given model in a format compatible
|
||||||
|
with litellm’s ``get_model_info``.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def _cost_per_token(val: str | None) -> float | None:
|
||||||
|
"""Convert a price string (USD per token) to a float."""
|
||||||
|
if val in (None, "", "0"):
|
||||||
|
return 0.0 if val == "0" else None
|
||||||
|
try:
|
||||||
|
return float(val)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class OpenRouterModelManager:
|
||||||
|
MODELS_URL = "https://openrouter.ai/api/v1/models"
|
||||||
|
CACHE_TTL = 60 * 60 * 24 # 24 h
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.cache_dir = Path.home() / ".aider" / "caches"
|
||||||
|
self.cache_file = self.cache_dir / "openrouter_models.json"
|
||||||
|
self.content: Dict | None = None
|
||||||
|
self.verify_ssl: bool = True
|
||||||
|
self._cache_loaded = False
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Public API #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
def set_verify_ssl(self, verify_ssl: bool) -> None:
|
||||||
|
"""Enable/disable SSL verification for API requests."""
|
||||||
|
self.verify_ssl = verify_ssl
|
||||||
|
|
||||||
|
def get_model_info(self, model: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Return metadata for *model* or an empty ``dict`` when unknown.
|
||||||
|
|
||||||
|
``model`` should use the aider naming convention, e.g.
|
||||||
|
``openrouter/nousresearch/deephermes-3-mistral-24b-preview:free``.
|
||||||
|
"""
|
||||||
|
self._ensure_content()
|
||||||
|
if not self.content or "data" not in self.content:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
route = self._strip_prefix(model)
|
||||||
|
|
||||||
|
# Consider both the exact id and id without any “:suffix”.
|
||||||
|
candidates = {route}
|
||||||
|
if ":" in route:
|
||||||
|
candidates.add(route.split(":", 1)[0])
|
||||||
|
|
||||||
|
record = next((item for item in self.content["data"] if item.get("id") in candidates), None)
|
||||||
|
if not record:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
context_len = (
|
||||||
|
record.get("top_provider", {}).get("context_length")
|
||||||
|
or record.get("context_length")
|
||||||
|
or None
|
||||||
|
)
|
||||||
|
|
||||||
|
pricing = record.get("pricing", {})
|
||||||
|
return {
|
||||||
|
"max_input_tokens": context_len,
|
||||||
|
"max_tokens": context_len,
|
||||||
|
"max_output_tokens": context_len,
|
||||||
|
"input_cost_per_token": _cost_per_token(pricing.get("prompt")),
|
||||||
|
"output_cost_per_token": _cost_per_token(pricing.get("completion")),
|
||||||
|
"litellm_provider": "openrouter",
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Internal helpers #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
def _strip_prefix(self, model: str) -> str:
|
||||||
|
return model[len("openrouter/") :] if model.startswith("openrouter/") else model
|
||||||
|
|
||||||
|
def _ensure_content(self) -> None:
|
||||||
|
self._load_cache()
|
||||||
|
if not self.content:
|
||||||
|
self._update_cache()
|
||||||
|
|
||||||
|
def _load_cache(self) -> None:
|
||||||
|
if self._cache_loaded:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
if self.cache_file.exists():
|
||||||
|
cache_age = time.time() - self.cache_file.stat().st_mtime
|
||||||
|
if cache_age < self.CACHE_TTL:
|
||||||
|
try:
|
||||||
|
self.content = json.loads(self.cache_file.read_text())
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
self.content = None
|
||||||
|
except OSError:
|
||||||
|
# Cache directory might be unwritable; ignore.
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._cache_loaded = True
|
||||||
|
|
||||||
|
def _update_cache(self) -> None:
|
||||||
|
try:
|
||||||
|
response = requests.get(self.MODELS_URL, timeout=10, verify=self.verify_ssl)
|
||||||
|
if response.status_code == 200:
|
||||||
|
self.content = response.json()
|
||||||
|
try:
|
||||||
|
self.cache_file.write_text(json.dumps(self.content, indent=2))
|
||||||
|
except OSError:
|
||||||
|
pass # Non-fatal if we can’t write the cache
|
||||||
|
except Exception as ex: # noqa: BLE001
|
||||||
|
print(f"Failed to fetch OpenRouter model list: {ex}")
|
||||||
|
try:
|
||||||
|
self.cache_file.write_text("{}")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
@@ -13,13 +13,12 @@ Generate a one-line commit message for those changes.
|
|||||||
The commit message should be structured as follows: <type>: <description>
|
The commit message should be structured as follows: <type>: <description>
|
||||||
Use these for <type>: fix, feat, build, chore, ci, docs, style, refactor, perf, test
|
Use these for <type>: fix, feat, build, chore, ci, docs, style, refactor, perf, test
|
||||||
|
|
||||||
Ensure the commit message:
|
Ensure the commit message:{language_instruction}
|
||||||
- Starts with the appropriate prefix.
|
- Starts with the appropriate prefix.
|
||||||
- Is in the imperative mood (e.g., \"Add feature\" not \"Added feature\" or \"Adding feature\").
|
- Is in the imperative mood (e.g., \"add feature\" not \"added feature\" or \"adding feature\").
|
||||||
- Does not exceed 72 characters.
|
- Does not exceed 72 characters.
|
||||||
|
|
||||||
Reply only with the one-line commit message, without any additional text, explanations, \
|
Reply only with the one-line commit message, without any additional text, explanations, or line breaks.
|
||||||
or line breaks.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# COMMANDS
|
# COMMANDS
|
||||||
|
|||||||
7
aider/queries/tree-sitter-language-pack/README.md
Normal file
7
aider/queries/tree-sitter-language-pack/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
These scm files are all adapted from the github repositories listed here:
|
||||||
|
|
||||||
|
https://github.com/Goldziher/tree-sitter-language-pack/blob/main/sources/language_definitions.json
|
||||||
|
|
||||||
|
See this URL for information on the licenses of each repo:
|
||||||
|
|
||||||
|
https://github.com/Goldziher/tree-sitter-language-pack/
|
||||||
5
aider/queries/tree-sitter-language-pack/arduino-tags.scm
Normal file
5
aider/queries/tree-sitter-language-pack/arduino-tags.scm
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
(function_declarator
|
||||||
|
declarator: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (identifier) @name.reference.call) @reference.call
|
||||||
16
aider/queries/tree-sitter-language-pack/chatito-tags.scm
Normal file
16
aider/queries/tree-sitter-language-pack/chatito-tags.scm
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
; Definitions
|
||||||
|
(intent_def
|
||||||
|
(intent) @name.definition.intent) @definition.intent
|
||||||
|
|
||||||
|
(slot_def
|
||||||
|
(slot) @name.definition.slot) @definition.slot
|
||||||
|
|
||||||
|
(alias_def
|
||||||
|
(alias) @name.definition.alias) @definition.alias
|
||||||
|
|
||||||
|
; References
|
||||||
|
(slot_ref
|
||||||
|
(slot) @name.reference.slot) @reference.slot
|
||||||
|
|
||||||
|
(alias_ref
|
||||||
|
(alias) @name.reference.alias) @reference.alias
|
||||||
7
aider/queries/tree-sitter-language-pack/clojure-tags.scm
Normal file
7
aider/queries/tree-sitter-language-pack/clojure-tags.scm
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
(list_lit
|
||||||
|
meta: _*
|
||||||
|
. (sym_lit name: (sym_name) @ignore)
|
||||||
|
. (sym_lit name: (sym_name) @name.definition.method)
|
||||||
|
(#match? @ignore "^def.*"))
|
||||||
|
|
||||||
|
(sym_lit name: (sym_name) @name.reference.call)
|
||||||
122
aider/queries/tree-sitter-language-pack/commonlisp-tags.scm
Normal file
122
aider/queries/tree-sitter-language-pack/commonlisp-tags.scm
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;; Function Definitions ;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defun_header
|
||||||
|
function_name: (sym_lit) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;; Function Calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;
|
||||||
|
;;; Basically, we consider every list literal with symbol as the
|
||||||
|
;;; first element to be a call to a function named by that element.
|
||||||
|
;;; But we must exclude some cases. Note, tree-sitter @ignore
|
||||||
|
;;; cases only work if they are declared before the cases
|
||||||
|
;;; we want to include.
|
||||||
|
|
||||||
|
;; Exclude lambda lists for function definitions
|
||||||
|
;; For example:
|
||||||
|
;;
|
||||||
|
;; (defun my-func (arg1 arg2) ...)
|
||||||
|
;;
|
||||||
|
;; do not treat (arg1 arg2) as a call of function arg1
|
||||||
|
;;
|
||||||
|
(defun_header
|
||||||
|
lambda_list: (list_lit . [(sym_lit) (package_lit)] @ignore))
|
||||||
|
|
||||||
|
;; Similar to the above, but for
|
||||||
|
;;
|
||||||
|
;; (defmethod m ((type1 param1) (type2 param2)) ...)
|
||||||
|
;;
|
||||||
|
;; where list literals having symbol as their first element
|
||||||
|
;; are nested inside the lambda list.
|
||||||
|
(defun_header
|
||||||
|
lambda_list: (list_lit (list_lit . [(sym_lit) (package_lit)] @ignore)))
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; (let ((var ...) (var2 ...)) ...)
|
||||||
|
;;
|
||||||
|
;; - exclude var, var2
|
||||||
|
;; - the same for let*, flet, labels, macrolet, symbol-macrolet
|
||||||
|
(list_lit . [(sym_lit) (package_lit)] @name.reference.call
|
||||||
|
. (list_lit (list_lit . [(sym_lit) (package_lit)] @ignore))
|
||||||
|
(#match? @name.reference.call
|
||||||
|
"(?i)^(cl:)?(let|let\\*|flet|labels|macrolet|symbol-macrolet)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
;; TODO:
|
||||||
|
;; - exclude also:
|
||||||
|
;; - (defclass name (parent parent2)
|
||||||
|
;; ((slot1 ...)
|
||||||
|
;; (slot2 ...))
|
||||||
|
;; exclude the parent, slot1, slot2
|
||||||
|
;; - (flet ((func-1 (param1 param2))) ...)
|
||||||
|
;; - we already exclude func-1, but param1 is still recognized
|
||||||
|
;; as a function call - exclude it too
|
||||||
|
;; - the same for labels
|
||||||
|
;; - the same macrolet
|
||||||
|
;; - what else?
|
||||||
|
;; (that's a non-goal to completely support all macros
|
||||||
|
;; and special operators, but every one we support
|
||||||
|
;; makes the solution a little bit better)
|
||||||
|
;; - (flet ((func-1 (param1 param2))) ...)
|
||||||
|
;; - instead of simply excluding it, as we do today,
|
||||||
|
;; tag func-1 as @local.definition.function (I suppose)
|
||||||
|
;; - the same for labels, macrolet
|
||||||
|
;; - @local.scope for let, let*, flet, labels, macrolet
|
||||||
|
;; - I guess the whole span of the scope text,
|
||||||
|
;; till the closing paren, should be tagged as @local.scope;
|
||||||
|
;; Hopefully, combined with @local.definition.function
|
||||||
|
;; within the scope, the usual @reference.call within
|
||||||
|
;; that scope will refer to the local definition,
|
||||||
|
;; and there will be no need to use @local.reference.call
|
||||||
|
;; (which is more difficult to implement).
|
||||||
|
;; - When implementing, remember the scope rules differences
|
||||||
|
;; of let vs let*, flet vs labels.
|
||||||
|
|
||||||
|
|
||||||
|
;; Include all other cases - list literal with symbol as the
|
||||||
|
;; first element
|
||||||
|
(list_lit . [(sym_lit) (package_lit)] @name.reference.call) @reference.call
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;; classes
|
||||||
|
|
||||||
|
(list_lit . [(sym_lit) (package_lit)] @ignore
|
||||||
|
. [(sym_lit) (package_lit)] @name.definition.class
|
||||||
|
(#match? @ignore "(?i)^(cl:)?defclass$")
|
||||||
|
) @definition.class
|
||||||
|
|
||||||
|
(list_lit . [(sym_lit) (package_lit)] @ignore
|
||||||
|
. (quoting_lit [(sym_lit) (package_lit)] @name.reference.class)
|
||||||
|
(#match? @ignore "(?i)^(cl:)?make-instance$")
|
||||||
|
) @reference.class
|
||||||
|
|
||||||
|
;;; TODO:
|
||||||
|
;; - @reference.class for base classes
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;; TODO:
|
||||||
|
;; - Symbols referenced in defpackage
|
||||||
|
;;
|
||||||
|
;; (defpackage ...
|
||||||
|
;; (:export (symbol-a :symbol-b #:symbol-c "SYMBOL-D")))
|
||||||
|
;;
|
||||||
|
;; The goal is to allow quick navigation from the API
|
||||||
|
;; overview in the form of defpackage, to the definition
|
||||||
|
;; where user can read parameters, docstring, etc.
|
||||||
|
;; - The @name must not include the colon, or sharpsign colon, quotes,
|
||||||
|
;; just symbol-a, symbol-b, symbol-c, sybmol-d
|
||||||
|
;; - Downcase the names specified as string literals?
|
||||||
|
;; ("SYMBOL-D" -> symbol-d)
|
||||||
|
;; - We don't know if the exported symbol is a function, variable,
|
||||||
|
;; class or something else. The official doc
|
||||||
|
;; (https://tree-sitter.github.io/tree-sitter/code-navigation-systems)
|
||||||
|
;; does not even suggest a tag for variable reference.
|
||||||
|
;; (Although in practice, the `tree-sitter tags` command
|
||||||
|
;; allows any @reference.* and @definition.* tags)
|
||||||
|
;; Probably it's better to just use @reference.call for all
|
||||||
|
;; the symbols in the :export clause.
|
||||||
|
;;
|
||||||
|
;; - The same for the export function call:
|
||||||
|
;;
|
||||||
|
;; (export '(symbol-a :symbol-b #:symbol-c "SYMBOL-D"))
|
||||||
15
aider/queries/tree-sitter-language-pack/cpp-tags.scm
Normal file
15
aider/queries/tree-sitter-language-pack/cpp-tags.scm
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
(struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class
|
||||||
|
|
||||||
|
(declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class
|
||||||
|
|
||||||
|
(function_declarator declarator: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(function_declarator declarator: (field_identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(function_declarator declarator: (qualified_identifier scope: (namespace_identifier) @local.scope name: (identifier) @name.definition.method)) @definition.method
|
||||||
|
|
||||||
|
(type_definition declarator: (type_identifier) @name.definition.type) @definition.type
|
||||||
|
|
||||||
|
(enum_specifier name: (type_identifier) @name.definition.type) @definition.type
|
||||||
|
|
||||||
|
(class_specifier name: (type_identifier) @name.definition.class) @definition.class
|
||||||
26
aider/queries/tree-sitter-language-pack/csharp-tags.scm
Normal file
26
aider/queries/tree-sitter-language-pack/csharp-tags.scm
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
; Based on https://github.com/tree-sitter/tree-sitter-c-sharp/blob/master/queries/tags.scm
|
||||||
|
; MIT License.
|
||||||
|
|
||||||
|
(class_declaration name: (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(class_declaration (base_list (_) @name.reference.class)) @reference.class
|
||||||
|
|
||||||
|
(interface_declaration name: (identifier) @name.definition.interface) @definition.interface
|
||||||
|
|
||||||
|
(interface_declaration (base_list (_) @name.reference.interface)) @reference.interface
|
||||||
|
|
||||||
|
(method_declaration name: (identifier) @name.definition.method) @definition.method
|
||||||
|
|
||||||
|
(object_creation_expression type: (identifier) @name.reference.class) @reference.class
|
||||||
|
|
||||||
|
(type_parameter_constraints_clause (identifier) @name.reference.class) @reference.class
|
||||||
|
|
||||||
|
(type_parameter_constraint (type type: (identifier) @name.reference.class)) @reference.class
|
||||||
|
|
||||||
|
(variable_declaration type: (identifier) @name.reference.class) @reference.class
|
||||||
|
|
||||||
|
(invocation_expression function: (member_access_expression name: (identifier) @name.reference.send)) @reference.send
|
||||||
|
|
||||||
|
(namespace_declaration name: (identifier) @name.definition.module) @definition.module
|
||||||
|
|
||||||
|
(namespace_declaration name: (identifier) @name.definition.module) @module
|
||||||
26
aider/queries/tree-sitter-language-pack/d-tags.scm
Normal file
26
aider/queries/tree-sitter-language-pack/d-tags.scm
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
(module_def (module_declaration (module_fqn) @name.definition.module)) @definition.module
|
||||||
|
|
||||||
|
(struct_declaration (struct) . (identifier) @name.definition.class) @definition.class
|
||||||
|
(interface_declaration (interface) . (identifier) @name.definition.interface) @definition.interface
|
||||||
|
(enum_declaration (enum) . (identifier) @name.definition.type) @definition.type
|
||||||
|
|
||||||
|
(class_declaration (class) . (identifier) @name.definition.class) @definition.class
|
||||||
|
(constructor (this) @name.definition.method) @definition.method
|
||||||
|
(destructor (this) @name.definition.method) @definition.method
|
||||||
|
(postblit (this) @name.definition.method) @definition.method
|
||||||
|
|
||||||
|
(manifest_declarator . (identifier) @name.definition.type) @definition.type
|
||||||
|
|
||||||
|
(function_declaration (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(union_declaration (union) . (identifier) @name.definition.type) @definition.type
|
||||||
|
|
||||||
|
(anonymous_enum_declaration (enum_member . (identifier) @name.definition.constant)) @definition.constant
|
||||||
|
|
||||||
|
(enum_declaration (enum_member . (identifier) @name.definition.constant)) @definition.constant
|
||||||
|
|
||||||
|
(call_expression (identifier) @name.reference.call) @reference.call
|
||||||
|
(call_expression (type (template_instance (identifier) @name.reference.call))) @reference.call
|
||||||
|
(parameter (type (identifier) @name.reference.class) @reference.class (identifier))
|
||||||
|
|
||||||
|
(variable_declaration (type (identifier) @name.reference.class) @reference.class (declarator))
|
||||||
92
aider/queries/tree-sitter-language-pack/dart-tags.scm
Normal file
92
aider/queries/tree-sitter-language-pack/dart-tags.scm
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
|
||||||
|
(class_definition
|
||||||
|
name: (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(method_signature
|
||||||
|
(function_signature)) @definition.method
|
||||||
|
|
||||||
|
(type_alias
|
||||||
|
(type_identifier) @name.definition.type) @definition.type
|
||||||
|
|
||||||
|
(method_signature
|
||||||
|
(getter_signature
|
||||||
|
name: (identifier) @name.definition.method)) @definition.method
|
||||||
|
|
||||||
|
(method_signature
|
||||||
|
(setter_signature
|
||||||
|
name: (identifier) @name.definition.method)) @definition.method
|
||||||
|
|
||||||
|
(method_signature
|
||||||
|
(function_signature
|
||||||
|
name: (identifier) @name.definition.method)) @definition.method
|
||||||
|
|
||||||
|
(method_signature
|
||||||
|
(factory_constructor_signature
|
||||||
|
(identifier) @name.definition.method)) @definition.method
|
||||||
|
|
||||||
|
(method_signature
|
||||||
|
(constructor_signature
|
||||||
|
name: (identifier) @name.definition.method)) @definition.method
|
||||||
|
|
||||||
|
(method_signature
|
||||||
|
(operator_signature)) @definition.method
|
||||||
|
|
||||||
|
(method_signature) @definition.method
|
||||||
|
|
||||||
|
(mixin_declaration
|
||||||
|
(mixin)
|
||||||
|
(identifier) @name.definition.mixin) @definition.mixin
|
||||||
|
|
||||||
|
(extension_declaration
|
||||||
|
name: (identifier) @name.definition.extension) @definition.extension
|
||||||
|
|
||||||
|
|
||||||
|
(new_expression
|
||||||
|
(type_identifier) @name.reference.class) @reference.class
|
||||||
|
|
||||||
|
(enum_declaration
|
||||||
|
name: (identifier) @name.definition.enum) @definition.enum
|
||||||
|
|
||||||
|
(function_signature
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(initialized_variable_definition
|
||||||
|
name: (identifier)
|
||||||
|
value: (identifier) @name.reference.class
|
||||||
|
value: (selector
|
||||||
|
"!"?
|
||||||
|
(argument_part
|
||||||
|
(arguments
|
||||||
|
(argument)*))?)?) @reference.class
|
||||||
|
|
||||||
|
(assignment_expression
|
||||||
|
left: (assignable_expression
|
||||||
|
(identifier)
|
||||||
|
(unconditional_assignable_selector
|
||||||
|
"."
|
||||||
|
(identifier) @name.reference.send))) @reference.call
|
||||||
|
|
||||||
|
(assignment_expression
|
||||||
|
left: (assignable_expression
|
||||||
|
(identifier)
|
||||||
|
(conditional_assignable_selector
|
||||||
|
"?."
|
||||||
|
(identifier) @name.reference.send))) @reference.call
|
||||||
|
|
||||||
|
((identifier) @name.reference.send
|
||||||
|
(selector
|
||||||
|
"!"?
|
||||||
|
(conditional_assignable_selector
|
||||||
|
"?." (identifier) @name.reference.send)?
|
||||||
|
(unconditional_assignable_selector
|
||||||
|
"."? (identifier) @name.reference.send)?
|
||||||
|
(argument_part
|
||||||
|
(arguments
|
||||||
|
(argument)*))?)*
|
||||||
|
(cascade_section
|
||||||
|
(cascade_selector
|
||||||
|
(identifier)) @name.reference.send
|
||||||
|
(argument_part
|
||||||
|
(arguments
|
||||||
|
(argument)*))?)?) @reference.call
|
||||||
|
|
||||||
5
aider/queries/tree-sitter-language-pack/elisp-tags.scm
Normal file
5
aider/queries/tree-sitter-language-pack/elisp-tags.scm
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
;; defun/defsubst
|
||||||
|
(function_definition name: (symbol) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
;; Treat macros as function definitions for the sake of TAGS.
|
||||||
|
(macro_definition name: (symbol) @name.definition.function) @definition.function
|
||||||
54
aider/queries/tree-sitter-language-pack/elixir-tags.scm
Normal file
54
aider/queries/tree-sitter-language-pack/elixir-tags.scm
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
; Definitions
|
||||||
|
|
||||||
|
; * modules and protocols
|
||||||
|
(call
|
||||||
|
target: (identifier) @ignore
|
||||||
|
(arguments (alias) @name.definition.module)
|
||||||
|
(#any-of? @ignore "defmodule" "defprotocol")) @definition.module
|
||||||
|
|
||||||
|
; * functions/macros
|
||||||
|
(call
|
||||||
|
target: (identifier) @ignore
|
||||||
|
(arguments
|
||||||
|
[
|
||||||
|
; zero-arity functions with no parentheses
|
||||||
|
(identifier) @name.definition.function
|
||||||
|
; regular function clause
|
||||||
|
(call target: (identifier) @name.definition.function)
|
||||||
|
; function clause with a guard clause
|
||||||
|
(binary_operator
|
||||||
|
left: (call target: (identifier) @name.definition.function)
|
||||||
|
operator: "when")
|
||||||
|
])
|
||||||
|
(#any-of? @ignore "def" "defp" "defdelegate" "defguard" "defguardp" "defmacro" "defmacrop" "defn" "defnp")) @definition.function
|
||||||
|
|
||||||
|
; References
|
||||||
|
|
||||||
|
; ignore calls to kernel/special-forms keywords
|
||||||
|
(call
|
||||||
|
target: (identifier) @ignore
|
||||||
|
(#any-of? @ignore "def" "defp" "defdelegate" "defguard" "defguardp" "defmacro" "defmacrop" "defn" "defnp" "defmodule" "defprotocol" "defimpl" "defstruct" "defexception" "defoverridable" "alias" "case" "cond" "else" "for" "if" "import" "quote" "raise" "receive" "require" "reraise" "super" "throw" "try" "unless" "unquote" "unquote_splicing" "use" "with"))
|
||||||
|
|
||||||
|
; ignore module attributes
|
||||||
|
(unary_operator
|
||||||
|
operator: "@"
|
||||||
|
operand: (call
|
||||||
|
target: (identifier) @ignore))
|
||||||
|
|
||||||
|
; * function call
|
||||||
|
(call
|
||||||
|
target: [
|
||||||
|
; local
|
||||||
|
(identifier) @name.reference.call
|
||||||
|
; remote
|
||||||
|
(dot
|
||||||
|
right: (identifier) @name.reference.call)
|
||||||
|
]) @reference.call
|
||||||
|
|
||||||
|
; * pipe into function call
|
||||||
|
(binary_operator
|
||||||
|
operator: "|>"
|
||||||
|
right: (identifier) @name.reference.call) @reference.call
|
||||||
|
|
||||||
|
; * modules
|
||||||
|
(alias) @name.reference.module @reference.module
|
||||||
19
aider/queries/tree-sitter-language-pack/elm-tags.scm
Normal file
19
aider/queries/tree-sitter-language-pack/elm-tags.scm
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
(value_declaration (function_declaration_left (lower_case_identifier) @name.definition.function)) @definition.function
|
||||||
|
|
||||||
|
(function_call_expr (value_expr (value_qid) @name.reference.function)) @reference.function
|
||||||
|
(exposed_value (lower_case_identifier) @name.reference.function) @reference.function
|
||||||
|
(type_annotation ((lower_case_identifier) @name.reference.function) (colon)) @reference.function
|
||||||
|
|
||||||
|
(type_declaration ((upper_case_identifier) @name.definition.type) ) @definition.type
|
||||||
|
|
||||||
|
(type_ref (upper_case_qid (upper_case_identifier) @name.reference.type)) @reference.type
|
||||||
|
(exposed_type (upper_case_identifier) @name.reference.type) @reference.type
|
||||||
|
|
||||||
|
(type_declaration (union_variant (upper_case_identifier) @name.definition.union)) @definition.union
|
||||||
|
|
||||||
|
(value_expr (upper_case_qid (upper_case_identifier) @name.reference.union)) @reference.union
|
||||||
|
|
||||||
|
|
||||||
|
(module_declaration
|
||||||
|
(upper_case_qid (upper_case_identifier)) @name.definition.module
|
||||||
|
) @definition.module
|
||||||
41
aider/queries/tree-sitter-language-pack/gleam-tags.scm
Normal file
41
aider/queries/tree-sitter-language-pack/gleam-tags.scm
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
; Modules
|
||||||
|
(module) @name.reference.module @reference.module
|
||||||
|
(import alias: (identifier) @name.reference.module) @reference.module
|
||||||
|
(remote_type_identifier
|
||||||
|
module: (identifier) @name.reference.module) @reference.module
|
||||||
|
((field_access
|
||||||
|
record: (identifier) @name.reference.module)
|
||||||
|
(#is-not? local)) @reference.module
|
||||||
|
|
||||||
|
; Functions
|
||||||
|
(function
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
(external_function
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
(unqualified_import (identifier) @name.reference.function) @reference.function
|
||||||
|
((function_call
|
||||||
|
function: (identifier) @name.reference.function) @reference.function
|
||||||
|
(#is-not? local))
|
||||||
|
((field_access
|
||||||
|
record: (identifier) @ignore
|
||||||
|
field: (label) @name.reference.function)
|
||||||
|
(#is-not? local)) @reference.function
|
||||||
|
((binary_expression
|
||||||
|
operator: "|>"
|
||||||
|
right: (identifier) @name.reference.function)
|
||||||
|
(#is-not? local)) @reference.function
|
||||||
|
|
||||||
|
; Types
|
||||||
|
(type_definition
|
||||||
|
(type_name
|
||||||
|
name: (type_identifier) @name.definition.type)) @definition.type
|
||||||
|
(type_definition
|
||||||
|
(data_constructors
|
||||||
|
(data_constructor
|
||||||
|
name: (constructor_name) @name.definition.constructor))) @definition.constructor
|
||||||
|
(external_type
|
||||||
|
(type_name
|
||||||
|
name: (type_identifier) @name.definition.type)) @definition.type
|
||||||
|
|
||||||
|
(type_identifier) @name.reference.type @reference.type
|
||||||
|
(constructor_name) @name.reference.constructor @reference.constructor
|
||||||
42
aider/queries/tree-sitter-language-pack/go-tags.scm
Normal file
42
aider/queries/tree-sitter-language-pack/go-tags.scm
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
(
|
||||||
|
(comment)* @doc
|
||||||
|
.
|
||||||
|
(function_declaration
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
(#strip! @doc "^//\\s*")
|
||||||
|
(#set-adjacent! @doc @definition.function)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @doc
|
||||||
|
.
|
||||||
|
(method_declaration
|
||||||
|
name: (field_identifier) @name.definition.method) @definition.method
|
||||||
|
(#strip! @doc "^//\\s*")
|
||||||
|
(#set-adjacent! @doc @definition.method)
|
||||||
|
)
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: [
|
||||||
|
(identifier) @name.reference.call
|
||||||
|
(parenthesized_expression (identifier) @name.reference.call)
|
||||||
|
(selector_expression field: (field_identifier) @name.reference.call)
|
||||||
|
(parenthesized_expression (selector_expression field: (field_identifier) @name.reference.call))
|
||||||
|
]) @reference.call
|
||||||
|
|
||||||
|
(type_spec
|
||||||
|
name: (type_identifier) @name.definition.type) @definition.type
|
||||||
|
|
||||||
|
(type_identifier) @name.reference.type @reference.type
|
||||||
|
|
||||||
|
(package_clause "package" (package_identifier) @name.definition.module)
|
||||||
|
|
||||||
|
(type_declaration (type_spec name: (type_identifier) @name.definition.interface type: (interface_type)))
|
||||||
|
|
||||||
|
(type_declaration (type_spec name: (type_identifier) @name.definition.class type: (struct_type)))
|
||||||
|
|
||||||
|
(import_declaration (import_spec) @name.reference.module)
|
||||||
|
|
||||||
|
(var_declaration (var_spec name: (identifier) @name.definition.variable))
|
||||||
|
|
||||||
|
(const_declaration (const_spec name: (identifier) @name.definition.constant))
|
||||||
20
aider/queries/tree-sitter-language-pack/java-tags.scm
Normal file
20
aider/queries/tree-sitter-language-pack/java-tags.scm
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
(class_declaration
|
||||||
|
name: (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(method_declaration
|
||||||
|
name: (identifier) @name.definition.method) @definition.method
|
||||||
|
|
||||||
|
(method_invocation
|
||||||
|
name: (identifier) @name.reference.method
|
||||||
|
arguments: (argument_list) @reference.call)
|
||||||
|
|
||||||
|
(interface_declaration
|
||||||
|
name: (identifier) @name.definition.interface) @definition.interface
|
||||||
|
|
||||||
|
(type_list
|
||||||
|
(type_identifier) @name.reference.interface) @reference.implementation
|
||||||
|
|
||||||
|
(object_creation_expression
|
||||||
|
type: (type_identifier) @name.reference.class) @reference.class
|
||||||
|
|
||||||
|
(superclass (type_identifier) @name.reference.class) @reference.class
|
||||||
88
aider/queries/tree-sitter-language-pack/javascript-tags.scm
Normal file
88
aider/queries/tree-sitter-language-pack/javascript-tags.scm
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
(
|
||||||
|
(comment)* @doc
|
||||||
|
.
|
||||||
|
(method_definition
|
||||||
|
name: (property_identifier) @name.definition.method) @definition.method
|
||||||
|
(#not-eq? @name.definition.method "constructor")
|
||||||
|
(#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
|
||||||
|
(#select-adjacent! @doc @definition.method)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @doc
|
||||||
|
.
|
||||||
|
[
|
||||||
|
(class
|
||||||
|
name: (_) @name.definition.class)
|
||||||
|
(class_declaration
|
||||||
|
name: (_) @name.definition.class)
|
||||||
|
] @definition.class
|
||||||
|
(#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
|
||||||
|
(#select-adjacent! @doc @definition.class)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @doc
|
||||||
|
.
|
||||||
|
[
|
||||||
|
(function_expression
|
||||||
|
name: (identifier) @name.definition.function)
|
||||||
|
(function_declaration
|
||||||
|
name: (identifier) @name.definition.function)
|
||||||
|
(generator_function
|
||||||
|
name: (identifier) @name.definition.function)
|
||||||
|
(generator_function_declaration
|
||||||
|
name: (identifier) @name.definition.function)
|
||||||
|
] @definition.function
|
||||||
|
(#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
|
||||||
|
(#select-adjacent! @doc @definition.function)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @doc
|
||||||
|
.
|
||||||
|
(lexical_declaration
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @name.definition.function
|
||||||
|
value: [(arrow_function) (function_expression)]) @definition.function)
|
||||||
|
(#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
|
||||||
|
(#select-adjacent! @doc @definition.function)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)* @doc
|
||||||
|
.
|
||||||
|
(variable_declaration
|
||||||
|
(variable_declarator
|
||||||
|
name: (identifier) @name.definition.function
|
||||||
|
value: [(arrow_function) (function_expression)]) @definition.function)
|
||||||
|
(#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
|
||||||
|
(#select-adjacent! @doc @definition.function)
|
||||||
|
)
|
||||||
|
|
||||||
|
(assignment_expression
|
||||||
|
left: [
|
||||||
|
(identifier) @name.definition.function
|
||||||
|
(member_expression
|
||||||
|
property: (property_identifier) @name.definition.function)
|
||||||
|
]
|
||||||
|
right: [(arrow_function) (function_expression)]
|
||||||
|
) @definition.function
|
||||||
|
|
||||||
|
(pair
|
||||||
|
key: (property_identifier) @name.definition.function
|
||||||
|
value: [(arrow_function) (function_expression)]) @definition.function
|
||||||
|
|
||||||
|
(
|
||||||
|
(call_expression
|
||||||
|
function: (identifier) @name.reference.call) @reference.call
|
||||||
|
(#not-match? @name.reference.call "^(require)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (member_expression
|
||||||
|
property: (property_identifier) @name.reference.call)
|
||||||
|
arguments: (_) @reference.call)
|
||||||
|
|
||||||
|
(new_expression
|
||||||
|
constructor: (_) @name.reference.class) @reference.class
|
||||||
34
aider/queries/tree-sitter-language-pack/lua-tags.scm
Normal file
34
aider/queries/tree-sitter-language-pack/lua-tags.scm
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
(function_declaration
|
||||||
|
name: [
|
||||||
|
(identifier) @name.definition.function
|
||||||
|
(dot_index_expression
|
||||||
|
field: (identifier) @name.definition.function)
|
||||||
|
]) @definition.function
|
||||||
|
|
||||||
|
(function_declaration
|
||||||
|
name: (method_index_expression
|
||||||
|
method: (identifier) @name.definition.method)) @definition.method
|
||||||
|
|
||||||
|
(assignment_statement
|
||||||
|
(variable_list .
|
||||||
|
name: [
|
||||||
|
(identifier) @name.definition.function
|
||||||
|
(dot_index_expression
|
||||||
|
field: (identifier) @name.definition.function)
|
||||||
|
])
|
||||||
|
(expression_list .
|
||||||
|
value: (function_definition))) @definition.function
|
||||||
|
|
||||||
|
(table_constructor
|
||||||
|
(field
|
||||||
|
name: (identifier) @name.definition.function
|
||||||
|
value: (function_definition))) @definition.function
|
||||||
|
|
||||||
|
(function_call
|
||||||
|
name: [
|
||||||
|
(identifier) @name.reference.call
|
||||||
|
(dot_index_expression
|
||||||
|
field: (identifier) @name.reference.call)
|
||||||
|
(method_index_expression
|
||||||
|
method: (identifier) @name.reference.method)
|
||||||
|
]) @reference.call
|
||||||
10
aider/queries/tree-sitter-language-pack/matlab-tags.scm
Normal file
10
aider/queries/tree-sitter-language-pack/matlab-tags.scm
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
(class_definition
|
||||||
|
name: (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(function_definition
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(function_call
|
||||||
|
name: (identifier) @name.reference.call) @reference.call
|
||||||
|
|
||||||
|
(command (command_name) @name.reference.call) @reference.call
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
; Modules
|
||||||
|
;--------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(module_definition
|
||||||
|
(module_binding (module_name) @name) @definition.module
|
||||||
|
)
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(module_path (module_name) @name) @reference.module
|
||||||
|
(extended_module_path (module_name) @name) @reference.module
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(module_type_definition (module_type_name) @name) @definition.interface
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(module_type_path (module_type_name) @name) @reference.implementation
|
||||||
|
|
||||||
|
|
||||||
|
; Classes
|
||||||
|
;--------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
[
|
||||||
|
(class_definition
|
||||||
|
(class_binding (class_name) @name) @definition.class
|
||||||
|
)
|
||||||
|
(class_type_definition
|
||||||
|
(class_type_binding (class_type_name) @name) @definition.class
|
||||||
|
)
|
||||||
|
]
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
(class_path (class_name) @name)
|
||||||
|
(class_type_path (class_type_name) @name)
|
||||||
|
] @reference.class
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(method_definition (method_name) @name) @definition.method
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(method_invocation (method_name) @name) @reference.call
|
||||||
|
|
||||||
|
|
||||||
|
; Types
|
||||||
|
;------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(type_definition
|
||||||
|
(type_binding
|
||||||
|
name: [
|
||||||
|
(type_constructor) @name
|
||||||
|
(type_constructor_path (type_constructor) @name)
|
||||||
|
]
|
||||||
|
) @definition.type
|
||||||
|
)
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(type_constructor_path (type_constructor) @name) @reference.type
|
||||||
|
|
||||||
|
[
|
||||||
|
(constructor_declaration (constructor_name) @name)
|
||||||
|
(tag_specification (tag) @name)
|
||||||
|
] @definition.enum_variant
|
||||||
|
|
||||||
|
[
|
||||||
|
(constructor_path (constructor_name) @name)
|
||||||
|
(tag) @name
|
||||||
|
] @reference.enum_variant
|
||||||
|
|
||||||
|
(field_declaration (field_name) @name) @definition.field
|
||||||
|
|
||||||
|
(field_path (field_name) @name) @reference.field
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(external (value_name) @name) @definition.function
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(value_specification
|
||||||
|
(value_name) @name.definition.function
|
||||||
|
) @definition.function
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
39
aider/queries/tree-sitter-language-pack/pony-tags.scm
Normal file
39
aider/queries/tree-sitter-language-pack/pony-tags.scm
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
;Class definitions @definition.class
|
||||||
|
;Function definitions @definition.function
|
||||||
|
;Interface definitions @definition.interface
|
||||||
|
;Method definitions @definition.method
|
||||||
|
;Module definitions @definition.module
|
||||||
|
;Function/method calls @reference.call
|
||||||
|
;Class reference @reference.class
|
||||||
|
;Interface implementation @reference.implementation
|
||||||
|
(
|
||||||
|
(identifier) @reference.class
|
||||||
|
(#match? @reference.class "^_*[A-Z][a-zA-Z0-9_]*$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(class_definition (identifier) @name.definition.class) @definition.class
|
||||||
|
(actor_definition (identifier) @name.definition.class) @definition.class
|
||||||
|
(primitive_definition (identifier) @name.definition.class) @definition.class
|
||||||
|
(struct_definition (identifier) @name.definition.class) @definition.class
|
||||||
|
(type_alias (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(trait_definition (identifier) @name.definition.interface) @definition.interface
|
||||||
|
(interface_definition (identifier) @name.definition.interface) @definition.interface
|
||||||
|
|
||||||
|
(constructor (identifier) @name.definition.method) @definition.method
|
||||||
|
(method (identifier) @name.definition.method) @definition.method
|
||||||
|
(behavior (identifier) @name.definition.method) @definition.method
|
||||||
|
|
||||||
|
(class_definition (type) @name.reference.implementation) @reference.implementation
|
||||||
|
(actor_definition (type) @name.reference.implementation) @reference.implementation
|
||||||
|
(primitive_definition (type) @name.reference.implementation) @reference.implementation
|
||||||
|
(struct_definition (type) @name.reference.implementation) @reference.implementation
|
||||||
|
(type_alias (type) @name.reference.implementation) @reference.implementation
|
||||||
|
|
||||||
|
; calls - not catching all possible call cases of callees for capturing the method name
|
||||||
|
(call_expression callee: [(identifier) (ffi_identifier)] @name.reference.call) @reference.call
|
||||||
|
(call_expression callee: (generic_expression [(identifier) (ffi_identifier)] @name.reference.call)) @reference.call
|
||||||
|
(call_expression callee: (member_expression (identifier) @name.reference.call .)) @reference.call
|
||||||
|
(call_expression callee: (member_expression (generic_expression [(identifier) (ffi_identifier)] @name.reference.call) .)) @reference.call
|
||||||
|
; TODO: add more possible callee expressions
|
||||||
|
(call_expression) @reference.call
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(property
|
||||||
|
(key) @name.definition.property) @definition.property
|
||||||
|
|
||||||
|
(substitution
|
||||||
|
(key) @name.reference.property) @reference.property
|
||||||
14
aider/queries/tree-sitter-language-pack/python-tags.scm
Normal file
14
aider/queries/tree-sitter-language-pack/python-tags.scm
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
(module (expression_statement (assignment left: (identifier) @name.definition.constant) @definition.constant))
|
||||||
|
|
||||||
|
(class_definition
|
||||||
|
name: (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(function_definition
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(call
|
||||||
|
function: [
|
||||||
|
(identifier) @name.reference.call
|
||||||
|
(attribute
|
||||||
|
attribute: (identifier) @name.reference.call)
|
||||||
|
]) @reference.call
|
||||||
21
aider/queries/tree-sitter-language-pack/r-tags.scm
Normal file
21
aider/queries/tree-sitter-language-pack/r-tags.scm
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
(binary_operator
|
||||||
|
lhs: (identifier) @name.definition.function
|
||||||
|
operator: "<-"
|
||||||
|
rhs: (function_definition)
|
||||||
|
) @definition.function
|
||||||
|
|
||||||
|
(binary_operator
|
||||||
|
lhs: (identifier) @name.definition.function
|
||||||
|
operator: "="
|
||||||
|
rhs: (function_definition)
|
||||||
|
) @definition.function
|
||||||
|
|
||||||
|
(call
|
||||||
|
function: (identifier) @name.reference.call
|
||||||
|
) @reference.call
|
||||||
|
|
||||||
|
(call
|
||||||
|
function: (namespace_operator
|
||||||
|
rhs: (identifier) @name.reference.call
|
||||||
|
)
|
||||||
|
) @reference.call
|
||||||
12
aider/queries/tree-sitter-language-pack/racket-tags.scm
Normal file
12
aider/queries/tree-sitter-language-pack/racket-tags.scm
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
(list
|
||||||
|
.
|
||||||
|
(symbol) @reference._define
|
||||||
|
(#match? @reference._define "^(define|define/contract)$")
|
||||||
|
.
|
||||||
|
(list
|
||||||
|
.
|
||||||
|
(symbol) @name.definition.function) @definition.function)
|
||||||
|
|
||||||
|
(list
|
||||||
|
.
|
||||||
|
(symbol) @name.reference.call)
|
||||||
60
aider/queries/tree-sitter-language-pack/rust-tags.scm
Normal file
60
aider/queries/tree-sitter-language-pack/rust-tags.scm
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
; ADT definitions
|
||||||
|
|
||||||
|
(struct_item
|
||||||
|
name: (type_identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(enum_item
|
||||||
|
name: (type_identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(union_item
|
||||||
|
name: (type_identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
; type aliases
|
||||||
|
|
||||||
|
(type_item
|
||||||
|
name: (type_identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
; method definitions
|
||||||
|
|
||||||
|
(declaration_list
|
||||||
|
(function_item
|
||||||
|
name: (identifier) @name.definition.method) @definition.method)
|
||||||
|
|
||||||
|
; function definitions
|
||||||
|
|
||||||
|
(function_item
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
; trait definitions
|
||||||
|
(trait_item
|
||||||
|
name: (type_identifier) @name.definition.interface) @definition.interface
|
||||||
|
|
||||||
|
; module definitions
|
||||||
|
(mod_item
|
||||||
|
name: (identifier) @name.definition.module) @definition.module
|
||||||
|
|
||||||
|
; macro definitions
|
||||||
|
|
||||||
|
(macro_definition
|
||||||
|
name: (identifier) @name.definition.macro) @definition.macro
|
||||||
|
|
||||||
|
; references
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (identifier) @name.reference.call) @reference.call
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
function: (field_expression
|
||||||
|
field: (field_identifier) @name.reference.call)) @reference.call
|
||||||
|
|
||||||
|
(macro_invocation
|
||||||
|
macro: (identifier) @name.reference.call) @reference.call
|
||||||
|
|
||||||
|
; implementations
|
||||||
|
|
||||||
|
(impl_item
|
||||||
|
trait: (type_identifier) @name.reference.implementation) @reference.implementation
|
||||||
|
|
||||||
|
(impl_item
|
||||||
|
type: (type_identifier) @name.reference.implementation
|
||||||
|
!trait) @reference.implementation
|
||||||
43
aider/queries/tree-sitter-language-pack/solidity-tags.scm
Normal file
43
aider/queries/tree-sitter-language-pack/solidity-tags.scm
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
;; Method and Function declarations
|
||||||
|
(contract_declaration (_
|
||||||
|
(function_definition
|
||||||
|
name: (identifier) @name.definition.function) @definition.method))
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(function_definition
|
||||||
|
name: (identifier) @name.definition.function) @definition.function)
|
||||||
|
|
||||||
|
;; Contract, struct, enum and interface declarations
|
||||||
|
(contract_declaration
|
||||||
|
name: (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(interface_declaration
|
||||||
|
name: (identifier) @name.definition.interface) @definition.interface
|
||||||
|
|
||||||
|
(library_declaration
|
||||||
|
name: (identifier) @name.definition.class) @definition.interface
|
||||||
|
|
||||||
|
(struct_declaration name: (identifier) @name.definition.class) @definition.class
|
||||||
|
(enum_declaration name: (identifier) @name.definition.class) @definition.class
|
||||||
|
(event_definition name: (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
;; Function calls
|
||||||
|
(call_expression (expression (identifier)) @name.reference.call ) @reference.call
|
||||||
|
|
||||||
|
(call_expression
|
||||||
|
(expression (member_expression
|
||||||
|
property: (_) @name.reference.method ))) @reference.call
|
||||||
|
|
||||||
|
;; Log emit
|
||||||
|
(emit_statement name: (_) @name.reference.class) @reference.class
|
||||||
|
|
||||||
|
|
||||||
|
;; Inheritance
|
||||||
|
|
||||||
|
(inheritance_specifier
|
||||||
|
ancestor: (user_defined_type (_) @name.reference.class . )) @reference.class
|
||||||
|
|
||||||
|
|
||||||
|
;; Imports ( note that unknown is not standardised )
|
||||||
|
(import_directive
|
||||||
|
import_name: (_) @name.reference.module ) @reference.unknown
|
||||||
51
aider/queries/tree-sitter-language-pack/swift-tags.scm
Normal file
51
aider/queries/tree-sitter-language-pack/swift-tags.scm
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
(class_declaration
|
||||||
|
name: (type_identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(protocol_declaration
|
||||||
|
name: (type_identifier) @name.definition.interface) @definition.interface
|
||||||
|
|
||||||
|
(class_declaration
|
||||||
|
(class_body
|
||||||
|
[
|
||||||
|
(function_declaration
|
||||||
|
name: (simple_identifier) @name.definition.method
|
||||||
|
)
|
||||||
|
(subscript_declaration
|
||||||
|
(parameter (simple_identifier) @name.definition.method)
|
||||||
|
)
|
||||||
|
(init_declaration "init" @name.definition.method)
|
||||||
|
(deinit_declaration "deinit" @name.definition.method)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
) @definition.method
|
||||||
|
|
||||||
|
(protocol_declaration
|
||||||
|
(protocol_body
|
||||||
|
[
|
||||||
|
(protocol_function_declaration
|
||||||
|
name: (simple_identifier) @name.definition.method
|
||||||
|
)
|
||||||
|
(subscript_declaration
|
||||||
|
(parameter (simple_identifier) @name.definition.method)
|
||||||
|
)
|
||||||
|
(init_declaration "init" @name.definition.method)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
) @definition.method
|
||||||
|
|
||||||
|
(class_declaration
|
||||||
|
(class_body
|
||||||
|
[
|
||||||
|
(property_declaration
|
||||||
|
(pattern (simple_identifier) @name.definition.property)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
) @definition.property
|
||||||
|
|
||||||
|
(property_declaration
|
||||||
|
(pattern (simple_identifier) @name.definition.property)
|
||||||
|
) @definition.property
|
||||||
|
|
||||||
|
(function_declaration
|
||||||
|
name: (simple_identifier) @name.definition.function) @definition.function
|
||||||
20
aider/queries/tree-sitter-language-pack/udev-tags.scm
Normal file
20
aider/queries/tree-sitter-language-pack/udev-tags.scm
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
(assignment
|
||||||
|
key: "LABEL"
|
||||||
|
(value
|
||||||
|
(content) @name.definition.label)) @definition.label
|
||||||
|
|
||||||
|
(assignment
|
||||||
|
key: "GOTO"
|
||||||
|
(value
|
||||||
|
(content) @name.reference.label)) @reference.label
|
||||||
|
|
||||||
|
(assignment
|
||||||
|
key: "ENV"
|
||||||
|
(env_var) @name.definition.variable) @definition.variable
|
||||||
|
|
||||||
|
(match
|
||||||
|
key: "ENV"
|
||||||
|
(env_var) @name.reference.variable) @reference.variable
|
||||||
|
|
||||||
|
(var_sub
|
||||||
|
(env_var) @name.reference.variable) @reference.variable
|
||||||
9
aider/queries/tree-sitter-languages/c-tags.scm
Normal file
9
aider/queries/tree-sitter-languages/c-tags.scm
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
(struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class
|
||||||
|
|
||||||
|
(declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class
|
||||||
|
|
||||||
|
(function_declarator declarator: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(type_definition declarator: (type_identifier) @name.definition.type) @definition.type
|
||||||
|
|
||||||
|
(enum_specifier name: (type_identifier) @name.definition.type) @definition.type
|
||||||
77
aider/queries/tree-sitter-languages/hcl-tags.scm
Normal file
77
aider/queries/tree-sitter-languages/hcl-tags.scm
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
;; Based on https://github.com/tree-sitter-grammars/tree-sitter-hcl/blob/main/make_grammar.js
|
||||||
|
;; Which has Apache 2.0 License
|
||||||
|
;; tags.scm for Terraform (tree-sitter-hcl)
|
||||||
|
|
||||||
|
; === Definitions: Terraform Blocks ===
|
||||||
|
(block
|
||||||
|
(identifier) @block_type
|
||||||
|
(string_lit (template_literal) @resource_type)
|
||||||
|
(string_lit (template_literal) @name.definition.resource)
|
||||||
|
(body) @definition.resource
|
||||||
|
) (#eq? @block_type "resource")
|
||||||
|
|
||||||
|
(block
|
||||||
|
(identifier) @block_type
|
||||||
|
(string_lit (template_literal) @name.definition.module)
|
||||||
|
(body) @definition.module
|
||||||
|
) (#eq? @block_type "module")
|
||||||
|
|
||||||
|
(block
|
||||||
|
(identifier) @block_type
|
||||||
|
(string_lit (template_literal) @name.definition.variable)
|
||||||
|
(body) @definition.variable
|
||||||
|
) (#eq? @block_type "variable")
|
||||||
|
|
||||||
|
(block
|
||||||
|
(identifier) @block_type
|
||||||
|
(string_lit (template_literal) @name.definition.output)
|
||||||
|
(body) @definition.output
|
||||||
|
) (#eq? @block_type "output")
|
||||||
|
|
||||||
|
(block
|
||||||
|
(identifier) @block_type
|
||||||
|
(string_lit (template_literal) @name.definition.provider)
|
||||||
|
(body) @definition.provider
|
||||||
|
) (#eq? @block_type "provider")
|
||||||
|
|
||||||
|
(block
|
||||||
|
(identifier) @block_type
|
||||||
|
(body
|
||||||
|
(attribute
|
||||||
|
(identifier) @name.definition.local
|
||||||
|
(expression) @definition.local
|
||||||
|
)+
|
||||||
|
)
|
||||||
|
) (#eq? @block_type "locals")
|
||||||
|
|
||||||
|
; === References: Variables, Locals, Modules, Data, Resources ===
|
||||||
|
((variable_expr) @ref_type
|
||||||
|
(get_attr (identifier) @name.reference.variable)
|
||||||
|
) @reference.variable
|
||||||
|
(#eq? @ref_type "var")
|
||||||
|
|
||||||
|
((variable_expr) @ref_type
|
||||||
|
(get_attr (identifier) @name.reference.local)
|
||||||
|
) @reference.local
|
||||||
|
(#eq? @ref_type "local")
|
||||||
|
|
||||||
|
((variable_expr) @ref_type
|
||||||
|
(get_attr (identifier) @name.reference.module)
|
||||||
|
) @reference.module
|
||||||
|
(#eq? @ref_type "module")
|
||||||
|
|
||||||
|
((variable_expr) @ref_type
|
||||||
|
(get_attr (identifier) @data_source_type)
|
||||||
|
(get_attr (identifier) @name.reference.data)
|
||||||
|
) @reference.data
|
||||||
|
(#eq? @ref_type "data")
|
||||||
|
|
||||||
|
((variable_expr) @resource_type
|
||||||
|
(get_attr (identifier) @name.reference.resource)
|
||||||
|
) @reference.resource
|
||||||
|
(#not-eq? @resource_type "var")
|
||||||
|
(#not-eq? @resource_type "local")
|
||||||
|
(#not-eq? @resource_type "module")
|
||||||
|
(#not-eq? @resource_type "data")
|
||||||
|
(#not-eq? @resource_type "provider")
|
||||||
|
(#not-eq? @resource_type "output")
|
||||||
10
aider/queries/tree-sitter-languages/matlab-tags.scm
Normal file
10
aider/queries/tree-sitter-languages/matlab-tags.scm
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
(class_definition
|
||||||
|
name: (identifier) @name.definition.class) @definition.class
|
||||||
|
|
||||||
|
(function_definition
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(function_call
|
||||||
|
name: (identifier) @name.reference.call) @reference.call
|
||||||
|
|
||||||
|
(command (command_name) @name.reference.call) @reference.call
|
||||||
115
aider/queries/tree-sitter-languages/ocaml-tags.scm
Normal file
115
aider/queries/tree-sitter-languages/ocaml-tags.scm
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
; Modules
|
||||||
|
;--------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(module_definition (module_binding (module_name) @name.definition.module) @definition.module)
|
||||||
|
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(module_path (module_name) @name.reference.module) @reference.module
|
||||||
|
|
||||||
|
; Module types
|
||||||
|
;--------------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(module_type_definition (module_type_name) @name.definition.interface) @definition.interface
|
||||||
|
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(module_type_path (module_type_name) @name.reference.implementation) @reference.implementation
|
||||||
|
|
||||||
|
; Functions
|
||||||
|
;----------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(value_definition
|
||||||
|
[
|
||||||
|
(let_binding
|
||||||
|
pattern: (value_name) @name.definition.function
|
||||||
|
(parameter))
|
||||||
|
(let_binding
|
||||||
|
pattern: (value_name) @name.definition.function
|
||||||
|
body: [(fun_expression) (function_expression)])
|
||||||
|
] @definition.function
|
||||||
|
)
|
||||||
|
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(external (value_name) @name.definition.function) @definition.function
|
||||||
|
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(application_expression
|
||||||
|
function: (value_path (value_name) @name.reference.call)) @reference.call
|
||||||
|
|
||||||
|
(infix_expression
|
||||||
|
left: (value_path (value_name) @name.reference.call)
|
||||||
|
operator: (concat_operator) @reference.call
|
||||||
|
(#eq? @reference.call "@@"))
|
||||||
|
|
||||||
|
(infix_expression
|
||||||
|
operator: (rel_operator) @reference.call
|
||||||
|
right: (value_path (value_name) @name.reference.call)
|
||||||
|
(#eq? @reference.call "|>"))
|
||||||
|
|
||||||
|
; Operator
|
||||||
|
;---------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(value_definition
|
||||||
|
(let_binding
|
||||||
|
pattern: (parenthesized_operator (_) @name.definition.function)) @definition.function)
|
||||||
|
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
(prefix_operator)
|
||||||
|
(sign_operator)
|
||||||
|
(pow_operator)
|
||||||
|
(mult_operator)
|
||||||
|
(add_operator)
|
||||||
|
(concat_operator)
|
||||||
|
(rel_operator)
|
||||||
|
(and_operator)
|
||||||
|
(or_operator)
|
||||||
|
(assign_operator)
|
||||||
|
(hash_operator)
|
||||||
|
(indexing_operator)
|
||||||
|
(let_operator)
|
||||||
|
(let_and_operator)
|
||||||
|
(match_operator)
|
||||||
|
] @name.reference.call @reference.call
|
||||||
|
|
||||||
|
; Classes
|
||||||
|
;--------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
[
|
||||||
|
(class_definition (class_binding (class_name) @name.definition.class) @definition.class)
|
||||||
|
(class_type_definition (class_type_binding (class_type_name) @name.definition.class) @definition.class)
|
||||||
|
]
|
||||||
|
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
(class_path (class_name) @name.reference.class)
|
||||||
|
(class_type_path (class_type_name) @name.reference.class)
|
||||||
|
] @reference.class
|
||||||
|
|
||||||
|
; Methods
|
||||||
|
;--------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(method_definition (method_name) @name.definition.method) @definition.method
|
||||||
|
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(method_invocation (method_name) @name.reference.call) @reference.call
|
||||||
98
aider/queries/tree-sitter-languages/ocaml_interface-tags.scm
Normal file
98
aider/queries/tree-sitter-languages/ocaml_interface-tags.scm
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
; Modules
|
||||||
|
;--------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(module_definition
|
||||||
|
(module_binding (module_name) @name) @definition.module
|
||||||
|
)
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(module_path (module_name) @name) @reference.module
|
||||||
|
(extended_module_path (module_name) @name) @reference.module
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(module_type_definition (module_type_name) @name) @definition.interface
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(module_type_path (module_type_name) @name) @reference.implementation
|
||||||
|
|
||||||
|
|
||||||
|
; Classes
|
||||||
|
;--------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
[
|
||||||
|
(class_definition
|
||||||
|
(class_binding (class_name) @name) @definition.class
|
||||||
|
)
|
||||||
|
(class_type_definition
|
||||||
|
(class_type_binding (class_type_name) @name) @definition.class
|
||||||
|
)
|
||||||
|
]
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
(class_path (class_name) @name)
|
||||||
|
(class_type_path (class_type_name) @name)
|
||||||
|
] @reference.class
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(method_definition (method_name) @name) @definition.method
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(method_invocation (method_name) @name) @reference.call
|
||||||
|
|
||||||
|
|
||||||
|
; Types
|
||||||
|
;------
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(type_definition
|
||||||
|
(type_binding
|
||||||
|
name: [
|
||||||
|
(type_constructor) @name
|
||||||
|
(type_constructor_path (type_constructor) @name)
|
||||||
|
]
|
||||||
|
) @definition.type
|
||||||
|
)
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(type_constructor_path (type_constructor) @name) @reference.type
|
||||||
|
|
||||||
|
[
|
||||||
|
(constructor_declaration (constructor_name) @name)
|
||||||
|
(tag_specification (tag) @name)
|
||||||
|
] @definition.enum_variant
|
||||||
|
|
||||||
|
[
|
||||||
|
(constructor_path (constructor_name) @name)
|
||||||
|
(tag) @name
|
||||||
|
] @reference.enum_variant
|
||||||
|
|
||||||
|
(field_declaration (field_name) @name) @definition.field
|
||||||
|
|
||||||
|
(field_path (field_name) @name) @reference.field
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(external (value_name) @name) @definition.function
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
(comment)? @doc .
|
||||||
|
(value_specification
|
||||||
|
(value_name) @name.definition.function
|
||||||
|
) @definition.function
|
||||||
|
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||||
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user