pi-cache-optimizer 2.8.16 → 2.8.18

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/src/config.ts ADDED
@@ -0,0 +1,374 @@
1
+ import { type AtomicTargetGuard, atomicCreateTextFileNoReplace, atomicReplaceTextFilePreservingMode, hashText, withModelsJsonTransactionLock } from "./atomic-fs.ts";
2
+ import { LOG_PREFIX, type MutableEnv, asRecord, getErrorCode, isNonEmptyString } from "./common.ts";
3
+ import { STATE_DIR } from "./paths.ts";
4
+ import { PI_CACHE_RETENTION_ENV, STARTUP_CACHE_RETENTION_ENV, requestLongCacheRetention, restoreCacheRetentionEnv } from "./retention.ts";
5
+ import { type Stats, readFileSync } from "node:fs";
6
+ import { lstat, mkdir, readFile } from "node:fs/promises";
7
+ import { dirname, join } from "node:path";
8
+
9
+
10
+ export const CONFIG_FILE_PATH = join(STATE_DIR, "pi-cache-optimizer-config.json");
11
+
12
+ export const OPENAI_CACHE_KEY_ENV = "PI_CACHE_OPTIMIZER_OPENAI_CACHE_KEY";
13
+
14
+ export const NO_OPENAI_CACHE_KEY_ENV = "PI_CACHE_OPTIMIZER_NO_OPENAI_CACHE_KEY";
15
+
16
+ export const TOOL_ORDER_ENV = "PI_CACHE_OPTIMIZER_TOOL_ORDER";
17
+
18
+ export const FOOTER_MODE_ENV = "PI_CACHE_OPTIMIZER_FOOTER_MODE";
19
+
20
+ export type FooterStatsMode = "session" | "total" | "process";
21
+
22
+ export type FooterStatsModeSource = "config" | "env" | "default";
23
+
24
+ export type PersistedCacheOptimizerConfigV1 = {
25
+ version: 1;
26
+ footerMode?: FooterStatsMode;
27
+ };
28
+
29
+ export type PersistedCacheOptimizerFeature = "promptRewrite" | "virtualRewrite" | "skillCompression" | "openAICacheKey" | "toolOrder";
30
+
31
+ export type PersistedCacheOptimizerConfigV2 = {
32
+ version: 2;
33
+ footerMode?: FooterStatsMode;
34
+ promptCacheKey?: {
35
+ omit?: string[];
36
+ };
37
+ };
38
+
39
+ export type PersistedCacheOptimizerConfigV3 = {
40
+ version: 2 | 3;
41
+ footerMode?: FooterStatsMode;
42
+ promptCacheKey?: { omit?: string[] };
43
+ features?: Partial<Record<PersistedCacheOptimizerFeature, boolean>>;
44
+ };
45
+
46
+ export type PersistedCacheOptimizerConfig = PersistedCacheOptimizerConfigV1 | PersistedCacheOptimizerConfigV2 | PersistedCacheOptimizerConfigV3;
47
+
48
+ export let runtimeOptimizerEnabled = true;
49
+
50
+ export function isEnabledEnv(value: string | undefined): boolean {
51
+ if (!value) return false;
52
+ const normalized = value.trim().toLowerCase();
53
+ return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
54
+ }
55
+
56
+ export function featureEnabled(
57
+ feature: PersistedCacheOptimizerFeature,
58
+ envName: string,
59
+ defaultValue: boolean,
60
+ env: MutableEnv = process.env,
61
+ config: PersistedCacheOptimizerConfigV3 = persistedCacheOptimizerConfig,
62
+ ): boolean {
63
+ const configured = config.features?.[feature];
64
+ if (configured !== undefined) return configured;
65
+ if (feature === "virtualRewrite") return isEnabledEnv(env[envName]);
66
+ if (feature === "openAICacheKey") {
67
+ return !isEnabledEnv(env[envName]) && !isDisabledEnv(env[OPENAI_CACHE_KEY_ENV]);
68
+ }
69
+ if (feature === "promptRewrite" || feature === "skillCompression") {
70
+ return !isEnabledEnv(env[envName]);
71
+ }
72
+ return isEnabledEnv(env[envName]) || defaultValue;
73
+ }
74
+
75
+ export function isToolOrderEnabled(env: MutableEnv = process.env): boolean {
76
+ return runtimeOptimizerEnabled && featureEnabled("toolOrder", TOOL_ORDER_ENV, false);
77
+ }
78
+
79
+ export function parseFooterStatsMode(value: unknown): FooterStatsMode | undefined {
80
+ if (typeof value !== "string") return undefined;
81
+ const normalized = value.trim().toLowerCase();
82
+ return normalized === "session" || normalized === "total" || normalized === "process" ? normalized : undefined;
83
+ }
84
+
85
+ export function parsePersistedCacheOptimizerConfig(value: unknown): PersistedCacheOptimizerConfig | undefined {
86
+ const record = asRecord(value);
87
+ if (!record || (record.version !== 1 && record.version !== 2 && record.version !== 3)) return undefined;
88
+ const allowedTopLevel = new Set(record.version === 1 ? ["version", "footerMode"] : record.version === 2 ? ["version", "footerMode", "promptCacheKey"] : ["version", "footerMode", "promptCacheKey", "features"]);
89
+ if (Object.keys(record).some((key) => !allowedTopLevel.has(key))) return undefined;
90
+ const footerMode = parseFooterStatsMode(record.footerMode);
91
+ if (record.footerMode !== undefined && !footerMode) return undefined;
92
+ if (record.version === 1) return { version: 1, ...(footerMode ? { footerMode } : {}) };
93
+
94
+ const rawFeatures = record.version === 3 ? record.features : undefined;
95
+ if (rawFeatures !== undefined && !asRecord(rawFeatures)) return undefined;
96
+ const featuresRecord = asRecord(rawFeatures);
97
+ const featureNames = new Set<PersistedCacheOptimizerFeature>(["promptRewrite", "virtualRewrite", "skillCompression", "openAICacheKey", "toolOrder"]);
98
+ if (featuresRecord && Object.keys(featuresRecord).some((key) => !featureNames.has(key as PersistedCacheOptimizerFeature) || typeof featuresRecord[key] !== "boolean")) return undefined;
99
+
100
+ const rawPromptCacheKey = record.promptCacheKey;
101
+ if (rawPromptCacheKey !== undefined && !asRecord(rawPromptCacheKey)) return undefined;
102
+ const promptCacheKey = asRecord(rawPromptCacheKey);
103
+ if (promptCacheKey && Object.keys(promptCacheKey).some((key) => key !== "omit")) return undefined;
104
+ const omit = promptCacheKey?.omit;
105
+ if (omit !== undefined && (!Array.isArray(omit) || omit.some((value): value is string => !isNonEmptyString(value)))) return undefined;
106
+ const stringOmit = omit as string[] | undefined;
107
+ const uniqueOmit = stringOmit ? [...new Set(stringOmit.map((value) => value.trim()))].sort() : undefined;
108
+ return {
109
+ version: record.version === 3 ? 3 : 2,
110
+ ...(footerMode ? { footerMode } : {}),
111
+ ...(uniqueOmit && uniqueOmit.length > 0 ? { promptCacheKey: { omit: uniqueOmit } } : {}),
112
+ ...(featuresRecord ? { features: Object.fromEntries(Object.entries(featuresRecord)) as Partial<Record<PersistedCacheOptimizerFeature, boolean>> } : {}),
113
+ } as PersistedCacheOptimizerConfigV3;
114
+ }
115
+
116
+ export function normalizePersistedCacheOptimizerConfig(value: PersistedCacheOptimizerConfig | undefined): PersistedCacheOptimizerConfigV3 {
117
+ if (!value) return { version: 2 };
118
+ const features = value.version === 3 ? value.features : undefined;
119
+ return {
120
+ version: features ? 3 : 2,
121
+ ...(value.footerMode ? { footerMode: value.footerMode } : {}),
122
+ ...(value.version !== 1 && value.promptCacheKey ? { promptCacheKey: value.promptCacheKey } : {}),
123
+ ...(features ? { features } : {}),
124
+ };
125
+ }
126
+
127
+ export function readPersistedCacheOptimizerConfig(configPath: string = CONFIG_FILE_PATH): PersistedCacheOptimizerConfigV3 {
128
+ try {
129
+ return normalizePersistedCacheOptimizerConfig(parsePersistedCacheOptimizerConfig(JSON.parse(readFileSync(configPath, "utf8"))));
130
+ } catch (error) {
131
+ if (getErrorCode(error) !== "ENOENT") console.warn(`${LOG_PREFIX}: failed to read optimizer config; using defaults`, error);
132
+ return { version: 2 };
133
+ }
134
+ }
135
+
136
+ export async function writePersistedCacheOptimizerConfigUnlocked(
137
+ config: PersistedCacheOptimizerConfig | PersistedCacheOptimizerConfigV3,
138
+ configPath: string,
139
+ expectedTarget?: AtomicTargetGuard | null,
140
+ ): Promise<void> {
141
+ await mkdir(dirname(configPath), { recursive: true });
142
+ const payloadText = JSON.stringify(normalizePersistedCacheOptimizerConfig(config), null, 2) + "\n";
143
+ let targetInfo: Awaited<ReturnType<typeof lstat>> | undefined;
144
+ let targetMode = 0o600;
145
+ let targetHash: string | undefined;
146
+ try {
147
+ targetInfo = await lstat(configPath);
148
+ if (targetInfo.isSymbolicLink() || !targetInfo.isFile()) throw new Error("optimizer config is not a regular file; no changes were made");
149
+ const targetText = await readFile(configPath, "utf8");
150
+ targetMode = targetInfo.mode & 0o7777;
151
+ targetHash = hashText(targetText);
152
+ } catch (error) {
153
+ if (getErrorCode(error) !== "ENOENT") throw error;
154
+ }
155
+ // Feature mutations bind the original read, not a newer snapshot taken by
156
+ // this writer. The lease serializes extension writers; this guard also
157
+ // rejects a manual edit/replacement between the read and atomic commit.
158
+ if ((expectedTarget === null && targetInfo) || (expectedTarget && !targetInfo)) {
159
+ throw new Error("optimizer config changed during the update; refusing to overwrite user changes");
160
+ }
161
+ if (targetInfo) {
162
+ await atomicReplaceTextFilePreservingMode(configPath, payloadText, targetMode, "config", expectedTarget ?? {
163
+ identity: targetInfo,
164
+ hash: targetHash,
165
+ mode: targetMode,
166
+ });
167
+ return;
168
+ }
169
+
170
+ await atomicCreateTextFileNoReplace(configPath, payloadText, targetMode, "config");
171
+ }
172
+
173
+ export async function writePersistedCacheOptimizerConfig(
174
+ config: PersistedCacheOptimizerConfig | PersistedCacheOptimizerConfigV3,
175
+ configPath: string = CONFIG_FILE_PATH,
176
+ ): Promise<void> {
177
+ await withModelsJsonTransactionLock(() => writePersistedCacheOptimizerConfigUnlocked(config, configPath));
178
+ }
179
+
180
+ export async function writePersistedFooterModeUnlocked(
181
+ mode: FooterStatsMode,
182
+ configPath: string,
183
+ ): Promise<void> {
184
+ let version: 1 | 2 | 3 = 1;
185
+ let raw: PersistedCacheOptimizerConfig | undefined;
186
+ let targetExists = false;
187
+ try {
188
+ raw = parsePersistedCacheOptimizerConfig(JSON.parse(readFileSync(configPath, "utf8")));
189
+ targetExists = true;
190
+ if (!raw) throw new Error("invalid footer config schema");
191
+ version = raw.version === 3 ? 3 : raw.version === 2 ? 2 : 1;
192
+ } catch (error) {
193
+ if (getErrorCode(error) !== "ENOENT") throw new Error("invalid footer config schema");
194
+ }
195
+ const current = normalizePersistedCacheOptimizerConfig(raw);
196
+ if (version === 1 && !current.promptCacheKey) {
197
+ await mkdir(dirname(configPath), { recursive: true });
198
+ const targetInfo = targetExists ? await lstat(configPath) : undefined;
199
+ if (targetInfo && (targetInfo.isSymbolicLink() || !targetInfo.isFile())) throw new Error("optimizer config is not a regular file; no changes were made");
200
+ const targetText = targetInfo ? await readFile(configPath, "utf8") : undefined;
201
+ const targetMode = targetInfo ? targetInfo.mode & 0o7777 : 0o600;
202
+ const footerText = JSON.stringify({ version: 1, footerMode: mode }, null, 2) + "\n";
203
+ if (targetInfo && targetText !== undefined) {
204
+ await atomicReplaceTextFilePreservingMode(configPath, footerText, targetMode, "config-footer", {
205
+ identity: targetInfo,
206
+ hash: hashText(targetText),
207
+ mode: targetMode,
208
+ });
209
+ } else {
210
+ await atomicCreateTextFileNoReplace(configPath, footerText, targetMode, "config-footer");
211
+ }
212
+ return;
213
+ }
214
+ await writePersistedCacheOptimizerConfigUnlocked({ ...current, version: 3, footerMode: mode } as PersistedCacheOptimizerConfigV3, configPath);
215
+ }
216
+
217
+ export async function writePersistedFooterMode(
218
+ mode: FooterStatsMode,
219
+ configPath: string = CONFIG_FILE_PATH,
220
+ ): Promise<void> {
221
+ await withModelsJsonTransactionLock(() => writePersistedFooterModeUnlocked(mode, configPath));
222
+ }
223
+
224
+ async function updatePersistedFeatures(
225
+ update: (features: Partial<Record<PersistedCacheOptimizerFeature, boolean>>) => PersistedCacheOptimizerConfigV3["features"],
226
+ configPath: string,
227
+ ): Promise<void> {
228
+ await withModelsJsonTransactionLock(async () => {
229
+ let targetInfo: Stats | undefined;
230
+ try {
231
+ targetInfo = await lstat(configPath);
232
+ } catch (error) {
233
+ if (getErrorCode(error) !== "ENOENT") throw error;
234
+ }
235
+ let raw: PersistedCacheOptimizerConfig | undefined;
236
+ let guard: AtomicTargetGuard | null = null;
237
+ if (targetInfo) {
238
+ if (targetInfo.isSymbolicLink() || !targetInfo.isFile()) {
239
+ throw new Error("optimizer config is not a regular file; no changes were made");
240
+ }
241
+ try {
242
+ const text = await readFile(configPath, "utf8");
243
+ raw = parsePersistedCacheOptimizerConfig(JSON.parse(text));
244
+ if (!raw) throw new Error("invalid optimizer config schema");
245
+ guard = { identity: targetInfo, hash: hashText(text), mode: targetInfo.mode & 0o7777 };
246
+ } catch {
247
+ throw new Error(`optimizer config ${configPath} is invalid or unreadable; refusing to overwrite user changes. Fix or remove it, then retry.`);
248
+ }
249
+ }
250
+ const current = normalizePersistedCacheOptimizerConfig(raw);
251
+ await writePersistedCacheOptimizerConfigUnlocked({
252
+ ...current,
253
+ version: 3,
254
+ features: update(current.features ?? {}),
255
+ }, configPath, guard);
256
+ });
257
+ if (configPath === CONFIG_FILE_PATH) setPersistedCacheOptimizerConfig(readPersistedCacheOptimizerConfig());
258
+ }
259
+
260
+ export async function writePersistedFeature(
261
+ feature: PersistedCacheOptimizerFeature,
262
+ enabled: boolean,
263
+ configPath: string = CONFIG_FILE_PATH,
264
+ ): Promise<void> {
265
+ await updatePersistedFeatures((features) => ({ ...features, [feature]: enabled }), configPath);
266
+ }
267
+
268
+ export async function resetPersistedFeatures(configPath: string = CONFIG_FILE_PATH): Promise<void> {
269
+ await updatePersistedFeatures(() => undefined, configPath);
270
+ }
271
+
272
+ export function resolveFooterStatsMode(
273
+ configuredMode: FooterStatsMode | undefined,
274
+ env: MutableEnv = process.env,
275
+ ): { mode: FooterStatsMode; source: FooterStatsModeSource } {
276
+ if (configuredMode) return { mode: configuredMode, source: "config" };
277
+ const envMode = parseFooterStatsMode(env[FOOTER_MODE_ENV]);
278
+ return envMode ? { mode: envMode, source: "env" } : { mode: "session", source: "default" };
279
+ }
280
+
281
+ export function footerStatsMode(
282
+ env: MutableEnv = process.env,
283
+ configuredMode: FooterStatsMode | undefined = persistedFooterStatsMode,
284
+ ): FooterStatsMode {
285
+ return resolveFooterStatsMode(configuredMode, env).mode;
286
+ }
287
+
288
+ export let persistedCacheOptimizerConfig = readPersistedCacheOptimizerConfig();
289
+
290
+ export let persistedFooterStatsMode = persistedCacheOptimizerConfig.footerMode;
291
+
292
+ export function isDisabledEnv(value: string | undefined): boolean {
293
+ if (!value) return false;
294
+ const normalized = value.trim().toLowerCase();
295
+ return normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off";
296
+ }
297
+
298
+ export function shouldInjectOpenAIPromptCacheKey(): boolean {
299
+ return runtimeOptimizerEnabled && featureEnabled("openAICacheKey", NO_OPENAI_CACHE_KEY_ENV, true);
300
+ }
301
+
302
+ export function setPersistedCacheOptimizerConfig(config: PersistedCacheOptimizerConfig | PersistedCacheOptimizerConfigV3): void {
303
+ persistedCacheOptimizerConfig = normalizePersistedCacheOptimizerConfig(config);
304
+ persistedFooterStatsMode = persistedCacheOptimizerConfig.footerMode;
305
+ }
306
+
307
+ export function setRuntimeOptimizerEnabled(enabled: boolean, env: MutableEnv = process.env): void {
308
+ runtimeOptimizerEnabled = enabled;
309
+ if (enabled) {
310
+ requestLongCacheRetention(env);
311
+ } else {
312
+ restoreCacheRetentionEnv(STARTUP_CACHE_RETENTION_ENV, env);
313
+ }
314
+ }
315
+
316
+ export function isRuntimeOptimizerEnabled(): boolean {
317
+ return runtimeOptimizerEnabled;
318
+ }
319
+
320
+ export function readPersistedFooterMode(configPath: string = CONFIG_FILE_PATH): FooterStatsMode | undefined {
321
+ return readPersistedCacheOptimizerConfig(configPath).footerMode;
322
+ }
323
+
324
+ export function getOptimizerRuntimeModeLines(): string[] {
325
+ const state = runtimeOptimizerEnabled ? "enabled" : "disabled";
326
+ const lines: string[] = [];
327
+ lines.push(`Runtime state: ${state}`);
328
+ const promptRewriteActive = runtimeOptimizerEnabled && featureEnabled("promptRewrite", NO_PROMPT_REWRITE_ENV, true);
329
+ lines.push(`• Prompt rewrite: ${promptRewriteActive ? "on" : "off"} (in-place: session-overview churn strip, skill list compression; never reorders the prompt)`);
330
+ lines.push(`• Native virtual rewrite: ${featureEnabled("virtualRewrite", VIRTUAL_REWRITE_ENV, false) ? "opt-in" : "off"}`);
331
+ const skillCompressionSetting = featureEnabled("skillCompression", NO_SKILL_COMPRESSION_ENV, true);
332
+ // Compression runs inside prompt rewrite, so its own switch is not enough to make it active.
333
+ lines.push(`• Skill compression: ${!skillCompressionSetting ? "off" : promptRewriteActive ? "on" : "on (inactive: prompt rewrite is off)"}`);
334
+ lines.push(`• Deterministic tool ordering: ${isToolOrderEnabled() ? "on (verified built-in payloads, opt-in)" : "off"}`);
335
+ lines.push(`• OpenAI prompt_cache_key fallback: ${shouldInjectOpenAIPromptCacheKey() ? "on" : "off"}`);
336
+ lines.push(`• Footer cache stats: on${runtimeOptimizerEnabled ? "" : " (comparison mode)"}`);
337
+ lines.push(`• Compat warnings: ${runtimeOptimizerEnabled ? "on" : "off"}`);
338
+ lines.push(`• ${PI_CACHE_RETENTION_ENV}: ${process.env[PI_CACHE_RETENTION_ENV] ?? "(unset)"}`);
339
+ lines.push(`• Persistent feature overrides: ${Object.keys(persistedCacheOptimizerConfig.features ?? {}).length > 0 ? "configured" : "none"}`);
340
+ if (!runtimeOptimizerEnabled) {
341
+ lines.push("This is a current-process switch. Run /reload or restart Pi to return to startup behavior.");
342
+ } else if (!featureEnabled("promptRewrite", NO_PROMPT_REWRITE_ENV, true) || !shouldInjectOpenAIPromptCacheKey()) {
343
+ lines.push("Some features are disabled by persistent configuration or environment variables.");
344
+ }
345
+ return lines;
346
+ }
347
+
348
+ export function formatPersistentFeatureConfig(): string {
349
+ const features = persistedCacheOptimizerConfig.features ?? {};
350
+ const lines = ["Persistent feature configuration:"];
351
+ const entries: Array<[string, PersistedCacheOptimizerFeature, string, boolean]> = [
352
+ ["Prompt rewrite", "promptRewrite", NO_PROMPT_REWRITE_ENV, true],
353
+ ["Native virtual rewrite", "virtualRewrite", VIRTUAL_REWRITE_ENV, false],
354
+ ["Skill compression", "skillCompression", NO_SKILL_COMPRESSION_ENV, true],
355
+ ["OpenAI cache key", "openAICacheKey", NO_OPENAI_CACHE_KEY_ENV, true],
356
+ ["Tool ordering", "toolOrder", TOOL_ORDER_ENV, false],
357
+ ];
358
+ for (const [label, feature, envName, defaultValue] of entries) {
359
+ const hasEnv = process.env[envName] !== undefined || (feature === "openAICacheKey" && process.env[OPENAI_CACHE_KEY_ENV] !== undefined);
360
+ const source = features[feature] !== undefined ? "config" : hasEnv ? "env" : "default";
361
+ lines.push(`• ${label}: ${featureEnabled(feature, envName, defaultValue) ? "on" : "off"} (${source})`);
362
+ }
363
+ return lines.join("\n");
364
+ }
365
+
366
+ export function formatOptimizerRuntimeMode(): string {
367
+ return getOptimizerRuntimeModeLines().join("\n");
368
+ }
369
+
370
+ export const NO_PROMPT_REWRITE_ENV = "PI_CACHE_OPTIMIZER_NO_PROMPT_REWRITE";
371
+
372
+ export const NO_SKILL_COMPRESSION_ENV = "PI_CACHE_OPTIMIZER_NO_SKILL_COMPRESSION";
373
+
374
+ export const VIRTUAL_REWRITE_ENV = "PI_CACHE_OPTIMIZER_VIRTUAL_REWRITE";