free-coding-models 0.5.21 → 0.5.23
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.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog v0.5.23 - 2026-06-10
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
- **Critical SyntaxError crash on startup** in `src/tui/render-table.js`: `speedCell` and `moodCell` were declared twice in the same scope (lines 855–856 vs 866–867), causing the published 0.5.22 CLI to fail with `SyntaxError: Identifier 'speedCell' has already been declared` before the TUI could render. Removed the dead first declarations; the badge-aware declarations are now the only ones in scope.
|
|
5
|
+
- **Missing `NEW_MODELS` import** in `src/tui/render-table.js`: the NEW (🆕) badge check added in 0.5.22 was reading from `NEW_MODELS` without importing it. Exported the set from `src/core/utils.js` and imported it correctly in the renderer.
|
|
6
|
+
- **Stale renderTable unit test** regex that assumed the old `🟩 Perfect` verdict format. Updated to match the new `🟩✨ Perfect` (sparkle emoji) format introduced in 0.5.22.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- Bumped package version to `0.5.23`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.23",
|
|
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
|
+
export 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
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
} from '../core/constants.js'
|
|
50
50
|
import { themeColors, currentPalette, getProviderRgb, getTierRgb, getReadableTextRgb, getTheme, THEME_BG_RGB } from './theme.js'
|
|
51
51
|
import { TIER_COLOR } from './tier-colors.js'
|
|
52
|
-
import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo } from '../core/utils.js'
|
|
52
|
+
import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo, NEW_MODELS } from '../core/utils.js'
|
|
53
53
|
import { usagePlaceholderForProvider } from '../core/ping.js'
|
|
54
54
|
import { formatBenchmarkLatency, formatBenchmarkTps } from '../core/benchmark.js'
|
|
55
55
|
import { calculateViewport, sortResultsWithPinnedFavorites, padEndDisplay, displayWidth, stripAnsi, fadedRow } from './render-helpers.js'
|
|
@@ -794,66 +794,70 @@ 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
|
+
// 📖 Add NEW badge for recently added models
|
|
855
|
+
// 📖 Use padEndDisplay to account for emoji display width (2 cols each) so all rows align
|
|
856
|
+
const isNewModel = NEW_MODELS.has(r.modelId) || NEW_MODELS.has(`${r.providerKey}/${r.modelId}`)
|
|
857
|
+
const newBadge = isNewModel ? '🆕' : ''
|
|
858
|
+
const verdictTextWithBadge = newBadge ? `${newBadge} ${verdictText}` : verdictText
|
|
859
|
+
const speedCell = verdictColor(padEndDisplay(verdictTextWithBadge, W_VERDICT))
|
|
860
|
+
const moodCell = padEndDisplay(verdictIcon, W_MOOD)
|
|
857
861
|
|
|
858
862
|
// 📖 Stability column - composite score (0–100) from p95 + jitter + spikes + uptime
|
|
859
863
|
// 📖 Left-aligned to sit flush under the column header
|