pi-cache-optimizer 2.8.22 → 2.8.23
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/README.md +10 -1
- package/README.zh-CN.md +10 -1
- package/index.ts +2 -0
- package/package.json +1 -1
- package/src/cache-warming.ts +150 -0
- package/src/command.ts +11 -2
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ Pi extension for improving provider-side KV / prompt cache hit rates. It keeps s
|
|
|
21
21
|
- [Opt-in deterministic tool ordering](#opt-in-deterministic-tool-ordering)
|
|
22
22
|
- [Footer cache stats mode](#footer-cache-stats-mode)
|
|
23
23
|
- [OpenAI-compatible proxy setup](#openai-compatible-proxy-setup)
|
|
24
|
+
- [Prompt cache warming (Pi 0.86+)](#prompt-cache-warming-pi-086)
|
|
24
25
|
- [Adaptive thinking models](#adaptive-thinking-models)
|
|
25
26
|
- [Auto-repair with `/cache-optimizer fix`](#auto-repair-with-cache-optimizer-fix)
|
|
26
27
|
- [DeepSeek protocol safety and rollback](#deepseek-protocol-safety-and-rollback)
|
|
@@ -71,7 +72,7 @@ This extension requires Pi 0.82+ and is validated against Pi 1.1.0. It uses the
|
|
|
71
72
|
| `/cache-optimizer` | Interactive menu when UI supports it; otherwise prints help and current state. |
|
|
72
73
|
| `/cache-optimizer enable` | Enables runtime optimizations for the current Pi process, resets local footer stats, and starts a fresh “enabled” measurement. |
|
|
73
74
|
| `/cache-optimizer disable` | Disables optimization for the current Pi process, resets local footer stats, and keeps collecting footer stats in disabled comparison mode. Run `/reload` or restart Pi to return to startup behavior. |
|
|
74
|
-
| `/cache-optimizer doctor` | Shows active model/provider/API/base URL/compat, whether the skill list was compressed on the last prompt (and why not, e.g. an unrecognised Pi format), plus low-hit diagnosis. |
|
|
75
|
+
| `/cache-optimizer doctor` | Shows active model/provider/API/base URL/compat, whether the skill list was compressed on the last prompt (and why not, e.g. an unrecognised Pi format), plus Pi cache-warming eligibility and low-hit diagnosis. |
|
|
75
76
|
| `/cache-optimizer compat` | Shows copyable compat advice for the active model, if applicable. |
|
|
76
77
|
| `/cache-optimizer stats` | Shows detailed counters for every cache-adapter-matched model used by the current conversation session today. |
|
|
77
78
|
| `/cache-optimizer stats all` | Shows detailed per-model totals across all valid local sessions/shards today, including request and token counts. |
|
|
@@ -203,6 +204,14 @@ Notes:
|
|
|
203
204
|
- Do not add `thinkingFormat: "deepseek"` merely because a model id contains `deepseek`; `openai`, `qwen`, `openrouter`, `together`, or no explicit format are all valid catalog cases.
|
|
204
205
|
- This extension's `doctor` and `compat` commands only advise; they do not modify `models.json`.
|
|
205
206
|
|
|
207
|
+
## Prompt cache warming (Pi 0.86+)
|
|
208
|
+
|
|
209
|
+
Pi itself can keep a prompt cache alive by re-sending the last request with a one-token output cap shortly before the entry expires (the global `cacheWarming` setting: `off`, `streaming` (default), or `idle`; change it with `/settings` → Cache warming or `"cacheWarming"` in `~/.pi/agent/settings.json`). This extension does not run a keepalive timer of its own: a second warmer would pay for duplicate refreshes and cannot replay Pi's exact request.
|
|
210
|
+
|
|
211
|
+
Pi only warms a model that declares a cache lifetime for the retention tier in use, has input/cache prices, and is replayable (Anthropic budget-based thinking is not). Custom proxy models in `models.json` often lack the first two, so warming never starts. `/cache-optimizer doctor` now reports the mode, the tier, and what is missing, with a snippet such as `"promptCache": {"short":300,"long":3600}` for `anthropic-messages` models. Put it on the model entry (custom models) or under the provider's `modelOverrides` (built-in models). It is advice only; the extension does not write it for you.
|
|
212
|
+
|
|
213
|
+
Streaming warming stops 60 minutes after the real request and idle warming after 30 minutes. With this extension's 1-hour retention, the refresh would fire at 54 minutes, so `idle` adds nothing; the 1-hour lifetime already covers breaks under an hour. Keep the default `streaming` unless you use 5-minute caches and often return within half an hour.
|
|
214
|
+
|
|
206
215
|
## Anthropic cache TTL compatibility
|
|
207
216
|
|
|
208
217
|
Anthropic processes cache breakpoints in `tools → system → messages` order and rejects a `ttl: "1h"` breakpoint that appears after a 5-minute breakpoint. An ephemeral `cache_control` without `ttl` uses the default 5-minute retention.
|
package/README.zh-CN.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
- [Opt-in 确定性工具排序](#opt-in-确定性工具排序)
|
|
22
22
|
- [Footer 缓存统计模式](#footer-缓存统计模式)
|
|
23
23
|
- [OpenAI-compatible 代理配置](#openai-compatible-代理配置)
|
|
24
|
+
- [Prompt cache 保温(Pi 0.86+)](#prompt-cache-保温pi-086)
|
|
24
25
|
- [Adaptive thinking 模型](#adaptive-thinking-模型)
|
|
25
26
|
- [使用 `/cache-optimizer fix` 自动修复](#使用-cache-optimizer-fix-自动修复)
|
|
26
27
|
- [DeepSeek 协议安全与回滚](#deepseek-协议安全与回滚)
|
|
@@ -71,7 +72,7 @@ Pi 0.79.7 及之后,`pi update` 默认只更新 Pi 本体。若要更新已安
|
|
|
71
72
|
| `/cache-optimizer` | UI 支持时打开交互菜单;否则打印帮助和当前状态。 |
|
|
72
73
|
| `/cache-optimizer enable` | 在当前 Pi 进程中开启运行时优化,清零本地 footer 统计,并开始新的“开启状态”测量。 |
|
|
73
74
|
| `/cache-optimizer disable` | 在当前 Pi 进程中关闭优化,清零本地 footer 统计,并继续以 disabled 对比模式采集 footer 统计。运行 `/reload` 或重启 Pi 后回到启动时行为。 |
|
|
74
|
-
| `/cache-optimizer doctor` | 显示当前模型 / provider / API / base URL / compat、上一次提示中 skill 列表是否被压缩(未压缩时给出原因,例如无法识别 Pi
|
|
75
|
+
| `/cache-optimizer doctor` | 显示当前模型 / provider / API / base URL / compat、上一次提示中 skill 列表是否被压缩(未压缩时给出原因,例如无法识别 Pi 的格式)、Pi 缓存保温是否生效以及低命中诊断。 |
|
|
75
76
|
| `/cache-optimizer compat` | 对当前模型显示可复制的 compat 建议(如适用)。 |
|
|
76
77
|
| `/cache-optimizer stats` | 显示当前 conversation session 今天使用过的各 cache-adapter-matched 模型详细统计。 |
|
|
77
78
|
| `/cache-optimizer stats all` | 显示所有有效本地 session/shard 今天的逐模型详细总计,包括请求数与 token 数。 |
|
|
@@ -203,6 +204,14 @@ Pi 0.84.1 还修复了内置 Fireworks 渠道对拒绝 `prompt_cache_retention`
|
|
|
203
204
|
- 不要因为模型 id 含有 `deepseek` 就添加 `thinkingFormat: "deepseek"`;catalog 中也存在 `openai`、`qwen`、`openrouter`、`together` 或没有显式 format 的情况。
|
|
204
205
|
- 本扩展的 `doctor` 和 `compat` 命令只给建议,不会修改 `models.json`。
|
|
205
206
|
|
|
207
|
+
## Prompt cache 保温(Pi 0.86+)
|
|
208
|
+
|
|
209
|
+
Pi 自带缓存保温:在缓存条目过期前,用一个 token 的输出上限原样重放上一次请求(全局设置 `cacheWarming`:`off`、`streaming`(默认)或 `idle`;在 `/settings` → Cache warming 中修改,或在 `~/.pi/agent/settings.json` 中设置 `"cacheWarming"`)。本扩展不自行运行保活定时器:第二个保温器会重复计费,而且无法精确重放 Pi 的请求。
|
|
210
|
+
|
|
211
|
+
Pi 只会为满足以下条件的模型保温:为当前保留档位声明了缓存时长、有输入/缓存价格、请求可重放(Anthropic 基于 budget 的 thinking 不可重放)。`models.json` 里的自定义代理模型常常缺前两项,保温根本不会启动。`/cache-optimizer doctor` 现在会显示保温模式、档位和缺失项,并给出片段,例如 `anthropic-messages` 模型的 `"promptCache": {"short":300,"long":3600}`。自定义模型写在该模型条目中,内置模型写在 provider 的 `modelOverrides` 下。这只是建议,扩展不会替你写入。
|
|
212
|
+
|
|
213
|
+
streaming 保温在真实请求后 60 分钟停止,idle 保温在 30 分钟后停止。本扩展使用 1 小时保留时,刷新会在第 54 分钟才触发,所以 `idle` 没有额外作用;1 小时的缓存本身已覆盖一小时内的中断。除非你使用 5 分钟缓存且经常在半小时内回来,否则保持默认的 `streaming` 即可。
|
|
214
|
+
|
|
206
215
|
## Anthropic 缓存 TTL 兼容
|
|
207
216
|
|
|
208
217
|
Anthropic 按 `tools → system → messages` 顺序处理 cache breakpoint,并拒绝位于 5 分钟 breakpoint 之后的 `ttl: "1h"` breakpoint。省略 `ttl` 的 ephemeral `cache_control` 使用默认 5 分钟保留时间。
|
package/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { analyzeModelsJsonForMissingEntry, applyModelsJsonFixTransaction, choose
|
|
|
16
16
|
import { type CacheProviderId, type CacheStats, LEGACY_STATE_FILE_PATH, type PersistedRoutedModelRef, type PersistedStatsShardV7, SHARD_FILES_DIR, SHARD_GLOBAL_EPOCH_PATH, SHARD_STATE_DIR, STATE_FILE_PATH, type ShardAggregate, type UsageSnapshot, addUsageToCacheStats, advanceGlobalStatsEpoch, advanceModelStatsEpoch, aggregateStatsShardsV7, cleanupStatsShardsV7, cloneCacheStats, currentLocalDay, emptyAllCacheStats, emptyCacheStats, initialEpoch, loadStatsShardAggregateV7, maybeCleanupStatsShardsV7, modelEpochPath, parseCacheStats, parsePersistedRoutedModelRef, parsePersistedStatsShardV7, readGlobalStatsEpoch, readModelStatsEpoch, readValidStatsShardsV7, removeLegacyStatsFiles, writeStatsShardV7 } from "./src/stats-store.ts";
|
|
17
17
|
import { PI_VIRTUAL_MODEL_API, getCompat, isAnthropicMessagesApi, isKimiCodingEmptySignatureModel, isKnownThirdPartyOpenAIEndpoint, isMistralConversationsApi, isNativeVirtualModel, isOfficialOpenAIBaseUrl, isOpenAICompatibleApi, isOpenAICompatibleProxyApi, isOpenAIFamilyAssistantMessage, isOpenAIFamilyModel, isOpenAIFamilyToken, isPiBuiltInLlamaCppModel, isResponsesPromptRewriteBypassApi, isRoutedFallbackModel, isValidModelsConfigForEffectiveCompat, modelKey, readEffectiveCompatConfig } from "./src/model-identity.ts";
|
|
18
18
|
import { CONFIG_FILE_PATH, VIRTUAL_REWRITE_ENV, FOOTER_MODE_ENV, TOOL_ORDER_ENV, featureEnabled, footerStatsMode, isEnabledEnv, isToolOrderEnabled, parseFooterStatsMode, parsePersistedCacheOptimizerConfig, readPersistedCacheOptimizerConfig, resolveFooterStatsMode, runtimeOptimizerEnabled, setPersistedCacheOptimizerConfig, shouldInjectOpenAIPromptCacheKey, writePersistedCacheOptimizerConfig, writePersistedFeature, writePersistedFooterMode } from "./src/config.ts";
|
|
19
|
+
import { readCacheWarmingMode } from "./src/cache-warming.ts";
|
|
19
20
|
import { LONG_CACHE_RETENTION_VALUE, PI_CACHE_RETENTION_BASELINE_SYMBOL, PI_CACHE_RETENTION_ENV, STARTUP_CACHE_RETENTION_ENV, captureCacheRetentionEnv, getOrCaptureCacheRetentionBaseline, decidePromptCacheRetention, recordPromptCacheRetentionDecision, requestLongCacheRetention, restoreCacheRetentionEnv, userRequestedLongCacheRetention } from "./src/retention.ts";
|
|
20
21
|
import { isRuntimeOptimizerEnabled, setRuntimeOptimizerEnabled } from "./src/config.ts";
|
|
21
22
|
import { compareToolOrderEntries, getToolNameForPayload, isKnownToolOrderApi, isToolOrderingEligibleModel, isVerifiedToolForApi, normalizeToolsInPayload, sortToolsInPayload } from "./src/tool-ordering.ts";
|
|
@@ -1611,6 +1612,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1611
1612
|
getCurrentSessionHash: () => currentSessionHash,
|
|
1612
1613
|
getLastStatusText: () => lastStatusText,
|
|
1613
1614
|
clearLastStatusText: () => { lastStatusText = undefined; },
|
|
1615
|
+
getCacheWarmingMode: () => readCacheWarmingMode(typeof pi.getSettings === "function" ? () => pi.getSettings() : undefined),
|
|
1614
1616
|
});
|
|
1615
1617
|
|
|
1616
1618
|
pi.registerCommand("cache-optimizer", {
|
package/package.json
CHANGED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { type PiModel, asRecord } from "./common.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Diagnostics for Pi's native prompt-cache warming (Pi 0.86+, `cacheWarming`
|
|
5
|
+
* setting). Pi re-sends the last request with a one-token output cap at 90% of
|
|
6
|
+
* the cache lifetime, but only when:
|
|
7
|
+
* - the global `cacheWarming` setting is not "off";
|
|
8
|
+
* - the model declares `promptCache.<tier>` for the retention tier in use;
|
|
9
|
+
* - the model's prices let Pi estimate the savings (cost.cacheRead/cacheWrite/input);
|
|
10
|
+
* - the request is replayable (Anthropic budget-based thinking is not).
|
|
11
|
+
* Streaming warming stops 60 minutes after the real request and idle warming
|
|
12
|
+
* after 30 minutes, so idle mode never refreshes a 1-hour cache entry (its
|
|
13
|
+
* refresh would fire at 54 minutes).
|
|
14
|
+
*
|
|
15
|
+
* This module only reads state and explains it; it never changes settings or
|
|
16
|
+
* models.json. The extension does not run its own keepalive timer, because a
|
|
17
|
+
* second warmer would double-bill refreshes and could not replay Pi's exact
|
|
18
|
+
* request context.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export type CacheWarmingMode = "off" | "streaming" | "idle";
|
|
22
|
+
|
|
23
|
+
export const CACHE_WARMING_DEFAULT_MODE: CacheWarmingMode = "streaming";
|
|
24
|
+
export const CACHE_WARMING_STREAMING_MAX_MS = 60 * 60_000;
|
|
25
|
+
export const CACHE_WARMING_IDLE_MAX_MS = 30 * 60_000;
|
|
26
|
+
|
|
27
|
+
/** Same schedule Pi uses: 90% of the TTL, keeping at least ten seconds of margin. */
|
|
28
|
+
export function cacheWarmingRefreshDelayMs(ttlMs: number): number | undefined {
|
|
29
|
+
if (ttlMs <= 10_000) return undefined;
|
|
30
|
+
return Math.max(1, Math.floor(Math.min(ttlMs * 0.9, ttlMs - 10_000)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type CacheWarmingSettingsReader = () => unknown;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read the `cacheWarming` mode from `pi.getSettings()` (Pi 0.86+). Returns
|
|
37
|
+
* undefined when the host has no settings API or no warming support.
|
|
38
|
+
*/
|
|
39
|
+
export function readCacheWarmingMode(getSettings: CacheWarmingSettingsReader | undefined): CacheWarmingMode | "unsupported" {
|
|
40
|
+
if (typeof getSettings !== "function") return "unsupported";
|
|
41
|
+
let settings: unknown;
|
|
42
|
+
try {
|
|
43
|
+
settings = getSettings();
|
|
44
|
+
} catch {
|
|
45
|
+
return "unsupported";
|
|
46
|
+
}
|
|
47
|
+
const value = asRecord(settings)?.cacheWarming;
|
|
48
|
+
if (value === "off" || value === "streaming" || value === "idle") return value;
|
|
49
|
+
return CACHE_WARMING_DEFAULT_MODE;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function suggestedPromptCacheLifetimes(model: PiModel): { short: number; long?: number } {
|
|
53
|
+
// Anthropic documents a 5-minute default and an optional 1-hour TTL. For
|
|
54
|
+
// other APIs only the conservative short lifetime is suggested; extended
|
|
55
|
+
// retention differs per provider and deployment.
|
|
56
|
+
if (model.api === "anthropic-messages") return { short: 300, long: 3600 };
|
|
57
|
+
return { short: 300 };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readPromptCacheTier(model: PiModel, tier: "short" | "long"): number | undefined {
|
|
61
|
+
const promptCache = asRecord((model as unknown as Record<string, unknown>).promptCache);
|
|
62
|
+
const value = promptCache?.[tier];
|
|
63
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasPricing(model: PiModel): boolean {
|
|
67
|
+
const cost = asRecord((model as unknown as Record<string, unknown>).cost);
|
|
68
|
+
if (!cost) return false;
|
|
69
|
+
return ["input", "cacheRead", "cacheWrite"].some((key) => typeof cost[key] === "number" && (cost[key] as number) > 0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isReplayableForWarming(model: PiModel): boolean {
|
|
73
|
+
if (!model.reasoning || model.api !== "anthropic-messages") return true;
|
|
74
|
+
return asRecord(asRecord(model as unknown)?.compat)?.forceAdaptiveThinking === true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type CacheWarmingDiagnosisInput = {
|
|
78
|
+
mode: CacheWarmingMode | "unsupported";
|
|
79
|
+
/** Retention tier Pi uses for this request ("long" when PI_CACHE_RETENTION=long). */
|
|
80
|
+
tier: "short" | "long";
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type CacheWarmingDiagnosis = {
|
|
84
|
+
eligible: boolean;
|
|
85
|
+
issues: string[];
|
|
86
|
+
lines: string[];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function formatSeconds(seconds: number): string {
|
|
90
|
+
if (seconds % 3600 === 0) return `${seconds / 3600}h`;
|
|
91
|
+
if (seconds % 60 === 0) return `${seconds / 60}m`;
|
|
92
|
+
return `${seconds}s`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function buildCacheWarmingDiagnosis(model: PiModel, input: CacheWarmingDiagnosisInput): CacheWarmingDiagnosis {
|
|
96
|
+
const lines: string[] = ["", "🔥 Prompt cache warming (Pi cacheWarming):"];
|
|
97
|
+
const issues: string[] = [];
|
|
98
|
+
|
|
99
|
+
if (input.mode === "unsupported") {
|
|
100
|
+
lines.push("- This Pi host does not expose cache warming settings (requires Pi 0.86+). Nothing to check.");
|
|
101
|
+
return { eligible: false, issues: ["unsupported"], lines };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
lines.push(`- Mode: ${input.mode}${input.mode === CACHE_WARMING_DEFAULT_MODE ? " (default)" : ""} — global setting, change with /settings → Cache warming or ~/.pi/agent/settings.json`);
|
|
105
|
+
lines.push(`- Retention tier in use: ${input.tier}`);
|
|
106
|
+
|
|
107
|
+
if (input.mode === "off") {
|
|
108
|
+
issues.push("mode_off");
|
|
109
|
+
lines.push("- Warming is off: long tool runs can outlive the cache entry and the next request rewrites it.");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const ttl = readPromptCacheTier(model, input.tier);
|
|
113
|
+
if (ttl === undefined) {
|
|
114
|
+
issues.push("prompt_cache_missing");
|
|
115
|
+
const suggested = suggestedPromptCacheLifetimes(model);
|
|
116
|
+
const snippet: Record<string, number> = { short: suggested.short };
|
|
117
|
+
if (suggested.long !== undefined) snippet.long = suggested.long;
|
|
118
|
+
lines.push(`- ⚠️ The model declares no promptCache.${input.tier} lifetime, so Pi never warms its cache.`);
|
|
119
|
+
lines.push(` Add to this model's entry in models.json (custom models) or under the provider's modelOverrides.${JSON.stringify(model.id)} (built-in models):`);
|
|
120
|
+
lines.push(` "promptCache": ${JSON.stringify(snippet)}`);
|
|
121
|
+
if (input.tier === "long" && suggested.long === undefined) {
|
|
122
|
+
lines.push(" Only add a long lifetime your provider documents; use the conservative end of any range.");
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
lines.push(`- promptCache.${input.tier}: ${formatSeconds(ttl)}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!hasPricing(model)) {
|
|
129
|
+
issues.push("pricing_missing");
|
|
130
|
+
lines.push("- ⚠️ The model has no input/cache prices, so Pi cannot estimate savings and skips warming. Add a cost block to the model entry.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!isReplayableForWarming(model)) {
|
|
134
|
+
issues.push("not_replayable");
|
|
135
|
+
lines.push("- ⚠️ Anthropic budget-based thinking cannot be replayed with a one-token cap, so Pi skips warming. Set compat.forceAdaptiveThinking only if the model supports adaptive thinking.");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (ttl !== undefined && input.mode === "idle") {
|
|
139
|
+
const delay = cacheWarmingRefreshDelayMs(ttl * 1000);
|
|
140
|
+
if (delay !== undefined && delay > CACHE_WARMING_IDLE_MAX_MS) {
|
|
141
|
+
lines.push(`- Note: idle warming stops after 30m, but a ${formatSeconds(ttl)} entry would refresh at ${Math.round(delay / 60_000)}m, so idle mode adds nothing for this tier. The ${formatSeconds(ttl)} lifetime already covers shorter breaks.`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const eligible = input.mode !== "off" && issues.length === 0;
|
|
146
|
+
if (eligible) {
|
|
147
|
+
lines.push(`- ✓ Eligible: Pi keeps this cache warm ${input.mode === "idle" ? "during runs and briefly between runs" : "during agent runs"} when the expected savings are at least $0.05.`);
|
|
148
|
+
}
|
|
149
|
+
return { eligible, issues, lines };
|
|
150
|
+
}
|
package/src/command.ts
CHANGED
|
@@ -21,7 +21,8 @@ import { type ProviderRequestState } from "./request-state.ts";
|
|
|
21
21
|
import { type CacheStats, type ShardAggregate } from "./stats-store.ts";
|
|
22
22
|
import { type CacheUsageSample } from "./stats-report.ts";
|
|
23
23
|
import { describeSkillCompressionOutcome } from "./prompt-rewrite.ts";
|
|
24
|
-
import { describePromptCacheRetentionDecision, getLastPromptCacheRetentionDecision } from "./retention.ts";
|
|
24
|
+
import { describePromptCacheRetentionDecision, getLastPromptCacheRetentionDecision, LONG_CACHE_RETENTION_VALUE, PI_CACHE_RETENTION_ENV } from "./retention.ts";
|
|
25
|
+
import { buildCacheWarmingDiagnosis, type CacheWarmingMode } from "./cache-warming.ts";
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Everything the command needs from the extension instance. The extension keeps this state in closures
|
|
@@ -50,6 +51,8 @@ export type CommandRuntime = {
|
|
|
50
51
|
getCurrentSessionHash(): string;
|
|
51
52
|
getLastStatusText(): string | undefined;
|
|
52
53
|
clearLastStatusText(): void;
|
|
54
|
+
/** Pi `cacheWarming` mode, or "unsupported" on hosts without the settings API. */
|
|
55
|
+
getCacheWarmingMode?(): CacheWarmingMode | "unsupported";
|
|
53
56
|
};
|
|
54
57
|
|
|
55
58
|
export function createCacheOptimizerCommandHandler(runtime: CommandRuntime) {
|
|
@@ -110,7 +113,13 @@ export function createCacheOptimizerCommandHandler(runtime: CommandRuntime) {
|
|
|
110
113
|
const samples = sk ? getRecentSamples(sk) : [];
|
|
111
114
|
const lowHitLines = buildLowHitDiagnosis(model, adapter, statsState, samples);
|
|
112
115
|
const routeNote = describeNativeVirtualRouteNote(selectedModel, model);
|
|
113
|
-
const
|
|
116
|
+
const warmingLines = runtime.getCacheWarmingMode
|
|
117
|
+
? buildCacheWarmingDiagnosis(model, {
|
|
118
|
+
mode: runtime.getCacheWarmingMode(),
|
|
119
|
+
tier: process.env[PI_CACHE_RETENTION_ENV] === LONG_CACHE_RETENTION_VALUE ? "long" : "short",
|
|
120
|
+
}).lines
|
|
121
|
+
: [];
|
|
122
|
+
const fullDiagnosis = [routeNote, diagnosis, describeSkillCompressionOutcome(), describePromptCacheRetentionDecision(getLastPromptCacheRetentionDecision()), ...warmingLines, ...lowHitLines].filter((line) => line !== undefined).join("\n");
|
|
114
123
|
cmdCtx.ui.notify(fullDiagnosis, "info");
|
|
115
124
|
} else if (subcommand === "stats") {
|
|
116
125
|
const aggregate = await refreshShardAggregate();
|