free-coding-models 0.5.37 โ 0.5.38
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/v0.5.38.md +15 -0
- package/package.json +1 -1
- package/src/core/endpoint-installer.js +182 -1
- package/src/core/installed-models-manager.js +192 -36
- package/src/core/tool-launchers.js +148 -25
- package/src/core/utils.js +4 -2
- package/src/tui/app.js +5 -4
- package/src/tui/key-handler.js +7 -6
- package/web/dist/assets/{index-DcYKZCte.js โ index-DR1HvFdW.js} +2 -2
- package/web/dist/index.html +1 -1
- package/web/src/components/install/InstallEndpointsView.jsx +1 -0
- package/web/src/utils/m3.js +1 -0
|
@@ -90,6 +90,8 @@ function getDefaultToolPaths(homeDir = homedir()) {
|
|
|
90
90
|
continueConfigPath: join(homeDir, '.continue', 'config.yaml'),
|
|
91
91
|
clineConfigPath: join(homeDir, '.cline', 'globalState.json'),
|
|
92
92
|
forgeCodeConfigPath: join(homeDir, '.forge', '.forge.toml'),
|
|
93
|
+
zcodeConfigPath: join(homeDir, '.zcode', 'v2', 'config.json'),
|
|
94
|
+
zcodeModelCachePath: join(homeDir, '.zcode', 'v2', 'bots-model-cache.v2.json'),
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
|
|
@@ -560,6 +562,137 @@ function writeForgeCodeConfig(model, apiKey, baseUrl, providerKey, paths = getDe
|
|
|
560
562
|
return { filePath, backupPath }
|
|
561
563
|
}
|
|
562
564
|
|
|
565
|
+
// ๐ writeZCodeConfig: Writes provider + model into ZCode's config.json and
|
|
566
|
+
// ๐ bots-model-cache.v2.json so the selected model appears in ZCode's model picker.
|
|
567
|
+
// ๐ Uses a deterministic provider ID (fcm-{providerKey}) and merges models so
|
|
568
|
+
// ๐ re-running on the same provider/model is idempotent โ no duplicates.
|
|
569
|
+
export function writeZCodeConfig(model, config, paths = getDefaultToolPaths()) {
|
|
570
|
+
const configPath = paths.zcodeConfigPath
|
|
571
|
+
const cachePath = paths.zcodeModelCachePath
|
|
572
|
+
|
|
573
|
+
const providerKey = model.providerKey || 'nvidia'
|
|
574
|
+
const apiKey = getApiKey(config, providerKey)
|
|
575
|
+
const baseUrl = sources[providerKey]?.url
|
|
576
|
+
? sources[providerKey].url.replace(/\/chat\/completions$/i, '').replace(/\/responses$/i, '').replace(/\/predictions$/i, '')
|
|
577
|
+
: null
|
|
578
|
+
const providerId = `fcm-${providerKey}`
|
|
579
|
+
const providerLabel = `FCM ${sources[providerKey]?.name || providerKey}`
|
|
580
|
+
|
|
581
|
+
if (!baseUrl) {
|
|
582
|
+
throw new Error(`Cannot resolve base URL for ${sources[providerKey]?.name || providerKey}`)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const ctx = parseCtxToTokens(model.ctx) || 200000
|
|
586
|
+
const maxOutput = Math.max(4096, Math.min(ctx, 32768))
|
|
587
|
+
|
|
588
|
+
// โโ 1. Write to config.json โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
589
|
+
const cfg = readJson(configPath, { $schema: 'https://opencode.ai/config.json' })
|
|
590
|
+
if (!cfg.provider || typeof cfg.provider !== 'object') cfg.provider = {}
|
|
591
|
+
|
|
592
|
+
const existingProvider = cfg.provider[providerId]
|
|
593
|
+
let configModified = false
|
|
594
|
+
|
|
595
|
+
// ๐ If the provider already exists with this model โ skip (idempotent)
|
|
596
|
+
if (existingProvider && existingProvider.models?.[model.modelId]) {
|
|
597
|
+
// Model already configured โ skip config write, but still check cache
|
|
598
|
+
} else if (existingProvider) {
|
|
599
|
+
// ๐ Provider exists but model is new โ add model to existing provider
|
|
600
|
+
existingProvider.models[model.modelId] = {
|
|
601
|
+
limit: { context: ctx },
|
|
602
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
603
|
+
}
|
|
604
|
+
if (ctx > 8192) {
|
|
605
|
+
existingProvider.models[model.modelId].limit.output = maxOutput
|
|
606
|
+
}
|
|
607
|
+
configModified = true
|
|
608
|
+
} else {
|
|
609
|
+
// ๐ New provider โ create it
|
|
610
|
+
cfg.provider[providerId] = {
|
|
611
|
+
name: providerLabel,
|
|
612
|
+
kind: 'openai-compatible',
|
|
613
|
+
options: {
|
|
614
|
+
apiKey: apiKey || '',
|
|
615
|
+
baseURL: baseUrl.replace(/\/v1\/chat\/completions$/, '').replace(/\/v1$/, '') + '/v1',
|
|
616
|
+
apiKeyRequired: true,
|
|
617
|
+
},
|
|
618
|
+
enabled: true,
|
|
619
|
+
source: 'custom',
|
|
620
|
+
models: {
|
|
621
|
+
[model.modelId]: {
|
|
622
|
+
limit: { context: ctx },
|
|
623
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
}
|
|
627
|
+
if (ctx > 8192) {
|
|
628
|
+
cfg.provider[providerId].models[model.modelId].limit.output = maxOutput
|
|
629
|
+
}
|
|
630
|
+
configModified = true
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const configBackupPath = configModified ? writeJson(configPath, cfg) : null
|
|
634
|
+
|
|
635
|
+
// โโ 2. Write to bots-model-cache.v2.json โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
636
|
+
const cache = readJson(cachePath, { version: 2, updatedAt: Date.now(), providers: [] })
|
|
637
|
+
if (!Array.isArray(cache.providers)) cache.providers = []
|
|
638
|
+
|
|
639
|
+
const existingCacheIdx = cache.providers.findIndex((p) => p?.id === providerId)
|
|
640
|
+
const modelAlreadyCached = existingCacheIdx >= 0
|
|
641
|
+
&& cache.providers[existingCacheIdx].models?.some((m) => m?.id === model.modelId)
|
|
642
|
+
|
|
643
|
+
let cacheModified = false
|
|
644
|
+
|
|
645
|
+
if (!modelAlreadyCached) {
|
|
646
|
+
const modelEntry = {
|
|
647
|
+
id: model.modelId,
|
|
648
|
+
name: model.label || model.modelId,
|
|
649
|
+
kinds: ['openai-compatible'],
|
|
650
|
+
defaultKind: 'openai-compatible',
|
|
651
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
652
|
+
contextWindow: ctx,
|
|
653
|
+
...(ctx > 8192 ? { maxOutputTokens: maxOutput } : {}),
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (existingCacheIdx >= 0) {
|
|
657
|
+
// ๐ Provider exists in cache โ add model to existing entry
|
|
658
|
+
cache.providers[existingCacheIdx].models.push(modelEntry)
|
|
659
|
+
cache.providers[existingCacheIdx].updatedAt = Date.now()
|
|
660
|
+
} else {
|
|
661
|
+
// ๐ New provider in cache
|
|
662
|
+
cache.providers.push({
|
|
663
|
+
id: providerId,
|
|
664
|
+
name: providerLabel,
|
|
665
|
+
enabled: true,
|
|
666
|
+
endpoints: {
|
|
667
|
+
baseURL: baseUrl.replace(/\/v1\/chat\/completions$/, '').replace(/\/v1$/, '') + '/v1',
|
|
668
|
+
paths: { 'openai-compatible': '/chat/completions' },
|
|
669
|
+
},
|
|
670
|
+
apiFormat: 'openai-chat-completions',
|
|
671
|
+
source: 'custom',
|
|
672
|
+
apiKeyRequired: true,
|
|
673
|
+
apiKey: apiKey ? '__zcode_cached_api_key_present__' : '',
|
|
674
|
+
defaultKind: 'openai-compatible',
|
|
675
|
+
models: [modelEntry],
|
|
676
|
+
createdAt: Date.now(),
|
|
677
|
+
updatedAt: Date.now(),
|
|
678
|
+
})
|
|
679
|
+
}
|
|
680
|
+
cache.updatedAt = Date.now()
|
|
681
|
+
cacheModified = true
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const cacheBackupPath = cacheModified ? writeJson(cachePath, cache) : null
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
filePath: configPath,
|
|
688
|
+
backupPath: configBackupPath,
|
|
689
|
+
providerId,
|
|
690
|
+
cachePath,
|
|
691
|
+
cacheBackupPath,
|
|
692
|
+
skipped: !configModified && !cacheModified,
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
563
696
|
// ๐ restartHermesGateway โ restart the Hermes messaging gateway after config changes.
|
|
564
697
|
// ๐ Non-blocking: if gateway is not running, this is a no-op.
|
|
565
698
|
function restartHermesGateway() {
|
|
@@ -829,18 +962,21 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
|
|
|
829
962
|
}
|
|
830
963
|
|
|
831
964
|
if (mode === 'zcode') {
|
|
832
|
-
// ๐
|
|
833
|
-
|
|
834
|
-
// ๐ startExternalTool hook below prints manual setup steps and skips spawning.
|
|
965
|
+
// ๐ Write provider + model into ZCode config so it appears in ZCode's model picker.
|
|
966
|
+
const result = writeZCodeConfig(model, config, paths)
|
|
835
967
|
const isMac = process.platform === 'darwin'
|
|
968
|
+
|
|
836
969
|
return {
|
|
837
|
-
command: isMac ? 'open' : '
|
|
838
|
-
args: isMac ? ['-a', 'ZCode'] : [],
|
|
970
|
+
command: isMac ? 'open' : 'node',
|
|
971
|
+
args: isMac ? ['-a', 'ZCode'] : ['-e', 'process.exit(0)'],
|
|
839
972
|
env,
|
|
840
973
|
apiKey,
|
|
841
974
|
baseUrl,
|
|
842
975
|
meta,
|
|
843
|
-
configArtifacts: [
|
|
976
|
+
configArtifacts: [
|
|
977
|
+
{ path: result.filePath, backupPath: result.backupPath, label: 'config' },
|
|
978
|
+
...(result.cachePath ? [{ path: result.cachePath, backupPath: result.cacheBackupPath, label: 'model cache' }] : []),
|
|
979
|
+
],
|
|
844
980
|
}
|
|
845
981
|
}
|
|
846
982
|
|
|
@@ -982,22 +1118,10 @@ export async function startExternalTool(mode, model, config) {
|
|
|
982
1118
|
console.log(chalk.dim(` ๐ Attempting to launch Xcode...`))
|
|
983
1119
|
}
|
|
984
1120
|
if (mode === 'zcode') {
|
|
985
|
-
// ๐ ZCode
|
|
986
|
-
// ๐
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
console.log(chalk.bold.cyan('\n ๐ง ZCode Setup Instructions:'))
|
|
990
|
-
console.log(chalk.white(' 1. Open ZCode and click the model selector in the chat input.'))
|
|
991
|
-
console.log(chalk.white(' 2. At the bottom of the list, click ') + chalk.bold('Manage Models') + chalk.white(' (็ฎก็ๆจกๅ)'))
|
|
992
|
-
console.log(chalk.white(' 3. Click ') + chalk.bold('Add Provider') + chalk.white(' (ๆทปๅ ไพๅบๅ) in the left sidebar.'))
|
|
993
|
-
console.log(chalk.white(' 4. Fill in the following details:'))
|
|
994
|
-
console.log(chalk.dim(' Name: ') + chalk.green(`FCM Router`))
|
|
995
|
-
console.log(chalk.dim(' Base URL: ') + chalk.green(routerBase))
|
|
996
|
-
console.log(chalk.dim(' API Key: ') + chalk.green('fcm-local'))
|
|
997
|
-
console.log(chalk.dim(' Protocol: ') + chalk.green('OpenAI-compatible'))
|
|
998
|
-
console.log(chalk.white(' 5. Save, then pick ') + chalk.bold(`fcm`) + chalk.white(' from the model list. FCM picks the best live model.'))
|
|
999
|
-
console.log(chalk.dim(` ๐ If you prefer a direct provider, use the URL/key for ${chalk.bold(sources[model.providerKey]?.name || model.providerKey)} shown above.\n`))
|
|
1000
|
-
console.log(chalk.dim(` ๐ Attempting to launch ZCode...`))
|
|
1121
|
+
// ๐ ZCode is a desktop app โ config was already written by prepareExternalToolLaunch.
|
|
1122
|
+
// ๐ No process to spawn; the TUI stays open.
|
|
1123
|
+
console.log(chalk.dim(` ๐ ZCode config updated with model: ${model.modelId}. Open ZCode and select the model from the picker.`))
|
|
1124
|
+
return 0
|
|
1001
1125
|
}
|
|
1002
1126
|
if (mode === 'crush') console.log(chalk.dim(' ๐ Crush will use the provider directly for this launch.'))
|
|
1003
1127
|
|
|
@@ -1012,11 +1136,10 @@ export async function startExternalTool(mode, model, config) {
|
|
|
1012
1136
|
jcode: ' ๐ Launching jcode...',
|
|
1013
1137
|
copilot: ` ๐ Copilot CLI configured with model: ${model.modelId}`,
|
|
1014
1138
|
forgecode: ` ๐ ForgeCode configured with model: ${model.modelId}`,
|
|
1015
|
-
zcode: ` ๐ ZCode is a desktop app โ setup instructions printed below.`,
|
|
1016
1139
|
}
|
|
1017
1140
|
if (infoMessages[mode]) console.log(chalk.dim(infoMessages[mode]))
|
|
1018
1141
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1142
|
+
// ๐ xcode uses raw command ("open"), everything else resolves via tool-bootstrap
|
|
1143
|
+
const command = (mode === 'xcode') ? launchPlan.command : resolveLaunchCommand(mode, launchPlan.command)
|
|
1021
1144
|
return spawnCommand(command, launchPlan.args, launchPlan.env)
|
|
1022
1145
|
}
|
package/src/core/utils.js
CHANGED
|
@@ -447,7 +447,7 @@ export function findBestModel(results) {
|
|
|
447
447
|
// - Boolean flags: --best, --fiable, --opencode, --opencode-desktop, --opencode-web, --openclaw,
|
|
448
448
|
// --aider, --crush, --goose, --qwen, --kilo,
|
|
449
449
|
// --openhands, --amp, --pi, --hermes, --continue, --cline,
|
|
450
|
-
// --xcode, --jcode, --copilot, --forgecode,
|
|
450
|
+
// --xcode, --jcode, --copilot, --forgecode, --zcode,
|
|
451
451
|
// --daemon, --daemon-bg, --daemon-stop,
|
|
452
452
|
// --daemon-status, --no-telemetry, --json, --help/-h (case-insensitive)
|
|
453
453
|
// --playground / playground subcommand (open the in-TUI chat playground)
|
|
@@ -456,7 +456,7 @@ export function findBestModel(results) {
|
|
|
456
456
|
// Returns:
|
|
457
457
|
// { apiKey, bestMode, fiableMode, openCodeMode, openCodeDesktopMode, openCodeWebMode, openClawMode,
|
|
458
458
|
// aiderMode, crushMode, gooseMode, qwenMode, openHandsMode, ampMode,
|
|
459
|
-
// piMode, jcodeMode, copilotMode, forgecodeMode, noTelemetry, jsonMode, helpMode, tierFilter }
|
|
459
|
+
// piMode, jcodeMode, copilotMode, forgecodeMode, zcodeMode, noTelemetry, jsonMode, helpMode, tierFilter }
|
|
460
460
|
//
|
|
461
461
|
// ๐ Note: apiKey may be null here โ the main CLI falls back to env vars and saved config.
|
|
462
462
|
export function parseArgs(argv) {
|
|
@@ -534,6 +534,7 @@ export function parseArgs(argv) {
|
|
|
534
534
|
const jcodeMode = flags.includes('--jcode')
|
|
535
535
|
const copilotMode = flags.includes('--copilot')
|
|
536
536
|
const forgecodeMode = flags.includes('--forgecode')
|
|
537
|
+
const zcodeMode = flags.includes('--zcode')
|
|
537
538
|
const noTelemetry = flags.includes('--no-telemetry')
|
|
538
539
|
const devMode = flags.includes('--dev')
|
|
539
540
|
const jsonMode = flags.includes('--json')
|
|
@@ -597,6 +598,7 @@ export function parseArgs(argv) {
|
|
|
597
598
|
jcodeMode,
|
|
598
599
|
copilotMode,
|
|
599
600
|
forgecodeMode,
|
|
601
|
+
zcodeMode,
|
|
600
602
|
noTelemetry,
|
|
601
603
|
jsonMode,
|
|
602
604
|
helpMode,
|
package/src/tui/app.js
CHANGED
|
@@ -250,10 +250,11 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
250
250
|
cline: cliArgs.clineMode,
|
|
251
251
|
xcode: cliArgs.xcodeMode,
|
|
252
252
|
pi: cliArgs.piMode,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
253
|
+
caveman: cliArgs.cavemanMode,
|
|
254
|
+
copilot: cliArgs.copilotMode,
|
|
255
|
+
forgecode: cliArgs.forgecodeMode,
|
|
256
|
+
zcode: cliArgs.zcodeMode,
|
|
257
|
+
}
|
|
257
258
|
return flagByMode[toolMode] === true
|
|
258
259
|
})
|
|
259
260
|
if (requestedMode) mode = requestedMode
|
package/src/tui/key-handler.js
CHANGED
|
@@ -181,12 +181,13 @@ export function createKeyHandler(ctx) {
|
|
|
181
181
|
state.toolInstallPromptErrorMsg = null
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
function shouldCheckMissingTool(mode) {
|
|
185
|
+
// ๐ opencode-desktop doesn't have a binary check (it uses 'open -a').
|
|
186
|
+
// ๐ opencode-web, opencode, and kilo manage their own ENOENT errors in spawn handlers.
|
|
187
|
+
// ๐ xcode uses 'open -a Xcode' which doesn't need a binary path resolution.
|
|
188
|
+
// ๐ zcode is a desktop app with no CLI binary โ the launch handler prints setup instructions.
|
|
189
|
+
return !['opencode-desktop', 'opencode-web', 'opencode', 'kilo', 'xcode', 'zcode'].includes(mode)
|
|
190
|
+
}
|
|
190
191
|
|
|
191
192
|
function getModelTelemetryFamily(providerKey) {
|
|
192
193
|
if (providerKey === 'opencode-zen') return providerKey
|