free-coding-models 0.3.48 → 0.3.50
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/CHANGELOG.md +39 -8
- package/README.md +32 -32
- package/package.json +1 -1
- package/sources.js +65 -63
- package/src/command-palette.js +1 -1
- package/src/key-handler.js +137 -20
- package/web/dist/assets/{index-Dg9WC-oF.js → index-DOtGtGLl.js} +1 -1
- package/web/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
|
-
## [0.3.
|
|
1
|
+
## [0.3.50] - 2026-04-11
|
|
2
2
|
|
|
3
|
-
###
|
|
4
|
-
- **jcode model validation freeze (for real this time)** — jcode has a hardcoded model whitelist that rejects bare model names like `gpt-oss-120b` via `--model` flag. The v0.3.47 fix using `JCODE_MODEL` env var didn't actually work — jcode silently ignores it for the `openai-compatible` provider. The real fix uses two strategies:
|
|
5
|
-
1. **Native providers**: For providers that jcode supports natively (Groq, Cerebras, DeepInfra, Scaleway, Together, Hugging Face, Fireworks, Chutes, OpenRouter, Perplexity, ZAI, Mistral), we now use `--provider groq` instead of `--provider openai-compatible`. Native providers accept namespaced model names like `openai/gpt-oss-120b` without validation issues.
|
|
6
|
-
2. **OpenAI-compatible fallback**: For providers without a native jcode match (NVIDIA NIM, SambaNova, etc.), we keep `--provider openai-compatible` but ensure model IDs always have a namespace prefix (`openai/gpt-oss-120b` instead of `gpt-oss-120b`) and set a placeholder `OPENROUTER_API_KEY` to satisfy jcode's false credential check on namespaced models.
|
|
3
|
+
### Changed
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
5
|
+
- **Providers reordered by generosity of free tier** — All 25 providers are now sorted from most generous to least generous in the README, TUI Settings page, and `D` key filter cycling. No more hunting for the best free option.
|
|
6
|
+
|
|
7
|
+
- **Free tier limits corrected across all providers** — Verified and corrected free tier limits for every provider using live web research. Key corrections:
|
|
8
|
+
- **Groq**: 30 RPM, 1K-14.4K req/day (previously listed as "30-50 RPM per model")
|
|
9
|
+
- **Google AI Studio**: 15-60 RPM, 250-1.5K req/day (previously listed as "14.4K req/day, 30/min")
|
|
10
|
+
- **Together AI**: ❌ **No free tier** — requires $5 minimum purchase. Removed from the "free" recommendation.
|
|
11
|
+
- **iFlow**: ⚠️ **Shutting down April 17, 2026** — marked with warning in README and sources.js
|
|
12
|
+
|
|
13
|
+
- **README subtitle updated** — Now says "ranked by generosity of free tier (most generous first)" instead of "ranked by SWE-bench"
|
|
14
|
+
|
|
15
|
+
### Provider generosity ranking (most generous first)
|
|
16
|
+
|
|
17
|
+
1. Groq (30 RPM, 1K-14.4K req/day)
|
|
18
|
+
2. Cerebras (1M tokens/day)
|
|
19
|
+
3. Google AI Studio (15-60 RPM, 250-1.5K req/day)
|
|
20
|
+
4. NVIDIA NIM (~40 RPM)
|
|
21
|
+
5. Cloudflare Workers AI (10K neurons/day)
|
|
22
|
+
6. OpenRouter (50 req/day free)
|
|
23
|
+
7. DeepInfra (200 concurrent requests)
|
|
24
|
+
8. HuggingFace (~$0.10/month)
|
|
25
|
+
9. Perplexity (~50 RPM tiered)
|
|
26
|
+
10. SambaNova (generous dev quota)
|
|
27
|
+
11. Fireworks AI ($1 credits)
|
|
28
|
+
12. Hyperbolic ($1 credits)
|
|
29
|
+
13. OVHcloud AI (2 req/min/IP free)
|
|
30
|
+
14. Replicate (6 req/min free)
|
|
31
|
+
15. Codestral (30 RPM, 2K req/day)
|
|
32
|
+
16. ZAI (generous free quota)
|
|
33
|
+
17. Scaleway (1M tokens)
|
|
34
|
+
18. Alibaba DashScope (1M tokens/90 days)
|
|
35
|
+
19. SiliconFlow (100 req/day + $1 credits)
|
|
36
|
+
20. Rovo Dev CLI (5M tokens/day)
|
|
37
|
+
21. Gemini CLI (1K req/day)
|
|
38
|
+
22. Chutes AI (free community GPU)
|
|
39
|
+
23. OpenCode Zen (free with account)
|
|
40
|
+
24. Together AI (❌ no free tier)
|
|
41
|
+
25. iFlow (⚠️ shutting down April 17, 2026)
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ create a free account on one of the [providers](#-list-of-free-ai-providers)
|
|
|
34
34
|
<a href="#-why-this-tool">💡 Why</a> •
|
|
35
35
|
<a href="#-quick-start">⚡ Quick Start</a> •
|
|
36
36
|
<a href="#-list-of-free-ai-providers">🟢 Providers</a> •
|
|
37
|
-
<a href="#-
|
|
37
|
+
<a href="#-other-free-ai-resources">🆓 Other Free AI Resources</a> •
|
|
38
38
|
<a href="#-usage">🚀 Usage</a> •
|
|
39
39
|
<a href="#-tui-keys">⌨️ TUI Keys</a> •
|
|
40
40
|
<a href="#-features">✨ Features</a> •
|
|
@@ -72,43 +72,43 @@ It then writes the model you pick directly into your coding tool's config — so
|
|
|
72
72
|
|
|
73
73
|
Create a free account on one provider below to get started:
|
|
74
74
|
|
|
75
|
-
**238 coding models** across 25 providers, ranked by
|
|
76
|
-
|
|
77
|
-
| Provider | Models | Tier range | Free tier | Env var |
|
|
78
|
-
|
|
79
|
-
| [
|
|
80
|
-
| [
|
|
81
|
-
| [
|
|
82
|
-
| [
|
|
83
|
-
| [
|
|
84
|
-
| [
|
|
85
|
-
| [
|
|
86
|
-
| [
|
|
87
|
-
| [
|
|
88
|
-
| [
|
|
89
|
-
| [
|
|
90
|
-
| [
|
|
91
|
-
| [
|
|
92
|
-
|
|
|
93
|
-
| [
|
|
94
|
-
| [
|
|
95
|
-
| [
|
|
96
|
-
| [
|
|
97
|
-
| [
|
|
98
|
-
| [
|
|
99
|
-
| [
|
|
100
|
-
| [
|
|
101
|
-
| [
|
|
102
|
-
| [
|
|
103
|
-
| [
|
|
75
|
+
**238 coding models** across 25 providers, ranked by generosity of free tier (most generous first).
|
|
76
|
+
|
|
77
|
+
| # | Provider | Models | Tier range | Free tier | Env var |
|
|
78
|
+
|---|----------|--------|-----------|-----------|--------|
|
|
79
|
+
| 1 | [Groq](https://console.groq.com/keys) | 8 | S → B | 30 RPM, 1K‑14.4K req/day (no credit card) | `GROQ_API_KEY` |
|
|
80
|
+
| 2 | [Cerebras](https://cloud.cerebras.ai) | 4 | S+ → B | 30 RPM, 1M tokens/day (no credit card) | `CEREBRAS_API_KEY` |
|
|
81
|
+
| 3 | [Google AI Studio](https://aistudio.google.com/apikey) | 6 | B+ → C | 15‑60 RPM, 250‑1.5K req/day (no credit card) | `GOOGLE_API_KEY` |
|
|
82
|
+
| 4 | [NVIDIA NIM](https://build.nvidia.com) | 46 | S+ → C | ~40 RPM (no credit card) | `NVIDIA_API_KEY` |
|
|
83
|
+
| 5 | [Cloudflare Workers AI](https://dash.cloudflare.com) | 15 | S → B | 10K neurons/day, 300 RPM (no credit card) | `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` |
|
|
84
|
+
| 6 | [OpenRouter](https://openrouter.ai/keys) | 25 | S+ → C | 50 req/day free, 1K/day with $10 spend | `OPENROUTER_API_KEY` |
|
|
85
|
+
| 7 | [DeepInfra](https://deepinfra.com/login) | 4 | A- → B+ | 200 concurrent requests (no credit card) | `DEEPINFRA_API_KEY` |
|
|
86
|
+
| 8 | [Hugging Face](https://huggingface.com/settings/tokens) | 2 | S → B | ~$0.10/month free credits | `HUGGINGFACE_API_KEY` |
|
|
87
|
+
| 9 | [Perplexity API](https://www.perplexity.ai/settings/api) | 4 | A+ → B | ~50 RPM (tiered by spend) | `PERPLEXITY_API_KEY` |
|
|
88
|
+
| 10 | [SambaNova](https://sambanova.ai/developers) | 13 | S+ → B | Dev tier generous quota (no credit card) | `SAMBANOVA_API_KEY` |
|
|
89
|
+
| 11 | [Fireworks AI](https://fireworks.ai) | 4 | S → B+ | $1 free credits, 10 RPM without payment | `FIREWORKS_API_KEY` |
|
|
90
|
+
| 12 | [Hyperbolic](https://app.hyperbolic.ai/settings) | 13 | S+ → A- | $1 free credits (permanent) | `HYPERBOLIC_API_KEY` |
|
|
91
|
+
| 13 | [OVHcloud AI Endpoints](https://endpoints.ai.cloud.ovh.net) | 8 | S → B | 2 req/min/IP free, 400 RPM with key | `OVH_AI_ENDPOINTS_ACCESS_TOKEN` |
|
|
92
|
+
| 14 | [Replicate](https://replicate.com/account/api-tokens) | 2 | A- → B | 6 req/min free, 3K RPM with payment | `REPLICATE_API_TOKEN` |
|
|
93
|
+
| 15 | [Codestral](https://codestral.mistral.ai) | 1 | B+ | 30 RPM, 2K req/day (no credit card) | `CODESTRAL_API_KEY` |
|
|
94
|
+
| 16 | [ZAI](https://z.ai) | 7 | S+ → S | Generous free quota (concurrency limited) | `ZAI_API_KEY` |
|
|
95
|
+
| 17 | [Scaleway](https://console.scaleway.com/iam/api-keys) | 10 | S+ → B+ | 1M free tokens (permanent) | `SCALEWAY_API_KEY` |
|
|
96
|
+
| 18 | [Alibaba DashScope](https://modelstudio.console.alibabacloud.com) | 11 | S+ → A | 1M free tokens/model (90 days) | `DASHSCOPE_API_KEY` |
|
|
97
|
+
| 19 | [SiliconFlow](https://cloud.siliconflow.cn/account/ak) | 6 | S+ → A | 100 req/day + $1 free credits | `SILICONFLOW_API_KEY` |
|
|
98
|
+
| 20 | [Rovo Dev CLI](https://www.atlassian.com/rovo) | 5 | S+ | 5M tokens/day (beta) | CLI tool 🦘 |
|
|
99
|
+
| 21 | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | 3 | S+ → A+ | 1,000 req/day (no credit card) | CLI tool ♊ |
|
|
100
|
+
| 22 | [Chutes AI](https://chutes.ai) | 4 | S → A | Free community GPU (no credit card) | `CHUTES_API_KEY` |
|
|
101
|
+
| 23 | [OpenCode Zen](https://opencode.ai/zen) | 7 | S+ → A+ | Free with OpenCode account | Zen models ✨ |
|
|
102
|
+
| 24 | [Together AI](https://api.together.ai/settings/api-keys) | 19 | S+ → A- | ❌ No free tier — requires $5 purchase | `TOGETHER_API_KEY` |
|
|
103
|
+
| 25 | [iFlow ⚠️](https://platform.iflow.cn) | 11 | S+ → A+ | Shutting down April 17, 2026 | `IFLOW_API_KEY` |
|
|
104
104
|
|
|
105
105
|
> 💡 One key is enough. Add more at any time with **`P`** inside the TUI.
|
|
106
106
|
|
|
107
107
|
---
|
|
108
108
|
|
|
109
|
-
##
|
|
109
|
+
## 🆓 Other Free AI Resources
|
|
110
110
|
|
|
111
|
-
**
|
|
111
|
+
**Curated free resources outside the CLI** — IDE extensions, coding agents, GitHub lists, and trial credits.
|
|
112
112
|
|
|
113
113
|
### 📚 Awesome Lists (curated by the community)
|
|
114
114
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.50",
|
|
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
|
@@ -480,12 +480,9 @@ export const opencodeZen = [
|
|
|
480
480
|
|
|
481
481
|
// 📖 All sources combined - used by the main script
|
|
482
482
|
// 📖 Each source has: name (display), url (API endpoint), models (array of model tuples)
|
|
483
|
+
// 📖 Providers ordered by generosity of free tier (most generous first)
|
|
484
|
+
// 📖 See README for full tier-by-tier comparison
|
|
483
485
|
export const sources = {
|
|
484
|
-
nvidia: {
|
|
485
|
-
name: 'NIM',
|
|
486
|
-
url: 'https://integrate.api.nvidia.com/v1/chat/completions',
|
|
487
|
-
models: nvidiaNim,
|
|
488
|
-
},
|
|
489
486
|
groq: {
|
|
490
487
|
name: 'Groq',
|
|
491
488
|
url: 'https://api.groq.com/openai/v1/chat/completions',
|
|
@@ -496,92 +493,91 @@ export const sources = {
|
|
|
496
493
|
url: 'https://api.cerebras.ai/v1/chat/completions',
|
|
497
494
|
models: cerebras,
|
|
498
495
|
},
|
|
499
|
-
|
|
500
|
-
name: '
|
|
501
|
-
url: 'https://
|
|
502
|
-
models:
|
|
496
|
+
googleai: {
|
|
497
|
+
name: 'Google AI Studio',
|
|
498
|
+
url: 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',
|
|
499
|
+
models: googleai,
|
|
500
|
+
},
|
|
501
|
+
nvidia: {
|
|
502
|
+
name: 'NIM',
|
|
503
|
+
url: 'https://integrate.api.nvidia.com/v1/chat/completions',
|
|
504
|
+
models: nvidiaNim,
|
|
505
|
+
},
|
|
506
|
+
cloudflare: {
|
|
507
|
+
name: 'Cloudflare AI',
|
|
508
|
+
url: 'https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/chat/completions',
|
|
509
|
+
models: cloudflare,
|
|
503
510
|
},
|
|
504
511
|
openrouter: {
|
|
505
512
|
name: 'OpenRouter',
|
|
506
513
|
url: 'https://openrouter.ai/api/v1/chat/completions',
|
|
507
514
|
models: openrouter,
|
|
508
515
|
},
|
|
516
|
+
deepinfra: {
|
|
517
|
+
name: 'DeepInfra',
|
|
518
|
+
url: 'https://api.deepinfra.com/v1/openai/chat/completions',
|
|
519
|
+
models: deepinfra,
|
|
520
|
+
},
|
|
509
521
|
huggingface: {
|
|
510
522
|
name: 'Hugging Face',
|
|
511
523
|
url: 'https://router.huggingface.co/v1/chat/completions',
|
|
512
524
|
models: huggingface,
|
|
513
525
|
},
|
|
514
|
-
|
|
515
|
-
name: '
|
|
516
|
-
url: 'https://api.
|
|
517
|
-
models:
|
|
526
|
+
perplexity: {
|
|
527
|
+
name: 'Perplexity',
|
|
528
|
+
url: 'https://api.perplexity.ai/chat/completions',
|
|
529
|
+
models: perplexity,
|
|
518
530
|
},
|
|
519
|
-
|
|
520
|
-
name: '
|
|
521
|
-
url: 'https://api.
|
|
522
|
-
models:
|
|
531
|
+
sambanova: {
|
|
532
|
+
name: 'SambaNova',
|
|
533
|
+
url: 'https://api.sambanova.ai/v1/chat/completions',
|
|
534
|
+
models: sambanova,
|
|
523
535
|
},
|
|
524
536
|
fireworks: {
|
|
525
537
|
name: 'Fireworks',
|
|
526
538
|
url: 'https://api.fireworks.ai/inference/v1/chat/completions',
|
|
527
539
|
models: fireworks,
|
|
528
540
|
},
|
|
529
|
-
codestral: {
|
|
530
|
-
name: 'Codestral',
|
|
531
|
-
url: 'https://api.mistral.ai/v1/chat/completions',
|
|
532
|
-
models: codestral,
|
|
533
|
-
},
|
|
534
541
|
hyperbolic: {
|
|
535
542
|
name: 'Hyperbolic',
|
|
536
543
|
url: 'https://api.hyperbolic.xyz/v1/chat/completions',
|
|
537
544
|
models: hyperbolic,
|
|
538
545
|
},
|
|
539
|
-
|
|
540
|
-
name: '
|
|
541
|
-
url: 'https://
|
|
542
|
-
models:
|
|
546
|
+
ovhcloud: {
|
|
547
|
+
name: 'OVHcloud AI',
|
|
548
|
+
url: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/chat/completions',
|
|
549
|
+
models: ovhcloud,
|
|
543
550
|
},
|
|
544
|
-
|
|
545
|
-
name: '
|
|
546
|
-
url: 'https://
|
|
547
|
-
models:
|
|
551
|
+
replicate: {
|
|
552
|
+
name: 'Replicate',
|
|
553
|
+
url: 'https://api.replicate.com/v1/predictions',
|
|
554
|
+
models: replicate,
|
|
555
|
+
},
|
|
556
|
+
codestral: {
|
|
557
|
+
name: 'Codestral',
|
|
558
|
+
url: 'https://api.mistral.ai/v1/chat/completions',
|
|
559
|
+
models: codestral,
|
|
548
560
|
},
|
|
549
561
|
zai: {
|
|
550
562
|
name: 'ZAI',
|
|
551
563
|
url: 'https://api.z.ai/api/coding/paas/v4/chat/completions',
|
|
552
564
|
models: zai,
|
|
553
565
|
},
|
|
554
|
-
|
|
555
|
-
name: '
|
|
556
|
-
url: 'https://api.
|
|
557
|
-
models:
|
|
558
|
-
},
|
|
559
|
-
together: {
|
|
560
|
-
name: 'Together AI',
|
|
561
|
-
url: 'https://api.together.xyz/v1/chat/completions',
|
|
562
|
-
models: together,
|
|
563
|
-
},
|
|
564
|
-
cloudflare: {
|
|
565
|
-
name: 'Cloudflare AI',
|
|
566
|
-
url: 'https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/chat/completions',
|
|
567
|
-
models: cloudflare,
|
|
568
|
-
},
|
|
569
|
-
perplexity: {
|
|
570
|
-
name: 'Perplexity',
|
|
571
|
-
url: 'https://api.perplexity.ai/chat/completions',
|
|
572
|
-
models: perplexity,
|
|
566
|
+
scaleway: {
|
|
567
|
+
name: 'Scaleway',
|
|
568
|
+
url: 'https://api.scaleway.ai/v1/chat/completions',
|
|
569
|
+
models: scaleway,
|
|
573
570
|
},
|
|
574
571
|
qwen: {
|
|
575
|
-
name: 'Alibaba
|
|
572
|
+
name: 'Alibaba DashScope',
|
|
576
573
|
url: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions',
|
|
577
574
|
models: qwen,
|
|
578
575
|
},
|
|
579
|
-
|
|
580
|
-
name: '
|
|
581
|
-
url: 'https://
|
|
582
|
-
models:
|
|
576
|
+
siliconflow: {
|
|
577
|
+
name: 'SiliconFlow',
|
|
578
|
+
url: 'https://api.siliconflow.com/v1/chat/completions',
|
|
579
|
+
models: siliconflow,
|
|
583
580
|
},
|
|
584
|
-
// 📖 CLI-only tools (no API endpoint - launched directly)
|
|
585
581
|
rovo: {
|
|
586
582
|
name: 'Rovo Dev CLI',
|
|
587
583
|
url: null, // CLI tool - no API endpoint
|
|
@@ -600,21 +596,27 @@ export const sources = {
|
|
|
600
596
|
binary: 'gemini',
|
|
601
597
|
checkArgs: ['--version'],
|
|
602
598
|
},
|
|
599
|
+
chutes: {
|
|
600
|
+
name: 'Chutes AI',
|
|
601
|
+
url: 'https://chutes.ai/v1/chat/completions',
|
|
602
|
+
models: chutes,
|
|
603
|
+
},
|
|
603
604
|
'opencode-zen': {
|
|
604
605
|
name: 'OpenCode Zen',
|
|
605
606
|
url: 'https://opencode.ai/zen/v1/chat/completions',
|
|
606
607
|
models: opencodeZen,
|
|
607
608
|
zenOnly: true,
|
|
608
609
|
},
|
|
609
|
-
|
|
610
|
-
name: '
|
|
611
|
-
url: 'https://
|
|
612
|
-
models:
|
|
610
|
+
together: {
|
|
611
|
+
name: 'Together AI',
|
|
612
|
+
url: 'https://api.together.xyz/v1/chat/completions',
|
|
613
|
+
models: together,
|
|
613
614
|
},
|
|
614
|
-
|
|
615
|
-
name: '
|
|
616
|
-
url: 'https://
|
|
617
|
-
models:
|
|
615
|
+
iflow: {
|
|
616
|
+
name: 'iFlow ⚠️',
|
|
617
|
+
url: 'https://apis.iflow.cn/v1/chat/completions',
|
|
618
|
+
models: iflow,
|
|
619
|
+
shutdownDate: '2026-04-17', // 📖 Shutting down April 17, 2026
|
|
618
620
|
},
|
|
619
621
|
}
|
|
620
622
|
|
package/src/command-palette.js
CHANGED
|
@@ -40,7 +40,7 @@ const TOOL_MODE_COMMANDS = TOOL_MODE_ORDER.map((toolMode) => {
|
|
|
40
40
|
const meta = TOOL_METADATA[toolMode] || { label: toolMode, emoji: '🧰' }
|
|
41
41
|
return {
|
|
42
42
|
id: `action-set-tool-${toolMode}`,
|
|
43
|
-
label:
|
|
43
|
+
label: meta.label,
|
|
44
44
|
toolMode,
|
|
45
45
|
icon: meta.emoji,
|
|
46
46
|
description: TOOL_MODE_DESCRIPTIONS[toolMode] || 'Set this as the active launch target.',
|
package/src/key-handler.js
CHANGED
|
@@ -55,12 +55,83 @@ const PROVIDER_TEST_MODEL_OVERRIDES = {
|
|
|
55
55
|
const SETTINGS_TEST_MAX_ATTEMPTS = 10
|
|
56
56
|
const SETTINGS_TEST_RETRY_DELAY_MS = 4000
|
|
57
57
|
|
|
58
|
+
// 📖 PROVIDER_AUTH_ENDPOINTS maps provider keys to their auth-check URL + method.
|
|
59
|
+
// 📖 For most providers this is the /models endpoint (returns 200=valid, 401=invalid).
|
|
60
|
+
// 📖 Providers without an auth-check endpoint use null (falls back to chat completion ping).
|
|
61
|
+
// 📖 Special cases:
|
|
62
|
+
// 📖 - replicate: uses /v1/predictions (not /models) but needs a different payload
|
|
63
|
+
// 📖 - cloudflare: no auth endpoint — only has chat completions, always uses ping fallback
|
|
64
|
+
const PROVIDER_AUTH_ENDPOINTS = {
|
|
65
|
+
nvidia: { url: 'https://api.nvidia.com/v1/account', method: 'GET' },
|
|
66
|
+
groq: { url: 'https://api.groq.com/v1/models', method: 'GET' },
|
|
67
|
+
cerebras: { url: 'https://api.cerebras.ai/v1/models', method: 'GET' },
|
|
68
|
+
sambanova: { url: 'https://api.sambanova.ai/v1/models', method: 'GET' },
|
|
69
|
+
openrouter: { url: 'https://openrouter.ai/api/v1/models', method: 'GET' },
|
|
70
|
+
huggingface: { url: 'https://router.huggingface.co/v1/models', method: 'GET' },
|
|
71
|
+
deepinfra: { url: 'https://api.deepinfra.com/v1/models', method: 'GET' },
|
|
72
|
+
fireworks: { url: 'https://api.fireworks.ai/v1/models', method: 'GET' },
|
|
73
|
+
hyperbolic: { url: 'https://api.hyperbolic.xyz/v1/models', method: 'GET' },
|
|
74
|
+
scaleway: { url: 'https://api.scaleway.ai/v1/models', method: 'GET' },
|
|
75
|
+
siliconflow: { url: 'https://api.siliconflow.com/v1/models', method: 'GET' },
|
|
76
|
+
together: { url: 'https://api.together.xyz/v1/models', method: 'GET' },
|
|
77
|
+
perplexity: { url: 'https://api.perplexity.ai/v1/models', method: 'GET' },
|
|
78
|
+
chutes: { url: 'https://chutes.ai/v1/models', method: 'GET' },
|
|
79
|
+
ovhcloud: { url: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/models', method: 'GET' },
|
|
80
|
+
qwen: { url: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models', method: 'GET' },
|
|
81
|
+
iflow: { url: 'https://apis.iflow.cn/v1/models', method: 'GET' },
|
|
82
|
+
replicate: null, // 📖 Replicate has no /models endpoint; use chat completions ping
|
|
83
|
+
cloudflare: null, // 📖 Workers AI has no auth-check endpoint; use ping only
|
|
84
|
+
zai: null, // 📖 ZAI undocumented; use ping only
|
|
85
|
+
googleai: null, // 📖 Google AI Studio has no OpenAI-compatible /models; use ping
|
|
86
|
+
'opencode-zen': null, // 📖 OpenCode Zen uses OpenCode auth only; use ping
|
|
87
|
+
rovo: null, // 📖 CLI tool — no API key
|
|
88
|
+
gemini: null, // 📖 CLI tool — no API key
|
|
89
|
+
}
|
|
90
|
+
|
|
58
91
|
// 📖 Sleep helper kept local to this module so the Settings key test flow can
|
|
59
92
|
// 📖 back off between retries without leaking timer logic into the rest of the TUI.
|
|
60
93
|
function sleep(ms) {
|
|
61
94
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
62
95
|
}
|
|
63
96
|
|
|
97
|
+
// 📖 testProviderKeyDirect: Fast auth-only check using /v1/account or /v1/models.
|
|
98
|
+
// 📖 Fires 3 parallel probes to get a fast decisive result (auth error vs timeout vs 200).
|
|
99
|
+
// 📖 Returns { code, ms } from the first non-timeout response, or the best available.
|
|
100
|
+
async function testProviderKeyDirect(apiKey, providerKey) {
|
|
101
|
+
const authConfig = PROVIDER_AUTH_ENDPOINTS[providerKey]
|
|
102
|
+
if (!authConfig) return null
|
|
103
|
+
|
|
104
|
+
const { url, method } = authConfig
|
|
105
|
+
const headers = { Authorization: `Bearer ${apiKey}` }
|
|
106
|
+
if (providerKey === 'openrouter') {
|
|
107
|
+
headers['HTTP-Referer'] = 'https://github.com/vava-nessa/free-coding-models'
|
|
108
|
+
headers['X-Title'] = 'free-coding-models'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const parallel = 3
|
|
112
|
+
const promises = Array.from({ length: parallel }, async () => {
|
|
113
|
+
const ctrl = new AbortController()
|
|
114
|
+
const timer = setTimeout(() => ctrl.abort(), 8000)
|
|
115
|
+
const t0 = performance.now()
|
|
116
|
+
try {
|
|
117
|
+
const resp = await fetch(url, { method, headers, signal: ctrl.signal })
|
|
118
|
+
return { code: resp.status, ms: Math.round(performance.now() - t0) }
|
|
119
|
+
} catch (err) {
|
|
120
|
+
const isTimeout = err.name === 'AbortError'
|
|
121
|
+
return { code: isTimeout ? '000' : 'ERR', ms: isTimeout ? 'TIMEOUT' : Math.round(performance.now() - t0) }
|
|
122
|
+
} finally {
|
|
123
|
+
clearTimeout(timer)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const results = await Promise.all(promises)
|
|
128
|
+
const success = results.find(r => r.code === 200)
|
|
129
|
+
if (success) return success
|
|
130
|
+
const authFailure = results.find(r => r.code === 401 || r.code === 403)
|
|
131
|
+
if (authFailure) return authFailure
|
|
132
|
+
return results[0]
|
|
133
|
+
}
|
|
134
|
+
|
|
64
135
|
/**
|
|
65
136
|
* 📖 buildProviderModelsUrl derives the matching `/models` endpoint for providers
|
|
66
137
|
* 📖 that expose an OpenAI-compatible model list next to `/chat/completions`.
|
|
@@ -471,7 +542,10 @@ export function createKeyHandler(ctx) {
|
|
|
471
542
|
}
|
|
472
543
|
|
|
473
544
|
// ─── Settings key test helper ───────────────────────────────────────────────
|
|
474
|
-
// 📖
|
|
545
|
+
// 📖 Verifies an API key by first doing a fast parallel auth-only probe (3×8s)
|
|
546
|
+
// 📖 to /v1/account or /v1/models, then falling back to chat completion pings.
|
|
547
|
+
// 📖 Auth-only result is decisive (200=valid, 401/403=invalid); only timeouts or
|
|
548
|
+
// 📖 providers without auth endpoints fall through to the ping-based approach.
|
|
475
549
|
async function testProviderKey(providerKey) {
|
|
476
550
|
const src = sources[providerKey]
|
|
477
551
|
if (!src) return
|
|
@@ -484,6 +558,24 @@ export function createKeyHandler(ctx) {
|
|
|
484
558
|
return
|
|
485
559
|
}
|
|
486
560
|
|
|
561
|
+
// 📖 Fast path: parallel auth-only probes (3×8s) to /v1/account or /v1/models.
|
|
562
|
+
// 📖 200 = key valid and accepted. 401/403 = key rejected. null = no auth endpoint.
|
|
563
|
+
const authResult = await testProviderKeyDirect(testKey, providerKey)
|
|
564
|
+
if (authResult) {
|
|
565
|
+
if (authResult.code === 200) {
|
|
566
|
+
state.settingsTestResults[providerKey] = 'ok'
|
|
567
|
+
state.settingsTestDetails[providerKey] = buildProviderTestDetail(providerLabel, 'ok', [], `Auth-only probe returned HTTP 200.`)
|
|
568
|
+
return
|
|
569
|
+
}
|
|
570
|
+
if (authResult.code === 401 || authResult.code === 403) {
|
|
571
|
+
state.settingsTestResults[providerKey] = 'auth_error'
|
|
572
|
+
state.settingsTestDetails[providerKey] = buildProviderTestDetail(providerLabel, 'auth_error', [], `Auth probe returned HTTP ${authResult.code}.`)
|
|
573
|
+
return
|
|
574
|
+
}
|
|
575
|
+
// 📖 Timeout or ERR — fall through to ping-based approach below.
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// 📖 Slow path: ping-based verification (providers without auth endpoint or timeouts).
|
|
487
579
|
state.settingsTestResults[providerKey] = 'pending'
|
|
488
580
|
state.settingsTestDetails[providerKey] = `Testing ${providerLabel} across up to ${SETTINGS_TEST_MAX_ATTEMPTS} probes...`
|
|
489
581
|
const discoveredModelIds = []
|
|
@@ -508,7 +600,6 @@ export function createKeyHandler(ctx) {
|
|
|
508
600
|
discoveryNote = `Live model discovery returned HTTP ${modelsResp.status}; falling back to the repo catalog.`
|
|
509
601
|
}
|
|
510
602
|
} catch (err) {
|
|
511
|
-
// 📖 Discovery failure is non-fatal; we still have repo-defined fallbacks.
|
|
512
603
|
discoveryNote = `Live model discovery failed (${err?.name || 'error'}); falling back to the repo catalog.`
|
|
513
604
|
}
|
|
514
605
|
}
|
|
@@ -519,35 +610,46 @@ export function createKeyHandler(ctx) {
|
|
|
519
610
|
state.settingsTestDetails[providerKey] = buildProviderTestDetail(providerLabel, 'fail', [], discoveryNote || 'No candidate model was available for probing.')
|
|
520
611
|
return
|
|
521
612
|
}
|
|
613
|
+
|
|
614
|
+
// 📖 Parallel ping burst: fire up to 5 probes simultaneously to get fast feedback.
|
|
615
|
+
const PARALLEL_PROBES = 5
|
|
522
616
|
const attempts = []
|
|
617
|
+
let settled = false
|
|
523
618
|
|
|
524
|
-
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
619
|
+
while (!settled) {
|
|
620
|
+
const batch = []
|
|
621
|
+
for (let i = 0; i < PARALLEL_PROBES && attempts.length + batch.length < SETTINGS_TEST_MAX_ATTEMPTS; i++) {
|
|
622
|
+
const testModel = candidateModels[(attempts.length + batch.length) % candidateModels.length]
|
|
623
|
+
batch.push(ping(testKey, testModel, providerKey, src.url).then(({ code }) => ({ attempt: attempts.length + batch.length + 1, model: testModel, code })))
|
|
624
|
+
}
|
|
625
|
+
const batchResults = await Promise.all(batch)
|
|
626
|
+
attempts.push(...batchResults)
|
|
528
627
|
|
|
529
|
-
|
|
628
|
+
// 📖 Check outcome after each parallel batch.
|
|
629
|
+
const outcome = classifyProviderTestOutcome(attempts.map(({ code }) => code))
|
|
630
|
+
if (outcome === 'ok') {
|
|
530
631
|
state.settingsTestResults[providerKey] = 'ok'
|
|
531
632
|
state.settingsTestDetails[providerKey] = buildProviderTestDetail(providerLabel, 'ok', attempts, discoveryNote)
|
|
532
|
-
|
|
633
|
+
settled = true
|
|
634
|
+
continue
|
|
533
635
|
}
|
|
534
|
-
|
|
535
|
-
const outcome = classifyProviderTestOutcome(attempts.map(({ code: attemptCode }) => attemptCode))
|
|
536
636
|
if (outcome === 'auth_error') {
|
|
537
637
|
state.settingsTestResults[providerKey] = 'auth_error'
|
|
538
638
|
state.settingsTestDetails[providerKey] = buildProviderTestDetail(providerLabel, 'auth_error', attempts, discoveryNote)
|
|
539
|
-
|
|
639
|
+
settled = true
|
|
640
|
+
continue
|
|
540
641
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
state.settingsTestDetails[providerKey] =
|
|
544
|
-
|
|
642
|
+
if (attempts.length >= SETTINGS_TEST_MAX_ATTEMPTS) {
|
|
643
|
+
state.settingsTestResults[providerKey] = outcome
|
|
644
|
+
state.settingsTestDetails[providerKey] = buildProviderTestDetail(providerLabel, outcome, attempts, discoveryNote)
|
|
645
|
+
settled = true
|
|
646
|
+
continue
|
|
545
647
|
}
|
|
546
|
-
}
|
|
547
648
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
649
|
+
// 📖 Show progress between batches, then pause before next round.
|
|
650
|
+
state.settingsTestDetails[providerKey] = `Testing ${providerLabel}... ${attempts.length}/${SETTINGS_TEST_MAX_ATTEMPTS} probes tried. Retrying in ${SETTINGS_TEST_RETRY_DELAY_MS / 1000}s.`
|
|
651
|
+
await sleep(SETTINGS_TEST_RETRY_DELAY_MS)
|
|
652
|
+
}
|
|
551
653
|
}
|
|
552
654
|
|
|
553
655
|
// 📖 Manual update checker from settings; keeps status visible in maintenance row.
|
|
@@ -747,6 +849,20 @@ export function createKeyHandler(ctx) {
|
|
|
747
849
|
state.settingsAddKeyMode = false
|
|
748
850
|
state.settingsEditBuffer = ''
|
|
749
851
|
state.settingsScrollOffset = 0
|
|
852
|
+
|
|
853
|
+
// 📖 Auto-test all configured API keys in parallel on Settings open.
|
|
854
|
+
// 📖 Each provider with a saved key fires a parallel auth probe batch immediately.
|
|
855
|
+
// 📖 The T key re-triggers a focused test on the selected row without clearing others.
|
|
856
|
+
const providerKeys = Object.keys(sources)
|
|
857
|
+
for (const pk of providerKeys) {
|
|
858
|
+
const testKey = getApiKey(state.config, pk)
|
|
859
|
+
if (testKey) {
|
|
860
|
+
// 📖 Fire and forget — update state as probes resolve.
|
|
861
|
+
testProviderKey(pk)
|
|
862
|
+
} else {
|
|
863
|
+
state.settingsTestResults[pk] = 'missing_key'
|
|
864
|
+
}
|
|
865
|
+
}
|
|
750
866
|
}
|
|
751
867
|
|
|
752
868
|
function openRecommendOverlay() {
|
|
@@ -2293,7 +2409,8 @@ export function createKeyHandler(ctx) {
|
|
|
2293
2409
|
}
|
|
2294
2410
|
|
|
2295
2411
|
// 📖 Alt+W: toggle footer visibility (collapse to single hint when hidden)
|
|
2296
|
-
|
|
2412
|
+
// 📖 Note: readline doesn't set key.alt=true for ALT combos; detect via str starting with \x1b (ESC)
|
|
2413
|
+
if (key.name === 'w' && str && str.startsWith('\x1b') && !key.ctrl && !key.meta) {
|
|
2297
2414
|
state.footerHidden = !state.footerHidden
|
|
2298
2415
|
if (!state.config.settings || typeof state.config.settings !== 'object') state.config.settings = {}
|
|
2299
2416
|
state.config.settings.footerHidden = state.footerHidden
|