turbollm 1.8.2 → 1.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/dist/cli.js +279 -33
- package/dist/webdist/assets/{AgentEditPage-DrcwQcJE.js → AgentEditPage-VoVmMCf1.js} +1 -1
- package/dist/webdist/assets/{ChatScreen-BkpwUk-O.js → ChatScreen-DHGEKPZr.js} +1 -1
- package/dist/webdist/assets/{CodeComposer-DG4lgSoA.js → CodeComposer-Cxhvsus4.js} +1 -1
- package/dist/webdist/assets/CodeHomeScreen-BHIrDU_t.js +1 -0
- package/dist/webdist/assets/{CodeSessionScreen-aQesvCPO.js → CodeSessionScreen-CqogdVEN.js} +1 -1
- package/dist/webdist/assets/{CustomizeScreen-DRrw2hs7.js → CustomizeScreen-B4XGEyxA.js} +1 -1
- package/dist/webdist/assets/DeveloperScreen-jGamwaJG.js +1 -0
- package/dist/webdist/assets/EnginesScreen-BRkOTacr.js +7 -0
- package/dist/webdist/assets/{FsBrowser-D0rDep29.js → FsBrowser-5VN4Yj8z.js} +1 -1
- package/dist/webdist/assets/{ModelLoadMenu-K1Qolm9B.js → ModelLoadMenu-CgM5a39X.js} +1 -1
- package/dist/webdist/assets/{ModelsScreen-HfLjpGmf.js → ModelsScreen-GUtj_sux.js} +2 -2
- package/dist/webdist/assets/SettingsScreen-D65xd3_e.js +1 -0
- package/dist/webdist/assets/{SkillEditPage-W3ofhEQn.js → SkillEditPage-CkKqavr8.js} +1 -1
- package/dist/webdist/assets/TokensScreen-CCS_y7DZ.js +1 -0
- package/dist/webdist/assets/{ToolApprovalBar-BGRW2hFN.js → ToolApprovalBar-CRZPKrcR.js} +3 -3
- package/dist/webdist/assets/WorkspaceScreen-D060hDIn.js +1 -0
- package/dist/webdist/assets/{agent-api-C48x10WZ.js → agent-api-BZQuCIAd.js} +1 -1
- package/dist/webdist/assets/{arc-B6W63qO_.js → arc-CG_F6y0n.js} +1 -1
- package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-BNxjHCm7.js → architectureDiagram-3BPJPVTR-bK0viBCO.js} +1 -1
- package/dist/webdist/assets/{arrow-left-DmGW0W4g.js → arrow-left-Cxqlqe4Q.js} +1 -1
- package/dist/webdist/assets/{badge-IsOHcYvf.js → badge-D1brk9rd.js} +1 -1
- package/dist/webdist/assets/{blockDiagram-GPEHLZMM-DrPlDIMe.js → blockDiagram-GPEHLZMM-DqMi7LYs.js} +1 -1
- package/dist/webdist/assets/{bot-DAa4xIIF.js → bot-CldCBtRY.js} +1 -1
- package/dist/webdist/assets/{c4Diagram-AAUBKEIU-BKtn6SbN.js → c4Diagram-AAUBKEIU-D5JOtr9l.js} +1 -1
- package/dist/webdist/assets/channel-C-X5wnqi.js +1 -0
- package/dist/webdist/assets/{chat-api-CtbwBsSY.js → chat-api-uAJNrEFg.js} +1 -1
- package/dist/webdist/assets/{chat-queries-BANbggZ1.js → chat-queries-OevUA_jH.js} +1 -1
- package/dist/webdist/assets/check-r_BeDgvE.js +1 -0
- package/dist/webdist/assets/{chevron-left-DScqk7lr.js → chevron-left-Dv-zay0l.js} +1 -1
- package/dist/webdist/assets/{chevron-right-DNc_yz-y.js → chevron-right-DvvJS6ye.js} +1 -1
- package/dist/webdist/assets/{chunk-2J33WTMH-B97vCEvd.js → chunk-2J33WTMH-D5FvNAKn.js} +1 -1
- package/dist/webdist/assets/{chunk-4BX2VUAB-B5mAgaq1.js → chunk-4BX2VUAB-PiXdGtQT.js} +1 -1
- package/dist/webdist/assets/{chunk-55IACEB6-CCZRTh0d.js → chunk-55IACEB6-BcFnIoE2.js} +1 -1
- package/dist/webdist/assets/{chunk-727SXJPM-tZT9XNMs.js → chunk-727SXJPM-BigbNnpE.js} +1 -1
- package/dist/webdist/assets/{chunk-AQP2D5EJ-CfIdLt8G.js → chunk-AQP2D5EJ-ksTbyzzH.js} +1 -1
- package/dist/webdist/assets/{chunk-FMBD7UC4-BRdScA0W.js → chunk-FMBD7UC4-DpDAzpmq.js} +1 -1
- package/dist/webdist/assets/{chunk-ND2GUHAM-BedHAIQl.js → chunk-ND2GUHAM-Ch7eIfE0.js} +1 -1
- package/dist/webdist/assets/{chunk-QZHKN3VN-ByAw17Nt.js → chunk-QZHKN3VN-DMDG8lEf.js} +1 -1
- package/dist/webdist/assets/{circle-check-BNIKoJDg.js → circle-check-BFtGWNjh.js} +1 -1
- package/dist/webdist/assets/{circle-x-DfIf4ChY.js → circle-x-BmwEPZU0.js} +1 -1
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-Dw2O5mDo.js +1 -0
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-Dw2O5mDo.js +1 -0
- package/dist/webdist/assets/{common-BwXsHduN.js → common-BEcCBxuG.js} +1 -1
- package/dist/webdist/assets/{copy-button-BcFHHH9b.js → copy-button-B3sedfsF.js} +1 -1
- package/dist/webdist/assets/{cose-bilkent-S5V4N54A--x5WjVCv.js → cose-bilkent-S5V4N54A-BBGTUfOp.js} +1 -1
- package/dist/webdist/assets/{dagre-BM42HDAG-DWQy_ulr.js → dagre-BM42HDAG-D8W6jcq_.js} +1 -1
- package/dist/webdist/assets/{diagram-2AECGRRQ-DQo4yJeJ.js → diagram-2AECGRRQ-CvCrWf_H.js} +1 -1
- package/dist/webdist/assets/{diagram-5GNKFQAL-DwbynoQ2.js → diagram-5GNKFQAL-DZS4yZU1.js} +1 -1
- package/dist/webdist/assets/{diagram-KO2AKTUF-CjKV0fFm.js → diagram-KO2AKTUF-BlKSql5d.js} +1 -1
- package/dist/webdist/assets/{diagram-LMA3HP47-CXfjg5Fe.js → diagram-LMA3HP47-CKo-tRYQ.js} +1 -1
- package/dist/webdist/assets/{diagram-OG6HWLK6-CKEjI4NC.js → diagram-OG6HWLK6-NSePJGx1.js} +1 -1
- package/dist/webdist/assets/dialog-B-GLhgc2.js +1 -0
- package/dist/webdist/assets/{erDiagram-TEJ5UH35-D43kYg84.js → erDiagram-TEJ5UH35-haPVT0D3.js} +1 -1
- package/dist/webdist/assets/{external-link-jnEQCJGd.js → external-link-UbaLGQ_h.js} +1 -1
- package/dist/webdist/assets/{flowDiagram-I6XJVG4X-qGS0J-De.js → flowDiagram-I6XJVG4X-BzKZFBHV.js} +1 -1
- package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-3Ud_YPtk.js → ganttDiagram-6RSMTGT7-CCmAecLV.js} +1 -1
- package/dist/webdist/assets/{git-branch-BZW2_OwW.js → git-branch-Bj6Xfs8n.js} +1 -1
- package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-BhOiYdR2.js → gitGraphDiagram-PVQCEYII-OD-Aumfw.js} +1 -1
- package/dist/webdist/assets/{index-DffknE3S.css → index-CfEenPkR.css} +1 -1
- package/dist/webdist/assets/{index-rezSzJo3.js → index-D4oBHgyO.js} +4 -4
- package/dist/webdist/assets/{infoDiagram-5YYISTIA-D2Yo845I.js → infoDiagram-5YYISTIA-CXiblcgy.js} +1 -1
- package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-BoTTMzTX.js → ishikawaDiagram-YF4QCWOH-C8sCKBhs.js} +1 -1
- package/dist/webdist/assets/{journeyDiagram-JHISSGLW-B30FNQqG.js → journeyDiagram-JHISSGLW-B3be8ACK.js} +1 -1
- package/dist/webdist/assets/{kanban-definition-UN3LZRKU-DwuHMcyp.js → kanban-definition-UN3LZRKU-BbfHizAI.js} +1 -1
- package/dist/webdist/assets/{layers-BbMBWfEB.js → layers-BqbkDoHi.js} +1 -1
- package/dist/webdist/assets/{linear-BI-xSqy2.js → linear-C0lkBXID.js} +1 -1
- package/dist/webdist/assets/{mermaid.core-DjBtB3Ob.js → mermaid.core-CW2TnRwR.js} +4 -4
- package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-P0Blz3ba.js → mindmap-definition-RKZ34NQL-h-ro0B1A.js} +1 -1
- package/dist/webdist/assets/{pencil-pd4qdmIY.js → pencil-BJsQ5uv5.js} +1 -1
- package/dist/webdist/assets/{personas-DGNsw4JH.js → personas-fIGMukzj.js} +14 -3
- package/dist/webdist/assets/{pieDiagram-4H26LBE5-D_RWWNYB.js → pieDiagram-4H26LBE5-DEGupnzV.js} +1 -1
- package/dist/webdist/assets/{plus-Co-23h9C.js → plus-BESpOgWu.js} +1 -1
- package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-0UW-qXoo.js → quadrantDiagram-W4KKPZXB-CMkivigQ.js} +1 -1
- package/dist/webdist/assets/{refresh-cw-DBHReAx0.js → refresh-cw-CuuCQSpX.js} +1 -1
- package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-DQPViUOU.js → requirementDiagram-4Y6WPE33-DyChhWBw.js} +1 -1
- package/dist/webdist/assets/{rotate-ccw-L6hm18l-.js → rotate-ccw-D99lmEA-.js} +1 -1
- package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-CoFKXFxN.js → sankeyDiagram-5OEKKPKP-BDPQ7Ai5.js} +1 -1
- package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-CTKbZzaz.js → sequenceDiagram-3UESZ5HK-C_jdr66e.js} +1 -1
- package/dist/webdist/assets/{skeleton-CTqhU4nE.js → skeleton-nGnGDZ2Z.js} +1 -1
- package/dist/webdist/assets/{sliders-horizontal-CqA4xQwh.js → sliders-horizontal-BXU5PC3A.js} +1 -1
- package/dist/webdist/assets/{star-C4xvUEne.js → star-BGgeiEjL.js} +1 -1
- package/dist/webdist/assets/{stateDiagram-AJRCARHV-Bbgyioxb.js → stateDiagram-AJRCARHV-CwtPbKxE.js} +1 -1
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-DlCLpW5_.js +1 -0
- package/dist/webdist/assets/{switch-vVu6UIJB.js → switch-BQT_NipX.js} +1 -1
- package/dist/webdist/assets/{terminal-BqY0B6tG.js → terminal-CrCLNyjB.js} +1 -1
- package/dist/webdist/assets/{timeline-definition-PNZ67QCA-CNuDMLf0.js → timeline-definition-PNZ67QCA-B1bN1YFW.js} +1 -1
- package/dist/webdist/assets/{trash-2-CI-4vgjV.js → trash-2-CuUIi8kE.js} +1 -1
- package/dist/webdist/assets/useIsDesktop-BwBLPtTS.js +1 -0
- package/dist/webdist/assets/{vennDiagram-CIIHVFJN-5KfL6f8c.js → vennDiagram-CIIHVFJN-B23LrfT3.js} +1 -1
- package/dist/webdist/assets/{wardley-L42UT6IY-KSQpq69w.js → wardley-L42UT6IY-Bkk_DpTu.js} +1 -1
- package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-CRDC9-52.js → wardleyDiagram-YWT4CUSO-Di9GYhE4.js} +1 -1
- package/dist/webdist/assets/{wrench-B6O2XFxJ.js → wrench-CUU3qZM0.js} +1 -1
- package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-CFJUnPty.js → xychartDiagram-2RQKCTM6-BF1BnBYj.js} +1 -1
- package/dist/webdist/index.html +2 -2
- package/package.json +1 -1
- package/dist/webdist/assets/CodeHomeScreen-Cqv5c0nM.js +0 -1
- package/dist/webdist/assets/DeveloperScreen-pTaZZ6r5.js +0 -1
- package/dist/webdist/assets/EnginesScreen-BvZtUKd0.js +0 -7
- package/dist/webdist/assets/SettingsScreen-z-UuH8MI.js +0 -1
- package/dist/webdist/assets/TokensScreen-dLb-UDmL.js +0 -1
- package/dist/webdist/assets/WorkspaceScreen-C5R7gzUi.js +0 -1
- package/dist/webdist/assets/channel-CDs60xNM.js +0 -1
- package/dist/webdist/assets/check-DeJoIInG.js +0 -1
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-BLIfDCvc.js +0 -1
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-BLIfDCvc.js +0 -1
- package/dist/webdist/assets/dialog-uY3tk3E9.js +0 -1
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-DBMc27o_.js +0 -1
- 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, **
|
|
206
|
-
low-bit on supporting forks), CPU threads, flash attention,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
5547
|
-
milestone:
|
|
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
|
|
5685
|
-
for (const b of dayBuckets.values())
|
|
5686
|
-
const
|
|
5687
|
-
const
|
|
5688
|
-
const
|
|
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
|
|
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
|
}
|
|
@@ -6686,6 +6828,20 @@ var DownloadManager = class {
|
|
|
6686
6828
|
this.pump();
|
|
6687
6829
|
return true;
|
|
6688
6830
|
}
|
|
6831
|
+
/** Resume a paused or errored job (spec 10 §5): re-queue it so pump() picks it back
|
|
6832
|
+
* up. run() already resumes from the .part file's byte offset via a Range request —
|
|
6833
|
+
* this just flips the status so the queue actually starts it again. A restart-
|
|
6834
|
+
* restored job comes back as 'paused' with no automatic way back to 'downloading'
|
|
6835
|
+
* until this is called. */
|
|
6836
|
+
resume(id) {
|
|
6837
|
+
const rec = this.records.get(id);
|
|
6838
|
+
if (!rec || rec.status !== "paused" && rec.status !== "error") return false;
|
|
6839
|
+
rec.status = "queued";
|
|
6840
|
+
rec.error = null;
|
|
6841
|
+
this.persist();
|
|
6842
|
+
this.pump();
|
|
6843
|
+
return true;
|
|
6844
|
+
}
|
|
6689
6845
|
/** Remove a record entirely (and any lingering .part). */
|
|
6690
6846
|
remove(id) {
|
|
6691
6847
|
const rec = this.records.get(id);
|
|
@@ -10772,10 +10928,11 @@ function buildSnapshot(conv, cfg2, version2, exportedAt, format) {
|
|
|
10772
10928
|
}
|
|
10773
10929
|
|
|
10774
10930
|
// src/tools/tool-policy.ts
|
|
10775
|
-
function resolveToolPolicy(name, globalPolicies, convOverrides) {
|
|
10931
|
+
function resolveToolPolicy(name, globalPolicies, convOverrides, autoAllowAll = false) {
|
|
10776
10932
|
if (convOverrides[name]) return convOverrides[name];
|
|
10777
|
-
|
|
10778
|
-
return "ask";
|
|
10933
|
+
const global = globalPolicies[name];
|
|
10934
|
+
if (global) return global === "ask" && autoAllowAll ? "allow" : global;
|
|
10935
|
+
return autoAllowAll ? "allow" : "ask";
|
|
10779
10936
|
}
|
|
10780
10937
|
|
|
10781
10938
|
// src/tools/approval-gate.ts
|
|
@@ -10813,8 +10970,8 @@ function resolveToolApproval(key, decision) {
|
|
|
10813
10970
|
|
|
10814
10971
|
// src/tools/execute-with-approval.ts
|
|
10815
10972
|
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);
|
|
10973
|
+
const { tools, sink, convId, id, name, args, globalPolicies, convOverrides, signal, interactive, agentAllowedTools, autoAllowAll } = params;
|
|
10974
|
+
const policy = resolveToolPolicy(name, globalPolicies, convOverrides, autoAllowAll);
|
|
10818
10975
|
if (policy === "deny") {
|
|
10819
10976
|
const result2 = "Blocked: this tool is set to Deny in Tool Permissions settings.";
|
|
10820
10977
|
await sink({ event: "tool_call", data: { id, name, args, status: "done", result: result2 } });
|
|
@@ -11927,6 +12084,7 @@ After reviewing the search results, include a confidence assessment on a line by
|
|
|
11927
12084
|
// start) — otherwise a same-turn repeat tool call misses an "Allow for this
|
|
11928
12085
|
// chat" decision the user just made via POST .../tool-calls/:id/approve.
|
|
11929
12086
|
convOverrides: d.db.getToolOverrides(convId),
|
|
12087
|
+
autoAllowAll: d.store.snapshot().tools.autoAllowAll ?? false,
|
|
11930
12088
|
signal: ac.signal,
|
|
11931
12089
|
interactive: true
|
|
11932
12090
|
});
|
|
@@ -12689,6 +12847,60 @@ var ALL = [
|
|
|
12689
12847
|
hasPrebuilt: false
|
|
12690
12848
|
}
|
|
12691
12849
|
]
|
|
12850
|
+
},
|
|
12851
|
+
{
|
|
12852
|
+
id: "prism",
|
|
12853
|
+
name: "Prism (llama.cpp fork)",
|
|
12854
|
+
kind: "llama-server",
|
|
12855
|
+
description: "A llama.cpp fork tuned for 1-2 bit ternary/Bonsai models. Ships llama-server but has no install endpoint here yet \u2014 build it, then add your own engine.",
|
|
12856
|
+
provision: "github-release",
|
|
12857
|
+
homepage: "https://github.com/PrismML-Eng/llama.cpp",
|
|
12858
|
+
repo: "PrismML-Eng/llama.cpp",
|
|
12859
|
+
// The fork itself publishes extensive per-commit prebuilt archives (CUDA/Vulkan/ROCm ×
|
|
12860
|
+
// win/linux/macOS, same naming convention as official llama.cpp) — but TurboLLM doesn't
|
|
12861
|
+
// resolve them automatically (deliberately deferred, see ADR-238). Build-from-source only.
|
|
12862
|
+
platforms: ["win32", "darwin", "linux"],
|
|
12863
|
+
support: "experimental",
|
|
12864
|
+
installEndpoint: "",
|
|
12865
|
+
note: "Upstream publishes prebuilt binaries, but TurboLLM builds from source here (guided walkthrough) rather than resolving a specific release asset. Proven on a ternary/Bonsai-class model at 200K context (Q2_0 + q8 KV).",
|
|
12866
|
+
variants: [
|
|
12867
|
+
{
|
|
12868
|
+
id: "prism-source",
|
|
12869
|
+
label: "Build from source",
|
|
12870
|
+
repo: "PrismML-Eng/llama.cpp",
|
|
12871
|
+
requires: {},
|
|
12872
|
+
stability: "experimental",
|
|
12873
|
+
speed: "fast",
|
|
12874
|
+
hasPrebuilt: false
|
|
12875
|
+
}
|
|
12876
|
+
]
|
|
12877
|
+
},
|
|
12878
|
+
{
|
|
12879
|
+
id: "beellama",
|
|
12880
|
+
name: "BeeLlama.cpp",
|
|
12881
|
+
kind: "llama-server",
|
|
12882
|
+
description: "A llama.cpp fork adding variance-normalized KV-cache quantization (KVarN), a KV precision tail, and adaptive DFlash speculative decoding. Ships llama-server but has no install endpoint here yet \u2014 build it, then add your own engine.",
|
|
12883
|
+
provision: "github-release",
|
|
12884
|
+
homepage: "https://github.com/Anbeeld/beellama.cpp",
|
|
12885
|
+
repo: "Anbeeld/beellama.cpp",
|
|
12886
|
+
// Also publishes a full prebuilt matrix (CUDA/ROCm/Vulkan/SYCL/CPU) in one normal
|
|
12887
|
+
// release — but solo-maintainer, fast-churn fork (v0.4.0 dropped its own TurboQuant/TCQ
|
|
12888
|
+
// support), so one-click install is deliberately deferred; build-from-source only.
|
|
12889
|
+
platforms: ["win32", "darwin", "linux"],
|
|
12890
|
+
support: "experimental",
|
|
12891
|
+
installEndpoint: "",
|
|
12892
|
+
note: "Upstream publishes prebuilt binaries, but TurboLLM builds from source here. The KVarN cache types (kvarn2...kvarn8, set via --cache-type-k/-v) aren't in the auto-tune sweep yet \u2014 use the custom/raw flags field to set them manually.",
|
|
12893
|
+
variants: [
|
|
12894
|
+
{
|
|
12895
|
+
id: "beellama-source",
|
|
12896
|
+
label: "Build from source",
|
|
12897
|
+
repo: "Anbeeld/beellama.cpp",
|
|
12898
|
+
requires: {},
|
|
12899
|
+
stability: "experimental",
|
|
12900
|
+
speed: "fast",
|
|
12901
|
+
hasPrebuilt: false
|
|
12902
|
+
}
|
|
12903
|
+
]
|
|
12692
12904
|
}
|
|
12693
12905
|
];
|
|
12694
12906
|
function catalogForPlatform(platform = process.platform) {
|
|
@@ -12998,6 +13210,8 @@ function registerApi(app2, d) {
|
|
|
12998
13210
|
pid: ms.pid
|
|
12999
13211
|
};
|
|
13000
13212
|
if (ms.err) engine.error = ms.err;
|
|
13213
|
+
const launchCommand = d.manager.launchCommand();
|
|
13214
|
+
if (launchCommand) engine.launchCommand = launchCommand;
|
|
13001
13215
|
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
13216
|
const engineStats = ms.state === "running" ? d.manager.sessionStats() : null;
|
|
13003
13217
|
const liveGeneration = ms.state === "running" ? d.manager.liveGeneration() : null;
|
|
@@ -13852,7 +14066,8 @@ function registerApi(app2, d) {
|
|
|
13852
14066
|
model: { key: entry.key, name: entry.name, quant: entry.quant, ctx: entry.nativeCtx, vision: entry.vision },
|
|
13853
14067
|
modelPath: entry.path,
|
|
13854
14068
|
extraArgs,
|
|
13855
|
-
tensorParallelSize: savedProfile?.gpu?.tensorParallelSize
|
|
14069
|
+
tensorParallelSize: savedProfile?.gpu?.tensorParallelSize,
|
|
14070
|
+
preferredPort: savedProfile?.port
|
|
13856
14071
|
};
|
|
13857
14072
|
} else {
|
|
13858
14073
|
const saved = getModelProfile(cfg2, entry.key, active.id);
|
|
@@ -13862,7 +14077,8 @@ function registerApi(app2, d) {
|
|
|
13862
14077
|
engine: active,
|
|
13863
14078
|
model: { key: entry.key, name: entry.name, quant: entry.quant, ctx: profile.ctx, vision: entry.vision },
|
|
13864
14079
|
modelPath: entry.path,
|
|
13865
|
-
extraArgs
|
|
14080
|
+
extraArgs,
|
|
14081
|
+
preferredPort: profile.port
|
|
13866
14082
|
};
|
|
13867
14083
|
}
|
|
13868
14084
|
void d.manager.load(opts2, { beforeStart: () => d.comfy?.freeComfyUIBeforeLoad() ?? Promise.resolve() }).catch((e) => console.warn(`engine load failed: ${e}`));
|
|
@@ -14204,6 +14420,7 @@ function registerApi(app2, d) {
|
|
|
14204
14420
|
Object.assign(cfg2.gateway, gwUpdates);
|
|
14205
14421
|
if (toolchainDirs !== void 0) cfg2.build.toolchainDirs = toolchainDirs;
|
|
14206
14422
|
if (toolPolicies !== void 0) cfg2.tools.toolPolicies = toolPolicies;
|
|
14423
|
+
if (b.autoAllowAll !== void 0) cfg2.tools.autoAllowAll = !!b.autoAllowAll;
|
|
14207
14424
|
if (b.autoLoadOnStart !== void 0) cfg2.autoLoadOnStart = !!b.autoLoadOnStart;
|
|
14208
14425
|
if (vramHeadroomMb !== void 0) cfg2.vramHeadroomMb = vramHeadroomMb;
|
|
14209
14426
|
if (telemetryLevel !== void 0) cfg2.telemetry.level = telemetryLevel;
|
|
@@ -14433,6 +14650,11 @@ function registerApi(app2, d) {
|
|
|
14433
14650
|
if (!ok) return err2(c, 404, "no_such_download", "No download with that id.");
|
|
14434
14651
|
return c.json({ ok: true });
|
|
14435
14652
|
});
|
|
14653
|
+
app2.post("/api/v1/downloads/:id/resume", (c) => {
|
|
14654
|
+
const ok = d.downloads.resume(c.req.param("id"));
|
|
14655
|
+
if (!ok) return err2(c, 404, "no_such_download", "No paused or errored download with that id.");
|
|
14656
|
+
return c.json({ ok: true });
|
|
14657
|
+
});
|
|
14436
14658
|
app2.delete("/api/v1/downloads/:id", (c) => {
|
|
14437
14659
|
const ok = d.downloads.remove(c.req.param("id"));
|
|
14438
14660
|
if (!ok) return err2(c, 404, "no_such_download", "No download with that id.");
|
|
@@ -14580,7 +14802,8 @@ function settingsPayload(d) {
|
|
|
14580
14802
|
experimental: cfg2.daemon.experimental,
|
|
14581
14803
|
// Tool-call approval gate: global per-tool policy ('ask' | 'allow' | 'deny').
|
|
14582
14804
|
// Not secret — echoed back directly so Settings can render Tool Permissions.
|
|
14583
|
-
toolPolicies: cfg2.tools.toolPolicies ?? {}
|
|
14805
|
+
toolPolicies: cfg2.tools.toolPolicies ?? {},
|
|
14806
|
+
autoAllowAll: cfg2.tools.autoAllowAll ?? false
|
|
14584
14807
|
};
|
|
14585
14808
|
}
|
|
14586
14809
|
function telemetryPreview(level, version2) {
|
|
@@ -17607,6 +17830,7 @@ function registerGateway(app2, d) {
|
|
|
17607
17830
|
(u) => {
|
|
17608
17831
|
try {
|
|
17609
17832
|
d.manager.recordCompletion({ inputTokens: u.inputTokens, outputTokens: u.outputTokens });
|
|
17833
|
+
d.db.recordApiUsage({ source: "anthropic", modelKey: req.model ?? null, promptTokens: u.inputTokens, genTokens: u.outputTokens });
|
|
17610
17834
|
} catch {
|
|
17611
17835
|
}
|
|
17612
17836
|
},
|
|
@@ -17633,7 +17857,7 @@ function registerGateway(app2, d) {
|
|
|
17633
17857
|
}
|
|
17634
17858
|
try {
|
|
17635
17859
|
const oaiRes = await res.json();
|
|
17636
|
-
recordOpenAiUsage(d, oaiRes);
|
|
17860
|
+
recordOpenAiUsage(d, oaiRes, "anthropic", req.model ?? null);
|
|
17637
17861
|
return c.json(mapFromOpenAI(oaiRes, modelName));
|
|
17638
17862
|
} finally {
|
|
17639
17863
|
d.manager.generationEnd();
|
|
@@ -17710,6 +17934,9 @@ function registerGateway(app2, d) {
|
|
|
17710
17934
|
const alias = engineModelAlias(d.registry.active()?.kind ?? "");
|
|
17711
17935
|
if (alias) parsedBody.model = alias;
|
|
17712
17936
|
}
|
|
17937
|
+
if (parsedBody?.stream === true && parsedBody.stream_options === void 0) {
|
|
17938
|
+
parsedBody.stream_options = { include_usage: true };
|
|
17939
|
+
}
|
|
17713
17940
|
headers.delete("content-length");
|
|
17714
17941
|
init.body = parsedBody ? JSON.stringify(parsedBody) : "";
|
|
17715
17942
|
} else {
|
|
@@ -17722,7 +17949,8 @@ function registerGateway(app2, d) {
|
|
|
17722
17949
|
try {
|
|
17723
17950
|
const [a, b] = res.body.tee();
|
|
17724
17951
|
d.manager.generationStart();
|
|
17725
|
-
|
|
17952
|
+
const drain = parsedBody?.stream === true ? recordOpenAiStreamUsage(d, b, "openai", requestedModel || null) : recordOpenAiJsonUsage(d, b, "openai", requestedModel || null);
|
|
17953
|
+
void drain.finally(() => d.manager.generationEnd());
|
|
17726
17954
|
return new Response(a, { status: res.status, headers: res.headers });
|
|
17727
17955
|
} catch {
|
|
17728
17956
|
return new Response(res.body, { status: res.status, headers: res.headers });
|
|
@@ -17731,7 +17959,7 @@ function registerGateway(app2, d) {
|
|
|
17731
17959
|
return new Response(res.body, { status: res.status, headers: res.headers });
|
|
17732
17960
|
});
|
|
17733
17961
|
}
|
|
17734
|
-
function recordOpenAiUsage(d, oai) {
|
|
17962
|
+
function recordOpenAiUsage(d, oai, source, modelKey) {
|
|
17735
17963
|
try {
|
|
17736
17964
|
const usage = oai.usage;
|
|
17737
17965
|
const timings = oai.timings;
|
|
@@ -17741,10 +17969,19 @@ function recordOpenAiUsage(d, oai) {
|
|
|
17741
17969
|
promptTps: timings?.prompt_per_second,
|
|
17742
17970
|
genTps: timings?.predicted_per_second
|
|
17743
17971
|
});
|
|
17972
|
+
d.db.recordApiUsage({ source, modelKey, promptTokens: usage?.prompt_tokens ?? 0, genTokens: usage?.completion_tokens ?? 0 });
|
|
17973
|
+
} catch {
|
|
17974
|
+
}
|
|
17975
|
+
}
|
|
17976
|
+
async function recordOpenAiJsonUsage(d, body6, source, modelKey) {
|
|
17977
|
+
try {
|
|
17978
|
+
const text = await new Response(body6).text();
|
|
17979
|
+
const oai = JSON.parse(text);
|
|
17980
|
+
recordOpenAiUsage(d, oai, source, modelKey);
|
|
17744
17981
|
} catch {
|
|
17745
17982
|
}
|
|
17746
17983
|
}
|
|
17747
|
-
async function recordOpenAiStreamUsage(d, body6) {
|
|
17984
|
+
async function recordOpenAiStreamUsage(d, body6, source, modelKey) {
|
|
17748
17985
|
try {
|
|
17749
17986
|
const reader = body6.getReader();
|
|
17750
17987
|
const dec = new TextDecoder();
|
|
@@ -17790,6 +18027,7 @@ async function recordOpenAiStreamUsage(d, body6) {
|
|
|
17790
18027
|
}
|
|
17791
18028
|
}
|
|
17792
18029
|
d.manager.recordCompletion({ inputTokens: promptTokens, outputTokens: completionTokens, promptTps, genTps });
|
|
18030
|
+
d.db.recordApiUsage({ source, modelKey, promptTokens, genTokens: completionTokens });
|
|
17793
18031
|
} catch {
|
|
17794
18032
|
}
|
|
17795
18033
|
}
|
|
@@ -17800,13 +18038,21 @@ var WEB_ROOT = join29(dirname11(fileURLToPath(import.meta.url)), "webdist");
|
|
|
17800
18038
|
function createApp(d) {
|
|
17801
18039
|
const app2 = new Hono();
|
|
17802
18040
|
app2.use(
|
|
17803
|
-
"
|
|
18041
|
+
"/v1/*",
|
|
17804
18042
|
cors({
|
|
17805
18043
|
origin: "*",
|
|
17806
18044
|
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
17807
18045
|
allowHeaders: ["Content-Type", "Authorization", "x-api-key", "X-TurboLLM-Auth"]
|
|
17808
18046
|
})
|
|
17809
18047
|
);
|
|
18048
|
+
app2.use(
|
|
18049
|
+
"/api/*",
|
|
18050
|
+
cors({
|
|
18051
|
+
origin: "https://turbollm.dev",
|
|
18052
|
+
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
18053
|
+
allowHeaders: ["Content-Type", "Authorization", "x-api-key", "X-TurboLLM-Auth"]
|
|
18054
|
+
})
|
|
18055
|
+
);
|
|
17810
18056
|
app2.use("*", async (c, next) => {
|
|
17811
18057
|
c.header("Server", `TurboLLM/${d.version}`);
|
|
17812
18058
|
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-
|
|
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-D4oBHgyO.js";import{f as U}from"./chat-api-uAJNrEFg.js";import{f as V,a as X}from"./agent-api-BZQuCIAd.js";import{P as z}from"./personas-fIGMukzj.js";import{C as Z}from"./chevron-left-Dv-zay0l.js";import{R as ee}from"./rotate-ccw-D99lmEA-.js";import{T as te}from"./trash-2-CuUIi8kE.js";import{C as se}from"./chevron-right-DvvJS6ye.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};
|