useful-pi-extensions 1.1.0 → 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.
|
|
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",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* stepStartTime; decodeMs = completedTime - firstTokenTime; tok/s = usage.output / (decodeMs / 1000)
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import { readFile, stat } from 'node:fs/promises'
|
|
21
22
|
import { homedir } from 'node:os'
|
|
22
23
|
import { join } from 'node:path'
|
|
23
24
|
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
pair,
|
|
39
40
|
row,
|
|
40
41
|
shortenPath,
|
|
42
|
+
ttftMs,
|
|
41
43
|
USD,
|
|
42
44
|
type Currency,
|
|
43
45
|
} from './render.ts'
|
|
@@ -58,8 +60,19 @@ const CONFIG_PATH = join(homedir(), '.pi', 'agent', 'statusline.json')
|
|
|
58
60
|
* bug. Editing the file therefore takes effect on `/reload`.
|
|
59
61
|
*/
|
|
60
62
|
async function loadCurrency(notify: (message: string) => void): Promise<Currency> {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
try {
|
|
64
|
+
await stat(CONFIG_PATH)
|
|
65
|
+
} catch {
|
|
66
|
+
// No config file is the normal case, and it means USD.
|
|
67
|
+
return USD
|
|
68
|
+
}
|
|
69
|
+
let text: string
|
|
70
|
+
try {
|
|
71
|
+
text = await readFile(CONFIG_PATH, 'utf8')
|
|
72
|
+
} catch {
|
|
73
|
+
notify('statusline.json exists but could not be read, showing USD')
|
|
74
|
+
return USD
|
|
75
|
+
}
|
|
63
76
|
const { currency, problem } = currencyFromConfig(text)
|
|
64
77
|
if (problem !== null) notify(problem)
|
|
65
78
|
return currency
|
|
@@ -173,7 +186,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
173
186
|
let reading: { rate: number; exact: boolean; ttftMs: number | null } | null = null
|
|
174
187
|
|
|
175
188
|
let chars = 0
|
|
176
|
-
let
|
|
189
|
+
let requestAt: number | null = null
|
|
177
190
|
let firstTokenAt: number | null = null
|
|
178
191
|
let windowAt = 0
|
|
179
192
|
let windowTokens = 0
|
|
@@ -187,8 +200,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
187
200
|
renderedAt = 0
|
|
188
201
|
}
|
|
189
202
|
|
|
190
|
-
function publish(rate: number, exact: boolean,
|
|
191
|
-
reading = { rate, exact, ttftMs }
|
|
203
|
+
function publish(rate: number, exact: boolean, ttft: number | null): void {
|
|
204
|
+
reading = { rate, exact, ttftMs: ttft }
|
|
192
205
|
requestRender?.()
|
|
193
206
|
}
|
|
194
207
|
|
|
@@ -274,6 +287,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
274
287
|
pi.on('session_start', async (_event, ctx) => {
|
|
275
288
|
ratio = seedRatio(ctx)
|
|
276
289
|
reading = null
|
|
290
|
+
requestAt = null
|
|
277
291
|
resetStream()
|
|
278
292
|
currency = await loadCurrency((message) => ctx.ui.notify(message, 'warning'))
|
|
279
293
|
installFooter(ctx)
|
|
@@ -282,7 +296,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
282
296
|
pi.on('message_start', async (event) => {
|
|
283
297
|
if (event.message.role !== 'assistant') return
|
|
284
298
|
resetStream()
|
|
285
|
-
|
|
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()
|
|
286
310
|
})
|
|
287
311
|
|
|
288
312
|
pi.on('message_update', async (event) => {
|
|
@@ -314,7 +338,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
314
338
|
const rate =
|
|
315
339
|
windowMs > 0 ? ((tokens - windowTokens) / windowMs) * 1000 : (tokens / decodeMs) * 1000
|
|
316
340
|
renderedAt = now
|
|
317
|
-
publish(rate, false, firstTokenAt
|
|
341
|
+
publish(rate, false, ttftMs(requestAt, firstTokenAt))
|
|
318
342
|
})
|
|
319
343
|
|
|
320
344
|
pi.on('message_end', async (event) => {
|
|
@@ -329,10 +353,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
329
353
|
}
|
|
330
354
|
|
|
331
355
|
const decodeMs = firstTokenAt !== null ? Date.now() - firstTokenAt : 0
|
|
332
|
-
const
|
|
356
|
+
const measured = ttftMs(requestAt, firstTokenAt)
|
|
333
357
|
const tokens = output > 0 ? output : totalChars * (ratio ?? FALLBACK_TOKENS_PER_CHAR)
|
|
334
|
-
if (
|
|
335
|
-
publish((tokens / decodeMs) * 1000, output > 0,
|
|
358
|
+
if (measured !== null && decodeMs >= MIN_SAMPLE_MS)
|
|
359
|
+
publish((tokens / decodeMs) * 1000, output > 0, measured)
|
|
336
360
|
resetStream()
|
|
337
361
|
})
|
|
338
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()
|