free-coding-models 0.5.58 → 0.5.60
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 +64 -0
- package/bin/free-coding-models.js +12 -0
- package/changelog/v0.5.59.md +52 -0
- package/changelog/v0.5.60.md +54 -0
- package/package.json +2 -2
- package/src/core/ping.js +7 -2
- package/src/core/provider-quota-fetchers.js +254 -2
- package/src/core/router-daemon.js +115 -3
- package/src/core/runtime-telemetry.js +541 -0
- package/src/core/utils.js +15 -0
- package/src/tui/app.js +22 -1
- package/src/tui/key-handler.js +80 -0
- package/src/tui/render-table.js +28 -2
- package/src/tui/tui-state.js +9 -0
- package/web/dist/assets/{index-4Jq00xKl.js → index-CQQJkofy.js} +2 -2
- package/web/dist/assets/{index-BkK1gdJN.css → index-wI9xrm0w.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/components/router/RouterView.jsx +68 -0
- package/web/src/components/router/RouterView.module.css +53 -0
package/src/tui/key-handler.js
CHANGED
|
@@ -836,6 +836,40 @@ export function createKeyHandler(ctx) {
|
|
|
836
836
|
state.tokenUsageError = null
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
+
// 📖 Runtime Report overlay (t3): per-model real-world breakdown + recent calls.
|
|
840
|
+
// 📖 Reads the local ~/.free-coding-models/runtime-telemetry.json file directly
|
|
841
|
+
// 📖 (the daemon writes to the same file; the CLI doesn't need to talk to it).
|
|
842
|
+
async function openRuntimeReportOverlay() {
|
|
843
|
+
state.runtimeReportOpen = true
|
|
844
|
+
state.runtimeReportScrollOffset = 0
|
|
845
|
+
state.runtimeReportError = null
|
|
846
|
+
state.runtimeReportData = null
|
|
847
|
+
state.runtimeReportSelectedKey = null
|
|
848
|
+
try {
|
|
849
|
+
const { loadRuntimeTelemetry, getAllModelTelemetry } = await import('../core/runtime-telemetry.js')
|
|
850
|
+
const cache = loadRuntimeTelemetry()
|
|
851
|
+
const all = getAllModelTelemetry({ cache })
|
|
852
|
+
const entries = Object.entries(all).sort((a, b) => (b[1].totalCalls || 0) - (a[1].totalCalls || 0))
|
|
853
|
+
state.runtimeReportData = entries
|
|
854
|
+
// 📖 Pre-select the currently-focused model if it has telemetry.
|
|
855
|
+
const focused = state.visibleSorted?.[state.cursor]
|
|
856
|
+
if (focused) {
|
|
857
|
+
const key = `${focused.providerKey}/${focused.modelId}`
|
|
858
|
+
if (entries.find(([k]) => k === key)) state.runtimeReportSelectedKey = key
|
|
859
|
+
}
|
|
860
|
+
} catch (err) {
|
|
861
|
+
state.runtimeReportError = err?.message || 'Failed to load runtime telemetry'
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function closeRuntimeReportOverlay() {
|
|
866
|
+
state.runtimeReportOpen = false
|
|
867
|
+
state.runtimeReportScrollOffset = 0
|
|
868
|
+
state.runtimeReportError = null
|
|
869
|
+
state.runtimeReportData = null
|
|
870
|
+
state.runtimeReportSelectedKey = null
|
|
871
|
+
}
|
|
872
|
+
|
|
839
873
|
|
|
840
874
|
function cycleToolMode() {
|
|
841
875
|
const modeOrder = getToolModeOrder()
|
|
@@ -1355,6 +1389,7 @@ export function createKeyHandler(ctx) {
|
|
|
1355
1389
|
case 'sort-verdict': return setSortColumnFromCommand('verdict')
|
|
1356
1390
|
case 'sort-stability': return setSortColumnFromCommand('stability')
|
|
1357
1391
|
case 'sort-uptime': return setSortColumnFromCommand('uptime')
|
|
1392
|
+
case 'sort-realworld': return setSortColumnFromCommand('realworld')
|
|
1358
1393
|
case 'open-settings': return openSettingsOverlay()
|
|
1359
1394
|
case 'open-help':
|
|
1360
1395
|
state.helpVisible = true
|
|
@@ -1366,6 +1401,7 @@ export function createKeyHandler(ctx) {
|
|
|
1366
1401
|
case 'open-router-dashboard': return openRouterDashboardOverlay(state)
|
|
1367
1402
|
case 'open-playground': return openPlaygroundOverlay(state)
|
|
1368
1403
|
case 'open-token-usage': return openTokenUsageOverlay()
|
|
1404
|
+
case 'open-runtime-report': return openRuntimeReportOverlay()
|
|
1369
1405
|
case 'open-install-endpoints': return openInstallEndpointsOverlay()
|
|
1370
1406
|
case 'open-installed-models': return openInstalledModelsOverlay()
|
|
1371
1407
|
case 'action-cycle-theme': return cycleGlobalTheme()
|
|
@@ -2120,6 +2156,42 @@ export function createKeyHandler(ctx) {
|
|
|
2120
2156
|
return
|
|
2121
2157
|
}
|
|
2122
2158
|
|
|
2159
|
+
// 📖 Runtime Report overlay (t3): Shift+W. Per-model real-world breakdown +
|
|
2160
|
+
// 📖 recent calls. Scrolling with up/down/pageup/pagedown/escape.
|
|
2161
|
+
if (state.runtimeReportOpen) {
|
|
2162
|
+
if (key.ctrl && key.name === 'c') { exit(0); return }
|
|
2163
|
+
const pageStep = Math.max(1, (state.terminalRows || 1) - 4)
|
|
2164
|
+
if (key.name === 'escape') {
|
|
2165
|
+
closeRuntimeReportOverlay()
|
|
2166
|
+
return
|
|
2167
|
+
}
|
|
2168
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
2169
|
+
state.runtimeReportScrollOffset = Math.max(0, state.runtimeReportScrollOffset - 1)
|
|
2170
|
+
return
|
|
2171
|
+
}
|
|
2172
|
+
if (key.name === 'down' || key.name === 'j') {
|
|
2173
|
+
state.runtimeReportScrollOffset += 1
|
|
2174
|
+
return
|
|
2175
|
+
}
|
|
2176
|
+
if (key.name === 'pageup') {
|
|
2177
|
+
state.runtimeReportScrollOffset = Math.max(0, state.runtimeReportScrollOffset - pageStep)
|
|
2178
|
+
return
|
|
2179
|
+
}
|
|
2180
|
+
if (key.name === 'pagedown') {
|
|
2181
|
+
state.runtimeReportScrollOffset += pageStep
|
|
2182
|
+
return
|
|
2183
|
+
}
|
|
2184
|
+
if (key.name === 'home') {
|
|
2185
|
+
state.runtimeReportScrollOffset = 0
|
|
2186
|
+
return
|
|
2187
|
+
}
|
|
2188
|
+
if (key.name === 'end') {
|
|
2189
|
+
state.runtimeReportScrollOffset = Number.MAX_SAFE_INTEGER
|
|
2190
|
+
return
|
|
2191
|
+
}
|
|
2192
|
+
return
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2123
2195
|
// 📖 Router Onboarding overlay: shown on first launch. Y=yes enable, N=not now, Esc=cancel.
|
|
2124
2196
|
if (state.routerOnboardingOpen) {
|
|
2125
2197
|
if (key.ctrl && key.name === 'c') { exit(0); return }
|
|
@@ -2872,6 +2944,14 @@ export function createKeyHandler(ctx) {
|
|
|
2872
2944
|
return
|
|
2873
2945
|
}
|
|
2874
2946
|
|
|
2947
|
+
// 📖 Shift+W: open the Runtime Report overlay (t3) — per-model real-world
|
|
2948
|
+
// 📖 success rate + throughput + recent calls. See command palette
|
|
2949
|
+
// 📖 'open-runtime-report' for the same action.
|
|
2950
|
+
if (key.name === 'w' && key.shift && !key.alt && !key.ctrl && !key.meta) {
|
|
2951
|
+
openRuntimeReportOverlay()
|
|
2952
|
+
return
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2875
2955
|
// 📖 E cycles: Normal → Configured only → Usable only → Normal
|
|
2876
2956
|
// 📖 Configured only: hides models with no key or noauth/auth_error health
|
|
2877
2957
|
// 📖 Usable only: only shows models with Health UP and Verdict ≤ Slow (Perfect/Normal/Slow)
|
package/src/tui/render-table.js
CHANGED
|
@@ -155,6 +155,10 @@ 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
|
+
* quota?: Record<string, { // t2: live quota from response headers
|
|
159
|
+
* remaining: number, limit: number, percent: number,
|
|
160
|
+
* source: 'header'|'endpoint', lastUpdated: number, windowType?: string,
|
|
161
|
+
* }>,
|
|
158
162
|
* }} opts
|
|
159
163
|
* @returns {string}
|
|
160
164
|
*/
|
|
@@ -205,6 +209,7 @@ export function renderTable({
|
|
|
205
209
|
probeCacheMisses = 0,
|
|
206
210
|
probeCacheBrokenHidden = 0,
|
|
207
211
|
showBrokenMode = false,
|
|
212
|
+
quota = {},
|
|
208
213
|
} = _) {
|
|
209
214
|
// 📖 Filter out hidden models for display
|
|
210
215
|
const visibleResults = results.filter(r => !r.hidden)
|
|
@@ -1219,8 +1224,27 @@ export function renderTable({
|
|
|
1219
1224
|
probeCacheLabel = chalk.bgRgb(40, 90, 140).rgb(220, 235, 255).bold(` ${cachedTxt}${brokenTxt} `)
|
|
1220
1225
|
}
|
|
1221
1226
|
|
|
1222
|
-
// 📖
|
|
1223
|
-
|
|
1227
|
+
// 📖 Passive quota chip (t2): `📊 groq 78% · sambanova 41%` (top-N depleted first).
|
|
1228
|
+
// 📖 Shows live rate-limit headers that the daemon / pings collected from upstream
|
|
1229
|
+
// 📖 responses. Zero extra network requests — see provider-quota-fetchers.js.
|
|
1230
|
+
// 📖 Cap at 5 providers to avoid footer bloat; pick most-depleted first.
|
|
1231
|
+
let quotaLabel = ''
|
|
1232
|
+
if (quota && typeof quota === 'object') {
|
|
1233
|
+
const entries = Object.entries(quota)
|
|
1234
|
+
.filter(([, s]) => s && typeof s.percent === 'number')
|
|
1235
|
+
.sort((a, b) => a[1].percent - b[1].percent) // 📖 most depleted first
|
|
1236
|
+
.slice(0, 5)
|
|
1237
|
+
if (entries.length > 0) {
|
|
1238
|
+
const parts = entries.map(([providerKey, snap]) => {
|
|
1239
|
+
const icon = snap.percent <= 10 ? '🚨' : snap.percent <= 25 ? '⚠️ ' : '📊'
|
|
1240
|
+
return `${icon} ${providerKey} ${snap.percent}%`
|
|
1241
|
+
})
|
|
1242
|
+
quotaLabel = chalk.bgRgb(60, 100, 60).rgb(220, 255, 220).bold(` ${parts.join(' · ')} `)
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// 📖 Line 3: Speed Test + Global Benchmark + Probe + Probe-cache + Quota + Last release
|
|
1247
|
+
if (releaseLabel || speedTestLabel || globalBenchmarkLabel || probeLabel || probeCacheLabel || quotaLabel) {
|
|
1224
1248
|
const parts = [
|
|
1225
1249
|
{ text: ' ', key: null },
|
|
1226
1250
|
{ text: speedTestLabel, key: 'a' },
|
|
@@ -1230,6 +1254,8 @@ export function renderTable({
|
|
|
1230
1254
|
{ text: probeLabel, key: null },
|
|
1231
1255
|
{ text: probeCacheLabel ? ' ' : '', key: null },
|
|
1232
1256
|
{ text: probeCacheLabel, key: null },
|
|
1257
|
+
{ text: quotaLabel ? ' ' : '', key: null },
|
|
1258
|
+
{ text: quotaLabel, key: null },
|
|
1233
1259
|
{ text: ' ', key: null },
|
|
1234
1260
|
{ text: releaseLabel, key: null },
|
|
1235
1261
|
]
|
package/src/tui/tui-state.js
CHANGED
|
@@ -297,6 +297,15 @@ export function createTuiState({
|
|
|
297
297
|
probeCacheTtlMs: null, // 📖 Set in app.js from cliArgs.probeTtlMs || DEFAULT
|
|
298
298
|
showBrokenMode: false,
|
|
299
299
|
|
|
300
|
+
// 📖 Runtime telemetry overlay state (t3): Shift+W opens the Runtime Report
|
|
301
|
+
// 📖 overlay. Data is loaded fresh from ~/.free-coding-models/runtime-telemetry.json
|
|
302
|
+
// 📖 when the overlay opens — no polling, no daemon dependency.
|
|
303
|
+
runtimeReportOpen: false,
|
|
304
|
+
runtimeReportScrollOffset: 0,
|
|
305
|
+
runtimeReportError: null,
|
|
306
|
+
runtimeReportData: null, // 📖 Array<[key, ModelTelemetry]>
|
|
307
|
+
runtimeReportSelectedKey: null, // 📖 Pre-selected model key
|
|
308
|
+
|
|
300
309
|
// 📖 Header click flash animation: briefly highlights the clicked column header
|
|
301
310
|
// 📖 with an inverse/bright style for ~250ms (3 frames at 12 FPS).
|
|
302
311
|
headerFlashColumn: null, // 📖 Column name being flashed (null = no flash active)
|