useful-pi-extensions 1.1.1 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "useful-pi-extensions",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "A small collection of pi extensions, installed with one command — a labelled status line with context pressure, cache, cost, effort, TTFT and tokens/sec.",
5
5
  "keywords": [
6
6
  "bun",
@@ -39,6 +39,7 @@ import {
39
39
  pair,
40
40
  row,
41
41
  shortenPath,
42
+ ttftMs,
42
43
  USD,
43
44
  type Currency,
44
45
  } from './render.ts'
@@ -185,7 +186,7 @@ export default function (pi: ExtensionAPI) {
185
186
  let reading: { rate: number; exact: boolean; ttftMs: number | null } | null = null
186
187
 
187
188
  let chars = 0
188
- let stepStartedAt = 0
189
+ let requestAt: number | null = null
189
190
  let firstTokenAt: number | null = null
190
191
  let windowAt = 0
191
192
  let windowTokens = 0
@@ -199,8 +200,8 @@ export default function (pi: ExtensionAPI) {
199
200
  renderedAt = 0
200
201
  }
201
202
 
202
- function publish(rate: number, exact: boolean, ttftMs: number | null): void {
203
- reading = { rate, exact, ttftMs }
203
+ function publish(rate: number, exact: boolean, ttft: number | null): void {
204
+ reading = { rate, exact, ttftMs: ttft }
204
205
  requestRender?.()
205
206
  }
206
207
 
@@ -286,6 +287,7 @@ export default function (pi: ExtensionAPI) {
286
287
  pi.on('session_start', async (_event, ctx) => {
287
288
  ratio = seedRatio(ctx)
288
289
  reading = null
290
+ requestAt = null
289
291
  resetStream()
290
292
  currency = await loadCurrency((message) => ctx.ui.notify(message, 'warning'))
291
293
  installFooter(ctx)
@@ -294,7 +296,17 @@ export default function (pi: ExtensionAPI) {
294
296
  pi.on('message_start', async (event) => {
295
297
  if (event.message.role !== 'assistant') return
296
298
  resetStream()
297
- stepStartedAt = Date.now()
299
+ })
300
+
301
+ // TTFT's request anchor, from the agent loop in pi's docs: turn_start opens the LLM call and
302
+ // before_provider_request is the last thing pi does before the wire. message_start is not that
303
+ // moment, and anchoring there made a first token that arrived early read as a clamped 0ms.
304
+ pi.on('turn_start', async () => {
305
+ requestAt = null
306
+ })
307
+
308
+ pi.on('before_provider_request', async () => {
309
+ requestAt = Date.now()
298
310
  })
299
311
 
300
312
  pi.on('message_update', async (event) => {
@@ -326,7 +338,7 @@ export default function (pi: ExtensionAPI) {
326
338
  const rate =
327
339
  windowMs > 0 ? ((tokens - windowTokens) / windowMs) * 1000 : (tokens / decodeMs) * 1000
328
340
  renderedAt = now
329
- publish(rate, false, firstTokenAt - stepStartedAt)
341
+ publish(rate, false, ttftMs(requestAt, firstTokenAt))
330
342
  })
331
343
 
332
344
  pi.on('message_end', async (event) => {
@@ -341,10 +353,10 @@ export default function (pi: ExtensionAPI) {
341
353
  }
342
354
 
343
355
  const decodeMs = firstTokenAt !== null ? Date.now() - firstTokenAt : 0
344
- const ttftMs = firstTokenAt !== null ? firstTokenAt - stepStartedAt : null
356
+ const measured = ttftMs(requestAt, firstTokenAt)
345
357
  const tokens = output > 0 ? output : totalChars * (ratio ?? FALLBACK_TOKENS_PER_CHAR)
346
- if (ttftMs !== null && decodeMs >= MIN_SAMPLE_MS)
347
- publish((tokens / decodeMs) * 1000, output > 0, ttftMs)
358
+ if (measured !== null && decodeMs >= MIN_SAMPLE_MS)
359
+ publish((tokens / decodeMs) * 1000, output > 0, measured)
348
360
  resetStream()
349
361
  })
350
362
  }
@@ -66,6 +66,21 @@ export function formatLatency(ms: number): string {
66
66
  return `${seconds < 10 ? Math.round(seconds * 10) / 10 : Math.round(seconds)}s`
67
67
  }
68
68
 
69
+ /**
70
+ * TTFT from its two wall-clock anchors: the moment the request was dispatched, and the first
71
+ * streamed delta.
72
+ *
73
+ * The guard is the point. Both anchors come from extension events whose ordering around a stream is
74
+ * not guaranteed, and a first token that appears to precede its own request yields a negative that
75
+ * `formatLatency` would clamp to `0ms` — a confident fake where an honest absence belongs.
76
+ *
77
+ * @returns The milliseconds between the two, or null when they do not line up.
78
+ */
79
+ export function ttftMs(requestAt: number | null, firstTokenAt: number | null): number | null {
80
+ if (requestAt === null || firstTokenAt === null) return null
81
+ return firstTokenAt > requestAt ? firstTokenAt - requestAt : null
82
+ }
83
+
69
84
  /** Home-relative path, or the absolute path when it is outside the home directory. */
70
85
  export function formatCwd(cwd: string): string {
71
86
  const home = homedir()