pi-kimi-keepalive 0.3.2 → 0.3.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +38 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-kimi-keepalive",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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,10 @@ import {
51
51
 
52
52
  const STATE_DIR = join(homedir(), ".pi", "cache-keepalive");
53
53
  const STATE_FILE = join(STATE_DIR, "state.json");
54
- const AUTH_FILE = join(process.env.PI_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "auth.json");
54
+ /** Live OAuth state; resolved lazily so tests can redirect it via PI_AGENT_DIR. */
55
+ function authFile(): string {
56
+ return join(process.env.PI_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "auth.json");
57
+ }
55
58
 
56
59
  interface PersistedConfig {
57
60
  enabled: boolean;
@@ -473,22 +476,26 @@ export default function (pi: ExtensionAPI) {
473
476
  */
474
477
  function probeHeaders(): Record<string, string> | null {
475
478
  if (!capture) return null;
476
- const headers = buildProbeHeaders(capture.headers);
477
- if (!headers.authorization) {
478
- try {
479
- const raw = JSON.parse(readFileSync(AUTH_FILE, "utf8")) as Record<string, unknown>;
480
- const entry = (raw["kimi-coding"] ?? null) as { access?: unknown } | null;
481
- if (entry && typeof entry.access === "string" && entry.access.length > 0) {
482
- headers.authorization = `Bearer ${entry.access}`;
483
- } else {
484
- debug(`no kimi-coding access token in ${AUTH_FILE}`);
485
- }
486
- } catch (error) {
487
- debug(`auth.json unavailable: ${error instanceof Error ? error.message : String(error)}`);
479
+ // Prefer the live token from auth.json: pi refreshes it there on real
480
+ // requests, while captured request headers freeze the token from capture
481
+ // time — hours later that Bearer can already be expired (HTTP 401).
482
+ // Fall back to the captured headers when auth.json is unavailable.
483
+ try {
484
+ const raw = JSON.parse(readFileSync(authFile(), "utf8")) as Record<string, unknown>;
485
+ const entry = (raw["kimi-coding"] ?? null) as { access?: unknown } | null;
486
+ if (entry && typeof entry.access === "string" && entry.access.length > 0) {
487
+ const headers = buildProbeHeaders(capture.headers);
488
+ headers.authorization = `Bearer ${entry.access}`;
489
+ return headers;
488
490
  }
491
+ debug(`no kimi-coding access token in ${authFile()}`);
492
+ } catch (error) {
493
+ debug(
494
+ `auth.json unavailable (${error instanceof Error ? error.message : String(error)}) — falling back to captured headers`,
495
+ );
489
496
  }
490
- if (!headers.authorization) return null;
491
- return headers;
497
+ const headers = buildProbeHeaders(capture.headers);
498
+ return headers.authorization ? headers : null;
492
499
  }
493
500
 
494
501
  async function runProbe(): Promise<boolean> {
@@ -952,8 +959,23 @@ export default function (pi: ExtensionAPI) {
952
959
  const raw = payload as Record<string, unknown>;
953
960
  if (!Array.isArray(raw.messages) || raw.messages.length === 0) return;
954
961
  if (raw.system !== undefined && !Array.isArray(raw.system) && typeof raw.system !== "string") return;
962
+ let clonedPayload: Record<string, unknown>;
963
+ try {
964
+ clonedPayload = structuredClone(raw);
965
+ } catch {
966
+ // DataCloneError: the payload carries non-cloneable values (BigInt,
967
+ // symbols, functions). Fall back to a JSON round-trip; probing never
968
+ // mutates the captured payload, so a by-reference capture is the last
969
+ // resort rather than failing the real request.
970
+ try {
971
+ clonedPayload = JSON.parse(JSON.stringify(raw)) as Record<string, unknown>;
972
+ } catch {
973
+ debug("payload not cloneable — capturing by reference");
974
+ clonedPayload = raw;
975
+ }
976
+ }
955
977
  capture = {
956
- payload: structuredClone(raw),
978
+ payload: clonedPayload,
957
979
  headers: capturedHeaders,
958
980
  provider: model.provider,
959
981
  api: (model as { api?: string }).api,