pi-llamacpp-infra 1.2.1 → 1.2.2

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 CHANGED
@@ -247,20 +247,20 @@ llama.cpp-family models are registered as reasoning models, exactly like a nativ
247
247
 
248
248
  ## Live Speed & Metrics (footer)
249
249
 
250
- When enabled, the speed reading appears in the footer's status line (no extra terminal row) whenever the active model is from llamacpp-infra, updating constantly while tokens flow:
250
+ When enabled, the speed reading appears in the footer's status line (no extra terminal row) whenever the active model is from llamacpp-infra, updating constantly while tokens flow. Both entries are kept ultra-compact so they coexist with other extensions on pi's single status line (which truncates from the end):
251
251
 
252
252
  ```
253
- 🦙 12 models · 3/3 ✓ 📊 · ⚡ prefill… (before the first token)
254
- 🦙 12 models · 3/3 ✓ 📊 · 🔥 38.1 t/s · ⚡ 420 · 1.2k tok (while streaming)
255
- 🦙 12 models · 3/3 ✓ 📊 · 🔥 38.1 t/s · ⚡ 420 · 1.2k tok (just after the answer ends)
256
- 🦙 12 models · 3/3 ✓ 📊 · idle (between turns)
257
- 🦙 12 models · 3/3 ✓ 📊 · 2 · ⚡ 150 · 🔥 18.0 · server (pi idle, server busy for other clients)
253
+ 🦙(12) ⚡… (before the first token)
254
+ 🦙(12) 420 t/s 🔥 38.1 t/s (while streaming)
255
+ 🦙(12) 420 t/s 🔥 38.1 t/s (just after the answer ends)
256
+ 🦙(12) (between turns)
257
+ 🦙(12) ▶2 ⚡ 150 t/s 🔥 18.0 t/s (pi idle, server busy for other clients)
258
258
  ```
259
259
 
