free-coding-models 0.3.55 ā 0.3.57
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 +55 -56
- package/README.md +214 -160
- package/bin/free-coding-models.js +46 -0
- package/package.json +2 -2
- package/sources.js +134 -310
- package/src/analysis.js +23 -10
- package/src/app.js +66 -27
- package/src/cache.js +1 -1
- package/src/cli-help.js +9 -0
- package/src/command-palette.js +15 -13
- package/src/config.js +201 -35
- package/src/constants.js +4 -4
- package/src/endpoint-installer.js +45 -1
- package/src/favorites.js +22 -0
- package/src/graphify-out/cache/089db1c1def873cf6d112f1590da4490e61e691aff0db41e006aa2fb15ba0656.json +1 -0
- package/src/graphify-out/cache/0b510b53cf1a1393fb52b1fc3bbbf88b63938e961ec5b82119a2e9715fee8bd7.json +1 -0
- package/src/graphify-out/cache/0ec9a95a326bde58e0316889018b278062d06d494d0f31ba177c9de71e5fed2d.json +1 -0
- package/src/graphify-out/cache/1548663a24a68dce740ebab1bd1d3091048c9604e9d067a1650a42a6d82541d4.json +1 -0
- package/src/graphify-out/cache/1783af63cb6d0dfb4d469009f71ac83a74ba0b33d48186ff2c6e63f9429e900a.json +1 -0
- package/src/graphify-out/cache/1e109f5eb5dc4fd285871c3613e32b6b14a8c225f4080ee34b51c7e1a1764571.json +1 -0
- package/src/graphify-out/cache/1eb24dbeb69b46c8bc1caf925df2f2a964af0f33aea143adf8ddf88e017db6ca.json +1 -0
- package/src/graphify-out/cache/21e1bcfed11685e8347243f9d8516072dda183266a4bfe22c52fb31753a446c8.json +1 -0
- package/src/graphify-out/cache/2327473478b9c4b1940bf7ef66c9ee960b3cba8d5302e56b625df8274246e0b4.json +1 -0
- package/src/graphify-out/cache/25955b81fd25454c8fa90fb71a47db8d1215cf621beb8ff3cbd580aaf011b4f3.json +1 -0
- package/src/graphify-out/cache/2739677f19c702f88f3de0a0bac475066adbda98709907ad3de967aef689f86d.json +1 -0
- package/src/graphify-out/cache/2bba03422f6b3ee7f5b5d29cc90314a064d259e5822a176657bda3e04505cf00.json +1 -0
- package/src/graphify-out/cache/2ddf1d2c6d10147b0402446bc71a7988187b79b6210dd7e7250be8c555b9ff35.json +1 -0
- package/src/graphify-out/cache/2ee07457a5767c95a57f8e9eb95b28f800044f35666e0715e9d88ad1103a092e.json +1 -0
- package/src/graphify-out/cache/2fe9f75dc2951c417f2c8dd22749092cf550dc67599f1c8d1866900dc6e9154e.json +1 -0
- package/src/graphify-out/cache/41c4b7c27e7fc3e2948d3a4bf95a72de2ed9a6f0463994babdce8ed2cc84598c.json +1 -0
- package/src/graphify-out/cache/5028defd54b7fbd3c7e444973e493de036e097e9b1d2a7cae7f19b88d68aacde.json +1 -0
- package/src/graphify-out/cache/5b133aba3fb16410c5b1fdbd1730039fc7fa1ac93abd99d7be08f60da70fc8d4.json +1 -0
- package/src/graphify-out/cache/74252e5b0978d85ab3421a3de1a9384aa282ffd2be2cfe7db2530139089f4275.json +1 -0
- package/src/graphify-out/cache/7695ebeea056095edd14332963cc43354ef3a097caf46f1e28d0f01369642901.json +1 -0
- package/src/graphify-out/cache/777aa7085c395a935c6556bbde182cd871edb61f3a685ed8068ec0c8f6fb0075.json +1 -0
- package/src/graphify-out/cache/82a723881980e82273c113def8315533d7da28827e300413d9ad30f27b7407df.json +1 -0
- package/src/graphify-out/cache/86b87c9603e6cd188f42c7eed3b86c291d48a781c223a707e74f3e7ed0c02a21.json +1 -0
- package/src/graphify-out/cache/890fead9a78cadaed560a2d2453916121fa605c3e43a334910ac4bc951a9ef6d.json +1 -0
- package/src/graphify-out/cache/89d3ea66f52783caa775ef9a30923d7d6225e1d8ae9e962f4741b8c7785dab1e.json +1 -0
- package/src/graphify-out/cache/8cc82cd9edce41f0e1c092f14a94fd52bf847addf3237b616dc5a9e505bd05bd.json +1 -0
- package/src/graphify-out/cache/93ba2e25e3ff7ad525f397902345fbd375df7315de7b402e20cc803c14eccde8.json +1 -0
- package/src/graphify-out/cache/99beed29580b9c7bfecfee794cb3d8e535fcf0eb3b92113108f88bdd0a8e79b3.json +1 -0
- package/src/graphify-out/cache/aeeb931fa477c65ce2e51d8149957350fa54225c613222bbbe8448998d1afd3d.json +1 -0
- package/src/graphify-out/cache/baf91bef5b5ecb2a476433b6cc0c48c563c54ee2d07fc3c192e543685e3e7222.json +1 -0
- package/src/graphify-out/cache/bd98b94ac4e9b92b6336d47b26e0366b51a4eaf0711d722f05f98dfae23ab42b.json +1 -0
- package/src/graphify-out/cache/bfcb51e9328e9cbfbee4f6fee0f56635d7b03488addc9f6c4e4b190b70a73362.json +1 -0
- package/src/graphify-out/cache/c0d3dabeb093aa758c49eadf41b87ecc96a16c1449c2670aaf48cbfc891d8da6.json +1 -0
- package/src/graphify-out/cache/c20d6630236f473c1406068c3ae205853e649b216495c93dfec055dd222c55cf.json +1 -0
- package/src/graphify-out/cache/c22b9122816bebce0a2f79af41a986559d01e00163dbcd579c5755621b4cb483.json +1 -0
- package/src/graphify-out/cache/ca556ec14453ddb8f9e0c5a832dac90d77111b9bad5f8c2d80d272e2e7a06371.json +1 -0
- package/src/graphify-out/cache/d6dbc9135dfa35a756b3b09b06700e4bc229fdccba11bb963f2ba44028e0bbae.json +1 -0
- package/src/graphify-out/cache/e1cf71276f1779d0fa075f79bd7c8a9fd0b8eef6932ac043137451b7c7fa7cbe.json +1 -0
- package/src/graphify-out/cache/e4b3be14494467df2d2ed389bc4f18f099021cb5bc355b901fa88387b2d8b8a2.json +1 -0
- package/src/graphify-out/cache/eaea0dded097f6f9553b654220046c6ec0c9be592a5973d906564ee60af34e0d.json +1 -0
- package/src/graphify-out/cache/ef07d0cd2675d1f79d2a2fdbf3bc3319687638751e9ce89b0d0d97ed1cd9f7e1.json +1 -0
- package/src/graphify-out/cache/f81272d6eb8aaff9e96d5a1d9f06777db70ac3652a646b951ded51f79871d733.json +1 -0
- package/src/graphify-out/cache/f9619dd92186f75a6dbda937e0c606647153918524cdb5763f956e6ec2a9e386.json +1 -0
- package/src/graphify-out/cache/fd88b1b2ff4bfcae08559d9c2aaeeb9a3f1e2f5cd8928762c311196956c170a5.json +1 -0
- package/src/key-handler.js +322 -114
- package/src/kilo.js +20 -1
- package/src/opencode.js +23 -2
- package/src/overlays.js +199 -98
- package/src/provider-metadata.js +26 -17
- package/src/quota-capabilities.js +6 -10
- package/src/render-helpers.js +38 -8
- package/src/render-table.js +119 -248
- package/src/router-daemon.js +1986 -0
- package/src/router-dashboard.js +902 -0
- package/src/sync-set.js +479 -0
- package/src/theme.js +4 -0
- package/src/tool-launchers.js +1 -0
- package/src/tool-metadata.js +6 -2
- package/src/utils.js +30 -6
- package/web/dist/assets/{index-C03JjCgA.js ā index-DKHCzbK1.js} +2 -2
- package/web/dist/index.html +1 -1
package/src/kilo.js
CHANGED
|
@@ -9,6 +9,7 @@ import { loadKiloConfig, saveKiloConfig, getKiloConfigPath } from './kilo-config
|
|
|
9
9
|
import { getApiKey } from './config.js'
|
|
10
10
|
import { ENV_VAR_NAMES, OPENCODE_MODEL_MAP } from './provider-metadata.js'
|
|
11
11
|
import { resolveToolBinaryPath } from './tool-bootstrap.js'
|
|
12
|
+
import { sources } from '../sources.js'
|
|
12
13
|
|
|
13
14
|
// š Map source model IDs to Kilo built-in IDs (same as OpenCode).
|
|
14
15
|
function getKiloModelId(providerKey, modelId) {
|
|
@@ -17,6 +18,21 @@ function getKiloModelId(providerKey, modelId) {
|
|
|
17
18
|
return OPENCODE_MODEL_MAP[providerKey]?.[modelId] || modelId
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
function buildOpenAiCompatibleProviderConfig(providerKey) {
|
|
22
|
+
const source = sources[providerKey]
|
|
23
|
+
const envVarName = ENV_VAR_NAMES[providerKey]
|
|
24
|
+
if (!source?.url || !envVarName) return null
|
|
25
|
+
const baseURL = source.url
|
|
26
|
+
.replace(/\/chat\/completions$/i, '')
|
|
27
|
+
.replace(/\/responses$/i, '')
|
|
28
|
+
return {
|
|
29
|
+
npm: '@ai-sdk/openai-compatible',
|
|
30
|
+
name: source.name || providerKey,
|
|
31
|
+
options: { baseURL, apiKey: `{env:${envVarName}}` },
|
|
32
|
+
models: {},
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
// š spawnKilo: Resolve API keys + spawn kilo CLI with correct env.
|
|
21
37
|
async function spawnKilo(args, providerKey, fcmConfig) {
|
|
22
38
|
const envVarName = ENV_VAR_NAMES[providerKey]
|
|
@@ -120,7 +136,7 @@ export async function startKilo(model, fcmConfig) {
|
|
|
120
136
|
config.provider.codestral = {
|
|
121
137
|
npm: '@ai-sdk/openai-compatible',
|
|
122
138
|
name: 'Mistral Codestral',
|
|
123
|
-
options: { baseURL: 'https://api.mistral.ai/v1', apiKey: '{env:
|
|
139
|
+
options: { baseURL: 'https://api.mistral.ai/v1', apiKey: '{env:MISTRAL_API_KEY}' },
|
|
124
140
|
models: {}
|
|
125
141
|
}
|
|
126
142
|
} else if (providerKey === 'hyperbolic') {
|
|
@@ -179,6 +195,9 @@ export async function startKilo(model, fcmConfig) {
|
|
|
179
195
|
options: { baseURL: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1', apiKey: '{env:OVH_AI_ENDPOINTS_ACCESS_TOKEN}' },
|
|
180
196
|
models: {}
|
|
181
197
|
}
|
|
198
|
+
} else {
|
|
199
|
+
const providerConfig = buildOpenAiCompatibleProviderConfig(providerKey)
|
|
200
|
+
if (providerConfig) config.provider[providerKey] = providerConfig
|
|
182
201
|
}
|
|
183
202
|
}
|
|
184
203
|
|
package/src/opencode.js
CHANGED
|
@@ -98,6 +98,21 @@ function getOpenCodeModelId(providerKey, modelId) {
|
|
|
98
98
|
return OPENCODE_MODEL_MAP[providerKey]?.[modelId] || modelId
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
function buildOpenAiCompatibleProviderConfig(providerKey) {
|
|
102
|
+
const source = sources[providerKey]
|
|
103
|
+
const envVarName = ENV_VAR_NAMES[providerKey]
|
|
104
|
+
if (!source?.url || !envVarName) return null
|
|
105
|
+
const baseURL = source.url
|
|
106
|
+
.replace(/\/chat\/completions$/i, '')
|
|
107
|
+
.replace(/\/responses$/i, '')
|
|
108
|
+
return {
|
|
109
|
+
npm: '@ai-sdk/openai-compatible',
|
|
110
|
+
name: source.name || providerKey,
|
|
111
|
+
options: { baseURL, apiKey: `{env:${envVarName}}` },
|
|
112
|
+
models: {},
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
101
116
|
// āāā ZAI proxy bridge āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
102
117
|
|
|
103
118
|
// š createZaiProxy: Localhost reverse proxy bridging ZAI's non-standard API paths
|
|
@@ -434,7 +449,7 @@ export async function startOpenCode(model, fcmConfig) {
|
|
|
434
449
|
config.provider.codestral = {
|
|
435
450
|
npm: '@ai-sdk/openai-compatible',
|
|
436
451
|
name: 'Mistral Codestral',
|
|
437
|
-
options: { baseURL: 'https://api.mistral.ai/v1', apiKey: '{env:
|
|
452
|
+
options: { baseURL: 'https://api.mistral.ai/v1', apiKey: '{env:MISTRAL_API_KEY}' },
|
|
438
453
|
models: {}
|
|
439
454
|
}
|
|
440
455
|
} else if (providerKey === 'hyperbolic') {
|
|
@@ -514,6 +529,9 @@ export async function startOpenCode(model, fcmConfig) {
|
|
|
514
529
|
options: { baseURL: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1', apiKey: '{env:OVH_AI_ENDPOINTS_ACCESS_TOKEN}' },
|
|
515
530
|
models: {}
|
|
516
531
|
}
|
|
532
|
+
} else {
|
|
533
|
+
const providerConfig = buildOpenAiCompatibleProviderConfig(providerKey)
|
|
534
|
+
if (providerConfig) config.provider[providerKey] = providerConfig
|
|
517
535
|
}
|
|
518
536
|
}
|
|
519
537
|
|
|
@@ -799,7 +817,7 @@ export async function startOpenCodeDesktop(model, fcmConfig) {
|
|
|
799
817
|
config.provider.codestral = {
|
|
800
818
|
npm: '@ai-sdk/openai-compatible',
|
|
801
819
|
name: 'Mistral Codestral',
|
|
802
|
-
options: { baseURL: 'https://api.mistral.ai/v1', apiKey: '{env:
|
|
820
|
+
options: { baseURL: 'https://api.mistral.ai/v1', apiKey: '{env:MISTRAL_API_KEY}' },
|
|
803
821
|
models: {}
|
|
804
822
|
}
|
|
805
823
|
} else if (providerKey === 'hyperbolic') {
|
|
@@ -879,6 +897,9 @@ export async function startOpenCodeDesktop(model, fcmConfig) {
|
|
|
879
897
|
options: { baseURL: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1', apiKey: '{env:OVH_AI_ENDPOINTS_ACCESS_TOKEN}' },
|
|
880
898
|
models: {}
|
|
881
899
|
}
|
|
900
|
+
} else {
|
|
901
|
+
const providerConfig = buildOpenAiCompatibleProviderConfig(providerKey)
|
|
902
|
+
if (providerConfig) config.provider[providerKey] = providerConfig
|
|
882
903
|
}
|
|
883
904
|
}
|
|
884
905
|
|
package/src/overlays.js
CHANGED
|
@@ -4,17 +4,16 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @details
|
|
6
6
|
* This module centralizes all overlay rendering in one place:
|
|
7
|
-
* - Settings, Install Endpoints, Command Palette, Help, Smart Recommend,
|
|
7
|
+
* - Settings, Install Endpoints, Command Palette, Help, Smart Recommend, Changelog, Router Dashboard
|
|
8
8
|
* - Settings diagnostics for provider key tests, including wrapped retry/error details
|
|
9
9
|
* - Recommend analysis timer orchestration and progress updates
|
|
10
10
|
*
|
|
11
11
|
* The factory pattern keeps stateful UI logic isolated while still
|
|
12
12
|
* allowing the main CLI to control shared state and dependencies.
|
|
13
13
|
*
|
|
14
|
-
* š Feedback overlay (I key) combines feature requests + bug reports in one left-aligned input
|
|
15
|
-
*
|
|
16
14
|
* ā Functions:
|
|
17
15
|
* - `createOverlayRenderers` ā returns renderer + analysis helpers + overlayLayout
|
|
16
|
+
* - `renderRouterDashboard` ā mounts the Smart Model Router dashboard renderer
|
|
18
17
|
*
|
|
19
18
|
* @exports { createOverlayRenderers }
|
|
20
19
|
* @see ./key-handler.js ā handles keypresses for all overlay interactions
|
|
@@ -22,6 +21,7 @@
|
|
|
22
21
|
|
|
23
22
|
import { loadChangelog } from './changelog-loader.js'
|
|
24
23
|
import { buildCliHelpLines } from './cli-help.js'
|
|
24
|
+
import { renderRouterDashboard as renderRouterDashboardOverlay } from './router-dashboard.js'
|
|
25
25
|
import { themeColors, getThemeStatusLabel, getProviderRgb } from './theme.js'
|
|
26
26
|
|
|
27
27
|
export function createOverlayRenderers(state, deps) {
|
|
@@ -297,12 +297,16 @@ export function createOverlayRenderers(state, deps) {
|
|
|
297
297
|
}
|
|
298
298
|
lines.push('')
|
|
299
299
|
|
|
300
|
-
// š Footer with credits
|
|
300
|
+
// š Footer with credits + community links ā Discord and Buy me a coffee
|
|
301
|
+
// š live here (and in the onboarding) instead of the main TUI footer to
|
|
302
|
+
// š keep the table chrome lean.
|
|
301
303
|
lines.push('')
|
|
302
304
|
lines.push(
|
|
303
305
|
themeColors.dim(' ') +
|
|
304
306
|
themeColors.footerLove('Made with š & ā by ') +
|
|
305
307
|
themeColors.link('\x1b]8;;https://github.com/vava-nessa\x1b\\vava-nessa\x1b]8;;\x1b\\') +
|
|
308
|
+
themeColors.dim(' ⢠š¬ ') +
|
|
309
|
+
themeColors.footerDiscord('\x1b]8;;https://discord.gg/ZTNFHvvCkU\x1b\\Join the Discord\x1b]8;;\x1b\\') +
|
|
306
310
|
themeColors.dim(' ⢠ā ') +
|
|
307
311
|
themeColors.footerCoffee('\x1b]8;;https://buymeacoffee.com/vavanessadev\x1b\\Buy me a coffee\x1b]8;;\x1b\\') +
|
|
308
312
|
themeColors.dim(' ⢠') +
|
|
@@ -866,8 +870,9 @@ export function createOverlayRenderers(state, deps) {
|
|
|
866
870
|
// š Branding header
|
|
867
871
|
lines.push(` ${themeColors.accent('š')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
868
872
|
lines.push(` ${heading('ā Help & Keyboard Shortcuts')}`)
|
|
873
|
+
lines.push(` ${themeColors.successBold('š Yellow = active key')}`)
|
|
869
874
|
lines.push('')
|
|
870
|
-
lines.push(` ${hint('ā āā / PgUp / PgDn / Home / End scroll ⢠K or Esc close')}`)
|
|
875
|
+
lines.push(` ${hint('ā āā / PgUp / PgDn / Home / End scroll ⢠K or ')}${themeColors.successBold('Esc close')}`)
|
|
871
876
|
lines.push(` ${heading('Columns')}`)
|
|
872
877
|
lines.push('')
|
|
873
878
|
lines.push(` ${label('Rank')} SWE-bench rank (1 = best coding score) ${hint('Sort:')} ${key('R')}`)
|
|
@@ -882,7 +887,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
882
887
|
lines.push(` ${label('CTX')} Context window size (128k, 200k, 256k, 1m, etc.) ${hint('Sort:')} ${key('C')}`)
|
|
883
888
|
lines.push(` ${hint('Bigger context = the model can read more of your codebase at once without forgetting.')}`)
|
|
884
889
|
lines.push('')
|
|
885
|
-
lines.push(` ${label('Model')} Model name (
|
|
890
|
+
lines.push(` ${label('Model')} Model name (1ļøā£2ļøā£3ļøā£ = favorite order) ${hint('Sort:')} ${key('M')} ${hint('Favorite:')} ${key('F')}`)
|
|
886
891
|
lines.push(` ${hint('Star the ones you like. Press Y to switch between pinned mode and normal filter/sort mode.')}`)
|
|
887
892
|
lines.push('')
|
|
888
893
|
lines.push(` ${label('Provider')} Provider source (NIM, Groq, Cerebras, etc.) ${hint('Sort:')} ${key('O')} ${hint('Cycle:')} ${key('D')}`)
|
|
@@ -923,17 +928,18 @@ export function createOverlayRenderers(state, deps) {
|
|
|
923
928
|
lines.push(` ${key('Ctrl+P')} Open ā”ļø command palette ${hint('(search and run actions quickly)')}`)
|
|
924
929
|
lines.push(` ${key('E')} Toggle configured models only ${hint('(enabled by default)')}`)
|
|
925
930
|
lines.push(` ${key('Z')} Cycle tool mode ${hint('(š¦ OpenCode ā Ļ Pi ā šŖ¼ jcode ā š¦ Desktop ā š¦ OpenClaw ā š Crush ā šŖæ Goose ā š Aider ā š Qwen ā š¤² OpenHands ā ā” Amp ā š¦ Rovo ā ā Gemini)')}`)
|
|
926
|
-
lines.push(` ${key('F')} Toggle favorite on selected row ${hint('(
|
|
931
|
+
lines.push(` ${key('F')} Toggle favorite on selected row ${hint('(1ļøā£2ļøā£3ļøā£ = router fallback order, capped at š)')}`)
|
|
932
|
+
lines.push(` ${key('ā§ā/ā§ā')} Reorder selected favorite up/down ${hint('(changes router priority)')}`)
|
|
927
933
|
lines.push(` ${key('Y')} Toggle favorites mode ${hint('(Pinned + always visible ā Normal filter/sort behavior)')}`)
|
|
928
934
|
lines.push(` ${key('X')} Clear active text filter ${hint('(remove custom query applied from ā”ļø Command Palette)')}`)
|
|
929
935
|
lines.push(` ${key('Q')} Smart Recommend ${hint('(šÆ find the best model for your task ā questionnaire + live analysis)')}`)
|
|
930
936
|
lines.push(` ${key('G')} Cycle theme ${hint('(auto ā dark ā light)')}`)
|
|
931
|
-
|
|
937
|
+
|
|
932
938
|
lines.push(` ${key('P')} Open settings ${hint('(manage API keys, provider toggles, updates, legacy cleanup)')}`)
|
|
933
939
|
// š Profile system removed - API keys now persist permanently across all sessions
|
|
934
|
-
lines.push(` ${key('
|
|
940
|
+
lines.push(` ${key('Ctrl+P')} Reset view settings ${hint('(search "Reset view" in the command palette)')}`)
|
|
935
941
|
lines.push(` ${key('N')} Changelog ${hint('(š browse all versions, Enter to view details)')}`)
|
|
936
|
-
lines.push(` ${key('
|
|
942
|
+
lines.push(` ${key('I')} / ${key('Esc')} Show/hide this help`)
|
|
937
943
|
lines.push(` ${key('Ctrl+C')} Exit`)
|
|
938
944
|
lines.push('')
|
|
939
945
|
lines.push(` ${heading('Settings (P)')}`)
|
|
@@ -1166,92 +1172,6 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1166
1172
|
}, PING_RATE)
|
|
1167
1173
|
}
|
|
1168
1174
|
|
|
1169
|
-
// āāā Feedback overlay renderer āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1170
|
-
// š renderFeedback: Draw the overlay for anonymous Discord feedback.
|
|
1171
|
-
// š Shows an input field where users can type feedback, bug reports, or any comments.
|
|
1172
|
-
function renderFeedback() {
|
|
1173
|
-
const EL = '\x1b[K'
|
|
1174
|
-
const lines = []
|
|
1175
|
-
|
|
1176
|
-
// š Calculate available space for multi-line input (dynamic based on terminal width)
|
|
1177
|
-
const maxInputWidth = state.terminalCols - 8 // 8 = padding (4 spaces each side)
|
|
1178
|
-
const maxInputLines = 10 // Show up to 10 lines of input
|
|
1179
|
-
|
|
1180
|
-
// š Split buffer into lines for display (with wrapping)
|
|
1181
|
-
const wrapText = (text, width) => {
|
|
1182
|
-
const words = text.split(' ')
|
|
1183
|
-
const lines = []
|
|
1184
|
-
let currentLine = ''
|
|
1185
|
-
|
|
1186
|
-
for (const word of words) {
|
|
1187
|
-
const testLine = currentLine ? currentLine + ' ' + word : word
|
|
1188
|
-
if (testLine.length <= width) {
|
|
1189
|
-
currentLine = testLine
|
|
1190
|
-
} else {
|
|
1191
|
-
if (currentLine) lines.push(currentLine)
|
|
1192
|
-
currentLine = word
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
if (currentLine) lines.push(currentLine)
|
|
1196
|
-
return lines
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
const inputLines = wrapText(state.bugReportBuffer, maxInputWidth)
|
|
1200
|
-
const displayLines = inputLines.slice(0, maxInputLines)
|
|
1201
|
-
|
|
1202
|
-
// š Branding header
|
|
1203
|
-
lines.push('')
|
|
1204
|
-
lines.push(` ${themeColors.accent('š')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
1205
|
-
lines.push(` ${themeColors.successBold('š Feedback, bugs & requests')}`)
|
|
1206
|
-
lines.push('')
|
|
1207
|
-
lines.push(themeColors.dim(" ā don't hesitate to send us feedback, bug reports, or just your feeling about the app"))
|
|
1208
|
-
lines.push('')
|
|
1209
|
-
|
|
1210
|
-
// š Status messages (if any)
|
|
1211
|
-
if (state.bugReportStatus === 'sending') {
|
|
1212
|
-
lines.push(` ${themeColors.warning('ā³ Sending...')}`)
|
|
1213
|
-
lines.push('')
|
|
1214
|
-
} else if (state.bugReportStatus === 'success') {
|
|
1215
|
-
lines.push(` ${themeColors.successBold('ā
Successfully sent!')} ${themeColors.dim('Closing overlay in 3 seconds...')}`)
|
|
1216
|
-
lines.push('')
|
|
1217
|
-
lines.push(` ${themeColors.dim('Thank you for your feedback! It has been sent to the project team.')}`)
|
|
1218
|
-
lines.push('')
|
|
1219
|
-
} else if (state.bugReportStatus === 'error') {
|
|
1220
|
-
lines.push(` ${themeColors.error('ā Error:')} ${themeColors.warning(state.bugReportError || 'Failed to send')}`)
|
|
1221
|
-
lines.push(` ${themeColors.dim('Press Backspace to edit, or Esc to close')}`)
|
|
1222
|
-
lines.push('')
|
|
1223
|
-
} else {
|
|
1224
|
-
lines.push(` ${themeColors.dim('Type your feedback below. Press Enter to send, Esc to cancel.')}`)
|
|
1225
|
-
lines.push(` ${themeColors.dim('Your message will be sent anonymously to the project team.')}`)
|
|
1226
|
-
lines.push('')
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
// š Simple input area ā left-aligned, framed by horizontal lines
|
|
1230
|
-
lines.push(` ${themeColors.info('Message')} (${state.bugReportBuffer.length}/500 chars)`)
|
|
1231
|
-
lines.push(` ${themeColors.dim('ā'.repeat(maxInputWidth))}`)
|
|
1232
|
-
// š Input lines ā left-aligned, or placeholder when empty
|
|
1233
|
-
if (displayLines.length > 0) {
|
|
1234
|
-
for (const line of displayLines) {
|
|
1235
|
-
lines.push(` ${line}`)
|
|
1236
|
-
}
|
|
1237
|
-
// š Show cursor on last line
|
|
1238
|
-
if (state.bugReportStatus === 'idle' || state.bugReportStatus === 'error') {
|
|
1239
|
-
lines[lines.length - 1] += themeColors.accentBold('ā')
|
|
1240
|
-
}
|
|
1241
|
-
} else {
|
|
1242
|
-
const placeholderBR = state.bugReportStatus === 'idle' ? chalk.italic.rgb(...getProviderRgb('googleai'))('Type your message here...') : ''
|
|
1243
|
-
lines.push(` ${placeholderBR}${themeColors.accentBold('ā')}`)
|
|
1244
|
-
}
|
|
1245
|
-
lines.push(` ${themeColors.dim('ā'.repeat(maxInputWidth))}`)
|
|
1246
|
-
lines.push('')
|
|
1247
|
-
lines.push(themeColors.dim(' Enter Send ⢠Esc Cancel ⢠Backspace Delete'))
|
|
1248
|
-
|
|
1249
|
-
// š Apply overlay tint and return
|
|
1250
|
-
const tintedLines = tintOverlayLines(lines, themeColors.overlayBgFeedback, state.terminalCols)
|
|
1251
|
-
const cleared = tintedLines.map(l => l + EL)
|
|
1252
|
-
return cleared.join('\n')
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
1175
|
// āāā Changelog overlay renderer āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1256
1176
|
// š renderChangelog: Two-phase overlay ā index of all versions or details of one version
|
|
1257
1177
|
function renderChangelog() {
|
|
@@ -1394,6 +1314,10 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1394
1314
|
if (state.recommendPingTimer) { clearInterval(state.recommendPingTimer); state.recommendPingTimer = null }
|
|
1395
1315
|
}
|
|
1396
1316
|
|
|
1317
|
+
function renderRouterDashboard() {
|
|
1318
|
+
return renderRouterDashboardOverlay(state, { LOCAL_VERSION })
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1397
1321
|
// āāā Incompatible fallback overlay āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1398
1322
|
// š renderIncompatibleFallback shows when user presses Enter on a model that
|
|
1399
1323
|
// š is NOT compatible with the active tool. Two sections:
|
|
@@ -1490,6 +1414,181 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1490
1414
|
return cleared.join('\n')
|
|
1491
1415
|
}
|
|
1492
1416
|
|
|
1417
|
+
|
|
1418
|
+
// āāā Token Usage screen renderer āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1419
|
+
// š renderTokenUsage: shows today/all-time breakdowns, by-model breakdown,
|
|
1420
|
+
// š and a 7-day bar chart. Triggered by Shift+T from the main table.
|
|
1421
|
+
// š Data fetched from GET /stats/tokens on the daemon.
|
|
1422
|
+
function renderTokenUsage() {
|
|
1423
|
+
const EL = '\x1b[K'
|
|
1424
|
+
const lines = []
|
|
1425
|
+
const cursorLineByRow = {}
|
|
1426
|
+
|
|
1427
|
+
lines.push('')
|
|
1428
|
+
lines.push(` ${themeColors.accent('š')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
1429
|
+
lines.push(` ${themeColors.textBold('š Token Usage')} ${themeColors.dim('Shift+T from main table')}`)
|
|
1430
|
+
lines.push('')
|
|
1431
|
+
|
|
1432
|
+
const data = state.tokenUsageData
|
|
1433
|
+
|
|
1434
|
+
if (state.tokenUsageError) {
|
|
1435
|
+
lines.push(` ${themeColors.warning(state.tokenUsageError)}`)
|
|
1436
|
+
lines.push('')
|
|
1437
|
+
lines.push(themeColors.dim(' Press Shift+S to start the router daemon first, then reopen this screen.'))
|
|
1438
|
+
lines.push(themeColors.dim(' Esc to return to the main table'))
|
|
1439
|
+
const { visible, offset } = sliceOverlayLines(lines, state.tokenUsageScrollOffset, state.terminalRows)
|
|
1440
|
+
state.tokenUsageScrollOffset = offset
|
|
1441
|
+
const tintedLines = tintOverlayLines(visible, themeColors.overlayBgSettings, state.terminalCols)
|
|
1442
|
+
return tintedLines.map((l) => l + EL).join('\n')
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (!data) {
|
|
1446
|
+
lines.push(themeColors.dim(' Loading token stats...'))
|
|
1447
|
+
const { visible, offset } = sliceOverlayLines(lines, state.tokenUsageScrollOffset, state.terminalRows)
|
|
1448
|
+
state.tokenUsageScrollOffset = offset
|
|
1449
|
+
const tintedLines = tintOverlayLines(visible, themeColors.overlayBgSettings, state.terminalCols)
|
|
1450
|
+
return tintedLines.map((l) => l + EL).join('\n')
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const today = data.today || {}
|
|
1454
|
+
const allTime = data.all_time || {}
|
|
1455
|
+
const dailyData = data.daily || {}
|
|
1456
|
+
|
|
1457
|
+
const todayTotal = today.total_tokens || 0
|
|
1458
|
+
const todayPrompt = today.prompt_tokens || 0
|
|
1459
|
+
const todayCompletion = today.completion_tokens || 0
|
|
1460
|
+
const todayReq = today.requests || 0
|
|
1461
|
+
const allTimeTotal = allTime.total_tokens || 0
|
|
1462
|
+
const allTimeReq = allTime.requests || 0
|
|
1463
|
+
const firstTracked = allTime.first_tracked || null
|
|
1464
|
+
|
|
1465
|
+
lines.push(` ${themeColors.textBold('TODAY')} ${themeColors.dim(new Date().toISOString().slice(0, 10))} ${themeColors.dim('|')} ${themeColors.textBold('ALL TIME')}`)
|
|
1466
|
+
lines.push(` ${themeColors.dim('ā'.repeat(40))} ${themeColors.dim('ā'.repeat(30))}`)
|
|
1467
|
+
lines.push(` ${themeColors.textBold('Total:')} ${themeColors.info(formatTokenTotalCompact(todayTotal))} tok ${themeColors.dim('ā')} ${themeColors.textBold('Total:')} ${themeColors.info(formatTokenTotalCompact(allTimeTotal))} tok`)
|
|
1468
|
+
lines.push(` ${themeColors.textBold('Prompt:')} ${themeColors.dim(formatTokenTotalCompact(todayPrompt))} tok ${themeColors.dim('ā')} ${themeColors.textBold('Requests:')} ${themeColors.dim(String(allTimeReq))}`)
|
|
1469
|
+
lines.push(` ${themeColors.textBold('Completion:')} ${themeColors.dim(formatTokenTotalCompact(todayCompletion))} tok ${themeColors.dim('ā')} ${themeColors.textBold('Since:')} ${themeColors.dim(firstTracked ? new Date(firstTracked).toLocaleDateString() : 'ā')}`)
|
|
1470
|
+
lines.push(` ${themeColors.textBold('Requests:')} ${themeColors.dim(String(todayReq))} ${themeColors.dim('ā')}`)
|
|
1471
|
+
|
|
1472
|
+
const byModel = today.by_model || {}
|
|
1473
|
+
const sortedModels = Object.entries(byModel)
|
|
1474
|
+
.map(([key, val]) => {
|
|
1475
|
+
// š val can be a number (legacy) or { total, prompt, completion } object
|
|
1476
|
+
const total = (val && typeof val === 'object' && !Array.isArray(val)) ? (val.total || 0) : Number(val) || 0
|
|
1477
|
+
return { key, total }
|
|
1478
|
+
})
|
|
1479
|
+
.filter((m) => m.total > 0)
|
|
1480
|
+
.sort((a, b) => b.total - a.total)
|
|
1481
|
+
.slice(0, 8)
|
|
1482
|
+
|
|
1483
|
+
lines.push('')
|
|
1484
|
+
lines.push(` ${themeColors.textBold('TOP MODELS TODAY')}`)
|
|
1485
|
+
if (sortedModels.length === 0) {
|
|
1486
|
+
lines.push(themeColors.dim(' No usage tracked yet today.'))
|
|
1487
|
+
} else {
|
|
1488
|
+
const maxTotal = sortedModels[0]?.total || 1
|
|
1489
|
+
for (const m of sortedModels) {
|
|
1490
|
+
const barLen = Math.max(2, Math.round((m.total / maxTotal) * 28))
|
|
1491
|
+
const bar = themeColors.success('ā'.repeat(barLen)) + themeColors.dim('ā'.repeat(28 - barLen))
|
|
1492
|
+
const pct = todayTotal > 0 ? Math.round((m.total / todayTotal) * 100) : 0
|
|
1493
|
+
lines.push(` ${bar} ${themeColors.textBold(formatTokenTotalCompact(m.total))} tok ${themeColors.dim(`${pct}% ${m.key}`)}`)
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
lines.push('')
|
|
1498
|
+
lines.push(` ${themeColors.textBold('LAST 7 DAYS')}`)
|
|
1499
|
+
const dayLabels = []
|
|
1500
|
+
const dayTotals = []
|
|
1501
|
+
for (let i = 6; i >= 0; i--) {
|
|
1502
|
+
const d = new Date()
|
|
1503
|
+
d.setDate(d.getDate() - i)
|
|
1504
|
+
const key = d.toISOString().slice(0, 10)
|
|
1505
|
+
const dayData = dailyData[key]
|
|
1506
|
+
const total = dayData?.total_tokens || 0
|
|
1507
|
+
dayLabels.push(d.toLocaleDateString('en-US', { weekday: 'short' }))
|
|
1508
|
+
dayTotals.push(total)
|
|
1509
|
+
}
|
|
1510
|
+
const maxDay = Math.max(...dayTotals, 1)
|
|
1511
|
+
lines.push(` ${dayLabels.map((l, i) => themeColors.dim(padEndDisplay(l, 6))).join(' ')}`)
|
|
1512
|
+
const barHeights = [14, 10, 7, 4]
|
|
1513
|
+
for (const bh of barHeights) {
|
|
1514
|
+
const row = dayTotals.map((t) => {
|
|
1515
|
+
const filled = Math.round((t / maxDay) * bh)
|
|
1516
|
+
const bar = themeColors.info('ā'.repeat(filled)) + themeColors.dim('ā'.repeat(bh - filled))
|
|
1517
|
+
return padEndDisplay(bar, 6)
|
|
1518
|
+
})
|
|
1519
|
+
lines.push(` ${row.join(' ')}`)
|
|
1520
|
+
}
|
|
1521
|
+
const totalRow = dayTotals.map((t) => padEndDisplay(themeColors.textBold(formatTokenTotalCompact(t)), 6))
|
|
1522
|
+
lines.push(` ${totalRow.join(' ')}`)
|
|
1523
|
+
|
|
1524
|
+
lines.push('')
|
|
1525
|
+
lines.push(themeColors.dim(' Esc Back to main table'))
|
|
1526
|
+
|
|
1527
|
+
const { visible, offset } = sliceOverlayLines(lines, state.tokenUsageScrollOffset, state.terminalRows)
|
|
1528
|
+
state.tokenUsageScrollOffset = offset
|
|
1529
|
+
const tintedLines = tintOverlayLines(visible, themeColors.overlayBgSettings, state.terminalCols)
|
|
1530
|
+
return tintedLines.map((l) => l + EL).join('\n')
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// āāā Router Onboarding overlay renderer āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1534
|
+
// š renderRouterOnboarding: shown on first launch (no config.router) or
|
|
1535
|
+
// š first launch after upgrade (existing config but router.onboardingSeen !== true).
|
|
1536
|
+
// š Two options: Enable (Y) or Not now (N). Phase 6 ā Smart Model Router.
|
|
1537
|
+
function renderRouterOnboarding() {
|
|
1538
|
+
const EL = '\x1b[K'
|
|
1539
|
+
const lines = []
|
|
1540
|
+
const cursorLineByRow = {}
|
|
1541
|
+
|
|
1542
|
+
lines.push('')
|
|
1543
|
+
lines.push(` ${themeColors.accent('š')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
1544
|
+
lines.push(` ${themeColors.textBold('š Smart Router Available!')}`)
|
|
1545
|
+
lines.push('')
|
|
1546
|
+
lines.push(themeColors.dim(' FCM can run a background daemon that automatically'))
|
|
1547
|
+
lines.push(themeColors.dim(' routes your requests to the fastest healthy model ā'))
|
|
1548
|
+
lines.push(themeColors.dim(' with zero manual intervention after initial setup.'))
|
|
1549
|
+
lines.push('')
|
|
1550
|
+
|
|
1551
|
+
const options = [
|
|
1552
|
+
{ label: 'Yes, enable the router', hint: 'Recommended ā creates default set and starts daemon', key: 'Y' },
|
|
1553
|
+
{ label: 'Not now', hint: 'You can enable it later from the TUI', key: 'N' },
|
|
1554
|
+
]
|
|
1555
|
+
|
|
1556
|
+
if (state.routerOnboardingPhase === 'loading') {
|
|
1557
|
+
lines.push(themeColors.info(' Enabling router, please wait...'))
|
|
1558
|
+
} else if (state.routerOnboardingPhase === 'success') {
|
|
1559
|
+
lines.push(themeColors.success(' ā
Router enabled! Dashboard opening...'))
|
|
1560
|
+
lines.push(themeColors.dim(' Setup complete. Return to the main table to continue.'))
|
|
1561
|
+
} else if (state.routerOnboardingPhase === 'error') {
|
|
1562
|
+
lines.push(themeColors.error(` ā ${state.routerOnboardingError || 'Failed to enable router'}`))
|
|
1563
|
+
lines.push(themeColors.dim(' Press Esc or Enter to continue to the main table'))
|
|
1564
|
+
} else {
|
|
1565
|
+
for (let i = 0; i < options.length; i++) {
|
|
1566
|
+
const opt = options[i]
|
|
1567
|
+
const isCursor = i === state.routerOnboardingCursor
|
|
1568
|
+
const keyLabel = themeColors.hotkey(` ${opt.key}]`)
|
|
1569
|
+
const row = `${bullet(isCursor)}${keyLabel} ${isCursor ? themeColors.textBold(opt.label) : themeColors.text(opt.label)}`
|
|
1570
|
+
cursorLineByRow[i] = lines.length
|
|
1571
|
+
lines.push(isCursor ? themeColors.bgCursorSettingsList(row) : row)
|
|
1572
|
+
lines.push(themeColors.dim(` ${opt.hint}`))
|
|
1573
|
+
lines.push('')
|
|
1574
|
+
}
|
|
1575
|
+
lines.push(themeColors.dim(' āā Navigate ⢠Enter Select ⢠Esc Skip for now'))
|
|
1576
|
+
lines.push('')
|
|
1577
|
+
lines.push(
|
|
1578
|
+
themeColors.dim(' š¬ ') +
|
|
1579
|
+
themeColors.footerDiscord('\x1b]8;;https://discord.gg/ZTNFHvvCkU\x1b\\Join the Discord community\x1b]8;;\x1b\\') +
|
|
1580
|
+
themeColors.dim(' ⢠Get help, share feedback, follow updates')
|
|
1581
|
+
)
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
const targetLine = cursorLineByRow[state.routerOnboardingCursor] ?? 0
|
|
1585
|
+
state.routerOnboardingScrollOffset = keepOverlayTargetVisible(state.routerOnboardingScrollOffset, targetLine, lines.length, state.terminalRows)
|
|
1586
|
+
const { visible, offset } = sliceOverlayLines(lines, state.routerOnboardingScrollOffset, state.terminalRows)
|
|
1587
|
+
state.routerOnboardingScrollOffset = offset
|
|
1588
|
+
const tintedLines = tintOverlayLines(visible, themeColors.overlayBgSettings, state.terminalCols)
|
|
1589
|
+
return tintedLines.map((l) => l + EL).join('\n')
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1493
1592
|
return {
|
|
1494
1593
|
renderSettings,
|
|
1495
1594
|
renderInstallEndpoints,
|
|
@@ -1497,12 +1596,14 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1497
1596
|
renderCommandPalette,
|
|
1498
1597
|
renderHelp,
|
|
1499
1598
|
renderRecommend,
|
|
1500
|
-
renderFeedback,
|
|
1501
1599
|
renderChangelog,
|
|
1502
1600
|
renderInstalledModels,
|
|
1601
|
+
renderRouterDashboard,
|
|
1503
1602
|
renderIncompatibleFallback,
|
|
1603
|
+
renderTokenUsage,
|
|
1604
|
+
renderRouterOnboarding,
|
|
1504
1605
|
startRecommendAnalysis,
|
|
1505
1606
|
stopRecommendAnalysis,
|
|
1506
|
-
overlayLayout,
|
|
1607
|
+
overlayLayout,
|
|
1507
1608
|
}
|
|
1508
1609
|
}
|
package/src/provider-metadata.js
CHANGED
|
@@ -45,22 +45,17 @@ export const ENV_VAR_NAMES = {
|
|
|
45
45
|
cerebras: 'CEREBRAS_API_KEY',
|
|
46
46
|
sambanova: 'SAMBANOVA_API_KEY',
|
|
47
47
|
openrouter: 'OPENROUTER_API_KEY',
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
fireworks: 'FIREWORKS_API_KEY',
|
|
52
|
-
codestral: 'CODESTRAL_API_KEY',
|
|
53
|
-
hyperbolic: 'HYPERBOLIC_API_KEY',
|
|
48
|
+
'github-models': 'GITHUB_TOKEN',
|
|
49
|
+
mistral: 'MISTRAL_API_KEY',
|
|
50
|
+
codestral: 'MISTRAL_API_KEY',
|
|
54
51
|
scaleway: 'SCALEWAY_API_KEY',
|
|
55
52
|
googleai: 'GOOGLE_API_KEY',
|
|
56
|
-
siliconflow:'SILICONFLOW_API_KEY',
|
|
57
|
-
together: 'TOGETHER_API_KEY',
|
|
58
53
|
cloudflare: 'CLOUDFLARE_API_TOKEN',
|
|
59
|
-
perplexity: 'PERPLEXITY_API_KEY',
|
|
60
54
|
zai: 'ZAI_API_KEY',
|
|
61
55
|
gemini: 'GEMINI_API_KEY',
|
|
62
|
-
chutes: 'CHUTES_API_KEY',
|
|
63
56
|
ovhcloud: 'OVH_AI_ENDPOINTS_ACCESS_TOKEN',
|
|
57
|
+
qwen: 'DASHSCOPE_API_KEY',
|
|
58
|
+
'opencode-zen': 'OPENCODE_ZEN_API_KEY',
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
// š OPENCODE_MODEL_MAP: sparse table of model IDs that differ between sources.js and OpenCode's
|
|
@@ -103,7 +98,7 @@ export const PROVIDER_METADATA = {
|
|
|
103
98
|
color: chalk.rgb(255, 224, 178),
|
|
104
99
|
signupUrl: 'https://cloud.sambanova.ai/apis',
|
|
105
100
|
signupHint: 'SambaCloud portal ā Create API key',
|
|
106
|
-
rateLimits: '
|
|
101
|
+
rateLimits: 'Small developer quota; useful for light coding and smoke tests',
|
|
107
102
|
},
|
|
108
103
|
openrouter: {
|
|
109
104
|
label: 'OpenRouter',
|
|
@@ -113,6 +108,20 @@ export const PROVIDER_METADATA = {
|
|
|
113
108
|
rateLimits: 'Free on :free: 50/day <$10, 1000/day ā„$10 (20 req/min)',
|
|
114
109
|
detailedLimits: 'No credits (or <$10) ā 50 requests/day (20 req/min)\nā„ $10 in credits ā 1000 requests/day (20 req/min)\n⢠Free models (:free) never consume credits\n⢠Failed requests count toward quota\n⢠Quota resets daily at midnight UTC\n⢠Free-tier models may be rate-limited during peak hours',
|
|
115
110
|
},
|
|
111
|
+
'github-models': {
|
|
112
|
+
label: 'GitHub Models',
|
|
113
|
+
color: chalk.rgb(183, 201, 255),
|
|
114
|
+
signupUrl: 'https://models.github.ai',
|
|
115
|
+
signupHint: 'Use a GitHub token with Models access (GITHUB_TOKEN works in GitHub contexts)',
|
|
116
|
+
rateLimits: 'Quota depends on GitHub/Copilot tier; no separate provider billing',
|
|
117
|
+
},
|
|
118
|
+
mistral: {
|
|
119
|
+
label: 'Mistral LP',
|
|
120
|
+
color: chalk.rgb(255, 196, 120),
|
|
121
|
+
signupUrl: 'https://console.mistral.ai/api-keys',
|
|
122
|
+
signupHint: 'La Plateforme ā API keys (MISTRAL_API_KEY)',
|
|
123
|
+
rateLimits: 'Experiment plan: free evaluation tier with limited RPS/TPM/monthly tokens',
|
|
124
|
+
},
|
|
116
125
|
huggingface: {
|
|
117
126
|
label: 'Hugging Face Inference',
|
|
118
127
|
color: chalk.rgb(255, 245, 157),
|
|
@@ -146,9 +155,9 @@ export const PROVIDER_METADATA = {
|
|
|
146
155
|
codestral: {
|
|
147
156
|
label: 'Mistral Codestral',
|
|
148
157
|
color: chalk.rgb(248, 187, 208),
|
|
149
|
-
signupUrl: 'https://
|
|
150
|
-
signupHint: '
|
|
151
|
-
rateLimits: '30 req/min, 2000/day',
|
|
158
|
+
signupUrl: 'https://console.mistral.ai/api-keys',
|
|
159
|
+
signupHint: 'La Plateforme ā API keys (MISTRAL_API_KEY; CODESTRAL_API_KEY also works)',
|
|
160
|
+
rateLimits: 'Codestral free access: 30 req/min, 2000/day',
|
|
152
161
|
},
|
|
153
162
|
hyperbolic: {
|
|
154
163
|
label: 'Hyperbolic',
|
|
@@ -169,7 +178,7 @@ export const PROVIDER_METADATA = {
|
|
|
169
178
|
color: chalk.rgb(187, 222, 251),
|
|
170
179
|
signupUrl: 'https://aistudio.google.com/apikey',
|
|
171
180
|
signupHint: 'Get API key',
|
|
172
|
-
rateLimits: '
|
|
181
|
+
rateLimits: 'Gemini free quotas vary by model and region',
|
|
173
182
|
},
|
|
174
183
|
siliconflow: {
|
|
175
184
|
label: 'SiliconFlow',
|
|
@@ -211,7 +220,7 @@ export const PROVIDER_METADATA = {
|
|
|
211
220
|
color: chalk.rgb(174, 213, 255),
|
|
212
221
|
signupUrl: 'https://z.ai',
|
|
213
222
|
signupHint: 'Sign up and generate an API key',
|
|
214
|
-
rateLimits: 'Free tier
|
|
223
|
+
rateLimits: 'Free tier: Flash models only in this catalog',
|
|
215
224
|
},
|
|
216
225
|
iflow: {
|
|
217
226
|
label: 'iFlow',
|
|
@@ -240,7 +249,7 @@ export const PROVIDER_METADATA = {
|
|
|
240
249
|
color: chalk.rgb(66, 165, 245), // blue
|
|
241
250
|
signupUrl: 'https://github.com/google-gemini/gemini-cli',
|
|
242
251
|
signupHint: 'Install: npm install -g @google/gemini-cli',
|
|
243
|
-
rateLimits: 'Free tier: 1,000 req/day
|
|
252
|
+
rateLimits: 'Free tier: 1,000 req/day with personal Google account',
|
|
244
253
|
cliOnly: true,
|
|
245
254
|
},
|
|
246
255
|
'opencode-zen': {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @property {'header'|'endpoint'|'unknown'} telemetryType
|
|
30
30
|
* @property {boolean} [supportsEndpoint]
|
|
31
31
|
* @property {'percent'|'ok'} usageDisplay
|
|
32
|
-
* @property {'rolling'|'daily'|'unknown'|'none'} resetCadence
|
|
32
|
+
* @property {'rolling'|'daily'|'monthly'|'unknown'|'none'} resetCadence
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
/** @type {Record<string, ProviderCapability>} */
|
|
@@ -39,26 +39,22 @@ export const PROVIDER_CAPABILITIES = {
|
|
|
39
39
|
groq: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'daily' },
|
|
40
40
|
cerebras: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
41
41
|
sambanova: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
together: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
45
|
-
hyperbolic: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
42
|
+
'github-models': { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
43
|
+
mistral: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'monthly' },
|
|
46
44
|
scaleway: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
47
45
|
googleai: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'daily' },
|
|
48
46
|
codestral: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'daily' },
|
|
49
|
-
perplexity: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
50
47
|
qwen: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
48
|
+
ovhcloud: { telemetryType: 'header', supportsEndpoint: false, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
51
49
|
|
|
52
50
|
// Providers that have a dedicated usage/credits endpoint
|
|
53
51
|
openrouter: { telemetryType: 'endpoint', supportsEndpoint: true, usageDisplay: 'percent', resetCadence: 'unknown' },
|
|
54
|
-
siliconflow: { telemetryType: 'endpoint', supportsEndpoint: true, usageDisplay: 'ok', resetCadence: 'unknown' },
|
|
55
52
|
|
|
56
53
|
// Providers with no reliable quota signal
|
|
57
|
-
huggingface: { telemetryType: 'unknown', supportsEndpoint: false, usageDisplay: 'ok', resetCadence: 'none' },
|
|
58
|
-
replicate: { telemetryType: 'unknown', supportsEndpoint: false, usageDisplay: 'ok', resetCadence: 'none' },
|
|
59
54
|
cloudflare: { telemetryType: 'unknown', supportsEndpoint: false, usageDisplay: 'ok', resetCadence: 'daily' },
|
|
60
55
|
zai: { telemetryType: 'unknown', supportsEndpoint: false, usageDisplay: 'ok', resetCadence: 'none' },
|
|
61
|
-
|
|
56
|
+
gemini: { telemetryType: 'unknown', supportsEndpoint: false, usageDisplay: 'ok', resetCadence: 'daily' },
|
|
57
|
+
'opencode-zen': { telemetryType: 'unknown', supportsEndpoint: false, usageDisplay: 'ok', resetCadence: 'unknown' },
|
|
62
58
|
}
|
|
63
59
|
|
|
64
60
|
/** Fallback for unrecognized providers */
|