free-coding-models 0.5.60 โ 0.5.61
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.
- package/README.md +2 -0
- package/bin/free-coding-models.js +19 -0
- package/changelog/v0.5.61.md +65 -0
- package/package.json +6 -2
- package/src/core/extended-benchmarks.js +421 -0
- package/src/core/model-merger.js +155 -0
- package/src/core/models-dev-fetcher.js +210 -0
- package/src/core/models-dev-index.js +311 -0
- package/src/core/models-drift.js +296 -0
- package/src/core/utils.js +14 -0
- package/src/data/benchmarks.json +302 -0
- package/src/tui/app.js +86 -0
- package/src/tui/cli-help.js +2 -0
- package/src/tui/render-table.js +38 -2
- package/web/dist/assets/{index-CQQJkofy.js โ index-4IyXp-vf.js} +2 -2
- package/web/dist/index.html +1 -1
- package/web/server.js +39 -0
package/src/tui/app.js
CHANGED
|
@@ -149,6 +149,69 @@ const mergedModels = buildMergedModels(MODELS)
|
|
|
149
149
|
const mergedModelByLabel = new Map(mergedModels.map(m => [m.label, m]))
|
|
150
150
|
setOpenCodeModelData(mergedModels, mergedModelByLabel)
|
|
151
151
|
|
|
152
|
+
// ๐ Enrichment (t4 + t5): module-level state. The sync part (extended
|
|
153
|
+
// ๐ benchmark catalog stats) is computed lazily on first read via the
|
|
154
|
+
// ๐ getter, so we don't block module load. The async part (models.dev
|
|
155
|
+
// ๐ fetch) runs inside runApp as a background task โ never at module load,
|
|
156
|
+
// ๐ never blocking the TUI from rendering.
|
|
157
|
+
// ๐
|
|
158
|
+
// ๐ The TUI reads from this object on every render to update the footer chips.
|
|
159
|
+
const moduleEnrichmentStats = {
|
|
160
|
+
bench: { total: 0, lastUpdated: 'unknown', source: 'unknown' },
|
|
161
|
+
modelsDev: { 'sources.js': 0, 'models.dev': 0, cached: false },
|
|
162
|
+
}
|
|
163
|
+
let _benchStatsLoaded = false
|
|
164
|
+
function ensureBenchStatsLoaded() {
|
|
165
|
+
if (_benchStatsLoaded) return
|
|
166
|
+
_benchStatsLoaded = true
|
|
167
|
+
try {
|
|
168
|
+
// ๐ require() works synchronously and is safe at module load because
|
|
169
|
+
// ๐ model-merger.js has no top-level await side effects.
|
|
170
|
+
const { getEnrichmentStats } = require('../core/model-merger.js')
|
|
171
|
+
moduleEnrichmentStats.bench = getEnrichmentStats().extendedBench
|
|
172
|
+
} catch (err) {
|
|
173
|
+
if (process.env.FCM_BENCH_DEBUG) console.warn('[t4] enrichment stats failed:', err?.message ?? err)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ๐ Background enrichment: fetch models.dev and update moduleEnrichmentStats
|
|
178
|
+
// ๐ when it resolves. Fully decoupled from runApp โ even if the fetch hangs
|
|
179
|
+
// ๐ for 30s+, the TUI keeps rendering. The chip is updated by the next render
|
|
180
|
+
// ๐ after resolution.
|
|
181
|
+
function runModelsDevEnrichmentInBackground(merged) {
|
|
182
|
+
// ๐ Fire-and-forget; never awaited. If it throws, the catch swallows it.
|
|
183
|
+
;(async () => {
|
|
184
|
+
try {
|
|
185
|
+
// ๐ Bound the total wait to 12s (worst case 3ร4s retries). If the
|
|
186
|
+
// ๐ network is truly down, the chip stays at "0 live" and the TUI
|
|
187
|
+
// ๐ is unaffected. Callers can still trigger a refresh via the
|
|
188
|
+
// ๐ settings panel (future work).
|
|
189
|
+
const timeout = new Promise(resolve => setTimeout(() => resolve(null), 12_000))
|
|
190
|
+
const { overlayModelsDevMetadata } = await import('../core/model-merger.js')
|
|
191
|
+
const result = await Promise.race([
|
|
192
|
+
overlayModelsDevMetadata(merged, { mutate: true }),
|
|
193
|
+
timeout,
|
|
194
|
+
])
|
|
195
|
+
if (!result) {
|
|
196
|
+
if (process.env.FCM_BENCH_DEBUG) console.warn('[t5] models.dev fetch timed out (12s)')
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
let live = 0, curated = 0
|
|
200
|
+
for (const m of result) {
|
|
201
|
+
if (m.metaSource === 'models.dev') live++
|
|
202
|
+
else if (m.metaSource === 'sources.js') curated++
|
|
203
|
+
}
|
|
204
|
+
moduleEnrichmentStats.modelsDev = { 'sources.js': curated, 'models.dev': live, cached: true }
|
|
205
|
+
// ๐ Note: state is owned by runApp โ we don't have a direct ref here.
|
|
206
|
+
// ๐ The footer reads moduleEnrichmentStats lazily on each render, so
|
|
207
|
+
// ๐ the next render will pick up the new counts. We do NOT trigger a
|
|
208
|
+
// ๐ render from here (we're not in the render context).
|
|
209
|
+
} catch (err) {
|
|
210
|
+
if (process.env.FCM_BENCH_DEBUG) console.warn('[t5] models.dev overlay failed:', err?.message ?? err)
|
|
211
|
+
}
|
|
212
|
+
})()
|
|
213
|
+
}
|
|
214
|
+
|
|
152
215
|
// ๐ Provider quota cache is managed by lib/provider-quota-fetchers.js (TTL + backoff).
|
|
153
216
|
// ๐ Usage placeholder logic uses isKnownQuotaTelemetry() from lib/quota-capabilities.js.
|
|
154
217
|
|
|
@@ -189,6 +252,16 @@ const LOCAL_VERSION = pkg.version
|
|
|
189
252
|
// ๐ OpenCode helpers are imported from ../src/opencode.js
|
|
190
253
|
|
|
191
254
|
export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
255
|
+
// ๐ t4: prime the sync extended-benchmark stats so the footer chip is
|
|
256
|
+
// ๐ accurate on the first render (no async, no network โ just a sync read).
|
|
257
|
+
ensureBenchStatsLoaded()
|
|
258
|
+
|
|
259
|
+
// ๐ t5: kick off the async models.dev enrichment in the background. This
|
|
260
|
+
// ๐ does NOT block runApp โ the TUI starts rendering immediately with
|
|
261
|
+
// ๐ curated values, then re-renders when the models.dev fetch resolves.
|
|
262
|
+
// ๐ If the network is unreachable, the chip just stays at "0 live" forever
|
|
263
|
+
// ๐ (no user-visible freeze). The background task is bounded to 12s.
|
|
264
|
+
runModelsDevEnrichmentInBackground(mergedModels)
|
|
192
265
|
|
|
193
266
|
// ๐ Detect user active terminal theme
|
|
194
267
|
detectActiveTheme(config.settings?.theme || 'auto')
|
|
@@ -505,6 +578,8 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
505
578
|
}
|
|
506
579
|
state.probeCacheHits = probeCacheHits
|
|
507
580
|
state.probeCacheMisses = probeCacheMisses
|
|
581
|
+
// ๐ Enrichment (t4 + t5): pulled fresh on every render from moduleEnrichmentStats
|
|
582
|
+
// ๐ (see the tableOpts construction below). No need to mirror into state here.
|
|
508
583
|
state.probeCacheBrokenHidden = state.results.filter(r => r.cachedBroken && r.hidden).length
|
|
509
584
|
|
|
510
585
|
// ๐ Runtime telemetry (t3): load the per-model metrics file + compute the
|
|
@@ -971,6 +1046,17 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
971
1046
|
probeCacheMisses: state.probeCacheMisses || 0,
|
|
972
1047
|
probeCacheBrokenHidden: state.probeCacheBrokenHidden || 0,
|
|
973
1048
|
showBrokenMode: !!state.showBrokenMode,
|
|
1049
|
+
// ๐ Enrichment chips (t4 + t5): benchmark catalog + models.dev provenance
|
|
1050
|
+
// ๐ Read fresh from moduleEnrichmentStats on every render so the async
|
|
1051
|
+
// ๐ models.dev fetch (background, never blocks the TUI) is reflected as
|
|
1052
|
+
// ๐ soon as it resolves โ even if the user isn't pressing keys.
|
|
1053
|
+
enrichmentBenchCount: moduleEnrichmentStats.bench.total || 0,
|
|
1054
|
+
enrichmentBenchLastUpdated: moduleEnrichmentStats.bench.lastUpdated || null,
|
|
1055
|
+
metaSourceCounts: {
|
|
1056
|
+
'sources.js': moduleEnrichmentStats.modelsDev['sources.js'] || 0,
|
|
1057
|
+
'models.dev': moduleEnrichmentStats.modelsDev['models.dev'] || 0,
|
|
1058
|
+
},
|
|
1059
|
+
modelsDevCacheCached: moduleEnrichmentStats.modelsDev.cached === true,
|
|
974
1060
|
// ๐ Passive quota (t2): live snapshots from response headers, populated
|
|
975
1061
|
// ๐ by pings (every ping pre-warms quota for its provider). See
|
|
976
1062
|
// ๐ src/core/provider-quota-fetchers.js for getAllPassiveQuotas().
|
package/src/tui/cli-help.js
CHANGED
|
@@ -36,6 +36,8 @@ const ANALYSIS_FLAGS = [
|
|
|
36
36
|
{ flag: '--reprobe, --no-cache', description: 'Force-rebuild the persistent probe-cache this run (skip cached healthy models)' },
|
|
37
37
|
{ flag: '--probe-ttl <ms>', description: 'Override probe-cache TTL (default 24h); healthy models within TTL are not re-pinged' },
|
|
38
38
|
{ flag: '--show-broken', description: "Don't auto-hide models that the probe-cache marked broken (one-shot override)" },
|
|
39
|
+
{ flag: '--check-drift', description: 'Diff sources.js against models.dev and print a drift report; exit 1 on mismatch (t5)' },
|
|
40
|
+
{ flag: '--drift-threshold <N>', description: 'Only fail --check-drift when N+ mismatches are found (default: 0 = any drift)' },
|
|
39
41
|
]
|
|
40
42
|
|
|
41
43
|
const CONFIG_FLAGS = [
|
package/src/tui/render-table.js
CHANGED
|
@@ -155,6 +155,13 @@ export const PROVIDER_COLOR = new Proxy({}, {
|
|
|
155
155
|
* probeCacheMisses?: number, // t1: number of models that needed a live probe
|
|
156
156
|
* probeCacheBrokenHidden?: number, // t1: number of broken models auto-hidden this session
|
|
157
157
|
* showBrokenMode?: boolean, // t1: true when Shift+B has un-hidden broken models
|
|
158
|
+
* enrichmentBenchCount?: number, // t4: extended benchmark catalog size (committed JSON)
|
|
159
|
+
* enrichmentBenchLastUpdated?: string, // t4: lastUpdated field from the catalog _meta
|
|
160
|
+
* metaSourceCounts?: { // t5: count of models per metadata source
|
|
161
|
+
* 'sources.js': number,
|
|
162
|
+
* 'models.dev': number,
|
|
163
|
+
* },
|
|
164
|
+
* modelsDevCacheCached?: boolean, // t5: whether the models.dev cache is still within TTL
|
|
158
165
|
* quota?: Record<string, { // t2: live quota from response headers
|
|
159
166
|
* remaining: number, limit: number, percent: number,
|
|
160
167
|
* source: 'header'|'endpoint', lastUpdated: number, windowType?: string,
|
|
@@ -209,6 +216,10 @@ export function renderTable({
|
|
|
209
216
|
probeCacheMisses = 0,
|
|
210
217
|
probeCacheBrokenHidden = 0,
|
|
211
218
|
showBrokenMode = false,
|
|
219
|
+
enrichmentBenchCount = 0,
|
|
220
|
+
enrichmentBenchLastUpdated = null,
|
|
221
|
+
metaSourceCounts = null,
|
|
222
|
+
modelsDevCacheCached = false,
|
|
212
223
|
quota = {},
|
|
213
224
|
} = _) {
|
|
214
225
|
// ๐ Filter out hidden models for display
|
|
@@ -1224,6 +1235,29 @@ export function renderTable({
|
|
|
1224
1235
|
probeCacheLabel = chalk.bgRgb(40, 90, 140).rgb(220, 235, 255).bold(` ${cachedTxt}${brokenTxt} `)
|
|
1225
1236
|
}
|
|
1226
1237
|
|
|
1238
|
+
// ๐ Enrichment chips (t4 + t5):
|
|
1239
|
+
// ๐ `๐ bench N (date) ยท ๐ก live K / ๐ฆ cached M`
|
|
1240
|
+
// ๐ Always shown when any enrichment state exists.
|
|
1241
|
+
let enrichmentLabel = ''
|
|
1242
|
+
if ((enrichmentBenchCount && enrichmentBenchCount > 0) || metaSourceCounts) {
|
|
1243
|
+
const parts = []
|
|
1244
|
+
if (enrichmentBenchCount && enrichmentBenchCount > 0) {
|
|
1245
|
+
const dateTxt = enrichmentBenchLastUpdated && enrichmentBenchLastUpdated !== 'unknown'
|
|
1246
|
+
? ` (${enrichmentBenchLastUpdated})`
|
|
1247
|
+
: ''
|
|
1248
|
+
parts.push(`๐ bench ${enrichmentBenchCount}${dateTxt}`)
|
|
1249
|
+
}
|
|
1250
|
+
if (metaSourceCounts && (metaSourceCounts['models.dev'] > 0 || metaSourceCounts['sources.js'] > 0)) {
|
|
1251
|
+
const liveTxt = metaSourceCounts['models.dev'] ?? 0
|
|
1252
|
+
const cachedTxt = metaSourceCounts['sources.js'] ?? 0
|
|
1253
|
+
const liveIcon = modelsDevCacheCached ? '๐ก' : '๐ฆ'
|
|
1254
|
+
parts.push(`${liveIcon} ${liveTxt} live ยท ${cachedTxt} curated`)
|
|
1255
|
+
}
|
|
1256
|
+
if (parts.length > 0) {
|
|
1257
|
+
enrichmentLabel = chalk.bgRgb(80, 50, 110).rgb(230, 220, 245).bold(` ${parts.join(' ยท ')} `)
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1227
1261
|
// ๐ Passive quota chip (t2): `๐ groq 78% ยท sambanova 41%` (top-N depleted first).
|
|
1228
1262
|
// ๐ Shows live rate-limit headers that the daemon / pings collected from upstream
|
|
1229
1263
|
// ๐ responses. Zero extra network requests โ see provider-quota-fetchers.js.
|
|
@@ -1243,8 +1277,8 @@ export function renderTable({
|
|
|
1243
1277
|
}
|
|
1244
1278
|
}
|
|
1245
1279
|
|
|
1246
|
-
// ๐ Line 3: Speed Test + Global Benchmark + Probe + Probe-cache + Quota + Last release
|
|
1247
|
-
if (releaseLabel || speedTestLabel || globalBenchmarkLabel || probeLabel || probeCacheLabel || quotaLabel) {
|
|
1280
|
+
// ๐ Line 3: Speed Test + Global Benchmark + Probe + Probe-cache + Enrichment + Quota + Last release
|
|
1281
|
+
if (releaseLabel || speedTestLabel || globalBenchmarkLabel || probeLabel || probeCacheLabel || enrichmentLabel || quotaLabel) {
|
|
1248
1282
|
const parts = [
|
|
1249
1283
|
{ text: ' ', key: null },
|
|
1250
1284
|
{ text: speedTestLabel, key: 'a' },
|
|
@@ -1254,6 +1288,8 @@ export function renderTable({
|
|
|
1254
1288
|
{ text: probeLabel, key: null },
|
|
1255
1289
|
{ text: probeCacheLabel ? ' ' : '', key: null },
|
|
1256
1290
|
{ text: probeCacheLabel, key: null },
|
|
1291
|
+
{ text: enrichmentLabel ? ' ' : '', key: null },
|
|
1292
|
+
{ text: enrichmentLabel, key: null },
|
|
1257
1293
|
{ text: quotaLabel ? ' ' : '', key: null },
|
|
1258
1294
|
{ text: quotaLabel, key: null },
|
|
1259
1295
|
{ text: ' ', key: null },
|