pi-llamacpp-infra 1.3.0 β 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/package.json +1 -1
- package/src/core.ts +10 -0
- package/src/ui.ts +44 -32
package/README.md
CHANGED
|
@@ -286,11 +286,12 @@ Every assistant message therefore carries a realistic `usage.cost.total`: pi's o
|
|
|
286
286
|
### Setup
|
|
287
287
|
|
|
288
288
|
```
|
|
289
|
-
/llamacpp-infra config β π° Energy cost
|
|
289
|
+
/llamacpp-infra config β π° Energy cost (global: currency, master toggle)
|
|
290
|
+
/llamacpp-infra config β π Servers β machine β π° Energy cost (per machine)
|
|
290
291
|
```
|
|
291
292
|
|
|
292
|
-
1. **Currency** β USD, EUR, GBP or CNY. It only changes the display unit; tariffs are stored per kWh in that currency.
|
|
293
|
-
2. **Per-server power draw & tariff** β the power draw belongs to the *machine*, so
|
|
293
|
+
1. **Currency** (global) β USD, EUR, GBP or CNY. It only changes the display unit; tariffs are stored per kWh in that currency.
|
|
294
|
+
2. **Per-server power draw & tariff** β the power draw belongs to the *machine*, so each server gets its own profile inside its own config menu (`π Servers` β select the machine β `π° Energy cost`; adding a new server asks for it right away). Set `kW` during inference (e.g. `0.15` for 150 W β GPU TDP + idle draw is a good approximation) and the electricity price per kWh. All models served by that machine inherit it.
|
|
294
295
|
|
|
295
296
|
Once set, each provider request is timed (`before_provider_request` β assistant `message_end`, partial/aborted requests included) and charged. The footer shows the session total as `π°` (e.g. `π°3.2c` = 3.2 euro-cents; `Β’`/`c`/`p`/`ε` per currency); enable/disable anytime from the same menu.
|
|
296
297
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-llamacpp-infra",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Discovery, metrics and control of llama.cpp-family servers for pi (llama.cpp, ZINC, DwarfStar/ds4, lucebox, LM Studio): scan machines (localhost, LAN, Tailscale), register models, live metrics, per-model thinking budgets, vision detection, native config UI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/core.ts
CHANGED
|
@@ -96,6 +96,16 @@ export function normalizeSettings(raw: unknown): SettingsConfig {
|
|
|
96
96
|
return s;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** True if two servers would collide on the same (host, port) endpoint. */
|
|
100
|
+
export function serversShareEndpoint(
|
|
101
|
+
a: Pick<import("./types.ts").ServerConfig, "host" | "ports">,
|
|
102
|
+
b: Pick<import("./types.ts").ServerConfig, "host" | "ports">,
|
|
103
|
+
): boolean {
|
|
104
|
+
if (a.host !== b.host) return false;
|
|
105
|
+
const aPorts = new Set(a.ports);
|
|
106
|
+
return b.ports.some((p) => aPorts.has(p));
|
|
107
|
+
}
|
|
108
|
+
|
|
99
109
|
function normalizeServer(s: Record<string, unknown>): import("./types.ts").ServerConfig {
|
|
100
110
|
return {
|
|
101
111
|
id: String(s.id ?? "local"),
|
package/src/ui.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
modelOptions,
|
|
8
8
|
saveConfig,
|
|
9
9
|
serverLabel,
|
|
10
|
+
serversShareEndpoint,
|
|
10
11
|
shared,
|
|
11
12
|
} from "./core.ts";
|
|
12
13
|
import type {
|
|
@@ -225,7 +226,7 @@ export async function showConfigMenu(ctx: ExtensionContext, deps: UiDeps): Promi
|
|
|
225
226
|
{
|
|
226
227
|
value: "cost",
|
|
227
228
|
label: `π° Energy cost: ${config.settings.costTracking ? "ON" : "OFF"}`,
|
|
228
|
-
description: `currency ${currencySymbol(config.settings.currency)} Β·
|
|
229
|
+
description: `currency ${currencySymbol(config.settings.currency)} Β· kW/tariff per server under π Servers`,
|
|
229
230
|
},
|
|
230
231
|
{
|
|
231
232
|
value: "metrics",
|
|
@@ -333,6 +334,11 @@ async function showServerMenu(ctx: ExtensionContext, srv: ServerConfig, deps: Ui
|
|
|
333
334
|
label: srv.apiKey ? "π API key: set" : "π API key: none",
|
|
334
335
|
description: srv.apiKey ? "clear or replace bearer token" : "optional",
|
|
335
336
|
},
|
|
337
|
+
{
|
|
338
|
+
value: "cost",
|
|
339
|
+
label: srv.costProfile?.kW ? `π° Energy cost: ${formatProfile(srv.costProfile, config.settings.currency)}` : "π° Energy cost: not set",
|
|
340
|
+
description: "power draw during inference (kW) + electricity tariff",
|
|
341
|
+
},
|
|
336
342
|
{ value: "test", label: "π§ͺ Test this server", description: `probe ${srv.ports.length} port(s) now` },
|
|
337
343
|
{ value: "delete", label: "ποΈ Delete server", description: "remove from configuration" },
|
|
338
344
|
{ value: "__back", label: "β Back", description: "" },
|
|
@@ -348,6 +354,15 @@ async function showServerMenu(ctx: ExtensionContext, srv: ServerConfig, deps: Ui
|
|
|
348
354
|
ctx.ui.notify("β Host cannot be empty", "error");
|
|
349
355
|
break;
|
|
350
356
|
}
|
|
357
|
+
const dup = config.servers.find((s) => s.id !== srv.id && serversShareEndpoint(s, { host: trimmed, ports: srv.ports }));
|
|
358
|
+
if (dup) {
|
|
359
|
+
const sharedPorts = srv.ports.filter((p) => dup.ports.includes(p)).join(", ");
|
|
360
|
+
ctx.ui.notify(
|
|
361
|
+
`β οΈ Server "${serverLabel(dup)}" already probes ${trimmed}:${sharedPorts} β pick a different host`,
|
|
362
|
+
"warning",
|
|
363
|
+
);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
351
366
|
srv.host = trimmed;
|
|
352
367
|
saveConfig(config);
|
|
353
368
|
ctx.ui.notify(`β
Host updated: ${srv.host}`, "info");
|
|
@@ -409,6 +424,10 @@ async function showServerMenu(ctx: ExtensionContext, srv: ServerConfig, deps: Ui
|
|
|
409
424
|
}
|
|
410
425
|
break;
|
|
411
426
|
}
|
|
427
|
+
case "cost": {
|
|
428
|
+
await showServerCostMenu(ctx, srv, config);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
412
431
|
case "test": {
|
|
413
432
|
ctx.ui.setStatus("llamacpp-infra", "π§ͺ testingβ¦");
|
|
414
433
|
const localServers = config.settings.detectVision ? scanLocalServers() : new Map();
|
|
@@ -460,10 +479,6 @@ async function addServerFlow(ctx: ExtensionContext, deps: UiDeps): Promise<void>
|
|
|
460
479
|
ctx.ui.notify("β Host cannot be empty", "error");
|
|
461
480
|
return;
|
|
462
481
|
}
|
|
463
|
-
if (config.servers.some((s) => s.host === trimmedHost)) {
|
|
464
|
-
ctx.ui.notify(`β οΈ A server with host "${trimmedHost}" already exists`, "warning");
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
482
|
const portsRaw = await ctx.ui.input("β Ports to probe", "e.g. 1234, 8000, 8080-8082");
|
|
468
483
|
if (portsRaw === undefined) return;
|
|
469
484
|
const ports = parsePorts(portsRaw);
|
|
@@ -471,6 +486,15 @@ async function addServerFlow(ctx: ExtensionContext, deps: UiDeps): Promise<void>
|
|
|
471
486
|
ctx.ui.notify("β No valid ports in input", "error");
|
|
472
487
|
return;
|
|
473
488
|
}
|
|
489
|
+
const dup = config.servers.find((s) => serversShareEndpoint(s, { host: trimmedHost, ports }));
|
|
490
|
+
if (dup) {
|
|
491
|
+
const sharedPorts = ports.filter((p) => dup.ports.includes(p)).join(", ");
|
|
492
|
+
ctx.ui.notify(
|
|
493
|
+
`β οΈ Server "${serverLabel(dup)}" already probes ${trimmedHost}:${sharedPorts} β use different ports`,
|
|
494
|
+
"warning",
|
|
495
|
+
);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
474
498
|
const label = await ctx.ui.input("π·οΈ Label (optional)", trimmedHost);
|
|
475
499
|
if (label === undefined) return;
|
|
476
500
|
const probeDs4 = await ctx.ui.confirm(
|
|
@@ -486,6 +510,12 @@ async function addServerFlow(ctx: ExtensionContext, deps: UiDeps): Promise<void>
|
|
|
486
510
|
saveConfig(config);
|
|
487
511
|
ctx.ui.notify(`β Server added: ${label.trim() || trimmedHost} (${trimmedHost}) β ports ${ports.join(", ")}`, "info");
|
|
488
512
|
await deps.rescan(ctx);
|
|
513
|
+
|
|
514
|
+
// Per-machine energy cost: offered right after adding, since it belongs to
|
|
515
|
+
// the machine's own configuration (also editable later under π Servers).
|
|
516
|
+
const srv = config.servers.find((s) => s.id === id)!;
|
|
517
|
+
const wantCost = await ctx.ui.confirm("π° Energy cost?", "Set this machine's power draw (kW) and tariff to estimate electricity cost of local inference?");
|
|
518
|
+
if (wantCost) await showServerCostMenu(ctx, srv, config);
|
|
489
519
|
}
|
|
490
520
|
|
|
491
521
|
// ββ Thinking budgets βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -646,8 +676,8 @@ async function showCostMenu(ctx: ExtensionContext, deps: UiDeps): Promise<void>
|
|
|
646
676
|
},
|
|
647
677
|
{
|
|
648
678
|
value: "servers",
|
|
649
|
-
label: "π₯οΈ Per-server kW / tariff",
|
|
650
|
-
description: `${withKw.length}
|
|
679
|
+
label: "π₯οΈ Per-server kW / tariffβ¦",
|
|
680
|
+
description: `${withKw.length} configured β edit under π Servers β machine`,
|
|
651
681
|
},
|
|
652
682
|
{ value: "__back", label: "β Back", description: "" },
|
|
653
683
|
]);
|
|
@@ -668,33 +698,22 @@ async function showCostMenu(ctx: ExtensionContext, deps: UiDeps): Promise<void>
|
|
|
668
698
|
}
|
|
669
699
|
}
|
|
670
700
|
else if (action === "servers") {
|
|
671
|
-
await
|
|
701
|
+
await showServersMenu(ctx, deps);
|
|
672
702
|
}
|
|
673
703
|
}
|
|
674
704
|
}
|
|
675
705
|
|
|
676
|
-
|
|
706
|
+
/** Per-machine energy cost (kW + tariff). Lives inside the server's own menu. */
|
|
707
|
+
async function showServerCostMenu(ctx: ExtensionContext, srv: ServerConfig, config: NonNullable<typeof shared.activeConfig>): Promise<void> {
|
|
677
708
|
for (;;) {
|
|
678
|
-
const items = config.servers.map((srv) => ({
|
|
679
|
-
value: srv.id,
|
|
680
|
-
label: `${srv.costProfile?.kW ? "π°" : "Β·"} ${serverLabel(srv)}`,
|
|
681
|
-
description: srv.costProfile?.kW ? formatProfile(srv.costProfile, config.settings.currency) : `${srv.host} β no kW set`,
|
|
682
|
-
}));
|
|
683
|
-
items.push({ value: "__back", label: "β Back", description: "" });
|
|
684
|
-
const picked = await selectFrom(ctx, "π° Per-server energy cost β select a machine", items);
|
|
685
|
-
if (!picked || picked === "__back") return;
|
|
686
|
-
const srv = config.servers.find((s) => s.id === picked);
|
|
687
|
-
if (!srv) return;
|
|
688
|
-
|
|
689
709
|
const p = srv.costProfile;
|
|
690
|
-
const sub = await selectFrom(ctx, `π° ${serverLabel(srv)}
|
|
691
|
-
{ value: "kW", label: p?.kW ? `β‘ Power draw: ${p.kW} kW` : "β‘ Set power draw (kW)", description: "
|
|
710
|
+
const sub = await selectFrom(ctx, `π° ${serverLabel(srv)} β energy cost`, [
|
|
711
|
+
{ value: "kW", label: p?.kW ? `β‘ Power draw: ${p.kW} kW` : "β‘ Set power draw (kW)", description: "watts consumed during inference, e.g. 0.15 for 150 W" },
|
|
692
712
|
{ value: "rate", label: p?.ratePerKwh ? `π§Ύ Tariff: ${p.ratePerKwh} ${currencySymbol(config.settings.currency)}/kWh` : `π§Ύ Set tariff (${currencySymbol(config.settings.currency)}/kWh)`, description: "electricity price per kWh" },
|
|
693
|
-
|
|
694
|
-
...(p?.kW ? [{ value: "clear", label: "ποΈ Remove cost profile", description: "stop estimating cost for this machine" }] : []),
|
|
713
|
+
...(p?.kW || p?.ratePerKwh ? [{ value: "clear", label: "ποΈ Remove cost profile", description: "stop estimating cost for this machine" }] : []),
|
|
695
714
|
{ value: "__back", label: "β Back", description: "" },
|
|
696
715
|
]);
|
|
697
|
-
if (!sub || sub === "__back")
|
|
716
|
+
if (!sub || sub === "__back") return;
|
|
698
717
|
|
|
699
718
|
if (sub === "kW") {
|
|
700
719
|
const raw = await ctx.ui.input("β‘ Power draw in kW (e.g. 0.15 = 150 W)", p?.kW ? String(p.kW) : "0.15");
|
|
@@ -712,13 +731,6 @@ async function showServerCostMenu(ctx: ExtensionContext, config: NonNullable<typ
|
|
|
712
731
|
saveConfig(config);
|
|
713
732
|
ctx.ui.notify(`π§Ύ ${serverLabel(srv)}: ${rate} ${currencySymbol(config.settings.currency)}/kWh`, "info");
|
|
714
733
|
}
|
|
715
|
-
else if (sub === "label") {
|
|
716
|
-
const raw = await ctx.ui.input("π·οΈ Label", p?.label ?? "");
|
|
717
|
-
if (raw !== undefined) {
|
|
718
|
-
srv.costProfile = { ...(srv.costProfile ?? { kW: 0.15, ratePerKwh: 0.21 }), label: raw.trim() || undefined };
|
|
719
|
-
saveConfig(config);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
734
|
else if (sub === "clear") {
|
|
723
735
|
delete srv.costProfile;
|
|
724
736
|
saveConfig(config);
|