turbollm 1.8.2 → 1.8.3

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 (109) hide show
  1. package/README.md +10 -5
  2. package/dist/cli.js +206 -33
  3. package/dist/webdist/assets/{AgentEditPage-DrcwQcJE.js → AgentEditPage-Bn7Ipokq.js} +1 -1
  4. package/dist/webdist/assets/{ChatScreen-BkpwUk-O.js → ChatScreen-B3w1GUl6.js} +1 -1
  5. package/dist/webdist/assets/{CodeComposer-DG4lgSoA.js → CodeComposer-cj9FLZq8.js} +1 -1
  6. package/dist/webdist/assets/CodeHomeScreen-Brq-hPKU.js +1 -0
  7. package/dist/webdist/assets/{CodeSessionScreen-aQesvCPO.js → CodeSessionScreen-QQPjiOnR.js} +1 -1
  8. package/dist/webdist/assets/{CustomizeScreen-DRrw2hs7.js → CustomizeScreen-CnBGN3ac.js} +1 -1
  9. package/dist/webdist/assets/DeveloperScreen-CM5RpeHx.js +1 -0
  10. package/dist/webdist/assets/{EnginesScreen-BvZtUKd0.js → EnginesScreen-B8zlkteU.js} +1 -1
  11. package/dist/webdist/assets/{FsBrowser-D0rDep29.js → FsBrowser-BmIXHDoO.js} +1 -1
  12. package/dist/webdist/assets/{ModelLoadMenu-K1Qolm9B.js → ModelLoadMenu-BhuL3y0h.js} +1 -1
  13. package/dist/webdist/assets/{ModelsScreen-HfLjpGmf.js → ModelsScreen-CxpMiIH-.js} +2 -2
  14. package/dist/webdist/assets/SettingsScreen-CvilSjLV.js +1 -0
  15. package/dist/webdist/assets/{SkillEditPage-W3ofhEQn.js → SkillEditPage-D8TxMXvF.js} +1 -1
  16. package/dist/webdist/assets/TokensScreen-Bgg-ezpv.js +1 -0
  17. package/dist/webdist/assets/{ToolApprovalBar-BGRW2hFN.js → ToolApprovalBar-N1UEc8ol.js} +3 -3
  18. package/dist/webdist/assets/WorkspaceScreen-MBZpXsG1.js +1 -0
  19. package/dist/webdist/assets/{agent-api-C48x10WZ.js → agent-api-D5VFF3mP.js} +1 -1
  20. package/dist/webdist/assets/{arc-B6W63qO_.js → arc-DIn4J2Lk.js} +1 -1
  21. package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-BNxjHCm7.js → architectureDiagram-3BPJPVTR-BzJslHoS.js} +1 -1
  22. package/dist/webdist/assets/{arrow-left-DmGW0W4g.js → arrow-left-BpVSMrri.js} +1 -1
  23. package/dist/webdist/assets/{badge-IsOHcYvf.js → badge-Bbul2j74.js} +1 -1
  24. package/dist/webdist/assets/{blockDiagram-GPEHLZMM-DrPlDIMe.js → blockDiagram-GPEHLZMM-B9IcI0TI.js} +1 -1
  25. package/dist/webdist/assets/{bot-DAa4xIIF.js → bot-CGlgDM61.js} +1 -1
  26. package/dist/webdist/assets/{c4Diagram-AAUBKEIU-BKtn6SbN.js → c4Diagram-AAUBKEIU-DTNAE2w5.js} +1 -1
  27. package/dist/webdist/assets/channel-DzOJVoSc.js +1 -0
  28. package/dist/webdist/assets/{chat-api-CtbwBsSY.js → chat-api-vZefHZ4v.js} +1 -1
  29. package/dist/webdist/assets/{chat-queries-BANbggZ1.js → chat-queries-C2DFl0KI.js} +1 -1
  30. package/dist/webdist/assets/check-D5Mwo3Z5.js +1 -0
  31. package/dist/webdist/assets/{chevron-left-DScqk7lr.js → chevron-left-Ciwr8fJO.js} +1 -1
  32. package/dist/webdist/assets/{chevron-right-DNc_yz-y.js → chevron-right-CzRe7Kav.js} +1 -1
  33. package/dist/webdist/assets/{chunk-2J33WTMH-B97vCEvd.js → chunk-2J33WTMH-zyTy7dVO.js} +1 -1
  34. package/dist/webdist/assets/{chunk-4BX2VUAB-B5mAgaq1.js → chunk-4BX2VUAB-PB8x0_Hd.js} +1 -1
  35. package/dist/webdist/assets/{chunk-55IACEB6-CCZRTh0d.js → chunk-55IACEB6-DA5kOQ34.js} +1 -1
  36. package/dist/webdist/assets/{chunk-727SXJPM-tZT9XNMs.js → chunk-727SXJPM-Bw7cNW67.js} +1 -1
  37. package/dist/webdist/assets/{chunk-AQP2D5EJ-CfIdLt8G.js → chunk-AQP2D5EJ-WCV-mwo1.js} +1 -1
  38. package/dist/webdist/assets/{chunk-FMBD7UC4-BRdScA0W.js → chunk-FMBD7UC4-DIrWPMJ6.js} +1 -1
  39. package/dist/webdist/assets/{chunk-ND2GUHAM-BedHAIQl.js → chunk-ND2GUHAM-CpNR0agz.js} +1 -1
  40. package/dist/webdist/assets/{chunk-QZHKN3VN-ByAw17Nt.js → chunk-QZHKN3VN-rraroDc3.js} +1 -1
  41. package/dist/webdist/assets/{circle-check-BNIKoJDg.js → circle-check-CuPI_Sim.js} +1 -1
  42. package/dist/webdist/assets/{circle-x-DfIf4ChY.js → circle-x-BVHvkV4f.js} +1 -1
  43. package/dist/webdist/assets/classDiagram-4FO5ZUOK-BvOZ0d_w.js +1 -0
  44. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-BvOZ0d_w.js +1 -0
  45. package/dist/webdist/assets/{common-BwXsHduN.js → common-n231fP12.js} +1 -1
  46. package/dist/webdist/assets/{copy-button-BcFHHH9b.js → copy-button-Bvc3-lid.js} +1 -1
  47. package/dist/webdist/assets/{cose-bilkent-S5V4N54A--x5WjVCv.js → cose-bilkent-S5V4N54A-D7iUMme8.js} +1 -1
  48. package/dist/webdist/assets/{dagre-BM42HDAG-DWQy_ulr.js → dagre-BM42HDAG-BrLWg27e.js} +1 -1
  49. package/dist/webdist/assets/{diagram-2AECGRRQ-DQo4yJeJ.js → diagram-2AECGRRQ-CSGxcQMM.js} +1 -1
  50. package/dist/webdist/assets/{diagram-5GNKFQAL-DwbynoQ2.js → diagram-5GNKFQAL-DclhxgFp.js} +1 -1
  51. package/dist/webdist/assets/{diagram-KO2AKTUF-CjKV0fFm.js → diagram-KO2AKTUF-CSStfYhJ.js} +1 -1
  52. package/dist/webdist/assets/{diagram-LMA3HP47-CXfjg5Fe.js → diagram-LMA3HP47-CIpoucHo.js} +1 -1
  53. package/dist/webdist/assets/{diagram-OG6HWLK6-CKEjI4NC.js → diagram-OG6HWLK6-DiA9zxOl.js} +1 -1
  54. package/dist/webdist/assets/dialog-D7uorVmS.js +1 -0
  55. package/dist/webdist/assets/{erDiagram-TEJ5UH35-D43kYg84.js → erDiagram-TEJ5UH35-ldfqRXS3.js} +1 -1
  56. package/dist/webdist/assets/{external-link-jnEQCJGd.js → external-link-Djq4PRwx.js} +1 -1
  57. package/dist/webdist/assets/{flowDiagram-I6XJVG4X-qGS0J-De.js → flowDiagram-I6XJVG4X-DL9lyEaN.js} +1 -1
  58. package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-3Ud_YPtk.js → ganttDiagram-6RSMTGT7-BOhbVgim.js} +1 -1
  59. package/dist/webdist/assets/{git-branch-BZW2_OwW.js → git-branch-BnK0yXx9.js} +1 -1
  60. package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-BhOiYdR2.js → gitGraphDiagram-PVQCEYII-DsiAhP7w.js} +1 -1
  61. package/dist/webdist/assets/{index-rezSzJo3.js → index-15S4jFJa.js} +2 -2
  62. package/dist/webdist/assets/{index-DffknE3S.css → index-CfEenPkR.css} +1 -1
  63. package/dist/webdist/assets/{infoDiagram-5YYISTIA-D2Yo845I.js → infoDiagram-5YYISTIA-Cjo31Bf1.js} +1 -1
  64. package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-BoTTMzTX.js → ishikawaDiagram-YF4QCWOH-BcrXs5eW.js} +1 -1
  65. package/dist/webdist/assets/{journeyDiagram-JHISSGLW-B30FNQqG.js → journeyDiagram-JHISSGLW-Br8yhmM3.js} +1 -1
  66. package/dist/webdist/assets/{kanban-definition-UN3LZRKU-DwuHMcyp.js → kanban-definition-UN3LZRKU-BRabkdYf.js} +1 -1
  67. package/dist/webdist/assets/{layers-BbMBWfEB.js → layers-DPA9Qzse.js} +1 -1
  68. package/dist/webdist/assets/{linear-BI-xSqy2.js → linear-BHyBqymd.js} +1 -1
  69. package/dist/webdist/assets/{mermaid.core-DjBtB3Ob.js → mermaid.core-1LxyS8HV.js} +4 -4
  70. package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-P0Blz3ba.js → mindmap-definition-RKZ34NQL-mkA1uqUK.js} +1 -1
  71. package/dist/webdist/assets/{pencil-pd4qdmIY.js → pencil-QTtpg3yU.js} +1 -1
  72. package/dist/webdist/assets/{personas-DGNsw4JH.js → personas-DkWvIHOp.js} +7 -3
  73. package/dist/webdist/assets/{pieDiagram-4H26LBE5-D_RWWNYB.js → pieDiagram-4H26LBE5-DA8Xl9Ac.js} +1 -1
  74. package/dist/webdist/assets/{plus-Co-23h9C.js → plus-CCp3TOSc.js} +1 -1
  75. package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-0UW-qXoo.js → quadrantDiagram-W4KKPZXB-CUdAHRpJ.js} +1 -1
  76. package/dist/webdist/assets/{refresh-cw-DBHReAx0.js → refresh-cw-Dx7SuJg9.js} +1 -1
  77. package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-DQPViUOU.js → requirementDiagram-4Y6WPE33-BAZNjAEw.js} +1 -1
  78. package/dist/webdist/assets/{rotate-ccw-L6hm18l-.js → rotate-ccw-Dc5bDRuI.js} +1 -1
  79. package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-CoFKXFxN.js → sankeyDiagram-5OEKKPKP-Caa8Igai.js} +1 -1
  80. package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-CTKbZzaz.js → sequenceDiagram-3UESZ5HK-B7v4zjHc.js} +1 -1
  81. package/dist/webdist/assets/{skeleton-CTqhU4nE.js → skeleton-zDNAncEV.js} +1 -1
  82. package/dist/webdist/assets/{sliders-horizontal-CqA4xQwh.js → sliders-horizontal-pdFDRpIF.js} +1 -1
  83. package/dist/webdist/assets/{star-C4xvUEne.js → star-CxZtY_PT.js} +1 -1
  84. package/dist/webdist/assets/{stateDiagram-AJRCARHV-Bbgyioxb.js → stateDiagram-AJRCARHV-CJ5GWb0F.js} +1 -1
  85. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-pOp9pkGE.js +1 -0
  86. package/dist/webdist/assets/{switch-vVu6UIJB.js → switch-D3C8JOte.js} +1 -1
  87. package/dist/webdist/assets/{terminal-BqY0B6tG.js → terminal-Bbrza0GS.js} +1 -1
  88. package/dist/webdist/assets/{timeline-definition-PNZ67QCA-CNuDMLf0.js → timeline-definition-PNZ67QCA-BPUdD63z.js} +1 -1
  89. package/dist/webdist/assets/{trash-2-CI-4vgjV.js → trash-2-CACOtmLn.js} +1 -1
  90. package/dist/webdist/assets/useIsDesktop-ClfQtAGq.js +1 -0
  91. package/dist/webdist/assets/{vennDiagram-CIIHVFJN-5KfL6f8c.js → vennDiagram-CIIHVFJN-CdZ4KAkR.js} +1 -1
  92. package/dist/webdist/assets/{wardley-L42UT6IY-KSQpq69w.js → wardley-L42UT6IY-Dzejgqpe.js} +1 -1
  93. package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-CRDC9-52.js → wardleyDiagram-YWT4CUSO-CwsJvJ20.js} +1 -1
  94. package/dist/webdist/assets/{wrench-B6O2XFxJ.js → wrench-ChbW0-h1.js} +1 -1
  95. package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-CFJUnPty.js → xychartDiagram-2RQKCTM6-ikNJwN3D.js} +1 -1
  96. package/dist/webdist/index.html +2 -2
  97. package/package.json +1 -1
  98. package/dist/webdist/assets/CodeHomeScreen-Cqv5c0nM.js +0 -1
  99. package/dist/webdist/assets/DeveloperScreen-pTaZZ6r5.js +0 -1
  100. package/dist/webdist/assets/SettingsScreen-z-UuH8MI.js +0 -1
  101. package/dist/webdist/assets/TokensScreen-dLb-UDmL.js +0 -1
  102. package/dist/webdist/assets/WorkspaceScreen-C5R7gzUi.js +0 -1
  103. package/dist/webdist/assets/channel-CDs60xNM.js +0 -1
  104. package/dist/webdist/assets/check-DeJoIInG.js +0 -1
  105. package/dist/webdist/assets/classDiagram-4FO5ZUOK-BLIfDCvc.js +0 -1
  106. package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-BLIfDCvc.js +0 -1
  107. package/dist/webdist/assets/dialog-uY3tk3E9.js +0 -1
  108. package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-DBMc27o_.js +0 -1
  109. package/dist/webdist/assets/useIsDesktop-B-UPxY57.js +0 -1
