@nijaru/pi-peak-hours 0.0.3 → 0.0.4
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 +2 -2
- package/extensions/index.ts +12 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -100,10 +100,10 @@ A malformed field falls back to its default rather than disabling the extension;
|
|
|
100
100
|
|
|
101
101
|
- **Only the selected model drives the indicator.** Schedules are provider- and model-scoped, so the footer never shows a rate for a service that does not bill by time of day. A rate above the recorded one reads `peak`, below it `off-peak`, and a discount is never labelled a peak. The quoted rate and the written cost come from the same multiplication, so they cannot disagree.
|
|
102
102
|
- **The footer follows the window it quotes.** pi has no render tick and fires no event while a session is idle, and a rate that changes at 04:00 would otherwise stay on screen until the next turn. The extension arms a single timer for the next instant the quoted rate changes and re-arms it after each fire, from the same clock reading as the status it is keeping fresh. It arms nothing when there is no status on screen, and the timer is unreferenced and cleared on session shutdown, so it neither holds pi open nor outlives its session.
|
|
103
|
-
- **Rate by request start.** DeepSeek bills by arrival time, so the multiplier is chosen from the
|
|
103
|
+
- **Rate by request start.** DeepSeek bills by arrival time, so the multiplier is chosen from the logical request start captured in `before_provider_request`, not `message_end`. `message_start` only fires once the response begins, so it would misprice a request that spans a window boundary.
|
|
104
104
|
- **Scaled once.** Retries and overflow recovery can re-deliver the same message object, so adjustments are guarded by identity; the replacement returned to pi is registered too, so a replayed replacement cannot be scaled twice.
|
|
105
105
|
- **Only cost moves.** The four cost fields are scaled and `total` is recomputed from them, matching pi's own invariant. Token counts are untouched, so rate-from-cost derivations stay self-consistent — including pi's cache-miss accounting, which derives its paid and read rates from the same message and scales with it.
|
|
106
|
-
- **Coverage.** Assistant messages carry the bulk of a session's spend. Compaction and branch-summary usage,
|
|
106
|
+
- **Coverage.** Assistant messages carry the bulk of a session's spend. Compaction and branch-summary usage, prompt-cache-warming refreshes, and token usage reported by tools also consume budget but cannot be adjusted after the fact, so totals stay an estimate rather than an invoice.
|
|
107
107
|
|
|
108
108
|
## Stack
|
|
109
109
|
|
package/extensions/index.ts
CHANGED
|
@@ -552,6 +552,7 @@ export default function peakHours(pi: ExtensionAPI, options: PeakHoursOptions =
|
|
|
552
552
|
let active = parseActive(config.active);
|
|
553
553
|
const adjusted = adjustedMessages();
|
|
554
554
|
let pendingStart: number | undefined;
|
|
555
|
+
let requestStartMs: number | undefined;
|
|
555
556
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
556
557
|
|
|
557
558
|
function refresh(): void {
|
|
@@ -635,6 +636,7 @@ export default function peakHours(pi: ExtensionAPI, options: PeakHoursOptions =
|
|
|
635
636
|
|
|
636
637
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
637
638
|
pendingStart = undefined;
|
|
639
|
+
requestStartMs = undefined;
|
|
638
640
|
disarm();
|
|
639
641
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
640
642
|
});
|
|
@@ -649,8 +651,17 @@ export default function peakHours(pi: ExtensionAPI, options: PeakHoursOptions =
|
|
|
649
651
|
updateStatus(ctx);
|
|
650
652
|
});
|
|
651
653
|
|
|
654
|
+
// The provider bills when the request arrives, and `message_start` is emitted
|
|
655
|
+
// only once the response begins, so capture the logical request start here and
|
|
656
|
+
// fall back to the message clock when no request was observed.
|
|
657
|
+
pi.on("before_provider_request", async () => {
|
|
658
|
+
if (active) requestStartMs = Date.now();
|
|
659
|
+
});
|
|
660
|
+
|
|
652
661
|
pi.on("message_start", async (event) => {
|
|
653
|
-
if (event.message.role
|
|
662
|
+
if (event.message.role !== "assistant") return;
|
|
663
|
+
pendingStart = requestStartMs ?? Date.now();
|
|
664
|
+
requestStartMs = undefined;
|
|
654
665
|
});
|
|
655
666
|
|
|
656
667
|
pi.on("message_end", async (event, ctx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nijaru/pi-peak-hours",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Correct pi's recorded cost for providers that bill by time of day, with a peak multiplier in the footer status.",
|
|
6
6
|
"keywords": [
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"check": "bun test && oxlint extensions/ tests/ && tsc --noEmit"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@earendil-works/pi-ai": "0.
|
|
38
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
37
|
+
"@earendil-works/pi-ai": "0.87.0",
|
|
38
|
+
"@earendil-works/pi-coding-agent": "0.87.0",
|
|
39
39
|
"@types/bun": "latest",
|
|
40
40
|
"oxlint": "latest",
|
|
41
41
|
"typescript": "^6.0.3"
|