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.
- package/README.md +10 -5
- package/dist/cli.js +206 -33
- package/dist/webdist/assets/{AgentEditPage-DrcwQcJE.js → AgentEditPage-Bn7Ipokq.js} +1 -1
- package/dist/webdist/assets/{ChatScreen-BkpwUk-O.js → ChatScreen-B3w1GUl6.js} +1 -1
- package/dist/webdist/assets/{CodeComposer-DG4lgSoA.js → CodeComposer-cj9FLZq8.js} +1 -1
- package/dist/webdist/assets/CodeHomeScreen-Brq-hPKU.js +1 -0
- package/dist/webdist/assets/{CodeSessionScreen-aQesvCPO.js → CodeSessionScreen-QQPjiOnR.js} +1 -1
- package/dist/webdist/assets/{CustomizeScreen-DRrw2hs7.js → CustomizeScreen-CnBGN3ac.js} +1 -1
- package/dist/webdist/assets/DeveloperScreen-CM5RpeHx.js +1 -0
- package/dist/webdist/assets/{EnginesScreen-BvZtUKd0.js → EnginesScreen-B8zlkteU.js} +1 -1
- package/dist/webdist/assets/{FsBrowser-D0rDep29.js → FsBrowser-BmIXHDoO.js} +1 -1
- package/dist/webdist/assets/{ModelLoadMenu-K1Qolm9B.js → ModelLoadMenu-BhuL3y0h.js} +1 -1
- package/dist/webdist/assets/{ModelsScreen-HfLjpGmf.js → ModelsScreen-CxpMiIH-.js} +2 -2
- package/dist/webdist/assets/SettingsScreen-CvilSjLV.js +1 -0
- package/dist/webdist/assets/{SkillEditPage-W3ofhEQn.js → SkillEditPage-D8TxMXvF.js} +1 -1
- package/dist/webdist/assets/TokensScreen-Bgg-ezpv.js +1 -0
- package/dist/webdist/assets/{ToolApprovalBar-BGRW2hFN.js → ToolApprovalBar-N1UEc8ol.js} +3 -3
- package/dist/webdist/assets/WorkspaceScreen-MBZpXsG1.js +1 -0
- package/dist/webdist/assets/{agent-api-C48x10WZ.js → agent-api-D5VFF3mP.js} +1 -1
- package/dist/webdist/assets/{arc-B6W63qO_.js → arc-DIn4J2Lk.js} +1 -1
- package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-BNxjHCm7.js → architectureDiagram-3BPJPVTR-BzJslHoS.js} +1 -1
- package/dist/webdist/assets/{arrow-left-DmGW0W4g.js → arrow-left-BpVSMrri.js} +1 -1
- package/dist/webdist/assets/{badge-IsOHcYvf.js → badge-Bbul2j74.js} +1 -1
- package/dist/webdist/assets/{blockDiagram-GPEHLZMM-DrPlDIMe.js → blockDiagram-GPEHLZMM-B9IcI0TI.js} +1 -1
- package/dist/webdist/assets/{bot-DAa4xIIF.js → bot-CGlgDM61.js} +1 -1
- package/dist/webdist/assets/{c4Diagram-AAUBKEIU-BKtn6SbN.js → c4Diagram-AAUBKEIU-DTNAE2w5.js} +1 -1
- package/dist/webdist/assets/channel-DzOJVoSc.js +1 -0
- package/dist/webdist/assets/{chat-api-CtbwBsSY.js → chat-api-vZefHZ4v.js} +1 -1
- package/dist/webdist/assets/{chat-queries-BANbggZ1.js → chat-queries-C2DFl0KI.js} +1 -1
- package/dist/webdist/assets/check-D5Mwo3Z5.js +1 -0
- package/dist/webdist/assets/{chevron-left-DScqk7lr.js → chevron-left-Ciwr8fJO.js} +1 -1
- package/dist/webdist/assets/{chevron-right-DNc_yz-y.js → chevron-right-CzRe7Kav.js} +1 -1
- package/dist/webdist/assets/{chunk-2J33WTMH-B97vCEvd.js → chunk-2J33WTMH-zyTy7dVO.js} +1 -1
- package/dist/webdist/assets/{chunk-4BX2VUAB-B5mAgaq1.js → chunk-4BX2VUAB-PB8x0_Hd.js} +1 -1
- package/dist/webdist/assets/{chunk-55IACEB6-CCZRTh0d.js → chunk-55IACEB6-DA5kOQ34.js} +1 -1
- package/dist/webdist/assets/{chunk-727SXJPM-tZT9XNMs.js → chunk-727SXJPM-Bw7cNW67.js} +1 -1
- package/dist/webdist/assets/{chunk-AQP2D5EJ-CfIdLt8G.js → chunk-AQP2D5EJ-WCV-mwo1.js} +1 -1
- package/dist/webdist/assets/{chunk-FMBD7UC4-BRdScA0W.js → chunk-FMBD7UC4-DIrWPMJ6.js} +1 -1
- package/dist/webdist/assets/{chunk-ND2GUHAM-BedHAIQl.js → chunk-ND2GUHAM-CpNR0agz.js} +1 -1
- package/dist/webdist/assets/{chunk-QZHKN3VN-ByAw17Nt.js → chunk-QZHKN3VN-rraroDc3.js} +1 -1
- package/dist/webdist/assets/{circle-check-BNIKoJDg.js → circle-check-CuPI_Sim.js} +1 -1
- package/dist/webdist/assets/{circle-x-DfIf4ChY.js → circle-x-BVHvkV4f.js} +1 -1
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-BvOZ0d_w.js +1 -0
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-BvOZ0d_w.js +1 -0
- package/dist/webdist/assets/{common-BwXsHduN.js → common-n231fP12.js} +1 -1
- package/dist/webdist/assets/{copy-button-BcFHHH9b.js → copy-button-Bvc3-lid.js} +1 -1
- package/dist/webdist/assets/{cose-bilkent-S5V4N54A--x5WjVCv.js → cose-bilkent-S5V4N54A-D7iUMme8.js} +1 -1
- package/dist/webdist/assets/{dagre-BM42HDAG-DWQy_ulr.js → dagre-BM42HDAG-BrLWg27e.js} +1 -1
- package/dist/webdist/assets/{diagram-2AECGRRQ-DQo4yJeJ.js → diagram-2AECGRRQ-CSGxcQMM.js} +1 -1
- package/dist/webdist/assets/{diagram-5GNKFQAL-DwbynoQ2.js → diagram-5GNKFQAL-DclhxgFp.js} +1 -1
- package/dist/webdist/assets/{diagram-KO2AKTUF-CjKV0fFm.js → diagram-KO2AKTUF-CSStfYhJ.js} +1 -1
- package/dist/webdist/assets/{diagram-LMA3HP47-CXfjg5Fe.js → diagram-LMA3HP47-CIpoucHo.js} +1 -1
- package/dist/webdist/assets/{diagram-OG6HWLK6-CKEjI4NC.js → diagram-OG6HWLK6-DiA9zxOl.js} +1 -1
- package/dist/webdist/assets/dialog-D7uorVmS.js +1 -0
- package/dist/webdist/assets/{erDiagram-TEJ5UH35-D43kYg84.js → erDiagram-TEJ5UH35-ldfqRXS3.js} +1 -1
- package/dist/webdist/assets/{external-link-jnEQCJGd.js → external-link-Djq4PRwx.js} +1 -1
- package/dist/webdist/assets/{flowDiagram-I6XJVG4X-qGS0J-De.js → flowDiagram-I6XJVG4X-DL9lyEaN.js} +1 -1
- package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-3Ud_YPtk.js → ganttDiagram-6RSMTGT7-BOhbVgim.js} +1 -1
- package/dist/webdist/assets/{git-branch-BZW2_OwW.js → git-branch-BnK0yXx9.js} +1 -1
- package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-BhOiYdR2.js → gitGraphDiagram-PVQCEYII-DsiAhP7w.js} +1 -1
- package/dist/webdist/assets/{index-rezSzJo3.js → index-15S4jFJa.js} +2 -2
- package/dist/webdist/assets/{index-DffknE3S.css → index-CfEenPkR.css} +1 -1
- package/dist/webdist/assets/{infoDiagram-5YYISTIA-D2Yo845I.js → infoDiagram-5YYISTIA-Cjo31Bf1.js} +1 -1
- package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-BoTTMzTX.js → ishikawaDiagram-YF4QCWOH-BcrXs5eW.js} +1 -1
- package/dist/webdist/assets/{journeyDiagram-JHISSGLW-B30FNQqG.js → journeyDiagram-JHISSGLW-Br8yhmM3.js} +1 -1
- package/dist/webdist/assets/{kanban-definition-UN3LZRKU-DwuHMcyp.js → kanban-definition-UN3LZRKU-BRabkdYf.js} +1 -1
- package/dist/webdist/assets/{layers-BbMBWfEB.js → layers-DPA9Qzse.js} +1 -1
- package/dist/webdist/assets/{linear-BI-xSqy2.js → linear-BHyBqymd.js} +1 -1
- package/dist/webdist/assets/{mermaid.core-DjBtB3Ob.js → mermaid.core-1LxyS8HV.js} +4 -4
- package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-P0Blz3ba.js → mindmap-definition-RKZ34NQL-mkA1uqUK.js} +1 -1
- package/dist/webdist/assets/{pencil-pd4qdmIY.js → pencil-QTtpg3yU.js} +1 -1
- package/dist/webdist/assets/{personas-DGNsw4JH.js → personas-DkWvIHOp.js} +7 -3
- package/dist/webdist/assets/{pieDiagram-4H26LBE5-D_RWWNYB.js → pieDiagram-4H26LBE5-DA8Xl9Ac.js} +1 -1
- package/dist/webdist/assets/{plus-Co-23h9C.js → plus-CCp3TOSc.js} +1 -1
- package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-0UW-qXoo.js → quadrantDiagram-W4KKPZXB-CUdAHRpJ.js} +1 -1
- package/dist/webdist/assets/{refresh-cw-DBHReAx0.js → refresh-cw-Dx7SuJg9.js} +1 -1
- package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-DQPViUOU.js → requirementDiagram-4Y6WPE33-BAZNjAEw.js} +1 -1
- package/dist/webdist/assets/{rotate-ccw-L6hm18l-.js → rotate-ccw-Dc5bDRuI.js} +1 -1
- package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-CoFKXFxN.js → sankeyDiagram-5OEKKPKP-Caa8Igai.js} +1 -1
- package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-CTKbZzaz.js → sequenceDiagram-3UESZ5HK-B7v4zjHc.js} +1 -1
- package/dist/webdist/assets/{skeleton-CTqhU4nE.js → skeleton-zDNAncEV.js} +1 -1
- package/dist/webdist/assets/{sliders-horizontal-CqA4xQwh.js → sliders-horizontal-pdFDRpIF.js} +1 -1
- package/dist/webdist/assets/{star-C4xvUEne.js → star-CxZtY_PT.js} +1 -1
- package/dist/webdist/assets/{stateDiagram-AJRCARHV-Bbgyioxb.js → stateDiagram-AJRCARHV-CJ5GWb0F.js} +1 -1
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-pOp9pkGE.js +1 -0
- package/dist/webdist/assets/{switch-vVu6UIJB.js → switch-D3C8JOte.js} +1 -1
- package/dist/webdist/assets/{terminal-BqY0B6tG.js → terminal-Bbrza0GS.js} +1 -1
- package/dist/webdist/assets/{timeline-definition-PNZ67QCA-CNuDMLf0.js → timeline-definition-PNZ67QCA-BPUdD63z.js} +1 -1
- package/dist/webdist/assets/{trash-2-CI-4vgjV.js → trash-2-CACOtmLn.js} +1 -1
- package/dist/webdist/assets/useIsDesktop-ClfQtAGq.js +1 -0
- package/dist/webdist/assets/{vennDiagram-CIIHVFJN-5KfL6f8c.js → vennDiagram-CIIHVFJN-CdZ4KAkR.js} +1 -1
- package/dist/webdist/assets/{wardley-L42UT6IY-KSQpq69w.js → wardley-L42UT6IY-Dzejgqpe.js} +1 -1
- package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-CRDC9-52.js → wardleyDiagram-YWT4CUSO-CwsJvJ20.js} +1 -1
- package/dist/webdist/assets/{wrench-B6O2XFxJ.js → wrench-ChbW0-h1.js} +1 -1
- package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-CFJUnPty.js → xychartDiagram-2RQKCTM6-ikNJwN3D.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/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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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-
|
|
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};
|