turbollm 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +54 -4
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -705,6 +705,11 @@ function vllmServerCommand(python, model, port2, host2, tensorParallelSize = 1)
705
705
  "vllm.entrypoints.openai.api_server",
706
706
  "--model",
707
707
  model,
708
+ // Serve under a fixed alias so requests can address the model by a stable name
709
+ // (TurboLLM's internal key is a display string with spaces). Mirrors mlx-lm's
710
+ // built-in `default_model` alias; see engineModelAlias() in compat.ts.
711
+ "--served-model-name",
712
+ "default_model",
708
713
  "--host",
709
714
  host2,
710
715
  "--port",
@@ -781,7 +786,7 @@ var Manager = class {
781
786
  }
782
787
  }
783
788
  const { cmd, args } = engineCommand(opts, port2, slotSavePath);
784
- const child = spawn(cmd, args, { cwd: dirname2(cmd), windowsHide: true });
789
+ const child = spawn(cmd, args, { cwd: dirname2(cmd), windowsHide: true, env: pyEngineEnv(opts.engine.kind, this.store.dir()) });
785
790
  child.stdout?.pipe(logStream, { end: false });
786
791
  child.stderr?.pipe(logStream, { end: false });
787
792
  this.state = "starting";
@@ -953,10 +958,22 @@ var Manager = class {
953
958
  this.resolveExited?.();
954
959
  }
