turbollm 1.2.1 → 1.3.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.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +809 -153
  3. package/dist/webdist/assets/{ChatScreen-Di2U5guk.js → ChatScreen-DY2flMe9.js} +1 -1
  4. package/dist/webdist/assets/{CustomizeScreen-C1N3VSzu.js → CustomizeScreen-Cl6H68Sy.js} +4 -4
  5. package/dist/webdist/assets/DeveloperScreen-zC1nePXh.js +1 -0
  6. package/dist/webdist/assets/EnginesScreen-CrsziC3B.js +7 -0
  7. package/dist/webdist/assets/ModelDetailDialog-BOvumdHI.js +1 -0
  8. package/dist/webdist/assets/{ModelDirs-z0eidPHf.js → ModelDirs-Bj-XrX0c.js} +1 -1
  9. package/dist/webdist/assets/ModelsScreen-BTir-kDY.js +1 -0
  10. package/dist/webdist/assets/SettingsScreen-B696egCa.js +1 -0
  11. package/dist/webdist/assets/check-DFwkcl_5.js +1 -0
  12. package/dist/webdist/assets/{circle-x-nfGRpYmv.js → circle-x-CsuAddI_.js} +1 -1
  13. package/dist/webdist/assets/{common-VLx-PnhW.js → common-DIXPIrUd.js} +1 -1
  14. package/dist/webdist/assets/{copy-button-DvxLdCBa.js → copy-button-BtQZA6P8.js} +1 -1
  15. package/dist/webdist/assets/{dialog-BQ-Ai22D.js → dialog-Dpyml8ZT.js} +1 -1
  16. package/dist/webdist/assets/index-BklQn62W.css +1 -0
  17. package/dist/webdist/assets/{index-BGye3qtk.js → index-sjgdofsd.js} +7 -7
  18. package/dist/webdist/assets/{pencil-fITP-Ux2.js → pencil-BuQwoNyj.js} +1 -1
  19. package/dist/webdist/assets/{personas-Cq8LxYil.js → personas-CQapYHeK.js} +1 -1
  20. package/dist/webdist/assets/{plus-nMHxYBqJ.js → plus-B1w15FoZ.js} +1 -1
  21. package/dist/webdist/assets/{radix-C-SVreIl.js → radix-BFvMxQq9.js} +1 -1
  22. package/dist/webdist/assets/refresh-cw-BoAcjLtt.js +1 -0
  23. package/dist/webdist/assets/save-DE1APWTG.js +1 -0
  24. package/dist/webdist/assets/{skeleton-CSAyV68N.js → skeleton-Dvq8tYzC.js} +1 -1
  25. package/dist/webdist/assets/{sparkles-Cs6GG6EW.js → sparkles-C54kYWeQ.js} +1 -1
  26. package/dist/webdist/assets/{trash-2-BdOxLswi.js → trash-2-DIMVgidH.js} +1 -1
  27. package/dist/webdist/index.html +3 -3
  28. package/package.json +1 -1
  29. package/dist/webdist/assets/DeveloperScreen-DWf0TZqq.js +0 -1
  30. package/dist/webdist/assets/EnginesScreen-Dqy-jlqZ.js +0 -5
  31. package/dist/webdist/assets/ModelDetailDialog-BRKjfp4G.js +0 -1
  32. package/dist/webdist/assets/ModelsScreen-DqIQ4ST-.js +0 -1
  33. package/dist/webdist/assets/SettingsScreen-DXp8YTPJ.js +0 -1
  34. package/dist/webdist/assets/check-BnpDXpw8.js +0 -1
  35. package/dist/webdist/assets/external-link-CO5laD7p.js +0 -1
  36. package/dist/webdist/assets/index-Bx0PAdfC.css +0 -1
  37. package/dist/webdist/assets/save-Cm1zJnW7.js +0 -1
package/dist/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { spawn as spawn4 } from "child_process";
5
- import { openSync as openSync3, readFileSync as readFileSync9 } from "fs";
4
+ import { spawn as spawn5 } from "child_process";
5
+ import { openSync as openSync3, readFileSync as readFileSync10 } from "fs";
6
6
  import { serve } from "@hono/node-server";
7
- import { dirname as dirname8, join as join19 } from "path";
7
+ import { dirname as dirname9, join as join21 } from "path";
8
8
  import { fileURLToPath as fileURLToPath2 } from "url";
9
9
 
10
10
  // src/config/config.ts
@@ -90,7 +90,8 @@ function defaultConfig() {
90
90
  comfyui: { enabled: false, gatePath: "", url: "", reverseGate: false, cachePersist: false },
91
91
  gateway: { autoSwap: true, keepN: 1 },
92
92
  tools: {},
93
- mcp: { servers: [] }
93
+ mcp: { servers: [] },
94
+ build: { toolchainDirs: [] }
94
95
  };
95
96
  }
