turbollm 0.7.0 → 0.7.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.
package/dist/cli.js CHANGED
@@ -2311,6 +2311,21 @@ function mlxEntryFor(dir) {
2311
2311
  if (existsSync9(tc)) hasChatTemplate = readFileSync3(tc, "utf8").includes("chat_template");
2312
2312
  } catch {
2313
2313
  }
2314
+ let incomplete = false;
2315
+ try {
2316
+ const indexPath = join7(dir, "model.safetensors.index.json");
2317
+ if (existsSync9(indexPath)) {
2318
+ const index = JSON.parse(readFileSync3(indexPath, "utf8"));
2319
+ const shards = new Set(Object.values(index.weight_map ?? {}));
2320
+ for (const shard of shards) {
2321
+ if (!existsSync9(join7(dir, shard))) {
2322
+ incomplete = true;
2323
+ break;
2324
+ }
2325
+ }
2326
+ }
2327
+ } catch {
2328
+ }
2314
2329
  const expertCount = cfg2.num_local_experts ?? cfg2.num_experts ?? 0;
2315
2330
  const bits = cfg2.quantization?.bits;
2316
2331
  const quant = bits ? `${bits}bit` : "fp16";
@@ -2336,7 +2351,7 @@ function mlxEntryFor(dir) {
2336
2351
  mmprojPath: null,
2337
2352
  hasChatTemplate,
2338
2353
  embedding: isEmbeddingModel(arch2, basename(dir)),
2339
- incomplete: false,
2354
+ incomplete,
2340
2355
  parseError,
2341
2356
  loaded: false,
2342
2357
  hasProfile: false,
@@ -5584,7 +5599,7 @@ function inferRepoFromPath(filePath, modelDirs) {
5584
5599
  const root = norm(dir);
5585
5600
  if (!fp.toLowerCase().startsWith(root.toLowerCase() + "/")) continue;
5586
5601
  const parts = fp.slice(root.length + 1).split("/");
5587
- if (parts.length >= 3 && seg.test(parts[0]) && seg.test(parts[1])) {
5602
+ if (parts.length >= 2 && seg.test(parts[0]) && seg.test(parts[1])) {
5588
5603
  return `${parts[0]}/${parts[1]}`;
5589
5604
  }
5590
5605
  return null;
@@ -5864,6 +5879,11 @@ function buildConnectSnippets(cli, base2, apiKey, modelName) {
5864
5879
  // src/chat/chat-routes.ts
5865
5880
  import { streamSSE as streamSSE2 } from "hono/streaming";
5866
5881
  var inflight = /* @__PURE__ */ new Map();
5882
+ var THINK_OPEN = "<think>";
5883
+ var THINK_CLOSE = "</think>";
5884
+ var CHAN_ANALYSIS_OPEN = "<|channel|>analysis<|message|>";
5885
+ var CHAN_CLOSE = "<|end|>";
5886
+ var CHAN_FINAL_SKIP = "<|start|>assistant<|channel|>final<|message|>";
5867
5887
  var EXPERT_SYSTEM_PROMPT = `You are the TurboLLM in-app expert assistant \u2014 a knowledgeable, friendly guide built into TurboLLM, a local-first desktop app for running large language models on the user's own machine.
5868
5888
 
5869
5889
  Your job is to help the user get the most out of TurboLLM:
@@ -6126,9 +6146,9 @@ async function runGeneration(d, stream, ctx) {
6126
6146
  ac.signal.addEventListener("abort", cancelReader, { once: true });
6127
6147
  }
6128
6148
  let roundContent = "";
6129
- let inThink = false;
6130
- let pendingThinkBuf = "";
6131
- let firstDelta = false;
6149
+ let parsePhase = "initial";
6150
+ let parseIsChannel = false;
6151
+ let parseBuf = "";
6132
6152
  let finishReason = "";
6133
6153
  const pendingToolCalls = /* @__PURE__ */ new Map();
6134
6154
  roundLoop: while (true) {
@@ -6172,8 +6192,8 @@ async function runGeneration(d, stream, ctx) {
6172
6192
  }
6173
6193
  continue;
6174
6194
  }
6175
- if (delta.reasoning_content) {
6176
- const rc = delta.reasoning_content;
6195
+ const rc = delta.reasoning_content ?? delta.reasoning;
6196
+ if (rc) {
6177
6197
  if (!thinkStart) thinkStart = Date.now();
6178
6198
  thinkEnd = Date.now();
6179
6199
  fullReasoning += rc;
@@ -6182,88 +6202,109 @@ async function runGeneration(d, stream, ctx) {
6182
6202
  }
6183
6203
  const raw_content = delta.content ?? "";
6184
6204
  if (!raw_content) continue;
6185
- let toProcess = raw_content;
6186
- while (toProcess.length > 0) {
6187
- if (!inThink && !firstDelta) {
6188
- pendingThinkBuf += toProcess;
6189
- const openIdx = pendingThinkBuf.indexOf("<think>");
6205
+ parseBuf += raw_content;
6206
+ while (parseBuf.length > 0) {
6207
+ if (parsePhase === "initial") {
6208
+ const thinkIdx = parseBuf.indexOf(THINK_OPEN);
6209
+ const chanIdx = parseBuf.indexOf(CHAN_ANALYSIS_OPEN);
6210
+ const hasThink = thinkIdx >= 0;
6211
+ const hasChan = chanIdx >= 0;
6212
+ const useThink = hasThink && (!hasChan || thinkIdx <= chanIdx);
6213
+ const openIdx = useThink ? thinkIdx : hasChan ? chanIdx : -1;
6214
+ const openTag = useThink ? THINK_OPEN : CHAN_ANALYSIS_OPEN;
6190
6215
  if (openIdx === 0) {
6191
- inThink = true;
6216
+ parseIsChannel = !useThink;
6217
+ parsePhase = "reasoning";
6192
6218
  if (!thinkStart) thinkStart = Date.now();
6193
- pendingThinkBuf = pendingThinkBuf.slice(7);
6194
- toProcess = "";
6219
+ parseBuf = parseBuf.slice(openTag.length);
6195
6220
  } else if (openIdx > 0) {
6196
- const before = pendingThinkBuf.slice(0, openIdx);
6221
+ const before = parseBuf.slice(0, openIdx);
6197
6222
  fullContent += before;
6198
6223
  roundContent += before;
6199
- firstDelta = true;
6200
6224
  if (!ttftMs) ttftMs = Date.now() - requestStart;
6201
6225
  await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: before }) });
6202
- inThink = true;
6226
+ parseIsChannel = !useThink;
6227
+ parsePhase = "reasoning";
6203
6228
  if (!thinkStart) thinkStart = Date.now();
6204
- pendingThinkBuf = pendingThinkBuf.slice(openIdx + 7);
6205
- toProcess = "";
6206
- } else if (pendingThinkBuf.length > 20) {
6207
- const flush = pendingThinkBuf;
6208
- pendingThinkBuf = "";
6209
- fullContent += flush;
6210
- roundContent += flush;
6211
- firstDelta = true;
6212
- if (!ttftMs) ttftMs = Date.now() - requestStart;
6213
- await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: flush }) });
6214
- toProcess = "";
6229
+ parseBuf = parseBuf.slice(openIdx + openTag.length);
6215
6230
  } else {
6216
- toProcess = "";
6231
+ const safeLen = parseBuf.length - (CHAN_ANALYSIS_OPEN.length - 1);
6232
+ if (safeLen > 0) {
6233
+ const flush = parseBuf.slice(0, safeLen);
6234
+ parseBuf = parseBuf.slice(safeLen);
6235
+ fullContent += flush;
6236
+ roundContent += flush;
6237
+ if (!ttftMs) ttftMs = Date.now() - requestStart;
6238
+ d.manager.setLiveGen({ phase: "gen", pct: 0, outputTokens: ++liveOut });
6239
+ await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: flush }) });
6240
+ parsePhase = "content";
6241
+ } else {
6242
+ break;
6243
+ }
6217
6244
  }
6218
- } else if (inThink) {
6219
- pendingThinkBuf += toProcess;
6220
- const closeIdx = pendingThinkBuf.indexOf("</think>");
6245
+ } else if (parsePhase === "reasoning") {
6246
+ const closeTag = parseIsChannel ? CHAN_CLOSE : THINK_CLOSE;
6247
+ const closeIdx = parseBuf.indexOf(closeTag);
6221
6248
  if (closeIdx >= 0) {
6222
- const thinkChunk = pendingThinkBuf.slice(0, closeIdx);
6223
- if (thinkChunk) {
6224
- thinkEnd = Date.now();
6225
- fullReasoning += thinkChunk;
6226
- await stream.writeSSE({ event: "reasoning", data: JSON.stringify({ delta: thinkChunk }) });
6249
+ if (closeIdx > 0) {
6250
+ const chunk2 = parseBuf.slice(0, closeIdx);
6251
+ fullReasoning += chunk2;
6252
+ await stream.writeSSE({ event: "reasoning", data: JSON.stringify({ delta: chunk2 }) });
6227
6253
  }
6228
- inThink = false;
6229
6254
  thinkEnd = Date.now();
6230
- pendingThinkBuf = pendingThinkBuf.slice(closeIdx + 8);
6231
- toProcess = "";
6232
- if (pendingThinkBuf) {
6233
- fullContent += pendingThinkBuf;
6234
- roundContent += pendingThinkBuf;
6235
- firstDelta = true;
6255
+ const wasChannel = parseIsChannel;
6256
+ parseBuf = parseBuf.slice(closeIdx + closeTag.length);
6257
+ parsePhase = wasChannel ? "skipFinal" : "content";
6258
+ if (!wasChannel && parseBuf) {
6259
+ fullContent += parseBuf;
6260
+ roundContent += parseBuf;
6236
6261
  if (!ttftMs) ttftMs = Date.now() - requestStart;
6237
- await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: pendingThinkBuf }) });
6238
- pendingThinkBuf = "";
6262
+ await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: parseBuf }) });
6263
+ parseBuf = "";
6239
6264
  }
6240
- } else {
6265
+ } else if (parseBuf.length >= closeTag.length) {
6266
+ const safe = parseBuf.length - (closeTag.length - 1);
6267
+ const chunk2 = parseBuf.slice(0, safe);
6268
+ parseBuf = parseBuf.slice(safe);
6241
6269
  thinkEnd = Date.now();
6242
- fullReasoning += pendingThinkBuf;
6243
- await stream.writeSSE({ event: "reasoning", data: JSON.stringify({ delta: pendingThinkBuf }) });
6244
- pendingThinkBuf = "";
6245
- toProcess = "";
6270
+ fullReasoning += chunk2;
6271
+ await stream.writeSSE({ event: "reasoning", data: JSON.stringify({ delta: chunk2 }) });
6272
+ } else {
6273
+ break;
6274
+ }
6275
+ } else if (parsePhase === "skipFinal") {
6276
+ if (parseBuf.startsWith(CHAN_FINAL_SKIP)) {
6277
+ parseBuf = parseBuf.slice(CHAN_FINAL_SKIP.length);
6278
+ parsePhase = "content";
6279
+ } else if (CHAN_FINAL_SKIP.startsWith(parseBuf) && parseBuf.length < CHAN_FINAL_SKIP.length) {
6280
+ break;
6281
+ } else {
6282
+ const skipIdx = parseBuf.indexOf(CHAN_FINAL_SKIP);
6283
+ parseBuf = skipIdx >= 0 ? parseBuf.slice(skipIdx + CHAN_FINAL_SKIP.length) : "";
6284
+ parsePhase = "content";
6246
6285
  }
6247
6286
  } else {
6248
6287
  if (!ttftMs) ttftMs = Date.now() - requestStart;
6249
- firstDelta = true;
6250
- fullContent += toProcess;
6251
- roundContent += toProcess;
6288
+ fullContent += parseBuf;
6289
+ roundContent += parseBuf;
6252
6290
  d.manager.setLiveGen({ phase: "gen", pct: 0, outputTokens: ++liveOut });
6253
- await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: toProcess }) });
6254
- toProcess = "";
6291
+ await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: parseBuf }) });
6292
+ parseBuf = "";
6293
+ break;
6255
6294
  }
6256
6295
  }
6257
6296
  }
6258
6297
  }
6259
6298
  ac.signal.removeEventListener("abort", cancelReader);
6260
- if (pendingThinkBuf && inThink) {
6261
- fullReasoning += pendingThinkBuf;
6262
- await stream.writeSSE({ event: "reasoning", data: JSON.stringify({ delta: pendingThinkBuf }) });
6263
- } else if (pendingThinkBuf) {
6264
- fullContent += pendingThinkBuf;
6265
- roundContent += pendingThinkBuf;
6266
- await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: pendingThinkBuf }) });
6299
+ if (parseBuf) {
6300
+ if (parsePhase === "reasoning") {
6301
+ fullReasoning += parseBuf;
6302
+ await stream.writeSSE({ event: "reasoning", data: JSON.stringify({ delta: parseBuf }) });
6303
+ } else {
6304
+ fullContent += parseBuf;
6305
+ roundContent += parseBuf;
6306
+ await stream.writeSSE({ event: "delta", data: JSON.stringify({ delta: parseBuf }) });
6307
+ }
6267
6308
  }
6268
6309
  if ((finishReason === "tool_calls" || pendingToolCalls.size > 0) && d.tools && toolIter <= MAX_TOOL_ITER) {
6269
6310
  const roundToolCalls = Array.from(pendingToolCalls.values());