free-coding-models 0.5.95 → 0.5.97
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 +15 -11
- package/bin/free-coding-models.js +2 -0
- package/changelog/v0.5.96.md +22 -0
- package/changelog/v0.5.97.md +24 -0
- package/package.json +1 -1
- package/sources.js +197 -91
- package/src/core/config.js +28 -3
- package/src/core/i18n/index.js +49 -0
- package/src/core/i18n/locales/en.js +991 -0
- package/src/core/i18n/locales/zh-CN.js +991 -0
- package/src/core/opencode.js +15 -8
- package/src/core/ping.js +31 -1
- package/src/core/playground.js +20 -19
- package/src/core/provider-key-tester.js +1 -0
- package/src/core/provider-metadata.js +11 -2
- package/src/core/quota-capabilities.js +2 -0
- package/src/core/router-daemon.js +58 -8
- package/src/core/router-dashboard.js +15 -14
- package/src/core/router-v2/tui-dashboard.js +27 -26
- package/src/core/shell-env.js +6 -0
- package/src/core/utils.js +22 -0
- package/src/tui/app.js +2 -0
- package/src/tui/cli-help.js +45 -21
- package/src/tui/command-palette.js +19 -1
- package/src/tui/key-handler.js +21 -1
- package/src/tui/overlays.js +113 -107
- package/src/tui/render-helpers.js +4 -3
- package/src/tui/render-table.js +137 -132
- package/src/tui/theme.js +2 -0
- package/web/dist/assets/index-C2lIYp0O.js +45 -0
- package/web/dist/assets/index-DZFdxy2N.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/server.js +58 -6
- package/web/dist/assets/index-BOpGJzSO.js +0 -45
- package/web/dist/assets/index-CCaxIOti.css +0 -1
package/src/core/config.js
CHANGED
|
@@ -23,8 +23,14 @@
|
|
|
23
23
|
* "scaleway": "scw-xxx",
|
|
24
24
|
* "googleai": "AIza...",
|
|
25
25
|
* "cloudflare": "cf-xxx",
|
|
26
|
-
* "zai": "zai-xxx"
|
|
26
|
+
* "zai": "zai-xxx",
|
|
27
|
+
* "onomeo": "${ONOMEO_API_KEY}"
|
|
27
28
|
* },
|
|
29
|
+
*
|
|
30
|
+
* 📖 ${VAR} values (issue #196): an apiKeys entry of the form "${MY_VAR}" is
|
|
31
|
+
* 📖 resolved from the environment at read time. The file on disk keeps the
|
|
32
|
+
* 📖 placeholder so secrets never land on disk; saveConfig never writes the
|
|
33
|
+
* 📖 resolved value back. See getApiKey() and resolveEnvPlaceholderValue().
|
|
28
34
|
* "providers": {
|
|
29
35
|
* "nvidia": { "enabled": true },
|
|
30
36
|
* "groq": { "enabled": true },
|
|
@@ -105,6 +111,8 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSy
|
|
|
105
111
|
import { homedir } from 'node:os'
|
|
106
112
|
import { join, resolve, dirname } from 'node:path'
|
|
107
113
|
import { syncShellEnv } from './shell-env.js'
|
|
114
|
+
import { DEFAULT_LOCALE, normalizeLocale } from './i18n/index.js'
|
|
115
|
+
import { resolveEnvPlaceholderValue } from './utils.js'
|
|
108
116
|
|
|
109
117
|
// 📖 Expand leading ~/ or bare ~ to homedir so --config-dir ~/.config/... works.
|
|
110
118
|
// 📖 resolve() alone does NOT expand tilde, it would treat ~ as a literal folder.
|
|
@@ -188,6 +196,7 @@ const ENV_VARS = {
|
|
|
188
196
|
requesty: 'REQUESTY_API_KEY',
|
|
189
197
|
orcarouter: 'ORCAROUTER_API_KEY',
|
|
190
198
|
'vercel-gateway': ['VERCEL_AI_GATEWAY_API_KEY', 'AI_GATEWAY_API_KEY'],
|
|
199
|
+
onomeo: 'ONOMEO_API_KEY',
|
|
191
200
|
}
|
|
192
201
|
|
|
193
202
|
// 📖 Smart Router defaults are intentionally conservative: balanced probing,
|
|
@@ -331,6 +340,7 @@ function normalizeSettingsSection(settings) {
|
|
|
331
340
|
delete safeSettings.footerHidden
|
|
332
341
|
return {
|
|
333
342
|
...safeSettings,
|
|
343
|
+
language: normalizeLocale(safeSettings.language),
|
|
334
344
|
hideUnconfiguredModels: typeof safeSettings.hideUnconfiguredModels === 'boolean' ? safeSettings.hideUnconfiguredModels : true,
|
|
335
345
|
favoritesPinnedAndSticky: typeof safeSettings.favoritesPinnedAndSticky === 'boolean' ? safeSettings.favoritesPinnedAndSticky : false,
|
|
336
346
|
runAiSpeedTestOnStartup: typeof safeSettings.runAiSpeedTestOnStartup === 'boolean' ? safeSettings.runAiSpeedTestOnStartup : false,
|
|
@@ -456,10 +466,22 @@ function normalizeRouterFailover(failover, routerFallback = {}) {
|
|
|
456
466
|
const rawStreamStall = numberOrNull(safeFailover.streamStallTimeoutMs) ?? numberOrNull(routerFallback.streamStallTimeoutMs)
|
|
457
467
|
const rawRequestTimeout = numberOrNull(safeFailover.requestTimeoutMs) ?? numberOrNull(routerFallback.requestTimeoutMs)
|
|
458
468
|
|
|
469
|
+
// 📖 router-v2 documented failover fields (lastResortModel,
|
|
470
|
+
// bodyReadTimeoutMs, totalBudgetMs, contentValidation — docs/router-v2.md)
|
|
471
|
+
// are read by the daemon at runtime but are not v1 knobs normalized here.
|
|
472
|
+
// Carry any unknown keys through so they survive load()/save() round-trips
|
|
473
|
+
// instead of being silently dropped from the on-disk config.
|
|
474
|
+
const v1FailoverKeys = new Set(['maxRetries', 'streamStallTimeoutMs', 'requestTimeoutMs'])
|
|
475
|
+
const extras = {}
|
|
476
|
+
for (const key of Object.keys(safeFailover)) {
|
|
477
|
+
if (!v1FailoverKeys.has(key)) extras[key] = safeFailover[key]
|
|
478
|
+
}
|
|
479
|
+
|
|
459
480
|
return {
|
|
460
481
|
maxRetries: normalizePositiveInteger(rawMaxRetries, DEFAULT_ROUTER_SETTINGS.failover.maxRetries, { min: 1, max: 20 }),
|
|
461
482
|
streamStallTimeoutMs: normalizePositiveInteger(rawStreamStall, DEFAULT_ROUTER_SETTINGS.failover.streamStallTimeoutMs, { min: 1000, max: 120000 }),
|
|
462
483
|
requestTimeoutMs: normalizePositiveInteger(rawRequestTimeout, DEFAULT_ROUTER_SETTINGS.failover.requestTimeoutMs, { min: 1000, max: 300000 }),
|
|
484
|
+
...extras,
|
|
463
485
|
}
|
|
464
486
|
}
|
|
465
487
|
|
|
@@ -1142,12 +1164,14 @@ export function getApiKey(config, providerKey) {
|
|
|
1142
1164
|
// 📖 but every consumer (ping Authorization header, tool env vars, daemon) needs
|
|
1143
1165
|
// 📖 a single string: an array would stringify to "key1,key2" and get a
|
|
1144
1166
|
// 📖 guaranteed 401. Return the first non-empty entry instead.
|
|
1167
|
+
// 📖 ${VAR} references (issue #196) are expanded here so the file on disk
|
|
1168
|
+
// 📖 can stay secret-free while every consumer gets the real value.
|
|
1145
1169
|
const key = config?.apiKeys?.[providerKey]
|
|
1146
1170
|
if (Array.isArray(key)) {
|
|
1147
1171
|
const firstUsable = key.find(k => typeof k === 'string' && k.length > 0)
|
|
1148
|
-
return firstUsable
|
|
1172
|
+
return firstUsable !== undefined ? resolveEnvPlaceholderValue(firstUsable) : null
|
|
1149
1173
|
}
|
|
1150
|
-
if (key) return key
|
|
1174
|
+
if (key) return resolveEnvPlaceholderValue(key)
|
|
1151
1175
|
|
|
1152
1176
|
return null
|
|
1153
1177
|
}
|
|
@@ -1295,6 +1319,7 @@ export function isProviderEnabled(config, providerKey) {
|
|
|
1295
1319
|
*/
|
|
1296
1320
|
export function _emptyProfileSettings() {
|
|
1297
1321
|
return {
|
|
1322
|
+
language: DEFAULT_LOCALE,
|
|
1298
1323
|
tierFilter: null, // 📖 null = show all tiers, or 'S'|'A'|'B'|'C'|'D'
|
|
1299
1324
|
sortColumn: 'avg', // 📖 default sort column
|
|
1300
1325
|
sortAsc: true, // 📖 true = ascending (fastest first for latency)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import en from './locales/en.js'
|
|
2
|
+
import zhCN from './locales/zh-CN.js'
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_LOCALE = 'en'
|
|
5
|
+
export const SUPPORTED_LOCALES = Object.freeze(['en', 'zh-CN'])
|
|
6
|
+
export const LOCALE_OPTIONS = Object.freeze([
|
|
7
|
+
Object.freeze({ value: 'en', labelKey: 'common.language.english' }),
|
|
8
|
+
Object.freeze({ value: 'zh-CN', labelKey: 'common.language.simplifiedChinese' }),
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
const dictionaries = Object.freeze({ en, 'zh-CN': zhCN })
|
|
12
|
+
let activeLocale = DEFAULT_LOCALE
|
|
13
|
+
|
|
14
|
+
export function normalizeLocale(locale) {
|
|
15
|
+
return SUPPORTED_LOCALES.includes(locale) ? locale : DEFAULT_LOCALE
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getLocale() {
|
|
19
|
+
return activeLocale
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function setLocale(locale) {
|
|
23
|
+
activeLocale = normalizeLocale(locale)
|
|
24
|
+
return activeLocale
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createTranslator(resources = dictionaries) {
|
|
28
|
+
return (locale, key, values = {}) => {
|
|
29
|
+
if (typeof key !== 'string') return ''
|
|
30
|
+
const active = normalizeLocale(locale)
|
|
31
|
+
const localized = resources[active]?.[key]
|
|
32
|
+
const fallback = resources[DEFAULT_LOCALE]?.[key]
|
|
33
|
+
const template = typeof localized === 'string' ? localized : typeof fallback === 'string' ? fallback : key
|
|
34
|
+
return template.replace(/\{([\w.]+)\}/g, (match, name) => {
|
|
35
|
+
const value = values && Object.prototype.hasOwnProperty.call(values, name) ? values[name] : undefined
|
|
36
|
+
return value === null || value === undefined ? match : String(value)
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const translate = createTranslator()
|
|
42
|
+
|
|
43
|
+
export function t(key, values = {}) {
|
|
44
|
+
return translate(activeLocale, key, values)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function hasTranslation(locale, key) {
|
|
48
|
+
return typeof dictionaries[normalizeLocale(locale)]?.[key] === 'string'
|
|
49
|
+
}
|