free-coding-models 0.2.17 → 0.3.1

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/src/opencode.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * - Launch OpenCode CLI or Desktop
9
9
  * - Manage ZAI proxy bridge for non-standard API paths
10
10
  * - Start/stop the multi-account proxy server (fcm-proxy)
11
- * - Auto-start proxy when OpenCode config is already synced
11
+ * - Auto-start proxy when the current tool is configured for proxy auto-sync
12
12
  *
13
13
  * 🎯 Key features:
14
14
  * - Provider-aware config setup for OpenCode (NIM, Groq, Cerebras, etc.)
@@ -21,7 +21,7 @@
21
21
  * - `startOpenCode` — Launch OpenCode CLI with selected model
22
22
  * - `startOpenCodeDesktop` — Set model and open Desktop app
23
23
  * - `startProxyAndLaunch` — Start fcm-proxy then launch OpenCode
24
- * - `autoStartProxyIfSynced` — Auto-start proxy if opencode.json has fcm-proxy
24
+ * - `autoStartProxyIfSynced` — Auto-start proxy and sync the current tool when enabled
25
25
  * - `ensureProxyRunning` — Ensure proxy is running (start or reuse)
26
26
  * - `isProxyEnabledForConfig` — Check whether proxy mode is opted in
27
27
  *
@@ -39,12 +39,15 @@ import { join } from 'path'
39
39
  import { copyFileSync, existsSync } from 'fs'
40
40
  import { sources } from '../sources.js'
41
41
  import { PROVIDER_COLOR } from './render-table.js'
42
- import { resolveCloudflareUrl } from './ping.js'
43
42
  import { ProxyServer } from './proxy-server.js'
44
- import { loadOpenCodeConfig, saveOpenCodeConfig, syncToOpenCode } from './opencode-sync.js'
45
- import { getApiKey, getProxySettings, resolveApiKeys } from './config.js'
43
+ import { loadOpenCodeConfig, saveOpenCodeConfig } from './opencode-sync.js'
44
+ import { getApiKey, getProxySettings } from './config.js'
46
45
  import { ENV_VAR_NAMES, OPENCODE_MODEL_MAP, isWindows, isMac, isLinux } from './provider-metadata.js'
47
46
  import { setActiveProxy } from './render-table.js'
47
+ import { buildProxyTopologyFromConfig as _buildTopology } from './proxy-topology.js'
48
+ import { isDaemonRunning, getDaemonInfo } from './daemon-manager.js'
49
+ import { syncProxyToTool, resolveProxySyncToolMode } from './proxy-sync.js'
50
+ import { getToolMeta } from './tool-metadata.js'
48
51
 
49
52
  // 📖 OpenCode config location: ~/.config/opencode/opencode.json on ALL platforms.
50
53
  // 📖 OpenCode uses xdg-basedir which resolves to %USERPROFILE%\.config on Windows.
@@ -527,6 +530,7 @@ export async function startOpenCode(model, fcmConfig) {
527
530
  // ─── Proxy lifecycle ─────────────────────────────────────────────────────────
528
531
 
529
532
  async function cleanupProxy() {
533
+ // 📖 Only clean up in-process proxy. If using daemon, it stays alive.
530
534
  if (proxyCleanedUp || !activeProxy) return
531
535
  proxyCleanedUp = true
532
536
  const proxy = activeProxy
@@ -546,35 +550,10 @@ function registerExitHandlers() {
546
550
  process.once('exit', cleanup)
547
551
  }
548
552
 
553
+ // 📖 Thin wrapper that passes module-level mergedModelsRef to the shared topology builder.
554
+ // 📖 The standalone daemon calls _buildTopology() directly with its own merged models.
549
555
  export function buildProxyTopologyFromConfig(fcmConfig) {
550
- const accounts = []
551
- const proxyModels = {}
552
-
553
- for (const merged of mergedModelsRef) {
554
- proxyModels[merged.slug] = { name: merged.label }
555
-
556
- for (const providerEntry of merged.providers) {
557
- const keys = resolveApiKeys(fcmConfig, providerEntry.providerKey)
558
- const providerSource = sources[providerEntry.providerKey]
559
- if (!providerSource) continue
560
-
561
- const rawUrl = resolveCloudflareUrl(providerSource.url)
562
- const baseUrl = rawUrl.replace(/\/chat\/completions$/, '')
563
-
564
- keys.forEach((apiKey, keyIdx) => {
565
- accounts.push({
566
- id: `${providerEntry.providerKey}/${merged.slug}/${keyIdx}`,
567
- providerKey: providerEntry.providerKey,
568
- proxyModelId: merged.slug,
569
- modelId: providerEntry.modelId,
570
- url: baseUrl,
571
- apiKey,
572
- })
573
- })
574
- }
575
- }
576
-
577
- return { accounts, proxyModels }
556
+ return _buildTopology(fcmConfig, mergedModelsRef, sources)
578
557
  }
579
558
 
580
559
  /**
@@ -596,6 +575,26 @@ export async function ensureProxyRunning(fcmConfig, { forceRestart = false } = {
596
575
  throw new Error('Proxy mode is disabled in Settings')
597
576
  }
598
577
 
578
+ // 📖 Phase 1: Check if background daemon is running — delegate instead of starting in-process
579
+ if (!forceRestart) {
580
+ try {
581
+ const daemonRunning = await isDaemonRunning()
582
+ if (daemonRunning) {
583
+ const info = getDaemonInfo()
584
+ if (info) {
585
+ return {
586
+ port: info.port,
587
+ accountCount: info.accountCount || 0,
588
+ proxyToken: info.token,
589
+ proxyModels: null,
590
+ availableModelSlugs: new Set(), // 📖 daemon handles model discovery
591
+ isDaemon: true,
592
+ }
593
+ }
594
+ }
595
+ } catch { /* daemon check failed — fall through to in-process */ }
596
+ }
597
+
599
598
  if (forceRestart && activeProxy) {
600
599
  await cleanupProxy()
601
600
  }
