pi-cache-optimizer 2.6.25 → 2.7.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 +34 -12
- package/README.zh-CN.md +34 -12
- package/index.ts +202 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ Pi extension for improving provider-side KV / prompt cache hit rates. It keeps s
|
|
|
16
16
|
- [Install](#install)
|
|
17
17
|
- [Commands](#commands)
|
|
18
18
|
- [Persistent opt-out](#persistent-opt-out)
|
|
19
|
+
- [Footer cache stats mode](#footer-cache-stats-mode)
|
|
19
20
|
- [OpenAI-compatible proxy setup](#openai-compatible-proxy-setup)
|
|
20
21
|
- [Adaptive thinking models](#adaptive-thinking-models)
|
|
21
22
|
- [Auto-repair with `/cache-optimizer fix`](#auto-repair-with-cache-optimizer-fix)
|
|
@@ -30,10 +31,10 @@ Pi extension for improving provider-side KV / prompt cache hit rates. It keeps s
|
|
|
30
31
|
- Reorders uniquely identifiable stable system-prompt content before dynamic context. If the same candidate appears more than once (for example, quoted inside dynamic context), it is left unchanged to avoid removing the wrong occurrence.
|
|
31
32
|
- Compresses Pi skill listings and strips session-overview churn.
|
|
32
33
|
- Requests long cache retention when Pi/provider compat supports it.
|
|
33
|
-
- Adds a session-id `prompt_cache_key` fallback for `openai-completions` / `openai-responses` payloads when no effective key exists, including Pi's built-in `llama.cpp` provider as Pi 0.82 core does.
|
|
34
|
+
- Adds a session-id `prompt_cache_key` fallback for `openai-completions` / `openai-responses` payloads when no effective key exists, including Pi's built-in `llama.cpp` provider as Pi 0.82+ core does.
|
|
34
35
|
- Warns once for third-party OpenAI-compatible proxies missing cache/session-affinity compat flags.
|
|
35
|
-
- Detects adaptive-thinking compat for Claude (opus-4.6
|
|
36
|
-
- Shows
|
|
36
|
+
- Detects adaptive-thinking compat for Claude (opus-4.6+ including Opus 5, sonnet-4.6+ including Sonnet 5, fable-5+) and Kimi Coding K3 / `kimi-for-coding` custom channels.
|
|
37
|
+
- Shows daily cumulative provider/model footer stats by default, with an opt-in current-session display mode.
|
|
37
38
|
- Supports optional router-extension integration through versioned global protocols (`Symbol.for("pi.routing.registry.v1")` and `Symbol.for("pi.cache.hints.v1")`) without importing router packages.
|
|
38
39
|
|
|
39
40
|
Caching is provider-side and best-effort. Third-party proxies and router extensions can still hide cache usage, reject unsupported parameters, or route requests across multiple upstreams.
|
|
@@ -54,6 +55,8 @@ Run `/reload` in Pi after install/update/remove so extension hooks refresh.
|
|
|
54
55
|
|
|
55
56
|
On Pi 0.79.7 and newer, `pi update` updates Pi itself only. To update installed Pi packages such as this extension, run `pi update --extensions` (packages only) or `pi update --all` (Pi + packages).
|
|
56
57
|
|
|
58
|
+
This extension is validated against Pi 0.83.0 and remains designed for Pi 0.82+. It uses the existing extension hooks, `getAgentDir()`, and prompt options shared by those versions; it does not depend on Pi 0.83-only APIs such as `ctx.scopedModels` or the bundled TypeBox 1.3 aliases.
|
|
59
|
+
|
|
57
60
|
## Commands
|
|
58
61
|
|
|
59
62
|
| Command | Effect |
|
|
@@ -65,9 +68,10 @@ On Pi 0.79.7 and newer, `pi update` updates Pi itself only. To update installed
|
|
|
65
68
|
| `/cache-optimizer compat` | Shows copyable compat advice for the active model, if applicable. |
|
|
66
69
|
| `/cache-optimizer stats` | Shows today's local provider/model counters and recent trend for the active model. |
|
|
67
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\|total` | Persist the footer stats mode. Persistent command configuration overrides the environment variable. |
|
|
68
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.** |
|
|
69
73
|
|
|
70
|
-
`enable` / `disable` are current-process switches. For a persistent opt-out, use environment variables below.
|
|
74
|
+
The interactive `/cache-optimizer` menu includes `Footer mode`, where you can choose `total` or `session`. `enable` / `disable` are current-process switches. For a persistent opt-out, use environment variables below.
|
|
71
75
|
|
|
72
76
|
## Persistent opt-out
|
|
73
77
|
|
|
@@ -78,11 +82,29 @@ On Pi 0.79.7 and newer, `pi update` updates Pi itself only. To update installed
|
|
|
78
82
|
| `PI_CACHE_OPTIMIZER_NO_OPENAI_CACHE_KEY=1` | Disable the OpenAI-compatible `prompt_cache_key` fallback. Preferred explicit opt-out. |
|
|
79
83
|
| `PI_CACHE_OPTIMIZER_OPENAI_CACHE_KEY=0` | Disable the same fallback via the legacy inverse switch. Values `0`, `false`, `no`, or `off` disable it. |
|
|
80
84
|
|
|
85
|
+
## Footer cache stats mode
|
|
86
|
+
|
|
87
|
+
**v2.7.0+** supports both daily cumulative and current-session footer scopes. **v2.7.1** adds the Footer mode option to the interactive `/cache-optimizer` menu. 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
|
+
|
|
89
|
+
| Value | Effect |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `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`; `/reload` in the same session restores that session bucket. |
|
|
93
|
+
|
|
94
|
+
Persistent command configuration takes precedence over the environment variable:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
/cache-optimizer config footer-mode session
|
|
98
|
+
/cache-optimizer config footer-mode total
|
|
99
|
+
```
|
|
100
|
+
|
|
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|total` 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
|
+
|
|
81
103
|
## OpenAI-compatible proxy setup
|
|
82
104
|
|
|
83
105
|
Third-party `openai-completions` proxies (LiteLLM / OneAPI / NewAPI / OpenRouter-like channels) often route one session across multiple upstream backends. That splits provider-side prompt caches.
|
|
84
106
|
|
|
85
|
-
Pi 0.81+ also has a built-in `llama.cpp` provider using an OpenAI-shaped transport. Pi 0.82 core generates a session `prompt_cache_key` for it when cache retention is enabled, so this extension preserves that key and may add the same conservative fallback when missing. The built-in provider's explicit compat fingerprint is excluded from generic proxy routing/session-affinity advice, but a custom or overridden provider that merely reuses the id `llama.cpp` is treated like any other OpenAI-compatible channel. `prompt_cache_retention` remains subject to the normal safety rule: keep it only for official OpenAI or an explicit effective `supportsLongCacheRetention: true` opt-in in `models.json`; otherwise strip it before sending. Effective values follow Pi's precedence: `modelOverrides[modelId].compat` first, then the matching `models[].compat`, then provider-level `compat`. An explicit `false` at a higher layer overrides `true` below it.
|
|
107
|
+
Pi 0.81+ also has a built-in `llama.cpp` provider using an OpenAI-shaped transport. Pi 0.82+ core generates a session `prompt_cache_key` for it when cache retention is enabled, so this extension preserves that key and may add the same conservative fallback when missing. The built-in provider's explicit compat fingerprint is excluded from generic proxy routing/session-affinity advice, but a custom or overridden provider that merely reuses the id `llama.cpp` is treated like any other OpenAI-compatible channel. `prompt_cache_retention` remains subject to the normal safety rule: keep it only for official OpenAI or an explicit effective `supportsLongCacheRetention: true` opt-in in `models.json`; otherwise strip it before sending. Effective values follow Pi's precedence: `modelOverrides[modelId].compat` first, then the matching `models[].compat`, then provider-level `compat`. An explicit `false` at a higher layer overrides `true` below it.
|
|
86
108
|
|
|
87
109
|
For real proxies, start with session affinity:
|
|
88
110
|
|
|
@@ -123,7 +145,7 @@ Some proxies rewrite or insert hidden 5-minute breakpoints after Pi's request ho
|
|
|
123
145
|
|
|
124
146
|
## Adaptive thinking models
|
|
125
147
|
|
|
126
|
-
Claude models from opus-4.6 / sonnet-4.6 (including Sonnet 5) / fable-5 onwards require `forceAdaptiveThinking: true` in compat. Kimi Coding K3 (`k3`) and `kimi-for-coding` also use adaptive thinking and need `allowEmptySignature: true` so replayed empty-signature thinking blocks remain valid. Without the required compat, Pi may send a legacy thinking payload or replay thinking incorrectly.
|
|
148
|
+
Claude models from opus-4.6 / sonnet-4.6 (including Opus 5 and Sonnet 5) / fable-5 onwards require `forceAdaptiveThinking: true` in compat. Kimi Coding K3 (`k3`) and `kimi-for-coding` also use adaptive thinking and need `allowEmptySignature: true` so replayed empty-signature thinking blocks remain valid. Without the required compat, Pi may send a legacy thinking payload or replay thinking incorrectly. Pi 0.83.0's native Opus 5 catalog is covered by the same adaptive-thinking detection; custom `anthropic-messages` channels still need the compat flag when Pi does not provide it.
|
|
127
149
|
|
|
128
150
|
Pi's built-in catalog already sets this flag for official models. Custom channels in `models.json` that override these models must include the flag:
|
|
129
151
|
|
|
@@ -249,7 +271,7 @@ If only one model should change, use `modelOverrides`:
|
|
|
249
271
|
|
|
250
272
|
## Footer stats
|
|
251
273
|
|
|
252
|
-
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`)
|
|
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, or the current session bucket when `session` mode is selected. 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`.
|
|
253
275
|
|
|
254
276
|
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.
|
|
255
277
|
|
|
@@ -378,13 +400,13 @@ When the query matches the current session/route, `hints` may contain `systemPro
|
|
|
378
400
|
pi remove npm:pi-cache-optimizer
|
|
379
401
|
```
|
|
380
402
|
|
|
381
|
-
Then run `/reload` or restart Pi. Optional local
|
|
403
|
+
Then run `/reload` or restart Pi. Optional local state cleanup (if you use `PI_CODING_AGENT_DIR`, delete the same files from that directory instead):
|
|
382
404
|
|
|
383
|
-
| Platform | Delete local
|
|
405
|
+
| Platform | Delete local state files |
|
|
384
406
|
|---|---|
|
|
385
|
-
| Linux / macOS / WSL | `rm -f ~/.pi/agent/pi-cache-optimizer-stats.json ~/.pi/agent/deepseek-cache-optimizer-stats.json` |
|
|
386
|
-
| Windows PowerShell | `Remove-Item -Force "$env:USERPROFILE\.pi\agent\pi-cache-optimizer-stats.json", "$env:USERPROFILE\.pi\agent\deepseek-cache-optimizer-stats.json" -ErrorAction SilentlyContinue` |
|
|
387
|
-
| Windows Command Prompt | `del /f /q "%USERPROFILE%\.pi\agent\pi-cache-optimizer-stats.json" "%USERPROFILE%\.pi\agent\deepseek-cache-optimizer-stats.json" 2>nul` |
|
|
407
|
+
| Linux / macOS / WSL | `rm -f ~/.pi/agent/pi-cache-optimizer-stats.json ~/.pi/agent/pi-cache-optimizer-config.json ~/.pi/agent/deepseek-cache-optimizer-stats.json` |
|
|
408
|
+
| Windows PowerShell | `Remove-Item -Force "$env:USERPROFILE\.pi\agent\pi-cache-optimizer-stats.json", "$env:USERPROFILE\.pi\agent\pi-cache-optimizer-config.json", "$env:USERPROFILE\.pi\agent\deepseek-cache-optimizer-stats.json" -ErrorAction SilentlyContinue` |
|
|
409
|
+
| Windows Command Prompt | `del /f /q "%USERPROFILE%\.pi\agent\pi-cache-optimizer-stats.json" "%USERPROFILE%\.pi\agent\pi-cache-optimizer-config.json" "%USERPROFILE%\.pi\agent\deepseek-cache-optimizer-stats.json" 2>nul` |
|
|
388
410
|
|
|
389
411
|
Do not delete `models.json` during cleanup; it contains your Pi model/provider configuration and is not owned by this package.
|
|
390
412
|
|
package/README.zh-CN.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- [安装](#安装)
|
|
17
17
|
- [命令](#命令)
|
|
18
18
|
- [持久 Opt-out](#持久-opt-out)
|
|
19
|
+
- [Footer 缓存统计模式](#footer-缓存统计模式)
|
|
19
20
|
- [OpenAI-compatible 代理配置](#openai-compatible-代理配置)
|
|
20
21
|
- [Adaptive thinking 模型](#adaptive-thinking-模型)
|
|
21
22
|
- [使用 `/cache-optimizer fix` 自动修复](#使用-cache-optimizer-fix-自动修复)
|
|
@@ -30,10 +31,10 @@
|
|
|
30
31
|
- 将能唯一定位的稳定 system prompt 内容移动到动态上下文之前。如果同一候选出现多次(例如动态上下文引用了它),则保持原样,避免删除错误的那一处。
|
|
31
32
|
- 压缩 Pi skill 列表,并移除 session-overview 中的易变字段。
|
|
32
33
|
- 在 Pi / provider compat 支持时请求长缓存保留。
|
|
33
|
-
- 对 `openai-completions` / `openai-responses` 请求,在没有有效 key 时使用 Pi session id 补 `prompt_cache_key`;Pi 0.82 core 对内置 `llama.cpp` 也使用这一语义。
|
|
34
|
+
- 对 `openai-completions` / `openai-responses` 请求,在没有有效 key 时使用 Pi session id 补 `prompt_cache_key`;Pi 0.82+ core 对内置 `llama.cpp` 也使用这一语义。
|
|
34
35
|
- 对缺少缓存 / session-affinity compat 的第三方 OpenAI-compatible 代理给出一次性提醒。
|
|
35
|
-
- 检测 Claude(opus-4.6
|
|
36
|
-
-
|
|
36
|
+
- 检测 Claude(opus-4.6+ 含 Opus 5、sonnet-4.6+ 含 Sonnet 5、fable-5+)以及 Kimi Coding K3 / `kimi-for-coding` 自定义渠道的 adaptive-thinking compat。
|
|
37
|
+
- 默认显示按本地日期累计的 provider/model footer 缓存统计,也可切换为仅显示当前 session。
|
|
37
38
|
- 通过版本化全局协议(`Symbol.for("pi.routing.registry.v1")` 与 `Symbol.for("pi.cache.hints.v1")`)支持可选的 router extension 集成,而不导入任何 router 包。
|
|
38
39
|
|
|
39
40
|
缓存是 provider 侧的 best-effort 行为。第三方代理和 router extension 仍可能隐藏缓存 usage、拒绝不支持的参数,或把请求路由到多个上游。
|
|
@@ -54,6 +55,8 @@ pi remove npm:pi-deepseek-cache-optimizer && pi install npm:pi-cache-optimizer
|
|
|
54
55
|
|
|
55
56
|
Pi 0.79.7 及之后,`pi update` 默认只更新 Pi 本体。若要更新已安装的 Pi package(包括本扩展),请运行 `pi update --extensions`(只更新 packages)或 `pi update --all`(Pi 与 packages 一起更新)。
|
|
56
57
|
|
|
58
|
+
本扩展已使用 Pi 0.83.0 验证,并继续面向 Pi 0.82+ 设计。它使用这些版本共有的 extension hooks、`getAgentDir()` 和 prompt options,不依赖 Pi 0.83 专有 API(例如 `ctx.scopedModels` 或 bundled TypeBox 1.3 aliases)。
|
|
59
|
+
|
|
57
60
|
## 命令
|
|
58
61
|
|
|
59
62
|
| 命令 | 作用 |
|
|
@@ -65,9 +68,10 @@ Pi 0.79.7 及之后,`pi update` 默认只更新 Pi 本体。若要更新已安
|
|
|
65
68
|
| `/cache-optimizer compat` | 对当前模型显示可复制的 compat 建议(如适用)。 |
|
|
66
69
|
| `/cache-optimizer stats` | 显示当前模型今天的本地 provider/model 统计和近期趋势。 |
|
|
67
70
|
| `/cache-optimizer reset` | 重置当前 provider/model 的本地 footer 统计;不会修改上游 provider 缓存。 |
|
|
71
|
+
| `/cache-optimizer config footer-mode session\|total` | 持久设置 footer 统计模式;持久命令配置优先于环境变量。 |
|
|
68
72
|
| `/cache-optimizer fix` | 为当前模型自动修复安全的 compat 问题(adaptive thinking、DeepSeek reasoning、OpenAI proxy session affinity)。展示预览 + 风险提示,需要用户确认。**仅在用户明确批准后才修改 `models.json`。** |
|
|
69
73
|
|
|
70
|
-
`enable` / `disable` 是当前进程内开关。若要持久关闭某些能力,请使用下面的环境变量。
|
|
74
|
+
交互式 `/cache-optimizer` 菜单包含 `Footer mode`,可以选择 `total` 或 `session`。`enable` / `disable` 是当前进程内开关。若要持久关闭某些能力,请使用下面的环境变量。
|
|
71
75
|
|
|
72
76
|
## 持久 Opt-out
|
|
73
77
|
|
|
@@ -78,11 +82,29 @@ Pi 0.79.7 及之后,`pi update` 默认只更新 Pi 本体。若要更新已安
|
|
|
78
82
|
| `PI_CACHE_OPTIMIZER_NO_OPENAI_CACHE_KEY=1` | 关闭 OpenAI-compatible `prompt_cache_key` fallback。推荐使用这个显式 opt-out。 |
|
|
79
83
|
| `PI_CACHE_OPTIMIZER_OPENAI_CACHE_KEY=0` | 通过旧的反向开关关闭同一个 fallback。取值 `0`、`false`、`no`、`off` 时关闭。 |
|
|
80
84
|
|
|
85
|
+
## Footer 缓存统计模式
|
|
86
|
+
|
|
87
|
+
**v2.7.0+** 同时支持每日累计与当前 session 两种 footer 统计范围。**v2.7.1** 在交互式 `/cache-optimizer` 菜单中加入了 Footer mode 选项。Footer 默认使用 `total`,显示当前本地日期内、跨 Pi session 和进程重启延续的 provider/model 统计。可以通过命令或环境变量切换显示范围:
|
|
88
|
+
|
|
89
|
+
| 值 | 作用 |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `total`(默认) | 显示今天跨 session 持久延续的 provider/model 累计统计;本地日期切换时重置。 |
|
|
92
|
+
| `session` | 仅显示当前 hashed Pi session 的统计。新 session 从 `0/0` 开始;同一 session 内 `/reload` 会恢复该 session 桶。 |
|
|
93
|
+
|
|
94
|
+
持久命令配置优先于环境变量:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
/cache-optimizer config footer-mode session
|
|
98
|
+
/cache-optimizer config footer-mode total
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
显式设置保存在 Pi agent 目录下的 `pi-cache-optimizer-config.json`。没有命令覆盖时,读取 `PI_CACHE_OPTIMIZER_FOOTER_MODE=session|total`;值不区分大小写,缺失或非法值均回退到 `total`。如需让已有安装重新由环境变量控制,请手动删除 `pi-cache-optimizer-config.json`,然后运行 `/reload`。
|
|
102
|
+
|
|
81
103
|
## OpenAI-compatible 代理配置
|
|
82
104
|
|
|
83
105
|
LiteLLM / OneAPI / NewAPI / 类 OpenRouter 渠道等第三方 `openai-completions` 代理,常会把同一个 session 分散到多个上游后端,导致 provider 侧 prompt cache 被拆散。
|
|
84
106
|
|
|
85
|
-
Pi 0.81+ 也内置了使用 OpenAI-shaped transport 的 `llama.cpp` provider。Pi 0.82 core 在启用 cache retention 时会为它生成 session `prompt_cache_key`,因此本扩展会保留该 key,并在缺失时使用同样的保守 fallback。只有符合 Pi 内置 provider 明确 compat 指纹的模型会跳过通用 proxy 路由 / session-affinity 建议;仅复用 `llama.cpp` id 的自定义或覆盖 provider 仍按普通 OpenAI-compatible 渠道处理。`prompt_cache_retention` 继续遵循统一安全规则:仅官方 OpenAI 或 `models.json` 中有效配置为 `supportsLongCacheRetention: true` 时保留,否则发送前移除。有效值遵循 Pi 的优先级:先看 `modelOverrides[modelId].compat`,再看匹配的 `models[].compat`,最后看 provider 级 `compat`;高层显式 `false` 会覆盖低层的 `true`。
|
|
107
|
+
Pi 0.81+ 也内置了使用 OpenAI-shaped transport 的 `llama.cpp` provider。Pi 0.82+ core 在启用 cache retention 时会为它生成 session `prompt_cache_key`,因此本扩展会保留该 key,并在缺失时使用同样的保守 fallback。只有符合 Pi 内置 provider 明确 compat 指纹的模型会跳过通用 proxy 路由 / session-affinity 建议;仅复用 `llama.cpp` id 的自定义或覆盖 provider 仍按普通 OpenAI-compatible 渠道处理。`prompt_cache_retention` 继续遵循统一安全规则:仅官方 OpenAI 或 `models.json` 中有效配置为 `supportsLongCacheRetention: true` 时保留,否则发送前移除。有效值遵循 Pi 的优先级:先看 `modelOverrides[modelId].compat`,再看匹配的 `models[].compat`,最后看 provider 级 `compat`;高层显式 `false` 会覆盖低层的 `true`。
|
|
86
108
|
|
|
87
109
|
对真正的代理,建议先启用 session affinity:
|
|
88
110
|
|
|
@@ -123,7 +145,7 @@ Anthropic 按 `tools → system → messages` 顺序处理 cache breakpoint,
|
|
|
123
145
|
|
|
124
146
|
## Adaptive thinking 模型
|
|
125
147
|
|
|
126
|
-
Claude 从 opus-4.6 / sonnet-4.6(含 Sonnet 5)/ fable-5 开始需要在 compat 中设置 `forceAdaptiveThinking: true`。Kimi Coding K3(`k3`)和 `kimi-for-coding` 也使用 adaptive thinking,并需要 `allowEmptySignature: true`,以正确重放空 signature 的 thinking block。缺少这些 compat 时,Pi 可能发送旧版 thinking payload 或错误重放 thinking。
|
|
148
|
+
Claude 从 opus-4.6 / sonnet-4.6(含 Opus 5、Sonnet 5)/ fable-5 开始需要在 compat 中设置 `forceAdaptiveThinking: true`。Kimi Coding K3(`k3`)和 `kimi-for-coding` 也使用 adaptive thinking,并需要 `allowEmptySignature: true`,以正确重放空 signature 的 thinking block。缺少这些 compat 时,Pi 可能发送旧版 thinking payload 或错误重放 thinking。Pi 0.83.0 的原生 Opus 5 catalog 已覆盖在同一 adaptive-thinking 检测中;如果自定义 `anthropic-messages` 渠道没有继承该 compat,仍需手动设置。
|
|
127
149
|
|
|
128
150
|
Pi 内置 catalog 已为官方模型设置此 flag。`models.json` 中覆盖这些模型的自定义渠道必须包含该 flag:
|
|
129
151
|
|
|
@@ -249,7 +271,7 @@ Provider 级最小 override:
|
|
|
249
271
|
|
|
250
272
|
## Footer 统计
|
|
251
273
|
|
|
252
|
-
统计是只读本地计数,保存在 Pi agent 目录(默认 `~/.pi/agent/pi-cache-optimizer-stats.json`;自定义 agent 目录使用 `PI_CODING_AGENT_DIR
|
|
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 桶。统计文件只包含日期和数字计数,不包含 API key、prompt、payload、headers、响应或模型输出。Footer 模式配置单独保存在 `pi-cache-optimizer-config.json`。
|
|
253
275
|
|
|
254
276
|
Pi 0.79+ 已内置 footer `CH` 标记,用于显示最近一次 prompt cache hit rate。本扩展在此基础上补充持久化的 provider/model 计数,以及代理 compat 诊断。
|
|
255
277
|
|
|
@@ -378,13 +400,13 @@ const hints = (globalThis as Record<symbol, any>)[CACHE_HINTS]?.getHints?.({
|
|
|
378
400
|
pi remove npm:pi-cache-optimizer
|
|
379
401
|
```
|
|
380
402
|
|
|
381
|
-
然后运行 `/reload` 或重启 Pi
|
|
403
|
+
然后运行 `/reload` 或重启 Pi。可选:删除本地状态文件(如果使用 `PI_CODING_AGENT_DIR`,请删除该目录中的同名文件):
|
|
382
404
|
|
|
383
|
-
| 平台 |
|
|
405
|
+
| 平台 | 删除本地状态文件 |
|
|
384
406
|
|---|---|
|
|
385
|
-
| Linux / macOS / WSL | `rm -f ~/.pi/agent/pi-cache-optimizer-stats.json ~/.pi/agent/deepseek-cache-optimizer-stats.json` |
|
|
386
|
-
| Windows PowerShell | `Remove-Item -Force "$env:USERPROFILE\.pi\agent\pi-cache-optimizer-stats.json", "$env:USERPROFILE\.pi\agent\deepseek-cache-optimizer-stats.json" -ErrorAction SilentlyContinue` |
|
|
387
|
-
| Windows 命令提示符 | `del /f /q "%USERPROFILE%\.pi\agent\pi-cache-optimizer-stats.json" "%USERPROFILE%\.pi\agent\deepseek-cache-optimizer-stats.json" 2>nul` |
|
|
407
|
+
| Linux / macOS / WSL | `rm -f ~/.pi/agent/pi-cache-optimizer-stats.json ~/.pi/agent/pi-cache-optimizer-config.json ~/.pi/agent/deepseek-cache-optimizer-stats.json` |
|
|
408
|
+
| Windows PowerShell | `Remove-Item -Force "$env:USERPROFILE\.pi\agent\pi-cache-optimizer-stats.json", "$env:USERPROFILE\.pi\agent\pi-cache-optimizer-config.json", "$env:USERPROFILE\.pi\agent\deepseek-cache-optimizer-stats.json" -ErrorAction SilentlyContinue` |
|
|
409
|
+
| Windows 命令提示符 | `del /f /q "%USERPROFILE%\.pi\agent\pi-cache-optimizer-stats.json" "%USERPROFILE%\.pi\agent\pi-cache-optimizer-config.json" "%USERPROFILE%\.pi\agent\deepseek-cache-optimizer-stats.json" 2>nul` |
|
|
388
410
|
|
|
389
411
|
清理时不要删除 `models.json`;它保存你的 Pi 模型 / provider 配置,不属于本包。
|
|
390
412
|
|
package/index.ts
CHANGED
|
@@ -95,12 +95,20 @@ const STATUS_KEY = "pi-cache-stats";
|
|
|
95
95
|
const STATE_DIR = getAgentDir();
|
|
96
96
|
const STATE_FILE_PATH = join(STATE_DIR, "pi-cache-optimizer-stats.json");
|
|
97
97
|
const LEGACY_STATE_FILE_PATH = join(STATE_DIR, "deepseek-cache-optimizer-stats.json");
|
|
98
|
+
const CONFIG_FILE_PATH = join(STATE_DIR, "pi-cache-optimizer-config.json");
|
|
98
99
|
const CACHE_PROVIDER_IDS: CacheProviderId[] = ["deepseek", "openai", "claude", "gemini"];
|
|
99
100
|
const OPENAI_CACHE_KEY_ENV = "PI_CACHE_OPTIMIZER_OPENAI_CACHE_KEY";
|
|
100
101
|
const NO_OPENAI_CACHE_KEY_ENV = "PI_CACHE_OPTIMIZER_NO_OPENAI_CACHE_KEY";
|
|
101
102
|
const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
|
|
102
103
|
const NO_SKILL_COMPRESSION_ENV = "PI_CACHE_OPTIMIZER_NO_SKILL_COMPRESSION";
|
|
103
104
|
const NO_PROMPT_REWRITE_ENV = "PI_CACHE_OPTIMIZER_NO_PROMPT_REWRITE";
|
|
105
|
+
const FOOTER_MODE_ENV = "PI_CACHE_OPTIMIZER_FOOTER_MODE";
|
|
106
|
+
type FooterStatsMode = "session" | "total";
|
|
107
|
+
type FooterStatsModeSource = "config" | "env" | "default";
|
|
108
|
+
type PersistedCacheOptimizerConfigV1 = {
|
|
109
|
+
version: 1;
|
|
110
|
+
footerMode?: FooterStatsMode;
|
|
111
|
+
};
|
|
104
112
|
const PI_ROUTING_REGISTRY_SYMBOL = Symbol.for("pi.routing.registry.v1");
|
|
105
113
|
const PI_CACHE_HINTS_SYMBOL = Symbol.for("pi.cache.hints.v1");
|
|
106
114
|
const PI_CACHE_HINTS_OWNER_SYMBOL = Symbol.for("pi.cache.optimizer.hints-owner.v1");
|
|
@@ -1132,6 +1140,62 @@ function isEnabledEnv(value: string | undefined): boolean {
|
|
|
1132
1140
|
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
1133
1141
|
}
|
|
1134
1142
|
|
|
1143
|
+
function parseFooterStatsMode(value: unknown): FooterStatsMode | undefined {
|
|
1144
|
+
if (typeof value !== "string") return undefined;
|
|
1145
|
+
const normalized = value.trim().toLowerCase();
|
|
1146
|
+
return normalized === "session" || normalized === "total" ? normalized : undefined;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function parsePersistedCacheOptimizerConfig(value: unknown): PersistedCacheOptimizerConfigV1 | undefined {
|
|
1150
|
+
const record = asRecord(value);
|
|
1151
|
+
if (!record || record.version !== 1) return undefined;
|
|
1152
|
+
const footerMode = parseFooterStatsMode(record.footerMode);
|
|
1153
|
+
if (record.footerMode !== undefined && !footerMode) return undefined;
|
|
1154
|
+
return { version: 1, ...(footerMode ? { footerMode } : {}) };
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function readPersistedFooterMode(configPath: string = CONFIG_FILE_PATH): FooterStatsMode | undefined {
|
|
1158
|
+
try {
|
|
1159
|
+
const parsed = parsePersistedCacheOptimizerConfig(JSON.parse(readFileSync(configPath, "utf8")));
|
|
1160
|
+
if (!parsed) throw new Error("invalid footer config schema");
|
|
1161
|
+
return parsed.footerMode;
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
if (getErrorCode(error) !== "ENOENT") {
|
|
1164
|
+
console.warn(`${LOG_PREFIX}: failed to read footer config; using environment/default mode`, error);
|
|
1165
|
+
}
|
|
1166
|
+
return undefined;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
async function writePersistedFooterMode(
|
|
1171
|
+
mode: FooterStatsMode,
|
|
1172
|
+
configPath: string = CONFIG_FILE_PATH,
|
|
1173
|
+
): Promise<void> {
|
|
1174
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
1175
|
+
const payload: PersistedCacheOptimizerConfigV1 = { version: 1, footerMode: mode };
|
|
1176
|
+
const tempPath = `${configPath}.${process.pid}.${Date.now()}.tmp`;
|
|
1177
|
+
await writeFile(tempPath, JSON.stringify(payload, null, 2) + "\n", "utf8");
|
|
1178
|
+
await rename(tempPath, configPath);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function resolveFooterStatsMode(
|
|
1182
|
+
configuredMode: FooterStatsMode | undefined,
|
|
1183
|
+
env: MutableEnv = process.env,
|
|
1184
|
+
): { mode: FooterStatsMode; source: FooterStatsModeSource } {
|
|
1185
|
+
if (configuredMode) return { mode: configuredMode, source: "config" };
|
|
1186
|
+
const envMode = parseFooterStatsMode(env[FOOTER_MODE_ENV]);
|
|
1187
|
+
return envMode ? { mode: envMode, source: "env" } : { mode: "total", source: "default" };
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function footerStatsMode(
|
|
1191
|
+
env: MutableEnv = process.env,
|
|
1192
|
+
configuredMode: FooterStatsMode | undefined = persistedFooterStatsMode,
|
|
1193
|
+
): FooterStatsMode {
|
|
1194
|
+
return resolveFooterStatsMode(configuredMode, env).mode;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
let persistedFooterStatsMode = readPersistedFooterMode();
|
|
1198
|
+
|
|
1135
1199
|
function isDisabledEnv(value: string | undefined): boolean {
|
|
1136
1200
|
if (!value) return false;
|
|
1137
1201
|
const normalized = value.trim().toLowerCase();
|
|
@@ -3827,11 +3891,24 @@ function routedModelRefToPiModel(ref: PersistedRoutedModelRef): PiModel {
|
|
|
3827
3891
|
} as PiModel;
|
|
3828
3892
|
}
|
|
3829
3893
|
|
|
3894
|
+
function selectFooterStatsForModel(
|
|
3895
|
+
mode: FooterStatsMode,
|
|
3896
|
+
sessionHash: string | undefined,
|
|
3897
|
+
statsByModel: Record<string, CacheStats>,
|
|
3898
|
+
totalsByModel: Record<string, CacheStats>,
|
|
3899
|
+
model: { provider: string; id: string },
|
|
3900
|
+
): CacheStats | undefined {
|
|
3901
|
+
if (mode === "total") return totalsByModel[modelKey(model)];
|
|
3902
|
+
if (!sessionHash) return undefined;
|
|
3903
|
+
return statsByModel[makeSessionModelKey(sessionHash, model.provider, model.id)];
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3830
3906
|
function buildExactRouterStatusEntry(
|
|
3831
3907
|
sessionHash: string | undefined,
|
|
3832
3908
|
statsByModel: Record<string, CacheStats>,
|
|
3833
3909
|
lastRoutedModel: PersistedRoutedModelRef | undefined,
|
|
3834
3910
|
totalsByModel: Record<string, CacheStats> = {},
|
|
3911
|
+
mode: FooterStatsMode = "total",
|
|
3835
3912
|
): { model: PiModel; adapter: CacheProviderAdapter; stats: CacheStats } | undefined {
|
|
3836
3913
|
if (!sessionHash || !lastRoutedModel) return undefined;
|
|
3837
3914
|
|
|
@@ -3839,8 +3916,43 @@ function buildExactRouterStatusEntry(
|
|
|
3839
3916
|
const adapter = selectAdapterForModel(model);
|
|
3840
3917
|
if (!adapter) return undefined;
|
|
3841
3918
|
|
|
3842
|
-
|
|
3843
|
-
|
|
3919
|
+
return {
|
|
3920
|
+
model,
|
|
3921
|
+
adapter,
|
|
3922
|
+
stats: selectFooterStatsForModel(mode, sessionHash, statsByModel, totalsByModel, model) ?? emptyCacheStats(),
|
|
3923
|
+
};
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
function findBestRouterModelStats(
|
|
3927
|
+
mode: FooterStatsMode,
|
|
3928
|
+
sessionHash: string | undefined,
|
|
3929
|
+
statsByModel: Record<string, CacheStats>,
|
|
3930
|
+
totalsByModel: Record<string, CacheStats>,
|
|
3931
|
+
): { model: PiModel; adapter: CacheProviderAdapter; stats: CacheStats } | undefined {
|
|
3932
|
+
const entries = mode === "total"
|
|
3933
|
+
? Object.entries(totalsByModel)
|
|
3934
|
+
: sessionHash
|
|
3935
|
+
? Object.entries(statsByModel)
|
|
3936
|
+
.filter(([key]) => key.startsWith(`${sessionHash}:`))
|
|
3937
|
+
.map(([key, stats]) => [key.slice(sessionHash.length + 1), stats] as const)
|
|
3938
|
+
: [];
|
|
3939
|
+
let best: { model: PiModel; adapter: CacheProviderAdapter; stats: CacheStats; total: number } | undefined;
|
|
3940
|
+
|
|
3941
|
+
for (const [modelKeyPart, stats] of entries) {
|
|
3942
|
+
const slashIdx = modelKeyPart.indexOf("/");
|
|
3943
|
+
if (slashIdx < 1 || slashIdx >= modelKeyPart.length - 1) continue;
|
|
3944
|
+
const model = routedModelRefToPiModel({
|
|
3945
|
+
provider: modelKeyPart.slice(0, slashIdx),
|
|
3946
|
+
id: modelKeyPart.slice(slashIdx + 1),
|
|
3947
|
+
});
|
|
3948
|
+
const adapter = selectAdapterForModel(model);
|
|
3949
|
+
if (!adapter) continue;
|
|
3950
|
+
if (!best || stats.totalRequests > best.total) {
|
|
3951
|
+
best = { model, adapter, stats, total: stats.totalRequests };
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
return best ? { model: best.model, adapter: best.adapter, stats: best.stats } : undefined;
|
|
3844
3956
|
}
|
|
3845
3957
|
|
|
3846
3958
|
function parsePersistedCacheStats(value: unknown): CacheStatsState | undefined {
|
|
@@ -6558,6 +6670,16 @@ export const __internals_for_tests = {
|
|
|
6558
6670
|
parsePersistedRoutedModelRef,
|
|
6559
6671
|
routedModelRefToPiModel,
|
|
6560
6672
|
buildExactRouterStatusEntry,
|
|
6673
|
+
findBestRouterModelStats,
|
|
6674
|
+
selectFooterStatsForModel,
|
|
6675
|
+
parseFooterStatsMode,
|
|
6676
|
+
parsePersistedCacheOptimizerConfig,
|
|
6677
|
+
readPersistedFooterMode,
|
|
6678
|
+
writePersistedFooterMode,
|
|
6679
|
+
resolveFooterStatsMode,
|
|
6680
|
+
footerStatsMode,
|
|
6681
|
+
CONFIG_FILE_PATH,
|
|
6682
|
+
FOOTER_MODE_ENV,
|
|
6561
6683
|
// Routing-provider protocol helpers
|
|
6562
6684
|
PI_ROUTING_REGISTRY_SYMBOL,
|
|
6563
6685
|
PI_CACHE_HINTS_SYMBOL,
|
|
@@ -6996,50 +7118,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
6996
7118
|
await rollOverStatsIfNeeded(ctx);
|
|
6997
7119
|
}
|
|
6998
7120
|
|
|
6999
|
-
/**
|
|
7000
|
-
* Fallback for older persisted files that do not yet carry exact
|
|
7001
|
-
* last-routed-model metadata. When the current model is a router channel
|
|
7002
|
-
* (e.g. router/auto), restorable stats are stored under the real upstream
|
|
7003
|
-
* model's provider/id key, not under router/auto. Find the best valid entry
|
|
7004
|
-
* (highest totalRequests among adapter-detectable model keys) so we can show
|
|
7005
|
-
* meaningful footer content on session_start after reload.
|
|
7006
|
-
*/
|
|
7007
|
-
function findBestRouterModelStats(): { adapter: CacheProviderAdapter; stats: CacheStats } | undefined {
|
|
7008
|
-
let best: { adapter: CacheProviderAdapter; stats: CacheStats; total: number } | undefined;
|
|
7009
|
-
|
|
7010
|
-
for (const [modelKeyPart, stats] of Object.entries(cacheStatsTotalsByModel)) {
|
|
7011
|
-
const slashIdx = modelKeyPart.indexOf("/");
|
|
7012
|
-
if (slashIdx < 0 || slashIdx >= modelKeyPart.length - 1) continue;
|
|
7013
|
-
const modelId = modelKeyPart.slice(slashIdx + 1);
|
|
7014
|
-
const providerName = modelKeyPart.slice(0, slashIdx);
|
|
7015
|
-
|
|
7016
|
-
// Construct a minimal model for adapter detection.
|
|
7017
|
-
// Every is*LikeModel function only accesses model.id and model.name
|
|
7018
|
-
// via getModelIdNameTokenValues, so { id, name } is sufficient.
|
|
7019
|
-
const mockModel = {
|
|
7020
|
-
id: modelId,
|
|
7021
|
-
name: modelId,
|
|
7022
|
-
provider: providerName,
|
|
7023
|
-
api: "",
|
|
7024
|
-
baseUrl: "",
|
|
7025
|
-
reasoning: false,
|
|
7026
|
-
input: ["text"],
|
|
7027
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
7028
|
-
contextWindow: 0,
|
|
7029
|
-
maxTokens: 0,
|
|
7030
|
-
} as PiModel;
|
|
7031
|
-
|
|
7032
|
-
const adapter = selectAdapterForModel(mockModel);
|
|
7033
|
-
if (!adapter) continue;
|
|
7034
|
-
|
|
7035
|
-
if (!best || stats.totalRequests > best.total) {
|
|
7036
|
-
best = { adapter, stats, total: stats.totalRequests };
|
|
7037
|
-
}
|
|
7038
|
-
}
|
|
7039
|
-
|
|
7040
|
-
return best ? { adapter: best.adapter, stats: best.stats } : undefined;
|
|
7041
|
-
}
|
|
7042
|
-
|
|
7043
7121
|
async function publishStatus(ctx: ExtensionContext, model: PiModel | undefined = ctx.model): Promise<void> {
|
|
7044
7122
|
syncSessionHash(ctx);
|
|
7045
7123
|
await rollOverStatsIfNeeded(ctx);
|
|
@@ -7049,6 +7127,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
7049
7127
|
const adapter = selectAdapterForModel(displayModel);
|
|
7050
7128
|
const activeIsVirtualRoute = !!routedModel || isVirtualRoutingModel(model, ctx);
|
|
7051
7129
|
let statusText: string | undefined;
|
|
7130
|
+
const mode = footerStatsMode();
|
|
7131
|
+
const sessionHash = currentSessionHashSet ? currentSessionHash : undefined;
|
|
7052
7132
|
|
|
7053
7133
|
if (!adapter && !routedModel && activeIsVirtualRoute) {
|
|
7054
7134
|
// On model_select (existing footer), keep the existing cache footer
|
|
@@ -7058,11 +7138,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
7058
7138
|
// heuristics only when no exact metadata exists.
|
|
7059
7139
|
if (lastStatusText !== undefined) return;
|
|
7060
7140
|
const realEntry = buildExactRouterStatusEntry(
|
|
7061
|
-
|
|
7141
|
+
sessionHash,
|
|
7062
7142
|
cacheStatsByModel,
|
|
7063
7143
|
lastActualRoutedModel,
|
|
7064
7144
|
cacheStatsTotalsByModel,
|
|
7065
|
-
|
|
7145
|
+
mode,
|
|
7146
|
+
) ?? findBestRouterModelStats(
|
|
7147
|
+
mode,
|
|
7148
|
+
sessionHash,
|
|
7149
|
+
cacheStatsByModel,
|
|
7150
|
+
cacheStatsTotalsByModel,
|
|
7151
|
+
);
|
|
7066
7152
|
if (realEntry) {
|
|
7067
7153
|
const statsText = formatCacheStats(realEntry.adapter, realEntry.stats);
|
|
7068
7154
|
statusText = runtimeOptimizerEnabled
|
|
@@ -7072,10 +7158,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
7072
7158
|
}
|
|
7073
7159
|
|
|
7074
7160
|
if (adapter) {
|
|
7075
|
-
//
|
|
7076
|
-
//
|
|
7077
|
-
|
|
7078
|
-
|
|
7161
|
+
// Footer mode defaults to daily provider/model totals. Users may select
|
|
7162
|
+
// current-session counters through persistent command config or the env var.
|
|
7163
|
+
const stats = displayModel
|
|
7164
|
+
? selectFooterStatsForModel(mode, sessionHash, cacheStatsByModel, cacheStatsTotalsByModel, displayModel)
|
|
7165
|
+
: undefined;
|
|
7079
7166
|
const statsText = formatCacheStats(adapter, stats ?? emptyCacheStats());
|
|
7080
7167
|
statusText = runtimeOptimizerEnabled ? statsText : `Cache Optimizer disabled · ${statsText}`;
|
|
7081
7168
|
}
|
|
@@ -7520,17 +7607,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
7520
7607
|
// with low-hit diagnosis
|
|
7521
7608
|
// stats — show active model stats bucket, recent trend, usage
|
|
7522
7609
|
// compat — show compat suggestion with file path
|
|
7610
|
+
// config footer-mode session|total — persist footer mode override
|
|
7523
7611
|
// fix — auto-fix compat issues (writes models.json, requires UI)
|
|
7524
7612
|
// reset — reset current provider/model footer stats bucket (local only)
|
|
7525
7613
|
// (no args) — interactive menu (with UI) or help summary
|
|
7526
7614
|
// ────────────────────────────────────────────────────────────────
|
|
7527
7615
|
pi.registerCommand("cache-optimizer", {
|
|
7528
|
-
description: "
|
|
7616
|
+
description: "Configure and diagnose Pi cache behavior",
|
|
7529
7617
|
handler: async (args: string, cmdCtx) => {
|
|
7530
7618
|
syncSessionHash(cmdCtx);
|
|
7531
7619
|
const selectedModel = cmdCtx.model;
|
|
7532
7620
|
const model = resolveRouteModel(selectedModel, cmdCtx as unknown as ExtensionContext) ?? selectedModel;
|
|
7533
|
-
const
|
|
7621
|
+
const commandParts = args.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
7622
|
+
const subcommand = commandParts[0] || "help";
|
|
7534
7623
|
|
|
7535
7624
|
if (subcommand === "enable") {
|
|
7536
7625
|
setRuntimeOptimizerEnabled(true);
|
|
@@ -7570,6 +7659,36 @@ export default function (pi: ExtensionAPI) {
|
|
|
7570
7659
|
const samples = sk ? getRecentSamples(sk) : [];
|
|
7571
7660
|
const output = buildStatsOutput(model, adapter, statsState, samples);
|
|
7572
7661
|
cmdCtx.ui.notify(output, "info");
|
|
7662
|
+
} else if (subcommand === "config") {
|
|
7663
|
+
const configKey = commandParts[1];
|
|
7664
|
+
const requestedMode = commandParts[2];
|
|
7665
|
+
if (configKey !== "footer-mode" || !requestedMode || !["session", "total"].includes(requestedMode)) {
|
|
7666
|
+
const resolved = resolveFooterStatsMode(persistedFooterStatsMode);
|
|
7667
|
+
cmdCtx.ui.notify(
|
|
7668
|
+
`Usage: /cache-optimizer config footer-mode session|total\n` +
|
|
7669
|
+
`Current footer mode: ${resolved.mode} (${resolved.source})`,
|
|
7670
|
+
"info",
|
|
7671
|
+
);
|
|
7672
|
+
return;
|
|
7673
|
+
}
|
|
7674
|
+
|
|
7675
|
+
const nextMode = requestedMode as FooterStatsMode;
|
|
7676
|
+
try {
|
|
7677
|
+
await writePersistedFooterMode(nextMode);
|
|
7678
|
+
persistedFooterStatsMode = nextMode;
|
|
7679
|
+
lastStatusText = undefined;
|
|
7680
|
+
await publishStatus(cmdCtx as unknown as ExtensionContext, model);
|
|
7681
|
+
const resolved = resolveFooterStatsMode(persistedFooterStatsMode);
|
|
7682
|
+
cmdCtx.ui.notify(
|
|
7683
|
+
`✅ Footer mode set to ${resolved.mode}. Persistent config overrides ${FOOTER_MODE_ENV}.`,
|
|
7684
|
+
"info",
|
|
7685
|
+
);
|
|
7686
|
+
} catch (error) {
|
|
7687
|
+
cmdCtx.ui.notify(
|
|
7688
|
+
`❌ Could not update footer mode config: ${error instanceof Error ? error.message : String(error)}`,
|
|
7689
|
+
"error",
|
|
7690
|
+
);
|
|
7691
|
+
}
|
|
7573
7692
|
} else if (subcommand === "compat") {
|
|
7574
7693
|
if (!model) {
|
|
7575
7694
|
cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
|
|
@@ -7955,6 +8074,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7955
8074
|
"Stats — Show cache stats and trend",
|
|
7956
8075
|
"Compat — Show compat suggestion",
|
|
7957
8076
|
"Fix — Auto-fix compat issues (writes models.json)",
|
|
8077
|
+
"Footer mode — Choose total or current-session footer stats",
|
|
7958
8078
|
"Reset — Reset local provider/model stats",
|
|
7959
8079
|
"Cancel",
|
|
7960
8080
|
];
|
|
@@ -8137,6 +8257,31 @@ export default function (pi: ExtensionAPI) {
|
|
|
8137
8257
|
);
|
|
8138
8258
|
}
|
|
8139
8259
|
} else if (choice === menuOptions[6]) {
|
|
8260
|
+
const modeOptions = ["total — Daily cumulative totals (default)", "session — Current Pi session only", "Cancel"];
|
|
8261
|
+
const modeChoice = await cmdCtx.ui.select("Footer cache stats mode", modeOptions);
|
|
8262
|
+
const nextMode = modeChoice === modeOptions[0]
|
|
8263
|
+
? "total"
|
|
8264
|
+
: modeChoice === modeOptions[1]
|
|
8265
|
+
? "session"
|
|
8266
|
+
: undefined;
|
|
8267
|
+
if (nextMode) {
|
|
8268
|
+
try {
|
|
8269
|
+
await writePersistedFooterMode(nextMode);
|
|
8270
|
+
persistedFooterStatsMode = nextMode;
|
|
8271
|
+
lastStatusText = undefined;
|
|
8272
|
+
await publishStatus(cmdCtx as unknown as ExtensionContext, model);
|
|
8273
|
+
cmdCtx.ui.notify(
|
|
8274
|
+
`✅ Footer mode set to ${nextMode}. Persistent config overrides ${FOOTER_MODE_ENV}.`,
|
|
8275
|
+
"info",
|
|
8276
|
+
);
|
|
8277
|
+
} catch (error) {
|
|
8278
|
+
cmdCtx.ui.notify(
|
|
8279
|
+
`❌ Could not update footer mode config: ${error instanceof Error ? error.message : String(error)}`,
|
|
8280
|
+
"error",
|
|
8281
|
+
);
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
} else if (choice === menuOptions[7]) {
|
|
8140
8285
|
if (!model) {
|
|
8141
8286
|
cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
|
|
8142
8287
|
} else {
|
|
@@ -8168,10 +8313,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
8168
8313
|
diagnosis.push(" doctor — Show current model/provider/api/baseUrl/compat and low-hit diagnosis");
|
|
8169
8314
|
diagnosis.push(" stats — Show active model stats bucket and recent trend");
|
|
8170
8315
|
diagnosis.push(" compat — Show compat suggestion with edit location");
|
|
8316
|
+
diagnosis.push(" config footer-mode session|total — Persist the footer stats mode");
|
|
8171
8317
|
diagnosis.push(" fix — Auto-fix compat issues (writes models.json, requires UI)");
|
|
8172
8318
|
diagnosis.push(" reset — Reset local provider/model stats for current model (does not affect upstream)");
|
|
8173
8319
|
diagnosis.push("");
|
|
8174
8320
|
diagnosis.push(formatOptimizerRuntimeMode());
|
|
8321
|
+
const resolvedFooterMode = resolveFooterStatsMode(persistedFooterStatsMode);
|
|
8322
|
+
diagnosis.push(`Footer stats mode: ${resolvedFooterMode.mode} (${resolvedFooterMode.source})`);
|
|
8175
8323
|
diagnosis.push("");
|
|
8176
8324
|
if (model) {
|
|
8177
8325
|
const displayKey = modelKey(model);
|
package/package.json
CHANGED