turbollm 1.13.5 → 1.13.7
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/dist/cli.js +266 -125
- package/dist/webdist/assets/{AgentEditPage-DQSQhhdM.js → AgentEditPage-E7HtOsup.js} +1 -1
- package/dist/webdist/assets/{ChatScreen-CtFHffeH.js → ChatScreen-dEouSpAv.js} +1 -1
- package/dist/webdist/assets/{CodeComposer-v8HSPbTb.js → CodeComposer-CkqctDdp.js} +1 -1
- package/dist/webdist/assets/{CodeHomeScreen-BRjo9YC3.js → CodeHomeScreen-W_lZHAT2.js} +1 -1
- package/dist/webdist/assets/{CodeSessionScreen-nXHx5Rkf.js → CodeSessionScreen-BX1Eaqga.js} +1 -1
- package/dist/webdist/assets/{CustomizeScreen-bSsL1Dj0.js → CustomizeScreen-BszOveS0.js} +1 -1
- package/dist/webdist/assets/EnginesScreen-C3Tr39oE.js +10 -0
- package/dist/webdist/assets/{HardwareSection-a31KOorO.js → HardwareSection-Cvzgs-RM.js} +1 -1
- package/dist/webdist/assets/{ModelDetailDialog-k3r2SJWx.js → ModelDetailDialog-CQ_x0n0p.js} +1 -1
- package/dist/webdist/assets/{ModelLoadMenu-DgXJo77N.js → ModelLoadMenu-Lir_ZeaD.js} +1 -1
- package/dist/webdist/assets/{ModelsScreen-DUTDnwez.js → ModelsScreen-BwzqukhW.js} +1 -1
- package/dist/webdist/assets/{OnboardingScreen-Hgz6i2ot.js → OnboardingScreen-Df_1EYOW.js} +1 -1
- package/dist/webdist/assets/{RoutineEditPage-ItERtPTI.js → RoutineEditPage-CdZnyBEw.js} +1 -1
- package/dist/webdist/assets/{RoutinesPanel-BjclCgWV.js → RoutinesPanel-CBOpHq9E.js} +1 -1
- package/dist/webdist/assets/{SettingsScreen-BtlVJafk.js → SettingsScreen-Bhih9go0.js} +1 -1
- package/dist/webdist/assets/{SidebarResizeHandle-DBtMs1un.js → SidebarResizeHandle-CNnIPZEp.js} +1 -1
- package/dist/webdist/assets/{SkillEditPage-BJHoMA87.js → SkillEditPage-CCtOQTmq.js} +1 -1
- package/dist/webdist/assets/{ToolApprovalBar-pMCAf9oT.js → ToolApprovalBar-PlYgG25-.js} +3 -3
- package/dist/webdist/assets/WorkspaceScreen-CqmBBbxz.js +1 -0
- package/dist/webdist/assets/{abnfDiagram-N423BO3Z-C_g2c5t9.js → abnfDiagram-N423BO3Z-DsZ93mAM.js} +1 -1
- package/dist/webdist/assets/{agent-api-DZpsdnDb.js → agent-api-BQn5nZNX.js} +1 -1
- package/dist/webdist/assets/{alert-dialog-Be0J8mqx.js → alert-dialog-D9Z7ppH_.js} +1 -1
- package/dist/webdist/assets/{arc-YFjxy_2h.js → arc-BGrHHnrw.js} +1 -1
- package/dist/webdist/assets/{architectureDiagram-T3A2C74G-1eWQR9Yo.js → architectureDiagram-T3A2C74G-Ey1UrzU-.js} +1 -1
- package/dist/webdist/assets/{blockDiagram-VBNYF7ZC-DCkxfK2C.js → blockDiagram-VBNYF7ZC-D78LM23z.js} +1 -1
- package/dist/webdist/assets/{c4Diagram-5PPSVZJV-CvkqNRPH.js → c4Diagram-5PPSVZJV-CvPfXzDr.js} +1 -1
- package/dist/webdist/assets/channel-DCuz_wdC.js +1 -0
- package/dist/webdist/assets/{chat-api-C84sO1C9.js → chat-api-BKO4BTWe.js} +1 -1
- package/dist/webdist/assets/{chat-queries--trWZzdu.js → chat-queries-CqiPDqGV.js} +1 -1
- package/dist/webdist/assets/{chevron-down-BvP7K1yU.js → chevron-down-SLGilXKT.js} +1 -1
- package/dist/webdist/assets/{chevron-left-wCh6MmEl.js → chevron-left-CU5U4gIb.js} +1 -1
- package/dist/webdist/assets/{chevron-right-BC1tvE2N.js → chevron-right-BY4anLEq.js} +1 -1
- package/dist/webdist/assets/{chunk-2GRJ4B5K-MwqLqeQo.js → chunk-2GRJ4B5K-P3cu6Bc0.js} +1 -1
- package/dist/webdist/assets/{chunk-2Q5K7J3B-B1JqT0Ms.js → chunk-2Q5K7J3B-BgP7AimG.js} +1 -1
- package/dist/webdist/assets/{chunk-5RXB4S5H-BT__I3gy.js → chunk-5RXB4S5H-B-FXWALA.js} +1 -1
- package/dist/webdist/assets/{chunk-5VM5RSS4-CCCHTmAY.js → chunk-5VM5RSS4-C-lQ75_C.js} +1 -1
- package/dist/webdist/assets/{chunk-6Q2QTUOP-DpdhRs5e.js → chunk-6Q2QTUOP-Cv-tXjKv.js} +1 -1
- package/dist/webdist/assets/{chunk-GF5L2VYU-CqvwSdm0.js → chunk-GF5L2VYU-C3sxcn3E.js} +1 -1
- package/dist/webdist/assets/{chunk-JWPE2WC7-XlQN0MBh.js → chunk-JWPE2WC7-C-AvRcoC.js} +1 -1
- package/dist/webdist/assets/{chunk-KBJHAD2P-BtIxJswZ.js → chunk-KBJHAD2P-DMDEB7Sy.js} +1 -1
- package/dist/webdist/assets/{chunk-RYQCIY6F-deUgJS0Q.js → chunk-RYQCIY6F-DX_4YbYG.js} +1 -1
- package/dist/webdist/assets/{chunk-XXDRQBXY-38CMPJj8.js → chunk-XXDRQBXY-C-C12qlP.js} +1 -1
- package/dist/webdist/assets/{circle-check-BIhBTR1T.js → circle-check-D0IMfrYq.js} +1 -1
- package/dist/webdist/assets/{circle-x-DM6fYSRv.js → circle-x-pqL2JhJm.js} +1 -1
- package/dist/webdist/assets/classDiagram-JCYQIIEL-DFsr_nh-.js +1 -0
- package/dist/webdist/assets/classDiagram-v2-OCEON4UE-DFsr_nh-.js +1 -0
- package/dist/webdist/assets/{collapsible-Dej2BQVn.js → collapsible-Bwm3DfUf.js} +1 -1
- package/dist/webdist/assets/{cose-bilkent-JH36ORCC-B_BQCrmS.js → cose-bilkent-JH36ORCC-0_jV9L5K.js} +1 -1
- package/dist/webdist/assets/{cynefin-VYW2F7L2-lGt_QLfe.js → cynefin-VYW2F7L2-D-cQNJqC.js} +1 -1
- package/dist/webdist/assets/{cynefinDiagram-MW4NZA55-B172ktgP.js → cynefinDiagram-MW4NZA55-QRp_3yKa.js} +1 -1
- package/dist/webdist/assets/{dagre-VZM6K2ZE-De4fl1iF.js → dagre-VZM6K2ZE-DYHS_FuW.js} +1 -1
- package/dist/webdist/assets/{diagram-7IWD3JNH-DtUW-Boo.js → diagram-7IWD3JNH-js1EUUk6.js} +1 -1
- package/dist/webdist/assets/{diagram-B4RE2ZJO-B6_p4VKc.js → diagram-B4RE2ZJO-BHVEhFez.js} +1 -1
- package/dist/webdist/assets/{diagram-LBJQPF4R-DC_wtD7r.js → diagram-LBJQPF4R-Cc-5wAwq.js} +1 -1
- package/dist/webdist/assets/{diagram-Q27KOJAE-6oF2_OjQ.js → diagram-Q27KOJAE-jTxFyyeN.js} +1 -1
- package/dist/webdist/assets/{diagram-UB23O5K3-BLh1k_fo.js → diagram-UB23O5K3-LP8zrNnQ.js} +1 -1
- package/dist/webdist/assets/{dropdown-menu-Bhu8YtbI.js → dropdown-menu-IOTY3_-B.js} +1 -1
- package/dist/webdist/assets/{ebnfDiagram-BXEA7PRR-ehMxmR4l.js → ebnfDiagram-BXEA7PRR-qDNqlger.js} +1 -1
- package/dist/webdist/assets/{erDiagram-JOGREHBK-96Mu4CA5.js → erDiagram-JOGREHBK-owRtaAi_.js} +1 -1
- package/dist/webdist/assets/{file-text-Dn3ZCp31.js → file-text-BotUNsIW.js} +1 -1
- package/dist/webdist/assets/{flowDiagram-UKHOOZJN-CRRZZa0_.js → flowDiagram-UKHOOZJN-D26LSAka.js} +1 -1
- package/dist/webdist/assets/{folder-input-DF1gn4vH.js → folder-input-D3qEcRaK.js} +1 -1
- package/dist/webdist/assets/{folder-plus-C3110N2s.js → folder-plus-4hzyN--0.js} +1 -1
- package/dist/webdist/assets/{ganttDiagram-PKOTCBZU-CTAHvUdG.js → ganttDiagram-PKOTCBZU-FOBYQpFD.js} +1 -1
- package/dist/webdist/assets/{git-branch-ClzWfV5B.js → git-branch-CSkwfOC2.js} +1 -1
- package/dist/webdist/assets/{gitGraphDiagram-DS77QQ5N-BwUcUxwZ.js → gitGraphDiagram-DS77QQ5N-D1pwlyQx.js} +1 -1
- package/dist/webdist/assets/{globe-UxyIyIxr.js → globe-BvQjzBKb.js} +1 -1
- package/dist/webdist/assets/{index-D5gUIqLa.js → index-CXLOA4-7.js} +2 -2
- package/dist/webdist/assets/{infoDiagram-6WML65LV-KXXRYeq1.js → infoDiagram-6WML65LV-DAxebGLC.js} +1 -1
- package/dist/webdist/assets/{ishikawaDiagram-WSZJBQD7-F9eL-DH9.js → ishikawaDiagram-WSZJBQD7-DIdOYDDr.js} +1 -1
- package/dist/webdist/assets/{journeyDiagram-NVQOT4AX-Br82xXB7.js → journeyDiagram-NVQOT4AX-BLvkNn5k.js} +1 -1
- package/dist/webdist/assets/{kanban-definition-27J2QSJJ-TxuwPPK2.js → kanban-definition-27J2QSJJ-DKyJQb6a.js} +1 -1
- package/dist/webdist/assets/{linear-Dnua3zVN.js → linear-sR1aOS-1.js} +1 -1
- package/dist/webdist/assets/{lock-CIkAh19m.js → lock-K-a91AFa.js} +1 -1
- package/dist/webdist/assets/{mermaid.core-3DYUUSdD.js → mermaid.core-sVxlfu4j.js} +4 -4
- package/dist/webdist/assets/{mindmap-definition-FAOFIHXS-C2Je1XD3.js → mindmap-definition-FAOFIHXS-Yo_cYEEu.js} +1 -1
- package/dist/webdist/assets/{monitor-Dao9gtZQ.js → monitor-_13VL1du.js} +1 -1
- package/dist/webdist/assets/{pegDiagram-VL7TDLO6-Imiwx_Mi.js → pegDiagram-VL7TDLO6-BSAx7r44.js} +1 -1
- package/dist/webdist/assets/{pencil-DLPQKbE9.js → pencil-CshqlsN6.js} +1 -1
- package/dist/webdist/assets/{pieDiagram-7S7Q4E2Y-DaWNxcI-.js → pieDiagram-7S7Q4E2Y-DJDpIi_G.js} +1 -1
- package/dist/webdist/assets/{quadrantDiagram-CIZ2JOQS-DeQ1njXO.js → quadrantDiagram-CIZ2JOQS-BYUOd1Ld.js} +1 -1
- package/dist/webdist/assets/{railroadDiagram-AXF67PYL-BvJgh6Z_.js → railroadDiagram-AXF67PYL-Cp7ActLQ.js} +1 -1
- package/dist/webdist/assets/{refresh-cw-BE_VKWCq.js → refresh-cw-BgvgHNs_.js} +1 -1
- package/dist/webdist/assets/{remote-failure-z1LSyZOB.js → remote-failure-CU6FQAv2.js} +1 -1
- package/dist/webdist/assets/{requirementDiagram-LRYGKXZP-BiZx2AHw.js → requirementDiagram-LRYGKXZP-BRIn9Bao.js} +1 -1
- package/dist/webdist/assets/{rocket-CxCRl5WL.js → rocket-Bdyspsau.js} +1 -1
- package/dist/webdist/assets/{rotate-ccw-CUHjjSni.js → rotate-ccw-CXchjxoU.js} +1 -1
- package/dist/webdist/assets/{sankeyDiagram-W5VNT64P-DbDt5_xz.js → sankeyDiagram-W5VNT64P-DrfFnEda.js} +1 -1
- package/dist/webdist/assets/{sequenceDiagram-SI44F4Z6-Ya9d9pJx.js → sequenceDiagram-SI44F4Z6-BRvCwGTa.js} +1 -1
- package/dist/webdist/assets/{sheet-uLkeKdTj.js → sheet-BEHvWodM.js} +1 -1
- package/dist/webdist/assets/{sizeCapture-X5ZJPWSS-CKcIKwbb.js → sizeCapture-X5ZJPWSS-BLSbFiEe.js} +1 -1
- package/dist/webdist/assets/{skeleton-C4DVQSnO.js → skeleton-_WE_l7Ss.js} +1 -1
- package/dist/webdist/assets/{sliders-horizontal-DVdVp0P2.js → sliders-horizontal-X0H751ey.js} +1 -1
- package/dist/webdist/assets/{sparkles-daM-8UXC.js → sparkles-B57ip_p2.js} +1 -1
- package/dist/webdist/assets/{star-CpCXu8tE.js → star-DFChWJ1H.js} +1 -1
- package/dist/webdist/assets/{stateDiagram-OKZ733FA-CqU-1wd8.js → stateDiagram-OKZ733FA-dWf7zqCN.js} +1 -1
- package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-CQPsdlPu.js +1 -0
- package/dist/webdist/assets/{swimlanes-SLNWSIFB-nzSF-_JW.js → swimlanes-SLNWSIFB-lCDiXKRw.js} +2 -2
- package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-CzicNSYv.js +8 -0
- package/dist/webdist/assets/{switch-BmXyQvbr.js → switch-1qn4bGLW.js} +1 -1
- package/dist/webdist/assets/{terminal-BFz2js-b.js → terminal-BiLS4W9p.js} +1 -1
- package/dist/webdist/assets/{timeline-definition-Z64GVDOM-BFB3jSJV.js → timeline-definition-Z64GVDOM-DSxynxhf.js} +1 -1
- package/dist/webdist/assets/{trash-2-DmBQACGe.js → trash-2-JXgZx0Gm.js} +1 -1
- package/dist/webdist/assets/{vennDiagram-T6HMQDX7-cdR5hz8k.js → vennDiagram-T6HMQDX7-AFtVyBx_.js} +1 -1
- package/dist/webdist/assets/{wardleyDiagram-T6FBY63Y-1Z-jUDaw.js → wardleyDiagram-T6FBY63Y-Bi_TjYlH.js} +1 -1
- package/dist/webdist/assets/{wrench-BV5hCWSo.js → wrench-C6y9CDpZ.js} +1 -1
- package/dist/webdist/assets/{xychartDiagram-ELKLHX3M-DfGwd0jU.js → xychartDiagram-ELKLHX3M-BaREW8xI.js} +1 -1
- package/dist/webdist/index.html +1 -1
- package/package.json +1 -1
- package/dist/webdist/assets/EnginesScreen-DKIeTAXe.js +0 -10
- package/dist/webdist/assets/WorkspaceScreen-GL1Nw-SI.js +0 -1
- package/dist/webdist/assets/channel-BLq3w_Ai.js +0 -1
- package/dist/webdist/assets/classDiagram-JCYQIIEL-Bsb3bMvc.js +0 -1
- package/dist/webdist/assets/classDiagram-v2-OCEON4UE-Bsb3bMvc.js +0 -1
- package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-Erur5nvV.js +0 -1
- package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-Ds2N1ZDj.js +0 -8
package/dist/cli.js
CHANGED
|
@@ -788,7 +788,7 @@ function clampMaxTokens(requested, limit) {
|
|
|
788
788
|
}
|
|
789
789
|
|
|
790
790
|
// src/engines/manager.ts
|
|
791
|
-
import { execFile as
|
|
791
|
+
import { execFile as execFile7, spawn, spawnSync } from "child_process";
|
|
792
792
|
import { createWriteStream, existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
793
793
|
import { createConnection, createServer } from "net";
|
|
794
794
|
import { dirname as dirname2, join as join10 } from "path";
|
|
@@ -1213,10 +1213,99 @@ async function restoreSlot(p) {
|
|
|
1213
1213
|
|
|
1214
1214
|
// src/engines/vllm.ts
|
|
1215
1215
|
import { existsSync as existsSync8 } from "fs";
|
|
1216
|
-
import { execFile as
|
|
1216
|
+
import { execFile as execFile5 } from "child_process";
|
|
1217
1217
|
import { join as join8 } from "path";
|
|
1218
|
+
import { promisify as promisify5 } from "util";
|
|
1219
|
+
|
|
1220
|
+
// src/engines/py-engine-blocker.ts
|
|
1221
|
+
import { execFile as execFile4 } from "child_process";
|
|
1222
|
+
import { homedir as homedir2 } from "os";
|
|
1218
1223
|
import { promisify as promisify4 } from "util";
|
|
1219
1224
|
var execFileP4 = promisify4(execFile4);
|
|
1225
|
+
var DETAIL_LIMIT = 200;
|
|
1226
|
+
var UvloopPreflight = class {
|
|
1227
|
+
constructor(engine, homeDirectory = homedir2) {
|
|
1228
|
+
this.engine = engine;
|
|
1229
|
+
this.homeDirectory = homeDirectory;
|
|
1230
|
+
}
|
|
1231
|
+
engine;
|
|
1232
|
+
homeDirectory;
|
|
1233
|
+
async blockerFor(python) {
|
|
1234
|
+
try {
|
|
1235
|
+
await execFileP4(python, ["-c", "import uvloop"], { timeout: 2e4 });
|
|
1236
|
+
return null;
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
return this.classify(process.platform, error);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
// uvloop publishes manylinux *and* macOS wheels, so only Windows is a real platform wall —
|
|
1242
|
+
// anywhere else a failed import is a broken local environment the user can reinstall.
|
|
1243
|
+
classify(platform, error) {
|
|
1244
|
+
if (platform === "win32") return this.unsupportedPlatform();
|
|
1245
|
+
const failure = error;
|
|
1246
|
+
if (failure?.code === "ENOENT") return this.missingInterpreter();
|
|
1247
|
+
return this.brokenEnvironment(platform, failure);
|
|
1248
|
+
}
|
|
1249
|
+
unsupportedPlatform() {
|
|
1250
|
+
return `${this.engine} cannot run on Windows: its server requires uvloop (and other Linux-only components such as NCCL/Triton), which have no Windows build. Use the llama.cpp / TurboQuant engine for GGUF models here, or run ${this.engine} under WSL2 / Linux.`;
|
|
1251
|
+
}
|
|
1252
|
+
missingInterpreter() {
|
|
1253
|
+
return `${this.engine}'s environment looks missing or broken (interpreter not found). Reinstall the ${this.engine} engine from the Engines page.`;
|
|
1254
|
+
}
|
|
1255
|
+
brokenEnvironment(platform, failure) {
|
|
1256
|
+
return `${this.engine}'s environment looks broken or incomplete (uvloop failed to import: ${failureDetail(failure, this.homeOrEmpty())}). ${this.platformStanding(platform)}`;
|
|
1257
|
+
}
|
|
1258
|
+
// homedir() throws (uv_os_homedir ENOENT) for a uid with no passwd entry and no HOME, e.g. a container.
|
|
1259
|
+
homeOrEmpty() {
|
|
1260
|
+
try {
|
|
1261
|
+
return this.homeDirectory();
|
|
1262
|
+
} catch {
|
|
1263
|
+
return "";
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
// Only what is true is claimed: Linux is where these engines are officially supported, while on
|
|
1267
|
+
// macOS the catalog rates them experimental / unsupported upstream, so only uvloop's own support
|
|
1268
|
+
// may be asserted there.
|
|
1269
|
+
platformStanding(platform) {
|
|
1270
|
+
const plat = platformDisplay(platform);
|
|
1271
|
+
if (platform === "linux") {
|
|
1272
|
+
return `${plat} is a supported platform for ${this.engine} \u2014 reinstall the ${this.engine} engine from the Engines page rather than switching engines.`;
|
|
1273
|
+
}
|
|
1274
|
+
if (platform === "darwin") {
|
|
1275
|
+
return `uvloop itself supports ${plat}, so reinstall the ${this.engine} engine from the Engines page first \u2014 though ${plat} is not a first-class platform for ${this.engine} upstream.`;
|
|
1276
|
+
}
|
|
1277
|
+
return `${this.engine} support on ${plat} is unverified \u2014 reinstall the ${this.engine} engine from the Engines page; if the import keeps failing, ${this.engine} may have no working build here.`;
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
function platformDisplay(platform) {
|
|
1281
|
+
if (platform === "win32") return "Windows";
|
|
1282
|
+
if (platform === "darwin") return "macOS";
|
|
1283
|
+
if (platform === "linux") return "Linux";
|
|
1284
|
+
return platform;
|
|
1285
|
+
}
|
|
1286
|
+
function failureDetail(failure, home) {
|
|
1287
|
+
const raw = failure?.stderr ? String(failure.stderr) : failure?.message ?? "";
|
|
1288
|
+
return capped(redactHome(lastMeaningfulLine(raw), home));
|
|
1289
|
+
}
|
|
1290
|
+
function lastMeaningfulLine(text) {
|
|
1291
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1292
|
+
return lines.at(-1) ?? "unknown error";
|
|
1293
|
+
}
|
|
1294
|
+
function redactHome(text, home) {
|
|
1295
|
+
if (home.length < 2) return text;
|
|
1296
|
+
const spellings = /* @__PURE__ */ new Set([home, home.split("\\").join("/"), home.split("/").join("\\")]);
|
|
1297
|
+
const anySpelling = new RegExp([...spellings].map(escapeRegExp).join("|"), "gi");
|
|
1298
|
+
return text.replace(anySpelling, "~");
|
|
1299
|
+
}
|
|
1300
|
+
function escapeRegExp(text) {
|
|
1301
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1302
|
+
}
|
|
1303
|
+
function capped(detail) {
|
|
1304
|
+
return detail.length <= DETAIL_LIMIT ? detail : `${detail.slice(0, DETAIL_LIMIT)}\u2026`;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/engines/vllm.ts
|
|
1308
|
+
var execFileP5 = promisify5(execFile5);
|
|
1220
1309
|
var VLLM_PYTHON = "3.12";
|
|
1221
1310
|
function venvPython4(envDir) {
|
|
1222
1311
|
return process.platform === "win32" ? join8(envDir, "Scripts", "python.exe") : join8(envDir, "bin", "python");
|
|
@@ -1227,43 +1316,23 @@ async function ensureVllmEnv(root, onProgress, upgrade = false) {
|
|
|
1227
1316
|
const py = venvPython4(envDir);
|
|
1228
1317
|
if (!existsSync8(py)) {
|
|
1229
1318
|
onProgress?.({ phase: "extracting", pct: -1 });
|
|
1230
|
-
await
|
|
1319
|
+
await execFileP5(uv, ["venv", "--python", VLLM_PYTHON, envDir], { cwd: root });
|
|
1231
1320
|
}
|
|
1232
1321
|
onProgress?.({ phase: "extracting", pct: -1 });
|
|
1233
1322
|
const installArgs = ["pip", "install", "--python", py, ...upgrade ? ["-U"] : [], "vllm"];
|
|
1234
|
-
await
|
|
1323
|
+
await execFileP5(uv, installArgs, {
|
|
1235
1324
|
cwd: root,
|
|
1236
1325
|
maxBuffer: 64 * 1024 * 1024
|
|
1237
1326
|
});
|
|
1238
1327
|
const version2 = await probeVllm(py);
|
|
1239
1328
|
return { python: py, version: version2 };
|
|
1240
1329
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
function classifyVllmBlocker(platform, error) {
|
|
1245
|
-
const plat = platformDisplay(platform);
|
|
1246
|
-
if (platform === "win32" || platform === "darwin") {
|
|
1247
|
-
return `vLLM cannot run on ${plat}: its OpenAI server requires uvloop (and other Linux-only components such as NCCL/Triton), which have no ${plat} build. Use the llama.cpp / TurboQuant engine for GGUF models here, or run vLLM under WSL2 / Linux.`;
|
|
1248
|
-
}
|
|
1249
|
-
const err9 = error;
|
|
1250
|
-
if (err9?.code === "ENOENT") {
|
|
1251
|
-
return `vLLM's environment looks missing or broken (interpreter not found). Reinstall the vLLM engine from the Engines page.`;
|
|
1252
|
-
}
|
|
1253
|
-
const raw = err9?.stderr ? String(err9.stderr) : err9?.message ?? "unknown error";
|
|
1254
|
-
const detail = raw.trim().split("\n").filter(Boolean).slice(-1)[0] ?? raw.trim();
|
|
1255
|
-
return `vLLM's environment looks broken or incomplete (uvloop failed to import: ${detail}). ${plat} is a supported platform for vLLM \u2014 reinstall the vLLM engine from the Engines page rather than switching engines.`;
|
|
1256
|
-
}
|
|
1257
|
-
async function vllmServeBlocker(python) {
|
|
1258
|
-
try {
|
|
1259
|
-
await execFileP4(python, ["-c", "import uvloop"], { timeout: 2e4 });
|
|
1260
|
-
return null;
|
|
1261
|
-
} catch (error) {
|
|
1262
|
-
return classifyVllmBlocker(process.platform, error);
|
|
1263
|
-
}
|
|
1330
|
+
var uvloopPreflight = new UvloopPreflight("vLLM");
|
|
1331
|
+
function vllmServeBlocker(python) {
|
|
1332
|
+
return uvloopPreflight.blockerFor(python);
|
|
1264
1333
|
}
|
|
1265
1334
|
async function probeVllm(python) {
|
|
1266
|
-
const { stdout } = await
|
|
1335
|
+
const { stdout } = await execFileP5(
|
|
1267
1336
|
python,
|
|
1268
1337
|
["-c", 'import importlib.metadata as m; print(m.version("vllm"))'],
|
|
1269
1338
|
{ timeout: 3e4 }
|
|
@@ -1293,10 +1362,10 @@ function vllmServerCommand(python, model, port2, host2, tensorParallelSize = 1,
|
|
|
1293
1362
|
|
|
1294
1363
|
// src/engines/sglang.ts
|
|
1295
1364
|
import { existsSync as existsSync9 } from "fs";
|
|
1296
|
-
import { execFile as
|
|
1365
|
+
import { execFile as execFile6 } from "child_process";
|
|
1297
1366
|
import { join as join9 } from "path";
|
|
1298
|
-
import { promisify as
|
|
1299
|
-
var
|
|
1367
|
+
import { promisify as promisify6 } from "util";
|
|
1368
|
+
var execFileP6 = promisify6(execFile6);
|
|
1300
1369
|
var SGLANG_PYTHON = "3.12";
|
|
1301
1370
|
function venvPython5(envDir) {
|
|
1302
1371
|
return process.platform === "win32" ? join9(envDir, "Scripts", "python.exe") : join9(envDir, "bin", "python");
|
|
@@ -1307,40 +1376,20 @@ async function ensureSglangEnv(root, onProgress, upgrade = false) {
|
|
|
1307
1376
|
const py = venvPython5(envDir);
|
|
1308
1377
|
if (!existsSync9(py)) {
|
|
1309
1378
|
onProgress?.({ phase: "extracting", pct: -1 });
|
|
1310
|
-
await
|
|
1379
|
+
await execFileP6(uv, ["venv", "--python", SGLANG_PYTHON, envDir], { cwd: root });
|
|
1311
1380
|
}
|
|
1312
1381
|
onProgress?.({ phase: "extracting", pct: -1 });
|
|
1313
1382
|
const installArgs = ["pip", "install", "--python", py, ...upgrade ? ["-U"] : [], "sglang[all]"];
|
|
1314
|
-
await
|
|
1383
|
+
await execFileP6(uv, installArgs, { cwd: root, maxBuffer: 64 * 1024 * 1024 });
|
|
1315
1384
|
const version2 = await probeSglang(py);
|
|
1316
1385
|
return { python: py, version: version2 };
|
|
1317
1386
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
function classifySglangBlocker(platform, error) {
|
|
1322
|
-
const plat = platformDisplay2(platform);
|
|
1323
|
-
if (platform === "win32" || platform === "darwin") {
|
|
1324
|
-
return `SGLang cannot run on ${plat}: its server requires uvloop (and other Linux-only components), which have no ${plat} build. Use the llama.cpp / TurboQuant engine for GGUF models here, or run SGLang under WSL2 / Linux.`;
|
|
1325
|
-
}
|
|
1326
|
-
const err9 = error;
|
|
1327
|
-
if (err9?.code === "ENOENT") {
|
|
1328
|
-
return `SGLang's environment looks missing or broken (interpreter not found). Reinstall the SGLang engine from the Engines page.`;
|
|
1329
|
-
}
|
|
1330
|
-
const raw = err9?.stderr ? String(err9.stderr) : err9?.message ?? "unknown error";
|
|
1331
|
-
const detail = raw.trim().split("\n").filter(Boolean).slice(-1)[0] ?? raw.trim();
|
|
1332
|
-
return `SGLang's environment looks broken or incomplete (uvloop failed to import: ${detail}). ${plat} is a supported platform for SGLang \u2014 reinstall the SGLang engine from the Engines page rather than switching engines.`;
|
|
1333
|
-
}
|
|
1334
|
-
async function sgLangServeBlocker(python) {
|
|
1335
|
-
try {
|
|
1336
|
-
await execFileP5(python, ["-c", "import uvloop"], { timeout: 2e4 });
|
|
1337
|
-
return null;
|
|
1338
|
-
} catch (error) {
|
|
1339
|
-
return classifySglangBlocker(process.platform, error);
|
|
1340
|
-
}
|
|
1387
|
+
var uvloopPreflight2 = new UvloopPreflight("SGLang");
|
|
1388
|
+
function sgLangServeBlocker(python) {
|
|
1389
|
+
return uvloopPreflight2.blockerFor(python);
|
|
1341
1390
|
}
|
|
1342
1391
|
async function probeSglang(python) {
|
|
1343
|
-
const { stdout } = await
|
|
1392
|
+
const { stdout } = await execFileP6(
|
|
1344
1393
|
python,
|
|
1345
1394
|
["-c", 'import importlib.metadata as m; print(m.version("sglang"))'],
|
|
1346
1395
|
{ timeout: 3e4 }
|
|
@@ -1952,7 +2001,7 @@ async function gracefulStop(child, exited) {
|
|
|
1952
2001
|
function signalTerm(child) {
|
|
1953
2002
|
if (!child.pid) return;
|
|
1954
2003
|
if (process.platform === "win32") {
|
|
1955
|
-
|
|
2004
|
+
execFile7("taskkill", ["/PID", String(child.pid), "/T"], () => {
|
|
1956
2005
|
});
|
|
1957
2006
|
} else {
|
|
1958
2007
|
child.kill("SIGTERM");
|
|
@@ -1961,7 +2010,7 @@ function signalTerm(child) {
|
|
|
1961
2010
|
function forceKill(child) {
|
|
1962
2011
|
if (!child.pid) return;
|
|
1963
2012
|
if (process.platform === "win32") {
|
|
1964
|
-
|
|
2013
|
+
execFile7("taskkill", ["/PID", String(child.pid), "/F", "/T"], () => {
|
|
1965
2014
|
});
|
|
1966
2015
|
} else {
|
|
1967
2016
|
child.kill("SIGKILL");
|
|
@@ -2066,7 +2115,7 @@ function killTrackedEnginesSync(dataDir) {
|
|
|
2066
2115
|
}
|
|
2067
2116
|
function killPidTree(pid) {
|
|
2068
2117
|
if (process.platform === "win32") {
|
|
2069
|
-
|
|
2118
|
+
execFile7("taskkill", ["/PID", String(pid), "/F", "/T"], () => {
|
|
2070
2119
|
});
|
|
2071
2120
|
} else {
|
|
2072
2121
|
try {
|
|
@@ -2321,7 +2370,7 @@ import { existsSync as existsSync14 } from "fs";
|
|
|
2321
2370
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2322
2371
|
|
|
2323
2372
|
// src/engines/probe.ts
|
|
2324
|
-
import { execFile as
|
|
2373
|
+
import { execFile as execFile8 } from "child_process";
|
|
2325
2374
|
import { closeSync, existsSync as existsSync11, openSync, readSync, statSync as statSync2 } from "fs";
|
|
2326
2375
|
import { dirname as dirname3 } from "path";
|
|
2327
2376
|
function probeEnv(bin) {
|
|
@@ -2350,7 +2399,7 @@ function runCaptured(bin, arg) {
|
|
|
2350
2399
|
return new Promise((resolve6) => {
|
|
2351
2400
|
try {
|
|
2352
2401
|
const timeoutMs = process.platform === "darwin" ? 6e4 : 15e3;
|
|
2353
|
-
|
|
2402
|
+
execFile8(bin, [arg], { cwd: dirname3(bin), env: probeEnv(bin), timeout: timeoutMs, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
2354
2403
|
resolve6({ out: (stdout || "") + (stderr || ""), err: error });
|
|
2355
2404
|
});
|
|
2356
2405
|
} catch (e) {
|
|
@@ -2507,17 +2556,17 @@ function trimLen(s, n) {
|
|
|
2507
2556
|
}
|
|
2508
2557
|
|
|
2509
2558
|
// src/engines/build-runner.ts
|
|
2510
|
-
import { execFile as
|
|
2559
|
+
import { execFile as execFile10, spawn as spawn2 } from "child_process";
|
|
2511
2560
|
import { createHash as createHash2 } from "crypto";
|
|
2512
2561
|
import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync3, readdirSync as readdirSync3, rmSync as rmSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
2513
2562
|
import { delimiter as delimiter2, dirname as dirname5, join as join12 } from "path";
|
|
2514
|
-
import { promisify as
|
|
2563
|
+
import { promisify as promisify8 } from "util";
|
|
2515
2564
|
|
|
2516
2565
|
// src/engines/build-prereqs.ts
|
|
2517
|
-
import { execFile as
|
|
2566
|
+
import { execFile as execFile9 } from "child_process";
|
|
2518
2567
|
import { delimiter, join as join11 } from "path";
|
|
2519
|
-
import { promisify as
|
|
2520
|
-
var
|
|
2568
|
+
import { promisify as promisify7 } from "util";
|
|
2569
|
+
var execFileP7 = promisify7(execFile9);
|
|
2521
2570
|
function buildEnv(toolchainDirs = []) {
|
|
2522
2571
|
const dirs = toolchainDirs.map((d) => d.trim()).filter(Boolean);
|
|
2523
2572
|
const env = { ...process.env };
|
|
@@ -2579,7 +2628,7 @@ var INSTALL_URLS = {
|
|
|
2579
2628
|
gcc: "https://gcc.gnu.org/install/"
|
|
2580
2629
|
};
|
|
2581
2630
|
async function runVersion(cmd, args, env) {
|
|
2582
|
-
const { stdout, stderr } = await
|
|
2631
|
+
const { stdout, stderr } = await execFileP7(cmd, args, {
|
|
2583
2632
|
timeout: 8e3,
|
|
2584
2633
|
windowsHide: true,
|
|
2585
2634
|
maxBuffer: 1024 * 1024,
|
|
@@ -2777,10 +2826,10 @@ function suggestEngineName(binPath, version2) {
|
|
|
2777
2826
|
}
|
|
2778
2827
|
|
|
2779
2828
|
// src/engines/build-runner.ts
|
|
2780
|
-
var
|
|
2829
|
+
var execFileP8 = promisify8(execFile10);
|
|
2781
2830
|
async function findLibcudaDir(signal) {
|
|
2782
2831
|
try {
|
|
2783
|
-
const { stdout } = await
|
|
2832
|
+
const { stdout } = await execFileP8("ldconfig", ["-p"], {
|
|
2784
2833
|
timeout: 5e3,
|
|
2785
2834
|
maxBuffer: 4 * 1024 * 1024,
|
|
2786
2835
|
signal
|
|
@@ -2796,16 +2845,86 @@ function chooseEngineName(name, priorName) {
|
|
|
2796
2845
|
return name ?? priorName ?? "";
|
|
2797
2846
|
}
|
|
2798
2847
|
function findPriorEngine(engines, build2) {
|
|
2799
|
-
|
|
2800
|
-
|
|
2848
|
+
const byBinPath = engines.find((e) => e.binPath === build2.binPath);
|
|
2849
|
+
if (byBinPath) return byBinPath;
|
|
2850
|
+
const sameTarget = engines.filter(
|
|
2851
|
+
(e) => sameRepo(e.sourceRepo, build2.sourceRepo) && (e.sourceCommit ?? "") === (build2.sourceCommit ?? "")
|
|
2852
|
+
);
|
|
2853
|
+
return pickByBranch(sameTarget, (build2.sourceBranch ?? "").trim(), effectiveBranch(build2.sourceBranch, build2.defaultBranch), build2.defaultBranch);
|
|
2854
|
+
}
|
|
2855
|
+
function effectiveBranch(branch, defaultBranch) {
|
|
2856
|
+
return (branch ?? "").trim() || (defaultBranch ?? "").trim();
|
|
2857
|
+
}
|
|
2858
|
+
function pickByBranch(candidates, exactBranch, effective, defaultBranch) {
|
|
2859
|
+
return candidates.find((e) => (e.sourceBranch ?? "").trim() === exactBranch) ?? candidates.find((e) => effectiveBranch(e.sourceBranch, defaultBranch) === effective);
|
|
2860
|
+
}
|
|
2861
|
+
var isPinnedEntry = (entry) => !!(entry.sourceCommit || entry.patchUrl);
|
|
2862
|
+
function findEngineForCatalogEntry(engines, entry, requestedBranch) {
|
|
2863
|
+
const sameBuild = engines.filter(
|
|
2864
|
+
(e) => sameRepo(e.sourceRepo, entry.homepage) && (e.sourceCommit ?? "") === (entry.sourceCommit ?? "") && (e.sourcePatchUrl ?? "") === (entry.patchUrl ?? "")
|
|
2801
2865
|
);
|
|
2866
|
+
if (isPinnedEntry(entry)) return sameBuild[0];
|
|
2867
|
+
const wanted = effectiveBranch(requestedBranch, entry.defaultBranch);
|
|
2868
|
+
return pickByBranch(sameBuild, wanted, wanted, entry.defaultBranch);
|
|
2869
|
+
}
|
|
2870
|
+
function catalogBranchesToScan(entry, requestedBranch) {
|
|
2871
|
+
if (isPinnedEntry(entry)) return [void 0];
|
|
2872
|
+
const wanted = effectiveBranch(requestedBranch, entry.defaultBranch);
|
|
2873
|
+
if (!wanted) return [void 0];
|
|
2874
|
+
return wanted === (entry.defaultBranch ?? "").trim() ? [wanted, void 0] : [wanted];
|
|
2875
|
+
}
|
|
2876
|
+
function legacyBuildDirName(repoUrl, branch, commit) {
|
|
2877
|
+
const last = repoUrl.trim().replace(/\/+$/, "").split(/[\\/]/).pop() ?? "";
|
|
2878
|
+
const repo = last.replace(/\.git$/i, "").trim() || "engine";
|
|
2879
|
+
const b = (branch ?? "").trim();
|
|
2880
|
+
const sha = (commit ?? "").trim();
|
|
2881
|
+
return asDirName(sha ? `${repo}-${sha.slice(0, 12)}` : b ? `${repo}-${b}` : repo);
|
|
2882
|
+
}
|
|
2883
|
+
function serverBinaryIn(enginesRoot, dirName) {
|
|
2884
|
+
const root = join12(enginesRoot, "build", dirName);
|
|
2885
|
+
return resolveServerBinary(join12(root, "build")) ?? resolveServerBinary(root);
|
|
2886
|
+
}
|
|
2887
|
+
function findCatalogBuildOnDisk(enginesRoot, entry, requestedBranch) {
|
|
2888
|
+
for (const branch of catalogBranchesToScan(entry, requestedBranch)) {
|
|
2889
|
+
const binPath = sourceBuildBinary(enginesRoot, entry.homepage, branch, entry.sourceCommit);
|
|
2890
|
+
if (binPath) return { binPath, branch: branch ?? "" };
|
|
2891
|
+
}
|
|
2892
|
+
if (!entry.sourceCommit) return void 0;
|
|
2893
|
+
const legacy = serverBinaryIn(enginesRoot, legacyBuildDirName(entry.homepage, void 0, entry.sourceCommit));
|
|
2894
|
+
return legacy ? { binPath: legacy, branch: "" } : void 0;
|
|
2895
|
+
}
|
|
2896
|
+
var slashed = (path) => path.replace(/\\/g, "/").toLowerCase();
|
|
2897
|
+
function isEngineInBuildDir(binPath, buildRoot) {
|
|
2898
|
+
return slashed(binPath).startsWith(`${slashed(buildRoot).replace(/\/+$/, "")}/`);
|
|
2899
|
+
}
|
|
2900
|
+
var branchesProvablyDiffer = (a, b) => {
|
|
2901
|
+
const x = (a ?? "").trim();
|
|
2902
|
+
const y = (b ?? "").trim();
|
|
2903
|
+
return !!x && !!y && x !== y;
|
|
2904
|
+
};
|
|
2905
|
+
function findNameConflict(engines, name, build2) {
|
|
2906
|
+
const wanted = (name ?? "").trim().toLowerCase();
|
|
2907
|
+
if (!wanted) return void 0;
|
|
2908
|
+
const holder = engines.find((e) => e.name.trim().toLowerCase() === wanted);
|
|
2909
|
+
if (!holder) return void 0;
|
|
2910
|
+
const replaced = isEngineInBuildDir(holder.binPath, build2.buildRoot) || sameRepo(holder.sourceRepo, build2.sourceRepo) && (holder.sourceCommit ?? "") === (build2.sourceCommit ?? "") && !branchesProvablyDiffer(holder.sourceBranch, build2.sourceBranch);
|
|
2911
|
+
return replaced ? void 0 : holder;
|
|
2912
|
+
}
|
|
2913
|
+
function parseDefaultBranch(lsRemoteOutput) {
|
|
2914
|
+
return /^ref:\s+refs\/heads\/(\S+)\s+HEAD\s*$/m.exec(lsRemoteOutput)?.[1];
|
|
2802
2915
|
}
|
|
2803
2916
|
function buildDirName(repoUrl, branch, commit) {
|
|
2804
2917
|
const repo = normRepoUrl(repoUrl) || "engine";
|
|
2805
2918
|
const b = (branch ?? "").trim();
|
|
2806
2919
|
const sha = (commit ?? "").trim();
|
|
2807
|
-
|
|
2808
|
-
|
|
2920
|
+
return asDirName(sha ? `${repo}-${sha.slice(0, 12)}` : b ? `${repo}-${b}` : repo);
|
|
2921
|
+
}
|
|
2922
|
+
function asDirName(raw) {
|
|
2923
|
+
const slug = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2924
|
+
return isPathStep(slug) ? "engine" : slug;
|
|
2925
|
+
}
|
|
2926
|
+
function isPathStep(name) {
|
|
2927
|
+
return /^\.*$/.test(name);
|
|
2809
2928
|
}
|
|
2810
2929
|
function sameRepo(a, b) {
|
|
2811
2930
|
const na = normRepoUrl(a);
|
|
@@ -2815,12 +2934,11 @@ function normRepoUrl(s) {
|
|
|
2815
2934
|
return (s ?? "").trim().toLowerCase().replace(/^https?:\/\//, "").replace(/^github\.com\//, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
2816
2935
|
}
|
|
2817
2936
|
function sourceBuildBinary(enginesRoot, repoUrl, branch, commit) {
|
|
2818
|
-
|
|
2819
|
-
return resolveServerBinary(join12(root, "build")) ?? resolveServerBinary(root);
|
|
2937
|
+
return serverBinaryIn(enginesRoot, buildDirName(repoUrl, branch, commit));
|
|
2820
2938
|
}
|
|
2821
2939
|
function sourceBuildDirOf(binPath, enginesRoot) {
|
|
2822
2940
|
const m = binPath.replace(/\\/g, "/").match(/\/engines\/build\/([^/]+)(?:\/|$)/i);
|
|
2823
|
-
if (!m) return null;
|
|
2941
|
+
if (!m || isPathStep(m[1])) return null;
|
|
2824
2942
|
return join12(enginesRoot, "build", m[1]);
|
|
2825
2943
|
}
|
|
2826
2944
|
function pickGenerator(hasNinja, isWindows) {
|
|
@@ -2972,7 +3090,7 @@ async function findVcvarsall() {
|
|
|
2972
3090
|
try {
|
|
2973
3091
|
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
2974
3092
|
const vswhere = join12(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
|
|
2975
|
-
const { stdout } = await
|
|
3093
|
+
const { stdout } = await execFileP8(
|
|
2976
3094
|
vswhere,
|
|
2977
3095
|
["-latest", "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-find", "VC\\Auxiliary\\Build\\vcvarsall.bat"],
|
|
2978
3096
|
{ timeout: 8e3, windowsHide: true, maxBuffer: 1024 * 1024 }
|
|
@@ -3168,6 +3286,15 @@ async function runBuild(req, hooks, signal) {
|
|
|
3168
3286
|
}
|
|
3169
3287
|
const commit = (await runStep("git", ["-C", srcDir, "rev-parse", "HEAD"], { env, signal, onLine: () => {
|
|
3170
3288
|
} })).trim();
|
|
3289
|
+
let defaultBranch;
|
|
3290
|
+
try {
|
|
3291
|
+
const lsRemoteArgs = ["-c", "http.lowSpeedLimit=1000", "-c", "http.lowSpeedTime=15", "ls-remote", "--symref", req.repoUrl, "HEAD"];
|
|
3292
|
+
const lsRemote = await runStep("git", lsRemoteArgs, { env, signal, onLine: () => {
|
|
3293
|
+
} });
|
|
3294
|
+
defaultBranch = parseDefaultBranch(lsRemote);
|
|
3295
|
+
} catch (e) {
|
|
3296
|
+
if (e?.name === "AbortError") throw e;
|
|
3297
|
+
}
|
|
3171
3298
|
const patchUrl = (req.patchUrl ?? "").trim();
|
|
3172
3299
|
if (patchUrl) {
|
|
3173
3300
|
const expectedSha = (req.patchSha256 ?? "").trim();
|
|
@@ -3253,7 +3380,7 @@ async function runBuild(req, hooks, signal) {
|
|
|
3253
3380
|
}
|
|
3254
3381
|
if (isWindows) copyCudaRuntimeDlls(env, dirname5(binPath), hooks.log);
|
|
3255
3382
|
else if (!isMac && !isAndroid2) copyCudaRuntimeLibs(env, dirname5(binPath), hooks.log);
|
|
3256
|
-
return { binPath, commit, buildRoot };
|
|
3383
|
+
return { binPath, commit, buildRoot, defaultBranch };
|
|
3257
3384
|
}
|
|
3258
3385
|
|
|
3259
3386
|
// src/engines/registry.ts
|
|
@@ -3265,11 +3392,19 @@ var NotFoundError = class extends Error {
|
|
|
3265
3392
|
}
|
|
3266
3393
|
};
|
|
3267
3394
|
var NameTakenError = class extends Error {
|
|
3268
|
-
constructor() {
|
|
3269
|
-
super(
|
|
3395
|
+
constructor(holder, incoming) {
|
|
3396
|
+
super(nameTakenMessage(holder, incoming));
|
|
3270
3397
|
this.name = "NameTakenError";
|
|
3271
3398
|
}
|
|
3272
3399
|
};
|
|
3400
|
+
function nameTakenMessage(holder, incoming) {
|
|
3401
|
+
if (!holder) return "Name already in use \u2014 choose a different name.";
|
|
3402
|
+
const base2 = `Name already in use by "${holder.name}" \u2014 choose a different name.`;
|
|
3403
|
+
const held = holder.sourceBranch?.trim();
|
|
3404
|
+
const wanted = incoming?.sourceBranch?.trim();
|
|
3405
|
+
const anotherBranchOfTheSameRepo = !!held && !!wanted && held !== wanted && sameRepo(holder.sourceRepo, incoming?.sourceRepo);
|
|
3406
|
+
return anotherBranchOfTheSameRepo ? `${base2} It is a build of branch "${held}"; a build of "${wanted}" is a separate engine and needs its own name.` : base2;
|
|
3407
|
+
}
|
|
3273
3408
|
var Registry = class {
|
|
3274
3409
|
constructor(store2) {
|
|
3275
3410
|
this.store = store2;
|
|
@@ -3288,7 +3423,7 @@ var Registry = class {
|
|
|
3288
3423
|
}
|
|
3289
3424
|
async add(name, binPath, source) {
|
|
3290
3425
|
const finalName = name.trim() || "llama-server";
|
|
3291
|
-
this.assertNameFree(finalName);
|
|
3426
|
+
this.assertNameFree(finalName, source);
|
|
3292
3427
|
const pr = await probe(binPath);
|
|
3293
3428
|
const sourceRepo = source?.sourceRepo?.trim() || void 0;
|
|
3294
3429
|
const sourceBranch = source?.sourceBranch?.trim() || void 0;
|
|
@@ -3308,19 +3443,21 @@ var Registry = class {
|
|
|
3308
3443
|
...sourcePatchUrl ? { sourcePatchUrl } : {}
|
|
3309
3444
|
};
|
|
3310
3445
|
this.store.update((c) => {
|
|
3311
|
-
|
|
3446
|
+
const holder = this.nameHolder(c.engines, finalName);
|
|
3447
|
+
if (holder) throw new NameTakenError(holder, source);
|
|
3312
3448
|
c.engines.push(eng);
|
|
3313
3449
|
if (!c.activeEngineId) c.activeEngineId = eng.id;
|
|
3314
3450
|
});
|
|
3315
3451
|
return { engine: eng, warning: pr.version === "unknown" ? "no_version" : void 0 };
|
|
3316
3452
|
}
|
|
3317
|
-
/**
|
|
3318
|
-
|
|
3453
|
+
/** The registered engine that already uses `name` (case-insensitive, trimmed), if any. */
|
|
3454
|
+
nameHolder(engines, name) {
|
|
3319
3455
|
const n = name.trim().toLowerCase();
|
|
3320
|
-
return engines.
|
|
3456
|
+
return engines.find((e) => e.name.trim().toLowerCase() === n);
|
|
3321
3457
|
}
|
|
3322
|
-
assertNameFree(name) {
|
|
3323
|
-
|
|
3458
|
+
assertNameFree(name, incoming) {
|
|
3459
|
+
const holder = this.nameHolder(this.store.snapshot().engines, name);
|
|
3460
|
+
if (holder) throw new NameTakenError(holder, incoming);
|
|
3324
3461
|
}
|
|
3325
3462
|
/** Register an MLX engine (kind='mlx'). No llama-server probe — the binPath is
|
|
3326
3463
|
* a venv python, not a llama-server, so capabilities/flags don't apply. */
|
|
@@ -5142,9 +5279,9 @@ ${e.message}`, timedOut });
|
|
|
5142
5279
|
resolve6({ exitCode, stdout, stderr, timedOut });
|
|
5143
5280
|
};
|
|
5144
5281
|
const armGrace = (ms) => {
|
|
5145
|
-
const
|
|
5282
|
+
const capped2 = hardDeadlineAt === void 0 ? ms : Math.max(0, Math.min(ms, hardDeadlineAt - Date.now()));
|
|
5146
5283
|
if (graceTimer) clearTimeout(graceTimer);
|
|
5147
|
-
graceTimer = setTimeout(finish,
|
|
5284
|
+
graceTimer = setTimeout(finish, capped2);
|
|
5148
5285
|
};
|
|
5149
5286
|
const finishIfDrained = () => {
|
|
5150
5287
|
if (!exited || settled) return;
|
|
@@ -5257,7 +5394,7 @@ function parseClaudeCliStreamJson(stdout) {
|
|
|
5257
5394
|
|
|
5258
5395
|
// src/cli-launch.ts
|
|
5259
5396
|
import { spawn as spawn4 } from "child_process";
|
|
5260
|
-
import { homedir as
|
|
5397
|
+
import { homedir as homedir3 } from "os";
|
|
5261
5398
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
5262
5399
|
import { join as join15, dirname as dirname7 } from "path";
|
|
5263
5400
|
|
|
@@ -5430,7 +5567,7 @@ function resolveModelKey(models, input) {
|
|
|
5430
5567
|
return null;
|
|
5431
5568
|
}
|
|
5432
5569
|
var realFs = {
|
|
5433
|
-
home:
|
|
5570
|
+
home: homedir3(),
|
|
5434
5571
|
readFile: (p) => readFile(p, "utf8"),
|
|
5435
5572
|
writeFile: (p, data) => writeFile(p, data, "utf8"),
|
|
5436
5573
|
mkdir: async (p) => {
|
|
@@ -7840,7 +7977,7 @@ var AppUpdateChecker = class {
|
|
|
7840
7977
|
};
|
|
7841
7978
|
|
|
7842
7979
|
// src/app-update-apply.ts
|
|
7843
|
-
import { execFile as
|
|
7980
|
+
import { execFile as execFile11, spawn as spawn6 } from "child_process";
|
|
7844
7981
|
import { existsSync as existsSync17, openSync as openSync2, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
|
|
7845
7982
|
import { dirname as dirname8, join as join18, resolve as resolve2 } from "path";
|
|
7846
7983
|
import { fileURLToPath } from "url";
|
|
@@ -7937,7 +8074,7 @@ async function npmGlobalRoot() {
|
|
|
7937
8074
|
if (npmRootCache) return npmRootCache.value;
|
|
7938
8075
|
const value = await new Promise((res) => {
|
|
7939
8076
|
const cmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
7940
|
-
|
|
8077
|
+
execFile11(cmd, ["root", "-g"], { timeout: 1e4, windowsHide: true }, (e, stdout) => {
|
|
7941
8078
|
if (e) return res(null);
|
|
7942
8079
|
const out = String(stdout).trim();
|
|
7943
8080
|
res(out || null);
|
|
@@ -10133,9 +10270,9 @@ async function loadPrimaryModel(opts, { manager: manager2, comfy: comfy2, modelR
|
|
|
10133
10270
|
|
|
10134
10271
|
// src/models/hf-cache.ts
|
|
10135
10272
|
import { existsSync as existsSync20, mkdirSync as mkdirSync8 } from "fs";
|
|
10136
|
-
import { homedir as
|
|
10273
|
+
import { homedir as homedir4 } from "os";
|
|
10137
10274
|
import { join as join22 } from "path";
|
|
10138
|
-
function hfHubCacheDir(env = process.env, home =
|
|
10275
|
+
function hfHubCacheDir(env = process.env, home = homedir4()) {
|
|
10139
10276
|
const explicit = env.HUGGINGFACE_HUB_CACHE;
|
|
10140
10277
|
if (explicit && explicit.trim()) return explicit;
|
|
10141
10278
|
const hfHome = env.HF_HOME;
|
|
@@ -13668,7 +13805,7 @@ function repoSubdir(repo) {
|
|
|
13668
13805
|
}
|
|
13669
13806
|
|
|
13670
13807
|
// src/bench/bench.ts
|
|
13671
|
-
import { execFile as
|
|
13808
|
+
import { execFile as execFile12 } from "child_process";
|
|
13672
13809
|
import { readFileSync as readFileSync11 } from "fs";
|
|
13673
13810
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
13674
13811
|
|
|
@@ -15977,7 +16114,7 @@ function kvSpeedAdvisory(tps, currentKv, supportedKvTypes) {
|
|
|
15977
16114
|
function readNvidiaVramMb() {
|
|
15978
16115
|
return new Promise((resolve6) => {
|
|
15979
16116
|
try {
|
|
15980
|
-
|
|
16117
|
+
execFile12(
|
|
15981
16118
|
"nvidia-smi",
|
|
15982
16119
|
["--query-gpu=memory.used", "--format=csv,noheader,nounits"],
|
|
15983
16120
|
{ timeout: 8e3, windowsHide: true },
|
|
@@ -16010,7 +16147,7 @@ function parseRocmVramUsed(memJson) {
|
|
|
16010
16147
|
function readRocmVramMb() {
|
|
16011
16148
|
return new Promise((resolve6) => {
|
|
16012
16149
|
try {
|
|
16013
|
-
|
|
16150
|
+
execFile12("rocm-smi", ["--showmeminfo", "vram", "--json"], { timeout: 8e3, windowsHide: true }, (err9, stdout) => {
|
|
16014
16151
|
resolve6(err9 || !stdout ? null : parseRocmVramUsed(stdout));
|
|
16015
16152
|
});
|
|
16016
16153
|
} catch {
|
|
@@ -16030,7 +16167,7 @@ function readWddmSharedMb() {
|
|
|
16030
16167
|
if (process.platform !== "win32" || wddmSharedUnsupported) return Promise.resolve(null);
|
|
16031
16168
|
return new Promise((resolve6) => {
|
|
16032
16169
|
try {
|
|
16033
|
-
|
|
16170
|
+
execFile12(
|
|
16034
16171
|
"powershell",
|
|
16035
16172
|
["-NoProfile", "-NonInteractive", "-Command", WDDM_SHARED_PS],
|
|
16036
16173
|
{ timeout: 1e4, windowsHide: true },
|
|
@@ -16069,7 +16206,7 @@ function parseRocmGttUsed(memJson) {
|
|
|
16069
16206
|
function readRocmGttMb() {
|
|
16070
16207
|
return new Promise((resolve6) => {
|
|
16071
16208
|
try {
|
|
16072
|
-
|
|
16209
|
+
execFile12("rocm-smi", ["--showmeminfo", "gtt", "--json"], { timeout: 8e3, windowsHide: true }, (err9, stdout) => {
|
|
16073
16210
|
if (err9 || !stdout) return resolve6(null);
|
|
16074
16211
|
resolve6(parseRocmGttUsed(stdout));
|
|
16075
16212
|
});
|
|
@@ -19327,7 +19464,7 @@ __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
|
|
19327
19464
|
|
|
19328
19465
|
// src/api/routes.ts
|
|
19329
19466
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
19330
|
-
import { homedir as
|
|
19467
|
+
import { homedir as homedir5 } from "os";
|
|
19331
19468
|
|
|
19332
19469
|
// src/config/config-bounds.ts
|
|
19333
19470
|
var CONFIG_BOUNDS = {
|
|
@@ -22065,12 +22202,12 @@ function catalogEngine(id) {
|
|
|
22065
22202
|
}
|
|
22066
22203
|
|
|
22067
22204
|
// src/engines/cuda-provision.ts
|
|
22068
|
-
import { execFile as
|
|
22205
|
+
import { execFile as execFile13 } from "child_process";
|
|
22069
22206
|
import { cpSync as cpSync2, existsSync as existsSync24, mkdirSync as mkdirSync14, readdirSync as readdirSync8, rmSync as rmSync13 } from "fs";
|
|
22070
22207
|
import { tmpdir } from "os";
|
|
22071
22208
|
import { join as join30 } from "path";
|
|
22072
|
-
import { promisify as
|
|
22073
|
-
var
|
|
22209
|
+
import { promisify as promisify9 } from "util";
|
|
22210
|
+
var execFileP9 = promisify9(execFile13);
|
|
22074
22211
|
var REDIST_BASE = "https://developer.download.nvidia.com/compute/cuda/redist/";
|
|
22075
22212
|
var CUDA_REQUIRED_COMPONENTS = ["cuda_nvcc", "cuda_cudart", "libcublas", "cuda_cccl"];
|
|
22076
22213
|
var CUDA_OPTIONAL_COMPONENTS = ["libnvvm", "cuda_crt"];
|
|
@@ -22086,7 +22223,7 @@ function cmpMajorMinor(a, b) {
|
|
|
22086
22223
|
}
|
|
22087
22224
|
async function driverMaxCuda() {
|
|
22088
22225
|
try {
|
|
22089
|
-
const { stdout } = await
|
|
22226
|
+
const { stdout } = await execFileP9("nvidia-smi", [], { timeout: 8e3, windowsHide: true, maxBuffer: 1024 * 1024 });
|
|
22090
22227
|
const m = stdout.match(/CUDA Version:\s*([\d.]+)/i);
|
|
22091
22228
|
return m ? m[1] : null;
|
|
22092
22229
|
} catch {
|
|
@@ -22248,7 +22385,7 @@ function recommendEngines(p, engines) {
|
|
|
22248
22385
|
}
|
|
22249
22386
|
|
|
22250
22387
|
// src/sysinfo/usage.ts
|
|
22251
|
-
import { execFile as
|
|
22388
|
+
import { execFile as execFile14, spawn as spawn8 } from "child_process";
|
|
22252
22389
|
import { existsSync as existsSync25, readFileSync as readFileSync15, readdirSync as readdirSync9 } from "fs";
|
|
22253
22390
|
import os2 from "os";
|
|
22254
22391
|
|
|
@@ -22498,7 +22635,7 @@ var MAX_CONSECUTIVE_FAILURES = 3;
|
|
|
22498
22635
|
function run(cmd, args) {
|
|
22499
22636
|
return new Promise((resolve6, reject) => {
|
|
22500
22637
|
try {
|
|
22501
|
-
|
|
22638
|
+
execFile14(cmd, args, { timeout: READER_TIMEOUT_MS, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (err9, stdout) => {
|
|
22502
22639
|
if (err9) reject(err9);
|
|
22503
22640
|
else resolve6(String(stdout ?? ""));
|
|
22504
22641
|
});
|
|
@@ -23815,13 +23952,9 @@ function registerApi(app2, d) {
|
|
|
23815
23952
|
installed2 = existsSync27(llamafileBinPath(enginesRoot));
|
|
23816
23953
|
enabled = regEngines.some((x) => x.kind === "llamafile");
|
|
23817
23954
|
}
|
|
23818
|
-
const
|
|
23819
|
-
const
|
|
23820
|
-
const
|
|
23821
|
-
(x) => sameRepo(x.sourceRepo, e.homepage) && (x.sourceCommit ?? "") === (e.sourceCommit ?? "") && (x.sourcePatchUrl ?? "") === (e.patchUrl ?? "") && (x.sourceBranch ?? "") === (matchBranch ?? "")
|
|
23822
|
-
);
|
|
23823
|
-
let sourceBinPath = srcEng?.binPath;
|
|
23824
|
-
if (!srcEng && !e.excludeFromSourceMatch) sourceBinPath = sourceBuildBinary(enginesRoot, e.homepage, matchBranch, e.sourceCommit) ?? void 0;
|
|
23955
|
+
const srcEng = e.excludeFromSourceMatch ? void 0 : findEngineForCatalogEntry(regEngines, e, branchParam);
|
|
23956
|
+
const onDisk = !srcEng && !e.excludeFromSourceMatch ? findCatalogBuildOnDisk(enginesRoot, e, branchParam) : void 0;
|
|
23957
|
+
const sourceBinPath = srcEng?.binPath ?? onDisk?.binPath;
|
|
23825
23958
|
const sourceBuilt = !!srcEng || !!sourceBinPath;
|
|
23826
23959
|
if (srcEng) {
|
|
23827
23960
|
installed2 = true;
|
|
@@ -23836,7 +23969,7 @@ function registerApi(app2, d) {
|
|
|
23836
23969
|
enabled,
|
|
23837
23970
|
sourceBuilt,
|
|
23838
23971
|
sourceEngineId: srcEng?.id,
|
|
23839
|
-
sourceBranch: srcEng?.sourceBranch ?? "",
|
|
23972
|
+
sourceBranch: srcEng?.sourceBranch ?? onDisk?.branch ?? "",
|
|
23840
23973
|
sourceBinPath: sourceBinPath ?? ""
|
|
23841
23974
|
};
|
|
23842
23975
|
});
|
|
@@ -23921,6 +24054,12 @@ function registerApi(app2, d) {
|
|
|
23921
24054
|
const enginesRoot = join34(d.store.dir(), "engines");
|
|
23922
24055
|
const toolchainDirs = d.store.snapshot().build.toolchainDirs;
|
|
23923
24056
|
const buildRoot = join34(enginesRoot, "build", buildDirName(repoUrl, branch, commit));
|
|
24057
|
+
const engines = d.registry.list().engines;
|
|
24058
|
+
const conflict = findNameConflict(engines, name, { buildRoot, sourceRepo: repoUrl, sourceBranch: branch, sourceCommit: commit });
|
|
24059
|
+
if (conflict) return err5(c, 400, "name_already_taken", nameTakenMessage(conflict, { sourceRepo: repoUrl, sourceBranch: branch }));
|
|
24060
|
+
const running2 = d.registry.active();
|
|
24061
|
+
if (running2 && engineBusy(d) && isEngineInBuildDir(running2.binPath, buildRoot))
|
|
24062
|
+
return err5(c, 409, "engine_in_use", `Stop "${running2.name}" before rebuilding it \u2014 the rebuild replaces its files.`);
|
|
23924
24063
|
const ac = new AbortController();
|
|
23925
24064
|
buildAbort = ac;
|
|
23926
24065
|
d.build.start(name ?? repoUrl);
|
|
@@ -23954,7 +24093,8 @@ function registerApi(app2, d) {
|
|
|
23954
24093
|
binPath: out.binPath,
|
|
23955
24094
|
sourceRepo: repoUrl,
|
|
23956
24095
|
sourceBranch: branch,
|
|
23957
|
-
sourceCommit: commit
|
|
24096
|
+
sourceCommit: commit,
|
|
24097
|
+
defaultBranch: out.defaultBranch
|
|
23958
24098
|
});
|
|
23959
24099
|
if (prior) {
|
|
23960
24100
|
if (d.registry.active()?.id === prior.id) await d.manager.stopAndWait();
|
|
@@ -24205,10 +24345,11 @@ function registerApi(app2, d) {
|
|
|
24205
24345
|
const { engine, warning } = await d.registry.add(b.name ?? "", b.binPath, {
|
|
24206
24346
|
sourceRepo: b.sourceRepo,
|
|
24207
24347
|
sourceBranch: b.sourceBranch,
|
|
24208
|
-
sourceCommit: b.sourceCommit
|
|
24348
|
+
sourceCommit: b.sourceCommit,
|
|
24349
|
+
sourcePatchUrl: b.sourcePatchUrl
|
|
24209
24350
|
});
|
|
24210
24351
|
if (!isCatalogRepo(b.sourceRepo)) {
|
|
24211
|
-
d.registry.recordCustomSource({ name: engine.name, binPath: engine.binPath, kind: engine.kind, sourceRepo: b.sourceRepo, sourceBranch: b.sourceBranch, sourceCommit: b.sourceCommit });
|
|
24352
|
+
d.registry.recordCustomSource({ name: engine.name, binPath: engine.binPath, kind: engine.kind, sourceRepo: b.sourceRepo, sourceBranch: b.sourceBranch, sourceCommit: b.sourceCommit, sourcePatchUrl: b.sourcePatchUrl });
|
|
24212
24353
|
}
|
|
24213
24354
|
return c.json({ ...engine, warning: warning ?? null }, 201);
|
|
24214
24355
|
} catch (e) {
|
|
@@ -24436,7 +24577,7 @@ function registerApi(app2, d) {
|
|
|
24436
24577
|
if (raw === FS_DRIVES_ROOT && process.platform === "win32") {
|
|
24437
24578
|
return c.json({ path: FS_DRIVES_ROOT, parent: null, entries: listWindowsDrives() });
|
|
24438
24579
|
}
|
|
24439
|
-
const target = raw ? resolve5(raw) :
|
|
24580
|
+
const target = raw ? resolve5(raw) : homedir5();
|
|
24440
24581
|
let real;
|
|
24441
24582
|
try {
|
|
24442
24583
|
real = realpathSync3(target);
|
|
@@ -24977,7 +25118,7 @@ function registerApi(app2, d) {
|
|
|
24977
25118
|
};
|
|
24978
25119
|
const isMemoryServer = server2.transport === "stdio" && (server2.command?.includes("server-memory") || server2.args?.some((a) => a.includes("server-memory")));
|
|
24979
25120
|
if (isMemoryServer && !server2.env?.MEMORY_FILE_PATH) {
|
|
24980
|
-
server2.env = { ...server2.env, MEMORY_FILE_PATH: join34(
|
|
25121
|
+
server2.env = { ...server2.env, MEMORY_FILE_PATH: join34(homedir5(), ".turbollm", "mcp-memory.jsonl") };
|
|
24981
25122
|
}
|
|
24982
25123
|
d.store.update((cfg2) => {
|
|
24983
25124
|
cfg2.mcp.servers.push(server2);
|
|
@@ -25786,8 +25927,8 @@ function isValidMachineName(name) {
|
|
|
25786
25927
|
}
|
|
25787
25928
|
function sanitizeMachineName(raw) {
|
|
25788
25929
|
const collapsed = (raw ?? "").replace(/[\\/]+/g, "-").replace(/\s+/g, " ").trim();
|
|
25789
|
-
const
|
|
25790
|
-
return /[^\s-]/.test(
|
|
25930
|
+
const capped2 = collapsed.slice(0, MAX_NAME_LEN).trim();
|
|
25931
|
+
return /[^\s-]/.test(capped2) ? capped2 : FALLBACK_MACHINE_NAME;
|
|
25791
25932
|
}
|
|
25792
25933
|
function uniqueMachineName(desired, taken) {
|
|
25793
25934
|
const used = /* @__PURE__ */ new Set();
|
|
@@ -25877,7 +26018,7 @@ function redactDownload(rec) {
|
|
|
25877
26018
|
// src/link/link-client.ts
|
|
25878
26019
|
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
25879
26020
|
var ERROR_CODE = /^[a-z][a-z0-9_]{0,63}$/i;
|
|
25880
|
-
async function
|
|
26021
|
+
async function failureDetail2(res) {
|
|
25881
26022
|
if (!(res.headers.get("content-type") ?? "").includes("application/json")) return {};
|
|
25882
26023
|
try {
|
|
25883
26024
|
const body10 = await res.json();
|
|
@@ -26102,7 +26243,7 @@ var LinkClient = class {
|
|
|
26102
26243
|
...json === void 0 ? {} : { body: JSON.stringify(json) },
|
|
26103
26244
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
26104
26245
|
});
|
|
26105
|
-
if (!res.ok) return { kind: "http", status: res.status, ...await
|
|
26246
|
+
if (!res.ok) return { kind: "http", status: res.status, ...await failureDetail2(res) };
|
|
26106
26247
|
const ct = res.headers.get("content-type") ?? "";
|
|
26107
26248
|
if (!ct.includes("application/json")) return { kind: "network" };
|
|
26108
26249
|
const parsed = await res.json();
|