pi-cache-optimizer 2.6.20 → 2.6.22
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/index.ts +141 -30
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -14,6 +14,12 @@ type CacheRetentionEnvSnapshot = {
|
|
|
14
14
|
|
|
15
15
|
const PI_CACHE_RETENTION_ENV = "PI_CACHE_RETENTION";
|
|
16
16
|
const LONG_CACHE_RETENTION_VALUE = "long";
|
|
17
|
+
const PI_CACHE_RETENTION_BASELINE_SYMBOL = Symbol.for("pi.cache.optimizer.retention-baseline.v1");
|
|
18
|
+
|
|
19
|
+
type CacheRetentionBaselineV1 = {
|
|
20
|
+
version: 1;
|
|
21
|
+
snapshot: CacheRetentionEnvSnapshot;
|
|
22
|
+
};
|
|
17
23
|
|
|
18
24
|
function captureCacheRetentionEnv(env: MutableEnv = process.env): CacheRetentionEnvSnapshot {
|
|
19
25
|
return {
|
|
@@ -36,7 +42,28 @@ function restoreCacheRetentionEnv(snapshot: CacheRetentionEnvSnapshot, env: Muta
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
function isCacheRetentionBaselineV1(value: unknown): value is CacheRetentionBaselineV1 {
|
|
46
|
+
if (typeof value !== "object" || value === null) return false;
|
|
47
|
+
const record = value as { version?: unknown; snapshot?: unknown };
|
|
48
|
+
if (record.version !== 1 || typeof record.snapshot !== "object" || record.snapshot === null) return false;
|
|
49
|
+
const snapshot = record.snapshot as { wasSet?: unknown; value?: unknown };
|
|
50
|
+
return typeof snapshot.wasSet === "boolean" &&
|
|
51
|
+
(snapshot.value === undefined || typeof snapshot.value === "string");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getOrCaptureCacheRetentionBaseline(
|
|
55
|
+
env: MutableEnv = process.env,
|
|
56
|
+
globals: Record<symbol, unknown> = globalThis as Record<symbol, unknown>,
|
|
57
|
+
): CacheRetentionEnvSnapshot {
|
|
58
|
+
const existing = globals[PI_CACHE_RETENTION_BASELINE_SYMBOL];
|
|
59
|
+
if (isCacheRetentionBaselineV1(existing)) return { ...existing.snapshot };
|
|
60
|
+
|
|
61
|
+
const snapshot = captureCacheRetentionEnv(env);
|
|
62
|
+
globals[PI_CACHE_RETENTION_BASELINE_SYMBOL] = { version: 1, snapshot: { ...snapshot } };
|
|
63
|
+
return snapshot;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const STARTUP_CACHE_RETENTION_ENV = getOrCaptureCacheRetentionBaseline();
|
|
40
67
|
|
|
41
68
|
/**
|
|
42
69
|
* Pi Cache Optimizer (formerly pi-deepseek-cache-optimizer)
|
|
@@ -76,6 +103,7 @@ const NO_SKILL_COMPRESSION_ENV = "PI_CACHE_OPTIMIZER_NO_SKILL_COMPRESSION";
|
|
|
76
103
|
const NO_PROMPT_REWRITE_ENV = "PI_CACHE_OPTIMIZER_NO_PROMPT_REWRITE";
|
|
77
104
|
const PI_ROUTING_REGISTRY_SYMBOL = Symbol.for("pi.routing.registry.v1");
|
|
78
105
|
const PI_CACHE_HINTS_SYMBOL = Symbol.for("pi.cache.hints.v1");
|
|
106
|
+
const PI_CACHE_HINTS_OWNER_SYMBOL = Symbol.for("pi.cache.optimizer.hints-owner.v1");
|
|
79
107
|
|
|
80
108
|
let runtimeOptimizerEnabled = true;
|
|
81
109
|
|
|
@@ -921,16 +949,29 @@ function getCacheHintsService(): PiCacheHintsV1 | undefined {
|
|
|
921
949
|
return isCacheHintsServiceV1(candidate) ? candidate : undefined;
|
|
922
950
|
}
|
|
923
951
|
|
|
924
|
-
function
|
|
952
|
+
function markOptimizerOwnedCacheHintsService(service: PiCacheHintsV1): PiCacheHintsV1 {
|
|
953
|
+
(service as PiCacheHintsV1 & Record<symbol, unknown>)[PI_CACHE_HINTS_OWNER_SYMBOL] = true;
|
|
954
|
+
return service;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function isOptimizerOwnedCacheHintsService(value: unknown): boolean {
|
|
958
|
+
return typeof value === "object" && value !== null &&
|
|
959
|
+
(value as Record<symbol, unknown>)[PI_CACHE_HINTS_OWNER_SYMBOL] === true;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function installCacheHintsService(
|
|
963
|
+
service: PiCacheHintsV1,
|
|
964
|
+
options: { discardPrevious?: (value: unknown) => boolean } = {},
|
|
965
|
+
): () => void {
|
|
925
966
|
const globals = getProtocolGlobal();
|
|
926
967
|
const previous = globals[PI_CACHE_HINTS_SYMBOL];
|
|
927
968
|
globals[PI_CACHE_HINTS_SYMBOL] = service;
|
|
928
969
|
return () => {
|
|
929
970
|
if (globals[PI_CACHE_HINTS_SYMBOL] !== service) return;
|
|
930
|
-
if (previous
|
|
931
|
-
delete globals[PI_CACHE_HINTS_SYMBOL];
|
|
932
|
-
} else {
|
|
971
|
+
if (previous !== undefined && !options.discardPrevious?.(previous)) {
|
|
933
972
|
globals[PI_CACHE_HINTS_SYMBOL] = previous;
|
|
973
|
+
} else {
|
|
974
|
+
delete globals[PI_CACHE_HINTS_SYMBOL];
|
|
934
975
|
}
|
|
935
976
|
};
|
|
936
977
|
}
|
|
@@ -1794,10 +1835,21 @@ function modelFromAssistantMessage(message: unknown, fallback: PiModel | undefin
|
|
|
1794
1835
|
const api = firstNonEmptyString(record.api, fallback?.api) ?? "";
|
|
1795
1836
|
if (!id || !provider) return fallback;
|
|
1796
1837
|
|
|
1838
|
+
const fallbackName = isNonEmptyString(fallback?.name) ? fallback.name : undefined;
|
|
1839
|
+
const preservesFallbackIdentity =
|
|
1840
|
+
!isVirtualRoutingModel(fallback) &&
|
|
1841
|
+
provider === fallback?.provider &&
|
|
1842
|
+
id === fallback?.id &&
|
|
1843
|
+
fallbackName !== undefined;
|
|
1844
|
+
|
|
1797
1845
|
return {
|
|
1798
1846
|
...(fallback ?? {}),
|
|
1799
1847
|
id,
|
|
1800
|
-
|
|
1848
|
+
// Direct providers such as kimi-coding may echo only a short model id
|
|
1849
|
+
// (`k3`) while the active model name (`Kimi K3`) carries the adapter token.
|
|
1850
|
+
// Preserve that display name only when response and fallback identities are
|
|
1851
|
+
// exactly the same; routed/different identities keep message-local naming.
|
|
1852
|
+
name: preservesFallbackIdentity ? fallbackName : id,
|
|
1801
1853
|
provider,
|
|
1802
1854
|
api,
|
|
1803
1855
|
baseUrl: fallback?.baseUrl ?? "",
|
|
@@ -3908,6 +3960,15 @@ function mergeCacheSessions(
|
|
|
3908
3960
|
return sessions;
|
|
3909
3961
|
}
|
|
3910
3962
|
|
|
3963
|
+
function createSerializedAsyncRunner(): <T>(operation: () => Promise<T>) => Promise<T> {
|
|
3964
|
+
let tail: Promise<unknown> = Promise.resolve();
|
|
3965
|
+
return <T>(operation: () => Promise<T>): Promise<T> => {
|
|
3966
|
+
const result = tail.then(operation);
|
|
3967
|
+
tail = result.catch(() => undefined);
|
|
3968
|
+
return result;
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3911
3972
|
function mergeCacheTotals(
|
|
3912
3973
|
existingTotalsByModel: Record<string, CacheStats>,
|
|
3913
3974
|
stateTotalsByModel: Record<string, CacheStats>,
|
|
@@ -5954,6 +6015,7 @@ export const __internals_for_tests = {
|
|
|
5954
6015
|
getAssistantMessageModelTokenValues,
|
|
5955
6016
|
getCompat,
|
|
5956
6017
|
modelKey,
|
|
6018
|
+
modelFromAssistantMessage,
|
|
5957
6019
|
consolidateDirectProviderStatsModel,
|
|
5958
6020
|
// Platform-friendly path helpers
|
|
5959
6021
|
getAgentDirDisplayPath,
|
|
@@ -6013,10 +6075,15 @@ export const __internals_for_tests = {
|
|
|
6013
6075
|
isVirtualRoutingModel,
|
|
6014
6076
|
installCacheHintsService,
|
|
6015
6077
|
getCacheHintsService,
|
|
6078
|
+
markOptimizerOwnedCacheHintsService,
|
|
6079
|
+
isOptimizerOwnedCacheHintsService,
|
|
6080
|
+
getOrCaptureCacheRetentionBaseline,
|
|
6081
|
+
PI_CACHE_RETENTION_BASELINE_SYMBOL,
|
|
6016
6082
|
// Persistence helpers (for reload/reset tests)
|
|
6017
6083
|
mergeCacheSessions,
|
|
6018
6084
|
mergeCacheTotals,
|
|
6019
6085
|
mergeLastRoutedModels,
|
|
6086
|
+
createSerializedAsyncRunner,
|
|
6020
6087
|
writePersistedCacheStats,
|
|
6021
6088
|
readPersistedCacheStats,
|
|
6022
6089
|
STATE_FILE_PATH,
|
|
@@ -6066,7 +6133,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
6066
6133
|
let lastStatusText: string | undefined;
|
|
6067
6134
|
let persistenceWarningShown = false;
|
|
6068
6135
|
let persistTimer: ReturnType<typeof setTimeout> | null = null;
|
|
6136
|
+
const enqueuePersist = createSerializedAsyncRunner();
|
|
6069
6137
|
let replacePersistedTotalsOnNextWrite = false;
|
|
6138
|
+
let forceReplaceTotalsAfterFailure = false;
|
|
6070
6139
|
const pendingDeletedTotalModelKeys = new Set<string>();
|
|
6071
6140
|
let integrityNotificationShown = false;
|
|
6072
6141
|
let currentSessionId = "";
|
|
@@ -6088,7 +6157,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6088
6157
|
}
|
|
6089
6158
|
}
|
|
6090
6159
|
|
|
6091
|
-
const uninstallCacheHintsService = installCacheHintsService({
|
|
6160
|
+
const uninstallCacheHintsService = installCacheHintsService(markOptimizerOwnedCacheHintsService({
|
|
6092
6161
|
version: 1,
|
|
6093
6162
|
getHints(input: PiCacheHintsInput): PiCacheHintsOutput | undefined {
|
|
6094
6163
|
if (!runtimeOptimizerEnabled || isEnabledEnv(process.env[NO_PROMPT_REWRITE_ENV])) return undefined;
|
|
@@ -6107,8 +6176,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6107
6176
|
cacheRetention: hint.cacheRetention,
|
|
6108
6177
|
};
|
|
6109
6178
|
},
|
|
6110
|
-
});
|
|
6111
|
-
void uninstallCacheHintsService;
|
|
6179
|
+
}), { discardPrevious: isOptimizerOwnedCacheHintsService });
|
|
6112
6180
|
|
|
6113
6181
|
/**
|
|
6114
6182
|
* Build a session-scoped stats key from the current session hash + model key.
|
|
@@ -6231,28 +6299,59 @@ export default function (pi: ExtensionAPI) {
|
|
|
6231
6299
|
lastStatusText = undefined;
|
|
6232
6300
|
}
|
|
6233
6301
|
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
} catch (error) {
|
|
6247
|
-
console.warn(`${LOG_PREFIX}: failed to persist cache stats`, error);
|
|
6248
|
-
if (!persistenceWarningShown) {
|
|
6249
|
-
persistenceWarningShown = true;
|
|
6250
|
-
ctx?.ui.notify(
|
|
6251
|
-
`${LOG_PREFIX}: failed to persist footer stats; using in-memory stats for this process.`,
|
|
6252
|
-
"warning",
|
|
6253
|
-
);
|
|
6254
|
-
}
|
|
6302
|
+
function snapshotCacheStatsState(): CacheStatsState {
|
|
6303
|
+
const state = getCacheStatsState();
|
|
6304
|
+
const statsByModel = Object.fromEntries(
|
|
6305
|
+
Object.entries(state.statsByModel).map(([key, stats]) => [key, { ...stats }]),
|
|
6306
|
+
);
|
|
6307
|
+
const totalsByModel = Object.fromEntries(
|
|
6308
|
+
Object.entries(state.totalsByModel).map(([key, stats]) => [key, { ...stats }]),
|
|
6309
|
+
);
|
|
6310
|
+
const legacyFamily: Partial<Record<CacheProviderId, CacheStats>> = {};
|
|
6311
|
+
for (const id of CACHE_PROVIDER_IDS) {
|
|
6312
|
+
const stats = state.legacyFamily[id];
|
|
6313
|
+
if (stats) legacyFamily[id] = { ...stats };
|
|
6255
6314
|
}
|
|
6315
|
+
const lastRoutedModelBySession = state.lastRoutedModelBySession
|
|
6316
|
+
? Object.fromEntries(
|
|
6317
|
+
Object.entries(state.lastRoutedModelBySession).map(([key, model]) => [key, { ...model }]),
|
|
6318
|
+
)
|
|
6319
|
+
: undefined;
|
|
6320
|
+
return { statsByModel, totalsByModel, legacyFamily, lastRoutedModelBySession };
|
|
6321
|
+
}
|
|
6322
|
+
|
|
6323
|
+
function persistCacheStats(ctx?: ExtensionContext): Promise<void> {
|
|
6324
|
+
const state = snapshotCacheStatsState();
|
|
6325
|
+
const sessionHash = currentSessionHashSet ? currentSessionHash : undefined;
|
|
6326
|
+
const deleteModelKeys = Array.from(pendingDeletedTotalModelKeys);
|
|
6327
|
+
const replaceTotals = replacePersistedTotalsOnNextWrite;
|
|
6328
|
+
// This queued write owns the current intents. Later mutations can enqueue
|
|
6329
|
+
// independent intents without an earlier completion clearing them.
|
|
6330
|
+
pendingDeletedTotalModelKeys.clear();
|
|
6331
|
+
replacePersistedTotalsOnNextWrite = false;
|
|
6332
|
+
|
|
6333
|
+
return enqueuePersist(async () => {
|
|
6334
|
+
const recoverFromEarlierFailure = forceReplaceTotalsAfterFailure;
|
|
6335
|
+
try {
|
|
6336
|
+
await writePersistedCacheStats(state, sessionHash, {
|
|
6337
|
+
deleteModelKeys,
|
|
6338
|
+
replaceTotals: replaceTotals || recoverFromEarlierFailure,
|
|
6339
|
+
});
|
|
6340
|
+
if (recoverFromEarlierFailure) forceReplaceTotalsAfterFailure = false;
|
|
6341
|
+
} catch (error) {
|
|
6342
|
+
// The next queued write must replace totals authoritatively so a failed
|
|
6343
|
+
// reset/delete cannot merge stale persisted counters back into state.
|
|
6344
|
+
forceReplaceTotalsAfterFailure = true;
|
|
6345
|
+
console.warn(`${LOG_PREFIX}: failed to persist cache stats`, error);
|
|
6346
|
+
if (!persistenceWarningShown) {
|
|
6347
|
+
persistenceWarningShown = true;
|
|
6348
|
+
ctx?.ui.notify(
|
|
6349
|
+
`${LOG_PREFIX}: failed to persist footer stats; using in-memory stats for this process.`,
|
|
6350
|
+
"warning",
|
|
6351
|
+
);
|
|
6352
|
+
}
|
|
6353
|
+
}
|
|
6354
|
+
});
|
|
6256
6355
|
}
|
|
6257
6356
|
|
|
6258
6357
|
/** Schedule a debounced persist. Coalesces rapid message_end writes
|
|
@@ -6542,10 +6641,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
6542
6641
|
}
|
|
6543
6642
|
|
|
6544
6643
|
pi.on("session_start", async (event, ctx) => {
|
|
6644
|
+
if (runtimeOptimizerEnabled) requestLongCacheRetention();
|
|
6545
6645
|
await restoreCacheStats(event.reason, ctx);
|
|
6546
6646
|
await publishStatus(ctx);
|
|
6547
6647
|
});
|
|
6548
6648
|
|
|
6649
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
6650
|
+
try {
|
|
6651
|
+
await flushPersistCacheStats(ctx);
|
|
6652
|
+
} finally {
|
|
6653
|
+
latestCacheHint = undefined;
|
|
6654
|
+
delete getProtocolGlobal().__piCacheOptimizerCacheKey__;
|
|
6655
|
+
uninstallCacheHintsService();
|
|
6656
|
+
restoreCacheRetentionEnv(STARTUP_CACHE_RETENTION_ENV);
|
|
6657
|
+
}
|
|
6658
|
+
});
|
|
6659
|
+
|
|
6549
6660
|
pi.on("model_select", async (event, ctx) => {
|
|
6550
6661
|
if (runtimeOptimizerEnabled) notifyCacheCompatIfNeeded(resolveRouteModel(event.model, ctx) ?? event.model, ctx, warnedModels);
|
|
6551
6662
|
await publishStatus(ctx, event.model);
|
package/package.json
CHANGED