mirror of
https://github.com/Aider-AI/aider
synced 2026-05-05 06:32:04 +02:00
Compare commits
530 Commits
v0.79.3.de
...
v0.82.3.de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2d8d5dc82 | ||
|
|
20a7e3552c | ||
|
|
888168f044 | ||
|
|
851642a1bd | ||
|
|
f7bdebfba9 | ||
|
|
a4d3222108 | ||
|
|
f1caab9de0 | ||
|
|
c08336fdb0 | ||
|
|
541b496d09 | ||
|
|
622bf349c5 | ||
|
|
05eaf82b36 | ||
|
|
5c8150fd16 | ||
|
|
ec9327dcb4 | ||
|
|
8e689d35af | ||
|
|
50fd544070 | ||
|
|
4f8bd2e06d | ||
|
|
6f1b6f5f31 | ||
|
|
bdfda399cb | ||
|
|
a08ffc3513 | ||
|
|
21beee2fe1 | ||
|
|
a564f94bf3 | ||
|
|
9e54898866 | ||
|
|
739e01da95 | ||
|
|
3e0af2cc84 | ||
|
|
9ff13740f2 | ||
|
|
00e5c33444 | ||
|
|
57abaf7500 | ||
|
|
ed14be4e70 | ||
|
|
80909e17c7 | ||
|
|
52697ea884 | ||
|
|
9f01c8d0d6 | ||
|
|
e91d7e74ae | ||
|
|
20ca0463ea | ||
|
|
5e40f469bf | ||
|
|
7f28d63c33 | ||
|
|
bb1fa24971 | ||
|
|
ffbbaa06d7 | ||
|
|
14e1b96f05 | ||
|
|
d8c781b66b | ||
|
|
2fbec8545c | ||
|
|
b66901fc75 | ||
|
|
d569bca520 | ||
|
|
efbefc669f | ||
|
|
24805ff85d | ||
|
|
8b917d5716 | ||
|
|
3502f335ec | ||
|
|
758979e4f3 | ||
|
|
8b5fc801da | ||
|
|
f5c4214c93 | ||
|
|
270e84287a | ||
|
|
daec7cf3f4 | ||
|
|
bb42d1e9a5 | ||
|
|
23f182aab3 | ||
|
|
119fbc995c | ||
|
|
3081f49179 | ||
|
|
8cf1874453 | ||
|
|
31b4bd5bcf | ||
|
|
71d1591cc1 | ||
|
|
134a2d60fe | ||
|
|
152b8912ae | ||
|
|
36f23c101d | ||
|
|
0e40510295 | ||
|
|
db0d0768d7 | ||
|
|
c68cade9f2 | ||
|
|
14928727eb | ||
|
|
67b9345929 | ||
|
|
dae1a376a2 | ||
|
|
1e359f1dcf | ||
|
|
1c54857422 | ||
|
|
0f78a0ac5c | ||
|
|
4e1e77890b | ||
|
|
5573cdfba1 | ||
|
|
14028d3758 | ||
|
|
3ab673b398 | ||
|
|
861f51f6c3 | ||
|
|
64f5d0d388 | ||
|
|
9059af8d5f | ||
|
|
c3a543b99d | ||
|
|
c85cd783e5 | ||
|
|
af2d241c99 | ||
|
|
30839a5273 | ||
|
|
8baa99b7ef | ||
|
|
d1e5572343 | ||
|
|
96aa648e17 | ||
|
|
1ae5f23dc8 | ||
|
|
f565f72679 | ||
|
|
78e76648d0 | ||
|
|
8e1e2210dd | ||
|
|
e8c43c36d7 | ||
|
|
97e2a7bae0 | ||
|
|
6b75a578ac | ||
|
|
8b9238ebc9 | ||
|
|
8cc8027b40 | ||
|
|
ffb743e108 | ||
|
|
0f805752d3 | ||
|
|
4e9de4d51b | ||
|
|
a4e9539040 | ||
|
|
0c383dfb11 | ||
|
|
11d2b7ca98 | ||
|
|
e38be2f280 | ||
|
|
febdd3c0d0 | ||
|
|
0b08ca64a8 | ||
|
|
0f8e7fbd34 | ||
|
|
1a080ba71c | ||
|
|
1622531d85 | ||
|
|
7d0a9c7233 | ||
|
|
53a64c88ad | ||
|
|
27b51d51d8 | ||
|
|
bec35e0538 | ||
|
|
f65e6a3bb1 | ||
|
|
fd94f1a5f9 | ||
|
|
09fc037d4d | ||
|
|
cf0e6dac61 | ||
|
|
3b10e3bcb5 | ||
|
|
4c17784444 | ||
|
|
6616f0886d | ||
|
|
dcafab2764 | ||
|
|
3b6146301f | ||
|
|
42e09b3c7f | ||
|
|
73da42bee6 | ||
|
|
415b1cf5f0 | ||
|
|
c011285904 | ||
|
|
4314b4fefb | ||
|
|
d686f6844d | ||
|
|
65a0e5f771 | ||
|
|
5ca6d8ce67 | ||
|
|
688c2b9ee5 | ||
|
|
271f39505c | ||
|
|
3e8367ea3b | ||
|
|
67a1e52259 | ||
|
|
7561687b7b | ||
|
|
93fc7acbe3 | ||
|
|
72dc67950f | ||
|
|
e2bebd1d51 | ||
|
|
03560d3386 | ||
|
|
a3a3303a83 | ||
|
|
232a6f87d2 | ||
|
|
ab71ea0a65 | ||
|
|
1302224f39 | ||
|
|
733bf0dcdf | ||
|
|
4ed48178a9 | ||
|
|
8cffb975d9 | ||
|
|
97b18797a4 | ||
|
|
579794b265 | ||
|
|
bea746595e | ||
|
|
87711b048a | ||
|
|
0b468ebd85 | ||
|
|
aefc250e30 | ||
|
|
4a86fea86b | ||
|
|
fe6e2e1ea7 | ||
|
|
09d90b9b70 | ||
|
|
14eb7b46a2 | ||
|
|
66077fe3a4 | ||
|
|
d50cf806db | ||
|
|
95edae9bd1 | ||
|
|
a6c35305ed | ||
|
|
b382005a4c | ||
|
|
a71b90bdd6 | ||
|
|
d4a68c80bc | ||
|
|
fcf44cbebe | ||
|
|
51d8cb063a | ||
|
|
cdc86565cc | ||
|
|
1c54907b30 | ||
|
|
b6d4246e18 | ||
|
|
cc1a984c7e | ||
|
|
52d39657ab | ||
|
|
363ec82a48 | ||
|
|
f164b0e3eb | ||
|
|
3aaf7a69ec | ||
|
|
6d2828bc3c | ||
|
|
dd6e2051a8 | ||
|
|
ef440972bb | ||
|
|
da96888669 | ||
|
|
75639059e1 | ||
|
|
0a15dd311a | ||
|
|
434a1c6710 | ||
|
|
f961eecab6 | ||
|
|
d33a571f7d | ||
|
|
ea1239efef | ||
|
|
19c7c7a9dc | ||
|
|
49e4af4fab | ||
|
|
3e27c1bb17 | ||
|
|
0f8d196741 | ||
|
|
4c45f0e44b | ||
|
|
e39eef1ed7 | ||
|
|
c9c7aea1c4 | ||
|
|
18ff9eb2b4 | ||
|
|
b2f3d2cd84 | ||
|
|
5e0832cb8b | ||
|
|
a14c0ccac6 | ||
|
|
278f90acdd | ||
|
|
8e8b18e9a9 | ||
|
|
a277d74869 | ||
|
|
7ca3b6455d | ||
|
|
5ec6f69037 | ||
|
|
39962ba5eb | ||
|
|
51fa1f9abd | ||
|
|
47af5d463c | ||
|
|
33f0b0b41c | ||
|
|
48038b1f5e | ||
|
|
323698d387 | ||
|
|
1f702beb74 | ||
|
|
7d34c28af1 | ||
|
|
d26be77010 | ||
|
|
3b96d1bd57 | ||
|
|
48fd0e71d5 | ||
|
|
bcb35ccf44 | ||
|
|
a663ff7fa8 | ||
|
|
813d34a0e9 | ||
|
|
a4074a13c4 | ||
|
|
249f329b07 | ||
|
|
cf160a8f84 | ||
|
|
4db963182d | ||
|
|
199b59fdb9 | ||
|
|
2d09bfa0f3 | ||
|
|
729285e8a2 | ||
|
|
afd17bd96a | ||
|
|
380d8570dc | ||
|
|
e711eaa810 | ||
|
|
7dfdc2094e | ||
|
|
838646ac5b | ||
|
|
507f07575b | ||
|
|
f5e8808770 | ||
|
|
ae5b6e88a5 | ||
|
|
b45186dde0 | ||
|
|
38be8aa0da | ||
|
|
816d4ba206 | ||
|
|
ede59e4d2a | ||
|
|
ce0931a3c8 | ||
|
|
a44e148818 | ||
|
|
71115c6558 | ||
|
|
8ae837e98b | ||
|
|
9518193d0a | ||
|
|
60a2b799e6 | ||
|
|
9d7dc00f25 | ||
|
|
882e7b6716 | ||
|
|
8ba29ee8e6 | ||
|
|
3f67c41759 | ||
|
|
7fbeafa1cf | ||
|
|
028257480b | ||
|
|
e42a0c45b6 | ||
|
|
1e7f8549ff | ||
|
|
668de71f9d | ||
|
|
067245b810 | ||
|
|
8f236c69e1 | ||
|
|
8ee33da114 | ||
|
|
2fedc2e699 | ||
|
|
1961543e2f | ||
|
|
b4f65734a5 | ||
|
|
0eb80553f6 | ||
|
|
110c63ae95 | ||
|
|
57304536bf | ||
|
|
a9ca5da139 | ||
|
|
947aebfbe0 | ||
|
|
fafc9268d4 | ||
|
|
65a5d55436 | ||
|
|
96b350400f | ||
|
|
7983b4caf2 | ||
|
|
e44122f1be | ||
|
|
42618d7ec6 | ||
|
|
1d0167bbf4 | ||
|
|
43d4b21b23 | ||
|
|
562171c548 | ||
|
|
8dccecdd9f | ||
|
|
940ae364d7 | ||
|
|
532bc454c5 | ||
|
|
14ffe7782c | ||
|
|
2dd40fce44 | ||
|
|
0c8bc46e28 | ||
|
|
7d0dd29937 | ||
|
|
349cd77821 | ||
|
|
dc2d7b1dfe | ||
|
|
be30329288 | ||
|
|
71446d9f3c | ||
|
|
c9d4c8d09b | ||
|
|
c580ffdb70 | ||
|
|
f46deb4eb7 | ||
|
|
b3215bed48 | ||
|
|
2a9ab02753 | ||
|
|
0da586154d | ||
|
|
26d736551d | ||
|
|
9445a3118b | ||
|
|
a2c46c7436 | ||
|
|
8df7a0960e | ||
|
|
e7f35e7a35 | ||
|
|
088e80e38b | ||
|
|
2d65c7f387 | ||
|
|
94db758eb7 | ||
|
|
2bfb615d68 | ||
|
|
87275140f9 | ||
|
|
0672a68ba4 | ||
|
|
246e3ccfad | ||
|
|
b275ee919f | ||
|
|
eda796d5e0 | ||
|
|
d1b3917309 | ||
|
|
ffee2b971f | ||
|
|
b9a80f9c8c | ||
|
|
980f673ce2 | ||
|
|
55767a0003 | ||
|
|
fb44bebe40 | ||
|
|
b79f072499 | ||
|
|
d65a2e8b51 | ||
|
|
e0b42d51db | ||
|
|
c057dc9466 | ||
|
|
fff53a94d3 | ||
|
|
12beedd0a6 | ||
|
|
80f60a7394 | ||
|
|
2359348505 | ||
|
|
63e3e06a8c | ||
|
|
dca92b580c | ||
|
|
24e2960092 | ||
|
|
be1a52c5c1 | ||
|
|
8a34a6c8f4 | ||
|
|
7924ea9bb9 | ||
|
|
a3a17ae792 | ||
|
|
f8801d811b | ||
|
|
425284ac62 | ||
|
|
4872cdf905 | ||
|
|
88cd81c692 | ||
|
|
d45ecd0800 | ||
|
|
4bfcef60f4 | ||
|
|
e9b7e933f5 | ||
|
|
e5301cef49 | ||
|
|
01ca552174 | ||
|
|
4529d73bf3 | ||
|
|
0798906a51 | ||
|
|
8547c24dac | ||
|
|
0e1e1aae2e | ||
|
|
9cc31e4087 | ||
|
|
e9c7555bb9 | ||
|
|
6f897fec59 | ||
|
|
8c3d77f4c7 | ||
|
|
f9b60d83ac | ||
|
|
3992681b84 | ||
|
|
340bd78259 | ||
|
|
12a46275a2 | ||
|
|
b56234f1c9 | ||
|
|
60859ec2b9 | ||
|
|
0a840860f1 | ||
|
|
cebae18dd6 | ||
|
|
9c9c6b6591 | ||
|
|
ca0ffc66d1 | ||
|
|
b0623f04fe | ||
|
|
2dec862ea6 | ||
|
|
f18fe53a9a | ||
|
|
73348de2b4 | ||
|
|
f4a418bfcd | ||
|
|
50588800f5 | ||
|
|
2762215d66 | ||
|
|
4e53797aac | ||
|
|
b24ac4b3a2 | ||
|
|
88ab6afd3e | ||
|
|
5c5db0a961 | ||
|
|
587186d96c | ||
|
|
d9ddf93f83 | ||
|
|
d3882d3513 | ||
|
|
a458215bbb | ||
|
|
7ae0fa3775 | ||
|
|
f1695f8b15 | ||
|
|
4c08bbb9e5 | ||
|
|
9b55ff8c4c | ||
|
|
2096d2b786 | ||
|
|
70196cd6fd | ||
|
|
c2cba97722 | ||
|
|
7534ebd145 | ||
|
|
6b2331340b | ||
|
|
da7b5005fe | ||
|
|
9210e12316 | ||
|
|
2c47a79c38 | ||
|
|
48cebef974 | ||
|
|
52952efd33 | ||
|
|
30dfd28ac4 | ||
|
|
b5a04f05f3 | ||
|
|
d5a34dcbc5 | ||
|
|
fc6a05ced6 | ||
|
|
2d3162a90b | ||
|
|
83c599e741 | ||
|
|
c7f1671d5a | ||
|
|
9f2d945691 | ||
|
|
36ff099145 | ||
|
|
120e010e48 | ||
|
|
2887816cf0 | ||
|
|
9848479306 | ||
|
|
b662e6b9eb | ||
|
|
258f1f0848 | ||
|
|
a07f312089 | ||
|
|
605d8fe59a | ||
|
|
1c7db4da0d | ||
|
|
b0acc95b01 | ||
|
|
5bcad73515 | ||
|
|
db05754d29 | ||
|
|
dfe3457906 | ||
|
|
7dbb1a2aa8 | ||
|
|
83dac4aae2 | ||
|
|
75b79fa002 | ||
|
|
27c1fd0262 | ||
|
|
8069e06f43 | ||
|
|
8cd106fc8a | ||
|
|
a9c9877580 | ||
|
|
19e1201c8a | ||
|
|
912f98e6eb | ||
|
|
b6808e3700 | ||
|
|
a4f78b60e0 | ||
|
|
6c9906c639 | ||
|
|
8a90af6779 | ||
|
|
9831a13284 | ||
|
|
d2386bc1f6 | ||
|
|
5b10af7b1a | ||
|
|
eacf3cc4ed | ||
|
|
87090139f6 | ||
|
|
650c4cf948 | ||
|
|
24c074eeaa | ||
|
|
b54629addb | ||
|
|
cd67d11ecf | ||
|
|
16bb0c93e7 | ||
|
|
7c40c3a61c | ||
|
|
b8e8b7496d | ||
|
|
d29d5e3a47 | ||
|
|
db261d0fa4 | ||
|
|
b7f6b847d6 | ||
|
|
5311a842a5 | ||
|
|
e288f59da7 | ||
|
|
c62ceb5db1 | ||
|
|
35decf122d | ||
|
|
2adfe1507b | ||
|
|
5516e6b279 | ||
|
|
8b811c610a | ||
|
|
23348f8e65 | ||
|
|
e1c3a2f8cf | ||
|
|
0b0493fa21 | ||
|
|
14d1742869 | ||
|
|
96aa77288b | ||
|
|
a4c9c10029 | ||
|
|
c9d561e7ad | ||
|
|
87ba63c14c | ||
|
|
0decbad7d0 | ||
|
|
0e647dbc0e | ||
|
|
2540d28b34 | ||
|
|
19a5e5bb00 | ||
|
|
f22afc6458 | ||
|
|
ab00415ca1 | ||
|
|
8df5406986 | ||
|
|
7f05159f0f | ||
|
|
61147dfecf | ||
|
|
06da133aac | ||
|
|
ff1d047048 | ||
|
|
4a8b17cb84 | ||
|
|
fbafc09e6a | ||
|
|
c3c960383e | ||
|
|
9e3adf0bf8 | ||
|
|
2bc0aa1777 | ||
|
|
3bc4064b61 | ||
|
|
b4f9258f3c | ||
|
|
ad844cce5c | ||
|
|
c73b064133 | ||
|
|
bd9b63a1aa | ||
|
|
2d87431aeb | ||
|
|
3f3b1fb657 | ||
|
|
477f9eb4ec | ||
|
|
91497dc2ee | ||
|
|
928b78d9f6 | ||
|
|
51825663b9 | ||
|
|
01fdbda728 | ||
|
|
fa3c68fccd | ||
|
|
189977e4c7 | ||
|
|
290fd99b6d | ||
|
|
15cec5bd50 | ||
|
|
f53db636e1 | ||
|
|
47d3802ffe | ||
|
|
e98ffb5ae0 | ||
|
|
5d77eb1314 | ||
|
|
f124cdbb6f | ||
|
|
1649d084d2 | ||
|
|
36ca790c3d | ||
|
|
a91a8216b7 | ||
|
|
8cae7b20e7 | ||
|
|
a537119f3d | ||
|
|
15fe0afe62 | ||
|
|
1b2a4db1ed | ||
|
|
88a02723fa | ||
|
|
7d013f35e2 | ||
|
|
e881d33bea | ||
|
|
38da91becd | ||
|
|
c99d96a700 | ||
|
|
fa89a6950b | ||
|
|
cde08da282 | ||
|
|
8619bd4e84 | ||
|
|
f49449b520 | ||
|
|
2fe79ac6a3 | ||
|
|
d8830c43c5 | ||
|
|
4ac945da70 | ||
|
|
ee0019e25f | ||
|
|
f37b814570 | ||
|
|
e559bc8694 | ||
|
|
f7618440e7 | ||
|
|
7bc62cb674 | ||
|
|
db77e2e9b9 | ||
|
|
083b49f3c4 | ||
|
|
f3f0416d31 | ||
|
|
775a9f86a1 | ||
|
|
e2bfdc444a | ||
|
|
3c7783585e | ||
|
|
761a297903 | ||
|
|
6d30094a93 | ||
|
|
d8e1816774 | ||
|
|
ae371cb362 | ||
|
|
73c46e8e24 | ||
|
|
ef4c40c692 | ||
|
|
04b3ada7f7 | ||
|
|
424b43b3d3 | ||
|
|
9a9255d6f9 | ||
|
|
d9e52e41ff | ||
|
|
a038bc002a | ||
|
|
fa256eb1a7 | ||
|
|
6689f001cf | ||
|
|
cc043bab9c | ||
|
|
5af73b1dcf | ||
|
|
85925a2dc6 | ||
|
|
fb23b6c26f | ||
|
|
d5cec5f71e | ||
|
|
13b62e3d06 | ||
|
|
779f07f072 | ||
|
|
b923d63700 | ||
|
|
7e2dd9bc04 | ||
|
|
7924657584 | ||
|
|
4f5ed8ace0 | ||
|
|
a1286d0d4d | ||
|
|
eef3a3afeb | ||
|
|
3e8f9aa31c | ||
|
|
afebfe5f4f |
86
.github/workflows/check_pypi_version.yml
vendored
Normal file
86
.github/workflows/check_pypi_version.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
name: Check PyPI Version
|
||||
|
||||
# Check to be sure `pip install aider-chat` installs the most recently published version.
|
||||
# If dependencies get yanked, it may render the latest version uninstallable.
|
||||
# See https://github.com/Aider-AI/aider/issues/3699 for example.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run once a day at midnight UTC
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch: # Allows manual triggering
|
||||
|
||||
jobs:
|
||||
check_version:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install aider-chat
|
||||
run: pip install aider-chat
|
||||
|
||||
- name: Get installed aider version
|
||||
id: installed_version
|
||||
run: |
|
||||
set -x # Enable debugging output
|
||||
aider_version_output=$(aider --version)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: 'aider --version' command failed."
|
||||
exit 1
|
||||
fi
|
||||
echo "Raw aider --version output: $aider_version_output"
|
||||
|
||||
# Extract version number (format X.Y.Z)
|
||||
version_num=$(echo "$aider_version_output" | grep -oP '\d+\.\d+\.\d+')
|
||||
|
||||
# Check if grep found anything
|
||||
if [ -z "$version_num" ]; then
|
||||
echo "Error: Could not extract version number using grep -oP '\d+\.\d+\.\d+' from output: $aider_version_output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Extracted version number: $version_num"
|
||||
echo "version=$version_num" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for all tags
|
||||
|
||||
- name: Get latest tag
|
||||
id: latest_tag
|
||||
run: |
|
||||
set -x # Enable debugging output
|
||||
# Fetch all tags from remote just in case
|
||||
git fetch --tags origin main
|
||||
# Get the latest tag that strictly matches vX.Y.Z (no suffixes like .dev)
|
||||
# List all tags, sort by version descending, filter for exact pattern, take the first one
|
||||
latest_tag=$(git tag --sort=-v:refname | grep -P '^v\d+\.\d+\.\d+$' | head -n 1)
|
||||
|
||||
if [ -z "$latest_tag" ]; then
|
||||
echo "Error: Could not find any tags matching the pattern '^v\d+\.\d+\.\d+$'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Latest non-dev tag: $latest_tag"
|
||||
# Remove 'v' prefix for comparison
|
||||
tag_num=${latest_tag#v}
|
||||
echo "Extracted tag number: $tag_num"
|
||||
echo "tag=$tag_num" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Compare versions
|
||||
run: |
|
||||
echo "Installed version: ${{ steps.installed_version.outputs.version }}"
|
||||
echo "Latest tag version: ${{ steps.latest_tag.outputs.tag }}"
|
||||
if [ "${{ steps.installed_version.outputs.version }}" != "${{ steps.latest_tag.outputs.tag }}" ]; then
|
||||
echo "Error: Installed aider version (${{ steps.installed_version.outputs.version }}) does not match the latest tag (${{ steps.latest_tag.outputs.tag }})."
|
||||
exit 1
|
||||
fi
|
||||
echo "Versions match."
|
||||
90
.github/workflows/windows_check_pypi_version.yml
vendored
Normal file
90
.github/workflows/windows_check_pypi_version.yml
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
name: Windows Check PyPI Version
|
||||
|
||||
# Check to be sure `pip install aider-chat` installs the most recently published version on Windows.
|
||||
# If dependencies get yanked, it may render the latest version uninstallable.
|
||||
# See https://github.com/Aider-AI/aider/issues/3699 for example.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run once a day at 1 AM UTC (offset from Ubuntu check)
|
||||
- cron: '0 1 * * *'
|
||||
workflow_dispatch: # Allows manual triggering
|
||||
|
||||
jobs:
|
||||
check_version:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh # Use PowerShell for all run steps
|
||||
|
||||
steps:
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install aider-chat
|
||||
run: pip install aider-chat
|
||||
|
||||
- name: Get installed aider version
|
||||
id: installed_version
|
||||
run: |
|
||||
Write-Host "Running 'aider --version'..."
|
||||
$aider_version_output = aider --version
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Error: 'aider --version' command failed."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Raw aider --version output: $aider_version_output"
|
||||
|
||||
# Extract version number (format X.Y.Z) using PowerShell regex
|
||||
$match = [regex]::Match($aider_version_output, '\d+\.\d+\.\d+')
|
||||
|
||||
if (-not $match.Success) {
|
||||
Write-Error "Error: Could not extract version number using regex '\d+\.\d+\.\d+' from output: $aider_version_output"
|
||||
exit 1
|
||||
}
|
||||
$version_num = $match.Value
|
||||
|
||||
Write-Host "Extracted version number: $version_num"
|
||||
echo "version=$version_num" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for all tags
|
||||
|
||||
- name: Get latest tag
|
||||
id: latest_tag
|
||||
run: |
|
||||
Write-Host "Fetching tags..."
|
||||
# Fetch all tags from remote just in case
|
||||
git fetch --tags origin main
|
||||
Write-Host "Getting latest non-dev tag..."
|
||||
# Get the latest tag that strictly matches vX.Y.Z (no suffixes like .dev)
|
||||
# List all tags, sort by version descending, filter for exact pattern, take the first one
|
||||
$latest_tag = (git tag --sort=-v:refname | Select-String -Pattern '^v\d+\.\d+\.\d+$' | Select-Object -First 1).Line
|
||||
|
||||
if (-not $latest_tag) {
|
||||
Write-Error "Error: Could not find any tags matching the pattern '^v\d+\.\d+\.\d+$'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Latest non-dev tag: $latest_tag"
|
||||
# Remove 'v' prefix for comparison
|
||||
$tag_num = $latest_tag.Substring(1)
|
||||
Write-Host "Extracted tag number: $tag_num"
|
||||
echo "tag=$tag_num" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Compare versions
|
||||
run: |
|
||||
Write-Host "Installed version: ${{ steps.installed_version.outputs.version }}"
|
||||
Write-Host "Latest tag version: ${{ steps.latest_tag.outputs.tag }}"
|
||||
if ("${{ steps.installed_version.outputs.version }}" -ne "${{ steps.latest_tag.outputs.tag }}") {
|
||||
Write-Error "Error: Installed aider version (${{ steps.installed_version.outputs.version }}) does not match the latest tag (${{ steps.latest_tag.outputs.tag }})."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Versions match."
|
||||
@@ -18,5 +18,6 @@ repos:
|
||||
rev: v2.2.6
|
||||
hooks:
|
||||
- id: codespell
|
||||
args: ["--skip", "aider/website/docs/languages.md"]
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
|
||||
91
HISTORY.md
91
HISTORY.md
@@ -1,6 +1,95 @@
|
||||
# Release history
|
||||
|
||||
### main branch
|
||||
### Aider v0.82.1
|
||||
- Added support for `o3` and `o4-mini` including provider-specific versions for OpenAI, OpenRouter, and Azure.
|
||||
- Added support for Azure specific `gpt-4.1` and `gpt-4.1-mini` models.
|
||||
- Disabled streaming for `o3` models since you need identity verification to stream.
|
||||
- Fixed handling of file paths in unified diffs, especially those generated by git.
|
||||
|
||||
### Aider v0.82.0
|
||||
|
||||
- Support for GPT 4.1, mini and nano.
|
||||
- Added new `patch` edit format for OpenAI's GPT-4.1 model.
|
||||
- Improved support for using architect mode with Gemini 2.5 Pro.
|
||||
- Added new `editor-diff`, `editor-whole`, and `editor-diff-fenced` edit formats.
|
||||
- Bugfix for automatically selecting the best edit format to use in architect mode.
|
||||
- Added support for `grok-3-fast-beta` and `grok-3-mini-fast-beta` models.
|
||||
- Aider wrote 92% of the code in this release.
|
||||
|
||||
### Aider v0.81.3
|
||||
|
||||
- Commit messages generated by aider are no longer forced to be entirely lowercase, by Peter Hadlaw.
|
||||
- Updated default settings for Grok models.
|
||||
|
||||
### Aider v0.81.2
|
||||
|
||||
- Add support for `xai/grok-3-beta`, `xai/grok-3-mini-beta`, `openrouter/x-ai/grok-3-beta`, `openrouter/x-ai/grok-3-mini-beta`, and `openrouter/openrouter/optimus-alpha` models.
|
||||
- Add alias "grok3" for `xai/grok-3-beta`.
|
||||
- Add alias "optimus" for `openrouter/openrouter/optimus-alpha`.
|
||||
- Fix URL extraction from error messages.
|
||||
- Allow adding files by full path even if a file with the same basename is already in the chat.
|
||||
- Fix quoting of values containing '#' in the sample `aider.conf.yml`.
|
||||
- Add support for Fireworks AI model 'deepseek-v3-0324', by Felix Lisczyk.
|
||||
- Commit messages generated by aider are now lowercase, by Anton Ödman.
|
||||
|
||||
### Aider v0.81.1
|
||||
|
||||
- Added support for the `gemini/gemini-2.5-pro-preview-03-25` model.
|
||||
- Updated the `gemini` alias to point to `gemini/gemini-2.5-pro-preview-03-25`.
|
||||
- Added the `gemini-exp` alias for `gemini/gemini-2.5-pro-exp-03-25`.
|
||||
|
||||
### Aider v0.81.0
|
||||
|
||||
- Added support for the `openrouter/openrouter/quasar-alpha` model.
|
||||
- Run with `aider --model quasar`
|
||||
- Offer OpenRouter OAuth authentication if an OpenRouter model is specified but the API key is missing.
|
||||
- Prevent retrying API calls when the provider reports insufficient credits.
|
||||
- Improve URL detection to exclude trailing double quotes.
|
||||
- Aider wrote 86% of the code in this release.
|
||||
|
||||
### Aider v0.80.4
|
||||
|
||||
- Bumped deps to pickup litellm change to properly display the root cause of OpenRouter "choices" errors.
|
||||
|
||||
### Aider v0.80.3
|
||||
|
||||
- Improve error message for OpenRouter API connection issues to mention potential rate limiting or upstream provider issues.
|
||||
- Configure weak models (`gemini/gemini-2.0-flash` and `openrouter/google/gemini-2.0-flash-exp:free`) for Gemini 2.5 Pro models.
|
||||
- Add model metadata for `openrouter/google/gemini-2.0-flash-exp:free`.
|
||||
|
||||
### Aider v0.80.2
|
||||
|
||||
- Bumped deps.
|
||||
|
||||
### Aider v0.80.1
|
||||
|
||||
- Updated deps for yanked fsspec and aiohttp packages #3699
|
||||
- Removed redundant dependency check during OpenRouter OAuth flow, by Claudia Pellegrino.
|
||||
|
||||
### Aider v0.80.0
|
||||
|
||||
- OpenRouter OAuth integration:
|
||||
- Offer to OAuth against OpenRouter if no model and keys are provided.
|
||||
- Select OpenRouter default model based on free/paid tier status if `OPENROUTER_API_KEY` is set and no model is specified.
|
||||
- Prioritize `gemini/gemini-2.5-pro-exp-03-25` if `GEMINI_API_KEY` is set, and `vertex_ai/gemini-2.5-pro-exp-03-25` if `VERTEXAI_PROJECT` is set, when no model is specified.
|
||||
- Validate user-configured color settings on startup and warn/disable invalid ones.
|
||||
- Warn at startup if `--stream` and `--cache-prompts` are used together, as cost estimates may be inaccurate.
|
||||
- Boost repomap ranking for files whose path components match identifiers mentioned in the chat.
|
||||
- Change web scraping timeout from an error to a warning, allowing scraping to continue with potentially incomplete content.
|
||||
- Left-align markdown headings in the terminal output, by Peter Schilling.
|
||||
- Update edit format to the new model's default when switching models with `/model`, if the user was using the old model's default format.
|
||||
- Add `Ctrl-X Ctrl-E` keybinding to edit the current input buffer in an external editor, by Matteo Landi.
|
||||
- Fix linting errors for filepaths containing shell metacharacters, by Mir Adnan ALI.
|
||||
- Add the `openrouter/deepseek-chat-v3-0324:free` model.
|
||||
- Add repomap support for the Scala language, by Vasil Markoukin.
|
||||
- Fixed bug in `/run` that was preventing auto-testing.
|
||||
- Fix bug preventing `UnboundLocalError` during git tree traversal.
|
||||
- Handle `GitCommandNotFound` error if git is not installed or not in PATH.
|
||||
- Handle `FileNotFoundError` if the current working directory is deleted while aider is running.
|
||||
- Fix completion menu current item color styling, by Andrey Ivanov.
|
||||
- Aider wrote 87% of the code in this release.
|
||||
|
||||
### Aider v0.79.2
|
||||
|
||||
- Added 'gemini' alias for gemini-2.5-pro model.
|
||||
- Updated Gemini 2.5 Pro max output tokens to 64k.
|
||||
|
||||
23
README.md
23
README.md
@@ -20,20 +20,20 @@ Aider lets you pair program with LLMs to start a new project or build on your ex
|
||||
|
||||
<p align="center">
|
||||
<!--[[[cog
|
||||
from scripts.badges import get_badges_md
|
||||
from scripts.homepage import get_badges_md
|
||||
text = get_badges_md()
|
||||
cog.out(text)
|
||||
]]]-->
|
||||
<a href="https://github.com/Aider-AI/aider/stargazers"><img alt="GitHub Stars" title="Total number of GitHub stars the Aider project has received"
|
||||
src="https://img.shields.io/github/stars/Aider-AI/aider?style=flat-square&logo=github&color=f1c40f&labelColor=555555"/></a>
|
||||
<a href="https://pypi.org/project/aider-chat/"><img alt="PyPI Downloads" title="Total number of installations via pip from PyPI"
|
||||
src="https://img.shields.io/badge/📦%20Installs-1.7M-2ecc71?style=flat-square&labelColor=555555"/></a>
|
||||
src="https://img.shields.io/badge/📦%20Installs-2.0M-2ecc71?style=flat-square&labelColor=555555"/></a>
|
||||
<img alt="Tokens per week" title="Number of tokens processed weekly by Aider users"
|
||||
src="https://img.shields.io/badge/📈%20Tokens%2Fweek-15B-3498db?style=flat-square&labelColor=555555"/>
|
||||
<a href="https://openrouter.ai/"><img alt="OpenRouter Ranking" title="Aider's ranking among applications on the OpenRouter platform"
|
||||
<a href="https://openrouter.ai/#options-menu"><img alt="OpenRouter Ranking" title="Aider's ranking among applications on the OpenRouter platform"
|
||||
src="https://img.shields.io/badge/🏆%20OpenRouter-Top%2020-9b59b6?style=flat-square&labelColor=555555"/></a>
|
||||
<a href="https://aider.chat/HISTORY.html"><img alt="Singularity" title="Percentage of the new code in Aider's last release written by Aider itself"
|
||||
src="https://img.shields.io/badge/🔄%20Singularity-65%25-e74c3c?style=flat-square&labelColor=555555"/></a>
|
||||
src="https://img.shields.io/badge/🔄%20Singularity-92%25-e74c3c?style=flat-square&labelColor=555555"/></a>
|
||||
<!--[[[end]]]-->
|
||||
</p>
|
||||
|
||||
@@ -140,6 +140,7 @@ See the [installation instructions](https://aider.chat/docs/install.html) and [u
|
||||
|
||||
## Kind Words From Users
|
||||
|
||||
- *"My life has changed this week. There's finally an AI coding tool that's good enough to keep up with me... Aider... It's going to rock your world."* — [Eric S. Raymond](https://x.com/esrtweet/status/1910809356381413593)
|
||||
- *"The best free open source AI coding assistant."* — [IndyDevDan](https://youtu.be/YALpX8oOn78)
|
||||
- *"The best AI coding assistant so far."* — [Matthew Berman](https://www.youtube.com/watch?v=df8afeb1FY8)
|
||||
- *"Aider ... has easily quadrupled my coding productivity."* — [SOLAR_FIELDS](https://news.ycombinator.com/item?id=36212100)
|
||||
@@ -160,3 +161,17 @@ See the [installation instructions](https://aider.chat/docs/install.html) and [u
|
||||
- *"Hands down, this is the best AI coding assistant tool so far."* — [IndyDevDan](https://www.youtube.com/watch?v=MPYFPvxfGZs)
|
||||
- *"[Aider] changed my daily coding workflows. It's mind-blowing how a single Python application can change your life."* — [maledorak](https://discord.com/channels/1131200896827654144/1131200896827654149/1258453375620747264)
|
||||
- *"Best agent for actual dev work in existing codebases."* — [Nick Dobos](https://twitter.com/NickADobos/status/1690408967963652097?s=20)
|
||||
- *"One of my favorite pieces of software. Blazing trails on new paradigms!"* — [Chris Wall](https://x.com/chris65536/status/1905053299251798432)
|
||||
- *"Aider has been revolutionary for me and my work."* — [Starry Hope](https://x.com/starryhopeblog/status/1904985812137132056)
|
||||
- *"Try aider! One of the best ways to vibe code."* — [Chris Wall](https://x.com/Chris65536/status/1905053418961391929)
|
||||
- *"Aider is hands down the best. And it's free and opensource."* — [AriyaSavakaLurker](https://www.reddit.com/r/ChatGPTCoding/comments/1ik16y6/whats_your_take_on_aider/mbip39n/)
|
||||
- *"Aider is also my best friend."* — [jzn21](https://www.reddit.com/r/ChatGPTCoding/comments/1heuvuo/aider_vs_cline_vs_windsurf_vs_cursor/m27dcnb/)
|
||||
- *"Try Aider, it's worth it."* — [jorgejhms](https://www.reddit.com/r/ChatGPTCoding/comments/1heuvuo/aider_vs_cline_vs_windsurf_vs_cursor/m27cp99/)
|
||||
- *"I like aider :)"* — [Chenwei Cui](https://x.com/ccui42/status/1904965344999145698)
|
||||
- *"Aider is the precision tool of LLM code gen. It is minimal, thoughtful and capable of surgical changes to your codebase all while keeping the developer in control."* — [Reilly Sweetland](https://x.com/rsweetland/status/1904963807237259586)
|
||||
- *"Cannot believe aider vibe coded a 650 LOC feature across service and cli today in 1 shot."* - [autopoietist](https://discord.com/channels/1131200896827654144/1131200896827654149/1355675042259796101)
|
||||
- *"Oh no the secret is out! Yes, Aider is the best coding tool around. I highly, highly recommend it to anyone."* — [Joshua D Vander Hook](https://x.com/jodavaho/status/1911154899057795218)
|
||||
- *"thanks to aider, i have started and finished three personal projects within the last two days"* — [joseph stalzyn](https://x.com/anitaheeder/status/1908338609645904160)
|
||||
- *"Been using aider as my daily driver for over a year ... I absolutely love the tool, like beyond words."* — [koleok](https://discord.com/channels/1131200896827654144/1273248471394291754/1356727448372252783)
|
||||
- *"aider is really cool"* — [kache (@yacineMTB)](https://x.com/yacineMTB/status/1911224442430124387)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from packaging import version
|
||||
|
||||
__version__ = "0.79.3.dev"
|
||||
__version__ = "0.82.3.dev"
|
||||
safe_version = __version__
|
||||
|
||||
try:
|
||||
|
||||
@@ -143,7 +143,10 @@ class YamlHelpFormatter(argparse.HelpFormatter):
|
||||
default = "true"
|
||||
|
||||
if default:
|
||||
parts.append(f"#{switch}: {default}\n")
|
||||
if "#" in default:
|
||||
parts.append(f'#{switch}: "{default}"\n')
|
||||
else:
|
||||
parts.append(f"#{switch}: {default}\n")
|
||||
elif action.nargs in ("*", "+") or isinstance(action, argparse._AppendAction):
|
||||
parts.append(f"#{switch}: xxx")
|
||||
parts.append("## Specify multiple values like this:")
|
||||
|
||||
@@ -4,9 +4,11 @@ from .base_coder import Coder
|
||||
from .context_coder import ContextCoder
|
||||
from .editblock_coder import EditBlockCoder
|
||||
from .editblock_fenced_coder import EditBlockFencedCoder
|
||||
from .editor_diff_fenced_coder import EditorDiffFencedCoder
|
||||
from .editor_editblock_coder import EditorEditBlockCoder
|
||||
from .editor_whole_coder import EditorWholeFileCoder
|
||||
from .help_coder import HelpCoder
|
||||
from .patch_coder import PatchCoder
|
||||
from .udiff_coder import UnifiedDiffCoder
|
||||
from .wholefile_coder import WholeFileCoder
|
||||
|
||||
@@ -19,10 +21,12 @@ __all__ = [
|
||||
EditBlockCoder,
|
||||
EditBlockFencedCoder,
|
||||
WholeFileCoder,
|
||||
PatchCoder,
|
||||
UnifiedDiffCoder,
|
||||
# SingleWholeFileFunctionCoder,
|
||||
ArchitectCoder,
|
||||
EditorEditBlockCoder,
|
||||
EditorWholeFileCoder,
|
||||
EditorDiffFencedCoder,
|
||||
ContextCoder,
|
||||
]
|
||||
|
||||
@@ -209,12 +209,12 @@ class Coder:
|
||||
output = f"{prefix}: {main_model.name} with {self.edit_format} edit format"
|
||||
|
||||
# Check for thinking token budget
|
||||
thinking_tokens = main_model.get_thinking_tokens(main_model)
|
||||
thinking_tokens = main_model.get_thinking_tokens()
|
||||
if thinking_tokens:
|
||||
output += f", {thinking_tokens} think tokens"
|
||||
|
||||
# Check for reasoning effort
|
||||
reasoning_effort = main_model.get_reasoning_effort(main_model)
|
||||
reasoning_effort = main_model.get_reasoning_effort()
|
||||
if reasoning_effort:
|
||||
output += f", reasoning {reasoning_effort}"
|
||||
|
||||
@@ -922,10 +922,11 @@ class Coder:
|
||||
else:
|
||||
self.io.tool_error(text)
|
||||
|
||||
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*)")
|
||||
# Exclude double quotes from the matched URL characters
|
||||
url_pattern = re.compile(r'(https?://[^\s/$.?#].[^\s"]*)')
|
||||
urls = list(set(url_pattern.findall(text))) # Use set to remove duplicates
|
||||
for url in urls:
|
||||
url = url.rstrip(".',\"")
|
||||
url = url.rstrip(".',\"}") # Added } to the characters to strip
|
||||
self.io.offer_url(url)
|
||||
return urls
|
||||
|
||||
@@ -934,7 +935,8 @@ class Coder:
|
||||
if not self.detect_urls:
|
||||
return inp
|
||||
|
||||
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*[^\s,.])")
|
||||
# Exclude double quotes from the matched URL characters
|
||||
url_pattern = re.compile(r'(https?://[^\s/$.?#].[^\s"]*[^\s,.])')
|
||||
urls = list(set(url_pattern.findall(inp))) # Use set to remove duplicates
|
||||
group = ConfirmGroup(urls)
|
||||
for url in urls:
|
||||
@@ -1089,11 +1091,13 @@ class Coder:
|
||||
if self.suggest_shell_commands:
|
||||
shell_cmd_prompt = self.gpt_prompts.shell_cmd_prompt.format(platform=platform_text)
|
||||
shell_cmd_reminder = self.gpt_prompts.shell_cmd_reminder.format(platform=platform_text)
|
||||
rename_with_shell = self.gpt_prompts.rename_with_shell
|
||||
else:
|
||||
shell_cmd_prompt = self.gpt_prompts.no_shell_cmd_prompt.format(platform=platform_text)
|
||||
shell_cmd_reminder = self.gpt_prompts.no_shell_cmd_reminder.format(
|
||||
platform=platform_text
|
||||
)
|
||||
rename_with_shell = ""
|
||||
|
||||
if self.chat_language:
|
||||
language = self.chat_language
|
||||
@@ -1113,7 +1117,9 @@ class Coder:
|
||||
lazy_prompt=lazy_prompt,
|
||||
platform=platform_text,
|
||||
shell_cmd_prompt=shell_cmd_prompt,
|
||||
rename_with_shell=rename_with_shell,
|
||||
shell_cmd_reminder=shell_cmd_reminder,
|
||||
go_ahead_tip=self.gpt_prompts.go_ahead_tip,
|
||||
language=language,
|
||||
)
|
||||
|
||||
@@ -1624,10 +1630,6 @@ class Coder:
|
||||
mentioned_rel_fnames = set()
|
||||
fname_to_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_words = set(word.replace("\\", "/") for word in words)
|
||||
if normalized_rel_fname in normalized_words:
|
||||
@@ -1642,6 +1644,10 @@ class Coder:
|
||||
fname_to_rel_fnames[fname].append(rel_fname)
|
||||
|
||||
for fname, rel_fnames in fname_to_rel_fnames.items():
|
||||
# If the basename is already in chat, don't add based on a basename mention
|
||||
if fname in existing_basenames:
|
||||
continue
|
||||
# If the basename mention is unique among addable files and present in the text
|
||||
if len(rel_fnames) == 1 and fname in words:
|
||||
mentioned_rel_fnames.add(rel_fnames[0])
|
||||
|
||||
@@ -1961,11 +1967,6 @@ class Coder:
|
||||
f" ${format_cost(self.total_cost)} session."
|
||||
)
|
||||
|
||||
if self.add_cache_headers and self.stream:
|
||||
warning = " Use --no-stream for accurate caching costs."
|
||||
self.usage_report = tokens_report + "\n" + cost_report + warning
|
||||
return
|
||||
|
||||
if cache_hit_tokens and cache_write_tokens:
|
||||
sep = "\n"
|
||||
else:
|
||||
|
||||
@@ -53,3 +53,6 @@ Do not edit these files!
|
||||
shell_cmd_reminder = ""
|
||||
no_shell_cmd_prompt = ""
|
||||
no_shell_cmd_reminder = ""
|
||||
|
||||
rename_with_shell = ""
|
||||
go_ahead_tip = ""
|
||||
|
||||
@@ -454,7 +454,10 @@ def find_original_update_blocks(content, fence=DEFAULT_FENCE, valid_fnames=None)
|
||||
"```csh",
|
||||
"```tcsh",
|
||||
]
|
||||
next_is_editblock = i + 1 < len(lines) and head_pattern.match(lines[i + 1].strip())
|
||||
|
||||
# Check if the next line or the one after that is an editblock
|
||||
next_is_editblock = (i + 1 < len(lines) and head_pattern.match(lines[i + 1].strip())
|
||||
or i + 2 < len(lines) and head_pattern.match(lines[i + 2].strip()))
|
||||
|
||||
if any(line.strip().startswith(start) for start in shell_starts) and not next_is_editblock:
|
||||
shell_content = []
|
||||
|
||||
@@ -19,7 +19,7 @@ class EditBlockFencedPrompts(EditBlockPrompts):
|
||||
|
||||
Here are the *SEARCH/REPLACE* blocks:
|
||||
|
||||
{fence[0]}
|
||||
{fence[0]}python
|
||||
mathweb/flask/app.py
|
||||
<<<<<<< SEARCH
|
||||
from flask import Flask
|
||||
@@ -29,7 +29,7 @@ from flask import Flask
|
||||
>>>>>>> REPLACE
|
||||
{fence[1]}
|
||||
|
||||
{fence[0]}
|
||||
{fence[0]}python
|
||||
mathweb/flask/app.py
|
||||
<<<<<<< SEARCH
|
||||
def factorial(n):
|
||||
@@ -44,7 +44,7 @@ def factorial(n):
|
||||
>>>>>>> REPLACE
|
||||
{fence[1]}
|
||||
|
||||
{fence[0]}
|
||||
{fence[0]}python
|
||||
mathweb/flask/app.py
|
||||
<<<<<<< SEARCH
|
||||
return str(factorial(n))
|
||||
@@ -68,7 +68,7 @@ mathweb/flask/app.py
|
||||
|
||||
Here are the *SEARCH/REPLACE* blocks:
|
||||
|
||||
{fence[0]}
|
||||
{fence[0]}python
|
||||
hello.py
|
||||
<<<<<<< SEARCH
|
||||
=======
|
||||
@@ -79,7 +79,7 @@ def hello():
|
||||
>>>>>>> REPLACE
|
||||
{fence[1]}
|
||||
|
||||
{fence[0]}
|
||||
{fence[0]}python
|
||||
main.py
|
||||
<<<<<<< SEARCH
|
||||
def hello():
|
||||
@@ -93,3 +93,51 @@ from hello import hello
|
||||
""",
|
||||
),
|
||||
]
|
||||
|
||||
system_reminder = """
|
||||
# *SEARCH/REPLACE block* Rules:
|
||||
|
||||
Every *SEARCH/REPLACE block* must use this format:
|
||||
1. The opening fence and code language, eg: {fence[0]}python
|
||||
2. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
|
||||
3. The start of search block: <<<<<<< SEARCH
|
||||
4. A contiguous chunk of lines to search for in the existing source code
|
||||
5. The dividing line: =======
|
||||
6. The lines to replace into the source code
|
||||
7. The end of the replace block: >>>>>>> REPLACE
|
||||
8. The closing fence: {fence[1]}
|
||||
|
||||
Use the *FULL* file path, as shown to you by the user.
|
||||
{quad_backtick_reminder}
|
||||
Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
|
||||
If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.
|
||||
|
||||
*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
|
||||
Including multiple unique *SEARCH/REPLACE* blocks if needed.
|
||||
Include enough lines in each SEARCH section to uniquely match each set of lines that need to change.
|
||||
|
||||
Keep *SEARCH/REPLACE* blocks concise.
|
||||
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
|
||||
Include just the changing lines, and a few surrounding lines if needed for uniqueness.
|
||||
Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
|
||||
|
||||
Only create *SEARCH/REPLACE* blocks for files that the user has added to the chat!
|
||||
|
||||
To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location.
|
||||
|
||||
Pay attention to which filenames the user wants you to edit, especially if they are asking you to create a new file.
|
||||
|
||||
If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
||||
- A new file path, including dir name if needed
|
||||
- An empty `SEARCH` section
|
||||
- The new file's contents in the `REPLACE` section
|
||||
|
||||
To rename files which have been added to the chat, use shell commands at the end of your response.
|
||||
|
||||
If the user just says something like "ok" or "go ahead" or "do that" they probably want you to make SEARCH/REPLACE blocks for the code changes you just proposed.
|
||||
The user will say when they've applied your edits. If they haven't explicitly confirmed the edits have been applied, they probably want proper SEARCH/REPLACE blocks.
|
||||
|
||||
{lazy_prompt}
|
||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
{shell_cmd_reminder}
|
||||
"""
|
||||
|
||||
@@ -181,14 +181,17 @@ If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
||||
- An empty `SEARCH` section
|
||||
- The new file's contents in the `REPLACE` section
|
||||
|
||||
To rename files which have been added to the chat, use shell commands at the end of your response.
|
||||
{rename_with_shell}{go_ahead_tip}{lazy_prompt}ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
{shell_cmd_reminder}
|
||||
"""
|
||||
|
||||
If the user just says something like "ok" or "go ahead" or "do that" they probably want you to make SEARCH/REPLACE blocks for the code changes you just proposed.
|
||||
rename_with_shell = """To rename files which have been added to the chat, use shell commands at the end of your response.
|
||||
|
||||
"""
|
||||
|
||||
go_ahead_tip = """If the user just says something like "ok" or "go ahead" or "do that" they probably want you to make SEARCH/REPLACE blocks for the code changes you just proposed.
|
||||
The user will say when they've applied your edits. If they haven't explicitly confirmed the edits have been applied, they probably want proper SEARCH/REPLACE blocks.
|
||||
|
||||
{lazy_prompt}
|
||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
{shell_cmd_reminder}
|
||||
"""
|
||||
|
||||
shell_cmd_reminder = """
|
||||
@@ -200,4 +203,5 @@ Examples of when to suggest shell commands:
|
||||
- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations.
|
||||
- If your code changes add new dependencies, suggest the command to install them.
|
||||
- Etc.
|
||||
|
||||
"""
|
||||
|
||||
9
aider/coders/editor_diff_fenced_coder.py
Normal file
9
aider/coders/editor_diff_fenced_coder.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .editblock_fenced_coder import EditBlockFencedCoder
|
||||
from .editor_diff_fenced_prompts import EditorDiffFencedPrompts
|
||||
|
||||
|
||||
class EditorDiffFencedCoder(EditBlockFencedCoder):
|
||||
"A coder that uses search/replace blocks, focused purely on editing files."
|
||||
|
||||
edit_format = "editor-diff-fenced"
|
||||
gpt_prompts = EditorDiffFencedPrompts()
|
||||
11
aider/coders/editor_diff_fenced_prompts.py
Normal file
11
aider/coders/editor_diff_fenced_prompts.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# flake8: noqa: E501
|
||||
|
||||
from .editblock_fenced_prompts import EditBlockFencedPrompts
|
||||
|
||||
|
||||
class EditorDiffFencedPrompts(EditBlockFencedPrompts):
|
||||
shell_cmd_prompt = ""
|
||||
no_shell_cmd_prompt = ""
|
||||
shell_cmd_reminder = ""
|
||||
go_ahead_tip = ""
|
||||
rename_with_shell = ""
|
||||
@@ -14,3 +14,5 @@ ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
shell_cmd_prompt = ""
|
||||
no_shell_cmd_prompt = ""
|
||||
shell_cmd_reminder = ""
|
||||
go_ahead_tip = ""
|
||||
rename_with_shell = ""
|
||||
|
||||
706
aider/coders/patch_coder.py
Normal file
706
aider/coders/patch_coder.py
Normal file
@@ -0,0 +1,706 @@
|
||||
import pathlib
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from .base_coder import Coder
|
||||
from .patch_prompts import PatchPrompts
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Domain objects & Exceptions (Adapted from apply_patch.py)
|
||||
# --------------------------------------------------------------------------- #
|
||||
class DiffError(ValueError):
|
||||
"""Any problem detected while parsing or applying a patch."""
|
||||
|
||||
|
||||
class ActionType(str, Enum):
|
||||
ADD = "Add"
|
||||
DELETE = "Delete"
|
||||
UPDATE = "Update"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Chunk:
|
||||
orig_index: int = -1 # Line number in the *original* file block where the change starts
|
||||
del_lines: List[str] = field(default_factory=list)
|
||||
ins_lines: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PatchAction:
|
||||
type: ActionType
|
||||
path: str
|
||||
# For ADD:
|
||||
new_content: Optional[str] = None
|
||||
# For UPDATE:
|
||||
chunks: List[Chunk] = field(default_factory=list)
|
||||
move_path: Optional[str] = None
|
||||
|
||||
|
||||
# Type alias for the return type of get_edits
|
||||
EditResult = Tuple[str, PatchAction]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Patch:
|
||||
actions: Dict[str, PatchAction] = field(default_factory=dict)
|
||||
fuzz: int = 0 # Track fuzziness used during parsing
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Helper functions (Adapted from apply_patch.py)
|
||||
# --------------------------------------------------------------------------- #
|
||||
def _norm(line: str) -> str:
|
||||
"""Strip CR so comparisons work for both LF and CRLF input."""
|
||||
return line.rstrip("\r")
|
||||
|
||||
|
||||
def find_context_core(lines: List[str], context: List[str], start: int) -> Tuple[int, int]:
|
||||
"""Finds context block, returns start index and fuzz level."""
|
||||
if not context:
|
||||
return start, 0
|
||||
|
||||
# Exact match
|
||||
for i in range(start, len(lines) - len(context) + 1):
|
||||
if lines[i : i + len(context)] == context:
|
||||
return i, 0
|
||||
# Rstrip match
|
||||
norm_context = [s.rstrip() for s in context]
|
||||
for i in range(start, len(lines) - len(context) + 1):
|
||||
if [s.rstrip() for s in lines[i : i + len(context)]] == norm_context:
|
||||
return i, 1 # Fuzz level 1
|
||||
# Strip match
|
||||
norm_context_strip = [s.strip() for s in context]
|
||||
for i in range(start, len(lines) - len(context) + 1):
|
||||
if [s.strip() for s in lines[i : i + len(context)]] == norm_context_strip:
|
||||
return i, 100 # Fuzz level 100
|
||||
return -1, 0
|
||||
|
||||
|
||||
def find_context(lines: List[str], context: List[str], start: int, eof: bool) -> Tuple[int, int]:
|
||||
"""Finds context, handling EOF marker."""
|
||||
if eof:
|
||||
# If EOF marker, first try matching at the very end
|
||||
if len(lines) >= len(context):
|
||||
new_index, fuzz = find_context_core(lines, context, len(lines) - len(context))
|
||||
if new_index != -1:
|
||||
return new_index, fuzz
|
||||
# If not found at end, search from `start` as fallback
|
||||
new_index, fuzz = find_context_core(lines, context, start)
|
||||
return new_index, fuzz + 10_000 # Add large fuzz penalty if EOF wasn't at end
|
||||
# Normal case: search from `start`
|
||||
return find_context_core(lines, context, start)
|
||||
|
||||
|
||||
def peek_next_section(lines: List[str], index: int) -> Tuple[List[str], List[Chunk], int, bool]:
|
||||
"""
|
||||
Parses one section (context, -, + lines) of an Update block.
|
||||
Returns: (context_lines, chunks_in_section, next_index, is_eof)
|
||||
"""
|
||||
context_lines: List[str] = []
|
||||
del_lines: List[str] = []
|
||||
ins_lines: List[str] = []
|
||||
chunks: List[Chunk] = []
|
||||
mode = "keep" # Start by expecting context lines
|
||||
start_index = index
|
||||
|
||||
while index < len(lines):
|
||||
line = lines[index]
|
||||
norm_line = _norm(line)
|
||||
|
||||
# Check for section terminators
|
||||
if norm_line.startswith(
|
||||
(
|
||||
"@@",
|
||||
"*** End Patch",
|
||||
"*** Update File:",
|
||||
"*** Delete File:",
|
||||
"*** Add File:",
|
||||
"*** End of File", # Special terminator
|
||||
)
|
||||
):
|
||||
break
|
||||
if norm_line == "***": # Legacy/alternative terminator? Handle just in case.
|
||||
break
|
||||
if norm_line.startswith("***"): # Invalid line
|
||||
raise DiffError(f"Invalid patch line found in update section: {line}")
|
||||
|
||||
index += 1
|
||||
last_mode = mode
|
||||
|
||||
# Determine line type and strip prefix
|
||||
if line.startswith("+"):
|
||||
mode = "add"
|
||||
line_content = line[1:]
|
||||
elif line.startswith("-"):
|
||||
mode = "delete"
|
||||
line_content = line[1:]
|
||||
elif line.startswith(" "):
|
||||
mode = "keep"
|
||||
line_content = line[1:]
|
||||
elif line.strip() == "": # Treat blank lines in patch as context ' '
|
||||
mode = "keep"
|
||||
line_content = "" # Keep it as a blank line
|
||||
else:
|
||||
# Assume lines without prefix are context if format is loose,
|
||||
# but strict format requires ' '. Raise error for strictness.
|
||||
raise DiffError(f"Invalid line prefix in update section: {line}")
|
||||
|
||||
# If mode changes from add/delete back to keep, finalize the previous chunk
|
||||
if mode == "keep" and last_mode != "keep":
|
||||
if del_lines or ins_lines:
|
||||
chunks.append(
|
||||
Chunk(
|
||||
# orig_index is relative to the start of the *context* block found
|
||||
orig_index=len(context_lines) - len(del_lines),
|
||||
del_lines=del_lines,
|
||||
ins_lines=ins_lines,
|
||||
)
|
||||
)
|
||||
del_lines, ins_lines = [], []
|
||||
|
||||
# Collect lines based on mode
|
||||
if mode == "delete":
|
||||
del_lines.append(line_content)
|
||||
context_lines.append(line_content) # Deleted lines are part of the original context
|
||||
elif mode == "add":
|
||||
ins_lines.append(line_content)
|
||||
elif mode == "keep":
|
||||
context_lines.append(line_content)
|
||||
|
||||
# Finalize any pending chunk at the end of the section
|
||||
if del_lines or ins_lines:
|
||||
chunks.append(
|
||||
Chunk(
|
||||
orig_index=len(context_lines) - len(del_lines),
|
||||
del_lines=del_lines,
|
||||
ins_lines=ins_lines,
|
||||
)
|
||||
)
|
||||
|
||||
# Check for EOF marker
|
||||
is_eof = False
|
||||
if index < len(lines) and _norm(lines[index]) == "*** End of File":
|
||||
index += 1
|
||||
is_eof = True
|
||||
|
||||
if index == start_index and not is_eof: # Should not happen if patch is well-formed
|
||||
raise DiffError("Empty patch section found.")
|
||||
|
||||
return context_lines, chunks, index, is_eof
|
||||
|
||||
|
||||
def identify_files_needed(text: str) -> List[str]:
|
||||
"""Extracts file paths from Update and Delete actions."""
|
||||
lines = text.splitlines()
|
||||
paths = set()
|
||||
for line in lines:
|
||||
norm_line = _norm(line)
|
||||
if norm_line.startswith("*** Update File: "):
|
||||
paths.add(norm_line[len("*** Update File: ") :].strip())
|
||||
elif norm_line.startswith("*** Delete File: "):
|
||||
paths.add(norm_line[len("*** Delete File: ") :].strip())
|
||||
return list(paths)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# PatchCoder Class Implementation
|
||||
# --------------------------------------------------------------------------- #
|
||||
class PatchCoder(Coder):
|
||||
"""
|
||||
A coder that uses a custom patch format for code modifications,
|
||||
inspired by the format described in tmp.gpt41edits.txt.
|
||||
Applies patches using logic adapted from the reference apply_patch.py script.
|
||||
"""
|
||||
|
||||
edit_format = "patch"
|
||||
gpt_prompts = PatchPrompts()
|
||||
|
||||
def get_edits(self) -> List[EditResult]:
|
||||
"""
|
||||
Parses the LLM response content (containing the patch) into a list of
|
||||
tuples, where each tuple contains the file path and the PatchAction object.
|
||||
"""
|
||||
content = self.partial_response_content
|
||||
if not content or not content.strip():
|
||||
return []
|
||||
|
||||
# Check for patch sentinels
|
||||
lines = content.splitlines()
|
||||
if (
|
||||
len(lines) < 2
|
||||
or not _norm(lines[0]).startswith("*** Begin Patch")
|
||||
# Allow flexible end, might be EOF or just end of stream
|
||||
# or _norm(lines[-1]) != "*** End Patch"
|
||||
):
|
||||
# Tolerate missing sentinels if content looks like a patch action
|
||||
is_patch_like = any(
|
||||
_norm(line).startswith(
|
||||
("@@", "*** Update File:", "*** Add File:", "*** Delete File:")
|
||||
)
|
||||
for line in lines
|
||||
)
|
||||
if not is_patch_like:
|
||||
# If it doesn't even look like a patch, return empty
|
||||
self.io.tool_warning("Response does not appear to be in patch format.")
|
||||
return []
|
||||
# If it looks like a patch but lacks sentinels, try parsing anyway but warn.
|
||||
self.io.tool_warning(
|
||||
"Patch format warning: Missing '*** Begin Patch'/'*** End Patch' sentinels."
|
||||
)
|
||||
start_index = 0
|
||||
else:
|
||||
start_index = 1 # Skip "*** Begin Patch"
|
||||
|
||||
# Identify files needed for context lookups during parsing
|
||||
needed_paths = identify_files_needed(content)
|
||||
current_files: Dict[str, str] = {}
|
||||
for rel_path in needed_paths:
|
||||
abs_path = self.abs_root_path(rel_path)
|
||||
try:
|
||||
# Use io.read_text to handle potential errors/encodings
|
||||
file_content = self.io.read_text(abs_path)
|
||||
if file_content is None:
|
||||
raise DiffError(
|
||||
f"File referenced in patch not found or could not be read: {rel_path}"
|
||||
)
|
||||
current_files[rel_path] = file_content
|
||||
except FileNotFoundError:
|
||||
raise DiffError(f"File referenced in patch not found: {rel_path}")
|
||||
except IOError as e:
|
||||
raise DiffError(f"Error reading file {rel_path}: {e}")
|
||||
|
||||
try:
|
||||
# Parse the patch text using adapted logic
|
||||
patch_obj = self._parse_patch_text(lines, start_index, current_files)
|
||||
# Convert Patch object actions dict to a list of tuples (path, action)
|
||||
# for compatibility with the base Coder's prepare_to_edit method.
|
||||
results = []
|
||||
for path, action in patch_obj.actions.items():
|
||||
results.append((path, action))
|
||||
return results
|
||||
except DiffError as e:
|
||||
# Raise as ValueError for consistency with other coders' error handling
|
||||
raise ValueError(f"Error parsing patch content: {e}")
|
||||
except Exception as e:
|
||||
# Catch unexpected errors during parsing
|
||||
raise ValueError(f"Unexpected error parsing patch: {e}")
|
||||
|
||||
def _parse_patch_text(
|
||||
self, lines: List[str], start_index: int, current_files: Dict[str, str]
|
||||
) -> Patch:
|
||||
"""
|
||||
Parses patch content lines into a Patch object.
|
||||
Adapted from the Parser class in apply_patch.py.
|
||||
"""
|
||||
patch = Patch()
|
||||
index = start_index
|
||||
fuzz_accumulator = 0
|
||||
|
||||
while index < len(lines):
|
||||
line = lines[index]
|
||||
norm_line = _norm(line)
|
||||
|
||||
if norm_line == "*** End Patch":
|
||||
index += 1
|
||||
break # Successfully reached end
|
||||
|
||||
# ---------- UPDATE ---------- #
|
||||
if norm_line.startswith("*** Update File: "):
|
||||
path = norm_line[len("*** Update File: ") :].strip()
|
||||
index += 1
|
||||
if not path:
|
||||
raise DiffError("Update File action missing path.")
|
||||
|
||||
# Optional move target
|
||||
move_to = None
|
||||
if index < len(lines) and _norm(lines[index]).startswith("*** Move to: "):
|
||||
move_to = _norm(lines[index])[len("*** Move to: ") :].strip()
|
||||
index += 1
|
||||
if not move_to:
|
||||
raise DiffError("Move to action missing path.")
|
||||
|
||||
if path not in current_files:
|
||||
raise DiffError(f"Update File Error - missing file content for: {path}")
|
||||
|
||||
file_content = current_files[path]
|
||||
|
||||
existing_action = patch.actions.get(path)
|
||||
if existing_action is not None:
|
||||
# Merge additional UPDATE block into the existing one
|
||||
if existing_action.type != ActionType.UPDATE:
|
||||
raise DiffError(f"Conflicting actions for file: {path}")
|
||||
|
||||
new_action, index, fuzz = self._parse_update_file_sections(
|
||||
lines, index, file_content
|
||||
)
|
||||
existing_action.chunks.extend(new_action.chunks)
|
||||
|
||||
if move_to:
|
||||
if existing_action.move_path and existing_action.move_path != move_to:
|
||||
raise DiffError(f"Conflicting move targets for file: {path}")
|
||||
existing_action.move_path = move_to
|
||||
fuzz_accumulator += fuzz
|
||||
else:
|
||||
# First UPDATE block for this file
|
||||
action, index, fuzz = self._parse_update_file_sections(
|
||||
lines, index, file_content
|
||||
)
|
||||
action.path = path
|
||||
action.move_path = move_to
|
||||
patch.actions[path] = action
|
||||
fuzz_accumulator += fuzz
|
||||
continue
|
||||
|
||||
# ---------- DELETE ---------- #
|
||||
elif norm_line.startswith("*** Delete File: "):
|
||||
path = norm_line[len("*** Delete File: ") :].strip()
|
||||
index += 1
|
||||
if not path:
|
||||
raise DiffError("Delete File action missing path.")
|
||||
existing_action = patch.actions.get(path)
|
||||
if existing_action:
|
||||
if existing_action.type == ActionType.DELETE:
|
||||
# Duplicate delete – ignore the extra block
|
||||
self.io.tool_warning(f"Duplicate delete action for file: {path} ignored.")
|
||||
continue
|
||||
else:
|
||||
raise DiffError(f"Conflicting actions for file: {path}")
|
||||
if path not in current_files:
|
||||
raise DiffError(
|
||||
f"Delete File Error - file not found: {path}"
|
||||
) # Check against known files
|
||||
|
||||
patch.actions[path] = PatchAction(type=ActionType.DELETE, path=path)
|
||||
continue
|
||||
|
||||
# ---------- ADD ---------- #
|
||||
elif norm_line.startswith("*** Add File: "):
|
||||
path = norm_line[len("*** Add File: ") :].strip()
|
||||
index += 1
|
||||
if not path:
|
||||
raise DiffError("Add File action missing path.")
|
||||
if path in patch.actions:
|
||||
raise DiffError(f"Duplicate action for file: {path}")
|
||||
# Check if file exists in the context provided (should not for Add).
|
||||
# Note: We only have needed files, a full check requires FS access.
|
||||
# if path in current_files:
|
||||
# raise DiffError(f"Add File Error - file already exists: {path}")
|
||||
|
||||
action, index = self._parse_add_file_content(lines, index)
|
||||
action.path = path # Ensure path is set
|
||||
patch.actions[path] = action
|
||||
continue
|
||||
|
||||
# If we are here, the line is unexpected
|
||||
# Allow blank lines between actions
|
||||
if not norm_line.strip():
|
||||
index += 1
|
||||
continue
|
||||
|
||||
raise DiffError(f"Unknown or misplaced line while parsing patch: {line}")
|
||||
|
||||
# Check if we consumed the whole input or stopped early
|
||||
# Tolerate missing "*** End Patch" if we processed actions
|
||||
# if index < len(lines) and _norm(lines[index-1]) != "*** End Patch":
|
||||
# raise DiffError("Patch parsing finished unexpectedly before end of input.")
|
||||
|
||||
patch.fuzz = fuzz_accumulator
|
||||
return patch
|
||||
|
||||
def _parse_update_file_sections(
|
||||
self, lines: List[str], index: int, file_content: str
|
||||
) -> Tuple[PatchAction, int, int]:
|
||||
"""Parses all sections (@@, context, -, +) for a single Update File action."""
|
||||
action = PatchAction(type=ActionType.UPDATE, path="") # Path set by caller
|
||||
orig_lines = file_content.splitlines() # Use splitlines for consistency
|
||||
current_file_index = 0 # Track position in original file content
|
||||
total_fuzz = 0
|
||||
|
||||
while index < len(lines):
|
||||
norm_line = _norm(lines[index])
|
||||
# Check for terminators for *this* file update
|
||||
if norm_line.startswith(
|
||||
(
|
||||
"*** End Patch",
|
||||
"*** Update File:",
|
||||
"*** Delete File:",
|
||||
"*** Add File:",
|
||||
)
|
||||
):
|
||||
break # End of this file's update section
|
||||
|
||||
# Handle @@ scope lines (optional)
|
||||
scope_lines = []
|
||||
while index < len(lines) and _norm(lines[index]).startswith("@@"):
|
||||
scope_line_content = lines[index][len("@@") :].strip()
|
||||
if scope_line_content: # Ignore empty @@ lines?
|
||||
scope_lines.append(scope_line_content)
|
||||
index += 1
|
||||
|
||||
# Find the scope in the original file if specified
|
||||
if scope_lines:
|
||||
# Simple scope finding: search from current position
|
||||
# A more robust finder could handle nested scopes like the reference @@ @@
|
||||
found_scope = False
|
||||
temp_index = current_file_index
|
||||
while temp_index < len(orig_lines):
|
||||
# Check if all scope lines match sequentially from temp_index
|
||||
match = True
|
||||
for i, scope in enumerate(scope_lines):
|
||||
if (
|
||||
temp_index + i >= len(orig_lines)
|
||||
or _norm(orig_lines[temp_index + i]).strip() != scope
|
||||
):
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
current_file_index = temp_index + len(scope_lines)
|
||||
found_scope = True
|
||||
break
|
||||
temp_index += 1
|
||||
|
||||
if not found_scope:
|
||||
# Try fuzzy scope matching (strip whitespace)
|
||||
temp_index = current_file_index
|
||||
while temp_index < len(orig_lines):
|
||||
match = True
|
||||
for i, scope in enumerate(scope_lines):
|
||||
if (
|
||||
temp_index + i >= len(orig_lines)
|
||||
or _norm(orig_lines[temp_index + i]).strip() != scope.strip()
|
||||
):
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
current_file_index = temp_index + len(scope_lines)
|
||||
found_scope = True
|
||||
total_fuzz += 1 # Add fuzz for scope match difference
|
||||
break
|
||||
temp_index += 1
|
||||
|
||||
if not found_scope:
|
||||
scope_txt = "\n".join(scope_lines)
|
||||
raise DiffError(f"Could not find scope context:\n{scope_txt}")
|
||||
|
||||
# Peek and parse the next context/change section
|
||||
context_block, chunks_in_section, next_index, is_eof = peek_next_section(lines, index)
|
||||
|
||||
# Find where this context block appears in the original file
|
||||
found_index, fuzz = find_context(orig_lines, context_block, current_file_index, is_eof)
|
||||
total_fuzz += fuzz
|
||||
|
||||
if found_index == -1:
|
||||
ctx_txt = "\n".join(context_block)
|
||||
marker = "*** End of File" if is_eof else ""
|
||||
raise DiffError(
|
||||
f"Could not find patch context {marker} starting near line"
|
||||
f" {current_file_index}:\n{ctx_txt}"
|
||||
)
|
||||
|
||||
# Adjust chunk original indices to be absolute within the file
|
||||
for chunk in chunks_in_section:
|
||||
# chunk.orig_index from peek is relative to context_block start
|
||||
# We need it relative to the file start
|
||||
chunk.orig_index += found_index
|
||||
action.chunks.append(chunk)
|
||||
|
||||
# Advance file index past the matched context block
|
||||
current_file_index = found_index + len(context_block)
|
||||
# Advance line index past the processed section in the patch
|
||||
index = next_index
|
||||
|
||||
return action, index, total_fuzz
|
||||
|
||||
def _parse_add_file_content(self, lines: List[str], index: int) -> Tuple[PatchAction, int]:
|
||||
"""Parses the content (+) lines for an Add File action."""
|
||||
added_lines: List[str] = []
|
||||
while index < len(lines):
|
||||
line = lines[index]
|
||||
norm_line = _norm(line)
|
||||
# Stop if we hit another action or end marker
|
||||
if norm_line.startswith(
|
||||
(
|
||||
"*** End Patch",
|
||||
"*** Update File:",
|
||||
"*** Delete File:",
|
||||
"*** Add File:",
|
||||
)
|
||||
):
|
||||
break
|
||||
|
||||
# Expect lines to start with '+'
|
||||
if not line.startswith("+"):
|
||||
# Tolerate blank lines? Or require '+'? Reference implies '+' required.
|
||||
if norm_line.strip() == "":
|
||||
# Treat blank line as adding a blank line
|
||||
added_lines.append("")
|
||||
else:
|
||||
raise DiffError(f"Invalid Add File line (missing '+'): {line}")
|
||||
else:
|
||||
added_lines.append(line[1:]) # Strip leading '+'
|
||||
|
||||
index += 1
|
||||
|
||||
action = PatchAction(type=ActionType.ADD, path="", new_content="\n".join(added_lines))
|
||||
return action, index
|
||||
|
||||
def apply_edits(self, edits: List[PatchAction]):
|
||||
"""
|
||||
Applies the parsed PatchActions to the corresponding files.
|
||||
"""
|
||||
if not edits:
|
||||
return
|
||||
|
||||
# Group edits by original path? Not strictly needed if processed sequentially.
|
||||
|
||||
# Edits are now List[Tuple[str, PatchAction]]
|
||||
for _path_tuple_element, action in edits:
|
||||
# action is the PatchAction object
|
||||
# action.path is the canonical path within the action logic
|
||||
full_path = self.abs_root_path(action.path)
|
||||
path_obj = pathlib.Path(full_path)
|
||||
|
||||
try:
|
||||
if action.type == ActionType.ADD:
|
||||
# Check existence *before* writing
|
||||
if path_obj.exists():
|
||||
raise DiffError(f"ADD Error: File already exists: {action.path}")
|
||||
if action.new_content is None:
|
||||
# Parser should ensure this doesn't happen
|
||||
raise DiffError(f"ADD change for {action.path} has no content")
|
||||
|
||||
self.io.tool_output(f"Adding {action.path}")
|
||||
path_obj.parent.mkdir(parents=True, exist_ok=True)
|
||||
# Ensure single trailing newline, matching reference behavior
|
||||
content_to_write = action.new_content
|
||||
if not content_to_write.endswith("\n"):
|
||||
content_to_write += "\n"
|
||||
self.io.write_text(full_path, content_to_write)
|
||||
|
||||
elif action.type == ActionType.DELETE:
|
||||
self.io.tool_output(f"Deleting {action.path}")
|
||||
if not path_obj.exists():
|
||||
self.io.tool_warning(
|
||||
f"DELETE Warning: File not found, skipping: {action.path}"
|
||||
)
|
||||
else:
|
||||
path_obj.unlink()
|
||||
|
||||
elif action.type == ActionType.UPDATE:
|
||||
if not path_obj.exists():
|
||||
raise DiffError(f"UPDATE Error: File does not exist: {action.path}")
|
||||
|
||||
current_content = self.io.read_text(full_path)
|
||||
if current_content is None:
|
||||
# Should have been caught during parsing if file was needed
|
||||
raise DiffError(f"Could not read file for UPDATE: {action.path}")
|
||||
|
||||
# Apply the update logic using the parsed chunks
|
||||
new_content = self._apply_update(current_content, action, action.path)
|
||||
|
||||
target_full_path = (
|
||||
self.abs_root_path(action.move_path) if action.move_path else full_path
|
||||
)
|
||||
target_path_obj = pathlib.Path(target_full_path)
|
||||
|
||||
if action.move_path:
|
||||
self.io.tool_output(
|
||||
f"Updating and moving {action.path} to {action.move_path}"
|
||||
)
|
||||
# Check if target exists before overwriting/moving
|
||||
if target_path_obj.exists() and full_path != target_full_path:
|
||||
self.io.tool_warning(
|
||||
"UPDATE Warning: Target file for move already exists, overwriting:"
|
||||
f" {action.move_path}"
|
||||
)
|
||||
else:
|
||||
self.io.tool_output(f"Updating {action.path}")
|
||||
|
||||
# Ensure parent directory exists for target
|
||||
target_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.io.write_text(target_full_path, new_content)
|
||||
|
||||
# Remove original file *after* successful write to new location if moved
|
||||
if action.move_path and full_path != target_full_path:
|
||||
path_obj.unlink()
|
||||
|
||||
else:
|
||||
# Should not happen
|
||||
raise DiffError(f"Unknown action type encountered: {action.type}")
|
||||
|
||||
except (DiffError, FileNotFoundError, IOError, OSError) as e:
|
||||
# Raise a ValueError to signal failure, consistent with other coders.
|
||||
raise ValueError(f"Error applying action '{action.type}' to {action.path}: {e}")
|
||||
except Exception as e:
|
||||
# Catch unexpected errors during application
|
||||
raise ValueError(
|
||||
f"Unexpected error applying action '{action.type}' to {action.path}: {e}"
|
||||
)
|
||||
|
||||
def _apply_update(self, text: str, action: PatchAction, path: str) -> str:
|
||||
"""
|
||||
Applies UPDATE chunks to the given text content.
|
||||
Adapted from _get_updated_file in apply_patch.py.
|
||||
"""
|
||||
if action.type is not ActionType.UPDATE:
|
||||
# Should not be called otherwise, but check for safety
|
||||
raise DiffError("_apply_update called with non-update action")
|
||||
|
||||
orig_lines = text.splitlines() # Use splitlines to handle endings consistently
|
||||
dest_lines: List[str] = []
|
||||
current_orig_line_idx = 0 # Tracks index in orig_lines processed so far
|
||||
|
||||
# Sort chunks by their original index to apply them sequentially
|
||||
sorted_chunks = sorted(action.chunks, key=lambda c: c.orig_index)
|
||||
|
||||
for chunk in sorted_chunks:
|
||||
# chunk.orig_index is the absolute line number where the change starts
|
||||
# (where the first deleted line was, or where inserted lines go if no deletes)
|
||||
chunk_start_index = chunk.orig_index
|
||||
|
||||
if chunk_start_index < current_orig_line_idx:
|
||||
# This indicates overlapping chunks or incorrect indices from parsing
|
||||
raise DiffError(
|
||||
f"{path}: Overlapping or out-of-order chunk detected."
|
||||
f" Current index {current_orig_line_idx}, chunk starts at {chunk_start_index}."
|
||||
)
|
||||
|
||||
# Add lines from original file between the last chunk and this one
|
||||
dest_lines.extend(orig_lines[current_orig_line_idx:chunk_start_index])
|
||||
|
||||
# Verify that the lines to be deleted actually match the original file content
|
||||
# (The parser should have used find_context, but double-check here)
|
||||
num_del = len(chunk.del_lines)
|
||||
actual_deleted_lines = orig_lines[chunk_start_index : chunk_start_index + num_del]
|
||||
|
||||
# Use the same normalization as find_context_core for comparison robustness
|
||||
norm_chunk_del = [_norm(s).strip() for s in chunk.del_lines]
|
||||
norm_actual_del = [_norm(s).strip() for s in actual_deleted_lines]
|
||||
|
||||
if norm_chunk_del != norm_actual_del:
|
||||
# This indicates the context matching failed or the file changed since parsing
|
||||
# Provide detailed error message
|
||||
expected_str = "\n".join(f"- {s}" for s in chunk.del_lines)
|
||||
actual_str = "\n".join(f" {s}" for s in actual_deleted_lines)
|
||||
raise DiffError(
|
||||
f"{path}: Mismatch applying patch near line {chunk_start_index + 1}.\n"
|
||||
f"Expected lines to remove:\n{expected_str}\n"
|
||||
f"Found lines in file:\n{actual_str}"
|
||||
)
|
||||
|
||||
# Add the inserted lines from the chunk
|
||||
dest_lines.extend(chunk.ins_lines)
|
||||
|
||||
# Advance the original line index past the lines processed (deleted lines)
|
||||
current_orig_line_idx = chunk_start_index + num_del
|
||||
|
||||
# Add any remaining lines from the original file after the last chunk
|
||||
dest_lines.extend(orig_lines[current_orig_line_idx:])
|
||||
|
||||
# Join lines and ensure a single trailing newline
|
||||
result = "\n".join(dest_lines)
|
||||
if result or orig_lines: # Add newline unless result is empty and original was empty
|
||||
result += "\n"
|
||||
return result
|
||||
161
aider/coders/patch_prompts.py
Normal file
161
aider/coders/patch_prompts.py
Normal file
@@ -0,0 +1,161 @@
|
||||
# flake8: noqa: E501
|
||||
|
||||
from .base_prompts import CoderPrompts
|
||||
from .editblock_prompts import EditBlockPrompts
|
||||
|
||||
|
||||
class PatchPrompts(EditBlockPrompts):
|
||||
# --------------------------------------------------------------------- #
|
||||
# SYSTEM PROMPT
|
||||
# --------------------------------------------------------------------- #
|
||||
main_system = """Act as an expert software developer.
|
||||
Always use best practices when coding.
|
||||
Respect and use existing conventions, libraries, etc that are already present in the code base.
|
||||
{lazy_prompt}
|
||||
Take requests for changes to the supplied code.
|
||||
If the request is ambiguous, ask questions.
|
||||
|
||||
Always reply to the user in {language}.
|
||||
|
||||
Once you understand the request you MUST:
|
||||
|
||||
1. Decide if you need to propose edits to any files that haven't been added to the chat. You can create new files without asking!
|
||||
|
||||
• If you need to propose edits to existing files not already added to the chat, you *MUST* tell the user their full path names and ask them to *add the files to the chat*.
|
||||
• End your reply and wait for their approval.
|
||||
• You can keep asking if you then decide you need to edit more files.
|
||||
|
||||
2. Think step‑by‑step and explain the needed changes in a few short sentences.
|
||||
|
||||
3. Describe the changes using the V4A diff format, enclosed within `*** Begin Patch` and `*** End Patch` markers.
|
||||
|
||||
IMPORTANT: Each file MUST appear only once in the patch.
|
||||
Consolidate **all** edits for a given file into a single `*** [ACTION] File:` block.
|
||||
{shell_cmd_prompt}
|
||||
"""
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# EXAMPLE MESSAGES
|
||||
# --------------------------------------------------------------------- #
|
||||
example_messages = [
|
||||
# --- Example 1 ---------------------------------------------------- #
|
||||
dict(
|
||||
role="user",
|
||||
content="Change get_factorial() to use math.factorial",
|
||||
),
|
||||
dict(
|
||||
role="assistant",
|
||||
content="""To make this change we need to modify `mathweb/flask/app.py`:
|
||||
|
||||
1. Import the `math` module.
|
||||
2. Replace the custom recursive `factorial()` helper with `math.factorial`.
|
||||
3. Update `get_factorial()` to call `math.factorial`.
|
||||
|
||||
Here is the patch:
|
||||
|
||||
*** Begin Patch
|
||||
*** Update File: mathweb/flask/app.py
|
||||
@@
|
||||
-from flask import Flask
|
||||
-
|
||||
-from flask import Flask
|
||||
+from flask import Flask
|
||||
+import math
|
||||
@@
|
||||
-def factorial(n):
|
||||
- "compute factorial"
|
||||
-
|
||||
- if n == 0:
|
||||
- return 1
|
||||
- else:
|
||||
- return n * factorial(n-1)
|
||||
+def factorial(n):
|
||||
+ "compute factorial"
|
||||
+
|
||||
+ return math.factorial(n)
|
||||
@@ def get_factorial(self, n):
|
||||
- return str(factorial(n))
|
||||
+ return str(math.factorial(n))
|
||||
*** End Patch
|
||||
""",
|
||||
),
|
||||
# --- Example 2 ---------------------------------------------------- #
|
||||
dict(
|
||||
role="user",
|
||||
content="Refactor hello() into its own file.",
|
||||
),
|
||||
dict(
|
||||
role="assistant",
|
||||
content="""To make this change we need to modify `main.py` and create a new file `hello.py`:
|
||||
|
||||
1. Create `hello.py` with the `hello()` function.
|
||||
2. Remove `hello()` from `main.py` and import it instead.
|
||||
|
||||
Here is the patch:
|
||||
|
||||
*** Begin Patch
|
||||
*** Add File: hello.py
|
||||
+# A simple function
|
||||
+def hello():
|
||||
+ "print a greeting"
|
||||
+
|
||||
+ print("hello")
|
||||
*** Update File: main.py
|
||||
@@
|
||||
-def hello():
|
||||
- "print a greeting"
|
||||
-
|
||||
- print("hello")
|
||||
+from hello import hello
|
||||
*** End Patch
|
||||
""",
|
||||
),
|
||||
]
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# SYSTEM REMINDER
|
||||
# --------------------------------------------------------------------- #
|
||||
system_reminder = """# V4A Diff Format Rules:
|
||||
|
||||
Your entire response containing the patch MUST start with `*** Begin Patch` on a line by itself.
|
||||
Your entire response containing the patch MUST end with `*** End Patch` on a line by itself.
|
||||
|
||||
Use the *FULL* file path, as shown to you by the user.
|
||||
{quad_backtick_reminder}
|
||||
|
||||
For each file you need to modify, start with a marker line:
|
||||
|
||||
*** [ACTION] File: [path/to/file]
|
||||
|
||||
Where `[ACTION]` is one of `Add`, `Update`, or `Delete`.
|
||||
|
||||
⇨ **Each file MUST appear only once in the patch.**
|
||||
Consolidate all changes for that file into the same block.
|
||||
If you are moving code within a file, include both the deletions and the
|
||||
insertions as separate hunks inside this single `*** Update File:` block
|
||||
(do *not* open a second block for the same file).
|
||||
|
||||
For `Update` actions, describe each snippet of code that needs to be changed using the following format:
|
||||
1. Context lines: Include 3 lines of context *before* the change. These lines MUST start with a single space ` `.
|
||||
2. Lines to remove: Precede each line to be removed with a minus sign `-`.
|
||||
3. Lines to add: Precede each line to be added with a plus sign `+`.
|
||||
4. Context lines: Include 3 lines of context *after* the change. These lines MUST start with a single space ` `.
|
||||
|
||||
Context lines MUST exactly match the existing file content, character for character, including indentation.
|
||||
If a change is near the beginning or end of the file, include fewer than 3 context lines as appropriate.
|
||||
If 3 lines of context is insufficient to uniquely identify the snippet, use `@@ [CLASS_OR_FUNCTION_NAME]` markers on their own lines *before* the context lines to specify the scope. You can use multiple `@@` markers if needed.
|
||||
Do not include line numbers.
|
||||
|
||||
Only create patches for files that the user has added to the chat!
|
||||
|
||||
When moving code *within* a single file, keep everything inside one
|
||||
`*** Update File:` block. Provide one hunk that deletes the code from its
|
||||
original location and another hunk that inserts it at the new location.
|
||||
|
||||
For `Add` actions, use the `*** Add File: [path/to/new/file]` marker, followed by the lines of the new file, each preceded by a plus sign `+`.
|
||||
|
||||
For `Delete` actions, use the `*** Delete File: [path/to/file]` marker. No other lines are needed for the deletion.
|
||||
|
||||
{rename_with_shell}{go_ahead_tip}{lazy_prompt}ONLY EVER RETURN CODE IN THE SPECIFIED V4A DIFF FORMAT!
|
||||
{shell_cmd_reminder}
|
||||
"""
|
||||
@@ -235,20 +235,6 @@ Left
|
||||
Left
|
||||
"""
|
||||
|
||||
"""
|
||||
ri = RelativeIndenter([example])
|
||||
dump(example)
|
||||
|
||||
rel_example = ri.make_relative(example)
|
||||
dump(repr(rel_example))
|
||||
|
||||
abs_example = ri.make_absolute(rel_example)
|
||||
dump(abs_example)
|
||||
|
||||
|
||||
sys.exit()
|
||||
"""
|
||||
|
||||
|
||||
def relative_indent(texts):
|
||||
ri = RelativeIndenter(texts)
|
||||
@@ -349,7 +335,7 @@ def lines_to_chars(lines, mapping):
|
||||
return new_text
|
||||
|
||||
|
||||
def dmp_lines_apply(texts, remap=True):
|
||||
def dmp_lines_apply(texts):
|
||||
debug = False
|
||||
# debug = True
|
||||
|
||||
@@ -655,8 +641,6 @@ def proc(dname):
|
||||
(dmp_lines_apply, all_preprocs),
|
||||
]
|
||||
|
||||
_strategies = editblock_strategies # noqa: F841
|
||||
|
||||
short_names = dict(
|
||||
search_and_replace="sr",
|
||||
git_cherry_pick_osr_onto_o="cp_o",
|
||||
|
||||
@@ -45,6 +45,7 @@ other_hunks_applied = (
|
||||
|
||||
class UnifiedDiffCoder(Coder):
|
||||
"""A coder that uses unified diff format for code modifications."""
|
||||
|
||||
edit_format = "udiff"
|
||||
gpt_prompts = UnifiedDiffPrompts()
|
||||
|
||||
@@ -344,7 +345,16 @@ def process_fenced_block(lines, start_line_num):
|
||||
|
||||
if block[0].startswith("--- ") and block[1].startswith("+++ "):
|
||||
# Extract the file path, considering that it might contain spaces
|
||||
fname = block[1][4:].strip()
|
||||
a_fname = block[0][4:].strip()
|
||||
b_fname = block[1][4:].strip()
|
||||
|
||||
# Check if standard git diff prefixes are present and strip them
|
||||
if a_fname.startswith("a/") and b_fname.startswith("b/"):
|
||||
fname = b_fname[2:]
|
||||
else:
|
||||
# Otherwise, assume the path is as intended
|
||||
fname = b_fname
|
||||
|
||||
block = block[2:]
|
||||
else:
|
||||
fname = None
|
||||
|
||||
@@ -93,7 +93,17 @@ class Commands:
|
||||
weak_model=self.coder.main_model.weak_model.name,
|
||||
)
|
||||
models.sanity_check_models(self.io, model)
|
||||
raise SwitchCoder(main_model=model)
|
||||
|
||||
# Check if the current edit format is the default for the old model
|
||||
old_model_edit_format = self.coder.main_model.edit_format
|
||||
current_edit_format = self.coder.edit_format
|
||||
|
||||
new_edit_format = current_edit_format
|
||||
if current_edit_format == old_model_edit_format:
|
||||
# If the user was using the old model's default, switch to the new model's default
|
||||
new_edit_format = model.edit_format
|
||||
|
||||
raise SwitchCoder(main_model=model, edit_format=new_edit_format)
|
||||
|
||||
def cmd_editor_model(self, args):
|
||||
"Switch the Editor Model to a new LLM"
|
||||
@@ -1004,9 +1014,15 @@ class Commands:
|
||||
dict(role="assistant", content="Ok."),
|
||||
]
|
||||
|
||||
if add and exit_status != 0:
|
||||
if add_on_nonzero_exit and exit_status != 0:
|
||||
# Return the formatted output message for test failures
|
||||
return msg
|
||||
elif add and exit_status != 0:
|
||||
self.io.placeholder = "What's wrong? Fix"
|
||||
|
||||
# Return None if output wasn't added or command succeeded
|
||||
return None
|
||||
|
||||
def cmd_exit(self, args):
|
||||
"Exit the application"
|
||||
self.coder.event("exit", reason="/exit")
|
||||
@@ -1496,11 +1512,11 @@ class Commands:
|
||||
|
||||
if not args.strip():
|
||||
# Display current value if no args are provided
|
||||
formatted_budget = model.get_thinking_tokens(model)
|
||||
formatted_budget = model.get_thinking_tokens()
|
||||
if formatted_budget is None:
|
||||
self.io.tool_output("Thinking tokens are not currently set.")
|
||||
else:
|
||||
budget = model.extra_params["thinking"].get("budget_tokens")
|
||||
budget = model.get_raw_thinking_tokens()
|
||||
self.io.tool_output(
|
||||
f"Current thinking token budget: {budget:,} tokens ({formatted_budget})."
|
||||
)
|
||||
@@ -1509,8 +1525,8 @@ class Commands:
|
||||
value = args.strip()
|
||||
model.set_thinking_tokens(value)
|
||||
|
||||
formatted_budget = model.get_thinking_tokens(model)
|
||||
budget = model.extra_params["thinking"].get("budget_tokens")
|
||||
formatted_budget = model.get_thinking_tokens()
|
||||
budget = model.get_raw_thinking_tokens()
|
||||
|
||||
self.io.tool_output(f"Set thinking token budget to {budget:,} tokens ({formatted_budget}).")
|
||||
self.io.tool_output()
|
||||
@@ -1525,7 +1541,7 @@ class Commands:
|
||||
|
||||
if not args.strip():
|
||||
# Display current value if no args are provided
|
||||
reasoning_value = model.get_reasoning_effort(model)
|
||||
reasoning_value = model.get_reasoning_effort()
|
||||
if reasoning_value is None:
|
||||
self.io.tool_output("Reasoning effort is not currently set.")
|
||||
else:
|
||||
@@ -1534,7 +1550,7 @@ class Commands:
|
||||
|
||||
value = args.strip()
|
||||
model.set_reasoning_effort(value)
|
||||
reasoning_value = model.get_reasoning_effort(model)
|
||||
reasoning_value = model.get_reasoning_effort()
|
||||
self.io.tool_output(f"Set reasoning effort to {reasoning_value}")
|
||||
self.io.tool_output()
|
||||
|
||||
|
||||
@@ -85,6 +85,23 @@ class LiteLLMExceptions:
|
||||
return ExInfo("APIConnectionError", False, "You need to: pip install boto3")
|
||||
if "OpenrouterException" in str(ex) and "'choices'" in str(ex):
|
||||
return ExInfo(
|
||||
"APIConnectionError", True, "The OpenRouter API provider is down or overloaded."
|
||||
"APIConnectionError",
|
||||
True,
|
||||
(
|
||||
"OpenRouter or the upstream API provider is down, overloaded or rate"
|
||||
" limiting your requests."
|
||||
),
|
||||
)
|
||||
|
||||
# Check for specific non-retryable APIError cases like insufficient credits
|
||||
if ex.__class__ is litellm.APIError:
|
||||
err_str = str(ex).lower()
|
||||
if "insufficient credits" in err_str and '"code":402' in err_str:
|
||||
return ExInfo(
|
||||
"APIError",
|
||||
False,
|
||||
"Insufficient credits with the API provider. Please add credits.",
|
||||
)
|
||||
# Fall through to default APIError handling if not the specific credits error
|
||||
|
||||
return self.exceptions.get(ex.__class__, ExInfo(None, None, None))
|
||||
|
||||
53
aider/io.py
53
aider/io.py
@@ -26,6 +26,7 @@ from prompt_toolkit.shortcuts import CompleteStyle, PromptSession
|
||||
from prompt_toolkit.styles import Style
|
||||
from pygments.lexers import MarkdownLexer, guess_lexer_for_filename
|
||||
from pygments.token import Token
|
||||
from rich.color import ColorParseError
|
||||
from rich.columns import Columns
|
||||
from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
@@ -35,6 +36,7 @@ from rich.text import Text
|
||||
from aider.mdstream import MarkdownStream
|
||||
|
||||
from .dump import dump # noqa: F401
|
||||
from .editor import pipe_editor
|
||||
from .utils import is_image_file
|
||||
|
||||
# Constants
|
||||
@@ -360,6 +362,35 @@ class InputOutput:
|
||||
self.file_watcher = file_watcher
|
||||
self.root = root
|
||||
|
||||
# Validate color settings after console is initialized
|
||||
self._validate_color_settings()
|
||||
|
||||
def _validate_color_settings(self):
|
||||
"""Validate configured color strings and reset invalid ones."""
|
||||
color_attributes = [
|
||||
"user_input_color",
|
||||
"tool_output_color",
|
||||
"tool_error_color",
|
||||
"tool_warning_color",
|
||||
"assistant_output_color",
|
||||
"completion_menu_color",
|
||||
"completion_menu_bg_color",
|
||||
"completion_menu_current_color",
|
||||
"completion_menu_current_bg_color",
|
||||
]
|
||||
for attr_name in color_attributes:
|
||||
color_value = getattr(self, attr_name, None)
|
||||
if color_value:
|
||||
try:
|
||||
# Try creating a style to validate the color
|
||||
RichStyle(color=color_value)
|
||||
except ColorParseError as e:
|
||||
self.console.print(
|
||||
"[bold red]Warning:[/bold red] Invalid configuration for"
|
||||
f" {attr_name}: '{color_value}'. {e}. Disabling this color."
|
||||
)
|
||||
setattr(self, attr_name, None) # Reset invalid color to None
|
||||
|
||||
def _get_style(self):
|
||||
style_dict = {}
|
||||
if not self.pretty:
|
||||
@@ -385,9 +416,9 @@ class InputOutput:
|
||||
# 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}")
|
||||
completion_menu_current_style.append(self.completion_menu_current_bg_color)
|
||||
if self.completion_menu_current_color:
|
||||
completion_menu_current_style.append(self.completion_menu_current_color)
|
||||
completion_menu_current_style.append(f"bg:{self.completion_menu_current_color}")
|
||||
if completion_menu_current_style:
|
||||
style_dict["completion-menu.completion.current"] = " ".join(
|
||||
completion_menu_current_style
|
||||
@@ -557,6 +588,21 @@ class InputOutput:
|
||||
"Navigate forward through history"
|
||||
event.current_buffer.history_forward()
|
||||
|
||||
@kb.add("c-x", "c-e")
|
||||
def _(event):
|
||||
"Edit current input in external editor (like Bash)"
|
||||
buffer = event.current_buffer
|
||||
current_text = buffer.text
|
||||
|
||||
# Open the editor with the current text
|
||||
edited_text = pipe_editor(input_data=current_text)
|
||||
|
||||
# Replace the buffer with the edited text, strip any trailing newlines
|
||||
buffer.text = edited_text.rstrip("\n")
|
||||
|
||||
# Move cursor to the end of the text
|
||||
buffer.cursor_position = len(buffer.text)
|
||||
|
||||
@kb.add("enter", eager=True, filter=~is_searching)
|
||||
def _(event):
|
||||
"Handle Enter key press"
|
||||
@@ -917,6 +963,7 @@ class InputOutput:
|
||||
|
||||
if not isinstance(message, Text):
|
||||
message = Text(message)
|
||||
color = ensure_hash_prefix(color) if color else None
|
||||
style = dict(style=color) if self.pretty and color else dict()
|
||||
try:
|
||||
self.console.print(message, **style)
|
||||
@@ -947,7 +994,7 @@ class InputOutput:
|
||||
style = dict()
|
||||
if self.pretty:
|
||||
if self.tool_output_color:
|
||||
style["color"] = self.tool_output_color
|
||||
style["color"] = ensure_hash_prefix(self.tool_output_color)
|
||||
style["reverse"] = bold
|
||||
|
||||
style = RichStyle(**style)
|
||||
|
||||
@@ -4,6 +4,7 @@ import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
import shlex
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
@@ -44,7 +45,7 @@ class Linter:
|
||||
return fname
|
||||
|
||||
def run_cmd(self, cmd, rel_fname, code):
|
||||
cmd += " " + rel_fname
|
||||
cmd += " " + shlex.quote(rel_fname)
|
||||
|
||||
returncode = 0
|
||||
stdout = ""
|
||||
|
||||
@@ -30,6 +30,7 @@ from aider.history import ChatSummary
|
||||
from aider.io import InputOutput
|
||||
from aider.llm import litellm # noqa: F401; properly init litellm on launch
|
||||
from aider.models import ModelSettings
|
||||
from aider.onboarding import offer_openrouter_oauth, select_default_model
|
||||
from aider.repo import ANY_GIT_ERROR, GitRepo
|
||||
from aider.report import report_uncaught_exceptions
|
||||
from aider.versioncheck import check_version, install_from_main_branch, install_upgrade
|
||||
@@ -357,11 +358,21 @@ def register_models(git_root, model_settings_fname, io, verbose=False):
|
||||
|
||||
|
||||
def load_dotenv_files(git_root, dotenv_fname, encoding="utf-8"):
|
||||
# Standard .env file search path
|
||||
dotenv_files = generate_search_path_list(
|
||||
".env",
|
||||
git_root,
|
||||
dotenv_fname,
|
||||
)
|
||||
|
||||
# Explicitly add the OAuth keys file to the beginning of the list
|
||||
oauth_keys_file = Path.home() / ".aider" / "oauth-keys.env"
|
||||
if oauth_keys_file.exists():
|
||||
# Insert at the beginning so it's loaded first (and potentially overridden)
|
||||
dotenv_files.insert(0, str(oauth_keys_file.resolve()))
|
||||
# Remove duplicates if it somehow got included by generate_search_path_list
|
||||
dotenv_files = list(dict.fromkeys(dotenv_files))
|
||||
|
||||
loaded = []
|
||||
for fname in dotenv_files:
|
||||
try:
|
||||
@@ -751,26 +762,49 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||
alias, model = parts
|
||||
models.MODEL_ALIASES[alias.strip()] = model.strip()
|
||||
|
||||
if not args.model:
|
||||
# Select model based on available API keys
|
||||
model_key_pairs = [
|
||||
("ANTHROPIC_API_KEY", "sonnet"),
|
||||
("DEEPSEEK_API_KEY", "deepseek"),
|
||||
("OPENROUTER_API_KEY", "openrouter/anthropic/claude-3.7-sonnet"),
|
||||
("OPENAI_API_KEY", "gpt-4o"),
|
||||
("GEMINI_API_KEY", "flash"),
|
||||
]
|
||||
selected_model_name = select_default_model(args, io, analytics)
|
||||
if not selected_model_name:
|
||||
# Error message and analytics event are handled within select_default_model
|
||||
# It might have already offered OAuth if no model/keys were found.
|
||||
# If it failed here, we exit.
|
||||
return 1
|
||||
args.model = selected_model_name # Update args with the selected model
|
||||
|
||||
for env_key, model_name in model_key_pairs:
|
||||
if os.environ.get(env_key):
|
||||
args.model = model_name
|
||||
io.tool_warning(
|
||||
f"Found {env_key} so using {model_name} since no --model was specified."
|
||||
# Check if an OpenRouter model was selected/specified but the key is missing
|
||||
if args.model.startswith("openrouter/") and not os.environ.get("OPENROUTER_API_KEY"):
|
||||
io.tool_warning(
|
||||
f"The specified model '{args.model}' requires an OpenRouter API key, which was not"
|
||||
" found."
|
||||
)
|
||||
# Attempt OAuth flow because the specific model needs it
|
||||
if offer_openrouter_oauth(io, analytics):
|
||||
# OAuth succeeded, the key should now be in os.environ.
|
||||
# Check if the key is now present after the flow.
|
||||
if os.environ.get("OPENROUTER_API_KEY"):
|
||||
io.tool_output(
|
||||
"OpenRouter successfully connected."
|
||||
) # Inform user connection worked
|
||||
else:
|
||||
# This case should ideally not happen if offer_openrouter_oauth succeeded
|
||||
# but check defensively.
|
||||
io.tool_error(
|
||||
"OpenRouter authentication seemed successful, but the key is still missing."
|
||||
)
|
||||
break
|
||||
if not args.model:
|
||||
io.tool_error("You need to specify a --model and an --api-key to use.")
|
||||
io.offer_url(urls.models_and_keys, "Open documentation url for more info?")
|
||||
analytics.event(
|
||||
"exit",
|
||||
reason="OpenRouter key missing after successful OAuth for specified model",
|
||||
)
|
||||
return 1
|
||||
else:
|
||||
# OAuth failed or was declined by the user
|
||||
io.tool_error(
|
||||
f"Unable to proceed without an OpenRouter API key for model '{args.model}'."
|
||||
)
|
||||
io.offer_url(urls.models_and_keys, "Open documentation URL for more info?")
|
||||
analytics.event(
|
||||
"exit",
|
||||
reason="OpenRouter key missing for specified model and OAuth failed/declined",
|
||||
)
|
||||
return 1
|
||||
|
||||
main_model = models.Model(
|
||||
@@ -823,7 +857,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||
)
|
||||
|
||||
if args.copy_paste and args.edit_format is None:
|
||||
if main_model.edit_format in ("diff", "whole"):
|
||||
if main_model.edit_format in ("diff", "whole", "diff-fenced"):
|
||||
main_model.edit_format = "editor-" + main_model.edit_format
|
||||
|
||||
if args.verbose:
|
||||
@@ -918,6 +952,9 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||
else:
|
||||
map_tokens = args.map_tokens
|
||||
|
||||
# Track auto-commits configuration
|
||||
analytics.event("auto_commits", enabled=bool(args.auto_commits))
|
||||
|
||||
try:
|
||||
coder = Coder.create(
|
||||
main_model=main_model,
|
||||
@@ -1064,6 +1101,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"Git working dir: {git_root}")
|
||||
|
||||
if args.stream and args.cache_prompts:
|
||||
io.tool_warning("Cost estimates may be inaccurate when using streaming and caching.")
|
||||
|
||||
if args.load:
|
||||
commands.cmd_load(args.load)
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import io
|
||||
import time
|
||||
|
||||
from rich import box
|
||||
from rich.console import Console
|
||||
from rich.live import Live
|
||||
from rich.markdown import CodeBlock, Markdown
|
||||
from rich.markdown import CodeBlock, Heading, Markdown
|
||||
from rich.panel import Panel
|
||||
from rich.syntax import Syntax
|
||||
from rich.text import Text
|
||||
|
||||
@@ -56,13 +58,34 @@ class NoInsetCodeBlock(CodeBlock):
|
||||
yield syntax
|
||||
|
||||
|
||||
class LeftHeading(Heading):
|
||||
"""A heading class that renders left-justified."""
|
||||
|
||||
def __rich_console__(self, console, options):
|
||||
text = self.text
|
||||
text.justify = "left" # Override justification
|
||||
if self.tag == "h1":
|
||||
# Draw a border around h1s, but keep text left-aligned
|
||||
yield Panel(
|
||||
text,
|
||||
box=box.HEAVY,
|
||||
style="markdown.h1.border",
|
||||
)
|
||||
else:
|
||||
# Styled text for h2 and beyond
|
||||
if self.tag == "h2":
|
||||
yield Text("") # Keep the blank line before h2
|
||||
yield text
|
||||
|
||||
|
||||
class NoInsetMarkdown(Markdown):
|
||||
"""Markdown with code blocks that have no padding."""
|
||||
"""Markdown with code blocks that have no padding and left-justified headings."""
|
||||
|
||||
elements = {
|
||||
**Markdown.elements,
|
||||
"fence": NoInsetCodeBlock,
|
||||
"code_block": NoInsetCodeBlock,
|
||||
"heading_open": LeftHeading,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -88,10 +88,14 @@ MODEL_ALIASES = {
|
||||
"3": "gpt-3.5-turbo",
|
||||
# Other models
|
||||
"deepseek": "deepseek/deepseek-chat",
|
||||
"r1": "deepseek/deepseek-reasoner",
|
||||
"flash": "gemini/gemini-2.0-flash-exp",
|
||||
"quasar": "openrouter/openrouter/quasar-alpha",
|
||||
"r1": "deepseek/deepseek-reasoner",
|
||||
"gemini-2.5-pro": "gemini/gemini-2.5-pro-exp-03-25",
|
||||
"gemini": "gemini/gemini-2.5-pro-exp-03-25",
|
||||
"gemini": "gemini/gemini-2.5-pro-preview-03-25",
|
||||
"gemini-exp": "gemini/gemini-2.5-pro-exp-03-25",
|
||||
"grok3": "xai/grok-3-beta",
|
||||
"optimus": "openrouter/openrouter/optimus-alpha",
|
||||
}
|
||||
# Model metadata loaded from resources and user's files.
|
||||
|
||||
@@ -310,7 +314,11 @@ class Model(ModelSettings):
|
||||
self.apply_generic_model_settings(model)
|
||||
|
||||
# Apply override settings last if they exist
|
||||
if self.extra_model_settings and self.extra_model_settings.extra_params:
|
||||
if (
|
||||
self.extra_model_settings
|
||||
and self.extra_model_settings.extra_params
|
||||
and self.extra_model_settings.name == "aider/extra_params"
|
||||
):
|
||||
# Initialize extra_params if it doesn't exist
|
||||
if not self.extra_params:
|
||||
self.extra_params = {}
|
||||
@@ -330,10 +338,25 @@ class Model(ModelSettings):
|
||||
self.use_repo_map = True
|
||||
self.use_temperature = False
|
||||
self.system_prompt_prefix = "Formatting re-enabled. "
|
||||
self.system_prompt_prefix = "Formatting re-enabled. "
|
||||
if "reasoning_effort" not in self.accepts_settings:
|
||||
self.accepts_settings.append("reasoning_effort")
|
||||
return # <--
|
||||
|
||||
if "gpt-4.1-mini" in model:
|
||||
self.edit_format = "diff"
|
||||
self.use_repo_map = True
|
||||
self.reminder = "sys"
|
||||
self.examples_as_sys_msg = False
|
||||
return # <--
|
||||
|
||||
if "gpt-4.1" in model:
|
||||
self.edit_format = "diff"
|
||||
self.use_repo_map = True
|
||||
self.reminder = "sys"
|
||||
self.examples_as_sys_msg = False
|
||||
return # <--
|
||||
|
||||
if "/o1-mini" in model:
|
||||
self.use_repo_map = True
|
||||
self.use_temperature = False
|
||||
@@ -484,6 +507,8 @@ class Model(ModelSettings):
|
||||
|
||||
if not self.editor_edit_format:
|
||||
self.editor_edit_format = self.editor_model.edit_format
|
||||
if self.editor_edit_format in ("diff", "whole", "diff-fenced"):
|
||||
self.editor_edit_format = "editor-" + self.editor_edit_format
|
||||
|
||||
return self.editor_model
|
||||
|
||||
@@ -688,23 +713,24 @@ class Model(ModelSettings):
|
||||
else:
|
||||
self.extra_params["thinking"] = {"type": "enabled", "budget_tokens": num_tokens}
|
||||
|
||||
def get_thinking_tokens(self, model):
|
||||
def get_raw_thinking_tokens(self):
|
||||
"""Get formatted thinking token budget if available"""
|
||||
budget = None
|
||||
|
||||
if model.extra_params:
|
||||
if self.extra_params:
|
||||
# Check for OpenRouter reasoning format
|
||||
if (
|
||||
"reasoning" in model.extra_params
|
||||
and "max_tokens" in model.extra_params["reasoning"]
|
||||
):
|
||||
budget = model.extra_params["reasoning"]["max_tokens"]
|
||||
if "reasoning" in self.extra_params and "max_tokens" in self.extra_params["reasoning"]:
|
||||
budget = self.extra_params["reasoning"]["max_tokens"]
|
||||
# Check for standard thinking format
|
||||
elif (
|
||||
"thinking" in model.extra_params
|
||||
and "budget_tokens" in model.extra_params["thinking"]
|
||||
"thinking" in self.extra_params and "budget_tokens" in self.extra_params["thinking"]
|
||||
):
|
||||
budget = model.extra_params["thinking"]["budget_tokens"]
|
||||
budget = self.extra_params["thinking"]["budget_tokens"]
|
||||
|
||||
return budget
|
||||
|
||||
def get_thinking_tokens(self):
|
||||
budget = self.get_raw_thinking_tokens()
|
||||
|
||||
if budget is not None:
|
||||
# Format as xx.yK for thousands, xx.yM for millions
|
||||
@@ -722,14 +748,14 @@ class Model(ModelSettings):
|
||||
return f"{value:.1f}k"
|
||||
return None
|
||||
|
||||
def get_reasoning_effort(self, model):
|
||||
def get_reasoning_effort(self):
|
||||
"""Get reasoning effort value if available"""
|
||||
if (
|
||||
model.extra_params
|
||||
and "extra_body" in model.extra_params
|
||||
and "reasoning_effort" in model.extra_params["extra_body"]
|
||||
self.extra_params
|
||||
and "extra_body" in self.extra_params
|
||||
and "reasoning_effort" in self.extra_params["extra_body"]
|
||||
):
|
||||
return model.extra_params["extra_body"]["reasoning_effort"]
|
||||
return self.extra_params["extra_body"]["reasoning_effort"]
|
||||
return None
|
||||
|
||||
def is_deepseek_r1(self):
|
||||
|
||||
428
aider/onboarding.py
Normal file
428
aider/onboarding.py
Normal file
@@ -0,0 +1,428 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import http.server
|
||||
import os
|
||||
import secrets
|
||||
import socketserver
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import requests
|
||||
|
||||
from aider import urls
|
||||
from aider.io import InputOutput
|
||||
|
||||
|
||||
def check_openrouter_tier(api_key):
|
||||
"""
|
||||
Checks if the user is on a free tier for OpenRouter.
|
||||
|
||||
Args:
|
||||
api_key: The OpenRouter API key to check.
|
||||
|
||||
Returns:
|
||||
A boolean indicating if the user is on a free tier (True) or paid tier (False).
|
||||
Returns True if the check fails.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
"https://openrouter.ai/api/v1/auth/key",
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
timeout=5, # Add a reasonable timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
# According to the documentation, 'is_free_tier' will be true if the user has never paid
|
||||
return data.get("data", {}).get("is_free_tier", True) # Default to True if not found
|
||||
except Exception:
|
||||
# If there's any error, we'll default to assuming free tier
|
||||
return True
|
||||
|
||||
|
||||
def try_to_select_default_model():
|
||||
"""
|
||||
Attempts to select a default model based on available API keys.
|
||||
Checks OpenRouter tier status to select appropriate model.
|
||||
|
||||
Returns:
|
||||
The name of the selected model, or None if no suitable default is found.
|
||||
"""
|
||||
# Special handling for OpenRouter
|
||||
openrouter_key = os.environ.get("OPENROUTER_API_KEY")
|
||||
if openrouter_key:
|
||||
# Check if the user is on a free tier
|
||||
is_free_tier = check_openrouter_tier(openrouter_key)
|
||||
if is_free_tier:
|
||||
return "openrouter/google/gemini-2.5-pro-exp-03-25:free"
|
||||
else:
|
||||
return "openrouter/anthropic/claude-3.7-sonnet"
|
||||
|
||||
# Select model based on other available API keys
|
||||
model_key_pairs = [
|
||||
("ANTHROPIC_API_KEY", "sonnet"),
|
||||
("DEEPSEEK_API_KEY", "deepseek"),
|
||||
("OPENAI_API_KEY", "gpt-4o"),
|
||||
("GEMINI_API_KEY", "gemini/gemini-2.5-pro-exp-03-25"),
|
||||
("VERTEXAI_PROJECT", "vertex_ai/gemini-2.5-pro-exp-03-25"),
|
||||
]
|
||||
|
||||
for env_key, model_name in model_key_pairs:
|
||||
api_key_value = os.environ.get(env_key)
|
||||
if api_key_value:
|
||||
return model_name
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def offer_openrouter_oauth(io, analytics):
|
||||
"""
|
||||
Offers OpenRouter OAuth flow to the user if no API keys are found.
|
||||
|
||||
Args:
|
||||
io: The InputOutput object for user interaction.
|
||||
analytics: The Analytics object for tracking events.
|
||||
|
||||
Returns:
|
||||
True if authentication was successful, False otherwise.
|
||||
"""
|
||||
# No API keys found - Offer OpenRouter OAuth
|
||||
io.tool_output("OpenRouter provides free and paid access to many LLMs.")
|
||||
# Use confirm_ask which handles non-interactive cases
|
||||
if io.confirm_ask(
|
||||
"Login to OpenRouter or create a free account?",
|
||||
default="y",
|
||||
):
|
||||
analytics.event("oauth_flow_initiated", provider="openrouter")
|
||||
openrouter_key = start_openrouter_oauth_flow(io, analytics)
|
||||
if openrouter_key:
|
||||
# Successfully got key via OAuth, use the default OpenRouter model
|
||||
# Ensure OPENROUTER_API_KEY is now set in the environment for later use
|
||||
os.environ["OPENROUTER_API_KEY"] = openrouter_key
|
||||
# Track OAuth success leading to model selection
|
||||
analytics.event("oauth_flow_success")
|
||||
return True
|
||||
|
||||
# OAuth failed or was cancelled by user implicitly (e.g., closing browser)
|
||||
# Error messages are handled within start_openrouter_oauth_flow
|
||||
analytics.event("oauth_flow_failure")
|
||||
io.tool_error("OpenRouter authentication did not complete successfully.")
|
||||
# Fall through to the final error message
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def select_default_model(args, io, analytics):
|
||||
"""
|
||||
Selects a default model based on available API keys if no model is specified.
|
||||
Offers OAuth flow for OpenRouter if no keys are found.
|
||||
|
||||
Args:
|
||||
args: The command line arguments object.
|
||||
io: The InputOutput object for user interaction.
|
||||
analytics: The Analytics object for tracking events.
|
||||
|
||||
Returns:
|
||||
The name of the selected model, or None if no suitable default is found.
|
||||
"""
|
||||
if args.model:
|
||||
return args.model # Model already specified
|
||||
|
||||
model = try_to_select_default_model()
|
||||
if model:
|
||||
io.tool_warning(f"Using {model} model with API key from environment.")
|
||||
analytics.event("auto_model_selection", model=model)
|
||||
return model
|
||||
|
||||
no_model_msg = "No LLM model was specified and no API keys were provided."
|
||||
io.tool_warning(no_model_msg)
|
||||
|
||||
# Try OAuth if no model was detected
|
||||
offer_openrouter_oauth(io, analytics)
|
||||
|
||||
# Check again after potential OAuth success
|
||||
model = try_to_select_default_model()
|
||||
if model:
|
||||
return model
|
||||
|
||||
io.offer_url(urls.models_and_keys, "Open documentation URL for more info?")
|
||||
|
||||
|
||||
# Helper function to find an available port
|
||||
def find_available_port(start_port=8484, end_port=8584):
|
||||
for port in range(start_port, end_port + 1):
|
||||
try:
|
||||
# Check if the port is available by trying to bind to it
|
||||
with socketserver.TCPServer(("localhost", port), None):
|
||||
return port
|
||||
except OSError:
|
||||
# Port is likely already in use
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
# PKCE code generation
|
||||
def generate_pkce_codes():
|
||||
code_verifier = secrets.token_urlsafe(64)
|
||||
hasher = hashlib.sha256()
|
||||
hasher.update(code_verifier.encode("utf-8"))
|
||||
code_challenge = base64.urlsafe_b64encode(hasher.digest()).rstrip(b"=").decode("utf-8")
|
||||
return code_verifier, code_challenge
|
||||
|
||||
|
||||
# Function to exchange the authorization code for an API key
|
||||
def exchange_code_for_key(code, code_verifier, io):
|
||||
try:
|
||||
response = requests.post(
|
||||
"https://openrouter.ai/api/v1/auth/keys",
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={
|
||||
"code": code,
|
||||
"code_verifier": code_verifier,
|
||||
"code_challenge_method": "S256",
|
||||
},
|
||||
timeout=30, # Add a timeout
|
||||
)
|
||||
response.raise_for_status() # Raise exception for bad status codes (4xx or 5xx)
|
||||
data = response.json()
|
||||
api_key = data.get("key")
|
||||
if not api_key:
|
||||
io.tool_error("Error: 'key' not found in OpenRouter response.")
|
||||
io.tool_error(f"Response: {response.text}")
|
||||
return None
|
||||
return api_key
|
||||
except requests.exceptions.Timeout:
|
||||
io.tool_error("Error: Request to OpenRouter timed out during code exchange.")
|
||||
return None
|
||||
except requests.exceptions.HTTPError as e:
|
||||
io.tool_error(
|
||||
"Error exchanging code for OpenRouter key:"
|
||||
f" {e.response.status_code} {e.response.reason}"
|
||||
)
|
||||
io.tool_error(f"Response: {e.response.text}")
|
||||
return None
|
||||
except requests.exceptions.RequestException as e:
|
||||
io.tool_error(f"Error exchanging code for OpenRouter key: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
io.tool_error(f"Unexpected error during code exchange: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# Function to start the OAuth flow
|
||||
def start_openrouter_oauth_flow(io, analytics):
|
||||
"""Initiates the OpenRouter OAuth PKCE flow using a local server."""
|
||||
|
||||
port = find_available_port()
|
||||
if not port:
|
||||
io.tool_error("Could not find an available port between 8484 and 8584.")
|
||||
io.tool_error("Please ensure a port in this range is free, or configure manually.")
|
||||
return None
|
||||
|
||||
callback_url = f"http://localhost:{port}/callback/aider"
|
||||
auth_code = None
|
||||
server_error = None
|
||||
server_started = threading.Event()
|
||||
shutdown_server = threading.Event()
|
||||
|
||||
class OAuthCallbackHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
nonlocal auth_code, server_error
|
||||
parsed_path = urlparse(self.path)
|
||||
if parsed_path.path == "/callback/aider":
|
||||
query_params = parse_qs(parsed_path.query)
|
||||
if "code" in query_params:
|
||||
auth_code = query_params["code"][0]
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
self.wfile.write(
|
||||
b"<html><body><h1>Success!</h1>"
|
||||
b"<p>Aider has received the authentication code. "
|
||||
b"You can close this browser tab.</p></body></html>"
|
||||
)
|
||||
# Signal the main thread to shut down the server
|
||||
# Signal the main thread to shut down the server
|
||||
shutdown_server.set()
|
||||
else:
|
||||
# Redirect to aider website if 'code' is missing (e.g., user visited manually)
|
||||
self.send_response(302) # Found (temporary redirect)
|
||||
self.send_header("Location", urls.website)
|
||||
self.end_headers()
|
||||
# No need to set server_error, just redirect.
|
||||
# Do NOT shut down the server here; wait for timeout or success.
|
||||
else:
|
||||
# Redirect anything else (e.g., favicon.ico) to the main website as well
|
||||
self.send_response(302)
|
||||
self.send_header("Location", urls.website)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Not Found")
|
||||
|
||||
def log_message(self, format, *args):
|
||||
# Suppress server logging to keep terminal clean
|
||||
pass
|
||||
|
||||
def run_server():
|
||||
nonlocal server_error
|
||||
try:
|
||||
with socketserver.TCPServer(("localhost", port), OAuthCallbackHandler) as httpd:
|
||||
io.tool_output(f"Temporary server listening on {callback_url}", log_only=True)
|
||||
server_started.set() # Signal that the server is ready
|
||||
# Wait until shutdown is requested or timeout occurs (handled by main thread)
|
||||
while not shutdown_server.is_set():
|
||||
httpd.handle_request() # Handle one request at a time
|
||||
# Add a small sleep to prevent busy-waiting if needed,
|
||||
# though handle_request should block appropriately.
|
||||
time.sleep(0.1)
|
||||
io.tool_output("Shutting down temporary server.", log_only=True)
|
||||
except Exception as e:
|
||||
server_error = f"Failed to start or run temporary server: {e}"
|
||||
server_started.set() # Signal even if failed, error will be checked
|
||||
shutdown_server.set() # Ensure shutdown logic proceeds
|
||||
|
||||
server_thread = threading.Thread(target=run_server, daemon=True)
|
||||
server_thread.start()
|
||||
|
||||
# Wait briefly for the server to start, or for an error
|
||||
if not server_started.wait(timeout=5):
|
||||
io.tool_error("Temporary authentication server failed to start in time.")
|
||||
shutdown_server.set() # Ensure thread exits if it eventually starts
|
||||
server_thread.join(timeout=1)
|
||||
return None
|
||||
|
||||
# Check if server failed during startup
|
||||
if server_error:
|
||||
io.tool_error(server_error)
|
||||
shutdown_server.set() # Ensure thread exits
|
||||
server_thread.join(timeout=1)
|
||||
return None
|
||||
|
||||
# Generate codes and URL
|
||||
code_verifier, code_challenge = generate_pkce_codes()
|
||||
auth_url_base = "https://openrouter.ai/auth"
|
||||
auth_params = {
|
||||
"callback_url": callback_url,
|
||||
"code_challenge": code_challenge,
|
||||
"code_challenge_method": "S256",
|
||||
}
|
||||
auth_url = f"{auth_url_base}?{'&'.join(f'{k}={v}' for k, v in auth_params.items())}"
|
||||
|
||||
io.tool_output("\nPlease open this URL in your browser to connect Aider with OpenRouter:")
|
||||
io.tool_output()
|
||||
print(auth_url)
|
||||
|
||||
MINUTES = 5
|
||||
io.tool_output(f"\nWaiting up to {MINUTES} minutes for you to finish in the browser...")
|
||||
io.tool_output("Use Control-C to interrupt.")
|
||||
|
||||
try:
|
||||
webbrowser.open(auth_url)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Wait for the callback to set the auth_code or for timeout/error
|
||||
interrupted = False
|
||||
try:
|
||||
shutdown_server.wait(timeout=MINUTES * 60) # Convert minutes to seconds
|
||||
except KeyboardInterrupt:
|
||||
io.tool_warning("\nOAuth flow interrupted.")
|
||||
analytics.event("oauth_flow_failed", provider="openrouter", reason="user_interrupt")
|
||||
interrupted = True
|
||||
# Ensure the server thread is signaled to shut down
|
||||
shutdown_server.set()
|
||||
|
||||
# Join the server thread to ensure it's cleaned up
|
||||
server_thread.join(timeout=1)
|
||||
|
||||
if interrupted:
|
||||
return None # Return None if interrupted by user
|
||||
|
||||
if server_error:
|
||||
io.tool_error(f"Authentication failed: {server_error}")
|
||||
analytics.event("oauth_flow_failed", provider="openrouter", reason=server_error)
|
||||
return None
|
||||
|
||||
if not auth_code:
|
||||
io.tool_error("Authentication with OpenRouter failed.")
|
||||
analytics.event("oauth_flow_failed", provider="openrouter")
|
||||
return None
|
||||
|
||||
io.tool_output("Completing authentication...")
|
||||
analytics.event("oauth_flow_code_received", provider="openrouter")
|
||||
|
||||
# Exchange code for key
|
||||
api_key = exchange_code_for_key(auth_code, code_verifier, io)
|
||||
|
||||
if api_key:
|
||||
# Set env var for the current session immediately
|
||||
os.environ["OPENROUTER_API_KEY"] = api_key
|
||||
|
||||
# Save the key to the oauth-keys.env file
|
||||
try:
|
||||
config_dir = os.path.expanduser("~/.aider")
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
key_file = os.path.join(config_dir, "oauth-keys.env")
|
||||
with open(key_file, "a", encoding="utf-8") as f:
|
||||
f.write(f'OPENROUTER_API_KEY="{api_key}"\n')
|
||||
|
||||
io.tool_warning("Aider will load the OpenRouter key automatically in future sessions.")
|
||||
io.tool_output()
|
||||
|
||||
analytics.event("oauth_flow_success", provider="openrouter")
|
||||
return api_key
|
||||
except Exception as e:
|
||||
io.tool_error(f"Successfully obtained key, but failed to save it to file: {e}")
|
||||
io.tool_warning("Set OPENROUTER_API_KEY environment variable for this session only.")
|
||||
# Still return the key for the current session even if saving failed
|
||||
analytics.event("oauth_flow_save_failed", provider="openrouter", reason=str(e))
|
||||
return api_key
|
||||
else:
|
||||
io.tool_error("Authentication with OpenRouter failed.")
|
||||
analytics.event("oauth_flow_failed", provider="openrouter", reason="code_exchange_failed")
|
||||
return None
|
||||
|
||||
|
||||
# Dummy Analytics class for testing
|
||||
class DummyAnalytics:
|
||||
def event(self, *args, **kwargs):
|
||||
# print(f"Analytics Event: {args} {kwargs}") # Optional: print events
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to test the OpenRouter OAuth flow."""
|
||||
print("Starting OpenRouter OAuth flow test...")
|
||||
|
||||
# Use a real IO object for interaction
|
||||
io = InputOutput(
|
||||
pretty=True,
|
||||
yes=False,
|
||||
input_history_file=None,
|
||||
chat_history_file=None,
|
||||
tool_output_color="BLUE",
|
||||
tool_error_color="RED",
|
||||
)
|
||||
# Use a dummy analytics object
|
||||
analytics = DummyAnalytics()
|
||||
|
||||
# Ensure OPENROUTER_API_KEY is not set, to trigger the flow naturally
|
||||
# (though start_openrouter_oauth_flow doesn't check this itself)
|
||||
if "OPENROUTER_API_KEY" in os.environ:
|
||||
print("Warning: OPENROUTER_API_KEY is already set in environment.")
|
||||
# del os.environ["OPENROUTER_API_KEY"] # Optionally unset it for testing
|
||||
|
||||
api_key = start_openrouter_oauth_flow(io, analytics)
|
||||
|
||||
if api_key:
|
||||
print("\nOAuth flow completed successfully!")
|
||||
print(f"Obtained API Key (first 5 chars): {api_key[:5]}...")
|
||||
# Be careful printing the key, even partially
|
||||
else:
|
||||
print("\nOAuth flow failed or was cancelled.")
|
||||
|
||||
print("\nOpenRouter OAuth flow test finished.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -15,7 +15,7 @@ Use these for <type>: fix, feat, build, chore, ci, docs, style, refactor, perf,
|
||||
|
||||
Ensure the commit message:
|
||||
- Starts with the appropriate prefix.
|
||||
- Is in the imperative mood (e.g., \"Add feature\" not \"Added feature\" or \"Adding feature\").
|
||||
- Is in the imperative mood (e.g., \"add feature\" not \"added feature\" or \"adding feature\").
|
||||
- Does not exceed 72 characters.
|
||||
|
||||
Reply only with the one-line commit message, without any additional text, explanations, \
|
||||
|
||||
65
aider/queries/tree-sitter-languages/scala-tags.scm
Normal file
65
aider/queries/tree-sitter-languages/scala-tags.scm
Normal file
@@ -0,0 +1,65 @@
|
||||
; Definitions
|
||||
|
||||
(package_clause
|
||||
name: (package_identifier) @name.definition.module) @definition.module
|
||||
|
||||
(trait_definition
|
||||
name: (identifier) @name.definition.interface) @definition.interface
|
||||
|
||||
(enum_definition
|
||||
name: (identifier) @name.definition.enum) @definition.enum
|
||||
|
||||
(simple_enum_case
|
||||
name: (identifier) @name.definition.class) @definition.class
|
||||
|
||||
(full_enum_case
|
||||
name: (identifier) @name.definition.class) @definition.class
|
||||
|
||||
(class_definition
|
||||
name: (identifier) @name.definition.class) @definition.class
|
||||
|
||||
(object_definition
|
||||
name: (identifier) @name.definition.object) @definition.object
|
||||
|
||||
(function_definition
|
||||
name: (identifier) @name.definition.function) @definition.function
|
||||
|
||||
(val_definition
|
||||
pattern: (identifier) @name.definition.variable) @definition.variable
|
||||
|
||||
(given_definition
|
||||
name: (identifier) @name.definition.variable) @definition.variable
|
||||
|
||||
(var_definition
|
||||
pattern: (identifier) @name.definition.variable) @definition.variable
|
||||
|
||||
(val_declaration
|
||||
name: (identifier) @name.definition.variable) @definition.variable
|
||||
|
||||
(var_declaration
|
||||
name: (identifier) @name.definition.variable) @definition.variable
|
||||
|
||||
(type_definition
|
||||
name: (type_identifier) @name.definition.type) @definition.type
|
||||
|
||||
(class_parameter
|
||||
name: (identifier) @name.definition.property) @definition.property
|
||||
|
||||
; References
|
||||
|
||||
(call_expression
|
||||
(identifier) @name.reference.call) @reference.call
|
||||
|
||||
(instance_expression
|
||||
(type_identifier) @name.reference.interface) @reference.interface
|
||||
|
||||
(instance_expression
|
||||
(generic_type
|
||||
(type_identifier) @name.reference.interface)) @reference.interface
|
||||
|
||||
(extends_clause
|
||||
(type_identifier) @name.reference.class) @reference.class
|
||||
|
||||
(extends_clause
|
||||
(generic_type
|
||||
(type_identifier) @name.reference.class)) @reference.class
|
||||
@@ -9,6 +9,7 @@ try:
|
||||
git.exc.ODBError,
|
||||
git.exc.GitError,
|
||||
git.exc.InvalidGitRepositoryError,
|
||||
git.exc.GitCommandNotFound,
|
||||
]
|
||||
except ImportError:
|
||||
git = None
|
||||
@@ -293,13 +294,19 @@ class GitRepo:
|
||||
else:
|
||||
try:
|
||||
iterator = commit.tree.traverse()
|
||||
blob = None # Initialize blob
|
||||
while True:
|
||||
try:
|
||||
blob = next(iterator)
|
||||
if blob.type == "blob": # blob is a file
|
||||
files.add(blob.path)
|
||||
except IndexError:
|
||||
self.io.tool_warning(f"GitRepo: read error skipping {blob.path}")
|
||||
# Handle potential index error during tree traversal
|
||||
# without relying on potentially unassigned 'blob'
|
||||
self.io.tool_warning(
|
||||
"GitRepo: Index error encountered while reading git tree object."
|
||||
" Skipping."
|
||||
)
|
||||
continue
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
@@ -398,13 +398,30 @@ class RepoMap:
|
||||
|
||||
# dump(fname)
|
||||
rel_fname = self.get_rel_fname(fname)
|
||||
current_pers = 0.0 # Start with 0 personalization score
|
||||
|
||||
if fname in chat_fnames:
|
||||
personalization[rel_fname] = personalize
|
||||
current_pers += personalize
|
||||
chat_rel_fnames.add(rel_fname)
|
||||
|
||||
if rel_fname in mentioned_fnames:
|
||||
personalization[rel_fname] = personalize
|
||||
# Use max to avoid double counting if in chat_fnames and mentioned_fnames
|
||||
current_pers = max(current_pers, personalize)
|
||||
|
||||
# Check path components against mentioned_idents
|
||||
path_obj = Path(rel_fname)
|
||||
path_components = set(path_obj.parts)
|
||||
basename_with_ext = path_obj.name
|
||||
basename_without_ext, _ = os.path.splitext(basename_with_ext)
|
||||
components_to_check = path_components.union({basename_with_ext, basename_without_ext})
|
||||
|
||||
matched_idents = components_to_check.intersection(mentioned_idents)
|
||||
if matched_idents:
|
||||
# Add personalization *once* if any path component matches a mentioned ident
|
||||
current_pers += personalize
|
||||
|
||||
if current_pers > 0:
|
||||
personalization[rel_fname] = current_pers # Assign the final calculated value
|
||||
|
||||
tags = list(self.get_tags(fname, rel_fname))
|
||||
if tags is None:
|
||||
|
||||
@@ -79,6 +79,17 @@
|
||||
//"supports_tool_choice": true,
|
||||
"supports_prompt_caching": true
|
||||
},
|
||||
"openrouter/deepseek/deepseek-chat-v3-0324:free": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0,
|
||||
"output_cost_per_token": 0,
|
||||
"litellm_provider": "openrouter",
|
||||
"supports_prompt_caching": true,
|
||||
"mode": "chat",
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"fireworks_ai/accounts/fireworks/models/deepseek-r1": {
|
||||
"max_tokens": 160000,
|
||||
"max_input_tokens": 128000,
|
||||
@@ -97,6 +108,15 @@
|
||||
"output_cost_per_token": 0.0000009,
|
||||
"mode": "chat",
|
||||
},
|
||||
"fireworks_ai/accounts/fireworks/models/deepseek-v3-0324": {
|
||||
"max_tokens": 160000,
|
||||
"max_input_tokens": 100000,
|
||||
"max_output_tokens": 8192,
|
||||
"litellm_provider": "fireworks_ai",
|
||||
"input_cost_per_token": 0.0000009,
|
||||
"output_cost_per_token": 0.0000009,
|
||||
"mode": "chat",
|
||||
},
|
||||
"o3-mini": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
@@ -145,6 +165,26 @@
|
||||
"supports_system_messages": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"openrouter/openrouter/quasar-alpha": {
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 32000,
|
||||
"input_cost_per_token": 0.0,
|
||||
"output_cost_per_token": 0.0,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat",
|
||||
"supports_vision": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_prompt_caching": true
|
||||
},
|
||||
"openrouter/openrouter/optimus-alpha": {
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 32000,
|
||||
"input_cost_per_token": 0.0,
|
||||
"output_cost_per_token": 0.0,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat"
|
||||
},
|
||||
"openrouter/openai/gpt-4o-mini": {
|
||||
"max_tokens": 16384,
|
||||
"max_input_tokens": 128000,
|
||||
@@ -294,6 +334,42 @@
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
|
||||
},
|
||||
"gemini/gemini-2.5-pro-preview-03-25": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 1048576,
|
||||
"max_output_tokens": 64000,
|
||||
"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,
|
||||
"input_cost_per_image": 0,
|
||||
"input_cost_per_video_per_second": 0,
|
||||
"input_cost_per_audio_per_second": 0,
|
||||
"input_cost_per_token": 0.00000125,
|
||||
"input_cost_per_character": 0,
|
||||
"input_cost_per_token_above_128k_tokens": 0,
|
||||
"input_cost_per_character_above_128k_tokens": 0,
|
||||
"input_cost_per_image_above_128k_tokens": 0,
|
||||
"input_cost_per_video_per_second_above_128k_tokens": 0,
|
||||
"input_cost_per_audio_per_second_above_128k_tokens": 0,
|
||||
"output_cost_per_token": 0.000010,
|
||||
"output_cost_per_character": 0,
|
||||
"output_cost_per_token_above_128k_tokens": 0,
|
||||
"output_cost_per_character_above_128k_tokens": 0,
|
||||
"litellm_provider": "gemini",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"supports_audio_input": true,
|
||||
"supports_video_input": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
|
||||
},
|
||||
"vertex_ai/gemini-2.5-pro-exp-03-25": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 1048576,
|
||||
@@ -330,6 +406,78 @@
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
|
||||
},
|
||||
"vertex_ai/gemini-2.5-pro-preview-03-25": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 1048576,
|
||||
"max_output_tokens": 64000,
|
||||
"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,
|
||||
"input_cost_per_image": 0,
|
||||
"input_cost_per_video_per_second": 0,
|
||||
"input_cost_per_audio_per_second": 0,
|
||||
"input_cost_per_token": 0.00000125,
|
||||
"input_cost_per_character": 0,
|
||||
"input_cost_per_token_above_128k_tokens": 0,
|
||||
"input_cost_per_character_above_128k_tokens": 0,
|
||||
"input_cost_per_image_above_128k_tokens": 0,
|
||||
"input_cost_per_video_per_second_above_128k_tokens": 0,
|
||||
"input_cost_per_audio_per_second_above_128k_tokens": 0,
|
||||
"output_cost_per_token": 0.000010,
|
||||
"output_cost_per_character": 0,
|
||||
"output_cost_per_token_above_128k_tokens": 0,
|
||||
"output_cost_per_character_above_128k_tokens": 0,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"supports_audio_input": true,
|
||||
"supports_video_input": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
|
||||
},
|
||||
"openrouter/google/gemini-2.5-pro-preview-03-25": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 1048576,
|
||||
"max_output_tokens": 64000,
|
||||
"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,
|
||||
"input_cost_per_image": 0,
|
||||
"input_cost_per_video_per_second": 0,
|
||||
"input_cost_per_audio_per_second": 0,
|
||||
"input_cost_per_token": 0.00000125,
|
||||
"input_cost_per_character": 0,
|
||||
"input_cost_per_token_above_128k_tokens": 0,
|
||||
"input_cost_per_character_above_128k_tokens": 0,
|
||||
"input_cost_per_image_above_128k_tokens": 0,
|
||||
"input_cost_per_video_per_second_above_128k_tokens": 0,
|
||||
"input_cost_per_audio_per_second_above_128k_tokens": 0,
|
||||
"output_cost_per_token": 0.000010,
|
||||
"output_cost_per_character": 0,
|
||||
"output_cost_per_token_above_128k_tokens": 0,
|
||||
"output_cost_per_character_above_128k_tokens": 0,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"supports_audio_input": true,
|
||||
"supports_video_input": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
|
||||
},
|
||||
"openrouter/google/gemini-2.5-pro-exp-03-25:free": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 1048576,
|
||||
@@ -344,9 +492,9 @@
|
||||
"input_cost_per_video_per_second": 0,
|
||||
"input_cost_per_audio_per_second": 0,
|
||||
"input_cost_per_token": 0,
|
||||
"input_cost_per_character": 0,
|
||||
"input_cost_per_token_above_128k_tokens": 0,
|
||||
"input_cost_per_character_above_128k_tokens": 0,
|
||||
"input_cost_per_character": 0,
|
||||
"input_cost_per_token_above_128k_tokens": 0,
|
||||
"input_cost_per_character_above_128k_tokens": 0,
|
||||
"input_cost_per_image_above_128k_tokens": 0,
|
||||
"input_cost_per_video_per_second_above_128k_tokens": 0,
|
||||
"input_cost_per_audio_per_second_above_128k_tokens": 0,
|
||||
@@ -366,4 +514,95 @@
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
|
||||
},
|
||||
"openrouter/x-ai/grok-3-beta": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.000003,
|
||||
"output_cost_per_token": 0.000015,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat"
|
||||
},
|
||||
"xai/grok-3-beta": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.000003,
|
||||
"output_cost_per_token": 0.000015,
|
||||
"litellm_provider": "xai",
|
||||
"mode": "chat"
|
||||
},
|
||||
"openrouter/x-ai/grok-3-mini-beta": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.0000003,
|
||||
"output_cost_per_token": 0.0000005,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat"
|
||||
},
|
||||
"xai/grok-3-mini-beta": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.0000003,
|
||||
"output_cost_per_token": 0.0000005,
|
||||
"litellm_provider": "xai",
|
||||
"mode": "chat"
|
||||
},
|
||||
"openrouter/x-ai/grok-3-fast-beta": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.000005,
|
||||
"output_cost_per_token": 0.000025,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat"
|
||||
},
|
||||
"xai/grok-3-fast-beta": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.000005,
|
||||
"output_cost_per_token": 0.000025,
|
||||
"litellm_provider": "xai",
|
||||
"mode": "chat"
|
||||
},
|
||||
"openrouter/x-ai/grok-3-mini-fast-beta": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.0000006,
|
||||
"output_cost_per_token": 0.000004,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat"
|
||||
},
|
||||
"xai/grok-3-mini-fast-beta": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.0000006,
|
||||
"output_cost_per_token": 0.000004,
|
||||
"litellm_provider": "xai",
|
||||
"mode": "chat"
|
||||
},
|
||||
"openrouter/google/gemini-2.0-flash-exp:free": {
|
||||
"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": "openrouter",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_audio_output": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
}
|
||||
|
||||
@@ -583,6 +583,16 @@
|
||||
extra_params:
|
||||
max_tokens: 8192
|
||||
caches_by_default: true
|
||||
|
||||
- name: openrouter/deepseek/deepseek-chat-v3-0324:free
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/deepseek/deepseek-chat-v3-0324:free
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
caches_by_default: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/deepseek/deepseek-chat-v3-0324:free
|
||||
editor_edit_format: editor-diff
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/deepseek/deepseek-r1:free
|
||||
editor_edit_format: editor-diff
|
||||
@@ -807,7 +817,7 @@
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/deepseek/deepseek-chat
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
|
||||
- name: fireworks_ai/accounts/fireworks/models/deepseek-r1
|
||||
edit_format: diff
|
||||
weak_model_name: fireworks_ai/accounts/fireworks/models/deepseek-v3
|
||||
@@ -828,6 +838,14 @@
|
||||
extra_params:
|
||||
max_tokens: 128000
|
||||
|
||||
- name: fireworks_ai/accounts/fireworks/models/deepseek-v3-0324
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: true
|
||||
extra_params:
|
||||
max_tokens: 160000
|
||||
|
||||
- name: openai/o3-mini
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4o-mini
|
||||
@@ -837,7 +855,7 @@
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
|
||||
|
||||
- name: o3-mini
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4o-mini
|
||||
@@ -887,7 +905,7 @@
|
||||
examples_as_sys_msg: true
|
||||
editor_model_name: gpt-4o
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
|
||||
- name: openai/gpt-4.5-preview
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4o-mini
|
||||
@@ -932,22 +950,420 @@
|
||||
|
||||
- name: gemini/gemma-3-27b-it
|
||||
use_system_prompt: false
|
||||
|
||||
|
||||
- name: openrouter/google/gemma-3-27b-it:free
|
||||
use_system_prompt: false
|
||||
|
||||
|
||||
- name: openrouter/google/gemma-3-27b-it
|
||||
use_system_prompt: false
|
||||
|
||||
|
||||
- name: gemini/gemini-2.5-pro-preview-03-25
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
weak_model_name: gemini/gemini-2.0-flash
|
||||
|
||||
- name: gemini/gemini-2.5-pro-exp-03-25
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
weak_model_name: gemini/gemini-2.0-flash
|
||||
|
||||
- name: openrouter/google/gemini-2.5-pro-exp-03-25:free
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
weak_model_name: openrouter/google/gemini-2.0-flash-exp:free
|
||||
|
||||
- name: vertex_ai/gemini-2.5-pro-exp-03-25
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
# Need metadata for this one...
|
||||
#weak_model_name: vertex_ai/gemini-2.0-flash
|
||||
|
||||
- name: vertex_ai/gemini-2.5-pro-preview-03-25
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
# Need metadata for this one...
|
||||
#weak_model_name: vertex_ai/gemini-2.0-flash
|
||||
|
||||
- name: openrouter/openrouter/quasar-alpha
|
||||
use_repo_map: true
|
||||
edit_format: diff
|
||||
examples_as_sys_msg: true
|
||||
|
||||
- name: openrouter/x-ai/grok-3-beta
|
||||
use_repo_map: true
|
||||
edit_format: diff
|
||||
|
||||
- name: xai/grok-3-beta
|
||||
use_repo_map: true
|
||||
edit_format: diff
|
||||
|
||||
- name: openrouter/x-ai/grok-3-mini-beta
|
||||
use_repo_map: true
|
||||
edit_format: whole
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: xai/grok-3-mini-beta
|
||||
use_repo_map: true
|
||||
edit_format: whole
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: low
|
||||
|
||||
- name: openrouter/x-ai/grok-3-fast-beta
|
||||
use_repo_map: true
|
||||
edit_format: diff
|
||||
|
||||
- name: xai/grok-3-fast-beta
|
||||
use_repo_map: true
|
||||
edit_format: diff
|
||||
|
||||
- name: openrouter/x-ai/grok-3-mini-fast-beta
|
||||
use_repo_map: true
|
||||
edit_format: whole
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: xai/grok-3-mini-fast-beta
|
||||
use_repo_map: true
|
||||
edit_format: whole
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/openrouter/optimus-alpha
|
||||
use_repo_map: true
|
||||
edit_format: diff
|
||||
examples_as_sys_msg: true
|
||||
|
||||
- name: gpt-4.1
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
reminder: sys # user: 52.x%/96.9%
|
||||
examples_as_sys_msg: false # true: 51.6% correct, 95.6% well formed; false: 52.4%/98.2%
|
||||
editor_model_name: gpt-4.1-mini
|
||||
|
||||
- name: openai/gpt-4.1
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: false
|
||||
editor_model_name: openai/gpt-4.1-mini
|
||||
|
||||
- name: azure/gpt-4.1
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: false
|
||||
editor_model_name: azure/gpt-4.1-mini
|
||||
|
||||
- name: openrouter/openai/gpt-4.1
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1-mini
|
||||
|
||||
- name: gpt-4.1-mini
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: false # false: 32.x%/92.4% (60+ malformed responses); true: 31.7/90.2/60+
|
||||
|
||||
- name: openai/gpt-4.1-mini
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: false
|
||||
|
||||
- name: azure/gpt-4.1-mini
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: false
|
||||
|
||||
- name: openrouter/openai/gpt-4.1-mini
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: false
|
||||
|
||||
- name: o3
|
||||
streaming: false
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
editor_model_name: gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openai/o3
|
||||
streaming: false
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openrouter/openai/o3
|
||||
streaming: false
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: azure/o3
|
||||
streaming: false
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
|
||||
- name: o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
use_temperature: false
|
||||
editor_model_name: gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: "Formatting re-enabled. "
|
||||
accepts_settings: ["reasoning_effort"]
|
||||
examples_as_sys_msg: true
|
||||
#extra_params:
|
||||
# extra_body:
|
||||
# reasoning_effort: high
|
||||
|
||||
|
||||
@@ -159,7 +159,8 @@ class Scraper:
|
||||
try:
|
||||
response = page.goto(url, wait_until="networkidle", timeout=5000)
|
||||
except PlaywrightTimeoutError:
|
||||
self.print_error(f"Timeout while loading {url}")
|
||||
print(f"Page didn't quiesce, scraping content anyway: {url}")
|
||||
response = None
|
||||
except PlaywrightError as e:
|
||||
self.print_error(f"Error navigating to {url}: {str(e)}")
|
||||
return None, None
|
||||
|
||||
@@ -308,7 +308,11 @@ def find_common_root(abs_fnames):
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return safe_abs_path(os.getcwd())
|
||||
try:
|
||||
return safe_abs_path(os.getcwd())
|
||||
except FileNotFoundError:
|
||||
# Fallback if cwd is deleted
|
||||
return "."
|
||||
|
||||
|
||||
def format_tokens(count):
|
||||
|
||||
@@ -24,7 +24,96 @@ cog.out(text)
|
||||
]]]-->
|
||||
|
||||
|
||||
### main branch
|
||||
### Aider v0.82.1
|
||||
- Added support for `o3` and `o4-mini` including provider-specific versions for OpenAI, OpenRouter, and Azure.
|
||||
- Added support for Azure specific `gpt-4.1` and `gpt-4.1-mini` models.
|
||||
- Disabled streaming for `o3` models since you need identity verification to stream.
|
||||
- Fixed handling of file paths in unified diffs, especially those generated by git.
|
||||
|
||||
### Aider v0.82.0
|
||||
|
||||
- Support for GPT 4.1, mini and nano.
|
||||
- Added new `patch` edit format for OpenAI's GPT-4.1 model.
|
||||
- Improved support for using architect mode with Gemini 2.5 Pro.
|
||||
- Added new `editor-diff`, `editor-whole`, and `editor-diff-fenced` edit formats.
|
||||
- Bugfix for automatically selecting the best edit format to use in architect mode.
|
||||
- Added support for `grok-3-fast-beta` and `grok-3-mini-fast-beta` models.
|
||||
- Aider wrote 92% of the code in this release.
|
||||
|
||||
### Aider v0.81.3
|
||||
|
||||
- Commit messages generated by aider are no longer forced to be entirely lowercase, by Peter Hadlaw.
|
||||
- Updated default settings for Grok models.
|
||||
|
||||
### Aider v0.81.2
|
||||
|
||||
- Add support for `xai/grok-3-beta`, `xai/grok-3-mini-beta`, `openrouter/x-ai/grok-3-beta`, `openrouter/x-ai/grok-3-mini-beta`, and `openrouter/openrouter/optimus-alpha` models.
|
||||
- Add alias "grok3" for `xai/grok-3-beta`.
|
||||
- Add alias "optimus" for `openrouter/openrouter/optimus-alpha`.
|
||||
- Fix URL extraction from error messages.
|
||||
- Allow adding files by full path even if a file with the same basename is already in the chat.
|
||||
- Fix quoting of values containing '#' in the sample `aider.conf.yml`.
|
||||
- Add support for Fireworks AI model 'deepseek-v3-0324', by Felix Lisczyk.
|
||||
- Commit messages generated by aider are now lowercase, by Anton Ödman.
|
||||
|
||||
### Aider v0.81.1
|
||||
|
||||
- Added support for the `gemini/gemini-2.5-pro-preview-03-25` model.
|
||||
- Updated the `gemini` alias to point to `gemini/gemini-2.5-pro-preview-03-25`.
|
||||
- Added the `gemini-exp` alias for `gemini/gemini-2.5-pro-exp-03-25`.
|
||||
|
||||
### Aider v0.81.0
|
||||
|
||||
- Added support for the `openrouter/openrouter/quasar-alpha` model.
|
||||
- Run with `aider --model quasar`
|
||||
- Offer OpenRouter OAuth authentication if an OpenRouter model is specified but the API key is missing.
|
||||
- Prevent retrying API calls when the provider reports insufficient credits.
|
||||
- Improve URL detection to exclude trailing double quotes.
|
||||
- Aider wrote 86% of the code in this release.
|
||||
|
||||
### Aider v0.80.4
|
||||
|
||||
- Bumped deps to pickup litellm change to properly display the root cause of OpenRouter "choices" errors.
|
||||
|
||||
### Aider v0.80.3
|
||||
|
||||
- Improve error message for OpenRouter API connection issues to mention potential rate limiting or upstream provider issues.
|
||||
- Configure weak models (`gemini/gemini-2.0-flash` and `openrouter/google/gemini-2.0-flash-exp:free`) for Gemini 2.5 Pro models.
|
||||
- Add model metadata for `openrouter/google/gemini-2.0-flash-exp:free`.
|
||||
|
||||
### Aider v0.80.2
|
||||
|
||||
- Bumped deps.
|
||||
|
||||
### Aider v0.80.1
|
||||
|
||||
- Updated deps for yanked fsspec and aiohttp packages #3699
|
||||
- Removed redundant dependency check during OpenRouter OAuth flow, by Claudia Pellegrino.
|
||||
|
||||
### Aider v0.80.0
|
||||
|
||||
- OpenRouter OAuth integration:
|
||||
- Offer to OAuth against OpenRouter if no model and keys are provided.
|
||||
- Select OpenRouter default model based on free/paid tier status if `OPENROUTER_API_KEY` is set and no model is specified.
|
||||
- Prioritize `gemini/gemini-2.5-pro-exp-03-25` if `GEMINI_API_KEY` is set, and `vertex_ai/gemini-2.5-pro-exp-03-25` if `VERTEXAI_PROJECT` is set, when no model is specified.
|
||||
- Validate user-configured color settings on startup and warn/disable invalid ones.
|
||||
- Warn at startup if `--stream` and `--cache-prompts` are used together, as cost estimates may be inaccurate.
|
||||
- Boost repomap ranking for files whose path components match identifiers mentioned in the chat.
|
||||
- Change web scraping timeout from an error to a warning, allowing scraping to continue with potentially incomplete content.
|
||||
- Left-align markdown headings in the terminal output, by Peter Schilling.
|
||||
- Update edit format to the new model's default when switching models with `/model`, if the user was using the old model's default format.
|
||||
- Add `Ctrl-X Ctrl-E` keybinding to edit the current input buffer in an external editor, by Matteo Landi.
|
||||
- Fix linting errors for filepaths containing shell metacharacters, by Mir Adnan ALI.
|
||||
- Add the `openrouter/deepseek-chat-v3-0324:free` model.
|
||||
- Add repomap support for the Scala language, by Vasil Markoukin.
|
||||
- Fixed bug in `/run` that was preventing auto-testing.
|
||||
- Fix bug preventing `UnboundLocalError` during git tree traversal.
|
||||
- Handle `GitCommandNotFound` error if git is not installed or not in PATH.
|
||||
- Handle `FileNotFoundError` if the current working directory is deleted while aider is running.
|
||||
- Fix completion menu current item color styling, by Andrey Ivanov.
|
||||
- Aider wrote 87% of the code in this release.
|
||||
|
||||
### Aider v0.79.2
|
||||
|
||||
- Added 'gemini' alias for gemini-2.5-pro model.
|
||||
- Updated Gemini 2.5 Pro max output tokens to 64k.
|
||||
|
||||
@@ -4319,3 +4319,184 @@
|
||||
Paul Gauthier (aider): 221
|
||||
start_tag: v0.78.0
|
||||
total_lines: 338
|
||||
- aider_percentage: 86.86
|
||||
aider_total: 1837
|
||||
end_date: '2025-03-31'
|
||||
end_tag: v0.80.0
|
||||
file_counts:
|
||||
aider/__init__.py:
|
||||
Paul Gauthier: 1
|
||||
aider/coders/base_coder.py:
|
||||
Paul Gauthier: 2
|
||||
aider/commands.py:
|
||||
Paul Gauthier: 4
|
||||
Paul Gauthier (aider): 20
|
||||
aider/exceptions.py:
|
||||
Paul Gauthier: 1
|
||||
Paul Gauthier (aider): 3
|
||||
aider/io.py:
|
||||
Andrey Ivanov: 2
|
||||
Matteo Landi (aider): 11
|
||||
Paul Gauthier (aider): 38
|
||||
aider/linter.py:
|
||||
Mir Adnan ALI: 2
|
||||
aider/main.py:
|
||||
Paul Gauthier: 1
|
||||
Paul Gauthier (aider): 21
|
||||
aider/mdstream.py:
|
||||
Peter Schilling (aider) (aider): 25
|
||||
aider/models.py:
|
||||
Paul Gauthier: 12
|
||||
Paul Gauthier (aider): 9
|
||||
aider/onboarding.py:
|
||||
Paul Gauthier: 44
|
||||
Paul Gauthier (aider): 389
|
||||
aider/queries/tree-sitter-languages/scala-tags.scm:
|
||||
Vasil Markoukin: 65
|
||||
aider/repo.py:
|
||||
Paul Gauthier: 1
|
||||
Paul Gauthier (aider): 7
|
||||
aider/repomap.py:
|
||||
Paul Gauthier (aider): 19
|
||||
aider/resources/model-settings.yml:
|
||||
Paul Gauthier (aider): 13
|
||||
aider/scrape.py:
|
||||
Paul Gauthier: 1
|
||||
Paul Gauthier (aider): 1
|
||||
aider/utils.py:
|
||||
Paul Gauthier (aider): 5
|
||||
aider/watch.py:
|
||||
Matteo Landi (aider): 2
|
||||
aider/website/_includes/leaderboard.js:
|
||||
Paul Gauthier: 1
|
||||
Paul Gauthier (aider): 2
|
||||
aider/website/docs/leaderboards/index.md:
|
||||
Paul Gauthier: 1
|
||||
aider/website/index.html:
|
||||
Paul Gauthier: 51
|
||||
Paul Gauthier (aider): 175
|
||||
scripts/30k-image.py:
|
||||
Paul Gauthier: 8
|
||||
Paul Gauthier (aider): 227
|
||||
scripts/homepage.py:
|
||||
Paul Gauthier (aider): 122
|
||||
tests/basic/test_commands.py:
|
||||
Paul Gauthier: 2
|
||||
Paul Gauthier (aider): 48
|
||||
tests/basic/test_exceptions.py:
|
||||
Paul Gauthier (aider): 17
|
||||
tests/basic/test_io.py:
|
||||
Paul Gauthier (aider): 28
|
||||
tests/basic/test_main.py:
|
||||
Paul Gauthier: 15
|
||||
Paul Gauthier (aider): 199
|
||||
tests/basic/test_onboarding.py:
|
||||
Paul Gauthier (aider): 439
|
||||
tests/basic/test_repomap.py:
|
||||
Vasil Markoukin: 3
|
||||
tests/basic/test_ssl_verification.py:
|
||||
Paul Gauthier (aider): 8
|
||||
tests/basic/test_watch.py:
|
||||
Matteo Landi (aider): 9
|
||||
tests/fixtures/languages/scala/test.scala:
|
||||
Vasil Markoukin: 61
|
||||
grand_total:
|
||||
Andrey Ivanov: 2
|
||||
Matteo Landi (aider): 22
|
||||
Mir Adnan ALI: 2
|
||||
Paul Gauthier: 145
|
||||
Paul Gauthier (aider): 1790
|
||||
Peter Schilling (aider) (aider): 25
|
||||
Vasil Markoukin: 129
|
||||
start_tag: v0.79.0
|
||||
total_lines: 2115
|
||||
- aider_percentage: 85.55
|
||||
aider_total: 225
|
||||
end_date: '2025-04-04'
|
||||
end_tag: v0.81.0
|
||||
file_counts:
|
||||
.github/workflows/check_pypi_version.yml:
|
||||
Paul Gauthier: 11
|
||||
Paul Gauthier (aider): 75
|
||||
.github/workflows/windows_check_pypi_version.yml:
|
||||
Paul Gauthier: 4
|
||||
Paul Gauthier (aider): 86
|
||||
aider/__init__.py:
|
||||
Paul Gauthier: 1
|
||||
aider/coders/base_coder.py:
|
||||
Paul Gauthier (aider): 4
|
||||
aider/exceptions.py:
|
||||
Paul Gauthier: 6
|
||||
Paul Gauthier (aider): 12
|
||||
aider/main.py:
|
||||
Paul Gauthier (aider): 40
|
||||
aider/models.py:
|
||||
Paul Gauthier (aider): 2
|
||||
aider/resources/model-settings.yml:
|
||||
Paul Gauthier: 9
|
||||
Paul Gauthier (aider): 1
|
||||
aider/website/_includes/leaderboard.js:
|
||||
Paul Gauthier (aider): 5
|
||||
aider/website/docs/leaderboards/index.md:
|
||||
Paul Gauthier: 1
|
||||
aider/website/index.html:
|
||||
Paul Gauthier: 3
|
||||
tests/basic/test_exceptions.py:
|
||||
Paul Gauthier: 3
|
||||
grand_total:
|
||||
Paul Gauthier: 38
|
||||
Paul Gauthier (aider): 225
|
||||
start_tag: v0.80.0
|
||||
total_lines: 263
|
||||
- aider_percentage: 91.85
|
||||
aider_total: 1567
|
||||
end_date: '2025-04-14'
|
||||
end_tag: v0.82.0
|
||||
file_counts:
|
||||
aider/__init__.py:
|
||||
Paul Gauthier: 1
|
||||
aider/args_formatter.py:
|
||||
Paul Gauthier (aider): 4
|
||||
aider/coders/__init__.py:
|
||||
Paul Gauthier (aider): 4
|
||||
aider/coders/base_coder.py:
|
||||
Paul Gauthier: 4
|
||||
Paul Gauthier (aider): 5
|
||||
aider/coders/editor_diff_fenced_coder.py:
|
||||
Paul Gauthier (aider): 9
|
||||
aider/coders/patch_coder.py:
|
||||
Paul Gauthier (aider): 679
|
||||
aider/coders/search_replace.py:
|
||||
Paul Gauthier (aider): 1
|
||||
aider/main.py:
|
||||
Paul Gauthier (aider): 1
|
||||
aider/models.py:
|
||||
Paul Gauthier: 1
|
||||
Paul Gauthier (aider): 25
|
||||
aider/resources/model-settings.yml:
|
||||
Felix Lisczyk: 13
|
||||
Paul Gauthier: 37
|
||||
Paul Gauthier (aider): 68
|
||||
aider/website/_includes/leaderboard.js:
|
||||
Paul Gauthier: 38
|
||||
Paul Gauthier (aider): 6
|
||||
aider/website/_includes/leaderboard_table.js:
|
||||
Paul Gauthier (aider): 518
|
||||
aider/website/docs/leaderboards/index.md:
|
||||
Paul Gauthier: 15
|
||||
Paul Gauthier (aider): 209
|
||||
aider/website/index.html:
|
||||
Paul Gauthier: 28
|
||||
scripts/homepage.py:
|
||||
Paul Gauthier (aider): 2
|
||||
scripts/versionbump.py:
|
||||
Paul Gauthier (aider): 11
|
||||
tests/basic/test_coder.py:
|
||||
Paul Gauthier: 2
|
||||
Paul Gauthier (aider): 25
|
||||
grand_total:
|
||||
Felix Lisczyk: 13
|
||||
Paul Gauthier: 126
|
||||
Paul Gauthier (aider): 1567
|
||||
start_tag: v0.81.0
|
||||
total_lines: 1706
|
||||
|
||||
@@ -643,7 +643,7 @@
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 1
|
||||
total_tests: 225
|
||||
command: "aider --model anthropic/claude-3-7-sonnet-20250219 # plus yml config"
|
||||
command: "aider --model anthropic/claude-3-7-sonnet-20250219 --thinking-tokens 32k"
|
||||
date: 2025-02-24
|
||||
versions: 0.75.1.dev
|
||||
seconds_per_case: 105.2
|
||||
@@ -807,28 +807,394 @@
|
||||
seconds_per_case: 290.0
|
||||
total_cost: 1.1164
|
||||
|
||||
- dirname: 2025-03-25-19-46-45--gemini-25-pro-exp-diff-fenced
|
||||
- dirname: 2025-04-12-04-55-50--gemini-25-pro-diff-fenced
|
||||
test_cases: 225
|
||||
model: Gemini 2.5 Pro exp-03-25
|
||||
model: Gemini 2.5 Pro Preview 03-25
|
||||
edit_format: diff-fenced
|
||||
commit_hash: 33413ec
|
||||
pass_rate_1: 39.1
|
||||
commit_hash: 0282574
|
||||
pass_rate_1: 40.9
|
||||
pass_rate_2: 72.9
|
||||
pass_num_1: 88
|
||||
pass_num_1: 92
|
||||
pass_num_2: 164
|
||||
percent_cases_well_formed: 89.8
|
||||
error_outputs: 30
|
||||
num_malformed_responses: 30
|
||||
num_with_malformed_responses: 23
|
||||
user_asks: 57
|
||||
percent_cases_well_formed: 92.4
|
||||
error_outputs: 21
|
||||
num_malformed_responses: 21
|
||||
num_with_malformed_responses: 17
|
||||
user_asks: 69
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 2
|
||||
total_tests: 225
|
||||
command: aider --model gemini/gemini-2.5-pro-preview-03-25
|
||||
date: 2025-04-12
|
||||
versions: 0.81.3.dev
|
||||
seconds_per_case: 45.3
|
||||
total_cost: 6.3174
|
||||
|
||||
- dirname: 2025-03-29-05-24-55--chatgpt4o-mar28-diff
|
||||
test_cases: 225
|
||||
model: chatgpt-4o-latest (2025-03-29)
|
||||
edit_format: diff
|
||||
commit_hash: 0decbad
|
||||
pass_rate_1: 16.4
|
||||
pass_rate_2: 45.3
|
||||
pass_num_1: 37
|
||||
pass_num_2: 102
|
||||
percent_cases_well_formed: 64.4
|
||||
error_outputs: 85
|
||||
num_malformed_responses: 85
|
||||
num_with_malformed_responses: 80
|
||||
user_asks: 174
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 4
|
||||
total_tests: 225
|
||||
command: aider --model chatgpt-4o-latest
|
||||
date: 2025-03-29
|
||||
versions: 0.79.3.dev
|
||||
seconds_per_case: 10.3
|
||||
total_cost: 19.7416
|
||||
|
||||
- dirname: 2025-04-04-02-57-25--qalpha-diff-exsys
|
||||
test_cases: 225
|
||||
model: Quasar Alpha
|
||||
edit_format: diff
|
||||
commit_hash: 8a34a6c-dirty
|
||||
pass_rate_1: 21.8
|
||||
pass_rate_2: 54.7
|
||||
pass_num_1: 49
|
||||
pass_num_2: 123
|
||||
percent_cases_well_formed: 98.2
|
||||
error_outputs: 4
|
||||
num_malformed_responses: 4
|
||||
num_with_malformed_responses: 4
|
||||
user_asks: 187
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 4
|
||||
total_tests: 225
|
||||
command: aider --model openrouter/openrouter/quasar-alpha
|
||||
date: 2025-04-04
|
||||
versions: 0.80.5.dev
|
||||
seconds_per_case: 14.8
|
||||
total_cost: 0.0000
|
||||
|
||||
- dirname: 2025-04-06-08-39-52--llama-4-maverick-17b-128e-instruct-polyglot-whole
|
||||
test_cases: 225
|
||||
model: Llama 4 Maverick
|
||||
edit_format: whole
|
||||
commit_hash: 9445a31
|
||||
pass_rate_1: 4.4
|
||||
pass_rate_2: 15.6
|
||||
pass_num_1: 10
|
||||
pass_num_2: 35
|
||||
percent_cases_well_formed: 99.1
|
||||
error_outputs: 12
|
||||
num_malformed_responses: 2
|
||||
num_with_malformed_responses: 2
|
||||
user_asks: 248
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 4
|
||||
total_tests: 225
|
||||
command: aider --model nvidia_nim/meta/llama-4-maverick-17b-128e-instruct
|
||||
date: 2025-04-06
|
||||
versions: 0.81.2.dev
|
||||
seconds_per_case: 20.5
|
||||
total_cost: 0.0000
|
||||
|
||||
- dirname: 2025-04-10-04-21-31--grok3-diff-exuser
|
||||
test_cases: 225
|
||||
model: Grok 3 Beta
|
||||
edit_format: diff
|
||||
commit_hash: 2dd40fc-dirty
|
||||
pass_rate_1: 22.2
|
||||
pass_rate_2: 53.3
|
||||
pass_num_1: 50
|
||||
pass_num_2: 120
|
||||
percent_cases_well_formed: 99.6
|
||||
error_outputs: 1
|
||||
num_malformed_responses: 1
|
||||
num_with_malformed_responses: 1
|
||||
user_asks: 68
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 2
|
||||
total_tests: 225
|
||||
command: aider --model openrouter/x-ai/grok-3-beta
|
||||
date: 2025-04-10
|
||||
versions: 0.81.2.dev
|
||||
seconds_per_case: 15.3
|
||||
total_cost: 11.0338
|
||||
|
||||
- dirname: 2025-04-10-18-47-24--grok3-mini-whole-exuser
|
||||
test_cases: 225
|
||||
model: Grok 3 Mini Beta (low)
|
||||
edit_format: whole
|
||||
commit_hash: 14ffe77-dirty
|
||||
pass_rate_1: 11.1
|
||||
pass_rate_2: 34.7
|
||||
pass_num_1: 25
|
||||
pass_num_2: 78
|
||||
percent_cases_well_formed: 100.0
|
||||
error_outputs: 3
|
||||
num_malformed_responses: 0
|
||||
num_with_malformed_responses: 0
|
||||
user_asks: 73
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 5
|
||||
total_tests: 225
|
||||
command: aider --model openrouter/x-ai/grok-3-mini-beta
|
||||
date: 2025-04-10
|
||||
versions: 0.81.2.dev
|
||||
seconds_per_case: 35.1
|
||||
total_cost: 0.7856
|
||||
|
||||
- dirname: 2025-04-10-23-59-02--xai-grok3-mini-whole-high
|
||||
test_cases: 225
|
||||
model: Grok 3 Mini Beta (high)
|
||||
edit_format: whole
|
||||
commit_hash: 8ee33da-dirty
|
||||
pass_rate_1: 17.3
|
||||
pass_rate_2: 49.3
|
||||
pass_num_1: 39
|
||||
pass_num_2: 111
|
||||
percent_cases_well_formed: 99.6
|
||||
error_outputs: 1
|
||||
num_malformed_responses: 1
|
||||
num_with_malformed_responses: 1
|
||||
user_asks: 64
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 0
|
||||
total_tests: 225
|
||||
command: aider --model xai/grok-3-mini-beta --reasoning-effort high
|
||||
date: 2025-04-10
|
||||
versions: 0.81.3.dev
|
||||
seconds_per_case: 79.1
|
||||
total_cost: 0.7346
|
||||
|
||||
- dirname: 2025-04-10-19-02-44--oalpha-diff-exsys
|
||||
test_cases: 225
|
||||
model: Optimus Alpha
|
||||
edit_format: diff
|
||||
commit_hash: 532bc45-dirty
|
||||
pass_rate_1: 21.3
|
||||
pass_rate_2: 52.9
|
||||
pass_num_1: 48
|
||||
pass_num_2: 119
|
||||
percent_cases_well_formed: 97.3
|
||||
error_outputs: 7
|
||||
num_malformed_responses: 6
|
||||
num_with_malformed_responses: 6
|
||||
user_asks: 182
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 3
|
||||
total_tests: 225
|
||||
command: aider --model gemini/gemini-2.5-pro-exp-03-25
|
||||
date: 2025-03-25
|
||||
versions: 0.78.1.dev
|
||||
seconds_per_case: 47.1
|
||||
command: aider --model openrouter/openrouter/optimus-alpha
|
||||
date: 2025-04-10
|
||||
versions: 0.81.2.dev
|
||||
seconds_per_case: 18.4
|
||||
total_cost: 0.0000
|
||||
|
||||
- dirname: 2025-04-14-21-05-54--gpt41-diff-exuser
|
||||
test_cases: 225
|
||||
model: gpt-4.1
|
||||
edit_format: diff
|
||||
commit_hash: 7a87db5-dirty
|
||||
pass_rate_1: 20.0
|
||||
pass_rate_2: 52.4
|
||||
pass_num_1: 45
|
||||
pass_num_2: 118
|
||||
percent_cases_well_formed: 98.2
|
||||
error_outputs: 6
|
||||
num_malformed_responses: 5
|
||||
num_with_malformed_responses: 4
|
||||
user_asks: 171
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 1
|
||||
test_timeouts: 5
|
||||
total_tests: 225
|
||||
command: aider --model gpt-4.1
|
||||
date: 2025-04-14
|
||||
versions: 0.81.4.dev
|
||||
seconds_per_case: 20.5
|
||||
total_cost: 9.8556
|
||||
|
||||
- dirname: 2025-04-14-21-27-53--gpt41mini-diff
|
||||
test_cases: 225
|
||||
model: gpt-4.1-mini
|
||||
edit_format: diff
|
||||
commit_hash: ffb743e-dirty
|
||||
pass_rate_1: 11.1
|
||||
pass_rate_2: 32.4
|
||||
pass_num_1: 25
|
||||
pass_num_2: 73
|
||||
percent_cases_well_formed: 92.4
|
||||
error_outputs: 64
|
||||
num_malformed_responses: 62
|
||||
num_with_malformed_responses: 17
|
||||
user_asks: 159
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 2
|
||||
test_timeouts: 2
|
||||
total_tests: 225
|
||||
command: aider --model gpt-4.1-mini
|
||||
date: 2025-04-14
|
||||
versions: 0.81.4.dev
|
||||
seconds_per_case: 19.5
|
||||
total_cost: 1.9918
|
||||
|
||||
- dirname: 2025-04-14-22-46-01--gpt41nano-diff
|
||||
test_cases: 225
|
||||
model: gpt-4.1-nano
|
||||
edit_format: whole
|
||||
commit_hash: 71d1591-dirty
|
||||
pass_rate_1: 3.1
|
||||
pass_rate_2: 8.9
|
||||
pass_num_1: 7
|
||||
pass_num_2: 20
|
||||
percent_cases_well_formed: 94.2
|
||||
error_outputs: 20
|
||||
num_malformed_responses: 20
|
||||
num_with_malformed_responses: 13
|
||||
user_asks: 316
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 8
|
||||
total_tests: 225
|
||||
command: aider --model gpt-4.1-nano
|
||||
date: 2025-04-14
|
||||
versions: 0.81.4.dev
|
||||
seconds_per_case: 12.0
|
||||
total_cost: 0.4281
|
||||
|
||||
- dirname: 2025-04-16-21-20-55--o3-high-diff-temp0-exsys
|
||||
test_cases: 225
|
||||
model: o3 (high)
|
||||
edit_format: diff
|
||||
commit_hash: 24805ff-dirty
|
||||
pass_rate_1: 36.9
|
||||
pass_rate_2: 79.6
|
||||
pass_num_1: 83
|
||||
pass_num_2: 179
|
||||
percent_cases_well_formed: 95.1
|
||||
error_outputs: 11
|
||||
num_malformed_responses: 11
|
||||
num_with_malformed_responses: 11
|
||||
user_asks: 110
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 2
|
||||
total_tests: 225
|
||||
command: aider --model o3
|
||||
date: 2025-04-16
|
||||
versions: 0.82.1.dev
|
||||
seconds_per_case: 113.8
|
||||
total_cost: 111.0325
|
||||
|
||||
- dirname: 2025-04-16-22-01-58--o4-mini-high-diff-exsys
|
||||
test_cases: 225
|
||||
model: o4-mini (high)
|
||||
edit_format: diff
|
||||
commit_hash: b66901f-dirty
|
||||
pass_rate_1: 19.6
|
||||
pass_rate_2: 72.0
|
||||
pass_num_1: 44
|
||||
pass_num_2: 162
|
||||
percent_cases_well_formed: 90.7
|
||||
error_outputs: 26
|
||||
num_malformed_responses: 24
|
||||
num_with_malformed_responses: 21
|
||||
user_asks: 66
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 1
|
||||
test_timeouts: 2
|
||||
total_tests: 225
|
||||
command: aider --model o4-mini
|
||||
date: 2025-04-16
|
||||
versions: 0.82.1.dev
|
||||
seconds_per_case: 176.5
|
||||
total_cost: 19.6399
|
||||
|
||||
- dirname: 2025-04-17-01-20-35--o3-mini-high-diff-arch
|
||||
test_cases: 225
|
||||
model: o3 (high) + gpt-4.1
|
||||
edit_format: architect
|
||||
commit_hash: 80909e1-dirty
|
||||
editor_model: gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
pass_rate_1: 36.0
|
||||
pass_rate_2: 82.7
|
||||
pass_num_1: 81
|
||||
pass_num_2: 186
|
||||
percent_cases_well_formed: 100.0
|
||||
error_outputs: 9
|
||||
num_malformed_responses: 0
|
||||
num_with_malformed_responses: 0
|
||||
user_asks: 166
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 0
|
||||
total_tests: 225
|
||||
command: aider --model o3 --architect
|
||||
date: 2025-04-17
|
||||
versions: 0.82.2.dev
|
||||
seconds_per_case: 110.0
|
||||
total_cost: 69.2921
|
||||
|
||||
- dirname: 2025-04-19-14-43-04--o4-mini-patch
|
||||
test_cases: 225
|
||||
model: openhands-lm-32b-v0.1
|
||||
edit_format: whole
|
||||
commit_hash: c08336f
|
||||
pass_rate_1: 4.0
|
||||
pass_rate_2: 10.2
|
||||
pass_num_1: 9
|
||||
pass_num_2: 23
|
||||
percent_cases_well_formed: 95.1
|
||||
error_outputs: 55
|
||||
num_malformed_responses: 41
|
||||
num_with_malformed_responses: 11
|
||||
user_asks: 166
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 11
|
||||
total_tests: 225
|
||||
command: aider --model openrouter/all-hands/openhands-lm-32b-v0.1
|
||||
date: 2025-04-19
|
||||
versions: 0.82.2.dev
|
||||
seconds_per_case: 195.6
|
||||
total_cost: 0.0000
|
||||
@@ -4,7 +4,11 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const redDiagonalPattern = pattern.draw('diagonal', 'rgba(255, 99, 132, 0.2)');
|
||||
let displayedData = [];
|
||||
|
||||
const HIGHLIGHT_MODEL = '{{ highlight_model | default: "no no no" }}';
|
||||
// Get highlight model from query string or Jekyll variable
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const queryHighlight = urlParams.get('highlight');
|
||||
const HIGHLIGHT_MODEL = queryHighlight || '{{ highlight_model | default: "no no no" }}';
|
||||
|
||||
var leaderboardData = {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
@@ -13,14 +17,14 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
backgroundColor: function(context) {
|
||||
const row = allData[context.dataIndex];
|
||||
if (row && row.edit_format === 'whole') {
|
||||
return diagonalPattern;
|
||||
return redDiagonalPattern; // Use red pattern for highlighted whole format
|
||||
}
|
||||
const label = leaderboardData.labels[context.dataIndex] || '';
|
||||
return (label && label.includes(HIGHLIGHT_MODEL)) ? 'rgba(255, 99, 132, 0.2)' : 'rgba(54, 162, 235, 0.2)';
|
||||
return (label && HIGHLIGHT_MODEL && label.toLowerCase().includes(HIGHLIGHT_MODEL.toLowerCase())) ? '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(HIGHLIGHT_MODEL)) ? 'rgba(255, 99, 132, 1)' : 'rgba(54, 162, 235, 1)';
|
||||
return (label && HIGHLIGHT_MODEL && label.toLowerCase().includes(HIGHLIGHT_MODEL.toLowerCase())) ? 'rgba(255, 99, 132, 1)' : 'rgba(54, 162, 235, 1)';
|
||||
},
|
||||
borderWidth: 1
|
||||
}, {
|
||||
@@ -74,11 +78,13 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
leaderboardChart.render();
|
||||
}
|
||||
|
||||
// Use displayedData in the backgroundColor callback instead of allData
|
||||
// Update backgroundColor and borderColor for the main dataset based on displayedData
|
||||
leaderboardData.datasets[0].backgroundColor = function(context) {
|
||||
const row = displayedData[context.dataIndex];
|
||||
const label = leaderboardData.labels[context.dataIndex] || '';
|
||||
if (label && label.includes(HIGHLIGHT_MODEL)) {
|
||||
const isHighlighted = label && HIGHLIGHT_MODEL && label.toLowerCase().includes(HIGHLIGHT_MODEL.toLowerCase());
|
||||
|
||||
if (isHighlighted) {
|
||||
if (row && row.edit_format === 'whole') return redDiagonalPattern;
|
||||
else return 'rgba(255, 99, 132, 0.2)';
|
||||
} else if (row && row.edit_format === 'whole') {
|
||||
@@ -171,6 +177,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
autoSkip: false, // Prevent labels from being automatically skipped
|
||||
maxRotation: 90, // Allow labels to rotate up to 90 degrees
|
||||
minRotation: 0,
|
||||
callback: function(value, index) {
|
||||
const label = this.getLabelForValue(value);
|
||||
if (label.length <= "claude-3-5-sonnet".length) {
|
||||
|
||||
515
aider/website/_includes/leaderboard_table.js
Normal file
515
aider/website/_includes/leaderboard_table.js
Normal file
@@ -0,0 +1,515 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let currentMode = 'view'; // 'view', 'select', 'detail'
|
||||
let selectedRows = new Set(); // Store indices of selected rows
|
||||
const MAX_DISPLAY_COST_CAP = 75; // Define the constant here
|
||||
|
||||
const allMainRows = document.querySelectorAll('tr[id^="main-row-"]');
|
||||
const allDetailsRows = document.querySelectorAll('tr[id^="details-"]');
|
||||
const searchInput = document.getElementById('editSearchInput');
|
||||
const modeViewButton = document.getElementById('mode-view-btn');
|
||||
const modeDetailButton = document.getElementById('mode-detail-btn');
|
||||
const modeSelectButton = document.getElementById('mode-select-btn');
|
||||
const modeButtons = [modeViewButton, modeSelectButton, modeDetailButton];
|
||||
const selectAllCheckbox = document.getElementById('select-all-checkbox');
|
||||
const leaderboardTitle = document.getElementById('leaderboard-title'); // Get title element
|
||||
const defaultTitle = "Aider polyglot coding leaderboard";
|
||||
const filteredTitle = "Aider polyglot coding benchmark results (selected)";
|
||||
|
||||
function applySearchFilter() {
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
allMainRows.forEach(row => {
|
||||
const textContent = row.textContent.toLowerCase();
|
||||
const detailsRow = document.getElementById(row.id.replace('main-row-', 'details-'));
|
||||
const matchesSearch = textContent.includes(searchTerm);
|
||||
|
||||
if (matchesSearch) {
|
||||
row.classList.remove('hidden-by-search');
|
||||
if (detailsRow) detailsRow.classList.remove('hidden-by-search');
|
||||
} else {
|
||||
row.classList.add('hidden-by-search');
|
||||
if (detailsRow) detailsRow.classList.add('hidden-by-search');
|
||||
}
|
||||
});
|
||||
// After applying search filter, re-apply view mode filter and update select-all state
|
||||
updateTableView(currentMode);
|
||||
if (currentMode === 'select') {
|
||||
updateSelectAllCheckboxState();
|
||||
}
|
||||
|
||||
// Update cost bars and ticks since visible rows may have changed
|
||||
updateCostBars();
|
||||
updateCostTicks();
|
||||
}
|
||||
|
||||
function getVisibleMainRows() {
|
||||
// Helper to get rows currently visible (not hidden by search or mode)
|
||||
return Array.from(allMainRows).filter(row =>
|
||||
!row.classList.contains('hidden-by-search') && !row.classList.contains('hidden-by-mode')
|
||||
);
|
||||
}
|
||||
|
||||
function updateSelectAllCheckboxState() {
|
||||
// Update the header checkbox based on the selection state of *visible* rows
|
||||
if (currentMode !== 'select') return; // Only relevant in select mode
|
||||
|
||||
const visibleRows = getVisibleMainRows();
|
||||
const visibleRowCount = visibleRows.length;
|
||||
const selectedVisibleRowCount = visibleRows.filter(row => selectedRows.has(row.querySelector('.row-selector')?.dataset.rowIndex)).length;
|
||||
|
||||
if (visibleRowCount === 0) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
} else if (selectedVisibleRowCount === visibleRowCount) {
|
||||
selectAllCheckbox.checked = true;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
} else if (selectedVisibleRowCount > 0) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = true;
|
||||
} else {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateTableView(mode) {
|
||||
currentMode = mode; // Update global state ('view', 'select', 'detail')
|
||||
|
||||
// Update button styles first
|
||||
modeButtons.forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
// Reset specific styles potentially added by .active
|
||||
btn.style.backgroundColor = '';
|
||||
btn.style.color = '';
|
||||
});
|
||||
let activeButton;
|
||||
if (mode === 'view') activeButton = modeViewButton;
|
||||
else if (mode === 'select') activeButton = modeSelectButton;
|
||||
else if (mode === 'detail') activeButton = modeDetailButton;
|
||||
|
||||
activeButton.classList.add('active');
|
||||
activeButton.style.backgroundColor = '#e7f3ff'; // Use selected row highlight blue
|
||||
activeButton.style.color = '#495057'; // Use dark text for contrast on light blue
|
||||
|
||||
// Get the first header cell (for the toggle/checkbox column)
|
||||
const firstHeaderCell = document.querySelector('table thead th:first-child');
|
||||
|
||||
// Show/hide header checkbox based on mode
|
||||
selectAllCheckbox.style.display = mode === 'select' ? 'inline-block' : 'none';
|
||||
|
||||
allMainRows.forEach(row => {
|
||||
const rowIndex = row.querySelector('.row-selector')?.dataset.rowIndex;
|
||||
const toggleButton = row.querySelector('.toggle-details');
|
||||
const selectorCheckbox = row.querySelector('.row-selector');
|
||||
const firstCell = row.querySelector('td:first-child'); // Get the first cell of the main row
|
||||
const detailsRow = document.getElementById(`details-${rowIndex}`);
|
||||
const isSelected = selectedRows.has(rowIndex);
|
||||
|
||||
// Reset visibility classes before applying mode logic
|
||||
row.classList.remove('hidden-by-mode');
|
||||
if (detailsRow) detailsRow.classList.remove('hidden-by-mode');
|
||||
|
||||
// Show/hide the first column (header and data cells) based on mode
|
||||
if (firstHeaderCell) {
|
||||
firstHeaderCell.style.display = mode === 'view' ? 'none' : '';
|
||||
}
|
||||
if (firstCell) {
|
||||
firstCell.style.display = mode === 'view' ? 'none' : '';
|
||||
}
|
||||
|
||||
// Apply mode-specific logic
|
||||
if (mode === 'view') { // --- VIEW MODE ---
|
||||
toggleButton.style.display = 'none'; // Hide toggle in view mode
|
||||
selectorCheckbox.style.display = 'none';
|
||||
row.classList.remove('row-selected'); // Ensure no selection highlight
|
||||
// view-highlighted is handled by row click listener
|
||||
|
||||
// In 'view' mode, hide row if selections exist AND this row is NOT selected
|
||||
if (selectedRows.size > 0 && !isSelected) {
|
||||
row.classList.add('hidden-by-mode');
|
||||
if (detailsRow) detailsRow.classList.add('hidden-by-mode');
|
||||
} else {
|
||||
// Ensure row is not hidden by mode if it's selected or no selections exist
|
||||
// This is handled by the reset at the start of the loop:
|
||||
// row.classList.remove('hidden-by-mode');
|
||||
// if (detailsRow) detailsRow.classList.remove('hidden-by-mode');
|
||||
}
|
||||
// Always hide details row content in view mode regardless of visibility class
|
||||
if (detailsRow) {
|
||||
detailsRow.style.display = 'none';
|
||||
}
|
||||
|
||||
} else if (mode === 'select') { // --- SELECT MODE ---
|
||||
toggleButton.style.display = 'none';
|
||||
selectorCheckbox.style.display = 'inline-block';
|
||||
selectorCheckbox.checked = isSelected;
|
||||
row.classList.toggle('row-selected', isSelected);
|
||||
row.classList.remove('view-highlighted'); // Clear view highlight when switching to select
|
||||
// Always hide details row in select mode
|
||||
if (detailsRow) detailsRow.style.display = 'none';
|
||||
|
||||
// In 'select' mode, no rows should be hidden based on selection status
|
||||
row.classList.remove('hidden-by-mode');
|
||||
if (detailsRow) detailsRow.classList.remove('hidden-by-mode');
|
||||
|
||||
} else { // --- DETAIL MODE --- (mode === 'detail')
|
||||
toggleButton.style.display = 'inline-block'; // Show toggle
|
||||
selectorCheckbox.style.display = 'none';
|
||||
row.classList.remove('row-selected'); // Clear selection highlight
|
||||
row.classList.remove('view-highlighted'); // Clear view highlight when switching to detail
|
||||
// Details row visibility is controlled by the toggle button state, don't force hide/show here
|
||||
// Ensure main row is visible if not hidden by search
|
||||
row.classList.remove('hidden-by-mode');
|
||||
if (detailsRow) {
|
||||
detailsRow.classList.remove('hidden-by-mode');
|
||||
// Preserve existing display state (controlled by toggle) unless hidden by search
|
||||
if (detailsRow.classList.contains('hidden-by-search')) {
|
||||
detailsRow.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Ensure rows hidden by search remain hidden regardless of mode
|
||||
if (row.classList.contains('hidden-by-search')) {
|
||||
row.style.display = 'none';
|
||||
if (detailsRow) detailsRow.style.display = 'none';
|
||||
} else if (!row.classList.contains('hidden-by-mode')) {
|
||||
// Make row visible if not hidden by search or mode
|
||||
row.style.display = ''; // Or 'table-row' if needed, but '' usually works
|
||||
} else {
|
||||
// Row is hidden by mode, ensure it's hidden
|
||||
row.style.display = 'none';
|
||||
if (detailsRow) detailsRow.style.display = 'none';
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Update the leaderboard title based on mode and selection
|
||||
if (leaderboardTitle) {
|
||||
if (currentMode === 'view' && selectedRows.size > 0) {
|
||||
leaderboardTitle.textContent = filteredTitle;
|
||||
} else {
|
||||
leaderboardTitle.textContent = defaultTitle;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the select-all checkbox state after updating the view
|
||||
updateSelectAllCheckboxState();
|
||||
|
||||
// Update cost bars and ticks since visible/selected rows may have changed
|
||||
updateCostBars();
|
||||
updateCostTicks();
|
||||
}
|
||||
|
||||
|
||||
// --- Existing Initializations ---
|
||||
// Add percentage ticks
|
||||
const percentCells = document.querySelectorAll('.bar-cell:not(.cost-bar-cell)');
|
||||
percentCells.forEach(cell => {
|
||||
// Add ticks at 0%, 10%, 20%, ..., 100%
|
||||
for (let i = 0; i <= 100; i += 10) {
|
||||
const tick = document.createElement('div');
|
||||
tick.className = 'percent-tick';
|
||||
tick.style.left = `${i}%`;
|
||||
cell.appendChild(tick);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to calculate the appropriate max display cost based on visible/selected entries
|
||||
function calculateDisplayMaxCost() {
|
||||
// Get the appropriate set of rows based on the current mode and selection state
|
||||
let rowsToConsider;
|
||||
|
||||
if (currentMode === 'view' && selectedRows.size > 0) {
|
||||
// In view mode with selections, only consider selected rows
|
||||
rowsToConsider = Array.from(allMainRows).filter(row => {
|
||||
const rowIndex = row.querySelector('.row-selector')?.dataset.rowIndex;
|
||||
return rowIndex && selectedRows.has(rowIndex) && !row.classList.contains('hidden-by-search');
|
||||
});
|
||||
} else {
|
||||
// In other modes or without selections, consider all visible rows
|
||||
rowsToConsider = getVisibleMainRows();
|
||||
}
|
||||
|
||||
// Find the maximum cost among the rows to consider
|
||||
let maxCost = 0;
|
||||
rowsToConsider.forEach(row => {
|
||||
const costBar = row.querySelector('.cost-bar');
|
||||
if (costBar) {
|
||||
const cost = parseFloat(costBar.dataset.cost || '0');
|
||||
if (cost > maxCost) maxCost = cost;
|
||||
}
|
||||
});
|
||||
|
||||
// Cap at MAX_DISPLAY_COST_CAP if any entries exceed that amount, otherwise use actual max
|
||||
return maxCost > MAX_DISPLAY_COST_CAP ? MAX_DISPLAY_COST_CAP : Math.max(1, maxCost); // Ensure at least 1 to avoid division by zero
|
||||
}
|
||||
|
||||
// Process cost bars with dynamic scale
|
||||
function updateCostBars() {
|
||||
const costBars = document.querySelectorAll('.cost-bar');
|
||||
const currentMaxDisplayCost = calculateDisplayMaxCost();
|
||||
|
||||
// Remove existing special indicators first
|
||||
document.querySelectorAll('.dark-section, .tear-line').forEach(el => el.remove());
|
||||
|
||||
costBars.forEach(bar => {
|
||||
const cost = parseFloat(bar.dataset.cost);
|
||||
|
||||
if (cost > 0) {
|
||||
// Calculate percentage based on the dynamic display max
|
||||
const percent = Math.min(cost, currentMaxDisplayCost) / currentMaxDisplayCost * 100;
|
||||
// Clamp percentage between 0 and 100
|
||||
bar.style.width = Math.max(0, Math.min(100, percent)) + '%';
|
||||
|
||||
// Mark bars that exceed the limit (only if our display max is capped at 50)
|
||||
if (currentMaxDisplayCost === MAX_DISPLAY_COST_CAP && cost > MAX_DISPLAY_COST_CAP) {
|
||||
// Create a darker section at the end with diagonal stripes
|
||||
const darkSection = document.createElement('div');
|
||||
darkSection.className = 'bar-viz dark-section';
|
||||
darkSection.style.width = '15%'; // From 85% to 100%
|
||||
darkSection.style.left = '85%';
|
||||
darkSection.style.backgroundColor = 'rgba(13, 110, 253, 0.6)'; // Darker blue
|
||||
darkSection.style.borderRight = '1px solid rgba(13, 110, 253, 0.8)';
|
||||
darkSection.style.zIndex = '1';
|
||||
// Add diagonal stripes with CSS background
|
||||
darkSection.style.backgroundImage = 'repeating-linear-gradient(45deg, rgba(255,255,255,0.3), rgba(255,255,255,0.3) 5px, transparent 5px, transparent 10px)';
|
||||
bar.parentNode.appendChild(darkSection);
|
||||
|
||||
// Add a dashed "tear line" at the transition point
|
||||
const tearLine = document.createElement('div');
|
||||
tearLine.className = 'tear-line';
|
||||
tearLine.style.position = 'absolute';
|
||||
tearLine.style.left = '85%';
|
||||
// Center the tear line vertically and make it 1.5x as tall as the bar
|
||||
tearLine.style.top = '50%';
|
||||
tearLine.style.transform = 'translateY(-50%)';
|
||||
tearLine.style.height = '54px'; // 1.5x the bar height (36px)
|
||||
tearLine.style.width = '2px';
|
||||
tearLine.style.backgroundColor = 'white';
|
||||
tearLine.style.borderLeft = '2px dashed rgba(0, 0, 0, 0.3)';
|
||||
tearLine.style.zIndex = '2'; // Above the bar
|
||||
bar.parentNode.appendChild(tearLine);
|
||||
}
|
||||
} else {
|
||||
// Set width to 0 if cost is 0 or negative
|
||||
bar.style.width = '0%';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Call this initially to set up the bars
|
||||
updateCostBars();
|
||||
|
||||
// Update cost ticks dynamically based on current max display cost
|
||||
function updateCostTicks() {
|
||||
const costCells = document.querySelectorAll('.cost-bar-cell');
|
||||
if (costCells.length === 0) return;
|
||||
|
||||
const currentMaxDisplayCost = calculateDisplayMaxCost();
|
||||
|
||||
// Remove existing ticks first
|
||||
document.querySelectorAll('.cost-tick').forEach(tick => tick.remove());
|
||||
|
||||
// Generate appropriate tick values based on current max
|
||||
let tickValues = [];
|
||||
|
||||
// Always use $10 increments, regardless of the max
|
||||
const maxTickValue = Math.ceil(currentMaxDisplayCost / 10) * 10; // Round up to nearest $10
|
||||
|
||||
for (let i = 0; i <= maxTickValue; i += 10) {
|
||||
tickValues.push(i);
|
||||
}
|
||||
|
||||
// Calculate percentage positions for each tick
|
||||
const tickPercentages = tickValues.map(tickCost => {
|
||||
return (tickCost / currentMaxDisplayCost) * 100;
|
||||
});
|
||||
|
||||
// Add tick divs to each cost cell
|
||||
costCells.forEach(cell => {
|
||||
const costBar = cell.querySelector('.cost-bar');
|
||||
// Use optional chaining and provide '0' as fallback if costBar or dataset.cost is missing
|
||||
const cost = parseFloat(costBar?.dataset?.cost || '0');
|
||||
|
||||
// Only add ticks if the cost is actually greater than 0
|
||||
if (cost > 0) {
|
||||
tickPercentages.forEach((percent, index) => {
|
||||
// Ensure percentage is within valid range
|
||||
if (percent >= 0 && percent <= 100) {
|
||||
const tick = document.createElement('div');
|
||||
tick.className = 'cost-tick';
|
||||
tick.style.left = `${percent}%`;
|
||||
cell.appendChild(tick);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Call this initially to set up the ticks
|
||||
updateCostTicks();
|
||||
|
||||
|
||||
// --- New Event Listeners ---
|
||||
|
||||
// Listener for mode toggle buttons
|
||||
modeButtons.forEach(button => {
|
||||
button.addEventListener('click', function(event) {
|
||||
const newMode = this.dataset.mode;
|
||||
if (newMode !== currentMode) {
|
||||
// Update active button style
|
||||
modeButtons.forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
// Reset specific styles potentially added by .active
|
||||
btn.style.backgroundColor = '';
|
||||
btn.style.color = '';
|
||||
});
|
||||
this.classList.add('active');
|
||||
// Apply active styles directly as inline styles might interfere
|
||||
this.style.backgroundColor = '#e7f3ff'; // Use selected row highlight blue
|
||||
this.style.color = '#495057'; // Use dark text for contrast on light blue
|
||||
|
||||
// Update table view and apply filters
|
||||
updateTableView(newMode);
|
||||
applySearchFilter(); // Re-apply search filter when mode changes
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Listener for row selector checkboxes (using event delegation on table body)
|
||||
const tableBody = document.querySelector('table tbody');
|
||||
tableBody.addEventListener('change', function(event) {
|
||||
if (event.target.classList.contains('row-selector') && currentMode === 'select') {
|
||||
const checkbox = event.target;
|
||||
const rowIndex = checkbox.dataset.rowIndex;
|
||||
const mainRow = checkbox.closest('tr');
|
||||
|
||||
if (checkbox.checked) {
|
||||
selectedRows.add(rowIndex);
|
||||
mainRow.classList.add('row-selected');
|
||||
} else {
|
||||
selectedRows.delete(rowIndex);
|
||||
mainRow.classList.remove('row-selected');
|
||||
}
|
||||
// Update select-all checkbox state
|
||||
updateSelectAllCheckboxState();
|
||||
|
||||
// Update cost bars and ticks if in view mode, as selection affects what's shown
|
||||
if (currentMode === 'view') {
|
||||
updateCostBars();
|
||||
updateCostTicks();
|
||||
}
|
||||
}
|
||||
}); // End of tableBody listener
|
||||
|
||||
// Listener for Select All checkbox
|
||||
selectAllCheckbox.addEventListener('change', function() {
|
||||
if (currentMode !== 'select') return;
|
||||
|
||||
const isChecked = selectAllCheckbox.checked;
|
||||
// Select/deselect only the rows that are currently visible
|
||||
const visibleRows = getVisibleMainRows();
|
||||
|
||||
visibleRows.forEach(row => {
|
||||
const checkbox = row.querySelector('.row-selector');
|
||||
const rowIndex = checkbox?.dataset.rowIndex;
|
||||
if (!checkbox || !rowIndex) return; // Skip if no checkbox/index found
|
||||
|
||||
// Only change state if it differs from target state
|
||||
if (checkbox.checked !== isChecked) {
|
||||
checkbox.checked = isChecked;
|
||||
row.classList.toggle('row-selected', isChecked);
|
||||
if (isChecked) {
|
||||
selectedRows.add(rowIndex);
|
||||
} else {
|
||||
selectedRows.delete(rowIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
// After bulk change, ensure the selectAll checkbox state is correct (not indeterminate)
|
||||
updateSelectAllCheckboxState();
|
||||
|
||||
// Update cost bars and ticks after selection changes
|
||||
updateCostBars();
|
||||
updateCostTicks();
|
||||
});
|
||||
|
||||
// Listener for search input
|
||||
searchInput.addEventListener('input', applySearchFilter);
|
||||
|
||||
// Add toggle functionality for details (Modified to respect modes)
|
||||
const toggleButtons = document.querySelectorAll('.toggle-details');
|
||||
toggleButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
// Only allow toggling in 'detail' mode
|
||||
if (currentMode !== 'detail') return;
|
||||
|
||||
const targetId = this.getAttribute('data-target');
|
||||
const targetRow = document.getElementById(targetId);
|
||||
const mainRow = this.closest('tr'); // Get the main row associated with this button
|
||||
|
||||
if (targetRow && !mainRow.classList.contains('hidden-by-mode') && !mainRow.classList.contains('hidden-by-search')) {
|
||||
const isVisible = targetRow.style.display !== 'none';
|
||||
targetRow.style.display = isVisible ? 'none' : 'table-row';
|
||||
this.textContent = isVisible ? '▶' : '▼';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Listener for clicking anywhere on a row
|
||||
tableBody.addEventListener('click', function(event) {
|
||||
const clickedRow = event.target.closest('tr');
|
||||
|
||||
// Ensure it's a main row and not a details row or header/footer
|
||||
if (!clickedRow || !clickedRow.id.startsWith('main-row-')) return;
|
||||
|
||||
// --- START conditional logic ---
|
||||
if (currentMode === 'select') {
|
||||
// --- SELECT MODE LOGIC (Existing) ---
|
||||
// Find the checkbox within this row
|
||||
const checkbox = clickedRow.querySelector('.row-selector');
|
||||
if (!checkbox) return; // No checkbox found in this row
|
||||
|
||||
// If the click was directly on the checkbox or its label (if any),
|
||||
// let the default behavior and the 'change' event listener handle it.
|
||||
// Otherwise, toggle the checkbox state programmatically.
|
||||
if (event.target !== checkbox && event.target.tagName !== 'LABEL' /* Add if you use labels */) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
// Manually trigger the change event to update state and UI
|
||||
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
// --- END SELECT MODE LOGIC ---
|
||||
|
||||
} else if (currentMode === 'view') {
|
||||
// --- VIEW MODE LOGIC (New) ---
|
||||
// Don't highlight if the click was on the details toggle button
|
||||
if (event.target.classList.contains('toggle-details')) {
|
||||
return;
|
||||
}
|
||||
// Toggle the highlight class on the clicked row
|
||||
clickedRow.classList.toggle('view-highlighted');
|
||||
// --- END VIEW MODE LOGIC ---
|
||||
}
|
||||
// --- END conditional logic ---
|
||||
});
|
||||
|
||||
|
||||
// --- Initial Setup ---
|
||||
updateTableView('view'); // Initialize view to 'view' mode
|
||||
applySearchFilter(); // Apply initial search filter (if any text is pre-filled or just to set initial state)
|
||||
|
||||
// Close button functionality
|
||||
const closeControlsBtn = document.getElementById('close-controls-btn');
|
||||
if (closeControlsBtn) {
|
||||
closeControlsBtn.addEventListener('click', function() {
|
||||
const controlsContainer = document.getElementById('controls-container');
|
||||
if (controlsContainer) {
|
||||
controlsContainer.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -4,7 +4,7 @@ You can send long, multi-line messages in the chat in a few ways:
|
||||
- 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 `/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 the `/editor` command (or press `Ctrl-X Ctrl-E` if your terminal allows) 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.
|
||||
|
||||
BIN
aider/website/assets/azure-deployment.png
Normal file
BIN
aider/website/assets/azure-deployment.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 260 KiB |
File diff suppressed because it is too large
Load Diff
@@ -171,19 +171,19 @@
|
||||
#stream: true
|
||||
|
||||
## Set the color for user input (default: #00cc00)
|
||||
#user-input-color: #00cc00
|
||||
#user-input-color: "#00cc00"
|
||||
|
||||
## Set the color for tool output (default: None)
|
||||
#tool-output-color: "xxx"
|
||||
|
||||
## Set the color for tool error messages (default: #FF2222)
|
||||
#tool-error-color: #FF2222
|
||||
#tool-error-color: "#FF2222"
|
||||
|
||||
## Set the color for tool warning messages (default: #FFA500)
|
||||
#tool-warning-color: #FFA500
|
||||
#tool-warning-color: "#FFA500"
|
||||
|
||||
## Set the color for assistant output (default: #0088ff)
|
||||
#assistant-output-color: #0088ff
|
||||
#assistant-output-color: "#0088ff"
|
||||
|
||||
## Set the color for the completion menu (default: terminal's default text color)
|
||||
#completion-menu-color: "xxx"
|
||||
|
||||
@@ -280,6 +280,18 @@ cog.out("```\n")
|
||||
anthropic-beta: prompt-caching-2024-07-31,pdfs-2024-09-25
|
||||
cache_control: true
|
||||
|
||||
- name: azure/gpt-4.1
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
editor_model_name: azure/gpt-4.1-mini
|
||||
|
||||
- name: azure/gpt-4.1-mini
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
|
||||
- name: azure/o1
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4o-mini
|
||||
@@ -308,6 +320,18 @@ cog.out("```\n")
|
||||
editor_model_name: azure/gpt-4o
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
- name: azure/o3
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
streaming: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: azure/o3-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4o-mini
|
||||
@@ -319,6 +343,66 @@ cog.out("```\n")
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: azure/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: azure/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: azure/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: bedrock/anthropic.claude-3-5-haiku-20241022-v1:0
|
||||
edit_format: diff
|
||||
weak_model_name: bedrock/anthropic.claude-3-5-haiku-20241022-v1:0
|
||||
@@ -569,6 +653,14 @@ cog.out("```\n")
|
||||
extra_params:
|
||||
max_tokens: 128000
|
||||
|
||||
- name: fireworks_ai/accounts/fireworks/models/deepseek-v3-0324
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
examples_as_sys_msg: true
|
||||
extra_params:
|
||||
max_tokens: 160000
|
||||
|
||||
- name: fireworks_ai/accounts/fireworks/models/qwq-32b
|
||||
edit_format: diff
|
||||
weak_model_name: fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct
|
||||
@@ -612,6 +704,12 @@ cog.out("```\n")
|
||||
|
||||
- name: gemini/gemini-2.5-pro-exp-03-25
|
||||
edit_format: diff-fenced
|
||||
weak_model_name: gemini/gemini-2.0-flash
|
||||
use_repo_map: true
|
||||
|
||||
- name: gemini/gemini-2.5-pro-preview-03-25
|
||||
edit_format: diff-fenced
|
||||
weak_model_name: gemini/gemini-2.0-flash
|
||||
use_repo_map: true
|
||||
|
||||
- name: gemini/gemini-exp-1114
|
||||
@@ -703,6 +801,18 @@ cog.out("```\n")
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
|
||||
- name: gpt-4.1
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
editor_model_name: gpt-4.1-mini
|
||||
|
||||
- name: gpt-4.1-mini
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
|
||||
- name: gpt-4.5-preview
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4o-mini
|
||||
@@ -789,6 +899,18 @@ cog.out("```\n")
|
||||
editor_model_name: gpt-4o
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
- name: o3
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
streaming: false
|
||||
editor_model_name: gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: o3-mini
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4o-mini
|
||||
@@ -800,6 +922,30 @@ cog.out("```\n")
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openai/gpt-4.1
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
editor_model_name: openai/gpt-4.1-mini
|
||||
|
||||
- name: openai/gpt-4.1-mini
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
|
||||
- name: openai/gpt-4.5-preview
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4o-mini
|
||||
@@ -869,6 +1015,18 @@ cog.out("```\n")
|
||||
editor_model_name: openai/gpt-4o
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
- name: openai/o3
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
streaming: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openai/o3-mini
|
||||
edit_format: diff
|
||||
weak_model_name: gpt-4o-mini
|
||||
@@ -880,6 +1038,66 @@ cog.out("```\n")
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/anthropic/claude-3-opus
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/anthropic/claude-3-5-haiku
|
||||
@@ -957,6 +1175,16 @@ cog.out("```\n")
|
||||
max_tokens: 8192
|
||||
caches_by_default: true
|
||||
|
||||
- name: openrouter/deepseek/deepseek-chat-v3-0324:free
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/deepseek/deepseek-chat-v3-0324:free
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
caches_by_default: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/deepseek/deepseek-r1:free
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
- name: openrouter/deepseek/deepseek-chat:free
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/deepseek/deepseek-chat:free
|
||||
@@ -1007,12 +1235,10 @@ cog.out("```\n")
|
||||
extra_params:
|
||||
max_tokens: 8192
|
||||
caches_by_default: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/deepseek/deepseek-r1:free
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
- name: openrouter/google/gemini-2.5-pro-exp-03-25:free
|
||||
edit_format: diff-fenced
|
||||
weak_model_name: openrouter/google/gemini-2.0-flash-exp:free
|
||||
use_repo_map: true
|
||||
|
||||
- name: openrouter/google/gemma-3-27b-it
|
||||
@@ -1026,6 +1252,18 @@ cog.out("```\n")
|
||||
weak_model_name: openrouter/meta-llama/llama-3-70b-instruct
|
||||
examples_as_sys_msg: true
|
||||
|
||||
- name: openrouter/openai/gpt-4.1
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
editor_model_name: openrouter/openai/gpt-4.1-mini
|
||||
|
||||
- name: openrouter/openai/gpt-4.1-mini
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
reminder: sys
|
||||
|
||||
- name: openrouter/openai/gpt-4o
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4o-mini
|
||||
@@ -1066,6 +1304,18 @@ cog.out("```\n")
|
||||
editor_model_name: openrouter/openai/gpt-4o
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
- name: openrouter/openai/o3
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
streaming: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/openai/o3-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4o-mini
|
||||
@@ -1088,6 +1338,76 @@ cog.out("```\n")
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/openai/o4-mini
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/openai/gpt-4.1-mini
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
use_temperature: false
|
||||
editor_model_name: openrouter/openai/gpt-4.1
|
||||
editor_edit_format: editor-diff
|
||||
system_prompt_prefix: 'Formatting re-enabled. '
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/openrouter/optimus-alpha
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
|
||||
- name: openrouter/openrouter/quasar-alpha
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
examples_as_sys_msg: true
|
||||
|
||||
- name: openrouter/qwen/qwen-2.5-coder-32b-instruct
|
||||
edit_format: diff
|
||||
weak_model_name: openrouter/qwen/qwen-2.5-coder-32b-instruct
|
||||
@@ -1095,6 +1415,24 @@ cog.out("```\n")
|
||||
editor_model_name: openrouter/qwen/qwen-2.5-coder-32b-instruct
|
||||
editor_edit_format: editor-diff
|
||||
|
||||
- name: openrouter/x-ai/grok-3-beta
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
|
||||
- name: openrouter/x-ai/grok-3-fast-beta
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
|
||||
- name: openrouter/x-ai/grok-3-mini-beta
|
||||
use_repo_map: true
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: openrouter/x-ai/grok-3-mini-fast-beta
|
||||
use_repo_map: true
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: vertex_ai-anthropic_models/vertex_ai/claude-3-7-sonnet@20250219
|
||||
edit_format: diff
|
||||
weak_model_name: vertex_ai/claude-3-5-haiku@20241022
|
||||
@@ -1160,9 +1498,31 @@ cog.out("```\n")
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
|
||||
- name: vertex_ai/gemini-2.5-pro-preview-03-25
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
|
||||
- name: vertex_ai/gemini-pro-experimental
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
|
||||
- name: xai/grok-3-beta
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
|
||||
- name: xai/grok-3-fast-beta
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
|
||||
- name: xai/grok-3-mini-beta
|
||||
use_repo_map: true
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
|
||||
- name: xai/grok-3-mini-fast-beta
|
||||
use_repo_map: true
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
```
|
||||
<!--[[[end]]]-->
|
||||
|
||||
|
||||
@@ -225,19 +225,19 @@ cog.outl("```")
|
||||
#stream: true
|
||||
|
||||
## Set the color for user input (default: #00cc00)
|
||||
#user-input-color: #00cc00
|
||||
#user-input-color: "#00cc00"
|
||||
|
||||
## Set the color for tool output (default: None)
|
||||
#tool-output-color: "xxx"
|
||||
|
||||
## Set the color for tool error messages (default: #FF2222)
|
||||
#tool-error-color: #FF2222
|
||||
#tool-error-color: "#FF2222"
|
||||
|
||||
## Set the color for tool warning messages (default: #FFA500)
|
||||
#tool-warning-color: #FFA500
|
||||
#tool-warning-color: "#FFA500"
|
||||
|
||||
## Set the color for assistant output (default: #0088ff)
|
||||
#assistant-output-color: #0088ff
|
||||
#assistant-output-color: "#0088ff"
|
||||
|
||||
## Set the color for the completion menu (default: terminal's default text color)
|
||||
#completion-menu-color: "xxx"
|
||||
|
||||
@@ -80,10 +80,14 @@ for alias, model in sorted(MODEL_ALIASES.items()):
|
||||
- `4o`: gpt-4o
|
||||
- `deepseek`: deepseek/deepseek-chat
|
||||
- `flash`: gemini/gemini-2.0-flash-exp
|
||||
- `gemini`: gemini/gemini-2.5-pro-exp-03-25
|
||||
- `gemini`: gemini/gemini-2.5-pro-preview-03-25
|
||||
- `gemini-2.5-pro`: gemini/gemini-2.5-pro-exp-03-25
|
||||
- `gemini-exp`: gemini/gemini-2.5-pro-exp-03-25
|
||||
- `grok3`: xai/grok-3-beta
|
||||
- `haiku`: claude-3-5-haiku-20241022
|
||||
- `optimus`: openrouter/openrouter/optimus-alpha
|
||||
- `opus`: claude-3-opus-20240229
|
||||
- `quasar`: openrouter/openrouter/quasar-alpha
|
||||
- `r1`: deepseek/deepseek-reasoner
|
||||
- `sonnet`: anthropic/claude-3-7-sonnet-20250219
|
||||
<!--[[[end]]]-->
|
||||
|
||||
@@ -264,12 +264,15 @@ tr:hover { background-color: #f5f5f5; }
|
||||
</style>
|
||||
<table>
|
||||
<tr><th>Model Name</th><th class='right'>Total Tokens</th><th class='right'>Percent</th></tr>
|
||||
<tr><td>anthropic/claude-3-7-sonnet-20250219</td><td class='right'>1,337,936</td><td class='right'>66.3%</td></tr>
|
||||
<tr><td>gemini/gemini-2.5-pro-exp-03-25</td><td class='right'>629,723</td><td class='right'>31.2%</td></tr>
|
||||
<tr><td>deepseek/deepseek-chat</td><td class='right'>30,307</td><td class='right'>1.5%</td></tr>
|
||||
<tr><td>openrouter/google/gemini-2.5-pro-exp-03-25:free</td><td class='right'>11,449</td><td class='right'>0.6%</td></tr>
|
||||
<tr><td>gemini/REDACTED</td><td class='right'>5,772</td><td class='right'>0.3%</td></tr>
|
||||
<tr><td>openrouter/REDACTED</td><td class='right'>3,830</td><td class='right'>0.2%</td></tr>
|
||||
<tr><td>gemini/gemini-2.5-pro-exp-03-25</td><td class='right'>2,499,338</td><td class='right'>83.9%</td></tr>
|
||||
<tr><td>openrouter/anthropic/claude-3.7-sonnet</td><td class='right'>313,377</td><td class='right'>10.5%</td></tr>
|
||||
<tr><td>o3</td><td class='right'>100,777</td><td class='right'>3.4%</td></tr>
|
||||
<tr><td>gemini/gemini-2.5-pro-preview-03-25</td><td class='right'>16,524</td><td class='right'>0.6%</td></tr>
|
||||
<tr><td>o4-mini</td><td class='right'>16,499</td><td class='right'>0.6%</td></tr>
|
||||
<tr><td>gpt-4.1-mini</td><td class='right'>11,775</td><td class='right'>0.4%</td></tr>
|
||||
<tr><td>gpt-4.1</td><td class='right'>10,687</td><td class='right'>0.4%</td></tr>
|
||||
<tr><td>None</td><td class='right'>8,001</td><td class='right'>0.3%</td></tr>
|
||||
<tr><td>gemini/REDACTED</td><td class='right'>606</td><td class='right'>0.0%</td></tr>
|
||||
</table>
|
||||
|
||||
{: .note :}
|
||||
@@ -287,6 +290,16 @@ by doing something like `git blame` on the repo,
|
||||
and counting up who wrote all the new lines of code in each release.
|
||||
Only lines in source code files are counted, not documentation or prompt files.
|
||||
|
||||
## Why did aider ignore/discard its proposed edits after it asked to add a new file to the chat?
|
||||
|
||||
If aider prompts you to add a new file to the chat and you say yes,
|
||||
it will re-submit the original request.
|
||||
The fact that the LLM's reply indicated that it needed to see another file (and you said yes)
|
||||
is often a sign that the LLM should have been able to see/edit that file in the first place.
|
||||
Without access to it, there is increased chance that it's done a bad implementation of the requested change.
|
||||
Often LLMs will hallucinate content for the files they needed but didn't have.
|
||||
So aider re-submits the original request in this situation.
|
||||
|
||||
## Why does aider sometimes stop highlighting code in its replies?
|
||||
|
||||
Aider displays the markdown responses that are coming back from the LLM.
|
||||
|
||||
@@ -36,17 +36,16 @@ If you can find and share that file in a
|
||||
[GitHub issue](https://github.com/Aider-AI/aider/issues),
|
||||
then it may be possible to add repo map support.
|
||||
|
||||
If aider doesn't support linting, it will be complicated to
|
||||
add linting and repo map support.
|
||||
That is because aider relies on
|
||||
[py-tree-sitter-languages](https://github.com/grantjenks/py-tree-sitter-languages)
|
||||
If aider doesn't already support linting your language,
|
||||
it will be more complicated to add support.
|
||||
Aider relies on
|
||||
[tree-sitter-language-pack](https://github.com/Goldziher/tree-sitter-language-pack)
|
||||
to provide pre-packaged versions of tree-sitter
|
||||
parsers for many languages.
|
||||
|
||||
Aider needs to be easy for users to install in many environments,
|
||||
and it is probably too complex to add dependencies on
|
||||
additional individual tree-sitter parsers.
|
||||
|
||||
language parsers.
|
||||
This makes it easy for users to install aider in many diverse environments.
|
||||
You probably need to work with that project to get your language
|
||||
supported, which will easily allow aider to lint that language.
|
||||
For repo-map support, you will also need to find or create a `tags.scm` file.
|
||||
|
||||
<!--[[[cog
|
||||
from aider.repomap import get_supported_languages_md
|
||||
@@ -215,8 +214,8 @@ cog.out(get_supported_languages_md())
|
||||
| rst | .rst | | ✓ |
|
||||
| ruby | .rb | ✓ | ✓ |
|
||||
| rust | .rs | ✓ | ✓ |
|
||||
| scala | .sc | | ✓ |
|
||||
| scala | .scala | | ✓ |
|
||||
| scala | .sc | ✓ | ✓ |
|
||||
| scala | .scala | ✓ | ✓ |
|
||||
| scheme | .scm | | ✓ |
|
||||
| scheme | .ss | | ✓ |
|
||||
| scss | .scss | | ✓ |
|
||||
|
||||
@@ -128,6 +128,6 @@ mod_dates = [get_last_modified_date(file) for file in files]
|
||||
latest_mod_date = max(mod_dates)
|
||||
cog.out(f"{latest_mod_date.strftime('%B %d, %Y.')}")
|
||||
]]]-->
|
||||
January 16, 2025.
|
||||
April 12, 2025.
|
||||
<!--[[[end]]]-->
|
||||
</p>
|
||||
|
||||
@@ -8,100 +8,261 @@ has_children: true
|
||||
|
||||
# Aider LLM Leaderboards
|
||||
|
||||
Aider works best with LLMs which are good at *editing* code, not just good at writing
|
||||
code.
|
||||
To evaluate an LLM's editing skill, aider uses benchmarks that
|
||||
assess a model's ability to consistently follow the system prompt
|
||||
to successfully edit code.
|
||||
Aider excels with LLMs skilled at writing and *editing* code,
|
||||
and uses benchmarks to
|
||||
evaluate an LLM's ability to follow instructions and edit code successfully without
|
||||
human intervention.
|
||||
[Aider's polyglot benchmark](https://aider.chat/2024/12/21/polyglot.html#the-polyglot-benchmark) tests LLMs on 225 challenging Exercism coding exercises across C++, Go, Java, JavaScript, Python, and Rust.
|
||||
|
||||
The leaderboards report the results from a number of popular LLMs.
|
||||
While [aider can connect to almost any LLM](/docs/llms.html),
|
||||
it works best with models that score well on the benchmarks.
|
||||
<h2 id="leaderboard-title">Aider polyglot coding leaderboard</h2>
|
||||
|
||||
|
||||
## Polyglot leaderboard
|
||||
|
||||
[Aider's polyglot benchmark](https://aider.chat/2024/12/21/polyglot.html#the-polyglot-benchmark)
|
||||
asks the LLM to edit source files to complete 225 coding exercises
|
||||
from Exercism.
|
||||
It contains exercises in many popular programming languages:
|
||||
C++, Go, Java, JavaScript, Python and Rust.
|
||||
The 225 exercises were purposely selected to be the *hardest*
|
||||
that Exercism offered in those languages, to provide
|
||||
a strong coding challenge to LLMs.
|
||||
|
||||
This benchmark measures the LLM's coding ability in popular languages,
|
||||
and whether it can
|
||||
write new code that integrates into existing code.
|
||||
The model also has to successfully apply all its changes to the source file without human intervention.
|
||||
|
||||
<input type="text" id="editSearchInput" placeholder="Search..." style="width: 100%; max-width: 800px; margin: 10px auto; padding: 8px; display: block; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<div id="controls-container" style="display: flex; align-items: center; width: 100%; max-width: 800px; margin: 10px auto; gap: 10px; box-sizing: border-box; padding: 0 5px; position: relative;">
|
||||
<input type="text" id="editSearchInput" placeholder="Search..." style="flex-grow: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<div id="view-mode-toggle" style="display: inline-flex; border: 1px solid #ccc; border-radius: 4px;">
|
||||
<button id="mode-view-btn" class="mode-button active" data-mode="view" style="padding: 8px 8px; border: none; border-radius: 3px 0 0 3px; cursor: pointer; font-size: 14px; line-height: 1.5; min-width: 50px;">View</button>
|
||||
<button id="mode-select-btn" class="mode-button" data-mode="select" style="padding: 8px 8px; border: none; background-color: #f8f9fa; border-radius: 0; cursor: pointer; border-left: 1px solid #ccc; font-size: 14px; line-height: 1.5; min-width: 50px;">Select</button>
|
||||
<button id="mode-detail-btn" class="mode-button" data-mode="detail" style="padding: 8px 8px; border: none; background-color: #f8f9fa; border-radius: 0 3px 3px 0; cursor: pointer; border-left: 1px solid #ccc; font-size: 14px; line-height: 1.5; min-width: 50px;">Detail</button>
|
||||
</div>
|
||||
<button id="close-controls-btn" style="width: 18px; height: 18px; padding: 0; border: 1px solid #ddd; border-radius: 50%; background-color: transparent; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 12px; margin-left: 4px; color: #999;">×</button>
|
||||
</div>
|
||||
|
||||
<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; width: 40px; text-align: center; vertical-align: middle;">
|
||||
<input type="checkbox" id="select-all-checkbox" style="display: none; cursor: pointer; vertical-align: middle;">
|
||||
</th> <!-- Header checkbox added here -->
|
||||
<th style="padding: 8px; text-align: left;">Model</th>
|
||||
<th style="padding: 8px; text-align: center;">Percent correct</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>
|
||||
<th style="padding: 8px; text-align: center;">Cost</th>
|
||||
<th style="padding: 8px; text-align: center; width: 25%">Percent correct</th>
|
||||
<th style="padding: 8px; text-align: center; width: 25%">Cost</th>
|
||||
<th style="padding: 8px; text-align: left;" class="col-command">Command</th>
|
||||
<th style="padding: 8px; text-align: center; width: 10%" class="col-conform">Correct edit format</th>
|
||||
<th style="padding: 8px; text-align: left; width: 10%" class="col-edit-format">Edit Format</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% assign max_cost = 0 %}
|
||||
{% for row in site.data.polyglot_leaderboard %}
|
||||
{% if row.total_cost > max_cost %}
|
||||
{% assign max_cost = row.total_cost %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if max_cost == 0 %}{% assign max_cost = 1 %}{% endif %}
|
||||
{% assign edit_sorted = site.data.polyglot_leaderboard | sort: 'pass_rate_2' | reverse %}
|
||||
{% for row in edit_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>
|
||||
<td style="padding: 8px; text-align: center;">{% if row.total_cost == 0 %}?{% else %}${{ row.total_cost | times: 1.0 | round: 2 }}{% endif %}</td>
|
||||
{% for row in edit_sorted %} {% comment %} Add loop index for unique IDs {% endcomment %}
|
||||
{% assign row_index = forloop.index0 %}
|
||||
<tr id="main-row-{{ row_index }}">
|
||||
<td style="padding: 8px; text-align: center; vertical-align: middle;">
|
||||
<button class="toggle-details" data-target="details-{{ row_index }}" style="background: none; border: none; cursor: pointer; font-size: 16px; padding: 0; vertical-align: middle;">▶</button>
|
||||
<input type="checkbox" class="row-selector" data-row-index="{{ row_index }}" style="display: none; cursor: pointer; vertical-align: middle;">
|
||||
</td>
|
||||
<td style="padding: 8px;"><span>{{ row.model }}</span></td>
|
||||
<td class="bar-cell">
|
||||
<div class="bar-viz" style="width: {{ row.pass_rate_2 }}%; background-color: rgba(40, 167, 69, 0.3); border-right: 1px solid rgba(40, 167, 69, 0.5);"></div>
|
||||
<span>{{ row.pass_rate_2 }}%</span>
|
||||
</td>
|
||||
<td class="bar-cell cost-bar-cell">
|
||||
{% if row.total_cost > 0 %}
|
||||
<div class="bar-viz cost-bar" data-cost="{{ row.total_cost }}" data-max-cost="{{ max_cost }}" style="width: 0%; background-color: rgba(13, 110, 253, 0.3); border-right: 1px solid rgba(13, 110, 253, 0.5);"></div>
|
||||
{% endif %}
|
||||
{% assign rounded_cost = row.total_cost | times: 1.0 | round: 2 %}
|
||||
<span>{% if row.total_cost == 0 or rounded_cost == 0.00 %}{% else %}${{ rounded_cost }}{% endif %}</span>
|
||||
</td>
|
||||
<td style="padding: 8px;" class="col-command"><span><code>{{ row.command }}</code></span></td>
|
||||
<td style="padding: 8px; text-align: center;" class="col-conform"><span>{{ row.percent_cases_well_formed }}%</span></td>
|
||||
<td style="padding: 8px;" class="col-edit-format"><span>{{ row.edit_format }}</span></td>
|
||||
</tr>
|
||||
<tr class="details-row" id="details-{{ row_index }}" style="display: none; background-color: #f9f9f9;">
|
||||
<td colspan="7" style="padding: 15px; border-bottom: 1px solid #ddd;">
|
||||
<ul style="margin: 0; padding-left: 20px; list-style: none; border-bottom: 1px solid #ddd;">
|
||||
{% for pair in row %}
|
||||
{% if pair[1] != "" and pair[1] != nil %}
|
||||
<li><strong>
|
||||
{% if pair[0] == 'percent_cases_well_formed' %}
|
||||
Percent cases well formed
|
||||
{% else %}
|
||||
{{ pair[0] | replace: '_', ' ' | capitalize }}
|
||||
{% endif %}
|
||||
:</strong>
|
||||
{% if pair[0] == 'command' %}<code>{{ pair[1] }}</code>{% else %}{{ pair[1] }}{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Aider polyglot benchmark results
|
||||
|
||||
<canvas id="editChart" width="800" height="450" style="margin-top: 20px"></canvas>
|
||||
<script src="https://unpkg.com/patternomaly/dist/patternomaly.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
{% assign data_source = edit_sorted %}
|
||||
{% assign pass_rate_field = "pass_rate_2" %}
|
||||
{% assign highlight_model = "xxxxxx" %}
|
||||
{% include leaderboard.js %}
|
||||
</script>
|
||||
<style>
|
||||
#leaderboard-title {
|
||||
margin-bottom: 20px; /* Add space below the title */
|
||||
}
|
||||
tr.selected {
|
||||
color: #0056b3;
|
||||
}
|
||||
table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
thead {
|
||||
border-top: 1px solid #ddd; /* Add top border to header */
|
||||
}
|
||||
td, th {
|
||||
border: none; /* Remove internal cell borders */
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
vertical-align: middle; /* Ensure consistent vertical alignment */
|
||||
}
|
||||
td:nth-child(3), td:nth-child(4) {
|
||||
font-size: 12px;
|
||||
tbody tr {
|
||||
height: 50px; /* Set a minimum height for all data rows */
|
||||
}
|
||||
|
||||
/* Hide command and edit format columns on mobile */
|
||||
td.col-command { /* Command column */
|
||||
font-size: 12px; /* Keep font size adjustment for command column if desired, or remove */
|
||||
}
|
||||
|
||||
/* Hide new columns first on smaller screens */
|
||||
@media screen and (max-width: 991px) {
|
||||
th.col-conform, td.col-conform,
|
||||
th.col-edit-format, td.col-edit-format {
|
||||
display: none;
|
||||
}
|
||||
/* Increase width of Percent correct and Cost columns when others are hidden */
|
||||
th:nth-child(3), td:nth-child(3), /* Percent correct */
|
||||
th:nth-child(4), td:nth-child(4) { /* Cost */
|
||||
width: 33% !important; /* Override inline style */
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide command column on even smaller screens */
|
||||
@media screen and (max-width: 767px) {
|
||||
th:nth-child(4), td:nth-child(4), /* Command column */
|
||||
th:nth-child(5), td:nth-child(5) { /* Edit format column */
|
||||
th.col-command, td.col-command { /* Command column */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Control Styles --- */
|
||||
#controls-container {
|
||||
margin-bottom: 20px; /* Add some space below controls */
|
||||
}
|
||||
|
||||
#editSearchInput, #view-mode-select {
|
||||
padding: 8px 12px; /* Consistent padding */
|
||||
border: 1px solid #ccc; /* Slightly softer border */
|
||||
border-radius: 4px;
|
||||
font-size: 14px; /* Match table font size */
|
||||
height: 38px; /* Match height */
|
||||
box-sizing: border-box; /* Include padding/border in height */
|
||||
}
|
||||
|
||||
|
||||
.bar-cell {
|
||||
position: relative; /* Positioning context for the bar */
|
||||
padding: 8px;
|
||||
/* text-align: center; Removed */
|
||||
overflow: hidden; /* Prevent bar from overflowing cell boundaries if needed */
|
||||
}
|
||||
.cost-bar-cell {
|
||||
background-image: none; /* Remove default gradient for cost cells */
|
||||
}
|
||||
.percent-tick, .cost-tick {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(10px);
|
||||
height: 8px; /* Short tick */
|
||||
width: 1px;
|
||||
background-color: rgba(170, 170, 170, 0.5);
|
||||
z-index: 2; /* Above the bar but below the text */
|
||||
}
|
||||
.bar-viz {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%; /* Position at the middle of the cell */
|
||||
transform: translateY(-50%); /* Center the bar vertically */
|
||||
z-index: 1; /* Above background, below ticks and text */
|
||||
height: 36px;
|
||||
border-radius: 0 2px 2px 0; /* Slightly rounded end corners */
|
||||
/* Width and colors are set inline via style attribute */
|
||||
}
|
||||
/* Add a tooltip class for showing cost information on hover */
|
||||
.cost-bar-cell:hover .bar-viz[style*="background-image"] {
|
||||
animation: stripe-animation 2s linear infinite;
|
||||
}
|
||||
@keyframes stripe-animation {
|
||||
0% { background-position: 0 0; }
|
||||
100% { background-position: 20px 0; }
|
||||
}
|
||||
.bar-cell span {
|
||||
position: absolute; /* Position relative to the cell */
|
||||
left: 5px; /* Position slightly inside the left edge */
|
||||
top: 50%; /* Center vertically */
|
||||
transform: translateY(-50%); /* Adjust vertical centering */
|
||||
z-index: 3; /* Ensure text is above everything else */
|
||||
background-color: rgba(255, 255, 255, 0.7); /* Semi-transparent white background */
|
||||
padding: 0 4px; /* Add padding around the text */
|
||||
border-radius: 3px; /* Rounded corners for the text background */
|
||||
font-size: 14px; /* Adjust font size for the numbers */
|
||||
}
|
||||
.toggle-details {
|
||||
color: #888; /* Make toggle symbol more subtle */
|
||||
transition: color 0.2s; /* Smooth transition on hover */
|
||||
}
|
||||
|
||||
|
||||
/* Style for selected rows */
|
||||
tr.row-selected > td {
|
||||
background-color: #e7f3ff; /* Example light blue highlight */
|
||||
}
|
||||
|
||||
/* Ensure checkbox is vertically aligned if needed */
|
||||
.row-selector {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Hide rows not matching the filter */
|
||||
tr.hidden-by-mode {
|
||||
display: none !important; /* Use important to override other display styles if necessary */
|
||||
}
|
||||
tr.hidden-by-search {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* --- Mode Toggle Button Styles --- */
|
||||
#view-mode-toggle {
|
||||
height: 38px; /* Match input height */
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0; /* Prevent toggle from shrinking on small screens */
|
||||
}
|
||||
.mode-button {
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
white-space: nowrap; /* Prevent text wrapping */
|
||||
}
|
||||
.mode-button:not(.active) {
|
||||
background-color: #f8f9fa; /* Light grey background */
|
||||
color: #495057; /* Dark grey text */
|
||||
}
|
||||
.mode-button:not(.active):hover {
|
||||
background-color: #e2e6ea; /* Slightly darker grey on hover */
|
||||
}
|
||||
|
||||
/* Style for highlighted rows in view mode */
|
||||
tr.view-highlighted > td {
|
||||
background-color: #fffef5; /* Very light yellow/cream */
|
||||
/* Border moved to specific cell below */
|
||||
}
|
||||
/* Apply border and adjust padding ONLY for the first *visible* cell (Model name) in view mode */
|
||||
tr.view-highlighted > td:nth-child(2) {
|
||||
border-left: 4px solid #ffc107; /* Warning yellow border */
|
||||
/* Original padding is 8px. Subtract border width. */
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
{% include leaderboard_table.js %}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<p class="post-date">
|
||||
<p class="post-date" style="margin-top: 20px;">
|
||||
By Paul Gauthier,
|
||||
last updated
|
||||
<!--[[[cog
|
||||
@@ -124,6 +285,6 @@ mod_dates = [get_last_modified_date(file) for file in files]
|
||||
latest_mod_date = max(mod_dates)
|
||||
cog.out(f"{latest_mod_date.strftime('%B %d, %Y.')}")
|
||||
]]]-->
|
||||
March 25, 2025.
|
||||
April 20, 2025.
|
||||
<!--[[[end]]]-->
|
||||
</p>
|
||||
|
||||
@@ -73,6 +73,6 @@ mod_dates = [get_last_modified_date(file) for file in files]
|
||||
latest_mod_date = max(mod_dates)
|
||||
cog.out(f"{latest_mod_date.strftime('%B %d, %Y.')}")
|
||||
]]]-->
|
||||
January 16, 2025.
|
||||
April 12, 2025.
|
||||
<!--[[[end]]]-->
|
||||
</p>
|
||||
|
||||
@@ -98,7 +98,7 @@ if result.returncode == 0:
|
||||
date = datetime.datetime.fromtimestamp(timestamp)
|
||||
cog.out(f"{date.strftime('%B %d, %Y.')}")
|
||||
]]]-->
|
||||
December 06, 2024.
|
||||
April 12, 2025.
|
||||
<!--[[[end]]]-->
|
||||
|
||||
</p>
|
||||
|
||||
@@ -12,16 +12,16 @@ python -m pip install -U aider-chat
|
||||
|
||||
# Mac/Linux:
|
||||
export AZURE_API_KEY=<key>
|
||||
export AZURE_API_VERSION=2023-05-15
|
||||
export AZURE_API_VERSION=2024-12-01-preview
|
||||
export AZURE_API_BASE=https://myendpt.openai.azure.com
|
||||
|
||||
# Windows
|
||||
setx AZURE_API_KEY <key>
|
||||
setx AZURE_API_VERSION 2023-05-15
|
||||
setx AZURE_API_VERSION 2024-12-01-preview
|
||||
setx AZURE_API_BASE https://myendpt.openai.azure.com
|
||||
# ... restart your shell after setx commands
|
||||
|
||||
aider --model azure/<your_deployment_name>
|
||||
aider --model azure/<your_model_deployment_name>
|
||||
|
||||
# List models available from Azure
|
||||
aider --list-models azure/
|
||||
@@ -29,3 +29,9 @@ aider --list-models azure/
|
||||
|
||||
Note that aider will also use environment variables
|
||||
like `AZURE_OPENAI_API_xxx`.
|
||||
|
||||
The `aider --list-models azure/` command will list all models that aider supports through Azure, not the models that are available for the provided endpoint.
|
||||
|
||||
When setting the model to use with `--model azure/<your_model_deployment_name>`, `<your_model_deployment_name>` is likely just the name of the model you have deployed to the endpoint for example `o3-mini` or `gpt-4o`. The screenshow below shows `o3-mini` and `gpt-4o` deployments in the Azure portal done under the `myendpt` resource.
|
||||
|
||||

|
||||
@@ -9,16 +9,37 @@ You'll need a [xAI API key](https://console.x.ai.).
|
||||
|
||||
To use xAI:
|
||||
|
||||
```
|
||||
python -m pip install -U aider-chat
|
||||
```bash
|
||||
python -m pip install aider-install
|
||||
aider-install
|
||||
|
||||
export XAI_API_KEY=<key> # Mac/Linux
|
||||
setx XAI_API_KEY <key> # Windows, restart shell after setx
|
||||
|
||||
aider --model xai/grok-beta
|
||||
# Grok 3
|
||||
aider --model xai/grok-3-beta
|
||||
|
||||
# Grok 3 fast (faster, more expensive)
|
||||
aider --model xai/grok-3-fast-beta
|
||||
|
||||
# Grok 3 Mini
|
||||
aider --model xai/grok-3-mini-beta
|
||||
|
||||
# Grok 3 Mini fast (faster, more expensive)
|
||||
aider --model xai/grok-3-mini-fast-beta
|
||||
|
||||
# List models available from xAI
|
||||
aider --list-models xai/
|
||||
```
|
||||
|
||||
The Grok 3 Mini models support the `--reasoning-effort` flag.
|
||||
See the [reasoning settings documentation](../config/reasoning.md) for details.
|
||||
Example:
|
||||
|
||||
```bash
|
||||
aider --model xai/grok-3-mini-beta --reasoning-effort high
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ cog.out(model_list)
|
||||
- claude-3-sonnet-20240229
|
||||
- codestral/codestral-2405
|
||||
- codestral/codestral-latest
|
||||
- databricks/databricks-claude-3-7-sonnet
|
||||
- deepseek/deepseek-chat
|
||||
- deepseek/deepseek-coder
|
||||
- deepseek/deepseek-reasoner
|
||||
|
||||
@@ -87,6 +87,8 @@ Aider optimizes the repo map by
|
||||
selecting the most important parts of the codebase
|
||||
which will
|
||||
fit into the active token budget.
|
||||
The optimization identifies and maps the portions of the code base
|
||||
which are most relevant to the current state of the chat.
|
||||
|
||||
The token budget is
|
||||
influenced by the `--map-tokens` switch, which defaults to 1k tokens.
|
||||
|
||||
@@ -5,7 +5,27 @@ nav_order: 28
|
||||
|
||||
# Models and API keys
|
||||
|
||||
You need to tell aider which LLM to use and provide an API key.
|
||||
Aider needs to know which LLM model you would like to work with and which keys
|
||||
to provide when accessing it via API.
|
||||
|
||||
## Defaults
|
||||
|
||||
If you don't explicitly name a model, aider will try to select a model
|
||||
for you to work with.
|
||||
|
||||
First, aider will check which
|
||||
[keys you have provided via the environment, config files, or command line arguments](https://aider.chat/docs/config/api-keys.html).
|
||||
Based on the available keys, aider will select the best model to use.
|
||||
|
||||
If you have not provided any keys, aider will offer to help you connect to
|
||||
[OpenRouter](http://openrouter.ai)
|
||||
which provides both free and paid access to most popular LLMs.
|
||||
Once connected, aider will select the best model available on OpenRouter
|
||||
based on whether you have a free or paid account there.
|
||||
|
||||
## Specifying model & key
|
||||
|
||||
You can also tell aider which LLM to use and provide an API key.
|
||||
The easiest way is to use the `--model` and `--api-key`
|
||||
command line arguments, like this:
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ The interactive prompt is built with [prompt-toolkit](https://github.com/prompt-
|
||||
- `Ctrl-N` : Move down to the next history entry.
|
||||
- `Ctrl-P` : Move up to the previous history entry.
|
||||
- `Ctrl-R` : Reverse search in command history.
|
||||
- `Ctrl-X Ctrl-E` : Open the current input in an external editor
|
||||
- `Ctrl-Y` : Paste (yank) text that was previously cut.
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ description: Using the code, architect, ask and help chat modes.
|
||||
Aider has a few different chat modes:
|
||||
|
||||
- `code` - Aider will make changes to your code to satisfy your requests.
|
||||
- `architect` - Aider will first propose a solution, then ask if you want it to turn that proposal into edits to your files.
|
||||
- `ask` - Aider will answer questions about your code, but never edit it.
|
||||
- `help` - Aider will answer questions about using aider, configuring, troubleshooting, etc.
|
||||
- `ask` - Aider will discuss your code and answer questions about it, but never make changes.
|
||||
- `architect` - Like code mode, aider will change your files. An architect model will propose changes and an editor model will translate that proposal into specific file edits.
|
||||
- `help` - Aider will answer questions about aider: usage, configuration, troubleshooting, etc.
|
||||
|
||||
By default, aider starts in "code" mode. As you are talking, you can
|
||||
send individual messages in a specific mode using
|
||||
@@ -40,31 +40,104 @@ Or you can switch between coding modes using these commands without arguments:
|
||||
Or you can launch aider in one of the modes with the `--chat-mode <mode>` switch.
|
||||
There is also a special shortcut `--architect` to launch in `--chat-mode architect`.
|
||||
|
||||
The aider prompt will indicate the active mode:
|
||||
|
||||
```
|
||||
> This is code mode.
|
||||
ask> This is ask mode.
|
||||
architect> This is architect mode.
|
||||
```
|
||||
|
||||
|
||||
## Ask/code workflow
|
||||
|
||||
A recommended workflow is to bounce back and forth between `/ask` and `/code` modes.
|
||||
|
||||
Use ask mode to discuss what you want to do, get suggestions or options from aider
|
||||
and provide feedback on the approach.
|
||||
Once aider understands the mission, switch to code mode to have it start editing
|
||||
your files.
|
||||
All the conversation and decision making from ask mode will
|
||||
help ensure that the correct code changes are performed.
|
||||
|
||||
You can be very terse when you finally switch from ask to code mode.
|
||||
Saying something as simple as "go ahead" in code mode will
|
||||
have aider execute on the plan you've been discussing.
|
||||
|
||||
Here's an example with two ask mode messages to agree on the plan,
|
||||
followed by two terse code mode messages to edit the code.
|
||||
|
||||
````
|
||||
─────────────────────────────────────────────────────────────────────────────────────
|
||||
Aider v0.79.0
|
||||
Model: gemini/gemini-2.5-pro-exp-03-25 with diff-fenced edit format
|
||||
|
||||
> /ask What's the best thing to print if we're making a quick little demo program?
|
||||
|
||||
It sounds like you'd like to write a "Hello, world!" program. That's a very
|
||||
popular phrase to use for small demo programs.
|
||||
|
||||
> /ask Yes, that's the idea. What are some more creative options?
|
||||
|
||||
1. "Greetings, Earthlings!"
|
||||
2. "Beep boop, I am a computer!"
|
||||
3. "To code, or not to code, that is the question."
|
||||
4. "I think, therefore I print."
|
||||
5. "01001000 01101001!" (Binary for "Hi!")
|
||||
|
||||
> do 4
|
||||
|
||||
hello.py
|
||||
>>>>>>> SEARCH
|
||||
=======
|
||||
def main():
|
||||
print("I think, therefore I print.")
|
||||
<<<<<<< REPLACE
|
||||
|
||||
> ALL CAPS!
|
||||
|
||||
hello.py
|
||||
>>>>>>> SEARCH
|
||||
print("I think, therefore I print.")
|
||||
=======
|
||||
print("I THINK, THEREFORE I PRINT!")
|
||||
<<<<<<< REPLACE
|
||||
|
||||
````
|
||||
|
||||
You can think of this ask/code workflow as a more fluid version of
|
||||
architect mode, but working just with one model the whole time.
|
||||
|
||||
## Architect mode and the editor model
|
||||
|
||||
When you are in architect mode, aider sends your request to two models:
|
||||
When you are in architect mode, aider sends your requests to two models:
|
||||
|
||||
1. First, it sends your request to the main active model.
|
||||
The main model is configured with `/model`, `--model` or the shortcut switches like `--sonnet`.
|
||||
After the main model replies, aider will offer to edit the files based on the response.
|
||||
1. First, it sends your request to the main model which will act as an architect
|
||||
to propose how to solve your coding request.
|
||||
The main model is configured with `/model` or `--model`.
|
||||
|
||||
2. To edit the files, aider sends a second LLM request asking for specific code editing instructions.
|
||||
This request goes to the "editor" model.
|
||||
2. Aider then sends another request to an "editor model",
|
||||
asking it to turn the architect's proposal into specific file editing instructions.
|
||||
Aider has built in defaults to select an editor model based on your main model.
|
||||
Or, you can choose an editor model yourself with `--editor-model <model>`.
|
||||
Or, you can choose a specific editor model with `--editor-model <model>`.
|
||||
|
||||
Architect mode produces better results than code mode, but uses two LLM requests.
|
||||
This probably makes it slower and more expensive than using code mode.
|
||||
Certain LLMs aren't able to propose coding solutions *and*
|
||||
specify detailed file edits all in one go.
|
||||
For these models, architect mode can produce better results than code mode
|
||||
by pairing them
|
||||
with an editor model that is responsible for generating the file editing instructions.
|
||||
But this uses two LLM requests,
|
||||
which can take longer and increase costs.
|
||||
|
||||
Architect mode is especially useful with OpenAI's o1 models, which are strong at
|
||||
reasoning but less capable at editing files.
|
||||
Pairing an o1 architect with an editor model like GPT-4o or Sonnet will
|
||||
give the best results.
|
||||
|
||||
But architect mode is also quite helpful when you use GPT-4o or Sonnet
|
||||
at both the architect and the editor.
|
||||
But architect mode can also be helpful when you use the same model
|
||||
as both the architect and the editor.
|
||||
Allowing the model two requests to solve the problem and edit the files
|
||||
usually provides a better result.
|
||||
can sometimes provide better results.
|
||||
|
||||
The editor model uses one of aider's edit formats to let the LLM
|
||||
edit source files.
|
||||
@@ -91,9 +164,9 @@ for more details.
|
||||
|
||||
#### /ask What is this repo?
|
||||
|
||||
This is the source code to the popular django package.
|
||||
This is collection of python functions that compute various math functions.
|
||||
|
||||
#### /help How do I use ollama?
|
||||
#### /help How do I use aider with ollama?
|
||||
|
||||
Run `aider --model ollama/<ollama-model>`.
|
||||
See these docs for more info: https://aider.chat/docs/llms/ollama.html
|
||||
@@ -122,8 +195,6 @@ builtin.
|
||||
This way you don't have to maintain a custom factorial implementation,
|
||||
and the builtin function is well optimized.
|
||||
|
||||
> Edit the files? (Y)es/(N)o [Yes]: Yes
|
||||
|
||||
```python
|
||||
<<<<<<< SEARCH
|
||||
def factorial(n):
|
||||
|
||||
@@ -63,29 +63,29 @@ layout: none
|
||||
|
||||
<div class="stats-container">
|
||||
<!--[[[cog
|
||||
from scripts.badges import get_badges_html
|
||||
from scripts.homepage import get_badges_html
|
||||
text = get_badges_html()
|
||||
cog.out(text)
|
||||
]]]-->
|
||||
<a href="https://github.com/Aider-AI/aider" class="github-badge badge-stars" title="Total number of GitHub stars the Aider project has received">
|
||||
<span class="badge-label">⭐ GitHub Stars</span>
|
||||
<span class="badge-value">30K</span>
|
||||
<span class="badge-value">32K</span>
|
||||
</a>
|
||||
<a href="https://pypi.org/project/aider-chat/" class="github-badge badge-installs" title="Total number of installations via pip from PyPI">
|
||||
<span class="badge-label">📦 Installs</span>
|
||||
<span class="badge-value">1.7M</span>
|
||||
<span class="badge-value">2.0M</span>
|
||||
</a>
|
||||
<div class="github-badge badge-tokens" title="Number of tokens processed weekly by Aider users">
|
||||
<span class="badge-label">📈 Tokens/week</span>
|
||||
<span class="badge-value">15B</span>
|
||||
</div>
|
||||
<a href="https://openrouter.ai/" class="github-badge badge-router" title="Aider's ranking among applications on the OpenRouter platform">
|
||||
<a href="https://openrouter.ai/#options-menu" class="github-badge badge-router" title="Aider's ranking among applications on the OpenRouter platform">
|
||||
<span class="badge-label">🏆 OpenRouter</span>
|
||||
<span class="badge-value">Top 20</span>
|
||||
</a>
|
||||
<a href="/HISTORY.html" class="github-badge badge-coded" title="Percentage of the new code in Aider's last release written by Aider itself">
|
||||
<span class="badge-label">🔄 Singularity</span>
|
||||
<span class="badge-value">65%</span>
|
||||
<span class="badge-value">92%</span>
|
||||
</a>
|
||||
<!--[[[end]]]-->
|
||||
</div>
|
||||
@@ -237,110 +237,211 @@ aider --model o3-mini --api-key openai=<key></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// All testimonials from the README
|
||||
const testimonials = [
|
||||
{
|
||||
text: "The best free open source AI coding assistant.",
|
||||
author: "IndyDevDan",
|
||||
link: "https://youtu.be/YALpX8oOn78"
|
||||
},
|
||||
{
|
||||
text: "The best AI coding assistant so far.",
|
||||
author: "Matthew Berman",
|
||||
link: "https://www.youtube.com/watch?v=df8afeb1FY8"
|
||||
},
|
||||
{
|
||||
text: "Aider ... has easily quadrupled my coding productivity.",
|
||||
author: "SOLAR_FIELDS",
|
||||
link: "https://news.ycombinator.com/item?id=36212100"
|
||||
},
|
||||
{
|
||||
text: "It's a cool workflow... Aider's ergonomics are perfect for me.",
|
||||
author: "qup",
|
||||
link: "https://news.ycombinator.com/item?id=38185326"
|
||||
},
|
||||
{
|
||||
text: "It's really like having your senior developer live right in your Git repo - truly amazing!",
|
||||
author: "rappster",
|
||||
link: "https://github.com/Aider-AI/aider/issues/124"
|
||||
},
|
||||
{
|
||||
text: "What an amazing tool. It's incredible.",
|
||||
author: "valyagolev",
|
||||
link: "https://github.com/Aider-AI/aider/issues/6#issue-1722897858"
|
||||
},
|
||||
{
|
||||
text: "Aider is such an astounding thing!",
|
||||
author: "cgrothaus",
|
||||
link: "https://github.com/Aider-AI/aider/issues/82#issuecomment-1631876700"
|
||||
},
|
||||
{
|
||||
text: "It was WAY faster than I would be getting off the ground and making the first few working versions.",
|
||||
author: "Daniel Feldman",
|
||||
link: "https://twitter.com/d_feldman/status/1662295077387923456"
|
||||
},
|
||||
{
|
||||
text: "THANK YOU for Aider! It really feels like a glimpse into the future of coding.",
|
||||
author: "derwiki",
|
||||
link: "https://news.ycombinator.com/item?id=38205643"
|
||||
},
|
||||
{
|
||||
text: "It's just amazing. It is freeing me to do things I felt were out my comfort zone before.",
|
||||
author: "Dougie",
|
||||
link: "https://discord.com/channels/1131200896827654144/1174002618058678323/1174084556257775656"
|
||||
},
|
||||
{
|
||||
text: "This project is stellar.",
|
||||
author: "funkytaco",
|
||||
link: "https://github.com/Aider-AI/aider/issues/112#issuecomment-1637429008"
|
||||
},
|
||||
{
|
||||
text: "Amazing project, definitely the best AI coding assistant I've used.",
|
||||
author: "joshuavial",
|
||||
link: "https://github.com/Aider-AI/aider/issues/84"
|
||||
},
|
||||
{
|
||||
text: "I absolutely love using Aider ... It makes software development feel so much lighter as an experience.",
|
||||
author: "principalideal0",
|
||||
link: "https://discord.com/channels/1131200896827654144/1133421607499595858/1229689636012691468"
|
||||
},
|
||||
{
|
||||
text: "I have been recovering from multiple shoulder surgeries ... and have used aider extensively. It has allowed me to continue productivity.",
|
||||
author: "codeninja",
|
||||
link: "https://www.reddit.com/r/OpenAI/s/nmNwkHy1zG"
|
||||
},
|
||||
{
|
||||
text: "I am an aider addict. I'm getting so much more work done, but in less time.",
|
||||
author: "dandandan",
|
||||
link: "https://discord.com/channels/1131200896827654144/1131200896827654149/1135913253483069470"
|
||||
},
|
||||
{
|
||||
text: "After wasting $100 on tokens trying to find something better, I'm back to Aider. It blows everything else out of the water hands down, there's no competition whatsoever.",
|
||||
author: "SystemSculpt",
|
||||
link: "https://discord.com/channels/1131200896827654144/1131200896827654149/1178736602797846548"
|
||||
},
|
||||
{
|
||||
text: "Aider is amazing, coupled with Sonnet 3.5 it's quite mind blowing.",
|
||||
author: "Josh Dingus",
|
||||
link: "https://discord.com/channels/1131200896827654144/1133060684540813372/1262374225298198548"
|
||||
},
|
||||
{
|
||||
text: "Hands down, this is the best AI coding assistant tool so far.",
|
||||
author: "IndyDevDan",
|
||||
link: "https://www.youtube.com/watch?v=MPYFPvxfGZs"
|
||||
},
|
||||
{
|
||||
text: "[Aider] changed my daily coding workflows. It's mind-blowing how a single Python application can change your life.",
|
||||
author: "maledorak",
|
||||
link: "https://discord.com/channels/1131200896827654144/1131200896827654149/1258453375620747264"
|
||||
},
|
||||
{
|
||||
text: "Best agent for actual dev work in existing codebases.",
|
||||
author: "Nick Dobos",
|
||||
link: "https://twitter.com/NickADobos/status/1690408967963652097?s=20"
|
||||
}
|
||||
];
|
||||
<style>
|
||||
.testimonial-card {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition: opacity 0.4s ease, transform 0.4s ease, height 0.4s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.testimonial-text {
|
||||
line-height: 1.5;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.testimonial-author {
|
||||
margin-top: auto;
|
||||
padding-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--[[[cog
|
||||
from scripts.homepage import get_testimonials_js
|
||||
text = get_testimonials_js()
|
||||
cog.out(text)
|
||||
]]]-->
|
||||
<script>
|
||||
const testimonials = [
|
||||
{
|
||||
text: "My life has changed this week. There's finally an AI coding tool that's good enough to keep up with me... Aider... It's going to rock your world.",
|
||||
author: "Eric S. Raymond",
|
||||
link: "https://x.com/esrtweet/status/1910809356381413593"
|
||||
},
|
||||
{
|
||||
text: "The best free open source AI coding assistant.",
|
||||
author: "IndyDevDan",
|
||||
link: "https://youtu.be/YALpX8oOn78"
|
||||
},
|
||||
{
|
||||
text: "The best AI coding assistant so far.",
|
||||
author: "Matthew Berman",
|
||||
link: "https://www.youtube.com/watch?v=df8afeb1FY8"
|
||||
},
|
||||
{
|
||||
text: "Aider ... has easily quadrupled my coding productivity.",
|
||||
author: "SOLAR_FIELDS",
|
||||
link: "https://news.ycombinator.com/item?id=36212100"
|
||||
},
|
||||
{
|
||||
text: "It's a cool workflow... Aider's ergonomics are perfect for me.",
|
||||
author: "qup",
|
||||
link: "https://news.ycombinator.com/item?id=38185326"
|
||||
},
|
||||
{
|
||||
text: "It's really like having your senior developer live right in your Git repo - truly amazing!",
|
||||
author: "rappster",
|
||||
link: "https://github.com/Aider-AI/aider/issues/124"
|
||||
},
|
||||
{
|
||||
text: "What an amazing tool. It's incredible.",
|
||||
author: "valyagolev",
|
||||
link: "https://github.com/Aider-AI/aider/issues/6#issue-1722897858"
|
||||
},
|
||||
{
|
||||
text: "Aider is such an astounding thing!",
|
||||
author: "cgrothaus",
|
||||
link: "https://github.com/Aider-AI/aider/issues/82#issuecomment-1631876700"
|
||||
},
|
||||
{
|
||||
text: "It was WAY faster than I would be getting off the ground and making the first few working versions.",
|
||||
author: "Daniel Feldman",
|
||||
link: "https://twitter.com/d_feldman/status/1662295077387923456"
|
||||
},
|
||||
{
|
||||
text: "THANK YOU for Aider! It really feels like a glimpse into the future of coding.",
|
||||
author: "derwiki",
|
||||
link: "https://news.ycombinator.com/item?id=38205643"
|
||||
},
|
||||
{
|
||||
text: "It's just amazing. It is freeing me to do things I felt were out my comfort zone before.",
|
||||
author: "Dougie",
|
||||
link: "https://discord.com/channels/1131200896827654144/1174002618058678323/1174084556257775656"
|
||||
},
|
||||
{
|
||||
text: "This project is stellar.",
|
||||
author: "funkytaco",
|
||||
link: "https://github.com/Aider-AI/aider/issues/112#issuecomment-1637429008"
|
||||
},
|
||||
{
|
||||
text: "Amazing project, definitely the best AI coding assistant I've used.",
|
||||
author: "joshuavial",
|
||||
link: "https://github.com/Aider-AI/aider/issues/84"
|
||||
},
|
||||
{
|
||||
text: "I absolutely love using Aider ... It makes software development feel so much lighter as an experience.",
|
||||
author: "principalideal0",
|
||||
link: "https://discord.com/channels/1131200896827654144/1133421607499595858/1229689636012691468"
|
||||
},
|
||||
{
|
||||
text: "I have been recovering from multiple shoulder surgeries ... and have used aider extensively. It has allowed me to continue productivity.",
|
||||
author: "codeninja",
|
||||
link: "https://www.reddit.com/r/OpenAI/s/nmNwkHy1zG"
|
||||
},
|
||||
{
|
||||
text: "I am an aider addict. I'm getting so much more work done, but in less time.",
|
||||
author: "dandandan",
|
||||
link: "https://discord.com/channels/1131200896827654144/1131200896827654149/1135913253483069470"
|
||||
},
|
||||
{
|
||||
text: "After wasting $100 on tokens trying to find something better, I'm back to Aider. It blows everything else out of the water hands down, there's no competition whatsoever.",
|
||||
author: "SystemSculpt",
|
||||
link: "https://discord.com/channels/1131200896827654144/1131200896827654149/1178736602797846548"
|
||||
},
|
||||
{
|
||||
text: "Aider is amazing, coupled with Sonnet 3.5 it's quite mind blowing.",
|
||||
author: "Josh Dingus",
|
||||
link: "https://discord.com/channels/1131200896827654144/1133060684540813372/1262374225298198548"
|
||||
},
|
||||
{
|
||||
text: "Hands down, this is the best AI coding assistant tool so far.",
|
||||
author: "IndyDevDan",
|
||||
link: "https://www.youtube.com/watch?v=MPYFPvxfGZs"
|
||||
},
|
||||
{
|
||||
text: "[Aider] changed my daily coding workflows. It's mind-blowing how a single Python application can change your life.",
|
||||
author: "maledorak",
|
||||
link: "https://discord.com/channels/1131200896827654144/1131200896827654149/1258453375620747264"
|
||||
},
|
||||
{
|
||||
text: "Best agent for actual dev work in existing codebases.",
|
||||
author: "Nick Dobos",
|
||||
link: "https://twitter.com/NickADobos/status/1690408967963652097?s=20"
|
||||
},
|
||||
{
|
||||
text: "One of my favorite pieces of software. Blazing trails on new paradigms!",
|
||||
author: "Chris Wall",
|
||||
link: "https://x.com/chris65536/status/1905053299251798432"
|
||||
},
|
||||
{
|
||||
text: "Aider has been revolutionary for me and my work.",
|
||||
author: "Starry Hope",
|
||||
link: "https://x.com/starryhopeblog/status/1904985812137132056"
|
||||
},
|
||||
{
|
||||
text: "Try aider! One of the best ways to vibe code.",
|
||||
author: "Chris Wall",
|
||||
link: "https://x.com/Chris65536/status/1905053418961391929"
|
||||
},
|
||||
{
|
||||
text: "Aider is hands down the best. And it's free and opensource.",
|
||||
author: "AriyaSavakaLurker",
|
||||
link: "https://www.reddit.com/r/ChatGPTCoding/comments/1ik16y6/whats_your_take_on_aider/mbip39n/"
|
||||
},
|
||||
{
|
||||
text: "Aider is also my best friend.",
|
||||
author: "jzn21",
|
||||
link: "https://www.reddit.com/r/ChatGPTCoding/comments/1heuvuo/aider_vs_cline_vs_windsurf_vs_cursor/m27dcnb/"
|
||||
},
|
||||
{
|
||||
text: "Try Aider, it's worth it.",
|
||||
author: "jorgejhms",
|
||||
link: "https://www.reddit.com/r/ChatGPTCoding/comments/1heuvuo/aider_vs_cline_vs_windsurf_vs_cursor/m27cp99/"
|
||||
},
|
||||
{
|
||||
text: "I like aider :)",
|
||||
author: "Chenwei Cui",
|
||||
link: "https://x.com/ccui42/status/1904965344999145698"
|
||||
},
|
||||
{
|
||||
text: "Aider is the precision tool of LLM code gen. It is minimal, thoughtful and capable of surgical changes to your codebase all while keeping the developer in control.",
|
||||
author: "Reilly Sweetland",
|
||||
link: "https://x.com/rsweetland/status/1904963807237259586"
|
||||
},
|
||||
{
|
||||
text: "Cannot believe aider vibe coded a 650 LOC feature across service and cli today in 1 shot.",
|
||||
author: "autopoietist",
|
||||
link: "https://discord.com/channels/1131200896827654144/1131200896827654149/1355675042259796101"
|
||||
},
|
||||
{
|
||||
text: "Oh no the secret is out! Yes, Aider is the best coding tool around. I highly, highly recommend it to anyone.",
|
||||
author: "Joshua D Vander Hook",
|
||||
link: "https://x.com/jodavaho/status/1911154899057795218"
|
||||
},
|
||||
{
|
||||
text: "thanks to aider, i have started and finished three personal projects within the last two days",
|
||||
author: "joseph stalzyn",
|
||||
link: "https://x.com/anitaheeder/status/1908338609645904160"
|
||||
},
|
||||
{
|
||||
text: "Been using aider as my daily driver for over a year ... I absolutely love the tool, like beyond words.",
|
||||
author: "koleok",
|
||||
link: "https://discord.com/channels/1131200896827654144/1273248471394291754/1356727448372252783"
|
||||
},
|
||||
{
|
||||
text: "aider is really cool",
|
||||
author: "kache (@yacineMTB)",
|
||||
link: "https://x.com/yacineMTB/status/1911224442430124387"
|
||||
}
|
||||
];
|
||||
</script>
|
||||
<!--[[[end]]]-->
|
||||
<script>
|
||||
|
||||
// Function to shuffle array
|
||||
function shuffleArray(array) {
|
||||
@@ -378,6 +479,32 @@ aider --model o3-mini --api-key openai=<key></code></pre>
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
// Set a fixed height for all testimonial cards after they've been created
|
||||
setTimeout(setMaxTestimonialHeight, 50);
|
||||
}
|
||||
|
||||
// Calculate and set the maximum height for all testimonial cards
|
||||
function setMaxTestimonialHeight() {
|
||||
const cards = document.querySelectorAll('.testimonial-card');
|
||||
if (!cards.length) return;
|
||||
|
||||
// Find the maximum height among all cards
|
||||
let maxHeight = 0;
|
||||
cards.forEach(card => {
|
||||
// Temporarily remove any set height to get the natural height
|
||||
card.style.height = 'auto';
|
||||
const height = card.offsetHeight;
|
||||
maxHeight = Math.max(maxHeight, height);
|
||||
});
|
||||
|
||||
// Add a bit of extra padding to ensure enough space
|
||||
maxHeight += 20;
|
||||
|
||||
// Set the max height on all cards
|
||||
cards.forEach(card => {
|
||||
card.style.height = `${maxHeight}px`;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to update a single testimonial
|
||||
@@ -423,19 +550,30 @@ aider --model o3-mini --api-key openai=<key></code></pre>
|
||||
// Update the displayed testimonial
|
||||
const testimonialElement = document.getElementById(`testimonial-${index}`);
|
||||
if (testimonialElement) {
|
||||
// Start the flip animation
|
||||
testimonialElement.style.transform = "rotateY(90deg)";
|
||||
// Start fade out with slight upward movement
|
||||
testimonialElement.style.transition = "opacity 0.4s ease, transform 0.4s ease";
|
||||
testimonialElement.style.opacity = "0";
|
||||
testimonialElement.style.transform = "translateY(10px)";
|
||||
|
||||
// Update content when card is perpendicular to view (hidden)
|
||||
// Update content when fully faded out
|
||||
setTimeout(() => {
|
||||
// Keep the current height during the content swap
|
||||
const currentHeight = testimonialElement.style.height;
|
||||
|
||||
testimonialElement.innerHTML = `
|
||||
<p class="testimonial-text">${newTestimonial.text}</p>
|
||||
<p class="testimonial-author">— <a href="${newTestimonial.link}" target="_blank">${newTestimonial.author}</a></p>
|
||||
`;
|
||||
|
||||
// Complete the flip
|
||||
testimonialElement.style.transform = "rotateY(0deg)";
|
||||
}, 300);
|
||||
// Ensure the height remains consistent
|
||||
testimonialElement.style.height = currentHeight;
|
||||
|
||||
// Start fade in
|
||||
setTimeout(() => {
|
||||
testimonialElement.style.opacity = "1";
|
||||
testimonialElement.style.transform = "translateY(0)";
|
||||
}, 50);
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,7 +609,7 @@ aider --model o3-mini --api-key openai=<key></code></pre>
|
||||
|
||||
// Store this index as the last flipped
|
||||
lastFlippedIndex = randomIndex;
|
||||
}, 3000);
|
||||
}, 1500);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ You can run `./benchmark/benchmark.py --help` for a list of all the arguments, b
|
||||
- `--threads` specifies how many exercises to benchmark in parallel. Start with a single thread if you are working out the kinks on your benchmarking setup or working with a new model, etc. Once you are getting reliable results, you can speed up the process by running with more threads. 10 works well against the OpenAI APIs.
|
||||
- `--num-tests` specifies how many of the tests to run before stopping. This is another way to start gently as you debug your benchmarking setup.
|
||||
- `--keywords` filters the tests to run to only the ones whose name match the supplied argument (similar to `pytest -k xxxx`).
|
||||
- `--read-model-settings=<filename.yml>` specify model settings, see here: https://aider.chat/docs/config/adv-model-settings.html#model-settings
|
||||
|
||||
### Benchmark report
|
||||
|
||||
|
||||
@@ -206,6 +206,9 @@ def main(
|
||||
read_model_settings: str = typer.Option(
|
||||
None, "--read-model-settings", help="Load aider model settings from YAML file"
|
||||
),
|
||||
reasoning_effort: Optional[str] = typer.Option(
|
||||
None, "--reasoning-effort", help="Set reasoning effort for models that support it"
|
||||
),
|
||||
exercises_dir: str = typer.Option(
|
||||
EXERCISES_DIR_DEFAULT, "--exercises-dir", help="Directory with exercise files"
|
||||
),
|
||||
@@ -362,6 +365,7 @@ def main(
|
||||
editor_edit_format,
|
||||
num_ctx,
|
||||
sleep,
|
||||
reasoning_effort,
|
||||
)
|
||||
|
||||
all_results.append(results)
|
||||
@@ -384,6 +388,9 @@ def main(
|
||||
replay,
|
||||
editor_model,
|
||||
editor_edit_format,
|
||||
num_ctx,
|
||||
sleep,
|
||||
reasoning_effort,
|
||||
)
|
||||
all_results = run_test_threaded.gather(tqdm=True)
|
||||
|
||||
@@ -481,6 +488,7 @@ def summarize_results(dirname, stats_languages=None):
|
||||
res.indentation_errors = 0
|
||||
res.lazy_comments = 0
|
||||
|
||||
res.reasoning_effort = None
|
||||
variants = defaultdict(set)
|
||||
|
||||
for results in all_results:
|
||||
@@ -509,6 +517,8 @@ def summarize_results(dirname, stats_languages=None):
|
||||
res.syntax_errors += results.get("syntax_errors", 0)
|
||||
res.indentation_errors += results.get("indentation_errors", 0)
|
||||
|
||||
res.reasoning_effort = results.get("reasoning_effort")
|
||||
|
||||
for key in "model edit_format commit_hash editor_model editor_edit_format".split():
|
||||
val = results.get(key)
|
||||
if val:
|
||||
@@ -552,6 +562,9 @@ def summarize_results(dirname, stats_languages=None):
|
||||
setattr(res, key, val)
|
||||
console.print(f" {key}: {val}", style=style)
|
||||
|
||||
if res.reasoning_effort is not None:
|
||||
print(f" reasoning_effort: {res.reasoning_effort}")
|
||||
|
||||
for i in range(tries):
|
||||
print(f" pass_rate_{i + 1}: {percents[i]:.1f}")
|
||||
for i in range(tries):
|
||||
@@ -663,6 +676,7 @@ def run_test_real(
|
||||
editor_edit_format,
|
||||
num_ctx=None,
|
||||
sleep=0,
|
||||
reasoning_effort: Optional[str] = None,
|
||||
read_model_settings=None,
|
||||
):
|
||||
if not os.path.isdir(testdir):
|
||||
@@ -767,8 +781,12 @@ def run_test_real(
|
||||
weak_model=weak_model_name,
|
||||
editor_model=editor_model,
|
||||
editor_edit_format=editor_edit_format,
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
if reasoning_effort is not None:
|
||||
main_model.set_reasoning_effort(reasoning_effort)
|
||||
|
||||
dump(main_model.max_chat_history_tokens)
|
||||
|
||||
if num_ctx:
|
||||
@@ -919,6 +937,7 @@ def run_test_real(
|
||||
syntax_errors=syntax_errors,
|
||||
indentation_errors=indentation_errors,
|
||||
lazy_comments=lazy_comments, # Add the count of pattern matches to the results
|
||||
reasoning_effort=reasoning_effort,
|
||||
chat_hashes=list(
|
||||
zip(
|
||||
coder.chat_completion_call_hashes,
|
||||
|
||||
@@ -4,7 +4,7 @@ aiohappyeyeballs==2.6.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
aiohttp==3.11.14
|
||||
aiohttp==3.11.16
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# litellm
|
||||
@@ -77,7 +77,7 @@ filelock==3.18.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# huggingface-hub
|
||||
flake8==7.1.2
|
||||
flake8==7.2.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
@@ -86,7 +86,7 @@ frozenlist==1.5.0
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
fsspec==2025.3.0
|
||||
fsspec==2025.3.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# huggingface-hub
|
||||
@@ -106,7 +106,7 @@ h11==0.14.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# httpcore
|
||||
httpcore==1.0.7
|
||||
httpcore==1.0.8
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# httpx
|
||||
@@ -115,7 +115,7 @@ httpx==0.28.1
|
||||
# -c requirements/common-constraints.txt
|
||||
# litellm
|
||||
# openai
|
||||
huggingface-hub==0.29.3
|
||||
huggingface-hub==0.30.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# tokenizers
|
||||
@@ -143,7 +143,7 @@ jiter==0.9.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# openai
|
||||
json5==0.10.0
|
||||
json5==0.12.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
@@ -156,7 +156,7 @@ jsonschema-specifications==2024.10.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# jsonschema
|
||||
litellm==1.63.11
|
||||
litellm==1.65.7
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
@@ -184,7 +184,7 @@ monotonic==1.6
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# posthog
|
||||
multidict==6.2.0
|
||||
multidict==6.4.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
@@ -198,7 +198,7 @@ numpy==1.26.4
|
||||
# -c requirements/common-constraints.txt
|
||||
# scipy
|
||||
# soundfile
|
||||
openai==1.66.3
|
||||
openai==1.73.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# litellm
|
||||
@@ -224,7 +224,7 @@ pip==25.0.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
posthog==3.21.0
|
||||
posthog==3.24.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
@@ -232,7 +232,7 @@ prompt-toolkit==3.0.50
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
propcache==0.3.0
|
||||
propcache==0.3.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
@@ -245,7 +245,7 @@ ptyprocess==0.7.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pexpect
|
||||
pycodestyle==2.12.1
|
||||
pycodestyle==2.13.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# flake8
|
||||
@@ -253,12 +253,12 @@ pycparser==2.22
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# cffi
|
||||
pydantic==2.10.6
|
||||
pydantic==2.11.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# litellm
|
||||
# openai
|
||||
pydantic-core==2.27.2
|
||||
pydantic-core==2.33.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pydantic
|
||||
@@ -266,7 +266,7 @@ pydub==0.25.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
pyflakes==3.2.0
|
||||
pyflakes==3.3.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# flake8
|
||||
@@ -286,7 +286,7 @@ python-dateutil==2.9.0.post0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# posthog
|
||||
python-dotenv==1.0.1
|
||||
python-dotenv==1.1.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# litellm
|
||||
@@ -311,11 +311,11 @@ requests==2.32.3
|
||||
# mixpanel
|
||||
# posthog
|
||||
# tiktoken
|
||||
rich==13.9.4
|
||||
rich==14.0.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
rpds-py==0.23.1
|
||||
rpds-py==0.24.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# jsonschema
|
||||
@@ -379,7 +379,7 @@ tree-sitter-embedded-template==0.23.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# tree-sitter-language-pack
|
||||
tree-sitter-language-pack==0.6.1
|
||||
tree-sitter-language-pack==0.7.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# grep-ast
|
||||
@@ -387,7 +387,7 @@ tree-sitter-yaml==0.7.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# tree-sitter-language-pack
|
||||
typing-extensions==4.12.2
|
||||
typing-extensions==4.13.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# anyio
|
||||
@@ -397,12 +397,17 @@ typing-extensions==4.12.2
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# referencing
|
||||
urllib3==2.3.0
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pydantic
|
||||
urllib3==2.4.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# mixpanel
|
||||
# requests
|
||||
watchfiles==1.0.4
|
||||
watchfiles==1.0.5
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
@@ -410,7 +415,7 @@ wcwidth==0.2.13
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# prompt-toolkit
|
||||
yarl==1.18.3
|
||||
yarl==1.19.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# uv pip compile --no-strip-extras --output-file=requirements/common-constraints.txt requirements/requirements.in requirements/requirements-browser.in requirements/requirements-dev.in requirements/requirements-help.in requirements/requirements-playwright.in
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via aiohttp
|
||||
aiohttp==3.11.14
|
||||
aiohttp==3.11.16
|
||||
# via
|
||||
# huggingface-hub
|
||||
# litellm
|
||||
@@ -27,6 +27,8 @@ backoff==2.2.1
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# posthog
|
||||
banks==2.1.1
|
||||
# via llama-index-core
|
||||
beautifulsoup4==4.13.3
|
||||
# via -r requirements/requirements.in
|
||||
blinker==1.9.0
|
||||
@@ -34,7 +36,9 @@ blinker==1.9.0
|
||||
build==1.2.2.post1
|
||||
# via pip-tools
|
||||
cachetools==5.5.2
|
||||
# via streamlit
|
||||
# via
|
||||
# google-auth
|
||||
# streamlit
|
||||
certifi==2025.1.31
|
||||
# via
|
||||
# httpcore
|
||||
@@ -59,6 +63,8 @@ codespell==2.4.1
|
||||
# via -r requirements/requirements-dev.in
|
||||
cogapp==3.4.1
|
||||
# via -r requirements/requirements-dev.in
|
||||
colorama==0.4.6
|
||||
# via griffe
|
||||
configargparse==1.7
|
||||
# via -r requirements/requirements.in
|
||||
contourpy==1.3.1
|
||||
@@ -68,7 +74,9 @@ cycler==0.12.1
|
||||
dataclasses-json==0.6.7
|
||||
# via llama-index-core
|
||||
deprecated==1.2.18
|
||||
# via llama-index-core
|
||||
# via
|
||||
# banks
|
||||
# llama-index-core
|
||||
diff-match-patch==20241021
|
||||
# via -r requirements/requirements.in
|
||||
dill==0.3.9
|
||||
@@ -93,15 +101,15 @@ filelock==3.18.0
|
||||
# virtualenv
|
||||
filetype==1.2.0
|
||||
# via llama-index-core
|
||||
flake8==7.1.2
|
||||
flake8==7.2.0
|
||||
# via -r requirements/requirements.in
|
||||
fonttools==4.56.0
|
||||
fonttools==4.57.0
|
||||
# via matplotlib
|
||||
frozenlist==1.5.0
|
||||
# via
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
fsspec==2025.3.0
|
||||
fsspec==2025.3.2
|
||||
# via
|
||||
# huggingface-hub
|
||||
# llama-index-core
|
||||
@@ -112,22 +120,51 @@ gitpython==3.1.44
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# streamlit
|
||||
google-api-core[grpc]==2.24.2
|
||||
# via
|
||||
# google-cloud-bigquery
|
||||
# google-cloud-core
|
||||
google-auth==2.38.0
|
||||
# via
|
||||
# google-api-core
|
||||
# google-cloud-bigquery
|
||||
# google-cloud-core
|
||||
google-cloud-bigquery==3.31.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
google-cloud-core==2.4.3
|
||||
# via google-cloud-bigquery
|
||||
google-crc32c==1.7.1
|
||||
# via google-resumable-media
|
||||
google-resumable-media==2.7.2
|
||||
# via google-cloud-bigquery
|
||||
googleapis-common-protos==1.69.2
|
||||
# via
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
greenlet==3.1.1
|
||||
# via
|
||||
# playwright
|
||||
# sqlalchemy
|
||||
grep-ast==0.8.1
|
||||
# via -r requirements/requirements.in
|
||||
griffe==1.7.2
|
||||
# via banks
|
||||
grpcio==1.71.0
|
||||
# via
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio-status==1.71.0
|
||||
# via google-api-core
|
||||
h11==0.14.0
|
||||
# via httpcore
|
||||
httpcore==1.0.7
|
||||
httpcore==1.0.8
|
||||
# via httpx
|
||||
httpx==0.28.1
|
||||
# via
|
||||
# litellm
|
||||
# llama-index-core
|
||||
# openai
|
||||
huggingface-hub[inference]==0.29.3
|
||||
huggingface-hub[inference]==0.30.2
|
||||
# via
|
||||
# llama-index-embeddings-huggingface
|
||||
# sentence-transformers
|
||||
@@ -149,11 +186,12 @@ importlib-metadata==7.2.1
|
||||
# litellm
|
||||
importlib-resources==6.5.2
|
||||
# via -r requirements/requirements.in
|
||||
iniconfig==2.0.0
|
||||
iniconfig==2.1.0
|
||||
# via pytest
|
||||
jinja2==3.1.6
|
||||
# via
|
||||
# altair
|
||||
# banks
|
||||
# litellm
|
||||
# pydeck
|
||||
# torch
|
||||
@@ -163,7 +201,7 @@ joblib==1.4.2
|
||||
# via
|
||||
# nltk
|
||||
# scikit-learn
|
||||
json5==0.10.0
|
||||
json5==0.12.0
|
||||
# via -r requirements/requirements.in
|
||||
jsonschema==4.23.0
|
||||
# via
|
||||
@@ -174,13 +212,13 @@ jsonschema-specifications==2024.10.1
|
||||
# via jsonschema
|
||||
kiwisolver==1.4.8
|
||||
# via matplotlib
|
||||
litellm==1.63.11
|
||||
litellm==1.65.7
|
||||
# via -r requirements/requirements.in
|
||||
llama-index-core==0.12.24.post1
|
||||
llama-index-core==0.12.26
|
||||
# via
|
||||
# -r requirements/requirements-help.in
|
||||
# llama-index-embeddings-huggingface
|
||||
llama-index-embeddings-huggingface==0.5.2
|
||||
llama-index-embeddings-huggingface==0.5.3
|
||||
# via -r requirements/requirements-help.in
|
||||
lox==0.13.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
@@ -202,7 +240,7 @@ monotonic==1.6
|
||||
# via posthog
|
||||
mpmath==1.3.0
|
||||
# via sympy
|
||||
multidict==6.2.0
|
||||
multidict==6.4.3
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
@@ -210,7 +248,7 @@ multiprocess==0.70.17
|
||||
# via pathos
|
||||
mypy-extensions==1.0.0
|
||||
# via typing-inspect
|
||||
narwhals==1.31.0
|
||||
narwhals==1.34.1
|
||||
# via altair
|
||||
nest-asyncio==1.6.0
|
||||
# via llama-index-core
|
||||
@@ -236,13 +274,14 @@ numpy==1.26.4
|
||||
# soundfile
|
||||
# streamlit
|
||||
# transformers
|
||||
openai==1.66.3
|
||||
openai==1.73.0
|
||||
# via litellm
|
||||
packaging==24.2
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# altair
|
||||
# build
|
||||
# google-cloud-bigquery
|
||||
# huggingface-hub
|
||||
# marshmallow
|
||||
# matplotlib
|
||||
@@ -274,44 +313,60 @@ pip==25.0.1
|
||||
# pip-tools
|
||||
pip-tools==7.4.1
|
||||
# via -r requirements/requirements-dev.in
|
||||
platformdirs==4.3.6
|
||||
# via virtualenv
|
||||
playwright==1.50.0
|
||||
platformdirs==4.3.7
|
||||
# via
|
||||
# banks
|
||||
# virtualenv
|
||||
playwright==1.51.0
|
||||
# via -r requirements/requirements-playwright.in
|
||||
pluggy==1.5.0
|
||||
# via pytest
|
||||
posthog==3.21.0
|
||||
posthog==3.24.1
|
||||
# via -r requirements/requirements.in
|
||||
pox==0.3.5
|
||||
# via pathos
|
||||
ppft==1.7.6.9
|
||||
# via pathos
|
||||
pre-commit==4.1.0
|
||||
pre-commit==4.2.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
prompt-toolkit==3.0.50
|
||||
# via -r requirements/requirements.in
|
||||
propcache==0.3.0
|
||||
propcache==0.3.1
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
protobuf==5.29.3
|
||||
# via streamlit
|
||||
proto-plus==1.26.1
|
||||
# via google-api-core
|
||||
protobuf==5.29.4
|
||||
# via
|
||||
# google-api-core
|
||||
# googleapis-common-protos
|
||||
# grpcio-status
|
||||
# proto-plus
|
||||
# streamlit
|
||||
psutil==7.0.0
|
||||
# via -r requirements/requirements.in
|
||||
ptyprocess==0.7.0
|
||||
# via pexpect
|
||||
pyarrow==19.0.1
|
||||
# via streamlit
|
||||
pycodestyle==2.12.1
|
||||
pyasn1==0.6.1
|
||||
# via
|
||||
# pyasn1-modules
|
||||
# rsa
|
||||
pyasn1-modules==0.4.2
|
||||
# via google-auth
|
||||
pycodestyle==2.13.0
|
||||
# via flake8
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pydantic==2.10.6
|
||||
pydantic==2.11.3
|
||||
# via
|
||||
# banks
|
||||
# litellm
|
||||
# llama-index-core
|
||||
# openai
|
||||
pydantic-core==2.27.2
|
||||
pydantic-core==2.33.1
|
||||
# via pydantic
|
||||
pydeck==0.9.1
|
||||
# via streamlit
|
||||
@@ -319,13 +374,13 @@ pydub==0.25.1
|
||||
# via -r requirements/requirements.in
|
||||
pyee==12.1.1
|
||||
# via playwright
|
||||
pyflakes==3.2.0
|
||||
pyflakes==3.3.2
|
||||
# via flake8
|
||||
pygments==2.19.1
|
||||
# via rich
|
||||
pypandoc==1.15
|
||||
# via -r requirements/requirements.in
|
||||
pyparsing==3.2.1
|
||||
pyparsing==3.2.3
|
||||
# via matplotlib
|
||||
pyperclip==1.9.0
|
||||
# via -r requirements/requirements.in
|
||||
@@ -341,12 +396,13 @@ pytest-env==1.1.5
|
||||
# via -r requirements/requirements-dev.in
|
||||
python-dateutil==2.9.0.post0
|
||||
# via
|
||||
# google-cloud-bigquery
|
||||
# matplotlib
|
||||
# pandas
|
||||
# posthog
|
||||
python-dotenv==1.0.1
|
||||
python-dotenv==1.1.0
|
||||
# via litellm
|
||||
pytz==2025.1
|
||||
pytz==2025.2
|
||||
# via pandas
|
||||
pyyaml==6.0.2
|
||||
# via
|
||||
@@ -366,6 +422,8 @@ regex==2024.11.6
|
||||
# transformers
|
||||
requests==2.32.3
|
||||
# via
|
||||
# google-api-core
|
||||
# google-cloud-bigquery
|
||||
# huggingface-hub
|
||||
# llama-index-core
|
||||
# mixpanel
|
||||
@@ -373,14 +431,16 @@ requests==2.32.3
|
||||
# streamlit
|
||||
# tiktoken
|
||||
# transformers
|
||||
rich==13.9.4
|
||||
rich==14.0.0
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# typer
|
||||
rpds-py==0.23.1
|
||||
rpds-py==0.24.0
|
||||
# via
|
||||
# jsonschema
|
||||
# referencing
|
||||
rsa==4.9
|
||||
# via google-auth
|
||||
safetensors==0.5.3
|
||||
# via transformers
|
||||
scikit-learn==1.6.1
|
||||
@@ -392,9 +452,9 @@ scipy==1.13.1
|
||||
# sentence-transformers
|
||||
semver==3.0.4
|
||||
# via -r requirements/requirements-dev.in
|
||||
sentence-transformers==3.4.1
|
||||
sentence-transformers==4.0.2
|
||||
# via llama-index-embeddings-huggingface
|
||||
setuptools==76.0.0
|
||||
setuptools==78.1.0
|
||||
# via pip-tools
|
||||
shellingham==1.5.4
|
||||
# via typer
|
||||
@@ -417,13 +477,13 @@ soundfile==0.13.1
|
||||
# via -r requirements/requirements.in
|
||||
soupsieve==2.6
|
||||
# via beautifulsoup4
|
||||
sqlalchemy[asyncio]==2.0.39
|
||||
sqlalchemy[asyncio]==2.0.40
|
||||
# via llama-index-core
|
||||
streamlit==1.43.2
|
||||
streamlit==1.44.1
|
||||
# via -r requirements/requirements-browser.in
|
||||
sympy==1.13.3
|
||||
# via torch
|
||||
tenacity==9.0.0
|
||||
tenacity==9.1.2
|
||||
# via
|
||||
# llama-index-core
|
||||
# streamlit
|
||||
@@ -453,7 +513,7 @@ tqdm==4.67.1
|
||||
# openai
|
||||
# sentence-transformers
|
||||
# transformers
|
||||
transformers==4.49.0
|
||||
transformers==4.51.2
|
||||
# via sentence-transformers
|
||||
tree-sitter==0.24.0
|
||||
# via tree-sitter-language-pack
|
||||
@@ -461,13 +521,13 @@ tree-sitter-c-sharp==0.23.1
|
||||
# via tree-sitter-language-pack
|
||||
tree-sitter-embedded-template==0.23.2
|
||||
# via tree-sitter-language-pack
|
||||
tree-sitter-language-pack==0.6.1
|
||||
tree-sitter-language-pack==0.7.1
|
||||
# via grep-ast
|
||||
tree-sitter-yaml==0.7.0
|
||||
# via tree-sitter-language-pack
|
||||
typer==0.15.2
|
||||
# via -r requirements/requirements-dev.in
|
||||
typing-extensions==4.12.2
|
||||
typing-extensions==4.13.2
|
||||
# via
|
||||
# altair
|
||||
# anyio
|
||||
@@ -479,26 +539,30 @@ typing-extensions==4.12.2
|
||||
# pydantic-core
|
||||
# pyee
|
||||
# referencing
|
||||
# sentence-transformers
|
||||
# sqlalchemy
|
||||
# streamlit
|
||||
# torch
|
||||
# typer
|
||||
# typing-inspect
|
||||
# typing-inspection
|
||||
typing-inspect==0.9.0
|
||||
# via
|
||||
# dataclasses-json
|
||||
# llama-index-core
|
||||
tzdata==2025.1
|
||||
typing-inspection==0.4.0
|
||||
# via pydantic
|
||||
tzdata==2025.2
|
||||
# via pandas
|
||||
urllib3==2.3.0
|
||||
urllib3==2.4.0
|
||||
# via
|
||||
# mixpanel
|
||||
# requests
|
||||
uv==0.6.6
|
||||
uv==0.6.14
|
||||
# via -r requirements/requirements-dev.in
|
||||
virtualenv==20.29.3
|
||||
virtualenv==20.30.0
|
||||
# via pre-commit
|
||||
watchfiles==1.0.4
|
||||
watchfiles==1.0.5
|
||||
# via -r requirements/requirements.in
|
||||
wcwidth==0.2.13
|
||||
# via prompt-toolkit
|
||||
@@ -508,7 +572,7 @@ wrapt==1.17.2
|
||||
# via
|
||||
# deprecated
|
||||
# llama-index-core
|
||||
yarl==1.18.3
|
||||
yarl==1.19.0
|
||||
# via aiohttp
|
||||
zipp==3.21.0
|
||||
# via importlib-metadata
|
||||
|
||||
@@ -58,7 +58,7 @@ markupsafe==3.0.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# jinja2
|
||||
narwhals==1.31.0
|
||||
narwhals==1.34.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# altair
|
||||
@@ -81,7 +81,7 @@ pillow==11.1.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# streamlit
|
||||
protobuf==5.29.3
|
||||
protobuf==5.29.4
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# streamlit
|
||||
@@ -97,7 +97,7 @@ python-dateutil==2.9.0.post0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pandas
|
||||
pytz==2025.1
|
||||
pytz==2025.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pandas
|
||||
@@ -110,7 +110,7 @@ requests==2.32.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# streamlit
|
||||
rpds-py==0.23.1
|
||||
rpds-py==0.24.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# jsonschema
|
||||
@@ -123,11 +123,11 @@ smmap==5.0.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# gitdb
|
||||
streamlit==1.43.2
|
||||
streamlit==1.44.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-browser.in
|
||||
tenacity==9.0.0
|
||||
tenacity==9.1.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# streamlit
|
||||
@@ -139,17 +139,17 @@ tornado==6.4.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# streamlit
|
||||
typing-extensions==4.12.2
|
||||
typing-extensions==4.13.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# altair
|
||||
# referencing
|
||||
# streamlit
|
||||
tzdata==2025.1
|
||||
tzdata==2025.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pandas
|
||||
urllib3==2.3.0
|
||||
urllib3==2.4.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# requests
|
||||
|
||||
@@ -11,3 +11,4 @@ cogapp
|
||||
semver
|
||||
codespell
|
||||
uv
|
||||
google-cloud-bigquery
|
||||
|
||||
@@ -4,10 +4,22 @@ build==1.2.2.post1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pip-tools
|
||||
cachetools==5.5.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-auth
|
||||
certifi==2025.1.31
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# requests
|
||||
cfgv==3.4.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pre-commit
|
||||
charset-normalizer==3.4.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# requests
|
||||
click==8.1.8
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
@@ -42,19 +54,64 @@ filelock==3.18.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# virtualenv
|
||||
fonttools==4.56.0
|
||||
fonttools==4.57.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# matplotlib
|
||||
google-api-core[grpc]==2.24.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-cloud-bigquery
|
||||
# google-cloud-core
|
||||
google-auth==2.38.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
# google-cloud-bigquery
|
||||
# google-cloud-core
|
||||
google-cloud-bigquery==3.31.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
google-cloud-core==2.4.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-cloud-bigquery
|
||||
google-crc32c==1.7.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-resumable-media
|
||||
google-resumable-media==2.7.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-cloud-bigquery
|
||||
googleapis-common-protos==1.69.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio==1.71.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio-status==1.71.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
identify==2.6.9
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pre-commit
|
||||
idna==3.10
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# requests
|
||||
imgcat==0.6.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
iniconfig==2.0.0
|
||||
iniconfig==2.1.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pytest
|
||||
@@ -96,6 +153,7 @@ packaging==24.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# build
|
||||
# google-cloud-bigquery
|
||||
# matplotlib
|
||||
# pytest
|
||||
pandas==2.2.3
|
||||
@@ -118,7 +176,7 @@ pip-tools==7.4.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
platformdirs==4.3.6
|
||||
platformdirs==4.3.7
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# virtualenv
|
||||
@@ -134,15 +192,35 @@ ppft==1.7.6.9
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pathos
|
||||
pre-commit==4.1.0
|
||||
pre-commit==4.2.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
proto-plus==1.26.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
protobuf==5.29.4
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
# googleapis-common-protos
|
||||
# grpcio-status
|
||||
# proto-plus
|
||||
pyasn1==0.6.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pyasn1-modules
|
||||
# rsa
|
||||
pyasn1-modules==0.4.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-auth
|
||||
pygments==2.19.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# rich
|
||||
pyparsing==3.2.1
|
||||
pyparsing==3.2.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# matplotlib
|
||||
@@ -163,9 +241,10 @@ pytest-env==1.1.5
|
||||
python-dateutil==2.9.0.post0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-cloud-bigquery
|
||||
# matplotlib
|
||||
# pandas
|
||||
pytz==2025.1
|
||||
pytz==2025.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pandas
|
||||
@@ -173,15 +252,24 @@ pyyaml==6.0.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pre-commit
|
||||
rich==13.9.4
|
||||
requests==2.32.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
# google-cloud-bigquery
|
||||
rich==14.0.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# typer
|
||||
rsa==4.9
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-auth
|
||||
semver==3.0.4
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
setuptools==76.0.0
|
||||
setuptools==78.1.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pip-tools
|
||||
@@ -197,19 +285,23 @@ typer==0.15.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
typing-extensions==4.12.2
|
||||
typing-extensions==4.13.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# typer
|
||||
tzdata==2025.1
|
||||
tzdata==2025.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pandas
|
||||
uv==0.6.6
|
||||
urllib3==2.4.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# requests
|
||||
uv==0.6.14
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
virtualenv==20.29.3
|
||||
virtualenv==20.30.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pre-commit
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
llama-index-core
|
||||
llama-index-embeddings-huggingface
|
||||
|
||||
# Because sentence-transformers doesn't like >=2
|
||||
@@ -6,4 +5,7 @@ numpy<2
|
||||
|
||||
# Mac x86 only supports 2.2.2
|
||||
# https://discuss.pytorch.org/t/why-no-macosx-x86-64-build-after-torch-2-2-2-cp39-none-macosx-10-9-x86-64-whl/204546/2
|
||||
torch==2.2.2
|
||||
torch==2.2.2
|
||||
|
||||
# Later versions break test_help in GitHub Actions on Windows and Ubuntu
|
||||
llama-index-core==0.12.26
|
||||
@@ -4,7 +4,7 @@ aiohappyeyeballs==2.6.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
aiohttp==3.11.14
|
||||
aiohttp==3.11.16
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# huggingface-hub
|
||||
@@ -25,6 +25,10 @@ attrs==25.3.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
banks==2.1.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-core
|
||||
certifi==2025.1.31
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
@@ -39,6 +43,10 @@ click==8.1.8
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# nltk
|
||||
colorama==0.4.6
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# griffe
|
||||
dataclasses-json==0.6.7
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
@@ -46,6 +54,7 @@ dataclasses-json==0.6.7
|
||||
deprecated==1.2.18
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# banks
|
||||
# llama-index-core
|
||||
dirtyjson==1.0.8
|
||||
# via
|
||||
@@ -66,7 +75,7 @@ frozenlist==1.5.0
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
fsspec==2025.3.0
|
||||
fsspec==2025.3.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# huggingface-hub
|
||||
@@ -76,11 +85,15 @@ greenlet==3.1.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# sqlalchemy
|
||||
griffe==1.7.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# banks
|
||||
h11==0.14.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# httpcore
|
||||
httpcore==1.0.7
|
||||
httpcore==1.0.8
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# httpx
|
||||
@@ -88,7 +101,7 @@ httpx==0.28.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-core
|
||||
huggingface-hub[inference]==0.29.3
|
||||
huggingface-hub[inference]==0.30.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-embeddings-huggingface
|
||||
@@ -105,18 +118,19 @@ idna==3.10
|
||||
jinja2==3.1.6
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# banks
|
||||
# torch
|
||||
joblib==1.4.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# nltk
|
||||
# scikit-learn
|
||||
llama-index-core==0.12.24.post1
|
||||
llama-index-core==0.12.26
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-help.in
|
||||
# llama-index-embeddings-huggingface
|
||||
llama-index-embeddings-huggingface==0.5.2
|
||||
llama-index-embeddings-huggingface==0.5.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-help.in
|
||||
@@ -132,7 +146,7 @@ mpmath==1.3.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# sympy
|
||||
multidict==6.2.0
|
||||
multidict==6.4.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
@@ -173,16 +187,21 @@ pillow==11.1.0
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-core
|
||||
# sentence-transformers
|
||||
propcache==0.3.0
|
||||
platformdirs==4.3.7
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# banks
|
||||
propcache==0.3.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
# yarl
|
||||
pydantic==2.10.6
|
||||
pydantic==2.11.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# banks
|
||||
# llama-index-core
|
||||
pydantic-core==2.27.2
|
||||
pydantic-core==2.33.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pydantic
|
||||
@@ -218,7 +237,7 @@ scipy==1.13.1
|
||||
# -c requirements/common-constraints.txt
|
||||
# scikit-learn
|
||||
# sentence-transformers
|
||||
sentence-transformers==3.4.1
|
||||
sentence-transformers==4.0.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-embeddings-huggingface
|
||||
@@ -226,7 +245,7 @@ sniffio==1.3.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# anyio
|
||||
sqlalchemy[asyncio]==2.0.39
|
||||
sqlalchemy[asyncio]==2.0.40
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-core
|
||||
@@ -234,7 +253,7 @@ sympy==1.13.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# torch
|
||||
tenacity==9.0.0
|
||||
tenacity==9.1.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-core
|
||||
@@ -263,11 +282,11 @@ tqdm==4.67.1
|
||||
# nltk
|
||||
# sentence-transformers
|
||||
# transformers
|
||||
transformers==4.49.0
|
||||
transformers==4.51.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# sentence-transformers
|
||||
typing-extensions==4.12.2
|
||||
typing-extensions==4.13.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# anyio
|
||||
@@ -275,15 +294,21 @@ typing-extensions==4.12.2
|
||||
# llama-index-core
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# sentence-transformers
|
||||
# sqlalchemy
|
||||
# torch
|
||||
# typing-inspect
|
||||
# typing-inspection
|
||||
typing-inspect==0.9.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# dataclasses-json
|
||||
# llama-index-core
|
||||
urllib3==2.3.0
|
||||
typing-inspection==0.4.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pydantic
|
||||
urllib3==2.4.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# requests
|
||||
@@ -292,7 +317,7 @@ wrapt==1.17.2
|
||||
# -c requirements/common-constraints.txt
|
||||
# deprecated
|
||||
# llama-index-core
|
||||
yarl==1.18.3
|
||||
yarl==1.19.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# aiohttp
|
||||
|
||||
@@ -4,7 +4,7 @@ greenlet==3.1.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# playwright
|
||||
playwright==1.50.0
|
||||
playwright==1.51.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-playwright.in
|
||||
@@ -12,7 +12,7 @@ pyee==12.1.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# playwright
|
||||
typing-extensions==4.12.2
|
||||
typing-extensions==4.13.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# pyee
|
||||
|
||||
@@ -288,7 +288,7 @@ src="https://img.shields.io/github/stars/Aider-AI/aider?style=flat-square&logo=g
|
||||
src="https://img.shields.io/badge/📦%20Installs-{downloads_formatted}-2ecc71?style=flat-square&labelColor=555555"/></a>
|
||||
<img alt="Tokens per week" title="{TOKENS_WEEKLY_TOOLTIP}"
|
||||
src="https://img.shields.io/badge/📈%20Tokens%2Fweek-{TOKENS_PER_WEEK}-3498db?style=flat-square&labelColor=555555"/>
|
||||
<a href="https://openrouter.ai/"><img alt="OpenRouter Ranking" title="{OPENROUTER_TOOLTIP}"
|
||||
<a href="https://openrouter.ai/#options-menu"><img alt="OpenRouter Ranking" title="{OPENROUTER_TOOLTIP}"
|
||||
src="https://img.shields.io/badge/🏆%20OpenRouter-Top%2020-9b59b6?style=flat-square&labelColor=555555"/></a>
|
||||
<a href="https://aider.chat/HISTORY.html"><img alt="Singularity" title="{SINGULARITY_TOOLTIP}"
|
||||
src="https://img.shields.io/badge/🔄%20Singularity-{aider_percent_rounded}%25-e74c3c?style=flat-square&labelColor=555555"/></a>""" # noqa
|
||||
@@ -398,7 +398,7 @@ def get_badges_html():
|
||||
<span class="badge-label">📈 Tokens/week</span>
|
||||
<span class="badge-value">{TOKENS_PER_WEEK}</span>
|
||||
</div>
|
||||
<a href="https://openrouter.ai/" class="github-badge badge-router" title="{OPENROUTER_TOOLTIP}">
|
||||
<a href="https://openrouter.ai/#options-menu" class="github-badge badge-router" title="{OPENROUTER_TOOLTIP}">
|
||||
<span class="badge-label">🏆 OpenRouter</span>
|
||||
<span class="badge-value">Top 20</span>
|
||||
</a>
|
||||
@@ -410,6 +410,123 @@ def get_badges_html():
|
||||
return html
|
||||
|
||||
|
||||
def get_testimonials_js():
|
||||
"""
|
||||
Extract testimonials from README.md and format them as JavaScript array
|
||||
"""
|
||||
# Path to README.md, relative to this script
|
||||
readme_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "README.md"
|
||||
)
|
||||
|
||||
testimonials = []
|
||||
in_testimonials_section = False
|
||||
|
||||
try:
|
||||
with open(readme_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Find the testimonials section
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == "## Kind Words From Users":
|
||||
in_testimonials_section = True
|
||||
# Start processing from the next line
|
||||
start_idx = i + 1
|
||||
break
|
||||
|
||||
# If we found the section
|
||||
if in_testimonials_section:
|
||||
for i in range(start_idx, len(lines)):
|
||||
line = lines[i]
|
||||
# If we've hit another section, stop
|
||||
if line.startswith("##"):
|
||||
break
|
||||
|
||||
# Process testimonial lines
|
||||
if line.strip().startswith('- *"'):
|
||||
try:
|
||||
# Get the full line
|
||||
full_line = line.strip()
|
||||
|
||||
# Extract the quote text between *" and "*
|
||||
if '*"' in full_line and '"*' in full_line:
|
||||
quote_parts = full_line.split('*"')
|
||||
if len(quote_parts) > 1:
|
||||
quote_text = quote_parts[1].split('"*')[0].strip()
|
||||
|
||||
# Default values
|
||||
author = "Anonymous"
|
||||
link = ""
|
||||
|
||||
# Try to extract author and link if they exist
|
||||
# Check for the em dash format first: "— [author](link)"
|
||||
if "— [" in full_line and "](" in full_line:
|
||||
author_parts = full_line.split("— [")
|
||||
if len(author_parts) > 1:
|
||||
author = author_parts[1].split("]")[0].strip()
|
||||
|
||||
# Extract the link if it exists
|
||||
link_parts = full_line.split("](")
|
||||
if len(link_parts) > 1:
|
||||
link = link_parts[1].split(")")[0].strip()
|
||||
# Check for regular dash format: "- [author](link)"
|
||||
elif " - [" in full_line and "](" in full_line:
|
||||
author_parts = full_line.split(" - [")
|
||||
if len(author_parts) > 1:
|
||||
author = author_parts[1].split("]")[0].strip()
|
||||
|
||||
# Extract the link if it exists
|
||||
link_parts = full_line.split("](")
|
||||
if len(link_parts) > 1:
|
||||
link = link_parts[1].split(")")[0].strip()
|
||||
# Check for em dash without link: "— author"
|
||||
elif "— " in full_line:
|
||||
# Format without a link, just plain text author
|
||||
author_parts = full_line.split("— ")
|
||||
if len(author_parts) > 1:
|
||||
author = author_parts[1].strip()
|
||||
# Check for regular dash without link: "- author"
|
||||
elif " - " in full_line:
|
||||
# Format without a link, just plain text author
|
||||
author_parts = full_line.split(" - ")
|
||||
if len(author_parts) > 1:
|
||||
author = author_parts[1].strip()
|
||||
|
||||
testimonials.append(
|
||||
{"text": quote_text, "author": author, "link": link}
|
||||
)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Error parsing testimonial line: {line}. Error: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
continue
|
||||
|
||||
# Format as JavaScript array with script tags
|
||||
if not testimonials:
|
||||
print("No testimonials found in README.md", file=sys.stderr)
|
||||
return "<script>\nconst testimonials = [];\n</script>"
|
||||
|
||||
js_array = "<script>\nconst testimonials = [\n"
|
||||
for i, t in enumerate(testimonials):
|
||||
js_array += " {\n"
|
||||
js_array += f" text: \"{t['text']}\",\n"
|
||||
js_array += f" author: \"{t['author']}\",\n"
|
||||
js_array += f" link: \"{t['link']}\"\n"
|
||||
js_array += " }"
|
||||
if i < len(testimonials) - 1:
|
||||
js_array += ","
|
||||
js_array += "\n"
|
||||
js_array += "];\n</script>"
|
||||
|
||||
return js_array
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error reading testimonials from README: {e}", file=sys.stderr)
|
||||
# Return empty array as fallback
|
||||
return "<script>\nconst testimonials = [];\n</script>"
|
||||
|
||||
|
||||
def main():
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
@@ -492,6 +609,11 @@ def main():
|
||||
percentage, version = get_latest_release_aider_percentage()
|
||||
print(f"Aider wrote {percentage:.2f}% of code in the LATEST release ({version})")
|
||||
|
||||
# Get testimonials JavaScript
|
||||
testimonials_js = get_testimonials_js()
|
||||
print("\nTestimonials JavaScript:")
|
||||
print(testimonials_js)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -81,15 +81,20 @@ def main():
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true", help="Print each step without actually executing them"
|
||||
)
|
||||
parser.add_argument("--force", action="store_true", help="Skip pre-push checks")
|
||||
|
||||
args = parser.parse_args()
|
||||
dry_run = args.dry_run
|
||||
force = args.force
|
||||
|
||||
# Perform checks before proceeding
|
||||
check_branch()
|
||||
check_working_directory_clean()
|
||||
check_main_branch_up_to_date()
|
||||
check_ok_to_push()
|
||||
# Perform checks before proceeding unless --force is used
|
||||
if not force:
|
||||
check_branch()
|
||||
check_working_directory_clean()
|
||||
check_main_branch_up_to_date()
|
||||
check_ok_to_push()
|
||||
else:
|
||||
print("Skipping pre-push checks due to --force flag.")
|
||||
|
||||
new_version_str = args.new_version
|
||||
if not re.match(r"^\d+\.\d+\.\d+$", new_version_str):
|
||||
|
||||
@@ -194,8 +194,8 @@ class TestCoder(unittest.TestCase):
|
||||
mock.return_value = set([str(fname1), str(fname2), str(fname3)])
|
||||
coder.repo.get_tracked_files = mock
|
||||
|
||||
# Check that file mentions skip files with duplicate basenames
|
||||
mentioned = coder.get_file_mentions(f"Check {fname2} and {fname3}")
|
||||
# Check that file mentions of a pure basename skips files with duplicate basenames
|
||||
mentioned = coder.get_file_mentions(f"Check {fname2.name} and {fname3}")
|
||||
self.assertEqual(mentioned, {str(fname3)})
|
||||
|
||||
# Add a read-only file with same basename
|
||||
@@ -366,6 +366,45 @@ class TestCoder(unittest.TestCase):
|
||||
f"Failed to extract mentions from: {content}",
|
||||
)
|
||||
|
||||
def test_get_file_mentions_multiline_backticks(self):
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
|
||||
# Create test files
|
||||
test_files = [
|
||||
"swebench/harness/test_spec/python.py",
|
||||
"swebench/harness/test_spec/javascript.py",
|
||||
]
|
||||
for fname in test_files:
|
||||
fpath = Path(fname)
|
||||
fpath.parent.mkdir(parents=True, exist_ok=True)
|
||||
fpath.touch()
|
||||
|
||||
# Mock get_addable_relative_files to return our test files
|
||||
coder.get_addable_relative_files = MagicMock(return_value=set(test_files))
|
||||
|
||||
# Input text with multiline backticked filenames
|
||||
content = """
|
||||
Could you please **add the following files to the chat**?
|
||||
|
||||
1. `swebench/harness/test_spec/python.py`
|
||||
2. `swebench/harness/test_spec/javascript.py`
|
||||
|
||||
Once I have these, I can show you precisely how to do the thing.
|
||||
"""
|
||||
expected_mentions = {
|
||||
"swebench/harness/test_spec/python.py",
|
||||
"swebench/harness/test_spec/javascript.py",
|
||||
}
|
||||
|
||||
mentioned_files = coder.get_file_mentions(content)
|
||||
self.assertEqual(
|
||||
mentioned_files,
|
||||
expected_mentions,
|
||||
f"Failed to extract mentions from multiline backticked content: {content}",
|
||||
)
|
||||
|
||||
def test_get_file_mentions_path_formats(self):
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
|
||||
@@ -1124,6 +1124,29 @@ class TestCommands(TestCase):
|
||||
# Check that the output was added to cur_messages
|
||||
self.assertTrue(any("exit 1" in msg["content"] for msg in coder.cur_messages))
|
||||
|
||||
def test_cmd_test_returns_output_on_failure(self):
|
||||
with ChdirTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, fancy_input=False, yes=False)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Define a command that prints to stderr and exits with non-zero status
|
||||
test_cmd = "echo 'error output' >&2 && exit 1"
|
||||
expected_output_fragment = "error output"
|
||||
|
||||
# Run cmd_test
|
||||
result = commands.cmd_test(test_cmd)
|
||||
|
||||
# Assert that the result contains the expected output
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIn(expected_output_fragment, result)
|
||||
# Check that the output was also added to cur_messages
|
||||
self.assertTrue(
|
||||
any(expected_output_fragment in msg["content"] for msg in coder.cur_messages)
|
||||
)
|
||||
|
||||
def test_cmd_add_drop_untracked_files(self):
|
||||
with GitTemporaryDirectory():
|
||||
repo = git.Repo()
|
||||
@@ -1682,6 +1705,27 @@ class TestCommands(TestCase):
|
||||
self.assertEqual(
|
||||
context.exception.kwargs.get("main_model").weak_model.name, self.GPT35.weak_model.name
|
||||
)
|
||||
# Check that the edit format is updated to the new model's default
|
||||
self.assertEqual(context.exception.kwargs.get("edit_format"), "diff")
|
||||
|
||||
def test_cmd_model_preserves_explicit_edit_format(self):
|
||||
io = InputOutput(pretty=False, fancy_input=False, yes=True)
|
||||
# Use gpt-3.5-turbo (default 'diff')
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
# Explicitly set edit format to something else
|
||||
coder.edit_format = "udiff"
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Mock sanity check to avoid network calls
|
||||
with mock.patch("aider.models.sanity_check_models"):
|
||||
# Test switching the main model to gpt-4 (default 'whole')
|
||||
with self.assertRaises(SwitchCoder) as context:
|
||||
commands.cmd_model("gpt-4")
|
||||
|
||||
# Check that the SwitchCoder exception contains the correct model configuration
|
||||
self.assertEqual(context.exception.kwargs.get("main_model").name, "gpt-4")
|
||||
# Check that the edit format is preserved
|
||||
self.assertEqual(context.exception.kwargs.get("edit_format"), "udiff")
|
||||
|
||||
def test_cmd_editor_model(self):
|
||||
io = InputOutput(pretty=False, fancy_input=False, yes=True)
|
||||
@@ -1716,6 +1760,25 @@ class TestCommands(TestCase):
|
||||
)
|
||||
self.assertEqual(context.exception.kwargs.get("main_model").weak_model.name, "gpt-4")
|
||||
|
||||
def test_cmd_model_updates_default_edit_format(self):
|
||||
io = InputOutput(pretty=False, fancy_input=False, yes=True)
|
||||
# Use gpt-3.5-turbo (default 'diff')
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
# Ensure current edit format is the default
|
||||
self.assertEqual(coder.edit_format, self.GPT35.edit_format)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Mock sanity check to avoid network calls
|
||||
with mock.patch("aider.models.sanity_check_models"):
|
||||
# Test switching the main model to gpt-4 (default 'whole')
|
||||
with self.assertRaises(SwitchCoder) as context:
|
||||
commands.cmd_model("gpt-4")
|
||||
|
||||
# Check that the SwitchCoder exception contains the correct model configuration
|
||||
self.assertEqual(context.exception.kwargs.get("main_model").name, "gpt-4")
|
||||
# Check that the edit format is updated to the new model's default
|
||||
self.assertEqual(context.exception.kwargs.get("edit_format"), "diff")
|
||||
|
||||
def test_cmd_ask(self):
|
||||
io = InputOutput(pretty=False, fancy_input=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
|
||||
@@ -575,6 +575,69 @@ Hope you like it!
|
||||
edits = list(eb.find_original_update_blocks(edit, fence=quad_backticks))
|
||||
self.assertEqual(edits, [("foo.txt", "", "Tooooo\n")])
|
||||
|
||||
#Test for shell script blocks with sh language identifier (issue #3785)
|
||||
def test_find_original_update_blocks_with_sh_language_identifier(self):
|
||||
# https://github.com/Aider-AI/aider/issues/3785
|
||||
edit = """
|
||||
Here's a shell script:
|
||||
|
||||
```sh
|
||||
test_hello.sh
|
||||
<<<<<<< SEARCH
|
||||
=======
|
||||
#!/bin/bash
|
||||
# Check if exactly one argument is provided
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <argument>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Echo the first argument
|
||||
echo "$1"
|
||||
|
||||
exit 0
|
||||
>>>>>>> REPLACE
|
||||
```
|
||||
"""
|
||||
|
||||
edits = list(eb.find_original_update_blocks(edit))
|
||||
# Instead of comparing exact strings, check that we got the right file and structure
|
||||
self.assertEqual(len(edits), 1)
|
||||
self.assertEqual(edits[0][0], "test_hello.sh")
|
||||
self.assertEqual(edits[0][1], "")
|
||||
|
||||
# Check that the content contains the expected shell script elements
|
||||
result_content = edits[0][2]
|
||||
self.assertIn("#!/bin/bash", result_content)
|
||||
self.assertIn("if [ \"$#\" -ne 1 ];", result_content)
|
||||
self.assertIn("echo \"Usage: $0 <argument>\"", result_content)
|
||||
self.assertIn("exit 1", result_content)
|
||||
self.assertIn("echo \"$1\"", result_content)
|
||||
self.assertIn("exit 0", result_content)
|
||||
|
||||
#Test for C# code blocks with csharp language identifier
|
||||
def test_find_original_update_blocks_with_csharp_language_identifier(self):
|
||||
edit = """
|
||||
Here's a C# code change:
|
||||
|
||||
```csharp
|
||||
Program.cs
|
||||
<<<<<<< SEARCH
|
||||
Console.WriteLine("Hello World!");
|
||||
=======
|
||||
Console.WriteLine("Hello, C# World!");
|
||||
>>>>>>> REPLACE
|
||||
```
|
||||
"""
|
||||
|
||||
edits = list(eb.find_original_update_blocks(edit))
|
||||
search_text = "Console.WriteLine(\"Hello World!\");\n"
|
||||
replace_text = "Console.WriteLine(\"Hello, C# World!\");\n"
|
||||
self.assertEqual(
|
||||
edits,
|
||||
[("Program.cs", search_text, replace_text)]
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -79,4 +79,6 @@ def test_openrouter_error():
|
||||
|
||||
ex_info = ex.get_ex_info(openrouter_error)
|
||||
assert ex_info.retry is True
|
||||
assert "OpenRouter API provider is down" in ex_info.description
|
||||
assert "OpenRouter" in ex_info.description
|
||||
assert "overloaded" in ex_info.description
|
||||
assert "rate" in ex_info.description
|
||||
|
||||
@@ -447,6 +447,34 @@ class TestInputOutputMultilineMode(unittest.TestCase):
|
||||
self.assertEqual(ensure_hash_prefix("xyz"), "xyz") # Invalid hex chars
|
||||
self.assertEqual(ensure_hash_prefix("12345g"), "12345g") # Invalid hex chars
|
||||
|
||||
def test_tool_output_color_handling(self):
|
||||
"""Test that tool_output correctly handles hex colors without # prefix"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
# Create IO with hex color without # for tool_output_color
|
||||
io = InputOutput(tool_output_color="FFA500", pretty=True)
|
||||
|
||||
# Patch console.print to avoid actual printing
|
||||
with patch.object(io.console, "print") as mock_print:
|
||||
# This would raise ColorParseError without the fix
|
||||
io.tool_output("Test message")
|
||||
|
||||
# Verify the call was made without error
|
||||
mock_print.assert_called_once()
|
||||
|
||||
# Verify the style was correctly created with # prefix
|
||||
# The first argument is the message, second would be the style
|
||||
kwargs = mock_print.call_args.kwargs
|
||||
self.assertIn("style", kwargs)
|
||||
|
||||
# Test with other hex color
|
||||
io = InputOutput(tool_output_color="00FF00", pretty=True)
|
||||
with patch.object(io.console, "print") as mock_print:
|
||||
io.tool_output("Test message")
|
||||
mock_print.assert_called_once()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -14,7 +14,7 @@ from prompt_toolkit.output import DummyOutput
|
||||
from aider.coders import Coder
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.main import check_gitignore, main, setup_git
|
||||
from aider.main import check_gitignore, load_dotenv_files, main, setup_git
|
||||
from aider.utils import GitTemporaryDirectory, IgnorantTemporaryDirectory, make_repo
|
||||
|
||||
|
||||
@@ -983,7 +983,7 @@ class TestMain(TestCase):
|
||||
coder = main(
|
||||
["--exit", "--yes"], input=DummyInput(), output=DummyOutput(), return_coder=True
|
||||
)
|
||||
self.assertIn("openrouter/anthropic/claude", coder.main_model.name.lower())
|
||||
self.assertIn("openrouter/", coder.main_model.name.lower())
|
||||
del os.environ["OPENROUTER_API_KEY"]
|
||||
|
||||
# Test OpenAI API key
|
||||
@@ -999,12 +999,15 @@ class TestMain(TestCase):
|
||||
coder = main(
|
||||
["--exit", "--yes"], input=DummyInput(), output=DummyOutput(), return_coder=True
|
||||
)
|
||||
self.assertIn("flash", coder.main_model.name.lower())
|
||||
self.assertIn("gemini", coder.main_model.name.lower())
|
||||
del os.environ["GEMINI_API_KEY"]
|
||||
|
||||
# Test no API keys
|
||||
result = main(["--exit", "--yes"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertEqual(result, 1)
|
||||
# Test no API keys - should offer OpenRouter OAuth
|
||||
with patch("aider.onboarding.offer_openrouter_oauth") as mock_offer_oauth:
|
||||
mock_offer_oauth.return_value = None # Simulate user declining or failure
|
||||
result = main(["--exit", "--yes"], input=DummyInput(), output=DummyOutput())
|
||||
self.assertEqual(result, 1) # Expect failure since no model could be selected
|
||||
mock_offer_oauth.assert_called_once()
|
||||
|
||||
def test_model_precedence(self):
|
||||
with GitTemporaryDirectory():
|
||||
@@ -1246,3 +1249,99 @@ class TestMain(TestCase):
|
||||
# Only set_reasoning_effort should be called, not set_thinking_tokens
|
||||
mock_instance.set_reasoning_effort.assert_called_once_with("3")
|
||||
mock_instance.set_thinking_tokens.assert_not_called()
|
||||
|
||||
@patch("aider.main.InputOutput")
|
||||
def test_stream_and_cache_warning(self, MockInputOutput):
|
||||
mock_io_instance = MockInputOutput.return_value
|
||||
with GitTemporaryDirectory():
|
||||
main(
|
||||
["--stream", "--cache-prompts", "--exit", "--yes"],
|
||||
input=DummyInput(),
|
||||
output=DummyOutput(),
|
||||
)
|
||||
mock_io_instance.tool_warning.assert_called_with(
|
||||
"Cost estimates may be inaccurate when using streaming and caching."
|
||||
)
|
||||
|
||||
@patch("aider.main.InputOutput")
|
||||
def test_stream_without_cache_no_warning(self, MockInputOutput):
|
||||
mock_io_instance = MockInputOutput.return_value
|
||||
with GitTemporaryDirectory():
|
||||
main(
|
||||
["--stream", "--exit", "--yes"],
|
||||
input=DummyInput(),
|
||||
output=DummyOutput(),
|
||||
)
|
||||
for call in mock_io_instance.tool_warning.call_args_list:
|
||||
self.assertNotIn("Cost estimates may be inaccurate", call[0][0])
|
||||
|
||||
def test_load_dotenv_files_override(self):
|
||||
with GitTemporaryDirectory() as git_dir:
|
||||
git_dir = Path(git_dir)
|
||||
|
||||
# Create fake home and .aider directory
|
||||
fake_home = git_dir / "fake_home"
|
||||
fake_home.mkdir()
|
||||
aider_dir = fake_home / ".aider"
|
||||
aider_dir.mkdir()
|
||||
|
||||
# Create oauth keys file
|
||||
oauth_keys_file = aider_dir / "oauth-keys.env"
|
||||
oauth_keys_file.write_text("OAUTH_VAR=oauth_val\nSHARED_VAR=oauth_shared\n")
|
||||
|
||||
# Create git root .env file
|
||||
git_root_env = git_dir / ".env"
|
||||
git_root_env.write_text("GIT_VAR=git_val\nSHARED_VAR=git_shared\n")
|
||||
|
||||
# Create CWD .env file in a subdir
|
||||
cwd_subdir = git_dir / "subdir"
|
||||
cwd_subdir.mkdir()
|
||||
cwd_env = cwd_subdir / ".env"
|
||||
cwd_env.write_text("CWD_VAR=cwd_val\nSHARED_VAR=cwd_shared\n")
|
||||
|
||||
# Change to subdir
|
||||
original_cwd = os.getcwd()
|
||||
os.chdir(cwd_subdir)
|
||||
|
||||
# Clear relevant env vars before test
|
||||
for var in ["OAUTH_VAR", "SHARED_VAR", "GIT_VAR", "CWD_VAR"]:
|
||||
if var in os.environ:
|
||||
del os.environ[var]
|
||||
|
||||
with patch("pathlib.Path.home", return_value=fake_home):
|
||||
loaded_files = load_dotenv_files(str(git_dir), None)
|
||||
|
||||
# Assert files were loaded in expected order (oauth first)
|
||||
self.assertIn(str(oauth_keys_file.resolve()), loaded_files)
|
||||
self.assertIn(str(git_root_env.resolve()), loaded_files)
|
||||
self.assertIn(str(cwd_env.resolve()), loaded_files)
|
||||
self.assertLess(
|
||||
loaded_files.index(str(oauth_keys_file.resolve())),
|
||||
loaded_files.index(str(git_root_env.resolve())),
|
||||
)
|
||||
self.assertLess(
|
||||
loaded_files.index(str(git_root_env.resolve())),
|
||||
loaded_files.index(str(cwd_env.resolve())),
|
||||
)
|
||||
|
||||
# Assert environment variables reflect the override order
|
||||
self.assertEqual(os.environ.get("OAUTH_VAR"), "oauth_val")
|
||||
self.assertEqual(os.environ.get("GIT_VAR"), "git_val")
|
||||
self.assertEqual(os.environ.get("CWD_VAR"), "cwd_val")
|
||||
# SHARED_VAR should be overridden by the last loaded file (cwd .env)
|
||||
self.assertEqual(os.environ.get("SHARED_VAR"), "cwd_shared")
|
||||
|
||||
# Restore CWD
|
||||
os.chdir(original_cwd)
|
||||
|
||||
@patch("aider.main.InputOutput")
|
||||
def test_cache_without_stream_no_warning(self, MockInputOutput):
|
||||
mock_io_instance = MockInputOutput.return_value
|
||||
with GitTemporaryDirectory():
|
||||
main(
|
||||
["--cache-prompts", "--exit", "--yes", "--no-stream"],
|
||||
input=DummyInput(),
|
||||
output=DummyOutput(),
|
||||
)
|
||||
for call in mock_io_instance.tool_warning.call_args_list:
|
||||
self.assertNotIn("Cost estimates may be inaccurate", call[0][0])
|
||||
|
||||
439
tests/basic/test_onboarding.py
Normal file
439
tests/basic/test_onboarding.py
Normal file
@@ -0,0 +1,439 @@
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import requests
|
||||
|
||||
# Import the functions to be tested
|
||||
from aider.onboarding import (
|
||||
check_openrouter_tier,
|
||||
exchange_code_for_key,
|
||||
find_available_port,
|
||||
generate_pkce_codes,
|
||||
offer_openrouter_oauth,
|
||||
select_default_model,
|
||||
try_to_select_default_model,
|
||||
)
|
||||
|
||||
|
||||
# Mock the Analytics class as it's used in some functions
|
||||
class DummyAnalytics:
|
||||
def event(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
# Mock the InputOutput class
|
||||
class DummyIO:
|
||||
def tool_output(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def tool_warning(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def tool_error(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def confirm_ask(self, *args, **kwargs):
|
||||
return False # Default to no confirmation
|
||||
|
||||
def offer_url(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class TestOnboarding(unittest.TestCase):
|
||||
@patch("requests.get")
|
||||
def test_check_openrouter_tier_free(self, mock_get):
|
||||
"""Test check_openrouter_tier identifies free tier."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"data": {"is_free_tier": True}}
|
||||
mock_response.raise_for_status.return_value = None
|
||||
mock_get.return_value = mock_response
|
||||
self.assertTrue(check_openrouter_tier("fake_key"))
|
||||
mock_get.assert_called_once_with(
|
||||
"https://openrouter.ai/api/v1/auth/key",
|
||||
headers={"Authorization": "Bearer fake_key"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
@patch("requests.get")
|
||||
def test_check_openrouter_tier_paid(self, mock_get):
|
||||
"""Test check_openrouter_tier identifies paid tier."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"data": {"is_free_tier": False}}
|
||||
mock_response.raise_for_status.return_value = None
|
||||
mock_get.return_value = mock_response
|
||||
self.assertFalse(check_openrouter_tier("fake_key"))
|
||||
|
||||
@patch("requests.get")
|
||||
def test_check_openrouter_tier_api_error(self, mock_get):
|
||||
"""Test check_openrouter_tier defaults to free on API error."""
|
||||
mock_get.side_effect = requests.exceptions.RequestException("API Error")
|
||||
self.assertTrue(check_openrouter_tier("fake_key"))
|
||||
|
||||
@patch("requests.get")
|
||||
def test_check_openrouter_tier_missing_key(self, mock_get):
|
||||
"""Test check_openrouter_tier defaults to free if key is missing in response."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"data": {}} # Missing 'is_free_tier'
|
||||
mock_response.raise_for_status.return_value = None
|
||||
mock_get.return_value = mock_response
|
||||
self.assertTrue(check_openrouter_tier("fake_key"))
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier")
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_try_select_default_model_no_keys(self, mock_check_tier):
|
||||
"""Test no model is selected when no keys are present."""
|
||||
self.assertIsNone(try_to_select_default_model())
|
||||
mock_check_tier.assert_not_called()
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier", return_value=True) # Assume free tier
|
||||
@patch.dict(os.environ, {"OPENROUTER_API_KEY": "or_key"}, clear=True)
|
||||
def test_try_select_default_model_openrouter_free(self, mock_check_tier):
|
||||
"""Test OpenRouter free model selection."""
|
||||
self.assertEqual(
|
||||
try_to_select_default_model(), "openrouter/google/gemini-2.5-pro-exp-03-25:free"
|
||||
)
|
||||
mock_check_tier.assert_called_once_with("or_key")
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier", return_value=False) # Assume paid tier
|
||||
@patch.dict(os.environ, {"OPENROUTER_API_KEY": "or_key"}, clear=True)
|
||||
def test_try_select_default_model_openrouter_paid(self, mock_check_tier):
|
||||
"""Test OpenRouter paid model selection."""
|
||||
self.assertEqual(try_to_select_default_model(), "openrouter/anthropic/claude-3.7-sonnet")
|
||||
mock_check_tier.assert_called_once_with("or_key")
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier")
|
||||
@patch.dict(os.environ, {"ANTHROPIC_API_KEY": "an_key"}, clear=True)
|
||||
def test_try_select_default_model_anthropic(self, mock_check_tier):
|
||||
"""Test Anthropic model selection."""
|
||||
self.assertEqual(try_to_select_default_model(), "sonnet")
|
||||
mock_check_tier.assert_not_called()
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier")
|
||||
@patch.dict(os.environ, {"DEEPSEEK_API_KEY": "ds_key"}, clear=True)
|
||||
def test_try_select_default_model_deepseek(self, mock_check_tier):
|
||||
"""Test Deepseek model selection."""
|
||||
self.assertEqual(try_to_select_default_model(), "deepseek")
|
||||
mock_check_tier.assert_not_called()
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier")
|
||||
@patch.dict(os.environ, {"OPENAI_API_KEY": "oa_key"}, clear=True)
|
||||
def test_try_select_default_model_openai(self, mock_check_tier):
|
||||
"""Test OpenAI model selection."""
|
||||
self.assertEqual(try_to_select_default_model(), "gpt-4o")
|
||||
mock_check_tier.assert_not_called()
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier")
|
||||
@patch.dict(os.environ, {"GEMINI_API_KEY": "gm_key"}, clear=True)
|
||||
def test_try_select_default_model_gemini(self, mock_check_tier):
|
||||
"""Test Gemini model selection."""
|
||||
self.assertEqual(try_to_select_default_model(), "gemini/gemini-2.5-pro-exp-03-25")
|
||||
mock_check_tier.assert_not_called()
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier")
|
||||
@patch.dict(os.environ, {"VERTEXAI_PROJECT": "vx_proj"}, clear=True)
|
||||
def test_try_select_default_model_vertex(self, mock_check_tier):
|
||||
"""Test Vertex AI model selection."""
|
||||
self.assertEqual(try_to_select_default_model(), "vertex_ai/gemini-2.5-pro-exp-03-25")
|
||||
mock_check_tier.assert_not_called()
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier", return_value=False) # Paid
|
||||
@patch.dict(
|
||||
os.environ, {"OPENROUTER_API_KEY": "or_key", "OPENAI_API_KEY": "oa_key"}, clear=True
|
||||
)
|
||||
def test_try_select_default_model_priority_openrouter(self, mock_check_tier):
|
||||
"""Test OpenRouter key takes priority."""
|
||||
self.assertEqual(try_to_select_default_model(), "openrouter/anthropic/claude-3.7-sonnet")
|
||||
mock_check_tier.assert_called_once_with("or_key")
|
||||
|
||||
@patch("aider.onboarding.check_openrouter_tier")
|
||||
@patch.dict(os.environ, {"ANTHROPIC_API_KEY": "an_key", "OPENAI_API_KEY": "oa_key"}, clear=True)
|
||||
def test_try_select_default_model_priority_anthropic(self, mock_check_tier):
|
||||
"""Test Anthropic key takes priority over OpenAI."""
|
||||
self.assertEqual(try_to_select_default_model(), "sonnet")
|
||||
mock_check_tier.assert_not_called()
|
||||
|
||||
@patch("socketserver.TCPServer")
|
||||
def test_find_available_port_success(self, mock_tcp_server):
|
||||
"""Test finding an available port."""
|
||||
# Simulate port 8484 being available
|
||||
mock_tcp_server.return_value.__enter__.return_value = None # Allow context manager
|
||||
port = find_available_port(start_port=8484, end_port=8484)
|
||||
self.assertEqual(port, 8484)
|
||||
mock_tcp_server.assert_called_once_with(("localhost", 8484), None)
|
||||
|
||||
@patch("socketserver.TCPServer")
|
||||
def test_find_available_port_in_use(self, mock_tcp_server):
|
||||
"""Test finding the next available port if the first is in use."""
|
||||
# Simulate port 8484 raising OSError, 8485 being available
|
||||
mock_tcp_server.side_effect = [OSError, MagicMock()]
|
||||
mock_tcp_server.return_value.__enter__.return_value = None # Allow context manager
|
||||
port = find_available_port(start_port=8484, end_port=8485)
|
||||
self.assertEqual(port, 8485)
|
||||
self.assertEqual(mock_tcp_server.call_count, 2)
|
||||
mock_tcp_server.assert_any_call(("localhost", 8484), None)
|
||||
mock_tcp_server.assert_any_call(("localhost", 8485), None)
|
||||
|
||||
@patch("socketserver.TCPServer", side_effect=OSError)
|
||||
def test_find_available_port_none_available(self, mock_tcp_server):
|
||||
"""Test returning None if no ports are available in the range."""
|
||||
port = find_available_port(start_port=8484, end_port=8485)
|
||||
self.assertIsNone(port)
|
||||
self.assertEqual(mock_tcp_server.call_count, 2) # Tried 8484 and 8485
|
||||
|
||||
def test_generate_pkce_codes(self):
|
||||
"""Test PKCE code generation."""
|
||||
verifier, challenge = generate_pkce_codes()
|
||||
self.assertIsInstance(verifier, str)
|
||||
self.assertIsInstance(challenge, str)
|
||||
self.assertGreater(len(verifier), 40) # Check reasonable length
|
||||
self.assertGreater(len(challenge), 40)
|
||||
# Verify the challenge is the SHA256 hash of the verifier, base64 encoded
|
||||
hasher = hashlib.sha256()
|
||||
hasher.update(verifier.encode("utf-8"))
|
||||
expected_challenge = base64.urlsafe_b64encode(hasher.digest()).rstrip(b"=").decode("utf-8")
|
||||
self.assertEqual(challenge, expected_challenge)
|
||||
|
||||
@patch("requests.post")
|
||||
def test_exchange_code_for_key_success(self, mock_post):
|
||||
"""Test successful code exchange for API key."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"key": "test_api_key"}
|
||||
mock_response.raise_for_status.return_value = None
|
||||
mock_post.return_value = mock_response
|
||||
io_mock = DummyIO()
|
||||
|
||||
api_key = exchange_code_for_key("auth_code", "verifier", io_mock)
|
||||
|
||||
self.assertEqual(api_key, "test_api_key")
|
||||
mock_post.assert_called_once_with(
|
||||
"https://openrouter.ai/api/v1/auth/keys",
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={
|
||||
"code": "auth_code",
|
||||
"code_verifier": "verifier",
|
||||
"code_challenge_method": "S256",
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
@patch("requests.post")
|
||||
def test_exchange_code_for_key_missing_key(self, mock_post):
|
||||
"""Test code exchange when 'key' is missing in response."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"other_data": "value"} # Missing 'key'
|
||||
mock_response.raise_for_status.return_value = None
|
||||
mock_response.text = '{"other_data": "value"}'
|
||||
mock_post.return_value = mock_response
|
||||
io_mock = DummyIO()
|
||||
io_mock.tool_error = MagicMock() # Track error output
|
||||
|
||||
api_key = exchange_code_for_key("auth_code", "verifier", io_mock)
|
||||
|
||||
self.assertIsNone(api_key)
|
||||
io_mock.tool_error.assert_any_call("Error: 'key' not found in OpenRouter response.")
|
||||
io_mock.tool_error.assert_any_call('Response: {"other_data": "value"}')
|
||||
|
||||
@patch("requests.post")
|
||||
def test_exchange_code_for_key_http_error(self, mock_post):
|
||||
"""Test code exchange with HTTP error."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 400
|
||||
mock_response.reason = "Bad Request"
|
||||
mock_response.text = '{"error": "invalid_code"}'
|
||||
http_error = requests.exceptions.HTTPError(response=mock_response)
|
||||
mock_post.side_effect = http_error
|
||||
io_mock = DummyIO()
|
||||
io_mock.tool_error = MagicMock()
|
||||
|
||||
api_key = exchange_code_for_key("auth_code", "verifier", io_mock)
|
||||
|
||||
self.assertIsNone(api_key)
|
||||
io_mock.tool_error.assert_any_call(
|
||||
"Error exchanging code for OpenRouter key: 400 Bad Request"
|
||||
)
|
||||
io_mock.tool_error.assert_any_call('Response: {"error": "invalid_code"}')
|
||||
|
||||
@patch("requests.post")
|
||||
def test_exchange_code_for_key_timeout(self, mock_post):
|
||||
"""Test code exchange with timeout."""
|
||||
mock_post.side_effect = requests.exceptions.Timeout("Timeout")
|
||||
io_mock = DummyIO()
|
||||
io_mock.tool_error = MagicMock()
|
||||
|
||||
api_key = exchange_code_for_key("auth_code", "verifier", io_mock)
|
||||
|
||||
self.assertIsNone(api_key)
|
||||
io_mock.tool_error.assert_called_once_with(
|
||||
"Error: Request to OpenRouter timed out during code exchange."
|
||||
)
|
||||
|
||||
@patch("requests.post")
|
||||
def test_exchange_code_for_key_request_exception(self, mock_post):
|
||||
"""Test code exchange with general request exception."""
|
||||
req_exception = requests.exceptions.RequestException("Network Error")
|
||||
mock_post.side_effect = req_exception
|
||||
io_mock = DummyIO()
|
||||
io_mock.tool_error = MagicMock()
|
||||
|
||||
api_key = exchange_code_for_key("auth_code", "verifier", io_mock)
|
||||
|
||||
self.assertIsNone(api_key)
|
||||
io_mock.tool_error.assert_called_once_with(
|
||||
f"Error exchanging code for OpenRouter key: {req_exception}"
|
||||
)
|
||||
|
||||
# --- Tests for select_default_model ---
|
||||
|
||||
@patch("aider.onboarding.try_to_select_default_model", return_value="gpt-4o")
|
||||
@patch("aider.onboarding.offer_openrouter_oauth")
|
||||
def test_select_default_model_already_specified(self, mock_offer_oauth, mock_try_select):
|
||||
"""Test select_default_model returns args.model if provided."""
|
||||
args = argparse.Namespace(model="specific-model")
|
||||
io_mock = DummyIO()
|
||||
analytics_mock = DummyAnalytics()
|
||||
selected_model = select_default_model(args, io_mock, analytics_mock)
|
||||
self.assertEqual(selected_model, "specific-model")
|
||||
mock_try_select.assert_not_called()
|
||||
mock_offer_oauth.assert_not_called()
|
||||
|
||||
@patch("aider.onboarding.try_to_select_default_model", return_value="gpt-4o")
|
||||
@patch("aider.onboarding.offer_openrouter_oauth")
|
||||
def test_select_default_model_found_via_env(self, mock_offer_oauth, mock_try_select):
|
||||
"""Test select_default_model returns model found by try_to_select."""
|
||||
args = argparse.Namespace(model=None) # No model specified
|
||||
io_mock = DummyIO()
|
||||
io_mock.tool_warning = MagicMock() # Track warnings
|
||||
analytics_mock = DummyAnalytics()
|
||||
analytics_mock.event = MagicMock() # Track events
|
||||
|
||||
selected_model = select_default_model(args, io_mock, analytics_mock)
|
||||
|
||||
self.assertEqual(selected_model, "gpt-4o")
|
||||
mock_try_select.assert_called_once()
|
||||
io_mock.tool_warning.assert_called_once_with(
|
||||
"Using gpt-4o model with API key from environment."
|
||||
)
|
||||
analytics_mock.event.assert_called_once_with("auto_model_selection", model="gpt-4o")
|
||||
mock_offer_oauth.assert_not_called()
|
||||
|
||||
@patch(
|
||||
"aider.onboarding.try_to_select_default_model", side_effect=[None, None]
|
||||
) # Fails first, fails after oauth attempt
|
||||
@patch(
|
||||
"aider.onboarding.offer_openrouter_oauth", return_value=False
|
||||
) # OAuth offered but fails/declined
|
||||
def test_select_default_model_no_keys_oauth_fail(self, mock_offer_oauth, mock_try_select):
|
||||
"""Test select_default_model offers OAuth when no keys, but OAuth fails."""
|
||||
args = argparse.Namespace(model=None)
|
||||
io_mock = DummyIO()
|
||||
io_mock.tool_warning = MagicMock()
|
||||
io_mock.offer_url = MagicMock()
|
||||
analytics_mock = DummyAnalytics()
|
||||
|
||||
selected_model = select_default_model(args, io_mock, analytics_mock)
|
||||
|
||||
self.assertIsNone(selected_model)
|
||||
self.assertEqual(mock_try_select.call_count, 2) # Called before and after oauth attempt
|
||||
mock_offer_oauth.assert_called_once_with(io_mock, analytics_mock)
|
||||
io_mock.tool_warning.assert_called_once_with(
|
||||
"No LLM model was specified and no API keys were provided."
|
||||
)
|
||||
io_mock.offer_url.assert_called_once() # Should offer docs URL
|
||||
|
||||
@patch(
|
||||
"aider.onboarding.try_to_select_default_model",
|
||||
side_effect=[None, "openrouter/google/gemini-2.5-pro-exp-03-25:free"],
|
||||
) # Fails first, succeeds after oauth
|
||||
@patch(
|
||||
"aider.onboarding.offer_openrouter_oauth", return_value=True
|
||||
) # OAuth offered and succeeds
|
||||
def test_select_default_model_no_keys_oauth_success(self, mock_offer_oauth, mock_try_select):
|
||||
"""Test select_default_model offers OAuth, which succeeds."""
|
||||
args = argparse.Namespace(model=None)
|
||||
io_mock = DummyIO()
|
||||
io_mock.tool_warning = MagicMock()
|
||||
analytics_mock = DummyAnalytics()
|
||||
|
||||
selected_model = select_default_model(args, io_mock, analytics_mock)
|
||||
|
||||
self.assertEqual(selected_model, "openrouter/google/gemini-2.5-pro-exp-03-25:free")
|
||||
self.assertEqual(mock_try_select.call_count, 2) # Called before and after oauth
|
||||
mock_offer_oauth.assert_called_once_with(io_mock, analytics_mock)
|
||||
# Only one warning is expected: "No LLM model..."
|
||||
self.assertEqual(io_mock.tool_warning.call_count, 1)
|
||||
io_mock.tool_warning.assert_called_once_with(
|
||||
"No LLM model was specified and no API keys were provided."
|
||||
)
|
||||
# The second call to try_select finds the model, so the *outer* function logs the usage.
|
||||
# Note: The warning comes from the second call within select_default_model,
|
||||
# not try_select itself.
|
||||
# We verify the final state and model returned.
|
||||
|
||||
# --- Tests for offer_openrouter_oauth ---
|
||||
@patch("aider.onboarding.start_openrouter_oauth_flow", return_value="new_or_key")
|
||||
@patch.dict(os.environ, {}, clear=True) # Ensure no key exists initially
|
||||
def test_offer_openrouter_oauth_confirm_yes_success(self, mock_start_oauth):
|
||||
"""Test offer_openrouter_oauth when user confirms and OAuth succeeds."""
|
||||
io_mock = DummyIO()
|
||||
io_mock.confirm_ask = MagicMock(return_value=True) # User says yes
|
||||
analytics_mock = DummyAnalytics()
|
||||
analytics_mock.event = MagicMock()
|
||||
|
||||
result = offer_openrouter_oauth(io_mock, analytics_mock)
|
||||
|
||||
self.assertTrue(result)
|
||||
io_mock.confirm_ask.assert_called_once()
|
||||
mock_start_oauth.assert_called_once_with(io_mock, analytics_mock)
|
||||
self.assertEqual(os.environ.get("OPENROUTER_API_KEY"), "new_or_key")
|
||||
analytics_mock.event.assert_any_call("oauth_flow_initiated", provider="openrouter")
|
||||
analytics_mock.event.assert_any_call("oauth_flow_success")
|
||||
# Clean up env var
|
||||
del os.environ["OPENROUTER_API_KEY"]
|
||||
|
||||
@patch("aider.onboarding.start_openrouter_oauth_flow", return_value=None) # OAuth fails
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_offer_openrouter_oauth_confirm_yes_fail(self, mock_start_oauth):
|
||||
"""Test offer_openrouter_oauth when user confirms but OAuth fails."""
|
||||
io_mock = DummyIO()
|
||||
io_mock.confirm_ask = MagicMock(return_value=True) # User says yes
|
||||
io_mock.tool_error = MagicMock()
|
||||
analytics_mock = DummyAnalytics()
|
||||
analytics_mock.event = MagicMock()
|
||||
|
||||
result = offer_openrouter_oauth(io_mock, analytics_mock)
|
||||
|
||||
self.assertFalse(result)
|
||||
io_mock.confirm_ask.assert_called_once()
|
||||
mock_start_oauth.assert_called_once_with(io_mock, analytics_mock)
|
||||
self.assertNotIn("OPENROUTER_API_KEY", os.environ)
|
||||
io_mock.tool_error.assert_called_once_with(
|
||||
"OpenRouter authentication did not complete successfully."
|
||||
)
|
||||
analytics_mock.event.assert_any_call("oauth_flow_initiated", provider="openrouter")
|
||||
analytics_mock.event.assert_any_call("oauth_flow_failure")
|
||||
|
||||
@patch("aider.onboarding.start_openrouter_oauth_flow")
|
||||
def test_offer_openrouter_oauth_confirm_no(self, mock_start_oauth):
|
||||
"""Test offer_openrouter_oauth when user declines."""
|
||||
io_mock = DummyIO()
|
||||
io_mock.confirm_ask = MagicMock(return_value=False) # User says no
|
||||
analytics_mock = DummyAnalytics()
|
||||
analytics_mock.event = MagicMock()
|
||||
|
||||
result = offer_openrouter_oauth(io_mock, analytics_mock)
|
||||
|
||||
self.assertFalse(result)
|
||||
io_mock.confirm_ask.assert_called_once()
|
||||
mock_start_oauth.assert_not_called()
|
||||
analytics_mock.event.assert_not_called() # No OAuth events if declined
|
||||
|
||||
# --- More complex test for start_openrouter_oauth_flow (simplified) ---
|
||||
# This test focuses on the successful path, mocking heavily
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -381,6 +381,9 @@ class TestRepoMapAllLanguages(unittest.TestCase):
|
||||
def test_language_udev(self):
|
||||
self._test_language_repo_map("udev", "rules", "USB_DRIVER")
|
||||
|
||||
def test_language_scala(self):
|
||||
self._test_language_repo_map("scala", "scala", "Greeter")
|
||||
|
||||
def _test_language_repo_map(self, lang, key, symbol):
|
||||
"""Helper method to test repo map generation for a specific language."""
|
||||
# Get the fixture file path and name based on language
|
||||
|
||||
61
tests/fixtures/languages/scala/test.scala
vendored
Normal file
61
tests/fixtures/languages/scala/test.scala
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.example.test
|
||||
|
||||
// A trait definition
|
||||
trait Greeter {
|
||||
def greet(name: String): String
|
||||
}
|
||||
|
||||
// A class definition with parameters
|
||||
class FormalGreeter(prefix: String) extends Greeter {
|
||||
// A method definition
|
||||
override def greet(name: String): String = {
|
||||
s"$prefix, $name!"
|
||||
}
|
||||
|
||||
// A val definition
|
||||
val defaultPrefix: String = "Hello"
|
||||
|
||||
// A var definition
|
||||
var counter: Int = 0
|
||||
}
|
||||
|
||||
// An object definition
|
||||
object GreeterFactory {
|
||||
// A function definition
|
||||
def createGreeter(formal: Boolean): Greeter = {
|
||||
if (formal) {
|
||||
new FormalGreeter("Good day")
|
||||
} else {
|
||||
new CasualGreeter
|
||||
}
|
||||
}
|
||||
|
||||
// A type definition
|
||||
type GreeterType = Greeter
|
||||
}
|
||||
|
||||
// An enum definition
|
||||
enum Greeting {
|
||||
// Simple enum cases
|
||||
case Hello, Hi, Hey
|
||||
|
||||
// Full enum case with parameters
|
||||
case Custom(text: String)
|
||||
}
|
||||
|
||||
// A class that uses generics
|
||||
class Container[T](val value: T) {
|
||||
def map[U](f: T => U): Container[U] = new Container(f(value))
|
||||
}
|
||||
|
||||
// A case class
|
||||
case class Person(name: String, age: Int) {
|
||||
def introduce(): String = {
|
||||
val greeter = GreeterFactory.createGreeter(age > 30)
|
||||
greeter.greet(name) + s" I am $age years old."
|
||||
}
|
||||
}
|
||||
|
||||
class CasualGreeter extends Greeter {
|
||||
override def greet(name: String): String = s"Hey, $name!"
|
||||
}
|
||||
Reference in New Issue
Block a user