package/README.md CHANGED
@@ -202,9 +202,12 @@ the detail:
202
202
  - **Real measured tokens/sec** in the model list — **live** while generating, **last-session**
203
203
  when idle (never a synthetic estimate).
204
204
  - **Full load-parameter UI**, a superset of what other tools expose: context length, GPU offload
205
- (`-ngl`), **MoE CPU-offload (`--n-cpu-moe`)**, parallel slots, **KV-cache quant type** (incl.
206
- low-bit on supporting forks), CPU threads, flash attention, and **speculative decoding (NextN /
207
- MTP / draft, with a configurable draft min/max window)**.
205
+ (`-ngl`), **MoE CPU-offload (`--n-cpu-moe`)**, parallel slots, **independent K and V cache-quant
206
+ type** (incl. low-bit on supporting forks), CPU threads, flash attention, **speculative decoding
207
+ (NextN / MTP / draft, with a configurable draft min/max window)**, a **pinned engine port** per
208
+ model, and a **custom/raw flags** field for anything not exposed as its own control.
209
+ - **Copy the exact launch command** for a loaded model — runs the same config standalone with
210
+ llama.cpp/the fork directly, no TurboLLM required.
208
211
  - **Auto-fit GPU layers / MoE offload** — an optional toggle (off by default) that hands the
209
212
  GPU/CPU split decision to llama.cpp's own memory-fitting logic at load time instead of a fixed
210
213
  number, honored by Auto-tune too. Useful when a large context or model doesn't fit your usual
@@ -266,10 +269,12 @@ the detail:
266
269
  in every chat with no restart. A **Research** persona forces multi-step web search and cites sources inline.
267
270
  - **Tool-call approval gate** — every tool call asks for your approval by default before it runs,
268
271
  with **Deny**, **Allow**, **Allow for this chat**, or **Always Allow** on an inline bar above the
269
- composer. Set per-tool defaults globally from Settings → Tools & safety.
272
+ composer. Set per-tool defaults globally from Settings → Tools & safety, or flip **Auto-allow
273
+ all** to skip prompts entirely — a tool set to Deny still stays blocked either way.
270
274
  - **Usage dashboard** — a GitHub-style activity heatmap of your local generation history (adaptive
271
275
  1h/12h/24h boxes for the 7-day/30-day/all-time views), streaks, peak hour, a per-model
272
- breakdown, and a lifetime token-milestone tracker.
276
+ breakdown, a lifetime token-milestone tracker, and a separate **API tab** tracking tokens hitting
277
+ the gateway from external tools like Claude Code — not just in-app chat.
273
278
  - **Auto-memory** *(experimental, off by default)* — silently extracts durable facts you mention
274
279
  in chat (name, preferences, hardware) using your own loaded model, and carries them into future
