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.
- package/README.md +1 -1
- package/dist/cli.js +809 -153
- package/dist/webdist/assets/{ChatScreen-Di2U5guk.js → ChatScreen-DY2flMe9.js} +1 -1
- package/dist/webdist/assets/{CustomizeScreen-C1N3VSzu.js → CustomizeScreen-Cl6H68Sy.js} +4 -4
- package/dist/webdist/assets/DeveloperScreen-zC1nePXh.js +1 -0
- package/dist/webdist/assets/EnginesScreen-CrsziC3B.js +7 -0
- package/dist/webdist/assets/ModelDetailDialog-BOvumdHI.js +1 -0
- package/dist/webdist/assets/{ModelDirs-z0eidPHf.js → ModelDirs-Bj-XrX0c.js} +1 -1
- package/dist/webdist/assets/ModelsScreen-BTir-kDY.js +1 -0
- package/dist/webdist/assets/SettingsScreen-B696egCa.js +1 -0
- package/dist/webdist/assets/check-DFwkcl_5.js +1 -0
- package/dist/webdist/assets/{circle-x-nfGRpYmv.js → circle-x-CsuAddI_.js} +1 -1
- package/dist/webdist/assets/{common-VLx-PnhW.js → common-DIXPIrUd.js} +1 -1
- package/dist/webdist/assets/{copy-button-DvxLdCBa.js → copy-button-BtQZA6P8.js} +1 -1
- package/dist/webdist/assets/{dialog-BQ-Ai22D.js → dialog-Dpyml8ZT.js} +1 -1
- package/dist/webdist/assets/index-BklQn62W.css +1 -0
- package/dist/webdist/assets/{index-BGye3qtk.js → index-sjgdofsd.js} +7 -7
- package/dist/webdist/assets/{pencil-fITP-Ux2.js → pencil-BuQwoNyj.js} +1 -1
- package/dist/webdist/assets/{personas-Cq8LxYil.js → personas-CQapYHeK.js} +1 -1
- package/dist/webdist/assets/{plus-nMHxYBqJ.js → plus-B1w15FoZ.js} +1 -1
- package/dist/webdist/assets/{radix-C-SVreIl.js → radix-BFvMxQq9.js} +1 -1
- package/dist/webdist/assets/refresh-cw-BoAcjLtt.js +1 -0
- package/dist/webdist/assets/save-DE1APWTG.js +1 -0
- package/dist/webdist/assets/{skeleton-CSAyV68N.js → skeleton-Dvq8tYzC.js} +1 -1
- package/dist/webdist/assets/{sparkles-Cs6GG6EW.js → sparkles-C54kYWeQ.js} +1 -1
- package/dist/webdist/assets/{trash-2-BdOxLswi.js → trash-2-DIMVgidH.js} +1 -1
- package/dist/webdist/index.html +3 -3
- package/package.json +1 -1
- package/dist/webdist/assets/DeveloperScreen-DWf0TZqq.js +0 -1
- package/dist/webdist/assets/EnginesScreen-Dqy-jlqZ.js +0 -5
- package/dist/webdist/assets/ModelDetailDialog-BRKjfp4G.js +0 -1
- package/dist/webdist/assets/ModelsScreen-DqIQ4ST-.js +0 -1
- package/dist/webdist/assets/SettingsScreen-DXp8YTPJ.js +0 -1
- package/dist/webdist/assets/check-BnpDXpw8.js +0 -1
- package/dist/webdist/assets/external-link-CO5laD7p.js +0 -1
- package/dist/webdist/assets/index-Bx0PAdfC.css +0 -1
- 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
|
|
5
|
-
import { openSync as openSync3, readFileSync as
|
|
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
|
|
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 [
|
|
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
|
|
395
|
-
|
|
396
|
-
|
|
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
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
6416
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
6357
6417
|
import { fileURLToPath } from "url";
|
|
6358
|
-
import { dirname as
|
|
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
|
|
6364
|
-
import { basename as basename3, dirname as
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
8144
|
-
const eng =
|
|
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: !!
|
|
8609
|
+
installed: !!eng,
|
|
8149
8610
|
enabled: !!eng,
|
|
8150
8611
|
recommended: b.id === recommended,
|
|
8151
|
-
active: !!
|
|
8612
|
+
active: !!eng && !!active && active.id === eng.id,
|
|
8152
8613
|
engineId: eng?.id ?? ""
|
|
8153
8614
|
};
|
|
8154
8615
|
});
|
|
8155
|
-
const mlxVenvPy =
|
|
8156
|
-
const mlxInstalledOnDisk =
|
|
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 =
|
|
8172
|
-
|
|
8173
|
-
|
|
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 =
|
|
8212
|
-
const oldBin =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
8268
|
-
const
|
|
8269
|
-
if (!
|
|
8270
|
-
let eng = d.registry.list().engines.find((e) => e.binPath === bin);
|
|
8271
|
-
if (!eng) eng = (await d.registry.add(`llama.cpp ${
|
|
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 =
|
|
8279
|
-
const
|
|
8280
|
-
const
|
|
8281
|
-
|
|
8282
|
-
if (
|
|
8283
|
-
|
|
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
|
-
|
|
8291
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8797
|
+
installed = existsSync17(pyPath);
|
|
8321
8798
|
enabled = regEngines.some((x) => x.kind === e.kind);
|
|
8322
8799
|
} else if (e.id === "turboquant") {
|
|
8323
|
-
const tqDir =
|
|
8324
|
-
installed =
|
|
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 =
|
|
8804
|
+
installed = existsSync17(koboldcppBinPath(enginesRoot));
|
|
8328
8805
|
enabled = regEngines.some((x) => x.kind === "koboldcpp");
|
|
8329
8806
|
} else if (e.id === "llamafile") {
|
|
8330
|
-
installed =
|
|
8807
|
+
installed = existsSync17(llamafileBinPath(enginesRoot));
|
|
8331
8808
|
enabled = regEngines.some((x) => x.kind === "llamafile");
|
|
8332
8809
|
}
|
|
8333
|
-
|
|
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(
|
|
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
|
-
|
|
8345
|
-
|
|
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
|
-
|
|
8367
|
-
|
|
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 =
|
|
8374
|
-
if (
|
|
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
|
-
|
|
8399
|
-
|
|
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 (
|
|
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
|
-
|
|
8427
|
-
|
|
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 (
|
|
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 =
|
|
9140
|
+
const enginesRoot = join19(d.store.dir(), "engines");
|
|
8515
9141
|
const purgeDir = engineInstallDir(eng, enginesRoot);
|
|
8516
|
-
if (purgeDir &&
|
|
8517
|
-
|
|
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 =
|
|
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(
|
|
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:
|
|
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 :
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
9344
|
+
const gateDir = join19(customNodes, "turbollm_gate");
|
|
8719
9345
|
try {
|
|
8720
|
-
|
|
8721
|
-
|
|
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 &&
|
|
9358
|
+
if (dir && existsSync17(dir)) {
|
|
8733
9359
|
try {
|
|
8734
|
-
|
|
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 &&
|
|
8768
|
-
const lines =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 || !
|
|
10059
|
+
if (!path || !existsSync17(path)) return [];
|
|
9405
10060
|
try {
|
|
9406
|
-
const lines =
|
|
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 =
|
|
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(
|
|
10123
|
-
if (!file.startsWith(WEB_ROOT) || !
|
|
10124
|
-
file =
|
|
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 (!
|
|
10127
|
-
return new Response(
|
|
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 =
|
|
10155
|
-
version = JSON.parse(
|
|
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 =
|
|
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 =
|
|
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(
|
|
11034
|
+
out = openSync3(join21(store.dir(), "restart.log"), "a");
|
|
10379
11035
|
} catch {
|
|
10380
11036
|
out = "ignore";
|
|
10381
11037
|
}
|
|
10382
|
-
const child =
|
|
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]
|