pi-kimi-keepalive 0.3.2 → 0.3.5
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 +1 -1
- package/src/index.ts +52 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-kimi-keepalive",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Prompt-cache keepalive for Kimi (kimi-coding) sessions in the Pi coding agent. Replays the last real provider request while idle so the automatic prefix cache never expires.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -51,7 +51,22 @@ import {
|
|
|
51
51
|
|
|
52
52
|
const STATE_DIR = join(homedir(), ".pi", "cache-keepalive");
|
|
53
53
|
const STATE_FILE = join(STATE_DIR, "state.json");
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
/** Package version, read from the package.json that ships this extension. */
|
|
56
|
+
function ownVersion(): string {
|
|
57
|
+
try {
|
|
58
|
+
const pkg = JSON.parse(
|
|
59
|
+
readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
|
|
60
|
+
) as { version?: unknown };
|
|
61
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
62
|
+
} catch {
|
|
63
|
+
return "unknown";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Live OAuth state; resolved lazily so tests can redirect it via PI_AGENT_DIR. */
|
|
67
|
+
function authFile(): string {
|
|
68
|
+
return join(process.env.PI_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "auth.json");
|
|
69
|
+
}
|
|
55
70
|
|
|
56
71
|
interface PersistedConfig {
|
|
57
72
|
enabled: boolean;
|
|
@@ -473,22 +488,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
473
488
|
*/
|
|
474
489
|
function probeHeaders(): Record<string, string> | null {
|
|
475
490
|
if (!capture) return null;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
debug(`auth.json unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
491
|
+
// Prefer the live token from auth.json: pi refreshes it there on real
|
|
492
|
+
// requests, while captured request headers freeze the token from capture
|
|
493
|
+
// time — hours later that Bearer can already be expired (HTTP 401).
|
|
494
|
+
// Fall back to the captured headers when auth.json is unavailable.
|
|
495
|
+
try {
|
|
496
|
+
const raw = JSON.parse(readFileSync(authFile(), "utf8")) as Record<string, unknown>;
|
|
497
|
+
const entry = (raw["kimi-coding"] ?? null) as { access?: unknown } | null;
|
|
498
|
+
if (entry && typeof entry.access === "string" && entry.access.length > 0) {
|
|
499
|
+
const headers = buildProbeHeaders(capture.headers);
|
|
500
|
+
headers.authorization = `Bearer ${entry.access}`;
|
|
501
|
+
return headers;
|
|
488
502
|
}
|
|
503
|
+
debug(`no kimi-coding access token in ${authFile()}`);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
debug(
|
|
506
|
+
`auth.json unavailable (${error instanceof Error ? error.message : String(error)}) — falling back to captured headers`,
|
|
507
|
+
);
|
|
489
508
|
}
|
|
490
|
-
|
|
491
|
-
return headers;
|
|
509
|
+
const headers = buildProbeHeaders(capture.headers);
|
|
510
|
+
return headers.authorization ? headers : null;
|
|
492
511
|
}
|
|
493
512
|
|
|
494
513
|
async function runProbe(): Promise<boolean> {
|
|
@@ -710,7 +729,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
710
729
|
const savings = hasPricing(capture?.cost) ? formatUsd(stats.savedUsd) : "n/a (no price data)";
|
|
711
730
|
ctx.ui.setStatus("cache-keepalive", `♥ ${state}`);
|
|
712
731
|
ctx.ui.setWidget("cache-keepalive", [
|
|
713
|
-
|
|
732
|
+
`pi-kimi-keepalive v${ownVersion()}`,
|
|
714
733
|
` state: ${state}`,
|
|
715
734
|
` probes: ${stats.probes} sent · ${stats.hits} hits · ${stats.misses} misses · ${stats.errors} errors`,
|
|
716
735
|
` est.: saved ${savings} · probe spend ${formatUsd(stats.spendUsd)} · cap ${config.spendCapUsd === null ? "none" : formatUsd(config.spendCapUsd)}`,
|
|
@@ -733,7 +752,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
733
752
|
? `${capture.provider} (${capture.api ?? "unknown api"}) @ ${capture.baseUrl}`
|
|
734
753
|
: "none yet — probes start after your first real turn";
|
|
735
754
|
return [
|
|
736
|
-
|
|
755
|
+
`pi-kimi-keepalive v${ownVersion()}`,
|
|
737
756
|
` state: ${config.enabled ? "on" : "off"}${pausedReason ? ` (paused: ${pausedReason})` : ""}`,
|
|
738
757
|
` capture: ${route}`,
|
|
739
758
|
` mode: ${config.mode}${config.mode === "smart" ? ` · cadence ${formatDuration(config.intervalMs)}${lastProbeInputTokens > SMART_MAX_CONTEXT_TOKENS ? ` · context ${lastProbeInputTokens.toLocaleString()} > cap, frozen at floor` : ""}` : " (fixed via interval=)"}`,
|
|
@@ -952,8 +971,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
952
971
|
const raw = payload as Record<string, unknown>;
|
|
953
972
|
if (!Array.isArray(raw.messages) || raw.messages.length === 0) return;
|
|
954
973
|
if (raw.system !== undefined && !Array.isArray(raw.system) && typeof raw.system !== "string") return;
|
|
974
|
+
let clonedPayload: Record<string, unknown>;
|
|
975
|
+
try {
|
|
976
|
+
clonedPayload = structuredClone(raw);
|
|
977
|
+
} catch {
|
|
978
|
+
// DataCloneError: the payload carries non-cloneable values (BigInt,
|
|
979
|
+
// symbols, functions). Fall back to a JSON round-trip; probing never
|
|
980
|
+
// mutates the captured payload, so a by-reference capture is the last
|
|
981
|
+
// resort rather than failing the real request.
|
|
982
|
+
try {
|
|
983
|
+
clonedPayload = JSON.parse(JSON.stringify(raw)) as Record<string, unknown>;
|
|
984
|
+
} catch {
|
|
985
|
+
debug("payload not cloneable — capturing by reference");
|
|
986
|
+
clonedPayload = raw;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
955
989
|
capture = {
|
|
956
|
-
payload:
|
|
990
|
+
payload: clonedPayload,
|
|
957
991
|
headers: capturedHeaders,
|
|
958
992
|
provider: model.provider,
|
|
959
993
|
api: (model as { api?: string }).api,
|