275
280
  new conversations. Nothing leaves your device; the full fact list is reviewable and deletable
package/dist/cli.js CHANGED
@@ -300,6 +300,7 @@ function normalize(c) {
300
300
  toolPolicies.run_code = "allow";
301
301
  }
302
302
  c.tools.toolPolicies = toolPolicies;
303
+ c.tools.autoAllowAll = tl.autoAllowAll === true;
303
304
  const mc = c.mcp ?? {};
304
305
  c.mcp = {
305
306
  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")) : []
@@ -1381,6 +1382,7 @@ var Manager = class _Manager {
1381
1382
  child = null;
1382
1383
  startedAt = 0;
1383
1384
  errInfo = null;
1385
+ lastCommand = null;
1384
1386
  lastActivity = 0;
1385
1387
  logPathStr = "";
1386
1388
  exited = Promise.resolve();
@@ -1450,7 +1452,7 @@ var Manager = class _Manager {
1450
1452
  return;
1451
1453
  }
1452
1454
  }
1453
- const port2 = await allocPort();
1455
+ const port2 = await allocPort(opts.preferredPort);
1454
1456
  const logPath = join10(this.store.dir(), "logs", `engine-${opts.engine.id}.log`);
1455
1457
  mkdirSync6(dirname2(logPath), { recursive: true });
1456
1458
  const logStream = createWriteStream2(logPath);
@@ -1468,6 +1470,7 @@ var Manager = class _Manager {
1468
1470
  }
1469
1471
  }
1470
1472
  const { cmd, args } = engineCommand(opts, port2, slotSavePath);
1473
+ this.lastCommand = { cmd, args };
1471
1474
  const spawned = needsShellWrapper(opts.engine.kind) ? shellWrapped(cmd, args) : { cmd, args };
1472
1475
  const child = spawn(spawned.cmd, spawned.args, { cwd: dirname2(cmd), windowsHide: true, env: pyEngineEnv(opts.engine.kind, this.store.dir(), opts.engine.binPath) });
1473
1476
  child.stdout?.pipe(logStream, { end: false });
@@ -1541,6 +1544,27 @@ var Manager = class _Manager {
1541
1544
  if (!opts?.modelPath) throw new Error("no_such_model");
1542
1545
  await this.load(opts);
1543
1546
  }