@@ -619,8 +618,9 @@ export async function ensureProxyRunning(fcmConfig, { forceRestart = false } = {
619
618
  throw new Error('No API keys found for proxy-capable models')
620
619
  }
621
620
 
622
- const proxyToken = `fcm_${randomUUID().replace(/-/g, '')}`
621
+ // 📖 Use stable token from config so env files / tool configs survive restarts
623
622
  const proxySettings = getProxySettings(fcmConfig)
623
+ const proxyToken = proxySettings.stableToken || `fcm_${randomUUID().replace(/-/g, '')}`
624
624
  const preferredPort = Number.isInteger(proxySettings.preferredPort) ? proxySettings.preferredPort : 0
625
625
  const proxy = new ProxyServer({ port: preferredPort, accounts, proxyApiKey: proxyToken })
626
626
  const { port } = await proxy.start()
@@ -635,24 +635,27 @@ export async function autoStartProxyIfSynced(fcmConfig, state) {
635
635
  try {
636
636
  const proxySettings = getProxySettings(fcmConfig)
637
637
  if (!proxySettings.enabled || !proxySettings.syncToOpenCode) return
638
-
639
- const ocConfig = loadOpenCodeConfig()
640
- if (!ocConfig?.provider?.['fcm-proxy']) return
638
+ const currentToolMode = state?.mode || 'opencode'
639
+ const syncTarget = resolveProxySyncToolMode(currentToolMode)
640
+ if (!syncTarget) return
641
641
 
642
642
  state.proxyStartupStatus = { phase: 'starting' }
643
643
 
644
644
  const started = await ensureProxyRunning(fcmConfig)
645
-
646
- syncToOpenCode(fcmConfig, sources, mergedModelsRef, {
647
- proxyPort: started.port,
648
- proxyToken: started.proxyToken,
649
- availableModelSlugs: started.availableModelSlugs,
650
- })
645
+ const syncResult = syncProxyToTool(syncTarget, {
646
+ baseUrl: `http://127.0.0.1:${started.port}/v1`,
647
+ token: started.proxyToken,
648
+ }, mergedModelsRef)
649
+ if (!syncResult.success) {
650
+ throw new Error(syncResult.error || `Proxy sync failed for ${syncTarget}`)
651
+ }
651
652
 
652
653
  state.proxyStartupStatus = {
653
654
  phase: 'running',
654
655
  port: started.port,
655
656
  accountCount: started.accountCount,
657
+ tool: getToolMeta(syncTarget).label,
658
+ path: syncResult.path || null,
656
659
  }
657
660
  } catch (err) {
658
661
  state.proxyStartupStatus = {
@@ -692,7 +695,7 @@ async function startOpenCodeWithProxy(model, port, proxyModelId, proxyModels, fc
692
695
 
693
696
  config.provider['fcm-proxy'] = {
694
697
  npm: '@ai-sdk/openai-compatible',
695
- name: 'FCM Proxy',
698
+ name: 'FCM Proxy V2',
696
699
  options: {
697
700
  baseURL: `http://127.0.0.1:${port}/v1`,
698
701
  apiKey: proxyToken