@nijaru/pi-peak-hours 0.0.7 → 0.0.8
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 +3 -3
- package/extensions/index.ts +17 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The default covers DeepSeek direct only: pi records it at the published **peak**
|
|
|
8
8
|
|
|
9
9
|
With a compatible [pi-provider-compaction](https://github.com/nijaru/pi-provider-compaction) companion loaded, this extension scales existing `usage.cost` before request completion reaches stream consumers or cache observers. It covers observed foreground requests, compaction, branch summaries, and cache warming for scheduled providers. Without the companion, it still corrects finalized foreground assistant messages through `message_end`; auxiliary usage stays unchanged. Both paths preserve token counts and existing cost corrections such as `/fast`.
|
|
10
10
|
|
|
11
|
-
The status line quotes the selected model's scheduled catalog estimate — `·
|
|
11
|
+
The status line quotes the selected model's scheduled catalog estimate — `· $0.30/$1.20/M` during a peak window, `· ▼ off-peak $0.15/$0.60/M` off it. `▲ peak` or `▼ off-peak` marks a rate above or below pi's recorded rate. With stock peak-base prices, peak has no indicator and off-peak is marked as a discount. `/peak-hours status` names the schedule.
|
|
12
12
|
|
|
13
13
|
The footer describes the current hour, not a particular request: a request invoked before a boundary uses its invocation timestamp even after the footer moves to the new hour. Request-specific corrections can also differ from the quoted catalog rate. Stale base prices remain stale; this extension applies a multiplier, not a price lookup. All costs are estimates, not invoices: server arrival times and Chinese public holidays are not modeled.
|
|
14
14
|
|
|
@@ -102,11 +102,11 @@ Note that a coding plan is billed in credits against a subscription, so pi's cos
|
|
|
102
102
|
|
|
103
103
|
OpenRouter publishes the same windows machine-readably for models it hosts (`pricing.overrides` with `utc_days` / `utc_start` / `utc_end` on `https://openrouter.ai/api/v1/models`), but this extension reads its schedule from config rather than the network: the correction is a rate difference, not a price lookup, and a fetch would add a failure mode to a handler that must never throw.
|
|
104
104
|
|
|
105
|
-
A malformed field falls back to its default rather than disabling the extension; a malformed file warns and resolves to defaults.
|
|
105
|
+
A malformed field falls back to its default rather than disabling the extension; a malformed file warns and resolves to defaults. `/peak-hours on|off` refuses to rewrite a malformed, unreadable, or non-object config and reports that the save failed, leaving the existing file untouched.
|
|
106
106
|
|
|
107
107
|
## Design notes
|
|
108
108
|
|
|
109
|
-
- **Only the selected model drives the indicator.** Schedules are provider- and model-scoped, so the footer shows no rate for uncovered models. A rate above the recorded one reads `peak`, below it `off-peak`.
|
|
109
|
+
- **Only the selected model drives the indicator.** Schedules are provider- and model-scoped, so the footer shows no rate for uncovered models. A rate above the recorded one reads `peak`, below it `off-peak`.
|
|
110
110
|
- **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.
|
|
111
111
|
- **Rate by provider invocation.** Pi providers timestamp each assistant message before preparing and sending its HTTP request. That timestamp selects the multiplier, not completion time or the shared payload hook. With the companion, active state and schedules are captured at request dispatch; without it, foreground fallback uses the configuration at `message_end`.
|
|
112
112
|
- **Scaled once.** Global weak identity guards protect usage and foreground message objects across loaded copies, retries, and returned replacements. Usage observed at dispatch remains handled even when its decision is unchanged (×1 or inactive), so fallback cannot overwrite it after a config change.
|
package/extensions/index.ts
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
*
|
|
16
16
|
* The footer indicator is scoped to the selected model and quotes the rate that
|
|
17
17
|
* applies to it right now, in dollars per million tokens: with the default
|
|
18
|
-
* schedule, `·
|
|
19
|
-
* `·
|
|
18
|
+
* schedule, `· $0.30/$1.20/M` at peak and
|
|
19
|
+
* `· ▼ off-peak $0.15/$0.60/M` off it. This is a scheduled catalog estimate;
|
|
20
20
|
* request-specific corrections such as /fast can change the actual usage cost.
|
|
21
21
|
* Uncovered models show no rate; switching models re-evaluates it.
|
|
22
22
|
*
|
|
@@ -415,11 +415,11 @@ export function statusIndicator(
|
|
|
415
415
|
|
|
416
416
|
switch (rateStateAt(now, schedule)) {
|
|
417
417
|
case "peak":
|
|
418
|
-
return `·
|
|
418
|
+
return `· ${STATUS_PEAK} ${rate}`;
|
|
419
419
|
case "off-peak":
|
|
420
|
-
return `·
|
|
420
|
+
return `· ${STATUS_OFF_PEAK} ${rate}`;
|
|
421
421
|
default:
|
|
422
|
-
return `·
|
|
422
|
+
return `· ${rate}`;
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
|
|
@@ -444,14 +444,16 @@ export function readConfig(path: string): ConfigFile {
|
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
/**
|
|
448
|
-
export function writeActive(path: string, active: boolean):
|
|
447
|
+
/** Refuse to replace unreadable/malformed config; preserve unknown valid keys. */
|
|
448
|
+
export function writeActive(path: string, active: boolean): boolean {
|
|
449
449
|
try {
|
|
450
|
+
const config: unknown = existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
|
|
451
|
+
if (!isRecord(config)) return false;
|
|
450
452
|
mkdirSync(dirname(path), { recursive: true });
|
|
451
|
-
writeFileSync(path, `${JSON.stringify({ ...
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
453
|
+
writeFileSync(path, `${JSON.stringify({ ...config, active }, null, 2)}\n`, "utf8");
|
|
454
|
+
return true;
|
|
455
|
+
} catch {
|
|
456
|
+
return false;
|
|
455
457
|
}
|
|
456
458
|
}
|
|
457
459
|
|
|
@@ -686,7 +688,10 @@ export default function peakHours(pi: ExtensionAPI, options: PeakHoursOptions =
|
|
|
686
688
|
ctx.ui.notify("Usage: /peak-hours [on|off|status]", "error");
|
|
687
689
|
return;
|
|
688
690
|
}
|
|
689
|
-
writeActive(configPath, arg === "on")
|
|
691
|
+
if (!writeActive(configPath, arg === "on")) {
|
|
692
|
+
ctx.ui.notify(`Peak hours: failed to save configuration at ${configPath}; keeping the previous setting.`, "error");
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
690
695
|
refresh();
|
|
691
696
|
await pricing.reconcile(ctx);
|
|
692
697
|
if (!pricing.isCurrent(ctx)) return;
|
package/package.json
CHANGED