260
- (The `🦙 …` prefix is the extension's model-count status; both live on the same footer line, so no extra row is consumed.)
260
+ (`🦙(n)` is the extension's model-count status; both live on the same footer line, so no extra row is consumed.)
261
261
 
262
262
  - **Client measurement (always, no `--metrics` needed)** — prefill speed = `prompt tokens ÷ (request → first token)` (pi's `usage.input`, OpenAI-style `prompt_tokens` as fallback); generation speed = a moving 1.5 s window over per-token arrival samples. Updated ~every 100 ms while a stream is live (throttled, and unchanged text is skipped, so the footer never churns).
263
- - **Server supplement (only when pi is idle)** — the poller fetches the server's Prometheus `/metrics` endpoint (or JSON `/stats`) every `metricsPollMs` (default 5 s). If the server reports other clients processing, their ⚡/🔥 rates are shown; when the server is idle, the plain `⏸ idle` reading returns.
263
+ - **Server supplement (only when pi is idle)** — the poller fetches the server's Prometheus `/metrics` endpoint (or JSON `/stats`) every `metricsPollMs` (default 5 s). If the server reports other clients processing, their ⚡/🔥 rates are shown (`▶n`); when the server is idle, the plain `⏸` reading returns.
264
264
 
265
265
  ## Architecture
266
266
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-llamacpp-infra",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
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/index.ts CHANGED
@@ -225,7 +225,7 @@ export default function (pi: ExtensionAPI) {
225
225
 
226
226
  async function rescan(ctx?: ExtensionContext) {
227
227
  if (!extensionActive) return;
228
- if (ctxHasUI(ctx)) ctx.ui.setStatus(STATUS_KEY, "🔎 scanning…");
228
+ if (ctxHasUI(ctx)) ctx.ui.setStatus(STATUS_KEY, "🔎");
229
229
  stopPolling();
230
230
  registerEmptyProvider();
231
231
  const r = await discoverAndRegister();
@@ -237,11 +237,9 @@ export default function (pi: ExtensionAPI) {
237
237
  function updateStatusFooter(ctx?: ExtensionContext) {
238
238
  if (!ctxHasUI(ctx)) return;
239
239
  if (shared.registeredCount > 0) {
240
- const up = shared.lastScan?.serversUp ?? 0;
241
- const total = shared.lastScan?.serversTotal ?? 0;
242
- ctx.ui.setStatus(STATUS_KEY, `🦙 ${shared.registeredCount} models · ${up}/${total} ✓`);
240
+ ctx.ui.setStatus(STATUS_KEY, `🦙(${shared.registeredCount})`);
243
241
  } else if (shared.lastScan?.endpoints.some((e) => e.loading)) {
244
- ctx.ui.setStatus(STATUS_KEY, "⏳ loading…");
242
+ ctx.ui.setStatus(STATUS_KEY, "⏳");
245
243
  } else if (shared.lastError) {
246
244
  ctx.ui.setStatus(STATUS_KEY, "⚠️");
247
245
  } else {
package/src/speed.ts CHANGED
@@ -6,10 +6,15 @@
6
6
  // prefill t/s = prompt tokens / (before_provider_request → first token)
7
7
  // gen t/s = moving window over per-token arrival samples
8
8
  //
9
+ // Footer format (kept ultra-compact so it coexists with other extensions on
10
+ // pi's single status line, which is truncated at the end):
11
+ //
12
+ // ⚡ {prefill} t/s 🔥 {gen} t/s
13
+ //
9
14
  // The server-side /metrics polling (metrics.ts) only supplements this:
10
15
  // when the client is idle but the server reports other clients processing,
11
- // their rate is shown. The client measurement works even when the server has
12
- // no --metrics endpoint.
16
+ // their rate is shown as `▶{n} t/s 🔥 t/s`. The client measurement
17
+ // works even when the server has no --metrics endpoint.
13
18
 
14
19
  import { debugLog, METRICS_STATUS_KEY } from "./core.ts";
15
20
  import type { AssistantMessageEvent, ExtensionContext, ServerMetricsState, ThemeFg } from "./types.ts";
@@ -126,58 +131,58 @@ export function createSpeedTracker(deps: SpeedDeps) {
126
131
  return tokens / (span / 1000);
127
132
  }
128
133
 
129
- function buildLine(ctx: ExtensionContext | undefined, now: number): string[] {
134
+ function buildLine(ctx: ExtensionContext | undefined, now: number): string {
130
135
  const f = fg(ctx);
131
- const parts: string[] = [f("accent", "📊")];
136
+ const parts: string[] = [];
132
137
 
133
138
  switch (state) {
134
139
  case "prefill":
135
- parts.push(f("warning", "⚡ prefill…"));
140
+ parts.push(f("warning", "⚡…"));
136
141
  break;
137
142
 
138
143
  case "streaming": {
144
+ // Prefill rate of the previous call (only known once it ended).
145
+ if (lastPrefillTps !== undefined) {
146
+ parts.push(f("muted", `⚡ ${Math.round(lastPrefillTps)} t/s`));
147
+ }
139
148
  const gen = genRateAt(now);
140
149
  if (gen !== undefined) {
141
150
  lastGenTps = gen;
142
151
  parts.push(f(rateColor(gen, "gen"), `🔥 ${formatRate(gen)} t/s`));
143
152
  }
144
- if (lastPrefillTps !== undefined) {
145
- parts.push(f("muted", `⚡ ${Math.round(lastPrefillTps)}`));
146
- }
147
- parts.push(f("muted", `${tokenCount} tok`));
148
153
  break;
149
154
  }
150
155
 
151
156
  case "done": {
152
- if (lastGenTps !== undefined) {
153
- parts.push(f("muted", `🔥 ${formatRate(lastGenTps)} t/s`));
154
- }
155
157
  if (lastPrefillTps !== undefined) {
156
- parts.push(f("muted", `⚡ ${Math.round(lastPrefillTps)}`));
158
+ parts.push(f(rateColor(lastPrefillTps, "prefill"), `⚡ ${Math.round(lastPrefillTps)} t/s`));
159
+ }
160
+ if (lastGenTps !== undefined) {
161
+ parts.push(f(rateColor(lastGenTps, "gen"), `🔥 ${formatRate(lastGenTps)} t/s`));
157
162
  }
158
- parts.push(f("muted", `${tokenCount} tok`));
159
163
  break;
160
164
  }
161
165
 
162
166
  case "idle": {
163
167
  // Server supplement: this endpoint is busy for *other* clients.
164
168
  if (server && server.processing > 0) {
165
- parts.push(f("success", `▶ ${server.processing}`));
169
+ parts.push(f("success", `▶${server.processing}`));
166
170
  if (server.promptTps !== undefined && server.promptTps > 0) {
167
- parts.push(f(rateColor(server.promptTps, "prefill"), `⚡ ${Math.round(server.promptTps)}`));
171
+ parts.push(f(rateColor(server.promptTps, "prefill"), `⚡ ${Math.round(server.promptTps)} t/s`));
168
172
  }
169
173
  if (server.genTps !== undefined && server.genTps > 0) {
170
- parts.push(f(rateColor(server.genTps, "gen"), `🔥 ${formatRate(server.genTps)}`));
174
+ parts.push(f(rateColor(server.genTps, "gen"), `🔥 ${formatRate(server.genTps)} t/s`));
171
175
  }
172
- parts.push(f("muted", "server"));
173
176
  } else {
174
- parts.push(f("muted", "⏸ idle"));
177
+ parts.push(f("muted", "⏸"));
175
178
  }
176
179
  break;
177
180
  }
178
181
  }
179
182
 
180
- return [parts.join(" · ")];
183
+ // A stream is live but no rate is computable yet (first ~300 ms of a call).
184
+ if (parts.length === 0) parts.push(f("muted", "🔥…"));
185
+ return parts.join(" ");
181
186
  }
182
187
 
183
188
  function render(ctx: ExtensionContext | undefined, now: number, force = false): void {
@@ -186,7 +191,7 @@ export function createSpeedTracker(deps: SpeedDeps) {
186
191
  return;
187
192
  }
188
193
  if (!deps.hasUI(ctx) || !deps.isOurs(ctx)) return;
189
- const line = buildLine(ctx, now).join(" · ");
194
+ const line = buildLine(ctx, now);
190
195
  if (!force && now - lastRenderAt < RENDER_THROTTLE_MS) return;
191
196
  lastRenderAt = now;
192
197
  // No visual change → skip (avoids a full UI re-render on the footer).