96
97
  var ConfigStore = class _ConfigStore {
@@ -254,6 +255,10 @@ function normalize(c) {
254
255
  c.mcp = {
255
256
  servers: Array.isArray(mc.servers) ? mc.servers.filter((s) => typeof s === "object" && s !== null && typeof s.id === "string" && typeof s.name === "string" && (s.transport === "stdio" || s.transport === "sse")) : []
256
257
  };
258
+ const bd = c.build ?? {};
259
+ c.build = {
260
+ toolchainDirs: Array.isArray(bd.toolchainDirs) ? bd.toolchainDirs.filter((p) => typeof p === "string" && p.trim() !== "").map((p) => p.trim()) : []
261
+ };
257
262
  c.telemetry.level = normalizeTelemetryLevel(c.telemetry.level);
258
263
  for (const e of c.engines) {
259
264
  e.capabilities ??= { kvTypes: [], flags: [] };
@@ -284,6 +289,9 @@ function validate(c) {
284
289
  if (c.comfyui.url && !/^https?:\/\//i.test(c.comfyui.url)) {
285
290
  throw new ValueError("comfyui.url", "must be an http(s):// origin (e.g. http://127.0.0.1:8188)");
286
291
  }
292
+ for (const dir of c.build.toolchainDirs) {
293
+ if (!isAbsolutePath(dir)) throw new ValueError("build.toolchainDirs", "toolchain directories must be absolute paths");
294
+ }
287
295
  }
288
296
  function isAbsolutePath(p) {
289
297
  return /^([a-zA-Z]:[\\/]|[\\/])/.test(p);
@@ -311,12 +319,19 @@ import { promisify as promisify2 } from "util";
311
319
 
312
320
  // src/engines/download.ts
313
321
  import { createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmSync } from "fs";
314
- import { execFile } from "child_process";
322
+ import { execFile, execFileSync } from "child_process";
315
323
  import { join as join2 } from "path";
316
324
  import { Readable } from "stream";
317
325
  import { pipeline } from "stream/promises";
318
326
  import { promisify } from "util";
319
327
  var execFileP = promisify(execFile);
328
+ function stripMacOsQuarantine(dir) {
329
+ if (process.platform !== "darwin") return;
330
+ try {
331
+ execFileSync("xattr", ["-r", "-d", "com.apple.quarantine", dir]);
332
+ } catch {
333
+ }
334
+ }
320
335
  var LLAMA_BUILD = "b9608";
321
336
  var REPO = "ggml-org/llama.cpp";
322
337
  var CUDA_VER = "13.3";
@@ -327,7 +342,10 @@ function availableBackends(tag = LLAMA_BUILD) {
327
342
  const a = arch();
328
343
  const def = (id, label, ...assets) => ({ id, label, assets });
329
344
  if (plat() === "darwin") {
330
- return [def("metal", "Metal (Apple GPU)", `llama-${tag}-bin-macos-${a}.tar.gz`)];
345
+ return [
346
+ def("metal", "Metal (Apple GPU)", `llama-${tag}-bin-macos-${a}.tar.gz`),
347
+ def("cpu", "CPU", `llama-${tag}-bin-macos-${a}.tar.gz`)
348
+ ];
331
349
  }
332
350
  if (plat() === "win32") {
333
351
  if (a === "arm64") return [def("cpu", "CPU", `llama-${tag}-bin-win-cpu-arm64.zip`)];
@@ -391,9 +409,28 @@ function backendDir(enginesRoot, id, tag = LLAMA_BUILD) {
391
409
  function backendDefAt(id, tag) {
392
410
  return availableBackends(tag).find((b) => b.id === id) ?? null;
393
411
  }
394
- function installedBackendServer(enginesRoot, id, tag = LLAMA_BUILD) {
395
- const dir = backendDir(enginesRoot, id, tag);
396
- return existsSync2(dir) ? findServer(dir) : null;
412
+ function installedBackendBuild(enginesRoot, id) {
413
+ let entries;
414
+ try {
415
+ entries = readdirSync(enginesRoot);
416
+ } catch {
417
+ return null;
418
+ }
419
+ const re = new RegExp(`^llama\\.cpp-(.+)-${id}$`);
420
+ const buildNum = (tag) => {
421
+ const m = tag.match(/^b(\d+)$/);
422
+ return m ? Number(m[1]) : -1;
423
+ };
424
+ let best = null;
425
+ for (const name of entries) {
426
+ const m = re.exec(name);
427
+ if (!m) continue;
428
+ const dir = join2(enginesRoot, name);
429
+ const bin = findServer(dir);
430
+ if (!bin) continue;
431
+ if (!best || buildNum(m[1]) > buildNum(best.tag)) best = { dir, tag: m[1], bin };
432
+ }
433
+ return best;
397
434
  }
398
435
  function findFile(dir, name, skipDir) {
399
436
  for (const e of readdirSync(dir, { withFileTypes: true })) {
@@ -437,6 +474,7 @@ async function extractArchive(archive, destDir) {
437
474
  } else {
438
475
  await execFileP("tar", ["-xzf", archive, "-C", destDir]);
439
476
  }
477
+ stripMacOsQuarantine(destDir);
440
478
  }
441
479
  async function provisionBackend(enginesRoot, backend, tag, onProgress, signal) {
442
480
  const destDir = join2(enginesRoot, `llama.cpp-${tag}-${backend.id}`);
@@ -467,11 +505,21 @@ async function provisionBackend(enginesRoot, backend, tag, onProgress, signal) {
467
505
  if (!bin) throw new Error("llama-server not found in extracted archive(s)");
468
506
  return bin;
469
507
  }
470
- function deleteBackend(enginesRoot, id, tag = LLAMA_BUILD) {
471
- const dir = backendDir(enginesRoot, id, tag);
472
- if (!existsSync2(dir)) return false;
473
- rmSync(dir, { recursive: true, force: true });
474
- return true;
508
+ function deleteAllBackendBuilds(enginesRoot, id) {
509
+ let entries;
510
+ try {
511
+ entries = readdirSync(enginesRoot);
512
+ } catch {
513
+ return 0;
514
+ }
515
+ const re = new RegExp(`^llama\\.cpp-(.+)-${id}$`);
516
+ let removed = 0;
517
+ for (const name of entries) {
518
+ if (!re.test(name)) continue;
519
+ rmSync(join2(enginesRoot, name), { recursive: true, force: true });
520
+ removed++;
521
+ }
522
+ return removed;
475
523
  }
476
524
  function scoreAsset(name, platform = process.platform, archStr = process.arch) {
477
525
  const n = name.toLowerCase();
@@ -524,32 +572,6 @@ async function latestCommitSha(repo, branch = "", signal) {
524
572
  const data = await res.json();
525
573
  return data.sha ?? "";
526
574
  }
527
- async function provisionForkRelease(enginesRoot, repo, destName, onProgress, signal) {
528
- const destDir = join2(enginesRoot, destName);
529
- if (existsSync2(destDir)) {
530
- const found = findServer(destDir);
531
- if (found) return found;
532
- }
533
- const rel = await latestGithubRelease(repo, signal);
534
- const asset = pickReleaseAsset(rel.assets ?? []);
535
- if (!asset) throw new Error("no_release_asset");
536
- mkdirSync2(destDir, { recursive: true });
537
- const tmp = join2(enginesRoot, asset.name);
538
- try {
539
- onProgress?.({ phase: "downloading", pct: 0 });
540
- await downloadFile(asset.browser_download_url, tmp, onProgress, signal);
541
- onProgress?.({ phase: "extracting", pct: -1 });
542
- await extractArchive(tmp, destDir);
543
- rmSync(tmp, { force: true });
544
- } catch (e) {
545
- rmSync(tmp, { force: true });
546
- rmSync(destDir, { recursive: true, force: true });
547
- throw e;
548
- }
549
- const bin = findServer(destDir);
550
- if (!bin) throw new Error("llama-server not found in extracted release archive");
551
- return bin;
552
- }
553
575
  async function turboquantAssetUrl(repo, platform = process.platform, archStr = process.arch, signal) {
554
576
  if (platform === "win32") return null;
555
577
  const res = await fetch(`https://api.github.com/repos/${repo}/releases?per_page=100`, {
@@ -570,7 +592,10 @@ async function provisionTurboquant(enginesRoot, repo, onProgress, signal) {
570
592
  const destDir = join2(enginesRoot, "turboquant");
571
593
  if (existsSync2(destDir)) {
572
594
  const found = findServer(destDir);
573
- if (found) return found;
595
+ if (found) {
596
+ stripMacOsQuarantine(destDir);
597
+ return found;
598
+ }
574
599
  }
575
600
  const url = await turboquantAssetUrl(repo, process.platform, process.arch, signal);
576
601
  if (!url) throw new Error("no_release_asset");
@@ -1746,7 +1771,8 @@ function isTimeout(err3) {
1746
1771
  function runCaptured(bin, arg) {
1747
1772
  return new Promise((resolve2) => {
1748
1773
  try {
1749
- execFile5(bin, [arg], { cwd: dirname3(bin), timeout: 1e4, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => {
1774
+ const timeoutMs = process.platform === "darwin" ? 6e4 : 15e3;
1775
+ execFile5(bin, [arg], { cwd: dirname3(bin), timeout: timeoutMs, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => {
1750
1776
  resolve2({ out: (stdout || "") + (stderr || ""), err: error });
1751
1777
  });
1752
1778
  } catch (e) {
@@ -1770,7 +1796,8 @@ async function probe(bin) {
1770
1796
  const h = await runCaptured(bin, "--help");
1771
1797
  if (v.err && h.err) {
1772
1798
  if (isTimeout(v.err) && isTimeout(h.err)) {
1773
- throw new ProbeError("probe_timeout", "The binary did not respond within 10 seconds.");
1799
+ const timeoutSec = process.platform === "darwin" ? 60 : 15;
1800
+ throw new ProbeError("probe_timeout", `The binary did not respond within ${timeoutSec} seconds.`);
1774
1801
  }
1775
1802
  let msg2 = "Could not run the binary (--version and --help both failed).";
1776
1803
  const tail = lastLine(v.out);
@@ -2110,6 +2137,40 @@ var ProvisionState = class {
2110
2137
  }
2111
2138
  };
2112
2139
 
2140
+ // src/engines/build-state.ts
2141
+ var LOG_TAIL = 200;
2142
+ var BuildState = class {
2143
+ s = { active: false, phase: "preparing", engine: "", log: [], error: null };
2144
+ get() {
2145
+ return { ...this.s, log: [...this.s.log] };
2146
+ }
2147
+ /** True while a build is running — used to reject a concurrent build/download. */
2148
+ isActive() {
2149
+ return this.s.active;
2150
+ }
2151
+ start(engine) {
2152
+ this.s = { active: true, phase: "preparing", engine, log: [], error: null };
2153
+ }
2154
+ phase(phase) {
2155
+ if (!this.s.active) return;
2156
+ this.s.phase = phase;
2157
+ }
2158
+ /** Append a log line (split multi-line chunks upstream). Keeps only the last N. */
2159
+ log(line) {
2160
+ if (!this.s.active) return;
2161
+ const trimmed = line.replace(/\r?\n$/, "");
2162
+ if (trimmed === "") return;
2163
+ this.s.log.push(trimmed);
2164
+ if (this.s.log.length > LOG_TAIL) this.s.log.splice(0, this.s.log.length - LOG_TAIL);
2165
+ }
2166
+ done() {
2167
+ this.s = { ...this.s, active: false, phase: "done", error: null };
2168
+ }
2169
+ fail(error) {
2170
+ this.s = { ...this.s, active: false, phase: "error", error };
2171
+ }
2172
+ };
2173
+
2113
2174
  // src/engines/update.ts
2114
2175
  function parseBuildTag(tag) {
2115
2176
  const m = /^v?b(\d+)$/i.exec(tag.trim());
@@ -2433,7 +2494,7 @@ import { rmSync as rmSync5 } from "fs";
2433
2494
  import { join as join9 } from "path";
2434
2495
 
2435
2496
  // src/sysinfo/sysinfo.ts
2436
- import { execFileSync } from "child_process";
2497
+ import { execFileSync as execFileSync2 } from "child_process";
2437
2498
  import os from "os";
2438
2499
  import fs from "fs";
2439
2500
  var cached = null;
@@ -2464,7 +2525,7 @@ function classifyVendor(name) {
2464
2525
  }
2465
2526
  function detectGpus() {
2466
2527
  try {
2467
- const out = execFileSync("nvidia-smi", ["--query-gpu=name,memory.total", "--format=csv,noheader,nounits"], {
2528
+ const out = execFileSync2("nvidia-smi", ["--query-gpu=name,memory.total", "--format=csv,noheader,nounits"], {
2468
2529
  timeout: 8e3,
2469
2530
  windowsHide: true
2470
2531
  }).toString();
@@ -2477,7 +2538,7 @@ function detectGpus() {
2477
2538
  }
2478
2539
  if (process.platform === "darwin") {
2479
2540
  try {
2480
- const out = execFileSync("system_profiler", ["SPDisplaysDataType"], { timeout: 8e3 }).toString();
2541
+ const out = execFileSync2("system_profiler", ["SPDisplaysDataType"], { timeout: 8e3 }).toString();
2481
2542
  const m = out.match(/Chipset Model:\s*(.+)/);
2482
2543
  if (m && /Apple/.test(out)) {
2483
2544
  return [{ name: m[1].trim(), vramMb: Math.round(os.totalmem() / 1e6 * 0.65), vendor: "apple" }];
@@ -2494,7 +2555,7 @@ function detectGpus() {
2494
2555
  }
2495
2556
  function enumWindowsGpus() {
2496
2557
  const ps = 'Get-CimInstance Win32_VideoController | ForEach-Object { "$($_.Name)|$($_.AdapterRAM)" }';
2497
- const out = execFileSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], {
2558
+ const out = execFileSync2("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], {
2498
2559
  timeout: 8e3,
2499
2560
  windowsHide: true
2500
2561
  }).toString();
@@ -2506,7 +2567,7 @@ function enumWindowsGpus() {
2506
2567
  }).filter((g) => g.name && g.vendor !== "unknown");
2507
2568
  }
2508
2569
  function enumLinuxGpus() {
2509
- const out = execFileSync("sh", ["-c", "lspci -mm 2>/dev/null | grep -iE 'VGA|3D|Display'"], {
2570
+ const out = execFileSync2("sh", ["-c", "lspci -mm 2>/dev/null | grep -iE 'VGA|3D|Display'"], {
2510
2571
  timeout: 8e3
2511
2572
  }).toString();
2512
2573
  return out.trim().split("\n").filter(Boolean).map((line) => {
@@ -2589,10 +2650,9 @@ async function applyForkUpdate(d, engine, root, signal) {
2589
2650
  if (oldEng && d.registry.active()?.id === oldEng.id) await d.manager.stopAndWait();
2590
2651
  const tqDir = join9(root, "turboquant");
2591
2652
  rmSync5(tqDir, { recursive: true, force: true });
2592
- const bin = await provisionForkRelease(
2653
+ const bin = await provisionTurboquant(
2593
2654
  root,
2594
2655
  "AtomicBot-ai/atomic-llama-cpp-turboquant",
2595
- "turboquant",
2596
2656
  (p) => d.provision.progress(p.phase, p.pct, p.part, p.parts),
2597
2657
  signal
2598
2658
  );
@@ -6353,15 +6413,15 @@ Install it: ${spec.install}
6353
6413
  // src/server.ts
6354
6414
  import { Hono } from "hono";
6355
6415
  import { cors } from "hono/cors";
6356
- import { existsSync as existsSync16, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
6416
+ import { existsSync as existsSync18, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
6357
6417
  import { fileURLToPath } from "url";
6358
- import { dirname as dirname7, join as join18, normalize as normalize2 } from "path";
6418
+ import { dirname as dirname8, join as join20, normalize as normalize2 } from "path";
6359
6419
  import { Agent, setGlobalDispatcher } from "undici";
6360
6420
 
6361
6421
  // src/api/routes.ts
6362
6422
  import { streamSSE as streamSSE2 } from "hono/streaming";
6363
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync7, readdirSync as readdirSync5, realpathSync, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync7 } from "fs";
6364
- import { basename as basename3, dirname as dirname6, join as join17, resolve, sep } from "path";
6423
+ import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync8, readdirSync as readdirSync7, realpathSync, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync8 } from "fs";
6424
+ import { basename as basename3, dirname as dirname7, join as join19, resolve, sep } from "path";
6365
6425
 
6366
6426
  // src/comfyui/gate-template.ts
6367
6427
  var GATE_VERSION = 2;
@@ -7900,9 +7960,17 @@ function catalogEngine(id) {
7900
7960
 
7901
7961
  // src/engines/build-prereqs.ts
7902
7962
  import { execFile as execFile7 } from "child_process";
7903
- import { join as join16 } from "path";
7963
+ import { delimiter, join as join16 } from "path";
7904
7964
  import { promisify as promisify4 } from "util";
7905
7965
  var execFileP4 = promisify4(execFile7);
7966
+ function buildEnv(toolchainDirs = []) {
7967
+ const dirs = toolchainDirs.map((d) => d.trim()).filter(Boolean);
7968
+ if (dirs.length === 0) return { ...process.env };
7969
+ const env = { ...process.env };
7970
+ const key = Object.keys(env).find((k) => k.toLowerCase() === "path") ?? "PATH";
7971
+ env[key] = [...dirs, env[key] ?? ""].filter(Boolean).join(delimiter);
7972
+ return env;
7973
+ }
7906
7974
  var INSTALL_URLS = {
7907
7975
  git: "https://git-scm.com/downloads",
7908
7976
  cmake: "https://cmake.org/download/",
@@ -7910,11 +7978,12 @@ var INSTALL_URLS = {
7910
7978
  // The "Desktop development with C++" workload from the VS Build Tools installer.
7911
7979
  msvc: "https://visualstudio.microsoft.com/downloads/"
7912
7980
  };
7913
- async function runVersion(cmd, args) {
7981
+ async function runVersion(cmd, args, env) {
7914
7982
  const { stdout, stderr } = await execFileP4(cmd, args, {
7915
7983
  timeout: 8e3,
7916
7984
  windowsHide: true,
7917
- maxBuffer: 1024 * 1024
7985
+ maxBuffer: 1024 * 1024,
7986
+ env
7918
7987
  });
7919
7988
  return (stdout || stderr || "").trim();
7920
7989
  }
@@ -7930,40 +7999,40 @@ function parseNvccVersion(out) {
7930
7999
  const m = out.match(/release\s+([\d.]+)/i);
7931
8000
  return m ? m[1] : "";
7932
8001
  }
7933
- async function checkGit() {
8002
+ async function checkGit(env) {
7934
8003
  let found = false;
7935
8004
  let version2;
7936
8005
  try {
7937
- version2 = parseGitVersion(await runVersion("git", ["--version"])) || void 0;
8006
+ version2 = parseGitVersion(await runVersion("git", ["--version"], env)) || void 0;
7938
8007
  found = true;
7939
8008
  } catch {
7940
8009
  found = false;
7941
8010
  }
7942
8011
  return { id: "git", name: "Git", found, version: version2, installUrl: INSTALL_URLS.git };
7943
8012
  }
7944
- async function checkCmake() {
8013
+ async function checkCmake(env) {
7945
8014
  let found = false;
7946
8015
  let version2;
7947
8016
  try {
7948
- version2 = parseCmakeVersion(await runVersion("cmake", ["--version"])) || void 0;
8017
+ version2 = parseCmakeVersion(await runVersion("cmake", ["--version"], env)) || void 0;
7949
8018
  found = true;
7950
8019
  } catch {
7951
8020
  found = false;
7952
8021
  }
7953
8022
  return { id: "cmake", name: "CMake", found, version: version2, installUrl: INSTALL_URLS.cmake };
7954
8023
  }
7955
- async function checkCuda() {
8024
+ async function checkCuda(env) {
7956
8025
  let found = false;
7957
8026
  let version2;
7958
8027
  try {
7959
- version2 = parseNvccVersion(await runVersion("nvcc", ["--version"])) || void 0;
8028
+ version2 = parseNvccVersion(await runVersion("nvcc", ["--version"], env)) || void 0;
7960
8029
  found = true;
7961
8030
  } catch {
7962
8031
  found = false;
7963
8032
  }
7964
8033
  return { id: "cuda", name: "CUDA Toolkit", found, version: version2, installUrl: INSTALL_URLS.cuda };
7965
8034
  }
7966
- async function checkMsvc() {
8035
+ async function checkMsvc(env) {
7967
8036
  let found = false;
7968
8037
  let version2;
7969
8038
  try {
@@ -7977,7 +8046,7 @@ async function checkMsvc() {
7977
8046
  "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
7978
8047
  "-property",
7979
8048
  "installationVersion"
7980
- ]);
8049
+ ], env);
7981
8050
  version2 = out.split(/\r?\n/)[0]?.trim() || void 0;
7982
8051
  found = !!version2;
7983
8052
  } catch {
@@ -7991,14 +8060,398 @@ async function checkMsvc() {
7991
8060
  installUrl: INSTALL_URLS.msvc
7992
8061
  };
7993
8062
  }
7994
- async function checkBuildPrereqs() {
8063
+ async function checkBuildPrereqs(toolchainDirs = []) {
7995
8064
  if (process.platform !== "win32") {
7996
8065
  return { supported: false, tools: [] };
7997
8066
  }
7998
- const tools = await Promise.all([checkGit(), checkCmake(), checkCuda(), checkMsvc()]);
8067
+ const env = buildEnv(toolchainDirs);
8068
+ const tools = await Promise.all([checkGit(env), checkCmake(env), checkCuda(env), checkMsvc(env)]);
7999
8069
  return { supported: true, tools };
8000
8070
  }
8001
8071
 
8072
+ // src/engines/build-runner.ts
8073
+ import { execFile as execFile8, spawn as spawn4 } from "child_process";
8074
+ import { copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync7, readdirSync as readdirSync5, rmSync as rmSync8, writeFileSync as writeFileSync7 } from "fs";
8075
+ import { delimiter as delimiter2, dirname as dirname6, join as join17 } from "path";
8076
+ import { promisify as promisify5 } from "util";
8077
+ var execFileP5 = promisify5(execFile8);
8078
+ function buildDirName(repoUrl, branch) {
8079
+ const last = repoUrl.trim().replace(/\/+$/, "").split(/[\\/]/).pop() ?? "";
8080
+ const repo = last.replace(/\.git$/i, "").trim() || "engine";
8081
+ const b = (branch ?? "").trim();
8082
+ const raw = b ? `${repo}-${b}` : repo;
8083
+ return raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "engine";
8084
+ }
8085
+ function sameRepo(a, b) {
8086
+ const norm = (s) => (s ?? "").trim().toLowerCase().replace(/^https?:\/\//, "").replace(/^github\.com\//, "").replace(/\.git$/, "").replace(/\/+$/, "");
8087
+ const na = norm(a);
8088
+ return na !== "" && na === norm(b);
8089
+ }
8090
+ function sourceBuildBinary(enginesRoot, repoUrl, branch) {
8091
+ const root = join17(enginesRoot, "build", buildDirName(repoUrl, branch));
8092
+ return resolveServerBinary(join17(root, "build")) ?? resolveServerBinary(root);
8093
+ }
8094
+ function sourceBuildDirOf(binPath, enginesRoot) {
8095
+ const m = binPath.replace(/\\/g, "/").match(/\/engines\/build\/([^/]+)(?:\/|$)/i);
8096
+ if (!m) return null;
8097
+ return join17(enginesRoot, "build", m[1]);
8098
+ }
8099
+ function pickGenerator(hasNinja) {
8100
+ return hasNinja ? "Ninja" : "NMake Makefiles";
8101
+ }
8102
+ var CMAKE_CONFIGURE_ARGS = ["-DGGML_CUDA=ON", "-DCMAKE_BUILD_TYPE=Release"];
8103
+ function vcvarsBatch(vcvars, cmakeArgs) {
8104
+ const quoted = cmakeArgs.map((a) => /[\s"]/.test(a) ? `"${a}"` : a).join(" ");
8105
+ return [
8106
+ "@echo off",
8107
+ `call "${vcvars}" x64`,
8108
+ "if errorlevel 1 exit /b 1",
8109
+ `cmake ${quoted}`,
8110
+ "exit /b %errorlevel%",
8111
+ ""
8112
+ ].join("\r\n");
8113
+ }
8114
+ function onPath(env, exe) {
8115
+ const key = Object.keys(env).find((k) => k.toLowerCase() === "path") ?? "PATH";
8116
+ return (env[key] ?? "").split(delimiter2).filter(Boolean).some((dir) => {
8117
+ try {
8118
+ return existsSync15(join17(dir, exe));
8119
+ } catch {
8120
+ return false;
8121
+ }
8122
+ });
8123
+ }
8124
+ var CUDA_RUNTIME_DLL_PREFIXES = ["cudart64_", "cublas64_", "cublaslt64_", "nvrtc64_", "nvrtc-builtins64_", "nvjitlink_"];
8125
+ function cudaDllSourceDirs(env) {
8126
+ const key = Object.keys(env).find((k) => k.toLowerCase() === "path") ?? "PATH";
8127
+ const pathDirs = (env[key] ?? "").split(delimiter2).filter(Boolean);
8128
+ const nvccDir = pathDirs.find((dir) => {
8129
+ try {
8130
+ return existsSync15(join17(dir, "nvcc.exe"));
8131
+ } catch {
8132
+ return false;
8133
+ }
8134
+ });
8135
+ if (!nvccDir) return [];
8136
+ return [nvccDir, join17(nvccDir, "x64")].filter((d) => {
8137
+ try {
8138
+ return existsSync15(d);
8139
+ } catch {
8140
+ return false;
8141
+ }
8142
+ });
8143
+ }
8144
+ function copyCudaRuntimeDlls(env, destDir, log) {
8145
+ const sources = cudaDllSourceDirs(env);
8146
+ if (sources.length === 0) {
8147
+ log("Note: could not find the CUDA toolkit DLLs to bundle \u2014 the engine may need the CUDA bin on PATH to run.");
8148
+ return 0;
8149
+ }
8150
+ const seen = /* @__PURE__ */ new Set();
8151
+ let copied = 0;
8152
+ for (const dir of sources) {
8153
+ let names;
8154
+ try {
8155
+ names = readdirSync5(dir);
8156
+ } catch {
8157
+ continue;
8158
+ }
8159
+ for (const name of names) {
8160
+ const lower = name.toLowerCase();
8161
+ if (!lower.endsWith(".dll")) continue;
8162
+ if (seen.has(lower)) continue;
8163
+ if (!CUDA_RUNTIME_DLL_PREFIXES.some((p) => lower.startsWith(p))) continue;
8164
+ try {
8165
+ copyFileSync(join17(dir, name), join17(destDir, name));
8166
+ seen.add(lower);
8167
+ copied++;
8168
+ } catch {
8169
+ }
8170
+ }
8171
+ }
8172
+ if (copied > 0) log(`Bundled ${copied} CUDA runtime DLL(s) next to the binary so the engine is self-contained.`);
8173
+ return copied;
8174
+ }
8175
+ async function findVcvarsall() {
8176
+ try {
8177
+ const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
8178
+ const vswhere = join17(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
8179
+ const { stdout } = await execFileP5(
8180
+ vswhere,
8181
+ ["-latest", "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-find", "VC\\Auxiliary\\Build\\vcvarsall.bat"],
8182
+ { timeout: 8e3, windowsHide: true, maxBuffer: 1024 * 1024 }
8183
+ );
8184
+ const p = stdout.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
8185
+ return p && existsSync15(p) ? p : null;
8186
+ } catch {
8187
+ return null;
8188
+ }
8189
+ }
8190
+ function runStep(cmd, args, opts) {
8191
+ return new Promise((resolve2, reject) => {
8192
+ const child = spawn4(cmd, args, {
8193
+ cwd: opts.cwd,
8194
+ env: opts.env,
8195
+ signal: opts.signal,
8196
+ windowsHide: true,
8197
+ stdio: ["ignore", "pipe", "pipe"]
8198
+ });
8199
+ let stdout = "";
8200
+ let outBuf = "";
8201
+ let errBuf = "";
8202
+ const pump = (chunk, isErr) => {
8203
+ if (!isErr) stdout += chunk;
8204
+ let buf = isErr ? errBuf + chunk : outBuf + chunk;
8205
+ let nl;
8206
+ while ((nl = buf.indexOf("\n")) >= 0) {
8207
+ opts.onLine(buf.slice(0, nl).replace(/\r$/, ""));
8208
+ buf = buf.slice(nl + 1);
8209
+ }
8210
+ if (isErr) errBuf = buf;
8211
+ else outBuf = buf;
8212
+ };
8213
+ child.stdout.setEncoding("utf8");
8214
+ child.stderr.setEncoding("utf8");
8215
+ child.stdout.on("data", (c) => pump(c, false));
8216
+ child.stderr.on("data", (c) => pump(c, true));
8217
+ child.on("error", (e) => reject(e));
8218
+ child.on("close", (code) => {
8219
+ if (outBuf) opts.onLine(outBuf.replace(/\r$/, ""));
8220
+ if (errBuf) opts.onLine(errBuf.replace(/\r$/, ""));
8221
+ if (code === 0) resolve2(stdout);
8222
+ else reject(new Error(`${cmd} exited with code ${code}`));
8223
+ });
8224
+ });
8225
+ }
8226
+ function hasAssemblySources(dir) {
8227
+ const SKIP = /* @__PURE__ */ new Set([".git", "build", "node_modules", ".cache", "vendor"]);
8228
+ const stack = [dir];
8229
+ while (stack.length) {
8230
+ const d = stack.pop();
8231
+ let entries;
8232
+ try {
8233
+ entries = readdirSync5(d, { withFileTypes: true });
8234
+ } catch {
8235
+ continue;
8236
+ }
8237
+ for (const e of entries) {
8238
+ if (e.isDirectory()) {
8239
+ if (!SKIP.has(e.name.toLowerCase()) && !e.name.startsWith(".")) stack.push(join17(d, e.name));
8240
+ } else if (/\.(s|asm)$/i.test(e.name)) {
8241
+ return true;
8242
+ }
8243
+ }
8244
+ }
8245
+ return false;
8246
+ }
8247
+ function stripGenericAsmLanguage(text) {
8248
+ let changed = false;
8249
+ const out = text.split(/\r?\n/).map((line) => {
8250
+ if (/\bproject\s*\(/i.test(line) && /\bASM\b/.test(line)) {
8251
+ const next = line.replace(/\s+ASM\b/g, "");
8252
+ if (next !== line) {
8253
+ changed = true;
8254
+ return next;
8255
+ }
8256
+ }
8257
+ if (/^\s*enable_language\s*\(\s*ASM\s*\)/i.test(line)) {
8258
+ changed = true;
8259
+ return `# ${line} # neutralized by TurboLLM (no assembly sources)`;
8260
+ }
8261
+ return line;
8262
+ });
8263
+ return { text: out.join("\n"), changed };
8264
+ }
8265
+ function neutralizeGratuitousAsm(srcDir, log) {
8266
+ if (hasAssemblySources(srcDir)) return;
8267
+ const candidates = [join17(srcDir, "CMakeLists.txt"), join17(srcDir, "ggml", "CMakeLists.txt")];
8268
+ let patched = 0;
8269
+ for (const file of candidates) {
8270
+ if (!existsSync15(file)) continue;
8271
+ let text;
8272
+ try {
8273
+ text = readFileSync7(file, "utf8");
8274
+ } catch {
8275
+ continue;
8276
+ }
8277
+ const { text: next, changed } = stripGenericAsmLanguage(text);
8278
+ if (changed) {
8279
+ try {
8280
+ writeFileSync7(file, next);
8281
+ patched++;
8282
+ } catch {
8283
+ }
8284
+ }
8285
+ }
8286
+ if (patched > 0) {
8287
+ log(
8288
+ "Removed an unused CMake ASM-language declaration (the repo ships no assembly sources) so the build works with MSVC \u2014 this fork enables ASM gratuitously, which modern CMake rejects with MSVC."
8289
+ );
8290
+ }
8291
+ }
8292
+ async function runBuild(req, hooks, signal) {
8293
+ const env = { ...buildEnv(req.toolchainDirs), GIT_TERMINAL_PROMPT: "0", GCM_INTERACTIVE: "never" };
8294
+ hooks.phase("preparing");
8295
+ const prereqs = await checkBuildPrereqs(req.toolchainDirs);
8296
+ if (!prereqs.supported) throw new Error("In-app build is currently Windows + CUDA only.");
8297
+ const missing = prereqs.tools.filter((t) => (t.id === "git" || t.id === "cmake" || t.id === "cuda") && !t.found);
8298
+ if (missing.length > 0) {
8299
+ const names = missing.map((t) => t.name).join(", ");
8300
+ throw new Error(
8301
+ `Missing build prerequisite(s): ${names}. Install them, or \u2014 if they live in a conda env / custom path \u2014 add that folder under Build environment so TurboLLM can find them.`
8302
+ );
8303
+ }
8304
+ const vcvars = await findVcvarsall();
8305
+ if (!vcvars) {
8306
+ throw new Error(
8307
+ 'Could not locate the Visual Studio C++ build environment (vcvarsall.bat). Install the "Desktop development with C++" workload from the Visual Studio Build Tools.'
8308
+ );
8309
+ }
8310
+ const buildRoot = join17(req.enginesRoot, "build", buildDirName(req.repoUrl, req.branch));
8311
+ const srcDir = join17(buildRoot, "src");
8312
+ const buildSubdir = join17(buildRoot, "build");
8313
+ rmSync8(buildRoot, { recursive: true, force: true });
8314
+ mkdirSync10(buildRoot, { recursive: true });
8315
+ hooks.phase("cloning");
8316
+ const cloneArgs = ["clone", "--depth", "1"];
8317
+ if ((req.branch ?? "").trim()) cloneArgs.push("--branch", req.branch.trim());
8318
+ cloneArgs.push(req.repoUrl, srcDir);
8319
+ await runStep("git", cloneArgs, { env, signal, onLine: hooks.log });
8320
+ const commit = (await runStep("git", ["-C", srcDir, "rev-parse", "HEAD"], { env, signal, onLine: () => {
8321
+ } })).trim();
8322
+ neutralizeGratuitousAsm(srcDir, hooks.log);
8323
+ const generator = pickGenerator(onPath(env, "ninja.exe"));
8324
+ hooks.log(
8325
+ generator === "Ninja" ? "Using the Ninja generator (drives nvcc directly \u2014 no Visual Studio CUDA integration needed)." : "Ninja not found on PATH \u2014 using the NMake generator (works, but single-threaded and slower; add a folder containing ninja.exe under Build environment for much faster builds)."
8326
+ );
8327
+ hooks.phase("configuring");
8328
+ const configureBat = join17(buildRoot, "_tllm_configure.bat");
8329
+ writeFileSync7(configureBat, vcvarsBatch(vcvars, ["-G", generator, "-B", buildSubdir, "-S", srcDir, ...CMAKE_CONFIGURE_ARGS]));
8330
+ await runStep("cmd.exe", ["/c", configureBat], { cwd: buildRoot, env, signal, onLine: hooks.log });
8331
+ hooks.phase("compiling");
8332
+ const compileBat = join17(buildRoot, "_tllm_build.bat");
8333
+ writeFileSync7(compileBat, vcvarsBatch(vcvars, ["--build", buildSubdir, "-j", "--target", "llama-server"]));
8334
+ await runStep("cmd.exe", ["/c", compileBat], { cwd: buildRoot, env, signal, onLine: hooks.log });
8335
+ hooks.phase("registering");
8336
+ const binPath = resolveServerBinary(buildSubdir) ?? resolveServerBinary(buildRoot);
8337
+ if (!binPath) {
8338
+ throw new Error("Build finished but no llama-server binary was found in the output \u2014 see the log above.");
8339
+ }
8340
+ copyCudaRuntimeDlls(env, dirname6(binPath), hooks.log);
8341
+ return { binPath, commit, buildRoot };
8342
+ }
8343
+
8344
+ // src/engines/cuda-provision.ts
8345
+ import { execFile as execFile9 } from "child_process";
8346
+ import { cpSync as cpSync2, existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, rmSync as rmSync9 } from "fs";
8347
+ import { tmpdir } from "os";
8348
+ import { join as join18 } from "path";
8349
+ import { promisify as promisify6 } from "util";
8350
+ var execFileP6 = promisify6(execFile9);
8351
+ var REDIST_BASE = "https://developer.download.nvidia.com/compute/cuda/redist/";
8352
+ var CUDA_REQUIRED_COMPONENTS = ["cuda_nvcc", "cuda_cudart", "libcublas", "cuda_cccl"];
8353
+ var CUDA_OPTIONAL_COMPONENTS = ["libnvvm", "cuda_crt"];
8354
+ var KNOWN_CUDA_VERSIONS = ["13.0.1", "13.0.0", "12.8.1", "12.6.3", "12.4.1"];
8355
+ function majorMinor(v) {
8356
+ const p = v.split(".");
8357
+ return `${p[0] ?? "0"}.${p[1] ?? "0"}`;
8358
+ }
8359
+ function cmpMajorMinor(a, b) {
8360
+ const [am, an] = a.split(".").map(Number);
8361
+ const [bm, bn] = b.split(".").map(Number);
8362
+ return am !== bm ? am - bm : (an || 0) - (bn || 0);
8363
+ }
8364
+ async function driverMaxCuda() {
8365
+ try {
8366
+ const { stdout } = await execFileP6("nvidia-smi", [], { timeout: 8e3, windowsHide: true, maxBuffer: 1024 * 1024 });
8367
+ const m = stdout.match(/CUDA Version:\s*([\d.]+)/i);
8368
+ return m ? m[1] : null;
8369
+ } catch {
8370
+ return null;
8371
+ }
8372
+ }
8373
+ function pickCudaVersion(driverMax, known = KNOWN_CUDA_VERSIONS) {
8374
+ if (!driverMax) return known[0];
8375
+ const dm = majorMinor(driverMax);
8376
+ return known.find((v) => cmpMajorMinor(majorMinor(v), dm) <= 0) ?? known[known.length - 1];
8377
+ }
8378
+ async function fetchManifest(version2, signal) {
8379
+ const res = await fetch(`${REDIST_BASE}redistrib_${version2}.json`, { redirect: "follow", signal });
8380
+ if (!res.ok) return null;
8381
+ return await res.json();
8382
+ }
8383
+ async function resolveVersion(driverMax, signal) {
8384
+ const preferred = pickCudaVersion(driverMax);
8385
+ const ordered = [preferred, ...KNOWN_CUDA_VERSIONS.filter((v) => v !== preferred)];
8386
+ for (const version2 of ordered) {
8387
+ const manifest = await fetchManifest(version2, signal);
8388
+ if (manifest) return { version: version2, manifest };
8389
+ }
8390
+ throw new Error("Could not reach NVIDIA to resolve a CUDA Toolkit version (offline?).");
8391
+ }
8392
+ async function provisionCuda(dataDir, onProgress, signal) {
8393
+ if (process.platform !== "win32") {
8394
+ throw new Error("Automatic CUDA download is currently Windows x86_64 only.");
8395
+ }
8396
+ onProgress({ phase: "resolving", message: "Checking your NVIDIA driver and resolving a CUDA version\u2026" });
8397
+ const driverMax = await driverMaxCuda();
8398
+ const { version: version2, manifest } = await resolveVersion(driverMax, signal);
8399
+ onProgress({
8400
+ phase: "resolving",
8401
+ message: `Using CUDA ${version2}${driverMax ? ` (driver supports up to ${driverMax})` : ""}.`
8402
+ });
8403
+ const root = join18(dataDir, "cuda", version2);
8404
+ const binDir = join18(root, "bin");
8405
+ if (existsSync16(join18(binDir, "nvcc.exe"))) {
8406
+ onProgress({ phase: "assembling", message: `CUDA ${version2} already downloaded \u2014 reusing it.` });
8407
+ return { root, binDir, version: version2 };
8408
+ }
8409
+ const relOf = (id) => manifest[id]?.["windows-x86_64"]?.relative_path;
8410
+ const components = [];
8411
+ for (const id of CUDA_REQUIRED_COMPONENTS) {
8412
+ const rel = relOf(id);
8413
+ if (!rel) throw new Error(`CUDA ${version2} manifest is missing ${id} for windows-x86_64.`);
8414
+ components.push({ id, url: REDIST_BASE + rel });
8415
+ }
8416
+ for (const id of CUDA_OPTIONAL_COMPONENTS) {
8417
+ const rel = relOf(id);
8418
+ if (rel) components.push({ id, url: REDIST_BASE + rel });
8419
+ }
8420
+ const work = join18(tmpdir(), `tllm-cuda-${version2}`);
8421
+ rmSync9(work, { recursive: true, force: true });
8422
+ mkdirSync11(work, { recursive: true });
8423
+ const stageRoot = `${root}.partial`;
8424
+ rmSync9(stageRoot, { recursive: true, force: true });
8425
+ mkdirSync11(stageRoot, { recursive: true });
8426
+ try {
8427
+ for (let i = 0; i < components.length; i++) {
8428
+ const { id, url } = components[i];
8429
+ const zip = join18(work, `${id}.zip`);
8430
+ onProgress({ phase: "downloading", message: `Downloading ${id} (${i + 1}/${components.length})\u2026`, pct: 0 });
8431
+ await downloadFile(url, zip, (p) => onProgress({ phase: "downloading", message: `Downloading ${id} (${i + 1}/${components.length})\u2026`, pct: p.pct }), signal);
8432
+ const exDir = join18(work, id);
8433
+ mkdirSync11(exDir, { recursive: true });
8434
+ onProgress({ phase: "extracting", message: `Extracting ${id}\u2026` });
8435
+ await extractArchive(zip, exDir);
8436
+ const inner = readdirSync6(exDir).map((n) => join18(exDir, n)).find((p) => existsSync16(join18(p, "include")) || existsSync16(join18(p, "bin")) || existsSync16(join18(p, "lib")));
8437
+ const srcRoot = inner ?? join18(exDir, readdirSync6(exDir)[0] ?? "");
8438
+ cpSync2(srcRoot, stageRoot, { recursive: true, force: true });
8439
+ rmSync9(zip, { force: true });
8440
+ }
8441
+ if (!existsSync16(join18(stageRoot, "bin", "nvcc.exe"))) {
8442
+ throw new Error("Assembled CUDA toolkit is missing bin/nvcc.exe \u2014 the component set may have changed.");
8443
+ }
8444
+ rmSync9(root, { recursive: true, force: true });
8445
+ mkdirSync11(join18(dataDir, "cuda"), { recursive: true });
8446
+ cpSync2(stageRoot, root, { recursive: true, force: true });
8447
+ onProgress({ phase: "assembling", message: `CUDA ${version2} ready.` });
8448
+ return { root, binDir, version: version2 };
8449
+ } finally {
8450
+ rmSync9(work, { recursive: true, force: true });
8451
+ rmSync9(stageRoot, { recursive: true, force: true });
8452
+ }
8453
+ }
8454
+
8002
8455
  // src/engines/hardware.ts
8003
8456
  function detectHardware(info = getSysInfo()) {
8004
8457
  const arch2 = process.arch === "arm64" ? "arm64" : "x64";
@@ -8106,6 +8559,8 @@ function registerApi(app2, d) {
8106
8559
  bench: d.bench.status(),
8107
8560
  downloads: { active: d.downloads.activeCount() },
8108
8561
  engineProvision: d.provision.get(),
8562
+ // In-app compile-from-source status (ADR-100): live phase + log tail while a build runs.
8563
+ engineBuild: d.build.get(),
8109
8564
  // ComfyUI GPU coordination: lets the UI explain a paused/unloaded engine.
8110
8565
  // Also expose the installed gate node version so the UI can prompt an upgrade.
8111
8566
  comfyui: (() => {
@@ -8115,7 +8570,7 @@ function registerApi(app2, d) {
8115
8570
  let installedVersion = null;
8116
8571
  if (gatePath) {
8117
8572
  try {
8118
- const src = readFileSync7(join17(gatePath, "__init__.py"), "utf-8");
8573
+ const src = readFileSync8(join19(gatePath, "__init__.py"), "utf-8");
8119
8574
  const m = src.match(/^GATE_VERSION\s*=\s*(\d+)/m);
8120
8575
  if (m) installedVersion = Number(m[1]);
8121
8576
  } catch {
@@ -8132,28 +8587,34 @@ function registerApi(app2, d) {
8132
8587
  return c.json({ engines, activeEngineId });
8133
8588
  });
8134
8589
  let provisionAbort = null;
8590
+ let buildAbort = null;
8135
8591
  app2.get("/api/v1/engines/backends", (c) => {
8136
8592
  const sys = getSysInfo();
8137
8593
  const vendor = primaryVendor(sys);
8138
8594
  const recommended = recommendBackendId(vendor, sys.gpus.length > 0);
8139
- const root = join17(d.store.dir(), "engines");
8595
+ const root = join19(d.store.dir(), "engines");
8140
8596
  const active = d.registry.active();
8141
8597
  const regEngines = d.registry.list().engines;
8598
+ const backendOf = (p) => p.match(/llama\.cpp-[^/\\]+-(cuda|rocm|sycl|vulkan|metal|cpu)/i)?.[1]?.toLowerCase();
8599
+ const buildNumOf = (p) => {
8600
+ const m = p.match(/llama\.cpp-b(\d+)/i);
8601
+ return m ? Number(m[1]) : -1;
8602
+ };
8142
8603
  const backends = availableBackends().map((b) => {
8143
- const bin = installedBackendServer(root, b.id);
8144
- const eng = bin ? regEngines.find((e) => e.binPath === bin) : void 0;
8604
+ const builds = regEngines.filter((e) => backendOf(e.binPath) === b.id).sort((x, y) => buildNumOf(y.binPath) - buildNumOf(x.binPath));
8605
+ const eng = builds[0];
8145
8606
  return {
8146
8607
  id: b.id,
8147
8608
  label: b.label,
8148
- installed: !!bin,
8609
+ installed: !!eng,
8149
8610
  enabled: !!eng,
8150
8611
  recommended: b.id === recommended,
8151
- active: !!bin && !!active && active.binPath === bin,
8612
+ active: !!eng && !!active && active.id === eng.id,
8152
8613
  engineId: eng?.id ?? ""
8153
8614
  };
8154
8615
  });
8155
- const mlxVenvPy = join17(root, "mlx", "venv", process.platform === "win32" ? "Scripts/python.exe" : "bin/python");
8156
- const mlxInstalledOnDisk = existsSync15(mlxVenvPy);
8616
+ const mlxVenvPy = join19(root, "mlx", "venv", process.platform === "win32" ? "Scripts/python.exe" : "bin/python");
8617
+ const mlxInstalledOnDisk = existsSync17(mlxVenvPy);
8157
8618
  const mlxEngine = regEngines.find((e) => e.kind === "mlx");
8158
8619
  const mlx = {
8159
8620
  supported: process.platform === "darwin",
@@ -8168,11 +8629,15 @@ function registerApi(app2, d) {
8168
8629
  const b = await body2(c);
8169
8630
  const def = availableBackends().find((x) => x.id === b.backend);
8170
8631
  if (!def) return err2(c, 400, "invalid_config_value", "Unknown backend for this platform.");
8171
- const root = join17(d.store.dir(), "engines");
8172
- if (installedBackendServer(root, def.id)) {
8173
- return c.json({ accepted: false, alreadyInstalled: true, build: LLAMA_BUILD });
8632
+ const root = join19(d.store.dir(), "engines");
8633
+ const existing = installedBackendBuild(root, def.id);
8634
+ if (existing) {
8635
+ return c.json({ accepted: false, alreadyInstalled: true, build: existing.tag });
8636
+ }
8637
+ {
8638
+ const busy = engineWorkBusy(d);
8639
+ if (busy) return err2(c, 409, "engine_already_running", busy);
8174
8640
  }
8175
- if (d.provision.get().active) return err2(c, 409, "engine_already_running", "Another engine download is already in progress.");
8176
8641
  const ac = new AbortController();
8177
8642
  provisionAbort = ac;
8178
8643
  void (async () => {
@@ -8208,10 +8673,13 @@ function registerApi(app2, d) {
8208
8673
  app2.post("/api/v1/engines/backends/:id/update", async (c) => {
8209
8674
  const def = availableBackends().find((x) => x.id === c.req.param("id"));
8210
8675
  if (!def) return err2(c, 400, "invalid_config_value", "Unknown backend for this platform.");
8211
- const root = join17(d.store.dir(), "engines");
8212
- const oldBin = installedBackendServer(root, def.id);
8676
+ const root = join19(d.store.dir(), "engines");
8677
+ const oldBin = installedBackendBuild(root, def.id)?.bin ?? null;
8213
8678
  if (!oldBin) return err2(c, 409, "not_installed", "Backend is not installed \u2014 download it first.");
8214
- if (d.provision.get().active) return err2(c, 409, "engine_already_running", "Another engine download is already in progress.");
8679
+ {
8680
+ const busy = engineWorkBusy(d);
8681
+ if (busy) return err2(c, 409, "engine_already_running", busy);
8682
+ }
8215
8683
  let latestTag;
8216
8684
  try {
8217
8685
  latestTag = await latestReleaseTag("ggml-org/llama.cpp", AbortSignal.timeout(15e3));
@@ -8249,7 +8717,7 @@ function registerApi(app2, d) {
8249
8717
  }
8250
8718
  }
8251
8719
  if (installedTag && installedTag !== latestTag) {
8252
- rmSync8(backendDir(root, def.id, installedTag), { recursive: true, force: true });
8720
+ rmSync10(backendDir(root, def.id, installedTag), { recursive: true, force: true });
8253
8721
  }
8254
8722
  d.provision.done();
8255
8723
  } catch (e) {
@@ -8264,31 +8732,40 @@ function registerApi(app2, d) {
8264
8732
  app2.post("/api/v1/engines/backends/:id/enable", async (c) => {
8265
8733
  const def = availableBackends().find((x) => x.id === c.req.param("id"));
8266
8734
  if (!def) return err2(c, 400, "invalid_config_value", "Unknown backend for this platform.");
8267
- const root = join17(d.store.dir(), "engines");
8268
- const bin = installedBackendServer(root, def.id);
8269
- if (!bin) return err2(c, 409, "not_installed", "Backend is not installed on disk \u2014 download it first.");
8270
- let eng = d.registry.list().engines.find((e) => e.binPath === bin);
8271
- if (!eng) eng = (await d.registry.add(`llama.cpp ${LLAMA_BUILD} (${def.id})`, bin)).engine;
8735
+ const root = join19(d.store.dir(), "engines");
8736
+ const build2 = installedBackendBuild(root, def.id);
8737
+ if (!build2) return err2(c, 409, "not_installed", "Backend is not installed on disk \u2014 download it first.");
8738
+ let eng = d.registry.list().engines.find((e) => e.binPath === build2.bin);
8739
+ if (!eng) eng = (await d.registry.add(`llama.cpp ${build2.tag} (${def.id})`, build2.bin)).engine;
8272
8740
  d.registry.activate(eng.id);
8273
8741
  return c.json({ ok: true, engineId: eng.id });
8274
8742
  });
8275
8743
  app2.delete("/api/v1/engines/backends/:id", async (c) => {
8276
8744
  const def = availableBackends().find((x) => x.id === c.req.param("id"));
8277
8745
  if (!def) return err2(c, 400, "invalid_config_value", "Unknown backend for this platform.");
8278
- const root = join17(d.store.dir(), "engines");
8279
- const bin = installedBackendServer(root, def.id);
8280
- const eng = bin ? d.registry.list().engines.find((e) => e.binPath === bin) : void 0;
8281
- if (eng && d.registry.active()?.id === eng.id) await d.manager.stopAndWait();
8282
- if (eng) d.registry.remove(eng.id);
8283
- deleteBackend(root, def.id, LLAMA_BUILD);
8746
+ const root = join19(d.store.dir(), "engines");
8747
+ const backendDirRe = new RegExp(`[\\\\/]engines[\\\\/]llama\\.cpp-[^\\\\/]+-${def.id}[\\\\/]`);
8748
+ const engs = d.registry.list().engines.filter((e) => backendDirRe.test(e.binPath));
8749
+ const activeId = d.registry.active()?.id;
8750
+ if (engs.some((e) => e.id === activeId)) await d.manager.stopAndWait();
8751
+ for (const e of engs) {
8752
+ try {
8753
+ d.registry.remove(e.id);
8754
+ } catch {
8755
+ }
8756
+ }
8757
+ deleteAllBackendBuilds(root, def.id);
8284
8758
  return c.json({ ok: true });
8285
8759
  });
8286
8760
  app2.post("/api/v1/engines/mlx", (c) => {
8287
8761
  if (process.platform !== "darwin") {
8288
8762
  return err2(c, 409, "unsupported_platform", "MLX is only available on macOS (Apple Silicon).");
8289
8763
  }
8290
- if (d.provision.get().active) return err2(c, 409, "engine_already_running", "Another engine download is already in progress.");
8291
- const root = join17(d.store.dir(), "engines");
8764
+ {
8765
+ const busy = engineWorkBusy(d);
8766
+ if (busy) return err2(c, 409, "engine_already_running", busy);
8767
+ }
8768
+ const root = join19(d.store.dir(), "engines");
8292
8769
  const upgrade = c.req.query("update") === "1";
8293
8770
  void (async () => {
8294
8771
  try {
@@ -8305,32 +8782,51 @@ function registerApi(app2, d) {
8305
8782
  });
8306
8783
  app2.get("/api/v1/engines/catalog", (c) => {
8307
8784
  const regEngines = d.registry.list().engines;
8308
- const enginesRoot = join17(d.store.dir(), "engines");
8785
+ const enginesRoot = join19(d.store.dir(), "engines");
8309
8786
  const items = catalogForPlatform().map((e) => {
8310
8787
  let installed;
8311
8788
  let enabled;
8312
8789
  if (e.provision === "pip") {
8313
8790
  const venvSubdir = e.id === "mlx" ? "mlx" : "vllm";
8314
- const pyPath = join17(
8791
+ const pyPath = join19(
8315
8792
  enginesRoot,
8316
8793
  venvSubdir,
8317
8794
  "venv",
8318
8795
  process.platform === "win32" ? "Scripts/python.exe" : "bin/python"
8319
8796
  );
8320
- installed = existsSync15(pyPath);
8797
+ installed = existsSync17(pyPath);
8321
8798
  enabled = regEngines.some((x) => x.kind === e.kind);
8322
8799
  } else if (e.id === "turboquant") {
8323
- const tqDir = join17(enginesRoot, "turboquant");
8324
- installed = existsSync15(tqDir);
8800
+ const tqDir = join19(enginesRoot, "turboquant");
8801
+ installed = existsSync17(tqDir);
8325
8802
  enabled = regEngines.some((x) => /[\\/]engines[\\/]turboquant[\\/]/.test(x.binPath));
8326
8803
  } else if (e.id === "koboldcpp") {
8327
- installed = existsSync15(koboldcppBinPath(enginesRoot));
8804
+ installed = existsSync17(koboldcppBinPath(enginesRoot));
8328
8805
  enabled = regEngines.some((x) => x.kind === "koboldcpp");
8329
8806
  } else if (e.id === "llamafile") {
8330
- installed = existsSync15(llamafileBinPath(enginesRoot));
8807
+ installed = existsSync17(llamafileBinPath(enginesRoot));
8331
8808
  enabled = regEngines.some((x) => x.kind === "llamafile");
8332
8809
  }
8333
- return { ...e, installed, enabled };
8810
+ const srcEng = regEngines.find((x) => sameRepo(x.sourceRepo, e.homepage));
8811
+ let sourceBinPath = srcEng?.binPath;
8812
+ if (!srcEng) sourceBinPath = sourceBuildBinary(enginesRoot, e.homepage) ?? void 0;
8813
+ const sourceBuilt = !!srcEng || !!sourceBinPath;
8814
+ if (srcEng) {
8815
+ installed = true;
8816
+ enabled = true;
8817
+ } else if (sourceBinPath) {
8818
+ installed = true;
8819
+ enabled = enabled ?? false;
8820
+ }
8821
+ return {
8822
+ ...e,
8823
+ installed,
8824
+ enabled,
8825
+ sourceBuilt,
8826
+ sourceEngineId: srcEng?.id,
8827
+ sourceBranch: srcEng?.sourceBranch ?? "",
8828
+ sourceBinPath: sourceBinPath ?? ""
8829
+ };
8334
8830
  });
8335
8831
  return c.json({ engines: items });
8336
8832
  });
@@ -8339,10 +8835,131 @@ function registerApi(app2, d) {
8339
8835
  const rec = recommendEngines(hw, catalogForPlatform().map(({ supportedHere, ...e }) => e));
8340
8836
  return c.json({ hardware: hw, recommendation: rec });
8341
8837
  });
8342
- app2.get("/api/v1/build/prereqs", async (c) => c.json(await checkBuildPrereqs()));
8838
+ app2.get(
8839
+ "/api/v1/build/prereqs",
8840
+ async (c) => c.json(await checkBuildPrereqs(d.store.snapshot().build.toolchainDirs))
8841
+ );
8842
+ app2.post("/api/v1/build/run", async (c) => {
8843
+ if (!isLocalRequest(c, d))
8844
+ return err2(c, 403, "forbidden", "Building an engine is only available on the machine running TurboLLM.");
8845
+ if (process.platform !== "win32")
8846
+ return err2(c, 409, "unsupported_platform", "In-app build is currently Windows + CUDA only.");
8847
+ const b = await body2(c);
8848
+ const repoUrl = (b.repoUrl ?? "").trim();
8849
+ if (!/^https?:\/\//i.test(repoUrl))
8850
+ return err2(c, 400, "invalid_config_value", "A http(s) repo URL is required.");
8851
+ const branch = (b.branch ?? "").trim() || void 0;
8852
+ if (branch && branch.startsWith("-"))
8853
+ return err2(c, 400, "invalid_config_value", 'Branch name cannot start with "-".');
8854
+ const busy = engineWorkBusy(d);
8855
+ if (busy) return err2(c, 409, "engine_already_running", busy);
8856
+ const name = (b.name ?? "").trim() || void 0;
8857
+ const enginesRoot = join19(d.store.dir(), "engines");
8858
+ const toolchainDirs = d.store.snapshot().build.toolchainDirs;
8859
+ const buildRoot = join19(enginesRoot, "build", buildDirName(repoUrl, branch));
8860
+ const ac = new AbortController();
8861
+ buildAbort = ac;
8862
+ d.build.start(name ?? repoUrl);
8863
+ void (async () => {
8864
+ try {
8865
+ let out;
8866
+ try {
8867
+ out = await runBuild(
8868
+ { repoUrl, branch, enginesRoot, toolchainDirs },
8869
+ { phase: (p) => d.build.phase(p), log: (line) => d.build.log(line) },
8870
+ ac.signal
8871
+ );
8872
+ } catch (e) {
8873
+ try {
8874
+ rmSync10(buildRoot, { recursive: true, force: true });
8875
+ } catch {
8876
+ }
8877
+ if (e?.name === "AbortError") {
8878
+ d.build.log("Build cancelled.");
8879
+ d.build.fail("Build cancelled.");
8880
+ } else {
8881
+ const asmIssue = d.build.get().log.some((l) => /No CMAKE_ASM_COMPILER could be found|ASM compiler identification is unknown/i.test(l));
8882
+ const raw = e instanceof Error ? e.message : String(e);
8883
+ d.build.fail(
8884
+ asmIssue ? "This fork needs a GAS/MinGW assembler (it enables CMake's generic ASM language), which isn't available with the MSVC toolchain \u2014 so it can't be built here on Windows. Use a prebuilt binary, or build it under MSYS2/MinGW and add it via \u201CAdd your own engine.\u201D" : raw
8885
+ );
8886
+ }
8887
+ return;
8888
+ }
8889
+ const engines = d.registry.list().engines;
8890
+ const prior = engines.find((e) => e.binPath === out.binPath) ?? engines.find((e) => e.sourceRepo === repoUrl && (e.sourceBranch ?? "") === (branch ?? ""));
8891
+ if (prior) {
8892
+ if (d.registry.active()?.id === prior.id) await d.manager.stopAndWait();
8893
+ try {
8894
+ d.registry.remove(prior.id);
8895
+ } catch {
8896
+ }
8897
+ }
8898
+ const eng = (await d.registry.add(prior?.name ?? name ?? "", out.binPath, {
8899
+ sourceRepo: repoUrl,
8900
+ sourceBranch: branch
8901
+ })).engine;
8902
+ d.registry.activate(eng.id);
8903
+ d.build.log(`Registered "${eng.name}" \u2014 built from ${out.commit.slice(0, 8)}.`);
8904
+ d.build.done();
8905
+ } catch (e) {
8906
+ d.build.fail(`Built successfully, but couldn't register the engine: ${e instanceof Error ? e.message : String(e)}`);
8907
+ } finally {
8908
+ if (buildAbort === ac) buildAbort = null;
8909
+ }
8910
+ })();
8911
+ return c.json({ accepted: true }, 202);
8912
+ });
8913
+ app2.post("/api/v1/build/cancel", (c) => {
8914
+ if (buildAbort) {
8915
+ buildAbort.abort();
8916
+ return c.json({ ok: true });
8917
+ }
8918
+ return c.json({ ok: false });
8919
+ });
8920
+ app2.post("/api/v1/build/cuda", async (c) => {
8921
+ if (!isLocalRequest(c, d))
8922
+ return err2(c, 403, "forbidden", "Downloading CUDA is only available on the machine running TurboLLM.");
8923
+ if (process.platform !== "win32")
8924
+ return err2(c, 409, "unsupported_platform", "Automatic CUDA download is currently Windows x86_64 only.");
8925
+ const busy = engineWorkBusy(d);
8926
+ if (busy) return err2(c, 409, "engine_already_running", busy);
8927
+ const dataDir = d.store.dir();
8928
+ const ac = new AbortController();
8929
+ buildAbort = ac;
8930
+ d.build.start("CUDA Toolkit");
8931
+ d.build.phase("provisioning");
8932
+ void (async () => {
8933
+ try {
8934
+ const tk = await provisionCuda(
8935
+ dataDir,
8936
+ (p) => d.build.log(p.pct != null ? `${p.message} ${Math.round(p.pct * 100)}%` : p.message),
8937
+ ac.signal
8938
+ );
8939
+ d.store.update((cfg2) => {
8940
+ if (!cfg2.build.toolchainDirs.includes(tk.binDir)) cfg2.build.toolchainDirs.unshift(tk.binDir);
8941
+ });
8942
+ d.build.log(`CUDA ${tk.version} installed and added to your build environment.`);
8943
+ d.build.done();
8944
+ } catch (e) {
8945
+ if (e?.name === "AbortError") {
8946
+ d.build.log("CUDA download cancelled.");
8947
+ d.build.fail("CUDA download cancelled.");
8948
+ } else {
8949
+ d.build.fail(`Could not download CUDA: ${e instanceof Error ? e.message : String(e)}`);
8950
+ }
8951
+ } finally {
8952
+ if (buildAbort === ac) buildAbort = null;
8953
+ }
8954
+ })();
8955
+ return c.json({ accepted: true }, 202);
8956
+ });
8343
8957
  app2.post("/api/v1/engines/vllm", (c) => {
8344
- if (d.provision.get().active) return err2(c, 409, "engine_already_running", "Another engine download is already in progress.");
8345
- const root = join17(d.store.dir(), "engines");
8958
+ {
8959
+ const busy = engineWorkBusy(d);
8960
+ if (busy) return err2(c, 409, "engine_already_running", busy);
8961
+ }
8962
+ const root = join19(d.store.dir(), "engines");
8346
8963
  const upgrade = c.req.query("update") === "1";
8347
8964
  void (async () => {
8348
8965
  try {
@@ -8363,15 +8980,18 @@ function registerApi(app2, d) {
8363
8980
  if (!entry.platforms.includes(process.platform)) {
8364
8981
  return err2(c, 409, "unsupported_platform", "TurboQuant has no prebuilt binary for this operating system yet.");
8365
8982
  }
8366
- if (d.provision.get().active) return err2(c, 409, "engine_already_running", "Another engine download is already in progress.");
8367
- const root = join17(d.store.dir(), "engines");
8983
+ {
8984
+ const busy = engineWorkBusy(d);
8985
+ if (busy) return err2(c, 409, "engine_already_running", busy);
8986
+ }
8987
+ const root = join19(d.store.dir(), "engines");
8368
8988
  const upgrade = c.req.query("update") === "1";
8369
8989
  void (async () => {
8370
8990
  try {
8371
8991
  d.provision.start("turboquant");
8372
8992
  if (upgrade) {
8373
- const tqDir = join17(root, "turboquant");
8374
- if (existsSync15(tqDir)) rmSync8(tqDir, { recursive: true, force: true });
8993
+ const tqDir = join19(root, "turboquant");
8994
+ if (existsSync17(tqDir)) rmSync10(tqDir, { recursive: true, force: true });
8375
8995
  }
8376
8996
  const bin = await provisionTurboquant(
8377
8997
  root,
@@ -8395,8 +9015,11 @@ function registerApi(app2, d) {
8395
9015
  if (!entry.platforms.includes(process.platform)) {
8396
9016
  return err2(c, 409, "unsupported_platform", "KoboldCpp has no prebuilt binary for this operating system yet.");
8397
9017
  }
8398
- if (d.provision.get().active) return err2(c, 409, "engine_already_running", "Another engine download is already in progress.");
8399
- const root = join17(d.store.dir(), "engines");
9018
+ {
9019
+ const busy = engineWorkBusy(d);
9020
+ if (busy) return err2(c, 409, "engine_already_running", busy);
9021
+ }
9022
+ const root = join19(d.store.dir(), "engines");
8400
9023
  const upgrade = c.req.query("update") === "1";
8401
9024
  const hasNvidia = primaryVendor(getSysInfo()) === "nvidia";
8402
9025
  void (async () => {
@@ -8404,7 +9027,7 @@ function registerApi(app2, d) {
8404
9027
  d.provision.start("koboldcpp");
8405
9028
  if (upgrade) {
8406
9029
  const dir = koboldcppDir(root);
8407
- if (existsSync15(dir)) rmSync8(dir, { recursive: true, force: true });
9030
+ if (existsSync17(dir)) rmSync10(dir, { recursive: true, force: true });
8408
9031
  }
8409
9032
  const rt = await ensureKoboldcpp(root, hasNvidia, (p) => d.provision.progress(p.phase, p.pct, p.part, p.parts));
8410
9033
  const eng = d.registry.addKoboldcpp(`KoboldCpp (${rt.version})`, rt.binPath, rt.version);
@@ -8423,15 +9046,18 @@ function registerApi(app2, d) {
8423
9046
  if (!entry.platforms.includes(process.platform)) {
8424
9047
  return err2(c, 409, "unsupported_platform", "llamafile is not available for this operating system yet.");
8425
9048
  }
8426
- if (d.provision.get().active) return err2(c, 409, "engine_already_running", "Another engine download is already in progress.");
8427
- const root = join17(d.store.dir(), "engines");
9049
+ {
9050
+ const busy = engineWorkBusy(d);
9051
+ if (busy) return err2(c, 409, "engine_already_running", busy);
9052
+ }
9053
+ const root = join19(d.store.dir(), "engines");
8428
9054
  const upgrade = c.req.query("update") === "1";
8429
9055
  void (async () => {
8430
9056
  try {
8431
9057
  d.provision.start("llamafile");
8432
9058
  if (upgrade) {
8433
9059
  const dir = llamafileDir(root);
8434
- if (existsSync15(dir)) rmSync8(dir, { recursive: true, force: true });
9060
+ if (existsSync17(dir)) rmSync10(dir, { recursive: true, force: true });
8435
9061
  }
8436
9062
  const rt = await ensureLlamafile(root, (p) => d.provision.progress(p.phase, p.pct, p.part, p.parts));
8437
9063
  const eng = d.registry.addLlamafile(`llamafile (${rt.version})`, rt.binPath, rt.version);
@@ -8511,10 +9137,10 @@ function registerApi(app2, d) {
8511
9137
  const eng = d.registry.get(id);
8512
9138
  d.registry.remove(id);
8513
9139
  if (purge && eng) {
8514
- const enginesRoot = join17(d.store.dir(), "engines");
9140
+ const enginesRoot = join19(d.store.dir(), "engines");
8515
9141
  const purgeDir = engineInstallDir(eng, enginesRoot);
8516
- if (purgeDir && existsSync15(purgeDir)) {
8517
- rmSync8(purgeDir, { recursive: true, force: true });
9142
+ if (purgeDir && existsSync17(purgeDir)) {
9143
+ rmSync10(purgeDir, { recursive: true, force: true });
8518
9144
  }
8519
9145
  }
8520
9146
  return c.json({ ok: true });
@@ -8594,21 +9220,21 @@ function registerApi(app2, d) {
8594
9220
  }
8595
9221
  let entries;
8596
9222
  try {
8597
- entries = readdirSync5(real, { withFileTypes: true }).filter((d2) => !d2.name.startsWith(".")).map((d2) => {
9223
+ entries = readdirSync7(real, { withFileTypes: true }).filter((d2) => !d2.name.startsWith(".")).map((d2) => {
8598
9224
  let isDir = d2.isDirectory();
8599
9225
  if (d2.isSymbolicLink()) {
8600
9226
  try {
8601
- isDir = statSync5(join17(real, d2.name)).isDirectory();
9227
+ isDir = statSync5(join19(real, d2.name)).isDirectory();
8602
9228
  } catch {
8603
9229
  isDir = false;
8604
9230
  }
8605
9231
  }
8606
- return { name: d2.name, path: join17(real, d2.name), isDir };
9232
+ return { name: d2.name, path: join19(real, d2.name), isDir };
8607
9233
  }).sort((a, b) => a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1);
8608
9234
  } catch {
8609
9235
  return err2(c, 400, "fs_read_failed", "Could not read that folder (permission denied or not a directory).");
8610
9236
  }
8611
- const parentDir = real === home ? null : dirname6(real);
9237
+ const parentDir = real === home ? null : dirname7(real);
8612
9238
  const parent = parentDir && isWithinHome(parentDir, home) ? parentDir : null;
8613
9239
  return c.json({ path: real, parent, entries });
8614
9240
  });
@@ -8703,22 +9329,22 @@ function registerApi(app2, d) {
8703
9329
  const raw = (b.path ?? "").trim();
8704
9330
  if (!raw) return err2(c, 400, "invalid_config_value", "Enter the path to your ComfyUI folder.");
8705
9331
  const root = resolve(raw);
8706
- if (!existsSync15(root) || !statSync5(root).isDirectory()) {
9332
+ if (!existsSync17(root) || !statSync5(root).isDirectory()) {
8707
9333
  return err2(c, 400, "invalid_config_value", "That folder does not exist.");
8708
9334
  }
8709
9335
  let customNodes;
8710
9336
  if (basename3(root).toLowerCase() === "custom_nodes") customNodes = root;
8711
- else if (existsSync15(join17(root, "custom_nodes"))) customNodes = join17(root, "custom_nodes");
9337
+ else if (existsSync17(join19(root, "custom_nodes"))) customNodes = join19(root, "custom_nodes");
8712
9338
  else {
8713
9339
  return err2(c, 400, "invalid_config_value", "No 'custom_nodes' folder here. Point me at your ComfyUI folder or its custom_nodes folder.");
8714
9340
  }
8715
9341
  const reqUrl = new URL(c.req.url);
8716
9342
  const port2 = reqUrl.port || (reqUrl.protocol === "https:" ? "443" : "80");
8717
9343
  const base2 = `http://127.0.0.1:${port2}`;
8718
- const gateDir = join17(customNodes, "turbollm_gate");
9344
+ const gateDir = join19(customNodes, "turbollm_gate");
8719
9345
  try {
8720
- mkdirSync10(gateDir, { recursive: true });
8721
- writeFileSync7(join17(gateDir, "__init__.py"), gateNodeSource(base2));
9346
+ mkdirSync12(gateDir, { recursive: true });
9347
+ writeFileSync8(join19(gateDir, "__init__.py"), gateNodeSource(base2));
8722
9348
  } catch (e) {
8723
9349
  return err2(c, 500, "fs_write_failed", `Could not write the gate node: ${e instanceof Error ? e.message : e}`);
8724
9350
  }
@@ -8729,9 +9355,9 @@ function registerApi(app2, d) {
8729
9355
  });
8730
9356
  app2.post("/api/v1/comfyui/uninstall", (c) => {
8731
9357
  const dir = d.store.snapshot().comfyui.gatePath;
8732
- if (dir && existsSync15(dir)) {
9358
+ if (dir && existsSync17(dir)) {
8733
9359
  try {
8734
- rmSync8(dir, { recursive: true, force: true });
9360
+ rmSync10(dir, { recursive: true, force: true });
8735
9361
  } catch (e) {
8736
9362
  return err2(c, 500, "fs_write_failed", `Could not remove the gate node: ${e instanceof Error ? e.message : e}`);
8737
9363
  }
@@ -8764,8 +9390,8 @@ function registerApi(app2, d) {
8764
9390
  });
8765
9391
  while (!aborted) {
8766
9392
  const path = d.manager.logPath();
8767
- if (path && existsSync15(path)) {
8768
- const lines = readFileSync7(path, "utf8").split("\n");
9393
+ if (path && existsSync17(path)) {
9394
+ const lines = readFileSync8(path, "utf8").split("\n");
8769
9395
  for (; sent < lines.length - 1; sent++) {
8770
9396
  await stream.writeSSE({ event: "line", data: JSON.stringify({ line: lines[sent].replace(/\r$/, "") }) });
8771
9397
  }
@@ -8938,12 +9564,26 @@ function registerApi(app2, d) {
8938
9564
  if (!Number.isInteger(v) || v < 1 || v > 4) return err2(c, 400, "invalid_config_value", "gateway.keepN must be 1\u20134.");
8939
9565
  gwUpdates.keepN = v;
8940
9566
  }
9567
+ let toolchainDirs;
9568
+ if (b.build?.toolchainDirs !== void 0) {
9569
+ if (!Array.isArray(b.build.toolchainDirs)) {
9570
+ return err2(c, 400, "invalid_config_value", "build.toolchainDirs must be an array of paths.");
9571
+ }
9572
+ const dirs = b.build.toolchainDirs.map((p) => String(p).trim()).filter(Boolean);
9573
+ for (const dir of dirs) {
9574
+ if (!/^([a-zA-Z]:[\\/]|[\\/])/.test(dir)) {
9575
+ return err2(c, 400, "invalid_config_value", `build.toolchainDirs: "${dir}" must be an absolute path.`);
9576
+ }
9577
+ }
9578
+ toolchainDirs = dirs;
9579
+ }
8941
9580
  const before = d.store.snapshot().daemon;
8942
9581
  d.store.update((cfg2) => {
8943
9582
  Object.assign(cfg2.daemon, updates2);
8944
9583
  Object.assign(cfg2.modelDefaults, mdUpdates);
8945
9584
  Object.assign(cfg2.comfyui, cuUpdates);
8946
9585
  Object.assign(cfg2.gateway, gwUpdates);
9586
+ if (toolchainDirs !== void 0) cfg2.build.toolchainDirs = toolchainDirs;
8947
9587
  if (b.autoLoadOnStart !== void 0) cfg2.autoLoadOnStart = !!b.autoLoadOnStart;
8948
9588
  if (telemetryLevel !== void 0) cfg2.telemetry.level = telemetryLevel;
8949
9589
  if (b.hfToken !== void 0) cfg2.hf.token = String(b.hfToken).trim();
@@ -9047,7 +9687,7 @@ function registerApi(app2, d) {
9047
9687
  const b = await body2(c);
9048
9688
  const dir = (b.dir ?? "").trim();
9049
9689
  if (!dir || !/^([a-zA-Z]:[\\/]|[\\/])/.test(dir)) return err2(c, 400, "invalid_config_value", "Path must be absolute.");
9050
- if (!existsSync15(dir)) return err2(c, 400, "invalid_config_value", "That folder does not exist.");
9690
+ if (!existsSync17(dir)) return err2(c, 400, "invalid_config_value", "That folder does not exist.");
9051
9691
  try {
9052
9692
  d.store.update((cfg2) => {
9053
9693
  if (!cfg2.modelDirs.includes(dir)) cfg2.modelDirs.push(dir);
@@ -9277,7 +9917,10 @@ function settingsPayload(d) {
9277
9917
  kagiKeySet: !!cfg2.tools.search?.kagiApiKey,
9278
9918
  searxngUrl: cfg2.tools.search?.searxngUrl ?? ""
9279
9919
  },
9280
- mcp: cfg2.mcp
9920
+ mcp: cfg2.mcp,
9921
+ // Build environment (ADR-100): folders prepended to PATH for compile-from-source so a
9922
+ // conda-env / custom-path CUDA Toolkit + compiler are found. Not secret — echoed back.
9923
+ build: { toolchainDirs: cfg2.build.toolchainDirs }
9281
9924
  };
9282
9925
  }
9283
9926
  function telemetryPreview(level, version2) {
@@ -9332,6 +9975,11 @@ function localCountFor(d, repo) {
9332
9975
  }
9333
9976
  return n;
9334
9977
  }
9978
+ function engineWorkBusy(d) {
9979
+ if (d.provision.get().active) return "An engine download is in progress \u2014 wait for it to finish.";
9980
+ if (d.build.isActive()) return "A build is in progress \u2014 wait for it to finish.";
9981
+ return null;
9982
+ }
9335
9983
  function hfErr(c, e) {
9336
9984
  if (e instanceof HfError) {
9337
9985
  const status = e.code === "hf_unauthorized" ? 401 : e.code === "hf_gated" ? 403 : e.code === "hf_not_found" ? 404 : 503;
@@ -9369,15 +10017,15 @@ function engineInstallDir(eng, enginesRoot) {
9369
10017
  return n.startsWith(norm + "/") || n.startsWith(norm + "\\");
9370
10018
  };
9371
10019
  if (eng.kind === "mlx") {
9372
- const d = join17(enginesRoot, "mlx", "venv");
10020
+ const d = join19(enginesRoot, "mlx", "venv");
9373
10021
  return inside(d) ? d : null;
9374
10022
  }
9375
10023
  if (eng.kind === "vllm") {
9376
- const d = join17(enginesRoot, "vllm", "venv");
10024
+ const d = join19(enginesRoot, "vllm", "venv");
9377
10025
  return inside(d) ? d : null;
9378
10026
  }
9379
10027
  if (/[\\/]engines[\\/]turboquant[\\/]/.test(eng.binPath)) {
9380
- const d = join17(enginesRoot, "turboquant");
10028
+ const d = join19(enginesRoot, "turboquant");
9381
10029
  return inside(d) ? d : null;
9382
10030
  }
9383
10031
  if (eng.kind === "koboldcpp") {
@@ -9388,6 +10036,13 @@ function engineInstallDir(eng, enginesRoot) {
9388
10036
  const d = llamafileDir(enginesRoot);
9389
10037
  return inside(d) ? d : null;
9390
10038
  }
10039
+ const srcDir = sourceBuildDirOf(eng.binPath, enginesRoot);
10040
+ if (srcDir) return inside(srcDir) ? srcDir : null;
10041
+ const llamaMatch = eng.binPath.replace(/\\/g, "/").match(/\/(llama\.cpp-[^/]+)\//i);
10042
+ if (llamaMatch) {
10043
+ const d = join19(enginesRoot, llamaMatch[1]);
10044
+ return inside(d) ? d : null;
10045
+ }
9391
10046
  return null;
9392
10047
  }
9393
10048
  function deriveModel(modelPath, name, extraArgs) {
@@ -9401,9 +10056,9 @@ function cleanModelName(p) {
9401
10056
  return basename3(p).replace(/\.gguf$/i, "");
9402
10057
  }
9403
10058
  function readTail2(path, n) {
9404
- if (!path || !existsSync15(path)) return [];
10059
+ if (!path || !existsSync17(path)) return [];
9405
10060
  try {
9406
- const lines = readFileSync7(path, "utf8").replace(/[\r\n]+$/, "").split("\n").map((l) => l.replace(/\r$/, ""));
10061
+ const lines = readFileSync8(path, "utf8").replace(/[\r\n]+$/, "").split("\n").map((l) => l.replace(/\r$/, ""));
9407
10062
  return lines.length > n ? lines.slice(-n) : lines;
9408
10063
  } catch {
9409
10064
  return [];
@@ -10094,7 +10749,7 @@ async function recordOpenAiStreamUsage(d, body3) {
10094
10749
 
10095
10750
  // src/server.ts
10096
10751
  setGlobalDispatcher(new Agent({ keepAliveMaxTimeout: 6e4, connections: 10 }));
10097
- var WEB_ROOT = join18(dirname7(fileURLToPath(import.meta.url)), "webdist");
10752
+ var WEB_ROOT = join20(dirname8(fileURLToPath(import.meta.url)), "webdist");
10098
10753
  function createApp(d) {
10099
10754
  const app2 = new Hono();
10100
10755
  app2.use(
@@ -10119,12 +10774,12 @@ function createApp(d) {
10119
10774
  if (path.startsWith("api/") || path.startsWith("v1/")) {
10120
10775
  return c.json({ error: { code: "not_found", message: "Unknown endpoint." } }, 404);
10121
10776
  }
10122
- let file = normalize2(join18(WEB_ROOT, path || "index.html"));
10123
- if (!file.startsWith(WEB_ROOT) || !existsSync16(file) || statSync6(file).isDirectory()) {
10124
- file = join18(WEB_ROOT, "index.html");
10777
+ let file = normalize2(join20(WEB_ROOT, path || "index.html"));
10778
+ if (!file.startsWith(WEB_ROOT) || !existsSync18(file) || statSync6(file).isDirectory()) {
10779
+ file = join20(WEB_ROOT, "index.html");
10125
10780
  }
10126
- if (!existsSync16(file)) return c.text("web ui not built \u2014 run `npm run build:web`", 500);
10127
- return new Response(readFileSync8(file), { status: 200, headers: { "Content-Type": contentType(file) } });
10781
+ if (!existsSync18(file)) return c.text("web ui not built \u2014 run `npm run build:web`", 500);
10782
+ return new Response(readFileSync9(file), { status: 200, headers: { "Content-Type": contentType(file) } });
10128
10783
  });
10129
10784
  return app2;
10130
10785
  }
@@ -10151,8 +10806,8 @@ function contentType(file) {
10151
10806
  // src/cli.ts
10152
10807
  var version = "0.1.1";
10153
10808
  try {
10154
- const pkgPath = join19(dirname8(fileURLToPath2(import.meta.url)), "..", "package.json");
10155
- version = JSON.parse(readFileSync9(pkgPath, "utf8")).version ?? version;
10809
+ const pkgPath = join21(dirname9(fileURLToPath2(import.meta.url)), "..", "package.json");
10810
+ version = JSON.parse(readFileSync10(pkgPath, "utf8")).version ?? version;
10156
10811
  } catch {
10157
10812
  }
10158
10813
  var nodeMajor = Number(process.versions.node.split(".")[0]);
@@ -10227,8 +10882,9 @@ var registry = new Registry(store);
10227
10882
  var pruned = registry.pruneDeadManagedBuilds();
10228
10883
  if (pruned > 0) console.log(`pruned ${pruned} dangling engine build(s)`);
10229
10884
  var provision = new ProvisionState();
10885
+ var build = new BuildState();
10230
10886
  var updates = new UpdateChecker();
10231
- var enginesDir = join19(store.dir(), "engines");
10887
+ var enginesDir = join21(store.dir(), "engines");
10232
10888
  void seedDefaultEngines(registry, enginesDir, provision).then(() => registry.ensureProbed());
10233
10889
  var manager = new Manager(store);
10234
10890
  var scanner = new Scanner(store);
@@ -10248,7 +10904,7 @@ void (async () => {
10248
10904
  })();
10249
10905
  var startedAt = Date.now();
10250
10906
  var appUpdates = new AppUpdateChecker(version);
10251
- var deps = { store, registry, manager, scanner, hashes, db, provision, updates, appUpdates, hf, downloads, bench, modelRouter, comfy, tools: toolRegistry, version, startedAt };
10907
+ var deps = { store, registry, manager, scanner, hashes, db, provision, build, updates, appUpdates, hf, downloads, bench, modelRouter, comfy, tools: toolRegistry, version, startedAt };
10252
10908
  var app = createApp(deps);
10253
10909
  setTimeout(() => void appUpdates.check(AbortSignal.timeout(1e4)).catch(() => {
10254
10910
  }), 5e3).unref();
@@ -10288,7 +10944,7 @@ function openBrowser(url) {
10288
10944
  cmd = "xdg-open";
10289
10945
  args = [url];
10290
10946
  }
10291
- const child = spawn4(cmd, args, { detached: true, stdio: "ignore" });
10947
+ const child = spawn5(cmd, args, { detached: true, stdio: "ignore" });
10292
10948
  child.unref();
10293
10949
  child.on("error", () => {
10294
10950
  console.log(` Could not open browser automatically. Visit the URL above manually.`);
@@ -10375,11 +11031,11 @@ var restarting = false;
10375
11031
  function spawnReplacement() {
10376
11032
  let out = "ignore";
10377
11033
  try {
10378
- out = openSync3(join19(store.dir(), "restart.log"), "a");
11034
+ out = openSync3(join21(store.dir(), "restart.log"), "a");
10379
11035
  } catch {
10380
11036
  out = "ignore";
10381
11037
  }
10382
- const child = spawn4(process.execPath, process.argv.slice(1), {
11038
+ const child = spawn5(process.execPath, process.argv.slice(1), {
10383
11039
  cwd: process.cwd(),
10384
11040
  detached: true,
10385
11041
  stdio: ["ignore", out, out]