955
960
  async readiness(child, port2) {
956
- const deadline = Date.now() + readinessTimeoutMs(this.opts?.engine.kind ?? "llama-server");
961
+ const kind = this.opts?.engine.kind ?? "llama-server";
962
+ const deadline = Date.now() + readinessTimeoutMs(kind);
957
963
  for (; ; ) {
958
964
  await sleep(500);
959
965
  if (this.child !== child || this.state !== "starting") return;
966
+ if (kind === "mlx" || kind === "vllm") {
967
+ const loadErr = detectPyLoadFailure(readTail(this.logPathStr, 200));
968
+ if (loadErr) {
969
+ if (this.child === child && this.state === "starting") {
970
+ this.state = "error";
971
+ this.errInfo = { code: "model_load_failed", message: loadErr, exitCode: -1, logTail: readTail(this.logPathStr, 20) };
972
+ child.kill("SIGKILL");
973
+ }
974
+ return;
975
+ }
976
+ }
960
977
  if (await probeReady(port2)) {
961
978
  if (this.child === child && this.state === "starting") {
962
979
  this.state = "running";
@@ -998,6 +1015,27 @@ function engineCommand(opts, port2, slotSavePath) {
998
1015
  function readinessTimeoutMs(kind) {
999
1016
  return kind === "vllm" ? 6e5 : 12e4;
1000
1017
  }
1018
+ function pyEngineEnv(kind, dataDir) {
1019
+ if (kind !== "mlx" && kind !== "vllm") return void 0;
1020
+ const hfHome = join6(dataDir, "hf-cache");
1021
+ const hubCache = join6(hfHome, "hub");
1022
+ mkdirSync4(hubCache, { recursive: true });
1023
+ return {
1024
+ ...process.env,
1025
+ HF_HUB_OFFLINE: "1",
1026
+ TRANSFORMERS_OFFLINE: "1",
1027
+ HF_HOME: hfHome,
1028
+ HF_HUB_CACHE: hubCache
1029
+ };
1030
+ }
1031
+ function detectPyLoadFailure(lines) {
1032
+ const text = lines.join("\n");
1033
+ const isLoadCrash = /Exception in thread[^\n]*_generate/.test(text) || /in load_default\b/.test(text) || /in load_model\b/.test(text) || /load_weights/.test(text);
1034
+ if (!isLoadCrash) return null;
1035
+ const errLine = [...lines].reverse().find((l) => /^[A-Za-z_][\w.]*(Error|Exception):/.test(l.trim()));
1036
+ const detail = (errLine ? errLine.trim() : "the model failed to load").slice(0, 200);
1037
+ return `MLX could not load this model \u2014 ${detail} This usually means the installed mlx-lm version does not support this model's architecture or quantization.`;
1038
+ }
1001
1039
  function buildArgs(opts, port2, slotSavePath) {
1002
1040
  const args = ["-m", opts.modelPath, "--host", "127.0.0.1", "--port", String(port2)];
1003
1041
  const flags = opts.engine.capabilities.flags;
@@ -1715,6 +1753,10 @@ function engineAcceptsFormat(engineKind, format) {
1715
1753
  if (engineKind === "vllm") return format === "mlx";
1716
1754
  return format === "gguf";
1717
1755
  }
1756
+ var ENGINE_MODEL_ALIAS = "default_model";
1757
+ function engineModelAlias(engineKind) {
1758
+ return engineKind === "mlx" || engineKind === "vllm" ? ENGINE_MODEL_ALIAS : null;
1759
+ }
1718
1760
 
1719
1761
  // src/models/scanner.ts
1720
1762
  import { existsSync as existsSync9, lstatSync, readdirSync as readdirSync3, readFileSync as readFileSync3, rmSync as rmSync4, writeFileSync as writeFileSync2 } from "fs";
@@ -5420,7 +5462,9 @@ async function runGeneration(d, stream, ctx) {
5420
5462
  if (!(k in SAMPLING_KEYS) && k !== "stop") samplingOverride[k] = v;
5421
5463
  }
5422
5464
  const reqBody = {
5423
- model: ms.model.key,
5465
+ // mlx-lm / vLLM serve under a fixed alias and 404 on TurboLLM's internal key;
5466
+ // llama.cpp ignores the field. engineModelAlias() returns the right value per kind.
5467
+ model: engineModelAlias(d.registry.active()?.kind ?? "") ?? ms.model.key,
5424
5468
  messages: engineMessages,
5425
5469
  stream: true,
5426
5470
  stream_options: { include_usage: true },
@@ -5668,7 +5712,7 @@ async function autoTitle(d, convId, prevMessages, assistantReply, target) {
5668
5712
  method: "POST",
5669
5713
  headers: { "Content-Type": "application/json" },
5670
5714
  body: JSON.stringify({
5671
- model: ms.model?.key,
5715
+ model: engineModelAlias(d.registry.active()?.kind ?? "") ?? ms.model?.key,
5672
5716
  messages: titleMessages,
5673
5717
  stream: false,
5674
5718
  temperature: 0.3,
@@ -6021,6 +6065,8 @@ function registerGateway(app2, d) {
6021
6065
  const status = d.manager.status();
6022
6066
  const modelName = status.state === "running" ? status.model?.name ?? req.model ?? "local" : req.model ?? "local";
6023
6067
  const oaiBody = mapToOpenAI(req);
6068
+ const oaiAlias = engineModelAlias(d.registry.active()?.kind ?? "");
6069
+ if (oaiAlias) oaiBody.model = oaiAlias;
6024
6070
  d.manager.generationStart();
6025
6071
  let res;
6026
6072
  try {
@@ -6144,6 +6190,10 @@ function registerGateway(app2, d) {
6144
6190
  if (parsedBody && maxLimit > 0) {
6145
6191
  parsedBody.max_tokens = clampMaxTokens(parsedBody.max_tokens, maxLimit);
6146
6192
  }
6193
+ if (parsedBody) {
6194
+ const alias = engineModelAlias(d.registry.active()?.kind ?? "");
6195
+ if (alias) parsedBody.model = alias;
6196
+ }
6147
6197
  headers.delete("content-length");
6148
6198
  init.body = parsedBody ? JSON.stringify(parsedBody) : "";
6149
6199
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbollm",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "TurboLLM — local LLM platform: run any inference engine auto-tuned to your GPU, with a web UI and OpenAI/Anthropic-compatible API. Point Claude Code at your own machine in one command.",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "author": "Mohit Soni",