1547
+ /** The exact command last spawned, formatted for copy-paste into a terminal
1548
+ * (GitHub Discord ask — "copy exact launch command"). Only meaningful while this
1549
+ * engine is the one actually running; null once stopped/swapped so a stale
1550
+ * command from a PREVIOUS model can never be copied as if it were current.
1551
+ *
1552
+ * Deliberately NOT byte-identical to what TurboLLM itself spawned: the point of
1553
+ * copying it is running the SAME model+config standalone, with llama.cpp/the fork
1554
+ * directly and TurboLLM out of the picture entirely — so `--no-webui` (added only
1555
+ * because TurboLLM serves its own frontend and doesn't want llama-server's built-in
1556
+ * one competing with it, `buildArgs` in this file) is stripped here: for a standalone
1557
+ * run it's the only UI available, so silently carrying it over would hand the user a
1558
+ * server with no way to talk to it. Everything else (model path, offload, KV type,
1559
+ * sampling, extra flags) is copied verbatim — it's what makes the command reproduce
1560
+ * the exact same config. */
1561
+ launchCommand() {
1562
+ if (this.state !== "running" && this.state !== "starting") return null;
1563
+ if (!this.lastCommand) return null;
1564
+ const args = this.lastCommand.args.filter((a) => a !== "--no-webui");
1565
+ const quote = (s) => /[\s"]/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
1566
+ return [this.lastCommand.cmd, ...args].map(quote).join(" ");
1567
+ }
1544
1568
  status() {
1545
1569
  const st = { state: this.state, err: this.errInfo, port: this.port, pid: this.pid, model: null, loadElapsedMs: 0 };
1546
1570
  if ((this.state === "running" || this.state === "starting") && this.opts) {
@@ -1774,7 +1798,15 @@ function buildArgs(opts, port2, slotSavePath) {
1774
1798
  args.push(...opts.extraArgs);
1775
1799
  return args;
1776
1800
  }
1777
- function allocPort() {
1801
+ function portFree(p) {
1802
+ return new Promise((resolve5) => {
1803
+ const srv = createServer();
1804
+ srv.once("error", () => resolve5(false));
1805
+ srv.listen(p, "127.0.0.1", () => srv.close(() => resolve5(true)));
1806
+ });
1807
+ }
1808
+ async function allocPort(preferredPort) {
1809
+ if (preferredPort && preferredPort > 0 && await portFree(preferredPort)) return preferredPort;
1778
1810
  return new Promise((resolve5, reject) => {
1779
1811
  const tryPort = (p) => {
1780
1812
  if (p > 8181) return reject(new Error("no_free_port"));
@@ -4274,6 +4306,24 @@ var EMBED_FILE_RE = /\b(bge[-_]|nomic[-_]embed|all[-_]minilm|e5[-_]|gte[-_]|stel
4274
4306
  function isEmbeddingModel(arch2, name) {
4275
4307
  return EMBED_ARCHS.has(arch2.toLowerCase()) || EMBED_FILE_RE.test(name);
4276
4308
  }
4309
+ function commonPrefixLen(a, b) {
4310
+ let i = 0;
4311
+ while (i < a.length && i < b.length && a[i] === b[i]) i++;
4312
+ return i;
4313
+ }
4314
+ var MIN_MMPROJ_CORRELATION_PREFIX = 6;
4315
+ function resolveMmproj(modelFile, candidates) {
4316
+ if (candidates.length <= 1) return candidates[0];
4317
+ const modelName = basename(modelFile.path).toLowerCase();
4318
+ const correlated = candidates.filter(
4319
+ (c) => commonPrefixLen(modelName, basename(c.path).toLowerCase()) >= MIN_MMPROJ_CORRELATION_PREFIX
4320
+ );
4321
+ if (correlated.length === 1) return correlated[0];
4322
+ const pool = correlated.length > 1 ? correlated : candidates;
4323
+ return pool.reduce(
4324
+ (best, c) => Math.abs(c.mtime - modelFile.mtime) < Math.abs(best.mtime - modelFile.mtime) ? c : best
4325
+ );
4326
+ }
4277
4327
  var ScannerError = class extends Error {
4278
4328
  constructor(code, message) {
4279
4329
  super(message);
@@ -4371,9 +4421,6 @@ var Scanner = class {
4371
4421
  for (const [dir, group] of byDir) {
4372
4422
  const mmprojFiles = group.filter((f) => basename(f.path).toLowerCase().includes("mmproj"));
4373
4423
  const modelFiles = group.filter((f) => !basename(f.path).toLowerCase().includes("mmproj"));
4374
- const mmproj = mmprojFiles.sort((a, b) => b.size - a.size)[0];
4375
- const mmprojPath = mmproj?.path ?? null;
4376
- const mmprojSizeBytes = mmproj?.size ?? 0;
4377
4424
  const splits = /* @__PURE__ */ new Map();
4378
4425
  const singles = [];
4379
4426
  for (const f of modelFiles) {
@@ -4390,7 +4437,8 @@ var Scanner = class {
4390
4437
  }
4391
4438
  }
4392
4439
  for (const f of singles) {
4393
- entries.push(await this.entryFor(f.path, f.size, f.mtime, dir, mmprojPath, mmprojSizeBytes, false));
4440
+ const mmproj = resolveMmproj(f, mmprojFiles);
4441
+ entries.push(await this.entryFor(f.path, f.size, f.mtime, dir, mmproj?.path ?? null, mmproj?.size ?? 0, false));
4394
4442
  await tick();
4395
4443
  }
4396
4444
  for (const { shards, total } of splits.values()) {
@@ -4405,7 +4453,8 @@ var Scanner = class {
4405
4453
  break;
4406
4454
  }
4407
4455
  }
4408
- entries.push(await this.entryFor(first.path, totalSize, first.mtime, dir, mmprojPath, mmprojSizeBytes, incomplete));
4456
+ const mmproj = resolveMmproj(first, mmprojFiles);
4457
+ entries.push(await this.entryFor(first.path, totalSize, first.mtime, dir, mmproj?.path ?? null, mmproj?.size ?? 0, incomplete));
4409
4458
  await tick();
4410
4459
  }
4411
4460
  }
@@ -4808,7 +4857,8 @@ function estimateVram(p, m, sys) {
4808
4857
  const weightsMb = sizeMb * Math.max(0, Math.min(1, gpuFrac));
4809
4858
  const kvElems = kvCacheElems(m, p.ctx);
4810
4859
  const ssmMb = ssmStateElems(m) * 4 / 1e6;
4811
- const kvMb = p.kvOffload === false ? 0 : (kvElems * kvBytesPerElem(p.kvTypeK) / 1e6 + ssmMb) * (p.kvUnified ? 1 : Math.max(1, p.parallel));
4860
+ const kvBytesMb = (kvElems / 2 * kvBytesPerElem(p.kvTypeK) + kvElems / 2 * kvBytesPerElem(p.kvTypeV)) / 1e6;
4861
+ const kvMb = p.kvOffload === false ? 0 : (kvBytesMb + ssmMb) * (p.kvUnified ? 1 : Math.max(1, p.parallel));
4812
4862
  const mmprojMb = p.useMmproj && p.mmprojGpu && m.mmprojPath ? m.mmprojSizeBytes / 1e6 * 1.15 : 0;
4813
4863
  const estMb = Math.round(weightsMb + kvMb + 800 + mmprojMb);
4814
4864
  const pct = estMb / totalVramMb;
@@ -5015,6 +5065,12 @@ var TOKEN_MILESTONES = [
5015
5065
  5e8,
5016
5066
  1e9
5017
5067
  ];
5068
+ function milestoneFor(lifetimeTotalTokens) {
5069
+ const achieved = [...TOKEN_MILESTONES].reverse().find((m) => m <= lifetimeTotalTokens) ?? null;
5070
+ const next = TOKEN_MILESTONES.find((m) => m > lifetimeTotalTokens) ?? null;
5071
+ const progressPct = next !== null ? Math.round((lifetimeTotalTokens - (achieved ?? 0)) / (next - (achieved ?? 0)) * 1e3) / 10 : null;
5072
+ return { achieved, next, progressPct };
5073
+ }
5018
5074
  function titleCaseModelName(modelKey) {
5019
5075
  const raw = modelKey.split("|")[0] ?? modelKey;
5020
5076
  return raw.replace(/\b\w/g, (c) => c.toUpperCase());
@@ -5359,6 +5415,20 @@ var ConversationStore = class {
5359
5415
  if (!this.hasColumn("agent_runs", "reverted_from_message_id")) this.db.exec(`ALTER TABLE agent_runs ADD COLUMN reverted_from_message_id TEXT;`);
5360
5416
  this.db.exec(`PRAGMA user_version = 33;`);
5361
5417
  }
5418
+ if (v < 34) {
5419
+ this.db.exec(`
5420
+ CREATE TABLE IF NOT EXISTS api_usage (
5421
+ id TEXT PRIMARY KEY,
5422
+ created_at TEXT NOT NULL,
5423
+ source TEXT NOT NULL,
5424
+ model_key TEXT,
5425
+ prompt_tokens INTEGER NOT NULL DEFAULT 0,
5426
+ gen_tokens INTEGER NOT NULL DEFAULT 0
5427
+ );
5428
+ CREATE INDEX IF NOT EXISTS idx_api_usage_created ON api_usage(created_at);
5429
+ PRAGMA user_version = 34;
5430
+ `);
5431
+ }
5362
5432
  }
5363
5433
  listConversations(q, kind = "all") {
5364
5434
  if (q) {
@@ -5532,22 +5602,25 @@ var ConversationStore = class {
5532
5602
  ORDER BY created_at ASC
5533
5603
  `).all();
5534
5604
  if (rows.length === 0) {
5605
+ const api2 = this.apiUsageStats(range);
5606
+ const milestone2 = milestoneFor(api2.lifetimeTotalTokens);
5535
5607
  return {
5536
5608
  range,
5537
5609
  sessions: 0,
5538
5610
  messages: 0,
5539
- totalTokens: 0,
5611
+ totalTokens: api2.totalTokens,
5540
5612
  activeDays: 0,
5541
5613
  currentStreak: 0,
5542
5614
  longestStreak: 0,
5543
5615
  peakHour: null,
5544
5616
  favoriteModel: null,
5545
5617
  firstMessageAt: null,
5546
- lifetimeTotalTokens: 0,
5547
- milestone: { achieved: null, next: null, progressPct: null },
5618
+ lifetimeTotalTokens: api2.lifetimeTotalTokens,
5619
+ milestone: milestone2,
5548
5620
  activity: { granularityHours: 24, buckets: [] },
5549
5621
  dailyByModel: [],
5550
- byModel: []
5622
+ byModel: [],
5623
+ api: api2
5551
5624
  };
5552
5625
  }
5553
5626
  const dayBuckets = /* @__PURE__ */ new Map();
@@ -5681,26 +5754,95 @@ var ConversationStore = class {
5681
5754
  }
5682
5755
  activity = { granularityHours, buckets };
5683
5756
  }
5684
- let lifetimeTotalTokens = 0;
5685
- for (const b of dayBuckets.values()) lifetimeTotalTokens += b.promptTokens + b.genTokens;
5686
- const achieved = [...TOKEN_MILESTONES].reverse().find((m) => m <= lifetimeTotalTokens) ?? null;
5687
- const next = TOKEN_MILESTONES.find((m) => m > lifetimeTotalTokens) ?? null;
5688
- const progressPct = next !== null ? Math.round((lifetimeTotalTokens - (achieved ?? 0)) / (next - (achieved ?? 0)) * 1e3) / 10 : null;
5757
+ let chatLifetimeTotalTokens = 0;
5758
+ for (const b of dayBuckets.values()) chatLifetimeTotalTokens += b.promptTokens + b.genTokens;
5759
+ const api = this.apiUsageStats(range);
5760
+ const combinedTotalTokens = totalTokens + api.totalTokens;
5761
+ const combinedLifetimeTotalTokens = chatLifetimeTotalTokens + api.lifetimeTotalTokens;
5762
+ const milestone = milestoneFor(combinedLifetimeTotalTokens);
5689
5763
  return {
5690
5764
  range,
5691
5765
  sessions: convIds.size,
5692
5766
  messages: scopedRows.length,
5693
- totalTokens,
5767
+ totalTokens: combinedTotalTokens,
5694
5768
  activeDays,
5695
5769
  currentStreak,
5696
5770
  longestStreak,
5697
5771
  peakHour,
5698
5772
  favoriteModel: byModel[0]?.displayName ?? null,
5699
5773
  firstMessageAt,
5700
- lifetimeTotalTokens,
5701
- milestone: { achieved, next, progressPct },
5774
+ lifetimeTotalTokens: combinedLifetimeTotalTokens,
5775
+ milestone,
5702
5776
  activity,
5703
5777
  dailyByModel,
5778
+ byModel,
5779
+ api
5780
+ };
5781
+ }
5782
+ /** Insert one completed gateway request (GitHub #71) — called from the two gateway entry
5783
+ * points (gateway.ts) right where in-app chat already records into `messages`. Fully
5784
+ * additive/fail-safe: callers wrap this the same way `recordCompletion` is wrapped. */
5785
+ recordApiUsage(rec) {
5786
+ this.db.prepare(`
5787
+ INSERT INTO api_usage (id, created_at, source, model_key, prompt_tokens, gen_tokens)
5788
+ VALUES ($id, $createdAt, $source, $modelKey, $promptTokens, $genTokens)
5789
+ `).run({
5790
+ $id: randomUUID3(),
5791
+ $createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5792
+ $source: rec.source,
5793
+ $modelKey: rec.modelKey,
5794
+ $promptTokens: Math.max(0, Math.floor(rec.promptTokens) || 0),
5795
+ $genTokens: Math.max(0, Math.floor(rec.genTokens) || 0)
5796
+ });
5797
+ }
5798
+ /** Gateway (external-client) token usage — see `ApiUsageStats`'s doc comment for why this
5799
+ * is a separate bucket from `tokenUsageStats`. Same local-day range scoping as the chat
5800
+ * stats above, but no streak/session concepts (a gateway request isn't a chat turn). */
5801
+ apiUsageStats(range = "all") {
5802
+ const rows = this.db.prepare(`
5803
+ SELECT created_at, source, model_key, prompt_tokens, gen_tokens FROM api_usage
5804
+ ORDER BY created_at ASC
5805
+ `).all();
5806
+ let lifetimeTotalTokens = 0;
5807
+ for (const r of rows) lifetimeTotalTokens += r.prompt_tokens + r.gen_tokens;
5808
+ if (rows.length === 0) {
5809
+ return { range, requests: 0, totalTokens: 0, lifetimeTotalTokens: 0, bySource: [], byModel: [] };
5810
+ }
5811
+ const todayKey = dayKey((/* @__PURE__ */ new Date()).toISOString());
5812
+ const heatStart = range === "all" ? null : addDays(todayKey, -(RANGE_WINDOW_DAYS[range] - 1));
5813
+ const scoped = heatStart === null ? rows : rows.filter((r) => dayKey(r.created_at) >= heatStart);
5814
+ const sourceTally = /* @__PURE__ */ new Map();
5815
+ const modelTally = /* @__PURE__ */ new Map();
5816
+ let totalTokens = 0;
5817
+ for (const r of scoped) {
5818
+ const tokens = r.prompt_tokens + r.gen_tokens;
5819
+ totalTokens += tokens;
5820
+ const st = sourceTally.get(r.source) ?? { requests: 0, totalTokens: 0 };
5821
+ st.requests++;
5822
+ st.totalTokens += tokens;
5823
+ sourceTally.set(r.source, st);
5824
+ if (r.model_key) {
5825
+ const mt = modelTally.get(r.model_key) ?? { requests: 0, promptTokens: 0, genTokens: 0 };
5826
+ mt.requests++;
5827
+ mt.promptTokens += r.prompt_tokens;
5828
+ mt.genTokens += r.gen_tokens;
5829
+ modelTally.set(r.model_key, mt);
5830
+ }
5831
+ }
5832
+ const byModel = [...modelTally.entries()].map(([modelKey, m]) => ({
5833
+ modelKey,
5834
+ displayName: titleCaseModelName(modelKey),
5835
+ requests: m.requests,
5836
+ promptTokens: m.promptTokens,
5837
+ genTokens: m.genTokens,
5838
+ totalTokens: m.promptTokens + m.genTokens
5839
+ })).sort((a, b) => b.totalTokens - a.totalTokens);
5840
+ return {
5841
+ range,
5842
+ requests: scoped.length,
5843
+ totalTokens,
5844
+ lifetimeTotalTokens,
5845
+ bySource: [...sourceTally.entries()].map(([source, s]) => ({ source, ...s })),
5704
5846
  byModel
5705
5847
  };
5706
5848
  }
@@ -10772,10 +10914,11 @@ function buildSnapshot(conv, cfg2, version2, exportedAt, format) {
10772
10914
  }
10773
10915
 
10774
10916
  // src/tools/tool-policy.ts
10775
- function resolveToolPolicy(name, globalPolicies, convOverrides) {
10917
+ function resolveToolPolicy(name, globalPolicies, convOverrides, autoAllowAll = false) {
10776
10918
  if (convOverrides[name]) return convOverrides[name];
10777
- if (globalPolicies[name]) return globalPolicies[name];
10778
- return "ask";
10919
+ const global = globalPolicies[name];
10920
+ if (global) return global === "ask" && autoAllowAll ? "allow" : global;
10921
+ return autoAllowAll ? "allow" : "ask";
10779
10922
  }
10780
10923
 
10781
10924
  // src/tools/approval-gate.ts
@@ -10813,8 +10956,8 @@ function resolveToolApproval(key, decision) {
10813
10956
 
10814
10957
  // src/tools/execute-with-approval.ts
10815
10958
  async function executeToolCallWithApproval(params) {
10816
- const { tools, sink, convId, id, name, args, globalPolicies, convOverrides, signal, interactive, agentAllowedTools } = params;
10817
- const policy = resolveToolPolicy(name, globalPolicies, convOverrides);
10959
+ const { tools, sink, convId, id, name, args, globalPolicies, convOverrides, signal, interactive, agentAllowedTools, autoAllowAll } = params;
10960
+ const policy = resolveToolPolicy(name, globalPolicies, convOverrides, autoAllowAll);
10818
10961
  if (policy === "deny") {
10819
10962
  const result2 = "Blocked: this tool is set to Deny in Tool Permissions settings.";
10820
10963
  await sink({ event: "tool_call", data: { id, name, args, status: "done", result: result2 } });
@@ -11927,6 +12070,7 @@ After reviewing the search results, include a confidence assessment on a line by
11927
12070
  // start) — otherwise a same-turn repeat tool call misses an "Allow for this
11928
12071
  // chat" decision the user just made via POST .../tool-calls/:id/approve.
11929
12072
  convOverrides: d.db.getToolOverrides(convId),
12073
+ autoAllowAll: d.store.snapshot().tools.autoAllowAll ?? false,
11930
12074
  signal: ac.signal,
11931
12075
  interactive: true
11932
12076
  });
@@ -12998,6 +13142,8 @@ function registerApi(app2, d) {
12998
13142
  pid: ms.pid
12999
13143
  };
13000
13144
  if (ms.err) engine.error = ms.err;
13145
+ const launchCommand = d.manager.launchCommand();
13146
+ if (launchCommand) engine.launchCommand = launchCommand;
13001
13147
  const model = ms.model ? { key: ms.model.key, name: ms.model.name, quant: ms.model.quant, ctx: ms.model.ctx, vision: ms.model.vision, loadElapsedMs: ms.loadElapsedMs } : null;
13002
13148
  const engineStats = ms.state === "running" ? d.manager.sessionStats() : null;
13003
13149
  const liveGeneration = ms.state === "running" ? d.manager.liveGeneration() : null;
@@ -13852,7 +13998,8 @@ function registerApi(app2, d) {
13852
13998
  model: { key: entry.key, name: entry.name, quant: entry.quant, ctx: entry.nativeCtx, vision: entry.vision },
13853
13999
  modelPath: entry.path,
13854
14000
  extraArgs,
13855
- tensorParallelSize: savedProfile?.gpu?.tensorParallelSize
14001
+ tensorParallelSize: savedProfile?.gpu?.tensorParallelSize,
14002
+ preferredPort: savedProfile?.port
13856
14003
  };
13857
14004
  } else {
13858
14005
  const saved = getModelProfile(cfg2, entry.key, active.id);
@@ -13862,7 +14009,8 @@ function registerApi(app2, d) {
13862
14009
  engine: active,
13863
14010
  model: { key: entry.key, name: entry.name, quant: entry.quant, ctx: profile.ctx, vision: entry.vision },
13864
14011
  modelPath: entry.path,
13865
- extraArgs
14012
+ extraArgs,
14013
+ preferredPort: profile.port
13866
14014
  };
13867
14015
  }
13868
14016
  void d.manager.load(opts2, { beforeStart: () => d.comfy?.freeComfyUIBeforeLoad() ?? Promise.resolve() }).catch((e) => console.warn(`engine load failed: ${e}`));
@@ -14204,6 +14352,7 @@ function registerApi(app2, d) {
14204
14352
  Object.assign(cfg2.gateway, gwUpdates);
14205
14353
  if (toolchainDirs !== void 0) cfg2.build.toolchainDirs = toolchainDirs;
14206
14354
  if (toolPolicies !== void 0) cfg2.tools.toolPolicies = toolPolicies;
14355
+ if (b.autoAllowAll !== void 0) cfg2.tools.autoAllowAll = !!b.autoAllowAll;
14207
14356
  if (b.autoLoadOnStart !== void 0) cfg2.autoLoadOnStart = !!b.autoLoadOnStart;
14208
14357
  if (vramHeadroomMb !== void 0) cfg2.vramHeadroomMb = vramHeadroomMb;
14209
14358
  if (telemetryLevel !== void 0) cfg2.telemetry.level = telemetryLevel;
@@ -14580,7 +14729,8 @@ function settingsPayload(d) {
14580
14729
  experimental: cfg2.daemon.experimental,
14581
14730
  // Tool-call approval gate: global per-tool policy ('ask' | 'allow' | 'deny').
14582
14731
  // Not secret — echoed back directly so Settings can render Tool Permissions.
14583
- toolPolicies: cfg2.tools.toolPolicies ?? {}
14732
+ toolPolicies: cfg2.tools.toolPolicies ?? {},
14733
+ autoAllowAll: cfg2.tools.autoAllowAll ?? false
14584
14734
  };
14585
14735
  }
14586
14736
  function telemetryPreview(level, version2) {
@@ -17607,6 +17757,7 @@ function registerGateway(app2, d) {
17607
17757
  (u) => {
17608
17758
  try {
17609
17759
  d.manager.recordCompletion({ inputTokens: u.inputTokens, outputTokens: u.outputTokens });
17760
+ d.db.recordApiUsage({ source: "anthropic", modelKey: req.model ?? null, promptTokens: u.inputTokens, genTokens: u.outputTokens });
17610
17761
  } catch {
17611
17762
  }
17612
17763
  },
@@ -17633,7 +17784,7 @@ function registerGateway(app2, d) {
17633
17784
  }
17634
17785
  try {
17635
17786
  const oaiRes = await res.json();
17636
- recordOpenAiUsage(d, oaiRes);
17787
+ recordOpenAiUsage(d, oaiRes, "anthropic", req.model ?? null);
17637
17788
  return c.json(mapFromOpenAI(oaiRes, modelName));
17638
17789
  } finally {
17639
17790
  d.manager.generationEnd();
@@ -17710,6 +17861,9 @@ function registerGateway(app2, d) {
17710
17861
  const alias = engineModelAlias(d.registry.active()?.kind ?? "");
17711
17862
  if (alias) parsedBody.model = alias;
17712
17863
  }
17864
+ if (parsedBody?.stream === true && parsedBody.stream_options === void 0) {
17865
+ parsedBody.stream_options = { include_usage: true };
17866
+ }
17713
17867
  headers.delete("content-length");
17714
17868
  init.body = parsedBody ? JSON.stringify(parsedBody) : "";
17715
17869
  } else {
@@ -17722,7 +17876,8 @@ function registerGateway(app2, d) {
17722
17876
  try {
17723
17877
  const [a, b] = res.body.tee();
17724
17878
  d.manager.generationStart();
17725
- void recordOpenAiStreamUsage(d, b).finally(() => d.manager.generationEnd());
17879
+ const drain = parsedBody?.stream === true ? recordOpenAiStreamUsage(d, b, "openai", requestedModel || null) : recordOpenAiJsonUsage(d, b, "openai", requestedModel || null);
17880
+ void drain.finally(() => d.manager.generationEnd());
17726
17881
  return new Response(a, { status: res.status, headers: res.headers });
17727
17882
  } catch {
17728
17883
  return new Response(res.body, { status: res.status, headers: res.headers });
@@ -17731,7 +17886,7 @@ function registerGateway(app2, d) {
17731
17886
  return new Response(res.body, { status: res.status, headers: res.headers });
17732
17887
  });
17733
17888
  }
17734
- function recordOpenAiUsage(d, oai) {
17889
+ function recordOpenAiUsage(d, oai, source, modelKey) {
17735
17890
  try {
17736
17891
  const usage = oai.usage;
17737
17892
  const timings = oai.timings;
@@ -17741,10 +17896,19 @@ function recordOpenAiUsage(d, oai) {
17741
17896
  promptTps: timings?.prompt_per_second,
17742
17897
  genTps: timings?.predicted_per_second
17743
17898
  });
17899
+ d.db.recordApiUsage({ source, modelKey, promptTokens: usage?.prompt_tokens ?? 0, genTokens: usage?.completion_tokens ?? 0 });
17900
+ } catch {
17901
+ }
17902
+ }
17903
+ async function recordOpenAiJsonUsage(d, body6, source, modelKey) {
17904
+ try {
17905
+ const text = await new Response(body6).text();
17906
+ const oai = JSON.parse(text);
17907
+ recordOpenAiUsage(d, oai, source, modelKey);
17744
17908
  } catch {
17745
17909
  }
17746
17910
  }
17747
- async function recordOpenAiStreamUsage(d, body6) {
17911
+ async function recordOpenAiStreamUsage(d, body6, source, modelKey) {
17748
17912
  try {
17749
17913
  const reader = body6.getReader();
17750
17914
  const dec = new TextDecoder();
@@ -17790,6 +17954,7 @@ async function recordOpenAiStreamUsage(d, body6) {
17790
17954
  }
17791
17955
  }
17792
17956
  d.manager.recordCompletion({ inputTokens: promptTokens, outputTokens: completionTokens, promptTps, genTps });
17957
+ d.db.recordApiUsage({ source, modelKey, promptTokens, genTokens: completionTokens });
17793
17958
  } catch {
17794
17959
  }
17795
17960
  }
@@ -17800,13 +17965,21 @@ var WEB_ROOT = join29(dirname11(fileURLToPath(import.meta.url)), "webdist");
17800
17965
  function createApp(d) {
17801
17966
  const app2 = new Hono();
17802
17967
  app2.use(
17803
- "*",
17968
+ "/v1/*",
17804
17969
  cors({
17805
17970
  origin: "*",
17806
17971
  allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
17807
17972
  allowHeaders: ["Content-Type", "Authorization", "x-api-key", "X-TurboLLM-Auth"]
17808
17973
  })
17809
17974
  );
17975
+ app2.use(
17976
+ "/api/*",
17977
+ cors({
17978
+ origin: "https://turbollm.dev",
17979
+ allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
17980
+ allowHeaders: ["Content-Type", "Authorization", "x-api-key", "X-TurboLLM-Auth"]
17981
+ })
17982
+ );
17810
17983
  app2.use("*", async (c, next) => {
17811
17984
  c.header("Server", `TurboLLM/${d.version}`);
17812
17985
  await next();
@@ -1 +1 @@
1
- import{j as e}from"./radix-C-sz7VcM.js";import{u as M,b as W,r as b}from"./vendor-BFom-Mq6.js";import{u as $,b as G,c as H,d as Y,e as J,a as E,B as y,f as B,t as h,A as C}from"./index-rezSzJo3.js";import{f as U}from"./chat-api-CtbwBsSY.js";import{f as V,a as X}from"./agent-api-C48x10WZ.js";import{P as z}from"./personas-DGNsw4JH.js";import{C as Z}from"./chevron-left-DScqk7lr.js";import{R as ee}from"./rotate-ccw-L6hm18l-.js";import{T as te}from"./trash-2-CI-4vgjV.js";import{C as se}from"./chevron-right-DNc_yz-y.js";const ne=()=>({name:"",description:"",systemPrompt:"",skillIds:[],tools:[]});function oe(s){const d=[],x=new Map;for(const n of s){const o=/^mcp__([^_]+(?:_[^_]+)*)__/.exec(n.name);if(!o){d.push(n);continue}const i=o[1],p=/^\[([^\]]+)\]/.exec(n.description),c=p?p[1]:i.replace(/_/g," ");x.has(i)||x.set(i,{key:i,label:c,tools:[]}),x.get(i).tools.push(n)}return{builtin:d,groups:[...x.values()].sort((n,o)=>n.label.localeCompare(o.label))}}function ae(s){return s.replace(/^mcp__[^_]+(?:_[^_]+)*__/,"")}function le(s){return s.replace(/^\[[^\]]+\]\s*/,"")}function q({tool:s,checked:d,onToggle:x}){return e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[12px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:d,onChange:x}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"font-mono text-ink",children:ae(s.name)}),s.description&&e.jsx("span",{className:"text-faint",children:le(s.description)})]})]})}function re({group:s,selected:d,onToggleAll:x,onToggleOne:n}){const[o,i]=b.useState(!1),p=s.tools.map(l=>l.name),c=p.filter(l=>d.has(l)).length,a=c===p.length,f=c>0&&!a,u=b.useRef(null);return b.useEffect(()=>{u.current&&(u.current.indeterminate=f)},[f]),e.jsxs("div",{className:"rounded-md border border-border",children:[e.jsxs("div",{className:"flex items-center gap-2 px-2.5 py-2",children:[e.jsx("input",{ref:u,type:"checkbox",checked:a,onChange:()=>x(p,!a)}),e.jsxs("button",{type:"button",onClick:()=>i(l=>!l),className:"flex flex-1 items-center gap-1.5 text-left text-[13px] text-ink",children:[e.jsx(se,{size:12,className:B("shrink-0 text-faint transition-transform",o&&"rotate-90")}),s.label,e.jsxs("span",{className:"text-[11px] text-faint",children:["(",c,"/",p.length,")"]})]})]}),o&&e.jsx("div",{className:"flex flex-col gap-1.5 border-t border-border px-3 py-2 pl-9",children:s.tools.map(l=>e.jsx(q,{tool:l,checked:d.has(l.name),onToggle:()=>n(l.name)},l.name))})]})}function ie({tools:s,selected:d,onChange:x}){const n=new Set(d),{builtin:o,groups:i}=oe(s),p=a=>x(n.has(a)?d.filter(f=>f!==a):[...d,a]),c=(a,f)=>{const u=d.filter(l=>!a.includes(l));x(f?[...u,...a]:u)};return s.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No tools available yet."}):e.jsxs("div",{className:"flex flex-col gap-2",children:[o.length>0&&e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:o.map(a=>e.jsx(q,{tool:a,checked:n.has(a.name),onToggle:()=>p(a.name)},a.name))}),i.map(a=>e.jsx(re,{group:a,selected:n,onToggleAll:c,onToggleOne:p},a.key))]})}function ke(){const{agentId:s="new"}=M(),d=W(),x=$(),n=s==="new",o=!n&&z.some(t=>t.id===s),i=o?z.find(t=>t.id===s):void 0,p=G(),c=!n&&!o?(p.data??[]).find(t=>t.id===s):void 0,a=H(),f=Y(),u=J(),l=o?f.data?.[s]:void 0,_=E({queryKey:X.list(),queryFn:V,staleTime:0}),k=_.data??[],I=E({queryKey:["available-tools"],queryFn:U}),j=I.data??[],[m,g]=b.useState(ne()),[S,T]=b.useState(!1),[A,P]=b.useState(!1),[D,w]=b.useState(!1),[R,L]=b.useState("skills");n&&!S&&(k.length||j.length)&&(g(t=>({...t,skillIds:k.map(r=>r.id),tools:j.map(r=>r.name)})),T(!0)),!n&&!o&&!S&&c&&(g({name:c.name,description:c.description,systemPrompt:c.systemPrompt,skillIds:c.skillIds,tools:c.tools}),T(!0)),o&&!S&&i&&f.isSuccess&&(k.length||j.length)&&(g({name:l?.name??i.name,description:l?.description??i.description,systemPrompt:l?.systemPrompt??i.systemPrompt,skillIds:l?.skillIds??k.map(t=>t.id),tools:l?.tools??j.map(t=>t.name)}),T(!0));const N=()=>d("/customize"),Q=t=>g(r=>({...r,skillIds:r.skillIds.includes(t)?r.skillIds.filter(v=>v!==t):[...r.skillIds,t]})),O=()=>{if(!m.name.trim()){h.error("Name is required.");return}P(!0);const t={name:m.name.trim(),description:m.description.trim(),systemPrompt:m.systemPrompt,skillIds:m.skillIds,tools:m.tools};if(o){u.save.mutate({id:s,override:t},{onSuccess:()=>{h.success("Agent saved."),N()},onError:v=>{h.error(v instanceof C?v.message:"Could not save agent."),P(!1)}});return}const r={onSuccess:()=>{x.invalidateQueries({queryKey:["chat-agents"]}),h.success(n?"Agent created.":"Agent saved."),N()},onError:v=>{h.error(v instanceof C?v.message:"Could not save agent."),P(!1)}};n?a.add.mutate(t,r):a.update.mutate({id:s,patch:t},r)},F=()=>{u.reset.mutate(s,{onSuccess:()=>{h.success("Reset to default."),i&&g({name:i.name,description:i.description,systemPrompt:i.systemPrompt,skillIds:k.map(t=>t.id),tools:j.map(t=>t.name)})},onError:t=>h.error(t instanceof C?t.message:"Could not reset agent.")})},K=()=>{a.remove.mutate(s,{onSuccess:()=>{h.success("Agent deleted."),N()},onError:t=>h.error(t instanceof C?t.message:"Could not delete agent.")})};return!n&&!o&&p.isSuccess&&!c?e.jsxs("div",{className:"flex w-full flex-col items-center gap-3 px-8 py-16",children:[e.jsx("p",{className:"text-[14px] text-muted",children:"That agent no longer exists."}),e.jsx(y,{size:"sm",variant:"outline",onClick:N,children:"Back to agents"})]}):e.jsxs("div",{className:"flex w-full max-w-4xl flex-col gap-5 px-4 py-6 md:px-8",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",onClick:N,className:"text-faint hover:text-ink",title:"Back to agents",children:e.jsx(Z,{size:18})}),e.jsx("span",{className:"text-[15px] font-medium text-ink",children:n?"New agent":o?l?"Edit agent (modified built-in)":"Edit agent (built-in)":"Edit agent"}),e.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[o&&e.jsxs(y,{size:"sm",variant:"outline",onClick:F,disabled:u.reset.isPending,children:[e.jsx(ee,{size:13})," Reset to default"]}),e.jsx(y,{size:"sm",variant:"outline",onClick:N,children:"Cancel"}),e.jsx(y,{size:"sm",onClick:O,disabled:A||!m.name.trim(),children:A?"Saving…":"Save"})]})]}),o&&e.jsx("p",{className:"rounded-md border border-border bg-panel px-3 py-2 text-[12px] text-muted",children:"Built-in agents can be edited in place — Reset to default discards your changes and restores TurboLLM's original prompt and access."}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Name"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"Release Notes Writer",value:m.name,onChange:t=>g(r=>({...r,name:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Description"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"What this agent is for, in one line",value:m.description,onChange:t=>g(r=>({...r,description:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"System prompt"}),e.jsx("textarea",{className:"min-h-[160px] resize-y rounded-md border border-border bg-bg px-3 py-2 font-mono text-[12px] leading-relaxed text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"You are…",value:m.systemPrompt,onChange:t=>g(r=>({...r,systemPrompt:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-2.5",children:[e.jsx("div",{className:"inline-flex w-fit rounded-md border border-border p-0.5",children:["skills","tools"].map(t=>e.jsx("button",{type:"button",onClick:()=>L(t),className:B("rounded px-3 py-1.5 text-[13px] font-medium transition-colors",R===t?"bg-accent/12 text-accent":"text-muted hover:text-ink"),children:t==="skills"?`Skills (${m.skillIds.length})`:`Tools (${m.tools.length})`},t))}),R==="skills"?_.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):k.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No skills yet — create one under Customize → Skills."}):e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:k.map(t=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[13px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:m.skillIds.includes(t.id),onChange:()=>Q(t.id)}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"text-ink",children:t.name}),t.description&&e.jsx("span",{className:"text-[12px] text-muted",children:t.description})]})]},t.id))}):I.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):e.jsx(ie,{tools:j,selected:m.tools,onChange:t=>g(r=>({...r,tools:t}))})]}),!n&&!o&&(D?e.jsxs("div",{className:"flex items-center gap-2 self-start rounded-md border border-border bg-panel p-2 text-[12px]",children:[e.jsx("span",{className:"flex-1 text-muted",children:"Delete this agent?"}),e.jsx("button",{type:"button",onClick:K,className:"rounded px-2 py-1 text-[color:var(--err)] hover:bg-[color:color-mix(in_srgb,var(--err)_12%,transparent)]",children:"Delete"}),e.jsx("button",{type:"button",onClick:()=>w(!1),className:"rounded px-2 py-1 text-faint hover:text-ink",children:"Cancel"})]}):e.jsxs("button",{type:"button",onClick:()=>w(!0),className:"flex items-center gap-1.5 self-start text-[12px] text-faint hover:text-[color:var(--err)]",children:[e.jsx(te,{size:13})," Delete agent"]}))]})}export{ke as AgentEditPage};
1
+ import{j as e}from"./radix-C-sz7VcM.js";import{u as M,b as W,r as b}from"./vendor-BFom-Mq6.js";import{u as $,b as G,c as H,d as Y,e as J,a as E,B as y,f as B,t as h,A as C}from"./index-15S4jFJa.js";import{f as U}from"./chat-api-vZefHZ4v.js";import{f as V,a as X}from"./agent-api-D5VFF3mP.js";import{P as z}from"./personas-DkWvIHOp.js";import{C as Z}from"./chevron-left-Ciwr8fJO.js";import{R as ee}from"./rotate-ccw-Dc5bDRuI.js";import{T as te}from"./trash-2-CACOtmLn.js";import{C as se}from"./chevron-right-CzRe7Kav.js";const ne=()=>({name:"",description:"",systemPrompt:"",skillIds:[],tools:[]});function oe(s){const d=[],x=new Map;for(const n of s){const o=/^mcp__([^_]+(?:_[^_]+)*)__/.exec(n.name);if(!o){d.push(n);continue}const i=o[1],p=/^\[([^\]]+)\]/.exec(n.description),c=p?p[1]:i.replace(/_/g," ");x.has(i)||x.set(i,{key:i,label:c,tools:[]}),x.get(i).tools.push(n)}return{builtin:d,groups:[...x.values()].sort((n,o)=>n.label.localeCompare(o.label))}}function ae(s){return s.replace(/^mcp__[^_]+(?:_[^_]+)*__/,"")}function le(s){return s.replace(/^\[[^\]]+\]\s*/,"")}function q({tool:s,checked:d,onToggle:x}){return e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[12px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:d,onChange:x}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"font-mono text-ink",children:ae(s.name)}),s.description&&e.jsx("span",{className:"text-faint",children:le(s.description)})]})]})}function re({group:s,selected:d,onToggleAll:x,onToggleOne:n}){const[o,i]=b.useState(!1),p=s.tools.map(l=>l.name),c=p.filter(l=>d.has(l)).length,a=c===p.length,f=c>0&&!a,u=b.useRef(null);return b.useEffect(()=>{u.current&&(u.current.indeterminate=f)},[f]),e.jsxs("div",{className:"rounded-md border border-border",children:[e.jsxs("div",{className:"flex items-center gap-2 px-2.5 py-2",children:[e.jsx("input",{ref:u,type:"checkbox",checked:a,onChange:()=>x(p,!a)}),e.jsxs("button",{type:"button",onClick:()=>i(l=>!l),className:"flex flex-1 items-center gap-1.5 text-left text-[13px] text-ink",children:[e.jsx(se,{size:12,className:B("shrink-0 text-faint transition-transform",o&&"rotate-90")}),s.label,e.jsxs("span",{className:"text-[11px] text-faint",children:["(",c,"/",p.length,")"]})]})]}),o&&e.jsx("div",{className:"flex flex-col gap-1.5 border-t border-border px-3 py-2 pl-9",children:s.tools.map(l=>e.jsx(q,{tool:l,checked:d.has(l.name),onToggle:()=>n(l.name)},l.name))})]})}function ie({tools:s,selected:d,onChange:x}){const n=new Set(d),{builtin:o,groups:i}=oe(s),p=a=>x(n.has(a)?d.filter(f=>f!==a):[...d,a]),c=(a,f)=>{const u=d.filter(l=>!a.includes(l));x(f?[...u,...a]:u)};return s.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No tools available yet."}):e.jsxs("div",{className:"flex flex-col gap-2",children:[o.length>0&&e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:o.map(a=>e.jsx(q,{tool:a,checked:n.has(a.name),onToggle:()=>p(a.name)},a.name))}),i.map(a=>e.jsx(re,{group:a,selected:n,onToggleAll:c,onToggleOne:p},a.key))]})}function ke(){const{agentId:s="new"}=M(),d=W(),x=$(),n=s==="new",o=!n&&z.some(t=>t.id===s),i=o?z.find(t=>t.id===s):void 0,p=G(),c=!n&&!o?(p.data??[]).find(t=>t.id===s):void 0,a=H(),f=Y(),u=J(),l=o?f.data?.[s]:void 0,_=E({queryKey:X.list(),queryFn:V,staleTime:0}),k=_.data??[],I=E({queryKey:["available-tools"],queryFn:U}),j=I.data??[],[m,g]=b.useState(ne()),[S,T]=b.useState(!1),[A,P]=b.useState(!1),[D,w]=b.useState(!1),[R,L]=b.useState("skills");n&&!S&&(k.length||j.length)&&(g(t=>({...t,skillIds:k.map(r=>r.id),tools:j.map(r=>r.name)})),T(!0)),!n&&!o&&!S&&c&&(g({name:c.name,description:c.description,systemPrompt:c.systemPrompt,skillIds:c.skillIds,tools:c.tools}),T(!0)),o&&!S&&i&&f.isSuccess&&(k.length||j.length)&&(g({name:l?.name??i.name,description:l?.description??i.description,systemPrompt:l?.systemPrompt??i.systemPrompt,skillIds:l?.skillIds??k.map(t=>t.id),tools:l?.tools??j.map(t=>t.name)}),T(!0));const N=()=>d("/customize"),Q=t=>g(r=>({...r,skillIds:r.skillIds.includes(t)?r.skillIds.filter(v=>v!==t):[...r.skillIds,t]})),O=()=>{if(!m.name.trim()){h.error("Name is required.");return}P(!0);const t={name:m.name.trim(),description:m.description.trim(),systemPrompt:m.systemPrompt,skillIds:m.skillIds,tools:m.tools};if(o){u.save.mutate({id:s,override:t},{onSuccess:()=>{h.success("Agent saved."),N()},onError:v=>{h.error(v instanceof C?v.message:"Could not save agent."),P(!1)}});return}const r={onSuccess:()=>{x.invalidateQueries({queryKey:["chat-agents"]}),h.success(n?"Agent created.":"Agent saved."),N()},onError:v=>{h.error(v instanceof C?v.message:"Could not save agent."),P(!1)}};n?a.add.mutate(t,r):a.update.mutate({id:s,patch:t},r)},F=()=>{u.reset.mutate(s,{onSuccess:()=>{h.success("Reset to default."),i&&g({name:i.name,description:i.description,systemPrompt:i.systemPrompt,skillIds:k.map(t=>t.id),tools:j.map(t=>t.name)})},onError:t=>h.error(t instanceof C?t.message:"Could not reset agent.")})},K=()=>{a.remove.mutate(s,{onSuccess:()=>{h.success("Agent deleted."),N()},onError:t=>h.error(t instanceof C?t.message:"Could not delete agent.")})};return!n&&!o&&p.isSuccess&&!c?e.jsxs("div",{className:"flex w-full flex-col items-center gap-3 px-8 py-16",children:[e.jsx("p",{className:"text-[14px] text-muted",children:"That agent no longer exists."}),e.jsx(y,{size:"sm",variant:"outline",onClick:N,children:"Back to agents"})]}):e.jsxs("div",{className:"flex w-full max-w-4xl flex-col gap-5 px-4 py-6 md:px-8",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",onClick:N,className:"text-faint hover:text-ink",title:"Back to agents",children:e.jsx(Z,{size:18})}),e.jsx("span",{className:"text-[15px] font-medium text-ink",children:n?"New agent":o?l?"Edit agent (modified built-in)":"Edit agent (built-in)":"Edit agent"}),e.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[o&&e.jsxs(y,{size:"sm",variant:"outline",onClick:F,disabled:u.reset.isPending,children:[e.jsx(ee,{size:13})," Reset to default"]}),e.jsx(y,{size:"sm",variant:"outline",onClick:N,children:"Cancel"}),e.jsx(y,{size:"sm",onClick:O,disabled:A||!m.name.trim(),children:A?"Saving…":"Save"})]})]}),o&&e.jsx("p",{className:"rounded-md border border-border bg-panel px-3 py-2 text-[12px] text-muted",children:"Built-in agents can be edited in place — Reset to default discards your changes and restores TurboLLM's original prompt and access."}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Name"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"Release Notes Writer",value:m.name,onChange:t=>g(r=>({...r,name:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Description"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"What this agent is for, in one line",value:m.description,onChange:t=>g(r=>({...r,description:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"System prompt"}),e.jsx("textarea",{className:"min-h-[160px] resize-y rounded-md border border-border bg-bg px-3 py-2 font-mono text-[12px] leading-relaxed text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"You are…",value:m.systemPrompt,onChange:t=>g(r=>({...r,systemPrompt:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-2.5",children:[e.jsx("div",{className:"inline-flex w-fit rounded-md border border-border p-0.5",children:["skills","tools"].map(t=>e.jsx("button",{type:"button",onClick:()=>L(t),className:B("rounded px-3 py-1.5 text-[13px] font-medium transition-colors",R===t?"bg-accent/12 text-accent":"text-muted hover:text-ink"),children:t==="skills"?`Skills (${m.skillIds.length})`:`Tools (${m.tools.length})`},t))}),R==="skills"?_.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):k.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No skills yet — create one under Customize → Skills."}):e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:k.map(t=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[13px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:m.skillIds.includes(t.id),onChange:()=>Q(t.id)}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"text-ink",children:t.name}),t.description&&e.jsx("span",{className:"text-[12px] text-muted",children:t.description})]})]},t.id))}):I.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):e.jsx(ie,{tools:j,selected:m.tools,onChange:t=>g(r=>({...r,tools:t}))})]}),!n&&!o&&(D?e.jsxs("div",{className:"flex items-center gap-2 self-start rounded-md border border-border bg-panel p-2 text-[12px]",children:[e.jsx("span",{className:"flex-1 text-muted",children:"Delete this agent?"}),e.jsx("button",{type:"button",onClick:K,className:"rounded px-2 py-1 text-[color:var(--err)] hover:bg-[color:color-mix(in_srgb,var(--err)_12%,transparent)]",children:"Delete"}),e.jsx("button",{type:"button",onClick:()=>w(!1),className:"rounded px-2 py-1 text-faint hover:text-ink",children:"Cancel"})]}):e.jsxs("button",{type:"button",onClick:()=>w(!0),className:"flex items-center gap-1.5 self-start text-[12px] text-faint hover:text-[color:var(--err)]",children:[e.jsx(te,{size:13})," Delete agent"]}))]})}export{ke as AgentEditPage};