@tiens.nguyen/gonext-local-worker 1.0.114 → 1.0.117

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.
@@ -1669,6 +1669,40 @@ async function checkOllamaTags(rawBase) {
1669
1669
  }
1670
1670
  }
1671
1671
 
1672
+ /**
1673
+ * Discover the mlx_lm.server processes actually running on this machine by
1674
+ * parsing `ps` output. Returns deduped `[{ port, modelPath }]` so the model
1675
+ * dropdown reflects live servers instead of a hand-maintained port list.
1676
+ * mlx_lm.server defaults to port 8080 when `--port` is omitted. macOS only.
1677
+ */
1678
+ async function discoverMlxServers() {
1679
+ if (platform() !== "darwin") return [];
1680
+ let stdout = "";
1681
+ try {
1682
+ ({ stdout } = await execFile("ps", ["-Ao", "args="], {
1683
+ maxBuffer: 8 * 1024 * 1024,
1684
+ }));
1685
+ } catch {
1686
+ return [];
1687
+ }
1688
+ const byPort = new Map();
1689
+ for (const raw of stdout.split("\n")) {
1690
+ const line = raw.trim();
1691
+ if (!line) continue;
1692
+ // Match `mlx_lm.server` or `mlx_lm server` (python -m mlx_lm server).
1693
+ if (!/mlx_lm[.\s]server\b/.test(line)) continue;
1694
+ // Ignore our own grep/ps invocations that happen to mention the string.
1695
+ if (/\bgrep\b/.test(line)) continue;
1696
+ const portMatch = line.match(/--port(?:=|\s+)(\d{2,5})/);
1697
+ const port = portMatch ? parseInt(portMatch[1], 10) : 8080;
1698
+ if (!(port > 0)) continue;
1699
+ const modelMatch = line.match(/--model(?:=|\s+)(\S+)/);
1700
+ const modelPath = modelMatch ? modelMatch[1] : "";
1701
+ if (!byPort.has(port)) byPort.set(port, { port, modelPath });
1702
+ }
1703
+ return [...byPort.values()];
1704
+ }
1705
+
1672
1706
  async function checkOpenAiModels(base, apiKey, source) {
1673
1707
  const endpoint = `${base}/models`;
1674
1708
  const headers = {};
@@ -1842,13 +1876,6 @@ async function runLocalHealthJob(job) {
1842
1876
  if (!dedup.has(m.value)) dedup.set(m.value, m);
1843
1877
  }
1844
1878
  }
1845
- // Support both single mlxOpenAiBaseUrl and multi-port mlxOpenAiBaseUrls
1846
- const mlxRootSingle = normalizeOpenAiV1Root(payload?.mlxOpenAiBaseUrl);
1847
- const mlxRootsMulti = Array.isArray(payload?.mlxOpenAiBaseUrls)
1848
- ? payload.mlxOpenAiBaseUrls.map(normalizeOpenAiV1Root).filter(Boolean)
1849
- : [];
1850
- const mlxRoots = mlxRootsMulti.length > 0 ? mlxRootsMulti : (mlxRootSingle ? [mlxRootSingle] : []);
1851
- const mlxRoot = mlxRoots[0] || null;
1852
1879
  const targetWorkerHostId =
1853
1880
  typeof payload?.targetWorkerHostId === "string"
1854
1881
  ? payload.targetWorkerHostId.trim()
