free-coding-models 0.2.0 → 0.2.2
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 +19 -2
- package/bin/free-coding-models.js +43 -14
- package/package.json +1 -1
- package/src/config.js +45 -4
- package/src/endpoint-installer.js +459 -0
- package/src/key-handler.js +344 -16
- package/src/log-reader.js +23 -2
- package/src/opencode.js +14 -2
- package/src/overlays.js +224 -8
- package/src/provider-metadata.js +3 -1
- package/src/proxy-server.js +52 -2
- package/src/render-helpers.js +14 -8
- package/src/render-table.js +18 -5
- package/src/token-stats.js +11 -1
- package/src/tool-launchers.js +50 -7
- package/src/utils.js +37 -4
package/src/tool-launchers.js
CHANGED
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
* fully official. The user still gets a reproducible env/config handoff.
|
|
17
17
|
*
|
|
18
18
|
* @functions
|
|
19
|
+
* → `resolveLauncherModelId` — choose the provider-specific id or proxy slug for a launch
|
|
19
20
|
* → `startExternalTool` — configure and launch the selected external tool mode
|
|
20
21
|
*
|
|
21
|
-
* @exports startExternalTool
|
|
22
|
+
* @exports resolveLauncherModelId, startExternalTool
|
|
22
23
|
*
|
|
23
24
|
* @see src/tool-metadata.js
|
|
24
25
|
* @see src/provider-metadata.js
|
|
@@ -34,7 +35,7 @@ import { sources } from '../sources.js'
|
|
|
34
35
|
import { getApiKey, getProxySettings } from './config.js'
|
|
35
36
|
import { ENV_VAR_NAMES, isWindows } from './provider-metadata.js'
|
|
36
37
|
import { getToolMeta } from './tool-metadata.js'
|
|
37
|
-
import { ensureProxyRunning } from './opencode.js'
|
|
38
|
+
import { ensureProxyRunning, resolveProxyModelId } from './opencode.js'
|
|
38
39
|
|
|
39
40
|
function ensureDir(filePath) {
|
|
40
41
|
const dir = dirname(filePath)
|
|
@@ -71,6 +72,31 @@ function getProviderBaseUrl(providerKey) {
|
|
|
71
72
|
.replace(/\/predictions$/i, '')
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
function applyOpenAiCompatEnv(env, apiKey, baseUrl, modelId) {
|
|
76
|
+
if (!apiKey || !baseUrl || !modelId) return env
|
|
77
|
+
env.OPENAI_API_KEY = apiKey
|
|
78
|
+
env.OPENAI_BASE_URL = baseUrl
|
|
79
|
+
env.OPENAI_API_BASE = baseUrl
|
|
80
|
+
env.OPENAI_MODEL = modelId
|
|
81
|
+
env.LLM_API_KEY = apiKey
|
|
82
|
+
env.LLM_BASE_URL = baseUrl
|
|
83
|
+
env.LLM_MODEL = `openai/${modelId}`
|
|
84
|
+
return env
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 📖 resolveLauncherModelId keeps proxy-backed launches on the universal
|
|
89
|
+
* 📖 `fcm-proxy` catalog slug instead of leaking a provider-specific upstream id.
|
|
90
|
+
*
|
|
91
|
+
* @param {{ label?: string, modelId?: string }} model
|
|
92
|
+
* @param {boolean} useProxy
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
export function resolveLauncherModelId(model, useProxy = false) {
|
|
96
|
+
if (useProxy) return resolveProxyModelId(model)
|
|
97
|
+
return model?.modelId ?? ''
|
|
98
|
+
}
|
|
99
|
+
|
|
74
100
|
function buildToolEnv(mode, model, config) {
|
|
75
101
|
const providerKey = model.providerKey
|
|
76
102
|
const providerUrl = sources[providerKey]?.url || ''
|
|
@@ -233,6 +259,7 @@ function printConfigResult(toolName, result) {
|
|
|
233
259
|
export async function startExternalTool(mode, model, config) {
|
|
234
260
|
const meta = getToolMeta(mode)
|
|
235
261
|
const { env, apiKey, baseUrl } = buildToolEnv(mode, model, config)
|
|
262
|
+
const proxySettings = getProxySettings(config)
|
|
236
263
|
|
|
237
264
|
if (!apiKey && mode !== 'amp') {
|
|
238
265
|
console.log(chalk.yellow(` ⚠ No API key configured for ${model.providerKey}.`))
|
|
@@ -252,27 +279,43 @@ export async function startExternalTool(mode, model, config) {
|
|
|
252
279
|
let crushApiKey = apiKey
|
|
253
280
|
let crushBaseUrl = baseUrl
|
|
254
281
|
let providerId = 'freeCodingModels'
|
|
255
|
-
|
|
282
|
+
let launchModelId = resolveLauncherModelId(model, false)
|
|
256
283
|
|
|
257
284
|
if (proxySettings.enabled) {
|
|
258
285
|
const started = await ensureProxyRunning(config)
|
|
259
286
|
crushApiKey = started.proxyToken
|
|
260
287
|
crushBaseUrl = `http://127.0.0.1:${started.port}/v1`
|
|
261
288
|
providerId = 'freeCodingModelsProxy'
|
|
289
|
+
launchModelId = resolveLauncherModelId(model, true)
|
|
262
290
|
console.log(chalk.dim(` 📖 Crush will use the local FCM proxy on :${started.port} for this launch.`))
|
|
263
291
|
} else {
|
|
264
292
|
console.log(chalk.dim(' 📖 Crush will use the provider directly for this launch.'))
|
|
265
293
|
}
|
|
266
294
|
|
|
267
|
-
|
|
295
|
+
const launchModel = { ...model, modelId: launchModelId }
|
|
296
|
+
applyOpenAiCompatEnv(env, crushApiKey, crushBaseUrl, launchModelId)
|
|
297
|
+
printConfigResult(meta.label, writeCrushConfig(launchModel, crushApiKey, crushBaseUrl, providerId))
|
|
268
298
|
return spawnCommand('crush', [], env)
|
|
269
299
|
}
|
|
270
300
|
|
|
271
301
|
if (mode === 'goose') {
|
|
272
|
-
|
|
302
|
+
let gooseBaseUrl = baseUrl
|
|
303
|
+
let gooseApiKey = apiKey
|
|
304
|
+
let gooseModelId = resolveLauncherModelId(model, false)
|
|
305
|
+
|
|
306
|
+
if (proxySettings.enabled) {
|
|
307
|
+
const started = await ensureProxyRunning(config)
|
|
308
|
+
gooseApiKey = started.proxyToken
|
|
309
|
+
gooseBaseUrl = `http://127.0.0.1:${started.port}/v1`
|
|
310
|
+
gooseModelId = resolveLauncherModelId(model, true)
|
|
311
|
+
applyOpenAiCompatEnv(env, gooseApiKey, gooseBaseUrl, gooseModelId)
|
|
312
|
+
console.log(chalk.dim(` 📖 Goose will use the local FCM proxy on :${started.port} for this launch.`))
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
env.OPENAI_HOST = gooseBaseUrl
|
|
273
316
|
env.OPENAI_BASE_PATH = 'v1/chat/completions'
|
|
274
|
-
env.OPENAI_MODEL =
|
|
275
|
-
console.log(chalk.dim(
|
|
317
|
+
env.OPENAI_MODEL = gooseModelId
|
|
318
|
+
console.log(chalk.dim(` 📖 Goose uses env-based OpenAI-compatible configuration for ${proxySettings.enabled ? 'the proxy' : 'this provider'} launch.`))
|
|
276
319
|
return spawnCommand('goose', [], env)
|
|
277
320
|
}
|
|
278
321
|
|
package/src/utils.js
CHANGED
|
@@ -218,9 +218,11 @@ export const getStabilityScore = (r) => {
|
|
|
218
218
|
// 📖 sortResults: Sort the results array by any column the user can click/press in the TUI.
|
|
219
219
|
// 📖 Returns a NEW array — never mutates the original (important for React-style re-renders).
|
|
220
220
|
//
|
|
221
|
-
// 📖 Supported columns
|
|
221
|
+
// 📖 Supported columns in the sorter.
|
|
222
|
+
// 📖 Most map directly to visible TUI sort hotkeys; `tier` remains available internally
|
|
223
|
+
// 📖 even though the live TUI now reserves `Y` for the install-endpoints flow.
|
|
222
224
|
// - 'rank' (R key) — original index from sources.js
|
|
223
|
-
// - 'tier' (
|
|
225
|
+
// - 'tier' (internal) — tier hierarchy (S+ first, C last)
|
|
224
226
|
// - 'origin' (O key) — provider name (all NIM for now, future-proofed)
|
|
225
227
|
// - 'model' (M key) — alphabetical by display label
|
|
226
228
|
// - 'ping' (L key) — last ping latency (only successful ones count)
|
|
@@ -656,15 +658,17 @@ export function getTopRecommendations(results, taskType, priority, contextBudget
|
|
|
656
658
|
* 2. proxyStartupStatus.phase === 'running' → state:'running' with port/accountCount
|
|
657
659
|
* 3. proxyStartupStatus.phase === 'failed' → state:'failed' with truncated reason
|
|
658
660
|
* 4. isProxyActive (legacy activeProxy flag) → state:'running' (no port detail)
|
|
659
|
-
* 5.
|
|
661
|
+
* 5. isProxyEnabled → state:'configured'
|
|
662
|
+
* 6. otherwise → state:'stopped'
|
|
660
663
|
*
|
|
661
664
|
* 📖 Reason is clamped to 80 characters to keep footer readable (no stack traces).
|
|
662
665
|
*
|
|
663
666
|
* @param {object|null} proxyStartupStatus — state.proxyStartupStatus value
|
|
664
667
|
* @param {boolean} isProxyActive — truthy when the module-level activeProxy is non-null
|
|
668
|
+
* @param {boolean} [isProxyEnabled=false] — truthy when proxy mode is enabled in settings
|
|
665
669
|
* @returns {{ state: string, port?: number, accountCount?: number, reason?: string }}
|
|
666
670
|
*/
|
|
667
|
-
export function getProxyStatusInfo(proxyStartupStatus, isProxyActive) {
|
|
671
|
+
export function getProxyStatusInfo(proxyStartupStatus, isProxyActive, isProxyEnabled = false) {
|
|
668
672
|
const MAX_REASON = 80
|
|
669
673
|
|
|
670
674
|
if (proxyStartupStatus) {
|
|
@@ -693,5 +697,34 @@ export function getProxyStatusInfo(proxyStartupStatus, isProxyActive) {
|
|
|
693
697
|
return { state: 'running' }
|
|
694
698
|
}
|
|
695
699
|
|
|
700
|
+
if (isProxyEnabled) {
|
|
701
|
+
return { state: 'configured' }
|
|
702
|
+
}
|
|
703
|
+
|
|
696
704
|
return { state: 'stopped' }
|
|
697
705
|
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* 📖 getVersionStatusInfo turns the settings update-check state into a compact,
|
|
709
|
+
* 📖 render-friendly footer descriptor for the main table.
|
|
710
|
+
*
|
|
711
|
+
* 📖 Only an explicit `available` state should mark the local install as outdated.
|
|
712
|
+
* 📖 This avoids showing a scary warning before the user has actually checked npm.
|
|
713
|
+
*
|
|
714
|
+
* @param {'idle'|'checking'|'available'|'up-to-date'|'error'|'installing'} updateState
|
|
715
|
+
* @param {string|null} latestVersion
|
|
716
|
+
* @returns {{ isOutdated: boolean, latestVersion: string|null }}
|
|
717
|
+
*/
|
|
718
|
+
export function getVersionStatusInfo(updateState, latestVersion) {
|
|
719
|
+
if (updateState === 'available' && typeof latestVersion === 'string' && latestVersion.trim()) {
|
|
720
|
+
return {
|
|
721
|
+
isOutdated: true,
|
|
722
|
+
latestVersion: latestVersion.trim(),
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return {
|
|
727
|
+
isOutdated: false,
|
|
728
|
+
latestVersion: null,
|
|
729
|
+
}
|
|
730
|
+
}
|