free-coding-models 0.5.91 โ†’ 0.5.93

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.
@@ -21,12 +21,16 @@
21
21
  * ๐Ÿ“– Continue: writes ~/.continue/config.yaml with provider: openai + apiBase
22
22
  * ๐Ÿ“– Cline: writes ~/.cline/globalState.json with openai-compatible provider config
23
23
  * ๐Ÿ“– ForgeCode: writes [[providers]] TOML block into ~/.forge/.forge.toml + sets [session] defaults
24
+ * ๐Ÿ“– FCM Router: built-in target with nothing to install or spawn; starts the daemon,
25
+ * pushes the selected model + favorites as the `fast-coding` routing set and prints
26
+ * the /v1 endpoint + model + key trio (issue #184)
24
27
  *
25
28
  * @functions
26
29
  * โ†’ `resolveLauncherModelId` โ€” choose the provider-specific id for a launch
27
30
  * โ†’ `writeGooseConfig` โ€” install provider + set GOOSE_PROVIDER/GOOSE_MODEL in config.yaml
28
31
  * โ†’ `writeCrushConfig` โ€” write provider + models.large/small to crush.json
29
32
  * โ†’ `prepareExternalToolLaunch` โ€” persist selected-model defaults and compute the launch command
33
+ * โ†’ `startFcmRouterLaunch` โ€” daemon lifecycle + routing-set push + connection instructions for fcm_router mode
30
34
  * โ†’ `startExternalTool` โ€” configure and launch the selected external tool mode
31
35
  *
32
36
  * @exports resolveLauncherModelId, buildToolEnv, prepareExternalToolLaunch, startExternalTool
@@ -1100,6 +1104,21 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
1100
1104
  }
1101
1105
  }
1102
1106
 
1107
+ if (mode === 'fcm_router') {
1108
+ // ๐Ÿ“– FCM Router is built into FCM: nothing to install and no external binary to
1109
+ // ๐Ÿ“– spawn. Enter in this mode starts the daemon and routes the selected model
1110
+ // ๐Ÿ“– through it (see startFcmRouterLaunch). Issue #184.
1111
+ return {
1112
+ command: null,
1113
+ args: [],
1114
+ env,
1115
+ apiKey,
1116
+ baseUrl,
1117
+ meta,
1118
+ configArtifacts: [],
1119
+ }
1120
+ }
1121
+
1103
1122
  return {
1104
1123
  blocked: true,
1105
1124
  exitCode: 1,
@@ -1109,6 +1128,92 @@ export function prepareExternalToolLaunch(mode, model, config, options = {}) {
1109
1128
  }
1110
1129
  }
1111
1130
 
