@vikrant82/opencode-cache-keepalive 0.1.5 → 0.2.0
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/MIGRATION.md +57 -0
- package/README.md +75 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1097 -614
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +8 -25
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/control.d.ts +10 -4
- package/dist/lib/control.d.ts.map +1 -1
- package/dist/lib/paths.d.ts +5 -12
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/replay-warmer.d.ts +95 -0
- package/dist/lib/replay-warmer.d.ts.map +1 -0
- package/dist/lib/state.d.ts +15 -114
- package/dist/lib/state.d.ts.map +1 -1
- package/dist/lib/tui/commands.d.ts +2 -0
- package/dist/lib/tui/commands.d.ts.map +1 -1
- package/dist/lib/tui/footer.d.ts +5 -5
- package/dist/lib/tui/footer.d.ts.map +1 -1
- package/dist/lib/tui/format.d.ts +8 -0
- package/dist/lib/tui/format.d.ts.map +1 -1
- package/dist/lib/types.d.ts +123 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/tui.d.ts +3 -0
- package/dist/tui.d.ts.map +1 -1
- package/lib/config.ts +106 -74
- package/lib/control.ts +69 -20
- package/lib/paths.ts +12 -14
- package/lib/replay-warmer.ts +1200 -0
- package/lib/state.ts +172 -175
- package/lib/tui/commands.tsx +76 -0
- package/lib/tui/footer.tsx +55 -130
- package/lib/tui/format.ts +40 -0
- package/lib/types.ts +130 -0
- package/package.json +3 -2
- package/tui.tsx +40 -0
- package/dist/lib/keepalive.d.ts +0 -85
- package/dist/lib/keepalive.d.ts.map +0 -1
- package/dist/lib/model.d.ts +0 -8
- package/dist/lib/model.d.ts.map +0 -1
- package/dist/lib/system.d.ts +0 -11
- package/dist/lib/system.d.ts.map +0 -1
- package/lib/keepalive.ts +0 -636
- package/lib/model.ts +0 -19
- package/lib/system.ts +0 -19
- package/lib/tui/commands.ts +0 -59
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/auth.ts","../lib/config.ts","../lib/control.ts","../lib/paths.ts","../lib/model.ts","../lib/keepalive.ts","../lib/logger.ts","../lib/state.ts","../lib/system.ts","../index.ts"],"sourcesContent":["/**\n * When opencode runs with `OPENCODE_SERVER_PASSWORD`, the local HTTP API requires\n * Basic auth. The SDK client handed to plugins does not carry it automatically,\n * so we attach an interceptor. No-op when the server is not password protected.\n *\n * Same approach as opencode-dynamic-context-pruning's auth helper.\n */\nexport function isSecureMode(): boolean {\n return !!process.env.OPENCODE_SERVER_PASSWORD\n}\n\nexport function getAuthorizationHeader(): string | undefined {\n const password = process.env.OPENCODE_SERVER_PASSWORD\n if (!password) return undefined\n const username = process.env.OPENCODE_SERVER_USERNAME ?? \"opencode\"\n const credentials = Buffer.from(`${username}:${password}`).toString(\"base64\")\n return `Basic ${credentials}`\n}\n\nexport function configureClientAuth(client: any): void {\n const header = getAuthorizationHeader()\n if (!header) return\n\n const inner = client?._client ?? client?.client\n if (!inner?.interceptors?.request) return\n\n inner.interceptors.request.use((request: Request) => {\n if (!request.headers.has(\"Authorization\")) {\n request.headers.set(\"Authorization\", header)\n }\n return request\n })\n}\n","export type KeepaliveConfig = {\n /** Master switch. When false the plugin registers no timers or hooks. */\n enabled: boolean\n /** Milliseconds between pings. Must stay under the shortest supported provider TTL. */\n intervalMs: number\n /**\n * How long to keep warming after the last real response before giving up and\n * letting the cache go cold. The default follows the Copilot Claude\n * write/read break-even; providers with automatic caching may be cheaper.\n */\n windowMs: number\n /**\n * Append a stable keepalive instruction to the system prompt so the model\n * answers a `~` ping with a single `~` token and never calls tools. The\n * instruction is added to real turns too, so the cached prefix stays identical.\n */\n injectSystemInstruction: boolean\n /** The single character/token used as the keepalive suffix. */\n pingToken: string\n /** Remove synthetic ping turns after measuring their cache usage. */\n revertPing: boolean\n /** Warm subagent/child sessions too. Off by default to avoid wasted pings. */\n includeChildSessions: boolean\n debug: boolean\n /** providerID substrings eligible for warming (cache-controlled providers). */\n providerAllowlist: string[]\n /** modelID substrings eligible for warming. */\n modelAllowlist: string[]\n}\n\nconst DEFAULTS: KeepaliveConfig = {\n enabled: true,\n intervalMs: 270_000, // 4.5 min — below the shortest supported cache TTL\n windowMs: 3_300_000, // 55 min — the Copilot Claude write/read break-even\n injectSystemInstruction: true,\n pingToken: \"~\",\n revertPing: true,\n includeChildSessions: false,\n debug: false,\n providerAllowlist: [\"copilot\"],\n modelAllowlist: [\"claude\", \"anthropic\", \"sonnet\", \"opus\", \"haiku\", \"gpt\"],\n}\n\nexport function getConfig(options: Record<string, unknown> | undefined): KeepaliveConfig {\n const o = options ?? {}\n const env = process.env\n\n return {\n enabled: boolOpt(o.enabled, env.OPENCODE_KEEPALIVE_ENABLED !== \"false\" && DEFAULTS.enabled),\n intervalMs: durationOpt(\n o.intervalMs,\n o.intervalSeconds,\n numEnv(env.OPENCODE_KEEPALIVE_INTERVAL_MS, DEFAULTS.intervalMs),\n ),\n windowMs: durationOpt(\n o.windowMs,\n o.windowMinutes ? Number(o.windowMinutes) * 60 : undefined,\n numEnv(env.OPENCODE_KEEPALIVE_WINDOW_MS, DEFAULTS.windowMs),\n ),\n injectSystemInstruction: boolOpt(\n o.injectSystemInstruction,\n DEFAULTS.injectSystemInstruction,\n ),\n pingToken:\n typeof o.pingToken === \"string\" && o.pingToken.length > 0\n ? o.pingToken\n : DEFAULTS.pingToken,\n revertPing: boolOpt(\n o.revertPing,\n env.OPENCODE_KEEPALIVE_REVERT_PING !== \"false\" && DEFAULTS.revertPing,\n ),\n includeChildSessions: boolOpt(o.includeChildSessions, DEFAULTS.includeChildSessions),\n debug: boolOpt(o.debug, env.OPENCODE_KEEPALIVE_DEBUG === \"true\"),\n providerAllowlist: listOpt(o.providerAllowlist, DEFAULTS.providerAllowlist),\n modelAllowlist: listOpt(o.modelAllowlist, DEFAULTS.modelAllowlist),\n }\n}\n\nfunction boolOpt(value: unknown, fallback: boolean): boolean {\n return typeof value === \"boolean\" ? value : fallback\n}\n\nfunction listOpt(value: unknown, fallback: string[]): string[] {\n if (!Array.isArray(value)) return fallback\n const filtered = value.filter((item): item is string => typeof item === \"string\")\n return filtered.length > 0 ? filtered : fallback\n}\n\n/** Accept an explicit `*Ms` option, a `*Seconds` option, or fall back. */\nfunction durationOpt(ms: unknown, seconds: unknown, fallback: number): number {\n if (typeof ms === \"number\" && Number.isFinite(ms) && ms > 0) return ms\n if (typeof seconds === \"number\" && Number.isFinite(seconds) && seconds > 0)\n return seconds * 1000\n return fallback\n}\n\nfunction numEnv(raw: string | undefined, fallback: number): number {\n if (!raw) return fallback\n const value = Number(raw)\n return Number.isFinite(value) && value > 0 ? value : fallback\n}\n","import { readFileSync } from \"node:fs\"\nimport { mkdir, rename, writeFile } from \"node:fs/promises\"\nimport { dirname } from \"node:path\"\nimport { controlFilePath } from \"./paths\"\n\nexport type KeepaliveControl = {\n version: 1\n enabled: boolean\n updatedAt: number\n}\n\nexport function readControl(directory: string): KeepaliveControl | undefined {\n try {\n const value = JSON.parse(\n readFileSync(controlFilePath(directory), \"utf8\"),\n ) as Partial<KeepaliveControl>\n if (value.version !== 1 || typeof value.enabled !== \"boolean\") return undefined\n if (typeof value.updatedAt !== \"number\") return undefined\n return value as KeepaliveControl\n } catch {\n return undefined\n }\n}\n\nexport async function writeControl(enabled: boolean, directory: string): Promise<KeepaliveControl> {\n const value: KeepaliveControl = {\n version: 1,\n enabled,\n updatedAt: Date.now(),\n }\n const path = controlFilePath(directory)\n const tmp = `${path}.${process.pid}.${value.updatedAt}.tmp`\n await mkdir(dirname(path), { recursive: true })\n await writeFile(tmp, `${JSON.stringify(value)}\\n`, \"utf8\")\n await rename(tmp, path)\n return value\n}\n","import { createHash } from \"node:crypto\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\n/**\n * Absolute path to the JSON state file shared between the server plugin (writer)\n * and the TUI plugin (reader). Both processes run on the same machine, so a file\n * under opencode's plugin storage dir is the simplest cross-process channel.\n *\n * Mirrors the storage layout used by other opencode plugins:\n * <XDG_DATA_HOME | ~/.local/share>/opencode/storage/plugin/keepalive/state.json\n */\nexport function stateFilePath(directory: string): string {\n const key = createHash(\"sha256\").update(directory).digest(\"hex\").slice(0, 16)\n return join(stateDirectoryPath(), `state-${key}.json`)\n}\n\nexport function stateDirectoryPath(): string {\n return join(\n process.env.XDG_DATA_HOME ?? join(homedir(), \".local\", \"share\"),\n \"opencode\",\n \"storage\",\n \"plugin\",\n \"keepalive\",\n )\n}\n\n/**\n * Runtime on/off control written by the TUI and read by the server plugin.\n * Scoped per directory (like the state file) so toggling keepalive in one\n * project does not affect every other open opencode session.\n */\nexport function controlFilePath(directory: string): string {\n const key = createHash(\"sha256\").update(directory).digest(\"hex\").slice(0, 16)\n return join(stateDirectoryPath(), `control-${key}.json`)\n}\n\n/** Append-only log file for the server plugin — never stdout, which corrupts the TUI. */\nexport function logFilePath(): string {\n return join(stateDirectoryPath(), \"server.log\")\n}\n","import type { KeepaliveConfig } from \"./config\"\n\n/**\n * Warming only helps on providers that do prefix caching we can refresh by\n * replaying the prefix. Supported defaults include Claude and GPT models served\n * through GitHub Copilot. Everything else is skipped.\n */\nexport function isEligibleModel(\n config: KeepaliveConfig,\n providerID: string | undefined,\n modelID: string | undefined,\n): boolean {\n if (!providerID || !modelID) return false\n const provider = providerID.toLowerCase()\n const model = modelID.toLowerCase()\n const providerOk = config.providerAllowlist.some((needle) => provider.includes(needle))\n const modelOk = config.modelAllowlist.some((needle) => model.includes(needle))\n return providerOk && modelOk\n}\n","import type { KeepaliveConfig } from \"./config\"\nimport { readControl } from \"./control\"\nimport { isEligibleModel } from \"./model\"\nimport type { InflightPing, KeepaliveStore, PingRequest, SessionKeepalive } from \"./state\"\nimport type { Logger } from \"./logger\"\n\nconst TICK_MS = 15_000\nconst JITTER_MS = 15_000\nconst CONTROL_POLL_MS = 1_000\nconst STATUS_POLL_MS = 2_000\nconst PING_STATUS_GRACE_MS = 5_000\n/**\n * A cache last touched more than this many intervals ago is presumed cold. Pinging\n * it would pay a full cache write for a session nobody is using, so the window\n * stays closed until the next real turn re-arms it.\n */\nconst COLD_AFTER_INTERVALS = 2\n/** Recent messages scanned for ping reverts and request-setting recovery. */\nconst MESSAGE_LOOKUP_LIMIT = 50\n\n/**\n * The keepalive engine.\n *\n * Lifecycle per session:\n * 1. A real turn finishes -> `session.status` idle -> arm a warm window anchored on\n * the real response and schedule pings.\n * 2. On each tick past `nextPingAt` (and within the window) -> `firePing`.\n * 3. `firePing` confirms with the server that the session is idle, sends a `~`\n * prompt with the last real turn's agent/model/variant (exact cached prefix),\n * reads `usage` to confirm a cache hit, then optionally reverts the `~`/`~` turn.\n * 4. A new real user turn, the window closing, or the cache presumed cold stops warming.\n *\n * Threading: all state is mutated on the single plugin event loop. opencode invokes\n * the `event` hook without awaiting it, so async handlers interleave; per-session\n * `arming` / `warming` flags serialize the async sections.\n *\n * Errors: network/API failures are logged and never thrown to opencode.\n */\nexport class KeepaliveEngine {\n private timer: ReturnType<typeof setInterval> | undefined\n private controlTimer: ReturnType<typeof setInterval> | undefined\n private statusTimer: ReturnType<typeof setInterval> | undefined\n private controlUpdatedAt = 0\n private reconcilingStatus = false\n private enabled: boolean\n\n constructor(\n private readonly client: any,\n private readonly config: KeepaliveConfig,\n private readonly store: KeepaliveStore,\n private readonly logger: Logger,\n private readonly directory: string,\n ) {\n const control = readControl(directory)\n this.enabled = control?.enabled ?? config.enabled\n this.controlUpdatedAt = control?.updatedAt ?? 0\n this.store.setEnabled(this.enabled)\n }\n\n start(): void {\n if (this.timer) return\n this.timer = setInterval(() => void this.tick(), TICK_MS)\n this.controlTimer = setInterval(() => this.pollControl(), CONTROL_POLL_MS)\n this.statusTimer = setInterval(() => void this.reconcileStatus(), STATUS_POLL_MS)\n this.store.persist()\n this.logger.info(\n `started (${this.enabled ? \"enabled\" : \"disabled\"}, ` +\n `interval=${Math.round(this.config.intervalMs / 1000)}s, ` +\n `window=${Math.round(this.config.windowMs / 60_000)}m)`,\n )\n }\n\n stop(): void {\n if (!this.timer) return\n clearInterval(this.timer)\n this.timer = undefined\n if (this.controlTimer) clearInterval(this.controlTimer)\n this.controlTimer = undefined\n if (this.statusTimer) clearInterval(this.statusTimer)\n this.statusTimer = undefined\n }\n\n /**\n * True while a ping turn owns the session, so tool calls must be blocked. Becomes\n * false as soon as a real user message joins the in-flight ping run: the rest of\n * that run is real work and needs its tools.\n */\n shouldBlockTools(sessionID: string): boolean {\n const s = this.store.get(sessionID)\n return !!s?.warming && !s.ping?.interrupted\n }\n\n async onEvent(event: any): Promise<void> {\n const type: string | undefined = event?.type\n if (!type) return\n\n if (type === \"session.deleted\") {\n const id = event.properties?.sessionID ?? event.properties?.info?.id\n if (id) {\n this.store.remove(id)\n this.store.persist()\n }\n return\n }\n\n if (type === \"message.part.updated\") {\n const part = event.properties?.part\n const sessionID: string | undefined = part?.sessionID ?? event.properties?.sessionID\n if (!sessionID) return\n const session = this.store.get(sessionID)\n if (session?.warming) this.notePingRunPart(session, part)\n return\n }\n\n if (type === \"message.updated\") {\n const info = event.properties?.info\n const sessionID: string | undefined = info?.sessionID ?? event.properties?.sessionID\n if (!sessionID) return\n const session = this.store.get(sessionID)\n // Everything inside our own ping turn is ignored — except a real user\n // message, which opencode joins to the running ping loop.\n if (session?.warming) {\n if (info?.role === \"user\") this.notePingRunUser(session, info)\n return\n }\n const eventAt = info?.time?.completed ?? info?.time?.created\n // Events are asynchronous: a completed ping may still emit message updates\n // after `warming` clears. Their timestamps predate the recorded ping.\n if (session?.lastPingAt && typeof eventAt === \"number\" && eventAt <= session.lastPingAt)\n return\n\n if (info?.role === \"user\") {\n const s = this.store.ensure(sessionID)\n // opencode re-emits older user messages (e.g. summary updates after a\n // turn); only a newly created message starts a turn.\n if (!isNewUserMessage(s, info)) return\n noteUserMessage(s, info)\n s.busy = true\n s.active = false\n this.store.persist()\n return\n }\n\n if (info?.role === \"assistant\" && typeof info?.time?.completed === \"number\") {\n const s = this.store.ensure(sessionID)\n s.lastResponseAt = info.time.completed\n this.store.persist()\n }\n return\n }\n\n if (type === \"session.status\") {\n const sessionID: string | undefined = event.properties?.sessionID\n const status: string | undefined = event.properties?.status?.type\n if (!sessionID || !status) return\n\n // The ping and its optional revert emit status transitions after their\n // requests resolve. Ignore them briefly so they cannot re-arm/reset the\n // active warm window. Real user-message events still stop warming now.\n const current = this.store.get(sessionID)\n if (\n current?.warming ||\n (status !== \"idle\" &&\n current?.lastPingAt &&\n Date.now() - current.lastPingAt < PING_STATUS_GRACE_MS)\n )\n return\n\n if (status === \"idle\") {\n await this.armWindow(sessionID)\n return\n }\n\n const s = this.store.ensure(sessionID)\n s.busy = true\n s.active = false\n this.store.persist()\n return\n }\n\n // Compatibility fallback for opencode versions predating session.status.\n if (type === \"session.idle\") {\n const sessionID: string | undefined = event.properties?.sessionID\n if (!sessionID) return\n if (this.store.get(sessionID)?.warming) return // idle from our own ping\n await this.armWindow(sessionID)\n }\n }\n\n /**\n * Track a new user message observed while a ping is in flight. Event order alone\n * cannot tell the ping's own message from a real prompt racing it, so messages are\n * held until their text part arrives (see `notePingRunPart`). Any message seen\n * before the ping is sent, or a second new message, is necessarily a real turn.\n */\n private notePingRunUser(s: SessionKeepalive, info: any): void {\n const ping = s.ping\n if (!ping || typeof info?.id !== \"string\") return\n const created = info?.time?.created\n if (typeof created === \"number\" && created < ping.startedAt) return // re-emitted old message\n if (info.id === ping.messageID || ping.pending.has(info.id)) return\n if (!ping.sent || ping.messageID) {\n this.interruptPing(s, info)\n return\n }\n ping.pending.set(info.id, info)\n if (ping.pending.size > 1) this.interruptPing(s)\n }\n\n /**\n * Classify a pending user message by its first text part: the ping token marks\n * the ping's own message; any other text is a real turn joining the ping run.\n * opencode publishes a user message's parts right after the message itself, before\n * the run reaches any tool call.\n */\n private notePingRunPart(s: SessionKeepalive, part: any): void {\n const ping = s.ping\n if (!ping || part?.type !== \"text\" || part?.synthetic) return\n const id = part?.messageID\n if (typeof id !== \"string\" || !ping.pending.has(id)) return\n const info = ping.pending.get(id)\n ping.pending.delete(id)\n if (!ping.messageID && String(part.text ?? \"\").trim() === this.config.pingToken) {\n ping.messageID = id\n return\n }\n this.interruptPing(s, info)\n }\n\n /** A real turn joined the in-flight ping: unblock tools and treat the session as busy. */\n private interruptPing(s: SessionKeepalive, info?: unknown): void {\n const ping = s.ping\n if (!ping) return\n if (!ping.interrupted) this.logger.dbg(`real turn joined ping ${short(s.sessionID)}`)\n ping.interrupted = true\n if (info) noteUserMessage(s, info)\n s.busy = true\n s.active = false\n this.store.persist()\n }\n\n private async armWindow(sessionID: string): Promise<void> {\n const s = this.store.ensure(sessionID)\n // Duplicate idle events (including late events from a ping) must not reset the\n // current idle stretch or extend its warm window.\n if (s.active && !s.busy) return\n // Serialize: only one armWindow may resolve metadata at a time per session.\n // This prevents the session.status + session.idle double-event race from\n // producing two concurrent resolveSession calls that can clobber each other.\n if (s.arming) return\n s.arming = true\n try {\n s.busy = false\n await this.resolveSession(s)\n\n // A new turn may have started while session metadata was being resolved.\n if (s.busy || !this.enabled || !s.eligible) {\n s.active = false\n this.store.persist()\n return\n }\n\n // Only a recent real response leaves a cache worth warming. Idle events can\n // also fire for sessions untouched for hours (restarts, re-emitted messages).\n const now = Date.now()\n if (!s.lastResponseAt || !this.cacheMayBeWarm(s, now)) {\n s.active = false\n this.store.persist()\n this.logger.dbg(`not armed ${short(sessionID)} — no recent real response`)\n return\n }\n\n this.openWindow(s, now)\n this.store.persist()\n this.logger.dbg(`armed ${short(sessionID)} model=${s.modelLabel}`)\n } finally {\n s.arming = false\n }\n }\n\n /** Start a warm window anchored on the last real response and the last cache touch. */\n private openWindow(s: SessionKeepalive, now: number): void {\n s.idleSince = now\n s.windowEndsAt = s.lastResponseAt + this.config.windowMs\n s.nextPingAt = Math.max(now, lastCacheTouch(s) + this.config.intervalMs)\n s.pingsSent = 0\n s.lastPing = undefined\n s.active = true\n }\n\n /** False once the cache has gone too long without a request to still be trusted warm. */\n private cacheMayBeWarm(s: SessionKeepalive, now: number): boolean {\n const touch = lastCacheTouch(s)\n return touch > 0 && now - touch <= COLD_AFTER_INTERVALS * this.config.intervalMs\n }\n\n private async tick(): Promise<void> {\n if (!this.enabled) return\n const now = Date.now()\n for (const s of this.store.all()) {\n if (!s.eligible || !s.active || s.busy || s.warming) continue\n\n if (now >= s.windowEndsAt) {\n s.active = false\n this.store.persist()\n this.logger.dbg(\n `window closed ${short(s.sessionID)} after ${s.pingsSent} ping(s) — cache may cool`,\n )\n continue\n }\n\n if (now < s.nextPingAt) continue\n\n // Far overdue (host sleep, stalled event loop): the cache is presumed cold.\n // Stop rather than pay a cold write or fire a catch-up burst.\n if (!this.cacheMayBeWarm(s, now)) {\n s.active = false\n this.store.persist()\n this.logger.dbg(\n `window closed ${short(s.sessionID)} — ping overdue, cache presumed cold`,\n )\n continue\n }\n\n void this.firePing(s.sessionID)\n }\n }\n\n private async firePing(sessionID: string): Promise<void> {\n const s = this.store.get(sessionID)\n if (!s || s.warming || s.busy || !s.active) return\n\n const ping: InflightPing = {\n startedAt: Date.now(),\n sent: false,\n pending: new Map(),\n interrupted: false,\n }\n s.warming = true\n s.ping = ping\n this.store.persist()\n\n try {\n // opencode joins a prompt to a running turn instead of rejecting it, so a\n // missed busy signal would inject the ping into real work. Ask the server.\n if (!(await this.isIdleOnServer(sessionID))) {\n s.busy = true\n s.active = false\n this.logger.dbg(`ping ${short(sessionID)} skipped — session busy`)\n return\n }\n\n const request = s.request ?? (await this.lookupRequest(s))\n if (!request) {\n s.active = false\n this.logger.dbg(`ping ${short(sessionID)} skipped — last real request unknown`)\n return\n }\n\n // A real turn may have started (or keepalive been disabled) while checking.\n if (ping.interrupted || s.busy || !s.active || !this.enabled) return\n\n ping.sent = true\n // Count every request attempt. A transport/provider failure still consumed a\n // scheduled ping slot and must remain visible in the footer.\n s.pingsSent += 1\n this.store.persist()\n\n const res = await this.client.session.prompt({\n path: { id: sessionID },\n body: {\n parts: [{ type: \"text\", text: this.config.pingToken }],\n ...(request.agent ? { agent: request.agent } : {}),\n model: { providerID: request.providerID, modelID: request.modelID },\n ...(request.variant ? { variant: request.variant } : {}),\n },\n })\n const info = res?.data?.info ?? res?.info\n\n if (ping.interrupted) {\n // A real turn ran inside this request: its reply is real work. Keep it,\n // do not score it as a ping, and re-arm from its completion.\n const completed = info?.time?.completed\n if (typeof completed === \"number\") s.lastResponseAt = completed\n this.logger.dbg(\n `ping ${short(sessionID)} absorbed a real turn — kept, not reverted`,\n )\n return\n }\n\n const cache = info?.tokens?.cache ?? {}\n const record = {\n at: Date.now(),\n hit: Number(cache.read ?? 0) > 0,\n input: Number(info?.tokens?.input ?? 0),\n cacheRead: Number(cache.read ?? 0),\n cacheWrite: Number(cache.write ?? 0),\n output: Number(info?.tokens?.output ?? 0),\n }\n s.lastPing = record\n this.logger.dbg(\n `ping ${short(sessionID)} ${record.hit ? \"HIT\" : \"MISS\"} ` +\n `in=${record.input} read=${record.cacheRead} write=${record.cacheWrite} out=${record.output}`,\n )\n\n if (this.config.revertPing) await this.revertPing(sessionID, ping)\n } catch (err) {\n this.logger.warn(`ping ${short(sessionID)} failed`, errText(err))\n } finally {\n s.warming = false\n s.ping = undefined\n const now = Date.now()\n if (ping.sent) {\n s.lastPingAt = now\n // Schedule relative to the intended time so ping duration doesn't\n // compound drift, but never into the past: that would fire catch-up\n // pings on consecutive ticks.\n const next = s.nextPingAt + jitter(this.config.intervalMs)\n s.nextPingAt = next > now ? next : now + jitter(this.config.intervalMs)\n }\n this.store.persist()\n if (ping.sent && ping.interrupted) {\n // The joined real turn has finished with this request; start its window.\n s.busy = false\n s.active = false\n void this.armWindow(sessionID)\n }\n }\n }\n\n /** True only when the server positively reports the session idle. */\n private async isIdleOnServer(sessionID: string): Promise<boolean> {\n try {\n const res = await this.client.session.status()\n const statuses = res?.data ?? res ?? {}\n return (statuses?.[sessionID]?.type ?? \"idle\") === \"idle\"\n } catch (err) {\n this.logger.dbg(`status check ${short(sessionID)} failed`, errText(err))\n return false\n }\n }\n\n /** Recover the last real turn's request settings when no user event was observed. */\n private async lookupRequest(s: SessionKeepalive): Promise<PingRequest | undefined> {\n const messages = await this.recentMessages(s.sessionID)\n if (!messages) return undefined\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i]\n if (m?.info?.role !== \"user\" || this.isPingMessage(m)) continue\n const request = requestFrom(m.info)\n if (request) {\n s.request = request\n return request\n }\n }\n return undefined\n }\n\n private async recentMessages(sessionID: string): Promise<any[] | undefined> {\n try {\n const res = await this.client.session.messages({\n path: { id: sessionID },\n query: { limit: MESSAGE_LOOKUP_LIMIT },\n })\n const messages = res?.data ?? res\n return Array.isArray(messages) ? messages : undefined\n } catch (err) {\n this.logger.warn(`messages ${short(sessionID)} list failed`, errText(err))\n return undefined\n }\n }\n\n private isPingMessage(m: any): boolean {\n const text = (m?.parts ?? [])\n .filter((p: any) => p?.type === \"text\")\n .map((p: any) => p?.text ?? \"\")\n .join(\"\")\n .trim()\n return text === this.config.pingToken\n }\n\n /**\n * Remove the `~` user + `~` assistant turn so it never enters real context.\n * Revert drops the target and everything after it (and rolls back file\n * snapshots), so it is skipped unless only the ping's own replies follow.\n */\n private async revertPing(sessionID: string, ping: InflightPing): Promise<void> {\n const messages = await this.recentMessages(sessionID)\n if (!messages) return\n\n let index = -1\n for (let i = 0; i < messages.length; i++) {\n const m = messages[i]\n if (m?.info?.role !== \"user\") continue\n if (ping.messageID ? m.info.id === ping.messageID : this.isRecentPing(m, ping))\n index = i\n }\n if (index < 0) {\n this.logger.dbg(`ping ${short(sessionID)} revert: ping message not found`)\n return\n }\n\n const target: string = messages[index].info.id\n const foreign = messages\n .slice(index + 1)\n .some((m: any) => m?.info?.role !== \"assistant\" || m?.info?.parentID !== target)\n if (foreign) {\n this.logger.warn(`ping ${short(sessionID)} not reverted — other messages followed it`)\n return\n }\n\n try {\n await this.client.session.revert({\n path: { id: sessionID },\n body: { messageID: target },\n })\n } catch (err) {\n // BusyError (a real turn started) or similar — leave the ping visible.\n this.logger.warn(`ping ${short(sessionID)} revert failed`, errText(err))\n }\n }\n\n private isRecentPing(m: any, ping: InflightPing): boolean {\n return (\n Number(m?.info?.time?.created ?? 0) >= ping.startedAt - 2_000 && this.isPingMessage(m)\n )\n }\n\n private async resolveSession(s: SessionKeepalive): Promise<void> {\n try {\n const res = await this.client.session.get({ path: { id: s.sessionID } })\n const info = res?.data ?? res\n if (info?.parentID && !this.config.includeChildSessions) {\n s.eligible = false\n s.modelLabel = info?.model?.id ?? \"child\"\n return\n }\n s.modelLabel = info?.model?.id ?? \"unknown\"\n s.eligible = isEligibleModel(this.config, info?.model?.providerID, info?.model?.id)\n } catch (err) {\n s.eligible = false\n this.logger.dbg(`resolve ${short(s.sessionID)} failed`, errText(err))\n }\n }\n\n private pollControl(): void {\n const control = readControl(this.directory)\n if (!control || control.updatedAt <= this.controlUpdatedAt) return\n this.controlUpdatedAt = control.updatedAt\n if (control.enabled === this.enabled) return\n\n this.enabled = control.enabled\n this.store.setEnabled(control.enabled)\n const now = Date.now()\n for (const session of this.store.all()) {\n if (!control.enabled) {\n session.active = false\n continue\n }\n // Only re-arm sessions with a real response whose cache may still be warm,\n // and that are not currently occupied.\n if (!session.eligible || session.busy || session.warming) continue\n if (!session.lastResponseAt || !this.cacheMayBeWarm(session, now)) continue\n this.openWindow(session, now)\n }\n this.store.persist()\n this.logger.info(`runtime ${control.enabled ? \"enabled\" : \"disabled\"}`)\n }\n\n /** Recover when a session.status idle event is missed by reconciling with the API. */\n private async reconcileStatus(): Promise<void> {\n if (this.reconcilingStatus) return\n const busy = this.store.all().filter((session) => session.busy && !session.warming)\n if (busy.length === 0) return\n\n this.reconcilingStatus = true\n try {\n const res = await this.client.session.status()\n const statuses = res?.data ?? res ?? {}\n for (const session of busy) {\n const status = statuses?.[session.sessionID]?.type ?? \"idle\"\n if (status === \"idle\") await this.armWindow(session.sessionID)\n }\n } catch (err) {\n this.logger.dbg(\"status reconciliation failed\", errText(err))\n } finally {\n this.reconcilingStatus = false\n }\n }\n}\n\n/** Last time the session's prompt cache was known to be used (real response or ping). */\nfunction lastCacheTouch(s: SessionKeepalive): number {\n return Math.max(s.lastResponseAt, s.lastPingAt ?? 0)\n}\n\nfunction isNewUserMessage(s: SessionKeepalive, info: any): boolean {\n if (typeof info?.id === \"string\" && info.id === s.lastUserID) return false\n const created = info?.time?.created\n return typeof created !== \"number\" || created >= (s.lastUserAt ?? 0)\n}\n\nfunction noteUserMessage(s: SessionKeepalive, info: any): void {\n if (typeof info?.id === \"string\") s.lastUserID = info.id\n const created = info?.time?.created\n if (typeof created === \"number\") s.lastUserAt = created\n const request = requestFrom(info)\n if (request) s.request = request\n}\n\n/** Extract replayable request settings from a user message's info. */\nfunction requestFrom(info: any): PingRequest | undefined {\n const model = info?.model\n const providerID = model?.providerID\n const modelID = model?.modelID ?? model?.id\n if (typeof providerID !== \"string\" || typeof modelID !== \"string\") return undefined\n const variant = model?.variant ?? info?.variant\n return {\n agent: typeof info?.agent === \"string\" ? info.agent : undefined,\n providerID,\n modelID,\n variant: typeof variant === \"string\" && variant !== \"default\" ? variant : undefined,\n }\n}\n\nfunction short(sessionID: string): string {\n return sessionID.slice(-6)\n}\n\nfunction jitter(base: number): number {\n return Math.max(1_000, base + Math.floor((Math.random() * 2 - 1) * JITTER_MS))\n}\n\nfunction errText(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","import { appendFileSync, mkdirSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\n\nconst PREFIX = \"[cache-keepalive]\"\n\nexport class Logger {\n private readonly logPath: string\n private readonly debug: boolean\n private initialized = false\n\n constructor(debug: boolean, logPath: string) {\n this.debug = debug\n this.logPath = logPath\n }\n\n info(message: string, data?: unknown) {\n this.write(\"INFO\", message, data)\n }\n\n warn(message: string, data?: unknown) {\n this.write(\"WARN\", message, data)\n }\n\n error(message: string, data?: unknown) {\n this.write(\"ERROR\", message, data)\n }\n\n dbg(message: string, data?: unknown) {\n if (this.debug) this.write(\"DEBUG\", message, data)\n }\n\n private write(level: string, message: string, data?: unknown): void {\n const ts = new Date().toISOString()\n const extra =\n data !== undefined ? ` ${typeof data === \"string\" ? data : JSON.stringify(data)}` : \"\"\n const line = `${ts} ${level} ${PREFIX} ${message}${extra}\\n`\n try {\n if (!this.initialized) {\n mkdirSync(dirname(this.logPath), { recursive: true })\n this.initialized = true\n }\n appendFileSync(this.logPath, line, \"utf8\")\n } catch {\n // Best-effort logging — never crash the plugin.\n }\n }\n}\n","import { mkdir, rename, writeFile } from \"node:fs/promises\"\nimport { dirname } from \"node:path\"\nimport type { KeepaliveConfig } from \"./config\"\nimport { stateFilePath } from \"./paths\"\n\nexport type PingRecord = {\n at: number\n hit: boolean\n input: number\n cacheRead: number\n cacheWrite: number\n output: number\n}\n\n/**\n * Request settings of the last real user turn. A ping must replay them exactly:\n * a prompt without explicit settings runs as opencode's default agent with no\n * variant, which changes the request shape (missing the cache) and overwrites\n * the session's persisted agent/model/variant.\n */\nexport type PingRequest = {\n agent?: string\n providerID: string\n modelID: string\n variant?: string\n}\n\n/** Bookkeeping for the ping request currently in flight. */\nexport type InflightPing = {\n /** Epoch ms the ping attempt began (before the pre-flight status check). */\n startedAt: number\n /** The prompt request has been issued. */\n sent: boolean\n /** The ping's own user message, identified by its ping-token text part. */\n messageID?: string\n /**\n * New user messages seen during the ping whose text has not yet been observed.\n * Each is classified by its first text part: the ping token marks our own\n * message, anything else a real turn.\n */\n pending: Map<string, unknown>\n /**\n * A real user message appeared while the ping was in flight. opencode joins such\n * a prompt to the running ping loop, so the rest of the run is real work.\n */\n interrupted: boolean\n}\n\n/** Live per-session bookkeeping held by the server plugin. */\nexport type SessionKeepalive = {\n sessionID: string\n /** Whether the session's provider/model pair supports cache warming. */\n eligible: boolean\n modelLabel?: string\n /** Epoch ms of the last real assistant response (excludes ping replies). */\n lastResponseAt: number\n /** Epoch ms the current idle stretch began. */\n idleSince: number\n /** Epoch ms after which warming stops and the cache is allowed to go cold. */\n windowEndsAt: number\n /** Epoch ms the next ping is due. */\n nextPingAt: number\n /** Number of scheduled ping requests attempted during this warm window. */\n pingsSent: number\n /** A real turn is currently running. */\n busy: boolean\n /** A keepalive ping is currently in flight. */\n warming: boolean\n /** The in-flight ping; set exactly while `warming` is true. */\n ping?: InflightPing\n /** ID and creation time of the newest user message seen, to tell new turns from re-emitted old messages. */\n lastUserID?: string\n lastUserAt?: number\n /** Settings of the last real user turn, replayed by pings. */\n request?: PingRequest\n /** Within the warm window and actively scheduling pings. */\n active: boolean\n /** True while `armWindow` is resolving session metadata (async guard). */\n arming?: boolean\n /** Epoch ms the last sent ping finished; ignores its late events and marks the last cache touch. */\n lastPingAt?: number\n lastPing?: PingRecord\n}\n\n/** Lean projection persisted to disk for the TUI reader. */\nexport type PersistedSession = {\n eligible: boolean\n modelLabel?: string\n lastResponseAt: number\n idleSince: number\n windowEndsAt: number\n nextPingAt: number\n intervalMs: number\n pingsSent: number\n busy: boolean\n warming: boolean\n active: boolean\n lastPing?: PingRecord\n}\n\nexport type PersistedState = {\n version: 1\n updatedAt: number\n enabled: boolean\n intervalMs: number\n windowMs: number\n sessions: Record<string, PersistedSession>\n}\n\nexport class KeepaliveStore {\n private readonly sessions = new Map<string, SessionKeepalive>()\n private writeQueue: Promise<void> = Promise.resolve()\n private enabled: boolean\n\n constructor(\n private readonly config: KeepaliveConfig,\n private readonly directory: string,\n ) {\n this.enabled = config.enabled\n }\n\n setEnabled(enabled: boolean): void {\n this.enabled = enabled\n }\n\n get(sessionID: string): SessionKeepalive | undefined {\n return this.sessions.get(sessionID)\n }\n\n ensure(sessionID: string): SessionKeepalive {\n const existing = this.sessions.get(sessionID)\n if (existing) return existing\n const created: SessionKeepalive = {\n sessionID,\n eligible: false,\n lastResponseAt: 0,\n idleSince: 0,\n windowEndsAt: 0,\n nextPingAt: 0,\n pingsSent: 0,\n busy: false,\n warming: false,\n active: false,\n }\n this.sessions.set(sessionID, created)\n return created\n }\n\n remove(sessionID: string): void {\n this.sessions.delete(sessionID)\n }\n\n all(): SessionKeepalive[] {\n return [...this.sessions.values()]\n }\n\n /**\n * Atomically persist a lean snapshot for the TUI. Never throws or rejects; the\n * returned promise resolves once this snapshot and every earlier one are written.\n */\n persist(): Promise<void> {\n const snapshot: PersistedState = {\n version: 1,\n updatedAt: Date.now(),\n enabled: this.enabled,\n intervalMs: this.config.intervalMs,\n windowMs: this.config.windowMs,\n sessions: Object.fromEntries(\n this.all().map((s) => [\n s.sessionID,\n {\n eligible: s.eligible,\n modelLabel: s.modelLabel,\n lastResponseAt: s.lastResponseAt,\n idleSince: s.idleSince,\n windowEndsAt: s.windowEndsAt,\n nextPingAt: s.nextPingAt,\n intervalMs: this.config.intervalMs,\n pingsSent: s.pingsSent,\n busy: s.busy,\n warming: s.warming,\n active: s.active,\n lastPing: s.lastPing,\n } satisfies PersistedSession,\n ]),\n ),\n }\n\n const write = () => atomicWrite(stateFilePath(this.directory), JSON.stringify(snapshot))\n this.writeQueue = this.writeQueue.then(write, write)\n return this.writeQueue\n }\n}\n\nasync function atomicWrite(path: string, contents: string): Promise<void> {\n try {\n const tmp = `${path}.${process.pid}.${Date.now()}.tmp`\n await mkdir(dirname(path), { recursive: true })\n await writeFile(tmp, `${contents}\\n`, \"utf8\")\n await rename(tmp, path)\n } catch {\n // Persistence is best-effort; the TUI readout simply falls behind.\n }\n}\n","/**\n * Stable keepalive instruction appended to the system prompt.\n *\n * IMPORTANT: this text must be byte-stable and applied to *every* request for an\n * eligible model (real turns and pings alike). If it were only added on pings, the\n * system prefix would differ from real turns and the ping would miss the cache.\n * Adding it consistently keeps the cached prefix identical and lets the model\n * answer a ping with a single token instead of a paragraph or a tool call.\n */\nexport function keepaliveInstruction(pingToken: string): string {\n return [\n \"<cache_keepalive>\",\n `A user message consisting of exactly the single token \"${pingToken}\" is an`,\n \"automated cache-keepalive ping, not a real request. When you receive it,\",\n `reply with exactly \"${pingToken}\" and nothing else. Do not call any tools,`,\n \"do not explain, and do not take any action.\",\n \"</cache_keepalive>\",\n ].join(\"\\n\")\n}\n","import type { Plugin } from \"@opencode-ai/plugin\"\nimport { configureClientAuth, isSecureMode } from \"./lib/auth\"\nimport { getConfig } from \"./lib/config\"\nimport { KeepaliveEngine } from \"./lib/keepalive\"\nimport { Logger } from \"./lib/logger\"\nimport { isEligibleModel } from \"./lib/model\"\nimport { logFilePath } from \"./lib/paths\"\nimport { KeepaliveStore } from \"./lib/state\"\nimport { keepaliveInstruction } from \"./lib/system\"\n\ndeclare const __KEEPALIVE_VERSION__: string\n\nconst server: Plugin = async (ctx, options) => {\n const config = getConfig(options as Record<string, unknown> | undefined)\n const logger = new Logger(config.debug, logFilePath())\n\n if (!config.enabled) {\n logger.info(\"disabled via config\")\n return {}\n }\n\n if (isSecureMode()) configureClientAuth(ctx.client)\n\n const store = new KeepaliveStore(config, ctx.directory)\n const engine = new KeepaliveEngine(ctx.client, config, store, logger, ctx.directory)\n engine.start()\n\n const version = typeof __KEEPALIVE_VERSION__ !== \"undefined\" ? __KEEPALIVE_VERSION__ : \"dev\"\n logger.info(`v${version} ready`)\n\n const instruction = keepaliveInstruction(config.pingToken)\n\n return {\n event: async ({ event }) => {\n await engine.onEvent(event)\n },\n\n // Keep the system prefix identical between real turns and pings, and make\n // the model answer a ping with a single token.\n \"experimental.chat.system.transform\": async (input, output) => {\n if (!config.injectSystemInstruction) return\n const model = input.model as { providerID?: string; id?: string; modelID?: string }\n if (!isEligibleModel(config, model?.providerID, model?.id ?? model?.modelID)) return\n output.system.push(instruction)\n },\n\n // Backstop: if the model ignores the instruction and tries a tool during a\n // ping, block it — the ping turn is going to be reverted anyway.\n \"tool.execute.before\": async (input) => {\n if (engine.shouldBlockTools(input.sessionID)) {\n throw new Error(\"cache-keepalive: tools are disabled during a keepalive ping\")\n }\n },\n\n dispose: async () => {\n engine.stop()\n },\n }\n}\n\nexport default server\n"],"mappings":"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","names":["dirname","mkdir","rename","writeFile","dirname","mkdir","dirname","writeFile","rename"]}
|
|
1
|
+
{"version":3,"sources":["../lib/auth.ts","../lib/config.ts","../lib/control.ts","../lib/paths.ts","../lib/replay-warmer.ts","../lib/logger.ts","../lib/state.ts","../index.ts"],"sourcesContent":["/**\n * When opencode runs with `OPENCODE_SERVER_PASSWORD`, the local HTTP API requires\n * Basic auth. The SDK client handed to plugins does not carry it automatically,\n * so we attach an interceptor. No-op when the server is not password protected.\n *\n * Same approach as opencode-dynamic-context-pruning's auth helper.\n */\nexport function isSecureMode(): boolean {\n return !!process.env.OPENCODE_SERVER_PASSWORD\n}\n\nexport function getAuthorizationHeader(): string | undefined {\n const password = process.env.OPENCODE_SERVER_PASSWORD\n if (!password) return undefined\n const username = process.env.OPENCODE_SERVER_USERNAME ?? \"opencode\"\n const credentials = Buffer.from(`${username}:${password}`).toString(\"base64\")\n return `Basic ${credentials}`\n}\n\nexport function configureClientAuth(client: any): void {\n const header = getAuthorizationHeader()\n if (!header) return\n\n const inner = client?._client ?? client?.client\n if (!inner?.interceptors?.request) return\n\n inner.interceptors.request.use((request: Request) => {\n if (!request.headers.has(\"Authorization\")) {\n request.headers.set(\"Authorization\", header)\n }\n return request\n })\n}\n","export type KeepaliveConfig = {\n enabled: boolean\n intervals: Record<string, number>\n hosts: string[]\n cacheReadFactor: number\n missFactor: number\n maxReplaysPerGap: \"auto\" | number\n includeChildSessions: boolean\n replayTimeoutMs: number\n maxStoredBytes: number\n debug: boolean\n}\n\nconst DEFAULTS: KeepaliveConfig = {\n enabled: true,\n intervals: { claude: 285_000, gpt: 1_680_000 },\n hosts: [\"githubcopilot.com\"],\n cacheReadFactor: 0.1,\n missFactor: 1,\n maxReplaysPerGap: \"auto\",\n includeChildSessions: false,\n replayTimeoutMs: 60_000,\n maxStoredBytes: 67_108_864,\n debug: false,\n}\n\nconst DEPRECATED = [\n \"intervalMs\",\n \"intervalSeconds\",\n \"windowMs\",\n \"windowMinutes\",\n \"revertPing\",\n \"pingToken\",\n \"injectSystemInstruction\",\n \"providerAllowlist\",\n \"modelAllowlist\",\n \"claudeBusyWarm\",\n \"claudeBusyWarmIntervalMs\",\n \"claudeBusyWarmWindowMs\",\n]\nconst warnedDeprecated = new Set<string>()\n\nexport function getConfig(\n options: Record<string, unknown> | undefined,\n warn: (message: string) => void = () => {},\n): KeepaliveConfig {\n const option = options ?? {}\n const env = process.env\n for (const key of DEPRECATED) {\n if (key in option && !warnedDeprecated.has(key)) {\n warnedDeprecated.add(key)\n warn(`deprecated option ${key} is ignored`)\n }\n }\n const parseJson = (raw: string | undefined): unknown => {\n if (!raw) return undefined\n try {\n return JSON.parse(raw)\n } catch {\n return undefined\n }\n }\n const intervals = objectOption(\n option.intervals ?? parseJson(env.OPENCODE_KEEPALIVE_INTERVALS),\n DEFAULTS.intervals,\n )\n const hosts = listOption(\n option.hosts ?? parseJson(env.OPENCODE_KEEPALIVE_HOSTS),\n DEFAULTS.hosts,\n )\n const maxRaw = option.maxReplaysPerGap ?? env.OPENCODE_KEEPALIVE_MAX_REPLAYS_PER_GAP\n const maxReplaysPerGap =\n maxRaw === \"auto\"\n ? \"auto\"\n : positiveNumber(maxRaw) !== undefined\n ? positiveNumber(maxRaw)!\n : DEFAULTS.maxReplaysPerGap\n return {\n enabled: boolOption(\n option.enabled,\n env.OPENCODE_KEEPALIVE_ENABLED === undefined\n ? DEFAULTS.enabled\n : env.OPENCODE_KEEPALIVE_ENABLED !== \"false\",\n ),\n intervals,\n hosts,\n cacheReadFactor:\n positiveNumber(option.cacheReadFactor ?? env.OPENCODE_KEEPALIVE_CACHE_READ_FACTOR) ??\n DEFAULTS.cacheReadFactor,\n missFactor:\n positiveNumber(option.missFactor ?? env.OPENCODE_KEEPALIVE_MISS_FACTOR) ??\n DEFAULTS.missFactor,\n maxReplaysPerGap,\n includeChildSessions: boolOption(\n option.includeChildSessions,\n env.OPENCODE_KEEPALIVE_INCLUDE_CHILD_SESSIONS === \"true\" ||\n DEFAULTS.includeChildSessions,\n ),\n replayTimeoutMs:\n positiveNumber(option.replayTimeoutMs ?? env.OPENCODE_KEEPALIVE_REPLAY_TIMEOUT_MS) ??\n DEFAULTS.replayTimeoutMs,\n maxStoredBytes:\n positiveNumber(option.maxStoredBytes ?? env.OPENCODE_KEEPALIVE_MAX_STORED_BYTES) ??\n DEFAULTS.maxStoredBytes,\n debug: boolOption(option.debug, env.OPENCODE_KEEPALIVE_DEBUG === \"true\"),\n }\n}\n\nfunction boolOption(value: unknown, fallback: boolean): boolean {\n return typeof value === \"boolean\" ? value : fallback\n}\n\nfunction positiveNumber(value: unknown): number | undefined {\n const parsed = typeof value === \"string\" ? Number(value) : value\n return typeof parsed === \"number\" && Number.isFinite(parsed) && parsed > 0 ? parsed : undefined\n}\n\nfunction objectOption(value: unknown, fallback: Record<string, number>): Record<string, number> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return fallback\n const entries = Object.entries(value).flatMap(([key, interval]) => {\n const parsed = positiveNumber(interval)\n return parsed === undefined ? [] : [[key.toLowerCase(), parsed] as const]\n })\n return entries.length ? Object.fromEntries(entries) : fallback\n}\n\nfunction listOption(value: unknown, fallback: string[]): string[] {\n if (!Array.isArray(value)) return fallback\n const values = value.filter(\n (item): item is string => typeof item === \"string\" && item.length > 0,\n )\n return values.length ? values : fallback\n}\n","import { randomUUID } from \"node:crypto\"\nimport { readFileSync } from \"node:fs\"\nimport { mkdir, rename, writeFile } from \"node:fs/promises\"\nimport { dirname } from \"node:path\"\nimport { controlFilePath } from \"./paths\"\n\nconst VERSION = 2\nconst MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000\n\nexport type SessionControl = { enabled: boolean; updatedAt: number }\nexport type SessionControlFile = { version: 2; sessions: Record<string, SessionControl> }\n\n/** Read current session overrides; old folder-level controls are intentionally ignored. */\nexport function readSessionControls(directory: string, now = Date.now()): SessionControlFile {\n try {\n const value = JSON.parse(readFileSync(controlFilePath(directory), \"utf8\")) as Record<\n string,\n unknown\n >\n if (value.version !== VERSION || !value.sessions || typeof value.sessions !== \"object\")\n return { version: VERSION, sessions: {} }\n const sessions: Record<string, SessionControl> = {}\n for (const [id, raw] of Object.entries(value.sessions)) {\n if (\n !raw ||\n typeof raw !== \"object\" ||\n typeof (raw as SessionControl).enabled !== \"boolean\" ||\n typeof (raw as SessionControl).updatedAt !== \"number\" ||\n (raw as SessionControl).updatedAt < now - MAX_AGE_MS\n )\n continue\n sessions[id] = {\n enabled: (raw as SessionControl).enabled,\n updatedAt: (raw as SessionControl).updatedAt,\n }\n }\n return { version: VERSION, sessions }\n } catch {\n return { version: VERSION, sessions: {} }\n }\n}\n\nexport function readSessionControl(\n directory: string,\n sessionID: string,\n): SessionControl | undefined {\n return readSessionControls(directory).sessions[sessionID]\n}\n\n/** Atomically update only this session key and prune expired overrides. */\nexport async function setSessionEnabled(\n directory: string,\n sessionID: string,\n enabled: boolean,\n now = Date.now(),\n): Promise<SessionControlFile> {\n const run = updateQueue.then(() => writeSessionControl(directory, sessionID, enabled, now))\n updateQueue = run.then(\n () => undefined,\n () => undefined,\n )\n return run\n}\n\nlet updateQueue: Promise<void> = Promise.resolve()\n\nasync function writeSessionControl(\n directory: string,\n sessionID: string,\n enabled: boolean,\n now: number,\n): Promise<SessionControlFile> {\n const current = readSessionControls(directory, now)\n const cutoff = now - MAX_AGE_MS\n const sessions = Object.fromEntries(\n Object.entries(current.sessions).filter(([, value]) => value.updatedAt >= cutoff),\n )\n sessions[sessionID] = { enabled, updatedAt: now }\n const value: SessionControlFile = { version: VERSION, sessions }\n const path = controlFilePath(directory)\n const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`\n await mkdir(dirname(path), { recursive: true })\n await writeFile(tmp, `${JSON.stringify(value)}\\n`, \"utf8\")\n await rename(tmp, path)\n return value\n}\n","import { createHash } from \"node:crypto\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\n/** Stable short directory key shared by that directory's state-file readers/writers. */\nexport function stateDirectoryKey(directory: string): string {\n return createHash(\"sha256\").update(directory).digest(\"hex\").slice(0, 16)\n}\n\n/** Filename for one process/plugin-instance state snapshot. */\nexport function instanceStateFilePath(directory: string, pid: number, instanceId: string): string {\n return join(\n stateDirectoryPath(),\n `state-v2-${stateDirectoryKey(directory)}-${pid}-${instanceId}.json`,\n )\n}\n\nexport function stateDirectoryPath(): string {\n return join(\n process.env.XDG_DATA_HOME ?? join(homedir(), \".local\", \"share\"),\n \"opencode\",\n \"storage\",\n \"plugin\",\n \"keepalive\",\n )\n}\n\n/**\n * Session runtime controls are stored by directory, with entries keyed by session ID.\n */\nexport function controlFilePath(directory: string): string {\n const key = createHash(\"sha256\").update(directory).digest(\"hex\").slice(0, 16)\n return join(stateDirectoryPath(), `control-${key}.json`)\n}\n\n/** Append-only log file for the server plugin — never stdout, which corrupts the TUI. */\nexport function logFilePath(): string {\n return join(stateDirectoryPath(), \"server.log\")\n}\n","import type { KeepaliveConfig } from \"./config\"\nimport type { Logger } from \"./logger\"\nimport type {\n ReplayResult,\n ResumeResult,\n SessionWarmEntry,\n WarmApi,\n WarmState,\n WarmStateSink,\n} from \"./types\"\nimport { readSessionControls } from \"./control\"\n\ntype Recorded = {\n url: string\n method: string\n headers: Headers\n body: string\n model: string\n api: WarmApi\n sentAt: number\n intervalMs: number\n}\ntype Session = {\n request: Recorded\n entry: SessionWarmEntry\n timer?: ReturnType<typeof setTimeout>\n controller?: AbortController\n runningTools: Set<string>\n active: boolean\n busy: boolean\n realRequests: number\n previousPrompt?: number\n replayReadTokens: number\n pendingResume?: { replays: number; promptPrev: number; replayReadTokens: number }\n retries: number\n child: boolean\n stopLoggedGap?: number\n touchedAt: number\n}\ntype Runtime = {\n originalFetch: typeof fetch\n sessions: Map<string, Session>\n owners: Map<string, ReplayWarmer>\n instancesSet: Set<ReplayWarmer>\n childSessions: Set<string>\n now: () => number\n loggedApis: Set<WarmApi>\n maxStoredBytes: number\n storedBytes: number\n hosts: Set<string>\n pruneTimer?: ReturnType<typeof setInterval>\n instances: Map<\n ReplayWarmer,\n {\n enabled: boolean\n controls: Map<string, boolean>\n effective: Map<string, boolean>\n directory: string\n hosts: Set<string>\n config: KeepaliveConfig\n sink: WarmStateSink\n }\n >\n}\nconst STATE_KEY = Symbol.for(\"opencode-cache-keepalive.replay-warmer\")\nconst RETRY_MS = 10_000\n\n/** Replays allowlisted model requests at cache-refresh deadlines without generating model output. */\nexport class ReplayWarmer {\n private runtime?: Runtime\n private controlTimer?: ReturnType<typeof setInterval>\n private enabled = false\n\n constructor(\n private readonly client: any,\n private readonly config: KeepaliveConfig,\n private readonly sink: WarmStateSink,\n private readonly logger: Logger,\n private readonly directory: string,\n private readonly now: () => number = Date.now,\n ) {}\n\n start(): void {\n if (this.runtime) return\n const runtime = getRuntime(this.now)\n this.enabled = this.config.enabled\n runtime.instances.set(this, {\n enabled: this.enabled,\n effective: new Map(),\n directory: this.directory,\n controls: new Map(\n Object.entries(readSessionControls(this.directory, this.now()).sessions).map(\n ([id, value]) => [id, value.enabled],\n ),\n ),\n hosts: new Set(this.config.hosts.map((host) => host.toLowerCase())),\n config: this.config,\n sink: this.sink,\n })\n runtime.maxStoredBytes = Math.min(runtime.maxStoredBytes, this.config.maxStoredBytes)\n for (const host of this.config.hosts) runtime.hosts.add(host.toLowerCase())\n runtime.instancesSet.add(this)\n this.runtime = runtime\n if (!runtime.pruneTimer) {\n runtime.pruneTimer = setInterval(() => pruneOrphanSessions(runtime), 60_000)\n }\n if (runtime.instancesSet.size === 1) {\n globalThis.fetch = ((input: string | URL | Request, init?: RequestInit) => {\n let bodySource: Promise<string> | undefined\n let rejection: string | undefined\n let shouldRecord = false\n try {\n rejection = cheapEligibilityRejection(runtime, input, init)\n if (rejection) {\n if (isCopilotRequest(input))\n runtime.instancesSet\n .values()\n .next()\n .value?.debugRecordRejected(rejection)\n } else if (!rejection) {\n shouldRecord = true\n if (\n isReplayRequest(input, init) &&\n input instanceof Request &&\n init?.body === undefined\n ) {\n // Clone synchronously: the transport may consume or lock the original Request.\n bodySource = input.clone().text()\n // Keep early-disposal paths safe while preserving rejection for recorder handling.\n void bodySource.catch(() => {})\n }\n }\n } catch {\n // Observation must never change whether or how the real fetch is forwarded.\n }\n const result = runtime.originalFetch(input, init)\n if (shouldRecord) {\n queueMicrotask(() => {\n void recordRequest(runtime, input, init, bodySource).catch(() => {})\n })\n }\n return result\n }) as typeof fetch\n }\n this.controlTimer = setInterval(() => this.syncControl(), 1000)\n }\n\n dispose(): void {\n if (this.controlTimer) clearInterval(this.controlTimer)\n this.controlTimer = undefined\n const runtime = this.runtime\n this.runtime = undefined\n if (!runtime) return\n runtime.instancesSet.delete(this)\n runtime.instances.delete(this)\n for (const [id, owner] of runtime.owners) {\n if (owner !== this) continue\n const session = runtime.sessions.get(id)\n if (session) {\n this.clearTimerFrom(session)\n session.controller?.abort()\n runtime.storedBytes = Math.max(\n 0,\n runtime.storedBytes - bodyBytes(session.request.body),\n )\n session.request.body = \"\"\n }\n runtime.owners.delete(id)\n runtime.sessions.delete(id)\n }\n if (runtime.instancesSet.size === 0) {\n if (runtime.pruneTimer) clearInterval(runtime.pruneTimer)\n globalThis.fetch = runtime.originalFetch\n for (const [id, session] of runtime.sessions) {\n this.clearTimerFrom(session)\n session.controller?.abort()\n runtime.sessions.delete(id)\n }\n delete (globalThis as typeof globalThis & { [STATE_KEY]?: Runtime })[STATE_KEY]\n }\n }\n\n onEvent(event: any): void {\n const runtime = this.runtime\n if (!runtime) return\n const props = event?.properties ?? {}\n const id = props.sessionID ?? props.info?.id ?? props.part?.sessionID\n const info = props.info ?? props.session\n if (event?.type === \"session.created\" && typeof info?.id === \"string\" && info.parentID)\n runtime.childSessions.add(info.id)\n if (event?.type === \"session.deleted\" && typeof id === \"string\")\n runtime.childSessions.delete(id)\n if (typeof id !== \"string\") return\n if (event?.type !== \"session.deleted\" && !this.sessionEnabled(id)) {\n const disabled = runtime.sessions.get(id)\n if (disabled && runtime.owners.get(id) === this) {\n this.disableSession(runtime, id, disabled)\n } else if (\n !disabled &&\n this.config.enabled &&\n this.runtime?.instances.get(this)?.controls.get(id) === false\n ) {\n runtime.owners.set(id, this)\n this.sink.upsert({\n sessionID: id,\n updatedAt: this.now(),\n enabled: false,\n model: \"\",\n api: \"messages\",\n state: \"off\",\n gapId: 0,\n gapReplays: 0,\n cap: this.cap,\n nextReplayAt: null,\n sessionReplays: 0,\n avoidedTokens: 0,\n resumeHits: 0,\n resumeCount: 0,\n })\n }\n return\n }\n if (!runtime.owners.has(id)) runtime.owners.set(id, this)\n if (runtime.owners.get(id) !== this) return\n const session = runtime.sessions.get(id)\n if (!session && event.type === \"session.deleted\") {\n this.sink.remove(id)\n return\n }\n if (!session) return\n session.touchedAt = this.now()\n const type = event.type\n if (type === \"session.deleted\") {\n this.clearTimer(session)\n session.controller?.abort()\n runtime.sessions.delete(id)\n runtime.storedBytes = Math.max(0, runtime.storedBytes - bodyBytes(session.request.body))\n session.request.body = \"\"\n this.sink.remove(id)\n return\n }\n if (type === \"session.status\" || type === \"session.idle\") {\n const status = props.status?.type ?? \"idle\"\n if (status === \"idle\") {\n session.active = false\n session.busy = false\n session.runningTools.clear()\n this.updateState(session, \"idle\")\n this.schedule(id, session, session.request.sentAt + session.request.intervalMs)\n } else if (status === \"busy\" || status === \"running\") {\n session.active = session.runningTools.size === 0\n session.busy = session.runningTools.size > 0\n if (session.active) this.clearTimer(session)\n this.updateState(session, session.active ? \"active\" : \"busy\")\n }\n return\n }\n if (type === \"message.updated\") {\n const info = props.info ?? {}\n const interactionType = String(info.interactionType ?? props.interactionType ?? \"\")\n if (/agent-session-(name|summary)-generation/i.test(interactionType)) return\n if (\n info.role === \"assistant\" &&\n ((typeof info.providerID === \"string\" &&\n !info.providerID\n .toLowerCase()\n .replace(/[^a-z]/g, \"\")\n .includes(\"copilot\")) ||\n (typeof info.modelID === \"string\" &&\n !info.modelID.toLowerCase().includes(session.request.model.toLowerCase()) &&\n !session.request.model.toLowerCase().includes(info.modelID.toLowerCase())))\n ) {\n this.invalidateTarget(runtime, session, this.now())\n return\n }\n if (info.role === \"assistant\" && info.time?.completed === undefined) {\n session.active = true\n this.clearTimer(session)\n this.updateState(session, \"active\")\n }\n return\n }\n if (type !== \"message.part.updated\") return\n const part = props.part\n if (part?.type === \"step-start\") {\n session.active = true\n this.clearTimer(session)\n this.updateState(session, \"active\")\n } else if (part?.type === \"step-finish\") {\n session.active = false\n const cache = part.tokens?.cache ?? {}\n const read = Number(cache.read ?? 0)\n const prompt = Number(part.tokens?.input ?? 0) + read + Number(cache.write ?? 0)\n const completedGap = session.pendingResume\n if (completedGap) {\n const hit = read >= 0.9 * completedGap.promptPrev\n const avoidedTokens = hit ? completedGap.promptPrev : 0\n const resume: ResumeResult = {\n at: this.now(),\n read,\n promptPrev: completedGap.promptPrev,\n hit,\n replays: completedGap.replays,\n avoidedTokens,\n replayReadTokens: completedGap.replayReadTokens,\n }\n session.entry.resume = resume\n session.entry.avoidedTokens += avoidedTokens\n session.entry.resumeCount++\n if (hit) session.entry.resumeHits++\n const totals = this.totals\n totals.avoidedTokens += avoidedTokens\n totals.replayReadTokens += completedGap.replayReadTokens\n if (hit) totals.resumeHits++\n else totals.resumeMisses++\n this.sink.setTotals(totals)\n this.logger.info(\n `resume session=${id} gap=${session.entry.gapId} hit=${hit} read=${read} prev=${completedGap.promptPrev} replays=${completedGap.replays} avoided=${avoidedTokens} replayRead=${completedGap.replayReadTokens}`,\n )\n session.pendingResume = undefined\n }\n session.previousPrompt = prompt\n if (session.busy) this.updateState(session, \"busy\")\n else {\n this.updateState(session, \"idle\")\n this.schedule(id, session, session.request.sentAt + session.request.intervalMs)\n }\n } else if (part?.type === \"tool\" && typeof part.callID === \"string\") {\n if (part.state?.status === \"running\") session.runningTools.add(part.callID)\n else if ([\"completed\", \"error\"].includes(part.state?.status))\n session.runningTools.delete(part.callID)\n session.busy = session.runningTools.size > 0\n if (session.busy) {\n session.active = false\n this.updateState(session, \"busy\")\n this.schedule(id, session, session.request.sentAt + session.request.intervalMs)\n } else if (!session.active) {\n this.updateState(session, \"idle\")\n this.schedule(id, session, session.request.sentAt + session.request.intervalMs)\n }\n }\n }\n\n private totals = {\n replays: 0,\n avoidedTokens: 0,\n replayReadTokens: 0,\n resumeHits: 0,\n resumeMisses: 0,\n }\n\n /** @internal Called asynchronously by the process-wide recorder. */\n debugRecordRejected(reason: string): void {\n this.logger.dbg(`record rejected reason=${reason}`)\n }\n\n /** @internal Called asynchronously by the process-wide recorder. */\n async recordRequest(\n runtime: Runtime,\n input: string | URL | Request,\n init?: RequestInit,\n bodySource?: Promise<string>,\n selectedBody?: string,\n selectedParsed?: any,\n ): Promise<void> {\n if (!this.runtime) return\n const request = input instanceof Request ? input : undefined\n const body = init?.body\n const method = (init?.method ?? request?.method ?? \"GET\").toUpperCase()\n const url =\n input instanceof URL ? input.href : typeof input === \"string\" ? input : input.url\n let u: URL\n try {\n u = new URL(url)\n } catch {\n this.debugRecordRejected(\"invalid-url\")\n return\n }\n if (\n !this.config.hosts.some((host) => {\n const allowed = host.toLowerCase()\n const hostname = u.hostname.toLowerCase()\n return hostname === allowed || hostname.endsWith(`.${allowed}`)\n })\n ) {\n if (isCopilotHost(u.hostname)) this.debugRecordRejected(\"host-not-allowed\")\n return\n }\n const headers = new Headers(request?.headers)\n if (init?.headers) new Headers(init.headers).forEach((v, k) => headers.set(k, v))\n if (\n /agent-session-(name|summary)-generation/i.test(headers.get(\"X-Interaction-Type\") ?? \"\")\n ) {\n if (isCopilotHost(u.hostname)) this.debugRecordRejected(\"utility-interaction\")\n return\n }\n const sessionID =\n headers.get(\"X-Session-Id\") ??\n headers.get(\"x-session-affinity\") ??\n headers.get(\"X-Interaction-Id\")\n if (!sessionID) {\n if (isCopilotHost(u.hostname)) this.debugRecordRejected(\"missing-session-id\")\n return\n }\n const owner = runtime.owners.get(sessionID)\n if (owner && owner !== this) return\n if (\n !runtime.instances.has(this) ||\n !this.enabled ||\n this.sessionEnabled(sessionID) === false\n )\n return\n let stringBody: string | undefined\n try {\n stringBody =\n selectedBody !== undefined\n ? selectedBody\n : typeof body === \"string\"\n ? body\n : body === undefined && request && isReplayRequest(input, init)\n ? await (bodySource ?? request.clone().text())\n : undefined\n } catch {\n stringBody = undefined\n }\n // Body reading may yield after this instance was disabled, disposed, or lost ownership.\n const instance = runtime.instances.get(this)\n if (instance) {\n instance.controls = new Map(\n Object.entries(readSessionControls(this.directory, this.now()).sessions).map(\n ([id, value]) => [id, value.enabled],\n ),\n )\n }\n if (\n !this.runtime ||\n !instance ||\n (runtime.owners.has(sessionID) && runtime.owners.get(sessionID) !== this) ||\n !this.enabled ||\n this.sessionEnabled(sessionID) === false\n )\n return\n const session = runtime.sessions.get(sessionID)\n const sentAt = this.now()\n if (session) session.touchedAt = sentAt\n if (session) {\n if (session.entry.gapReplays > 0 && session.previousPrompt !== undefined) {\n session.pendingResume = {\n promptPrev: session.previousPrompt,\n replays: session.entry.gapReplays,\n replayReadTokens: session.replayReadTokens,\n }\n }\n this.startGap(session, sentAt)\n session.entry.gapId++\n }\n if (method !== \"POST\" || !apiFor(u.pathname)) {\n if (session) this.invalidateTarget(runtime, session, sentAt)\n return\n }\n if (stringBody === undefined) {\n if (isCopilotHost(u.hostname)) this.debugRecordRejected(\"unsupported-body-type\")\n if (session) {\n this.invalidateTarget(runtime, session, sentAt)\n }\n return\n }\n let parsed: any\n try {\n if (stringBody === undefined) return\n parsed = selectedBody !== undefined ? selectedParsed : JSON.parse(stringBody)\n } catch {\n if (isCopilotHost(u.hostname)) this.debugRecordRejected(\"invalid-json-body\")\n if (session) {\n this.invalidateTarget(runtime, session, sentAt)\n }\n return\n }\n this.recordEligible(runtime, sessionID, url, method, headers, stringBody, parsed, u)\n }\n\n private recordEligible(\n runtime: Runtime,\n sessionID: string,\n url: string,\n method: string,\n headers: Headers,\n stringBody: string,\n parsed: any,\n u: URL,\n ): void {\n const child = runtime.childSessions.has(sessionID)\n let session = runtime.sessions.get(sessionID)\n const sentAt = this.now()\n if (session) session.touchedAt = sentAt\n const eligible =\n this.sessionEnabled(sessionID) &&\n typeof parsed?.model === \"string\" &&\n (!child || this.config.includeChildSessions)\n const model = typeof parsed?.model === \"string\" ? parsed.model.toLowerCase() : \"\"\n const match = Object.entries(this.config.intervals).find(([key]) =>\n model.includes(key.toLowerCase()),\n )\n const api = apiFor(u.pathname)\n if (!eligible || !match || !api) {\n if (isCopilotHost(u.hostname))\n this.debugRecordRejected(\n !this.sessionEnabled(sessionID)\n ? \"session-disabled\"\n : child && !this.config.includeChildSessions\n ? \"child-session\"\n : typeof parsed?.model !== \"string\"\n ? \"missing-model\"\n : !match\n ? \"model-not-configured\"\n : \"unsupported-path\",\n )\n if (session) {\n this.invalidateTarget(runtime, session, sentAt)\n }\n return\n }\n runtime.instances.get(this)!.effective.set(sessionID, true)\n if (!runtime.loggedApis.has(api)) {\n runtime.loggedApis.add(api)\n this.logger.info(`record api=${api} host=${u.hostname}${u.pathname}`)\n }\n if (session) {\n session.entry.gapReplays = 0\n session.entry.resume = undefined\n session.entry.lastReplay = undefined\n session.entry.stopReason = undefined\n session.entry.stopStatus = undefined\n session.entry.state = \"active\"\n session.entry.enabled = true\n session.entry.model = parsed.model\n session.entry.api = api\n session.entry.nextReplayAt = null\n session.entry.updatedAt = sentAt\n session.active = true\n session.busy = false\n session.retries = 0\n }\n runtime.owners.set(sessionID, this)\n const oldSession = runtime.sessions.get(sessionID)\n if (oldSession) {\n runtime.storedBytes = Math.max(\n 0,\n runtime.storedBytes - bodyBytes(oldSession.request.body),\n )\n oldSession.request = {\n url,\n method,\n headers,\n body: stringBody,\n model: parsed.model,\n api,\n sentAt,\n intervalMs: match[1],\n }\n runtime.storedBytes += bodyBytes(stringBody)\n oldSession.child = child\n this.publish(oldSession)\n this.enforceStoredBudget(runtime)\n return\n }\n session = {\n request: {\n url,\n method,\n headers,\n body: stringBody,\n model: parsed.model,\n api,\n sentAt,\n intervalMs: match[1],\n },\n entry: {\n sessionID,\n updatedAt: sentAt,\n enabled: true,\n model: parsed.model,\n api,\n state: \"active\",\n gapId: 1,\n gapReplays: 0,\n cap: this.cap,\n nextReplayAt: null,\n sessionReplays: 0,\n avoidedTokens: 0,\n resumeHits: 0,\n resumeCount: 0,\n },\n runningTools: new Set(),\n active: true,\n busy: false,\n realRequests: 1,\n retries: 0,\n child,\n replayReadTokens: 0,\n touchedAt: sentAt,\n }\n runtime.sessions.set(sessionID, session)\n runtime.owners.set(sessionID, this)\n runtime.storedBytes += bodyBytes(stringBody)\n this.publish(session)\n this.enforceStoredBudget(runtime)\n }\n\n private startGap(session: Session, sentAt: number, model?: string): void {\n const runtime = this.runtime!\n session.controller?.abort()\n session.controller = undefined\n this.clearTimer(session)\n runtime.storedBytes = Math.max(0, runtime.storedBytes - bodyBytes(session.request.body))\n session.request.body = \"\"\n session.entry.gapReplays = 0\n session.entry.resume = undefined\n session.entry.lastReplay = undefined\n session.entry.stopReason = undefined\n session.entry.stopStatus = undefined\n session.entry.nextReplayAt = null\n session.replayReadTokens = 0\n session.entry.updatedAt = sentAt\n session.touchedAt = sentAt\n session.entry.model = model ?? session.entry.model\n session.active = false\n session.busy = false\n session.retries = 0\n }\n\n private invalidateTarget(runtime: Runtime, session: Session, at: number): void {\n this.clearTimer(session)\n session.controller?.abort()\n session.controller = undefined\n runtime.storedBytes = Math.max(0, runtime.storedBytes - bodyBytes(session.request.body))\n session.request.body = \"\"\n session.entry.state = this.sessionEnabled(session.entry.sessionID) ? \"idle\" : \"off\"\n session.entry.nextReplayAt = null\n session.entry.updatedAt = at\n this.publish(session)\n }\n\n private enforceStoredBudget(runtime: Runtime): void {\n while (runtime.storedBytes > runtime.maxStoredBytes) {\n const oldestID = [...runtime.sessions.keys()]\n .filter((id) => runtime.sessions.get(id)!.request.body.length > 0)\n .sort(\n (a, b) =>\n runtime.sessions.get(a)!.touchedAt - runtime.sessions.get(b)!.touchedAt,\n )[0]\n if (!oldestID) break\n const session = runtime.sessions.get(oldestID)!\n this.clearTimer(session)\n session.controller?.abort()\n runtime.storedBytes -= bodyBytes(session.request.body)\n session.request.body = \"\"\n runtime.owners.get(oldestID)?.logger.info(`evict session=${oldestID}`)\n session.entry.nextReplayAt = null\n session.entry.state = \"idle\"\n session.entry.updatedAt = this.now()\n runtime.owners.get(oldestID)?.publish(session)\n }\n }\n\n private get cap(): number {\n return this.config.maxReplaysPerGap === \"auto\"\n ? Math.floor(\n (this.config.missFactor - this.config.cacheReadFactor) /\n this.config.cacheReadFactor,\n )\n : this.config.maxReplaysPerGap\n }\n\n private schedule(id: string, session: Session, at: number): void {\n this.clearTimer(session)\n if (\n this.runtime?.owners.get(id) !== this ||\n !session.entry.enabled ||\n session.active ||\n session.entry.state === \"stopped\" ||\n !session.request.body\n )\n return\n session.entry.nextReplayAt = at\n this.publish(session)\n this.logger.dbg(\n `schedule session=${id} state=${session.busy ? \"busy\" : \"idle\"} next=${new Date(at).toISOString()}`,\n )\n session.timer = setTimeout(\n () => void this.replay(id, session),\n Math.max(0, at - this.now()),\n )\n }\n\n private async replay(id: string, session: Session): Promise<void> {\n const runtime = this.runtime\n if (\n !runtime ||\n runtime.owners.get(id) !== this ||\n !session.entry.enabled ||\n runtime.sessions.get(id) !== session ||\n session.active\n )\n return\n const sentAt = this.now()\n session.request.sentAt = sentAt\n session.touchedAt = sentAt\n const controller = new AbortController()\n session.controller = controller\n session.entry.nextReplayAt = null\n this.updateState(session, session.busy ? \"busy\" : \"idle\")\n let timeout: ReturnType<typeof setTimeout> | undefined\n let latencyMs = 0\n let status = 0\n try {\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeout = setTimeout(() => {\n controller.abort()\n reject(new Error(\"timeout\"))\n }, this.config.replayTimeoutMs)\n })\n const response = await Promise.race([\n runtime.originalFetch(session.request.url, {\n method: session.request.method,\n headers: new Headers(session.request.headers),\n body: session.request.body,\n signal: controller.signal,\n }),\n timeoutPromise,\n ])\n status = response.status\n if ([400, 401, 403].includes(status)) {\n session.entry.lastReplay = { at: sentAt, status, latencyMs: this.now() - sentAt }\n this.logger.info(replayLogLine(session, session.entry.lastReplay, \"http-status\"))\n this.stopSession(id, session, \"rejected-4xx\", status)\n return\n }\n if (!response.ok) {\n session.entry.lastReplay = { at: sentAt, status, latencyMs: this.now() - sentAt }\n this.logger.info(replayLogLine(session, session.entry.lastReplay, \"http-status\"))\n this.retryOrLapse(id, session, sentAt, undefined, status)\n return\n }\n const result = await Promise.race([\n readAbortPoint(response, session.request.api, controller),\n timeoutPromise,\n ])\n latencyMs = this.now() - sentAt\n if (session.controller !== controller || runtime.sessions.get(id) !== session) return\n const { abortEvent, ...usage } = result\n const last: ReplayResult = { at: sentAt, status, latencyMs, ...usage }\n session.entry.lastReplay = last\n session.entry.gapReplays++\n session.entry.sessionReplays++\n session.replayReadTokens += result.cacheRead ?? session.previousPrompt ?? 0\n this.totals.replays++\n this.sink.setTotals(this.totals)\n session.retries = 0\n this.logger.info(replayLogLine(session, last, abortEvent))\n if (\n session.request.api === \"messages\" &&\n result.cacheRead === 0 &&\n (result.cacheWrite ?? 0) > 0\n ) {\n session.entry.gapReplays--\n this.stopSession(id, session, \"lapsed\")\n return\n }\n if (session.entry.gapReplays >= session.entry.cap) {\n this.stopSession(id, session, \"cap\")\n return\n }\n this.schedule(id, session, sentAt + session.request.intervalMs)\n } catch (error) {\n if (runtime.sessions.get(id) !== session || session.controller !== controller) return\n const timeoutHit = this.now() - sentAt >= this.config.replayTimeoutMs\n session.entry.lastReplay = { at: sentAt, status: 0, latencyMs: this.now() - sentAt }\n this.logger.info(\n replayLogLine(\n session,\n session.entry.lastReplay,\n timeoutHit ? \"timeout\" : \"network\",\n ),\n )\n this.retryOrLapse(id, session, sentAt, timeoutHit ? \"timeout\" : \"network\")\n } finally {\n if (timeout) clearTimeout(timeout)\n controller.abort()\n if (session.controller === controller) session.controller = undefined\n }\n }\n\n private retryOrLapse(\n id: string,\n session: Session,\n sentAt: number,\n reason?: string,\n status = 0,\n ): void {\n session.entry.lastReplay = { at: sentAt, status, latencyMs: this.now() - sentAt }\n if (session.retries++ === 0) this.schedule(id, session, sentAt + RETRY_MS)\n else this.stopSession(id, session, \"lapsed\")\n if (reason) this.logger.warn(`replay ${reason} session=${id}`)\n }\n\n private stopSession(\n id: string,\n session: Session,\n reason: \"cap\" | \"lapsed\" | \"rejected-4xx\",\n status?: number,\n ): void {\n if (session.stopLoggedGap !== session.entry.gapId) {\n session.stopLoggedGap = session.entry.gapId\n this.logger.info(\n `stop session=${id} reason=${reason}${status === undefined ? \"\" : ` status=${status}`}`,\n )\n }\n this.clearTimer(session)\n session.controller?.abort()\n session.entry.state = \"stopped\"\n session.entry.stopReason = reason\n session.entry.stopStatus = status\n session.entry.nextReplayAt = null\n session.entry.updatedAt = this.now()\n if (this.runtime)\n this.runtime.storedBytes = Math.max(\n 0,\n this.runtime.storedBytes - bodyBytes(session.request.body),\n )\n session.request.body = \"\"\n session.request.headers = new Headers()\n this.publish(session)\n }\n\n private updateState(session: Session, state: WarmState): void {\n const enabled = this.sessionEnabled(session.entry.sessionID)\n session.entry.state = enabled ? state : \"off\"\n session.entry.enabled = enabled\n session.entry.updatedAt = this.now()\n session.entry.cap = this.cap\n this.publish(session)\n }\n\n private publish(session: Session): void {\n this.sink.upsert({ ...session.entry })\n }\n private clearTimer(session: Session): void {\n if (!session) return\n if (session.timer) clearTimeout(session.timer)\n session.timer = undefined\n session.entry.nextReplayAt = null\n }\n\n private clearTimerFrom(session: Session): void {\n if (session.timer) clearTimeout(session.timer)\n session.timer = undefined\n session.entry.nextReplayAt = null\n }\n private sessionEnabled(sessionID: string): boolean {\n const instance = this.runtime?.instances.get(this)\n return this.config.enabled && instance?.controls.get(sessionID) !== false\n }\n private disableSession(runtime: Runtime, id: string, session: Session): void {\n const instance = runtime.instances.get(this)!\n instance.effective.set(id, false)\n this.clearTimer(session)\n session.controller?.abort()\n runtime.storedBytes = Math.max(0, runtime.storedBytes - bodyBytes(session.request.body))\n session.request.body = \"\"\n session.entry.stopReason = \"disabled\"\n session.entry.state = \"off\"\n session.entry.enabled = false\n session.entry.nextReplayAt = null\n session.entry.updatedAt = this.now()\n if (session.stopLoggedGap !== session.entry.gapId) {\n session.stopLoggedGap = session.entry.gapId\n this.logger.info(`stop session=${id} reason=disabled`)\n }\n this.publish(session)\n }\n private syncControl(): void {\n const runtime = this.runtime\n if (!runtime) return\n const instance = runtime.instances.get(this)!\n const controls = readSessionControls(this.directory, this.now()).sessions\n instance.controls = new Map(\n Object.entries(controls).map(([id, value]) => [id, value.enabled]),\n )\n for (const [id, session] of runtime.sessions) {\n if (runtime.owners.get(id) !== this) continue\n const enabled = this.sessionEnabled(id)\n const previous = instance.effective.get(id) ?? session.entry.enabled\n instance.effective.set(id, enabled)\n if (previous === enabled) continue\n if (!enabled) {\n this.disableSession(runtime, id, session)\n } else {\n session.entry.stopReason = undefined\n session.entry.state = session.active ? \"active\" : session.busy ? \"busy\" : \"idle\"\n session.entry.enabled = true\n session.entry.updatedAt = this.now()\n this.publish(session)\n }\n }\n }\n}\n\nfunction getRuntime(now: () => number): Runtime {\n const target = globalThis as typeof globalThis & { [STATE_KEY]?: Runtime }\n if (target[STATE_KEY]) return target[STATE_KEY]\n const runtime: Runtime = {\n originalFetch: globalThis.fetch,\n sessions: new Map(),\n owners: new Map(),\n instancesSet: new Set(),\n childSessions: new Set(),\n loggedApis: new Set(),\n now,\n maxStoredBytes: Number.MAX_SAFE_INTEGER,\n storedBytes: 0,\n hosts: new Set(),\n instances: new Map(),\n }\n target[STATE_KEY] = runtime\n return runtime\n}\n\nasync function recordRequest(\n runtime: Runtime,\n input: string | URL | Request,\n init?: RequestInit,\n bodySource?: Promise<string>,\n): Promise<void> {\n const headers = new Headers(input instanceof Request ? input.headers : init?.headers)\n if (init?.headers && input instanceof Request)\n new Headers(init.headers).forEach((value, key) => headers.set(key, value))\n const id =\n headers.get(\"X-Session-Id\") ??\n headers.get(\"x-session-affinity\") ??\n headers.get(\"X-Interaction-Id\")\n if (!id) return\n const method = (init?.method ?? (input instanceof Request ? input.method : \"GET\")).toUpperCase()\n const rawUrl = input instanceof URL ? input.href : typeof input === \"string\" ? input : input.url\n let url: URL\n try {\n url = new URL(rawUrl)\n } catch {\n return\n }\n const mapped = runtime.owners.get(id)\n let owner: ReplayWarmer | undefined\n if (mapped) {\n const state = runtime.instances.get(mapped)\n if (!state?.enabled || state.controls.get(id) === false) return\n owner = mapped\n } else {\n const hostname = url.hostname.toLowerCase()\n const eligiblePath = method === \"POST\" && !!apiFor(url.pathname)\n const candidates = [...runtime.instances].filter(\n ([, state]) =>\n state.enabled &&\n state.controls.get(id) !== false &&\n (eligiblePath || runtime.sessions.has(id)) &&\n [...state.hosts].some(\n (allowed) => hostname === allowed || hostname.endsWith(`.${allowed}`),\n ),\n )\n if (!candidates.length) return\n\n let stringBody: string | undefined\n try {\n stringBody =\n typeof init?.body === \"string\"\n ? init.body\n : init?.body === undefined && input instanceof Request\n ? await (bodySource ?? input.clone().text())\n : undefined\n } catch {\n stringBody = undefined\n }\n let parsed: any\n try {\n if (stringBody !== undefined) parsed = JSON.parse(stringBody)\n } catch {\n parsed = undefined\n }\n const concurrentOwner = runtime.owners.get(id)\n if (concurrentOwner) {\n const state = runtime.instances.get(concurrentOwner)\n if (!state?.enabled || state.controls.get(id) === false) return\n await concurrentOwner.recordRequest(runtime, input, init, undefined, stringBody, parsed)\n return\n }\n const child = runtime.childSessions.has(id)\n const liveCandidates = candidates.filter(\n ([instance, state]) =>\n runtime.instances.has(instance) &&\n state.enabled &&\n state.controls.get(id) !== false,\n )\n if (!liveCandidates.length) return\n const fullyEligible = liveCandidates.filter(([, state]) => {\n if (method !== \"POST\" || !apiFor(url.pathname)) return false\n if (child && !state.config.includeChildSessions) return false\n const model = typeof parsed?.model === \"string\" ? parsed.model.toLowerCase() : \"\"\n return (\n Object.entries(state.config.intervals).some(([key]) =>\n model.includes(key.toLowerCase()),\n ) &&\n (!child || state.config.includeChildSessions)\n )\n })\n if (!fullyEligible.length) return\n owner = fullyEligible[0][0]\n // Reserve an eligible unknown session so concurrent observations cannot cross-route it.\n runtime.owners.set(id, owner)\n await owner.recordRequest(runtime, input, init, undefined, stringBody, parsed)\n return\n }\n\n if (!runtime.instances.has(owner) || runtime.owners.get(id) !== owner) return\n await owner.recordRequest(runtime, input, init, bodySource)\n}\n\nfunction bodyBytes(body: string): number {\n return new TextEncoder().encode(body).byteLength\n}\n\nfunction pruneOrphanSessions(runtime: Runtime): void {\n const cutoff = runtime.now() - 60 * 60_000\n for (const [id, session] of runtime.sessions) {\n if (session.request.body || session.touchedAt >= cutoff) continue\n session.timer && clearTimeout(session.timer)\n session.controller?.abort()\n const owner = runtime.owners.get(id)\n if (owner) runtime.instances.get(owner)?.sink.remove(id)\n runtime.owners.delete(id)\n runtime.sessions.delete(id)\n }\n}\n\nfunction cheapEligibilityRejection(\n runtime: Runtime,\n input: string | URL | Request,\n init?: RequestInit,\n): string | undefined {\n const request = input instanceof Request ? input : undefined\n const method = (init?.method ?? request?.method ?? \"GET\").toUpperCase()\n const rawUrl = input instanceof URL ? input.href : typeof input === \"string\" ? input : input.url\n let url: URL\n try {\n url = new URL(rawUrl)\n } catch {\n return \"invalid-url\"\n }\n const copilotHost = isCopilotHost(url.hostname)\n const headers = new Headers(request?.headers)\n if (init?.headers) new Headers(init.headers).forEach((value, key) => headers.set(key, value))\n const id =\n headers.get(\"X-Session-Id\") ??\n headers.get(\"x-session-affinity\") ??\n headers.get(\"X-Interaction-Id\")\n if (!id) return \"missing-session-id\"\n if (/agent-session-(name|summary)-generation/i.test(headers.get(\"X-Interaction-Type\") ?? \"\"))\n return \"utility-interaction\"\n const hostname = url.hostname.toLowerCase()\n const owner = runtime.owners.get(id)\n const allowedHost = owner\n ? (() => {\n const state = runtime.instances.get(owner)\n return (\n !!state?.enabled &&\n state.controls.get(id) !== false &&\n [...state.hosts].some(\n (allowed) => hostname === allowed || hostname.endsWith(`.${allowed}`),\n )\n )\n })()\n : [...runtime.instances.values()].some(\n (state) =>\n state.enabled &&\n state.controls.get(id) !== false &&\n [...state.hosts].some(\n (allowed) => hostname === allowed || hostname.endsWith(`.${allowed}`),\n ),\n )\n if (!allowedHost) return \"host-not-allowed\"\n if (runtime.owners.has(id) || (method === \"POST\" && !!apiFor(url.pathname))) return undefined\n if (!copilotHost) return \"ineligible\"\n return method !== \"POST\" ? \"method-not-post\" : \"unsupported-path\"\n}\n\nfunction isReplayRequest(input: string | URL | Request, init?: RequestInit): boolean {\n const method = (init?.method ?? (input instanceof Request ? input.method : \"GET\")).toUpperCase()\n const rawUrl = input instanceof URL ? input.href : typeof input === \"string\" ? input : input.url\n try {\n return method === \"POST\" && !!apiFor(new URL(rawUrl).pathname)\n } catch {\n return false\n }\n}\n\nfunction isCopilotHost(hostname: string): boolean {\n const host = hostname.toLowerCase()\n return (\n host === \"githubcopilot.com\" ||\n host.endsWith(\".githubcopilot.com\") ||\n host.startsWith(\"copilot-api.\")\n )\n}\n\nfunction isCopilotRequest(input: string | URL | Request): boolean {\n const rawUrl = input instanceof URL ? input.href : typeof input === \"string\" ? input : input.url\n try {\n return isCopilotHost(new URL(rawUrl).hostname)\n } catch {\n return false\n }\n}\n\nfunction apiFor(path: string): WarmApi | undefined {\n if (/\\/v1\\/messages\\/?$/.test(path)) return \"messages\"\n if (/^\\/(?:v1\\/)?responses\\/?$/.test(path)) return \"responses\"\n if (/\\/chat\\/completions\\/?$/.test(path)) return \"chat\"\n}\n\nasync function readAbortPoint(\n response: Response,\n api: WarmApi,\n controller: AbortController,\n): Promise<{ cacheRead?: number; cacheWrite?: number; abortEvent: string }> {\n const reader = response.body?.getReader()\n if (!reader) {\n controller.abort()\n return { abortEvent: \"first-chunk\" }\n }\n let buffer = \"\"\n try {\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (!value) continue\n if (api === \"chat\") {\n controller.abort()\n return { abortEvent: \"first-chunk\" }\n }\n buffer += new TextDecoder().decode(value, { stream: true })\n const events = buffer.split(/\\r?\\n\\r?\\n/)\n buffer = events.pop() ?? \"\"\n for (const event of events) {\n const data = event\n .split(/\\r?\\n/)\n .filter((line) => line.startsWith(\"data:\"))\n .map((line) => line.slice(5).trim())\n .join(\"\\n\")\n if (!data || data === \"[DONE]\") continue\n let parsed: any\n try {\n parsed = JSON.parse(data)\n } catch {\n continue\n }\n if (\n api === \"responses\" &&\n [\"response.created\", \"response.in_progress\"].includes(parsed.type)\n )\n continue\n if (api === \"messages\") {\n if (parsed.type !== \"message_start\") continue\n const usage = parsed.message?.usage ?? {}\n controller.abort()\n return {\n abortEvent: \"message_start\",\n cacheRead: numeric(usage.cache_read_input_tokens),\n cacheWrite: numeric(usage.cache_creation_input_tokens),\n }\n }\n controller.abort()\n return { abortEvent: String(parsed.type ?? \"first-event\") }\n }\n }\n } finally {\n controller.abort()\n await reader.cancel().catch(() => {})\n }\n return { abortEvent: \"stream-end\" }\n}\n\nfunction replayLogLine(session: Session, result: ReplayResult, abortEvent?: string): string {\n const usage =\n result.cacheRead === undefined && result.cacheWrite === undefined\n ? \"\"\n : ` cacheRead=${result.cacheRead ?? 0} cacheWrite=${result.cacheWrite ?? 0}`\n return `replay session=${session.entry.sessionID} model=${session.entry.model} api=${session.entry.api} gap=${session.entry.gapId} n=${session.entry.gapReplays}/${session.entry.cap} status=${result.status} latency=${result.latencyMs}ms abort=${abortEvent ?? \"first-chunk\"}${usage}`\n}\n\nfunction numeric(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined\n}\n","import { appendFileSync, mkdirSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\n\nconst PREFIX = \"[cache-keepalive]\"\n\nexport class Logger {\n private readonly logPath: string\n private readonly debug: boolean\n private initialized = false\n\n constructor(debug: boolean, logPath: string) {\n this.debug = debug\n this.logPath = logPath\n }\n\n info(message: string, data?: unknown) {\n this.write(\"INFO\", message, data)\n }\n\n warn(message: string, data?: unknown) {\n this.write(\"WARN\", message, data)\n }\n\n error(message: string, data?: unknown) {\n this.write(\"ERROR\", message, data)\n }\n\n dbg(message: string, data?: unknown) {\n if (this.debug) this.write(\"DEBUG\", message, data)\n }\n\n private write(level: string, message: string, data?: unknown): void {\n const ts = new Date().toISOString()\n const extra =\n data !== undefined ? ` ${typeof data === \"string\" ? data : JSON.stringify(data)}` : \"\"\n const line = `${ts} ${level} ${PREFIX} ${message}${extra}\\n`\n try {\n if (!this.initialized) {\n mkdirSync(dirname(this.logPath), { recursive: true })\n this.initialized = true\n }\n appendFileSync(this.logPath, line, \"utf8\")\n } catch {\n // Best-effort logging — never crash the plugin.\n }\n }\n}\n","import { randomUUID } from \"node:crypto\"\nimport { readdirSync, readFileSync, unlinkSync } from \"node:fs\"\nimport { mkdir, rename, unlink, writeFile } from \"node:fs/promises\"\nimport { dirname, join } from \"node:path\"\nimport type { Logger } from \"./logger\"\nimport { instanceStateFilePath, stateDirectoryKey, stateDirectoryPath } from \"./paths\"\nimport type { ProcessTotals, SessionWarmEntry, WarmStateSink } from \"./types\"\n\nconst CLEANUP_INTERVAL_MS = 10 * 60_000\nconst PERSIST_DEBOUNCE_MS = 250\n\ntype StateFile = {\n version: 2\n pid: number\n instanceId: string\n updatedAt: number\n sessions: Record<string, SessionWarmEntry>\n totals: ProcessTotals\n}\n\nexport type StateStoreOptions = { logger?: Logger; now?: () => number }\n\n/**\n * Create an isolated v2 snapshot writer. Mutations are debounced, dispose flushes\n * the latest snapshot before removing it, and all filesystem failures stay\n * best-effort so state reporting cannot disrupt replay execution.\n */\nexport function createStateStore(\n directory: string,\n options: StateStoreOptions = {},\n): WarmStateSink {\n const now = options.now ?? Date.now\n const instanceId = randomUUID()\n const filePath = instanceStateFilePath(directory, process.pid, instanceId)\n const sessions = new Map<string, SessionWarmEntry>()\n let totals: ProcessTotals = {\n replays: 0,\n avoidedTokens: 0,\n replayReadTokens: 0,\n resumeHits: 0,\n resumeMisses: 0,\n }\n let timer: ReturnType<typeof setTimeout> | undefined\n let cleanupTimer: ReturnType<typeof setInterval> | undefined\n let writeQueue: Promise<void> = Promise.resolve()\n let disposed = false\n\n const flush = (): Promise<void> => {\n if (timer) clearTimeout(timer)\n timer = undefined\n const snapshot: StateFile = {\n version: 2,\n pid: process.pid,\n instanceId,\n updatedAt: now(),\n sessions: Object.fromEntries(sessions),\n totals,\n }\n const write = () => atomicWrite(filePath, JSON.stringify(snapshot))\n writeQueue = writeQueue.then(write, write)\n return writeQueue\n }\n\n const schedule = () => {\n if (disposed || timer) return\n timer = setTimeout(() => {\n timer = undefined\n void flush()\n }, PERSIST_DEBOUNCE_MS)\n }\n\n const onExit = () => {\n try {\n unlinkSync(filePath)\n } catch {\n // Exit cleanup is best-effort.\n }\n }\n\n cleanupStateFiles(directory, options.logger)\n cleanupTimer = setInterval(\n () => cleanupStateFiles(directory, options.logger),\n CLEANUP_INTERVAL_MS,\n )\n process.on(\"exit\", onExit)\n\n return {\n upsert(entry) {\n if (disposed) return\n sessions.set(entry.sessionID, { ...entry })\n schedule()\n },\n remove(sessionID) {\n if (disposed) return\n sessions.delete(sessionID)\n schedule()\n },\n setTotals(value) {\n if (disposed) return\n totals = { ...value }\n schedule()\n },\n async dispose() {\n if (disposed) return\n disposed = true\n if (timer) clearTimeout(timer)\n timer = undefined\n if (cleanupTimer) clearInterval(cleanupTimer)\n cleanupTimer = undefined\n process.off(\"exit\", onExit)\n await flush()\n await unlink(filePath).catch(() => {})\n },\n }\n}\n\n/** Return the newest self-contained entry for a session across matching v2 files. */\nexport function readSessionEntry(\n directory: string,\n sessionID: string,\n): SessionWarmEntry | undefined {\n let freshest: SessionWarmEntry | undefined\n for (const snapshot of readSnapshots(directory)) {\n const entry = snapshot.sessions[sessionID]\n if (entry && (!freshest || entry.updatedAt > freshest.updatedAt)) freshest = entry\n }\n return freshest\n}\n\n/** Sum process-level totals from live snapshots for this directory. */\nexport function readProcessTotals(directory: string): ProcessTotals {\n const totals = {\n replays: 0,\n avoidedTokens: 0,\n replayReadTokens: 0,\n resumeHits: 0,\n resumeMisses: 0,\n }\n for (const snapshot of readSnapshots(directory)) {\n totals.replays += snapshot.totals.replays\n totals.avoidedTokens += snapshot.totals.avoidedTokens\n totals.replayReadTokens += snapshot.totals.replayReadTokens\n totals.resumeHits += snapshot.totals.resumeHits\n totals.resumeMisses += snapshot.totals.resumeMisses\n }\n return totals\n}\n\n/** Remove only v2 snapshots whose owner PID is confirmed dead. */\nexport function cleanupStateFiles(directory: string, logger?: Logger): void {\n try {\n const prefix = `state-v2-${stateDirectoryKey(directory)}-`\n for (const name of readdirSync(stateDirectoryPath())) {\n const match = name.match(new RegExp(`^${prefix}(\\\\d+)-[^/]+\\\\.json$`))\n if (!match) continue\n const pid = Number(match[1])\n try {\n process.kill(pid, 0)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ESRCH\") {\n unlinkSync(join(stateDirectoryPath(), name))\n logger?.dbg(\"Removed stale state snapshot\", { pid })\n }\n }\n }\n } catch {\n // Storage can be unavailable during startup/shutdown; cleanup is optional.\n }\n}\n\nfunction readSnapshots(directory: string): StateFile[] {\n const prefix = `state-v2-${stateDirectoryKey(directory)}-`\n try {\n return readdirSync(stateDirectoryPath())\n .filter((name) => name.startsWith(prefix) && name.endsWith(\".json\"))\n .map((name) => {\n try {\n const value = JSON.parse(\n readFileSync(join(stateDirectoryPath(), name), \"utf8\"),\n ) as StateFile\n return value.version === 2 && value.sessions && value.totals ? value : undefined\n } catch {\n return undefined\n }\n })\n .filter((value): value is StateFile => value !== undefined)\n } catch {\n return []\n }\n}\n\nasync function atomicWrite(path: string, contents: string): Promise<void> {\n try {\n const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`\n await mkdir(dirname(path), { recursive: true })\n await writeFile(tmp, `${contents}\\n`, \"utf8\")\n await rename(tmp, path)\n } catch {\n // Best-effort persistence; stale display data is safer than disrupting replay.\n }\n}\n","import type { Plugin } from \"@opencode-ai/plugin\"\nimport { configureClientAuth, isSecureMode } from \"./lib/auth\"\nimport { getConfig } from \"./lib/config\"\nimport { ReplayWarmer } from \"./lib/replay-warmer\"\nimport { Logger } from \"./lib/logger\"\nimport { logFilePath } from \"./lib/paths\"\nimport { createStateStore } from \"./lib/state\"\n\ndeclare const __KEEPALIVE_VERSION__: string\n\nconst server: Plugin = async (ctx, options) => {\n const logger = new Logger(false, logFilePath())\n const config = getConfig(options as Record<string, unknown> | undefined, (message) =>\n logger.warn(message),\n )\n const activeLogger = new Logger(config.debug, logFilePath())\n if (isSecureMode()) configureClientAuth(ctx.client)\n const store = createStateStore(ctx.directory, { logger: activeLogger })\n const engine = new ReplayWarmer(ctx.client, config, store, activeLogger, ctx.directory)\n engine.start()\n\n const version = typeof __KEEPALIVE_VERSION__ !== \"undefined\" ? __KEEPALIVE_VERSION__ : \"dev\"\n logger.info(`v${version} ready`)\n\n return {\n event: async ({ event }) => {\n engine.onEvent(event)\n },\n\n dispose: async () => {\n engine.dispose()\n await store.dispose()\n },\n }\n}\n\nexport default server\n"],"mappings":"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","names":["info","dirname","randomUUID","readFileSync","mkdir","rename","writeFile","dirname","join","randomUUID","join","randomUUID","mkdir","dirname","writeFile","rename"]}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,31 +1,14 @@
|
|
|
1
1
|
export type KeepaliveConfig = {
|
|
2
|
-
/** Master switch. When false the plugin registers no timers or hooks. */
|
|
3
2
|
enabled: boolean;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* write/read break-even; providers with automatic caching may be cheaper.
|
|
10
|
-
*/
|
|
11
|
-
windowMs: number;
|
|
12
|
-
/**
|
|
13
|
-
* Append a stable keepalive instruction to the system prompt so the model
|
|
14
|
-
* answers a `~` ping with a single `~` token and never calls tools. The
|
|
15
|
-
* instruction is added to real turns too, so the cached prefix stays identical.
|
|
16
|
-
*/
|
|
17
|
-
injectSystemInstruction: boolean;
|
|
18
|
-
/** The single character/token used as the keepalive suffix. */
|
|
19
|
-
pingToken: string;
|
|
20
|
-
/** Remove synthetic ping turns after measuring their cache usage. */
|
|
21
|
-
revertPing: boolean;
|
|
22
|
-
/** Warm subagent/child sessions too. Off by default to avoid wasted pings. */
|
|
3
|
+
intervals: Record<string, number>;
|
|
4
|
+
hosts: string[];
|
|
5
|
+
cacheReadFactor: number;
|
|
6
|
+
missFactor: number;
|
|
7
|
+
maxReplaysPerGap: "auto" | number;
|
|
23
8
|
includeChildSessions: boolean;
|
|
9
|
+
replayTimeoutMs: number;
|
|
10
|
+
maxStoredBytes: number;
|
|
24
11
|
debug: boolean;
|
|
25
|
-
/** providerID substrings eligible for warming (cache-controlled providers). */
|
|
26
|
-
providerAllowlist: string[];
|
|
27
|
-
/** modelID substrings eligible for warming. */
|
|
28
|
-
modelAllowlist: string[];
|
|
29
12
|
};
|
|
30
|
-
export declare function getConfig(options: Record<string, unknown> | undefined): KeepaliveConfig;
|
|
13
|
+
export declare function getConfig(options: Record<string, unknown> | undefined, warn?: (message: string) => void): KeepaliveConfig;
|
|
31
14
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../lib/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC1B,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../lib/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACjC,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAAA;IACjC,oBAAoB,EAAE,OAAO,CAAA;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;CACjB,CAAA;AA+BD,wBAAgB,SAAS,CACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,IAAI,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,GAC3C,eAAe,CA6DjB"}
|
package/dist/lib/control.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
export type
|
|
2
|
-
version: 1;
|
|
1
|
+
export type SessionControl = {
|
|
3
2
|
enabled: boolean;
|
|
4
3
|
updatedAt: number;
|
|
5
4
|
};
|
|
6
|
-
export
|
|
7
|
-
|
|
5
|
+
export type SessionControlFile = {
|
|
6
|
+
version: 2;
|
|
7
|
+
sessions: Record<string, SessionControl>;
|
|
8
|
+
};
|
|
9
|
+
/** Read current session overrides; old folder-level controls are intentionally ignored. */
|
|
10
|
+
export declare function readSessionControls(directory: string, now?: number): SessionControlFile;
|
|
11
|
+
export declare function readSessionControl(directory: string, sessionID: string): SessionControl | undefined;
|
|
12
|
+
/** Atomically update only this session key and prune expired overrides. */
|
|
13
|
+
export declare function setSessionEnabled(directory: string, sessionID: string, enabled: boolean, now?: number): Promise<SessionControlFile>;
|
|
8
14
|
//# sourceMappingURL=control.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../lib/control.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../lib/control.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AACpE,MAAM,MAAM,kBAAkB,GAAG;IAAE,OAAO,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;CAAE,CAAA;AAEzF,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,kBAAkB,CA2B3F;AAED,wBAAgB,kBAAkB,CAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAClB,cAAc,GAAG,SAAS,CAE5B;AAED,2EAA2E;AAC3E,wBAAsB,iBAAiB,CACnC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,GAAG,SAAa,GACjB,OAAO,CAAC,kBAAkB,CAAC,CAO7B"}
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* Mirrors the storage layout used by other opencode plugins:
|
|
7
|
-
* <XDG_DATA_HOME | ~/.local/share>/opencode/storage/plugin/keepalive/state.json
|
|
8
|
-
*/
|
|
9
|
-
export declare function stateFilePath(directory: string): string;
|
|
1
|
+
/** Stable short directory key shared by that directory's state-file readers/writers. */
|
|
2
|
+
export declare function stateDirectoryKey(directory: string): string;
|
|
3
|
+
/** Filename for one process/plugin-instance state snapshot. */
|
|
4
|
+
export declare function instanceStateFilePath(directory: string, pid: number, instanceId: string): string;
|
|
10
5
|
export declare function stateDirectoryPath(): string;
|
|
11
6
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Scoped per directory (like the state file) so toggling keepalive in one
|
|
14
|
-
* project does not affect every other open opencode session.
|
|
7
|
+
* Session runtime controls are stored by directory, with entries keyed by session ID.
|
|
15
8
|
*/
|
|
16
9
|
export declare function controlFilePath(directory: string): string;
|
|
17
10
|
/** Append-only log file for the server plugin — never stdout, which corrupts the TUI. */
|
package/dist/lib/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../lib/paths.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../lib/paths.ts"],"names":[],"mappings":"AAIA,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,+DAA+D;AAC/D,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAKhG;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,yFAAyF;AACzF,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { KeepaliveConfig } from "./config";
|
|
2
|
+
import type { Logger } from "./logger";
|
|
3
|
+
import type { SessionWarmEntry, WarmApi, WarmStateSink } from "./types";
|
|
4
|
+
type Recorded = {
|
|
5
|
+
url: string;
|
|
6
|
+
method: string;
|
|
7
|
+
headers: Headers;
|
|
8
|
+
body: string;
|
|
9
|
+
model: string;
|
|
10
|
+
api: WarmApi;
|
|
11
|
+
sentAt: number;
|
|
12
|
+
intervalMs: number;
|
|
13
|
+
};
|
|
14
|
+
type Session = {
|
|
15
|
+
request: Recorded;
|
|
16
|
+
entry: SessionWarmEntry;
|
|
17
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
18
|
+
controller?: AbortController;
|
|
19
|
+
runningTools: Set<string>;
|
|
20
|
+
active: boolean;
|
|
21
|
+
busy: boolean;
|
|
22
|
+
realRequests: number;
|
|
23
|
+
previousPrompt?: number;
|
|
24
|
+
replayReadTokens: number;
|
|
25
|
+
pendingResume?: {
|
|
26
|
+
replays: number;
|
|
27
|
+
promptPrev: number;
|
|
28
|
+
replayReadTokens: number;
|
|
29
|
+
};
|
|
30
|
+
retries: number;
|
|
31
|
+
child: boolean;
|
|
32
|
+
stopLoggedGap?: number;
|
|
33
|
+
touchedAt: number;
|
|
34
|
+
};
|
|
35
|
+
type Runtime = {
|
|
36
|
+
originalFetch: typeof fetch;
|
|
37
|
+
sessions: Map<string, Session>;
|
|
38
|
+
owners: Map<string, ReplayWarmer>;
|
|
39
|
+
instancesSet: Set<ReplayWarmer>;
|
|
40
|
+
childSessions: Set<string>;
|
|
41
|
+
now: () => number;
|
|
42
|
+
loggedApis: Set<WarmApi>;
|
|
43
|
+
maxStoredBytes: number;
|
|
44
|
+
storedBytes: number;
|
|
45
|
+
hosts: Set<string>;
|
|
46
|
+
pruneTimer?: ReturnType<typeof setInterval>;
|
|
47
|
+
instances: Map<ReplayWarmer, {
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
controls: Map<string, boolean>;
|
|
50
|
+
effective: Map<string, boolean>;
|
|
51
|
+
directory: string;
|
|
52
|
+
hosts: Set<string>;
|
|
53
|
+
config: KeepaliveConfig;
|
|
54
|
+
sink: WarmStateSink;
|
|
55
|
+
}>;
|
|
56
|
+
};
|
|
57
|
+
/** Replays allowlisted model requests at cache-refresh deadlines without generating model output. */
|
|
58
|
+
export declare class ReplayWarmer {
|
|
59
|
+
private readonly client;
|
|
60
|
+
private readonly config;
|
|
61
|
+
private readonly sink;
|
|
62
|
+
private readonly logger;
|
|
63
|
+
private readonly directory;
|
|
64
|
+
private readonly now;
|
|
65
|
+
private runtime?;
|
|
66
|
+
private controlTimer?;
|
|
67
|
+
private enabled;
|
|
68
|
+
constructor(client: any, config: KeepaliveConfig, sink: WarmStateSink, logger: Logger, directory: string, now?: () => number);
|
|
69
|
+
start(): void;
|
|
70
|
+
dispose(): void;
|
|
71
|
+
onEvent(event: any): void;
|
|
72
|
+
private totals;
|
|
73
|
+
/** @internal Called asynchronously by the process-wide recorder. */
|
|
74
|
+
debugRecordRejected(reason: string): void;
|
|
75
|
+
/** @internal Called asynchronously by the process-wide recorder. */
|
|
76
|
+
recordRequest(runtime: Runtime, input: string | URL | Request, init?: RequestInit, bodySource?: Promise<string>, selectedBody?: string, selectedParsed?: any): Promise<void>;
|
|
77
|
+
private recordEligible;
|
|
78
|
+
private startGap;
|
|
79
|
+
private invalidateTarget;
|
|
80
|
+
private enforceStoredBudget;
|
|
81
|
+
private get cap();
|
|
82
|
+
private schedule;
|
|
83
|
+
private replay;
|
|
84
|
+
private retryOrLapse;
|
|
85
|
+
private stopSession;
|
|
86
|
+
private updateState;
|
|
87
|
+
private publish;
|
|
88
|
+
private clearTimer;
|
|
89
|
+
private clearTimerFrom;
|
|
90
|
+
private sessionEnabled;
|
|
91
|
+
private disableSession;
|
|
92
|
+
private syncControl;
|
|
93
|
+
}
|
|
94
|
+
export {};
|
|
95
|
+
//# sourceMappingURL=replay-warmer.d.ts.map
|