Skip to content

Commit 674ada5

Browse files
committed
0.5.42
1 parent 735da7d commit 674ada5

5 files changed

Lines changed: 147 additions & 127 deletions

File tree

changelog/v0.5.42.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Changelog v0.5.42 - 2026-07-07
2+
3+
### Added
4+
- Added an animated magenta progress spinner in the Pi TUI status line during candidate scans.
5+
- Added live rolling displays of the specific model and provider currently being probed or benchmarked.
6+
- Integrated the provider name (e.g., `Cerebras`, `Nvidia`) directly into the active model name shown in the Pi status line.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "free-coding-models",
3-
"version": "0.5.41",
3+
"version": "0.5.42",
44
"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.",
55
"keywords": [
66
"nvidia",

pi-extension/lib/direct-scanner.js

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ping } from 'free-coding-models/src/core/ping.js'
1414
import { benchmarkModel } from 'free-coding-models/src/core/benchmark.js'
1515
import { loadAllApiKeys } from './api-keys.js'
1616
import { parseSweScore } from './model-ranker.js'
17+
import chalk from 'chalk'
1718

1819
/**
1920
* @typedef {object} ScannedModel
@@ -81,15 +82,42 @@ export async function directScan(options = {}) {
8182
const totalPings = sortedCandidates.length
8283
let completedPings = 0
8384

84-
if (options.onProgress) {
85-
options.onProgress(`📡 Pinging models: 0% (0/${totalPings})`)
85+
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
86+
let spinnerIndex = 0
87+
let currentAction = 'Probing'
88+
let currentTargets = []
89+
let pct = 0
90+
let completed = 0
91+
let total = totalPings
92+
93+
const updateProgress = () => {
94+
if (!options.onProgress) return
95+
const spinner = chalk.bold.magenta(spinnerFrames[spinnerIndex])
96+
const actionStr = chalk.bold.yellow(`${currentAction}:`)
97+
const targetStr = currentTargets.length > 0
98+
? chalk.cyan(currentTargets.slice(-2).join(', '))
99+
: chalk.gray('...')
100+
const pctStr = chalk.bold.cyan(`${pct}%`)
101+
const counterStr = chalk.gray(`(${completed}/${total})`)
102+
103+
options.onProgress(`${spinner} ${actionStr} ${targetStr}${pctStr} ${counterStr}`)
86104
}
87105

106+
const intervalId = setInterval(() => {
107+
spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length
108+
updateProgress()
109+
}, 80)
110+
88111
// 📖 Step 3: Ping candidate models in parallel (15s timeout inside ping.js)
89112
const pingPromises = sortedCandidates.map(async (candidate) => {
90-
const { modelId, providerKey, sourceInfo } = candidate
113+
const { modelId, providerKey, sourceInfo, label } = candidate
91114
const apiKey = keys.get(providerKey) || null
92115
const url = sourceInfo.url
116+
const providerName = sourceInfo.name || providerKey
117+
const targetDesc = `${label} [${providerName}]`
118+
119+
currentTargets.push(targetDesc)
120+
updateProgress()
93121

94122
try {
95123
const res = await ping(apiKey, modelId, providerKey, url)
@@ -105,7 +133,7 @@ export async function directScan(options = {}) {
105133
return {
106134
...candidate,
107135
apiKey,
108-
providerName: sourceInfo.name || providerKey,
136+
providerName,
109137
providerUrl: url,
110138
status,
111139
latencyMs: typeof res.ms === 'number' ? res.ms : null,
@@ -117,7 +145,7 @@ export async function directScan(options = {}) {
117145
return {
118146
...candidate,
119147
apiKey,
120-
providerName: sourceInfo.name || providerKey,
148+
providerName,
121149
providerUrl: url,
122150
status: 'down',
123151
latencyMs: null,
@@ -127,10 +155,10 @@ export async function directScan(options = {}) {
127155
}
128156
} finally {
129157
completedPings++
130-
const pct = Math.round((completedPings / totalPings) * 100)
131-
if (options.onProgress) {
132-
options.onProgress(`📡 Pinging models: ${pct}% (${completedPings}/${totalPings})`)
133-
}
158+
pct = Math.round((completedPings / totalPings) * 100)
159+
completed = completedPings
160+
currentTargets = currentTargets.filter(t => t !== targetDesc)
161+
updateProgress()
134162
}
135163
})
136164

@@ -147,24 +175,31 @@ export async function directScan(options = {}) {
147175
const usableAlive = aliveModels.filter(m => m.status === 'up')
148176

149177
if (usableAlive.length === 0) {
178+
clearInterval(intervalId)
150179
return aliveModels // 📖 Return what we have (mostly timeouts/auth_errors)
151180
}
152181

153182
// 📖 Step 4: Run AI Latency + TPS Benchmark on top 5 alive candidates
154-
// 📖 Sort them again by SWE-bench to benchmark the smartest ones
155183
const benchmarkCandidates = usableAlive
156184
.sort((a, b) => parseSweScore(b.sweScore) - parseSweScore(a.sweScore))
157185
.slice(0, 5)
158186

187+
currentAction = 'Benchmarking'
188+
completed = 0
189+
pct = 0
190+
total = benchmarkCandidates.length
191+
currentTargets = []
192+
updateProgress()
193+
159194
const totalBenchmarks = benchmarkCandidates.length
160195
let completedBenchmarks = 0
161196

162-
if (options.onProgress && totalBenchmarks > 0) {
163-
options.onProgress(`⚡ AI latency bench: 0% (0/${totalBenchmarks})`)
164-
}
165-
166197
const benchmarkPromises = benchmarkCandidates.map(async (model) => {
167-
const { modelId, providerKey, providerUrl, apiKey } = model
198+
const { modelId, providerKey, providerUrl, apiKey, label, providerName } = model
199+
const targetDesc = `${label} [${providerName}]`
200+
currentTargets.push(targetDesc)
201+
updateProgress()
202+
168203
try {
169204
// 📖 Limit benchmark retries to 1 with a short delay for speed
170205
const res = await benchmarkModel({
@@ -187,15 +222,17 @@ export async function directScan(options = {}) {
187222
// 📖 Catch benchmark errors gracefully
188223
} finally {
189224
completedBenchmarks++
190-
const pct = Math.round((completedBenchmarks / totalBenchmarks) * 100)
191-
if (options.onProgress) {
192-
options.onProgress(`⚡ AI latency bench: ${pct}% (${completedBenchmarks}/${totalBenchmarks})`)
193-
}
225+
pct = Math.round((completedBenchmarks / totalBenchmarks) * 100)
226+
completed = completedBenchmarks
227+
currentTargets = currentTargets.filter(t => t !== targetDesc)
228+
updateProgress()
194229
}
195230
return { modelId, tps: null, totalMs: null }
196231
})
197232

198233
const benchmarkResults = await Promise.allSettled(benchmarkPromises)
234+
clearInterval(intervalId)
235+
199236
const benchMap = new Map()
200237

201238
for (const res of benchmarkResults) {

pi-extension/lib/scanner.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export async function handleModelSelection(model, options = {}) {
134134
api: 'openai-completions',
135135
models: [{
136136
id: model.modelId,
137-
name: `${model.label} [FCM ${model.tier}]`,
137+
name: `${model.label} (${model.providerName}) [FCM ${model.tier}]`,
138138
contextWindow,
139139
maxTokens: 8192,
140140
reasoning: model.tier === 'S+' || model.tier === 'S',

pi-extension/request-params.json

Lines changed: 83 additions & 106 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)