1131
+ // ๐Ÿ“– startFcmRouterLaunch: the "launch" flow for the FCM Router target in the Z cycle
1132
+ // ๐Ÿ“– (issue #184). The router ships inside FCM, so there is nothing to install and no
1133
+ // ๐Ÿ“– CLI to spawn. Instead: make sure the daemon is running, push the selected model
1134
+ // ๐Ÿ“– as primary with the user's favorites as failover into the active `fast-coding`
1135
+ // ๐Ÿ“– set, then print the endpoint + model + key trio any OpenAI-compatible tool needs.
1136
+ async function startFcmRouterLaunch(model, config) {
1137
+ const meta = getToolMeta('fcm_router')
1138
+ console.log(chalk.cyan(` โ–ถ Configuring ${meta.label} with ${chalk.bold(model.label)}...`))
1139
+
1140
+ let status = null
1141
+ try {
1142
+ // ๐Ÿ“– Lazy import: router-daemon.js is large and only needed in this mode.
1143
+ const { startRouterDaemonBackground } = await import('./router-daemon.js')
1144
+ status = await startRouterDaemonBackground()
1145
+ } catch (error) {
1146
+ status = { ok: false, error: error instanceof Error ? error.message : String(error) }
1147
+ }
1148
+
1149
+ if (!status?.ok) {
1150
+ console.log(chalk.red(` X Could not start the FCM Router daemon${status?.error ? `: ${status.error}` : '.'}`))
1151
+ console.log(chalk.dim(' Run `free-coding-models --daemon` in another terminal to see the startup error.'))
1152
+ console.log()
1153
+ return 1
1154
+ }
1155
+
1156
+ const routerBaseUrl = `http://localhost:${status.port}`
1157
+
1158
+ // ๐Ÿ“– Selected model first, favorites as failover. Mirrors syncFavoritesToRouter
1159
+ // ๐Ÿ“– in the TUI but without the router.enabled gate: choosing the FCM Router
1160
+ // ๐Ÿ“– target in the Z cycle IS the intent, no extra setting required.
1161
+ const favorites = Array.isArray(config?.favorites) ? config.favorites : []
1162
+ const selKey = `${model.providerKey}/${model.modelId}`
1163
+ const chain = [selKey, ...favorites.filter((f) => f !== selKey)]
1164
+ const routerModels = chain.map((key, index) => {
1165
+ const slashIdx = key.indexOf('/')
1166
+ return {
1167
+ provider: slashIdx >= 0 ? key.slice(0, slashIdx) : '?',
1168
+ model: slashIdx >= 0 ? key.slice(slashIdx + 1) : key,
1169
+ priority: index + 1,
1170
+ }
1171
+ })
1172
+
1173
+ // ๐Ÿ“– POST creates-or-replaces the set (PUT alone 404s when the set is missing),
1174
+ // ๐Ÿ“– then activate makes it the routing target. Best-effort: a failure here
1175
+ // ๐Ÿ“– leaves the daemon on its default set, which still works, so we warn
1176
+ // ๐Ÿ“– instead of failing the whole launch.
1177
+ let setSynced = false
1178
+ try {
1179
+ const payload = JSON.stringify({ name: 'fast-coding', models: routerModels, created: new Date().toISOString() })
1180
+ const createRes = await fetch(`${routerBaseUrl}/sets`, {
1181
+ method: 'POST',
1182
+ headers: { 'Content-Type': 'application/json' },
1183
+ body: payload,
1184
+ signal: AbortSignal.timeout(5000),
1185
+ })
1186
+ if (createRes.ok || (await fetch(`${routerBaseUrl}/sets/fast-coding`, {
1187
+ method: 'PUT',
1188
+ headers: { 'Content-Type': 'application/json' },
1189
+ body: payload,
1190
+ signal: AbortSignal.timeout(5000),
1191
+ })).ok) {
1192
+ await fetch(`${routerBaseUrl}/sets/fast-coding/activate`, {
1193
+ method: 'POST',
1194
+ signal: AbortSignal.timeout(5000),
1195
+ }).catch(() => {})
1196
+ setSynced = true
1197
+ }
1198
+ } catch {}
1199
+
1200
+ console.log(chalk.green(` โœ“ FCM Router ${status.alreadyRunning ? 'is running' : 'started'} at ${chalk.bold(`${routerBaseUrl}/v1`)}`))
1201
+ console.log()
1202
+ console.log(chalk.bold(' Point any OpenAI-compatible coding tool at the router:'))
1203
+ console.log(` ${chalk.dim('Base URL:')} ${routerBaseUrl}/v1`)
1204
+ console.log(` ${chalk.dim('API key:')} fcm-local${process.env.FCM_ROUTER_TOKEN ? ' (or your FCM_ROUTER_TOKEN value)' : ''}`)
1205
+ console.log(` ${chalk.dim('Model:')} fcm`)
1206
+ console.log()
1207
+ if (setSynced) {
1208
+ console.log(chalk.dim(` ๐Ÿ“– Routing chain: ${model.label} first${favorites.length > 0 ? `, ${favorites.length} favorite${favorites.length === 1 ? '' : 's'} as failover` : ''}.`))
1209
+ } else {
1210
+ console.log(chalk.yellow(' โš  Could not update the routing set; the daemon keeps its default set, which still works.'))
1211
+ }
1212
+ console.log(chalk.dim(' ๐Ÿ“– Dashboard: `free-coding-models web` ยท Docs: docs/router.md'))
1213
+ console.log()
1214
+ return 0
1215
+ }
1216
+
1112
1217
  export async function startExternalTool(mode, model, config) {
1113
1218
  const launchPlan = prepareExternalToolLaunch(mode, model, config)
1114
1219
  const { meta } = launchPlan
@@ -1119,6 +1224,10 @@ export async function startExternalTool(mode, model, config) {
1119
1224
  return launchPlan.exitCode || 1
1120
1225
  }
1121
1226
 
1227
+ // ๐Ÿ“– fcm_router has no binary to spawn: the helper starts the daemon, pushes
1228
+ // ๐Ÿ“– the routing set and prints connection instructions instead. Issue #184.
1229
+ if (mode === 'fcm_router') return startFcmRouterLaunch(model, config)
1230
+
1122
1231
  console.log(chalk.cyan(` โ–ถ Launching ${meta.label} with ${chalk.bold(model.label)}...`))
1123
1232
  printConfigArtifacts(meta.label, launchPlan.configArtifacts)
1124
1233
 
@@ -242,12 +242,15 @@ export function createKeyHandler(ctx) {
242
242
  state.toolInstallPromptErrorMsg = null
243
243
  }
244
244
 
245
- function shouldCheckMissingTool(mode) {
245
+ function shouldCheckMissingTool(mode) {
246
246
  // ๐Ÿ“– opencode-desktop doesn't have a binary check (it uses 'open -a').
247
247
  // ๐Ÿ“– opencode-web, opencode, and kilo manage their own ENOENT errors in spawn handlers.
248
248
  // ๐Ÿ“– xcode uses 'open -a Xcode' which doesn't need a binary path resolution.
249
249
  // ๐Ÿ“– zcode is a desktop app with no CLI binary โ€” the launch handler prints setup instructions.
250
- return !['opencode-desktop', 'opencode-web', 'opencode', 'kilo', 'xcode', 'zcode'].includes(mode)
250
+ // ๐Ÿ“– fcm_router ships inside FCM (nothing to install) โ€” the launch handler starts the
251
+ // ๐Ÿ“– daemon itself. Without this exclusion Enter showed a bogus "Missing Tool" prompt
252
+ // ๐Ÿ“– reading "Unknown tool mode: fcm_router" (issue #184).
253
+ return !['opencode-desktop', 'opencode-web', 'opencode', 'kilo', 'xcode', 'zcode', 'fcm_router'].includes(mode)
251
254
  }
252
255
 
253
256
  function getModelTelemetryFamily(providerKey) {