free-coding-models 0.5.21 → 0.5.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog v0.5.22 - 2026-06-10
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- Verdict now incorporates AI latency and TPS from benchmark results for a more accurate health assessment.
|
|
5
|
+
- Verdict column now displays magical sparkle emojis (✨) for visual appeal.
|
|
6
|
+
- NEW badge (🆕) added next to models introduced in the last 2–3 releases.
|
|
7
|
+
- Updated utility functions to include benchmark data in verdict calculation.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Updated `src/core/utils.js` to merge benchmark data into verdict logic.
|
|
11
|
+
- Updated `src/tui/render-table.js` to show sparkle emojis and NEW model badges.
|
|
12
|
+
- Bumped package version to `0.5.22`.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Ensured all tests pass with the new verdict logic.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.22",
|
|
4
4
|
"description": "Find the fastest coding LLM models in seconds — ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|
package/src/core/utils.js
CHANGED
|
@@ -115,6 +115,82 @@ export const getAvg = (r) => {
|
|
|
115
115
|
//
|
|
116
116
|
// 📖 The "wasUpBefore" check is key — it distinguishes between a model that's
|
|
117
117
|
// temporarily flaky vs one that was never reachable in the first place.
|
|
118
|
+
const NEW_MODELS = new Set([
|
|
119
|
+
'nvidia/nemotron-3-ultra-550b-a55b',
|
|
120
|
+
'@cf/meta/llama-3.2-90b-instruct',
|
|
121
|
+
'@cf/mistralai/mistral-7b-instruct-v0.2',
|
|
122
|
+
'@cf/google/gemma-2-9b-it',
|
|
123
|
+
'@cf/anthropic/claude-3-5-sonnet',
|
|
124
|
+
'@cf/openai/gpt-4o-mini',
|
|
125
|
+
'@cf/qwen/qwen3-30b-a3b-fp8',
|
|
126
|
+
'@cf/qwen/qwen2.5-coder-32b-instruct',
|
|
127
|
+
'@cf/meta/llama-3.3-70b-instruct-fp8-fast',
|
|
128
|
+
'@cf/google/gemma-4-26b-a4b-it',
|
|
129
|
+
'@cf/mistralai/mistral-small-3.1-24b-instruct',
|
|
130
|
+
'@cf/ibm/granite-4.0-h-micro',
|
|
131
|
+
'minimaxai/minimax-m2.7',
|
|
132
|
+
'z-ai/glm-5.1',
|
|
133
|
+
'moonshotai/kimi-k2.6',
|
|
134
|
+
'stepfun-ai/step-3.5-flash',
|
|
135
|
+
'stepfun-ai/step-3.7-flash',
|
|
136
|
+
'qwen/qwen3-coder-480b-a35b-instruct',
|
|
137
|
+
'qwen/qwen3.5-397b-a17b',
|
|
138
|
+
'meta/llama-4-maverick-17b-128e-instruct',
|
|
139
|
+
'mistralai/mistral-medium-3.5-128b',
|
|
140
|
+
'mistralai/mistral-small-4-119b-2603',
|
|
141
|
+
'qwen/qwen3.5-122b-a10b',
|
|
142
|
+
'mistralai/mistral-large-3-675b-instruct-2512',
|
|
143
|
+
'nvidia/nemotron-3-super-120b-a12b',
|
|
144
|
+
'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning',
|
|
145
|
+
'google/gemma-4-31b-it',
|
|
146
|
+
'bytedance/seed-oss-36b-instruct',
|
|
147
|
+
'stockmark/stockmark-2-100b-instruct',
|
|
148
|
+
'mistralai/ministral-14b-instruct-2512',
|
|
149
|
+
'meta/llama-3.2-11b-vision-instruct',
|
|
150
|
+
'microsoft/phi-4-mini-instruct',
|
|
151
|
+
'gemma-3-12b-it',
|
|
152
|
+
'nvidia/nemotron-nano-9b-v2',
|
|
153
|
+
'openrouter/owl-alpha',
|
|
154
|
+
'nousresearch/hermes-3-llama-3.1-405b:free',
|
|
155
|
+
'nvidia/nemotron-nano-30b-a3b:free',
|
|
156
|
+
'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
|
|
157
|
+
'meta-llama/llama-3.3-70b-instruct:free',
|
|
158
|
+
'meta-llama/llama-3.2-3b-instruct:free',
|
|
159
|
+
'liquid/lfm-2.5-1.2b-instruct:free',
|
|
160
|
+
'liquid/lfm-2.5-1.2b-thinking:free',
|
|
161
|
+
'qwen3.7-max',
|
|
162
|
+
'qwen3-max',
|
|
163
|
+
'qwen3.6-plus',
|
|
164
|
+
'qwen3-235b-a22b',
|
|
165
|
+
'qwen3.5-plus',
|
|
166
|
+
'qwen3-coder-plus',
|
|
167
|
+
'qwen3-coder-next',
|
|
168
|
+
'qwen3.6-flash',
|
|
169
|
+
'qwen3.5-flash',
|
|
170
|
+
'qwen3-coder-flash',
|
|
171
|
+
'qwen3-32b',
|
|
172
|
+
'qwen3-coder-30b-a3b-instruct',
|
|
173
|
+
'holo2-30b-a3b',
|
|
174
|
+
'llama-3.3-70b-instruct',
|
|
175
|
+
'mistral-small-3.2-24b-instruct-2506',
|
|
176
|
+
'gemma-3-27b-it',
|
|
177
|
+
'qwen3.5-397b-a17b',
|
|
178
|
+
'qwen3-coder-30b-a3b-instruct',
|
|
179
|
+
'gpt-oss-120b',
|
|
180
|
+
'gpt-oss-20b',
|
|
181
|
+
'Meta-Llama-3_3-70B-Instruct',
|
|
182
|
+
'Qwen3-32B',
|
|
183
|
+
'Mistral-Small-3.2-24B-Instruct-2506',
|
|
184
|
+
'Mistral-7B-Instruct-v0.3',
|
|
185
|
+
'Mistral-Nemo-Instruct-2407',
|
|
186
|
+
'Qwen3.5-9B',
|
|
187
|
+
'big-pickle',
|
|
188
|
+
'deepseek-v4-flash-free',
|
|
189
|
+
'mimo-v2.5-free',
|
|
190
|
+
'nemotron-3-super-free',
|
|
191
|
+
'minimax-m3-free'
|
|
192
|
+
]);
|
|
193
|
+
|
|
118
194
|
export const getVerdict = (r) => {
|
|
119
195
|
const avg = getAvg(r)
|
|
120
196
|
const wasUpBefore = r.pings.length > 0 && r.pings.some(p => p.code === '200')
|
|
@@ -128,6 +204,34 @@ export const getVerdict = (r) => {
|
|
|
128
204
|
const measurablePings = (r.pings || []).filter(p => measurablePingCodes.has(p.code))
|
|
129
205
|
const p95 = getP95(r)
|
|
130
206
|
|
|
207
|
+
// 📖 Incorporate benchmark data (AI Latency and TPS) if available
|
|
208
|
+
if (r.benchmark && r.benchmark.ok) {
|
|
209
|
+
// AI Latency from benchmark (totalMs)
|
|
210
|
+
const aiLatency = r.benchmark.totalMs
|
|
211
|
+
// TPS from benchmark (tokens per second)
|
|
212
|
+
const tps = r.benchmark.tokensPerSecond
|
|
213
|
+
|
|
214
|
+
// Adjust verdict based on benchmark data
|
|
215
|
+
if (aiLatency < 400) {
|
|
216
|
+
// 📖 Only flag as "Spiky" when we have enough data (≥3 pings) to judge stability
|
|
217
|
+
if (measurablePings.length >= 3 && p95 > 3000) return 'Spiky'
|
|
218
|
+
return 'Perfect'
|
|
219
|
+
}
|
|
220
|
+
if (aiLatency < 1000) {
|
|
221
|
+
if (measurablePings.length >= 3 && p95 > 5000) return 'Spiky'
|
|
222
|
+
return 'Normal'
|
|
223
|
+
}
|
|
224
|
+
if (aiLatency < 3000) return 'Slow'
|
|
225
|
+
if (aiLatency < 5000) return 'Very Slow'
|
|
226
|
+
if (aiLatency < 10000) return 'Unstable'
|
|
227
|
+
|
|
228
|
+
// 📖 High TPS can improve verdict for models with higher latency
|
|
229
|
+
if (tps > 20 && aiLatency < 15000) return 'Slow'
|
|
230
|
+
if (tps > 40 && aiLatency < 20000) return 'Normal'
|
|
231
|
+
if (tps > 60 && aiLatency < 25000) return 'Perfect'
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 📖 Fall back to ping-based verdict if no benchmark data
|
|
131
235
|
if (avg < 400) {
|
|
132
236
|
// 📖 Only flag as "Spiky" when we have enough data (≥3 pings) to judge stability
|
|
133
237
|
if (measurablePings.length >= 3 && p95 > 3000) return 'Spiky'
|
|
@@ -236,7 +340,10 @@ export const getStabilityScore = (r) => {
|
|
|
236
340
|
//
|
|
237
341
|
// 📖 sortDirection 'asc' = ascending (smallest first), 'desc' = descending (largest first)
|
|
238
342
|
export const sortResults = (results, sortColumn, sortDirection, { benchmarkResults = {} } = {}) => {
|
|
239
|
-
return [...results].
|
|
343
|
+
return [...results].map(r => ({
|
|
344
|
+
...r,
|
|
345
|
+
benchmark: benchmarkResults?.[`${r.providerKey}/${r.modelId}`]
|
|
346
|
+
})).sort((a, b) => {
|
|
240
347
|
let cmp = 0
|
|
241
348
|
|
|
242
349
|
switch (sortColumn) {
|
package/src/tui/render-table.js
CHANGED
|
@@ -794,66 +794,77 @@ export function renderTable({
|
|
|
794
794
|
})() : statusText
|
|
795
795
|
const status = statusColor(padEndDisplay(statusDisplayText, wStatus))
|
|
796
796
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
797
|
+
// 📖 Verdict column - use getVerdict() for stability-aware verdicts, then render with emoji
|
|
798
|
+
const verdict = getVerdict(r)
|
|
799
|
+
let verdictText, verdictIcon, verdictColor
|
|
800
|
+
// 📖 Verdict colors follow the same green→red gradient as TIER_COLOR / SWE%
|
|
801
|
+
switch (verdict) {
|
|
802
|
+
case 'Perfect':
|
|
803
|
+
verdictIcon = '🟩✨'
|
|
804
|
+
verdictText = `${verdictIcon} Perfect`
|
|
805
|
+
verdictColor = themeColors.successBold
|
|
806
|
+
break
|
|
807
|
+
case 'Normal':
|
|
808
|
+
verdictIcon = '🟢✨'
|
|
809
|
+
verdictText = `${verdictIcon} Normal`
|
|
810
|
+
verdictColor = themeColors.metricGood
|
|
811
|
+
break
|
|
812
|
+
case 'Spiky':
|
|
813
|
+
verdictIcon = '🟡✨'
|
|
814
|
+
verdictText = `${verdictIcon} Spiky`
|
|
815
|
+
verdictColor = (text) => chalk.bold.rgb(...getTierRgb('A+'))(text)
|
|
816
|
+
break
|
|
817
|
+
case 'Slow':
|
|
818
|
+
verdictIcon = '🟠✨'
|
|
819
|
+
verdictText = `${verdictIcon} Slow`
|
|
820
|
+
verdictColor = (text) => chalk.bold.rgb(...getTierRgb('A-'))(text)
|
|
821
|
+
break
|
|
822
|
+
case 'Very Slow':
|
|
823
|
+
verdictIcon = '🔴✨'
|
|
824
|
+
verdictText = `${verdictIcon} Very Slow`
|
|
825
|
+
verdictColor = (text) => chalk.bold.rgb(...getTierRgb('B+'))(text)
|
|
826
|
+
break
|
|
827
|
+
case 'Overloaded':
|
|
828
|
+
verdictIcon = '🔥✨'
|
|
829
|
+
verdictText = `${verdictIcon} Overloaded`
|
|
830
|
+
verdictColor = (text) => chalk.bold.rgb(...getTierRgb('B'))(text)
|
|
831
|
+
break
|
|
832
|
+
case 'Unstable':
|
|
833
|
+
// 📖 Avoid ⚠️ here: its variation selector has inconsistent terminal width and shifts the tiny ❔ column.
|
|
834
|
+
verdictIcon = '🟥✨'
|
|
835
|
+
verdictText = `${verdictIcon} Unstable`
|
|
836
|
+
verdictColor = themeColors.errorBold
|
|
837
|
+
break
|
|
838
|
+
case 'Not Active':
|
|
839
|
+
verdictIcon = '⚫✨'
|
|
840
|
+
verdictText = `${verdictIcon} Not Active`
|
|
841
|
+
verdictColor = themeColors.dim
|
|
842
|
+
break
|
|
843
|
+
case 'Pending':
|
|
844
|
+
verdictIcon = '⏳✨'
|
|
845
|
+
verdictText = `${verdictIcon} Pending`
|
|
846
|
+
verdictColor = themeColors.dim
|
|
847
|
+
break
|
|
848
|
+
default:
|
|
849
|
+
verdictIcon = '💀✨'
|
|
850
|
+
verdictText = `${verdictIcon} Unusable`
|
|
851
|
+
verdictColor = (text) => chalk.bold.rgb(...getTierRgb('C'))(text)
|
|
852
|
+
break
|
|
853
|
+
}
|
|
854
|
+
// 📖 Use padEndDisplay to account for emoji display width (2 cols each) so all rows align
|
|
855
|
+
const speedCell = verdictColor(padEndDisplay(verdictText, W_VERDICT))
|
|
856
|
+
const moodCell = padEndDisplay(verdictIcon, W_MOOD)
|
|
857
|
+
|
|
858
|
+
// 📖 Add NEW badge for recently added models
|
|
859
|
+
const isNewModel = NEW_MODELS.has(r.modelId) || NEW_MODELS.has(`${r.providerKey}/${r.modelId}`)
|
|
860
|
+
const newBadge = isNewModel ? '🆕' : ''
|
|
861
|
+
const verdictTextWithBadge = newBadge ? `${newBadge} ${verdictText}` : verdictText
|
|
862
|
+
const speedCellWithBadge = verdictColor(padEndDisplay(verdictTextWithBadge, W_VERDICT))
|
|
863
|
+
const moodCellWithBadge = padEndDisplay(verdictIcon, W_MOOD)
|
|
864
|
+
|
|
865
|
+
// 📖 Use the version with badge for display
|
|
866
|
+
const speedCell = speedCellWithBadge
|
|
867
|
+
const moodCell = moodCellWithBadge
|
|
857
868
|
|
|
858
869
|
// 📖 Stability column - composite score (0–100) from p95 + jitter + spikes + uptime
|
|
859
870
|
// 📖 Left-aligned to sit flush under the column header
|