mirror of
https://github.com/Aider-AI/aider
synced 2026-04-26 01:25:17 +02:00
Compare commits
2267 Commits
v0.57.2.de
...
v0.69.2.de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c848d59b2 | ||
|
|
b1c04dece9 | ||
|
|
b87c7987bb | ||
|
|
0bef52ae7d | ||
|
|
9ae04cf1ec | ||
|
|
0f7c4a8d4f | ||
|
|
07353207c0 | ||
|
|
d89be83414 | ||
|
|
01382527f5 | ||
|
|
dece2193fc | ||
|
|
d298f864fa | ||
|
|
ff37d8c691 | ||
|
|
f7f305a564 | ||
|
|
86f38e11cd | ||
|
|
0b60c48253 | ||
|
|
024b9840f0 | ||
|
|
2f4f59d82f | ||
|
|
830d5ee763 | ||
|
|
5bc63f7a33 | ||
|
|
b54f970e12 | ||
|
|
0cc8c54152 | ||
|
|
241e1e27d0 | ||
|
|
0145e86202 | ||
|
|
4d50e1e373 | ||
|
|
4b6ae34800 | ||
|
|
2feb85e831 | ||
|
|
a67ac81265 | ||
|
|
dab536c9e8 | ||
|
|
64cf298521 | ||
|
|
10b5aaa6a5 | ||
|
|
8c79e5ccfb | ||
|
|
2ef536a342 | ||
|
|
85b1303460 | ||
|
|
60aca3a241 | ||
|
|
db98381a86 | ||
|
|
f62ef34715 | ||
|
|
f95a6c1a5a | ||
|
|
65555b5dd0 | ||
|
|
9f6331a35e | ||
|
|
8c10cb6230 | ||
|
|
c8894bcead | ||
|
|
edc602c33a | ||
|
|
abfb2ca810 | ||
|
|
9967efe45a | ||
|
|
e77d80bda5 | ||
|
|
a691d1750a | ||
|
|
834e2f9304 | ||
|
|
66e5e9c1ce | ||
|
|
2d5f613984 | ||
|
|
868e7a278f | ||
|
|
f953d17889 | ||
|
|
3473969aae | ||
|
|
ec11ae7c40 | ||
|
|
8217ee1bbb | ||
|
|
1ad3ee0aec | ||
|
|
b3e9a2fede | ||
|
|
2f5d6bf909 | ||
|
|
cd79f479e9 | ||
|
|
12c0f675ce | ||
|
|
29471b8019 | ||
|
|
cad661f0c8 | ||
|
|
261d770482 | ||
|
|
a75c9c74fd | ||
|
|
8b5bdf9a16 | ||
|
|
e35eba2d51 | ||
|
|
df8c88cef4 | ||
|
|
844e12769c | ||
|
|
b982626ac4 | ||
|
|
517b2b42a6 | ||
|
|
6778c33628 | ||
|
|
bc0b11e1ef | ||
|
|
6b0d4b9c93 | ||
|
|
7d4e4f029e | ||
|
|
1fbdb629ff | ||
|
|
a437237947 | ||
|
|
910637c549 | ||
|
|
e8974a3e69 | ||
|
|
6c98310f7f | ||
|
|
72aed0d26d | ||
|
|
e10ef8b9e0 | ||
|
|
39fa8ba831 | ||
|
|
8ee8279044 | ||
|
|
c3f85c3bb2 | ||
|
|
ad59c4cbf3 | ||
|
|
26bc981981 | ||
|
|
514c34b242 | ||
|
|
6f266c0090 | ||
|
|
baa7352ca6 | ||
|
|
fdb1c8d99a | ||
|
|
79f5dba094 | ||
|
|
2134965f16 | ||
|
|
77b7a59a27 | ||
|
|
365e7126d0 | ||
|
|
d3daf9d159 | ||
|
|
8d59a519a6 | ||
|
|
239262f360 | ||
|
|
9eb938fd6f | ||
|
|
3fcbf1a43f | ||
|
|
2b7ee271df | ||
|
|
11512c6281 | ||
|
|
b16ba547ab | ||
|
|
e5b8899b4c | ||
|
|
fee3e9e63b | ||
|
|
57a24d30b5 | ||
|
|
5a83610fb1 | ||
|
|
96086f12c6 | ||
|
|
a6ee3ce07f | ||
|
|
5f36ddd425 | ||
|
|
bec67074e0 | ||
|
|
895c92cdae | ||
|
|
7406a5bc26 | ||
|
|
ef97714404 | ||
|
|
6d6daee511 | ||
|
|
32cd1c2a65 | ||
|
|
abad920a8a | ||
|
|
e1ab49e100 | ||
|
|
df300e89a2 | ||
|
|
9a278bed21 | ||
|
|
4a7fc084ce | ||
|
|
2649e736fb | ||
|
|
42ac891e28 | ||
|
|
4dc3b9072e | ||
|
|
ae3235b099 | ||
|
|
7cc5e0d577 | ||
|
|
f6b956dc8e | ||
|
|
fcb2bacd1e | ||
|
|
a9401e921e | ||
|
|
02e7e315b2 | ||
|
|
def72a64e0 | ||
|
|
46e7672197 | ||
|
|
2f8a5ce935 | ||
|
|
730e5bd831 | ||
|
|
b7984a05af | ||
|
|
5d4af67186 | ||
|
|
370e08cf4d | ||
|
|
aaf7e3f943 | ||
|
|
fbde0936e7 | ||
|
|
af48f7bab4 | ||
|
|
61def89878 | ||
|
|
88e86cee77 | ||
|
|
acc1625406 | ||
|
|
502f448053 | ||
|
|
55b081884c | ||
|
|
fd67171908 | ||
|
|
16a53aa641 | ||
|
|
4aec676950 | ||
|
|
d8fff0e90a | ||
|
|
57ad6cc3ea | ||
|
|
2b46a8b9c9 | ||
|
|
4d1a2e8c53 | ||
|
|
58b1409bd2 | ||
|
|
a2525dbf56 | ||
|
|
36eb2a31b5 | ||
|
|
0483bfc9cb | ||
|
|
3f63a64254 | ||
|
|
16332b285f | ||
|
|
f7371f6faf | ||
|
|
92d29805f1 | ||
|
|
4a37d07acb | ||
|
|
bb4c61e9cc | ||
|
|
f4e5515c82 | ||
|
|
141a2df19c | ||
|
|
e2385b4922 | ||
|
|
9974fb50f7 | ||
|
|
c5d51d62c4 | ||
|
|
4fcbf28f91 | ||
|
|
f678b664ff | ||
|
|
4206920a7d | ||
|
|
ad2e5ead54 | ||
|
|
eb72719117 | ||
|
|
20b46afbf2 | ||
|
|
1925e4a7f4 | ||
|
|
1a12322354 | ||
|
|
73f3f4ec7e | ||
|
|
a8c42f453a | ||
|
|
79dd0c5ba6 | ||
|
|
89bc3b6b16 | ||
|
|
a997dd6c49 | ||
|
|
6aa80d2a4c | ||
|
|
f009e8fb14 | ||
|
|
a8dde17155 | ||
|
|
cbcbff341b | ||
|
|
e50992bb93 | ||
|
|
fa6c3068c0 | ||
|
|
dd9bdca572 | ||
|
|
3f7dffe47d | ||
|
|
c7fde14458 | ||
|
|
57f4186cad | ||
|
|
fa5a6021b1 | ||
|
|
42ae279b91 | ||
|
|
a74cdbfc28 | ||
|
|
a56fa567dd | ||
|
|
c9c2d5ab6f | ||
|
|
ccf460c1f7 | ||
|
|
2325f542b8 | ||
|
|
63fdf3f3f6 | ||
|
|
efa83bdf34 | ||
|
|
5705bda818 | ||
|
|
20042334ff | ||
|
|
7ae9569816 | ||
|
|
cacba526b3 | ||
|
|
50f203cc8a | ||
|
|
3efcd154f3 | ||
|
|
bdb08d7c78 | ||
|
|
f3874dd40a | ||
|
|
935c39e341 | ||
|
|
7ddcc30e8d | ||
|
|
13ff038e58 | ||
|
|
1d4918dfbf | ||
|
|
ba14ab96da | ||
|
|
054e0820b7 | ||
|
|
af3d8dd6a7 | ||
|
|
e10d06cb47 | ||
|
|
87a1469c1e | ||
|
|
2492f1635c | ||
|
|
b3305e6e19 | ||
|
|
491d3e6606 | ||
|
|
e41bf67f73 | ||
|
|
66b99c4fa0 | ||
|
|
f2f2645fe6 | ||
|
|
4c77d0509a | ||
|
|
33d77f4355 | ||
|
|
ecf8617246 | ||
|
|
bec216d72a | ||
|
|
374e8c3307 | ||
|
|
b5eff8a9dd | ||
|
|
d53002a494 | ||
|
|
04662f0875 | ||
|
|
aec92c7c17 | ||
|
|
f7a7976923 | ||
|
|
1b50d4507f | ||
|
|
045cf887df | ||
|
|
277d7c0e04 | ||
|
|
0e2c703e77 | ||
|
|
62b3f38f00 | ||
|
|
422658293d | ||
|
|
74cfd05642 | ||
|
|
80586f1de3 | ||
|
|
a933177ac0 | ||
|
|
ade2db696b | ||
|
|
cf266037ff | ||
|
|
5f14277fbf | ||
|
|
b677349ec9 | ||
|
|
7f72d2a703 | ||
|
|
779983cb85 | ||
|
|
f2d2ab51b1 | ||
|
|
0a54f8c4a7 | ||
|
|
881527ddd0 | ||
|
|
cca4f894a8 | ||
|
|
20dae33be5 | ||
|
|
fd301c519d | ||
|
|
a96d87814b | ||
|
|
2a3eefc5be | ||
|
|
c5dc10e3cd | ||
|
|
c251a93609 | ||
|
|
2df509effd | ||
|
|
53e0d670b3 | ||
|
|
ccd19d2c96 | ||
|
|
b69f084db9 | ||
|
|
9639395937 | ||
|
|
117b7afd81 | ||
|
|
009c4dc8db | ||
|
|
0fdf3fc851 | ||
|
|
ffc2d91cef | ||
|
|
99975c8ff7 | ||
|
|
d44fd6b46d | ||
|
|
bd0a1426d7 | ||
|
|
353d144039 | ||
|
|
6efdf8a7f4 | ||
|
|
623770e24b | ||
|
|
402940a215 | ||
|
|
7fea85cdcd | ||
|
|
93b3b7a184 | ||
|
|
8f2a84629a | ||
|
|
7a1c1982f0 | ||
|
|
75d8982b23 | ||
|
|
87dbb56d7e | ||
|
|
aa217a3a43 | ||
|
|
22fc961dc0 | ||
|
|
8f57186945 | ||
|
|
f8277c55a8 | ||
|
|
6163713e34 | ||
|
|
d2d5887936 | ||
|
|
2a1065efc1 | ||
|
|
6f36a97c4a | ||
|
|
eeed79009a | ||
|
|
9930057171 | ||
|
|
e14c84f2fb | ||
|
|
a664c3dd47 | ||
|
|
73205b1f8c | ||
|
|
3016d7b8f3 | ||
|
|
beb3ddaa1f | ||
|
|
8db48fb4f1 | ||
|
|
6eb3641c1d | ||
|
|
8cfd64a7be | ||
|
|
e8d9ef269b | ||
|
|
80f83da9b8 | ||
|
|
6b8e235f88 | ||
|
|
a1d558a960 | ||
|
|
071ec80e2a | ||
|
|
995541db2e | ||
|
|
9121026856 | ||
|
|
341419788e | ||
|
|
f8f69fadc4 | ||
|
|
4830d82a73 | ||
|
|
f3a228c79a | ||
|
|
4bf96aaca0 | ||
|
|
a8a026d509 | ||
|
|
e1fd506269 | ||
|
|
63e86b5673 | ||
|
|
238299c759 | ||
|
|
4e9272e31c | ||
|
|
8f92c32693 | ||
|
|
699e283890 | ||
|
|
c51afc952c | ||
|
|
8fc030ba83 | ||
|
|
b754757c94 | ||
|
|
16bb40406a | ||
|
|
290ae04988 | ||
|
|
5c6f0e4d32 | ||
|
|
14143243b1 | ||
|
|
0e2c0c78f2 | ||
|
|
2a8e1f41d0 | ||
|
|
4e1d18e206 | ||
|
|
ff5a947a16 | ||
|
|
26ddb1e479 | ||
|
|
b2ddfd21c1 | ||
|
|
efe9268bcb | ||
|
|
974e618541 | ||
|
|
d09d281a8d | ||
|
|
43f3f54063 | ||
|
|
2b303e162c | ||
|
|
3947a755b1 | ||
|
|
0b764f4cb8 | ||
|
|
a0101c14a6 | ||
|
|
a4dcde2fc2 | ||
|
|
f6f5955753 | ||
|
|
d87f9fbd79 | ||
|
|
4c2bc47bf3 | ||
|
|
0d983d504b | ||
|
|
f26ccfa3e9 | ||
|
|
1ec6ba35e0 | ||
|
|
0378ea40e7 | ||
|
|
656250dd33 | ||
|
|
b914fa6da8 | ||
|
|
db77ec43a7 | ||
|
|
1a9d4bfb1c | ||
|
|
7180cb049c | ||
|
|
7e2769d8df | ||
|
|
23faeeb5d0 | ||
|
|
1e689a69a8 | ||
|
|
19004c0bad | ||
|
|
82059bb454 | ||
|
|
3fc846471e | ||
|
|
5379d45184 | ||
|
|
65b8bf6834 | ||
|
|
107dfcac65 | ||
|
|
2a29d077ee | ||
|
|
3d7f1f39ce | ||
|
|
51c02da206 | ||
|
|
16250e1b7c | ||
|
|
3e02c5e4a8 | ||
|
|
40da942bfe | ||
|
|
44909912cd | ||
|
|
631ee98738 | ||
|
|
0b68eea44a | ||
|
|
47743e7f90 | ||
|
|
b2da3545f9 | ||
|
|
fb005dd01b | ||
|
|
5b2bd90071 | ||
|
|
25e258e26a | ||
|
|
75d24974ec | ||
|
|
981bd950da | ||
|
|
0ca1886695 | ||
|
|
bf2f9e0f7e | ||
|
|
d74f1c22ff | ||
|
|
882e09f253 | ||
|
|
ce05c0e13d | ||
|
|
45c92f0868 | ||
|
|
e952f90b4c | ||
|
|
97bf5e8d69 | ||
|
|
ee7bb71a05 | ||
|
|
791cbab6f6 | ||
|
|
e001b7fb46 | ||
|
|
d8a75bef48 | ||
|
|
cfc4b79a05 | ||
|
|
fb3e5c9aab | ||
|
|
6796d8f124 | ||
|
|
154cb463e5 | ||
|
|
e9ce04ffe9 | ||
|
|
7a3fd11b6b | ||
|
|
e885f848a6 | ||
|
|
8343f074a2 | ||
|
|
cbadfd59db | ||
|
|
5d5fed62f2 | ||
|
|
c3d6484f6c | ||
|
|
315abec49c | ||
|
|
221b382e69 | ||
|
|
5fdbfef028 | ||
|
|
2cc671df75 | ||
|
|
ddbe2853ea | ||
|
|
2ab1b4b1c2 | ||
|
|
c5adc92f8b | ||
|
|
96b6056411 | ||
|
|
1108566e99 | ||
|
|
a54a5ee80d | ||
|
|
d57bc3ff01 | ||
|
|
f02dca2dd8 | ||
|
|
ad186701d3 | ||
|
|
a04b9ea053 | ||
|
|
41e6fb961b | ||
|
|
b2b43d2bd3 | ||
|
|
90339f9741 | ||
|
|
260806053b | ||
|
|
93c9bdcec7 | ||
|
|
935832028a | ||
|
|
c08f09b06b | ||
|
|
16410cf263 | ||
|
|
c62dde4c9c | ||
|
|
3fef1babcb | ||
|
|
59af7ed3bb | ||
|
|
ee4206099a | ||
|
|
8c1d9bffcd | ||
|
|
a3a510308c | ||
|
|
97e292e251 | ||
|
|
4c5aa6a326 | ||
|
|
d1dd40590f | ||
|
|
c4764fa4d8 | ||
|
|
5dedb39048 | ||
|
|
bc759a32e3 | ||
|
|
dcc0841443 | ||
|
|
9b0388d00a | ||
|
|
99a1e42381 | ||
|
|
9b4df54432 | ||
|
|
aac0c0f037 | ||
|
|
a8f20196b2 | ||
|
|
a8bae3fb01 | ||
|
|
e8ccc030c0 | ||
|
|
aa6d9779d3 | ||
|
|
b1dbf340b8 | ||
|
|
27540d16ed | ||
|
|
2e63f62215 | ||
|
|
633ba293a3 | ||
|
|
679c960ea1 | ||
|
|
cf91101a48 | ||
|
|
344d90aea0 | ||
|
|
8b7fa6f845 | ||
|
|
2eb7f78248 | ||
|
|
9a5925b1ad | ||
|
|
60f2da370f | ||
|
|
7ee69628a3 | ||
|
|
a64f14db51 | ||
|
|
1c84fc97a1 | ||
|
|
8b371ead92 | ||
|
|
14417f70af | ||
|
|
96c6d408fb | ||
|
|
cd922e919e | ||
|
|
c56f06fbcb | ||
|
|
21dffa26b9 | ||
|
|
a131d5ad35 | ||
|
|
b5dbb1d39d | ||
|
|
bd3231d8dd | ||
|
|
4d96de3514 | ||
|
|
c9df6c11c5 | ||
|
|
56aaa08224 | ||
|
|
014aeccde6 | ||
|
|
dcfa993806 | ||
|
|
69688cffd6 | ||
|
|
769a31b7e3 | ||
|
|
78c20a8c25 | ||
|
|
bb1b3fdca1 | ||
|
|
f872b20a97 | ||
|
|
f36239712f | ||
|
|
74108dd680 | ||
|
|
16af1751a6 | ||
|
|
b0e138952e | ||
|
|
1271c037ef | ||
|
|
36b59ba617 | ||
|
|
b671db7108 | ||
|
|
9304b80b69 | ||
|
|
03c2964364 | ||
|
|
49c78f2797 | ||
|
|
1a8d112055 | ||
|
|
7b193d693f | ||
|
|
e8fa5c36c2 | ||
|
|
c9a27ba6ac | ||
|
|
a6e162c37a | ||
|
|
9c55b7a317 | ||
|
|
4ef4e8cd72 | ||
|
|
e9942737c6 | ||
|
|
5c208dba41 | ||
|
|
ba032ce60e | ||
|
|
2fe0dda8af | ||
|
|
97daff4a10 | ||
|
|
caeceb58a5 | ||
|
|
3739d48e52 | ||
|
|
858fdb02ec | ||
|
|
b0cbb071e6 | ||
|
|
f222739b0d | ||
|
|
a280f5d5b2 | ||
|
|
cef333a01c | ||
|
|
24bd016a1a | ||
|
|
3c44f824cb | ||
|
|
3ee78ef557 | ||
|
|
d9b1bcbdcc | ||
|
|
283dd0b1f1 | ||
|
|
56345ddef9 | ||
|
|
4631008f8c | ||
|
|
e2ebde75be | ||
|
|
91613fcbf7 | ||
|
|
0b279143cc | ||
|
|
ade4847c61 | ||
|
|
a3e7203331 | ||
|
|
bbabd38a48 | ||
|
|
eb996c1e42 | ||
|
|
bf0ad4cda1 | ||
|
|
c32af6536a | ||
|
|
2437f9b051 | ||
|
|
0b781174bd | ||
|
|
f448d3e2db | ||
|
|
6b4222ec48 | ||
|
|
3f770cc5c0 | ||
|
|
14902f5b9a | ||
|
|
efdeb13bf5 | ||
|
|
62558291b6 | ||
|
|
edb0120bbf | ||
|
|
0dbaec553f | ||
|
|
295040c94c | ||
|
|
c1c4193b1d | ||
|
|
d17f0a9e1f | ||
|
|
871030dadb | ||
|
|
0ee9b74c0f | ||
|
|
82929f650c | ||
|
|
37b31c46bd | ||
|
|
c682bd858a | ||
|
|
2439891ee0 | ||
|
|
23825cafe7 | ||
|
|
a8f6f1fce2 | ||
|
|
e11faadf39 | ||
|
|
2c7251f4b9 | ||
|
|
fb9f18fc9c | ||
|
|
93cd3d0d8b | ||
|
|
ed2479ea82 | ||
|
|
accde0bfd0 | ||
|
|
7b34d9e4f4 | ||
|
|
6af71951af | ||
|
|
3eed45dc3e | ||
|
|
320b059bc7 | ||
|
|
a89ce06377 | ||
|
|
e4a1d6fe89 | ||
|
|
93c625bb81 | ||
|
|
87f84fb82d | ||
|
|
ce9e76a7dc | ||
|
|
4f3f1c5e23 | ||
|
|
22076d401f | ||
|
|
79ea82f147 | ||
|
|
3785415632 | ||
|
|
35eda73ad1 | ||
|
|
c8282fc8d3 | ||
|
|
5030881934 | ||
|
|
bc1e3a7059 | ||
|
|
ba17dceb4d | ||
|
|
4e689e20e9 | ||
|
|
2f78b6f7f3 | ||
|
|
920917a47e | ||
|
|
c1a7784781 | ||
|
|
62109f4ab1 | ||
|
|
c3a9c05455 | ||
|
|
333e4aa362 | ||
|
|
59ce26f28a | ||
|
|
2ff63aaebf | ||
|
|
8cf72a4409 | ||
|
|
496b92536f | ||
|
|
ac7900ecd7 | ||
|
|
310832f111 | ||
|
|
f69dd0ef8d | ||
|
|
4b51bb2334 | ||
|
|
0d4f9f00ee | ||
|
|
4a0100271f | ||
|
|
52a467806a | ||
|
|
0fedecff58 | ||
|
|
da4cb0aa62 | ||
|
|
3b8a771909 | ||
|
|
f9bcfa14e0 | ||
|
|
11c2eab967 | ||
|
|
094d2e12a4 | ||
|
|
af195a610c | ||
|
|
f7a05cf077 | ||
|
|
d54fbd6592 | ||
|
|
41b0868165 | ||
|
|
a1f5bfb746 | ||
|
|
3ab1018c66 | ||
|
|
62e96372fa | ||
|
|
945d10f554 | ||
|
|
f44e5ae5f9 | ||
|
|
a67b665846 | ||
|
|
8fb23b414c | ||
|
|
f5100626a8 | ||
|
|
9ab46fade7 | ||
|
|
2ce01b157b | ||
|
|
f714e42e11 | ||
|
|
447b7af573 | ||
|
|
ec2b635a1a | ||
|
|
7465b4bf91 | ||
|
|
4580fac6fa | ||
|
|
8c218e9edc | ||
|
|
44ceb8f1a0 | ||
|
|
642c1c50fb | ||
|
|
4de8c25a3f | ||
|
|
565f08a8e9 | ||
|
|
a85ae206c9 | ||
|
|
9e9b5e8d46 | ||
|
|
b623141a8f | ||
|
|
631cdc37c4 | ||
|
|
8d50bc0ef1 | ||
|
|
ae395fbb8f | ||
|
|
10877a99f1 | ||
|
|
0faff91c72 | ||
|
|
00f79fecd0 | ||
|
|
203128d935 | ||
|
|
4f6e52aed0 | ||
|
|
7bc7b2e3da | ||
|
|
27f0ca3b08 | ||
|
|
2337b2bb3e | ||
|
|
48ea13e130 | ||
|
|
5c73ab26c0 | ||
|
|
947e4ce71d | ||
|
|
200295e3ee | ||
|
|
ded5fe5ec0 | ||
|
|
7dffa943fa | ||
|
|
f702f67e27 | ||
|
|
a64956406d | ||
|
|
9c8bde2cff | ||
|
|
70a282ebf1 | ||
|
|
415652d38e | ||
|
|
1a745e4fa9 | ||
|
|
973e86df27 | ||
|
|
1f2917681f | ||
|
|
28d1feacf5 | ||
|
|
cf4ef8605d | ||
|
|
4be3728273 | ||
|
|
34b8c3f47c | ||
|
|
309893fd1e | ||
|
|
705eb06e8d | ||
|
|
5cfcf255e9 | ||
|
|
b8f36c8277 | ||
|
|
73c1dc697f | ||
|
|
a9c4647461 | ||
|
|
aaeaa24153 | ||
|
|
59a7494770 | ||
|
|
b2232cda7b | ||
|
|
3ba4aca268 | ||
|
|
f45533e20b | ||
|
|
2e7c5d6cfa | ||
|
|
476acc7715 | ||
|
|
ab3b50296c | ||
|
|
60c29b2839 | ||
|
|
7972f5f4bc | ||
|
|
83a6865eb3 | ||
|
|
29a9b650ed | ||
|
|
dd48b740f9 | ||
|
|
401ce7a63d | ||
|
|
8218d085f7 | ||
|
|
abb3c936f7 | ||
|
|
0cb5aca81e | ||
|
|
554d274fff | ||
|
|
13318219db | ||
|
|
474944fe74 | ||
|
|
d1a49cd9ce | ||
|
|
139d89d817 | ||
|
|
61759f984c | ||
|
|
a73e77a819 | ||
|
|
b5f1659382 | ||
|
|
beb6722f57 | ||
|
|
a052b89152 | ||
|
|
95b391350f | ||
|
|
5773eac03f | ||
|
|
da4ace2875 | ||
|
|
e507c5b502 | ||
|
|
722c2c2668 | ||
|
|
8e7bfef9f1 | ||
|
|
1811f0d0d5 | ||
|
|
6b9d534fe2 | ||
|
|
dc4562a845 | ||
|
|
c15af63bc9 | ||
|
|
5b68c2c7d9 | ||
|
|
b70e0bd1f6 | ||
|
|
743f0f5540 | ||
|
|
e647a5b733 | ||
|
|
df1d259e42 | ||
|
|
e3efab7fbf | ||
|
|
cd79f7f4b0 | ||
|
|
5d175745bf | ||
|
|
e648bac74b | ||
|
|
e5c0ebd0a0 | ||
|
|
608a43402c | ||
|
|
75bc2dd564 | ||
|
|
bbd81c3cf7 | ||
|
|
301eb7c74d | ||
|
|
a756039f27 | ||
|
|
b4d1b71ee7 | ||
|
|
8d85a4754d | ||
|
|
a1b48049a9 | ||
|
|
6789844c1f | ||
|
|
c602a839ca | ||
|
|
01c7793e90 | ||
|
|
47b013b034 | ||
|
|
dc2047804a | ||
|
|
885e5cbd7c | ||
|
|
635a5196e8 | ||
|
|
cbd339190b | ||
|
|
266093189d | ||
|
|
62b02d4370 | ||
|
|
8546a1dc86 | ||
|
|
ae98bf237f | ||
|
|
0398deb005 | ||
|
|
3a1492977b | ||
|
|
ef40a456e8 | ||
|
|
583b78c0c1 | ||
|
|
c0988de581 | ||
|
|
34a190e29b | ||
|
|
0940598708 | ||
|
|
7e6cbb3efa | ||
|
|
7dc4e00c75 | ||
|
|
8a598eacaf | ||
|
|
8592fad9cd | ||
|
|
fa72a89d35 | ||
|
|
5e99c51d93 | ||
|
|
905976e765 | ||
|
|
3ebd47d3db | ||
|
|
bf4c7c475a | ||
|
|
bf38371971 | ||
|
|
18460f4f91 | ||
|
|
f94e3e6aba | ||
|
|
1647da2942 | ||
|
|
86e2cdb1fb | ||
|
|
764702a377 | ||
|
|
837a97ffdf | ||
|
|
217e9b96d8 | ||
|
|
524274fcf4 | ||
|
|
6d5f576b92 | ||
|
|
445f9fa7df | ||
|
|
2ff3a23606 | ||
|
|
c5ce57ea7f | ||
|
|
351b8e50f0 | ||
|
|
68be6c5742 | ||
|
|
7a34a2dfa9 | ||
|
|
49ce9e1209 | ||
|
|
c84e192324 | ||
|
|
d696673f07 | ||
|
|
2957d463c9 | ||
|
|
af48e50898 | ||
|
|
f4b964a4b8 | ||
|
|
a79ce7a151 | ||
|
|
21bb83c55a | ||
|
|
7122ceb16c | ||
|
|
f9bcfe341c | ||
|
|
13c5bfdd88 | ||
|
|
bf79c2cb99 | ||
|
|
325cdfcf57 | ||
|
|
7d14d4ade9 | ||
|
|
1b7d12194e | ||
|
|
91f238aded | ||
|
|
78ff489995 | ||
|
|
ff791439e2 | ||
|
|
3f8b2d6b99 | ||
|
|
4dcbce58ed | ||
|
|
0427deb897 | ||
|
|
f3eb3409e3 | ||
|
|
86619052ca | ||
|
|
0c59d3234e | ||
|
|
939d7ea3fb | ||
|
|
dc8761763d | ||
|
|
4894914db1 | ||
|
|
aee94a0584 | ||
|
|
c550422168 | ||
|
|
cebd9cabb3 | ||
|
|
3f16652d56 | ||
|
|
2ebf48ca71 | ||
|
|
c2f184f5bb | ||
|
|
e56651e5c0 | ||
|
|
1d09e96127 | ||
|
|
73de0ea8be | ||
|
|
757eac0579 | ||
|
|
bb78e2f57f | ||
|
|
92579243c5 | ||
|
|
8d0ba40d67 | ||
|
|
ff8c1aace9 | ||
|
|
cf74dc9b48 | ||
|
|
e63df83091 | ||
|
|
14522dbbcd | ||
|
|
91daea9e01 | ||
|
|
12b789fc4e | ||
|
|
baa13351a6 | ||
|
|
8f83204f0f | ||
|
|
80f5b60e1d | ||
|
|
54525f6696 | ||
|
|
3dc50216b5 | ||
|
|
324430a696 | ||
|
|
65d7957610 | ||
|
|
6ac4993cf2 | ||
|
|
100744a952 | ||
|
|
3a331e55dc | ||
|
|
a57f81ba5f | ||
|
|
ca0b55fbbf | ||
|
|
38a3cf98dd | ||
|
|
1234ad92e5 | ||
|
|
488edc24ce | ||
|
|
307c23631a | ||
|
|
83d2241883 | ||
|
|
f9126416e8 | ||
|
|
4e9ae16cb3 | ||
|
|
ef8bfdffa7 | ||
|
|
bfb090331f | ||
|
|
5506d0f25b | ||
|
|
6ebd2d0883 | ||
|
|
a16dcaba4e | ||
|
|
f06452c6c5 | ||
|
|
6a0a97cb41 | ||
|
|
711102b438 | ||
|
|
6d53eb0aaa | ||
|
|
0ccf04a2c5 | ||
|
|
070ce35b44 | ||
|
|
a8296e5de5 | ||
|
|
d17f25e975 | ||
|
|
23095ada85 | ||
|
|
f9ef161991 | ||
|
|
28004bae2f | ||
|
|
17aef7be7d | ||
|
|
ebba8f5110 | ||
|
|
dbd7f51f5c | ||
|
|
fbadfcfa7c | ||
|
|
2c12234604 | ||
|
|
13cb6a315c | ||
|
|
48e7376002 | ||
|
|
60d82eddee | ||
|
|
30ee89c7e9 | ||
|
|
25bcea6aec | ||
|
|
488c88da91 | ||
|
|
8fdcd92260 | ||
|
|
781a40df52 | ||
|
|
2412c81d92 | ||
|
|
a7fc0f9d2e | ||
|
|
9eab021a50 | ||
|
|
c189a52e5e | ||
|
|
6d6d763dd3 | ||
|
|
3cfbaa0ed6 | ||
|
|
e1b4571fdf | ||
|
|
08027ea9c4 | ||
|
|
d0528a00c1 | ||
|
|
7ae6a2ba9a | ||
|
|
ad0e5c4770 | ||
|
|
51b181d5fb | ||
|
|
2785b0a857 | ||
|
|
45a113ff9e | ||
|
|
cee59c2ca3 | ||
|
|
5cb9b242e2 | ||
|
|
1ed48de928 | ||
|
|
c4e4bc71ed | ||
|
|
ed0763706b | ||
|
|
1c79d517fc | ||
|
|
1a7a2f3088 | ||
|
|
4199789786 | ||
|
|
c9b0941d1f | ||
|
|
3d544136d2 | ||
|
|
8448eff1eb | ||
|
|
a3dde4599a | ||
|
|
3d7440c673 | ||
|
|
8f41006eba | ||
|
|
7d1379fe9f | ||
|
|
ced4f9f0d9 | ||
|
|
7fba332f58 | ||
|
|
8eda09533d | ||
|
|
6c42ee4edf | ||
|
|
3053595bfe | ||
|
|
6cf78d1a3f | ||
|
|
6c0ee9d917 | ||
|
|
50051dbfd5 | ||
|
|
30a8c5e12e | ||
|
|
4498549783 | ||
|
|
2ce3fedbfc | ||
|
|
8e10be665c | ||
|
|
80636cb8aa | ||
|
|
1b893da07d | ||
|
|
2f8aa3471b | ||
|
|
92a6957f0b | ||
|
|
d7248b11e5 | ||
|
|
6f4ed00105 | ||
|
|
347b299bbc | ||
|
|
614d297f22 | ||
|
|
59d0fe00c3 | ||
|
|
d6ebdff28f | ||
|
|
0ec16d0eab | ||
|
|
3906894fde | ||
|
|
a43772b21d | ||
|
|
cd81c2619b | ||
|
|
5abadc31a3 | ||
|
|
87654b698b | ||
|
|
2a1d2ef294 | ||
|
|
2a387707ef | ||
|
|
ce68062290 | ||
|
|
1b9073b085 | ||
|
|
e94961a14f | ||
|
|
d4e77b3be4 | ||
|
|
b96b36d5ea | ||
|
|
72d559a92c | ||
|
|
9a7bdcb6db | ||
|
|
2e4cf48e7e | ||
|
|
b78f8abeb4 | ||
|
|
7aa4e44317 | ||
|
|
2619b92131 | ||
|
|
28be59582f | ||
|
|
f96cc03587 | ||
|
|
cbd35b0e87 | ||
|
|
443acbe4b5 | ||
|
|
1f0d26e8c7 | ||
|
|
8302e9d0dd | ||
|
|
c797af020a | ||
|
|
1c85afa320 | ||
|
|
eb5317f8e5 | ||
|
|
8b860615b8 | ||
|
|
c15ac341e2 | ||
|
|
c2c7ee1047 | ||
|
|
72c46ccec6 | ||
|
|
dd3bfaee01 | ||
|
|
03206ad90e | ||
|
|
2e00307190 | ||
|
|
b3e29ab20e | ||
|
|
5504ac535b | ||
|
|
4b3dd7f4ea | ||
|
|
8edf9540d5 | ||
|
|
1c62ecd1b5 | ||
|
|
7cf3d9f3ce | ||
|
|
9b5a703307 | ||
|
|
370993cbed | ||
|
|
ddc538cdfa | ||
|
|
062dc43c87 | ||
|
|
7d9b986c04 | ||
|
|
bd2b9a12ed | ||
|
|
2b55707738 | ||
|
|
093540507e | ||
|
|
8f1dcfda07 | ||
|
|
16b319174b | ||
|
|
35115f5707 | ||
|
|
e9e51db9c7 | ||
|
|
ec39f018e2 | ||
|
|
59f4a3bcc7 | ||
|
|
1a8949eea3 | ||
|
|
fa85bdceed | ||
|
|
6b703244ec | ||
|
|
1a1cb0d3f1 | ||
|
|
95c9863d0a | ||
|
|
82187f6a71 | ||
|
|
75f52a1324 | ||
|
|
6e076a40a9 | ||
|
|
ded60036cb | ||
|
|
539a6cde63 | ||
|
|
3a28e74d89 | ||
|
|
c93f3faed9 | ||
|
|
1aaa3d9279 | ||
|
|
a06a9ed7d8 | ||
|
|
18a88596a6 | ||
|
|
2ac077603b | ||
|
|
3bb8b163b8 | ||
|
|
aac45097ca | ||
|
|
788956e86d | ||
|
|
e917424f5d | ||
|
|
1f6a5d04d9 | ||
|
|
dba844c7f9 | ||
|
|
09c11ef8ad | ||
|
|
ff21669bb5 | ||
|
|
a059ca2537 | ||
|
|
f4531b9434 | ||
|
|
05b350ac4b | ||
|
|
cfba315a85 | ||
|
|
12188b6684 | ||
|
|
725b2fc16f | ||
|
|
90a28e31e8 | ||
|
|
c057b00e08 | ||
|
|
b0c0104ba3 | ||
|
|
855b184e91 | ||
|
|
bc88242dc0 | ||
|
|
6357cd623f | ||
|
|
d3e7d20e30 | ||
|
|
7b568c2df3 | ||
|
|
2bff0522e8 | ||
|
|
77e180171c | ||
|
|
1e8fc97ba4 | ||
|
|
a799851832 | ||
|
|
2c81105bfc | ||
|
|
bf99ac56a0 | ||
|
|
8bfb47948a | ||
|
|
b8c41198a3 | ||
|
|
fe138ac05b | ||
|
|
bed62fdcb5 | ||
|
|
f068df0297 | ||
|
|
6a05e34d20 | ||
|
|
8b69b9c6a4 | ||
|
|
10192296fe | ||
|
|
7b98db2f7d | ||
|
|
81696c2e7a | ||
|
|
16a4f823bd | ||
|
|
39bbfc56a4 | ||
|
|
308c49b2d8 | ||
|
|
53e46a9251 | ||
|
|
6075b3dc33 | ||
|
|
860a828973 | ||
|
|
033eaa36c6 | ||
|
|
b3566f5449 | ||
|
|
f1306d3301 | ||
|
|
cc18441435 | ||
|
|
7e787d93e6 | ||
|
|
b08f444e74 | ||
|
|
09c1acdbe2 | ||
|
|
7a4a956eff | ||
|
|
aabbf5a72f | ||
|
|
5ed44bb174 | ||
|
|
92cf2cbd47 | ||
|
|
34aff6c786 | ||
|
|
11f10e586a | ||
|
|
a31feae2bd | ||
|
|
3517d58f01 | ||
|
|
9d13fadd41 | ||
|
|
c1febce528 | ||
|
|
e0aadbd961 | ||
|
|
7b9a76c5ea | ||
|
|
6133fa8384 | ||
|
|
67cc215ed3 | ||
|
|
ea4ad2ea4d | ||
|
|
847454a4f7 | ||
|
|
8cba1fdd71 | ||
|
|
e56112739c | ||
|
|
76988b746f | ||
|
|
32eb365a0b | ||
|
|
2c5feddad5 | ||
|
|
210500ff3e | ||
|
|
7c8f10e832 | ||
|
|
644da2f9e4 | ||
|
|
2a80d9c555 | ||
|
|
76f8791f3c | ||
|
|
c51a013109 | ||
|
|
88c2f95ea5 | ||
|
|
062fa7de78 | ||
|
|
d6c1a41e8d | ||
|
|
20ab5f9326 | ||
|
|
ab5a8b24a5 | ||
|
|
21a28623ca | ||
|
|
06a5fec612 | ||
|
|
46ecb8a663 | ||
|
|
3c9c6eef6e | ||
|
|
72734de376 | ||
|
|
a20ea09a6e | ||
|
|
4bd7bce232 | ||
|
|
8adf42216d | ||
|
|
5f40aaabb5 | ||
|
|
5295abbb83 | ||
|
|
201abe1524 | ||
|
|
6d26051d11 | ||
|
|
4613bf78d5 | ||
|
|
1f8fdc6f5c | ||
|
|
abf804cf10 | ||
|
|
d7a195706f | ||
|
|
569370109a | ||
|
|
eae3f04c83 | ||
|
|
ee0987f331 | ||
|
|
bdafa842bd | ||
|
|
76c7c2562c | ||
|
|
f7de2234f2 | ||
|
|
c725c45726 | ||
|
|
383bef456d | ||
|
|
7f48f3d01d | ||
|
|
0bf17a48f7 | ||
|
|
c127f8f2f0 | ||
|
|
d4d5d15e18 | ||
|
|
b254afa498 | ||
|
|
0ce5a94c15 | ||
|
|
38a5405c65 | ||
|
|
ecef784686 | ||
|
|
e82b2c12b4 | ||
|
|
3c26ced8db | ||
|
|
2681a41abb | ||
|
|
805deb1002 | ||
|
|
6b792de802 | ||
|
|
26f8e34dcb | ||
|
|
e0c1b2458c | ||
|
|
66f94d2141 | ||
|
|
503a9a0038 | ||
|
|
d8a5bc3ae9 | ||
|
|
94c3957d92 | ||
|
|
266350b8ce | ||
|
|
fedaede3b0 | ||
|
|
721d852cc7 | ||
|
|
ffbf205aba | ||
|
|
e1a1e43c3a | ||
|
|
c538817b61 | ||
|
|
71d85d2771 | ||
|
|
69f4d5fca7 | ||
|
|
bb31fc5301 | ||
|
|
87ce51e314 | ||
|
|
33555ca2ea | ||
|
|
6fc3776c0c | ||
|
|
8a05f05bd4 | ||
|
|
0dff51920e | ||
|
|
262f217d04 | ||
|
|
8b9154bab0 | ||
|
|
533613d92b | ||
|
|
20d87e1136 | ||
|
|
1450c4194e | ||
|
|
bc82baa968 | ||
|
|
218623be28 | ||
|
|
ea1a4ecdc6 | ||
|
|
6acbff3c11 | ||
|
|
153021efcf | ||
|
|
fef1b59b42 | ||
|
|
733b11b7d4 | ||
|
|
400514ff10 | ||
|
|
bbfac316fc | ||
|
|
7d0b67f70e | ||
|
|
d0f1b38848 | ||
|
|
4d4b5bc366 | ||
|
|
1f39c7ef0d | ||
|
|
2e97fcc47f | ||
|
|
550c8322c4 | ||
|
|
2467e23033 | ||
|
|
0e53198c59 | ||
|
|
94ff9dc0a4 | ||
|
|
44063590e2 | ||
|
|
557f25bf80 | ||
|
|
190531543f | ||
|
|
acfb7c3a89 | ||
|
|
94a6d3bc7e | ||
|
|
5751bcd382 | ||
|
|
21b88c0e65 | ||
|
|
f66b916d4b | ||
|
|
bd9c43a48d | ||
|
|
352b91f342 | ||
|
|
be6e3254ea | ||
|
|
dd1ee209ab | ||
|
|
c0b1101a52 | ||
|
|
52c49fc8fd | ||
|
|
77cb64958e | ||
|
|
b3d13e44b2 | ||
|
|
9dd7b795ca | ||
|
|
7a8399571a | ||
|
|
d0e85d9c2c | ||
|
|
14d02bc843 | ||
|
|
b79c09cf58 | ||
|
|
c9dfe5daff | ||
|
|
092e7f6b3c | ||
|
|
203634314c | ||
|
|
c472e6e160 | ||
|
|
86d9275375 | ||
|
|
9db6780af6 | ||
|
|
e10205ff69 | ||
|
|
9f539436b8 | ||
|
|
acd75e1083 | ||
|
|
f30225db90 | ||
|
|
007305962a | ||
|
|
8065e38797 | ||
|
|
34dc684867 | ||
|
|
7edc9603d0 | ||
|
|
479d476878 | ||
|
|
92bbec1852 | ||
|
|
d406636878 | ||
|
|
6362199363 | ||
|
|
22dbcb7590 | ||
|
|
cefea38ee5 | ||
|
|
d44cd01493 | ||
|
|
e578655653 | ||
|
|
434dc27557 | ||
|
|
d4a88d08e4 | ||
|
|
79af39bd2c | ||
|
|
20d5a9fd4b | ||
|
|
80e57ca074 | ||
|
|
8a3c95d8dd | ||
|
|
4941a360cb | ||
|
|
9e7219c4d6 | ||
|
|
4d96728709 | ||
|
|
816fd5e65c | ||
|
|
8d4175536f | ||
|
|
bba9ca3d5a | ||
|
|
8bc9ebf2aa | ||
|
|
dad335b8b6 | ||
|
|
62e93d4002 | ||
|
|
728f4a0f81 | ||
|
|
0cafd6ee4b | ||
|
|
2962e51dac | ||
|
|
cf5733b237 | ||
|
|
c96e3326bc | ||
|
|
1cd373c0a5 | ||
|
|
ae970cf2da | ||
|
|
d43a01f182 | ||
|
|
42aac55b82 | ||
|
|
a66f31dc87 | ||
|
|
64c48f2151 | ||
|
|
9eead8a904 | ||
|
|
7761bccffe | ||
|
|
a848933875 | ||
|
|
e475f3d752 | ||
|
|
4d24dbc661 | ||
|
|
0368c3fae9 | ||
|
|
2cf93ccb54 | ||
|
|
3d72cafea4 | ||
|
|
af0466ea83 | ||
|
|
c84f2996ec | ||
|
|
add9b83d3b | ||
|
|
5411fb6fd4 | ||
|
|
49fc1b40e5 | ||
|
|
daef2eecdd | ||
|
|
1520422cc3 | ||
|
|
c7530085a6 | ||
|
|
bf43c567d8 | ||
|
|
b81f3e4f8d | ||
|
|
97051b9d40 | ||
|
|
90730845de | ||
|
|
f7c0c433c3 | ||
|
|
538752d0cf | ||
|
|
c71a92ac84 | ||
|
|
85f23b3408 | ||
|
|
44cab0a4d7 | ||
|
|
33db8ee0c3 | ||
|
|
da4b3770c0 | ||
|
|
12698998b9 | ||
|
|
6177856baf | ||
|
|
54b9c46b96 | ||
|
|
2dd83e7dbe | ||
|
|
17351e8f91 | ||
|
|
e8c153f72f | ||
|
|
7fc7ea6aba | ||
|
|
ddfd1276c5 | ||
|
|
46251c6a1c | ||
|
|
e699968be5 | ||
|
|
389b58b75b | ||
|
|
a7cf34dea4 | ||
|
|
e601682706 | ||
|
|
55f16dc0b5 | ||
|
|
775011033f | ||
|
|
8ffe21a2dd | ||
|
|
73d63ef5ce | ||
|
|
e12b1a9184 | ||
|
|
d099a95b92 | ||
|
|
496ed90439 | ||
|
|
7883db1834 | ||
|
|
0cfc23b1a8 | ||
|
|
d33104aec1 | ||
|
|
711b2a431c | ||
|
|
09d21b5951 | ||
|
|
d9193387cc | ||
|
|
d5330ae2f3 | ||
|
|
571c1b47b5 | ||
|
|
0c9d4dd123 | ||
|
|
7d79408683 | ||
|
|
e6d4c3558b | ||
|
|
3be2109964 | ||
|
|
ce37ff26b5 | ||
|
|
03bbdb010f | ||
|
|
0bde1da42c | ||
|
|
987cb3bca9 | ||
|
|
0b11024967 | ||
|
|
dfaaedb466 | ||
|
|
8bc0d5544d | ||
|
|
c67e63bc09 | ||
|
|
129f5fae76 | ||
|
|
966a613ffe | ||
|
|
96ad107c19 | ||
|
|
ebdc126b00 | ||
|
|
5e1be966ed | ||
|
|
8801fda972 | ||
|
|
a8a3e2401b | ||
|
|
a7f59a2e2b | ||
|
|
d8e9da35d6 | ||
|
|
b57ad6929c | ||
|
|
0022c1a67e | ||
|
|
6a0380b8c0 | ||
|
|
305b4fee87 | ||
|
|
9e18fc55d3 | ||
|
|
ebc9a30cc6 | ||
|
|
f53f5927df | ||
|
|
78a8d19ac5 | ||
|
|
bfa9dce1e0 | ||
|
|
3b2f1f1e66 | ||
|
|
b2bcca967f | ||
|
|
929aeb22bf | ||
|
|
66ad186edd | ||
|
|
c098354a67 | ||
|
|
d67d319b31 | ||
|
|
93639039d4 | ||
|
|
789535cb85 | ||
|
|
d8e1e823e7 | ||
|
|
5b9fe6fee0 | ||
|
|
91bc8cddf3 | ||
|
|
392162ae84 | ||
|
|
0c4e4a123a | ||
|
|
27711010a0 | ||
|
|
401967c27f | ||
|
|
d1b25b9a72 | ||
|
|
df478e1f8f | ||
|
|
a459f0e130 | ||
|
|
66b6788990 | ||
|
|
624f9122ab | ||
|
|
704350286c | ||
|
|
ec40ff5987 | ||
|
|
af7aaad903 | ||
|
|
3794cf941f | ||
|
|
72c1f25005 | ||
|
|
c13536e641 | ||
|
|
7a9988c060 | ||
|
|
b9939d4bd9 | ||
|
|
b71a3148cf | ||
|
|
42abdb0a41 | ||
|
|
4c39e92773 | ||
|
|
b7f7204c4e | ||
|
|
550d274941 | ||
|
|
5a974483b9 | ||
|
|
8f73c15f48 | ||
|
|
17330e53c3 | ||
|
|
3fcd79e165 | ||
|
|
750b12282f | ||
|
|
94c5ff2fd2 | ||
|
|
85ad2826da | ||
|
|
15c62e3e43 | ||
|
|
fea7134064 | ||
|
|
da706d9eb8 | ||
|
|
9a37c0491d | ||
|
|
1d3d6a589f | ||
|
|
c5a439e4e8 | ||
|
|
2b7584b651 | ||
|
|
1ef60b5a40 | ||
|
|
b79321da51 | ||
|
|
6829b29ef4 | ||
|
|
a9c0d20347 | ||
|
|
25a906950c | ||
|
|
740534dcff | ||
|
|
de65d86122 | ||
|
|
98bf9bd26d | ||
|
|
ade615c445 | ||
|
|
d959e1c60d | ||
|
|
8d2f66fe34 | ||
|
|
66dc8dbccd | ||
|
|
b228bcab8c | ||
|
|
ccbf1482c1 | ||
|
|
f9a0946472 | ||
|
|
faa80b7699 | ||
|
|
1eb2c724a5 | ||
|
|
c43e7f998e | ||
|
|
3e2454b84b | ||
|
|
2817766cf5 | ||
|
|
ba7656bc1c | ||
|
|
c14392a35a | ||
|
|
6bfb258473 | ||
|
|
1e872599fd | ||
|
|
c08b7f9c22 | ||
|
|
6e4ccf8715 | ||
|
|
e4913669d4 | ||
|
|
8e098752bd | ||
|
|
6867dab89c | ||
|
|
09a9fac91e | ||
|
|
2fd1681fab | ||
|
|
ece91dc724 | ||
|
|
01bf154151 | ||
|
|
b5916981b3 | ||
|
|
a68b87272b | ||
|
|
88b55df1c6 | ||
|
|
676c017eb1 | ||
|
|
139b8a2d4a | ||
|
|
24c68928d6 | ||
|
|
f957111141 | ||
|
|
e94e60b1d2 | ||
|
|
a565a63436 | ||
|
|
a045bda171 | ||
|
|
a3d78e0944 | ||
|
|
8082cbed98 | ||
|
|
aba8b5d00a | ||
|
|
3d5a4d9303 | ||
|
|
a899b0e27e | ||
|
|
267872b7e4 | ||
|
|
bd59a8debf | ||
|
|
c0e137889c | ||
|
|
5b84b901b2 | ||
|
|
059883abf7 | ||
|
|
0b622a6fd7 | ||
|
|
97989dd51a | ||
|
|
d81c421bfe | ||
|
|
bce586f510 | ||
|
|
a70b364842 | ||
|
|
920e8da57c | ||
|
|
71a8b286dc | ||
|
|
ee4decc50b | ||
|
|
63330aa833 | ||
|
|
15a0eb976f | ||
|
|
4f52ad385a | ||
|
|
cd1496f91b | ||
|
|
31babf39cd | ||
|
|
fe3dbba1d9 | ||
|
|
763724ed4e | ||
|
|
9e0e68caf9 | ||
|
|
52f697e513 | ||
|
|
aeca62bcf6 | ||
|
|
7e574bc214 | ||
|
|
e9beb1336c | ||
|
|
4ac8386313 | ||
|
|
f0233455d2 | ||
|
|
5c28dd039c | ||
|
|
26d1ab7a5f | ||
|
|
068fb38a5d | ||
|
|
53e7eba00b | ||
|
|
854d908fe0 | ||
|
|
ea3359fb4b | ||
|
|
55a2ba4bd6 | ||
|
|
20ca9c84c7 | ||
|
|
0d86124b15 | ||
|
|
554fa98c48 | ||
|
|
eb92fa4f88 | ||
|
|
0424e4b00a | ||
|
|
adca062081 | ||
|
|
ef0fcb8f38 | ||
|
|
0feed0047c | ||
|
|
7f027ff6e5 | ||
|
|
e25a46c892 | ||
|
|
68916b1186 | ||
|
|
e9771588e4 | ||
|
|
e1d55c82b1 | ||
|
|
5b6be29c1c | ||
|
|
28d9f6f8da | ||
|
|
3ccae4eff7 | ||
|
|
3ad240a10e | ||
|
|
87a31a583a | ||
|
|
7553104433 | ||
|
|
f9604633e6 | ||
|
|
eb9a73bdb0 | ||
|
|
d75e75190d | ||
|
|
94a49e601c | ||
|
|
3df3d86295 | ||
|
|
63f8979f2b | ||
|
|
ad94e49ef5 | ||
|
|
b40ff2a601 | ||
|
|
a4be01b474 | ||
|
|
c5dc44a73f | ||
|
|
358cbc9388 | ||
|
|
2e5fa9dea4 | ||
|
|
717592463e | ||
|
|
347a9f2a6d | ||
|
|
d288122fab | ||
|
|
d1cf3d4600 | ||
|
|
5188872791 | ||
|
|
a57a06e8c7 | ||
|
|
347ad34038 | ||
|
|
ccdd333ba3 | ||
|
|
56f3220d4c | ||
|
|
26a85c2047 | ||
|
|
f800ce1e5a | ||
|
|
d4103cc271 | ||
|
|
c3ba3b6f48 | ||
|
|
be54df4084 | ||
|
|
d06c6f8557 | ||
|
|
a876561ea0 | ||
|
|
6dc0b8d853 | ||
|
|
460311d49e | ||
|
|
94396070e8 | ||
|
|
4a3cb8dc95 | ||
|
|
f9005451fa | ||
|
|
16e292b1fd | ||
|
|
7d37793765 | ||
|
|
9e9c162a16 | ||
|
|
8d6db81a40 | ||
|
|
425bd8932b | ||
|
|
38820701be | ||
|
|
1bc74676ff | ||
|
|
0c37f002c9 | ||
|
|
18c41b6128 | ||
|
|
5876af4e94 | ||
|
|
e1d145013a | ||
|
|
143eeff4da | ||
|
|
3a7e4bac34 | ||
|
|
f5ca162576 | ||
|
|
dbefa6b010 | ||
|
|
79cdff6163 | ||
|
|
b111fc357c | ||
|
|
2af48e159c | ||
|
|
6cec44e402 | ||
|
|
fc6c01a9a5 | ||
|
|
01439875af | ||
|
|
fcdf998fac | ||
|
|
34f34879c9 | ||
|
|
ed4ad45e3d | ||
|
|
907c1dbe2b | ||
|
|
3baad86afd | ||
|
|
0351924628 | ||
|
|
29250f82ed | ||
|
|
a819bf1d64 | ||
|
|
bc515cf74a | ||
|
|
f9c45432e6 | ||
|
|
54d55c857b | ||
|
|
8e2a4b47d6 | ||
|
|
513f06be46 | ||
|
|
0fb79917ff | ||
|
|
cd133f95ee | ||
|
|
3d66b53791 | ||
|
|
bf63e7045b | ||
|
|
5c4b75909e | ||
|
|
41f126cad7 | ||
|
|
c26d7d23b5 | ||
|
|
4298ae00a9 | ||
|
|
47296e4294 | ||
|
|
f6daab0728 | ||
|
|
25dd9f1f35 | ||
|
|
130aedc474 | ||
|
|
e958f40bd9 | ||
|
|
ed2a2d7dc3 | ||
|
|
0890b32425 | ||
|
|
2e5981ecb3 | ||
|
|
ec00bb988c | ||
|
|
14a62586cc | ||
|
|
a8fe84acc7 | ||
|
|
144ba783a8 | ||
|
|
8d81ef811e | ||
|
|
3d8ec25a33 | ||
|
|
77f636f949 | ||
|
|
aa86f02b1a | ||
|
|
05daab24a2 | ||
|
|
0960663811 | ||
|
|
150cc56b35 | ||
|
|
7a9091fcae | ||
|
|
e6215d969d | ||
|
|
9f1b8347f5 | ||
|
|
311e1568d9 | ||
|
|
c2dadd8054 | ||
|
|
e2dff0a74b | ||
|
|
88bdfd6548 | ||
|
|
70edb62a6b | ||
|
|
6c2e0ccce5 | ||
|
|
fc6e0dfa65 | ||
|
|
54aebb7d98 | ||
|
|
378c67d51f | ||
|
|
d535035bdd | ||
|
|
43790db48e | ||
|
|
d957adf062 | ||
|
|
ff8f3eb8c2 | ||
|
|
88c54c918c | ||
|
|
1261a335f4 | ||
|
|
7587d76fd1 | ||
|
|
8e81300f37 | ||
|
|
a999020038 | ||
|
|
bc4664d62c | ||
|
|
6a1754aa6a | ||
|
|
da7bb312c2 | ||
|
|
5b7f813f81 | ||
|
|
923d9a0df2 | ||
|
|
c16224b37a | ||
|
|
01b5bdc829 | ||
|
|
5a0d66141f | ||
|
|
8e873e7450 | ||
|
|
add3fa43c6 | ||
|
|
b985a8d47a | ||
|
|
3db3150a7e | ||
|
|
17ce2a3cad | ||
|
|
bf60e58d5b | ||
|
|
e3a3a55dd5 | ||
|
|
e239304d89 | ||
|
|
893f3f343f | ||
|
|
5918a18dee | ||
|
|
c08dcff56d | ||
|
|
f33cd4e419 | ||
|
|
a625426a28 | ||
|
|
24e66337ff | ||
|
|
d06f1ecf19 | ||
|
|
9c6831c360 | ||
|
|
64ec0708c4 | ||
|
|
34aa1e8de0 | ||
|
|
b0f31d8296 | ||
|
|
e5fe5199a3 | ||
|
|
45e95d1d00 | ||
|
|
21876e72fe | ||
|
|
81903598a8 | ||
|
|
290500ae17 | ||
|
|
1ed2eefff4 | ||
|
|
df65e87ef3 | ||
|
|
ddb876d2fe | ||
|
|
a080581329 | ||
|
|
f70a82791b | ||
|
|
5d3a60228c | ||
|
|
fd820dbfe2 | ||
|
|
b911ceaa61 | ||
|
|
9edf9b4186 | ||
|
|
be74259df6 | ||
|
|
29293cc8ac | ||
|
|
9e7995b730 | ||
|
|
ba9fdaa755 | ||
|
|
dde2dee304 | ||
|
|
55a99143c2 | ||
|
|
ba2bee61de | ||
|
|
5fd8fb15b9 | ||
|
|
8908a48b68 | ||
|
|
43fbda0faf | ||
|
|
97a9c65125 | ||
|
|
8a63b1b3ce | ||
|
|
3c658ac66f | ||
|
|
055d529947 | ||
|
|
9507f3c008 | ||
|
|
e0a1eefe0a | ||
|
|
ea37ba8346 | ||
|
|
8ea789c29d | ||
|
|
901e4397cb | ||
|
|
bcf781da73 | ||
|
|
a68178fd1b | ||
|
|
e18b8b0a29 | ||
|
|
62ff822ee8 | ||
|
|
c9993dccc8 | ||
|
|
6181fe5efa | ||
|
|
dd6124fa64 | ||
|
|
239302a219 | ||
|
|
e0949bff69 | ||
|
|
e0c603c764 | ||
|
|
8f62f99db7 | ||
|
|
b46de7127d | ||
|
|
e9627cb3c6 | ||
|
|
eb8fdf483e | ||
|
|
a6d5fa9cda | ||
|
|
1bef71f57a | ||
|
|
63c393e269 | ||
|
|
1efb0ba53e | ||
|
|
4a3e6ef1e5 | ||
|
|
a800d219c7 | ||
|
|
7f962b5bb0 | ||
|
|
57642cf96c | ||
|
|
c2e49be096 | ||
|
|
c26b2e4859 | ||
|
|
9ce0757835 | ||
|
|
c80a032297 | ||
|
|
bd28d8f3fb | ||
|
|
748fd0cf12 | ||
|
|
ac6f495377 | ||
|
|
3b14eb98a5 | ||
|
|
914bf3d82b | ||
|
|
949b50bd69 | ||
|
|
d234d5e3ec | ||
|
|
1b530f9200 | ||
|
|
3c4dad7eb4 | ||
|
|
cfcb6656cb | ||
|
|
ae54ef57ae | ||
|
|
cb306b61cd | ||
|
|
46269f2e9b | ||
|
|
7c9f9be7d8 | ||
|
|
ef45fe1015 | ||
|
|
eaaf05d964 | ||
|
|
3ce1cfb908 | ||
|
|
9e0a9e919b | ||
|
|
671f3078c1 | ||
|
|
67d538a499 | ||
|
|
b69d131810 | ||
|
|
949b7ece36 | ||
|
|
d1e35bcdd1 | ||
|
|
34ec197199 | ||
|
|
5a1cf67ea3 | ||
|
|
e427e2da05 | ||
|
|
2cfd4d719a | ||
|
|
709633de72 | ||
|
|
bf46f6dc8b | ||
|
|
259a18f3dd | ||
|
|
4c3aea4bfc | ||
|
|
6c595c261c | ||
|
|
ecaf1e674d | ||
|
|
e76704e261 | ||
|
|
e092081c56 | ||
|
|
4d6db0cb09 | ||
|
|
0729a725ad | ||
|
|
b7a884d81e | ||
|
|
90e6941de5 | ||
|
|
c7a05d75fd | ||
|
|
ce253fec8f | ||
|
|
6bb9b2567f | ||
|
|
26058c89fe | ||
|
|
692f6a7b85 | ||
|
|
163a29b026 | ||
|
|
06534fa91e | ||
|
|
bf45a14b30 | ||
|
|
0a497b7fd7 | ||
|
|
e1f85aa19e | ||
|
|
2f307e46e2 | ||
|
|
22203124e9 | ||
|
|
7e881927d6 | ||
|
|
a4e359daf4 | ||
|
|
ef2c165399 | ||
|
|
deb27f8f27 | ||
|
|
e51d8a403f | ||
|
|
27ddf8aeab | ||
|
|
b915b1834d | ||
|
|
cd3e0ae914 | ||
|
|
e820fea289 | ||
|
|
fc63faa737 | ||
|
|
65b50cdaee | ||
|
|
464c3e29e1 | ||
|
|
f65bf9aee7 | ||
|
|
80f7e56785 | ||
|
|
0cb05dd001 | ||
|
|
0fe5247d4c | ||
|
|
91a013afb3 | ||
|
|
3bc056a107 | ||
|
|
456c163cb9 | ||
|
|
f3ff24e35a | ||
|
|
1d2a3f573c | ||
|
|
cbdabd3ae9 | ||
|
|
c24ec7f230 | ||
|
|
3146d285bf | ||
|
|
bde93903de | ||
|
|
e487351c8d | ||
|
|
10ecb4b97f | ||
|
|
04a2cbb494 | ||
|
|
7729974253 | ||
|
|
27bfb3c662 | ||
|
|
3d5a7dce4f | ||
|
|
2d0de85976 | ||
|
|
ea4e58e509 | ||
|
|
48272449bb | ||
|
|
989e7c18fb | ||
|
|
c6d1c45abc | ||
|
|
57efba2763 | ||
|
|
e56df2391d | ||
|
|
28fab9548b | ||
|
|
7d117d05c5 | ||
|
|
a482ef9bfa | ||
|
|
f82ab9cc6e | ||
|
|
13141fc733 | ||
|
|
3c7e285182 | ||
|
|
30238dbbd5 | ||
|
|
96cdd95988 | ||
|
|
c10442087b | ||
|
|
06bd2ace69 | ||
|
|
3759340eb4 | ||
|
|
46e182004e | ||
|
|
e84de1fb5a | ||
|
|
3b9af69dec | ||
|
|
d098a4cd80 | ||
|
|
501a304d32 | ||
|
|
19b6e79719 | ||
|
|
d77ae7c2ac | ||
|
|
2b4206fd57 | ||
|
|
4ef7022343 | ||
|
|
1c67ddcbff | ||
|
|
516e637c39 | ||
|
|
d17bd6ebcc | ||
|
|
cde45d9fdf | ||
|
|
bfba7e8e73 | ||
|
|
f8243b3632 | ||
|
|
7ff2dc89b4 | ||
|
|
0c743ce8e8 | ||
|
|
50f88a8d28 | ||
|
|
ff230554ce | ||
|
|
af10953534 | ||
|
|
6093a4c2bb | ||
|
|
33b45b68e2 | ||
|
|
e33e9c44bb | ||
|
|
b35bd06eb8 | ||
|
|
875fb3a178 | ||
|
|
97d6b68613 | ||
|
|
21adf405a0 | ||
|
|
1a355cbf74 | ||
|
|
1a3e8b3375 | ||
|
|
da9af264f8 | ||
|
|
93cb615ffd | ||
|
|
7e55a8f684 | ||
|
|
433223a7fc | ||
|
|
1b58e1f6ad | ||
|
|
03faad3110 | ||
|
|
992600fcb7 | ||
|
|
99c3a1ec7f | ||
|
|
d4fe0b18ad | ||
|
|
8dfda27151 | ||
|
|
70a509bbea | ||
|
|
8c45e25724 | ||
|
|
f12682a0c1 | ||
|
|
79350ab195 | ||
|
|
ff0bacc984 | ||
|
|
d76f7a597e | ||
|
|
9ad915f40e | ||
|
|
e5b27355b7 | ||
|
|
510fa24ade | ||
|
|
72cb5db530 | ||
|
|
8b6b558483 | ||
|
|
90e42045df | ||
|
|
1ff9157c95 | ||
|
|
cf1e6e65c7 | ||
|
|
be5012db96 | ||
|
|
aec8550e56 | ||
|
|
1a4ef0f532 | ||
|
|
20ab8eb90a | ||
|
|
d621d16255 | ||
|
|
61bc130464 | ||
|
|
aa911a847d | ||
|
|
268331d5c0 | ||
|
|
8d4dd5c9de | ||
|
|
fb34af4362 | ||
|
|
e381260932 | ||
|
|
e2d5f15aff | ||
|
|
d4cecf9fd0 | ||
|
|
12578c0388 | ||
|
|
9b22d6248a | ||
|
|
729cad640f | ||
|
|
7a56be174e | ||
|
|
9789668ca4 | ||
|
|
9f7aada9c5 | ||
|
|
51f46393c3 | ||
|
|
e1029a3858 | ||
|
|
51ba096916 | ||
|
|
d9c159fe7a | ||
|
|
8ebd519615 | ||
|
|
bfcba012fa | ||
|
|
8f48c68fc6 | ||
|
|
f2e1e17741 | ||
|
|
8fb0362b47 | ||
|
|
a42587b8de | ||
|
|
474056a068 | ||
|
|
adde5e08d0 | ||
|
|
0aaa37f528 | ||
|
|
e5862ae81e | ||
|
|
485bfa2492 | ||
|
|
d5117921cf | ||
|
|
98b3f5bbdb | ||
|
|
b73c518eb2 | ||
|
|
457b627b67 | ||
|
|
53ca83beea | ||
|
|
53a83734ca | ||
|
|
2bdd9360ab | ||
|
|
805e46a1df | ||
|
|
291ef5711d | ||
|
|
00605e67fb | ||
|
|
6c6c6076eb | ||
|
|
b8ec4a5291 | ||
|
|
387a9434f8 | ||
|
|
cc01313500 | ||
|
|
0091ab004c | ||
|
|
d647ebf058 | ||
|
|
c2b48b5256 | ||
|
|
d5b8a15410 | ||
|
|
2139de76fb | ||
|
|
e832ee8450 | ||
|
|
37236aa907 | ||
|
|
4ea68efd0e | ||
|
|
843cc9ee4e | ||
|
|
ec10ead0c3 | ||
|
|
b78d19c7a5 | ||
|
|
97ddcb2ae6 | ||
|
|
d1ee3644ec | ||
|
|
b9fb59dc3e | ||
|
|
ad0497dfdf | ||
|
|
c1642b5eca | ||
|
|
f8746feaa1 | ||
|
|
40588ccffa | ||
|
|
9884d838ea | ||
|
|
384391129d | ||
|
|
0819258e0f | ||
|
|
1e776863ac | ||
|
|
202a219d82 | ||
|
|
817530ccb7 | ||
|
|
ffd7364410 | ||
|
|
bebffee3dd | ||
|
|
8cb3e523a5 | ||
|
|
228ae24834 | ||
|
|
ffc7d35145 | ||
|
|
010f921c53 | ||
|
|
d7d87847a6 | ||
|
|
91307ecfc4 | ||
|
|
1a75b79c81 | ||
|
|
3ff5f280bb | ||
|
|
e465cda5c8 | ||
|
|
2d5db0dc23 | ||
|
|
537f5a1f98 | ||
|
|
ede3cbb372 | ||
|
|
d3908ca971 | ||
|
|
725b5f7063 | ||
|
|
fd9dc9e8d2 | ||
|
|
c5fb25eec3 | ||
|
|
d2e0d3c06d | ||
|
|
1cd5b11b5c | ||
|
|
b16050211c | ||
|
|
0a558682d8 | ||
|
|
bff1b3de9c | ||
|
|
cf46d9cdd0 | ||
|
|
2c084d65d9 | ||
|
|
41d5b284e3 | ||
|
|
45d94b1c7c | ||
|
|
2509f0704f | ||
|
|
1f56118c99 | ||
|
|
38d03bca09 | ||
|
|
731f312731 | ||
|
|
635a800032 | ||
|
|
87da3a7132 | ||
|
|
eef97e4938 | ||
|
|
36bc8f90db | ||
|
|
c4e766a4c4 | ||
|
|
8bb9bbef19 | ||
|
|
2a79723336 | ||
|
|
542afbeb74 | ||
|
|
9644546a65 | ||
|
|
79e0b80f34 | ||
|
|
7e4e6782d1 | ||
|
|
aab01086a2 | ||
|
|
f7818c6994 | ||
|
|
22c60cad1a | ||
|
|
237ee1c5a1 | ||
|
|
87e4010b8b | ||
|
|
7830dadccf | ||
|
|
6287bf37e5 | ||
|
|
369310a7c3 | ||
|
|
7e1497e114 | ||
|
|
57257b7c15 | ||
|
|
68c4817c14 | ||
|
|
c35a41466b | ||
|
|
bb740f3004 | ||
|
|
c745d0dc38 | ||
|
|
9c5ef0b41a | ||
|
|
2d3605156e | ||
|
|
8e276939a7 | ||
|
|
c2afdcfdb9 | ||
|
|
8e02cadfbc | ||
|
|
0a77b6cfac | ||
|
|
f8390a889b | ||
|
|
b930a1db40 | ||
|
|
ee4de6bd1c | ||
|
|
6c2c3942bf | ||
|
|
3ec0861727 | ||
|
|
37b512e4fc | ||
|
|
01437fa58c | ||
|
|
927d03cc37 | ||
|
|
114fb2a889 | ||
|
|
f95c4626cf | ||
|
|
11db5d95a0 | ||
|
|
7d79dd00af | ||
|
|
0c470662bb | ||
|
|
7c1318274e | ||
|
|
a766395651 | ||
|
|
2dfc47f5c6 | ||
|
|
48bd616092 | ||
|
|
810aeccf94 | ||
|
|
c24e947b18 | ||
|
|
74f615bbb4 | ||
|
|
eb0331baed | ||
|
|
c3e44b498d | ||
|
|
58c856505b | ||
|
|
932aaea845 | ||
|
|
d50d97ab26 | ||
|
|
3c8f15f924 | ||
|
|
c2c4dbd2a8 | ||
|
|
04d6fc5ef4 | ||
|
|
b2549a78bd | ||
|
|
3b9f561956 | ||
|
|
6ab1fdfe36 | ||
|
|
8b0314ae77 | ||
|
|
cb1f6f2e3a | ||
|
|
a230fa10b3 | ||
|
|
1971285345 | ||
|
|
e1c2dd53cf | ||
|
|
cfbf943eb1 | ||
|
|
57b832fed1 | ||
|
|
da3e0f6ec8 | ||
|
|
9575f4e5ea | ||
|
|
6e0f981689 | ||
|
|
04978a232b | ||
|
|
44b61fc370 | ||
|
|
0bd8058589 | ||
|
|
ad38339f74 | ||
|
|
2f951cde0a | ||
|
|
84eca42ca8 | ||
|
|
1df67b0650 | ||
|
|
8eed1b6ca5 | ||
|
|
0d55a51033 | ||
|
|
31e33ec8ef | ||
|
|
b16027e500 | ||
|
|
1acb4da8d0 | ||
|
|
f38ea0c8e2 | ||
|
|
9d55197cd8 | ||
|
|
667a58052e | ||
|
|
e3e0d57512 | ||
|
|
c742642df2 | ||
|
|
c063bdb039 | ||
|
|
6230060d6a | ||
|
|
2b03b58018 | ||
|
|
f0c55a949c | ||
|
|
d9e5d66957 | ||
|
|
5ac71d8b6d | ||
|
|
1099cda9e4 | ||
|
|
d1dd640577 | ||
|
|
9bc7c8f3b0 | ||
|
|
7027b7b724 | ||
|
|
87c6c636d1 | ||
|
|
0ab5b40f52 | ||
|
|
6f8fe44fd6 | ||
|
|
7c8f0ea7b6 | ||
|
|
6c946006e8 | ||
|
|
edab00064d | ||
|
|
d6cf8377f1 | ||
|
|
eb21cf2830 | ||
|
|
17108c4328 | ||
|
|
0e9f384732 | ||
|
|
162afd0cdd | ||
|
|
b551e29de3 | ||
|
|
51bf028860 | ||
|
|
fadd362dbd | ||
|
|
7a12ca90d4 | ||
|
|
0b6c3f1c28 | ||
|
|
6d5575c705 | ||
|
|
857cc787a5 | ||
|
|
6b85e29ea7 | ||
|
|
bb50e1a959 | ||
|
|
bb8b0e32f2 | ||
|
|
74a5ef51d4 | ||
|
|
f5cdaa06c8 | ||
|
|
7fe3518c8c | ||
|
|
e2d5545b48 | ||
|
|
862968ed20 | ||
|
|
50fb95c8b1 | ||
|
|
6b6f682a94 | ||
|
|
5e419ef8a6 | ||
|
|
c3b9d34e24 | ||
|
|
5a78e7d1b8 | ||
|
|
1c05192b69 | ||
|
|
068ff01cee | ||
|
|
7feaccd35a | ||
|
|
bf015e27d6 | ||
|
|
3fe3e0f700 | ||
|
|
b3e3a5a401 | ||
|
|
d375103b64 | ||
|
|
b7ddd74631 | ||
|
|
4b6dd92f73 | ||
|
|
aa7949467c | ||
|
|
0cb8c891d9 | ||
|
|
1d454c1729 | ||
|
|
b867c04354 | ||
|
|
b4bd5ffb69 | ||
|
|
337b5f1f7a | ||
|
|
2d5962ef2f | ||
|
|
4f32da2bf0 | ||
|
|
5378171ed1 | ||
|
|
975f35dfbc | ||
|
|
a4df572cfe | ||
|
|
e83c285d4f | ||
|
|
10eb25f5f5 | ||
|
|
04cc9d03e9 | ||
|
|
c7e2255570 | ||
|
|
d699f3025d | ||
|
|
c33dd0ee7b | ||
|
|
63a4ce0fe2 | ||
|
|
222b9cff09 | ||
|
|
89aa385613 | ||
|
|
ab8672574a | ||
|
|
034e65912e | ||
|
|
40bf3a1e58 | ||
|
|
d2fbc92507 | ||
|
|
fb9ffca9ea | ||
|
|
fbe0aa2a7b | ||
|
|
3661c2c08a | ||
|
|
8d22cadd30 | ||
|
|
f691878390 | ||
|
|
50570c4762 | ||
|
|
8ca92cdec0 | ||
|
|
bc1559fbc9 | ||
|
|
ac9d46c1f7 | ||
|
|
fb2e59cf08 | ||
|
|
e93744c990 | ||
|
|
2698d80c63 | ||
|
|
4d7d7ecd2b | ||
|
|
46f8b6dfe4 | ||
|
|
1676653ffb | ||
|
|
98e28642f9 | ||
|
|
f21c35c42a | ||
|
|
3602dc4819 | ||
|
|
6ccb8e3a13 | ||
|
|
e682eb8669 | ||
|
|
9f3cd92b18 | ||
|
|
25e833bbd4 | ||
|
|
3f682ed908 | ||
|
|
6568cd71d4 | ||
|
|
c18d6a8960 | ||
|
|
2ff5e4969e | ||
|
|
b3ae2c878f | ||
|
|
d1d043d924 | ||
|
|
839ebe5a7c | ||
|
|
ed7503dbbe | ||
|
|
e21cdafb15 | ||
|
|
8d90df1ebc | ||
|
|
e6c938c489 | ||
|
|
24c959af2d | ||
|
|
15cc709322 | ||
|
|
856d617610 | ||
|
|
24c15db8d7 | ||
|
|
0ded63cd31 | ||
|
|
f0d02f2c76 | ||
|
|
f8cef655f0 | ||
|
|
0a7e5f313f | ||
|
|
888d60d472 | ||
|
|
6d19abf7ec | ||
|
|
e718b43fc8 | ||
|
|
a4aa88a440 | ||
|
|
e74e76e51c | ||
|
|
926b3c9240 | ||
|
|
11cdc4175f | ||
|
|
df8f57b628 | ||
|
|
7bd1d49f23 | ||
|
|
130c6f7308 | ||
|
|
26745426ae | ||
|
|
087dbb40a3 | ||
|
|
095bae6513 | ||
|
|
95c70951fd | ||
|
|
58c27e401e | ||
|
|
f2d60528f7 | ||
|
|
65e57df7ea | ||
|
|
075bc828f6 | ||
|
|
c912982747 | ||
|
|
a9e9f9cdbe | ||
|
|
75e1d519da | ||
|
|
3c87e3670c | ||
|
|
ee3bf2311c | ||
|
|
6c570b1847 | ||
|
|
bbfafed372 | ||
|
|
0adb7e0fd3 | ||
|
|
486c5ab224 | ||
|
|
99bf23c5ff | ||
|
|
82ebb7713a | ||
|
|
117ff96c76 | ||
|
|
46ab701782 | ||
|
|
ba6ef29896 | ||
|
|
eff68a669d | ||
|
|
d8dd7a259b | ||
|
|
7569dea73c | ||
|
|
3edcd71a44 | ||
|
|
ab786279e6 | ||
|
|
e267dc13af | ||
|
|
d0e6dc2c1e | ||
|
|
da752bb00c | ||
|
|
6b5fe9bee3 | ||
|
|
8422441f74 | ||
|
|
86faaa65ff | ||
|
|
543437dd28 | ||
|
|
3e594877d7 | ||
|
|
c2a35fef05 | ||
|
|
bbeb4749cb | ||
|
|
00662aef54 | ||
|
|
7a0196e039 | ||
|
|
ee6cbddf65 | ||
|
|
7016ccc150 | ||
|
|
a4b79127b0 | ||
|
|
ed1eb38c5f | ||
|
|
8f583ca119 | ||
|
|
1fe2be4633 | ||
|
|
d063be23c9 | ||
|
|
25c3a959dd | ||
|
|
924eeb43de | ||
|
|
d518493bb9 | ||
|
|
ba0a328196 | ||
|
|
1de8c13974 | ||
|
|
f2397bb0cc | ||
|
|
5779006db0 | ||
|
|
39b2f7bdee | ||
|
|
5e9a4e01f9 | ||
|
|
9c47f26052 | ||
|
|
b03d5d1099 | ||
|
|
c9ac01f474 | ||
|
|
c5245d3d8b | ||
|
|
a4044f4175 | ||
|
|
6dc846d41b | ||
|
|
d0bce02c00 | ||
|
|
7a5947fc49 | ||
|
|
a695c6c46e | ||
|
|
ca4141564f | ||
|
|
6cc0f96e33 | ||
|
|
a1bd0c97ee | ||
|
|
6a733f8e76 | ||
|
|
60082d0d16 | ||
|
|
ea72ad61fe | ||
|
|
a4f608f3dd | ||
|
|
737ccdec11 | ||
|
|
129afd0396 | ||
|
|
62f4dc2097 | ||
|
|
ef75ba9495 | ||
|
|
cee0bb7135 | ||
|
|
54cfbc4142 | ||
|
|
330fa863c8 | ||
|
|
1cc30a22f9 | ||
|
|
a91d3fed01 | ||
|
|
edf8fc6327 | ||
|
|
12f1bf643a | ||
|
|
2314179b83 | ||
|
|
516a3a6647 | ||
|
|
07a95deaba | ||
|
|
212e22b2b7 | ||
|
|
3a96a10d06 | ||
|
|
3dfc63ce79 | ||
|
|
a77c8ccfa9 | ||
|
|
eff9325f2b | ||
|
|
0101ae76d1 | ||
|
|
26f9a10324 | ||
|
|
6731815251 | ||
|
|
b4fdb72a3a | ||
|
|
dca9b18871 | ||
|
|
baddc0b63c | ||
|
|
8cb83afcc4 | ||
|
|
83662b7470 | ||
|
|
5408dcb185 | ||
|
|
39ae106bb3 | ||
|
|
abd484bfa7 | ||
|
|
cc15909629 | ||
|
|
5b584db90c | ||
|
|
05dcbeecac | ||
|
|
ff3a75413b | ||
|
|
0aad7b46f6 | ||
|
|
62d8190733 | ||
|
|
4759297b67 | ||
|
|
607a9a8c86 | ||
|
|
96b67ab26c | ||
|
|
93b8cb9cec | ||
|
|
7b6ad16fdb | ||
|
|
b722572a28 | ||
|
|
46489f1a46 | ||
|
|
d7a29c42b7 | ||
|
|
1ebe5f8bd5 | ||
|
|
26fe63b7ac | ||
|
|
d2435c2e00 | ||
|
|
4d04a35e73 | ||
|
|
cabad84521 | ||
|
|
aeadf2f139 | ||
|
|
4ebbfa01b7 | ||
|
|
960b456e70 | ||
|
|
329514289d | ||
|
|
03f14dfe47 | ||
|
|
1c321df457 | ||
|
|
8a21eb7804 | ||
|
|
c1d4adbebf | ||
|
|
ef933c207b | ||
|
|
08aaae4d36 | ||
|
|
f52265362f | ||
|
|
00aa43d964 | ||
|
|
84489f16b5 | ||
|
|
e07194bbeb | ||
|
|
1567d3e3d1 | ||
|
|
eca7a57138 | ||
|
|
64df0ad590 | ||
|
|
5a28d499a8 | ||
|
|
4bad876f66 | ||
|
|
1a8763d989 | ||
|
|
13eaf5e5ce | ||
|
|
82250db8af | ||
|
|
85fa78f5a6 | ||
|
|
9ed732959e | ||
|
|
860a36fe6c | ||
|
|
01a9a8ffc4 | ||
|
|
aa840f0e28 | ||
|
|
a7a626423c | ||
|
|
c2c9b60ea6 | ||
|
|
a6282818db | ||
|
|
f563544761 | ||
|
|
48a344bc6d | ||
|
|
f110e8c8db | ||
|
|
65c0608d5c | ||
|
|
57ce0dca67 | ||
|
|
4129065d6c | ||
|
|
d59fd508c2 | ||
|
|
62a5cf8dee | ||
|
|
6ec4e60058 | ||
|
|
087b3d4ffb | ||
|
|
7d3585bafe | ||
|
|
1a49974f98 | ||
|
|
b11c17dbd4 | ||
|
|
474ac62391 | ||
|
|
6ee8a74d47 | ||
|
|
121dd908a6 | ||
|
|
2e1ac25ce2 | ||
|
|
b49ee06f23 |
12
.github/workflows/docker-build-test.yml
vendored
12
.github/workflows/docker-build-test.yml
vendored
@@ -24,20 +24,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
|
||||||
env:
|
|
||||||
dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
dockerhub_password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
|
||||||
if: ${{ env.dockerhub_username }} && ${{ env.dockerhub_password }}
|
|
||||||
|
|
||||||
- name: Build Docker standard image
|
- name: Build Docker standard image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
|
|||||||
2
.github/workflows/docker-release.yml
vendored
2
.github/workflows/docker-release.yml
vendored
@@ -12,6 +12,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|||||||
29
.github/workflows/issues.yml
vendored
Normal file
29
.github/workflows/issues.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Process GitHub Issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 */12 * * *' # Run every 12 hours
|
||||||
|
workflow_dispatch: # Allow manual triggers
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
process-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write # Required to modify issues
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install requests python-dotenv tqdm
|
||||||
|
|
||||||
|
- name: Run issues script
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: python scripts/issues.py --yes
|
||||||
12
.github/workflows/pages.yml
vendored
12
.github/workflows/pages.yml
vendored
@@ -36,7 +36,9 @@ jobs:
|
|||||||
working-directory: aider/website
|
working-directory: aider/website
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
@@ -70,16 +72,16 @@ jobs:
|
|||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v2
|
uses: actions/deploy-pages@v2
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python 3.12
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: '3.12'
|
||||||
|
|
||||||
- name: Install linkchecker
|
- name: Install linkchecker
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install linkchecker
|
python -m pip install linkchecker
|
||||||
|
|
||||||
- name: Run linkchecker
|
- name: Run linkchecker
|
||||||
run: |
|
run: |
|
||||||
linkchecker https://aider.chat
|
linkchecker --ignore-url='.+\.(mp4|mov|avi)' https://aider.chat
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -12,6 +12,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
|||||||
9
.github/workflows/ubuntu-tests.yml
vendored
9
.github/workflows/ubuntu-tests.yml
vendored
@@ -25,12 +25,19 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libportaudio2
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@@ -38,5 +45,7 @@ jobs:
|
|||||||
pip install .
|
pip install .
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
AIDER_ANALYTICS: false
|
||||||
run: |
|
run: |
|
||||||
pytest
|
pytest
|
||||||
|
|||||||
4
.github/workflows/windows-tests.yml
vendored
4
.github/workflows/windows-tests.yml
vendored
@@ -25,6 +25,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@@ -38,6 +40,8 @@ jobs:
|
|||||||
pip install .
|
pip install .
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
AIDER_ANALYTICS: false
|
||||||
run: |
|
run: |
|
||||||
pytest
|
pytest
|
||||||
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,3 +11,7 @@ _site
|
|||||||
.jekyll-cache/
|
.jekyll-cache/
|
||||||
.jekyll-metadata
|
.jekyll-metadata
|
||||||
aider/__version__.py
|
aider/__version__.py
|
||||||
|
aider/_version.py
|
||||||
|
.venv/
|
||||||
|
.#*
|
||||||
|
.gitattributes
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ Contributions of
|
|||||||
[LLM benchmark results](https://aider.chat/docs/leaderboards/)
|
[LLM benchmark results](https://aider.chat/docs/leaderboards/)
|
||||||
are welcome!
|
are welcome!
|
||||||
See the
|
See the
|
||||||
[benchmark README](https://github.com/paul-gauthier/aider/blob/main/benchmark/README.md)
|
[benchmark README](https://github.com/Aider-AI/aider/blob/main/benchmark/README.md)
|
||||||
for information on running aider's code editing benchmarks.
|
for information on running aider's code editing benchmarks.
|
||||||
Submit results by opening a PR with edits to the
|
Submit results by opening a PR with edits to the
|
||||||
[benchmark results data files](https://github.com/paul-gauthier/aider/blob/main/aider/website/_data/).
|
[benchmark results data files](https://github.com/Aider-AI/aider/blob/main/aider/website/_data/).
|
||||||
|
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
@@ -33,19 +33,16 @@ ensure that your contributions can be integrated smoothly.
|
|||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
By contributing to this project, you agree that your contributions
|
Before contributing a PR, please review our
|
||||||
will be licensed under the Apache License 2.0. Additionally, you
|
[Individual Contributor License Agreement](https://aider.chat/docs/legal/contributor-agreement.html).
|
||||||
understand and agree that contributions may be subject to a different
|
All contributors will be asked to complete the agreement as part of the PR process.
|
||||||
license, should the project maintainers decide to change the licensing
|
|
||||||
terms.
|
|
||||||
|
|
||||||
|
|
||||||
## Setting up a Development Environment
|
## Setting up a Development Environment
|
||||||
|
|
||||||
### Clone the Repository
|
### Clone the Repository
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/paul-gauthier/aider.git
|
git clone https://github.com/Aider-AI/aider.git
|
||||||
cd aider
|
cd aider
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -154,6 +151,10 @@ The project's documentation is built using Jekyll and hosted on GitHub Pages. To
|
|||||||
```
|
```
|
||||||
bundle exec jekyll build
|
bundle exec jekyll build
|
||||||
```
|
```
|
||||||
|
5. Preview the website while editing (optional):
|
||||||
|
```
|
||||||
|
bundle exec jekyll serve
|
||||||
|
```
|
||||||
|
|
||||||
The built documentation will be available in the `aider/website/_site` directory.
|
The built documentation will be available in the `aider/website/_site` directory.
|
||||||
|
|
||||||
@@ -186,8 +187,8 @@ pytest
|
|||||||
You can also run specific test files or test cases by providing the file path or test name:
|
You can also run specific test files or test cases by providing the file path or test name:
|
||||||
|
|
||||||
```
|
```
|
||||||
pytest aider/tests/test_coder.py
|
pytest tests/basic/test_coder.py
|
||||||
pytest aider/tests/test_coder.py::TestCoder::test_specific_case
|
pytest tests/basic/test_coder.py::TestCoder::test_specific_case
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Continuous Integration
|
#### Continuous Integration
|
||||||
|
|||||||
238
HISTORY.md
238
HISTORY.md
@@ -1,6 +1,240 @@
|
|||||||
|
|
||||||
# Release history
|
# Release history
|
||||||
|
|
||||||
|
### Aider v0.69.0
|
||||||
|
|
||||||
|
- [Watch files](https://aider.chat/docs/usage/watch.html) improvements:
|
||||||
|
- Use `# ... AI?` comments to trigger aider and ask questions about your code.
|
||||||
|
- Now watches *all* files, not just certain source files.
|
||||||
|
- Use `# AI comments`, `// AI comments`, or `-- AI comments` to give aider instructions in any text file.
|
||||||
|
- Full support for Gemini Flash 2.0 Exp:
|
||||||
|
- `aider --model flash` or `aider --model gemini/gemini-2.0-flash-exp`
|
||||||
|
- [New `--multiline` flag and `/multiline-mode` command](https://aider.chat/docs/usage/commands.html#entering-multi-line-chat-messages) makes ENTER a soft newline and META-ENTER send the message, by @miradnanali.
|
||||||
|
- `/copy-context <instructions>` now takes optional "instructions" when [copying code context to the clipboard](https://aider.chat/docs/usage/copypaste.html#copy-aiders-code-context-to-your-clipboard-paste-into-the-web-ui).
|
||||||
|
- Improved clipboard error handling with helpful requirements install info.
|
||||||
|
- Ask 5% of users if they want to opt-in to analytics.
|
||||||
|
- `/voice` now lets you edit the transcribed text before sending.
|
||||||
|
- Disabled auto-complete in Y/N prompts.
|
||||||
|
- Aider wrote 68% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.68.0
|
||||||
|
|
||||||
|
- [Aider works with LLM web chat UIs](https://aider.chat/docs/usage/copypaste.html).
|
||||||
|
- New `--copy-paste` mode.
|
||||||
|
- 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).
|
||||||
|
- New `--api-key provider=key` setting.
|
||||||
|
- New `--set-env VAR=value` setting.
|
||||||
|
- Added bash and zsh support to `--watch-files`.
|
||||||
|
- Better error messages when missing dependencies for Gemini and Bedrock models.
|
||||||
|
- Control-D now properly exits the program.
|
||||||
|
- Don't count token costs when API provider returns a hard error.
|
||||||
|
- Bugfix so watch files works with files that don't have tree-sitter support.
|
||||||
|
- Bugfix so o1 models can be used as weak model.
|
||||||
|
- Updated shell command prompt.
|
||||||
|
- Added docstrings for all Coders.
|
||||||
|
- Reorganized command line arguments with improved help messages and grouping.
|
||||||
|
- Use the exact `sys.python` for self-upgrades.
|
||||||
|
- Added experimental Gemini models.
|
||||||
|
- Aider wrote 71% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.67.0
|
||||||
|
|
||||||
|
- [Use aider in your IDE or editor](https://aider.chat/docs/usage/watch.html).
|
||||||
|
- Run `aider --watch-files` and it will watch for instructions you add to your source files.
|
||||||
|
- One-liner `# ...` or `// ...` comments that start or end with "AI" are instructions to aider.
|
||||||
|
- When aider sees "AI!" it reads and follows all the instructions in AI comments.
|
||||||
|
- Support for new Amazon Bedrock Nova models.
|
||||||
|
- When `/run` or `/test` have non-zero exit codes, pre-fill "Fix that" into the next message prompt.
|
||||||
|
- `/diff` now invokes `git diff` to use your preferred diff tool.
|
||||||
|
- Added Ctrl-Z support for process suspension.
|
||||||
|
- Spinner now falls back to ASCII art if fancy symbols throw unicode errors.
|
||||||
|
- `--read` now expands `~` home dirs.
|
||||||
|
- Enabled exception capture in analytics.
|
||||||
|
- [Aider wrote 61% of the code in this release.](https://aider.chat/HISTORY.html)
|
||||||
|
|
||||||
|
### Aider v0.66.0
|
||||||
|
|
||||||
|
- PDF support for Sonnet and Gemini models.
|
||||||
|
- Added `--voice-input-device` to select audio input device for voice recording, by @preynal.
|
||||||
|
- Added `--timeout` option to configure API call timeouts.
|
||||||
|
- Set cwd to repo root when running shell commands.
|
||||||
|
- Added Ctrl-Up/Down keyboard shortcuts for per-message history navigation.
|
||||||
|
- Improved error handling for failed .gitignore file operations.
|
||||||
|
- Improved error handling for input history file permissions.
|
||||||
|
- Improved error handling for analytics file access.
|
||||||
|
- Removed spurious warning about disabling pretty in VSCode.
|
||||||
|
- Removed broken support for Dart.
|
||||||
|
- Bugfix when scraping URLs found in chat messages.
|
||||||
|
- Better handling of __version__ import errors.
|
||||||
|
- Improved `/drop` command to support substring matching for non-glob patterns.
|
||||||
|
- Aider wrote 82% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.65.1
|
||||||
|
|
||||||
|
- Bugfix to `--alias`.
|
||||||
|
|
||||||
|
### Aider v0.65.0
|
||||||
|
|
||||||
|
- Added `--alias` config to define [custom model aliases](https://aider.chat/docs/config/model-aliases.html).
|
||||||
|
- Added `--[no-]detect-urls` flag to disable detecting and offering to scrape URLs found in the chat.
|
||||||
|
- Ollama models now default to an 8k context window.
|
||||||
|
- Added [RepoMap support for Dart language](https://aider.chat/docs/languages.html) by @malkoG.
|
||||||
|
- Ask 2.5% of users if they want to opt-in to [analytics](https://aider.chat/docs/more/analytics.html).
|
||||||
|
- Skip suggesting files that share names with files already in chat.
|
||||||
|
- `/editor` returns and prefill the file content into the prompt, so you can use `/editor` to compose messages that start with `/commands`, etc.
|
||||||
|
- Enhanced error handling for analytics.
|
||||||
|
- Improved handling of UnknownEditFormat exceptions with helpful documentation links.
|
||||||
|
- Bumped dependencies to pick up grep-ast 0.4.0 for Dart language support.
|
||||||
|
- Aider wrote 81% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.64.1
|
||||||
|
|
||||||
|
- Disable streaming for o1 on OpenRouter.
|
||||||
|
|
||||||
|
### Aider v0.64.0
|
||||||
|
|
||||||
|
- Added [`/editor` command](https://aider.chat/docs/usage/commands.html) to open system editor for writing prompts, by @thehunmonkgroup.
|
||||||
|
- Full support for `gpt-4o-2024-11-20`.
|
||||||
|
- Stream o1 models by default.
|
||||||
|
- `/run` and suggested shell commands are less mysterious and now confirm that they "Added XX lines of output to the chat."
|
||||||
|
- Ask 1% of users if they want to opt-in to [analytics](https://aider.chat/docs/more/analytics.html).
|
||||||
|
- Added support for [optional multiline input tags](https://aider.chat/docs/usage/commands.html#entering-multi-line-chat-messages) with matching closing tags.
|
||||||
|
- Improved [model settings configuration](https://aider.chat/docs/config/adv-model-settings.html#global-extra-params) with support for global `extra_params` for `litellm.completion()`.
|
||||||
|
- Architect mode now asks to add files suggested by the LLM.
|
||||||
|
- Fixed bug in fuzzy model name matching.
|
||||||
|
- Added Timeout exception to handle API provider timeouts.
|
||||||
|
- Added `--show-release-notes` to control release notes display on first run of new version.
|
||||||
|
- Save empty dict to cache file on model metadata download failure, to delay retry.
|
||||||
|
- Improved error handling and code formatting.
|
||||||
|
- Aider wrote 74% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.63.2
|
||||||
|
|
||||||
|
- Fixed bug in fuzzy model name matching when litellm provider info is missing.
|
||||||
|
- Modified model metadata file loading to allow override of resource file.
|
||||||
|
- Allow recursive loading of dirs using `--read`.
|
||||||
|
- Updated dependency versions to pick up litellm fix for ollama models.
|
||||||
|
- Added exponential backoff retry when writing files to handle editor file locks.
|
||||||
|
- Updated Qwen 2.5 Coder 32B model configuration.
|
||||||
|
|
||||||
|
### Aider v0.63.1
|
||||||
|
|
||||||
|
- Fixed bug in git ignored file handling.
|
||||||
|
- Improved error handling for git operations.
|
||||||
|
|
||||||
|
### Aider v0.63.0
|
||||||
|
|
||||||
|
- Support for Qwen 2.5 Coder 32B.
|
||||||
|
- `/web` command just adds the page to the chat, without triggering an LLM response.
|
||||||
|
- Improved prompting for the user's preferred chat language.
|
||||||
|
- Improved handling of LiteLLM exceptions.
|
||||||
|
- Bugfix for double-counting tokens when reporting cache stats.
|
||||||
|
- Bugfix for the LLM creating new files.
|
||||||
|
- Other small bug fixes.
|
||||||
|
- Aider wrote 55% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.62.0
|
||||||
|
|
||||||
|
- Full support for Claude 3.5 Haiku
|
||||||
|
- Scored 75% on [aider's code editing leaderboard](https://aider.chat/docs/leaderboards/).
|
||||||
|
- Almost as good as Sonnet at much lower cost.
|
||||||
|
- Launch with `--haiku` to use it.
|
||||||
|
- Easily apply file edits from ChatGPT, Claude or other web apps
|
||||||
|
- Chat with ChatGPT or Claude via their web app.
|
||||||
|
- Give it your source files and ask for the changes you want.
|
||||||
|
- Use the web app's "copy response" button to copy the entire reply from the LLM.
|
||||||
|
- Run `aider --apply-clipboard-edits file-to-edit.js`.
|
||||||
|
- Aider will edit your file with the LLM's changes.
|
||||||
|
- Bugfix for creating new files.
|
||||||
|
- Aider wrote 84% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.61.0
|
||||||
|
|
||||||
|
- Load and save aider slash-commands to files:
|
||||||
|
- `/save <fname>` command will make a file of `/add` and `/read-only` commands that recreate the current file context in the chat.
|
||||||
|
- `/load <fname>` will replay the commands in the file.
|
||||||
|
- You can use `/load` to run any arbitrary set of slash-commands, not just `/add` and `/read-only`.
|
||||||
|
- Use `--load <fname>` to run a list of commands on launch, before the interactive chat begins.
|
||||||
|
- Anonymous, opt-in [analytics](https://aider.chat/docs/more/analytics.html) with no personal data sharing.
|
||||||
|
- Aider follows litellm's `supports_vision` attribute to enable image support for models.
|
||||||
|
- Bugfix for when diff mode flexibly handles the model using the wrong filename.
|
||||||
|
- Displays filenames in sorted order for `/add` and `/read-only`.
|
||||||
|
- New `--no-fancy-input` switch disables prompt toolkit input, now still available with `--no-pretty`.
|
||||||
|
- Override browser config with `--no-browser` or `--no-gui`.
|
||||||
|
- Offer to open documentation URLs when errors occur.
|
||||||
|
- Properly support all o1 models, regardless of provider.
|
||||||
|
- Improved layout of filenames above input prompt.
|
||||||
|
- Better handle corrupted repomap tags cache.
|
||||||
|
- Improved handling of API errors, especially when accessing the weak model.
|
||||||
|
- Aider wrote 68% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.60.1
|
||||||
|
|
||||||
|
- Enable image support for Sonnet 10/22.
|
||||||
|
- Display filenames in sorted order.
|
||||||
|
|
||||||
|
### Aider v0.60.0
|
||||||
|
|
||||||
|
- Full support for Sonnet 10/22, the new SOTA model on aider's code editing benchmark.
|
||||||
|
- Aider uses Sonnet 10/22 by default.
|
||||||
|
- Improved formatting of added and read-only files above chat prompt, by @jbellis.
|
||||||
|
- Improved support for o1 models by more flexibly parsing their nonconforming code edit replies.
|
||||||
|
- Corrected diff edit format prompt that only the first match is replaced.
|
||||||
|
- Stronger whole edit format prompt asking for clean file names.
|
||||||
|
- Now offers to add `.env` to the `.gitignore` file.
|
||||||
|
- Ships with a small model metadata json file to handle models not yet updated in litellm.
|
||||||
|
- Model settings for o1 models on azure.
|
||||||
|
- Bugfix to properly include URLs in `/help` RAG results.
|
||||||
|
- Aider wrote 49% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.59.1
|
||||||
|
|
||||||
|
- Check for obsolete `yes: true` in yaml config, show helpful error.
|
||||||
|
- Model settings for openrouter/anthropic/claude-3.5-sonnet:beta
|
||||||
|
|
||||||
|
### Aider v0.59.0
|
||||||
|
|
||||||
|
- Improvements to `/read-only`:
|
||||||
|
- Now supports shell-style auto-complete of the full file system.
|
||||||
|
- Still auto-completes the full paths of the repo files like `/add`.
|
||||||
|
- Now supports globs like `src/**/*.py`
|
||||||
|
- Renamed `--yes` to `--yes-always`.
|
||||||
|
- Now uses `AIDER_YES_ALWAYS` env var and `yes-always:` yaml key.
|
||||||
|
- Existing YAML and .env files will need to be updated.
|
||||||
|
- Can still abbreviate to `--yes` on the command line.
|
||||||
|
- Config file now uses standard YAML list syntax with ` - list entries`, one per line.
|
||||||
|
- `/settings` now includes the same announcement lines that would print at launch.
|
||||||
|
- Sanity checks the `--editor-model` on launch now, same as main and weak models.
|
||||||
|
- Added `--skip-sanity-check-repo` switch to speedup launch in large repos.
|
||||||
|
- Bugfix so architect mode handles Control-C properly.
|
||||||
|
- Repo-map is deterministic now, with improved caching logic.
|
||||||
|
- Improved commit message prompt.
|
||||||
|
- Aider wrote 77% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.58.1
|
||||||
|
|
||||||
|
- Fixed bug where cache warming pings caused subsequent user messages to trigger a tight loop of LLM requests.
|
||||||
|
|
||||||
|
### Aider v0.58.0
|
||||||
|
|
||||||
|
- [Use a pair of Architect/Editor models for improved coding](https://aider.chat/2024/09/26/architect.html)
|
||||||
|
- Use a strong reasoning model like o1-preview as your Architect.
|
||||||
|
- Use a cheaper, faster model like gpt-4o as your Editor.
|
||||||
|
- New `--o1-preview` and `--o1-mini` shortcuts.
|
||||||
|
- Support for new Gemini 002 models.
|
||||||
|
- Better support for Qwen 2.5 models.
|
||||||
|
- Many confirmation questions can be skipped for the rest of the session with "(D)on't ask again" response.
|
||||||
|
- Autocomplete for `/read-only` supports the entire filesystem.
|
||||||
|
- New settings for completion menu colors.
|
||||||
|
- New `/copy` command to copy the last LLM response to the clipboard.
|
||||||
|
- Renamed `/clipboard` to `/paste`.
|
||||||
|
- Will now follow HTTP redirects when scraping urls.
|
||||||
|
- New `--voice-format` switch to send voice audio as wav/mp3/webm, by @mbailey.
|
||||||
|
- ModelSettings takes `extra_params` dict to specify any extras to pass to `litellm.completion()`.
|
||||||
|
- Support for cursor shapes when in vim mode.
|
||||||
|
- Numerous bug fixes.
|
||||||
|
- Aider wrote 53% of the code in this release.
|
||||||
|
|
||||||
### Aider v0.57.1
|
### Aider v0.57.1
|
||||||
|
|
||||||
- Fixed dependency conflict between aider-chat[help] and [playwright].
|
- Fixed dependency conflict between aider-chat[help] and [playwright].
|
||||||
@@ -665,7 +899,7 @@
|
|||||||
### Aider v0.14.0
|
### Aider v0.14.0
|
||||||
|
|
||||||
- [Support for Claude2 and other LLMs via OpenRouter](https://aider.chat/docs/faq.html#accessing-other-llms-with-openrouter) by @joshuavial
|
- [Support for Claude2 and other LLMs via OpenRouter](https://aider.chat/docs/faq.html#accessing-other-llms-with-openrouter) by @joshuavial
|
||||||
- Documentation for [running the aider benchmarking suite](https://github.com/paul-gauthier/aider/tree/main/benchmark)
|
- Documentation for [running the aider benchmarking suite](https://github.com/Aider-AI/aider/tree/main/benchmark)
|
||||||
- Aider now requires Python >= 3.9
|
- Aider now requires Python >= 3.9
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
MANIFEST.in
Normal file
14
MANIFEST.in
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# This needs to sync with aider/help_pats.py
|
||||||
|
|
||||||
|
global-exclude .DS_Store
|
||||||
|
recursive-exclude aider/website/examples *
|
||||||
|
recursive-exclude aider/website/_posts *
|
||||||
|
exclude aider/website/HISTORY.md
|
||||||
|
exclude aider/website/docs/benchmarks*.md
|
||||||
|
exclude aider/website/docs/ctags.md
|
||||||
|
exclude aider/website/docs/unified-diffs.md
|
||||||
|
exclude aider/website/docs/leaderboards/index.md
|
||||||
|
recursive-exclude aider/website/assets *
|
||||||
|
recursive-exclude aider/website *.js
|
||||||
|
recursive-exclude aider/website *.html
|
||||||
|
recursive-exclude aider/website *.yml
|
||||||
31
README.md
31
README.md
@@ -43,28 +43,27 @@ VIDEO END -->
|
|||||||
cog.out(open("aider/website/_includes/get-started.md").read())
|
cog.out(open("aider/website/_includes/get-started.md").read())
|
||||||
]]]-->
|
]]]-->
|
||||||
|
|
||||||
You can get started quickly like this:
|
You can get started quickly like this, with python 3.8-3.13:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
python -m pip install aider-chat
|
python -m pip install aider-install
|
||||||
|
aider-install
|
||||||
|
|
||||||
# Change directory into a git repo
|
# Change directory into your code base
|
||||||
cd /to/your/git/repo
|
cd /to/your/git/repo
|
||||||
|
|
||||||
# Work with Claude 3.5 Sonnet on your repo
|
# Work with Claude 3.5 Sonnet on your repo
|
||||||
export ANTHROPIC_API_KEY=your-key-goes-here
|
aider --model sonnet --anthropic-api-key your-key-goes-here
|
||||||
aider
|
|
||||||
|
|
||||||
# Work with GPT-4o on your repo
|
# Work with GPT-4o on your repo
|
||||||
export OPENAI_API_KEY=your-key-goes-here
|
aider --model gpt-4o --openai-api-key your-key-goes-here
|
||||||
aider
|
|
||||||
```
|
```
|
||||||
<!--[[[end]]]-->
|
<!--[[[end]]]-->
|
||||||
|
|
||||||
See the
|
See the
|
||||||
[installation instructions](https://aider.chat/docs/install.html)
|
[installation instructions](https://aider.chat/docs/install.html)
|
||||||
and other
|
and
|
||||||
[documentation](https://aider.chat/docs/usage.html)
|
[usage documentation](https://aider.chat/docs/usage.html)
|
||||||
for more details.
|
for more details.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -107,7 +106,7 @@ projects like django, scikitlearn, matplotlib, etc.
|
|||||||
- [Configuration](https://aider.chat/docs/config.html)
|
- [Configuration](https://aider.chat/docs/config.html)
|
||||||
- [Troubleshooting](https://aider.chat/docs/troubleshooting.html)
|
- [Troubleshooting](https://aider.chat/docs/troubleshooting.html)
|
||||||
- [LLM Leaderboards](https://aider.chat/docs/leaderboards/)
|
- [LLM Leaderboards](https://aider.chat/docs/leaderboards/)
|
||||||
- [GitHub](https://github.com/paul-gauthier/aider)
|
- [GitHub](https://github.com/Aider-AI/aider)
|
||||||
- [Discord](https://discord.gg/Tv2uQnR88V)
|
- [Discord](https://discord.gg/Tv2uQnR88V)
|
||||||
- [Blog](https://aider.chat/blog/)
|
- [Blog](https://aider.chat/blog/)
|
||||||
|
|
||||||
@@ -118,14 +117,14 @@ projects like django, scikitlearn, matplotlib, etc.
|
|||||||
- *The best AI coding assistant so far.* -- [Matthew Berman](https://www.youtube.com/watch?v=df8afeb1FY8)
|
- *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)
|
- *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 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/paul-gauthier/aider/issues/124)
|
- *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/paul-gauthier/aider/issues/6#issue-1722897858)
|
- *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/paul-gauthier/aider/issues/82#issuecomment-1631876700)
|
- *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)
|
- *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)
|
- *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)
|
- *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/paul-gauthier/aider/issues/112#issuecomment-1637429008)
|
- *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/paul-gauthier/aider/issues/84)
|
- *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 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 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)
|
- *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)
|
||||||
|
|||||||
@@ -1,6 +1,20 @@
|
|||||||
|
from packaging import version
|
||||||
|
|
||||||
|
__version__ = "0.69.2.dev"
|
||||||
|
safe_version = __version__
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from aider.__version__ import __version__
|
from aider._version import __version__
|
||||||
except Exception:
|
except Exception:
|
||||||
__version__ = "0.57.2.dev"
|
__version__ = safe_version + "+import"
|
||||||
|
|
||||||
|
if type(__version__) is not str:
|
||||||
|
__version__ = safe_version + "+type"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if version.parse(__version__) < version.parse(safe_version):
|
||||||
|
__version__ = safe_version + "+less"
|
||||||
|
except Exception:
|
||||||
|
__version__ = safe_version + "+parse"
|
||||||
|
|
||||||
__all__ = [__version__]
|
__all__ = [__version__]
|
||||||
|
|||||||
230
aider/analytics.py
Normal file
230
aider/analytics.py
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import json
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mixpanel import MixpanelException
|
||||||
|
from posthog import Posthog
|
||||||
|
|
||||||
|
from aider import __version__
|
||||||
|
from aider.dump import dump # noqa: F401
|
||||||
|
from aider.models import model_info_manager
|
||||||
|
|
||||||
|
mixpanel_project_token = "6da9a43058a5d1b9f3353153921fb04d"
|
||||||
|
posthog_project_api_key = "phc_99T7muzafUMMZX15H8XePbMSreEUzahHbtWjy3l5Qbv"
|
||||||
|
posthog_host = "https://us.i.posthog.com"
|
||||||
|
|
||||||
|
|
||||||
|
class Analytics:
|
||||||
|
# providers
|
||||||
|
mp = None
|
||||||
|
ph = None
|
||||||
|
|
||||||
|
# saved
|
||||||
|
user_id = None
|
||||||
|
permanently_disable = None
|
||||||
|
asked_opt_in = None
|
||||||
|
|
||||||
|
# ephemeral
|
||||||
|
logfile = None
|
||||||
|
|
||||||
|
def __init__(self, logfile=None, permanently_disable=False):
|
||||||
|
self.logfile = logfile
|
||||||
|
self.get_or_create_uuid()
|
||||||
|
|
||||||
|
if self.permanently_disable or permanently_disable or not self.asked_opt_in:
|
||||||
|
self.disable(permanently_disable)
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
if not self.user_id:
|
||||||
|
self.disable(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.permanently_disable:
|
||||||
|
self.disable(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.asked_opt_in:
|
||||||
|
self.disable(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
# self.mp = Mixpanel(mixpanel_project_token)
|
||||||
|
self.ph = Posthog(
|
||||||
|
project_api_key=posthog_project_api_key,
|
||||||
|
host=posthog_host,
|
||||||
|
on_error=self.posthog_error,
|
||||||
|
enable_exception_autocapture=True,
|
||||||
|
super_properties=self.get_system_info(), # Add system info to all events
|
||||||
|
)
|
||||||
|
|
||||||
|
def disable(self, permanently):
|
||||||
|
self.mp = None
|
||||||
|
self.ph = None
|
||||||
|
|
||||||
|
if permanently:
|
||||||
|
self.asked_opt_in = True
|
||||||
|
self.permanently_disable = True
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
|
def need_to_ask(self, args_analytics):
|
||||||
|
if args_analytics is False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
could_ask = not self.asked_opt_in and not self.permanently_disable
|
||||||
|
if not could_ask:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if args_analytics is True:
|
||||||
|
return True
|
||||||
|
|
||||||
|
assert args_analytics is None, args_analytics
|
||||||
|
|
||||||
|
if not self.user_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
PERCENT = 5
|
||||||
|
return self.is_uuid_in_percentage(self.user_id, PERCENT)
|
||||||
|
|
||||||
|
def is_uuid_in_percentage(self, uuid_str, percent):
|
||||||
|
"""Check if a UUID string falls within the first X percent of the UUID space.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
uuid_str: UUID string to test
|
||||||
|
percent: Percentage threshold (0-100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if UUID falls within the first X percent
|
||||||
|
"""
|
||||||
|
if not (0 <= percent <= 100):
|
||||||
|
raise ValueError("Percentage must be between 0 and 100")
|
||||||
|
|
||||||
|
if not uuid_str:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Convert percentage to hex threshold (1% = "04...", 10% = "1a...", etc)
|
||||||
|
# Using first 6 hex digits
|
||||||
|
if percent == 0:
|
||||||
|
return False
|
||||||
|
threshold = format(int(0xFFFFFF * percent / 100), "06x")
|
||||||
|
return uuid_str[:6] <= threshold
|
||||||
|
|
||||||
|
def get_data_file_path(self):
|
||||||
|
try:
|
||||||
|
data_file = Path.home() / ".aider" / "analytics.json"
|
||||||
|
data_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
return data_file
|
||||||
|
except OSError:
|
||||||
|
# If we can't create/access the directory, just disable analytics
|
||||||
|
self.disable(permanently=False)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_or_create_uuid(self):
|
||||||
|
self.load_data()
|
||||||
|
if self.user_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.user_id = str(uuid.uuid4())
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
|
def load_data(self):
|
||||||
|
data_file = self.get_data_file_path()
|
||||||
|
if not data_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
if data_file.exists():
|
||||||
|
try:
|
||||||
|
data = json.loads(data_file.read_text())
|
||||||
|
self.permanently_disable = data.get("permanently_disable")
|
||||||
|
self.user_id = data.get("uuid")
|
||||||
|
self.asked_opt_in = data.get("asked_opt_in", False)
|
||||||
|
except (json.decoder.JSONDecodeError, OSError):
|
||||||
|
self.disable(permanently=False)
|
||||||
|
|
||||||
|
def save_data(self):
|
||||||
|
data_file = self.get_data_file_path()
|
||||||
|
if not data_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = dict(
|
||||||
|
uuid=self.user_id,
|
||||||
|
permanently_disable=self.permanently_disable,
|
||||||
|
asked_opt_in=self.asked_opt_in,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data_file.write_text(json.dumps(data, indent=4))
|
||||||
|
except OSError:
|
||||||
|
# If we can't write the file, just disable analytics
|
||||||
|
self.disable(permanently=False)
|
||||||
|
|
||||||
|
def get_system_info(self):
|
||||||
|
return {
|
||||||
|
"python_version": sys.version.split()[0],
|
||||||
|
"os_platform": platform.system(),
|
||||||
|
"os_release": platform.release(),
|
||||||
|
"machine": platform.machine(),
|
||||||
|
"aider_version": __version__,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _redact_model_name(self, model):
|
||||||
|
if not model:
|
||||||
|
return None
|
||||||
|
|
||||||
|
info = model_info_manager.get_model_from_cached_json_db(model.name)
|
||||||
|
if info:
|
||||||
|
return model.name
|
||||||
|
elif "/" in model.name:
|
||||||
|
return model.name.split("/")[0] + "/REDACTED"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def posthog_error(self):
|
||||||
|
"""disable posthog if we get an error"""
|
||||||
|
print("X" * 100)
|
||||||
|
# https://github.com/PostHog/posthog-python/blob/9e1bb8c58afaa229da24c4fb576c08bb88a75752/posthog/consumer.py#L86
|
||||||
|
# https://github.com/Aider-AI/aider/issues/2532
|
||||||
|
self.ph = None
|
||||||
|
|
||||||
|
def event(self, event_name, main_model=None, **kwargs):
|
||||||
|
if not self.mp and not self.ph and not self.logfile:
|
||||||
|
return
|
||||||
|
|
||||||
|
properties = {}
|
||||||
|
|
||||||
|
if main_model:
|
||||||
|
properties["main_model"] = self._redact_model_name(main_model)
|
||||||
|
properties["weak_model"] = self._redact_model_name(main_model.weak_model)
|
||||||
|
properties["editor_model"] = self._redact_model_name(main_model.editor_model)
|
||||||
|
|
||||||
|
properties.update(kwargs)
|
||||||
|
|
||||||
|
# Handle numeric values
|
||||||
|
for key, value in properties.items():
|
||||||
|
if isinstance(value, (int, float)):
|
||||||
|
properties[key] = value
|
||||||
|
else:
|
||||||
|
properties[key] = str(value)
|
||||||
|
|
||||||
|
if self.mp:
|
||||||
|
try:
|
||||||
|
self.mp.track(self.user_id, event_name, dict(properties))
|
||||||
|
except MixpanelException:
|
||||||
|
self.mp = None # Disable mixpanel on connection errors
|
||||||
|
|
||||||
|
if self.ph:
|
||||||
|
self.ph.capture(self.user_id, event_name, dict(properties))
|
||||||
|
|
||||||
|
if self.logfile:
|
||||||
|
log_entry = {
|
||||||
|
"event": event_name,
|
||||||
|
"properties": properties,
|
||||||
|
"user_id": self.user_id,
|
||||||
|
"time": int(time.time()),
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
with open(self.logfile, "a") as f:
|
||||||
|
json.dump(log_entry, f)
|
||||||
|
f.write("\n")
|
||||||
|
except OSError:
|
||||||
|
pass # Ignore OS errors when writing to logfile
|
||||||
488
aider/args.py
488
aider/args.py
@@ -25,24 +25,13 @@ def get_parser(default_config_files, git_root):
|
|||||||
description="aider is AI pair programming in your terminal",
|
description="aider is AI pair programming in your terminal",
|
||||||
add_config_file_help=True,
|
add_config_file_help=True,
|
||||||
default_config_files=default_config_files,
|
default_config_files=default_config_files,
|
||||||
|
config_file_parser_class=configargparse.YAMLConfigFileParser,
|
||||||
auto_env_var_prefix="AIDER_",
|
auto_env_var_prefix="AIDER_",
|
||||||
)
|
)
|
||||||
group = parser.add_argument_group("Main")
|
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)"
|
||||||
)
|
)
|
||||||
group.add_argument(
|
|
||||||
"--openai-api-key",
|
|
||||||
metavar="OPENAI_API_KEY",
|
|
||||||
env_var="OPENAI_API_KEY",
|
|
||||||
help="Specify the OpenAI API key",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--anthropic-api-key",
|
|
||||||
metavar="ANTHROPIC_API_KEY",
|
|
||||||
env_var="ANTHROPIC_API_KEY",
|
|
||||||
help="Specify the Anthropic API key",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--model",
|
"--model",
|
||||||
metavar="MODEL",
|
metavar="MODEL",
|
||||||
@@ -57,7 +46,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
const=opus_model,
|
const=opus_model,
|
||||||
help=f"Use {opus_model} model for the main chat",
|
help=f"Use {opus_model} model for the main chat",
|
||||||
)
|
)
|
||||||
sonnet_model = "claude-3-5-sonnet-20240620"
|
sonnet_model = "claude-3-5-sonnet-20241022"
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--sonnet",
|
"--sonnet",
|
||||||
action="store_const",
|
action="store_const",
|
||||||
@@ -65,6 +54,14 @@ def get_parser(default_config_files, git_root):
|
|||||||
const=sonnet_model,
|
const=sonnet_model,
|
||||||
help=f"Use {sonnet_model} model for the main chat",
|
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"
|
gpt_4_model = "gpt-4-0613"
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--4",
|
"--4",
|
||||||
@@ -74,7 +71,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
const=gpt_4_model,
|
const=gpt_4_model,
|
||||||
help=f"Use {gpt_4_model} model for the main chat",
|
help=f"Use {gpt_4_model} model for the main chat",
|
||||||
)
|
)
|
||||||
gpt_4o_model = "gpt-4o-2024-08-06"
|
gpt_4o_model = "gpt-4o"
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--4o",
|
"--4o",
|
||||||
action="store_const",
|
action="store_const",
|
||||||
@@ -117,45 +114,77 @@ def get_parser(default_config_files, git_root):
|
|||||||
const=deepseek_model,
|
const=deepseek_model,
|
||||||
help=f"Use {deepseek_model} model for the main chat",
|
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("Model Settings")
|
group = parser.add_argument_group("API Keys and settings")
|
||||||
|
group.add_argument(
|
||||||
|
"--openai-api-key",
|
||||||
|
help="Specify the OpenAI API key",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--anthropic-api-key",
|
||||||
|
help="Specify the Anthropic API key",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--openai-api-base",
|
||||||
|
help="Specify the api base url",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--openai-api-type",
|
||||||
|
help="(deprecated, use --set-env OPENAI_API_TYPE=<value>)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--openai-api-version",
|
||||||
|
help="(deprecated, use --set-env OPENAI_API_VERSION=<value>)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--openai-api-deployment-id",
|
||||||
|
help="(deprecated, use --set-env OPENAI_API_DEPLOYMENT_ID=<value>)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--openai-organization-id",
|
||||||
|
help="(deprecated, use --set-env OPENAI_ORGANIZATION=<value>)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--set-env",
|
||||||
|
action="append",
|
||||||
|
metavar="ENV_VAR_NAME=value",
|
||||||
|
help="Set an environment variable (to control API settings, can be used multiple times)",
|
||||||
|
default=[],
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--api-key",
|
||||||
|
action="append",
|
||||||
|
metavar="PROVIDER=KEY",
|
||||||
|
help=(
|
||||||
|
"Set an API key for a provider (eg: --api-key provider=<key> sets"
|
||||||
|
" PROVIDER_API_KEY=<key>)"
|
||||||
|
),
|
||||||
|
default=[],
|
||||||
|
)
|
||||||
|
group = parser.add_argument_group("Model settings")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--list-models",
|
"--list-models",
|
||||||
"--models",
|
"--models",
|
||||||
metavar="MODEL",
|
metavar="MODEL",
|
||||||
help="List known models which match the (partial) MODEL name",
|
help="List known models which match the (partial) MODEL name",
|
||||||
)
|
)
|
||||||
group.add_argument(
|
|
||||||
"--openai-api-base",
|
|
||||||
metavar="OPENAI_API_BASE",
|
|
||||||
env_var="OPENAI_API_BASE",
|
|
||||||
help="Specify the api base url",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--openai-api-type",
|
|
||||||
metavar="OPENAI_API_TYPE",
|
|
||||||
env_var="OPENAI_API_TYPE",
|
|
||||||
help="Specify the api_type",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--openai-api-version",
|
|
||||||
metavar="OPENAI_API_VERSION",
|
|
||||||
env_var="OPENAI_API_VERSION",
|
|
||||||
help="Specify the api_version",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--openai-api-deployment-id",
|
|
||||||
metavar="OPENAI_API_DEPLOYMENT_ID",
|
|
||||||
env_var="OPENAI_API_DEPLOYMENT_ID",
|
|
||||||
help="Specify the deployment_id",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--openai-organization-id",
|
|
||||||
metavar="OPENAI_ORGANIZATION_ID",
|
|
||||||
env_var="OPENAI_ORGANIZATION_ID",
|
|
||||||
help="Specify the OpenAI organization ID",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--model-settings-file",
|
"--model-settings-file",
|
||||||
metavar="MODEL_SETTINGS_FILE",
|
metavar="MODEL_SETTINGS_FILE",
|
||||||
@@ -168,12 +197,24 @@ def get_parser(default_config_files, git_root):
|
|||||||
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",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--alias",
|
||||||
|
action="append",
|
||||||
|
metavar="ALIAS:MODEL",
|
||||||
|
help="Add a model alias (can be used multiple times)",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--verify-ssl",
|
"--verify-ssl",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
default=True,
|
default=True,
|
||||||
help="Verify the SSL cert when connecting to models (default: True)",
|
help="Verify the SSL cert when connecting to models (default: True)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Timeout in seconds for API calls (default: None)",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--edit-format",
|
"--edit-format",
|
||||||
"--chat-mode",
|
"--chat-mode",
|
||||||
@@ -181,6 +222,13 @@ def get_parser(default_config_files, git_root):
|
|||||||
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)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--architect",
|
||||||
|
action="store_const",
|
||||||
|
dest="edit_format",
|
||||||
|
const="architect",
|
||||||
|
help="Use architect edit format for the main chat",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--weak-model",
|
"--weak-model",
|
||||||
metavar="WEAK_MODEL",
|
metavar="WEAK_MODEL",
|
||||||
@@ -190,6 +238,18 @@ def get_parser(default_config_files, git_root):
|
|||||||
" depends on --model)"
|
" depends on --model)"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--editor-model",
|
||||||
|
metavar="EDITOR_MODEL",
|
||||||
|
default=None,
|
||||||
|
help="Specify the model to use for editor tasks (default depends on --model)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--editor-edit-format",
|
||||||
|
metavar="EDITOR_EDIT_FORMAT",
|
||||||
|
default=None,
|
||||||
|
help="Specify the edit format for the editor model (default: depends on editor model)",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--show-model-warnings",
|
"--show-model-warnings",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
@@ -197,17 +257,17 @@ def get_parser(default_config_files, git_root):
|
|||||||
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(
|
group.add_argument(
|
||||||
"--map-tokens",
|
"--max-chat-history-tokens",
|
||||||
type=int,
|
type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help="Suggested number of tokens to use for repo map, use 0 to disable (default: 1024)",
|
help=(
|
||||||
)
|
"Soft limit on tokens for chat history, after which summarization begins."
|
||||||
group.add_argument(
|
" If unspecified, defaults to the model's max_chat_history_tokens."
|
||||||
"--map-refresh",
|
),
|
||||||
choices=["auto", "always", "files", "manual"],
|
|
||||||
default="auto",
|
|
||||||
help="Control how often the repo map is refreshed (default: auto)",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
##########
|
||||||
|
group = parser.add_argument_group("Cache settings")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--cache-prompts",
|
"--cache-prompts",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
@@ -220,29 +280,30 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=0,
|
default=0,
|
||||||
help="Number of times to ping at 5min intervals to keep prompt cache warm (default: 0)",
|
help="Number of times to ping at 5min intervals to keep prompt cache warm (default: 0)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
##########
|
||||||
|
group = parser.add_argument_group("Repomap settings")
|
||||||
|
group.add_argument(
|
||||||
|
"--map-tokens",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Suggested number of tokens to use for repo map, use 0 to disable (default: 1024)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--map-refresh",
|
||||||
|
choices=["auto", "always", "files", "manual"],
|
||||||
|
default="auto",
|
||||||
|
help=(
|
||||||
|
"Control how often the repo map is refreshed. Options: auto, always, files, manual"
|
||||||
|
" (default: auto)"
|
||||||
|
),
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--map-multiplier-no-files",
|
"--map-multiplier-no-files",
|
||||||
type=float,
|
type=float,
|
||||||
default=2,
|
default=2,
|
||||||
help="Multiplier for map tokens when no files are specified (default: 2)",
|
help="Multiplier for map tokens when no files are specified (default: 2)",
|
||||||
)
|
)
|
||||||
group.add_argument(
|
|
||||||
"--max-chat-history-tokens",
|
|
||||||
type=int,
|
|
||||||
default=None,
|
|
||||||
help=(
|
|
||||||
"Maximum number of tokens to use for chat history. If not specified, uses the model's"
|
|
||||||
" max_chat_history_tokens."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# 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.
|
|
||||||
group.add_argument(
|
|
||||||
"--env-file",
|
|
||||||
metavar="ENV_FILE",
|
|
||||||
default=default_env_file(git_root),
|
|
||||||
help="Specify the .env file to load (default: .env in git root)",
|
|
||||||
)
|
|
||||||
|
|
||||||
##########
|
##########
|
||||||
group = parser.add_argument_group("History Files")
|
group = parser.add_argument_group("History Files")
|
||||||
@@ -278,7 +339,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
)
|
)
|
||||||
|
|
||||||
##########
|
##########
|
||||||
group = parser.add_argument_group("Output Settings")
|
group = parser.add_argument_group("Output settings")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--dark-mode",
|
"--dark-mode",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -328,6 +389,39 @@ def get_parser(default_config_files, git_root):
|
|||||||
default="#0088ff",
|
default="#0088ff",
|
||||||
help="Set the color for assistant output (default: #0088ff)",
|
help="Set the color for assistant output (default: #0088ff)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--completion-menu-color",
|
||||||
|
metavar="COLOR",
|
||||||
|
default=None,
|
||||||
|
help="Set the color for the completion menu (default: terminal's default text color)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--completion-menu-bg-color",
|
||||||
|
metavar="COLOR",
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
"Set the background color for the completion menu (default: terminal's default"
|
||||||
|
" background color)"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--completion-menu-current-color",
|
||||||
|
metavar="COLOR",
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
"Set the color for the current item in the completion menu (default: terminal's default"
|
||||||
|
" background color)"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--completion-menu-current-bg-color",
|
||||||
|
metavar="COLOR",
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
"Set the background color for the current item in the completion menu (default:"
|
||||||
|
" terminal's default text color)"
|
||||||
|
),
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--code-theme",
|
"--code-theme",
|
||||||
default="default",
|
default="default",
|
||||||
@@ -344,7 +438,7 @@ def get_parser(default_config_files, git_root):
|
|||||||
)
|
)
|
||||||
|
|
||||||
##########
|
##########
|
||||||
group = parser.add_argument_group("Git Settings")
|
group = parser.add_argument_group("Git settings")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--git",
|
"--git",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
@@ -425,6 +519,18 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=False,
|
default=False,
|
||||||
help="Perform a dry run without modifying files (default: False)",
|
help="Perform a dry run without modifying files (default: False)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--skip-sanity-check-repo",
|
||||||
|
action="store_true",
|
||||||
|
help="Skip the sanity check for the git repository (default: False)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--watch-files",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=False,
|
||||||
|
help="Enable/disable watching files for ai coding comments (default: False)",
|
||||||
|
)
|
||||||
group = parser.add_argument_group("Fixing and committing")
|
group = parser.add_argument_group("Fixing and committing")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--lint",
|
"--lint",
|
||||||
@@ -461,48 +567,32 @@ def get_parser(default_config_files, git_root):
|
|||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--test",
|
"--test",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Run tests and fix problems found",
|
help="Run tests, fix problems found and then exit",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
##########
|
##########
|
||||||
group = parser.add_argument_group("Other Settings")
|
group = parser.add_argument_group("Analytics")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--file",
|
"--analytics",
|
||||||
action="append",
|
action=argparse.BooleanOptionalAction,
|
||||||
metavar="FILE",
|
default=None,
|
||||||
help="specify a file to edit (can be used multiple times)",
|
help="Enable/disable analytics for current session (default: random)",
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--read",
|
"--analytics-log",
|
||||||
action="append",
|
metavar="ANALYTICS_LOG_FILE",
|
||||||
metavar="FILE",
|
help="Specify a file to log analytics events",
|
||||||
help="specify a read-only file (can be used multiple times)",
|
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--vim",
|
"--analytics-disable",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Use VI editing mode in the terminal (default: False)",
|
help="Permanently disable analytics",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
group.add_argument(
|
|
||||||
"--voice-language",
|
#########
|
||||||
metavar="VOICE_LANGUAGE",
|
group = parser.add_argument_group("Upgrading")
|
||||||
default="en",
|
|
||||||
help="Specify the language for voice using ISO 639-1 code (default: auto)",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--chat-language",
|
|
||||||
metavar="CHAT_LANGUAGE",
|
|
||||||
default=None,
|
|
||||||
help="Specify the language to use in the chat (default: None, uses system settings)",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--version",
|
|
||||||
action="version",
|
|
||||||
version=f"%(prog)s {__version__}",
|
|
||||||
help="Show the version number and exit",
|
|
||||||
)
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--just-check-update",
|
"--just-check-update",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -515,6 +605,12 @@ def get_parser(default_config_files, git_root):
|
|||||||
help="Check for new aider versions on launch",
|
help="Check for new aider versions on launch",
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--show-release-notes",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
help="Show release notes on first run of new version (default: None, ask user)",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--install-main-branch",
|
"--install-main-branch",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -529,41 +625,14 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--apply",
|
"--version",
|
||||||
metavar="FILE",
|
action="version",
|
||||||
help="Apply the changes from the given file instead of running the chat (debug)",
|
version=f"%(prog)s {__version__}",
|
||||||
)
|
help="Show the version number and exit",
|
||||||
group.add_argument(
|
|
||||||
"--yes",
|
|
||||||
action="store_true",
|
|
||||||
help="Always say yes to every confirmation",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"-v",
|
|
||||||
"--verbose",
|
|
||||||
action="store_true",
|
|
||||||
help="Enable verbose output",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--show-repo-map",
|
|
||||||
action="store_true",
|
|
||||||
help="Print the repo map and exit (debug)",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--show-prompts",
|
|
||||||
action="store_true",
|
|
||||||
help="Print the system prompts and exit (debug)",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--exit",
|
|
||||||
action="store_true",
|
|
||||||
help="Do all startup activities then exit before accepting user input (debug)",
|
|
||||||
default=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
##########
|
||||||
|
group = parser.add_argument_group("Modes")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--message",
|
"--message",
|
||||||
"--msg",
|
"--msg",
|
||||||
@@ -582,6 +651,115 @@ def get_parser(default_config_files, git_root):
|
|||||||
" (disables chat mode)"
|
" (disables chat mode)"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--gui",
|
||||||
|
"--browser",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
help="Run aider in your browser (default: False)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--copy-paste",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=False,
|
||||||
|
help="Enable automatic copy/paste of chat between aider and web UI (default: False)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--apply",
|
||||||
|
metavar="FILE",
|
||||||
|
help="Apply the changes from the given file instead of running the chat (debug)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--apply-clipboard-edits",
|
||||||
|
action="store_true",
|
||||||
|
help="Apply clipboard contents as edits using the main model's editor format",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--exit",
|
||||||
|
action="store_true",
|
||||||
|
help="Do all startup activities then exit before accepting user input (debug)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--show-repo-map",
|
||||||
|
action="store_true",
|
||||||
|
help="Print the repo map and exit (debug)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--show-prompts",
|
||||||
|
action="store_true",
|
||||||
|
help="Print the system prompts and exit (debug)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
##########
|
||||||
|
group = parser.add_argument_group("Voice settings")
|
||||||
|
group.add_argument(
|
||||||
|
"--voice-format",
|
||||||
|
metavar="VOICE_FORMAT",
|
||||||
|
default="wav",
|
||||||
|
choices=["wav", "mp3", "webm"],
|
||||||
|
help="Audio format for voice recording (default: wav). webm and mp3 require ffmpeg",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--voice-language",
|
||||||
|
metavar="VOICE_LANGUAGE",
|
||||||
|
default="en",
|
||||||
|
help="Specify the language for voice using ISO 639-1 code (default: auto)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--voice-input-device",
|
||||||
|
metavar="VOICE_INPUT_DEVICE",
|
||||||
|
default=None,
|
||||||
|
help="Specify the input device name for voice recording",
|
||||||
|
)
|
||||||
|
|
||||||
|
######
|
||||||
|
group = parser.add_argument_group("Other settings")
|
||||||
|
group.add_argument(
|
||||||
|
"--file",
|
||||||
|
action="append",
|
||||||
|
metavar="FILE",
|
||||||
|
help="specify a file to edit (can be used multiple times)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--read",
|
||||||
|
action="append",
|
||||||
|
metavar="FILE",
|
||||||
|
help="specify a read-only file (can be used multiple times)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--vim",
|
||||||
|
action="store_true",
|
||||||
|
help="Use VI editing mode in the terminal (default: False)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--chat-language",
|
||||||
|
metavar="CHAT_LANGUAGE",
|
||||||
|
default=None,
|
||||||
|
help="Specify the language to use in the chat (default: None, uses system settings)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--yes-always",
|
||||||
|
action="store_true",
|
||||||
|
help="Always say yes to every confirmation",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"-v",
|
||||||
|
"--verbose",
|
||||||
|
action="store_true",
|
||||||
|
help="Enable verbose output",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--load",
|
||||||
|
metavar="LOAD_FILE",
|
||||||
|
help="Load and execute /commands from a file on launch",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--encoding",
|
"--encoding",
|
||||||
default="utf-8",
|
default="utf-8",
|
||||||
@@ -597,12 +775,13 @@ def get_parser(default_config_files, git_root):
|
|||||||
" or home directory)"
|
" or home directory)"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
# 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.
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--gui",
|
"--env-file",
|
||||||
"--browser",
|
metavar="ENV_FILE",
|
||||||
action="store_true",
|
default=default_env_file(git_root),
|
||||||
help="Run aider in your browser",
|
help="Specify the .env file to load (default: .env in git root)",
|
||||||
default=False,
|
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--suggest-shell-commands",
|
"--suggest-shell-commands",
|
||||||
@@ -610,6 +789,28 @@ def get_parser(default_config_files, git_root):
|
|||||||
default=True,
|
default=True,
|
||||||
help="Enable/disable suggesting shell commands (default: True)",
|
help="Enable/disable suggesting shell commands (default: True)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--fancy-input",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=True,
|
||||||
|
help="Enable/disable fancy input with history and completion (default: True)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--multiline",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=False,
|
||||||
|
help="Enable/disable multi-line input mode with Meta-Enter to submit (default: False)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--detect-urls",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
default=True,
|
||||||
|
help="Enable/disable detection and offering to add URLs to chat (default: True)",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--editor",
|
||||||
|
help="Specify which editor to use for the /editor command",
|
||||||
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@@ -625,7 +826,6 @@ def get_md_help():
|
|||||||
parser.formatter_class = MarkdownHelpFormatter
|
parser.formatter_class = MarkdownHelpFormatter
|
||||||
|
|
||||||
return argparse.ArgumentParser.format_help(parser)
|
return argparse.ArgumentParser.format_help(parser)
|
||||||
return parser.format_help()
|
|
||||||
|
|
||||||
|
|
||||||
def get_sample_yaml():
|
def get_sample_yaml():
|
||||||
@@ -639,7 +839,6 @@ def get_sample_yaml():
|
|||||||
parser.formatter_class = YamlHelpFormatter
|
parser.formatter_class = YamlHelpFormatter
|
||||||
|
|
||||||
return argparse.ArgumentParser.format_help(parser)
|
return argparse.ArgumentParser.format_help(parser)
|
||||||
return parser.format_help()
|
|
||||||
|
|
||||||
|
|
||||||
def get_sample_dotenv():
|
def get_sample_dotenv():
|
||||||
@@ -653,7 +852,6 @@ def get_sample_dotenv():
|
|||||||
parser.formatter_class = DotEnvFormatter
|
parser.formatter_class = DotEnvFormatter
|
||||||
|
|
||||||
return argparse.ArgumentParser.format_help(parser)
|
return argparse.ArgumentParser.format_help(parser)
|
||||||
return parser.format_help()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -147,7 +147,10 @@ class YamlHelpFormatter(argparse.HelpFormatter):
|
|||||||
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}: [xxx,yyyy,zzz]\n")
|
parts.append(f"#{switch}:")
|
||||||
|
parts.append(f"# - xxx")
|
||||||
|
parts.append(f"# - yyy")
|
||||||
|
parts.append(f"# - zzz")
|
||||||
else:
|
else:
|
||||||
parts.append(f"#{switch}: xxx\n")
|
parts.append(f"#{switch}: xxx\n")
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
|
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 .editblock_coder import EditBlockCoder
|
from .editblock_coder import EditBlockCoder
|
||||||
from .editblock_fenced_coder import EditBlockFencedCoder
|
from .editblock_fenced_coder import EditBlockFencedCoder
|
||||||
|
from .editor_editblock_coder import EditorEditBlockCoder
|
||||||
|
from .editor_whole_coder import EditorWholeFileCoder
|
||||||
from .help_coder import HelpCoder
|
from .help_coder import HelpCoder
|
||||||
|
|
||||||
# from .single_wholefile_func_coder import SingleWholeFileFunctionCoder
|
|
||||||
from .udiff_coder import UnifiedDiffCoder
|
from .udiff_coder import UnifiedDiffCoder
|
||||||
from .wholefile_coder import WholeFileCoder
|
from .wholefile_coder import WholeFileCoder
|
||||||
|
|
||||||
|
# from .single_wholefile_func_coder import SingleWholeFileFunctionCoder
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
HelpCoder,
|
HelpCoder,
|
||||||
AskCoder,
|
AskCoder,
|
||||||
@@ -17,4 +20,7 @@ __all__ = [
|
|||||||
WholeFileCoder,
|
WholeFileCoder,
|
||||||
UnifiedDiffCoder,
|
UnifiedDiffCoder,
|
||||||
# SingleWholeFileFunctionCoder,
|
# SingleWholeFileFunctionCoder,
|
||||||
|
ArchitectCoder,
|
||||||
|
EditorEditBlockCoder,
|
||||||
|
EditorWholeFileCoder,
|
||||||
]
|
]
|
||||||
|
|||||||
47
aider/coders/architect_coder.py
Normal file
47
aider/coders/architect_coder.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from .architect_prompts import ArchitectPrompts
|
||||||
|
from .ask_coder import AskCoder
|
||||||
|
from .base_coder import Coder
|
||||||
|
|
||||||
|
|
||||||
|
class ArchitectCoder(AskCoder):
|
||||||
|
edit_format = "architect"
|
||||||
|
gpt_prompts = ArchitectPrompts()
|
||||||
|
|
||||||
|
def reply_completed(self):
|
||||||
|
content = self.partial_response_content
|
||||||
|
|
||||||
|
if not content or not content.strip():
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.io.confirm_ask("Edit the files?"):
|
||||||
|
return
|
||||||
|
|
||||||
|
kwargs = dict()
|
||||||
|
|
||||||
|
# Use the editor_model from the main_model if it exists, otherwise use the main_model itself
|
||||||
|
editor_model = self.main_model.editor_model or self.main_model
|
||||||
|
|
||||||
|
kwargs["main_model"] = editor_model
|
||||||
|
kwargs["edit_format"] = self.main_model.editor_edit_format
|
||||||
|
kwargs["suggest_shell_commands"] = False
|
||||||
|
kwargs["map_tokens"] = 0
|
||||||
|
kwargs["total_cost"] = self.total_cost
|
||||||
|
kwargs["cache_prompts"] = False
|
||||||
|
kwargs["num_cache_warming_pings"] = 0
|
||||||
|
kwargs["summarize_from_coder"] = False
|
||||||
|
|
||||||
|
new_kwargs = dict(io=self.io, from_coder=self)
|
||||||
|
new_kwargs.update(kwargs)
|
||||||
|
|
||||||
|
editor_coder = Coder.create(**new_kwargs)
|
||||||
|
editor_coder.cur_messages = []
|
||||||
|
editor_coder.done_messages = []
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
editor_coder.show_announcements()
|
||||||
|
|
||||||
|
editor_coder.run(with_message=content, preproc=False)
|
||||||
|
|
||||||
|
self.move_back_cur_messages("I made those changes to the files.")
|
||||||
|
self.total_cost = editor_coder.total_cost
|
||||||
|
self.aider_commit_hashes = editor_coder.aider_commit_hashes
|
||||||
40
aider/coders/architect_prompts.py
Normal file
40
aider/coders/architect_prompts.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
from .base_prompts import CoderPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class ArchitectPrompts(CoderPrompts):
|
||||||
|
main_system = """Act as an expert architect engineer and provide direction to your editor engineer.
|
||||||
|
Study the change request and the current code.
|
||||||
|
Describe how to modify the code to complete the request.
|
||||||
|
The editor engineer will rely solely on your instructions, so make them unambiguous and complete.
|
||||||
|
Explain all needed code changes clearly and completely, but concisely.
|
||||||
|
Just show the changes needed.
|
||||||
|
|
||||||
|
DO NOT show the entire updated function/file/etc!
|
||||||
|
|
||||||
|
Always reply to the user in {language}.
|
||||||
|
"""
|
||||||
|
|
||||||
|
example_messages = []
|
||||||
|
|
||||||
|
files_content_prefix = """I have *added these files to the chat* so you 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 = ""
|
||||||
@@ -6,8 +6,7 @@ from .base_prompts import CoderPrompts
|
|||||||
class AskPrompts(CoderPrompts):
|
class AskPrompts(CoderPrompts):
|
||||||
main_system = """Act as an expert code analyst.
|
main_system = """Act as an expert code analyst.
|
||||||
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 the same language they are using.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
example_messages = []
|
example_messages = []
|
||||||
@@ -17,6 +16,10 @@ Always reply to the user in the same language they are using.
|
|||||||
Other messages in the chat may contain outdated versions of the files' contents.
|
Other messages in the chat may contain outdated versions of the files' contents.
|
||||||
""" # noqa: E501
|
""" # 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 = "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 = ""
|
||||||
|
|||||||
@@ -17,9 +17,12 @@ from collections import defaultdict
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from aider import __version__, models, prompts, urls, utils
|
from aider import __version__, models, prompts, urls, utils
|
||||||
|
from aider.analytics import Analytics
|
||||||
from aider.commands import Commands
|
from aider.commands import Commands
|
||||||
|
from aider.exceptions import LiteLLMExceptions
|
||||||
from aider.history import ChatSummary
|
from aider.history import ChatSummary
|
||||||
from aider.io import ConfirmGroup, InputOutput
|
from aider.io import ConfirmGroup, InputOutput
|
||||||
from aider.linter import Linter
|
from aider.linter import Linter
|
||||||
@@ -27,13 +30,22 @@ from aider.llm import litellm
|
|||||||
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.sendchat import retry_exceptions, send_completion
|
from aider.sendchat import RETRY_TIMEOUT, send_completion
|
||||||
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 ..dump import dump # noqa: F401
|
from ..dump import dump # noqa: F401
|
||||||
from .chat_chunks import ChatChunks
|
from .chat_chunks import ChatChunks
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownEditFormat(ValueError):
|
||||||
|
def __init__(self, edit_format, valid_formats):
|
||||||
|
self.edit_format = edit_format
|
||||||
|
self.valid_formats = valid_formats
|
||||||
|
super().__init__(
|
||||||
|
f"Unknown edit format {edit_format}. Valid formats are: {', '.join(valid_formats)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MissingAPIKeyError(ValueError):
|
class MissingAPIKeyError(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -88,8 +100,10 @@ class Coder:
|
|||||||
cache_warming_thread = None
|
cache_warming_thread = None
|
||||||
num_cache_warming_pings = 0
|
num_cache_warming_pings = 0
|
||||||
suggest_shell_commands = True
|
suggest_shell_commands = True
|
||||||
|
detect_urls = True
|
||||||
ignore_mentions = None
|
ignore_mentions = None
|
||||||
chat_language = None
|
chat_language = None
|
||||||
|
file_watcher = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
@@ -140,8 +154,9 @@ class Coder:
|
|||||||
aider_commit_hashes=from_coder.aider_commit_hashes,
|
aider_commit_hashes=from_coder.aider_commit_hashes,
|
||||||
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,
|
||||||
|
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
|
||||||
|
|
||||||
@@ -153,11 +168,15 @@ class Coder:
|
|||||||
res.original_kwargs = dict(kwargs)
|
res.original_kwargs = dict(kwargs)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
raise ValueError(f"Unknown edit format {edit_format}")
|
valid_formats = [
|
||||||
|
str(c.edit_format)
|
||||||
|
for c in coders.__all__
|
||||||
|
if hasattr(c, "edit_format") and c.edit_format is not None
|
||||||
|
]
|
||||||
|
raise UnknownEditFormat(edit_format, valid_formats)
|
||||||
|
|
||||||
def clone(self, **kwargs):
|
def clone(self, **kwargs):
|
||||||
new_coder = Coder.create(from_coder=self, **kwargs)
|
new_coder = Coder.create(from_coder=self, **kwargs)
|
||||||
new_coder.ignore_mentions = self.ignore_mentions
|
|
||||||
return new_coder
|
return new_coder
|
||||||
|
|
||||||
def get_announcements(self):
|
def get_announcements(self):
|
||||||
@@ -180,6 +199,13 @@ class Coder:
|
|||||||
output += ", infinite output"
|
output += ", infinite output"
|
||||||
lines.append(output)
|
lines.append(output)
|
||||||
|
|
||||||
|
if self.edit_format == "architect":
|
||||||
|
output = (
|
||||||
|
f"Editor model: {main_model.editor_model.name} with"
|
||||||
|
f" {main_model.editor_edit_format} edit format"
|
||||||
|
)
|
||||||
|
lines.append(output)
|
||||||
|
|
||||||
if weak_model is not main_model:
|
if weak_model is not main_model:
|
||||||
output = f"Weak model: {weak_model.name}"
|
output = f"Weak model: {weak_model.name}"
|
||||||
lines.append(output)
|
lines.append(output)
|
||||||
@@ -222,6 +248,9 @@ class Coder:
|
|||||||
if self.done_messages:
|
if self.done_messages:
|
||||||
lines.append("Restored previous conversation history.")
|
lines.append("Restored previous conversation history.")
|
||||||
|
|
||||||
|
if self.io.multiline_mode:
|
||||||
|
lines.append("Multiline mode: Enabled. Enter inserts newline, Alt-Enter submits text")
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -251,20 +280,39 @@ class Coder:
|
|||||||
commands=None,
|
commands=None,
|
||||||
summarizer=None,
|
summarizer=None,
|
||||||
total_cost=0.0,
|
total_cost=0.0,
|
||||||
|
analytics=None,
|
||||||
map_refresh="auto",
|
map_refresh="auto",
|
||||||
cache_prompts=False,
|
cache_prompts=False,
|
||||||
num_cache_warming_pings=0,
|
num_cache_warming_pings=0,
|
||||||
suggest_shell_commands=True,
|
suggest_shell_commands=True,
|
||||||
chat_language=None,
|
chat_language=None,
|
||||||
|
detect_urls=True,
|
||||||
|
ignore_mentions=None,
|
||||||
|
file_watcher=None,
|
||||||
|
auto_copy_context=False,
|
||||||
):
|
):
|
||||||
|
# Fill in a dummy Analytics if needed, but it is never .enable()'d
|
||||||
|
self.analytics = analytics if analytics is not None else Analytics()
|
||||||
|
|
||||||
|
self.event = self.analytics.event
|
||||||
self.chat_language = chat_language
|
self.chat_language = chat_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.ignore_mentions = set()
|
|
||||||
|
self.auto_copy_context = auto_copy_context
|
||||||
|
|
||||||
|
self.ignore_mentions = ignore_mentions
|
||||||
|
if not self.ignore_mentions:
|
||||||
|
self.ignore_mentions = set()
|
||||||
|
|
||||||
|
self.file_watcher = file_watcher
|
||||||
|
if self.file_watcher:
|
||||||
|
self.file_watcher.coder = self
|
||||||
|
|
||||||
self.suggest_shell_commands = suggest_shell_commands
|
self.suggest_shell_commands = suggest_shell_commands
|
||||||
|
self.detect_urls = detect_urls
|
||||||
|
|
||||||
self.num_cache_warming_pings = num_cache_warming_pings
|
self.num_cache_warming_pings = num_cache_warming_pings
|
||||||
|
|
||||||
@@ -340,6 +388,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):
|
||||||
|
self.io.tool_warning(f"Skipping {fname} that matches gitignore spec.")
|
||||||
|
|
||||||
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.")
|
||||||
continue
|
continue
|
||||||
@@ -630,6 +681,8 @@ class Coder:
|
|||||||
|
|
||||||
def get_readonly_files_messages(self):
|
def get_readonly_files_messages(self):
|
||||||
readonly_messages = []
|
readonly_messages = []
|
||||||
|
|
||||||
|
# Handle non-image files
|
||||||
read_only_content = self.get_read_only_files_content()
|
read_only_content = self.get_read_only_files_content()
|
||||||
if read_only_content:
|
if read_only_content:
|
||||||
readonly_messages += [
|
readonly_messages += [
|
||||||
@@ -641,6 +694,15 @@ class Coder:
|
|||||||
content="Ok, I will use these files as references.",
|
content="Ok, I will use these files as references.",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Handle image files
|
||||||
|
images_message = self.get_images_message(self.abs_read_only_fnames)
|
||||||
|
if images_message is not None:
|
||||||
|
readonly_messages += [
|
||||||
|
images_message,
|
||||||
|
dict(role="assistant", content="Ok, I will use these images as references."),
|
||||||
|
]
|
||||||
|
|
||||||
return readonly_messages
|
return readonly_messages
|
||||||
|
|
||||||
def get_chat_files_messages(self):
|
def get_chat_files_messages(self):
|
||||||
@@ -648,7 +710,7 @@ class Coder:
|
|||||||
if self.abs_fnames:
|
if self.abs_fnames:
|
||||||
files_content = self.gpt_prompts.files_content_prefix
|
files_content = self.gpt_prompts.files_content_prefix
|
||||||
files_content += self.get_files_content()
|
files_content += self.get_files_content()
|
||||||
files_reply = "Ok, any changes I propose will be to those files."
|
files_reply = self.gpt_prompts.files_content_assistant_reply
|
||||||
elif self.get_repo_map() and self.gpt_prompts.files_no_full_files_with_repo_map:
|
elif self.get_repo_map() and self.gpt_prompts.files_no_full_files_with_repo_map:
|
||||||
files_content = self.gpt_prompts.files_no_full_files_with_repo_map
|
files_content = self.gpt_prompts.files_no_full_files_with_repo_map
|
||||||
files_reply = self.gpt_prompts.files_no_full_files_with_repo_map_reply
|
files_reply = self.gpt_prompts.files_no_full_files_with_repo_map_reply
|
||||||
@@ -662,7 +724,7 @@ class Coder:
|
|||||||
dict(role="assistant", content=files_reply),
|
dict(role="assistant", content=files_reply),
|
||||||
]
|
]
|
||||||
|
|
||||||
images_message = self.get_images_message()
|
images_message = self.get_images_message(self.abs_fnames)
|
||||||
if images_message is not None:
|
if images_message is not None:
|
||||||
chat_files_messages += [
|
chat_files_messages += [
|
||||||
images_message,
|
images_message,
|
||||||
@@ -671,23 +733,42 @@ class Coder:
|
|||||||
|
|
||||||
return chat_files_messages
|
return chat_files_messages
|
||||||
|
|
||||||
def get_images_message(self):
|
def get_images_message(self, fnames):
|
||||||
if not self.main_model.accepts_images:
|
supports_images = self.main_model.info.get("supports_vision")
|
||||||
|
supports_pdfs = self.main_model.info.get("supports_pdf_input") or self.main_model.info.get(
|
||||||
|
"max_pdf_size_mb"
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://github.com/BerriAI/litellm/pull/6928
|
||||||
|
supports_pdfs = supports_pdfs or "claude-3-5-sonnet-20241022" in self.main_model.name
|
||||||
|
|
||||||
|
if not (supports_images or supports_pdfs):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
image_messages = []
|
image_messages = []
|
||||||
for fname, content in self.get_abs_fnames_content():
|
for fname in fnames:
|
||||||
if is_image_file(fname):
|
if not is_image_file(fname):
|
||||||
with open(fname, "rb") as image_file:
|
continue
|
||||||
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
|
|
||||||
mime_type, _ = mimetypes.guess_type(fname)
|
mime_type, _ = mimetypes.guess_type(fname)
|
||||||
if mime_type and mime_type.startswith("image/"):
|
if not mime_type:
|
||||||
image_url = f"data:{mime_type};base64,{encoded_string}"
|
continue
|
||||||
rel_fname = self.get_rel_fname(fname)
|
|
||||||
image_messages += [
|
with open(fname, "rb") as image_file:
|
||||||
{"type": "text", "text": f"Image file: {rel_fname}"},
|
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
|
||||||
{"type": "image_url", "image_url": {"url": image_url, "detail": "high"}},
|
image_url = f"data:{mime_type};base64,{encoded_string}"
|
||||||
]
|
rel_fname = self.get_rel_fname(fname)
|
||||||
|
|
||||||
|
if mime_type.startswith("image/") and supports_images:
|
||||||
|
image_messages += [
|
||||||
|
{"type": "text", "text": f"Image file: {rel_fname}"},
|
||||||
|
{"type": "image_url", "image_url": {"url": image_url, "detail": "high"}},
|
||||||
|
]
|
||||||
|
elif mime_type == "application/pdf" and supports_pdfs:
|
||||||
|
image_messages += [
|
||||||
|
{"type": "text", "text": f"PDF file: {rel_fname}"},
|
||||||
|
{"type": "image_url", "image_url": image_url},
|
||||||
|
]
|
||||||
|
|
||||||
if not image_messages:
|
if not image_messages:
|
||||||
return None
|
return None
|
||||||
@@ -706,6 +787,7 @@ class Coder:
|
|||||||
self.lint_outcome = None
|
self.lint_outcome = None
|
||||||
self.test_outcome = None
|
self.test_outcome = None
|
||||||
self.shell_commands = []
|
self.shell_commands = []
|
||||||
|
self.message_cost = 0
|
||||||
|
|
||||||
if self.repo:
|
if self.repo:
|
||||||
self.commit_before_message.append(self.repo.get_head_commit_sha())
|
self.commit_before_message.append(self.repo.get_head_commit_sha())
|
||||||
@@ -716,9 +798,10 @@ class Coder:
|
|||||||
self.io.user_input(with_message)
|
self.io.user_input(with_message)
|
||||||
self.run_one(with_message, preproc)
|
self.run_one(with_message, preproc)
|
||||||
return self.partial_response_content
|
return self.partial_response_content
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
if not self.io.placeholder:
|
||||||
|
self.copy_context()
|
||||||
user_message = self.get_input()
|
user_message = self.get_input()
|
||||||
self.run_one(user_message, preproc)
|
self.run_one(user_message, preproc)
|
||||||
self.show_undo_hint()
|
self.show_undo_hint()
|
||||||
@@ -727,6 +810,10 @@ class Coder:
|
|||||||
except EOFError:
|
except EOFError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def copy_context(self):
|
||||||
|
if self.auto_copy_context:
|
||||||
|
self.commands.cmd_copy_context()
|
||||||
|
|
||||||
def get_input(self):
|
def get_input(self):
|
||||||
inchat_files = self.get_inchat_relative_files()
|
inchat_files = self.get_inchat_relative_files()
|
||||||
read_only_files = [self.get_rel_fname(fname) for fname in self.abs_read_only_fnames]
|
read_only_files = [self.get_rel_fname(fname) for fname in self.abs_read_only_fnames]
|
||||||
@@ -749,7 +836,7 @@ class Coder:
|
|||||||
return self.commands.run(inp)
|
return self.commands.run(inp)
|
||||||
|
|
||||||
self.check_for_file_mentions(inp)
|
self.check_for_file_mentions(inp)
|
||||||
self.check_for_urls(inp)
|
inp = self.check_for_urls(inp)
|
||||||
|
|
||||||
return inp
|
return inp
|
||||||
|
|
||||||
@@ -775,21 +862,43 @@ class Coder:
|
|||||||
self.num_reflections += 1
|
self.num_reflections += 1
|
||||||
message = self.reflected_message
|
message = self.reflected_message
|
||||||
|
|
||||||
def check_for_urls(self, inp):
|
def check_and_open_urls(self, exc, friendly_msg=None):
|
||||||
|
"""Check exception for URLs, offer to open in a browser, with user-friendly error msgs."""
|
||||||
|
text = str(exc)
|
||||||
|
|
||||||
|
if friendly_msg:
|
||||||
|
self.io.tool_warning(text)
|
||||||
|
self.io.tool_error(f"{friendly_msg}")
|
||||||
|
else:
|
||||||
|
self.io.tool_error(text)
|
||||||
|
|
||||||
|
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*)")
|
||||||
|
urls = list(set(url_pattern.findall(text))) # Use set to remove duplicates
|
||||||
|
for url in urls:
|
||||||
|
url = url.rstrip(".',\"")
|
||||||
|
self.io.offer_url(url)
|
||||||
|
return urls
|
||||||
|
|
||||||
|
def check_for_urls(self, inp: str) -> List[str]:
|
||||||
|
"""Check input for URLs and offer to add them to the chat."""
|
||||||
|
if not self.detect_urls:
|
||||||
|
return inp
|
||||||
|
|
||||||
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*[^\s,.])")
|
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
|
||||||
added_urls = []
|
|
||||||
group = ConfirmGroup(urls)
|
group = ConfirmGroup(urls)
|
||||||
for url in urls:
|
for url in urls:
|
||||||
if url not in self.rejected_urls:
|
if url not in self.rejected_urls:
|
||||||
if self.io.confirm_ask("Add URL to the chat?", subject=url, group=group):
|
url = url.rstrip(".',\"")
|
||||||
|
if self.io.confirm_ask(
|
||||||
|
"Add URL to the chat?", subject=url, group=group, allow_never=True
|
||||||
|
):
|
||||||
inp += "\n\n"
|
inp += "\n\n"
|
||||||
inp += self.commands.cmd_web(url)
|
inp += self.commands.cmd_web(url, return_content=True)
|
||||||
added_urls.append(url)
|
|
||||||
else:
|
else:
|
||||||
self.rejected_urls.add(url)
|
self.rejected_urls.add(url)
|
||||||
|
|
||||||
return added_urls
|
return inp
|
||||||
|
|
||||||
def keyboard_interrupt(self):
|
def keyboard_interrupt(self):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@@ -797,6 +906,7 @@ class Coder:
|
|||||||
thresh = 2 # seconds
|
thresh = 2 # seconds
|
||||||
if self.last_keyboard_interrupt and now - self.last_keyboard_interrupt < thresh:
|
if self.last_keyboard_interrupt and now - self.last_keyboard_interrupt < thresh:
|
||||||
self.io.tool_warning("\n\n^C KeyboardInterrupt")
|
self.io.tool_warning("\n\n^C KeyboardInterrupt")
|
||||||
|
self.event("exit", reason="Control-C")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
self.io.tool_warning("\n\n^C again to exit")
|
self.io.tool_warning("\n\n^C again to exit")
|
||||||
@@ -920,12 +1030,18 @@ class Coder:
|
|||||||
platform=platform_text
|
platform=platform_text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.chat_language:
|
||||||
|
language = self.chat_language
|
||||||
|
else:
|
||||||
|
language = "the same language they are using"
|
||||||
|
|
||||||
prompt = prompt.format(
|
prompt = prompt.format(
|
||||||
fence=self.fence,
|
fence=self.fence,
|
||||||
lazy_prompt=lazy_prompt,
|
lazy_prompt=lazy_prompt,
|
||||||
platform=platform_text,
|
platform=platform_text,
|
||||||
shell_cmd_prompt=shell_cmd_prompt,
|
shell_cmd_prompt=shell_cmd_prompt,
|
||||||
shell_cmd_reminder=shell_cmd_reminder,
|
shell_cmd_reminder=shell_cmd_reminder,
|
||||||
|
language=language,
|
||||||
)
|
)
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
@@ -1009,18 +1125,21 @@ class Coder:
|
|||||||
# add the reminder anyway
|
# add the reminder anyway
|
||||||
total_tokens = 0
|
total_tokens = 0
|
||||||
|
|
||||||
final = chunks.cur[-1]
|
if chunks.cur:
|
||||||
|
final = chunks.cur[-1]
|
||||||
|
else:
|
||||||
|
final = None
|
||||||
|
|
||||||
max_input_tokens = self.main_model.info.get("max_input_tokens") or 0
|
max_input_tokens = self.main_model.info.get("max_input_tokens") or 0
|
||||||
# Add the reminder prompt if we still have room to include it.
|
# Add the reminder prompt if we still have room to include it.
|
||||||
if (
|
if (
|
||||||
max_input_tokens is None
|
not max_input_tokens
|
||||||
or total_tokens < max_input_tokens
|
or total_tokens < max_input_tokens
|
||||||
and self.gpt_prompts.system_reminder
|
and self.gpt_prompts.system_reminder
|
||||||
):
|
):
|
||||||
if self.main_model.reminder == "sys":
|
if self.main_model.reminder == "sys":
|
||||||
chunks.reminder = reminder_message
|
chunks.reminder = reminder_message
|
||||||
elif self.main_model.reminder == "user" and final["role"] == "user":
|
elif self.main_model.reminder == "user" and final and final["role"] == "user":
|
||||||
# stuff it into the user message
|
# stuff it into the user message
|
||||||
new_content = (
|
new_content = (
|
||||||
final["content"]
|
final["content"]
|
||||||
@@ -1064,13 +1183,15 @@ class Coder:
|
|||||||
self.warming_pings_left -= 1
|
self.warming_pings_left -= 1
|
||||||
self.next_cache_warm = time.time() + delay
|
self.next_cache_warm = time.time() + delay
|
||||||
|
|
||||||
|
kwargs = dict(self.main_model.extra_params) or dict()
|
||||||
|
kwargs["max_tokens"] = 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
completion = litellm.completion(
|
completion = litellm.completion(
|
||||||
model=self.main_model.name,
|
model=self.main_model.name,
|
||||||
messages=self.cache_warming_chunks.cacheable_messages(),
|
messages=self.cache_warming_chunks.cacheable_messages(),
|
||||||
stream=False,
|
stream=False,
|
||||||
max_tokens=1,
|
**kwargs,
|
||||||
extra_headers=self.main_model.extra_headers,
|
|
||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.io.tool_warning(f"Cache warming error: {str(err)}")
|
self.io.tool_warning(f"Cache warming error: {str(err)}")
|
||||||
@@ -1090,6 +1211,8 @@ class Coder:
|
|||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
def send_message(self, inp):
|
def send_message(self, inp):
|
||||||
|
self.event("message_send_starting")
|
||||||
|
|
||||||
self.cur_messages += [
|
self.cur_messages += [
|
||||||
dict(role="user", content=inp),
|
dict(role="user", content=inp),
|
||||||
]
|
]
|
||||||
@@ -1102,10 +1225,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 = ""
|
||||||
self.mdstream = self.io.assistant_output("", self.stream)
|
if self.show_pretty() and self.stream:
|
||||||
|
self.mdstream = self.io.get_assistant_mdstream()
|
||||||
|
else:
|
||||||
|
self.mdstream = None
|
||||||
|
|
||||||
retry_delay = 0.125
|
retry_delay = 0.125
|
||||||
|
|
||||||
|
litellm_ex = LiteLLMExceptions()
|
||||||
|
|
||||||
self.usage_report = None
|
self.usage_report = None
|
||||||
exhausted = False
|
exhausted = False
|
||||||
interrupted = False
|
interrupted = False
|
||||||
@@ -1114,24 +1242,37 @@ class Coder:
|
|||||||
try:
|
try:
|
||||||
yield from self.send(messages, functions=self.functions)
|
yield from self.send(messages, functions=self.functions)
|
||||||
break
|
break
|
||||||
except retry_exceptions() as err:
|
except litellm_ex.exceptions_tuple() as err:
|
||||||
self.io.tool_warning(str(err))
|
ex_info = litellm_ex.get_ex_info(err)
|
||||||
retry_delay *= 2
|
|
||||||
if retry_delay > 60:
|
if ex_info.name == "ContextWindowExceededError":
|
||||||
|
exhausted = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
should_retry = ex_info.retry
|
||||||
|
if should_retry:
|
||||||
|
retry_delay *= 2
|
||||||
|
if retry_delay > RETRY_TIMEOUT:
|
||||||
|
should_retry = False
|
||||||
|
|
||||||
|
if not should_retry:
|
||||||
|
self.mdstream = None
|
||||||
|
self.check_and_open_urls(err, ex_info.description)
|
||||||
|
break
|
||||||
|
|
||||||
|
err_msg = str(err)
|
||||||
|
if ex_info.description:
|
||||||
|
self.io.tool_warning(err_msg)
|
||||||
|
self.io.tool_error(ex_info.description)
|
||||||
|
else:
|
||||||
|
self.io.tool_error(err_msg)
|
||||||
|
|
||||||
self.io.tool_output(f"Retrying in {retry_delay:.1f} seconds...")
|
self.io.tool_output(f"Retrying in {retry_delay:.1f} seconds...")
|
||||||
time.sleep(retry_delay)
|
time.sleep(retry_delay)
|
||||||
continue
|
continue
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
interrupted = True
|
interrupted = True
|
||||||
break
|
break
|
||||||
except litellm.ContextWindowExceededError:
|
|
||||||
# The input is overflowing the context window!
|
|
||||||
exhausted = True
|
|
||||||
break
|
|
||||||
except litellm.exceptions.BadRequestError as br_err:
|
|
||||||
self.io.tool_error(f"BadRequestError: {br_err}")
|
|
||||||
return
|
|
||||||
except FinishReasonLength:
|
except FinishReasonLength:
|
||||||
# We hit the output limit!
|
# We hit the output limit!
|
||||||
if not self.main_model.info.get("supports_assistant_prefill"):
|
if not self.main_model.info.get("supports_assistant_prefill"):
|
||||||
@@ -1147,9 +1288,11 @@ class Coder:
|
|||||||
dict(role="assistant", content=self.multi_response_content, prefix=True)
|
dict(role="assistant", content=self.multi_response_content, prefix=True)
|
||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.io.tool_error(f"Unexpected error: {err}")
|
self.mdstream = None
|
||||||
lines = traceback.format_exception(type(err), err, err.__traceback__)
|
lines = traceback.format_exception(type(err), err, err.__traceback__)
|
||||||
self.io.tool_error("".join(lines))
|
self.io.tool_warning("".join(lines))
|
||||||
|
self.io.tool_error(str(err))
|
||||||
|
self.event("message_send_exception", exception=str(err))
|
||||||
return
|
return
|
||||||
finally:
|
finally:
|
||||||
if self.mdstream:
|
if self.mdstream:
|
||||||
@@ -1179,6 +1322,20 @@ class Coder:
|
|||||||
else:
|
else:
|
||||||
content = ""
|
content = ""
|
||||||
|
|
||||||
|
if not interrupted:
|
||||||
|
add_rel_files_message = self.check_for_file_mentions(content)
|
||||||
|
if add_rel_files_message:
|
||||||
|
if self.reflected_message:
|
||||||
|
self.reflected_message += "\n\n" + add_rel_files_message
|
||||||
|
else:
|
||||||
|
self.reflected_message = add_rel_files_message
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.reply_completed()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
interrupted = True
|
||||||
|
|
||||||
if interrupted:
|
if interrupted:
|
||||||
content += "\n^C KeyboardInterrupt"
|
content += "\n^C KeyboardInterrupt"
|
||||||
self.cur_messages += [dict(role="assistant", content=content)]
|
self.cur_messages += [dict(role="assistant", content=content)]
|
||||||
@@ -1228,12 +1385,8 @@ class Coder:
|
|||||||
self.update_cur_messages()
|
self.update_cur_messages()
|
||||||
return
|
return
|
||||||
|
|
||||||
add_rel_files_message = self.check_for_file_mentions(content)
|
def reply_completed(self):
|
||||||
if add_rel_files_message:
|
pass
|
||||||
if self.reflected_message:
|
|
||||||
self.reflected_message += "\n\n" + add_rel_files_message
|
|
||||||
else:
|
|
||||||
self.reflected_message = add_rel_files_message
|
|
||||||
|
|
||||||
def show_exhausted_error(self):
|
def show_exhausted_error(self):
|
||||||
output_tokens = 0
|
output_tokens = 0
|
||||||
@@ -1274,9 +1427,7 @@ class Coder:
|
|||||||
res.append("- Ask for smaller changes in each request.")
|
res.append("- Ask for smaller changes in each request.")
|
||||||
res.append("- Break your code into smaller source files.")
|
res.append("- Break your code into smaller source files.")
|
||||||
if "diff" not in self.main_model.edit_format:
|
if "diff" not in self.main_model.edit_format:
|
||||||
res.append(
|
res.append("- Use a stronger model that can return diffs.")
|
||||||
"- Use a stronger model like gpt-4o, sonnet or opus that can return diffs."
|
|
||||||
)
|
|
||||||
|
|
||||||
if input_tokens >= max_input_tokens or total_tokens >= max_input_tokens:
|
if input_tokens >= max_input_tokens or total_tokens >= max_input_tokens:
|
||||||
res.append("")
|
res.append("")
|
||||||
@@ -1286,15 +1437,15 @@ class Coder:
|
|||||||
res.append("- Use /clear to clear the chat history.")
|
res.append("- Use /clear to clear the chat history.")
|
||||||
res.append("- Break your code into smaller source files.")
|
res.append("- Break your code into smaller source files.")
|
||||||
|
|
||||||
res.append("")
|
|
||||||
res.append(f"For more info: {urls.token_limits}")
|
|
||||||
|
|
||||||
res = "".join([line + "\n" for line in res])
|
res = "".join([line + "\n" for line in res])
|
||||||
self.io.tool_error(res)
|
self.io.tool_error(res)
|
||||||
|
self.io.offer_url(urls.token_limits)
|
||||||
|
|
||||||
def lint_edited(self, fnames):
|
def lint_edited(self, fnames):
|
||||||
res = ""
|
res = ""
|
||||||
for fname in fnames:
|
for fname in fnames:
|
||||||
|
if not fname:
|
||||||
|
continue
|
||||||
errors = self.linter.lint(self.abs_root_path(fname))
|
errors = self.linter.lint(self.abs_root_path(fname))
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
@@ -1323,7 +1474,7 @@ class Coder:
|
|||||||
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 = "".join(['"', "'", "`"])
|
||||||
@@ -1331,9 +1482,18 @@ class Coder:
|
|||||||
|
|
||||||
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
|
||||||
|
existing_basenames = {os.path.basename(f) for f in self.get_inchat_relative_files()} | {
|
||||||
|
os.path.basename(self.get_rel_fname(f)) for f in self.abs_read_only_fnames
|
||||||
|
}
|
||||||
|
|
||||||
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:
|
||||||
@@ -1364,7 +1524,7 @@ class Coder:
|
|||||||
added_fnames = []
|
added_fnames = []
|
||||||
group = ConfirmGroup(new_mentions)
|
group = ConfirmGroup(new_mentions)
|
||||||
for rel_fname in sorted(new_mentions):
|
for rel_fname in sorted(new_mentions):
|
||||||
if self.io.confirm_ask(f"Add {rel_fname} to the chat?", group=group):
|
if self.io.confirm_ask(f"Add {rel_fname} to the chat?", group=group, allow_never=True):
|
||||||
self.add_rel_fname(rel_fname)
|
self.add_rel_fname(rel_fname)
|
||||||
added_fnames.append(rel_fname)
|
added_fnames.append(rel_fname)
|
||||||
else:
|
else:
|
||||||
@@ -1395,8 +1555,7 @@ class Coder:
|
|||||||
functions,
|
functions,
|
||||||
self.stream,
|
self.stream,
|
||||||
temp,
|
temp,
|
||||||
extra_headers=model.extra_headers,
|
extra_params=model.extra_params,
|
||||||
max_tokens=model.max_tokens,
|
|
||||||
)
|
)
|
||||||
self.chat_completion_call_hashes.append(hash_object.hexdigest())
|
self.chat_completion_call_hashes.append(hash_object.hexdigest())
|
||||||
|
|
||||||
@@ -1404,6 +1563,16 @@ class Coder:
|
|||||||
yield from self.show_send_output_stream(completion)
|
yield from self.show_send_output_stream(completion)
|
||||||
else:
|
else:
|
||||||
self.show_send_output(completion)
|
self.show_send_output(completion)
|
||||||
|
|
||||||
|
# Calculate costs for successful responses
|
||||||
|
self.calculate_and_show_tokens_and_cost(messages, completion)
|
||||||
|
|
||||||
|
except LiteLLMExceptions().exceptions_tuple() as err:
|
||||||
|
ex_info = LiteLLMExceptions().get_ex_info(err)
|
||||||
|
if ex_info.name == "ContextWindowExceededError":
|
||||||
|
# Still calculate costs for context window errors
|
||||||
|
self.calculate_and_show_tokens_and_cost(messages, completion)
|
||||||
|
raise
|
||||||
except KeyboardInterrupt as kbi:
|
except KeyboardInterrupt as kbi:
|
||||||
self.keyboard_interrupt()
|
self.keyboard_interrupt()
|
||||||
raise kbi
|
raise kbi
|
||||||
@@ -1421,8 +1590,6 @@ class Coder:
|
|||||||
if args:
|
if args:
|
||||||
self.io.ai_output(json.dumps(args, indent=4))
|
self.io.ai_output(json.dumps(args, indent=4))
|
||||||
|
|
||||||
self.calculate_and_show_tokens_and_cost(messages, completion)
|
|
||||||
|
|
||||||
def show_send_output(self, completion):
|
def show_send_output(self, completion):
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(completion)
|
print(completion)
|
||||||
@@ -1459,7 +1626,7 @@ 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)
|
||||||
self.io.assistant_output(show_resp)
|
self.io.assistant_output(show_resp, pretty=self.show_pretty())
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hasattr(completion.choices[0], "finish_reason")
|
hasattr(completion.choices[0], "finish_reason")
|
||||||
@@ -1535,7 +1702,6 @@ class Coder:
|
|||||||
completion.usage, "cache_creation_input_tokens"
|
completion.usage, "cache_creation_input_tokens"
|
||||||
):
|
):
|
||||||
self.message_tokens_sent += prompt_tokens
|
self.message_tokens_sent += prompt_tokens
|
||||||
self.message_tokens_sent += cache_hit_tokens
|
|
||||||
self.message_tokens_sent += cache_write_tokens
|
self.message_tokens_sent += cache_write_tokens
|
||||||
else:
|
else:
|
||||||
self.message_tokens_sent += prompt_tokens
|
self.message_tokens_sent += prompt_tokens
|
||||||
@@ -1617,11 +1783,27 @@ class Coder:
|
|||||||
self.usage_report = tokens_report + sep + cost_report
|
self.usage_report = tokens_report + sep + cost_report
|
||||||
|
|
||||||
def show_usage_report(self):
|
def show_usage_report(self):
|
||||||
if self.usage_report:
|
if not self.usage_report:
|
||||||
self.io.tool_output(self.usage_report)
|
return
|
||||||
self.message_cost = 0.0
|
|
||||||
self.message_tokens_sent = 0
|
self.io.tool_output(self.usage_report)
|
||||||
self.message_tokens_received = 0
|
|
||||||
|
prompt_tokens = self.message_tokens_sent
|
||||||
|
completion_tokens = self.message_tokens_received
|
||||||
|
self.event(
|
||||||
|
"message_send",
|
||||||
|
main_model=self.main_model,
|
||||||
|
edit_format=self.edit_format,
|
||||||
|
prompt_tokens=prompt_tokens,
|
||||||
|
completion_tokens=completion_tokens,
|
||||||
|
total_tokens=prompt_tokens + completion_tokens,
|
||||||
|
cost=self.message_cost,
|
||||||
|
total_cost=self.total_cost,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.message_cost = 0.0
|
||||||
|
self.message_tokens_sent = 0
|
||||||
|
self.message_tokens_received = 0
|
||||||
|
|
||||||
def get_multi_response_content(self, final=False):
|
def get_multi_response_content(self, final=False):
|
||||||
cur = self.multi_response_content or ""
|
cur = self.multi_response_content or ""
|
||||||
@@ -1696,6 +1878,10 @@ class Coder:
|
|||||||
self.check_for_dirty_commit(path)
|
self.check_for_dirty_commit(path)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if self.repo and self.repo.git_ignored_file(path):
|
||||||
|
self.io.tool_warning(f"Skipping edits to {path} that matches gitignore spec.")
|
||||||
|
return
|
||||||
|
|
||||||
if not Path(full_path).exists():
|
if not Path(full_path).exists():
|
||||||
if not self.io.confirm_ask("Create new file?", subject=path):
|
if not self.io.confirm_ask("Create new file?", subject=path):
|
||||||
self.io.tool_output(f"Skipping edits to {path}")
|
self.io.tool_output(f"Skipping edits to {path}")
|
||||||
@@ -1790,8 +1976,10 @@ class Coder:
|
|||||||
edited = set()
|
edited = set()
|
||||||
try:
|
try:
|
||||||
edits = self.get_edits()
|
edits = self.get_edits()
|
||||||
|
edits = self.apply_edits_dry_run(edits)
|
||||||
edits = self.prepare_to_edit(edits)
|
edits = self.prepare_to_edit(edits)
|
||||||
edited = set(edit[0] for edit in edits)
|
edited = set(edit[0] for edit in edits)
|
||||||
|
|
||||||
self.apply_edits(edits)
|
self.apply_edits(edits)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
self.num_malformed_responses += 1
|
self.num_malformed_responses += 1
|
||||||
@@ -1919,6 +2107,9 @@ class Coder:
|
|||||||
def apply_edits(self, edits):
|
def apply_edits(self, edits):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def apply_edits_dry_run(self, edits):
|
||||||
|
return edits
|
||||||
|
|
||||||
def run_shell_commands(self):
|
def run_shell_commands(self):
|
||||||
if not self.suggest_shell_commands:
|
if not self.suggest_shell_commands:
|
||||||
return ""
|
return ""
|
||||||
@@ -1942,7 +2133,11 @@ class Coder:
|
|||||||
)
|
)
|
||||||
prompt = "Run shell command?" if command_count == 1 else "Run shell commands?"
|
prompt = "Run shell command?" if command_count == 1 else "Run shell commands?"
|
||||||
if not self.io.confirm_ask(
|
if not self.io.confirm_ask(
|
||||||
prompt, subject="\n".join(commands), explicit_yes_required=True, group=group
|
prompt,
|
||||||
|
subject="\n".join(commands),
|
||||||
|
explicit_yes_required=True,
|
||||||
|
group=group,
|
||||||
|
allow_never=True,
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1956,13 +2151,14 @@ class Coder:
|
|||||||
self.io.tool_output(f"Running {command}")
|
self.io.tool_output(f"Running {command}")
|
||||||
# Add the command to input history
|
# Add the command to input history
|
||||||
self.io.add_to_input_history(f"/run {command.strip()}")
|
self.io.add_to_input_history(f"/run {command.strip()}")
|
||||||
exit_status, output = run_cmd(command, error_print=self.io.tool_error)
|
exit_status, output = run_cmd(command, error_print=self.io.tool_error, cwd=self.root)
|
||||||
if output:
|
if output:
|
||||||
accumulated_output += f"Output from {command}\n{output}\n"
|
accumulated_output += f"Output from {command}\n{output}\n"
|
||||||
|
|
||||||
if accumulated_output.strip() and not self.io.confirm_ask(
|
if accumulated_output.strip() and self.io.confirm_ask(
|
||||||
"Add command output to the chat?"
|
"Add command output to the chat?", allow_never=True
|
||||||
):
|
):
|
||||||
accumulated_output = ""
|
num_lines = len(accumulated_output.strip().splitlines())
|
||||||
|
line_plural = "line" if num_lines == 1 else "lines"
|
||||||
return accumulated_output
|
self.io.tool_output(f"Added {num_lines} {line_plural} of output to the chat.")
|
||||||
|
return accumulated_output
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ You always COMPLETELY IMPLEMENT the needed code!
|
|||||||
Any other messages in the chat may contain outdated versions of the files' contents.
|
Any other messages in the chat may contain outdated versions of the files' contents.
|
||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
|
|
||||||
|
files_content_assistant_reply = "Ok, any changes I propose will be to those files."
|
||||||
|
|
||||||
files_no_full_files = "I am not sharing any files that you can edit yet."
|
files_no_full_files = "I am not sharing any files that you can edit yet."
|
||||||
|
|
||||||
files_no_full_files_with_repo_map = """Don't try and edit any existing code without asking me to add the files to the chat!
|
files_no_full_files_with_repo_map = """Don't try and edit any existing code without asking me to add the files to the chat!
|
||||||
|
|||||||
@@ -35,29 +35,47 @@ class EditBlockCoder(Coder):
|
|||||||
|
|
||||||
return edits
|
return edits
|
||||||
|
|
||||||
def apply_edits(self, edits):
|
def apply_edits_dry_run(self, edits):
|
||||||
|
return self.apply_edits(edits, dry_run=True)
|
||||||
|
|
||||||
|
def apply_edits(self, edits, dry_run=False):
|
||||||
failed = []
|
failed = []
|
||||||
passed = []
|
passed = []
|
||||||
|
updated_edits = []
|
||||||
|
|
||||||
for edit in edits:
|
for edit in edits:
|
||||||
path, original, updated = edit
|
path, original, updated = edit
|
||||||
full_path = self.abs_root_path(path)
|
full_path = self.abs_root_path(path)
|
||||||
content = self.io.read_text(full_path)
|
new_content = None
|
||||||
new_content = do_replace(full_path, content, original, updated, self.fence)
|
|
||||||
if not new_content:
|
if Path(full_path).exists():
|
||||||
|
content = self.io.read_text(full_path)
|
||||||
|
new_content = do_replace(full_path, content, original, updated, self.fence)
|
||||||
|
|
||||||
|
# If the edit failed, and
|
||||||
|
# this is not a "create a new file" with an empty original...
|
||||||
|
# https://github.com/Aider-AI/aider/issues/2258
|
||||||
|
if not new_content and original.strip():
|
||||||
# try patching any of the other files in the chat
|
# try patching any of the other files in the chat
|
||||||
for full_path in self.abs_fnames:
|
for full_path in self.abs_fnames:
|
||||||
content = self.io.read_text(full_path)
|
content = self.io.read_text(full_path)
|
||||||
new_content = do_replace(full_path, content, original, updated, self.fence)
|
new_content = do_replace(full_path, content, original, updated, self.fence)
|
||||||
if new_content:
|
if new_content:
|
||||||
|
path = self.get_rel_fname(full_path)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
updated_edits.append((path, original, updated))
|
||||||
|
|
||||||
if new_content:
|
if new_content:
|
||||||
self.io.write_text(full_path, new_content)
|
if not dry_run:
|
||||||
|
self.io.write_text(full_path, new_content)
|
||||||
passed.append(edit)
|
passed.append(edit)
|
||||||
else:
|
else:
|
||||||
failed.append(edit)
|
failed.append(edit)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
return updated_edits
|
||||||
|
|
||||||
if not failed:
|
if not failed:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -365,9 +383,9 @@ def do_replace(fname, content, before_text, after_text, fence=None):
|
|||||||
return new_content
|
return new_content
|
||||||
|
|
||||||
|
|
||||||
HEAD = r"<{5,9} SEARCH"
|
HEAD = r"^<{5,9} SEARCH\s*$"
|
||||||
DIVIDER = r"={5,9}"
|
DIVIDER = r"^={5,9}\s*$"
|
||||||
UPDATED = r">{5,9} REPLACE"
|
UPDATED = r"^>{5,9} REPLACE\s*$"
|
||||||
|
|
||||||
HEAD_ERR = "<<<<<<< SEARCH"
|
HEAD_ERR = "<<<<<<< SEARCH"
|
||||||
DIVIDER_ERR = "======="
|
DIVIDER_ERR = "======="
|
||||||
@@ -400,7 +418,7 @@ def strip_filename(filename, fence):
|
|||||||
filename = filename.strip("`")
|
filename = filename.strip("`")
|
||||||
filename = filename.strip("*")
|
filename = filename.strip("*")
|
||||||
|
|
||||||
# https://github.com/paul-gauthier/aider/issues/1158
|
# https://github.com/Aider-AI/aider/issues/1158
|
||||||
# filename = filename.replace("\\_", "_")
|
# filename = filename.replace("\\_", "_")
|
||||||
|
|
||||||
return filename
|
return filename
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Respect and use existing conventions, libraries, etc that are already present in
|
|||||||
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.
|
||||||
|
|
||||||
Always reply to the user in the same language they are using.
|
Always reply to the user in {language}.
|
||||||
|
|
||||||
Once you understand the request you MUST:
|
Once you understand the request you MUST:
|
||||||
|
|
||||||
@@ -34,8 +34,10 @@ ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
|||||||
4. *Concisely* suggest any shell commands the user might want to run in ```bash blocks.
|
4. *Concisely* suggest any shell commands the user might want to run in ```bash blocks.
|
||||||
|
|
||||||
Just suggest shell commands this way, not example code.
|
Just suggest shell commands this way, not example code.
|
||||||
Only suggest complete shell commands that area ready to execute, without placeholders.
|
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.
|
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:
|
Use the appropriate shell based on the user's system info:
|
||||||
{platform}
|
{platform}
|
||||||
@@ -159,8 +161,9 @@ Use the *FULL* file path, as shown to you by the user.
|
|||||||
Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
|
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.
|
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 replace *all* matching occurrences.
|
*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
|
||||||
Include enough lines to make the SEARCH blocks uniquely match the lines to change.
|
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.
|
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.
|
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
|
||||||
|
|||||||
8
aider/coders/editor_editblock_coder.py
Normal file
8
aider/coders/editor_editblock_coder.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from .editblock_coder import EditBlockCoder
|
||||||
|
from .editor_editblock_prompts import EditorEditBlockPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class EditorEditBlockCoder(EditBlockCoder):
|
||||||
|
"A coder that uses search/replace blocks, focused purely on editing files."
|
||||||
|
edit_format = "editor-diff"
|
||||||
|
gpt_prompts = EditorEditBlockPrompts()
|
||||||
16
aider/coders/editor_editblock_prompts.py
Normal file
16
aider/coders/editor_editblock_prompts.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
from .editblock_prompts import EditBlockPrompts
|
||||||
|
|
||||||
|
|
||||||
|
class EditorEditBlockPrompts(EditBlockPrompts):
|
||||||
|
main_system = """Act as an expert software developer who edits source code.
|
||||||
|
{lazy_prompt}
|
||||||
|
Describe each change with a *SEARCH/REPLACE block* per the examples below.
|
||||||
|
All changes to files must use this *SEARCH/REPLACE block* format.
|
||||||
|
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||||
|
"""
|
||||||
|
|
||||||
|
shell_cmd_prompt = ""
|
||||||
|
no_shell_cmd_prompt = ""
|
||||||
|
shell_cmd_reminder = ""
|
||||||
8
aider/coders/editor_whole_coder.py
Normal file
8
aider/coders/editor_whole_coder.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from .editor_whole_prompts import EditorWholeFilePrompts
|
||||||
|
from .wholefile_coder import WholeFileCoder
|
||||||
|
|
||||||
|
|
||||||
|
class EditorWholeFileCoder(WholeFileCoder):
|
||||||
|
"A coder that operates on entire files, focused purely on editing files."
|
||||||
|
edit_format = "editor-whole"
|
||||||
|
gpt_prompts = EditorWholeFilePrompts()
|
||||||
10
aider/coders/editor_whole_prompts.py
Normal file
10
aider/coders/editor_whole_prompts.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
from .wholefile_prompts import WholeFilePrompts
|
||||||
|
|
||||||
|
|
||||||
|
class EditorWholeFilePrompts(WholeFilePrompts):
|
||||||
|
main_system = """Act as an expert software developer and make changes to source code.
|
||||||
|
{lazy_prompt}
|
||||||
|
Output a copy of each file that needs changes.
|
||||||
|
"""
|
||||||
@@ -3,7 +3,11 @@
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import git
|
try:
|
||||||
|
import git
|
||||||
|
except ImportError:
|
||||||
|
git = None
|
||||||
|
|
||||||
from diff_match_patch import diff_match_patch
|
from diff_match_patch import diff_match_patch
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Respect and use existing conventions, libraries, etc that are already present in
|
|||||||
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.
|
||||||
|
|
||||||
Always reply to the user in the same language they are using.
|
Always reply to the user in {language}.
|
||||||
|
|
||||||
For each file that needs to be changed, write out the changes similar to a unified diff like `diff -U0` would produce.
|
For each file that needs to be changed, write out the changes similar to a unified diff like `diff -U0` would produce.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ class WholeFileCoder(Coder):
|
|||||||
fname = fname.strip("*") # handle **filename.py**
|
fname = fname.strip("*") # handle **filename.py**
|
||||||
fname = fname.rstrip(":")
|
fname = fname.rstrip(":")
|
||||||
fname = fname.strip("`")
|
fname = fname.strip("`")
|
||||||
|
fname = fname.lstrip("#")
|
||||||
|
fname = fname.strip()
|
||||||
|
|
||||||
# Issue #1232
|
# Issue #1232
|
||||||
if len(fname) > 250:
|
if len(fname) > 250:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class WholeFilePrompts(CoderPrompts):
|
|||||||
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.
|
||||||
|
|
||||||
Always reply to the user in the same language they are using.
|
Always reply to the user in {language}.
|
||||||
|
|
||||||
{lazy_prompt}
|
{lazy_prompt}
|
||||||
Once you understand the request you MUST:
|
Once you understand the request you MUST:
|
||||||
@@ -52,7 +52,7 @@ path/to/filename.js
|
|||||||
{fence[1]}
|
{fence[1]}
|
||||||
|
|
||||||
Every *file listing* MUST use this format:
|
Every *file listing* MUST use this format:
|
||||||
- First line: the filename with any originally provided path
|
- First line: the filename with any originally provided path; no extra markup, punctuation, comments, etc. **JUST** the filename with path.
|
||||||
- Second line: opening {fence[0]}
|
- Second line: opening {fence[0]}
|
||||||
- ... entire content of the file ...
|
- ... entire content of the file ...
|
||||||
- Final line: closing {fence[1]}
|
- Final line: closing {fence[1]}
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
|
import glob
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from os.path import expanduser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pyperclip
|
import pyperclip
|
||||||
from PIL import Image, ImageGrab
|
from PIL import Image, ImageGrab
|
||||||
|
from prompt_toolkit.completion import Completion, PathCompleter
|
||||||
|
from prompt_toolkit.document import Document
|
||||||
|
|
||||||
from aider import models, prompts, voice
|
from aider import models, prompts, voice
|
||||||
|
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.llm import litellm
|
from aider.llm import litellm
|
||||||
@@ -38,10 +43,20 @@ class Commands:
|
|||||||
verify_ssl=self.verify_ssl,
|
verify_ssl=self.verify_ssl,
|
||||||
args=self.args,
|
args=self.args,
|
||||||
parser=self.parser,
|
parser=self.parser,
|
||||||
|
verbose=self.verbose,
|
||||||
|
editor=self.editor,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, io, coder, voice_language=None, verify_ssl=True, args=None, parser=None, verbose=False
|
self,
|
||||||
|
io,
|
||||||
|
coder,
|
||||||
|
voice_language=None,
|
||||||
|
verify_ssl=True,
|
||||||
|
args=None,
|
||||||
|
parser=None,
|
||||||
|
verbose=False,
|
||||||
|
editor=None,
|
||||||
):
|
):
|
||||||
self.io = io
|
self.io = io
|
||||||
self.coder = coder
|
self.coder = coder
|
||||||
@@ -56,6 +71,7 @@ class Commands:
|
|||||||
self.voice_language = voice_language
|
self.voice_language = voice_language
|
||||||
|
|
||||||
self.help = None
|
self.help = None
|
||||||
|
self.editor = editor
|
||||||
|
|
||||||
def cmd_model(self, args):
|
def cmd_model(self, args):
|
||||||
"Switch to a new LLM"
|
"Switch to a new LLM"
|
||||||
@@ -87,6 +103,13 @@ class Commands:
|
|||||||
("help", "Get help about using aider (usage, config, troubleshoot)."),
|
("help", "Get help about using aider (usage, config, troubleshoot)."),
|
||||||
("ask", "Ask questions about your code without making any changes."),
|
("ask", "Ask questions about your code without making any changes."),
|
||||||
("code", "Ask for changes to your code (using the best edit format)."),
|
("code", "Ask for changes to your code (using the best edit format)."),
|
||||||
|
(
|
||||||
|
"architect",
|
||||||
|
(
|
||||||
|
"Work with an architect model to design code changes, and an editor to make"
|
||||||
|
" them."
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -135,7 +158,7 @@ class Commands:
|
|||||||
else:
|
else:
|
||||||
self.io.tool_output("Please provide a partial model name to search for.")
|
self.io.tool_output("Please provide a partial model name to search for.")
|
||||||
|
|
||||||
def cmd_web(self, args):
|
def cmd_web(self, args, return_content=False):
|
||||||
"Scrape a webpage, convert to markdown and send in a message"
|
"Scrape a webpage, convert to markdown and send in a message"
|
||||||
|
|
||||||
url = args.strip()
|
url = args.strip()
|
||||||
@@ -154,15 +177,28 @@ class Commands:
|
|||||||
)
|
)
|
||||||
|
|
||||||
content = self.scraper.scrape(url) or ""
|
content = self.scraper.scrape(url) or ""
|
||||||
content = f"{url}:\n\n" + content
|
content = f"Here is the content of {url}:\n\n" + content
|
||||||
|
if return_content:
|
||||||
|
return content
|
||||||
|
|
||||||
self.io.tool_output("... done.")
|
self.io.tool_output("... added to chat.")
|
||||||
|
|
||||||
return content
|
self.coder.cur_messages += [
|
||||||
|
dict(role="user", content=content),
|
||||||
|
dict(role="assistant", content="Ok."),
|
||||||
|
]
|
||||||
|
|
||||||
def is_command(self, inp):
|
def is_command(self, inp):
|
||||||
return inp[0] in "/!"
|
return inp[0] in "/!"
|
||||||
|
|
||||||
|
def get_raw_completions(self, cmd):
|
||||||
|
assert cmd.startswith("/")
|
||||||
|
cmd = cmd[1:]
|
||||||
|
cmd = cmd.replace("-", "_")
|
||||||
|
|
||||||
|
raw_completer = getattr(self, f"completions_raw_{cmd}", None)
|
||||||
|
return raw_completer
|
||||||
|
|
||||||
def get_completions(self, cmd):
|
def get_completions(self, cmd):
|
||||||
assert cmd.startswith("/")
|
assert cmd.startswith("/")
|
||||||
cmd = cmd[1:]
|
cmd = cmd[1:]
|
||||||
@@ -211,6 +247,7 @@ class Commands:
|
|||||||
|
|
||||||
def run(self, inp):
|
def run(self, inp):
|
||||||
if inp.startswith("!"):
|
if inp.startswith("!"):
|
||||||
|
self.coder.event("command_run")
|
||||||
return self.do_run("run", inp[1:])
|
return self.do_run("run", inp[1:])
|
||||||
|
|
||||||
res = self.matching_commands(inp)
|
res = self.matching_commands(inp)
|
||||||
@@ -218,9 +255,13 @@ class Commands:
|
|||||||
return
|
return
|
||||||
matching_commands, first_word, rest_inp = res
|
matching_commands, first_word, rest_inp = res
|
||||||
if len(matching_commands) == 1:
|
if len(matching_commands) == 1:
|
||||||
return self.do_run(matching_commands[0][1:], rest_inp)
|
command = matching_commands[0][1:]
|
||||||
|
self.coder.event(f"command_{command}")
|
||||||
|
return self.do_run(command, rest_inp)
|
||||||
elif first_word in matching_commands:
|
elif first_word in matching_commands:
|
||||||
return self.do_run(first_word[1:], rest_inp)
|
command = first_word[1:]
|
||||||
|
self.coder.event(f"command_{command}")
|
||||||
|
return self.do_run(command, rest_inp)
|
||||||
elif len(matching_commands) > 1:
|
elif len(matching_commands) > 1:
|
||||||
self.io.tool_error(f"Ambiguous command: {', '.join(matching_commands)}")
|
self.io.tool_error(f"Ambiguous command: {', '.join(matching_commands)}")
|
||||||
else:
|
else:
|
||||||
@@ -556,6 +597,10 @@ class Commands:
|
|||||||
|
|
||||||
self.io.tool_output(f"Diff since {commit_before_message[:7]}...")
|
self.io.tool_output(f"Diff since {commit_before_message[:7]}...")
|
||||||
|
|
||||||
|
if self.coder.pretty:
|
||||||
|
run_cmd(f"git diff {commit_before_message}")
|
||||||
|
return
|
||||||
|
|
||||||
diff = self.coder.repo.diff_commits(
|
diff = self.coder.repo.diff_commits(
|
||||||
self.coder.pretty,
|
self.coder.pretty,
|
||||||
commit_before_message,
|
commit_before_message,
|
||||||
@@ -569,8 +614,62 @@ class Commands:
|
|||||||
fname = f'"{fname}"'
|
fname = f'"{fname}"'
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
def completions_read_only(self):
|
def completions_raw_read_only(self, document, complete_event):
|
||||||
return self.completions_add()
|
# Get the text before the cursor
|
||||||
|
text = document.text_before_cursor
|
||||||
|
|
||||||
|
# Skip the first word and the space after it
|
||||||
|
after_command = text.split()[-1]
|
||||||
|
|
||||||
|
# Create a new Document object with the text after the command
|
||||||
|
new_document = Document(after_command, cursor_position=len(after_command))
|
||||||
|
|
||||||
|
def get_paths():
|
||||||
|
return [self.coder.root] if self.coder.root else None
|
||||||
|
|
||||||
|
path_completer = PathCompleter(
|
||||||
|
get_paths=get_paths,
|
||||||
|
only_directories=False,
|
||||||
|
expanduser=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Adjust the start_position to replace all of 'after_command'
|
||||||
|
adjusted_start_position = -len(after_command)
|
||||||
|
|
||||||
|
# Collect all completions
|
||||||
|
all_completions = []
|
||||||
|
|
||||||
|
# Iterate over the completions and modify them
|
||||||
|
for completion in path_completer.get_completions(new_document, complete_event):
|
||||||
|
quoted_text = self.quote_fname(after_command + completion.text)
|
||||||
|
all_completions.append(
|
||||||
|
Completion(
|
||||||
|
text=quoted_text,
|
||||||
|
start_position=adjusted_start_position,
|
||||||
|
display=completion.display,
|
||||||
|
style=completion.style,
|
||||||
|
selected_style=completion.selected_style,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add completions from the 'add' command
|
||||||
|
add_completions = self.completions_add()
|
||||||
|
for completion in add_completions:
|
||||||
|
if after_command in completion:
|
||||||
|
all_completions.append(
|
||||||
|
Completion(
|
||||||
|
text=completion,
|
||||||
|
start_position=adjusted_start_position,
|
||||||
|
display=completion,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sort all completions based on their text
|
||||||
|
sorted_completions = sorted(all_completions, key=lambda c: c.text)
|
||||||
|
|
||||||
|
# Yield the sorted completions
|
||||||
|
for completion in sorted_completions:
|
||||||
|
yield completion
|
||||||
|
|
||||||
def completions_add(self):
|
def completions_add(self):
|
||||||
files = set(self.coder.get_all_relative_files())
|
files = set(self.coder.get_all_relative_files())
|
||||||
@@ -588,7 +687,7 @@ class Commands:
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
raw_matched_files = list(Path(self.coder.root).glob(pattern))
|
raw_matched_files = list(Path(self.coder.root).glob(pattern))
|
||||||
except IndexError:
|
except (IndexError, AttributeError):
|
||||||
raw_matched_files = []
|
raw_matched_files = []
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
self.io.tool_error(f"Error matching {pattern}: {err}")
|
self.io.tool_error(f"Error matching {pattern}: {err}")
|
||||||
@@ -658,7 +757,7 @@ class Commands:
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.io.tool_error(f"Error creating file {fname}: {e}")
|
self.io.tool_error(f"Error creating file {fname}: {e}")
|
||||||
|
|
||||||
for matched_file in all_matched_files:
|
for matched_file in sorted(all_matched_files):
|
||||||
abs_file_path = self.coder.abs_root_path(matched_file)
|
abs_file_path = self.coder.abs_root_path(matched_file)
|
||||||
|
|
||||||
if not abs_file_path.startswith(self.coder.root) and not is_image_file(matched_file):
|
if not abs_file_path.startswith(self.coder.root) and not is_image_file(matched_file):
|
||||||
@@ -667,8 +766,13 @@ class Commands:
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if self.coder.repo and self.coder.repo.git_ignored_file(matched_file):
|
||||||
|
self.io.tool_error(f"Can't add {matched_file} which is in gitignore")
|
||||||
|
continue
|
||||||
|
|
||||||
if abs_file_path in self.coder.abs_fnames:
|
if abs_file_path in self.coder.abs_fnames:
|
||||||
self.io.tool_warning(f"{matched_file} is already in the chat")
|
self.io.tool_error(f"{matched_file} is already in the chat as an editable file")
|
||||||
|
continue
|
||||||
elif abs_file_path in self.coder.abs_read_only_fnames:
|
elif abs_file_path in self.coder.abs_read_only_fnames:
|
||||||
if self.coder.repo and self.coder.repo.path_in_repo(matched_file):
|
if self.coder.repo and self.coder.repo.path_in_repo(matched_file):
|
||||||
self.coder.abs_read_only_fnames.remove(abs_file_path)
|
self.coder.abs_read_only_fnames.remove(abs_file_path)
|
||||||
@@ -681,7 +785,9 @@ class Commands:
|
|||||||
f"Cannot add {matched_file} as it's not part of the repository"
|
f"Cannot add {matched_file} as it's not part of the repository"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if is_image_file(matched_file) and not self.coder.main_model.accepts_images:
|
if is_image_file(matched_file) and not self.coder.main_model.info.get(
|
||||||
|
"supports_vision"
|
||||||
|
):
|
||||||
self.io.tool_error(
|
self.io.tool_error(
|
||||||
f"Cannot add image file {matched_file} as the"
|
f"Cannot add image file {matched_file} as the"
|
||||||
f" {self.coder.main_model.name} does not support images."
|
f" {self.coder.main_model.name} does not support images."
|
||||||
@@ -692,7 +798,8 @@ class Commands:
|
|||||||
self.io.tool_error(f"Unable to read {matched_file}")
|
self.io.tool_error(f"Unable to read {matched_file}")
|
||||||
else:
|
else:
|
||||||
self.coder.abs_fnames.add(abs_file_path)
|
self.coder.abs_fnames.add(abs_file_path)
|
||||||
self.io.tool_output(f"Added {matched_file} to the chat")
|
fname = self.coder.get_rel_fname(abs_file_path)
|
||||||
|
self.io.tool_output(f"Added {fname} to the chat")
|
||||||
self.coder.check_added_files()
|
self.coder.check_added_files()
|
||||||
|
|
||||||
def completions_drop(self):
|
def completions_drop(self):
|
||||||
@@ -715,15 +822,33 @@ class Commands:
|
|||||||
# Expand tilde in the path
|
# Expand tilde in the path
|
||||||
expanded_word = os.path.expanduser(word)
|
expanded_word = os.path.expanduser(word)
|
||||||
|
|
||||||
# Handle read-only files separately, without glob_filtered_to_repo
|
# Handle read-only files with substring matching and samefile check
|
||||||
read_only_matched = [f for f in self.coder.abs_read_only_fnames if expanded_word in f]
|
read_only_matched = []
|
||||||
|
for f in self.coder.abs_read_only_fnames:
|
||||||
|
if expanded_word in f:
|
||||||
|
read_only_matched.append(f)
|
||||||
|
continue
|
||||||
|
|
||||||
if read_only_matched:
|
# Try samefile comparison for relative paths
|
||||||
for matched_file in read_only_matched:
|
try:
|
||||||
self.coder.abs_read_only_fnames.remove(matched_file)
|
abs_word = os.path.abspath(expanded_word)
|
||||||
self.io.tool_output(f"Removed read-only file {matched_file} from the chat")
|
if os.path.samefile(abs_word, f):
|
||||||
|
read_only_matched.append(f)
|
||||||
|
except (FileNotFoundError, OSError):
|
||||||
|
continue
|
||||||
|
|
||||||
matched_files = self.glob_filtered_to_repo(expanded_word)
|
for matched_file in read_only_matched:
|
||||||
|
self.coder.abs_read_only_fnames.remove(matched_file)
|
||||||
|
self.io.tool_output(f"Removed read-only file {matched_file} from the chat")
|
||||||
|
|
||||||
|
# For editable files, use glob if word contains glob chars, otherwise use substring
|
||||||
|
if any(c in expanded_word for c in "*?[]"):
|
||||||
|
matched_files = self.glob_filtered_to_repo(expanded_word)
|
||||||
|
else:
|
||||||
|
# Use substring matching like we do for read-only files
|
||||||
|
matched_files = [
|
||||||
|
self.coder.get_rel_fname(f) for f in self.coder.abs_fnames if expanded_word in f
|
||||||
|
]
|
||||||
|
|
||||||
if not matched_files:
|
if not matched_files:
|
||||||
matched_files.append(expanded_word)
|
matched_files.append(expanded_word)
|
||||||
@@ -735,7 +860,7 @@ class Commands:
|
|||||||
self.io.tool_output(f"Removed {matched_file} from the chat")
|
self.io.tool_output(f"Removed {matched_file} from the chat")
|
||||||
|
|
||||||
def cmd_git(self, args):
|
def cmd_git(self, args):
|
||||||
"Run a git command"
|
"Run a git command (output excluded from chat)"
|
||||||
combined_output = None
|
combined_output = None
|
||||||
try:
|
try:
|
||||||
args = "git " + args
|
args = "git " + args
|
||||||
@@ -783,9 +908,8 @@ class Commands:
|
|||||||
def cmd_run(self, args, add_on_nonzero_exit=False):
|
def cmd_run(self, args, add_on_nonzero_exit=False):
|
||||||
"Run a shell command and optionally add the output to the chat (alias: !)"
|
"Run a shell command and optionally add the output to the chat (alias: !)"
|
||||||
exit_status, combined_output = run_cmd(
|
exit_status, combined_output = run_cmd(
|
||||||
args, verbose=self.verbose, error_print=self.io.tool_error
|
args, verbose=self.verbose, error_print=self.io.tool_error, cwd=self.coder.root
|
||||||
)
|
)
|
||||||
instructions = None
|
|
||||||
|
|
||||||
if combined_output is None:
|
if combined_output is None:
|
||||||
return
|
return
|
||||||
@@ -793,44 +917,34 @@ class Commands:
|
|||||||
if add_on_nonzero_exit:
|
if add_on_nonzero_exit:
|
||||||
add = exit_status != 0
|
add = exit_status != 0
|
||||||
else:
|
else:
|
||||||
self.io.tool_output()
|
add = self.io.confirm_ask("Add command output to the chat?")
|
||||||
response = self.io.prompt_ask(
|
|
||||||
"Add the output to the chat?\n(Y)es/(n)o/message with instructions:",
|
|
||||||
).strip()
|
|
||||||
self.io.tool_output()
|
|
||||||
|
|
||||||
if response.lower() in ["yes", "y"]:
|
|
||||||
add = True
|
|
||||||
elif response.lower() in ["no", "n"]:
|
|
||||||
add = False
|
|
||||||
else:
|
|
||||||
add = True
|
|
||||||
instructions = response
|
|
||||||
if response.strip():
|
|
||||||
self.io.user_input(response, log_only=True)
|
|
||||||
self.io.add_to_input_history(response)
|
|
||||||
|
|
||||||
if add:
|
if add:
|
||||||
for line in combined_output.splitlines():
|
num_lines = len(combined_output.strip().splitlines())
|
||||||
self.io.tool_output(line, log_only=True)
|
line_plural = "line" if num_lines == 1 else "lines"
|
||||||
|
self.io.tool_output(f"Added {num_lines} {line_plural} of output to the chat.")
|
||||||
|
|
||||||
msg = prompts.run_output.format(
|
msg = prompts.run_output.format(
|
||||||
command=args,
|
command=args,
|
||||||
output=combined_output,
|
output=combined_output,
|
||||||
)
|
)
|
||||||
|
|
||||||
if instructions:
|
self.coder.cur_messages += [
|
||||||
msg = instructions + "\n\n" + msg
|
dict(role="user", content=msg),
|
||||||
|
dict(role="assistant", content="Ok."),
|
||||||
|
]
|
||||||
|
|
||||||
return msg
|
if add and exit_status != 0:
|
||||||
|
self.io.placeholder = "Fix that"
|
||||||
|
|
||||||
def cmd_exit(self, args):
|
def cmd_exit(self, args):
|
||||||
"Exit the application"
|
"Exit the application"
|
||||||
|
self.coder.event("exit", reason="/exit")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def cmd_quit(self, args):
|
def cmd_quit(self, args):
|
||||||
"Exit the application"
|
"Exit the application"
|
||||||
sys.exit()
|
self.cmd_exit(args)
|
||||||
|
|
||||||
def cmd_ls(self, args):
|
def cmd_ls(self, args):
|
||||||
"List all known files and indicate which are included in the chat session"
|
"List all known files and indicate which are included in the chat session"
|
||||||
@@ -894,6 +1008,7 @@ class Commands:
|
|||||||
self.basic_help()
|
self.basic_help()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.coder.event("interactive help")
|
||||||
from aider.coders import Coder
|
from aider.coders import Coder
|
||||||
|
|
||||||
if not self.help:
|
if not self.help:
|
||||||
@@ -945,6 +1060,10 @@ class Commands:
|
|||||||
"Ask for changes to your code"
|
"Ask for changes to your code"
|
||||||
return self._generic_chat_command(args, self.coder.main_model.edit_format)
|
return self._generic_chat_command(args, self.coder.main_model.edit_format)
|
||||||
|
|
||||||
|
def cmd_architect(self, args):
|
||||||
|
"Enter architect mode to discuss high-level design and architecture"
|
||||||
|
return self._generic_chat_command(args, "architect")
|
||||||
|
|
||||||
def _generic_chat_command(self, args, edit_format):
|
def _generic_chat_command(self, args, edit_format):
|
||||||
if not args.strip():
|
if not args.strip():
|
||||||
self.io.tool_error(f"Please provide a question or topic for the {edit_format} chat.")
|
self.io.tool_error(f"Please provide a question or topic for the {edit_format} chat.")
|
||||||
@@ -997,46 +1116,27 @@ class Commands:
|
|||||||
self.io.tool_error("To use /voice you must provide an OpenAI API key.")
|
self.io.tool_error("To use /voice you must provide an OpenAI API key.")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.voice = voice.Voice()
|
self.voice = voice.Voice(
|
||||||
|
audio_format=self.args.voice_format, device_name=self.args.voice_input_device
|
||||||
|
)
|
||||||
except voice.SoundDeviceError:
|
except voice.SoundDeviceError:
|
||||||
self.io.tool_error(
|
self.io.tool_error(
|
||||||
"Unable to import `sounddevice` and/or `soundfile`, is portaudio installed?"
|
"Unable to import `sounddevice` and/or `soundfile`, is portaudio installed?"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
history_iter = self.io.get_input_history()
|
|
||||||
|
|
||||||
history = []
|
|
||||||
size = 0
|
|
||||||
for line in history_iter:
|
|
||||||
if line.startswith("/"):
|
|
||||||
continue
|
|
||||||
if line in history:
|
|
||||||
continue
|
|
||||||
if size + len(line) > 1024:
|
|
||||||
break
|
|
||||||
size += len(line)
|
|
||||||
history.append(line)
|
|
||||||
|
|
||||||
history.reverse()
|
|
||||||
history = "\n".join(history)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
text = self.voice.record_and_transcribe(history, language=self.voice_language)
|
text = self.voice.record_and_transcribe(None, language=self.voice_language)
|
||||||
except litellm.OpenAIError as err:
|
except litellm.OpenAIError as err:
|
||||||
self.io.tool_error(f"Unable to use OpenAI whisper model: {err}")
|
self.io.tool_error(f"Unable to use OpenAI whisper model: {err}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if text:
|
if text:
|
||||||
self.io.add_to_input_history(text)
|
self.io.placeholder = text
|
||||||
self.io.print()
|
|
||||||
self.io.user_input(text, log_only=False)
|
|
||||||
self.io.print()
|
|
||||||
|
|
||||||
return text
|
def cmd_paste(self, args):
|
||||||
|
"""Paste image/text from the clipboard into the chat.\
|
||||||
def cmd_clipboard(self, args):
|
Optionally provide a name for the image."""
|
||||||
"Add image/text from the clipboard to the chat (optionally provide a name for the image)"
|
|
||||||
try:
|
try:
|
||||||
# Check for image first
|
# Check for image first
|
||||||
image = ImageGrab.grabclipboard()
|
image = ImageGrab.grabclipboard()
|
||||||
@@ -1085,33 +1185,61 @@ class Commands:
|
|||||||
self.io.tool_error(f"Error processing clipboard content: {e}")
|
self.io.tool_error(f"Error processing clipboard content: {e}")
|
||||||
|
|
||||||
def cmd_read_only(self, args):
|
def cmd_read_only(self, args):
|
||||||
"Add files to the chat that are for reference, not to be edited"
|
"Add files to the chat that are for reference only, or turn added files to read-only"
|
||||||
if not args.strip():
|
if not args.strip():
|
||||||
self.io.tool_error("Please provide filenames or directories to read.")
|
# Convert all files in chat to read-only
|
||||||
|
for fname in list(self.coder.abs_fnames):
|
||||||
|
self.coder.abs_fnames.remove(fname)
|
||||||
|
self.coder.abs_read_only_fnames.add(fname)
|
||||||
|
rel_fname = self.coder.get_rel_fname(fname)
|
||||||
|
self.io.tool_output(f"Converted {rel_fname} to read-only")
|
||||||
return
|
return
|
||||||
|
|
||||||
filenames = parse_quoted_filenames(args)
|
filenames = parse_quoted_filenames(args)
|
||||||
for word in filenames:
|
all_paths = []
|
||||||
# Expand the home directory if the path starts with "~"
|
|
||||||
expanded_path = os.path.expanduser(word)
|
|
||||||
abs_path = self.coder.abs_root_path(expanded_path)
|
|
||||||
|
|
||||||
if not os.path.exists(abs_path):
|
# First collect all expanded paths
|
||||||
self.io.tool_error(f"Path not found: {abs_path}")
|
for pattern in filenames:
|
||||||
continue
|
expanded_pattern = expanduser(pattern)
|
||||||
|
if os.path.isabs(expanded_pattern):
|
||||||
|
# For absolute paths, glob it
|
||||||
|
matches = list(glob.glob(expanded_pattern))
|
||||||
|
else:
|
||||||
|
# For relative paths and globs, use glob from the root directory
|
||||||
|
matches = list(Path(self.coder.root).glob(expanded_pattern))
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
self.io.tool_error(f"No matches found for: {pattern}")
|
||||||
|
else:
|
||||||
|
all_paths.extend(matches)
|
||||||
|
|
||||||
|
# Then process them in sorted order
|
||||||
|
for path in sorted(all_paths):
|
||||||
|
abs_path = self.coder.abs_root_path(path)
|
||||||
if os.path.isfile(abs_path):
|
if os.path.isfile(abs_path):
|
||||||
self._add_read_only_file(abs_path, word)
|
self._add_read_only_file(abs_path, path)
|
||||||
elif os.path.isdir(abs_path):
|
elif os.path.isdir(abs_path):
|
||||||
self._add_read_only_directory(abs_path, word)
|
self._add_read_only_directory(abs_path, path)
|
||||||
else:
|
else:
|
||||||
self.io.tool_error(f"Not a file or directory: {abs_path}")
|
self.io.tool_error(f"Not a file or directory: {abs_path}")
|
||||||
|
|
||||||
def _add_read_only_file(self, abs_path, original_name):
|
def _add_read_only_file(self, abs_path, original_name):
|
||||||
if abs_path in self.coder.abs_fnames:
|
if is_image_file(original_name) and not self.coder.main_model.info.get("supports_vision"):
|
||||||
self.io.tool_error(f"{original_name} is already in the chat as an editable file")
|
self.io.tool_error(
|
||||||
elif abs_path in self.coder.abs_read_only_fnames:
|
f"Cannot add image file {original_name} as the"
|
||||||
|
f" {self.coder.main_model.name} does not support images."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if abs_path in self.coder.abs_read_only_fnames:
|
||||||
self.io.tool_error(f"{original_name} is already in the chat as a read-only file")
|
self.io.tool_error(f"{original_name} is already in the chat as a read-only file")
|
||||||
|
return
|
||||||
|
elif abs_path in self.coder.abs_fnames:
|
||||||
|
self.coder.abs_fnames.remove(abs_path)
|
||||||
|
self.coder.abs_read_only_fnames.add(abs_path)
|
||||||
|
self.io.tool_output(
|
||||||
|
f"Moved {original_name} from editable to read-only files in the chat"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.coder.abs_read_only_fnames.add(abs_path)
|
self.coder.abs_read_only_fnames.add(abs_path)
|
||||||
self.io.tool_output(f"Added {original_name} to read-only files.")
|
self.io.tool_output(f"Added {original_name} to read-only files.")
|
||||||
@@ -1152,7 +1280,97 @@ class Commands:
|
|||||||
def cmd_settings(self, args):
|
def cmd_settings(self, args):
|
||||||
"Print out the current settings"
|
"Print out the current settings"
|
||||||
settings = format_settings(self.parser, self.args)
|
settings = format_settings(self.parser, self.args)
|
||||||
self.io.tool_output(settings)
|
announcements = "\n".join(self.coder.get_announcements())
|
||||||
|
output = f"{announcements}\n{settings}"
|
||||||
|
self.io.tool_output(output)
|
||||||
|
|
||||||
|
def completions_raw_load(self, document, complete_event):
|
||||||
|
return self.completions_raw_read_only(document, complete_event)
|
||||||
|
|
||||||
|
def cmd_load(self, args):
|
||||||
|
"Load and execute commands from a file"
|
||||||
|
if not args.strip():
|
||||||
|
self.io.tool_error("Please provide a filename containing commands to load.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(args.strip(), "r", encoding=self.io.encoding, errors="replace") as f:
|
||||||
|
commands = f.readlines()
|
||||||
|
except FileNotFoundError:
|
||||||
|
self.io.tool_error(f"File not found: {args}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
self.io.tool_error(f"Error reading file: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
for cmd in commands:
|
||||||
|
cmd = cmd.strip()
|
||||||
|
if not cmd or cmd.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.io.tool_output(f"\nExecuting: {cmd}")
|
||||||
|
self.run(cmd)
|
||||||
|
|
||||||
|
def completions_raw_save(self, document, complete_event):
|
||||||
|
return self.completions_raw_read_only(document, complete_event)
|
||||||
|
|
||||||
|
def cmd_save(self, args):
|
||||||
|
"Save commands to a file that can reconstruct the current chat session's files"
|
||||||
|
if not args.strip():
|
||||||
|
self.io.tool_error("Please provide a filename to save the commands to.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(args.strip(), "w", encoding=self.io.encoding) as f:
|
||||||
|
f.write("/drop\n")
|
||||||
|
# Write commands to add editable files
|
||||||
|
for fname in sorted(self.coder.abs_fnames):
|
||||||
|
rel_fname = self.coder.get_rel_fname(fname)
|
||||||
|
f.write(f"/add {rel_fname}\n")
|
||||||
|
|
||||||
|
# Write commands to add read-only files
|
||||||
|
for fname in sorted(self.coder.abs_read_only_fnames):
|
||||||
|
# Use absolute path for files outside repo root, relative path for files inside
|
||||||
|
if Path(fname).is_relative_to(self.coder.root):
|
||||||
|
rel_fname = self.coder.get_rel_fname(fname)
|
||||||
|
f.write(f"/read-only {rel_fname}\n")
|
||||||
|
else:
|
||||||
|
f.write(f"/read-only {fname}\n")
|
||||||
|
|
||||||
|
self.io.tool_output(f"Saved commands to {args.strip()}")
|
||||||
|
except Exception as e:
|
||||||
|
self.io.tool_error(f"Error saving commands to file: {e}")
|
||||||
|
|
||||||
|
def cmd_multiline_mode(self, args):
|
||||||
|
"Toggle multiline mode (swaps behavior of Enter and Meta+Enter)"
|
||||||
|
self.io.toggle_multiline_mode()
|
||||||
|
|
||||||
|
def cmd_copy(self, args):
|
||||||
|
"Copy the last assistant message to the clipboard"
|
||||||
|
all_messages = self.coder.done_messages + self.coder.cur_messages
|
||||||
|
assistant_messages = [msg for msg in reversed(all_messages) if msg["role"] == "assistant"]
|
||||||
|
|
||||||
|
if not assistant_messages:
|
||||||
|
self.io.tool_error("No assistant messages found to copy.")
|
||||||
|
return
|
||||||
|
|
||||||
|
last_assistant_message = assistant_messages[0]["content"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
pyperclip.copy(last_assistant_message)
|
||||||
|
preview = (
|
||||||
|
last_assistant_message[:50] + "..."
|
||||||
|
if len(last_assistant_message) > 50
|
||||||
|
else last_assistant_message
|
||||||
|
)
|
||||||
|
self.io.tool_output(f"Copied last assistant message to clipboard. Preview: {preview}")
|
||||||
|
except pyperclip.PyperclipException as e:
|
||||||
|
self.io.tool_error(f"Failed to copy to clipboard: {str(e)}")
|
||||||
|
self.io.tool_output(
|
||||||
|
"You may need to install xclip or xsel on Linux, or pbcopy on macOS."
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.io.tool_error(f"An unexpected error occurred while copying to clipboard: {str(e)}")
|
||||||
|
|
||||||
def cmd_report(self, args):
|
def cmd_report(self, args):
|
||||||
"Report a problem by opening a GitHub Issue"
|
"Report a problem by opening a GitHub Issue"
|
||||||
@@ -1168,6 +1386,57 @@ class Commands:
|
|||||||
|
|
||||||
report_github_issue(issue_text, title=title, confirm=False)
|
report_github_issue(issue_text, title=title, confirm=False)
|
||||||
|
|
||||||
|
def cmd_editor(self, initial_content=""):
|
||||||
|
"Open an editor to write a prompt"
|
||||||
|
|
||||||
|
user_input = pipe_editor(initial_content, suffix="md", editor=self.editor)
|
||||||
|
if user_input.strip():
|
||||||
|
self.io.set_placeholder(user_input.rstrip())
|
||||||
|
|
||||||
|
def cmd_copy_context(self, args=None):
|
||||||
|
"""Copy the current chat context as markdown, suitable to paste into a web UI"""
|
||||||
|
|
||||||
|
chunks = self.coder.format_chat_chunks()
|
||||||
|
|
||||||
|
markdown = ""
|
||||||
|
|
||||||
|
# Only include specified chunks in order
|
||||||
|
for messages in [chunks.repo, chunks.readonly_files, chunks.chat_files]:
|
||||||
|
for msg in messages:
|
||||||
|
# Only include user messages
|
||||||
|
if msg["role"] != "user":
|
||||||
|
continue
|
||||||
|
|
||||||
|
content = msg["content"]
|
||||||
|
|
||||||
|
# Handle image/multipart content
|
||||||
|
if isinstance(content, list):
|
||||||
|
for part in content:
|
||||||
|
if part.get("type") == "text":
|
||||||
|
markdown += part["text"] + "\n\n"
|
||||||
|
else:
|
||||||
|
markdown += content + "\n\n"
|
||||||
|
|
||||||
|
args = args or ""
|
||||||
|
markdown += f"""
|
||||||
|
Just tell me how to edit the files to make the changes.
|
||||||
|
Don't give me back entire files.
|
||||||
|
Just show me the edits I need to make.
|
||||||
|
|
||||||
|
{args}
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
pyperclip.copy(markdown)
|
||||||
|
self.io.tool_output("Copied code context to clipboard.")
|
||||||
|
except pyperclip.PyperclipException as e:
|
||||||
|
self.io.tool_error(f"Failed to copy to clipboard: {str(e)}")
|
||||||
|
self.io.tool_output(
|
||||||
|
"You may need to install xclip or xsel on Linux, or pbcopy on macOS."
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.io.tool_error(f"An unexpected error occurred while copying to clipboard: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
def expand_subdir(file_path):
|
def expand_subdir(file_path):
|
||||||
if file_path.is_file():
|
if file_path.is_file():
|
||||||
|
|||||||
72
aider/copypaste.py
Normal file
72
aider/copypaste.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pyperclip
|
||||||
|
|
||||||
|
|
||||||
|
class ClipboardWatcher:
|
||||||
|
"""Watches clipboard for changes and updates IO placeholder"""
|
||||||
|
|
||||||
|
def __init__(self, io, verbose=False):
|
||||||
|
self.io = io
|
||||||
|
self.verbose = verbose
|
||||||
|
self.stop_event = None
|
||||||
|
self.watcher_thread = None
|
||||||
|
self.last_clipboard = None
|
||||||
|
self.io.clipboard_watcher = self
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start watching clipboard for changes"""
|
||||||
|
self.stop_event = threading.Event()
|
||||||
|
self.last_clipboard = pyperclip.paste()
|
||||||
|
|
||||||
|
def watch_clipboard():
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
try:
|
||||||
|
current = pyperclip.paste()
|
||||||
|
if current != self.last_clipboard:
|
||||||
|
self.last_clipboard = current
|
||||||
|
self.io.interrupt_input()
|
||||||
|
self.io.placeholder = current
|
||||||
|
if len(current.splitlines()) > 1:
|
||||||
|
self.io.placeholder = "\n" + self.io.placeholder + "\n"
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
except Exception as e:
|
||||||
|
if self.verbose:
|
||||||
|
from aider.dump import dump
|
||||||
|
|
||||||
|
dump(f"Clipboard watcher error: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.watcher_thread = threading.Thread(target=watch_clipboard, daemon=True)
|
||||||
|
self.watcher_thread.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop watching clipboard for changes"""
|
||||||
|
if self.stop_event:
|
||||||
|
self.stop_event.set()
|
||||||
|
if self.watcher_thread:
|
||||||
|
self.watcher_thread.join()
|
||||||
|
self.watcher_thread = None
|
||||||
|
self.stop_event = None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Example usage of the clipboard watcher"""
|
||||||
|
from aider.io import InputOutput
|
||||||
|
|
||||||
|
io = InputOutput()
|
||||||
|
watcher = ClipboardWatcher(io, verbose=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
watcher.start()
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nStopped watching clipboard")
|
||||||
|
watcher.stop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -50,7 +50,6 @@ def diff_partial_update(lines_orig, lines_updated, final=False, fname=None):
|
|||||||
# dump(lines_orig)
|
# dump(lines_orig)
|
||||||
# dump(lines_updated)
|
# dump(lines_updated)
|
||||||
|
|
||||||
assert_newlines(lines_orig)
|
|
||||||
assert_newlines(lines_orig)
|
assert_newlines(lines_orig)
|
||||||
|
|
||||||
num_orig_lines = len(lines_orig)
|
num_orig_lines = len(lines_orig)
|
||||||
|
|||||||
146
aider/editor.py
Normal file
146
aider/editor.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
"""
|
||||||
|
Editor module for handling system text editor interactions.
|
||||||
|
|
||||||
|
This module provides functionality to:
|
||||||
|
- Discover and launch the system's configured text editor
|
||||||
|
- Create and manage temporary files for editing
|
||||||
|
- Handle editor preferences from environment variables
|
||||||
|
- Support cross-platform editor operations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
DEFAULT_EDITOR_NIX = "vi"
|
||||||
|
DEFAULT_EDITOR_OS_X = "vim"
|
||||||
|
DEFAULT_EDITOR_WINDOWS = "notepad"
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def print_status_message(success, message, style=None):
|
||||||
|
"""
|
||||||
|
Print a status message with appropriate styling.
|
||||||
|
|
||||||
|
:param success: Whether the operation was successful
|
||||||
|
:param message: The message to display
|
||||||
|
:param style: Optional style override. If None, uses green for success and red for failure
|
||||||
|
"""
|
||||||
|
if style is None:
|
||||||
|
style = "bold green" if success else "bold red"
|
||||||
|
console.print(message, style=style)
|
||||||
|
print("")
|
||||||
|
|
||||||
|
|
||||||
|
def write_temp_file(
|
||||||
|
input_data="",
|
||||||
|
suffix=None,
|
||||||
|
prefix=None,
|
||||||
|
dir=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a temporary file with the given input data.
|
||||||
|
|
||||||
|
:param input_data: Content to write to the temporary file
|
||||||
|
:param suffix: Optional file extension (without the dot)
|
||||||
|
:param prefix: Optional prefix for the temporary filename
|
||||||
|
:param dir: Optional directory to create the file in
|
||||||
|
:return: Path to the created temporary file
|
||||||
|
:raises: OSError if file creation or writing fails
|
||||||
|
"""
|
||||||
|
kwargs = {"prefix": prefix, "dir": dir}
|
||||||
|
if suffix:
|
||||||
|
kwargs["suffix"] = f".{suffix}"
|
||||||
|
fd, filepath = tempfile.mkstemp(**kwargs)
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, "w") as f:
|
||||||
|
f.write(input_data)
|
||||||
|
except Exception:
|
||||||
|
os.close(fd)
|
||||||
|
raise
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
def get_environment_editor(default=None):
|
||||||
|
"""
|
||||||
|
Fetches the preferred editor from the environment variables.
|
||||||
|
|
||||||
|
This function checks the following environment variables in order to
|
||||||
|
determine the user's preferred editor:
|
||||||
|
|
||||||
|
- VISUAL
|
||||||
|
- EDITOR
|
||||||
|
|
||||||
|
:param default: The default editor to return if no environment variable is set.
|
||||||
|
:type default: str or None
|
||||||
|
:return: The preferred editor as specified by environment variables or the default value.
|
||||||
|
:rtype: str or None
|
||||||
|
"""
|
||||||
|
editor = os.environ.get("VISUAL", os.environ.get("EDITOR", default))
|
||||||
|
return editor
|
||||||
|
|
||||||
|
|
||||||
|
def discover_editor(editor_override=None):
|
||||||
|
"""
|
||||||
|
Discovers and returns the appropriate editor command as a list of arguments.
|
||||||
|
|
||||||
|
Handles cases where the editor command includes arguments, including quoted arguments
|
||||||
|
with spaces (e.g. 'vim -c "set noswapfile"').
|
||||||
|
|
||||||
|
:return: A list of command parts ready for subprocess execution
|
||||||
|
:rtype: list[str]
|
||||||
|
"""
|
||||||
|
system = platform.system()
|
||||||
|
if system == "Windows":
|
||||||
|
default_editor = DEFAULT_EDITOR_WINDOWS
|
||||||
|
elif system == "Darwin":
|
||||||
|
default_editor = DEFAULT_EDITOR_OS_X
|
||||||
|
else:
|
||||||
|
default_editor = DEFAULT_EDITOR_NIX
|
||||||
|
if editor_override:
|
||||||
|
editor = editor_override
|
||||||
|
else:
|
||||||
|
editor = get_environment_editor(default_editor)
|
||||||
|
try:
|
||||||
|
return shlex.split(editor)
|
||||||
|
except ValueError as e:
|
||||||
|
raise RuntimeError(f"Invalid editor command format '{editor}': {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def pipe_editor(input_data="", suffix=None, editor=None):
|
||||||
|
"""
|
||||||
|
Opens the system editor with optional input data and returns the edited content.
|
||||||
|
|
||||||
|
This function creates a temporary file with the provided input data, opens it in
|
||||||
|
the system editor, waits for the user to make changes and close the editor, then
|
||||||
|
reads and returns the modified content. The temporary file is deleted afterwards.
|
||||||
|
|
||||||
|
:param input_data: Initial content to populate the editor with
|
||||||
|
:type input_data: str
|
||||||
|
:param suffix: Optional file extension for the temporary file (e.g. '.txt', '.md')
|
||||||
|
:type suffix: str or None
|
||||||
|
:return: The edited content after the editor is closed
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
filepath = write_temp_file(input_data, suffix)
|
||||||
|
command_parts = discover_editor(editor)
|
||||||
|
command_parts.append(filepath)
|
||||||
|
subprocess.call(command_parts)
|
||||||
|
with open(filepath, "r") as f:
|
||||||
|
output_data = f.read()
|
||||||
|
try:
|
||||||
|
os.remove(filepath)
|
||||||
|
except PermissionError:
|
||||||
|
print_status_message(
|
||||||
|
False,
|
||||||
|
(
|
||||||
|
f"WARNING: Unable to delete temporary file {filepath!r}. You may need to delete it"
|
||||||
|
" manually."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return output_data
|
||||||
90
aider/exceptions.py
Normal file
90
aider/exceptions.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExInfo:
|
||||||
|
name: str
|
||||||
|
retry: bool
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
EXCEPTIONS = [
|
||||||
|
ExInfo("APIConnectionError", True, None),
|
||||||
|
ExInfo("APIError", True, None),
|
||||||
|
ExInfo("APIResponseValidationError", True, None),
|
||||||
|
ExInfo(
|
||||||
|
"AuthenticationError",
|
||||||
|
False,
|
||||||
|
"The API provider is not able to authenticate you. Check your API key.",
|
||||||
|
),
|
||||||
|
ExInfo("AzureOpenAIError", True, None),
|
||||||
|
ExInfo("BadRequestError", False, None),
|
||||||
|
ExInfo("BudgetExceededError", True, None),
|
||||||
|
ExInfo(
|
||||||
|
"ContentPolicyViolationError",
|
||||||
|
True,
|
||||||
|
"The API provider has refused the request due to a safety policy about the content.",
|
||||||
|
),
|
||||||
|
ExInfo("ContextWindowExceededError", False, None), # special case handled in base_coder
|
||||||
|
ExInfo("InternalServerError", True, "The API provider's servers are down or overloaded."),
|
||||||
|
ExInfo("InvalidRequestError", True, None),
|
||||||
|
ExInfo("JSONSchemaValidationError", True, None),
|
||||||
|
ExInfo("NotFoundError", False, None),
|
||||||
|
ExInfo("OpenAIError", True, None),
|
||||||
|
ExInfo(
|
||||||
|
"RateLimitError",
|
||||||
|
True,
|
||||||
|
"The API provider has rate limited you. Try again later or check your quotas.",
|
||||||
|
),
|
||||||
|
ExInfo("RouterRateLimitError", True, None),
|
||||||
|
ExInfo("ServiceUnavailableError", True, "The API provider's servers are down or overloaded."),
|
||||||
|
ExInfo("UnprocessableEntityError", True, None),
|
||||||
|
ExInfo("UnsupportedParamsError", True, None),
|
||||||
|
ExInfo(
|
||||||
|
"Timeout",
|
||||||
|
True,
|
||||||
|
"The API provider timed out without returning a response. They may be down or overloaded.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class LiteLLMExceptions:
|
||||||
|
exceptions = dict()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._load()
|
||||||
|
|
||||||
|
def _load(self, strict=False):
|
||||||
|
import litellm
|
||||||
|
|
||||||
|
for var in dir(litellm):
|
||||||
|
if not var.endswith("Error"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
ex_info = None
|
||||||
|
for exi in EXCEPTIONS:
|
||||||
|
if var == exi.name:
|
||||||
|
ex_info = exi
|
||||||
|
break
|
||||||
|
|
||||||
|
if strict and not ex_info:
|
||||||
|
raise ValueError(f"{var} is in litellm but not in aider's exceptions list")
|
||||||
|
|
||||||
|
ex = getattr(litellm, var)
|
||||||
|
self.exceptions[ex] = ex_info
|
||||||
|
|
||||||
|
def exceptions_tuple(self):
|
||||||
|
return tuple(self.exceptions)
|
||||||
|
|
||||||
|
def get_ex_info(self, ex):
|
||||||
|
"""Return the ExInfo for a given exception instance"""
|
||||||
|
import litellm
|
||||||
|
|
||||||
|
if ex.__class__ is litellm.APIConnectionError:
|
||||||
|
if "google.auth" in str(ex):
|
||||||
|
return ExInfo(
|
||||||
|
"APIConnectionError", False, "You need to: pip install google-generativeai"
|
||||||
|
)
|
||||||
|
if "boto3" in str(ex):
|
||||||
|
return ExInfo("APIConnectionError", False, "You need to: pip install boto3")
|
||||||
|
return self.exceptions.get(ex.__class__, ExInfo(None, None, None))
|
||||||
@@ -160,7 +160,7 @@ class GUI:
|
|||||||
|
|
||||||
st.warning(
|
st.warning(
|
||||||
"This browser version of aider is experimental. Please share feedback in [GitHub"
|
"This browser version of aider is experimental. Please share feedback in [GitHub"
|
||||||
" issues](https://github.com/paul-gauthier/aider/issues)."
|
" issues](https://github.com/Aider-AI/aider/issues)."
|
||||||
)
|
)
|
||||||
|
|
||||||
def do_settings_tab(self):
|
def do_settings_tab(self):
|
||||||
@@ -528,7 +528,7 @@ def gui_main():
|
|||||||
page_icon=urls.favicon,
|
page_icon=urls.favicon,
|
||||||
menu_items={
|
menu_items={
|
||||||
"Get Help": urls.website,
|
"Get Help": urls.website,
|
||||||
"Report a bug": "https://github.com/paul-gauthier/aider/issues",
|
"Report a bug": "https://github.com/Aider-AI/aider/issues",
|
||||||
"About": "# Aider\nAI pair programming in your browser.",
|
"About": "# Aider\nAI pair programming in your browser.",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import warnings
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -38,24 +40,45 @@ def get_package_files():
|
|||||||
|
|
||||||
|
|
||||||
def fname_to_url(filepath):
|
def fname_to_url(filepath):
|
||||||
website = "website/"
|
website = "website"
|
||||||
index = "/index.md"
|
index = "index.md"
|
||||||
md = ".md"
|
md = ".md"
|
||||||
|
|
||||||
docid = ""
|
# Convert backslashes to forward slashes for consistency
|
||||||
if filepath.startswith("website/_includes/"):
|
filepath = filepath.replace("\\", "/")
|
||||||
pass
|
|
||||||
elif filepath.startswith(website):
|
|
||||||
docid = filepath[len(website) :]
|
|
||||||
|
|
||||||
if filepath.endswith(index):
|
# Convert to Path object for easier manipulation
|
||||||
filepath = filepath[: -len(index)] + "/"
|
path = Path(filepath)
|
||||||
elif filepath.endswith(md):
|
|
||||||
filepath = filepath[: -len(md)] + ".html"
|
|
||||||
|
|
||||||
docid = "https://aider.chat/" + filepath
|
# Split the path into parts
|
||||||
|
parts = path.parts
|
||||||
|
|
||||||
return docid
|
# Find the 'website' part in the path
|
||||||
|
try:
|
||||||
|
website_index = [p.lower() for p in parts].index(website.lower())
|
||||||
|
except ValueError:
|
||||||
|
return "" # 'website' not found in the path
|
||||||
|
|
||||||
|
# Extract the part of the path starting from 'website'
|
||||||
|
relevant_parts = parts[website_index + 1 :]
|
||||||
|
|
||||||
|
# Handle _includes directory
|
||||||
|
if relevant_parts and relevant_parts[0].lower() == "_includes":
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Join the remaining parts
|
||||||
|
url_path = "/".join(relevant_parts)
|
||||||
|
|
||||||
|
# Handle index.md and other .md files
|
||||||
|
if url_path.lower().endswith(index.lower()):
|
||||||
|
url_path = url_path[: -len(index)]
|
||||||
|
elif url_path.lower().endswith(md.lower()):
|
||||||
|
url_path = url_path[: -len(md)] + ".html"
|
||||||
|
|
||||||
|
# Ensure the URL starts and ends with '/'
|
||||||
|
url_path = url_path.strip("/")
|
||||||
|
|
||||||
|
return f"https://aider.chat/{url_path}"
|
||||||
|
|
||||||
|
|
||||||
def get_index():
|
def get_index():
|
||||||
@@ -69,12 +92,17 @@ def get_index():
|
|||||||
|
|
||||||
dname = Path.home() / ".aider" / "caches" / ("help." + __version__)
|
dname = Path.home() / ".aider" / "caches" / ("help." + __version__)
|
||||||
|
|
||||||
if dname.exists():
|
index = None
|
||||||
storage_context = StorageContext.from_defaults(
|
try:
|
||||||
persist_dir=dname,
|
if dname.exists():
|
||||||
)
|
storage_context = StorageContext.from_defaults(
|
||||||
index = load_index_from_storage(storage_context)
|
persist_dir=dname,
|
||||||
else:
|
)
|
||||||
|
index = load_index_from_storage(storage_context)
|
||||||
|
except (OSError, json.JSONDecodeError):
|
||||||
|
shutil.rmtree(dname)
|
||||||
|
|
||||||
|
if index is None:
|
||||||
parser = MarkdownNodeParser()
|
parser = MarkdownNodeParser()
|
||||||
|
|
||||||
nodes = []
|
nodes = []
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
# This needs to sync with MANIFEST.in
|
||||||
|
|
||||||
exclude_website_pats = [
|
exclude_website_pats = [
|
||||||
|
"**/.DS_Store",
|
||||||
"examples/**",
|
"examples/**",
|
||||||
"_posts/**",
|
"_posts/**",
|
||||||
"HISTORY.md",
|
"HISTORY.md",
|
||||||
|
|||||||
@@ -108,9 +108,7 @@ class ChatSummary:
|
|||||||
|
|
||||||
for model in self.models:
|
for model in self.models:
|
||||||
try:
|
try:
|
||||||
summary = simple_send_with_retries(
|
summary = simple_send_with_retries(model, summarize_messages)
|
||||||
model.name, summarize_messages, extra_headers=model.extra_headers
|
|
||||||
)
|
|
||||||
if summary is not None:
|
if summary is not None:
|
||||||
summary = prompts.summary_prefix + summary
|
summary = prompts.summary_prefix + summary
|
||||||
return [dict(role="user", content=summary)]
|
return [dict(role="user", content=summary)]
|
||||||
|
|||||||
475
aider/io.py
475
aider/io.py
@@ -1,23 +1,32 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
import webbrowser
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from prompt_toolkit.completion import Completer, Completion, ThreadedCompleter
|
from prompt_toolkit.completion import Completer, Completion, ThreadedCompleter
|
||||||
|
from prompt_toolkit.cursor_shapes import ModalCursorShapeConfig
|
||||||
from prompt_toolkit.enums import EditingMode
|
from prompt_toolkit.enums import EditingMode
|
||||||
|
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.keys import Keys
|
||||||
from prompt_toolkit.lexers import PygmentsLexer
|
from prompt_toolkit.lexers import PygmentsLexer
|
||||||
from prompt_toolkit.shortcuts import CompleteStyle, PromptSession
|
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.columns import Columns
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from rich.markdown import Markdown
|
||||||
from rich.style import Style as RichStyle
|
from rich.style import Style as RichStyle
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
from rich.markdown import Markdown
|
|
||||||
from aider.mdstream import MarkdownStream
|
from aider.mdstream import MarkdownStream
|
||||||
|
|
||||||
from .dump import dump # noqa: F401
|
from .dump import dump # noqa: F401
|
||||||
@@ -91,17 +100,16 @@ class AutoCompleter(Completer):
|
|||||||
(token[1], f"`{token[1]}`") for token in tokens if token[0] in Token.Name
|
(token[1], f"`{token[1]}`") for token in tokens if token[0] in Token.Name
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_command_completions(self, text, words):
|
def get_command_completions(self, document, complete_event, text, words):
|
||||||
candidates = []
|
|
||||||
if len(words) == 1 and not text[-1].isspace():
|
if len(words) == 1 and not text[-1].isspace():
|
||||||
partial = words[0].lower()
|
partial = words[0].lower()
|
||||||
candidates = [cmd for cmd in self.command_names if cmd.startswith(partial)]
|
candidates = [cmd for cmd in self.command_names if cmd.startswith(partial)]
|
||||||
return candidates
|
for candidate in sorted(candidates):
|
||||||
|
yield Completion(candidate, start_position=-len(words[-1]))
|
||||||
|
return
|
||||||
|
|
||||||
if len(words) <= 1:
|
if len(words) <= 1 or text[-1].isspace():
|
||||||
return []
|
return
|
||||||
if text[-1].isspace():
|
|
||||||
return []
|
|
||||||
|
|
||||||
cmd = words[0]
|
cmd = words[0]
|
||||||
partial = words[-1].lower()
|
partial = words[-1].lower()
|
||||||
@@ -112,6 +120,11 @@ class AutoCompleter(Completer):
|
|||||||
elif cmd not in matches:
|
elif cmd not in matches:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
raw_completer = self.commands.get_raw_completions(cmd)
|
||||||
|
if raw_completer:
|
||||||
|
yield from raw_completer(document, complete_event)
|
||||||
|
return
|
||||||
|
|
||||||
if cmd not in self.command_completions:
|
if cmd not in self.command_completions:
|
||||||
candidates = self.commands.get_completions(cmd)
|
candidates = self.commands.get_completions(cmd)
|
||||||
self.command_completions[cmd] = candidates
|
self.command_completions[cmd] = candidates
|
||||||
@@ -122,7 +135,8 @@ class AutoCompleter(Completer):
|
|||||||
return
|
return
|
||||||
|
|
||||||
candidates = [word for word in candidates if partial in word.lower()]
|
candidates = [word for word in candidates if partial in word.lower()]
|
||||||
return candidates
|
for candidate in sorted(candidates):
|
||||||
|
yield Completion(candidate, start_position=-len(words[-1]))
|
||||||
|
|
||||||
def get_completions(self, document, complete_event):
|
def get_completions(self, document, complete_event):
|
||||||
self.tokenize()
|
self.tokenize()
|
||||||
@@ -137,11 +151,8 @@ class AutoCompleter(Completer):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if text[0] == "/":
|
if text[0] == "/":
|
||||||
candidates = self.get_command_completions(text, words)
|
yield from self.get_command_completions(document, complete_event, text, words)
|
||||||
if candidates is not None:
|
return
|
||||||
for candidate in sorted(candidates):
|
|
||||||
yield Completion(candidate, start_position=-len(words[-1]))
|
|
||||||
return
|
|
||||||
|
|
||||||
candidates = self.words
|
candidates = self.words
|
||||||
candidates.update(set(self.fname_to_rel_fnames))
|
candidates.update(set(self.fname_to_rel_fnames))
|
||||||
@@ -165,6 +176,7 @@ class AutoCompleter(Completer):
|
|||||||
class InputOutput:
|
class InputOutput:
|
||||||
num_error_outputs = 0
|
num_error_outputs = 0
|
||||||
num_user_asks = 0
|
num_user_asks = 0
|
||||||
|
clipboard_watcher = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -179,13 +191,24 @@ class InputOutput:
|
|||||||
tool_error_color="red",
|
tool_error_color="red",
|
||||||
tool_warning_color="#FFA500",
|
tool_warning_color="#FFA500",
|
||||||
assistant_output_color="blue",
|
assistant_output_color="blue",
|
||||||
|
completion_menu_color=None,
|
||||||
|
completion_menu_bg_color=None,
|
||||||
|
completion_menu_current_color=None,
|
||||||
|
completion_menu_current_bg_color=None,
|
||||||
code_theme="default",
|
code_theme="default",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
llm_history_file=None,
|
llm_history_file=None,
|
||||||
editingmode=EditingMode.EMACS,
|
editingmode=EditingMode.EMACS,
|
||||||
|
fancy_input=True,
|
||||||
|
file_watcher=None,
|
||||||
|
multiline_mode=False,
|
||||||
):
|
):
|
||||||
|
self.placeholder = None
|
||||||
|
self.interrupted = False
|
||||||
|
self.never_prompts = set()
|
||||||
self.editingmode = editingmode
|
self.editingmode = editingmode
|
||||||
|
self.multiline_mode = multiline_mode
|
||||||
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
|
||||||
@@ -195,6 +218,11 @@ class InputOutput:
|
|||||||
self.tool_error_color = tool_error_color if pretty else None
|
self.tool_error_color = tool_error_color if pretty else None
|
||||||
self.tool_warning_color = tool_warning_color if pretty else None
|
self.tool_warning_color = tool_warning_color if pretty else None
|
||||||
self.assistant_output_color = assistant_output_color
|
self.assistant_output_color = assistant_output_color
|
||||||
|
self.completion_menu_color = completion_menu_color if pretty else None
|
||||||
|
self.completion_menu_bg_color = completion_menu_bg_color if pretty else None
|
||||||
|
self.completion_menu_current_color = completion_menu_current_color if pretty else None
|
||||||
|
self.completion_menu_current_bg_color = completion_menu_current_bg_color if pretty else None
|
||||||
|
|
||||||
self.code_theme = code_theme
|
self.code_theme = code_theme
|
||||||
|
|
||||||
self.input = input
|
self.input = input
|
||||||
@@ -220,7 +248,7 @@ class InputOutput:
|
|||||||
self.append_chat_history(f"\n# aider chat started at {current_time}\n\n")
|
self.append_chat_history(f"\n# aider chat started at {current_time}\n\n")
|
||||||
|
|
||||||
self.prompt_session = None
|
self.prompt_session = None
|
||||||
if self.pretty:
|
if fancy_input:
|
||||||
# Initialize PromptSession
|
# Initialize PromptSession
|
||||||
session_kwargs = {
|
session_kwargs = {
|
||||||
"input": self.input,
|
"input": self.input,
|
||||||
@@ -228,6 +256,8 @@ class InputOutput:
|
|||||||
"lexer": PygmentsLexer(MarkdownLexer),
|
"lexer": PygmentsLexer(MarkdownLexer),
|
||||||
"editing_mode": self.editingmode,
|
"editing_mode": self.editingmode,
|
||||||
}
|
}
|
||||||
|
if self.editingmode == EditingMode.VI:
|
||||||
|
session_kwargs["cursor"] = ModalCursorShapeConfig()
|
||||||
if self.input_history_file is not None:
|
if self.input_history_file is not None:
|
||||||
session_kwargs["history"] = FileHistory(self.input_history_file)
|
session_kwargs["history"] = FileHistory(self.input_history_file)
|
||||||
try:
|
try:
|
||||||
@@ -239,6 +269,43 @@ class InputOutput:
|
|||||||
else:
|
else:
|
||||||
self.console = Console(force_terminal=False, no_color=True) # non-pretty
|
self.console = Console(force_terminal=False, no_color=True) # non-pretty
|
||||||
|
|
||||||
|
self.file_watcher = file_watcher
|
||||||
|
|
||||||
|
def _get_style(self):
|
||||||
|
style_dict = {}
|
||||||
|
if not self.pretty:
|
||||||
|
return Style.from_dict(style_dict)
|
||||||
|
|
||||||
|
if self.user_input_color:
|
||||||
|
style_dict.setdefault("", self.user_input_color)
|
||||||
|
style_dict.update(
|
||||||
|
{
|
||||||
|
"pygments.literal.string": f"bold italic {self.user_input_color}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Conditionally add 'completion-menu' style
|
||||||
|
completion_menu_style = []
|
||||||
|
if self.completion_menu_bg_color:
|
||||||
|
completion_menu_style.append(f"bg:{self.completion_menu_bg_color}")
|
||||||
|
if self.completion_menu_color:
|
||||||
|
completion_menu_style.append(self.completion_menu_color)
|
||||||
|
if completion_menu_style:
|
||||||
|
style_dict["completion-menu"] = " ".join(completion_menu_style)
|
||||||
|
|
||||||
|
# Conditionally add 'completion-menu.completion.current' style
|
||||||
|
completion_menu_current_style = []
|
||||||
|
if self.completion_menu_current_bg_color:
|
||||||
|
completion_menu_current_style.append(f"bg:{self.completion_menu_current_bg_color}")
|
||||||
|
if self.completion_menu_current_color:
|
||||||
|
completion_menu_current_style.append(self.completion_menu_current_color)
|
||||||
|
if completion_menu_current_style:
|
||||||
|
style_dict["completion-menu.completion.current"] = " ".join(
|
||||||
|
completion_menu_current_style
|
||||||
|
)
|
||||||
|
|
||||||
|
return Style.from_dict(style_dict)
|
||||||
|
|
||||||
def read_image(self, filename):
|
def read_image(self, filename):
|
||||||
try:
|
try:
|
||||||
with open(str(filename), "rb") as image_file:
|
with open(str(filename), "rb") as image_file:
|
||||||
@@ -257,7 +324,7 @@ class InputOutput:
|
|||||||
self.tool_error(f"{filename}: {e}")
|
self.tool_error(f"{filename}: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
def read_text(self, filename):
|
def read_text(self, filename, silent=False):
|
||||||
if is_image_file(filename):
|
if is_image_file(filename):
|
||||||
return self.read_image(filename)
|
return self.read_image(filename)
|
||||||
|
|
||||||
@@ -265,27 +332,53 @@ class InputOutput:
|
|||||||
with open(str(filename), "r", encoding=self.encoding) as f:
|
with open(str(filename), "r", encoding=self.encoding) as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
self.tool_error(f"{filename}: unable to read: {err}")
|
if not silent:
|
||||||
|
self.tool_error(f"{filename}: unable to read: {err}")
|
||||||
return
|
return
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.tool_error(f"{filename}: file not found error")
|
if not silent:
|
||||||
|
self.tool_error(f"{filename}: file not found error")
|
||||||
return
|
return
|
||||||
except IsADirectoryError:
|
except IsADirectoryError:
|
||||||
self.tool_error(f"{filename}: is a directory")
|
if not silent:
|
||||||
|
self.tool_error(f"{filename}: is a directory")
|
||||||
return
|
return
|
||||||
except UnicodeError as e:
|
except UnicodeError as e:
|
||||||
self.tool_error(f"{filename}: {e}")
|
if not silent:
|
||||||
self.tool_error("Use --encoding to set the unicode encoding.")
|
self.tool_error(f"{filename}: {e}")
|
||||||
|
self.tool_error("Use --encoding to set the unicode encoding.")
|
||||||
return
|
return
|
||||||
|
|
||||||
def write_text(self, filename, content):
|
def write_text(self, filename, content, max_retries=5, initial_delay=0.1):
|
||||||
|
"""
|
||||||
|
Writes content to a file, retrying with progressive backoff if the file is locked.
|
||||||
|
|
||||||
|
:param filename: Path to the file to write.
|
||||||
|
:param content: Content to write to the file.
|
||||||
|
:param max_retries: Maximum number of retries if a file lock is encountered.
|
||||||
|
:param initial_delay: Initial delay (in seconds) before the first retry.
|
||||||
|
"""
|
||||||
if self.dry_run:
|
if self.dry_run:
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
with open(str(filename), "w", encoding=self.encoding) as f:
|
delay = initial_delay
|
||||||
f.write(content)
|
for attempt in range(max_retries):
|
||||||
except OSError as err:
|
try:
|
||||||
self.tool_error(f"Unable to write file {filename}: {err}")
|
with open(str(filename), "w", encoding=self.encoding) as f:
|
||||||
|
f.write(content)
|
||||||
|
return # Successfully wrote the file
|
||||||
|
except PermissionError as err:
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
time.sleep(delay)
|
||||||
|
delay *= 2 # Exponential backoff
|
||||||
|
else:
|
||||||
|
self.tool_error(
|
||||||
|
f"Unable to write file {filename} after {max_retries} attempts: {err}"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
except OSError as err:
|
||||||
|
self.tool_error(f"Unable to write file {filename}: {err}")
|
||||||
|
raise
|
||||||
|
|
||||||
def rule(self):
|
def rule(self):
|
||||||
if self.pretty:
|
if self.pretty:
|
||||||
@@ -294,6 +387,13 @@ class InputOutput:
|
|||||||
else:
|
else:
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
def interrupt_input(self):
|
||||||
|
if self.prompt_session and self.prompt_session.app:
|
||||||
|
# Store any partial input before interrupting
|
||||||
|
self.placeholder = self.prompt_session.app.current_buffer.text
|
||||||
|
self.interrupted = True
|
||||||
|
self.prompt_session.app.exit()
|
||||||
|
|
||||||
def get_input(
|
def get_input(
|
||||||
self,
|
self,
|
||||||
root,
|
root,
|
||||||
@@ -308,23 +408,20 @@ class InputOutput:
|
|||||||
rel_fnames = list(rel_fnames)
|
rel_fnames = list(rel_fnames)
|
||||||
show = ""
|
show = ""
|
||||||
if rel_fnames:
|
if rel_fnames:
|
||||||
show = " ".join(rel_fnames) + "\n"
|
rel_read_only_fnames = [
|
||||||
|
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)
|
||||||
if edit_format:
|
if edit_format:
|
||||||
show += edit_format
|
show += edit_format
|
||||||
|
if self.multiline_mode:
|
||||||
|
show += (" " if edit_format else "") + "multi"
|
||||||
show += "> "
|
show += "> "
|
||||||
|
|
||||||
inp = ""
|
inp = ""
|
||||||
multiline_input = False
|
multiline_input = False
|
||||||
|
|
||||||
if self.user_input_color and self.pretty:
|
style = self._get_style()
|
||||||
style = Style.from_dict(
|
|
||||||
{
|
|
||||||
"": self.user_input_color,
|
|
||||||
"pygments.literal.string": f"bold italic {self.user_input_color}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
style = None
|
|
||||||
|
|
||||||
completer_instance = ThreadedCompleter(
|
completer_instance = ThreadedCompleter(
|
||||||
AutoCompleter(
|
AutoCompleter(
|
||||||
@@ -337,11 +434,51 @@ class InputOutput:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def suspend_to_bg(event):
|
||||||
|
"""Suspend currently running application."""
|
||||||
|
event.app.suspend_to_background()
|
||||||
|
|
||||||
kb = KeyBindings()
|
kb = KeyBindings()
|
||||||
|
|
||||||
@kb.add("escape", "c-m", eager=True)
|
@kb.add(Keys.ControlZ, filter=Condition(lambda: hasattr(signal, "SIGTSTP")))
|
||||||
def _(event):
|
def _(event):
|
||||||
event.current_buffer.insert_text("\n")
|
"Suspend to background with ctrl-z"
|
||||||
|
suspend_to_bg(event)
|
||||||
|
|
||||||
|
@kb.add("c-space")
|
||||||
|
def _(event):
|
||||||
|
"Ignore Ctrl when pressing space bar"
|
||||||
|
event.current_buffer.insert_text(" ")
|
||||||
|
|
||||||
|
@kb.add("c-up")
|
||||||
|
def _(event):
|
||||||
|
"Navigate backward through history"
|
||||||
|
event.current_buffer.history_backward()
|
||||||
|
|
||||||
|
@kb.add("c-down")
|
||||||
|
def _(event):
|
||||||
|
"Navigate forward through history"
|
||||||
|
event.current_buffer.history_forward()
|
||||||
|
|
||||||
|
@kb.add("enter", eager=True, filter=~is_searching)
|
||||||
|
def _(event):
|
||||||
|
"Handle Enter key press"
|
||||||
|
if self.multiline_mode:
|
||||||
|
# In multiline mode, Enter adds a newline
|
||||||
|
event.current_buffer.insert_text("\n")
|
||||||
|
else:
|
||||||
|
# In normal mode, Enter submits
|
||||||
|
event.current_buffer.validate_and_handle()
|
||||||
|
|
||||||
|
@kb.add("escape", "enter", eager=True, filter=~is_searching) # This is Alt+Enter
|
||||||
|
def _(event):
|
||||||
|
"Handle Alt+Enter key press"
|
||||||
|
if self.multiline_mode:
|
||||||
|
# In multiline mode, Alt+Enter submits
|
||||||
|
event.current_buffer.validate_and_handle()
|
||||||
|
else:
|
||||||
|
# In normal mode, Alt+Enter adds a newline
|
||||||
|
event.current_buffer.insert_text("\n")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if multiline_input:
|
if multiline_input:
|
||||||
@@ -349,8 +486,20 @@ class InputOutput:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if self.prompt_session:
|
if self.prompt_session:
|
||||||
|
# Use placeholder if set, then clear it
|
||||||
|
default = self.placeholder or ""
|
||||||
|
self.placeholder = None
|
||||||
|
|
||||||
|
self.interrupted = False
|
||||||
|
if not multiline_input:
|
||||||
|
if self.file_watcher:
|
||||||
|
self.file_watcher.start()
|
||||||
|
if self.clipboard_watcher:
|
||||||
|
self.clipboard_watcher.start()
|
||||||
|
|
||||||
line = self.prompt_session.prompt(
|
line = self.prompt_session.prompt(
|
||||||
show,
|
show,
|
||||||
|
default=default,
|
||||||
completer=completer_instance,
|
completer=completer_instance,
|
||||||
reserve_space_for_menu=4,
|
reserve_space_for_menu=4,
|
||||||
complete_style=CompleteStyle.MULTI_COLUMN,
|
complete_style=CompleteStyle.MULTI_COLUMN,
|
||||||
@@ -359,17 +508,63 @@ class InputOutput:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
line = input(show)
|
line = input(show)
|
||||||
|
|
||||||
|
# Check if we were interrupted by a file change
|
||||||
|
if self.interrupted:
|
||||||
|
line = line or ""
|
||||||
|
if self.file_watcher:
|
||||||
|
cmd = self.file_watcher.process_changes()
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
except EOFError:
|
||||||
|
raise
|
||||||
|
except Exception as err:
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
self.tool_error(str(err))
|
||||||
|
self.tool_error(traceback.format_exc())
|
||||||
|
return ""
|
||||||
except UnicodeEncodeError as err:
|
except UnicodeEncodeError as err:
|
||||||
self.tool_error(str(err))
|
self.tool_error(str(err))
|
||||||
return ""
|
return ""
|
||||||
|
finally:
|
||||||
|
if self.file_watcher:
|
||||||
|
self.file_watcher.stop()
|
||||||
|
if self.clipboard_watcher:
|
||||||
|
self.clipboard_watcher.stop()
|
||||||
|
|
||||||
if line and line[0] == "{" and not multiline_input:
|
if line.strip("\r\n") and not multiline_input:
|
||||||
multiline_input = True
|
stripped = line.strip("\r\n")
|
||||||
inp += line[1:] + "\n"
|
if stripped == "{":
|
||||||
|
multiline_input = True
|
||||||
|
multiline_tag = None
|
||||||
|
inp += ""
|
||||||
|
elif stripped[0] == "{":
|
||||||
|
# Extract tag if it exists (only alphanumeric chars)
|
||||||
|
tag = "".join(c for c in stripped[1:] if c.isalnum())
|
||||||
|
if stripped == "{" + tag:
|
||||||
|
multiline_input = True
|
||||||
|
multiline_tag = tag
|
||||||
|
inp += ""
|
||||||
|
else:
|
||||||
|
inp = line
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
inp = line
|
||||||
|
break
|
||||||
continue
|
continue
|
||||||
elif line and line[-1] == "}" and multiline_input:
|
elif multiline_input and line.strip():
|
||||||
inp += line[:-1] + "\n"
|
if multiline_tag:
|
||||||
break
|
# Check if line is exactly "tag}"
|
||||||
|
if line.strip("\r\n") == f"{multiline_tag}}}":
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
inp += line + "\n"
|
||||||
|
# Check if line is exactly "}"
|
||||||
|
elif line.strip("\r\n") == "}":
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
inp += line + "\n"
|
||||||
elif multiline_input:
|
elif multiline_input:
|
||||||
inp += line + "\n"
|
inp += line + "\n"
|
||||||
else:
|
else:
|
||||||
@@ -383,10 +578,13 @@ class InputOutput:
|
|||||||
def add_to_input_history(self, inp):
|
def add_to_input_history(self, inp):
|
||||||
if not self.input_history_file:
|
if not self.input_history_file:
|
||||||
return
|
return
|
||||||
FileHistory(self.input_history_file).append_string(inp)
|
try:
|
||||||
# Also add to the in-memory history if it exists
|
FileHistory(self.input_history_file).append_string(inp)
|
||||||
if hasattr(self, "session") and hasattr(self.session, "history"):
|
# Also add to the in-memory history if it exists
|
||||||
self.session.history.append_string(inp)
|
if self.prompt_session and self.prompt_session.history:
|
||||||
|
self.prompt_session.history.append_string(inp)
|
||||||
|
except OSError as err:
|
||||||
|
self.tool_warning(f"Unable to write to input history file: {err}")
|
||||||
|
|
||||||
def get_input_history(self):
|
def get_input_history(self):
|
||||||
if not self.input_history_file:
|
if not self.input_history_file:
|
||||||
@@ -403,14 +601,17 @@ class InputOutput:
|
|||||||
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")
|
||||||
|
|
||||||
|
def display_user_input(self, inp):
|
||||||
|
if self.pretty and self.user_input_color:
|
||||||
|
style = dict(style=self.user_input_color)
|
||||||
|
else:
|
||||||
|
style = dict()
|
||||||
|
|
||||||
|
self.console.print(Text(inp), **style)
|
||||||
|
|
||||||
def user_input(self, inp, log_only=True):
|
def user_input(self, inp, log_only=True):
|
||||||
if not log_only:
|
if not log_only:
|
||||||
if self.pretty and self.user_input_color:
|
self.display_user_input(inp)
|
||||||
style = dict(style=self.user_input_color)
|
|
||||||
else:
|
|
||||||
style = dict()
|
|
||||||
|
|
||||||
self.console.print(Text(inp), **style)
|
|
||||||
|
|
||||||
prefix = "####"
|
prefix = "####"
|
||||||
if inp:
|
if inp:
|
||||||
@@ -430,13 +631,38 @@ class InputOutput:
|
|||||||
hist = "\n" + content.strip() + "\n\n"
|
hist = "\n" + content.strip() + "\n\n"
|
||||||
self.append_chat_history(hist)
|
self.append_chat_history(hist)
|
||||||
|
|
||||||
|
def offer_url(self, url, prompt="Open URL for more info?", allow_never=True):
|
||||||
|
"""Offer to open a URL in the browser, returns True if opened."""
|
||||||
|
if url in self.never_prompts:
|
||||||
|
return False
|
||||||
|
if self.confirm_ask(prompt, subject=url, allow_never=allow_never):
|
||||||
|
webbrowser.open(url)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def confirm_ask(
|
def confirm_ask(
|
||||||
self, question, default="y", subject=None, explicit_yes_required=False, group=None
|
self,
|
||||||
|
question,
|
||||||
|
default="y",
|
||||||
|
subject=None,
|
||||||
|
explicit_yes_required=False,
|
||||||
|
group=None,
|
||||||
|
allow_never=False,
|
||||||
):
|
):
|
||||||
|
# Temporarily disable multiline mode for yes/no prompts
|
||||||
|
orig_multiline = self.multiline_mode
|
||||||
|
self.multiline_mode = False
|
||||||
self.num_user_asks += 1
|
self.num_user_asks += 1
|
||||||
|
|
||||||
|
question_id = (question, subject)
|
||||||
|
|
||||||
|
if question_id in self.never_prompts:
|
||||||
|
return False
|
||||||
|
|
||||||
if group and not group.show_group:
|
if group and not group.show_group:
|
||||||
group = None
|
group = None
|
||||||
|
if group:
|
||||||
|
allow_never = True
|
||||||
|
|
||||||
valid_responses = ["yes", "no"]
|
valid_responses = ["yes", "no"]
|
||||||
options = " (Y)es/(N)o"
|
options = " (Y)es/(N)o"
|
||||||
@@ -446,6 +672,10 @@ class InputOutput:
|
|||||||
valid_responses.append("all")
|
valid_responses.append("all")
|
||||||
options += "/(S)kip all"
|
options += "/(S)kip all"
|
||||||
valid_responses.append("skip")
|
valid_responses.append("skip")
|
||||||
|
if allow_never:
|
||||||
|
options += "/(D)on't ask again"
|
||||||
|
valid_responses.append("don't")
|
||||||
|
|
||||||
question += options + " [Yes]: "
|
question += options + " [Yes]: "
|
||||||
|
|
||||||
if subject:
|
if subject:
|
||||||
@@ -459,10 +689,7 @@ class InputOutput:
|
|||||||
else:
|
else:
|
||||||
self.tool_output(subject, bold=True)
|
self.tool_output(subject, bold=True)
|
||||||
|
|
||||||
if self.pretty and self.user_input_color:
|
style = self._get_style()
|
||||||
style = {"": self.user_input_color}
|
|
||||||
else:
|
|
||||||
style = dict()
|
|
||||||
|
|
||||||
def is_valid_response(text):
|
def is_valid_response(text):
|
||||||
if not text:
|
if not text:
|
||||||
@@ -481,7 +708,8 @@ class InputOutput:
|
|||||||
if self.prompt_session:
|
if self.prompt_session:
|
||||||
res = self.prompt_session.prompt(
|
res = self.prompt_session.prompt(
|
||||||
question,
|
question,
|
||||||
style=Style.from_dict(style),
|
style=style,
|
||||||
|
complete_while_typing=False,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
res = input(question)
|
res = input(question)
|
||||||
@@ -499,6 +727,12 @@ class InputOutput:
|
|||||||
|
|
||||||
res = res.lower()[0]
|
res = res.lower()[0]
|
||||||
|
|
||||||
|
if res == "d" and allow_never:
|
||||||
|
self.never_prompts.add(question_id)
|
||||||
|
hist = f"{question.strip()} {res}"
|
||||||
|
self.append_chat_history(hist, linebreak=True, blockquote=True)
|
||||||
|
return False
|
||||||
|
|
||||||
if explicit_yes_required:
|
if explicit_yes_required:
|
||||||
is_yes = res == "y"
|
is_yes = res == "y"
|
||||||
else:
|
else:
|
||||||
@@ -516,19 +750,22 @@ class InputOutput:
|
|||||||
hist = f"{question.strip()} {res}"
|
hist = f"{question.strip()} {res}"
|
||||||
self.append_chat_history(hist, linebreak=True, blockquote=True)
|
self.append_chat_history(hist, linebreak=True, blockquote=True)
|
||||||
|
|
||||||
|
# Restore original multiline mode
|
||||||
|
self.multiline_mode = orig_multiline
|
||||||
|
|
||||||
return is_yes
|
return is_yes
|
||||||
|
|
||||||
def prompt_ask(self, question, default="", subject=None):
|
def prompt_ask(self, question, default="", subject=None):
|
||||||
|
# Temporarily disable multiline mode for prompts
|
||||||
|
orig_multiline = self.multiline_mode
|
||||||
|
self.multiline_mode = False
|
||||||
self.num_user_asks += 1
|
self.num_user_asks += 1
|
||||||
|
|
||||||
if subject:
|
if subject:
|
||||||
self.tool_output()
|
self.tool_output()
|
||||||
self.tool_output(subject, bold=True)
|
self.tool_output(subject, bold=True)
|
||||||
|
|
||||||
if self.pretty and self.user_input_color:
|
style = self._get_style()
|
||||||
style = Style.from_dict({"": self.user_input_color})
|
|
||||||
else:
|
|
||||||
style = None
|
|
||||||
|
|
||||||
if self.yes is True:
|
if self.yes is True:
|
||||||
res = "yes"
|
res = "yes"
|
||||||
@@ -545,6 +782,9 @@ class InputOutput:
|
|||||||
if self.yes in (True, False):
|
if self.yes in (True, False):
|
||||||
self.tool_output(hist)
|
self.tool_output(hist)
|
||||||
|
|
||||||
|
# Restore original multiline mode
|
||||||
|
self.multiline_mode = orig_multiline
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _tool_message(self, message="", strip=True, color=None):
|
def _tool_message(self, message="", strip=True, color=None):
|
||||||
@@ -586,27 +826,46 @@ class InputOutput:
|
|||||||
style = RichStyle(**style)
|
style = RichStyle(**style)
|
||||||
self.console.print(*messages, style=style)
|
self.console.print(*messages, style=style)
|
||||||
|
|
||||||
def assistant_output(self, message, stream=False):
|
def get_assistant_mdstream(self):
|
||||||
mdStream = None
|
mdargs = dict(style=self.assistant_output_color, code_theme=self.code_theme)
|
||||||
|
mdStream = MarkdownStream(mdargs=mdargs)
|
||||||
|
return mdStream
|
||||||
|
|
||||||
|
def assistant_output(self, message, pretty=None):
|
||||||
show_resp = message
|
show_resp = message
|
||||||
|
|
||||||
if self.pretty:
|
# Coder will force pretty off if fence is not triple-backticks
|
||||||
if stream:
|
if pretty is None:
|
||||||
mdargs = dict(style=self.assistant_output_color, code_theme=self.code_theme)
|
pretty = self.pretty
|
||||||
mdStream = MarkdownStream(mdargs=mdargs)
|
|
||||||
else:
|
if pretty:
|
||||||
show_resp = Markdown(
|
show_resp = Markdown(
|
||||||
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 "<no response>")
|
||||||
|
|
||||||
self.console.print(show_resp)
|
self.console.print(show_resp)
|
||||||
return mdStream
|
|
||||||
|
def set_placeholder(self, placeholder):
|
||||||
|
"""Set a one-time placeholder text for the next input prompt."""
|
||||||
|
self.placeholder = placeholder
|
||||||
|
|
||||||
def print(self, message=""):
|
def print(self, message=""):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
|
def toggle_multiline_mode(self):
|
||||||
|
"""Toggle between normal and multiline input modes"""
|
||||||
|
self.multiline_mode = not self.multiline_mode
|
||||||
|
if self.multiline_mode:
|
||||||
|
self.tool_output(
|
||||||
|
"Multiline mode: Enabled. Enter inserts newline, Alt-Enter submits text"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.tool_output(
|
||||||
|
"Multiline mode: Disabled. Alt-Enter inserts newline, Enter submits text"
|
||||||
|
)
|
||||||
|
|
||||||
def append_chat_history(self, text, linebreak=False, blockquote=False, strip=True):
|
def append_chat_history(self, text, linebreak=False, blockquote=False, strip=True):
|
||||||
if blockquote:
|
if blockquote:
|
||||||
if strip:
|
if strip:
|
||||||
@@ -620,11 +879,57 @@ class InputOutput:
|
|||||||
text += "\n"
|
text += "\n"
|
||||||
if self.chat_history_file is not None:
|
if self.chat_history_file is not None:
|
||||||
try:
|
try:
|
||||||
with self.chat_history_file.open("a", encoding=self.encoding) 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):
|
except (PermissionError, OSError) as err:
|
||||||
self.tool_error(
|
print(f"Warning: Unable to write to chat history file {self.chat_history_file}.")
|
||||||
f"Warning: Unable to write to chat history file {self.chat_history_file}."
|
print(err)
|
||||||
" Permission denied."
|
|
||||||
)
|
|
||||||
self.chat_history_file = None # Disable further attempts to write
|
self.chat_history_file = None # Disable further attempts to write
|
||||||
|
|
||||||
|
def format_files_for_input(self, rel_fnames, rel_read_only_fnames):
|
||||||
|
if not self.pretty:
|
||||||
|
read_only_files = []
|
||||||
|
for full_path in sorted(rel_read_only_fnames or []):
|
||||||
|
read_only_files.append(f"{full_path} (read only)")
|
||||||
|
|
||||||
|
editable_files = []
|
||||||
|
for full_path in sorted(rel_fnames):
|
||||||
|
if full_path in rel_read_only_fnames:
|
||||||
|
continue
|
||||||
|
editable_files.append(f"{full_path}")
|
||||||
|
|
||||||
|
return "\n".join(read_only_files + editable_files) + "\n"
|
||||||
|
|
||||||
|
output = StringIO()
|
||||||
|
console = Console(file=output, force_terminal=False)
|
||||||
|
|
||||||
|
read_only_files = sorted(rel_read_only_fnames or [])
|
||||||
|
editable_files = [f for f in sorted(rel_fnames) if f not in rel_read_only_fnames]
|
||||||
|
|
||||||
|
if read_only_files:
|
||||||
|
files_with_label = ["Readonly:"] + read_only_files
|
||||||
|
read_only_output = StringIO()
|
||||||
|
Console(file=read_only_output, force_terminal=False).print(Columns(files_with_label))
|
||||||
|
read_only_lines = read_only_output.getvalue().splitlines()
|
||||||
|
console.print(Columns(files_with_label))
|
||||||
|
|
||||||
|
if editable_files:
|
||||||
|
files_with_label = editable_files
|
||||||
|
if read_only_files:
|
||||||
|
files_with_label = ["Editable:"] + editable_files
|
||||||
|
editable_output = StringIO()
|
||||||
|
Console(file=editable_output, force_terminal=False).print(Columns(files_with_label))
|
||||||
|
editable_lines = editable_output.getvalue().splitlines()
|
||||||
|
|
||||||
|
if len(read_only_lines) > 1 or len(editable_lines) > 1:
|
||||||
|
console.print()
|
||||||
|
console.print(Columns(files_with_label))
|
||||||
|
|
||||||
|
return output.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def get_rel_fname(fname, root):
|
||||||
|
try:
|
||||||
|
return os.path.relpath(fname, root)
|
||||||
|
except ValueError:
|
||||||
|
return fname
|
||||||
|
|||||||
@@ -49,11 +49,11 @@ class Linter:
|
|||||||
try:
|
try:
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
cmd,
|
cmd,
|
||||||
cwd=self.root,
|
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
encoding=self.encoding,
|
encoding=self.encoding,
|
||||||
errors="replace",
|
errors="replace",
|
||||||
|
cwd=self.root,
|
||||||
)
|
)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
print(f"Unable to execute lint command: {err}")
|
print(f"Unable to execute lint command: {err}")
|
||||||
@@ -83,7 +83,11 @@ class Linter:
|
|||||||
|
|
||||||
def lint(self, fname, cmd=None):
|
def lint(self, fname, cmd=None):
|
||||||
rel_fname = self.get_rel_fname(fname)
|
rel_fname = self.get_rel_fname(fname)
|
||||||
code = Path(fname).read_text(encoding=self.encoding, errors="replace")
|
try:
|
||||||
|
code = Path(fname).read_text(encoding=self.encoding, errors="replace")
|
||||||
|
except OSError as err:
|
||||||
|
print(f"Unable to read {fname}: {err}")
|
||||||
|
return
|
||||||
|
|
||||||
if cmd:
|
if cmd:
|
||||||
cmd = cmd.strip()
|
cmd = cmd.strip()
|
||||||
@@ -148,12 +152,12 @@ class Linter:
|
|||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
flake8_cmd,
|
flake8_cmd,
|
||||||
cwd=self.root,
|
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
check=False,
|
check=False,
|
||||||
encoding=self.encoding,
|
encoding=self.encoding,
|
||||||
errors="replace",
|
errors="replace",
|
||||||
|
cwd=self.root,
|
||||||
)
|
)
|
||||||
errors = result.stdout + result.stderr
|
errors = result.stdout + result.stderr
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -211,13 +215,18 @@ def basic_lint(fname, code):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
parser = get_parser(lang)
|
parser = get_parser(lang)
|
||||||
except OSError as err:
|
except Exception as err:
|
||||||
print(f"Unable to load parser: {err}")
|
print(f"Unable to load parser: {err}")
|
||||||
return
|
return
|
||||||
|
|
||||||
tree = parser.parse(bytes(code, "utf-8"))
|
tree = parser.parse(bytes(code, "utf-8"))
|
||||||
|
|
||||||
errors = traverse_tree(tree.root_node)
|
try:
|
||||||
|
errors = traverse_tree(tree.root_node)
|
||||||
|
except RecursionError:
|
||||||
|
print(f"Unable to lint {fname} due to RecursionError")
|
||||||
|
return
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ os.environ["LITELLM_MODE"] = "PRODUCTION"
|
|||||||
|
|
||||||
# `import litellm` takes 1.5 seconds, defer it!
|
# `import litellm` takes 1.5 seconds, defer it!
|
||||||
|
|
||||||
|
VERBOSE = False
|
||||||
|
|
||||||
|
|
||||||
class LazyLiteLLM:
|
class LazyLiteLLM:
|
||||||
_lazy_module = None
|
_lazy_module = None
|
||||||
@@ -27,6 +29,9 @@ class LazyLiteLLM:
|
|||||||
if self._lazy_module is not None:
|
if self._lazy_module is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if VERBOSE:
|
||||||
|
print("Loading litellm...")
|
||||||
|
|
||||||
self._lazy_module = importlib.import_module("litellm")
|
self._lazy_module = importlib.import_module("litellm")
|
||||||
|
|
||||||
self._lazy_module.suppress_debug_info = True
|
self._lazy_module.suppress_debug_info = True
|
||||||
|
|||||||
463
aider/main.py
463
aider/main.py
@@ -5,27 +5,56 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
import webbrowser
|
||||||
|
from dataclasses import fields
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import git
|
try:
|
||||||
|
import git
|
||||||
|
except ImportError:
|
||||||
|
git = None
|
||||||
|
|
||||||
|
import importlib_resources
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from prompt_toolkit.enums import EditingMode
|
from prompt_toolkit.enums import EditingMode
|
||||||
|
|
||||||
from aider import __version__, models, utils
|
from aider import __version__, models, urls, utils
|
||||||
|
from aider.analytics import Analytics
|
||||||
from aider.args import get_parser
|
from aider.args import get_parser
|
||||||
from aider.coders import Coder
|
from aider.coders import Coder
|
||||||
|
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.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.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
|
||||||
|
from aider.watch import FileWatcher
|
||||||
|
|
||||||
from .dump import dump # noqa: F401
|
from .dump import dump # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
def check_config_files_for_yes(config_files):
|
||||||
|
found = False
|
||||||
|
for config_file in config_files:
|
||||||
|
if Path(config_file).exists():
|
||||||
|
try:
|
||||||
|
with open(config_file, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
if line.strip().startswith("yes:"):
|
||||||
|
print("Configuration error detected.")
|
||||||
|
print(f"The file {config_file} contains a line starting with 'yes:'")
|
||||||
|
print("Please replace 'yes:' with 'yes-always:' in this file.")
|
||||||
|
found = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
def get_git_root():
|
def get_git_root():
|
||||||
"""Try and guess the git repo, since the conf.yml can be at the repo root"""
|
"""Try and guess the git repo, since the conf.yml can be at the repo root"""
|
||||||
try:
|
try:
|
||||||
@@ -40,7 +69,7 @@ def guessed_wrong_repo(io, git_root, fnames, git_dname):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
check_repo = Path(GitRepo(io, fnames, git_dname).root).resolve()
|
check_repo = Path(GitRepo(io, fnames, git_dname).root).resolve()
|
||||||
except FileNotFoundError:
|
except (OSError,) + ANY_GIT_ERROR:
|
||||||
return
|
return
|
||||||
|
|
||||||
# we had no guess, rely on the "true" repo result
|
# we had no guess, rely on the "true" repo result
|
||||||
@@ -68,15 +97,28 @@ def make_new_repo(git_root, io):
|
|||||||
|
|
||||||
|
|
||||||
def setup_git(git_root, io):
|
def setup_git(git_root, io):
|
||||||
|
if git is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
cwd = Path.cwd()
|
||||||
|
except OSError:
|
||||||
|
cwd = None
|
||||||
|
|
||||||
repo = None
|
repo = None
|
||||||
|
|
||||||
if git_root:
|
if git_root:
|
||||||
repo = git.Repo(git_root)
|
try:
|
||||||
elif Path.cwd() == Path.home():
|
repo = git.Repo(git_root)
|
||||||
|
except ANY_GIT_ERROR:
|
||||||
|
pass
|
||||||
|
elif cwd == Path.home():
|
||||||
io.tool_warning("You should probably run aider in a directory, not your home dir.")
|
io.tool_warning("You should probably run aider in a directory, not your home dir.")
|
||||||
return
|
return
|
||||||
elif io.confirm_ask("No git repo found, create one to track aider's changes (recommended)?"):
|
elif cwd and io.confirm_ask(
|
||||||
git_root = str(Path.cwd().resolve())
|
"No git repo found, create one to track aider's changes (recommended)?"
|
||||||
|
):
|
||||||
|
git_root = str(cwd.resolve())
|
||||||
repo = make_new_repo(git_root, io)
|
repo = make_new_repo(git_root, io)
|
||||||
|
|
||||||
if not repo:
|
if not repo:
|
||||||
@@ -114,32 +156,51 @@ def check_gitignore(git_root, io, ask=True):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
repo = git.Repo(git_root)
|
repo = git.Repo(git_root)
|
||||||
if repo.ignored(".aider"):
|
if repo.ignored(".aider") and repo.ignored(".env"):
|
||||||
return
|
return
|
||||||
except ANY_GIT_ERROR:
|
except ANY_GIT_ERROR:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
pat = ".aider*"
|
patterns = [".aider*", ".env"]
|
||||||
|
patterns_to_add = []
|
||||||
|
|
||||||
gitignore_file = Path(git_root) / ".gitignore"
|
gitignore_file = Path(git_root) / ".gitignore"
|
||||||
if gitignore_file.exists():
|
if gitignore_file.exists():
|
||||||
content = io.read_text(gitignore_file)
|
try:
|
||||||
if content is None:
|
content = io.read_text(gitignore_file)
|
||||||
return
|
if content is None:
|
||||||
if pat in content.splitlines():
|
return
|
||||||
|
existing_lines = content.splitlines()
|
||||||
|
for pat in patterns:
|
||||||
|
if pat not in existing_lines:
|
||||||
|
patterns_to_add.append(pat)
|
||||||
|
except OSError as e:
|
||||||
|
io.tool_error(f"Error when trying to read {gitignore_file}: {e}")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
content = ""
|
content = ""
|
||||||
|
patterns_to_add = patterns
|
||||||
|
|
||||||
if ask and not io.confirm_ask(f"Add {pat} to .gitignore (recommended)?"):
|
if not patterns_to_add:
|
||||||
|
return
|
||||||
|
|
||||||
|
if ask and not io.confirm_ask(f"Add {', '.join(patterns_to_add)} to .gitignore (recommended)?"):
|
||||||
return
|
return
|
||||||
|
|
||||||
if content and not content.endswith("\n"):
|
if content and not content.endswith("\n"):
|
||||||
content += "\n"
|
content += "\n"
|
||||||
content += pat + "\n"
|
content += "\n".join(patterns_to_add) + "\n"
|
||||||
io.write_text(gitignore_file, content)
|
|
||||||
|
|
||||||
io.tool_output(f"Added {pat} to .gitignore")
|
try:
|
||||||
|
io.write_text(gitignore_file, content)
|
||||||
|
io.tool_output(f"Added {', '.join(patterns_to_add)} to .gitignore")
|
||||||
|
except OSError as e:
|
||||||
|
io.tool_error(f"Error when trying to write to {gitignore_file}: {e}")
|
||||||
|
io.tool_output(
|
||||||
|
"Try running with appropriate permissions or manually add these patterns to .gitignore:"
|
||||||
|
)
|
||||||
|
for pattern in patterns_to_add:
|
||||||
|
io.tool_output(f" {pattern}")
|
||||||
|
|
||||||
|
|
||||||
def check_streamlit_install(io):
|
def check_streamlit_install(io):
|
||||||
@@ -169,7 +230,10 @@ def launch_gui(args):
|
|||||||
"--server.runOnSave=false",
|
"--server.runOnSave=false",
|
||||||
]
|
]
|
||||||
|
|
||||||
if "-dev" in __version__:
|
# https://github.com/Aider-AI/aider/issues/2193
|
||||||
|
is_dev = "-dev" in str(__version__)
|
||||||
|
|
||||||
|
if is_dev:
|
||||||
print("Watching for file changes.")
|
print("Watching for file changes.")
|
||||||
else:
|
else:
|
||||||
st_args += [
|
st_args += [
|
||||||
@@ -217,16 +281,23 @@ def parse_lint_cmds(lint_cmds, io):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def generate_search_path_list(default_fname, git_root, command_line_file):
|
def generate_search_path_list(default_file, git_root, command_line_file):
|
||||||
files = []
|
files = []
|
||||||
default_file = Path(default_fname)
|
|
||||||
files.append(Path.home() / default_file) # homedir
|
files.append(Path.home() / default_file) # homedir
|
||||||
if git_root:
|
if git_root:
|
||||||
files.append(Path(git_root) / default_file) # git root
|
files.append(Path(git_root) / default_file) # git root
|
||||||
files.append(default_file.resolve())
|
files.append(default_file)
|
||||||
if command_line_file:
|
if command_line_file:
|
||||||
files.append(command_line_file)
|
files.append(command_line_file)
|
||||||
files = [Path(fn).resolve() for fn in files]
|
|
||||||
|
resolved_files = []
|
||||||
|
for fn in files:
|
||||||
|
try:
|
||||||
|
resolved_files.append(Path(fn).resolve())
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
files = resolved_files
|
||||||
files.reverse()
|
files.reverse()
|
||||||
uniq = []
|
uniq = []
|
||||||
for fn in files:
|
for fn in files:
|
||||||
@@ -266,7 +337,7 @@ def register_models(git_root, model_settings_fname, io, verbose=False):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_dotenv_files(git_root, dotenv_fname):
|
def load_dotenv_files(git_root, dotenv_fname, encoding="utf-8"):
|
||||||
dotenv_files = generate_search_path_list(
|
dotenv_files = generate_search_path_list(
|
||||||
".env",
|
".env",
|
||||||
git_root,
|
git_root,
|
||||||
@@ -274,14 +345,25 @@ def load_dotenv_files(git_root, dotenv_fname):
|
|||||||
)
|
)
|
||||||
loaded = []
|
loaded = []
|
||||||
for fname in dotenv_files:
|
for fname in dotenv_files:
|
||||||
if Path(fname).exists():
|
try:
|
||||||
loaded.append(fname)
|
if Path(fname).exists():
|
||||||
load_dotenv(fname, override=True)
|
load_dotenv(fname, override=True, encoding=encoding)
|
||||||
|
loaded.append(fname)
|
||||||
|
except OSError as e:
|
||||||
|
print(f"OSError loading {fname}: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading {fname}: {e}")
|
||||||
return loaded
|
return loaded
|
||||||
|
|
||||||
|
|
||||||
def register_litellm_models(git_root, model_metadata_fname, io, verbose=False):
|
def register_litellm_models(git_root, model_metadata_fname, io, verbose=False):
|
||||||
model_metatdata_files = generate_search_path_list(
|
model_metatdata_files = []
|
||||||
|
|
||||||
|
# Add the resource file path
|
||||||
|
resource_metadata = importlib_resources.files("aider.resources").joinpath("model-metadata.json")
|
||||||
|
model_metatdata_files.append(str(resource_metadata))
|
||||||
|
|
||||||
|
model_metatdata_files += generate_search_path_list(
|
||||||
".aider.model.metadata.json", git_root, model_metadata_fname
|
".aider.model.metadata.json", git_root, model_metadata_fname
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -304,6 +386,7 @@ def sanity_check_repo(repo, io):
|
|||||||
io.tool_error("The git repo does not seem to have a working tree?")
|
io.tool_error("The git repo does not seem to have a working tree?")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
bad_ver = False
|
||||||
try:
|
try:
|
||||||
repo.get_tracked_files()
|
repo.get_tracked_files()
|
||||||
if not repo.git_repo_error:
|
if not repo.git_repo_error:
|
||||||
@@ -320,7 +403,7 @@ def sanity_check_repo(repo, io):
|
|||||||
io.tool_error("Aider only works with git repos with version number 1 or 2.")
|
io.tool_error("Aider only works with git repos with version number 1 or 2.")
|
||||||
io.tool_output("You may be able to convert your repo: git update-index --index-version=2")
|
io.tool_output("You may be able to convert your repo: git update-index --index-version=2")
|
||||||
io.tool_output("Or run aider --no-git to proceed without using git.")
|
io.tool_output("Or run aider --no-git to proceed without using git.")
|
||||||
io.tool_output("https://github.com/paul-gauthier/aider/issues/211")
|
io.offer_url(urls.git_index_version, "Open documentation url for more info?")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
io.tool_error("Unable to read git repository, it may be corrupt?")
|
io.tool_error("Unable to read git repository, it may be corrupt?")
|
||||||
@@ -334,14 +417,21 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
if force_git_root:
|
if git is None:
|
||||||
|
git_root = None
|
||||||
|
elif force_git_root:
|
||||||
git_root = force_git_root
|
git_root = force_git_root
|
||||||
else:
|
else:
|
||||||
git_root = get_git_root()
|
git_root = get_git_root()
|
||||||
|
|
||||||
conf_fname = Path(".aider.conf.yml")
|
conf_fname = Path(".aider.conf.yml")
|
||||||
|
|
||||||
default_config_files = [conf_fname.resolve()] # CWD
|
default_config_files = []
|
||||||
|
try:
|
||||||
|
default_config_files += [conf_fname.resolve()] # CWD
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
if git_root:
|
if git_root:
|
||||||
git_conf = Path(git_root) / conf_fname # git root
|
git_conf = Path(git_root) / conf_fname # git root
|
||||||
if git_conf not in default_config_files:
|
if git_conf not in default_config_files:
|
||||||
@@ -350,7 +440,13 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
default_config_files = list(map(str, default_config_files))
|
default_config_files = list(map(str, default_config_files))
|
||||||
|
|
||||||
parser = get_parser(default_config_files, git_root)
|
parser = get_parser(default_config_files, git_root)
|
||||||
args, unknown = parser.parse_known_args(argv)
|
try:
|
||||||
|
args, unknown = parser.parse_known_args(argv)
|
||||||
|
except AttributeError as e:
|
||||||
|
if all(word in str(e) for word in ["bool", "object", "has", "no", "attribute", "strip"]):
|
||||||
|
if check_config_files_for_yes(default_config_files):
|
||||||
|
return 1
|
||||||
|
raise e
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print("Config files search order, if no --config:")
|
print("Config files search order, if no --config:")
|
||||||
@@ -361,19 +457,33 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
default_config_files.reverse()
|
default_config_files.reverse()
|
||||||
|
|
||||||
parser = get_parser(default_config_files, git_root)
|
parser = get_parser(default_config_files, git_root)
|
||||||
|
|
||||||
args, unknown = parser.parse_known_args(argv)
|
args, unknown = parser.parse_known_args(argv)
|
||||||
|
|
||||||
# Load the .env file specified in the arguments
|
# Load the .env file specified in the arguments
|
||||||
loaded_dotenvs = load_dotenv_files(git_root, args.env_file)
|
loaded_dotenvs = load_dotenv_files(git_root, args.env_file, args.encoding)
|
||||||
|
|
||||||
# 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 git is None:
|
||||||
|
args.git = False
|
||||||
|
|
||||||
|
if args.analytics_disable:
|
||||||
|
analytics = Analytics(permanently_disable=True)
|
||||||
|
print("Analytics have been permanently disabled.")
|
||||||
|
|
||||||
if not args.verify_ssl:
|
if not args.verify_ssl:
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
|
os.environ["SSL_VERIFY"] = ""
|
||||||
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)
|
||||||
|
|
||||||
|
if args.timeout:
|
||||||
|
litellm._load_litellm()
|
||||||
|
litellm._lazy_module.request_timeout = args.timeout
|
||||||
|
|
||||||
if args.dark_mode:
|
if args.dark_mode:
|
||||||
args.user_input_color = "#32FF32"
|
args.user_input_color = "#32FF32"
|
||||||
@@ -389,28 +499,35 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
args.assistant_output_color = "blue"
|
args.assistant_output_color = "blue"
|
||||||
args.code_theme = "default"
|
args.code_theme = "default"
|
||||||
|
|
||||||
if return_coder and args.yes is None:
|
if return_coder and args.yes_always is None:
|
||||||
args.yes = True
|
args.yes_always = True
|
||||||
|
|
||||||
editing_mode = EditingMode.VI if args.vim else EditingMode.EMACS
|
editing_mode = EditingMode.VI if args.vim else EditingMode.EMACS
|
||||||
|
|
||||||
def get_io(pretty):
|
def get_io(pretty):
|
||||||
return InputOutput(
|
return InputOutput(
|
||||||
pretty,
|
pretty,
|
||||||
args.yes,
|
args.yes_always,
|
||||||
args.input_history_file,
|
args.input_history_file,
|
||||||
args.chat_history_file,
|
args.chat_history_file,
|
||||||
input=input,
|
input=input,
|
||||||
output=output,
|
output=output,
|
||||||
user_input_color=args.user_input_color,
|
user_input_color=args.user_input_color,
|
||||||
tool_output_color=args.tool_output_color,
|
tool_output_color=args.tool_output_color,
|
||||||
|
tool_warning_color=args.tool_warning_color,
|
||||||
tool_error_color=args.tool_error_color,
|
tool_error_color=args.tool_error_color,
|
||||||
|
completion_menu_color=args.completion_menu_color,
|
||||||
|
completion_menu_bg_color=args.completion_menu_bg_color,
|
||||||
|
completion_menu_current_color=args.completion_menu_current_color,
|
||||||
|
completion_menu_current_bg_color=args.completion_menu_current_bg_color,
|
||||||
assistant_output_color=args.assistant_output_color,
|
assistant_output_color=args.assistant_output_color,
|
||||||
code_theme=args.code_theme,
|
code_theme=args.code_theme,
|
||||||
dry_run=args.dry_run,
|
dry_run=args.dry_run,
|
||||||
encoding=args.encoding,
|
encoding=args.encoding,
|
||||||
llm_history_file=args.llm_history_file,
|
llm_history_file=args.llm_history_file,
|
||||||
editingmode=editing_mode,
|
editingmode=editing_mode,
|
||||||
|
fancy_input=args.fancy_input,
|
||||||
|
multiline_mode=args.multiline,
|
||||||
)
|
)
|
||||||
|
|
||||||
io = get_io(args.pretty)
|
io = get_io(args.pretty)
|
||||||
@@ -422,10 +539,82 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
io = get_io(False)
|
io = get_io(False)
|
||||||
io.tool_warning("Terminal does not support pretty output (UnicodeDecodeError)")
|
io.tool_warning("Terminal does not support pretty output (UnicodeDecodeError)")
|
||||||
|
|
||||||
|
# Process any environment variables set via --set-env
|
||||||
|
if args.set_env:
|
||||||
|
for env_setting in args.set_env:
|
||||||
|
try:
|
||||||
|
name, value = env_setting.split("=", 1)
|
||||||
|
os.environ[name.strip()] = value.strip()
|
||||||
|
except ValueError:
|
||||||
|
io.tool_error(f"Invalid --set-env format: {env_setting}")
|
||||||
|
io.tool_output("Format should be: ENV_VAR_NAME=value")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Process any API keys set via --api-key
|
||||||
|
if args.api_key:
|
||||||
|
for api_setting in args.api_key:
|
||||||
|
try:
|
||||||
|
provider, key = api_setting.split("=", 1)
|
||||||
|
env_var = f"{provider.strip().upper()}_API_KEY"
|
||||||
|
os.environ[env_var] = key.strip()
|
||||||
|
except ValueError:
|
||||||
|
io.tool_error(f"Invalid --api-key format: {api_setting}")
|
||||||
|
io.tool_output("Format should be: provider=key")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if args.anthropic_api_key:
|
||||||
|
os.environ["ANTHROPIC_API_KEY"] = args.anthropic_api_key
|
||||||
|
|
||||||
|
if args.openai_api_key:
|
||||||
|
os.environ["OPENAI_API_KEY"] = args.openai_api_key
|
||||||
|
if args.openai_api_base:
|
||||||
|
os.environ["OPENAI_API_BASE"] = args.openai_api_base
|
||||||
|
if args.openai_api_version:
|
||||||
|
io.tool_warning(
|
||||||
|
"--openai-api-version is deprecated, use --set-env OPENAI_API_VERSION=<value>"
|
||||||
|
)
|
||||||
|
os.environ["OPENAI_API_VERSION"] = args.openai_api_version
|
||||||
|
if args.openai_api_type:
|
||||||
|
io.tool_warning("--openai-api-type is deprecated, use --set-env OPENAI_API_TYPE=<value>")
|
||||||
|
os.environ["OPENAI_API_TYPE"] = args.openai_api_type
|
||||||
|
if args.openai_organization_id:
|
||||||
|
io.tool_warning(
|
||||||
|
"--openai-organization-id is deprecated, use --set-env OPENAI_ORGANIZATION=<value>"
|
||||||
|
)
|
||||||
|
os.environ["OPENAI_ORGANIZATION"] = args.openai_organization_id
|
||||||
|
|
||||||
|
analytics = Analytics(logfile=args.analytics_log, permanently_disable=args.analytics_disable)
|
||||||
|
if args.analytics is not False:
|
||||||
|
if analytics.need_to_ask(args.analytics):
|
||||||
|
io.tool_output(
|
||||||
|
"Aider respects your privacy and never collects your code, chat messages, keys or"
|
||||||
|
" personal info."
|
||||||
|
)
|
||||||
|
io.tool_output(f"For more info: {urls.analytics}")
|
||||||
|
disable = not io.confirm_ask(
|
||||||
|
"Allow collection of anonymous analytics to help improve aider?"
|
||||||
|
)
|
||||||
|
|
||||||
|
analytics.asked_opt_in = True
|
||||||
|
if disable:
|
||||||
|
analytics.disable(permanently=True)
|
||||||
|
io.tool_output("Analytics have been permanently disabled.")
|
||||||
|
|
||||||
|
analytics.save_data()
|
||||||
|
io.tool_output()
|
||||||
|
|
||||||
|
# This is a no-op if the user has opted out
|
||||||
|
analytics.enable()
|
||||||
|
|
||||||
|
analytics.event("launched")
|
||||||
|
|
||||||
if args.gui and not return_coder:
|
if args.gui and not return_coder:
|
||||||
if not check_streamlit_install(io):
|
if not check_streamlit_install(io):
|
||||||
|
analytics.event("exit", reason="Streamlit not installed")
|
||||||
return
|
return
|
||||||
|
analytics.event("gui session")
|
||||||
launch_gui(argv)
|
launch_gui(argv)
|
||||||
|
analytics.event("exit", reason="GUI session ended")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
@@ -434,7 +623,14 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
|
|
||||||
all_files = args.files + (args.file or [])
|
all_files = args.files + (args.file or [])
|
||||||
fnames = [str(Path(fn).resolve()) for fn in all_files]
|
fnames = [str(Path(fn).resolve()) for fn in all_files]
|
||||||
read_only_fnames = [str(Path(fn).resolve()) for fn in (args.read or [])]
|
read_only_fnames = []
|
||||||
|
for fn in args.read or []:
|
||||||
|
path = Path(fn).expanduser().resolve()
|
||||||
|
if path.is_dir():
|
||||||
|
read_only_fnames.extend(str(f) for f in path.rglob("*") if f.is_file())
|
||||||
|
else:
|
||||||
|
read_only_fnames.append(str(path))
|
||||||
|
|
||||||
if len(all_files) > 1:
|
if len(all_files) > 1:
|
||||||
good = True
|
good = True
|
||||||
for fname in all_files:
|
for fname in all_files:
|
||||||
@@ -445,6 +641,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
io.tool_output(
|
io.tool_output(
|
||||||
"Provide either a single directory of a git repo, or a list of one or more files."
|
"Provide either a single directory of a git repo, or a list of one or more files."
|
||||||
)
|
)
|
||||||
|
analytics.event("exit", reason="Invalid directory input")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
git_dname = None
|
git_dname = None
|
||||||
@@ -455,26 +652,31 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
fnames = []
|
fnames = []
|
||||||
else:
|
else:
|
||||||
io.tool_error(f"{all_files[0]} is a directory, but --no-git selected.")
|
io.tool_error(f"{all_files[0]} is a directory, but --no-git selected.")
|
||||||
|
analytics.event("exit", reason="Directory with --no-git")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# We can't know the git repo for sure until after parsing the args.
|
# We can't know the git repo for sure until after parsing the args.
|
||||||
# If we guessed wrong, reparse because that changes things like
|
# If we guessed wrong, reparse because that changes things like
|
||||||
# the location of the config.yml and history files.
|
# the location of the config.yml and history files.
|
||||||
if args.git and not force_git_root:
|
if args.git and not force_git_root and git is not None:
|
||||||
right_repo_root = guessed_wrong_repo(io, git_root, fnames, git_dname)
|
right_repo_root = guessed_wrong_repo(io, git_root, fnames, git_dname)
|
||||||
if right_repo_root:
|
if right_repo_root:
|
||||||
|
analytics.event("exit", reason="Recursing with correct repo")
|
||||||
return main(argv, input, output, right_repo_root, return_coder=return_coder)
|
return main(argv, input, output, right_repo_root, return_coder=return_coder)
|
||||||
|
|
||||||
if args.just_check_update:
|
if args.just_check_update:
|
||||||
update_available = check_version(io, just_check=True, verbose=args.verbose)
|
update_available = check_version(io, just_check=True, verbose=args.verbose)
|
||||||
|
analytics.event("exit", reason="Just checking update")
|
||||||
return 0 if not update_available else 1
|
return 0 if not update_available else 1
|
||||||
|
|
||||||
if args.install_main_branch:
|
if args.install_main_branch:
|
||||||
success = install_from_main_branch(io)
|
success = install_from_main_branch(io)
|
||||||
|
analytics.event("exit", reason="Installed main branch")
|
||||||
return 0 if success else 1
|
return 0 if success else 1
|
||||||
|
|
||||||
if args.upgrade:
|
if args.upgrade:
|
||||||
success = install_upgrade(io)
|
success = install_upgrade(io)
|
||||||
|
analytics.event("exit", reason="Upgrade completed")
|
||||||
return 0 if success else 1
|
return 0 if success else 1
|
||||||
|
|
||||||
if args.check_update:
|
if args.check_update:
|
||||||
@@ -482,6 +684,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
|
|
||||||
if args.list_models:
|
if args.list_models:
|
||||||
models.print_matching_models(io, args.list_models)
|
models.print_matching_models(io, args.list_models)
|
||||||
|
analytics.event("exit", reason="Listed models")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if args.git:
|
if args.git:
|
||||||
@@ -497,49 +700,67 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
cmd_line = scrub_sensitive_info(args, cmd_line)
|
cmd_line = scrub_sensitive_info(args, cmd_line)
|
||||||
io.tool_output(cmd_line, log_only=True)
|
io.tool_output(cmd_line, log_only=True)
|
||||||
|
|
||||||
check_and_load_imports(io, verbose=args.verbose)
|
is_first_run = is_first_run_of_new_version(io, verbose=args.verbose)
|
||||||
|
check_and_load_imports(io, is_first_run, verbose=args.verbose)
|
||||||
if args.anthropic_api_key:
|
|
||||||
os.environ["ANTHROPIC_API_KEY"] = args.anthropic_api_key
|
|
||||||
|
|
||||||
if args.openai_api_key:
|
|
||||||
os.environ["OPENAI_API_KEY"] = args.openai_api_key
|
|
||||||
if args.openai_api_base:
|
|
||||||
os.environ["OPENAI_API_BASE"] = args.openai_api_base
|
|
||||||
if args.openai_api_version:
|
|
||||||
os.environ["OPENAI_API_VERSION"] = args.openai_api_version
|
|
||||||
if args.openai_api_type:
|
|
||||||
os.environ["OPENAI_API_TYPE"] = args.openai_api_type
|
|
||||||
if args.openai_organization_id:
|
|
||||||
os.environ["OPENAI_ORGANIZATION"] = args.openai_organization_id
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
# Process any command line aliases
|
||||||
|
if args.alias:
|
||||||
|
for alias_def in args.alias:
|
||||||
|
# Split on first colon only
|
||||||
|
parts = alias_def.split(":", 1)
|
||||||
|
if len(parts) != 2:
|
||||||
|
io.tool_error(f"Invalid alias format: {alias_def}")
|
||||||
|
io.tool_output("Format should be: alias:model-name")
|
||||||
|
analytics.event("exit", reason="Invalid alias format error")
|
||||||
|
return 1
|
||||||
|
alias, model = parts
|
||||||
|
models.MODEL_ALIASES[alias.strip()] = model.strip()
|
||||||
|
|
||||||
if not args.model:
|
if not args.model:
|
||||||
args.model = "gpt-4o-2024-08-06"
|
args.model = "gpt-4o-2024-08-06"
|
||||||
if os.environ.get("ANTHROPIC_API_KEY"):
|
if os.environ.get("ANTHROPIC_API_KEY"):
|
||||||
args.model = "claude-3-5-sonnet-20240620"
|
args.model = "claude-3-5-sonnet-20241022"
|
||||||
|
|
||||||
main_model = models.Model(args.model, weak_model=args.weak_model)
|
main_model = models.Model(
|
||||||
|
args.model,
|
||||||
|
weak_model=args.weak_model,
|
||||||
|
editor_model=args.editor_model,
|
||||||
|
editor_edit_format=args.editor_edit_format,
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.copy_paste and args.edit_format is None:
|
||||||
|
if main_model.edit_format in ("diff", "whole"):
|
||||||
|
main_model.edit_format = "editor-" + main_model.edit_format
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
io.tool_output("Model info:")
|
io.tool_output("Model metadata:")
|
||||||
io.tool_output(json.dumps(main_model.info, indent=4))
|
io.tool_output(json.dumps(main_model.info, indent=4))
|
||||||
|
|
||||||
|
io.tool_output("Model settings:")
|
||||||
|
for attr in sorted(fields(ModelSettings), key=lambda x: x.name):
|
||||||
|
val = getattr(main_model, attr.name)
|
||||||
|
val = json.dumps(val, indent=4)
|
||||||
|
io.tool_output(f"{attr.name}: {val}")
|
||||||
|
|
||||||
lint_cmds = parse_lint_cmds(args.lint_cmd, io)
|
lint_cmds = parse_lint_cmds(args.lint_cmd, io)
|
||||||
if lint_cmds is None:
|
if lint_cmds is None:
|
||||||
|
analytics.event("exit", reason="Invalid lint command format")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if args.show_model_warnings:
|
if args.show_model_warnings:
|
||||||
problem = models.sanity_check_models(io, main_model)
|
problem = models.sanity_check_models(io, main_model)
|
||||||
if problem:
|
if problem:
|
||||||
|
analytics.event("model warning", main_model=main_model)
|
||||||
io.tool_output("You can skip this check with --no-show-model-warnings")
|
io.tool_output("You can skip this check with --no-show-model-warnings")
|
||||||
io.tool_output()
|
|
||||||
try:
|
try:
|
||||||
if not io.confirm_ask("Proceed anyway?"):
|
io.offer_url(urls.model_warnings, "Open documentation url for more info?")
|
||||||
return 1
|
io.tool_output()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
analytics.event("exit", reason="Keyboard interrupt during model warnings")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
repo = None
|
repo = None
|
||||||
@@ -561,11 +782,24 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not sanity_check_repo(repo, io):
|
if not args.skip_sanity_check_repo:
|
||||||
return 1
|
if not sanity_check_repo(repo, io):
|
||||||
|
analytics.event("exit", reason="Repository sanity check failed")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if repo:
|
||||||
|
analytics.event("repo", num_files=len(repo.get_tracked_files()))
|
||||||
|
else:
|
||||||
|
analytics.event("no-repo")
|
||||||
|
|
||||||
commands = Commands(
|
commands = Commands(
|
||||||
io, None, verify_ssl=args.verify_ssl, args=args, parser=parser, verbose=args.verbose
|
io,
|
||||||
|
None,
|
||||||
|
verify_ssl=args.verify_ssl,
|
||||||
|
args=args,
|
||||||
|
parser=parser,
|
||||||
|
verbose=args.verbose,
|
||||||
|
editor=args.editor,
|
||||||
)
|
)
|
||||||
|
|
||||||
summarizer = ChatSummary(
|
summarizer = ChatSummary(
|
||||||
@@ -579,7 +813,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
if not main_model.streaming:
|
if not main_model.streaming:
|
||||||
if args.stream:
|
if args.stream:
|
||||||
io.tool_warning(
|
io.tool_warning(
|
||||||
"Warning: Streaming is not supported by the selected model. Disabling streaming."
|
f"Warning: Streaming is not supported by {main_model.name}. Disabling streaming."
|
||||||
)
|
)
|
||||||
args.stream = False
|
args.stream = False
|
||||||
|
|
||||||
@@ -606,20 +840,46 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
test_cmd=args.test_cmd,
|
test_cmd=args.test_cmd,
|
||||||
commands=commands,
|
commands=commands,
|
||||||
summarizer=summarizer,
|
summarizer=summarizer,
|
||||||
|
analytics=analytics,
|
||||||
map_refresh=args.map_refresh,
|
map_refresh=args.map_refresh,
|
||||||
cache_prompts=args.cache_prompts,
|
cache_prompts=args.cache_prompts,
|
||||||
map_mul_no_files=args.map_multiplier_no_files,
|
map_mul_no_files=args.map_multiplier_no_files,
|
||||||
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,
|
||||||
|
detect_urls=args.detect_urls,
|
||||||
|
auto_copy_context=args.copy_paste,
|
||||||
)
|
)
|
||||||
|
except UnknownEditFormat as err:
|
||||||
|
io.tool_error(str(err))
|
||||||
|
io.offer_url(urls.edit_formats, "Open documentation about edit formats?")
|
||||||
|
analytics.event("exit", reason="Unknown edit format")
|
||||||
|
return 1
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
io.tool_error(str(err))
|
io.tool_error(str(err))
|
||||||
|
analytics.event("exit", reason="ValueError during coder creation")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if return_coder:
|
if return_coder:
|
||||||
|
analytics.event("exit", reason="Returning coder object")
|
||||||
return coder
|
return coder
|
||||||
|
|
||||||
|
ignores = []
|
||||||
|
if git_root:
|
||||||
|
ignores.append(str(Path(git_root) / ".gitignore"))
|
||||||
|
if args.aiderignore:
|
||||||
|
ignores.append(args.aiderignore)
|
||||||
|
|
||||||
|
if args.watch_files:
|
||||||
|
file_watcher = FileWatcher(
|
||||||
|
coder, gitignores=ignores, verbose=args.verbose, analytics=analytics
|
||||||
|
)
|
||||||
|
coder.file_watcher = file_watcher
|
||||||
|
|
||||||
|
if args.copy_paste:
|
||||||
|
analytics.event("copy-paste mode")
|
||||||
|
ClipboardWatcher(coder.io, verbose=args.verbose)
|
||||||
|
|
||||||
coder.show_announcements()
|
coder.show_announcements()
|
||||||
|
|
||||||
if args.show_prompts:
|
if args.show_prompts:
|
||||||
@@ -628,6 +888,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
]
|
]
|
||||||
messages = coder.format_messages().all_messages()
|
messages = coder.format_messages().all_messages()
|
||||||
utils.show_messages(messages)
|
utils.show_messages(messages)
|
||||||
|
analytics.event("exit", reason="Showed prompts")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.lint:
|
if args.lint:
|
||||||
@@ -636,10 +897,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
if args.test:
|
if args.test:
|
||||||
if not args.test_cmd:
|
if not args.test_cmd:
|
||||||
io.tool_error("No --test-cmd provided.")
|
io.tool_error("No --test-cmd provided.")
|
||||||
|
analytics.event("exit", reason="No test command provided")
|
||||||
return 1
|
return 1
|
||||||
test_errors = coder.commands.cmd_test(args.test_cmd)
|
coder.commands.cmd_test(args.test_cmd)
|
||||||
if test_errors:
|
if io.placeholder:
|
||||||
coder.run(test_errors)
|
coder.run(io.placeholder)
|
||||||
|
|
||||||
if args.commit:
|
if args.commit:
|
||||||
if args.dry_run:
|
if args.dry_run:
|
||||||
@@ -648,27 +910,41 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
coder.commands.cmd_commit()
|
coder.commands.cmd_commit()
|
||||||
|
|
||||||
if args.lint or args.test or args.commit:
|
if args.lint or args.test or args.commit:
|
||||||
|
analytics.event("exit", reason="Completed lint/test/commit")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.show_repo_map:
|
if args.show_repo_map:
|
||||||
repo_map = coder.get_repo_map()
|
repo_map = coder.get_repo_map()
|
||||||
if repo_map:
|
if repo_map:
|
||||||
io.tool_output(repo_map)
|
io.tool_output(repo_map)
|
||||||
|
analytics.event("exit", reason="Showed repo map")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.apply:
|
if args.apply:
|
||||||
content = io.read_text(args.apply)
|
content = io.read_text(args.apply)
|
||||||
if content is None:
|
if content is None:
|
||||||
|
analytics.event("exit", reason="Failed to read apply content")
|
||||||
return
|
return
|
||||||
coder.partial_response_content = content
|
coder.partial_response_content = content
|
||||||
coder.apply_updates()
|
coder.apply_updates()
|
||||||
|
analytics.event("exit", reason="Applied updates")
|
||||||
return
|
return
|
||||||
|
|
||||||
if "VSCODE_GIT_IPC_HANDLE" in os.environ:
|
if args.apply_clipboard_edits:
|
||||||
args.pretty = False
|
args.edit_format = main_model.editor_edit_format
|
||||||
io.tool_output("VSCode terminal detected, pretty output has been disabled.")
|
args.message = "/paste"
|
||||||
|
|
||||||
io.tool_output('Use /help <question> for help, run "aider --help" to see cmd line args')
|
if args.show_release_notes is True:
|
||||||
|
io.tool_output(f"Opening release notes: {urls.release_notes}")
|
||||||
|
io.tool_output()
|
||||||
|
webbrowser.open(urls.release_notes)
|
||||||
|
elif args.show_release_notes is None and is_first_run:
|
||||||
|
io.tool_output()
|
||||||
|
io.offer_url(
|
||||||
|
urls.release_notes,
|
||||||
|
"Would you like to see what's new in this version?",
|
||||||
|
allow_never=False,
|
||||||
|
)
|
||||||
|
|
||||||
if git_root and Path.cwd().resolve() != Path(git_root).resolve():
|
if git_root and Path.cwd().resolve() != Path(git_root).resolve():
|
||||||
io.tool_warning(
|
io.tool_warning(
|
||||||
@@ -679,6 +955,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.load:
|
||||||
|
commands.cmd_load(args.load)
|
||||||
|
|
||||||
if args.message:
|
if args.message:
|
||||||
io.add_to_input_history(args.message)
|
io.add_to_input_history(args.message)
|
||||||
io.tool_output()
|
io.tool_output()
|
||||||
@@ -686,6 +965,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
coder.run(with_message=args.message)
|
coder.run(with_message=args.message)
|
||||||
except SwitchCoder:
|
except SwitchCoder:
|
||||||
pass
|
pass
|
||||||
|
analytics.event("exit", reason="Completed --message")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.message_file:
|
if args.message_file:
|
||||||
@@ -695,18 +975,26 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
coder.run(with_message=message_from_file)
|
coder.run(with_message=message_from_file)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
io.tool_error(f"Message file not found: {args.message_file}")
|
io.tool_error(f"Message file not found: {args.message_file}")
|
||||||
|
analytics.event("exit", reason="Message file not found")
|
||||||
return 1
|
return 1
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
io.tool_error(f"Error reading message file: {e}")
|
io.tool_error(f"Error reading message file: {e}")
|
||||||
|
analytics.event("exit", reason="Message file IO error")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
analytics.event("exit", reason="Completed --message-file")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.exit:
|
if args.exit:
|
||||||
|
analytics.event("exit", reason="Exit flag set")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
analytics.event("cli session", main_model=main_model, edit_format=main_model.edit_format)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
coder.run()
|
coder.run()
|
||||||
|
analytics.event("exit", reason="Completed main CLI coder.run")
|
||||||
return
|
return
|
||||||
except SwitchCoder as switch:
|
except SwitchCoder as switch:
|
||||||
kwargs = dict(io=io, from_coder=coder)
|
kwargs = dict(io=io, from_coder=coder)
|
||||||
@@ -720,10 +1008,15 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||||||
coder.show_announcements()
|
coder.show_announcements()
|
||||||
|
|
||||||
|
|
||||||
def check_and_load_imports(io, verbose=False):
|
def is_first_run_of_new_version(io, verbose=False):
|
||||||
|
"""Check if this is the first run of a new version/executable combination"""
|
||||||
installs_file = Path.home() / ".aider" / "installs.json"
|
installs_file = Path.home() / ".aider" / "installs.json"
|
||||||
key = (__version__, sys.executable)
|
key = (__version__, sys.executable)
|
||||||
|
|
||||||
|
# Never show notes for .dev versions
|
||||||
|
if ".dev" in __version__:
|
||||||
|
return False
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
io.tool_output(
|
io.tool_output(
|
||||||
f"Checking imports for version {__version__} and executable {sys.executable}"
|
f"Checking imports for version {__version__} and executable {sys.executable}"
|
||||||
@@ -741,7 +1034,26 @@ def check_and_load_imports(io, verbose=False):
|
|||||||
if verbose:
|
if verbose:
|
||||||
io.tool_output("Installs file does not exist, creating new dictionary")
|
io.tool_output("Installs file does not exist, creating new dictionary")
|
||||||
|
|
||||||
if str(key) not in installs:
|
is_first_run = str(key) not in installs
|
||||||
|
|
||||||
|
if is_first_run:
|
||||||
|
installs[str(key)] = True
|
||||||
|
installs_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(installs_file, "w") as f:
|
||||||
|
json.dump(installs, f, indent=4)
|
||||||
|
|
||||||
|
return is_first_run
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
io.tool_warning(f"Error checking version: {e}")
|
||||||
|
if verbose:
|
||||||
|
io.tool_output(f"Full exception details: {traceback.format_exc()}")
|
||||||
|
return True # Safer to assume it's a first run if we hit an error
|
||||||
|
|
||||||
|
|
||||||
|
def check_and_load_imports(io, is_first_run, verbose=False):
|
||||||
|
try:
|
||||||
|
if is_first_run:
|
||||||
if verbose:
|
if verbose:
|
||||||
io.tool_output(
|
io.tool_output(
|
||||||
"First run for this version and executable, loading imports synchronously"
|
"First run for this version and executable, loading imports synchronously"
|
||||||
@@ -751,13 +1063,9 @@ def check_and_load_imports(io, verbose=False):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
io.tool_error(str(err))
|
io.tool_error(str(err))
|
||||||
io.tool_output("Error loading required imports. Did you install aider properly?")
|
io.tool_output("Error loading required imports. Did you install aider properly?")
|
||||||
io.tool_output("https://aider.chat/docs/install/install.html")
|
io.offer_url(urls.install_properly, "Open documentation url for more info?")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
installs[str(key)] = True
|
|
||||||
installs_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(installs_file, "w") as f:
|
|
||||||
json.dump(installs, f, indent=4)
|
|
||||||
if verbose:
|
if verbose:
|
||||||
io.tool_output("Imports loaded and installs file updated")
|
io.tool_output("Imports loaded and installs file updated")
|
||||||
else:
|
else:
|
||||||
@@ -766,8 +1074,9 @@ def check_and_load_imports(io, verbose=False):
|
|||||||
thread = threading.Thread(target=load_slow_imports)
|
thread = threading.Thread(target=load_slow_imports)
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
io.tool_warning(f"Error in checking imports: {e}")
|
io.tool_warning(f"Error in loading imports: {e}")
|
||||||
if verbose:
|
if verbose:
|
||||||
io.tool_output(f"Full exception details: {traceback.format_exc()}")
|
io.tool_output(f"Full exception details: {traceback.format_exc()}")
|
||||||
|
|
||||||
|
|||||||
667
aider/models.py
667
aider/models.py
File diff suppressed because it is too large
Load Diff
@@ -5,15 +5,21 @@
|
|||||||
|
|
||||||
# Conventional Commits text adapted from:
|
# Conventional Commits text adapted from:
|
||||||
# https://www.conventionalcommits.org/en/v1.0.0/#summary
|
# https://www.conventionalcommits.org/en/v1.0.0/#summary
|
||||||
commit_system = """You are an expert software engineer.
|
commit_system = """You are an expert software engineer that generates concise, \
|
||||||
|
one-line Git commit messages based on the provided diffs.
|
||||||
Review the provided context and diffs which are about to be committed to a git repo.
|
Review the provided context and diffs which are about to be committed to a git repo.
|
||||||
Review the diffs carefully.
|
Review the diffs carefully.
|
||||||
Generate a commit message for those changes.
|
Generate a one-line commit message for those changes.
|
||||||
The commit message MUST use the imperative tense.
|
|
||||||
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
|
||||||
Reply with JUST the commit message, without quotes, comments, questions, etc!
|
|
||||||
Reply with one line only!
|
Ensure the commit message:
|
||||||
|
- Starts with the appropriate prefix.
|
||||||
|
- Is in the imperative mood (e.g., \"Add feature\" not \"Added feature\" or \"Adding feature\").
|
||||||
|
- Does not exceed 72 characters.
|
||||||
|
|
||||||
|
Reply only with the one-line commit message, without any additional text, explanations, \
|
||||||
|
or line breaks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# COMMANDS
|
# COMMANDS
|
||||||
|
|||||||
91
aider/queries/tree-sitter-dart-tags.scm
Normal file
91
aider/queries/tree-sitter-dart-tags.scm
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
(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
|
||||||
|
|
||||||
|
(enum_declaration
|
||||||
|
name: (identifier) @name.definition.enum) @definition.enum
|
||||||
|
|
||||||
|
(function_signature
|
||||||
|
name: (identifier) @name.definition.function) @definition.function
|
||||||
|
|
||||||
|
(new_expression
|
||||||
|
(type_identifier) @name.reference.class) @reference.class
|
||||||
|
|
||||||
|
(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.call))) @reference.call
|
||||||
|
|
||||||
|
(assignment_expression
|
||||||
|
left: (assignable_expression
|
||||||
|
(identifier)
|
||||||
|
(conditional_assignable_selector
|
||||||
|
"?."
|
||||||
|
(identifier) @name.reference.call))) @reference.call
|
||||||
|
|
||||||
|
((identifier) @name
|
||||||
|
(selector
|
||||||
|
"!"?
|
||||||
|
(conditional_assignable_selector
|
||||||
|
"?." (identifier) @name.reference.call)?
|
||||||
|
(unconditional_assignable_selector
|
||||||
|
"."? (identifier) @name.reference.call)?
|
||||||
|
(argument_part
|
||||||
|
(arguments
|
||||||
|
(argument)*))?)*
|
||||||
|
(cascade_section
|
||||||
|
(cascade_selector
|
||||||
|
(identifier)) @name.reference.call
|
||||||
|
(argument_part
|
||||||
|
(arguments
|
||||||
|
(argument)*))?)?) @reference.call
|
||||||
|
|
||||||
|
|
||||||
@@ -2,7 +2,17 @@ import os
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
|
|
||||||
import git
|
try:
|
||||||
|
import git
|
||||||
|
|
||||||
|
ANY_GIT_ERROR = [
|
||||||
|
git.exc.ODBError,
|
||||||
|
git.exc.GitError,
|
||||||
|
]
|
||||||
|
except ImportError:
|
||||||
|
git = None
|
||||||
|
ANY_GIT_ERROR = []
|
||||||
|
|
||||||
import pathspec
|
import pathspec
|
||||||
|
|
||||||
from aider import prompts, utils
|
from aider import prompts, utils
|
||||||
@@ -10,7 +20,14 @@ from aider.sendchat import simple_send_with_retries
|
|||||||
|
|
||||||
from .dump import dump # noqa: F401
|
from .dump import dump # noqa: F401
|
||||||
|
|
||||||
ANY_GIT_ERROR = (git.exc.ODBError, git.exc.GitError, OSError, IndexError, BufferError, TypeError)
|
ANY_GIT_ERROR += [
|
||||||
|
OSError,
|
||||||
|
IndexError,
|
||||||
|
BufferError,
|
||||||
|
TypeError,
|
||||||
|
ValueError,
|
||||||
|
]
|
||||||
|
ANY_GIT_ERROR = tuple(ANY_GIT_ERROR)
|
||||||
|
|
||||||
|
|
||||||
class GitRepo:
|
class GitRepo:
|
||||||
@@ -161,7 +178,7 @@ class GitRepo:
|
|||||||
def get_rel_repo_dir(self):
|
def get_rel_repo_dir(self):
|
||||||
try:
|
try:
|
||||||
return os.path.relpath(self.repo.git_dir, os.getcwd())
|
return os.path.relpath(self.repo.git_dir, os.getcwd())
|
||||||
except ValueError:
|
except (ValueError, OSError):
|
||||||
return self.repo.git_dir
|
return self.repo.git_dir
|
||||||
|
|
||||||
def get_commit_message(self, diffs, context):
|
def get_commit_message(self, diffs, context):
|
||||||
@@ -184,9 +201,7 @@ class GitRepo:
|
|||||||
max_tokens = model.info.get("max_input_tokens") or 0
|
max_tokens = model.info.get("max_input_tokens") or 0
|
||||||
if max_tokens and num_tokens > max_tokens:
|
if max_tokens and num_tokens > max_tokens:
|
||||||
continue
|
continue
|
||||||
commit_message = simple_send_with_retries(
|
commit_message = simple_send_with_retries(model, messages)
|
||||||
model.name, messages, extra_headers=model.extra_headers
|
|
||||||
)
|
|
||||||
if commit_message:
|
if commit_message:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -323,6 +338,15 @@ class GitRepo:
|
|||||||
lines,
|
lines,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def git_ignored_file(self, path):
|
||||||
|
if not self.repo:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if self.repo.ignored(path):
|
||||||
|
return True
|
||||||
|
except ANY_GIT_ERROR:
|
||||||
|
return False
|
||||||
|
|
||||||
def ignored_file(self, fname):
|
def ignored_file(self, fname):
|
||||||
self.refresh_aider_ignore()
|
self.refresh_aider_ignore()
|
||||||
|
|
||||||
@@ -336,7 +360,14 @@ class GitRepo:
|
|||||||
def ignored_file_raw(self, fname):
|
def ignored_file_raw(self, fname):
|
||||||
if self.subtree_only:
|
if self.subtree_only:
|
||||||
fname_path = Path(self.normalize_path(fname))
|
fname_path = Path(self.normalize_path(fname))
|
||||||
cwd_path = Path.cwd().resolve().relative_to(Path(self.root).resolve())
|
try:
|
||||||
|
cwd_path = Path.cwd().resolve().relative_to(Path(self.root).resolve())
|
||||||
|
except ValueError:
|
||||||
|
# Issue #1524
|
||||||
|
# ValueError: 'C:\\dev\\squid-certbot' is not in the subpath of
|
||||||
|
# 'C:\\dev\\squid-certbot'
|
||||||
|
# Clearly, fname is not under cwd... so ignore it
|
||||||
|
return True
|
||||||
|
|
||||||
if cwd_path not in fname_path.parents and fname_path != cwd_path:
|
if cwd_path not in fname_path.parents and fname_path != cwd_path:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import colorsys
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import shutil
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@@ -27,7 +28,7 @@ from tree_sitter_languages import get_language, get_parser # noqa: E402
|
|||||||
Tag = namedtuple("Tag", "rel_fname fname line name kind".split())
|
Tag = namedtuple("Tag", "rel_fname fname line name kind".split())
|
||||||
|
|
||||||
|
|
||||||
SQLITE_ERRORS = (sqlite3.OperationalError, sqlite3.DatabaseError)
|
SQLITE_ERRORS = (sqlite3.OperationalError, sqlite3.DatabaseError, OSError)
|
||||||
|
|
||||||
|
|
||||||
class RepoMap:
|
class RepoMap:
|
||||||
@@ -166,13 +167,52 @@ class RepoMap:
|
|||||||
# Just return the full fname.
|
# Just return the full fname.
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
|
def tags_cache_error(self, original_error=None):
|
||||||
|
"""Handle SQLite errors by trying to recreate cache, falling back to dict if needed"""
|
||||||
|
|
||||||
|
if self.verbose and original_error:
|
||||||
|
self.io.tool_warning(f"Tags cache error: {str(original_error)}")
|
||||||
|
|
||||||
|
if isinstance(getattr(self, "TAGS_CACHE", None), dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
path = Path(self.root) / self.TAGS_CACHE_DIR
|
||||||
|
|
||||||
|
# Try to recreate the cache
|
||||||
|
try:
|
||||||
|
# Delete existing cache dir
|
||||||
|
if path.exists():
|
||||||
|
shutil.rmtree(path)
|
||||||
|
|
||||||
|
# Try to create new cache
|
||||||
|
new_cache = Cache(path)
|
||||||
|
|
||||||
|
# Test that it works
|
||||||
|
test_key = "test"
|
||||||
|
new_cache[test_key] = "test"
|
||||||
|
_ = new_cache[test_key]
|
||||||
|
del new_cache[test_key]
|
||||||
|
|
||||||
|
# If we got here, the new cache works
|
||||||
|
self.TAGS_CACHE = new_cache
|
||||||
|
return
|
||||||
|
|
||||||
|
except SQLITE_ERRORS as e:
|
||||||
|
# If anything goes wrong, warn and fall back to dict
|
||||||
|
self.io.tool_warning(
|
||||||
|
f"Unable to use tags cache at {path}, falling back to memory cache"
|
||||||
|
)
|
||||||
|
if self.verbose:
|
||||||
|
self.io.tool_warning(f"Cache recreation error: {str(e)}")
|
||||||
|
|
||||||
|
self.TAGS_CACHE = dict()
|
||||||
|
|
||||||
def load_tags_cache(self):
|
def load_tags_cache(self):
|
||||||
path = Path(self.root) / self.TAGS_CACHE_DIR
|
path = Path(self.root) / self.TAGS_CACHE_DIR
|
||||||
try:
|
try:
|
||||||
self.TAGS_CACHE = Cache(path)
|
self.TAGS_CACHE = Cache(path)
|
||||||
except SQLITE_ERRORS:
|
except SQLITE_ERRORS as e:
|
||||||
self.io.tool_warning(f"Unable to use tags cache, delete {path} to resolve.")
|
self.tags_cache_error(e)
|
||||||
self.TAGS_CACHE = dict()
|
|
||||||
|
|
||||||
def save_tags_cache(self):
|
def save_tags_cache(self):
|
||||||
pass
|
pass
|
||||||
@@ -190,9 +230,18 @@ class RepoMap:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
cache_key = fname
|
cache_key = fname
|
||||||
val = self.TAGS_CACHE.get(cache_key) # Issue #1308
|
try:
|
||||||
|
val = self.TAGS_CACHE.get(cache_key) # Issue #1308
|
||||||
|
except SQLITE_ERRORS as e:
|
||||||
|
self.tags_cache_error(e)
|
||||||
|
val = self.TAGS_CACHE.get(cache_key)
|
||||||
|
|
||||||
if val is not None and val.get("mtime") == file_mtime:
|
if val is not None and val.get("mtime") == file_mtime:
|
||||||
return self.TAGS_CACHE[cache_key]["data"]
|
try:
|
||||||
|
return self.TAGS_CACHE[cache_key]["data"]
|
||||||
|
except SQLITE_ERRORS as e:
|
||||||
|
self.tags_cache_error(e)
|
||||||
|
return self.TAGS_CACHE[cache_key]["data"]
|
||||||
|
|
||||||
# miss!
|
# miss!
|
||||||
data = list(self.get_tags_raw(fname, rel_fname))
|
data = list(self.get_tags_raw(fname, rel_fname))
|
||||||
@@ -201,8 +250,9 @@ class RepoMap:
|
|||||||
try:
|
try:
|
||||||
self.TAGS_CACHE[cache_key] = {"mtime": file_mtime, "data": data}
|
self.TAGS_CACHE[cache_key] = {"mtime": file_mtime, "data": data}
|
||||||
self.save_tags_cache()
|
self.save_tags_cache()
|
||||||
except SQLITE_ERRORS:
|
except SQLITE_ERRORS as e:
|
||||||
pass
|
self.tags_cache_error(e)
|
||||||
|
self.TAGS_CACHE[cache_key] = {"mtime": file_mtime, "data": data}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -302,7 +352,13 @@ class RepoMap:
|
|||||||
# https://networkx.org/documentation/stable/_modules/networkx/algorithms/link_analysis/pagerank_alg.html#pagerank
|
# https://networkx.org/documentation/stable/_modules/networkx/algorithms/link_analysis/pagerank_alg.html#pagerank
|
||||||
personalize = 100 / len(fnames)
|
personalize = 100 / len(fnames)
|
||||||
|
|
||||||
if len(fnames) - len(self.TAGS_CACHE) > 100:
|
try:
|
||||||
|
cache_size = len(self.TAGS_CACHE)
|
||||||
|
except SQLITE_ERRORS as e:
|
||||||
|
self.tags_cache_error(e)
|
||||||
|
cache_size = len(self.TAGS_CACHE)
|
||||||
|
|
||||||
|
if len(fnames) - cache_size > 100:
|
||||||
self.io.tool_output(
|
self.io.tool_output(
|
||||||
"Initial repo scan can be slow in larger repos, but only happens once."
|
"Initial repo scan can be slow in larger repos, but only happens once."
|
||||||
)
|
)
|
||||||
@@ -312,6 +368,8 @@ class RepoMap:
|
|||||||
showing_bar = False
|
showing_bar = False
|
||||||
|
|
||||||
for fname in fnames:
|
for fname in fnames:
|
||||||
|
if self.verbose:
|
||||||
|
self.io.tool_output(f"Processing {fname}")
|
||||||
if progress and not showing_bar:
|
if progress and not showing_bar:
|
||||||
progress()
|
progress()
|
||||||
|
|
||||||
@@ -398,7 +456,11 @@ class RepoMap:
|
|||||||
try:
|
try:
|
||||||
ranked = nx.pagerank(G, weight="weight", **pers_args)
|
ranked = nx.pagerank(G, weight="weight", **pers_args)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
return []
|
# Issue #1536
|
||||||
|
try:
|
||||||
|
ranked = nx.pagerank(G, weight="weight")
|
||||||
|
except ZeroDivisionError:
|
||||||
|
return []
|
||||||
|
|
||||||
# distribute the rank from each source node, across all of its out edges
|
# distribute the rank from each source node, across all of its out edges
|
||||||
ranked_definitions = defaultdict(float)
|
ranked_definitions = defaultdict(float)
|
||||||
@@ -415,7 +477,9 @@ class RepoMap:
|
|||||||
ranked_definitions[(dst, ident)] += data["rank"]
|
ranked_definitions[(dst, ident)] += data["rank"]
|
||||||
|
|
||||||
ranked_tags = []
|
ranked_tags = []
|
||||||
ranked_definitions = sorted(ranked_definitions.items(), reverse=True, key=lambda x: x[1])
|
ranked_definitions = sorted(
|
||||||
|
ranked_definitions.items(), reverse=True, key=lambda x: (x[1], x[0])
|
||||||
|
)
|
||||||
|
|
||||||
# dump(ranked_definitions)
|
# dump(ranked_definitions)
|
||||||
|
|
||||||
@@ -451,11 +515,18 @@ class RepoMap:
|
|||||||
force_refresh=False,
|
force_refresh=False,
|
||||||
):
|
):
|
||||||
# Create a cache key
|
# Create a cache key
|
||||||
cache_key = (
|
cache_key = [
|
||||||
tuple(sorted(chat_fnames)) if chat_fnames else None,
|
tuple(sorted(chat_fnames)) if chat_fnames else None,
|
||||||
tuple(sorted(other_fnames)) if other_fnames else None,
|
tuple(sorted(other_fnames)) if other_fnames else None,
|
||||||
max_map_tokens,
|
max_map_tokens,
|
||||||
)
|
]
|
||||||
|
|
||||||
|
if self.refresh == "auto":
|
||||||
|
cache_key += [
|
||||||
|
tuple(sorted(mentioned_fnames)) if mentioned_fnames else None,
|
||||||
|
tuple(sorted(mentioned_idents)) if mentioned_idents else None,
|
||||||
|
]
|
||||||
|
cache_key = tuple(cache_key)
|
||||||
|
|
||||||
use_cache = False
|
use_cache = False
|
||||||
if not force_refresh:
|
if not force_refresh:
|
||||||
|
|||||||
3
aider/resources/__init__.py
Normal file
3
aider/resources/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This ensures that importlib_resources.files("aider.resources")
|
||||||
|
# doesn't raise ImportError, even if there are no other files in this
|
||||||
|
# dir.
|
||||||
38
aider/resources/model-metadata.json
Normal file
38
aider/resources/model-metadata.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"vertex_ai-language-models/gemini-2.0-flash-exp": {
|
||||||
|
"max_tokens": 8192,
|
||||||
|
"max_input_tokens": 1048576,
|
||||||
|
"max_output_tokens": 8192,
|
||||||
|
"max_images_per_prompt": 3000,
|
||||||
|
"max_videos_per_prompt": 10,
|
||||||
|
"max_video_length": 1,
|
||||||
|
"max_audio_length_hours": 8.4,
|
||||||
|
"max_audio_per_prompt": 1,
|
||||||
|
"max_pdf_size_mb": 30,
|
||||||
|
"litellm_provider": "vertex_ai-language-models",
|
||||||
|
"mode": "chat",
|
||||||
|
"supports_system_messages": true,
|
||||||
|
"supports_function_calling": true,
|
||||||
|
"supports_vision": true,
|
||||||
|
"supports_response_schema": true,
|
||||||
|
"source": "https://cloud.google.com/vertex-ai/generative-ai/docs/gemini-v2"
|
||||||
|
},
|
||||||
|
"gemini/gemini-2.0-flash-exp": {
|
||||||
|
"max_tokens": 8192,
|
||||||
|
"max_input_tokens": 1048576,
|
||||||
|
"max_output_tokens": 8192,
|
||||||
|
"max_images_per_prompt": 3000,
|
||||||
|
"max_videos_per_prompt": 10,
|
||||||
|
"max_video_length": 1,
|
||||||
|
"max_audio_length_hours": 8.4,
|
||||||
|
"max_audio_per_prompt": 1,
|
||||||
|
"max_pdf_size_mb": 30,
|
||||||
|
"litellm_provider": "gemini",
|
||||||
|
"mode": "chat",
|
||||||
|
"supports_system_messages": true,
|
||||||
|
"supports_function_calling": true,
|
||||||
|
"supports_vision": true,
|
||||||
|
"supports_response_schema": true,
|
||||||
|
"source": "https://cloud.google.com/vertex-ai/generative-ai/docs/gemini-v2"
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -8,12 +8,12 @@ import pexpect
|
|||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
|
|
||||||
def run_cmd(command, verbose=False, error_print=None):
|
def run_cmd(command, verbose=False, error_print=None, cwd=None):
|
||||||
try:
|
try:
|
||||||
if sys.stdin.isatty() and hasattr(pexpect, "spawn") and platform.system() != "Windows":
|
if sys.stdin.isatty() and hasattr(pexpect, "spawn") and platform.system() != "Windows":
|
||||||
return run_cmd_pexpect(command, verbose)
|
return run_cmd_pexpect(command, verbose, cwd)
|
||||||
|
|
||||||
return run_cmd_subprocess(command, verbose)
|
return run_cmd_subprocess(command, verbose, cwd)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
error_message = f"Error occurred while running command '{command}': {str(e)}"
|
error_message = f"Error occurred while running command '{command}': {str(e)}"
|
||||||
if error_print is None:
|
if error_print is None:
|
||||||
@@ -39,7 +39,7 @@ def get_windows_parent_process_name():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def run_cmd_subprocess(command, verbose=False):
|
def run_cmd_subprocess(command, verbose=False, cwd=None):
|
||||||
if verbose:
|
if verbose:
|
||||||
print("Using run_cmd_subprocess:", command)
|
print("Using run_cmd_subprocess:", command)
|
||||||
|
|
||||||
@@ -69,6 +69,7 @@ def run_cmd_subprocess(command, verbose=False):
|
|||||||
errors="replace",
|
errors="replace",
|
||||||
bufsize=0, # Set bufsize to 0 for unbuffered output
|
bufsize=0, # Set bufsize to 0 for unbuffered output
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
|
cwd=cwd,
|
||||||
)
|
)
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
@@ -85,7 +86,7 @@ def run_cmd_subprocess(command, verbose=False):
|
|||||||
return 1, str(e)
|
return 1, str(e)
|
||||||
|
|
||||||
|
|
||||||
def run_cmd_pexpect(command, verbose=False):
|
def run_cmd_pexpect(command, verbose=False, cwd=None):
|
||||||
"""
|
"""
|
||||||
Run a shell command interactively using pexpect, capturing all output.
|
Run a shell command interactively using pexpect, capturing all output.
|
||||||
|
|
||||||
@@ -112,12 +113,12 @@ def run_cmd_pexpect(command, verbose=False):
|
|||||||
# Use the shell from SHELL environment variable
|
# Use the shell from SHELL environment variable
|
||||||
if verbose:
|
if verbose:
|
||||||
print("Running pexpect.spawn with shell:", shell)
|
print("Running pexpect.spawn with shell:", shell)
|
||||||
child = pexpect.spawn(shell, args=["-c", command], encoding="utf-8")
|
child = pexpect.spawn(shell, args=["-i", "-c", command], encoding="utf-8", cwd=cwd)
|
||||||
else:
|
else:
|
||||||
# Fall back to spawning the command directly
|
# Fall back to spawning the command directly
|
||||||
if verbose:
|
if verbose:
|
||||||
print("Running pexpect.spawn without shell.")
|
print("Running pexpect.spawn without shell.")
|
||||||
child = pexpect.spawn(command, encoding="utf-8")
|
child = pexpect.spawn(command, encoding="utf-8", cwd=cwd)
|
||||||
|
|
||||||
# Transfer control to the user, capturing output
|
# Transfer control to the user, capturing output
|
||||||
child.interact(output_filter=output_callback)
|
child.interact(output_filter=output_callback)
|
||||||
|
|||||||
@@ -185,7 +185,9 @@ class Scraper:
|
|||||||
|
|
||||||
headers = {"User-Agent": f"Mozilla./5.0 ({aider_user_agent})"}
|
headers = {"User-Agent": f"Mozilla./5.0 ({aider_user_agent})"}
|
||||||
try:
|
try:
|
||||||
with httpx.Client(headers=headers, verify=self.verify_ssl) as client:
|
with httpx.Client(
|
||||||
|
headers=headers, verify=self.verify_ssl, follow_redirects=True
|
||||||
|
) as client:
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.text, response.headers.get("content-type", "").split(";")[0]
|
return response.text, response.headers.get("content-type", "").split(";")[0]
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
import backoff
|
|
||||||
|
|
||||||
from aider.dump import dump # noqa: F401
|
from aider.dump import dump # noqa: F401
|
||||||
|
from aider.exceptions import LiteLLMExceptions
|
||||||
from aider.llm import litellm
|
from aider.llm import litellm
|
||||||
|
|
||||||
# from diskcache import Cache
|
# from diskcache import Cache
|
||||||
@@ -13,37 +13,7 @@ CACHE_PATH = "~/.aider.send.cache.v1"
|
|||||||
CACHE = None
|
CACHE = None
|
||||||
# CACHE = Cache(CACHE_PATH)
|
# CACHE = Cache(CACHE_PATH)
|
||||||
|
|
||||||
|
RETRY_TIMEOUT = 60
|
||||||
def retry_exceptions():
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
return (
|
|
||||||
httpx.ConnectError,
|
|
||||||
httpx.RemoteProtocolError,
|
|
||||||
httpx.ReadTimeout,
|
|
||||||
litellm.exceptions.APIConnectionError,
|
|
||||||
litellm.exceptions.APIError,
|
|
||||||
litellm.exceptions.RateLimitError,
|
|
||||||
litellm.exceptions.ServiceUnavailableError,
|
|
||||||
litellm.exceptions.Timeout,
|
|
||||||
litellm.exceptions.InternalServerError,
|
|
||||||
litellm.llms.anthropic.chat.AnthropicError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def lazy_litellm_retry_decorator(func):
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
decorated_func = backoff.on_exception(
|
|
||||||
backoff.expo,
|
|
||||||
retry_exceptions(),
|
|
||||||
max_time=60,
|
|
||||||
on_backoff=lambda details: print(
|
|
||||||
f"{details.get('exception', 'Exception')}\nRetry in {details['wait']:.1f} seconds."
|
|
||||||
),
|
|
||||||
)(func)
|
|
||||||
return decorated_func(*args, **kwargs)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def send_completion(
|
def send_completion(
|
||||||
@@ -52,11 +22,8 @@ def send_completion(
|
|||||||
functions,
|
functions,
|
||||||
stream,
|
stream,
|
||||||
temperature=0,
|
temperature=0,
|
||||||
extra_headers=None,
|
extra_params=None,
|
||||||
max_tokens=None,
|
|
||||||
):
|
):
|
||||||
from aider.llm import litellm
|
|
||||||
|
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
model=model_name,
|
model=model_name,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
@@ -69,10 +36,9 @@ def send_completion(
|
|||||||
function = functions[0]
|
function = functions[0]
|
||||||
kwargs["tools"] = [dict(type="function", function=function)]
|
kwargs["tools"] = [dict(type="function", function=function)]
|
||||||
kwargs["tool_choice"] = {"type": "function", "function": {"name": function["name"]}}
|
kwargs["tool_choice"] = {"type": "function", "function": {"name": function["name"]}}
|
||||||
if extra_headers is not None:
|
|
||||||
kwargs["extra_headers"] = extra_headers
|
if extra_params is not None:
|
||||||
if max_tokens is not None:
|
kwargs.update(extra_params)
|
||||||
kwargs["max_tokens"] = max_tokens
|
|
||||||
|
|
||||||
key = json.dumps(kwargs, sort_keys=True).encode()
|
key = json.dumps(kwargs, sort_keys=True).encode()
|
||||||
|
|
||||||
@@ -82,8 +48,6 @@ def send_completion(
|
|||||||
if not stream and CACHE is not None and key in CACHE:
|
if not stream and CACHE is not None and key in CACHE:
|
||||||
return hash_object, CACHE[key]
|
return hash_object, CACHE[key]
|
||||||
|
|
||||||
# del kwargs['stream']
|
|
||||||
|
|
||||||
res = litellm.completion(**kwargs)
|
res = litellm.completion(**kwargs)
|
||||||
|
|
||||||
if not stream and CACHE is not None:
|
if not stream and CACHE is not None:
|
||||||
@@ -92,19 +56,43 @@ def send_completion(
|
|||||||
return hash_object, res
|
return hash_object, res
|
||||||
|
|
||||||
|
|
||||||
@lazy_litellm_retry_decorator
|
def simple_send_with_retries(model, messages):
|
||||||
def simple_send_with_retries(model_name, messages, extra_headers=None):
|
litellm_ex = LiteLLMExceptions()
|
||||||
try:
|
|
||||||
kwargs = {
|
|
||||||
"model_name": model_name,
|
|
||||||
"messages": messages,
|
|
||||||
"functions": None,
|
|
||||||
"stream": False,
|
|
||||||
}
|
|
||||||
if extra_headers is not None:
|
|
||||||
kwargs["extra_headers"] = extra_headers
|
|
||||||
|
|
||||||
_hash, response = send_completion(**kwargs)
|
retry_delay = 0.125
|
||||||
return response.choices[0].message.content
|
while True:
|
||||||
except (AttributeError, litellm.exceptions.BadRequestError):
|
try:
|
||||||
return
|
kwargs = {
|
||||||
|
"model_name": model.name,
|
||||||
|
"messages": messages,
|
||||||
|
"functions": None,
|
||||||
|
"stream": False,
|
||||||
|
"temperature": None if not model.use_temperature else 0,
|
||||||
|
"extra_params": model.extra_params,
|
||||||
|
}
|
||||||
|
|
||||||
|
_hash, response = send_completion(**kwargs)
|
||||||
|
if not response or not hasattr(response, "choices") or not response.choices:
|
||||||
|
return None
|
||||||
|
return response.choices[0].message.content
|
||||||
|
except litellm_ex.exceptions_tuple() as err:
|
||||||
|
ex_info = litellm_ex.get_ex_info(err)
|
||||||
|
|
||||||
|
print(str(err))
|
||||||
|
if ex_info.description:
|
||||||
|
print(ex_info.description)
|
||||||
|
|
||||||
|
should_retry = ex_info.retry
|
||||||
|
if should_retry:
|
||||||
|
retry_delay *= 2
|
||||||
|
if retry_delay > RETRY_TIMEOUT:
|
||||||
|
should_retry = False
|
||||||
|
|
||||||
|
if not should_retry:
|
||||||
|
return None
|
||||||
|
|
||||||
|
print(f"Retrying in {retry_delay:.1f} seconds...")
|
||||||
|
time.sleep(retry_delay)
|
||||||
|
continue
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -8,4 +8,9 @@ model_warnings = "https://aider.chat/docs/llms/warnings.html"
|
|||||||
token_limits = "https://aider.chat/docs/troubleshooting/token-limits.html"
|
token_limits = "https://aider.chat/docs/troubleshooting/token-limits.html"
|
||||||
llms = "https://aider.chat/docs/llms.html"
|
llms = "https://aider.chat/docs/llms.html"
|
||||||
large_repos = "https://aider.chat/docs/faq.html#can-i-use-aider-in-a-large-mono-repo"
|
large_repos = "https://aider.chat/docs/faq.html#can-i-use-aider-in-a-large-mono-repo"
|
||||||
github_issues = "https://github.com/paul-gauthier/aider/issues/new"
|
github_issues = "https://github.com/Aider-AI/aider/issues/new"
|
||||||
|
git_index_version = "https://github.com/Aider-AI/aider/issues/211"
|
||||||
|
install_properly = "https://aider.chat/docs/troubleshooting/imports.html"
|
||||||
|
analytics = "https://aider.chat/docs/more/analytics.html"
|
||||||
|
release_notes = "https://aider.chat/HISTORY.html#release-notes"
|
||||||
|
edit_formats = "https://aider.chat/docs/more/edit-formats.html"
|
||||||
|
|||||||
@@ -2,18 +2,15 @@ import itertools
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import git
|
|
||||||
|
|
||||||
from aider.dump import dump # noqa: F401
|
from aider.dump import dump # noqa: F401
|
||||||
|
|
||||||
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".webp"}
|
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".webp", ".pdf"}
|
||||||
|
|
||||||
|
|
||||||
class IgnorantTemporaryDirectory:
|
class IgnorantTemporaryDirectory:
|
||||||
@@ -74,6 +71,8 @@ class GitTemporaryDirectory(ChdirTemporaryDirectory):
|
|||||||
|
|
||||||
|
|
||||||
def make_repo(path=None):
|
def make_repo(path=None):
|
||||||
|
import git
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
path = "."
|
path = "."
|
||||||
repo = git.Repo.init(path)
|
repo = git.Repo.init(path)
|
||||||
@@ -194,28 +193,15 @@ def split_chat_history_markdown(text, include_tool=False):
|
|||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
|
||||||
# Copied from pip, MIT license
|
|
||||||
# https://github.com/pypa/pip/blob/b989e6ef04810bbd4033a3683020bd4ddcbdb627/src/pip/_internal/utils/entrypoints.py#L73
|
|
||||||
def get_best_invocation_for_this_python() -> str:
|
|
||||||
"""Try to figure out the best way to invoke the current Python."""
|
|
||||||
exe = sys.executable
|
|
||||||
exe_name = os.path.basename(exe)
|
|
||||||
|
|
||||||
# Try to use the basename, if it's the first executable.
|
|
||||||
found_executable = shutil.which(exe_name)
|
|
||||||
if found_executable and os.path.samefile(found_executable, exe):
|
|
||||||
return exe_name
|
|
||||||
|
|
||||||
# Use the full executable name, because we couldn't find something simpler.
|
|
||||||
return exe
|
|
||||||
|
|
||||||
|
|
||||||
def get_pip_install(args):
|
def get_pip_install(args):
|
||||||
cmd = [
|
cmd = [
|
||||||
get_best_invocation_for_this_python(),
|
sys.executable,
|
||||||
"-m",
|
"-m",
|
||||||
"pip",
|
"pip",
|
||||||
"install",
|
"install",
|
||||||
|
"--upgrade",
|
||||||
|
"--upgrade-strategy",
|
||||||
|
"only-if-needed",
|
||||||
]
|
]
|
||||||
cmd += args
|
cmd += args
|
||||||
return cmd
|
return cmd
|
||||||
@@ -265,15 +251,34 @@ def run_install(cmd):
|
|||||||
|
|
||||||
|
|
||||||
class Spinner:
|
class Spinner:
|
||||||
spinner_chars = itertools.cycle(["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
|
unicode_spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
||||||
|
ascii_spinner = ["|", "/", "-", "\\"]
|
||||||
|
|
||||||
def __init__(self, text):
|
def __init__(self, text):
|
||||||
self.text = text
|
self.text = text
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.last_update = 0
|
self.last_update = 0
|
||||||
self.visible = False
|
self.visible = False
|
||||||
|
self.is_tty = sys.stdout.isatty()
|
||||||
|
self.tested = False
|
||||||
|
|
||||||
|
def test_charset(self):
|
||||||
|
if self.tested:
|
||||||
|
return
|
||||||
|
self.tested = True
|
||||||
|
# Try unicode first, fall back to ascii if needed
|
||||||
|
try:
|
||||||
|
# Test if we can print unicode characters
|
||||||
|
print(self.unicode_spinner[0], end="", flush=True)
|
||||||
|
print("\r", end="", flush=True)
|
||||||
|
self.spinner_chars = itertools.cycle(self.unicode_spinner)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
self.spinner_chars = itertools.cycle(self.ascii_spinner)
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
|
if not self.is_tty:
|
||||||
|
return
|
||||||
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if not self.visible and current_time - self.start_time >= 0.5:
|
if not self.visible and current_time - self.start_time >= 0.5:
|
||||||
self.visible = True
|
self.visible = True
|
||||||
@@ -286,10 +291,11 @@ class Spinner:
|
|||||||
if not self.visible:
|
if not self.visible:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.test_charset()
|
||||||
print(f"\r{self.text} {next(self.spinner_chars)}\r{self.text} ", end="", flush=True)
|
print(f"\r{self.text} {next(self.spinner_chars)}\r{self.text} ", end="", flush=True)
|
||||||
|
|
||||||
def end(self):
|
def end(self):
|
||||||
if self.visible:
|
if self.visible and self.is_tty:
|
||||||
print("\r" + " " * (len(self.text) + 3))
|
print("\r" + " " * (len(self.text) + 3))
|
||||||
|
|
||||||
|
|
||||||
@@ -346,7 +352,7 @@ def check_pip_install_extra(io, module, prompt, pip_install_cmd, self_update=Fal
|
|||||||
success, output = run_install(cmd)
|
success, output = run_install(cmd)
|
||||||
if success:
|
if success:
|
||||||
if not module:
|
if not module:
|
||||||
return
|
return True
|
||||||
try:
|
try:
|
||||||
__import__(module)
|
__import__(module)
|
||||||
return True
|
return True
|
||||||
@@ -375,3 +381,15 @@ def printable_shell_command(cmd_list):
|
|||||||
return subprocess.list2cmdline(cmd_list)
|
return subprocess.list2cmdline(cmd_list)
|
||||||
else:
|
else:
|
||||||
return shlex.join(cmd_list)
|
return shlex.join(cmd_list)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
spinner = Spinner("Running spinner...")
|
||||||
|
for _ in range(40): # 40 steps * 0.25 seconds = 10 seconds
|
||||||
|
time.sleep(0.25)
|
||||||
|
spinner.step()
|
||||||
|
spinner.end()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ def install_from_main_branch(io):
|
|||||||
io,
|
io,
|
||||||
None,
|
None,
|
||||||
"Install the development version of aider from the main branch?",
|
"Install the development version of aider from the main branch?",
|
||||||
["--upgrade", "git+https://github.com/paul-gauthier/aider.git"],
|
["git+https://github.com/Aider-AI/aider.git"],
|
||||||
self_update=True,
|
self_update=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ def install_upgrade(io, latest_version=None):
|
|||||||
io,
|
io,
|
||||||
None,
|
None,
|
||||||
new_ver_text,
|
new_ver_text,
|
||||||
["--upgrade", "aider-chat"],
|
["aider-chat"],
|
||||||
self_update=True,
|
self_update=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,27 @@ import os
|
|||||||
import queue
|
import queue
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from prompt_toolkit.shortcuts import prompt
|
||||||
|
|
||||||
from aider.llm import litellm
|
from aider.llm import litellm
|
||||||
|
|
||||||
|
from .dump import dump # noqa: F401
|
||||||
|
|
||||||
|
warnings.filterwarnings(
|
||||||
|
"ignore", message="Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work"
|
||||||
|
)
|
||||||
|
warnings.filterwarnings("ignore", category=SyntaxWarning)
|
||||||
|
|
||||||
|
|
||||||
|
from pydub import AudioSegment # noqa
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import soundfile as sf
|
import soundfile as sf
|
||||||
except (OSError, ModuleNotFoundError):
|
except (OSError, ModuleNotFoundError):
|
||||||
sf = None
|
sf = None
|
||||||
|
|
||||||
from prompt_toolkit.shortcuts import prompt
|
|
||||||
|
|
||||||
from .dump import dump # noqa: F401
|
|
||||||
|
|
||||||
|
|
||||||
class SoundDeviceError(Exception):
|
class SoundDeviceError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -27,7 +36,7 @@ class Voice:
|
|||||||
|
|
||||||
threshold = 0.15
|
threshold = 0.15
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, audio_format="wav", device_name=None):
|
||||||
if sf is None:
|
if sf is None:
|
||||||
raise SoundDeviceError
|
raise SoundDeviceError
|
||||||
try:
|
try:
|
||||||
@@ -35,8 +44,34 @@ class Voice:
|
|||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
|
|
||||||
self.sd = sd
|
self.sd = sd
|
||||||
|
|
||||||
|
devices = sd.query_devices()
|
||||||
|
|
||||||
|
if device_name:
|
||||||
|
# Find the device with matching name
|
||||||
|
device_id = None
|
||||||
|
for i, device in enumerate(devices):
|
||||||
|
if device_name in device["name"]:
|
||||||
|
device_id = i
|
||||||
|
break
|
||||||
|
if device_id is None:
|
||||||
|
available_inputs = [d["name"] for d in devices if d["max_input_channels"] > 0]
|
||||||
|
raise ValueError(
|
||||||
|
f"Device '{device_name}' not found. Available input devices:"
|
||||||
|
f" {available_inputs}"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Using input device: {device_name} (ID: {device_id})")
|
||||||
|
|
||||||
|
self.device_id = device_id
|
||||||
|
else:
|
||||||
|
self.device_id = None
|
||||||
|
|
||||||
except (OSError, ModuleNotFoundError):
|
except (OSError, ModuleNotFoundError):
|
||||||
raise SoundDeviceError
|
raise SoundDeviceError
|
||||||
|
if audio_format not in ["wav", "mp3", "webm"]:
|
||||||
|
raise ValueError(f"Unsupported audio format: {audio_format}")
|
||||||
|
self.audio_format = audio_format
|
||||||
|
|
||||||
def callback(self, indata, frames, time, status):
|
def callback(self, indata, frames, time, status):
|
||||||
"""This is called (from a separate thread) for each audio block."""
|
"""This is called (from a separate thread) for each audio block."""
|
||||||
@@ -80,10 +115,10 @@ class Voice:
|
|||||||
def raw_record_and_transcribe(self, history, language):
|
def raw_record_and_transcribe(self, history, language):
|
||||||
self.q = queue.Queue()
|
self.q = queue.Queue()
|
||||||
|
|
||||||
filename = tempfile.mktemp(suffix=".wav")
|
temp_wav = tempfile.mktemp(suffix=".wav")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sample_rate = int(self.sd.query_devices(None, "input")["default_samplerate"])
|
sample_rate = int(self.sd.query_devices(self.device_id, "input")["default_samplerate"])
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
sample_rate = 16000 # fallback to 16kHz if unable to query device
|
sample_rate = 16000 # fallback to 16kHz if unable to query device
|
||||||
except self.sd.PortAudioError:
|
except self.sd.PortAudioError:
|
||||||
@@ -94,15 +129,25 @@ class Voice:
|
|||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.sd.InputStream(samplerate=sample_rate, channels=1, callback=self.callback):
|
with self.sd.InputStream(
|
||||||
|
samplerate=sample_rate, channels=1, callback=self.callback, device=self.device_id
|
||||||
|
):
|
||||||
prompt(self.get_prompt, refresh_interval=0.1)
|
prompt(self.get_prompt, refresh_interval=0.1)
|
||||||
except self.sd.PortAudioError as err:
|
except self.sd.PortAudioError as err:
|
||||||
raise SoundDeviceError(f"Error accessing audio input device: {err}")
|
raise SoundDeviceError(f"Error accessing audio input device: {err}")
|
||||||
|
|
||||||
with sf.SoundFile(filename, mode="x", samplerate=sample_rate, channels=1) as file:
|
with sf.SoundFile(temp_wav, mode="x", samplerate=sample_rate, channels=1) as file:
|
||||||
while not self.q.empty():
|
while not self.q.empty():
|
||||||
file.write(self.q.get())
|
file.write(self.q.get())
|
||||||
|
|
||||||
|
if self.audio_format != "wav":
|
||||||
|
filename = tempfile.mktemp(suffix=f".{self.audio_format}")
|
||||||
|
audio = AudioSegment.from_wav(temp_wav)
|
||||||
|
audio.export(filename, format=self.audio_format)
|
||||||
|
os.remove(temp_wav)
|
||||||
|
else:
|
||||||
|
filename = temp_wav
|
||||||
|
|
||||||
with open(filename, "rb") as fh:
|
with open(filename, "rb") as fh:
|
||||||
try:
|
try:
|
||||||
transcript = litellm.transcription(
|
transcript = litellm.transcription(
|
||||||
@@ -112,6 +157,9 @@ class Voice:
|
|||||||
print(f"Unable to transcribe {filename}: {err}")
|
print(f"Unable to transcribe {filename}: {err}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.audio_format != "wav":
|
||||||
|
os.remove(filename)
|
||||||
|
|
||||||
text = transcript.text
|
text = transcript.text
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|||||||
278
aider/watch.py
Normal file
278
aider/watch.py
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
import re
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from grep_ast import TreeContext
|
||||||
|
from pathspec import PathSpec
|
||||||
|
from pathspec.patterns import GitWildMatchPattern
|
||||||
|
from watchfiles import watch
|
||||||
|
|
||||||
|
from aider.dump import dump # noqa
|
||||||
|
from aider.watch_prompts import watch_ask_prompt, watch_code_prompt
|
||||||
|
|
||||||
|
|
||||||
|
def load_gitignores(gitignore_paths: list[Path]) -> Optional[PathSpec]:
|
||||||
|
"""Load and parse multiple .gitignore files into a single PathSpec"""
|
||||||
|
if not gitignore_paths:
|
||||||
|
return None
|
||||||
|
|
||||||
|
patterns = [
|
||||||
|
".aider*",
|
||||||
|
".git",
|
||||||
|
# Common editor backup/temp files
|
||||||
|
"*~", # Emacs/vim backup
|
||||||
|
"*.bak", # Generic backup
|
||||||
|
"*.swp", # Vim swap
|
||||||
|
"*.swo", # Vim swap
|
||||||
|
"\\#*\\#", # Emacs auto-save
|
||||||
|
".#*", # Emacs lock files
|
||||||
|
"*.tmp", # Generic temp files
|
||||||
|
"*.temp", # Generic temp files
|
||||||
|
"*.orig", # Merge conflict originals
|
||||||
|
"*.pyc", # Python bytecode
|
||||||
|
"__pycache__/", # Python cache dir
|
||||||
|
".DS_Store", # macOS metadata
|
||||||
|
"Thumbs.db", # Windows thumbnail cache
|
||||||
|
# IDE files
|
||||||
|
".idea/", # JetBrains IDEs
|
||||||
|
".vscode/", # VS Code
|
||||||
|
"*.sublime-*", # Sublime Text
|
||||||
|
".project", # Eclipse
|
||||||
|
".settings/", # Eclipse
|
||||||
|
"*.code-workspace", # VS Code workspace
|
||||||
|
# Environment files
|
||||||
|
".env", # Environment variables
|
||||||
|
".venv/", # Python virtual environments
|
||||||
|
"node_modules/", # Node.js dependencies
|
||||||
|
"vendor/", # Various dependencies
|
||||||
|
# Logs and caches
|
||||||
|
"*.log", # Log files
|
||||||
|
".cache/", # Cache directories
|
||||||
|
".pytest_cache/", # Python test cache
|
||||||
|
"coverage/", # Code coverage reports
|
||||||
|
] # Always ignore
|
||||||
|
for path in gitignore_paths:
|
||||||
|
if path.exists():
|
||||||
|
with open(path) as f:
|
||||||
|
patterns.extend(f.readlines())
|
||||||
|
|
||||||
|
return PathSpec.from_lines(GitWildMatchPattern, patterns) if patterns else None
|
||||||
|
|
||||||
|
|
||||||
|
class FileWatcher:
|
||||||
|
"""Watches source files for changes and AI comments"""
|
||||||
|
|
||||||
|
# Compiled regex pattern for AI comments
|
||||||
|
ai_comment_pattern = re.compile(r"(?:#|//|--) *(ai\b.*|ai\b.*|.*\bai[?!]?) *$", re.IGNORECASE)
|
||||||
|
|
||||||
|
def __init__(self, coder, gitignores=None, verbose=False, analytics=None):
|
||||||
|
self.coder = coder
|
||||||
|
self.io = coder.io
|
||||||
|
self.root = Path(coder.root)
|
||||||
|
self.verbose = verbose
|
||||||
|
self.analytics = analytics
|
||||||
|
self.stop_event = None
|
||||||
|
self.watcher_thread = None
|
||||||
|
self.changed_files = set()
|
||||||
|
self.gitignores = gitignores
|
||||||
|
|
||||||
|
self.gitignore_spec = load_gitignores(
|
||||||
|
[Path(g) for g in self.gitignores] if self.gitignores else []
|
||||||
|
)
|
||||||
|
|
||||||
|
coder.io.file_watcher = self
|
||||||
|
|
||||||
|
def filter_func(self, change_type, path):
|
||||||
|
"""Filter function for the file watcher"""
|
||||||
|
path_obj = Path(path)
|
||||||
|
path_abs = path_obj.absolute()
|
||||||
|
|
||||||
|
if not path_abs.is_relative_to(self.root.absolute()):
|
||||||
|
return False
|
||||||
|
|
||||||
|
rel_path = path_abs.relative_to(self.root)
|
||||||
|
if self.verbose:
|
||||||
|
dump(rel_path)
|
||||||
|
|
||||||
|
if self.gitignore_spec and self.gitignore_spec.match_file(str(rel_path)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
dump("ok", rel_path)
|
||||||
|
|
||||||
|
# Check if file contains AI markers
|
||||||
|
try:
|
||||||
|
comments, _, _ = self.get_ai_comments(str(path_abs))
|
||||||
|
return bool(comments)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start watching for file changes"""
|
||||||
|
self.stop_event = threading.Event()
|
||||||
|
self.changed_files = set()
|
||||||
|
|
||||||
|
def watch_files():
|
||||||
|
try:
|
||||||
|
for changes in watch(
|
||||||
|
str(self.root), watch_filter=self.filter_func, stop_event=self.stop_event
|
||||||
|
):
|
||||||
|
if not changes:
|
||||||
|
continue
|
||||||
|
changed_files = {str(Path(change[1])) for change in changes}
|
||||||
|
self.changed_files.update(changed_files)
|
||||||
|
self.io.interrupt_input()
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
if self.verbose:
|
||||||
|
dump(f"File watcher error: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
self.watcher_thread = threading.Thread(target=watch_files, daemon=True)
|
||||||
|
self.watcher_thread.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop watching for file changes"""
|
||||||
|
if self.stop_event:
|
||||||
|
self.stop_event.set()
|
||||||
|
if self.watcher_thread:
|
||||||
|
self.watcher_thread.join()
|
||||||
|
self.watcher_thread = None
|
||||||
|
self.stop_event = None
|
||||||
|
|
||||||
|
def process_changes(self):
|
||||||
|
"""Get any detected file changes"""
|
||||||
|
|
||||||
|
has_action = None
|
||||||
|
added = False
|
||||||
|
for fname in self.changed_files:
|
||||||
|
_, _, action = self.get_ai_comments(fname)
|
||||||
|
if action in ("!", "?"):
|
||||||
|
has_action = action
|
||||||
|
|
||||||
|
if fname in self.coder.abs_fnames:
|
||||||
|
continue
|
||||||
|
if self.analytics:
|
||||||
|
self.analytics.event("ai-comments file-add")
|
||||||
|
self.coder.abs_fnames.add(fname)
|
||||||
|
rel_fname = self.coder.get_rel_fname(fname)
|
||||||
|
if not added:
|
||||||
|
self.io.tool_output()
|
||||||
|
added = True
|
||||||
|
self.io.tool_output(f"Added {rel_fname} to the chat")
|
||||||
|
|
||||||
|
if not has_action:
|
||||||
|
if added:
|
||||||
|
self.io.tool_output(
|
||||||
|
"End your comment with AI! to request changes or AI? to ask questions"
|
||||||
|
)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if self.analytics:
|
||||||
|
self.analytics.event("ai-comments execute")
|
||||||
|
self.io.tool_output("Processing your request...")
|
||||||
|
|
||||||
|
if has_action == "!":
|
||||||
|
res = watch_code_prompt
|
||||||
|
elif has_action == "?":
|
||||||
|
res = watch_ask_prompt
|
||||||
|
|
||||||
|
# Refresh all AI comments from tracked files
|
||||||
|
for fname in self.coder.abs_fnames:
|
||||||
|
line_nums, comments, _action = self.get_ai_comments(fname)
|
||||||
|
if not line_nums:
|
||||||
|
continue
|
||||||
|
|
||||||
|
code = self.io.read_text(fname)
|
||||||
|
if not code:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rel_fname = self.coder.get_rel_fname(fname)
|
||||||
|
res += f"\n{rel_fname}:\n"
|
||||||
|
|
||||||
|
# Convert comment line numbers to line indices (0-based)
|
||||||
|
lois = [ln - 1 for ln, _ in zip(line_nums, comments) if ln > 0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
context = TreeContext(
|
||||||
|
rel_fname,
|
||||||
|
code,
|
||||||
|
color=False,
|
||||||
|
line_number=False,
|
||||||
|
child_context=False,
|
||||||
|
last_line=False,
|
||||||
|
margin=0,
|
||||||
|
mark_lois=True,
|
||||||
|
loi_pad=3,
|
||||||
|
show_top_of_file_parent_scope=False,
|
||||||
|
)
|
||||||
|
context.lines_of_interest = set()
|
||||||
|
context.add_lines_of_interest(lois)
|
||||||
|
context.add_context()
|
||||||
|
res += context.format()
|
||||||
|
except ValueError:
|
||||||
|
for ln, comment in zip(line_nums, comments):
|
||||||
|
res += f" Line {ln}: {comment}\n"
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_ai_comments(self, filepath):
|
||||||
|
"""Extract AI comment line numbers, comments and action status from a file"""
|
||||||
|
line_nums = []
|
||||||
|
comments = []
|
||||||
|
has_action = None # None, "!" or "?"
|
||||||
|
content = self.io.read_text(filepath, silent=True)
|
||||||
|
for i, line in enumerate(content.splitlines(), 1):
|
||||||
|
if match := self.ai_comment_pattern.search(line):
|
||||||
|
comment = match.group(0).strip()
|
||||||
|
if comment:
|
||||||
|
line_nums.append(i)
|
||||||
|
comments.append(comment)
|
||||||
|
comment = comment.lower()
|
||||||
|
comment = comment.lstrip("/#-")
|
||||||
|
comment = comment.strip()
|
||||||
|
if comment.startswith("ai!") or comment.endswith("ai!"):
|
||||||
|
has_action = "!"
|
||||||
|
elif comment.startswith("ai?") or comment.endswith("ai?"):
|
||||||
|
has_action = "?"
|
||||||
|
if not line_nums:
|
||||||
|
return None, None, None
|
||||||
|
return line_nums, comments, has_action
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Example usage of the file watcher"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Watch source files for changes")
|
||||||
|
parser.add_argument("directory", help="Directory to watch")
|
||||||
|
parser.add_argument(
|
||||||
|
"--gitignore",
|
||||||
|
action="append",
|
||||||
|
help="Path to .gitignore file (can be specified multiple times)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
directory = args.directory
|
||||||
|
print(f"Watching source files in {directory}...")
|
||||||
|
|
||||||
|
# Example ignore function that ignores files with "test" in the name
|
||||||
|
def ignore_test_files(path):
|
||||||
|
return "test" in path.name.lower()
|
||||||
|
|
||||||
|
watcher = FileWatcher(directory, gitignores=args.gitignore)
|
||||||
|
try:
|
||||||
|
watcher.start()
|
||||||
|
while True:
|
||||||
|
if changes := watcher.get_changes():
|
||||||
|
for file in sorted(changes.keys()):
|
||||||
|
print(file)
|
||||||
|
watcher.changed_files = None
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nStopped watching files")
|
||||||
|
watcher.stop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
11
aider/watch_prompts.py
Normal file
11
aider/watch_prompts.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
watch_code_prompt = """
|
||||||
|
Find the "AI" comments below (marked with █) in the code files I've shared with you.
|
||||||
|
They contain your instructions.
|
||||||
|
Make the requested changes.
|
||||||
|
Be sure to remove all these "AI" comments from the code!
|
||||||
|
"""
|
||||||
|
|
||||||
|
watch_ask_prompt = """/ask
|
||||||
|
Find the "AI" comments below (marked with █) in the code files I've shared with you.
|
||||||
|
They contain your questions you need to answer and other instructions.
|
||||||
|
"""
|
||||||
@@ -1,20 +1,262 @@
|
|||||||
---
|
---
|
||||||
title: Release history
|
title: Release history
|
||||||
parent: More info
|
nav_order: 925
|
||||||
nav_order: 999
|
|
||||||
highlight_image: /assets/blame.jpg
|
highlight_image: /assets/blame.jpg
|
||||||
description: Release notes and stats on aider writing its own code.
|
description: Release notes and stats on aider writing its own code.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Release history
|
||||||
|
|
||||||
{% include blame.md %}
|
{% include blame.md %}
|
||||||
|
|
||||||
|
The above
|
||||||
|
[stats are based on the git commit history](/docs/faq.html#how-are-the-aider-wrote-xx-of-code-stats-computed)
|
||||||
|
of the aider repo.
|
||||||
|
|
||||||
|
## Release notes
|
||||||
|
|
||||||
<!--[[[cog
|
<!--[[[cog
|
||||||
# This page is a copy of HISTORY.md, adding the front matter above.
|
# This page is a copy of HISTORY.md, adding the front matter above.
|
||||||
text = open("HISTORY.md").read()
|
text = open("HISTORY.md").read()
|
||||||
|
text = text.replace("# Release history", "")
|
||||||
cog.out(text)
|
cog.out(text)
|
||||||
]]]-->
|
]]]-->
|
||||||
|
|
||||||
# Release history
|
|
||||||
|
### Aider v0.69.0
|
||||||
|
|
||||||
|
- [Watch files](https://aider.chat/docs/usage/watch.html) improvements:
|
||||||
|
- Use `# ... AI?` comments to trigger aider and ask questions about your code.
|
||||||
|
- Now watches *all* files, not just certain source files.
|
||||||
|
- Use `# AI comments`, `// AI comments`, or `-- AI comments` to give aider instructions in any text file.
|
||||||
|
- Full support for Gemini Flash 2.0 Exp:
|
||||||
|
- `aider --model flash` or `aider --model gemini/gemini-2.0-flash-exp`
|
||||||
|
- [New `--multiline` flag and `/multiline-mode` command](https://aider.chat/docs/usage/commands.html#entering-multi-line-chat-messages) makes ENTER a soft newline and META-ENTER send the message, by @miradnanali.
|
||||||
|
- `/copy-context <instructions>` now takes optional "instructions" when [copying code context to the clipboard](https://aider.chat/docs/usage/copypaste.html#copy-aiders-code-context-to-your-clipboard-paste-into-the-web-ui).
|
||||||
|
- Improved clipboard error handling with helpful requirements install info.
|
||||||
|
- Ask 5% of users if they want to opt-in to analytics.
|
||||||
|
- `/voice` now lets you edit the transcribed text before sending.
|
||||||
|
- Disabled auto-complete in Y/N prompts.
|
||||||
|
- Aider wrote 68% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.68.0
|
||||||
|
|
||||||
|
- [Aider works with LLM web chat UIs](https://aider.chat/docs/usage/copypaste.html).
|
||||||
|
- New `--copy-paste` mode.
|
||||||
|
- 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).
|
||||||
|
- New `--api-key provider=key` setting.
|
||||||
|
- New `--set-env VAR=value` setting.
|
||||||
|
- Added bash and zsh support to `--watch-files`.
|
||||||
|
- Better error messages when missing dependencies for Gemini and Bedrock models.
|
||||||
|
- Control-D now properly exits the program.
|
||||||
|
- Don't count token costs when API provider returns a hard error.
|
||||||
|
- Bugfix so watch files works with files that don't have tree-sitter support.
|
||||||
|
- Bugfix so o1 models can be used as weak model.
|
||||||
|
- Updated shell command prompt.
|
||||||
|
- Added docstrings for all Coders.
|
||||||
|
- Reorganized command line arguments with improved help messages and grouping.
|
||||||
|
- Use the exact `sys.python` for self-upgrades.
|
||||||
|
- Added experimental Gemini models.
|
||||||
|
- Aider wrote 71% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.67.0
|
||||||
|
|
||||||
|
- [Use aider in your IDE or editor](https://aider.chat/docs/usage/watch.html).
|
||||||
|
- Run `aider --watch-files` and it will watch for instructions you add to your source files.
|
||||||
|
- One-liner `# ...` or `// ...` comments that start or end with "AI" are instructions to aider.
|
||||||
|
- When aider sees "AI!" it reads and follows all the instructions in AI comments.
|
||||||
|
- Support for new Amazon Bedrock Nova models.
|
||||||
|
- When `/run` or `/test` have non-zero exit codes, pre-fill "Fix that" into the next message prompt.
|
||||||
|
- `/diff` now invokes `git diff` to use your preferred diff tool.
|
||||||
|
- Added Ctrl-Z support for process suspension.
|
||||||
|
- Spinner now falls back to ASCII art if fancy symbols throw unicode errors.
|
||||||
|
- `--read` now expands `~` home dirs.
|
||||||
|
- Enabled exception capture in analytics.
|
||||||
|
- [Aider wrote 61% of the code in this release.](https://aider.chat/HISTORY.html)
|
||||||
|
|
||||||
|
### Aider v0.66.0
|
||||||
|
|
||||||
|
- PDF support for Sonnet and Gemini models.
|
||||||
|
- Added `--voice-input-device` to select audio input device for voice recording, by @preynal.
|
||||||
|
- Added `--timeout` option to configure API call timeouts.
|
||||||
|
- Set cwd to repo root when running shell commands.
|
||||||
|
- Added Ctrl-Up/Down keyboard shortcuts for per-message history navigation.
|
||||||
|
- Improved error handling for failed .gitignore file operations.
|
||||||
|
- Improved error handling for input history file permissions.
|
||||||
|
- Improved error handling for analytics file access.
|
||||||
|
- Removed spurious warning about disabling pretty in VSCode.
|
||||||
|
- Removed broken support for Dart.
|
||||||
|
- Bugfix when scraping URLs found in chat messages.
|
||||||
|
- Better handling of __version__ import errors.
|
||||||
|
- Improved `/drop` command to support substring matching for non-glob patterns.
|
||||||
|
- Aider wrote 82% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.65.1
|
||||||
|
|
||||||
|
- Bugfix to `--alias`.
|
||||||
|
|
||||||
|
### Aider v0.65.0
|
||||||
|
|
||||||
|
- Added `--alias` config to define [custom model aliases](https://aider.chat/docs/config/model-aliases.html).
|
||||||
|
- Added `--[no-]detect-urls` flag to disable detecting and offering to scrape URLs found in the chat.
|
||||||
|
- Ollama models now default to an 8k context window.
|
||||||
|
- Added [RepoMap support for Dart language](https://aider.chat/docs/languages.html) by @malkoG.
|
||||||
|
- Ask 2.5% of users if they want to opt-in to [analytics](https://aider.chat/docs/more/analytics.html).
|
||||||
|
- Skip suggesting files that share names with files already in chat.
|
||||||
|
- `/editor` returns and prefill the file content into the prompt, so you can use `/editor` to compose messages that start with `/commands`, etc.
|
||||||
|
- Enhanced error handling for analytics.
|
||||||
|
- Improved handling of UnknownEditFormat exceptions with helpful documentation links.
|
||||||
|
- Bumped dependencies to pick up grep-ast 0.4.0 for Dart language support.
|
||||||
|
- Aider wrote 81% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.64.1
|
||||||
|
|
||||||
|
- Disable streaming for o1 on OpenRouter.
|
||||||
|
|
||||||
|
### Aider v0.64.0
|
||||||
|
|
||||||
|
- Added [`/editor` command](https://aider.chat/docs/usage/commands.html) to open system editor for writing prompts, by @thehunmonkgroup.
|
||||||
|
- Full support for `gpt-4o-2024-11-20`.
|
||||||
|
- Stream o1 models by default.
|
||||||
|
- `/run` and suggested shell commands are less mysterious and now confirm that they "Added XX lines of output to the chat."
|
||||||
|
- Ask 1% of users if they want to opt-in to [analytics](https://aider.chat/docs/more/analytics.html).
|
||||||
|
- Added support for [optional multiline input tags](https://aider.chat/docs/usage/commands.html#entering-multi-line-chat-messages) with matching closing tags.
|
||||||
|
- Improved [model settings configuration](https://aider.chat/docs/config/adv-model-settings.html#global-extra-params) with support for global `extra_params` for `litellm.completion()`.
|
||||||
|
- Architect mode now asks to add files suggested by the LLM.
|
||||||
|
- Fixed bug in fuzzy model name matching.
|
||||||
|
- Added Timeout exception to handle API provider timeouts.
|
||||||
|
- Added `--show-release-notes` to control release notes display on first run of new version.
|
||||||
|
- Save empty dict to cache file on model metadata download failure, to delay retry.
|
||||||
|
- Improved error handling and code formatting.
|
||||||
|
- Aider wrote 74% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.63.2
|
||||||
|
|
||||||
|
- Fixed bug in fuzzy model name matching when litellm provider info is missing.
|
||||||
|
- Modified model metadata file loading to allow override of resource file.
|
||||||
|
- Allow recursive loading of dirs using `--read`.
|
||||||
|
- Updated dependency versions to pick up litellm fix for ollama models.
|
||||||
|
- Added exponential backoff retry when writing files to handle editor file locks.
|
||||||
|
- Updated Qwen 2.5 Coder 32B model configuration.
|
||||||
|
|
||||||
|
### Aider v0.63.1
|
||||||
|
|
||||||
|
- Fixed bug in git ignored file handling.
|
||||||
|
- Improved error handling for git operations.
|
||||||
|
|
||||||
|
### Aider v0.63.0
|
||||||
|
|
||||||
|
- Support for Qwen 2.5 Coder 32B.
|
||||||
|
- `/web` command just adds the page to the chat, without triggering an LLM response.
|
||||||
|
- Improved prompting for the user's preferred chat language.
|
||||||
|
- Improved handling of LiteLLM exceptions.
|
||||||
|
- Bugfix for double-counting tokens when reporting cache stats.
|
||||||
|
- Bugfix for the LLM creating new files.
|
||||||
|
- Other small bug fixes.
|
||||||
|
- Aider wrote 55% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.62.0
|
||||||
|
|
||||||
|
- Full support for Claude 3.5 Haiku
|
||||||
|
- Scored 75% on [aider's code editing leaderboard](https://aider.chat/docs/leaderboards/).
|
||||||
|
- Almost as good as Sonnet at much lower cost.
|
||||||
|
- Launch with `--haiku` to use it.
|
||||||
|
- Easily apply file edits from ChatGPT, Claude or other web apps
|
||||||
|
- Chat with ChatGPT or Claude via their web app.
|
||||||
|
- Give it your source files and ask for the changes you want.
|
||||||
|
- Use the web app's "copy response" button to copy the entire reply from the LLM.
|
||||||
|
- Run `aider --apply-clipboard-edits file-to-edit.js`.
|
||||||
|
- Aider will edit your file with the LLM's changes.
|
||||||
|
- Bugfix for creating new files.
|
||||||
|
- Aider wrote 84% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.61.0
|
||||||
|
|
||||||
|
- Load and save aider slash-commands to files:
|
||||||
|
- `/save <fname>` command will make a file of `/add` and `/read-only` commands that recreate the current file context in the chat.
|
||||||
|
- `/load <fname>` will replay the commands in the file.
|
||||||
|
- You can use `/load` to run any arbitrary set of slash-commands, not just `/add` and `/read-only`.
|
||||||
|
- Use `--load <fname>` to run a list of commands on launch, before the interactive chat begins.
|
||||||
|
- Anonymous, opt-in [analytics](https://aider.chat/docs/more/analytics.html) with no personal data sharing.
|
||||||
|
- Aider follows litellm's `supports_vision` attribute to enable image support for models.
|
||||||
|
- Bugfix for when diff mode flexibly handles the model using the wrong filename.
|
||||||
|
- Displays filenames in sorted order for `/add` and `/read-only`.
|
||||||
|
- New `--no-fancy-input` switch disables prompt toolkit input, now still available with `--no-pretty`.
|
||||||
|
- Override browser config with `--no-browser` or `--no-gui`.
|
||||||
|
- Offer to open documentation URLs when errors occur.
|
||||||
|
- Properly support all o1 models, regardless of provider.
|
||||||
|
- Improved layout of filenames above input prompt.
|
||||||
|
- Better handle corrupted repomap tags cache.
|
||||||
|
- Improved handling of API errors, especially when accessing the weak model.
|
||||||
|
- Aider wrote 68% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.60.1
|
||||||
|
|
||||||
|
- Enable image support for Sonnet 10/22.
|
||||||
|
- Display filenames in sorted order.
|
||||||
|
|
||||||
|
### Aider v0.60.0
|
||||||
|
|
||||||
|
- Full support for Sonnet 10/22, the new SOTA model on aider's code editing benchmark.
|
||||||
|
- Aider uses Sonnet 10/22 by default.
|
||||||
|
- Improved formatting of added and read-only files above chat prompt, by @jbellis.
|
||||||
|
- Improved support for o1 models by more flexibly parsing their nonconforming code edit replies.
|
||||||
|
- Corrected diff edit format prompt that only the first match is replaced.
|
||||||
|
- Stronger whole edit format prompt asking for clean file names.
|
||||||
|
- Now offers to add `.env` to the `.gitignore` file.
|
||||||
|
- Ships with a small model metadata json file to handle models not yet updated in litellm.
|
||||||
|
- Model settings for o1 models on azure.
|
||||||
|
- Bugfix to properly include URLs in `/help` RAG results.
|
||||||
|
- Aider wrote 49% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.59.1
|
||||||
|
|
||||||
|
- Check for obsolete `yes: true` in yaml config, show helpful error.
|
||||||
|
- Model settings for openrouter/anthropic/claude-3.5-sonnet:beta
|
||||||
|
|
||||||
|
### Aider v0.59.0
|
||||||
|
|
||||||
|
- Improvements to `/read-only`:
|
||||||
|
- Now supports shell-style auto-complete of the full file system.
|
||||||
|
- Still auto-completes the full paths of the repo files like `/add`.
|
||||||
|
- Now supports globs like `src/**/*.py`
|
||||||
|
- Renamed `--yes` to `--yes-always`.
|
||||||
|
- Now uses `AIDER_YES_ALWAYS` env var and `yes-always:` yaml key.
|
||||||
|
- Existing YAML and .env files will need to be updated.
|
||||||
|
- Can still abbreviate to `--yes` on the command line.
|
||||||
|
- Config file now uses standard YAML list syntax with ` - list entries`, one per line.
|
||||||
|
- `/settings` now includes the same announcement lines that would print at launch.
|
||||||
|
- Sanity checks the `--editor-model` on launch now, same as main and weak models.
|
||||||
|
- Added `--skip-sanity-check-repo` switch to speedup launch in large repos.
|
||||||
|
- Bugfix so architect mode handles Control-C properly.
|
||||||
|
- Repo-map is deterministic now, with improved caching logic.
|
||||||
|
- Improved commit message prompt.
|
||||||
|
- Aider wrote 77% of the code in this release.
|
||||||
|
|
||||||
|
### Aider v0.58.1
|
||||||
|
|
||||||
|
- Fixed bug where cache warming pings caused subsequent user messages to trigger a tight loop of LLM requests.
|
||||||
|
|
||||||
|
### Aider v0.58.0
|
||||||
|
|
||||||
|
- [Use a pair of Architect/Editor models for improved coding](https://aider.chat/2024/09/26/architect.html)
|
||||||
|
- Use a strong reasoning model like o1-preview as your Architect.
|
||||||
|
- Use a cheaper, faster model like gpt-4o as your Editor.
|
||||||
|
- New `--o1-preview` and `--o1-mini` shortcuts.
|
||||||
|
- Support for new Gemini 002 models.
|
||||||
|
- Better support for Qwen 2.5 models.
|
||||||
|
- Many confirmation questions can be skipped for the rest of the session with "(D)on't ask again" response.
|
||||||
|
- Autocomplete for `/read-only` supports the entire filesystem.
|
||||||
|
- New settings for completion menu colors.
|
||||||
|
- New `/copy` command to copy the last LLM response to the clipboard.
|
||||||
|
- Renamed `/clipboard` to `/paste`.
|
||||||
|
- Will now follow HTTP redirects when scraping urls.
|
||||||
|
- New `--voice-format` switch to send voice audio as wav/mp3/webm, by @mbailey.
|
||||||
|
- ModelSettings takes `extra_params` dict to specify any extras to pass to `litellm.completion()`.
|
||||||
|
- Support for cursor shapes when in vim mode.
|
||||||
|
- Numerous bug fixes.
|
||||||
|
- Aider wrote 53% of the code in this release.
|
||||||
|
|
||||||
### Aider v0.57.1
|
### Aider v0.57.1
|
||||||
|
|
||||||
@@ -680,7 +922,7 @@ cog.out(text)
|
|||||||
### Aider v0.14.0
|
### Aider v0.14.0
|
||||||
|
|
||||||
- [Support for Claude2 and other LLMs via OpenRouter](https://aider.chat/docs/faq.html#accessing-other-llms-with-openrouter) by @joshuavial
|
- [Support for Claude2 and other LLMs via OpenRouter](https://aider.chat/docs/faq.html#accessing-other-llms-with-openrouter) by @joshuavial
|
||||||
- Documentation for [running the aider benchmarking suite](https://github.com/paul-gauthier/aider/tree/main/benchmark)
|
- Documentation for [running the aider benchmarking suite](https://github.com/Aider-AI/aider/tree/main/benchmark)
|
||||||
- Aider now requires Python >= 3.9
|
- Aider now requires Python >= 3.9
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ exclude:
|
|||||||
|
|
||||||
aux_links:
|
aux_links:
|
||||||
"GitHub":
|
"GitHub":
|
||||||
- "https://github.com/paul-gauthier/aider"
|
- "https://github.com/Aider-AI/aider"
|
||||||
"Discord":
|
"Discord":
|
||||||
- "https://discord.gg/Tv2uQnR88V"
|
- "https://discord.gg/Tv2uQnR88V"
|
||||||
"Blog":
|
"Blog":
|
||||||
@@ -32,11 +32,11 @@ aux_links:
|
|||||||
|
|
||||||
nav_external_links:
|
nav_external_links:
|
||||||
- title: "GitHub"
|
- title: "GitHub"
|
||||||
url: "https://github.com/paul-gauthier/aider"
|
url: "https://github.com/Aider-AI/aider"
|
||||||
- title: "Discord"
|
- title: "Discord"
|
||||||
url: "https://discord.gg/Tv2uQnR88V"
|
url: "https://discord.gg/Tv2uQnR88V"
|
||||||
|
|
||||||
repository: paul-gauthier/aider
|
repository: Aider-AI/aider
|
||||||
|
|
||||||
callouts:
|
callouts:
|
||||||
tip:
|
tip:
|
||||||
|
|||||||
492
aider/website/_data/architect.yml
Normal file
492
aider/website/_data/architect.yml
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
- dirname: 2024-09-25-21-17-19--architect-sonnet-sonnet-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: claude-3.5-sonnet
|
||||||
|
editor_model: claude-3.5-sonnet
|
||||||
|
editor_edit_format: diff
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: c18d6a8-dirty
|
||||||
|
pass_rate_1: 62.4
|
||||||
|
pass_rate_2: 80.5
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 3
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 183
|
||||||
|
lazy_comments: 6
|
||||||
|
syntax_errors: 9
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model openrouter/anthropic/claude-3.5-sonnet
|
||||||
|
date: 2024-09-25
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 25.1
|
||||||
|
total_cost: 4.9502
|
||||||
|
|
||||||
|
- dirname: 2024-07-04-14-32-08--claude-3.5-sonnet-diff-continue
|
||||||
|
test_cases: 133
|
||||||
|
model: claude-3.5-sonnet
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 35f21b5
|
||||||
|
pass_rate_1: 57.1
|
||||||
|
pass_rate_2: 77.4
|
||||||
|
percent_cases_well_formed: 99.2
|
||||||
|
error_outputs: 23
|
||||||
|
released: 2024-06-20
|
||||||
|
num_malformed_responses: 4
|
||||||
|
num_with_malformed_responses: 1
|
||||||
|
user_asks: 2
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 1
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --sonnet
|
||||||
|
date: 2024-07-04
|
||||||
|
versions: 0.42.1-dev
|
||||||
|
seconds_per_case: 17.6
|
||||||
|
total_cost: 3.6346
|
||||||
|
|
||||||
|
- dirname: 2024-09-25-21-25-01--architect-o1mini-4o-jr-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-mini
|
||||||
|
editor_model: gpt-4o
|
||||||
|
editor_edit_format: diff
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 3f682ed-dirty, 25e833b
|
||||||
|
pass_rate_1: 51.1
|
||||||
|
pass_rate_2: 70.7
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 12
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 214
|
||||||
|
lazy_comments: 6
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model o1-mini
|
||||||
|
date: 2024-09-25
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 23.7
|
||||||
|
total_cost: 9.3158
|
||||||
|
|
||||||
|
- dirname: 2024-09-26-15-05-58--architect-o1mini-deep-jr-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-mini
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 1676653-dirty
|
||||||
|
editor_model: deepseek
|
||||||
|
editor_edit_format: whole
|
||||||
|
pass_rate_1: 51.9
|
||||||
|
pass_rate_2: 71.4
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 199
|
||||||
|
lazy_comments: 11
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model o1-mini
|
||||||
|
date: 2024-09-26
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 48.2
|
||||||
|
total_cost: 5.6069
|
||||||
|
|
||||||
|
- dirname: 2024-09-25-21-33-40--architect-4o-4o-jr-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: gpt-4o
|
||||||
|
editor_model: gpt-4o
|
||||||
|
editor_edit_format: diff
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 9f3cd92
|
||||||
|
pass_rate_1: 56.4
|
||||||
|
pass_rate_2: 75.2
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 13
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 207
|
||||||
|
lazy_comments: 8
|
||||||
|
syntax_errors: 1
|
||||||
|
indentation_errors: 1
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 3
|
||||||
|
command: aider --model gpt-4o
|
||||||
|
date: 2024-09-25
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 18.2
|
||||||
|
total_cost: 6.0918
|
||||||
|
|
||||||
|
- dirname: 2024-09-21-16-45-11--o1-preview-flex-sr-markers
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-preview
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 5493654-dirty
|
||||||
|
pass_rate_1: 57.9
|
||||||
|
pass_rate_2: 79.7
|
||||||
|
percent_cases_well_formed: 93.2
|
||||||
|
error_outputs: 11
|
||||||
|
num_malformed_responses: 11
|
||||||
|
num_with_malformed_responses: 9
|
||||||
|
user_asks: 3
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 10
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model o1-preview
|
||||||
|
date: 2024-09-21
|
||||||
|
versions: 0.56.1.dev
|
||||||
|
seconds_per_case: 80.9
|
||||||
|
total_cost: 63.9190
|
||||||
|
|
||||||
|
- dirname: 2024-09-25-21-39-05--architect-o1preview-4o-jr-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-preview
|
||||||
|
editor_model: gpt-4o
|
||||||
|
editor_edit_format: diff
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 9f3cd92
|
||||||
|
pass_rate_1: 63.2
|
||||||
|
pass_rate_2: 80.5
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 23
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 191
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 4
|
||||||
|
command: aider --model o1-preview
|
||||||
|
date: 2024-09-25
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 42.3
|
||||||
|
total_cost: 39.3766
|
||||||
|
|
||||||
|
- dirname: 2024-09-25-21-52-42--architect-o1preview-sonnet-jr-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-preview
|
||||||
|
editor_model: claude-3.5-sonnet
|
||||||
|
editor_edit_format: diff
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 9f3cd92
|
||||||
|
editor_model: claude-3-5-sonnet
|
||||||
|
pass_rate_1: 60.9
|
||||||
|
pass_rate_2: 82.7
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 1
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 180
|
||||||
|
lazy_comments: 3
|
||||||
|
syntax_errors: 9
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 3
|
||||||
|
command: aider --model o1-preview
|
||||||
|
date: 2024-09-25
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 44.9
|
||||||
|
total_cost: 37.6192
|
||||||
|
|
||||||
|
- dirname: 2024-09-21-16-40-56--o1-mini-flex-sr-markers
|
||||||
|
test_cases: 36
|
||||||
|
model: o1-mini
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 5493654
|
||||||
|
pass_rate_1: 50.0
|
||||||
|
pass_rate_2: 61.1
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 3
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 1
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model o1-mini
|
||||||
|
date: 2024-09-21
|
||||||
|
versions: 0.56.1.dev
|
||||||
|
seconds_per_case: 26.7
|
||||||
|
total_cost: 2.4226
|
||||||
|
|
||||||
|
- dirname: 2024-09-25-23-12-14--architect-o1mini-deep-jr-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-mini
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 9f3cd92-dirty
|
||||||
|
editor_model: deepseek
|
||||||
|
editor_edit_format: diff
|
||||||
|
pass_rate_1: 48.9
|
||||||
|
pass_rate_2: 69.2
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 1
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 202
|
||||||
|
lazy_comments: 12
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model o1-mini
|
||||||
|
date: 2024-09-25
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 52.2
|
||||||
|
total_cost: 5.7927
|
||||||
|
|
||||||
|
- dirname: 2024-09-25-23-18-16--architect-o1preview-deep-jr-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-preview
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 9f3cd92-dirty
|
||||||
|
editor_model: deepseek
|
||||||
|
editor_edit_format: diff
|
||||||
|
pass_rate_1: 64.7
|
||||||
|
pass_rate_2: 80.5
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 5
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 180
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model o1-preview
|
||||||
|
date: 2024-09-25
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 73.2
|
||||||
|
total_cost: 35.7887
|
||||||
|
|
||||||
|
- dirname: 2024-09-25-23-30-36--architect-o1preview-deep-jr-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-preview
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 9f3cd92-dirty
|
||||||
|
editor_model: deepseek
|
||||||
|
editor_edit_format: whole
|
||||||
|
pass_rate_1: 63.9
|
||||||
|
pass_rate_2: 85.0
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 181
|
||||||
|
lazy_comments: 12
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model o1-preview
|
||||||
|
date: 2024-09-25
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 67.4
|
||||||
|
total_cost: 35.3152
|
||||||
|
|
||||||
|
- dirname: 2024-09-26-15-15-17--architect-sonnet-deep-jr-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: claude-3.5-sonnet
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: bc1559f-dirty
|
||||||
|
editor_model: deepseek
|
||||||
|
editor_edit_format: whole
|
||||||
|
pass_rate_1: 61.7
|
||||||
|
pass_rate_2: 78.9
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 184
|
||||||
|
lazy_comments: 5
|
||||||
|
syntax_errors: 9
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 3
|
||||||
|
command: aider --model openrouter/anthropic/claude-3.5-sonnet
|
||||||
|
date: 2024-09-26
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 37.2
|
||||||
|
total_cost: 2.1510
|
||||||
|
|
||||||
|
- dirname: 2024-09-26-15-33-28--costs-gpt4o-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: gpt-4o
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 89aa385-dirty
|
||||||
|
pass_rate_1: 55.6
|
||||||
|
pass_rate_2: 71.4
|
||||||
|
percent_cases_well_formed: 97.7
|
||||||
|
error_outputs: 5
|
||||||
|
num_malformed_responses: 5
|
||||||
|
num_with_malformed_responses: 3
|
||||||
|
user_asks: 10
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 1
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model gpt-4o
|
||||||
|
date: 2024-09-26
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 9.7
|
||||||
|
total_cost: 3.8088
|
||||||
|
|
||||||
|
- dirname: 2024-09-26-15-41-08--architect-4o-deep-jr-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: gpt-4o
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 89aa385-dirty
|
||||||
|
editor_model: deepseek
|
||||||
|
editor_edit_format: whole
|
||||||
|
pass_rate_1: 60.9
|
||||||
|
pass_rate_2: 73.7
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 187
|
||||||
|
lazy_comments: 12
|
||||||
|
syntax_errors: 5
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model gpt-4o
|
||||||
|
date: 2024-09-26
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 38.0
|
||||||
|
total_cost: 2.4737
|
||||||
|
|
||||||
|
- dirname: 2024-09-26-15-54-08--architect-4o-deep-jr-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: gpt-4o
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 89aa385-dirty
|
||||||
|
editor_model: deepseek
|
||||||
|
editor_edit_format: diff
|
||||||
|
pass_rate_1: 57.1
|
||||||
|
pass_rate_2: 74.4
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 4
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 192
|
||||||
|
lazy_comments: 6
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model gpt-4o
|
||||||
|
date: 2024-09-26
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 44.0
|
||||||
|
total_cost: 2.5498
|
||||||
|
|
||||||
|
- dirname: 2024-09-26-16-06-39--architect-sonnet-deep-jr-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: claude-3.5-sonnet
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 89aa385-dirty
|
||||||
|
editor_model: deepseek
|
||||||
|
editor_edit_format: diff
|
||||||
|
pass_rate_1: 61.7
|
||||||
|
pass_rate_2: 78.9
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 2
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 184
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 9
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model openrouter/anthropic/claude-3.5-sonnet
|
||||||
|
date: 2024-09-26
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 43.2
|
||||||
|
total_cost: 2.1488
|
||||||
|
|
||||||
|
- dirname: 2024-09-27-18-15-32--architect-4omini-4omini
|
||||||
|
test_cases: 133
|
||||||
|
model: gpt-4o-mini
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 0bd8058-dirty
|
||||||
|
editor_model: gpt-4o-mini
|
||||||
|
editor_edit_format: whole
|
||||||
|
pass_rate_1: 43.6
|
||||||
|
pass_rate_2: 60.2
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 208
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 3
|
||||||
|
command: aider --model gpt-4o-mini
|
||||||
|
date: 2024-09-27
|
||||||
|
versions: 0.57.2.dev
|
||||||
|
seconds_per_case: 21.0
|
||||||
|
total_cost: 0.1527
|
||||||
|
|
||||||
|
- dirname: 2024-07-18-18-57-46--gpt-4o-mini-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: gpt-4o-mini
|
||||||
|
edit_format: whole
|
||||||
|
commit_hash: d31eef3-dirty
|
||||||
|
pass_rate_1: 40.6
|
||||||
|
pass_rate_2: 55.6
|
||||||
|
released: 2024-07-18
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 1
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 1
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 1
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model gpt-4o-mini
|
||||||
|
date: 2024-07-18
|
||||||
|
versions: 0.44.1-dev
|
||||||
|
seconds_per_case: 7.8
|
||||||
|
total_cost: 0.0916
|
||||||
|
|
||||||
|
- dirname: 2024-09-29-22-35-36--architect-o1preview-o1mini-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-preview
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 53ca83b
|
||||||
|
editor_model: o1-mini
|
||||||
|
editor_edit_format: whole
|
||||||
|
pass_rate_1: 65.4
|
||||||
|
pass_rate_2: 85.0
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 179
|
||||||
|
lazy_comments: 4
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model o1-preview
|
||||||
|
date: 2024-09-29
|
||||||
|
versions: 0.58.1.dev
|
||||||
|
seconds_per_case: 39.7
|
||||||
|
total_cost: 36.2078
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
322
aider/website/_data/quant.yml
Normal file
322
aider/website/_data/quant.yml
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
- dirname: 2024-11-09-11-09-15--Qwen2.5-Coder-32B-Instruct
|
||||||
|
test_cases: 133
|
||||||
|
model: "HuggingFace via GLHF: BF16"
|
||||||
|
released: 2024-11-12
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: ec9982a
|
||||||
|
pass_rate_1: 59.4
|
||||||
|
pass_rate_2: 71.4
|
||||||
|
percent_cases_well_formed: 94.7
|
||||||
|
error_outputs: 17
|
||||||
|
num_malformed_responses: 17
|
||||||
|
num_with_malformed_responses: 7
|
||||||
|
user_asks: 1
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 3
|
||||||
|
command: aider --model openai/hf:Qwen/Qwen2.5-Coder-32B-Instruct --openai-api-base https://glhf.chat/api/openai/v1
|
||||||
|
date: 2024-11-09
|
||||||
|
versions: 0.59.2.dev
|
||||||
|
seconds_per_case: 22.5
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-22-18-56-13--ollama-qwen2.5-coder:32b-instruct-fp16
|
||||||
|
test_cases: 132
|
||||||
|
model: "Ollama: fp16"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: f06452c-dirty, 6a0a97c-dirty, 4e9ae16-dirty, 5506d0f-dirty
|
||||||
|
pass_rate_1: 58.3
|
||||||
|
pass_rate_2: 71.4
|
||||||
|
percent_cases_well_formed: 90.2
|
||||||
|
error_outputs: 27
|
||||||
|
num_malformed_responses: 26
|
||||||
|
num_with_malformed_responses: 13
|
||||||
|
user_asks: 2
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model ollama/qwen2.5-coder:32b-instruct-fp16
|
||||||
|
date: 2024-11-22
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 119.6
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-22-14-53-26--hyperbolic-qwen25coder32binstruct
|
||||||
|
test_cases: 133
|
||||||
|
model: "Hyperbolic: BF16"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: f9ef161, 17aef7b-dirty
|
||||||
|
pass_rate_1: 57.9
|
||||||
|
pass_rate_2: 69.2
|
||||||
|
percent_cases_well_formed: 91.7
|
||||||
|
error_outputs: 30
|
||||||
|
num_malformed_responses: 29
|
||||||
|
num_with_malformed_responses: 11
|
||||||
|
user_asks: 9
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 4
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model openai/Qwen/Qwen2.5-Coder-32B-Instruct --openai-api-base https://api.hyperbolic.xyz/v1/
|
||||||
|
date: 2024-11-22
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 33.2
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-22-17-53-35--qwen25-coder-32b-Instruct-4bit
|
||||||
|
test_cases: 133
|
||||||
|
model: "mlx-community: 4bit"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: a16dcab-dirty
|
||||||
|
pass_rate_1: 60.2
|
||||||
|
pass_rate_2: 72.2
|
||||||
|
percent_cases_well_formed: 88.7
|
||||||
|
error_outputs: 31
|
||||||
|
num_malformed_responses: 30
|
||||||
|
num_with_malformed_responses: 15
|
||||||
|
user_asks: 6
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 1
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model openai/mlx-community/Qwen2.5-Coder-32B-Instruct-4bit
|
||||||
|
date: 2024-11-23
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 53.4
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-23-15-07-20--qwen25-coder-32b-Instruct-8bit
|
||||||
|
test_cases: 133
|
||||||
|
model: "mlx-community: 8bit"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: a16dcab-dirty
|
||||||
|
pass_rate_1: 59.4
|
||||||
|
pass_rate_2: 72.2
|
||||||
|
percent_cases_well_formed: 92.5
|
||||||
|
error_outputs: 20
|
||||||
|
num_malformed_responses: 15
|
||||||
|
num_with_malformed_responses: 10
|
||||||
|
user_asks: 7
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 5
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model openai/mlx-community/Qwen2.5-Coder-32B-Instruct-8bit
|
||||||
|
date: 2024-11-23
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 98.4
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-24-22-18-18--or-all-or-fixed-blank-messages2
|
||||||
|
test_cases: 133
|
||||||
|
model: "OpenRouter: multiple"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 0c59d32
|
||||||
|
pass_rate_1: 57.1
|
||||||
|
pass_rate_2: 67.7
|
||||||
|
percent_cases_well_formed: 95.5
|
||||||
|
error_outputs: 56
|
||||||
|
num_malformed_responses: 10
|
||||||
|
num_with_malformed_responses: 6
|
||||||
|
user_asks: 14
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 6
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 3
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model openrouter/qwen/qwen-2.5-coder-32b-instruct
|
||||||
|
date: 2024-11-24
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 21.2
|
||||||
|
total_cost: 0.1420
|
||||||
|
|
||||||
|
- dirname: 2024-11-23-21-08-53--ollama-qwen2.5-coder:32b-instruct-q4_K_M-8kctx
|
||||||
|
test_cases: 133
|
||||||
|
model: "Ollama: q4_K_M"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: baa1335-dirty, e63df83-dirty, ff8c1aa-dirty
|
||||||
|
pass_rate_1: 54.9
|
||||||
|
pass_rate_2: 66.9
|
||||||
|
percent_cases_well_formed: 94.0
|
||||||
|
error_outputs: 21
|
||||||
|
num_malformed_responses: 21
|
||||||
|
num_with_malformed_responses: 8
|
||||||
|
user_asks: 5
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 3
|
||||||
|
command: aider --model ollama/qwen2.5-coder:32b-instruct-q4_K_M
|
||||||
|
date: 2024-11-23
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 35.7
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-24-02-23-32--deepinfra-qwen-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: "Deepinfra: BF16"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: bb78e2f
|
||||||
|
pass_rate_1: 58.6
|
||||||
|
pass_rate_2: 72.2
|
||||||
|
percent_cases_well_formed: 94.7
|
||||||
|
error_outputs: 15
|
||||||
|
num_malformed_responses: 13
|
||||||
|
num_with_malformed_responses: 7
|
||||||
|
user_asks: 3
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 2
|
||||||
|
test_timeouts: 3
|
||||||
|
command: aider --model deepinfra/Qwen/Qwen2.5-Coder-32B-Instruct
|
||||||
|
date: 2024-11-24
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 17.5
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-24-04-12-58--fireworks-qwen-diff
|
||||||
|
test_cases: 133
|
||||||
|
model: "Fireworks: unknown"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 757eac0
|
||||||
|
pass_rate_1: 57.9
|
||||||
|
pass_rate_2: 72.2
|
||||||
|
percent_cases_well_formed: 94.0
|
||||||
|
error_outputs: 23
|
||||||
|
num_malformed_responses: 19
|
||||||
|
num_with_malformed_responses: 8
|
||||||
|
user_asks: 8
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 6
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 4
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct
|
||||||
|
date: 2024-11-24
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 10.4
|
||||||
|
total_cost: 0.5759
|
||||||
|
|
||||||
|
- dirname: 2024-11-24-02-04-59--ollama-qwen2.5-coder:32b-instruct-q2_K-8kctx
|
||||||
|
test_cases: 133
|
||||||
|
model: "Ollama: q2_K"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 757eac0, bb78e2f, 8d0ba40-dirty, 1d09e96
|
||||||
|
pass_rate_1: 48.9
|
||||||
|
pass_rate_2: 61.7
|
||||||
|
percent_cases_well_formed: 91.7
|
||||||
|
error_outputs: 32
|
||||||
|
num_malformed_responses: 32
|
||||||
|
num_with_malformed_responses: 11
|
||||||
|
user_asks: 8
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model ollama/qwen2.5-coder:32b-instruct-q2_K
|
||||||
|
date: 2024-11-24
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 97.8
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-24-14-56-49--qwen25-32b-or-fireworks
|
||||||
|
test_cases: 133
|
||||||
|
model: "Fireworks via OpenRouter: unknown"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: c2f184f
|
||||||
|
pass_rate_1: 55.6
|
||||||
|
pass_rate_2: 67.7
|
||||||
|
percent_cases_well_formed: 94.0
|
||||||
|
error_outputs: 39
|
||||||
|
num_malformed_responses: 24
|
||||||
|
num_with_malformed_responses: 8
|
||||||
|
user_asks: 13
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 1
|
||||||
|
indentation_errors: 1
|
||||||
|
exhausted_context_windows: 7
|
||||||
|
test_timeouts: 4
|
||||||
|
command: aider --model openrouter/qwen/qwen-2.5-coder-32b-instruct
|
||||||
|
date: 2024-11-24
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 16.1
|
||||||
|
total_cost: 0.1391
|
||||||
|
|
||||||
|
- dirname: 2024-11-24-22-03-19--or-hyperbolic-or-fixed-blank-messages2
|
||||||
|
test_cases: 133
|
||||||
|
model: "Hyperbolic via OpenRouter: BF16"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 0c59d32
|
||||||
|
pass_rate_1: 55.6
|
||||||
|
pass_rate_2: 68.4
|
||||||
|
percent_cases_well_formed: 89.5
|
||||||
|
error_outputs: 28
|
||||||
|
num_malformed_responses: 24
|
||||||
|
num_with_malformed_responses: 14
|
||||||
|
user_asks: 29
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 1
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 4
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model openrouter/qwen/qwen-2.5-coder-32b-instruct
|
||||||
|
date: 2024-11-24
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 41.5
|
||||||
|
total_cost: 0.1402
|
||||||
|
|
||||||
|
- dirname: 2024-11-24-15-00-50--qwen25-32b-or-deepinfra
|
||||||
|
test_cases: 133
|
||||||
|
model: "Deepinfra via OpenRouter: BF16"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: c2f184f
|
||||||
|
pass_rate_1: 57.1
|
||||||
|
pass_rate_2: 69.9
|
||||||
|
percent_cases_well_formed: 89.5
|
||||||
|
error_outputs: 35
|
||||||
|
num_malformed_responses: 31
|
||||||
|
num_with_malformed_responses: 14
|
||||||
|
user_asks: 11
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 1
|
||||||
|
indentation_errors: 1
|
||||||
|
exhausted_context_windows: 4
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model openrouter/qwen/qwen-2.5-coder-32b-instruct
|
||||||
|
date: 2024-11-24
|
||||||
|
versions: 0.64.2.dev
|
||||||
|
seconds_per_case: 28.5
|
||||||
|
total_cost: 0.1390
|
||||||
|
|
||||||
|
- dirname: 2024-11-26-03-15-06--ollama-qwen2.5-coder:32b-instruct-fp16-2kctx
|
||||||
|
test_cases: 132
|
||||||
|
model: "Ollama: fp16, 2k ctx"
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 68be6c5-dirty, 554d274, 2ff3a23, 2ff3a23-dirty, 61759f9, dd48b74, 3ebd47d-dirty
|
||||||
|
pass_rate_1: 43.2
|
||||||
|
pass_rate_2: 51.9
|
||||||
|
percent_cases_well_formed: 46.2
|
||||||
|
error_outputs: 171
|
||||||
|
num_malformed_responses: 165
|
||||||
|
num_with_malformed_responses: 71
|
||||||
|
user_asks: 97
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 4
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 0
|
||||||
|
command: "aider --model ollama/qwen2.5-coder:32b-instruct-fp16 # num_ctx: 2048"
|
||||||
|
date: 2024-11-26
|
||||||
|
versions: 0.64.2.dev,0.65.1.dev
|
||||||
|
seconds_per_case: 188.6
|
||||||
|
total_cost: 0.0000
|
||||||
170
aider/website/_data/qwq.yml
Normal file
170
aider/website/_data/qwq.yml
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
|
||||||
|
- dirname: 2024-11-28-21-38-50--architect-qwq-haiku-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: QwQ + Haiku
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: e4a1d6f
|
||||||
|
editor_model: claude-3-5-haiku-20241022
|
||||||
|
editor_edit_format: editor-whole
|
||||||
|
pass_rate_1: 54.1
|
||||||
|
pass_rate_2: 71.4
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 4
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 196
|
||||||
|
lazy_comments: 4
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model openrouter/qwen/qwq-32b-preview --editor-model claude-3-5-haiku-20241022 --edit-format editor-whole
|
||||||
|
date: 2024-11-28
|
||||||
|
versions: 0.65.2.dev
|
||||||
|
seconds_per_case: 154.7
|
||||||
|
total_cost: 1.4196
|
||||||
|
|
||||||
|
- dirname: 2024-11-28-19-24-35--architect-qwq-deepseek-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: QwQ + DeepSeek V2.5
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: e4a1d6f
|
||||||
|
editor_model: deepseek/deepseek-chat
|
||||||
|
editor_edit_format: editor-whole
|
||||||
|
pass_rate_1: 55.6
|
||||||
|
pass_rate_2: 67.7
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 3
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 193
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model openrouter/qwen/qwq-32b-preview --editor-model deepseek/deepseek-chat --edit-format editor-whole
|
||||||
|
date: 2024-11-28
|
||||||
|
versions: 0.65.2.dev
|
||||||
|
seconds_per_case: 170.3
|
||||||
|
total_cost: 0.1558
|
||||||
|
|
||||||
|
|
||||||
|
- dirname: 2024-11-09-11-09-15--Qwen2.5-Coder-32B-Instruct
|
||||||
|
test_cases: 133
|
||||||
|
model: Qwen2.5 Coder 32B-I
|
||||||
|
released: 2024-11-12
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: ec9982a
|
||||||
|
pass_rate_1: 59.4
|
||||||
|
pass_rate_2: 71.4
|
||||||
|
percent_cases_well_formed: 94.7
|
||||||
|
error_outputs: 17
|
||||||
|
num_malformed_responses: 17
|
||||||
|
num_with_malformed_responses: 7
|
||||||
|
user_asks: 1
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 3
|
||||||
|
command: aider --model openai/hf:Qwen/Qwen2.5-Coder-32B-Instruct --openai-api-base https://glhf.chat/api/openai/v1 (via GLHF)
|
||||||
|
date: 2024-11-09
|
||||||
|
versions: 0.59.2.dev
|
||||||
|
seconds_per_case: 22.5
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-12-04-00-10-39--architect-qwq-qwen
|
||||||
|
test_cases: 132
|
||||||
|
model: QwQ + Qwen2.5 Coder 32B-I
|
||||||
|
edit_format: architect
|
||||||
|
commit_hash: 51c02da
|
||||||
|
editor_model: openrouter/qwen/qwen-2.5-coder-32b-instruct
|
||||||
|
editor_edit_format: editor-whole
|
||||||
|
pass_rate_1: 58.3
|
||||||
|
pass_rate_2: 73.6
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 3
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 186
|
||||||
|
lazy_comments: 5
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model openrouter/qwen/qwq-32b-preview --editor-model openrouter/qwen/qwen-2.5-coder-32b-instruct --editor-edit-format editor-whole
|
||||||
|
date: 2024-12-04
|
||||||
|
versions: 0.66.1.dev
|
||||||
|
seconds_per_case: 144.1
|
||||||
|
total_cost: 0.1444
|
||||||
|
|
||||||
|
- dirname: 2024-12-04-00-42-05--qwq-alone-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: QwQ
|
||||||
|
edit_format: whole
|
||||||
|
commit_hash: 19004c0
|
||||||
|
pass_rate_1: 33.1
|
||||||
|
pass_rate_2: 42.1
|
||||||
|
percent_cases_well_formed: 91.0
|
||||||
|
error_outputs: 28
|
||||||
|
num_malformed_responses: 12
|
||||||
|
num_with_malformed_responses: 12
|
||||||
|
user_asks: 119
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 22
|
||||||
|
indentation_errors: 9
|
||||||
|
exhausted_context_windows: 2
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model openrouter/qwen/qwq-32b-preview
|
||||||
|
date: 2024-12-04
|
||||||
|
versions: 0.66.1.dev
|
||||||
|
seconds_per_case: 414.3
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-09-12-19-57-35--o1-mini-whole
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-mini
|
||||||
|
edit_format: whole
|
||||||
|
commit_hash: 36fa773-dirty, 291b456
|
||||||
|
pass_rate_1: 49.6
|
||||||
|
pass_rate_2: 70.7
|
||||||
|
percent_cases_well_formed: 90.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
user_asks: 17
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model o1-mini
|
||||||
|
date: 2024-09-12
|
||||||
|
versions: 0.56.1.dev
|
||||||
|
seconds_per_case: 103.0
|
||||||
|
total_cost: 5.3725
|
||||||
|
|
||||||
|
- dirname: 2024-09-21-16-45-11--o1-preview-flex-sr-markers
|
||||||
|
test_cases: 133
|
||||||
|
model: o1-preview
|
||||||
|
_released: 2024-09-12
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 5493654-dirty
|
||||||
|
pass_rate_1: 57.9
|
||||||
|
pass_rate_2: 79.7
|
||||||
|
percent_cases_well_formed: 93.2
|
||||||
|
error_outputs: 11
|
||||||
|
num_malformed_responses: 11
|
||||||
|
num_with_malformed_responses: 9
|
||||||
|
user_asks: 3
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 10
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 1
|
||||||
|
command: aider --model o1-preview
|
||||||
|
date: 2024-09-21
|
||||||
|
versions: 0.56.1.dev
|
||||||
|
seconds_per_case: 80.9
|
||||||
|
total_cost: 63.9190
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
|
|
||||||
- dirname: 2024-07-01-18-30-33--refac-claude-3.5-sonnet-diff-not-lazy
|
- dirname: 2024-07-01-18-30-33--refac-claude-3.5-sonnet-diff-not-lazy
|
||||||
test_cases: 89
|
test_cases: 89
|
||||||
model: claude-3.5-sonnet (diff)
|
model: claude-3.5-sonnet-20240620
|
||||||
edit_format: diff
|
edit_format: diff
|
||||||
commit_hash: 7396e38-dirty
|
commit_hash: 7396e38-dirty
|
||||||
pass_rate_1: 64.0
|
pass_rate_1: 64.0
|
||||||
@@ -229,4 +229,70 @@
|
|||||||
date: 2024-09-05
|
date: 2024-09-05
|
||||||
versions: 0.55.1.dev
|
versions: 0.55.1.dev
|
||||||
seconds_per_case: 225.4
|
seconds_per_case: 225.4
|
||||||
total_cost: 1.0338
|
total_cost: 1.0338
|
||||||
|
|
||||||
|
- dirname: 2024-10-22-19-57-27--refac-openrouter-sonnet-1022
|
||||||
|
test_cases: 89
|
||||||
|
model: claude-3-5-sonnet-20241022
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 4a3e6ef
|
||||||
|
pass_rate_1: 92.1
|
||||||
|
percent_cases_well_formed: 91.0
|
||||||
|
error_outputs: 13
|
||||||
|
num_malformed_responses: 12
|
||||||
|
num_with_malformed_responses: 8
|
||||||
|
user_asks: 14
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --sonnet
|
||||||
|
date: 2024-10-22
|
||||||
|
versions: 0.60.1.dev
|
||||||
|
seconds_per_case: 32.5
|
||||||
|
total_cost: 8.4644
|
||||||
|
|
||||||
|
- dirname: 2024-10-22-20-03-10--refac-o1mini
|
||||||
|
test_cases: 89
|
||||||
|
model: o1-mini
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 4a3e6ef-dirty
|
||||||
|
pass_rate_1: 44.9
|
||||||
|
percent_cases_well_formed: 29.2
|
||||||
|
error_outputs: 151
|
||||||
|
num_malformed_responses: 150
|
||||||
|
num_with_malformed_responses: 63
|
||||||
|
user_asks: 28
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 5
|
||||||
|
indentation_errors: 4
|
||||||
|
exhausted_context_windows: 1
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model o1-mini
|
||||||
|
date: 2024-10-22
|
||||||
|
versions: 0.60.1.dev
|
||||||
|
seconds_per_case: 115.3
|
||||||
|
total_cost: 29.0492
|
||||||
|
|
||||||
|
- dirname: 2024-10-22-20-26-36--refac-o1preview
|
||||||
|
test_cases: 89
|
||||||
|
model: o1-preview
|
||||||
|
edit_format: diff
|
||||||
|
commit_hash: 4a3e6ef-dirty
|
||||||
|
pass_rate_1: 75.3
|
||||||
|
percent_cases_well_formed: 57.3
|
||||||
|
error_outputs: 75
|
||||||
|
num_malformed_responses: 74
|
||||||
|
num_with_malformed_responses: 38
|
||||||
|
user_asks: 19
|
||||||
|
lazy_comments: 2
|
||||||
|
syntax_errors: 2
|
||||||
|
indentation_errors: 3
|
||||||
|
exhausted_context_windows: 1
|
||||||
|
test_timeouts: 0
|
||||||
|
command: aider --model o1-preview
|
||||||
|
date: 2024-10-22
|
||||||
|
versions: 0.60.1.dev
|
||||||
|
seconds_per_case: 231.7
|
||||||
|
total_cost: 120.9850
|
||||||
185
aider/website/_includes/edit-leaderboard.js
Normal file
185
aider/website/_includes/edit-leaderboard.js
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var ctx = document.getElementById('editChart').getContext('2d');
|
||||||
|
const blueDiagonalPattern = pattern.draw('diagonal', 'rgba(54, 162, 235, 0.2)');
|
||||||
|
const redDiagonalPattern = pattern.draw('diagonal', 'rgba(255, 99, 132, 0.2)');
|
||||||
|
let displayedData = [];
|
||||||
|
|
||||||
|
const HIGHTLIGHT_MODEL = 'no no no';
|
||||||
|
var leaderboardData = {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Percent completed correctly',
|
||||||
|
data: [],
|
||||||
|
backgroundColor: function(context) {
|
||||||
|
const row = allData[context.dataIndex];
|
||||||
|
if (row && row.edit_format === 'whole') {
|
||||||
|
return diagonalPattern;
|
||||||
|
}
|
||||||
|
const label = leaderboardData.labels[context.dataIndex] || '';
|
||||||
|
return (label && label.includes(HIGHTLIGHT_MODEL)) ? 'rgba(255, 99, 132, 0.2)' : 'rgba(54, 162, 235, 0.2)';
|
||||||
|
},
|
||||||
|
borderColor: function(context) {
|
||||||
|
const label = context.chart.data.labels[context.dataIndex] || '';
|
||||||
|
return (label && label.includes(HIGHTLIGHT_MODEL)) ? 'rgba(255, 99, 132, 1)' : 'rgba(54, 162, 235, 1)';
|
||||||
|
},
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
var allData = [];
|
||||||
|
{% for row in edit_sorted %}
|
||||||
|
allData.push({
|
||||||
|
model: '{{ row.model }}',
|
||||||
|
pass_rate_2: {{ row.pass_rate_2 }},
|
||||||
|
percent_cases_well_formed: {{ row.percent_cases_well_formed }},
|
||||||
|
edit_format: '{{ row.edit_format }}'
|
||||||
|
});
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
function updateChart() {
|
||||||
|
var selectedRows = document.querySelectorAll('tr.selected');
|
||||||
|
var showAll = selectedRows.length === 0;
|
||||||
|
|
||||||
|
displayedData = [];
|
||||||
|
leaderboardData.labels = [];
|
||||||
|
leaderboardData.datasets[0].data = [];
|
||||||
|
|
||||||
|
allData.forEach(function(row, index) {
|
||||||
|
var rowElement = document.getElementById('edit-row-' + index);
|
||||||
|
if (showAll) {
|
||||||
|
rowElement.classList.remove('selected');
|
||||||
|
}
|
||||||
|
if (showAll || rowElement.classList.contains('selected')) {
|
||||||
|
displayedData.push(row);
|
||||||
|
leaderboardData.labels.push(row.model);
|
||||||
|
leaderboardData.datasets[0].data.push(row.pass_rate_2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
leaderboardChart.update();
|
||||||
|
leaderboardChart.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use displayedData in the backgroundColor callback instead of allData
|
||||||
|
leaderboardData.datasets[0].backgroundColor = function(context) {
|
||||||
|
const row = displayedData[context.dataIndex];
|
||||||
|
const label = leaderboardData.labels[context.dataIndex] || '';
|
||||||
|
if (label && label.includes(HIGHTLIGHT_MODEL)) {
|
||||||
|
if (row && row.edit_format === 'whole') return redDiagonalPattern;
|
||||||
|
else return 'rgba(255, 99, 132, 0.2)';
|
||||||
|
} else if (row && row.edit_format === 'whole') {
|
||||||
|
return blueDiagonalPattern;
|
||||||
|
} else {
|
||||||
|
return 'rgba(54, 162, 235, 0.2)';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var tableBody = document.querySelector('table tbody');
|
||||||
|
allData.forEach(function(row, index) {
|
||||||
|
var tr = tableBody.children[index];
|
||||||
|
tr.id = 'edit-row-' + index;
|
||||||
|
tr.style.cursor = 'pointer';
|
||||||
|
tr.onclick = function() {
|
||||||
|
this.classList.toggle('selected');
|
||||||
|
updateChart();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var leaderboardChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: leaderboardData,
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
labels: {
|
||||||
|
generateLabels: function(chart) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: 'Diff-like format',
|
||||||
|
fillStyle: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
strokeStyle: 'rgba(54, 162, 235, 1)',
|
||||||
|
lineWidth: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Whole format',
|
||||||
|
fillStyle: blueDiagonalPattern,
|
||||||
|
strokeStyle: 'rgba(54, 162, 235, 1)',
|
||||||
|
lineWidth: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Percent completed correctly'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
callback: function(value, index) {
|
||||||
|
const label = this.getLabelForValue(value);
|
||||||
|
if (label.length <= "claude-3-5-sonnet".length) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all possible split positions
|
||||||
|
const splitPositions = [];
|
||||||
|
for (let i = 0; i < label.length; i++) {
|
||||||
|
if (label[i] === '-' || label[i] === ' ') {
|
||||||
|
splitPositions.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (splitPositions.length === 0) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find split position closest to middle
|
||||||
|
const middle = label.length / 2;
|
||||||
|
const splitIndex = splitPositions.reduce((closest, current) => {
|
||||||
|
return Math.abs(current - middle) < Math.abs(closest - middle) ? current : closest;
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
label.slice(0, splitIndex),
|
||||||
|
label.slice(splitIndex + 1)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateChart();
|
||||||
|
|
||||||
|
// Add search functionality for edit table
|
||||||
|
document.getElementById('editSearchInput').addEventListener('keyup', function() {
|
||||||
|
var searchWords = this.value.toLowerCase().split(' ').filter(word => word.length > 0);
|
||||||
|
var tableBody = document.querySelector('table:first-of-type tbody');
|
||||||
|
var rows = tableBody.getElementsByTagName('tr');
|
||||||
|
|
||||||
|
displayedData = [];
|
||||||
|
leaderboardData.labels = [];
|
||||||
|
leaderboardData.datasets[0].data = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
var rowText = rows[i].textContent;
|
||||||
|
if (searchWords.every(word => rowText.toLowerCase().includes(word))) {
|
||||||
|
rows[i].style.display = '';
|
||||||
|
displayedData.push(allData[i]);
|
||||||
|
leaderboardData.labels.push(allData[i].model);
|
||||||
|
leaderboardData.datasets[0].data.push(allData[i].pass_rate_2);
|
||||||
|
} else {
|
||||||
|
rows[i].style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leaderboardChart.update();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{: .tip }
|
|
||||||
All API keys can be stored in a
|
|
||||||
[.env file](/docs/config/dotenv.html).
|
|
||||||
Only OpenAI and Anthropic keys can be stored in the
|
|
||||||
[YAML config file](/docs/config/aider_conf.html).
|
|
||||||
|
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
|
|
||||||
You can get started quickly like this:
|
You can get started quickly like this, with python 3.8-3.13:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
python -m pip install aider-chat
|
python -m pip install aider-install
|
||||||
|
aider-install
|
||||||
|
|
||||||
# Change directory into a git repo
|
# Change directory into your code base
|
||||||
cd /to/your/git/repo
|
cd /to/your/git/repo
|
||||||
|
|
||||||
# Work with Claude 3.5 Sonnet on your repo
|
# Work with Claude 3.5 Sonnet on your repo
|
||||||
export ANTHROPIC_API_KEY=your-key-goes-here
|
aider --model sonnet --anthropic-api-key your-key-goes-here
|
||||||
aider
|
|
||||||
|
|
||||||
# Work with GPT-4o on your repo
|
# Work with GPT-4o on your repo
|
||||||
export OPENAI_API_KEY=your-key-goes-here
|
aider --model gpt-4o --openai-api-key your-key-goes-here
|
||||||
aider
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -18,3 +18,63 @@
|
|||||||
<link rel="mask-icon" href="{{ '/assets/icons/safari-pinned-tab.svg' | relative_url }}" color="#5bbad5">
|
<link rel="mask-icon" href="{{ '/assets/icons/safari-pinned-tab.svg' | relative_url }}" color="#5bbad5">
|
||||||
<meta name="msapplication-TileColor" content="#da532c">
|
<meta name="msapplication-TileColor" content="#da532c">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
|
<!-- Cookie Consent -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js" data-cfasync="false"></script>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function(){
|
||||||
|
window.cookieconsent.initialise({
|
||||||
|
palette: {
|
||||||
|
popup: {
|
||||||
|
background: "#333333",
|
||||||
|
text: "#ffffff"
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
background: "#ffffff",
|
||||||
|
text: "#333333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: "opt-in",
|
||||||
|
position: "bottom-left",
|
||||||
|
showLink: false,
|
||||||
|
dismissOnScroll: true,
|
||||||
|
cookie: {
|
||||||
|
name: 'cookieconsent_status',
|
||||||
|
path: '/',
|
||||||
|
domain: 'aider.chat',
|
||||||
|
expiryDays: 365
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
message: "This website uses analytics cookies to help us understand how you use the site.",
|
||||||
|
dismiss: "Decline",
|
||||||
|
allow: "Accept",
|
||||||
|
link: "Learn more",
|
||||||
|
href: "https://aider.chat/docs/legal/privacy.html"
|
||||||
|
},
|
||||||
|
onInitialise: function(status) {
|
||||||
|
var type = this.options.type;
|
||||||
|
var didConsent = this.hasConsented();
|
||||||
|
if (didConsent) {
|
||||||
|
initPostHog();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onStatusChange: function(status, chosenBefore) {
|
||||||
|
var type = this.options.type;
|
||||||
|
var didConsent = this.hasConsented();
|
||||||
|
if (didConsent) {
|
||||||
|
initPostHog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// PostHog initialization function
|
||||||
|
function initPostHog() {
|
||||||
|
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
|
||||||
|
posthog.init('phc_99T7muzafUMMZX15H8XePbMSreEUzahHbtWjy3l5Qbv', {
|
||||||
|
api_host:'https://us.i.posthog.com',
|
||||||
|
person_profiles: 'identified_only'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
If you need more help, please check our
|
If you need more help, please check our
|
||||||
[GitHub issues](https://github.com/paul-gauthier/aider/issues)
|
[GitHub issues](https://github.com/Aider-AI/aider/issues)
|
||||||
and file a new issue if your problem isn't discussed.
|
and file a new issue if your problem isn't discussed.
|
||||||
Or drop into our
|
Or drop into our
|
||||||
[Discord](https://discord.gg/Tv2uQnR88V)
|
[Discord](https://discord.gg/Tv2uQnR88V)
|
||||||
|
|||||||
4
aider/website/_includes/keys.md
Normal file
4
aider/website/_includes/keys.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{: .tip :}
|
||||||
|
See the
|
||||||
|
[API key configuration docs](/docs/config/api-keys.html)
|
||||||
|
for information on how to configure and store your API keys.
|
||||||
0
aider/website/_includes/leaderboard.js
Normal file
0
aider/website/_includes/leaderboard.js
Normal file
@@ -1,5 +1,22 @@
|
|||||||
You can send long, multi-line messages in the chat in a few ways:
|
You can send long, multi-line messages in the chat in a few ways:
|
||||||
- Paste a multi-line message directly into the chat.
|
- Paste a multi-line message directly into the chat.
|
||||||
- Enter `{` alone on the first line to start a multiline message and `}` alone on the last line to end it.
|
- Enter `{` alone on the first line to start a multiline message and `}` alone on the last line to end it.
|
||||||
|
- Or, start with `{tag` (where "tag" is any sequence of letters/numbers) and end with `tag}`. This is useful when you need to include closing braces `}` in your message.
|
||||||
- Use Meta-ENTER to start a new line without sending the message (Esc+ENTER in some environments).
|
- Use Meta-ENTER to start a new line without sending the message (Esc+ENTER in some environments).
|
||||||
- Use `/clipboard` to paste text from the clipboard into the chat.
|
- Use `/paste` to paste text from the clipboard into the chat.
|
||||||
|
- Use the `/editor` command to open your editor to create the next chat message. See [editor configuration docs](/docs/config/editor.html) for more info.
|
||||||
|
- Use multiline-mode, which swaps the function of Meta-Enter and Enter, so that Enter inserts a newline, and Meta-Enter submits your command. To enable multiline mode:
|
||||||
|
- Use the `/multiline-mode` command to toggle it during a session.
|
||||||
|
- Use the `--multiline` switch.
|
||||||
|
|
||||||
|
Example with a tag:
|
||||||
|
```
|
||||||
|
{python
|
||||||
|
def hello():
|
||||||
|
print("Hello}") # Note: contains a brace
|
||||||
|
python}
|
||||||
|
```
|
||||||
|
|
||||||
|
{: .note }
|
||||||
|
People often ask for SHIFT-ENTER to be a soft-newline.
|
||||||
|
Unfortunately there is no portable way to detect that keystroke in terminals.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
Aider is AI pair programming in your terminal.
|
Aider is AI pair programming in your terminal.
|
||||||
Aider is on
|
Aider is on
|
||||||
<a href="https://github.com/paul-gauthier/aider">GitHub</a>
|
<a href="https://github.com/Aider-AI/aider">GitHub</a>
|
||||||
and
|
and
|
||||||
<a href="https://discord.gg/Tv2uQnR88V">Discord</a>.
|
<a href="https://discord.gg/Tv2uQnR88V">Discord</a>.
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
95
aider/website/_includes/quant-chart.js
Normal file
95
aider/website/_includes/quant-chart.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var ctx = document.getElementById('quantChart').getContext('2d');
|
||||||
|
var allData = [];
|
||||||
|
{% for row in site.data.quant %}
|
||||||
|
allData.push({
|
||||||
|
model: '{{ row.model }}',
|
||||||
|
pass_rate_2: {{ row.pass_rate_2 }}
|
||||||
|
});
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
// Sort data by pass_rate_2 in descending order
|
||||||
|
allData.sort((a, b) => b.pass_rate_2 - a.pass_rate_2);
|
||||||
|
|
||||||
|
var chart;
|
||||||
|
|
||||||
|
function updateChart(filterText) {
|
||||||
|
var filteredData = allData.filter(row =>
|
||||||
|
row.model.toLowerCase().includes(filterText.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
var chartData = {
|
||||||
|
labels: filteredData.map(row => row.model),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Percent completed correctly',
|
||||||
|
data: filteredData.map(row => row.pass_rate_2),
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (chart) {
|
||||||
|
chart.data = chartData;
|
||||||
|
chart.update();
|
||||||
|
} else {
|
||||||
|
chart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: chartData,
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Aider code editing benchmark',
|
||||||
|
font: {
|
||||||
|
size: 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Percent completed correctly',
|
||||||
|
font: {
|
||||||
|
size: 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
font: {
|
||||||
|
size: 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
font: {
|
||||||
|
size: 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Provider: quantization',
|
||||||
|
font: {
|
||||||
|
size: 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial chart render
|
||||||
|
updateChart('');
|
||||||
|
|
||||||
|
// Connect search input to chart filtering
|
||||||
|
document.getElementById('quantSearchInput').addEventListener('keyup', function() {
|
||||||
|
updateChart(this.value);
|
||||||
|
});
|
||||||
|
});
|
||||||
120
aider/website/_includes/qwq-chart.js
Normal file
120
aider/website/_includes/qwq-chart.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var ctx = document.getElementById('qwqChart').getContext('2d');
|
||||||
|
var allData = [];
|
||||||
|
{% for row in site.data.qwq %}
|
||||||
|
allData.push({
|
||||||
|
model: '{{ row.model }}',
|
||||||
|
pass_rate_2: {{ row.pass_rate_2 }}
|
||||||
|
});
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
// Sort data by pass_rate_2 in descending order
|
||||||
|
allData.sort((a, b) => b.pass_rate_2 - a.pass_rate_2);
|
||||||
|
|
||||||
|
var chart;
|
||||||
|
|
||||||
|
function updateChart(filterText) {
|
||||||
|
var filteredData = allData.filter(row =>
|
||||||
|
row.model.toLowerCase().includes(filterText.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
var chartData = {
|
||||||
|
labels: filteredData.map(row => row.model),
|
||||||
|
datasets: [{
|
||||||
|
data: filteredData.map(row => row.pass_rate_2),
|
||||||
|
backgroundColor: filteredData.map(row =>
|
||||||
|
(row.model === 'Qwen2.5 Coder 32B-I' || row.model === 'Sonnet (SOTA)' || row.model === 'o1-mini' || row.model === 'o1-preview' || row.model === 'QwQ')
|
||||||
|
? 'rgba(75, 192, 192, 0.2)' // Green for solo models
|
||||||
|
: 'rgba(54, 162, 235, 0.2)' // Blue for architect+editor
|
||||||
|
),
|
||||||
|
borderColor: filteredData.map(row =>
|
||||||
|
(row.model === 'Qwen2.5 Coder 32B-I' || row.model === 'Sonnet (SOTA)' || row.model === 'o1-mini' || row.model === 'o1-preview' || row.model === 'QwQ')
|
||||||
|
? 'rgba(75, 192, 192, 1)' // Green border for solo models
|
||||||
|
: 'rgba(54, 162, 235, 1)' // Blue border for architect+editor
|
||||||
|
),
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (chart) {
|
||||||
|
chart.data = chartData;
|
||||||
|
chart.update();
|
||||||
|
} else {
|
||||||
|
chart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: chartData,
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
position: 'top',
|
||||||
|
labels: {
|
||||||
|
font: {
|
||||||
|
size: 14
|
||||||
|
},
|
||||||
|
generateLabels: function(chart) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: 'Solo model',
|
||||||
|
fillStyle: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
strokeStyle: 'rgba(75, 192, 192, 1)',
|
||||||
|
lineWidth: 1,
|
||||||
|
fontColor: '#666'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Architect + Editor',
|
||||||
|
fillStyle: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
strokeStyle: 'rgba(54, 162, 235, 1)',
|
||||||
|
lineWidth: 1,
|
||||||
|
fontColor: '#666'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Aider code editing benchmark (%)',
|
||||||
|
font: {
|
||||||
|
size: 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
font: {
|
||||||
|
size: 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
font: {
|
||||||
|
size: 16
|
||||||
|
},
|
||||||
|
callback: function(value, index) {
|
||||||
|
const label = this.getLabelForValue(value);
|
||||||
|
if (label.includes(" + ")) {
|
||||||
|
const parts = label.split(" + ");
|
||||||
|
return [parts[0] + " +", parts[1]];
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial chart render
|
||||||
|
updateChart('');
|
||||||
|
|
||||||
|
// Connect search input to chart filtering
|
||||||
|
document.getElementById('qwqSearchInput').addEventListener('keyup', function() {
|
||||||
|
updateChart(this.value);
|
||||||
|
});
|
||||||
|
});
|
||||||
90
aider/website/_includes/refactor-leaderboard.js
Normal file
90
aider/website/_includes/refactor-leaderboard.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var ctx = document.getElementById('refacChart').getContext('2d');
|
||||||
|
var leaderboardData = {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Percent completed correctly',
|
||||||
|
data: [],
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
var allData = [];
|
||||||
|
{% for row in refac_sorted %}
|
||||||
|
allData.push({
|
||||||
|
model: '{{ row.model }}',
|
||||||
|
pass_rate_1: {{ row.pass_rate_1 }},
|
||||||
|
percent_cases_well_formed: {{ row.percent_cases_well_formed }}
|
||||||
|
});
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
function updateChart() {
|
||||||
|
var selectedRows = document.querySelectorAll('tr.selected');
|
||||||
|
var showAll = selectedRows.length === 0;
|
||||||
|
|
||||||
|
leaderboardData.labels = [];
|
||||||
|
leaderboardData.datasets[0].data = [];
|
||||||
|
|
||||||
|
allData.forEach(function(row, index) {
|
||||||
|
var rowElement = document.getElementById('refac-row-' + index);
|
||||||
|
if (showAll) {
|
||||||
|
rowElement.classList.remove('selected');
|
||||||
|
}
|
||||||
|
if (showAll || rowElement.classList.contains('selected')) {
|
||||||
|
leaderboardData.labels.push(row.model);
|
||||||
|
leaderboardData.datasets[0].data.push(row.pass_rate_1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
leaderboardChart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
var tableBody = document.querySelectorAll('table tbody')[1];
|
||||||
|
allData.forEach(function(row, index) {
|
||||||
|
var tr = tableBody.children[index];
|
||||||
|
tr.id = 'refac-row-' + index;
|
||||||
|
tr.style.cursor = 'pointer';
|
||||||
|
tr.onclick = function() {
|
||||||
|
this.classList.toggle('selected');
|
||||||
|
updateChart();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var leaderboardChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: leaderboardData,
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateChart();
|
||||||
|
|
||||||
|
// Add search functionality for refactoring table
|
||||||
|
document.getElementById('refacSearchInput').addEventListener('keyup', function() {
|
||||||
|
var searchWords = this.value.toLowerCase().split(' ').filter(word => word.length > 0);
|
||||||
|
var tableBody = document.querySelectorAll('table tbody')[1];
|
||||||
|
var rows = tableBody.getElementsByTagName('tr');
|
||||||
|
|
||||||
|
leaderboardData.labels = [];
|
||||||
|
leaderboardData.datasets[0].data = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
var rowText = rows[i].textContent;
|
||||||
|
if (searchWords.every(word => rowText.toLowerCase().includes(word))) {
|
||||||
|
rows[i].style.display = '';
|
||||||
|
leaderboardData.labels.push(allData[i].model);
|
||||||
|
leaderboardData.datasets[0].data.push(allData[i].pass_rate_1);
|
||||||
|
} else {
|
||||||
|
rows[i].style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leaderboardChart.update();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
To use aider with pipx on replit, you can run these commands in the replit shell:
|
To use aider with pipx on replit, you can run these commands in the replit shell:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
pip install pipx
|
pip install pipx
|
||||||
pipx run aider-chat ...normal aider args...
|
pipx run aider-chat ...normal aider args...
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
Aider has special support for providing
|
|
||||||
OpenAI and Anthropic API keys
|
|
||||||
via
|
|
||||||
[command line switches](/docs/config/options.html)
|
|
||||||
and
|
|
||||||
[yaml config file](/docs/config/aider_conf.html).
|
|
||||||
*All other LLM providers* must
|
|
||||||
have their keys and settings
|
|
||||||
specified in environment variables.
|
|
||||||
This can be done in your shell,
|
|
||||||
or by using a
|
|
||||||
[.env file](/docs/config/dotenv.html).
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{: .tip }
|
|
||||||
Using a Python
|
|
||||||
[virtual environment](https://docs.python.org/3/library/venv.html){:target="_blank"}
|
|
||||||
is recommended.
|
|
||||||
Or, you could
|
|
||||||
[use pipx to install aider](/docs/install/pipx.html)
|
|
||||||
once for your whole system.
|
|
||||||
@@ -110,9 +110,9 @@ source code, by including the critical lines of code for each definition.
|
|||||||
|
|
||||||
Here's a
|
Here's a
|
||||||
sample of the map of the aider repo, just showing the maps of
|
sample of the map of the aider repo, just showing the maps of
|
||||||
[base_coder.py](https://github.com/paul-gauthier/aider/blob/main/aider/coders/base_coder.py)
|
[base_coder.py](https://github.com/Aider-AI/aider/blob/main/aider/coders/base_coder.py)
|
||||||
and
|
and
|
||||||
[commands.py](https://github.com/paul-gauthier/aider/blob/main/aider/commands.py)
|
[commands.py](https://github.com/Aider-AI/aider/blob/main/aider/commands.py)
|
||||||
:
|
:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -188,7 +188,7 @@ It specifically uses the
|
|||||||
[py-tree-sitter-languages](https://github.com/grantjenks/py-tree-sitter-languages)
|
[py-tree-sitter-languages](https://github.com/grantjenks/py-tree-sitter-languages)
|
||||||
python module,
|
python module,
|
||||||
which provides simple, pip-installable binary wheels for
|
which provides simple, pip-installable binary wheels for
|
||||||
[most popular programming languages](https://github.com/paul-gauthier/grep-ast/blob/main/grep_ast/parsers.py).
|
[most popular programming languages](https://github.com/Aider-AI/grep-ast/blob/main/grep_ast/parsers.py).
|
||||||
|
|
||||||
Tree-sitter parses source code into an Abstract Syntax Tree (AST) based
|
Tree-sitter parses source code into an Abstract Syntax Tree (AST) based
|
||||||
on the syntax of the programming language.
|
on the syntax of the programming language.
|
||||||
@@ -209,7 +209,7 @@ that aider originally used.
|
|||||||
Switching from ctags to tree-sitter provides a bunch of benefits:
|
Switching from ctags to tree-sitter provides a bunch of benefits:
|
||||||
|
|
||||||
- The map is richer, showing full function call signatures and other details straight from the source files.
|
- The map is richer, showing full function call signatures and other details straight from the source files.
|
||||||
- Thanks to `py-tree-sitter-languages`, we get full support for many programming languages via a python package that's automatically installed as part of the normal `python -m pip install aider-chat`.
|
- Thanks to `py-tree-sitter-languages`, we get full support for many programming languages via a python package that's automatically installed as part of the normal `python -m pip install -U aider-chat`.
|
||||||
- We remove the requirement for users to manually install `universal-ctags` via some external tool or package manager (brew, apt, choco, etc).
|
- We remove the requirement for users to manually install `universal-ctags` via some external tool or package manager (brew, apt, choco, etc).
|
||||||
- Tree-sitter integration is a key enabler for future work and capabilities for aider.
|
- Tree-sitter integration is a key enabler for future work and capabilities for aider.
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ just install [aider](https://aider.chat/docs/install.html).
|
|||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
Aider uses
|
Aider uses
|
||||||
[modified versions of the tags.scm files](https://github.com/paul-gauthier/aider/tree/main/aider/queries)
|
[modified versions of the tags.scm files](https://github.com/Aider-AI/aider/tree/main/aider/queries)
|
||||||
from these
|
from these
|
||||||
open source tree-sitter language implementations:
|
open source tree-sitter language implementations:
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ making it the best available model for pair programming with AI.
|
|||||||
To use Claude 3 Opus with aider:
|
To use Claude 3 Opus with aider:
|
||||||
|
|
||||||
```
|
```
|
||||||
python -m pip install aider-chat
|
python -m pip install -U aider-chat
|
||||||
export ANTHROPIC_API_KEY=sk-...
|
export ANTHROPIC_API_KEY=sk-...
|
||||||
aider --opus
|
aider --opus
|
||||||
```
|
```
|
||||||
|
|
||||||
## Aider's code editing benchmark
|
## Aider's code editing benchmark
|
||||||
|
|
||||||
[Aider](https://github.com/paul-gauthier/aider)
|
[Aider](https://github.com/Aider-AI/aider)
|
||||||
is an open source command line chat tool that lets you
|
is an open source command line chat tool that lets you
|
||||||
pair program with AI on code in your local git repo.
|
pair program with AI on code in your local git repo.
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def some_complex_method(foo, bar):
|
|||||||
# ... implement method here ...
|
# ... implement method here ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Aider uses a ["laziness" benchmark suite](https://github.com/paul-gauthier/refactor-benchmark)
|
Aider uses a ["laziness" benchmark suite](https://github.com/Aider-AI/refactor-benchmark)
|
||||||
which is designed to both provoke and quantify lazy coding.
|
which is designed to both provoke and quantify lazy coding.
|
||||||
It consists of
|
It consists of
|
||||||
89 python refactoring tasks
|
89 python refactoring tasks
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ It also supports [connecting to almost any LLM](https://aider.chat/docs/llms.htm
|
|||||||
Use the `--browser` switch to launch the browser version of aider:
|
Use the `--browser` switch to launch the browser version of aider:
|
||||||
|
|
||||||
```
|
```
|
||||||
python -m pip install aider-chat
|
python -m pip install -U aider-chat
|
||||||
|
|
||||||
export OPENAI_API_KEY=<key> # Mac/Linux
|
export OPENAI_API_KEY=<key> # Mac/Linux
|
||||||
setx OPENAI_API_KEY <key> # Windows, restart shell after setx
|
setx OPENAI_API_KEY <key> # Windows, restart shell after setx
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ nav_exclude: true
|
|||||||
I recently wanted to draw a graph showing how LLM code editing skill has been
|
I recently wanted to draw a graph showing how LLM code editing skill has been
|
||||||
changing over time as new models have been released by OpenAI, Anthropic and others.
|
changing over time as new models have been released by OpenAI, Anthropic and others.
|
||||||
I have all the
|
I have all the
|
||||||
[data in a yaml file](https://github.com/paul-gauthier/aider/blob/main/website/_data/edit_leaderboard.yml) that is used to render
|
[data in a yaml file](https://github.com/Aider-AI/aider/blob/main/website/_data/edit_leaderboard.yml) that is used to render
|
||||||
[aider's LLM leaderboards](https://aider.chat/docs/leaderboards/).
|
[aider's LLM leaderboards](https://aider.chat/docs/leaderboards/).
|
||||||
|
|
||||||
Below is the aider chat transcript, which shows:
|
Below is the aider chat transcript, which shows:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ This increases the ability of the LLM to understand the problem and
|
|||||||
make the correct changes to resolve it.
|
make the correct changes to resolve it.
|
||||||
|
|
||||||
Aider ships with basic linters built with tree-sitter that support
|
Aider ships with basic linters built with tree-sitter that support
|
||||||
[most popular programming languages](https://github.com/paul-gauthier/grep-ast/blob/main/grep_ast/parsers.py).
|
[most popular programming languages](https://github.com/Aider-AI/grep-ast/blob/main/grep_ast/parsers.py).
|
||||||
These built in linters will detect syntax errors and other fatal problems with the code.
|
These built in linters will detect syntax errors and other fatal problems with the code.
|
||||||
|
|
||||||
You can also configure aider to use your preferred linters.
|
You can also configure aider to use your preferred linters.
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ The held out "acceptance tests" were *only* used
|
|||||||
after benchmarking to compute statistics on which problems aider
|
after benchmarking to compute statistics on which problems aider
|
||||||
correctly resolved.
|
correctly resolved.
|
||||||
|
|
||||||
The [full harness to run aider on SWE Bench Lite is available on GitHub](https://github.com/paul-gauthier/aider-swe-bench).
|
The [full harness to run aider on SWE Bench Lite is available on GitHub](https://github.com/Aider-AI/aider-swe-bench).
|
||||||
|
|
||||||
The benchmarking process was similar to how a developer might use aider to
|
The benchmarking process was similar to how a developer might use aider to
|
||||||
resolve a GitHub issue:
|
resolve a GitHub issue:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Aider has written 7% of its own code
|
title: Aider has written 7% of its own code (outdated, now 70%)
|
||||||
excerpt: Aider has written 7% of its own code, via 600+ commits that inserted 4.8K and deleted 1.5K lines of code.
|
excerpt: This article is quite out dated. Aider is currently writing about 70% of the new code in each release.
|
||||||
highlight_image: /assets/self-assembly.jpg
|
highlight_image: /assets/self-assembly.jpg
|
||||||
nav_exclude: true
|
nav_exclude: true
|
||||||
---
|
---
|
||||||
@@ -8,12 +8,19 @@ nav_exclude: true
|
|||||||
<p class="post-date">{{ page.date | date: "%B %d, %Y" }}</p>
|
<p class="post-date">{{ page.date | date: "%B %d, %Y" }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Aider has written 7% of its own code
|
# Aider has written 7% of its own code (outdated, now 70%)
|
||||||
|
|
||||||
[](https://aider.chat/assets/self-assembly.jpg)
|
[](https://aider.chat/assets/self-assembly.jpg)
|
||||||
|
|
||||||
|
{: .note }
|
||||||
|
This article is quite old and outdated.
|
||||||
|
Aider is currently writing about 70% of the new code
|
||||||
|
in each release.
|
||||||
|
See
|
||||||
|
[aider's release history](/HISTORY.html) for the latest statistics.
|
||||||
|
|
||||||
The
|
The
|
||||||
[aider git repo](https://github.com/paul-gauthier/aider)
|
[aider git repo](https://github.com/Aider-AI/aider)
|
||||||
currently contains about 4K commits and 14K lines of code.
|
currently contains about 4K commits and 14K lines of code.
|
||||||
|
|
||||||
Aider made 15% of the commits, inserting 4.8K and deleting 1.5K lines of code.
|
Aider made 15% of the commits, inserting 4.8K and deleting 1.5K lines of code.
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ with the problem statement
|
|||||||
submitted as the opening chat message from "the user".
|
submitted as the opening chat message from "the user".
|
||||||
- After that aider ran as normal, except all of aider's
|
- After that aider ran as normal, except all of aider's
|
||||||
suggestions were always accepted without user approval.
|
suggestions were always accepted without user approval.
|
||||||
- A [simple harness](https://github.com/paul-gauthier/aider-swe-bench#the-aider-agent) was used to retry the SWE Bench problem if aider produced code that wasn't *plausibly correct*.
|
- A [simple harness](https://github.com/Aider-AI/aider-swe-bench#the-aider-agent) was used to retry the SWE Bench problem if aider produced code that wasn't *plausibly correct*.
|
||||||
Plausibly correct means that aider reported that it had successfully edited the repo
|
Plausibly correct means that aider reported that it had successfully edited the repo
|
||||||
without causing syntax errors or breaking any *pre-existing* tests.
|
without causing syntax errors or breaking any *pre-existing* tests.
|
||||||
- If the solution from aider with GPT-4o wasn't plausible, the harness launched aider to try again from scratch using Claude 3 Opus.
|
- If the solution from aider with GPT-4o wasn't plausible, the harness launched aider to try again from scratch using Claude 3 Opus.
|
||||||
@@ -90,7 +90,7 @@ For a detailed discussion of the benchmark
|
|||||||
methodology, see the
|
methodology, see the
|
||||||
[article about aider's SWE Bench Lite results](https://aider.chat/2024/05/22/swe-bench-lite.html).
|
[article about aider's SWE Bench Lite results](https://aider.chat/2024/05/22/swe-bench-lite.html).
|
||||||
Also, the
|
Also, the
|
||||||
[aider SWE Bench repository on GitHub](https://github.com/paul-gauthier/aider-swe-bench)
|
[aider SWE Bench repository on GitHub](https://github.com/Aider-AI/aider-swe-bench)
|
||||||
contains the harness and statistics code used for the benchmarks.
|
contains the harness and statistics code used for the benchmarks.
|
||||||
|
|
||||||
The benchmarking process was similar to how a developer might use aider to
|
The benchmarking process was similar to how a developer might use aider to
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ Users who tested Sonnet with a preview of
|
|||||||
[aider's latest release](https://aider.chat/HISTORY.html#aider-v0410)
|
[aider's latest release](https://aider.chat/HISTORY.html#aider-v0410)
|
||||||
were thrilled:
|
were thrilled:
|
||||||
|
|
||||||
- *Works like a charm. It is a monster. It refactors files of any size like it is nothing. The continue trick with Sonnet is truly the holy grail. Aider beats [other tools] hands down. I'm going to cancel both subscriptions.* -- [Emasoft](https://github.com/paul-gauthier/aider/issues/705#issuecomment-2200338971)
|
- *Works like a charm. It is a monster. It refactors files of any size like it is nothing. The continue trick with Sonnet is truly the holy grail. Aider beats [other tools] hands down. I'm going to cancel both subscriptions.* -- [Emasoft](https://github.com/Aider-AI/aider/issues/705#issuecomment-2200338971)
|
||||||
- *Thanks heaps for this feature - it's a real game changer. I can be more ambitious when asking Claude for larger features.* -- [cngarrison](https://github.com/paul-gauthier/aider/issues/705#issuecomment-2196026656)
|
- *Thanks heaps for this feature - it's a real game changer. I can be more ambitious when asking Claude for larger features.* -- [cngarrison](https://github.com/Aider-AI/aider/issues/705#issuecomment-2196026656)
|
||||||
- *Fantastic...! It's such an improvement not being constrained by output token length issues. [I refactored] a single JavaScript file into seven smaller files using a single Aider request.* -- [John Galt](https://discord.com/channels/1131200896827654144/1253492379336441907/1256250487934554143)
|
- *Fantastic...! It's such an improvement not being constrained by output token length issues. [I refactored] a single JavaScript file into seven smaller files using a single Aider request.* -- [John Galt](https://discord.com/channels/1131200896827654144/1253492379336441907/1256250487934554143)
|
||||||
|
|
||||||
## Hitting the 4k token output limit
|
## Hitting the 4k token output limit
|
||||||
@@ -116,7 +116,7 @@ for more details, but
|
|||||||
you can get started quickly with aider and Sonnet like this:
|
you can get started quickly with aider and Sonnet like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ python -m pip install aider-chat
|
$ python -m pip install -U aider-chat
|
||||||
|
|
||||||
$ export ANTHROPIC_API_KEY=<key> # Mac/Linux
|
$ export ANTHROPIC_API_KEY=<key> # Mac/Linux
|
||||||
$ setx ANTHROPIC_API_KEY <key> # Windows, restart shell after setx
|
$ setx ANTHROPIC_API_KEY <key> # Windows, restart shell after setx
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ included for scale.
|
|||||||
You can code with all of these models using aider like this:
|
You can code with all of these models using aider like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ python -m pip install aider-chat
|
$ python -m pip install -U aider-chat
|
||||||
|
|
||||||
# Change directory into a git repo to work on
|
# Change directory into a git repo to work on
|
||||||
$ cd /to/your/git/repo
|
$ cd /to/your/git/repo
|
||||||
|
|||||||
418
aider/website/_posts/2024-09-26-architect.md
Normal file
418
aider/website/_posts/2024-09-26-architect.md
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
---
|
||||||
|
title: Separating code reasoning and editing
|
||||||
|
excerpt: An Architect model describes how to solve the coding problem, and an Editor model translates that into file edits. This Architect/Editor approach produces SOTA benchmark results.
|
||||||
|
highlight_image: /assets/architect.jpg
|
||||||
|
draft: false
|
||||||
|
nav_exclude: true
|
||||||
|
---
|
||||||
|
{% if page.date %}
|
||||||
|
<p class="post-date">{{ page.date | date: "%B %d, %Y" }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Separating code reasoning and editing
|
||||||
|
|
||||||
|
Aider now has experimental support for using two models to complete each coding task:
|
||||||
|
|
||||||
|
- An Architect model is asked to describe how to solve the coding problem.
|
||||||
|
- An Editor model is given the Architect's solution and asked to produce specific code editing instructions to apply those changes to existing source files.
|
||||||
|
|
||||||
|
Splitting up "code reasoning" and "code editing" in this manner
|
||||||
|
has produced SOTA results on
|
||||||
|
[aider's code editing benchmark](/docs/benchmarks.html#the-benchmark).
|
||||||
|
Using o1-preview as the Architect with either DeepSeek or o1-mini as the
|
||||||
|
Editor produced the SOTA score of 85%.
|
||||||
|
Using the Architect/Editor approach
|
||||||
|
also significantly improved the benchmark scores of many
|
||||||
|
models, compared to their previous "solo" baseline scores (striped bars).
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.shaded td {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
.table-container {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.responsive-table {
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
.responsive-table th, .responsive-table td {
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.responsive-table th {
|
||||||
|
background-color: #e2e2e2;
|
||||||
|
}
|
||||||
|
.responsive-table th:first-child,
|
||||||
|
.responsive-table td:first-child {
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
.responsive-table th:last-child,
|
||||||
|
.responsive-table td:last-child {
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.responsive-table {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.responsive-table th, .responsive-table td {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#passRateChart {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@1.0.2"></script>
|
||||||
|
{% assign sorted_data = site.data.architect | sort: "pass_rate_2" | reverse %}
|
||||||
|
<canvas id="passRateChart" width="400" height="250"></canvas>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var ctx = document.getElementById('passRateChart').getContext('2d');
|
||||||
|
|
||||||
|
// Function to determine aspect ratio and base font size based on screen width
|
||||||
|
function getChartSettings() {
|
||||||
|
if (window.innerWidth < 600) {
|
||||||
|
return { aspectRatio: 1, baseFontSize: 8 }; // Slightly taller for small screens
|
||||||
|
} else if (window.innerWidth < 800) {
|
||||||
|
return { aspectRatio: 1.2, baseFontSize: 10 }; // Slightly taller for small screens
|
||||||
|
} else {
|
||||||
|
return { aspectRatio: 1.4, baseFontSize: 12 }; // Slightly taller for larger screens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var chartSettings = getChartSettings();
|
||||||
|
var baseFontSize = chartSettings.baseFontSize;
|
||||||
|
|
||||||
|
var labels = [];
|
||||||
|
var data = [];
|
||||||
|
var colorMapping = {
|
||||||
|
"claude-3.5-sonnet": "rgba(75, 192, 192, 0.2)",
|
||||||
|
"gpt-4o": "rgba(255, 99, 132, 0.2)",
|
||||||
|
"o1-preview": "rgba(54, 162, 235, 0.2)",
|
||||||
|
"o1-mini": "rgba(255, 206, 86, 0.2)",
|
||||||
|
"gpt-4o-mini": "rgba(153, 102, 255, 0.2)"
|
||||||
|
};
|
||||||
|
var borderColorMapping = {
|
||||||
|
"claude-3.5-sonnet": "rgba(75, 192, 192, 1)",
|
||||||
|
"gpt-4o": "rgba(255, 99, 132, 1)",
|
||||||
|
"o1-preview": "rgba(54, 162, 235, 1)",
|
||||||
|
"o1-mini": "rgba(255, 206, 86, 1)",
|
||||||
|
"gpt-4o-mini": "rgba(153, 102, 255, 1)"
|
||||||
|
};
|
||||||
|
var backgroundColors = [];
|
||||||
|
var borderColors = [];
|
||||||
|
var patterns = {};
|
||||||
|
for (var key in colorMapping) {
|
||||||
|
patterns[key] = ctx.createPattern(createStripePattern(colorMapping[key]), 'repeat');
|
||||||
|
}
|
||||||
|
{% assign grouped_data = sorted_data | group_by: "model" %}
|
||||||
|
{% for group in grouped_data %}
|
||||||
|
{% for item in group.items %}
|
||||||
|
if ("{{ item.editor_model }}" == "") {
|
||||||
|
labels.push("Baseline");
|
||||||
|
} else {
|
||||||
|
labels.push("{{ item.editor_model }}/{{ item.editor_edit_format | default: item.edit_format }}");
|
||||||
|
}
|
||||||
|
data.push({{ item.pass_rate_2 }});
|
||||||
|
if ("{{ item.editor_model }}" == "") {
|
||||||
|
backgroundColors.push(patterns["{{ item.model }}"]);
|
||||||
|
} else {
|
||||||
|
backgroundColors.push(colorMapping["{{ item.model }}"]);
|
||||||
|
}
|
||||||
|
borderColors.push(borderColorMapping["{{ item.model }}"]);
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
labels.reverse();
|
||||||
|
data.reverse();
|
||||||
|
backgroundColors.reverse();
|
||||||
|
borderColors.reverse();
|
||||||
|
var chart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Pass Rate',
|
||||||
|
data: data,
|
||||||
|
backgroundColor: backgroundColors,
|
||||||
|
borderColor: borderColors,
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
aspectRatio: chartSettings.aspectRatio,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Pass Rate (%)',
|
||||||
|
font: {
|
||||||
|
size: baseFontSize + 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
font: {
|
||||||
|
size: baseFontSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Editor model and edit format',
|
||||||
|
font: {
|
||||||
|
size: baseFontSize + 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
font: {
|
||||||
|
size: baseFontSize + 4
|
||||||
|
},
|
||||||
|
maxRotation: 90, // Allow full rotation if needed
|
||||||
|
minRotation: 45 // Start rotating at 45 degrees to fit more labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
annotation: {
|
||||||
|
annotations: {
|
||||||
|
line1: {
|
||||||
|
type: 'line',
|
||||||
|
yMin: 79.7,
|
||||||
|
yMax: 79.7,
|
||||||
|
borderColor: 'rgba(255, 99, 132, 0.8)',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderDash: [6, 6],
|
||||||
|
label: {
|
||||||
|
content: 'Previous SOTA',
|
||||||
|
enabled: true,
|
||||||
|
position: 'start',
|
||||||
|
xAdjust: 10,
|
||||||
|
font: {
|
||||||
|
size: baseFontSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Architect model',
|
||||||
|
font: {
|
||||||
|
size: baseFontSize + 2,
|
||||||
|
weight: 'bold'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
font: {
|
||||||
|
size: baseFontSize + 4
|
||||||
|
},
|
||||||
|
generateLabels: function(chart) {
|
||||||
|
var colorMapping = {
|
||||||
|
"o1-preview": "rgba(54, 162, 235, 0.2)",
|
||||||
|
"claude-3.5-sonnet": "rgba(75, 192, 192, 0.2)",
|
||||||
|
"gpt-4o": "rgba(255, 99, 132, 0.2)",
|
||||||
|
"o1-mini": "rgba(255, 206, 86, 0.2)",
|
||||||
|
"gpt-4o-mini": "rgba(153, 102, 255, 0.2)"
|
||||||
|
};
|
||||||
|
return Object.keys(colorMapping).reverse().map(function(key) {
|
||||||
|
return {
|
||||||
|
text: key,
|
||||||
|
fillStyle: colorMapping[key],
|
||||||
|
strokeStyle: colorMapping[key].replace('0.2', '1'),
|
||||||
|
lineWidth: 1
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update aspect ratio and font sizes on window resize
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
var newSettings = getChartSettings();
|
||||||
|
chart.options.aspectRatio = newSettings.aspectRatio;
|
||||||
|
baseFontSize = newSettings.baseFontSize;
|
||||||
|
|
||||||
|
// Update font sizes
|
||||||
|
chart.options.scales.y.title.font.size = baseFontSize + 6;
|
||||||
|
chart.options.scales.y.ticks.font.size = baseFontSize;
|
||||||
|
chart.options.scales.x.title.font.size = baseFontSize + 6;
|
||||||
|
chart.options.scales.x.ticks.font.size = baseFontSize + 4;
|
||||||
|
chart.options.plugins.annotation.annotations.line1.label.font.size = baseFontSize;
|
||||||
|
chart.options.plugins.legend.title.font.size = baseFontSize + 4;
|
||||||
|
chart.options.plugins.legend.labels.font.size = baseFontSize + 4;
|
||||||
|
|
||||||
|
chart.update();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createStripePattern(baseColor) {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 10;
|
||||||
|
canvas.height = 10;
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
ctx.fillStyle = baseColor;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.lineTo(10, 10);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
This approach was motivated by the release of OpenAI's o1 models.
|
||||||
|
They are strong at reasoning, but often fail to output properly formatted
|
||||||
|
code editing instructions.
|
||||||
|
It helps to instead let them describe the solution
|
||||||
|
however they prefer and then pass that output to a more traditional LLM.
|
||||||
|
This second Editor LLM can then interpret the solution description and
|
||||||
|
produce the code editing instructions needed to update
|
||||||
|
the existing source code.
|
||||||
|
|
||||||
|
This approach has recently become attractive for aider due to
|
||||||
|
rapid improvements in the speed and costs of frontier models.
|
||||||
|
In particular, chaining older LLMs would have been quite slow and
|
||||||
|
incompatible with aider's goal of providing an interactive,
|
||||||
|
pair programming AI coding experience.
|
||||||
|
|
||||||
|
## Code reasoning and code editing
|
||||||
|
|
||||||
|
Normally aider asks the model to solve a coding problem in one prompt,
|
||||||
|
asking the LLM to explain the solution and return
|
||||||
|
a well formatted series of file edits.
|
||||||
|
All of [aider's editing formats](/docs/more/edit-formats.html)
|
||||||
|
require the LLM to return source code edits in a specific text
|
||||||
|
format, so that aider can process the edits and apply them to the local source files.
|
||||||
|
|
||||||
|
Because this all happens in a single prompt/response round trip to the LLM,
|
||||||
|
the model has to split its attention between
|
||||||
|
solving the coding problem and conforming to the edit format.
|
||||||
|
|
||||||
|
The Architect/Editor approach splits this into two inference steps, possibly
|
||||||
|
using two different LLMs:
|
||||||
|
|
||||||
|
1. Solve the coding problem (Architect).
|
||||||
|
2. Turn the proposed solution into a series of well formed code edits (Editor).
|
||||||
|
|
||||||
|
The Architect/Editor approach allows the Architect to focus on solving the coding problem
|
||||||
|
and *describe the solution however comes naturally to it*.
|
||||||
|
Similarly, the Editor can focus all of its attention on properly formatting the edits
|
||||||
|
without needing to reason much about how to solve the coding problem.
|
||||||
|
|
||||||
|
We can assign the Architect and Editor roles to LLMs which are well suited to their needs.
|
||||||
|
Strong reasoning model like o1-preview make excellent Architects, while
|
||||||
|
the Editor role can be assigned to an appropriate model based on cost, speed
|
||||||
|
and code editing skill.
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
The graph above and the table below show the
|
||||||
|
[aider's code editing benchmark](/docs/benchmarks.html#the-benchmark)
|
||||||
|
score for various combinations of Architect and Editor models.
|
||||||
|
|
||||||
|
|
||||||
|
Some noteworthy observations:
|
||||||
|
|
||||||
|
- Pairing o1-preview as Architect with either Deepseek or o1-mini as Editor sets a SOTA significantly above the previous best score. This result is obtained with the "whole" editing format, requiring the Editor to output a full update copy of each edited source file. Both of these steps are therefore quite slow, so probably not practical for interactive use with aider.
|
||||||
|
- Pairing OpenAI's o1-preview with Anthropic's Sonnet as the Editor produces the second best result. This is an entirely practical configuration for users able to work with both providers.
|
||||||
|
- Pairing many models with themselves in the Architect/Editor configuration can provide
|
||||||
|
significant benefits.
|
||||||
|
Sonnet, GPT-4o and GPT-4o-mini all scored higher when used as an Architect/Editor pair.
|
||||||
|
- Deepseek is surprisingly effective as an Editor model. It seems remarkably capable at turning proposed coding solutions into new, updated versions of the source files. Using the efficient "diff" editing format, Deepseek helps all the Architect models except for Sonnet.
|
||||||
|
|
||||||
|
## Try it!
|
||||||
|
|
||||||
|
The development version of aider
|
||||||
|
has built in defaults to support Architect/Editor coding with
|
||||||
|
o1-preview, o1-mini, GPT-4o and Claude 3.5 Sonnet.
|
||||||
|
Run aider with `--architect` or get started quickly like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install -U aider-chat
|
||||||
|
|
||||||
|
# Change directory into a git repo
|
||||||
|
cd /to/your/git/repo
|
||||||
|
|
||||||
|
# Work with Claude 3.5 Sonnet as the Architect and Editor
|
||||||
|
export ANTHROPIC_API_KEY=your-key-goes-here
|
||||||
|
aider --sonnet --architect
|
||||||
|
|
||||||
|
# Work with OpenAI models, using gpt-4o as the Editor
|
||||||
|
export OPENAI_API_KEY=your-key-goes-here
|
||||||
|
aider --4o --architect
|
||||||
|
aider --o1-mini --architect
|
||||||
|
aider --o1-preview --architect
|
||||||
|
```
|
||||||
|
|
||||||
|
## More info
|
||||||
|
|
||||||
|
Aider has a number of "chat modes", and "architect" is available as a new chat mode.
|
||||||
|
The `--architect` switch is a shortcut for `--chat-mode architect`.
|
||||||
|
For more details, see documentation on
|
||||||
|
[aider's chat modes](/docs/usage/modes.html).
|
||||||
|
|
||||||
|
|
||||||
|
## Full results
|
||||||
|
|
||||||
|
Below are the benchmark results using various models as the Architect, paired with
|
||||||
|
various models as the Editor.
|
||||||
|
Each section includes a "baseline" result,
|
||||||
|
where the model works
|
||||||
|
by itself in aider's normal "code" editing mode
|
||||||
|
(not as part of an Architect/Editor configuration).
|
||||||
|
This "solo" baseline represents the performance previously available when using
|
||||||
|
this model with aider.
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="responsive-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Architect</th>
|
||||||
|
<th>Editor</th>
|
||||||
|
<th>Edit Format</th>
|
||||||
|
<th>Pass Rate</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for group in grouped_data %}
|
||||||
|
{% assign group_class = forloop.index | modulo: 2 | plus: 1 %}
|
||||||
|
{% for item in group.items %}
|
||||||
|
<tr class="{% if group_class == 1 %}shaded{% endif %}">
|
||||||
|
<td>{{ item.model }}</td>
|
||||||
|
<td>{% if item.editor_model %}{{ item.editor_model }}{% else %}<b>Baseline</b>{% endif %}</td>
|
||||||
|
<td style="text-align: center;">{{ item.editor_edit_format | default: item.edit_format }}</td>
|
||||||
|
<td style="text-align: right;">{{ item.pass_rate_2 }}%</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
194
aider/website/_posts/2024-11-21-quantization.md
Normal file
194
aider/website/_posts/2024-11-21-quantization.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
---
|
||||||
|
title: Details matter with open source models
|
||||||
|
excerpt: Open source LLMs are becoming very powerful, but pay attention to how you (or your provider) are serving the model. It can affect code editing skill.
|
||||||
|
highlight_image: /assets/quantization.jpg
|
||||||
|
draft: false
|
||||||
|
nav_exclude: true
|
||||||
|
---
|
||||||
|
{% if page.date %}
|
||||||
|
<p class="post-date">{{ page.date | date: "%B %d, %Y" }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Details matter with open source models
|
||||||
|
{: .no_toc }
|
||||||
|
|
||||||
|
<canvas id="quantChart" width="800" height="600" style="margin: 20px 0"></canvas>
|
||||||
|
|
||||||
|
Open source models like Qwen 2.5 32B Instruct are performing very well on
|
||||||
|
aider's code editing benchmark, rivaling closed source frontier models.
|
||||||
|
|
||||||
|
But pay attention to how your model is being served and quantized,
|
||||||
|
as it can impact code editing skill.
|
||||||
|
Open source models are often available at a variety of quantizations,
|
||||||
|
and can be served with different token limits.
|
||||||
|
These details matter when working with code.
|
||||||
|
|
||||||
|
The graph above and table below compares different versions of the Qwen 2.5 Coder 32B Instruct model,
|
||||||
|
served both locally and from a variety of cloud providers.
|
||||||
|
|
||||||
|
- The [HuggingFace BF16 weights](https://huggingface.co/Qwen/Qwen2.5-Coder-32B-Instruct) served via [glhf.chat](https://glhf.chat).
|
||||||
|
- [4bit and 8bit quants for mlx](https://t.co/cwX3DYX35D).
|
||||||
|
- The results from [OpenRouter's mix of providers](https://openrouter.ai/qwen/qwen-2.5-coder-32b-instruct/providers) which serve the model with different levels of quantization.
|
||||||
|
- Results from OpenRouter's providers, both served via OpenRouter and directly to their own APIs.
|
||||||
|
- Ollama locally serving different quantizations from the [Ollama model library](https://ollama.com/library/qwen2.5-coder:32b-instruct-q4_K_M) with 8k+
|
||||||
|
context windows.
|
||||||
|
- An Ollama fp16 quantization served with Ollama's default 2k context window.
|
||||||
|
|
||||||
|
### Pitfalls and details
|
||||||
|
|
||||||
|
This benchmarking effort highlighted a number of pitfalls and details specific to open source
|
||||||
|
models which
|
||||||
|
can have a significant impact on their ability to correctly edit code:
|
||||||
|
|
||||||
|
- **Quantization** -- Open source models are often available at dozens of different quantizations.
|
||||||
|
Most seem to only modestly decrease code editing skill, but stronger quantizations
|
||||||
|
do have a real impact.
|
||||||
|
- **Context window** -- Cloud providers can decide how large a context window to accept,
|
||||||
|
and they often choose differently. Ollama's local API server
|
||||||
|
defaults to a tiny 2k context window,
|
||||||
|
and silently discards data that exceeds it. Such a small window has
|
||||||
|
catastrophic effects on performance, without throwing obvious hard errors.
|
||||||
|
- **Output token limits** -- Open source models are often served with wildly
|
||||||
|
differing output token limits. This has a direct impact on how much code the
|
||||||
|
model can write or edit in a response.
|
||||||
|
- **Buggy cloud providers** -- While benchmarking Qwen 2.5 Coder 32B Instruct
|
||||||
|
and DeepSeek V2.5, I discovered
|
||||||
|
multiple cloud providers with broken or buggy API endpoints.
|
||||||
|
They seemed
|
||||||
|
to be returning results different from expected based on the advertised
|
||||||
|
quantization and context sizes.
|
||||||
|
The harm caused to the code editing benchmark varied from serious
|
||||||
|
to catastrophic.
|
||||||
|
One provider scored 0.5% on the benchmark with DeepSeek V2.5, a highly capable model.
|
||||||
|
|
||||||
|
Closed source, proprietary models don't typically have these issues.
|
||||||
|
They are owned and operated by the organization that created them,
|
||||||
|
and typically served with specific, predictable context window and output token limits.
|
||||||
|
Their quantization level is usually unknown, but fixed and unchanging for all users.
|
||||||
|
|
||||||
|
### Conclusions
|
||||||
|
|
||||||
|
The best versions of the Qwen model rival GPT-4o, while the worst performing
|
||||||
|
quantization is more like the older GPT-4 Turbo when served competently.
|
||||||
|
Even an otherwise excellent fp16 quantization falls to GPT-3.5 Turbo levels of performance
|
||||||
|
if run with Ollama's default 2k context window.
|
||||||
|
|
||||||
|
### Sections
|
||||||
|
{: .no_toc }
|
||||||
|
|
||||||
|
- TOC
|
||||||
|
{:toc}
|
||||||
|
|
||||||
|
## Benchmark results
|
||||||
|
|
||||||
|
{: .note :}
|
||||||
|
These are results from single benchmark runs, so expect normal variance of +/- 1-2%.
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script>
|
||||||
|
{% include quant-chart.js %}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="text" id="quantSearchInput" placeholder="Search..." style="width: 100%; max-width: 800px; margin: 10px auto; padding: 8px; display: block; border: 1px solid #ddd; border-radius: 4px;">
|
||||||
|
|
||||||
|
<table style="width: 100%; max-width: 800px; margin: auto; border-collapse: collapse; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-size: 14px;">
|
||||||
|
<thead style="background-color: #f2f2f2;">
|
||||||
|
<tr>
|
||||||
|
<th style="padding: 8px; text-align: left;">Model</th>
|
||||||
|
<th style="padding: 8px; text-align: center;">Percent completed correctly</th>
|
||||||
|
<th style="padding: 8px; text-align: center;">Percent using correct edit format</th>
|
||||||
|
<th style="padding: 8px; text-align: left;">Command</th>
|
||||||
|
<th style="padding: 8px; text-align: center;">Edit format</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% assign quant_sorted = site.data.quant | sort: 'pass_rate_2' | reverse %}
|
||||||
|
{% for row in quant_sorted %}
|
||||||
|
<tr style="border-bottom: 1px solid #ddd;">
|
||||||
|
<td style="padding: 8px;">{{ row.model }}</td>
|
||||||
|
<td style="padding: 8px; text-align: center;">{{ row.pass_rate_2 }}%</td>
|
||||||
|
<td style="padding: 8px; text-align: center;">{{ row.percent_cases_well_formed }}%</td>
|
||||||
|
<td style="padding: 8px;"><code>{{ row.command }}</code></td>
|
||||||
|
<td style="padding: 8px; text-align: center;">{{ row.edit_format }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
tr.selected {
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
td, th {
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
td:nth-child(3), td:nth-child(4) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('quantSearchInput').addEventListener('keyup', function() {
|
||||||
|
var input = this.value.toLowerCase();
|
||||||
|
var rows = document.querySelectorAll('tbody tr');
|
||||||
|
|
||||||
|
rows.forEach(function(row) {
|
||||||
|
var text = row.textContent.toLowerCase();
|
||||||
|
if(text.includes(input)) {
|
||||||
|
row.style.display = '';
|
||||||
|
row.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
row.classList.remove('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## Setting Ollama's context window size
|
||||||
|
|
||||||
|
[Ollama uses a 2k context window by default](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-specify-the-context-window-size),
|
||||||
|
which is very small for working with aider.
|
||||||
|
Unlike most other LLM servers, Ollama does not throw an error if you submit
|
||||||
|
a request that exceeds the context window.
|
||||||
|
Instead, it just silently truncates the request by discarding the "oldest" messages
|
||||||
|
in the chat to make it fit within the context window.
|
||||||
|
|
||||||
|
Except for the single 2k context result,
|
||||||
|
all of the Ollama results above were collected with at least an 8k context window.
|
||||||
|
An 8k window is large enough to attempt all the coding problems in the benchmark.
|
||||||
|
Aider sets Ollama's context window to 8k by default, starting in aider v0.65.0.
|
||||||
|
|
||||||
|
You can change the Ollama server's context window with a
|
||||||
|
[`.aider.model.settings.yml` file](https://aider.chat/docs/config/adv-model-settings.html#model-settings)
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
- name: ollama/qwen2.5-coder:32b-instruct-fp16
|
||||||
|
extra_params:
|
||||||
|
num_ctx: 8192
|
||||||
|
```
|
||||||
|
|
||||||
|
## Choosing providers with OpenRouter
|
||||||
|
|
||||||
|
OpenRouter allows you to ignore specific providers in your
|
||||||
|
[preferences](https://openrouter.ai/settings/preferences).
|
||||||
|
This can be used to limit your OpenRouter requests to be
|
||||||
|
served by only your preferred providers.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
This article went through many revisions as I received feedback from
|
||||||
|
numerous members of the community.
|
||||||
|
Here are some of the noteworthy learnings and changes:
|
||||||
|
|
||||||
|
- The first version of this article included incorrect Ollama models.
|
||||||
|
- Earlier Ollama results used the too small default 2k context window,
|
||||||
|
artificially harming the benchmark results.
|
||||||
|
- The benchmark results appear to have uncovered a problem in the way
|
||||||
|
OpenRouter was communicating with Hyperbolic.
|
||||||
|
They fixed the issue 11/24/24, shortly after it was pointed out.
|
||||||
140
aider/website/_posts/2024-12-03-qwq.md
Normal file
140
aider/website/_posts/2024-12-03-qwq.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
title: QwQ is a code architect, not an editor
|
||||||
|
excerpt: QwQ is reasoning model like o1, and needs to be used as an architect with another model as editor.
|
||||||
|
highlight_image: /assets/qwq.jpg
|
||||||
|
draft: false
|
||||||
|
nav_exclude: true
|
||||||
|
---
|
||||||
|
{% if page.date %}
|
||||||
|
<p class="post-date">{{ page.date | date: "%B %d, %Y" }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# QwQ is a code architect, not an editor
|
||||||
|
{: .no_toc }
|
||||||
|
|
||||||
|
<canvas id="qwqChart" width="800" height="500" style="margin: 20px 0"></canvas>
|
||||||
|
|
||||||
|
QwQ 32B Preview is a "reasoning" model, which spends a lot of tokens thinking before
|
||||||
|
rendering a final response.
|
||||||
|
This is similar to OpenAI's o1 models, which are most effective with aider
|
||||||
|
[when paired as an architect with a traditional LLM as an editor](https://aider.chat/2024/09/26/architect.html).
|
||||||
|
In this mode, the reasoning model acts as an "architect" to propose a solution to the
|
||||||
|
coding problem without regard for how to actually make edits to the source files.
|
||||||
|
The "editor" model receives that proposal, and focuses solely on how to
|
||||||
|
edit the existing source code to implement it.
|
||||||
|
|
||||||
|
Used alone without being paired with an editor,
|
||||||
|
QwQ was unable to comply with even the simplest
|
||||||
|
[editing format](https://aider.chat/docs/more/edit-formats.html).
|
||||||
|
It was not able to reliably edit source code files.
|
||||||
|
As a result, QwQ's solo score on the benchmark was quite underwhelming
|
||||||
|
(and far worse than the o1 models performing solo).
|
||||||
|
|
||||||
|
QwQ is based on
|
||||||
|
Qwen 2.5 Coder 32B Instruct,
|
||||||
|
and does better when paired with it as an architect + editor combo.
|
||||||
|
Though this provided only a modest benchmark improvement over just using Qwen alone,
|
||||||
|
and comes with a fairly high cost in terms of latency.
|
||||||
|
Each request must wait for QwQ to return all its thinking text
|
||||||
|
and the final solution proposal.
|
||||||
|
And then one must wait for Qwen to turn that large
|
||||||
|
response into actual file edits.
|
||||||
|
|
||||||
|
Pairing QwQ with other sensible editor models performed the same or worse than
|
||||||
|
just using Qwen 2.5 Coder 32B Instruct alone.
|
||||||
|
|
||||||
|
QwQ+Qwen seems to be the best way to use QwQ, achieving a score of 74%.
|
||||||
|
That is well below the
|
||||||
|
SOTA results for this benchmark: Sonnet alone scores 84%, and
|
||||||
|
o1-preview + o1-mini as architect + editor scores 85%.
|
||||||
|
|
||||||
|
|
||||||
|
## QwQ specific editing formats
|
||||||
|
|
||||||
|
I spent some time experimenting with a variety of custom editing formats
|
||||||
|
for QwQ.
|
||||||
|
In particular, I tried to parse the QwQ response and discard the long
|
||||||
|
sections of "thinking" and retain only the "final" solution.
|
||||||
|
None of this custom work seemed to translate
|
||||||
|
into any significant improvement in the benchmark results.
|
||||||
|
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script>
|
||||||
|
{% include qwq-chart.js %}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<table style="width: 100%; max-width: 800px; margin: auto; border-collapse: collapse; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-size: 14px;">
|
||||||
|
<thead style="background-color: #f2f2f2;">
|
||||||
|
<tr>
|
||||||
|
<th style="padding: 8px; text-align: left;">Model</th>
|
||||||
|
<th style="padding: 8px; text-align: center;">Percent completed correctly</th>
|
||||||
|
<th style="padding: 8px; text-align: center;">Percent using correct edit format</th>
|
||||||
|
<th style="padding: 8px; text-align: left;">Command</th>
|
||||||
|
<th style="padding: 8px; text-align: center;">Edit format</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% assign qwq_sorted = site.data.qwq | sort: 'pass_rate_2' | reverse %}
|
||||||
|
{% for row in qwq_sorted %}
|
||||||
|
<tr style="border-bottom: 1px solid #ddd;">
|
||||||
|
<td style="padding: 8px;">{{ row.model }}</td>
|
||||||
|
<td style="padding: 8px; text-align: center;">{{ row.pass_rate_2 }}%</td>
|
||||||
|
<td style="padding: 8px; text-align: center;">{{ row.percent_cases_well_formed }}%</td>
|
||||||
|
<td style="padding: 8px;"><code>{{ row.command }}</code></td>
|
||||||
|
<td style="padding: 8px; text-align: center;">{{ row.edit_format }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
tr.selected {
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
td, th {
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
td:nth-child(3), td:nth-child(4) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('qwqSearchInput').addEventListener('keyup', function() {
|
||||||
|
var input = this.value.toLowerCase();
|
||||||
|
var rows = document.querySelectorAll('tbody tr');
|
||||||
|
|
||||||
|
rows.forEach(function(row) {
|
||||||
|
var text = row.textContent.toLowerCase();
|
||||||
|
if(text.includes(input)) {
|
||||||
|
row.style.display = '';
|
||||||
|
row.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
row.classList.remove('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## Open source model caveats
|
||||||
|
|
||||||
|
As discussed in a recent blog post,
|
||||||
|
[details matter with open source models](https://aider.chat/2024/11/21/quantization.html).
|
||||||
|
For clarity, new benchmark runs for this article were
|
||||||
|
performed against OpenRouter's endpoints for
|
||||||
|
QwQ 32B Preview and Qwen 2.5 Coder 32B Instruct.
|
||||||
|
For the other models, the benchmark was direct to their providers' APIs.
|
||||||
|
|
||||||
|
Having recently done extensive testing of OpenRouter's Qwen 2.5 Coder 32B Instruct endpoint,
|
||||||
|
it seems reliable.
|
||||||
|
The provider Mancer was blocked due to the small context window it provides.
|
||||||
|
|
||||||
|
For QwQ 32B Preview, Fireworks was blocked because of its small context window.
|
||||||
BIN
aider/website/assets/architect.jpg
Normal file
BIN
aider/website/assets/architect.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 337 KiB |
BIN
aider/website/assets/copypaste.jpg
Normal file
BIN
aider/website/assets/copypaste.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 314 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user