@@ -1857,6 +1884,30 @@ async function runLocalHealthJob(job) {
1857
1884
  typeof payload?.targetWorkerHostName === "string"
1858
1885
  ? payload.targetWorkerHostName.trim()
1859
1886
  : "";
1887
+ // MLX servers are discovered dynamically from running mlx_lm.server
1888
+ // processes (macOS), so the dropdown reflects reality with no saved port
1889
+ // list. Only discover when a worker host is targeted so the encoded models
1890
+ // are host-scoped (routable). Each server is probed at 127.0.0.1:<port>/v1.
1891
+ const discoveredMlx = targetWorkerHostId ? await discoverMlxServers() : [];
1892
+ const discoveredByPort = new Map(discoveredMlx.map((s) => [s.port, s]));
1893
+ const mlxRootsDiscovered = discoveredMlx.map(
1894
+ (s) => `http://127.0.0.1:${s.port}/v1`
1895
+ );
1896
+ // Fall back to any explicitly provided base (e.g. a remote host) so
1897
+ // non-local MLX endpoints still work when no local process is found.
1898
+ const mlxRootSingle = normalizeOpenAiV1Root(payload?.mlxOpenAiBaseUrl);
1899
+ const mlxRootsMulti = Array.isArray(payload?.mlxOpenAiBaseUrls)
1900
+ ? payload.mlxOpenAiBaseUrls.map(normalizeOpenAiV1Root).filter(Boolean)
1901
+ : [];
1902
+ const mlxRoots =
1903
+ mlxRootsDiscovered.length > 0
1904
+ ? mlxRootsDiscovered
1905
+ : mlxRootsMulti.length > 0
1906
+ ? mlxRootsMulti
1907
+ : mlxRootSingle
1908
+ ? [mlxRootSingle]
1909
+ : [];
1910
+ const mlxRoot = mlxRoots[0] || null;
1860
1911
  let mlxNative = null;
1861
1912
  const modelPathChecksRaw = Array.isArray(payload?.modelPathChecks)
1862
1913
  ? payload.modelPathChecks
@@ -1892,12 +1943,29 @@ async function runLocalHealthJob(job) {
1892
1943
  // Merge all per-port results
1893
1944
  const mlxModelDedup = new Map();
1894
1945
  let mlxHttpOnline = false;
1946
+ const portsWithModels = new Set();
1895
1947
  for (const r of mlxHttpResults) {
1896
1948
  if (r.online) mlxHttpOnline = true;
1897
1949
  for (const m of r.models) {
1950
+ const pm = String(m.value ?? "").match(/@@port:(\d+)/);
1951
+ if (pm) portsWithModels.add(parseInt(pm[1], 10));
1898
1952
  if (!mlxModelDedup.has(m.value)) mlxModelDedup.set(m.value, m);
1899
1953
  }
1900
1954
  }
1955
+ // A discovered server may be booting (process up, /v1/models not ready).
1956
+ // Synthesize an entry from its `--model` path so it is still visible.
1957
+ for (const { port, modelPath } of discoveredByPort.values()) {
1958
+ if (portsWithModels.has(port) || !modelPath) continue;
1959
+ const hostSuffix = targetWorkerHostId ? `@@host:${targetWorkerHostId}` : "";
1960
+ const value = `mlx:${modelPath}${hostSuffix}@@port:${port}`;
1961
+ if (mlxModelDedup.has(value)) continue;
1962
+ const label = targetWorkerHostName || sourceLabelFromBase(`http://127.0.0.1:${port}`);
1963
+ mlxModelDedup.set(value, {
1964
+ id: targetWorkerHostId ? `${modelPath}@@${targetWorkerHostId}` : modelPath,
1965
+ name: `${modelPath} (${label}:${port}) — starting…`,
1966
+ value,
1967
+ });
1968
+ }
1901
1969
  const mlxHttp = mlxRoots.length > 0 ? {
1902
1970
  online: mlxHttpOnline,
1903
1971
  endpoint: mlxHttpResults[0]?.endpoint,
@@ -1905,7 +1973,7 @@ async function runLocalHealthJob(job) {
1905
1973
  } : null;
1906
1974
 
1907
1975
  const wantNativeFallback =
1908
- mlxRoot &&
1976
+ (targetWorkerHostId || mlxRoot) &&
1909
1977
  payload?.mlxNativeFallback !== false &&
1910
1978
  platform() === "darwin" &&
1911
1979
  (!mlxHttp?.online || (mlxHttp?.models?.length ?? 0) === 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.114",
3
+ "version": "1.0.117",
4
4
  "description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
5
5
  "type": "module",
6
6
  "license": "MIT",