pi-cache-optimizer 2.7.1 → 2.8.1
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 +14 -9
- package/README.zh-CN.md +13 -9
- package/index.ts +133 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,10 +68,12 @@ This extension is validated against Pi 0.83.0 and remains designed for Pi 0.82+.
|
|
|
68
68
|
| `/cache-optimizer compat` | Shows copyable compat advice for the active model, if applicable. |
|
|
69
69
|
| `/cache-optimizer stats` | Shows today's local provider/model counters and recent trend for the active model. |
|
|
70
70
|
| `/cache-optimizer reset` | Resets local footer stats for the active provider/model; upstream provider cache is not modified. |
|
|
71
|
-
| `/cache-optimizer config footer-mode session\|
|
|
71
|
+
| `/cache-optimizer config footer-mode total\|session\|process` | Persist the footer stats mode. Persistent command configuration overrides the environment variable. |
|
|
72
72
|
| `/cache-optimizer fix` | Auto-repairs safe compat issues for the active model (adaptive thinking, DeepSeek reasoning, OpenAI proxy session affinity). Shows preview + risk warning, requires confirmation. **Only modifies `models.json` after explicit user approval.** |
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
`/cache-optimizer` uses Pi's native Tab completion. Type `/cache-optimizer <Tab>` for the supported subcommands, `/cache-optimizer c<Tab>` for `config`, `/cache-optimizer config <Tab>` for `footer-mode`, and `/cache-optimizer config footer-mode <Tab>` for `total`, `session`, or `process`. Suggestions are prefix-filtered and invalid prefixes are left to Pi's normal fallback behavior.
|
|
75
|
+
|
|
76
|
+
The interactive `/cache-optimizer` menu includes `Footer mode`, where you can choose `total`, `session`, or `process`. `enable` / `disable` are current-process switches. For a persistent opt-out, use environment variables below.
|
|
75
77
|
|
|
76
78
|
## Persistent opt-out
|
|
77
79
|
|
|
@@ -84,21 +86,24 @@ The interactive `/cache-optimizer` menu includes `Footer mode`, where you can ch
|
|
|
84
86
|
|
|
85
87
|
## Footer cache stats mode
|
|
86
88
|
|
|
87
|
-
**v2.7.0+** supports
|
|
89
|
+
**v2.7.0+** supports daily cumulative and conversation-session footer scopes. **v2.7.1** adds the Footer mode option to the interactive `/cache-optimizer` menu. **v2.8.0+** also supports process-scoped counters. The footer defaults to `total`, which shows the provider/model's local counters for the current day across Pi sessions and process restarts. Use either the command or environment variable to select the scope:
|
|
88
90
|
|
|
89
91
|
| Value | Effect |
|
|
90
92
|
|---|---|
|
|
91
93
|
| `total` (default) | Show today's restart-persistent provider/model totals across sessions. Local day rollover resets the counters. |
|
|
92
|
-
| `session` | Show only the current hashed Pi session's counters. A fresh session starts at `0/0`;
|
|
94
|
+
| `session` | Show only the current hashed Pi conversation session's counters. A fresh conversation session starts at `0/0`; restarting Pi while resuming the same session restores that session bucket. |
|
|
95
|
+
|
|
96
|
+
| `process` | Show only counters collected by the current Pi process. It starts at `0/0` after Pi restart or extension reload and is never restored from disk. |
|
|
93
97
|
|
|
94
98
|
Persistent command configuration takes precedence over the environment variable:
|
|
95
99
|
|
|
96
100
|
```text
|
|
97
|
-
/cache-optimizer config footer-mode session
|
|
98
101
|
/cache-optimizer config footer-mode total
|
|
102
|
+
/cache-optimizer config footer-mode session
|
|
103
|
+
/cache-optimizer config footer-mode process
|
|
99
104
|
```
|
|
100
105
|
|
|
101
|
-
The explicit setting is stored in `pi-cache-optimizer-config.json` under Pi's agent directory. If no command override exists, `PI_CACHE_OPTIMIZER_FOOTER_MODE=session|
|
|
106
|
+
The explicit setting is stored in `pi-cache-optimizer-config.json` under Pi's agent directory. If no command override exists, `PI_CACHE_OPTIMIZER_FOOTER_MODE=total|session|process` is used; values are case-insensitive, and missing or invalid values fall back to `total`. To return an existing installation to environment-controlled behavior, manually delete `pi-cache-optimizer-config.json` and run `/reload`.
|
|
102
107
|
|
|
103
108
|
## OpenAI-compatible proxy setup
|
|
104
109
|
|
|
@@ -271,17 +276,17 @@ If only one model should change, use `modelOverrides`:
|
|
|
271
276
|
|
|
272
277
|
## Footer stats
|
|
273
278
|
|
|
274
|
-
Stats are read-only local counters stored in Pi's agent directory (default: `~/.pi/agent/pi-cache-optimizer-stats.json`; custom agent dirs use `PI_CODING_AGENT_DIR`). Both today's provider/model totals and hashed session buckets are maintained. The footer shows daily totals by default,
|
|
279
|
+
Stats are read-only local counters stored in Pi's agent directory (default: `~/.pi/agent/pi-cache-optimizer-stats.json`; custom agent dirs use `PI_CODING_AGENT_DIR`). Both today's provider/model totals and hashed session buckets are maintained. The footer shows daily totals by default, the conversation-session bucket in `session` mode, or the in-memory process bucket in `process` mode. The stats file contains only dates and numeric counters — no API keys, prompts, payloads, headers, responses, or model output. Footer mode configuration is stored separately in `pi-cache-optimizer-config.json`. Process-mode counters are memory-only and are intentionally absent from that file.
|
|
275
280
|
|
|
276
281
|
Pi 0.79+ also includes a built-in footer `CH` marker for the latest prompt cache hit rate. This extension complements that marker with persisted provider/model counters plus proxy compat diagnostics.
|
|
277
282
|
|
|
278
283
|
Example footer:
|
|
279
284
|
|
|
280
285
|
```text
|
|
281
|
-
OpenAI cache 3/10 · 0.002M/0.005M tok (40%) ⚠️ compat
|
|
286
|
+
· OpenAI cache 3/10 · 0.002M/0.005M tok (40%) ⚠️ compat
|
|
282
287
|
```
|
|
283
288
|
|
|
284
|
-
|
|
289
|
+
The leading `· ` is owned by this extension and separates its status from statuses published by other extensions in the same footer. It is present for normal, disabled, router-restored, and warning-suffixed statuses. The stats format remains `<label> <hit requests>/<total requests> · <cached input tokens>/<total input tokens> tok (<token hit rate>)`; `/cache-optimizer stats` output is unchanged. Some adapters may also append `· write <tokens> tok`, and runtime diagnostics may append `⚠️ compat` or `⚠️ integrity`.
|
|
285
290
|
|
|
286
291
|
Supported footer labels include: DS, Claude, OpenAI, Gemini, Kimi, Qwen, GLM, MiniMax, Mimo, Hunyuan, Mistral, Grok, Llama, Nemotron, Cohere, Yi, Doubao, ERNIE, Baichuan, StepFun, Spark, InternLM, Gemma, Phi, Jamba, Solar, Sonar, Nova, Reka, Falcon, DBRX, MPT, StableLM, Aquila, EXAONE, HyperCLOVA, Luminous, Hermes, Granite, Arctic, Pangu, SenseNova, Zhinao, MiniCPM, XVERSE, Orion, OpenChat, Vicuna, Wizard, Zephyr, Dolphin, OpenOrca, Starling, BLOOM, RWKV, and Aya.
|
|
287
292
|
|
package/README.zh-CN.md
CHANGED
|
@@ -68,10 +68,12 @@ Pi 0.79.7 及之后,`pi update` 默认只更新 Pi 本体。若要更新已安
|
|
|
68
68
|
| `/cache-optimizer compat` | 对当前模型显示可复制的 compat 建议(如适用)。 |
|
|
69
69
|
| `/cache-optimizer stats` | 显示当前模型今天的本地 provider/model 统计和近期趋势。 |
|
|
70
70
|
| `/cache-optimizer reset` | 重置当前 provider/model 的本地 footer 统计;不会修改上游 provider 缓存。 |
|
|
71
|
-
| `/cache-optimizer config footer-mode session\|
|
|
71
|
+
| `/cache-optimizer config footer-mode total\|session\|process` | 持久设置 footer 统计模式;持久命令配置优先于环境变量。 |
|
|
72
72
|
| `/cache-optimizer fix` | 为当前模型自动修复安全的 compat 问题(adaptive thinking、DeepSeek reasoning、OpenAI proxy session affinity)。展示预览 + 风险提示,需要用户确认。**仅在用户明确批准后才修改 `models.json`。** |
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
`/cache-optimizer` 使用 Pi 原生 Tab 补全:输入 `/cache-optimizer <Tab>` 查看支持的子命令,输入 `/cache-optimizer c<Tab>` 补全 `config`,输入 `/cache-optimizer config <Tab>` 补全 `footer-mode`,输入 `/cache-optimizer config footer-mode <Tab>` 补全 `total`、`session` 或 `process`。建议会按当前前缀过滤;无效前缀返回空结果,由 Pi 正常回退处理。
|
|
75
|
+
|
|
76
|
+
交互式 `/cache-optimizer` 菜单包含 `Footer mode`,可以选择 `total`、`session` 或 `process`。`enable` / `disable` 是当前进程内开关。若要持久关闭某些能力,请使用下面的环境变量。
|
|
75
77
|
|
|
76
78
|
## 持久 Opt-out
|
|
77
79
|
|
|
@@ -84,21 +86,23 @@ Pi 0.79.7 及之后,`pi update` 默认只更新 Pi 本体。若要更新已安
|
|
|
84
86
|
|
|
85
87
|
## Footer 缓存统计模式
|
|
86
88
|
|
|
87
|
-
**v2.7.0+**
|
|
89
|
+
**v2.7.0+** 支持每日累计和 conversation session 两种 footer 统计范围。**v2.7.1** 在交互式 `/cache-optimizer` 菜单中加入了 Footer mode 选项。**v2.8.0+** 另外支持进程级统计。Footer 默认使用 `total`,显示当前本地日期内、跨 Pi session 和进程重启延续的 provider/model 统计。可以通过命令或环境变量切换显示范围:
|
|
88
90
|
|
|
89
91
|
| 值 | 作用 |
|
|
90
92
|
|---|---|
|
|
91
93
|
| `total`(默认) | 显示今天跨 session 持久延续的 provider/model 累计统计;本地日期切换时重置。 |
|
|
92
|
-
| `session` | 仅显示当前 hashed Pi session 的统计。新 session 从 `0/0`
|
|
94
|
+
| `session` | 仅显示当前 hashed Pi conversation session 的统计。新 conversation session 从 `0/0` 开始;重启 Pi 后如果恢复同一 session,会恢复该 session 桶。 |
|
|
95
|
+
| `process` | 仅显示当前 Pi 进程采集的统计。Pi 重启或 extension reload 后从 `0/0` 开始,且不会从磁盘恢复。 |
|
|
93
96
|
|
|
94
97
|
持久命令配置优先于环境变量:
|
|
95
98
|
|
|
96
99
|
```text
|
|
97
|
-
/cache-optimizer config footer-mode session
|
|
98
100
|
/cache-optimizer config footer-mode total
|
|
101
|
+
/cache-optimizer config footer-mode session
|
|
102
|
+
/cache-optimizer config footer-mode process
|
|
99
103
|
```
|
|
100
104
|
|
|
101
|
-
显式设置保存在 Pi agent 目录下的 `pi-cache-optimizer-config.json`。没有命令覆盖时,读取 `PI_CACHE_OPTIMIZER_FOOTER_MODE=session|
|
|
105
|
+
显式设置保存在 Pi agent 目录下的 `pi-cache-optimizer-config.json`。没有命令覆盖时,读取 `PI_CACHE_OPTIMIZER_FOOTER_MODE=total|session|process`;值不区分大小写,缺失或非法值均回退到 `total`。如需让已有安装重新由环境变量控制,请手动删除 `pi-cache-optimizer-config.json`,然后运行 `/reload`。
|
|
102
106
|
|
|
103
107
|
## OpenAI-compatible 代理配置
|
|
104
108
|
|
|
@@ -271,17 +275,17 @@ Provider 级最小 override:
|
|
|
271
275
|
|
|
272
276
|
## Footer 统计
|
|
273
277
|
|
|
274
|
-
统计是只读本地计数,保存在 Pi agent 目录(默认 `~/.pi/agent/pi-cache-optimizer-stats.json`;自定义 agent 目录使用 `PI_CODING_AGENT_DIR`)。扩展同时维护当天的 provider/model totals 和 hashed session buckets。Footer 默认显示每日 totals;选择 `session` 模式后显示当前 session
|
|
278
|
+
统计是只读本地计数,保存在 Pi agent 目录(默认 `~/.pi/agent/pi-cache-optimizer-stats.json`;自定义 agent 目录使用 `PI_CODING_AGENT_DIR`)。扩展同时维护当天的 provider/model totals 和 hashed session buckets。Footer 默认显示每日 totals;选择 `session` 模式后显示当前 conversation session 桶,选择 `process` 模式后显示当前进程内存桶。统计文件只包含日期和数字计数,不包含 API key、prompt、payload、headers、响应或模型输出。Footer 模式配置单独保存在 `pi-cache-optimizer-config.json`。Process 模式统计只存在内存中,不会写入该文件。
|
|
275
279
|
|
|
276
280
|
Pi 0.79+ 已内置 footer `CH` 标记,用于显示最近一次 prompt cache hit rate。本扩展在此基础上补充持久化的 provider/model 计数,以及代理 compat 诊断。
|
|
277
281
|
|
|
278
282
|
示例 footer:
|
|
279
283
|
|
|
280
284
|
```text
|
|
281
|
-
OpenAI cache 3/10 · 0.002M/0.005M tok (40%) ⚠️ compat
|
|
285
|
+
· OpenAI cache 3/10 · 0.002M/0.005M tok (40%) ⚠️ compat
|
|
282
286
|
```
|
|
283
287
|
|
|
284
|
-
|
|
288
|
+
开头的 `· ` 由本扩展负责,用于把本扩展状态与同一 footer 中其他扩展发布的状态隔开。普通、disabled、router 恢复以及带 warning 的状态都会保留此前缀。统计格式仍是 `<label> <命中请求数>/<总请求数> · <cached input tokens>/<total input tokens> tok (<token 命中率>)`;`/cache-optimizer stats` 的输出不变。部分 adapter 还可能追加 `· write <tokens> tok`,运行时诊断可能追加 `⚠️ compat` 或 `⚠️ integrity`。
|
|
285
289
|
|
|
286
290
|
支持的 footer label 包括:DS、Claude、OpenAI、Gemini、Kimi、Qwen、GLM、MiniMax、Mimo、Hunyuan、Mistral、Grok、Llama、Nemotron、Cohere、Yi、Doubao、ERNIE、Baichuan、StepFun、Spark、InternLM、Gemma、Phi、Jamba、Solar、Sonar、Nova、Reka、Falcon、DBRX、MPT、StableLM、Aquila、EXAONE、HyperCLOVA、Luminous、Hermes、Granite、Arctic、Pangu、SenseNova、Zhinao、MiniCPM、XVERSE、Orion、OpenChat、Vicuna、Wizard、Zephyr、Dolphin、OpenOrca、Starling、BLOOM、RWKV、Aya。
|
|
287
291
|
|
package/index.ts
CHANGED
|
@@ -103,7 +103,7 @@ const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
|
|
|
103
103
|
const NO_SKILL_COMPRESSION_ENV = "PI_CACHE_OPTIMIZER_NO_SKILL_COMPRESSION";
|
|
104
104
|
const NO_PROMPT_REWRITE_ENV = "PI_CACHE_OPTIMIZER_NO_PROMPT_REWRITE";
|
|
105
105
|
const FOOTER_MODE_ENV = "PI_CACHE_OPTIMIZER_FOOTER_MODE";
|
|
106
|
-
type FooterStatsMode = "session" | "total";
|
|
106
|
+
type FooterStatsMode = "session" | "total" | "process";
|
|
107
107
|
type FooterStatsModeSource = "config" | "env" | "default";
|
|
108
108
|
type PersistedCacheOptimizerConfigV1 = {
|
|
109
109
|
version: 1;
|
|
@@ -1143,7 +1143,83 @@ function isEnabledEnv(value: string | undefined): boolean {
|
|
|
1143
1143
|
function parseFooterStatsMode(value: unknown): FooterStatsMode | undefined {
|
|
1144
1144
|
if (typeof value !== "string") return undefined;
|
|
1145
1145
|
const normalized = value.trim().toLowerCase();
|
|
1146
|
-
return normalized === "session" || normalized === "total" ? normalized : undefined;
|
|
1146
|
+
return normalized === "session" || normalized === "total" || normalized === "process" ? normalized : undefined;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
type CommandCompletionItem = {
|
|
1150
|
+
value: string;
|
|
1151
|
+
label: string;
|
|
1152
|
+
description?: string;
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
const CACHE_OPTIMIZER_COMMANDS = [
|
|
1156
|
+
"enable",
|
|
1157
|
+
"disable",
|
|
1158
|
+
"doctor",
|
|
1159
|
+
"stats",
|
|
1160
|
+
"config",
|
|
1161
|
+
"compat",
|
|
1162
|
+
"reset",
|
|
1163
|
+
"fix",
|
|
1164
|
+
] as const;
|
|
1165
|
+
const CACHE_OPTIMIZER_CONFIG_ARGUMENTS = ["footer-mode"] as const;
|
|
1166
|
+
const CACHE_OPTIMIZER_FOOTER_MODES = ["total", "session", "process"] as const;
|
|
1167
|
+
|
|
1168
|
+
function filterCommandCompletionItems(
|
|
1169
|
+
values: readonly string[],
|
|
1170
|
+
prefix: string,
|
|
1171
|
+
argumentPath = "",
|
|
1172
|
+
): CommandCompletionItem[] | null {
|
|
1173
|
+
const normalizedPrefix = prefix.trim().toLowerCase();
|
|
1174
|
+
const matches = values
|
|
1175
|
+
.filter((value) => value.startsWith(normalizedPrefix))
|
|
1176
|
+
.map((value) => ({
|
|
1177
|
+
// Pi replaces the complete argumentPrefix when applying a command
|
|
1178
|
+
// completion, so nested suggestions must include their full path.
|
|
1179
|
+
value: argumentPath ? `${argumentPath} ${value}` : value,
|
|
1180
|
+
label: value,
|
|
1181
|
+
}));
|
|
1182
|
+
return matches.length > 0 ? matches : null;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function getCacheOptimizerArgumentCompletions(argumentPrefix: string): CommandCompletionItem[] | null {
|
|
1186
|
+
if (typeof argumentPrefix !== "string") return null;
|
|
1187
|
+
const trimmed = argumentPrefix.trim();
|
|
1188
|
+
const parts = trimmed ? trimmed.split(/\s+/) : [];
|
|
1189
|
+
|
|
1190
|
+
if (parts.length === 0) {
|
|
1191
|
+
return filterCommandCompletionItems(CACHE_OPTIMIZER_COMMANDS, "");
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (parts.length === 1) {
|
|
1195
|
+
const subcommandPrefix = parts[0].toLowerCase();
|
|
1196
|
+
// `config` is the primary `c` completion; `compat` remains available
|
|
1197
|
+
// through its more specific `co` prefix. Surrounding whitespace is
|
|
1198
|
+
// ignored so ` c ` behaves the same as `c`.
|
|
1199
|
+
if (subcommandPrefix === "c") {
|
|
1200
|
+
return [{ value: "config", label: "config" }];
|
|
1201
|
+
}
|
|
1202
|
+
if (subcommandPrefix === "config") {
|
|
1203
|
+
return filterCommandCompletionItems(CACHE_OPTIMIZER_CONFIG_ARGUMENTS, "", "config");
|
|
1204
|
+
}
|
|
1205
|
+
return filterCommandCompletionItems(CACHE_OPTIMIZER_COMMANDS, parts[0]);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
if (parts[0].toLowerCase() !== "config") return null;
|
|
1209
|
+
|
|
1210
|
+
if (parts.length === 2) {
|
|
1211
|
+
const nestedPrefix = parts[1].toLowerCase();
|
|
1212
|
+
if (nestedPrefix === "footer-mode") {
|
|
1213
|
+
return filterCommandCompletionItems(CACHE_OPTIMIZER_FOOTER_MODES, "", "config footer-mode");
|
|
1214
|
+
}
|
|
1215
|
+
return filterCommandCompletionItems(CACHE_OPTIMIZER_CONFIG_ARGUMENTS, parts[1], "config");
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
if (parts.length === 3 && parts[1].toLowerCase() === "footer-mode") {
|
|
1219
|
+
return filterCommandCompletionItems(CACHE_OPTIMIZER_FOOTER_MODES, parts[2], "config footer-mode");
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
return null;
|
|
1147
1223
|
}
|
|
1148
1224
|
|
|
1149
1225
|
function parsePersistedCacheOptimizerConfig(value: unknown): PersistedCacheOptimizerConfigV1 | undefined {
|
|
@@ -3666,6 +3742,11 @@ function formatCacheStats(adapter: CacheProviderAdapter, stats: CacheStats): str
|
|
|
3666
3742
|
return `${adapter.label} ${stats.hitRequests}/${stats.totalRequests} · ${formatTokenCount(stats.cachedInputTokens)}/${formatTokenCount(stats.totalInputTokens)} tok${percent}${writeText}`;
|
|
3667
3743
|
}
|
|
3668
3744
|
|
|
3745
|
+
function prefixFooterStatus(statusText: string | undefined): string | undefined {
|
|
3746
|
+
if (!statusText || statusText.startsWith("· ")) return statusText;
|
|
3747
|
+
return `· ${statusText}`;
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3669
3750
|
/**
|
|
3670
3751
|
* Compute a hit-ratio percentage string for a value between 0 and 1.
|
|
3671
3752
|
* Returns e.g. "75%", "0%", "100%", or "N/A" for zero total.
|
|
@@ -3897,8 +3978,10 @@ function selectFooterStatsForModel(
|
|
|
3897
3978
|
statsByModel: Record<string, CacheStats>,
|
|
3898
3979
|
totalsByModel: Record<string, CacheStats>,
|
|
3899
3980
|
model: { provider: string; id: string },
|
|
3981
|
+
processByModel: Record<string, CacheStats> = {},
|
|
3900
3982
|
): CacheStats | undefined {
|
|
3901
3983
|
if (mode === "total") return totalsByModel[modelKey(model)];
|
|
3984
|
+
if (mode === "process") return processByModel[modelKey(model)];
|
|
3902
3985
|
if (!sessionHash) return undefined;
|
|
3903
3986
|
return statsByModel[makeSessionModelKey(sessionHash, model.provider, model.id)];
|
|
3904
3987
|
}
|
|
@@ -3909,6 +3992,7 @@ function buildExactRouterStatusEntry(
|
|
|
3909
3992
|
lastRoutedModel: PersistedRoutedModelRef | undefined,
|
|
3910
3993
|
totalsByModel: Record<string, CacheStats> = {},
|
|
3911
3994
|
mode: FooterStatsMode = "total",
|
|
3995
|
+
processByModel: Record<string, CacheStats> = {},
|
|
3912
3996
|
): { model: PiModel; adapter: CacheProviderAdapter; stats: CacheStats } | undefined {
|
|
3913
3997
|
if (!sessionHash || !lastRoutedModel) return undefined;
|
|
3914
3998
|
|
|
@@ -3919,7 +4003,7 @@ function buildExactRouterStatusEntry(
|
|
|
3919
4003
|
return {
|
|
3920
4004
|
model,
|
|
3921
4005
|
adapter,
|
|
3922
|
-
stats: selectFooterStatsForModel(mode, sessionHash, statsByModel, totalsByModel, model) ?? emptyCacheStats(),
|
|
4006
|
+
stats: selectFooterStatsForModel(mode, sessionHash, statsByModel, totalsByModel, model, processByModel) ?? emptyCacheStats(),
|
|
3923
4007
|
};
|
|
3924
4008
|
}
|
|
3925
4009
|
|
|
@@ -3928,9 +4012,12 @@ function findBestRouterModelStats(
|
|
|
3928
4012
|
sessionHash: string | undefined,
|
|
3929
4013
|
statsByModel: Record<string, CacheStats>,
|
|
3930
4014
|
totalsByModel: Record<string, CacheStats>,
|
|
4015
|
+
processByModel: Record<string, CacheStats> = {},
|
|
3931
4016
|
): { model: PiModel; adapter: CacheProviderAdapter; stats: CacheStats } | undefined {
|
|
3932
4017
|
const entries = mode === "total"
|
|
3933
4018
|
? Object.entries(totalsByModel)
|
|
4019
|
+
: mode === "process"
|
|
4020
|
+
? Object.entries(processByModel)
|
|
3934
4021
|
: sessionHash
|
|
3935
4022
|
? Object.entries(statsByModel)
|
|
3936
4023
|
.filter(([key]) => key.startsWith(`${sessionHash}:`))
|
|
@@ -6647,6 +6734,8 @@ export const __internals_for_tests = {
|
|
|
6647
6734
|
// Cache stats helpers (module-level, usable from verify script)
|
|
6648
6735
|
addUsageToCacheStats,
|
|
6649
6736
|
formatCacheStats,
|
|
6737
|
+
prefixFooterStatus,
|
|
6738
|
+
getCacheOptimizerArgumentCompletions,
|
|
6650
6739
|
emptyCacheStats,
|
|
6651
6740
|
emptyAllCacheStats,
|
|
6652
6741
|
parseCacheStats,
|
|
@@ -6752,6 +6841,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6752
6841
|
const openAISdkHeader403Models = new Set<string>();
|
|
6753
6842
|
const warnedOpenAISdkHeader403Models = new Set<string>();
|
|
6754
6843
|
let cacheStatsByModel: Record<string, CacheStats> = {};
|
|
6844
|
+
let cacheStatsProcessByModel: Record<string, CacheStats> = {};
|
|
6755
6845
|
let cacheStatsTotalsByModel: Record<string, CacheStats> = {};
|
|
6756
6846
|
let cacheStatsLegacyFamily: Partial<Record<CacheProviderId, CacheStats>> = emptyAllCacheStats();
|
|
6757
6847
|
let lastStatusText: string | undefined;
|
|
@@ -6920,6 +7010,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
6920
7010
|
return created;
|
|
6921
7011
|
}
|
|
6922
7012
|
|
|
7013
|
+
/** Get or create the current-process provider/model stats entry. */
|
|
7014
|
+
function getOrCreateProcessStatsForModel(model: PiModel): CacheStats {
|
|
7015
|
+
const key = modelKey(model);
|
|
7016
|
+
const existing = cacheStatsProcessByModel[key];
|
|
7017
|
+
if (existing) return existing;
|
|
7018
|
+
|
|
7019
|
+
const created = emptyCacheStats();
|
|
7020
|
+
cacheStatsProcessByModel[key] = created;
|
|
7021
|
+
return created;
|
|
7022
|
+
}
|
|
7023
|
+
|
|
6923
7024
|
/** Get or create the cumulative provider/model stats entry shown in the footer. */
|
|
6924
7025
|
function getOrCreateTotalStatsForModel(model: PiModel): CacheStats {
|
|
6925
7026
|
const key = modelKey(model);
|
|
@@ -6937,6 +7038,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6937
7038
|
if (modelKeyFromSessionScoped(key) === displayKey) delete cacheStatsByModel[key];
|
|
6938
7039
|
}
|
|
6939
7040
|
delete cacheStatsTotalsByModel[displayKey];
|
|
7041
|
+
delete cacheStatsProcessByModel[displayKey];
|
|
6940
7042
|
pendingDeletedTotalModelKeys.add(displayKey);
|
|
6941
7043
|
for (const key of Array.from(recentSamplesByModelKey.keys())) {
|
|
6942
7044
|
if (modelKeyFromSessionScoped(key) === displayKey) recentSamplesByModelKey.delete(key);
|
|
@@ -6949,6 +7051,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6949
7051
|
for (const key of Object.keys(cacheStatsByModel)) {
|
|
6950
7052
|
if (key.startsWith(prefix)) delete cacheStatsByModel[key];
|
|
6951
7053
|
}
|
|
7054
|
+
cacheStatsProcessByModel = {};
|
|
6952
7055
|
cacheStatsTotalsByModel = {};
|
|
6953
7056
|
replacePersistedTotalsOnNextWrite = true;
|
|
6954
7057
|
for (const key of Array.from(recentSamplesByModelKey.keys())) {
|
|
@@ -7051,6 +7154,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
7051
7154
|
}
|
|
7052
7155
|
|
|
7053
7156
|
// Roll over cumulative provider/model totals.
|
|
7157
|
+
for (const key of Object.keys(cacheStatsProcessByModel)) {
|
|
7158
|
+
const stats = cacheStatsProcessByModel[key];
|
|
7159
|
+
if (stats && stats.day !== day) {
|
|
7160
|
+
cacheStatsProcessByModel[key] = emptyCacheStats(day);
|
|
7161
|
+
changed = true;
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
7164
|
+
|
|
7054
7165
|
for (const key of Object.keys(cacheStatsTotalsByModel)) {
|
|
7055
7166
|
const stats = cacheStatsTotalsByModel[key];
|
|
7056
7167
|
if (stats && stats.day !== day) {
|
|
@@ -7091,6 +7202,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7091
7202
|
persisted,
|
|
7092
7203
|
currentSessionHashSet ? currentSessionHash : undefined,
|
|
7093
7204
|
);
|
|
7205
|
+
cacheStatsProcessByModel = {};
|
|
7094
7206
|
cacheStatsTotalsByModel = persisted?.totalsByModel ?? {};
|
|
7095
7207
|
cacheStatsLegacyFamily = persisted?.legacyFamily ?? emptyAllCacheStats();
|
|
7096
7208
|
lastActualRoutedModel = currentSessionHashSet
|
|
@@ -7109,6 +7221,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7109
7221
|
persisted,
|
|
7110
7222
|
currentSessionHashSet ? currentSessionHash : undefined,
|
|
7111
7223
|
);
|
|
7224
|
+
cacheStatsProcessByModel = {};
|
|
7112
7225
|
cacheStatsTotalsByModel = persisted?.totalsByModel ?? {};
|
|
7113
7226
|
cacheStatsLegacyFamily = persisted?.legacyFamily ?? emptyAllCacheStats();
|
|
7114
7227
|
lastActualRoutedModel = currentSessionHashSet
|
|
@@ -7143,11 +7256,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
7143
7256
|
lastActualRoutedModel,
|
|
7144
7257
|
cacheStatsTotalsByModel,
|
|
7145
7258
|
mode,
|
|
7259
|
+
cacheStatsProcessByModel,
|
|
7146
7260
|
) ?? findBestRouterModelStats(
|
|
7147
7261
|
mode,
|
|
7148
7262
|
sessionHash,
|
|
7149
7263
|
cacheStatsByModel,
|
|
7150
7264
|
cacheStatsTotalsByModel,
|
|
7265
|
+
cacheStatsProcessByModel,
|
|
7151
7266
|
);
|
|
7152
7267
|
if (realEntry) {
|
|
7153
7268
|
const statsText = formatCacheStats(realEntry.adapter, realEntry.stats);
|
|
@@ -7161,7 +7276,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7161
7276
|
// Footer mode defaults to daily provider/model totals. Users may select
|
|
7162
7277
|
// current-session counters through persistent command config or the env var.
|
|
7163
7278
|
const stats = displayModel
|
|
7164
|
-
? selectFooterStatsForModel(mode, sessionHash, cacheStatsByModel, cacheStatsTotalsByModel, displayModel)
|
|
7279
|
+
? selectFooterStatsForModel(mode, sessionHash, cacheStatsByModel, cacheStatsTotalsByModel, displayModel, cacheStatsProcessByModel)
|
|
7165
7280
|
: undefined;
|
|
7166
7281
|
const statsText = formatCacheStats(adapter, stats ?? emptyCacheStats());
|
|
7167
7282
|
statusText = runtimeOptimizerEnabled ? statsText : `Cache Optimizer disabled · ${statsText}`;
|
|
@@ -7208,6 +7323,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7208
7323
|
}
|
|
7209
7324
|
}
|
|
7210
7325
|
|
|
7326
|
+
statusText = prefixFooterStatus(statusText);
|
|
7211
7327
|
if (statusText === lastStatusText) return;
|
|
7212
7328
|
|
|
7213
7329
|
lastStatusText = statusText;
|
|
@@ -7584,11 +7700,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
7584
7700
|
|
|
7585
7701
|
await rollOverStatsIfNeeded(ctx);
|
|
7586
7702
|
|
|
7587
|
-
// Update
|
|
7588
|
-
//
|
|
7703
|
+
// Update session, process-local, and cumulative buckets for the actual
|
|
7704
|
+
// routed model. The process bucket is intentionally never persisted.
|
|
7589
7705
|
if (statsModel) {
|
|
7590
7706
|
const sk = sessionModelKey(statsModel);
|
|
7591
7707
|
addUsageToCacheStats(getOrCreateStatsByModelKey(sk), usage);
|
|
7708
|
+
addUsageToCacheStats(getOrCreateProcessStatsForModel(statsModel), usage);
|
|
7592
7709
|
addUsageToCacheStats(getOrCreateTotalStatsForModel(statsModel), usage);
|
|
7593
7710
|
} else {
|
|
7594
7711
|
addUsageToCacheStats(getStatsForModel(undefined, adapter), usage);
|
|
@@ -7607,13 +7724,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
7607
7724
|
// with low-hit diagnosis
|
|
7608
7725
|
// stats — show active model stats bucket, recent trend, usage
|
|
7609
7726
|
// compat — show compat suggestion with file path
|
|
7610
|
-
// config footer-mode session|
|
|
7727
|
+
// config footer-mode total|session|process — persist footer mode override
|
|
7611
7728
|
// fix — auto-fix compat issues (writes models.json, requires UI)
|
|
7612
7729
|
// reset — reset current provider/model footer stats bucket (local only)
|
|
7613
7730
|
// (no args) — interactive menu (with UI) or help summary
|
|
7614
7731
|
// ────────────────────────────────────────────────────────────────
|
|
7615
7732
|
pi.registerCommand("cache-optimizer", {
|
|
7616
7733
|
description: "Configure and diagnose Pi cache behavior",
|
|
7734
|
+
getArgumentCompletions: getCacheOptimizerArgumentCompletions,
|
|
7617
7735
|
handler: async (args: string, cmdCtx) => {
|
|
7618
7736
|
syncSessionHash(cmdCtx);
|
|
7619
7737
|
const selectedModel = cmdCtx.model;
|
|
@@ -7662,10 +7780,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
7662
7780
|
} else if (subcommand === "config") {
|
|
7663
7781
|
const configKey = commandParts[1];
|
|
7664
7782
|
const requestedMode = commandParts[2];
|
|
7665
|
-
if (configKey !== "footer-mode" || !requestedMode || !["session", "total"].includes(requestedMode)) {
|
|
7783
|
+
if (configKey !== "footer-mode" || !requestedMode || !["session", "total", "process"].includes(requestedMode)) {
|
|
7666
7784
|
const resolved = resolveFooterStatsMode(persistedFooterStatsMode);
|
|
7667
7785
|
cmdCtx.ui.notify(
|
|
7668
|
-
`Usage: /cache-optimizer config footer-mode session|
|
|
7786
|
+
`Usage: /cache-optimizer config footer-mode total|session|process\n` +
|
|
7669
7787
|
`Current footer mode: ${resolved.mode} (${resolved.source})`,
|
|
7670
7788
|
"info",
|
|
7671
7789
|
);
|
|
@@ -8074,7 +8192,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8074
8192
|
"Stats — Show cache stats and trend",
|
|
8075
8193
|
"Compat — Show compat suggestion",
|
|
8076
8194
|
"Fix — Auto-fix compat issues (writes models.json)",
|
|
8077
|
-
"Footer mode — Choose total or
|
|
8195
|
+
"Footer mode — Choose total, session, or process stats",
|
|
8078
8196
|
"Reset — Reset local provider/model stats",
|
|
8079
8197
|
"Cancel",
|
|
8080
8198
|
];
|
|
@@ -8257,13 +8375,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
8257
8375
|
);
|
|
8258
8376
|
}
|
|
8259
8377
|
} else if (choice === menuOptions[6]) {
|
|
8260
|
-
const modeOptions = ["total — Daily cumulative totals (default)", "session — Current Pi session only", "Cancel"];
|
|
8378
|
+
const modeOptions = ["total — Daily cumulative totals (default)", "session — Current Pi conversation session", "process — Current Pi process only", "Cancel"];
|
|
8261
8379
|
const modeChoice = await cmdCtx.ui.select("Footer cache stats mode", modeOptions);
|
|
8262
8380
|
const nextMode = modeChoice === modeOptions[0]
|
|
8263
8381
|
? "total"
|
|
8264
8382
|
: modeChoice === modeOptions[1]
|
|
8265
8383
|
? "session"
|
|
8266
|
-
:
|
|
8384
|
+
: modeChoice === modeOptions[2]
|
|
8385
|
+
? "process"
|
|
8386
|
+
: undefined;
|
|
8267
8387
|
if (nextMode) {
|
|
8268
8388
|
try {
|
|
8269
8389
|
await writePersistedFooterMode(nextMode);
|
|
@@ -8313,7 +8433,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8313
8433
|
diagnosis.push(" doctor — Show current model/provider/api/baseUrl/compat and low-hit diagnosis");
|
|
8314
8434
|
diagnosis.push(" stats — Show active model stats bucket and recent trend");
|
|
8315
8435
|
diagnosis.push(" compat — Show compat suggestion with edit location");
|
|
8316
|
-
diagnosis.push(" config footer-mode session|
|
|
8436
|
+
diagnosis.push(" config footer-mode total|session|process — Persist the footer stats mode");
|
|
8317
8437
|
diagnosis.push(" fix — Auto-fix compat issues (writes models.json, requires UI)");
|
|
8318
8438
|
diagnosis.push(" reset — Reset local provider/model stats for current model (does not affect upstream)");
|
|
8319
8439
|
diagnosis.push("");
|
package/package.json
CHANGED