free-coding-models 0.1.65 → 0.1.66
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 +67 -13
- package/bin/free-coding-models.js +240 -21
- package/lib/config.js +14 -2
- package/package.json +1 -1
- package/sources.js +70 -2
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<img src="https://img.shields.io/npm/v/free-coding-models?color=76b900&label=npm&logo=npm" alt="npm version">
|
|
3
3
|
<img src="https://img.shields.io/node/v/free-coding-models?color=76b900&logo=node.js" alt="node version">
|
|
4
4
|
<img src="https://img.shields.io/npm/l/free-coding-models?color=76b900" alt="license">
|
|
5
|
-
<img src="https://img.shields.io/badge/models-
|
|
6
|
-
<img src="https://img.shields.io/badge/providers-
|
|
5
|
+
<img src="https://img.shields.io/badge/models-134-76b900?logo=nvidia" alt="models count">
|
|
6
|
+
<img src="https://img.shields.io/badge/providers-17-blue" alt="providers count">
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<h1 align="center">free-coding-models</h1>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<strong>Find the fastest coding LLM models in seconds</strong><br>
|
|
27
|
-
<sub>Ping free coding models from
|
|
27
|
+
<sub>Ping free coding models from 17 providers in real-time — pick the best one for OpenCode, OpenClaw, or any AI coding assistant</sub>
|
|
28
28
|
</p>
|
|
29
29
|
|
|
30
30
|
<p align="center">
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
## ✨ Features
|
|
48
48
|
|
|
49
49
|
- **🎯 Coding-focused** — Only LLM models optimized for code generation, not chat or vision
|
|
50
|
-
- **🌐 Multi-provider** —
|
|
50
|
+
- **🌐 Multi-provider** — 134 models from NVIDIA NIM, Groq, Cerebras, SambaNova, OpenRouter, Hugging Face Inference, Replicate, DeepInfra, Fireworks AI, Codestral, Hyperbolic, Scaleway, Google AI, SiliconFlow, Together AI, Cloudflare Workers AI, and Perplexity API
|
|
51
51
|
- **⚙️ Settings screen** — Press `P` to manage provider API keys, enable/disable providers, test keys live, and manually check/install updates
|
|
52
52
|
- **🚀 Parallel pings** — All models tested simultaneously via native `fetch`
|
|
53
53
|
- **📊 Real-time animation** — Watch latency appear live in alternate screen buffer
|
|
@@ -88,10 +88,14 @@ Before using `free-coding-models`, make sure you have:
|
|
|
88
88
|
- **Hyperbolic** — [app.hyperbolic.ai/settings](https://app.hyperbolic.ai/settings) → API Keys ($1 free trial)
|
|
89
89
|
- **Scaleway** — [console.scaleway.com/iam/api-keys](https://console.scaleway.com/iam/api-keys) → IAM → API Keys (1M free tokens)
|
|
90
90
|
- **Google AI Studio** — [aistudio.google.com/apikey](https://aistudio.google.com/apikey) → Get API key (free Gemma models, 14.4K req/day)
|
|
91
|
+
- **SiliconFlow** — [cloud.siliconflow.cn/account/ak](https://cloud.siliconflow.cn/account/ak) → API Keys (free-model quotas vary by model)
|
|
92
|
+
- **Together AI** — [api.together.ai/settings/api-keys](https://api.together.ai/settings/api-keys) → API Keys (credits/promotions vary)
|
|
93
|
+
- **Cloudflare Workers AI** — [dash.cloudflare.com](https://dash.cloudflare.com) → Create API token + set `CLOUDFLARE_ACCOUNT_ID` (Free: 10k neurons/day)
|
|
94
|
+
- **Perplexity API** — [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api) → API Key (tiered limits by spend)
|
|
91
95
|
3. **OpenCode** *(optional)* — [Install OpenCode](https://github.com/opencode-ai/opencode) to use the OpenCode integration
|
|
92
96
|
4. **OpenClaw** *(optional)* — [Install OpenClaw](https://openclaw.ai) to use the OpenClaw integration
|
|
93
97
|
|
|
94
|
-
> 💡 **Tip:** You don't need all
|
|
98
|
+
> 💡 **Tip:** You don't need all seventeen providers. One key is enough to get started. Add more later via the Settings screen (`P` key). Models without a key still show real latency (`🔑 NO KEY`) so you can evaluate providers before signing up.
|
|
95
99
|
|
|
96
100
|
---
|
|
97
101
|
|
|
@@ -172,13 +176,13 @@ When you run `free-coding-models` without `--opencode` or `--openclaw`, you get
|
|
|
172
176
|
Use `↑↓` arrows to select, `Enter` to confirm. Then the TUI launches with your chosen mode shown in the header badge.
|
|
173
177
|
|
|
174
178
|
**How it works:**
|
|
175
|
-
1. **Ping phase** — All enabled models are pinged in parallel (up to
|
|
179
|
+
1. **Ping phase** — All enabled models are pinged in parallel (up to 134 across 17 providers)
|
|
176
180
|
2. **Continuous monitoring** — Models are re-pinged every 2 seconds forever
|
|
177
181
|
3. **Real-time updates** — Watch "Latest", "Avg", and "Up%" columns update live
|
|
178
182
|
4. **Select anytime** — Use ↑↓ arrows to navigate, press Enter on a model to act
|
|
179
183
|
5. **Smart detection** — Automatically detects if NVIDIA NIM is configured in OpenCode or OpenClaw
|
|
180
184
|
|
|
181
|
-
Setup wizard (first run — walks through all
|
|
185
|
+
Setup wizard (first run — walks through all 17 providers):
|
|
182
186
|
|
|
183
187
|
```
|
|
184
188
|
🔑 First-time setup — API keys
|
|
@@ -208,7 +212,7 @@ Setup wizard (first run — walks through all 13 providers):
|
|
|
208
212
|
You can add or change keys anytime with the P key in the TUI.
|
|
209
213
|
```
|
|
210
214
|
|
|
211
|
-
You don't need all
|
|
215
|
+
You don't need all seventeen — skip any provider by pressing Enter. At least one key is required.
|
|
212
216
|
|
|
213
217
|
### Adding or changing keys later
|
|
214
218
|
|
|
@@ -257,6 +261,10 @@ HUGGINGFACE_API_KEY=hf_xxx free-coding-models
|
|
|
257
261
|
REPLICATE_API_TOKEN=r8_xxx free-coding-models
|
|
258
262
|
DEEPINFRA_API_KEY=di_xxx free-coding-models
|
|
259
263
|
FIREWORKS_API_KEY=fw_xxx free-coding-models
|
|
264
|
+
SILICONFLOW_API_KEY=sk_xxx free-coding-models
|
|
265
|
+
TOGETHER_API_KEY=together_xxx free-coding-models
|
|
266
|
+
CLOUDFLARE_API_TOKEN=cf_xxx CLOUDFLARE_ACCOUNT_ID=your_account_id free-coding-models
|
|
267
|
+
PERPLEXITY_API_KEY=pplx_xxx free-coding-models
|
|
260
268
|
FREE_CODING_MODELS_TELEMETRY=0 free-coding-models
|
|
261
269
|
```
|
|
262
270
|
|
|
@@ -306,13 +314,46 @@ When enabled, telemetry events include: event name, app version, selected mode,
|
|
|
306
314
|
1. Sign up at [fireworks.ai](https://fireworks.ai)
|
|
307
315
|
2. Open Settings → Access Tokens and create a token
|
|
308
316
|
|
|
317
|
+
**Mistral Codestral**:
|
|
318
|
+
1. Sign up at [codestral.mistral.ai](https://codestral.mistral.ai)
|
|
319
|
+
2. Go to API Keys → Create
|
|
320
|
+
|
|
321
|
+
**Hyperbolic**:
|
|
322
|
+
1. Sign up at [app.hyperbolic.ai/settings](https://app.hyperbolic.ai/settings)
|
|
323
|
+
2. Create an API key in Settings
|
|
324
|
+
|
|
325
|
+
**Scaleway**:
|
|
326
|
+
1. Sign up at [console.scaleway.com/iam/api-keys](https://console.scaleway.com/iam/api-keys)
|
|
327
|
+
2. Go to IAM → API Keys
|
|
328
|
+
|
|
329
|
+
**Google AI Studio**:
|
|
330
|
+
1. Sign up at [aistudio.google.com/apikey](https://aistudio.google.com/apikey)
|
|
331
|
+
2. Create an API key for Gemini/Gemma endpoints
|
|
332
|
+
|
|
333
|
+
**SiliconFlow**:
|
|
334
|
+
1. Sign up at [cloud.siliconflow.cn/account/ak](https://cloud.siliconflow.cn/account/ak)
|
|
335
|
+
2. Create API key in Account → API Keys
|
|
336
|
+
|
|
337
|
+
**Together AI**:
|
|
338
|
+
1. Sign up at [api.together.ai/settings/api-keys](https://api.together.ai/settings/api-keys)
|
|
339
|
+
2. Create an API key in Settings
|
|
340
|
+
|
|
341
|
+
**Cloudflare Workers AI**:
|
|
342
|
+
1. Sign up at [dash.cloudflare.com](https://dash.cloudflare.com)
|
|
343
|
+
2. Create an API token with Workers AI permissions
|
|
344
|
+
3. Export both `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`
|
|
345
|
+
|
|
346
|
+
**Perplexity API**:
|
|
347
|
+
1. Sign up at [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api)
|
|
348
|
+
2. Create API key (`PERPLEXITY_API_KEY`)
|
|
349
|
+
|
|
309
350
|
> 💡 **Free tiers** — each provider exposes a dev/free tier with its own quotas.
|
|
310
351
|
|
|
311
352
|
---
|
|
312
353
|
|
|
313
354
|
## 🤖 Coding Models
|
|
314
355
|
|
|
315
|
-
**
|
|
356
|
+
**134 coding models** across 17 providers and 8 tiers, ranked by [SWE-bench Verified](https://www.swebench.com) — the industry-standard benchmark measuring real GitHub issue resolution. Scores are self-reported by providers unless noted.
|
|
316
357
|
|
|
317
358
|
### NVIDIA NIM (44 models)
|
|
318
359
|
|
|
@@ -327,7 +368,7 @@ When enabled, telemetry events include: event name, app version, selected mode,
|
|
|
327
368
|
| **B** 20–30% | R1 Distill 8B (28.2%), R1 Distill 7B (22.6%) |
|
|
328
369
|
| **C** <20% | Gemma 2 9B (18.0%), Phi 4 Mini (14.0%), Phi 3.5 Mini (12.0%) |
|
|
329
370
|
|
|
330
|
-
### Groq (
|
|
371
|
+
### Groq (10 models)
|
|
331
372
|
|
|
332
373
|
| Tier | SWE-bench | Model |
|
|
333
374
|
|------|-----------|-------|
|
|
@@ -336,7 +377,7 @@ When enabled, telemetry events include: event name, app version, selected mode,
|
|
|
336
377
|
| **A** 40–50% | Llama 4 Scout (44.0%), R1 Distill 70B (43.9%) |
|
|
337
378
|
| **A-** 35–40% | Llama 3.3 70B (39.5%) |
|
|
338
379
|
|
|
339
|
-
### Cerebras (
|
|
380
|
+
### Cerebras (7 models)
|
|
340
381
|
|
|
341
382
|
| Tier | SWE-bench | Model |
|
|
342
383
|
|------|-----------|-------|
|
|
@@ -582,6 +623,11 @@ This script:
|
|
|
582
623
|
| `HYPERBOLIC_API_KEY` | Hyperbolic key |
|
|
583
624
|
| `SCALEWAY_API_KEY` | Scaleway key |
|
|
584
625
|
| `GOOGLE_API_KEY` | Google AI Studio key |
|
|
626
|
+
| `SILICONFLOW_API_KEY` | SiliconFlow key |
|
|
627
|
+
| `TOGETHER_API_KEY` | Together AI key |
|
|
628
|
+
| `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_API_KEY` | Cloudflare Workers AI token/key |
|
|
629
|
+
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID (required for Workers AI endpoint URL) |
|
|
630
|
+
| `PERPLEXITY_API_KEY` / `PPLX_API_KEY` | Perplexity API key |
|
|
585
631
|
| `FREE_CODING_MODELS_TELEMETRY` | `0` disables analytics, `1` enables analytics |
|
|
586
632
|
| `FREE_CODING_MODELS_POSTHOG_KEY` | PostHog project API key used for anonymous event capture |
|
|
587
633
|
| `FREE_CODING_MODELS_POSTHOG_HOST` | Optional PostHog ingest host (`https://eu.i.posthog.com` default) |
|
|
@@ -597,7 +643,11 @@ This script:
|
|
|
597
643
|
"openrouter": "sk-or-xxx",
|
|
598
644
|
"huggingface": "hf_xxx",
|
|
599
645
|
"replicate": "r8_xxx",
|
|
600
|
-
"deepinfra": "di_xxx"
|
|
646
|
+
"deepinfra": "di_xxx",
|
|
647
|
+
"siliconflow": "sk_xxx",
|
|
648
|
+
"together": "together_xxx",
|
|
649
|
+
"cloudflare": "cf_xxx",
|
|
650
|
+
"perplexity": "pplx_xxx"
|
|
601
651
|
},
|
|
602
652
|
"providers": {
|
|
603
653
|
"nvidia": { "enabled": true },
|
|
@@ -606,7 +656,11 @@ This script:
|
|
|
606
656
|
"openrouter": { "enabled": true },
|
|
607
657
|
"huggingface": { "enabled": true },
|
|
608
658
|
"replicate": { "enabled": true },
|
|
609
|
-
"deepinfra": { "enabled": true }
|
|
659
|
+
"deepinfra": { "enabled": true },
|
|
660
|
+
"siliconflow": { "enabled": true },
|
|
661
|
+
"together": { "enabled": true },
|
|
662
|
+
"cloudflare": { "enabled": true },
|
|
663
|
+
"perplexity": { "enabled": true }
|
|
610
664
|
},
|
|
611
665
|
"favorites": [
|
|
612
666
|
"nvidia/deepseek-ai/deepseek-v3.2"
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
* ⚙️ Configuration:
|
|
61
61
|
* - API keys stored per-provider in ~/.free-coding-models.json (0600 perms)
|
|
62
62
|
* - Old ~/.free-coding-models plain-text auto-migrated as nvidia key on first run
|
|
63
|
-
* - Env vars override config: NVIDIA_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY, HUGGINGFACE_API_KEY/HF_TOKEN, REPLICATE_API_TOKEN, DEEPINFRA_API_KEY/DEEPINFRA_TOKEN, FIREWORKS_API_KEY, etc.
|
|
63
|
+
* - Env vars override config: NVIDIA_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY, HUGGINGFACE_API_KEY/HF_TOKEN, REPLICATE_API_TOKEN, DEEPINFRA_API_KEY/DEEPINFRA_TOKEN, FIREWORKS_API_KEY, SILICONFLOW_API_KEY, TOGETHER_API_KEY, PERPLEXITY_API_KEY, etc.
|
|
64
|
+
* - Cloudflare Workers AI requires both CLOUDFLARE_API_TOKEN (or CLOUDFLARE_API_KEY) and CLOUDFLARE_ACCOUNT_ID
|
|
64
65
|
* - Models loaded from sources.js — all provider/model definitions are centralized there
|
|
65
66
|
* - OpenCode config: ~/.config/opencode/opencode.json
|
|
66
67
|
* - OpenClaw config: ~/.openclaw/openclaw.json
|
|
@@ -755,6 +756,52 @@ const FRAMES = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏']
|
|
|
755
756
|
// 📖 Spinner cell: braille (1-wide) + padding to fill CELL_W visual chars
|
|
756
757
|
const spinCell = (f, o = 0) => chalk.dim.yellow(FRAMES[(f + o) % FRAMES.length].padEnd(CELL_W))
|
|
757
758
|
|
|
759
|
+
// 📖 Overlay-specific backgrounds so Settings (P) and Help (K) are visually distinct
|
|
760
|
+
// 📖 from the main table and from each other.
|
|
761
|
+
const SETTINGS_OVERLAY_BG = chalk.bgRgb(14, 20, 30)
|
|
762
|
+
const HELP_OVERLAY_BG = chalk.bgRgb(24, 16, 32)
|
|
763
|
+
const OVERLAY_PANEL_WIDTH = 116
|
|
764
|
+
|
|
765
|
+
// 📖 Strip ANSI color/control sequences to estimate visible text width before padding.
|
|
766
|
+
function stripAnsi(input) {
|
|
767
|
+
return String(input).replace(/\x1b\[[0-9;]*m/g, '').replace(/\x1b\][^\x1b]*\x1b\\/g, '')
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// 📖 Tint overlay lines with a fixed dark panel width so the background is clearly visible.
|
|
771
|
+
function tintOverlayLines(lines, bgColor) {
|
|
772
|
+
return lines.map((line) => {
|
|
773
|
+
const text = String(line)
|
|
774
|
+
const visibleWidth = stripAnsi(text).length
|
|
775
|
+
const padding = ' '.repeat(Math.max(0, OVERLAY_PANEL_WIDTH - visibleWidth))
|
|
776
|
+
return bgColor(text + padding)
|
|
777
|
+
})
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// 📖 Clamp overlay scroll to valid bounds for the current terminal height.
|
|
781
|
+
function clampOverlayOffset(offset, totalLines, terminalRows) {
|
|
782
|
+
const viewportRows = Math.max(1, terminalRows || 1)
|
|
783
|
+
const maxOffset = Math.max(0, totalLines - viewportRows)
|
|
784
|
+
return Math.max(0, Math.min(maxOffset, offset))
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// 📖 Ensure a target line is visible inside overlay viewport (used by Settings cursor).
|
|
788
|
+
function keepOverlayTargetVisible(offset, targetLine, totalLines, terminalRows) {
|
|
789
|
+
const viewportRows = Math.max(1, terminalRows || 1)
|
|
790
|
+
let next = clampOverlayOffset(offset, totalLines, terminalRows)
|
|
791
|
+
if (targetLine < next) next = targetLine
|
|
792
|
+
else if (targetLine >= next + viewportRows) next = targetLine - viewportRows + 1
|
|
793
|
+
return clampOverlayOffset(next, totalLines, terminalRows)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// 📖 Slice overlay lines to terminal viewport and pad with blanks to avoid stale frames.
|
|
797
|
+
function sliceOverlayLines(lines, offset, terminalRows) {
|
|
798
|
+
const viewportRows = Math.max(1, terminalRows || 1)
|
|
799
|
+
const nextOffset = clampOverlayOffset(offset, lines.length, terminalRows)
|
|
800
|
+
const visible = lines.slice(nextOffset, nextOffset + viewportRows)
|
|
801
|
+
while (visible.length < viewportRows) visible.push('')
|
|
802
|
+
return { visible, offset: nextOffset }
|
|
803
|
+
}
|
|
804
|
+
|
|
758
805
|
// ─── Table renderer ───────────────────────────────────────────────────────────
|
|
759
806
|
|
|
760
807
|
// 📖 Core logic functions (getAvg, getVerdict, getUptime, sortResults, etc.)
|
|
@@ -1139,6 +1186,15 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
1139
1186
|
// 📖 providerKey and url determine provider-specific request format.
|
|
1140
1187
|
// 📖 apiKey can be null — in that case no Authorization header is sent.
|
|
1141
1188
|
// 📖 A 401 response still tells us the server is UP and gives us real latency.
|
|
1189
|
+
function resolveCloudflareUrl(url) {
|
|
1190
|
+
// 📖 Cloudflare's OpenAI-compatible endpoint is account-scoped.
|
|
1191
|
+
// 📖 We resolve {account_id} from env so provider setup can stay simple in config.
|
|
1192
|
+
const accountId = (process.env.CLOUDFLARE_ACCOUNT_ID || '').trim()
|
|
1193
|
+
if (!url.includes('{account_id}')) return url
|
|
1194
|
+
if (!accountId) return url.replace('{account_id}', 'missing-account-id')
|
|
1195
|
+
return url.replace('{account_id}', encodeURIComponent(accountId))
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1142
1198
|
function buildPingRequest(apiKey, modelId, providerKey, url) {
|
|
1143
1199
|
if (providerKey === 'replicate') {
|
|
1144
1200
|
// 📖 Replicate uses /v1/predictions with a different payload than OpenAI chat-completions.
|
|
@@ -1151,6 +1207,17 @@ function buildPingRequest(apiKey, modelId, providerKey, url) {
|
|
|
1151
1207
|
}
|
|
1152
1208
|
}
|
|
1153
1209
|
|
|
1210
|
+
if (providerKey === 'cloudflare') {
|
|
1211
|
+
// 📖 Cloudflare Workers AI uses OpenAI-compatible payload but needs account_id in URL.
|
|
1212
|
+
const headers = { 'Content-Type': 'application/json' }
|
|
1213
|
+
if (apiKey) headers.Authorization = `Bearer ${apiKey}`
|
|
1214
|
+
return {
|
|
1215
|
+
url: resolveCloudflareUrl(url),
|
|
1216
|
+
headers,
|
|
1217
|
+
body: { model: modelId, messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 },
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1154
1221
|
const headers = { 'Content-Type': 'application/json' }
|
|
1155
1222
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`
|
|
1156
1223
|
if (providerKey === 'openrouter') {
|
|
@@ -1228,6 +1295,10 @@ const ENV_VAR_NAMES = {
|
|
|
1228
1295
|
hyperbolic: 'HYPERBOLIC_API_KEY',
|
|
1229
1296
|
scaleway: 'SCALEWAY_API_KEY',
|
|
1230
1297
|
googleai: 'GOOGLE_API_KEY',
|
|
1298
|
+
siliconflow:'SILICONFLOW_API_KEY',
|
|
1299
|
+
together: 'TOGETHER_API_KEY',
|
|
1300
|
+
cloudflare: 'CLOUDFLARE_API_TOKEN',
|
|
1301
|
+
perplexity: 'PERPLEXITY_API_KEY',
|
|
1231
1302
|
}
|
|
1232
1303
|
|
|
1233
1304
|
// 📖 Provider metadata used by the setup wizard and Settings details panel.
|
|
@@ -1324,6 +1395,34 @@ const PROVIDER_METADATA = {
|
|
|
1324
1395
|
signupHint: 'Get API key',
|
|
1325
1396
|
rateLimits: '14.4K req/day, 30/min',
|
|
1326
1397
|
},
|
|
1398
|
+
siliconflow: {
|
|
1399
|
+
label: 'SiliconFlow',
|
|
1400
|
+
color: chalk.rgb(255, 120, 30),
|
|
1401
|
+
signupUrl: 'https://cloud.siliconflow.cn/account/ak',
|
|
1402
|
+
signupHint: 'API Keys → Create',
|
|
1403
|
+
rateLimits: 'Free models: usually 100 RPM, varies by model',
|
|
1404
|
+
},
|
|
1405
|
+
together: {
|
|
1406
|
+
label: 'Together AI',
|
|
1407
|
+
color: chalk.rgb(0, 180, 255),
|
|
1408
|
+
signupUrl: 'https://api.together.ai/settings/api-keys',
|
|
1409
|
+
signupHint: 'Settings → API keys',
|
|
1410
|
+
rateLimits: 'Credits/promos vary by account (check console)',
|
|
1411
|
+
},
|
|
1412
|
+
cloudflare: {
|
|
1413
|
+
label: 'Cloudflare Workers AI',
|
|
1414
|
+
color: chalk.rgb(242, 119, 36),
|
|
1415
|
+
signupUrl: 'https://dash.cloudflare.com',
|
|
1416
|
+
signupHint: 'Create AI API token + set CLOUDFLARE_ACCOUNT_ID',
|
|
1417
|
+
rateLimits: 'Free: 10k neurons/day, text-gen 300 RPM',
|
|
1418
|
+
},
|
|
1419
|
+
perplexity: {
|
|
1420
|
+
label: 'Perplexity API',
|
|
1421
|
+
color: chalk.rgb(0, 210, 190),
|
|
1422
|
+
signupUrl: 'https://www.perplexity.ai/settings/api',
|
|
1423
|
+
signupHint: 'Generate API key (billing may be required)',
|
|
1424
|
+
rateLimits: 'Tiered limits by spend (default ~50 RPM)',
|
|
1425
|
+
},
|
|
1327
1426
|
}
|
|
1328
1427
|
|
|
1329
1428
|
// 📖 OpenCode config location varies by platform
|
|
@@ -1692,6 +1791,53 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1692
1791
|
},
|
|
1693
1792
|
models: {}
|
|
1694
1793
|
}
|
|
1794
|
+
} else if (providerKey === 'siliconflow') {
|
|
1795
|
+
config.provider.siliconflow = {
|
|
1796
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1797
|
+
name: 'SiliconFlow',
|
|
1798
|
+
options: {
|
|
1799
|
+
baseURL: 'https://api.siliconflow.com/v1',
|
|
1800
|
+
apiKey: '{env:SILICONFLOW_API_KEY}'
|
|
1801
|
+
},
|
|
1802
|
+
models: {}
|
|
1803
|
+
}
|
|
1804
|
+
} else if (providerKey === 'together') {
|
|
1805
|
+
config.provider.together = {
|
|
1806
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1807
|
+
name: 'Together AI',
|
|
1808
|
+
options: {
|
|
1809
|
+
baseURL: 'https://api.together.xyz/v1',
|
|
1810
|
+
apiKey: '{env:TOGETHER_API_KEY}'
|
|
1811
|
+
},
|
|
1812
|
+
models: {}
|
|
1813
|
+
}
|
|
1814
|
+
} else if (providerKey === 'cloudflare') {
|
|
1815
|
+
const cloudflareAccountId = (process.env.CLOUDFLARE_ACCOUNT_ID || '').trim()
|
|
1816
|
+
if (!cloudflareAccountId) {
|
|
1817
|
+
console.log(chalk.yellow(' ⚠ Cloudflare Workers AI requires CLOUDFLARE_ACCOUNT_ID for OpenCode integration.'))
|
|
1818
|
+
console.log(chalk.dim(' Export CLOUDFLARE_ACCOUNT_ID and retry this selection.'))
|
|
1819
|
+
console.log()
|
|
1820
|
+
return
|
|
1821
|
+
}
|
|
1822
|
+
config.provider.cloudflare = {
|
|
1823
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1824
|
+
name: 'Cloudflare Workers AI',
|
|
1825
|
+
options: {
|
|
1826
|
+
baseURL: `https://api.cloudflare.com/client/v4/accounts/${cloudflareAccountId}/ai/v1`,
|
|
1827
|
+
apiKey: '{env:CLOUDFLARE_API_TOKEN}'
|
|
1828
|
+
},
|
|
1829
|
+
models: {}
|
|
1830
|
+
}
|
|
1831
|
+
} else if (providerKey === 'perplexity') {
|
|
1832
|
+
config.provider.perplexity = {
|
|
1833
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1834
|
+
name: 'Perplexity API',
|
|
1835
|
+
options: {
|
|
1836
|
+
baseURL: 'https://api.perplexity.ai',
|
|
1837
|
+
apiKey: '{env:PERPLEXITY_API_KEY}'
|
|
1838
|
+
},
|
|
1839
|
+
models: {}
|
|
1840
|
+
}
|
|
1695
1841
|
}
|
|
1696
1842
|
}
|
|
1697
1843
|
|
|
@@ -2302,7 +2448,9 @@ async function main() {
|
|
|
2302
2448
|
st.scrollOffset = st.cursor - modelSlots + 1
|
|
2303
2449
|
}
|
|
2304
2450
|
// Final clamp
|
|
2305
|
-
|
|
2451
|
+
// 📖 Keep one extra scroll step when top indicator is visible,
|
|
2452
|
+
// 📖 otherwise the last rows become unreachable at the bottom.
|
|
2453
|
+
const maxOffset = Math.max(0, total - maxSlots + 1)
|
|
2306
2454
|
if (st.scrollOffset > maxOffset) st.scrollOffset = maxOffset
|
|
2307
2455
|
if (st.scrollOffset < 0) st.scrollOffset = 0
|
|
2308
2456
|
}
|
|
@@ -2337,6 +2485,8 @@ async function main() {
|
|
|
2337
2485
|
config, // 📖 Live reference to the config object (updated on save)
|
|
2338
2486
|
visibleSorted: [], // 📖 Cached visible+sorted models — shared between render loop and key handlers
|
|
2339
2487
|
helpVisible: false, // 📖 Whether the help overlay (K key) is active
|
|
2488
|
+
settingsScrollOffset: 0, // 📖 Vertical scroll offset for Settings overlay viewport
|
|
2489
|
+
helpScrollOffset: 0, // 📖 Vertical scroll offset for Help overlay viewport
|
|
2340
2490
|
}
|
|
2341
2491
|
|
|
2342
2492
|
// 📖 Re-clamp viewport on terminal resize
|
|
@@ -2395,6 +2545,7 @@ async function main() {
|
|
|
2395
2545
|
const updateRowIdx = providerKeys.length + 1
|
|
2396
2546
|
const EL = '\x1b[K'
|
|
2397
2547
|
const lines = []
|
|
2548
|
+
const cursorLineByRow = {}
|
|
2398
2549
|
|
|
2399
2550
|
lines.push('')
|
|
2400
2551
|
lines.push(` ${chalk.bold('⚙ Settings')} ${chalk.dim('— free-coding-models v' + LOCAL_VERSION)}`)
|
|
@@ -2437,6 +2588,7 @@ async function main() {
|
|
|
2437
2588
|
const bullet = isCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
|
|
2438
2589
|
|
|
2439
2590
|
const row = `${bullet}[ ${enabledBadge} ] ${providerName} ${keyDisplay.padEnd(30)} ${testBadge} ${rateSummary}`
|
|
2591
|
+
cursorLineByRow[i] = lines.length
|
|
2440
2592
|
lines.push(isCursor ? chalk.bgRgb(30, 30, 60)(row) : row)
|
|
2441
2593
|
}
|
|
2442
2594
|
|
|
@@ -2451,6 +2603,11 @@ async function main() {
|
|
|
2451
2603
|
lines.push(chalk.dim(` 1) Create a ${selectedMeta.label || selectedSource.name} account: ${selectedMeta.signupUrl || 'signup link missing'}`))
|
|
2452
2604
|
lines.push(chalk.dim(` 2) ${selectedMeta.signupHint || 'Generate an API key and paste it with Enter on this row'}`))
|
|
2453
2605
|
lines.push(chalk.dim(` 3) Press ${chalk.yellow('T')} to test your key. Status: ${setupStatus}`))
|
|
2606
|
+
if (selectedProviderKey === 'cloudflare') {
|
|
2607
|
+
const hasAccountId = Boolean((process.env.CLOUDFLARE_ACCOUNT_ID || '').trim())
|
|
2608
|
+
const accountIdStatus = hasAccountId ? chalk.green('CLOUDFLARE_ACCOUNT_ID detected ✅') : chalk.yellow('Set CLOUDFLARE_ACCOUNT_ID ⚠')
|
|
2609
|
+
lines.push(chalk.dim(` 4) Export ${chalk.yellow('CLOUDFLARE_ACCOUNT_ID')} in your shell. Status: ${accountIdStatus}`))
|
|
2610
|
+
}
|
|
2454
2611
|
lines.push('')
|
|
2455
2612
|
}
|
|
2456
2613
|
|
|
@@ -2467,6 +2624,7 @@ async function main() {
|
|
|
2467
2624
|
? chalk.dim('[Config]')
|
|
2468
2625
|
: chalk.yellow('[Env override]')
|
|
2469
2626
|
const telemetryRow = `${telemetryRowBullet}${chalk.bold('Anonymous usage analytics').padEnd(44)} ${telemetryStatus} ${telemetrySource}`
|
|
2627
|
+
cursorLineByRow[telemetryRowIdx] = lines.length
|
|
2470
2628
|
lines.push(telemetryCursor ? chalk.bgRgb(30, 30, 60)(telemetryRow) : telemetryRow)
|
|
2471
2629
|
|
|
2472
2630
|
lines.push('')
|
|
@@ -2488,6 +2646,7 @@ async function main() {
|
|
|
2488
2646
|
if (updateState === 'error') updateStatus = chalk.red('Check failed (press U to retry)')
|
|
2489
2647
|
if (updateState === 'installing') updateStatus = chalk.cyan('Installing update…')
|
|
2490
2648
|
const updateRow = `${updateBullet}${chalk.bold(updateActionLabel).padEnd(44)} ${updateStatus}`
|
|
2649
|
+
cursorLineByRow[updateRowIdx] = lines.length
|
|
2491
2650
|
lines.push(updateCursor ? chalk.bgRgb(30, 30, 60)(updateRow) : updateRow)
|
|
2492
2651
|
if (updateState === 'error' && state.settingsUpdateError) {
|
|
2493
2652
|
lines.push(chalk.red(` ${state.settingsUpdateError}`))
|
|
@@ -2501,9 +2660,19 @@ async function main() {
|
|
|
2501
2660
|
}
|
|
2502
2661
|
lines.push('')
|
|
2503
2662
|
|
|
2504
|
-
|
|
2505
|
-
const
|
|
2506
|
-
|
|
2663
|
+
// 📖 Keep selected Settings row visible on small terminals by scrolling the overlay viewport.
|
|
2664
|
+
const targetLine = cursorLineByRow[state.settingsCursor] ?? 0
|
|
2665
|
+
state.settingsScrollOffset = keepOverlayTargetVisible(
|
|
2666
|
+
state.settingsScrollOffset,
|
|
2667
|
+
targetLine,
|
|
2668
|
+
lines.length,
|
|
2669
|
+
state.terminalRows
|
|
2670
|
+
)
|
|
2671
|
+
const { visible, offset } = sliceOverlayLines(lines, state.settingsScrollOffset, state.terminalRows)
|
|
2672
|
+
state.settingsScrollOffset = offset
|
|
2673
|
+
|
|
2674
|
+
const tintedLines = tintOverlayLines(visible, SETTINGS_OVERLAY_BG)
|
|
2675
|
+
const cleared = tintedLines.map(l => l + EL)
|
|
2507
2676
|
return cleared.join('\n')
|
|
2508
2677
|
}
|
|
2509
2678
|
|
|
@@ -2514,7 +2683,7 @@ async function main() {
|
|
|
2514
2683
|
const EL = '\x1b[K'
|
|
2515
2684
|
const lines = []
|
|
2516
2685
|
lines.push('')
|
|
2517
|
-
lines.push(` ${chalk.bold('❓ Keyboard Shortcuts')} ${chalk.dim('—
|
|
2686
|
+
lines.push(` ${chalk.bold('❓ Keyboard Shortcuts')} ${chalk.dim('— ↑↓ / PgUp / PgDn / Home / End scroll • K or Esc close')}`)
|
|
2518
2687
|
lines.push('')
|
|
2519
2688
|
lines.push(` ${chalk.bold('Main TUI')}`)
|
|
2520
2689
|
lines.push(` ${chalk.bold('Navigation')}`)
|
|
@@ -2541,6 +2710,8 @@ async function main() {
|
|
|
2541
2710
|
lines.push('')
|
|
2542
2711
|
lines.push(` ${chalk.bold('Settings (P)')}`)
|
|
2543
2712
|
lines.push(` ${chalk.yellow('↑↓')} Navigate rows`)
|
|
2713
|
+
lines.push(` ${chalk.yellow('PgUp/PgDn')} Jump by page`)
|
|
2714
|
+
lines.push(` ${chalk.yellow('Home/End')} Jump first/last row`)
|
|
2544
2715
|
lines.push(` ${chalk.yellow('Enter')} Edit key / toggle analytics / check-install update`)
|
|
2545
2716
|
lines.push(` ${chalk.yellow('Space')} Toggle provider enable/disable`)
|
|
2546
2717
|
lines.push(` ${chalk.yellow('T')} Test selected provider key`)
|
|
@@ -2558,9 +2729,11 @@ async function main() {
|
|
|
2558
2729
|
lines.push(` ${chalk.cyan('free-coding-models --no-telemetry')} ${chalk.dim('Disable telemetry for this run')}`)
|
|
2559
2730
|
lines.push(` ${chalk.dim('Flags can be combined: --openclaw --tier S')}`)
|
|
2560
2731
|
lines.push('')
|
|
2561
|
-
|
|
2562
|
-
const
|
|
2563
|
-
|
|
2732
|
+
// 📖 Help overlay can be longer than viewport, so keep a dedicated scroll offset.
|
|
2733
|
+
const { visible, offset } = sliceOverlayLines(lines, state.helpScrollOffset, state.terminalRows)
|
|
2734
|
+
state.helpScrollOffset = offset
|
|
2735
|
+
const tintedLines = tintOverlayLines(visible, HELP_OVERLAY_BG)
|
|
2736
|
+
const cleared = tintedLines.map(l => l + EL)
|
|
2564
2737
|
return cleared.join('\n')
|
|
2565
2738
|
}
|
|
2566
2739
|
|
|
@@ -2635,9 +2808,20 @@ async function main() {
|
|
|
2635
2808
|
const onKeyPress = async (str, key) => {
|
|
2636
2809
|
if (!key) return
|
|
2637
2810
|
|
|
2638
|
-
// 📖 Help overlay:
|
|
2639
|
-
if (state.helpVisible
|
|
2640
|
-
state.
|
|
2811
|
+
// 📖 Help overlay: full keyboard navigation + key swallowing while overlay is open.
|
|
2812
|
+
if (state.helpVisible) {
|
|
2813
|
+
const pageStep = Math.max(1, (state.terminalRows || 1) - 2)
|
|
2814
|
+
if (key.name === 'escape' || key.name === 'k') {
|
|
2815
|
+
state.helpVisible = false
|
|
2816
|
+
return
|
|
2817
|
+
}
|
|
2818
|
+
if (key.name === 'up') { state.helpScrollOffset = Math.max(0, state.helpScrollOffset - 1); return }
|
|
2819
|
+
if (key.name === 'down') { state.helpScrollOffset += 1; return }
|
|
2820
|
+
if (key.name === 'pageup') { state.helpScrollOffset = Math.max(0, state.helpScrollOffset - pageStep); return }
|
|
2821
|
+
if (key.name === 'pagedown') { state.helpScrollOffset += pageStep; return }
|
|
2822
|
+
if (key.name === 'home') { state.helpScrollOffset = 0; return }
|
|
2823
|
+
if (key.name === 'end') { state.helpScrollOffset = Number.MAX_SAFE_INTEGER; return }
|
|
2824
|
+
if (key.ctrl && key.name === 'c') { exit(0); return }
|
|
2641
2825
|
return
|
|
2642
2826
|
}
|
|
2643
2827
|
|
|
@@ -2717,6 +2901,28 @@ async function main() {
|
|
|
2717
2901
|
return
|
|
2718
2902
|
}
|
|
2719
2903
|
|
|
2904
|
+
if (key.name === 'pageup') {
|
|
2905
|
+
const pageStep = Math.max(1, (state.terminalRows || 1) - 2)
|
|
2906
|
+
state.settingsCursor = Math.max(0, state.settingsCursor - pageStep)
|
|
2907
|
+
return
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
if (key.name === 'pagedown') {
|
|
2911
|
+
const pageStep = Math.max(1, (state.terminalRows || 1) - 2)
|
|
2912
|
+
state.settingsCursor = Math.min(updateRowIdx, state.settingsCursor + pageStep)
|
|
2913
|
+
return
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
if (key.name === 'home') {
|
|
2917
|
+
state.settingsCursor = 0
|
|
2918
|
+
return
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
if (key.name === 'end') {
|
|
2922
|
+
state.settingsCursor = updateRowIdx
|
|
2923
|
+
return
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2720
2926
|
if (key.name === 'return') {
|
|
2721
2927
|
if (state.settingsCursor === telemetryRowIdx) {
|
|
2722
2928
|
ensureTelemetryConfig(state.config)
|
|
@@ -2784,6 +2990,7 @@ async function main() {
|
|
|
2784
2990
|
state.settingsCursor = 0
|
|
2785
2991
|
state.settingsEditMode = false
|
|
2786
2992
|
state.settingsEditBuffer = ''
|
|
2993
|
+
state.settingsScrollOffset = 0
|
|
2787
2994
|
return
|
|
2788
2995
|
}
|
|
2789
2996
|
|
|
@@ -2816,12 +3023,21 @@ async function main() {
|
|
|
2816
3023
|
if (key.name === 'f') {
|
|
2817
3024
|
const selected = state.visibleSorted[state.cursor]
|
|
2818
3025
|
if (!selected) return
|
|
3026
|
+
const wasFavorite = selected.isFavorite
|
|
2819
3027
|
toggleFavoriteModel(state.config, selected.providerKey, selected.modelId)
|
|
2820
3028
|
syncFavoriteFlags(state.results, state.config)
|
|
2821
3029
|
applyTierFilter()
|
|
2822
|
-
const selectedKey = toFavoriteKey(selected.providerKey, selected.modelId)
|
|
2823
3030
|
const visible = state.results.filter(r => !r.hidden)
|
|
2824
3031
|
state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection)
|
|
3032
|
+
|
|
3033
|
+
// 📖 UX rule: when unpinning a favorite, jump back to the top of the list.
|
|
3034
|
+
if (wasFavorite) {
|
|
3035
|
+
state.cursor = 0
|
|
3036
|
+
state.scrollOffset = 0
|
|
3037
|
+
return
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
const selectedKey = toFavoriteKey(selected.providerKey, selected.modelId)
|
|
2825
3041
|
const newCursor = state.visibleSorted.findIndex(r => toFavoriteKey(r.providerKey, r.modelId) === selectedKey)
|
|
2826
3042
|
if (newCursor >= 0) state.cursor = newCursor
|
|
2827
3043
|
else if (state.cursor >= state.visibleSorted.length) state.cursor = Math.max(0, state.visibleSorted.length - 1)
|
|
@@ -2864,6 +3080,7 @@ async function main() {
|
|
|
2864
3080
|
// 📖 Help overlay key: K = toggle help overlay
|
|
2865
3081
|
if (key.name === 'k') {
|
|
2866
3082
|
state.helpVisible = !state.helpVisible
|
|
3083
|
+
if (state.helpVisible) state.helpScrollOffset = 0
|
|
2867
3084
|
return
|
|
2868
3085
|
}
|
|
2869
3086
|
|
|
@@ -2882,18 +3099,20 @@ async function main() {
|
|
|
2882
3099
|
}
|
|
2883
3100
|
|
|
2884
3101
|
if (key.name === 'up') {
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
3102
|
+
// 📖 Main list wrap navigation: top -> bottom on Up.
|
|
3103
|
+
const count = state.visibleSorted.length
|
|
3104
|
+
if (count === 0) return
|
|
3105
|
+
state.cursor = state.cursor > 0 ? state.cursor - 1 : count - 1
|
|
3106
|
+
adjustScrollOffset(state)
|
|
2889
3107
|
return
|
|
2890
3108
|
}
|
|
2891
3109
|
|
|
2892
3110
|
if (key.name === 'down') {
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
3111
|
+
// 📖 Main list wrap navigation: bottom -> top on Down.
|
|
3112
|
+
const count = state.visibleSorted.length
|
|
3113
|
+
if (count === 0) return
|
|
3114
|
+
state.cursor = state.cursor < count - 1 ? state.cursor + 1 : 0
|
|
3115
|
+
adjustScrollOffset(state)
|
|
2897
3116
|
return
|
|
2898
3117
|
}
|
|
2899
3118
|
|
package/lib/config.js
CHANGED
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
* "codestral": "csk-xxx",
|
|
25
25
|
* "hyperbolic": "eyJ...",
|
|
26
26
|
* "scaleway": "scw-xxx",
|
|
27
|
-
* "googleai": "AIza..."
|
|
27
|
+
* "googleai": "AIza...",
|
|
28
|
+
* "siliconflow":"sk-xxx",
|
|
29
|
+
* "together": "together-xxx",
|
|
30
|
+
* "cloudflare": "cf-xxx",
|
|
31
|
+
* "perplexity": "pplx-xxx"
|
|
28
32
|
* },
|
|
29
33
|
* "providers": {
|
|
30
34
|
* "nvidia": { "enabled": true },
|
|
@@ -39,7 +43,11 @@
|
|
|
39
43
|
* "codestral": { "enabled": true },
|
|
40
44
|
* "hyperbolic": { "enabled": true },
|
|
41
45
|
* "scaleway": { "enabled": true },
|
|
42
|
-
* "googleai": { "enabled": true }
|
|
46
|
+
* "googleai": { "enabled": true },
|
|
47
|
+
* "siliconflow":{ "enabled": true },
|
|
48
|
+
* "together": { "enabled": true },
|
|
49
|
+
* "cloudflare": { "enabled": true },
|
|
50
|
+
* "perplexity": { "enabled": true }
|
|
43
51
|
* },
|
|
44
52
|
* "favorites": [
|
|
45
53
|
* "nvidia/deepseek-ai/deepseek-v3.2"
|
|
@@ -94,6 +102,10 @@ const ENV_VARS = {
|
|
|
94
102
|
hyperbolic: 'HYPERBOLIC_API_KEY',
|
|
95
103
|
scaleway: 'SCALEWAY_API_KEY',
|
|
96
104
|
googleai: 'GOOGLE_API_KEY',
|
|
105
|
+
siliconflow:'SILICONFLOW_API_KEY',
|
|
106
|
+
together: 'TOGETHER_API_KEY',
|
|
107
|
+
cloudflare: ['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_API_KEY'],
|
|
108
|
+
perplexity: ['PERPLEXITY_API_KEY', 'PPLX_API_KEY'],
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.66",
|
|
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/sources.js
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
* 📖 Secondary: https://swe-rebench.com (independent evals, scores are lower)
|
|
28
28
|
* 📖 Leaderboard tracker: https://www.marc0.dev/en/leaderboard
|
|
29
29
|
*
|
|
30
|
-
* @exports nvidiaNim, groq, cerebras, sambanova, openrouter, huggingface, replicate, deepinfra, fireworks, codestral, hyperbolic, scaleway, googleai — model arrays per provider
|
|
31
|
-
* @exports sources — map of { nvidia, groq, cerebras, sambanova, openrouter, huggingface, replicate, deepinfra, fireworks, codestral, hyperbolic, scaleway, googleai } each with { name, url, models }
|
|
30
|
+
* @exports nvidiaNim, groq, cerebras, sambanova, openrouter, huggingface, replicate, deepinfra, fireworks, codestral, hyperbolic, scaleway, googleai, siliconflow, together, cloudflare, perplexity — model arrays per provider
|
|
31
|
+
* @exports sources — map of { nvidia, groq, cerebras, sambanova, openrouter, huggingface, replicate, deepinfra, fireworks, codestral, hyperbolic, scaleway, googleai, siliconflow, together, cloudflare, perplexity } each with { name, url, models }
|
|
32
32
|
* @exports MODELS — flat array of [modelId, label, tier, sweScore, ctx, providerKey]
|
|
33
33
|
*
|
|
34
34
|
* 📖 MODELS now includes providerKey as 6th element so ping() knows which
|
|
@@ -230,6 +230,54 @@ export const googleai = [
|
|
|
230
230
|
['gemma-3-4b-it', 'Gemma 3 4B', 'C', '10.0%', '128k'],
|
|
231
231
|
]
|
|
232
232
|
|
|
233
|
+
// 📖 SiliconFlow source - https://cloud.siliconflow.cn
|
|
234
|
+
// 📖 OpenAI-compatible endpoint: https://api.siliconflow.com/v1/chat/completions
|
|
235
|
+
// 📖 Free model quotas vary by model and can change over time.
|
|
236
|
+
export const siliconflow = [
|
|
237
|
+
['Qwen/Qwen3-Coder-480B-A35B-Instruct', 'Qwen3 Coder 480B', 'S+', '70.6%', '256k'],
|
|
238
|
+
['deepseek-ai/DeepSeek-V3.2', 'DeepSeek V3.2', 'S+', '73.1%', '128k'],
|
|
239
|
+
['Qwen/Qwen3-235B-A22B', 'Qwen3 235B', 'S+', '70.0%', '128k'],
|
|
240
|
+
['deepseek-ai/DeepSeek-R1', 'DeepSeek R1', 'S', '61.0%', '128k'],
|
|
241
|
+
['Qwen/Qwen3-Coder-30B-A3B-Instruct', 'Qwen3 Coder 30B', 'A+', '55.0%', '32k'],
|
|
242
|
+
['Qwen/Qwen2.5-Coder-32B-Instruct', 'Qwen2.5 Coder 32B', 'A', '46.0%', '32k'],
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
// 📖 Together AI source - https://api.together.ai
|
|
246
|
+
// 📖 OpenAI-compatible endpoint: https://api.together.xyz/v1/chat/completions
|
|
247
|
+
// 📖 Credits/promotions vary by account and region; verify current quota in console.
|
|
248
|
+
export const together = [
|
|
249
|
+
['moonshotai/Kimi-K2.5', 'Kimi K2.5', 'S+', '76.8%', '128k'],
|
|
250
|
+
['Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8', 'Qwen3 Coder 480B', 'S+', '70.6%', '256k'],
|
|
251
|
+
['deepseek-ai/DeepSeek-V3.1', 'DeepSeek V3.1', 'S', '62.0%', '128k'],
|
|
252
|
+
['deepseek-ai/DeepSeek-R1', 'DeepSeek R1', 'S', '61.0%', '128k'],
|
|
253
|
+
['openai/gpt-oss-120b', 'GPT OSS 120B', 'S', '60.0%', '128k'],
|
|
254
|
+
['openai/gpt-oss-20b', 'GPT OSS 20B', 'A', '42.0%', '128k'],
|
|
255
|
+
['meta-llama/Llama-3.3-70B-Instruct-Turbo', 'Llama 3.3 70B', 'A-', '39.5%', '128k'],
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
// 📖 Cloudflare Workers AI source - https://developers.cloudflare.com/workers-ai
|
|
259
|
+
// 📖 OpenAI-compatible endpoint requires account id:
|
|
260
|
+
// 📖 https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/chat/completions
|
|
261
|
+
// 📖 Free plan includes daily neuron quota and provider-level request limits.
|
|
262
|
+
export const cloudflare = [
|
|
263
|
+
['@cf/openai/gpt-oss-120b', 'GPT OSS 120B', 'S', '60.0%', '128k'],
|
|
264
|
+
['@cf/qwen/qwen2.5-coder-32b-instruct', 'Qwen2.5 Coder 32B', 'A', '46.0%', '32k'],
|
|
265
|
+
['@cf/deepseek-ai/deepseek-r1-distill-qwen-32b', 'R1 Distill 32B', 'A', '43.9%', '128k'],
|
|
266
|
+
['@cf/openai/gpt-oss-20b', 'GPT OSS 20B', 'A', '42.0%', '128k'],
|
|
267
|
+
['@cf/meta/llama-3.3-70b-instruct-fp8-fast', 'Llama 3.3 70B', 'A-', '39.5%', '128k'],
|
|
268
|
+
['@cf/meta/llama-3.1-8b-instruct', 'Llama 3.1 8B', 'B', '28.8%', '128k'],
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
// 📖 Perplexity source - https://docs.perplexity.ai
|
|
272
|
+
// 📖 Chat Completions endpoint: https://api.perplexity.ai/chat/completions
|
|
273
|
+
// 📖 Sonar models focus on search/reasoning and have tiered API rate limits.
|
|
274
|
+
export const perplexity = [
|
|
275
|
+
['sonar-reasoning-pro', 'Sonar Reasoning Pro', 'A+', '50.0%', '128k'],
|
|
276
|
+
['sonar-reasoning', 'Sonar Reasoning', 'A', '45.0%', '128k'],
|
|
277
|
+
['sonar-pro', 'Sonar Pro', 'B+', '32.0%', '128k'],
|
|
278
|
+
['sonar', 'Sonar', 'B', '25.0%', '128k'],
|
|
279
|
+
]
|
|
280
|
+
|
|
233
281
|
// 📖 All sources combined - used by the main script
|
|
234
282
|
// 📖 Each source has: name (display), url (API endpoint), models (array of model tuples)
|
|
235
283
|
export const sources = {
|
|
@@ -298,6 +346,26 @@ export const sources = {
|
|
|
298
346
|
url: 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',
|
|
299
347
|
models: googleai,
|
|
300
348
|
},
|
|
349
|
+
siliconflow: {
|
|
350
|
+
name: 'SiliconFlow',
|
|
351
|
+
url: 'https://api.siliconflow.com/v1/chat/completions',
|
|
352
|
+
models: siliconflow,
|
|
353
|
+
},
|
|
354
|
+
together: {
|
|
355
|
+
name: 'Together AI',
|
|
356
|
+
url: 'https://api.together.xyz/v1/chat/completions',
|
|
357
|
+
models: together,
|
|
358
|
+
},
|
|
359
|
+
cloudflare: {
|
|
360
|
+
name: 'Cloudflare AI',
|
|
361
|
+
url: 'https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/chat/completions',
|
|
362
|
+
models: cloudflare,
|
|
363
|
+
},
|
|
364
|
+
perplexity: {
|
|
365
|
+
name: 'Perplexity',
|
|
366
|
+
url: 'https://api.perplexity.ai/chat/completions',
|
|
367
|
+
models: perplexity,
|
|
368
|
+
},
|
|
301
369
|
}
|
|
302
370
|
|
|
303
371
|
// 📖 Flatten all models from all sources — each entry includes providerKey as 6th element
|