pi-cache-optimizer 2.6.18 → 2.6.21
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 -6
- package/README.zh-CN.md +10 -6
- package/index.ts +114 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
Pi extension for improving provider-side KV / prompt cache hit rates. It keeps stable prompt content near the front, adds a conservative OpenAI-compatible `prompt_cache_key` fallback, warns about common proxy cache-routing gaps, and shows read-only footer cache stats.
|
|
10
10
|
|
|
11
|
-
> Renamed from `pi-deepseek-cache-optimizer`. Existing footer counters migrate automatically. The extension does **not** touch `~/.pi/agent/models.json
|
|
11
|
+
> Renamed from `pi-deepseek-cache-optimizer`. Existing footer counters migrate automatically. The extension does **not** touch Pi's `models.json` during normal operation (default: `~/.pi/agent/models.json`; custom agent dirs use `PI_CODING_AGENT_DIR`); only `/cache-optimizer fix` can edit it, and only after an explicit interactive preview + confirmation with an automatic timestamped backup.
|
|
12
12
|
|
|
13
13
|
## Contents
|
|
14
14
|
|
|
@@ -30,7 +30,7 @@ Pi extension for improving provider-side KV / prompt cache hit rates. It keeps s
|
|
|
30
30
|
- Reorders stable system-prompt content before dynamic context.
|
|
31
31
|
- Compresses Pi skill listings and strips session-overview churn.
|
|
32
32
|
- 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.
|
|
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
34
|
- Warns once for third-party OpenAI-compatible proxies missing cache/session-affinity compat flags.
|
|
35
35
|
- Detects adaptive-thinking compat for Claude (opus-4.6+, sonnet-4.6+ including Sonnet 5, fable-5+) and Kimi Coding K3 / `kimi-for-coding` custom channels.
|
|
36
36
|
- Shows restart-persistent provider/model footer stats for supported model families.
|
|
@@ -80,7 +80,11 @@ On Pi 0.79.7 and newer, `pi update` updates Pi itself only. To update installed
|
|
|
80
80
|
|
|
81
81
|
## OpenAI-compatible proxy setup
|
|
82
82
|
|
|
83
|
-
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.
|
|
83
|
+
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
|
+
|
|
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 `supportsLongCacheRetention: true` opt-in in `models.json`; otherwise strip it before sending.
|
|
86
|
+
|
|
87
|
+
For real proxies, start with session affinity:
|
|
84
88
|
|
|
85
89
|
```json
|
|
86
90
|
{
|
|
@@ -199,7 +203,7 @@ Pi 0.80.9+ already includes Kimi K3 in built-in Kimi Coding, Moonshot AI / China
|
|
|
199
203
|
|
|
200
204
|
### Channels without a `models.json` provider entry
|
|
201
205
|
|
|
202
|
-
Some Pi channels may be available even when there is no provider block in `~/.pi/agent/models.json`
|
|
206
|
+
Some Pi channels may be available even when there is no provider block in Pi's agent `models.json` yet (default: `~/.pi/agent/models.json`; with `PI_CODING_AGENT_DIR`, use `$PI_CODING_AGENT_DIR/models.json`). Keep existing authentication as-is and do not copy credentials, tokens, or API keys. Add only cache/routing compatibility overrides in `models.json`.
|
|
203
207
|
|
|
204
208
|
Provider-level minimal override:
|
|
205
209
|
|
|
@@ -235,7 +239,7 @@ If only one model should change, use `modelOverrides`:
|
|
|
235
239
|
|
|
236
240
|
## Footer stats
|
|
237
241
|
|
|
238
|
-
Stats are read-only local counters stored
|
|
242
|
+
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`) and keyed by provider/model, so the same channel/model keeps today's footer counters after a Pi process or terminal restart. The file also keeps hashed session buckets for migration/reload bookkeeping. It contains only dates and numeric counters — no API keys, prompts, payloads, headers, responses, or model output.
|
|
239
243
|
|
|
240
244
|
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.
|
|
241
245
|
|
|
@@ -364,7 +368,7 @@ When the query matches the current session/route, `hints` may contain `systemPro
|
|
|
364
368
|
pi remove npm:pi-cache-optimizer
|
|
365
369
|
```
|
|
366
370
|
|
|
367
|
-
Then run `/reload` or restart Pi. Optional local stats cleanup:
|
|
371
|
+
Then run `/reload` or restart Pi. Optional local stats cleanup (if you use `PI_CODING_AGENT_DIR`, delete the same files from that directory instead):
|
|
368
372
|
|
|
369
373
|
| Platform | Delete local stats files |
|
|
370
374
|
|---|---|
|
package/README.zh-CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
用于提升 Pi 中 provider 侧 KV Cache / Prompt Cache 命中率的扩展:把稳定 prompt 内容前置,给 OpenAI-compatible 请求补保守的 `prompt_cache_key`,提示代理渠道常见缓存路由兼容问题,并在底部显示只读缓存统计。
|
|
10
10
|
|
|
11
|
-
> 本包已从 `pi-deepseek-cache-optimizer`
|
|
11
|
+
> 本包已从 `pi-deepseek-cache-optimizer` 改名。已有底部统计会自动迁移。正常运行时扩展不会触碰 Pi 的 `models.json`(默认 `~/.pi/agent/models.json`;自定义 agent 目录使用 `PI_CODING_AGENT_DIR`);只有 `/cache-optimizer fix` 会在展示交互式预览、风险提示并得到明确确认后写入,且会先创建带时间戳的自动备份。
|
|
12
12
|
|
|
13
13
|
## 目录
|
|
14
14
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
- 将稳定的 system prompt 内容移动到动态上下文之前。
|
|
31
31
|
- 压缩 Pi skill 列表,并移除 session-overview 中的易变字段。
|
|
32
32
|
- 在 Pi / provider compat 支持时请求长缓存保留。
|
|
33
|
-
- 对 `openai-completions` / `openai-responses` 请求,在没有有效 key 时使用 Pi session id 补 `prompt_cache_key
|
|
33
|
+
- 对 `openai-completions` / `openai-responses` 请求,在没有有效 key 时使用 Pi session id 补 `prompt_cache_key`;Pi 0.82 core 对内置 `llama.cpp` 也使用这一语义。
|
|
34
34
|
- 对缺少缓存 / session-affinity compat 的第三方 OpenAI-compatible 代理给出一次性提醒。
|
|
35
35
|
- 检测 Claude(opus-4.6+、sonnet-4.6+ 含 Sonnet 5、fable-5+)以及 Kimi Coding K3 / `kimi-for-coding` 自定义渠道的 adaptive-thinking compat。
|
|
36
36
|
- 为支持的模型家族显示可跨 Pi 进程 / 终端重启延续的 provider/model footer 缓存统计。
|
|
@@ -80,7 +80,11 @@ Pi 0.79.7 及之后,`pi update` 默认只更新 Pi 本体。若要更新已安
|
|
|
80
80
|
|
|
81
81
|
## OpenAI-compatible 代理配置
|
|
82
82
|
|
|
83
|
-
LiteLLM / OneAPI / NewAPI / 类 OpenRouter 渠道等第三方 `openai-completions` 代理,常会把同一个 session 分散到多个上游后端,导致 provider 侧 prompt cache
|
|
83
|
+
LiteLLM / OneAPI / NewAPI / 类 OpenRouter 渠道等第三方 `openai-completions` 代理,常会把同一个 session 分散到多个上游后端,导致 provider 侧 prompt cache 被拆散。
|
|
84
|
+
|
|
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` 时保留,否则发送前移除。
|
|
86
|
+
|
|
87
|
+
对真正的代理,建议先启用 session affinity:
|
|
84
88
|
|
|
85
89
|
```json
|
|
86
90
|
{
|
|
@@ -199,7 +203,7 @@ Pi 0.80.9+ 已在内置 Kimi Coding、Moonshot AI / 中国区、OpenRouter 和 V
|
|
|
199
203
|
|
|
200
204
|
### 没有 `models.json` provider entry 的渠道
|
|
201
205
|
|
|
202
|
-
有些 Pi
|
|
206
|
+
有些 Pi 渠道可用时,Pi agent `models.json`(默认 `~/.pi/agent/models.json`;若设置了 `PI_CODING_AGENT_DIR`,则为 `$PI_CODING_AGENT_DIR/models.json`)里可能还没有对应 provider block。保留现有认证方式,不要复制 credential、token 或 API key。只在 `models.json` 里添加缓存 / 路由兼容覆盖。
|
|
203
207
|
|
|
204
208
|
Provider 级最小 override:
|
|
205
209
|
|
|
@@ -235,7 +239,7 @@ Provider 级最小 override:
|
|
|
235
239
|
|
|
236
240
|
## Footer 统计
|
|
237
241
|
|
|
238
|
-
统计是只读本地计数,保存在 `~/.pi/agent/pi-cache-optimizer-stats.json
|
|
242
|
+
统计是只读本地计数,保存在 Pi agent 目录(默认 `~/.pi/agent/pi-cache-optimizer-stats.json`;自定义 agent 目录使用 `PI_CODING_AGENT_DIR`),按 provider/model 作为 footer 展示维度,因此同一个渠道/模型在 Pi 进程或终端重启后会延续今天的统计。文件也保留 hashed session buckets,用于迁移和 reload 记录。文件只包含日期和数字计数,不包含 API key、prompt、payload、headers、响应或模型输出。
|
|
239
243
|
|
|
240
244
|
Pi 0.79+ 已内置 footer `CH` 标记,用于显示最近一次 prompt cache hit rate。本扩展在此基础上补充持久化的 provider/model 计数,以及代理 compat 诊断。
|
|
241
245
|
|
|
@@ -364,7 +368,7 @@ const hints = (globalThis as Record<symbol, any>)[CACHE_HINTS]?.getHints?.({
|
|
|
364
368
|
pi remove npm:pi-cache-optimizer
|
|
365
369
|
```
|
|
366
370
|
|
|
367
|
-
然后运行 `/reload` 或重启 Pi
|
|
371
|
+
然后运行 `/reload` 或重启 Pi。可选:删除本地统计文件(如果使用 `PI_CODING_AGENT_DIR`,请删除该目录中的同名文件):
|
|
368
372
|
|
|
369
373
|
| 平台 | 删除本地统计文件 |
|
|
370
374
|
|---|---|
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { copyFile, mkdir, readFile, rename, unlink, writeFile } from "node:fs/pr
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
-
import type
|
|
6
|
+
import { getAgentDir, type BuildSystemPromptOptions, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
|
|
8
8
|
type MutableEnv = Record<string, string | undefined>;
|
|
9
9
|
|
|
@@ -63,7 +63,9 @@ type CacheProviderId = "deepseek" | "openai" | "claude" | "gemini";
|
|
|
63
63
|
|
|
64
64
|
const LOG_PREFIX = "pi-cache-optimizer";
|
|
65
65
|
const STATUS_KEY = "pi-cache-stats";
|
|
66
|
-
|
|
66
|
+
|
|
67
|
+
/** Use Pi core's resolver so rebranded config names and env semantics stay aligned. */
|
|
68
|
+
const STATE_DIR = getAgentDir();
|
|
67
69
|
const STATE_FILE_PATH = join(STATE_DIR, "pi-cache-optimizer-stats.json");
|
|
68
70
|
const LEGACY_STATE_FILE_PATH = join(STATE_DIR, "deepseek-cache-optimizer-stats.json");
|
|
69
71
|
const CACHE_PROVIDER_IDS: CacheProviderId[] = ["deepseek", "openai", "claude", "gemini"];
|
|
@@ -141,6 +143,12 @@ const AYA_MODEL_PATTERN = /(^|[\/\s:_-])aya($|[\-_.:\/\s])/i;
|
|
|
141
143
|
const ORION_MODEL_PATTERN = /(^|[\/\s:_-])orion($|[\-_.:\/\s])/i;
|
|
142
144
|
|
|
143
145
|
type CacheCompat = {
|
|
146
|
+
supportsStore?: boolean;
|
|
147
|
+
supportsDeveloperRole?: boolean;
|
|
148
|
+
supportsReasoningEffort?: boolean;
|
|
149
|
+
supportsUsageInStreaming?: boolean;
|
|
150
|
+
supportsStrictMode?: boolean;
|
|
151
|
+
maxTokensField?: "max_completion_tokens" | "max_tokens";
|
|
144
152
|
sendSessionAffinityHeaders?: boolean;
|
|
145
153
|
sessionAffinityFormat?: "openai" | "openai-nosession" | "openrouter";
|
|
146
154
|
supportsLongCacheRetention?: boolean;
|
|
@@ -980,24 +988,50 @@ function getCompat(model: PiModel | undefined): CacheCompat {
|
|
|
980
988
|
return modelCompat;
|
|
981
989
|
}
|
|
982
990
|
|
|
991
|
+
/** Join display-only path fragments without resolving them for I/O. */
|
|
992
|
+
function joinDisplayPath(base: string, child: string, platform: string = process.platform): string {
|
|
993
|
+
const sep = platform.startsWith("win") ? "\\" : "/";
|
|
994
|
+
return `${base.replace(/[\\/]+$/, "")}${sep}${child}`;
|
|
995
|
+
}
|
|
996
|
+
|
|
983
997
|
/**
|
|
984
|
-
* Return a platform-friendly display path for
|
|
985
|
-
*
|
|
986
|
-
* On Windows (platform starts with "win") the path is shown as
|
|
987
|
-
* `%USERPROFILE%\.pi\agent\models.json` to match Windows conventions.
|
|
988
|
-
* On all other platforms (Linux, macOS, etc.) it is shown as
|
|
989
|
-
* `~/.pi/agent/models.json` (the Unix-style tilde shorthand).
|
|
998
|
+
* Return a platform-friendly display path for Pi's agent directory.
|
|
990
999
|
*
|
|
991
|
-
*
|
|
992
|
-
*
|
|
993
|
-
*
|
|
994
|
-
* platform see a copyable path they recognize.
|
|
1000
|
+
* Derives the display path from Pi core's `getAgentDir()` result. Home-relative
|
|
1001
|
+
* paths use `%USERPROFILE%` on Windows and `~` on Unix-like systems; custom
|
|
1002
|
+
* absolute or relative agent directories remain visible as configured.
|
|
995
1003
|
*/
|
|
996
|
-
function
|
|
997
|
-
|
|
998
|
-
|
|
1004
|
+
function getAgentDirDisplayPath(
|
|
1005
|
+
platform: string = process.platform,
|
|
1006
|
+
agentDir: string = getAgentDir(),
|
|
1007
|
+
homeDir: string = homedir(),
|
|
1008
|
+
): string {
|
|
1009
|
+
const sep = platform.startsWith("win") ? "\\" : "/";
|
|
1010
|
+
const normalizedAgentDir = agentDir.replace(/[\\/]+/g, sep);
|
|
1011
|
+
const normalizedHomeDir = homeDir.replace(/[\\/]+/g, sep).replace(/[\\/]+$/, "");
|
|
1012
|
+
const homePrefix = `${normalizedHomeDir}${sep}`;
|
|
1013
|
+
|
|
1014
|
+
if (normalizedAgentDir === normalizedHomeDir || normalizedAgentDir.startsWith(homePrefix)) {
|
|
1015
|
+
const relative = normalizedAgentDir.slice(normalizedHomeDir.length).replace(/^[\\/]+/, "");
|
|
1016
|
+
const homeLabel = platform.startsWith("win") ? "%USERPROFILE%" : "~";
|
|
1017
|
+
return relative ? `${homeLabel}${sep}${relative}` : homeLabel;
|
|
999
1018
|
}
|
|
1000
|
-
|
|
1019
|
+
|
|
1020
|
+
return normalizedAgentDir;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Return a platform-friendly display path for Pi's `models.json`.
|
|
1025
|
+
*
|
|
1026
|
+
* This is a DISPLAY helper only. Actual I/O uses `MODELS_JSON_PATH`, resolved
|
|
1027
|
+
* from the same custom agent/config directory rules as `STATE_DIR`.
|
|
1028
|
+
*/
|
|
1029
|
+
function getModelsJsonDisplayPath(
|
|
1030
|
+
platform: string = process.platform,
|
|
1031
|
+
agentDir: string = getAgentDir(),
|
|
1032
|
+
homeDir: string = homedir(),
|
|
1033
|
+
): string {
|
|
1034
|
+
return joinDisplayPath(getAgentDirDisplayPath(platform, agentDir, homeDir), "models.json", platform);
|
|
1001
1035
|
}
|
|
1002
1036
|
|
|
1003
1037
|
function isEnabledEnv(value: string | undefined): boolean {
|
|
@@ -1099,6 +1133,28 @@ function isOpenAICompatibleProxyApi(api: unknown): boolean {
|
|
|
1099
1133
|
return lower(api) === "openai-completions";
|
|
1100
1134
|
}
|
|
1101
1135
|
|
|
1136
|
+
function isPiBuiltInLlamaCppModel(model: PiModel | undefined): boolean {
|
|
1137
|
+
if (lower(model?.provider) !== "llama.cpp" || !isOpenAICompatibleProxyApi(model?.api)) return false;
|
|
1138
|
+
|
|
1139
|
+
// Pi's built-in llama.cpp provider supplies this exact explicit compat
|
|
1140
|
+
// fingerprint. Provider ids are extension-overridable and models.json can add
|
|
1141
|
+
// cache/routing overrides, so provider id alone must never imply exemption.
|
|
1142
|
+
const compat = getCompat(model);
|
|
1143
|
+
return compat.supportsStore === false
|
|
1144
|
+
&& compat.supportsDeveloperRole === false
|
|
1145
|
+
&& compat.supportsReasoningEffort === false
|
|
1146
|
+
&& compat.supportsUsageInStreaming === false
|
|
1147
|
+
&& compat.supportsStrictMode === false
|
|
1148
|
+
&& compat.maxTokensField === "max_tokens"
|
|
1149
|
+
&& compat.sendSessionAffinityHeaders === undefined
|
|
1150
|
+
&& compat.sessionAffinityFormat === undefined
|
|
1151
|
+
&& compat.supportsLongCacheRetention === undefined;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function shouldInjectOpenAIPromptCacheKeyForModel(model: PiModel | undefined): boolean {
|
|
1155
|
+
return isOpenAICompatibleApi(model?.api);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1102
1158
|
function isResponsesPromptRewriteBypassApi(api: unknown): boolean {
|
|
1103
1159
|
const value = lower(api);
|
|
1104
1160
|
return value === "openai-codex-responses" || value === "openai-responses" || value === "azure-openai-responses";
|
|
@@ -1738,10 +1794,21 @@ function modelFromAssistantMessage(message: unknown, fallback: PiModel | undefin
|
|
|
1738
1794
|
const api = firstNonEmptyString(record.api, fallback?.api) ?? "";
|
|
1739
1795
|
if (!id || !provider) return fallback;
|
|
1740
1796
|
|
|
1797
|
+
const fallbackName = isNonEmptyString(fallback?.name) ? fallback.name : undefined;
|
|
1798
|
+
const preservesFallbackIdentity =
|
|
1799
|
+
!isVirtualRoutingModel(fallback) &&
|
|
1800
|
+
provider === fallback?.provider &&
|
|
1801
|
+
id === fallback?.id &&
|
|
1802
|
+
fallbackName !== undefined;
|
|
1803
|
+
|
|
1741
1804
|
return {
|
|
1742
1805
|
...(fallback ?? {}),
|
|
1743
1806
|
id,
|
|
1744
|
-
|
|
1807
|
+
// Direct providers such as kimi-coding may echo only a short model id
|
|
1808
|
+
// (`k3`) while the active model name (`Kimi K3`) carries the adapter token.
|
|
1809
|
+
// Preserve that display name only when response and fallback identities are
|
|
1810
|
+
// exactly the same; routed/different identities keep message-local naming.
|
|
1811
|
+
name: preservesFallbackIdentity ? fallbackName : id,
|
|
1745
1812
|
provider,
|
|
1746
1813
|
api,
|
|
1747
1814
|
baseUrl: fallback?.baseUrl ?? "",
|
|
@@ -2031,6 +2098,7 @@ function describeMissingOpenAICompatibleProxyCompat(model: PiModel): string[] {
|
|
|
2031
2098
|
|
|
2032
2099
|
if (!isOpenAICompatibleProxyApi(model.api)) return missing;
|
|
2033
2100
|
if (isOfficialOpenAIBaseUrl(model)) return missing;
|
|
2101
|
+
if (isPiBuiltInLlamaCppModel(model)) return missing;
|
|
2034
2102
|
|
|
2035
2103
|
if (compat.sendSessionAffinityHeaders === undefined) {
|
|
2036
2104
|
missing.push("sendSessionAffinityHeaders");
|
|
@@ -2058,6 +2126,7 @@ function describeOptionalOpenAICompatibleProxyCompat(model: PiModel): string[] {
|
|
|
2058
2126
|
|
|
2059
2127
|
if (!isOpenAICompatibleProxyApi(model.api)) return optional;
|
|
2060
2128
|
if (isOfficialOpenAIBaseUrl(model)) return optional;
|
|
2129
|
+
if (isPiBuiltInLlamaCppModel(model)) return optional;
|
|
2061
2130
|
|
|
2062
2131
|
if (compat.supportsLongCacheRetention !== true) {
|
|
2063
2132
|
optional.push("supportsLongCacheRetention");
|
|
@@ -2231,7 +2300,7 @@ function describeMissingDeepSeekCompat(model: PiModel): string[] {
|
|
|
2231
2300
|
}
|
|
2232
2301
|
|
|
2233
2302
|
function isDeepSeekCompatCheckApplicable(model: PiModel): boolean {
|
|
2234
|
-
return isDeepSeekLikeModel(model) && isOpenAICompatibleApi(model.api);
|
|
2303
|
+
return isDeepSeekLikeModel(model) && isOpenAICompatibleApi(model.api) && !isPiBuiltInLlamaCppModel(model);
|
|
2235
2304
|
}
|
|
2236
2305
|
|
|
2237
2306
|
function describeMissingCacheCompatForModel(model: PiModel): string[] {
|
|
@@ -2315,7 +2384,7 @@ const CACHE_PROVIDER_ADAPTERS: CacheProviderAdapter[] = [
|
|
|
2315
2384
|
return normalizeWithFallback(message, getDeepSeekRawUsage, { allowInputOnlyPiUsage: true });
|
|
2316
2385
|
},
|
|
2317
2386
|
warningText(model) {
|
|
2318
|
-
if (!
|
|
2387
|
+
if (!isDeepSeekCompatCheckApplicable(model)) return undefined;
|
|
2319
2388
|
|
|
2320
2389
|
const missing = describeMissingDeepSeekCompat(model);
|
|
2321
2390
|
if (missing.length === 0) return undefined;
|
|
@@ -2337,7 +2406,7 @@ const CACHE_PROVIDER_ADAPTERS: CacheProviderAdapter[] = [
|
|
|
2337
2406
|
return normalizeWithFallback(message, getAnthropicRawUsage);
|
|
2338
2407
|
},
|
|
2339
2408
|
warningText(model) {
|
|
2340
|
-
if (!isClaudeLikeModel(model) || !isOpenAICompatibleApi(model.api)) return undefined;
|
|
2409
|
+
if (!isClaudeLikeModel(model) || !isOpenAICompatibleApi(model.api) || isPiBuiltInLlamaCppModel(model)) return undefined;
|
|
2341
2410
|
if (getCompat(model).cacheControlFormat === "anthropic") return undefined;
|
|
2342
2411
|
|
|
2343
2412
|
return (
|
|
@@ -3947,12 +4016,13 @@ async function writePersistedCacheStats(
|
|
|
3947
4016
|
|
|
3948
4017
|
|
|
3949
4018
|
function isCompatCheckApplicable(model: PiModel): boolean {
|
|
3950
|
-
return isOpenAICompatibleProxyApi(model.api) && !isOfficialOpenAIBaseUrl(model);
|
|
4019
|
+
return isOpenAICompatibleProxyApi(model.api) && !isOfficialOpenAIBaseUrl(model) && !isPiBuiltInLlamaCppModel(model);
|
|
3951
4020
|
}
|
|
3952
4021
|
|
|
3953
4022
|
function isPromptCacheRetention400Applicable(model: PiModel): boolean {
|
|
3954
4023
|
return isOpenAICompatibleApi(model.api) &&
|
|
3955
4024
|
!isOfficialOpenAIBaseUrl(model) &&
|
|
4025
|
+
!isPiBuiltInLlamaCppModel(model) &&
|
|
3956
4026
|
getCompat(model).supportsLongCacheRetention === true;
|
|
3957
4027
|
}
|
|
3958
4028
|
|
|
@@ -3969,6 +4039,7 @@ function isPromptCacheRetention400Applicable(model: PiModel): boolean {
|
|
|
3969
4039
|
*/
|
|
3970
4040
|
function isSessionAffinity403Applicable(model: PiModel): boolean {
|
|
3971
4041
|
if (!isOpenAICompatibleProxyApi(model.api)) return false;
|
|
4042
|
+
if (isPiBuiltInLlamaCppModel(model)) return false;
|
|
3972
4043
|
return getCompat(model).sendSessionAffinityHeaders === true;
|
|
3973
4044
|
}
|
|
3974
4045
|
|
|
@@ -3985,6 +4056,7 @@ function isSessionAffinity403Applicable(model: PiModel): boolean {
|
|
|
3985
4056
|
function isOpenAISdkHeader403Applicable(model: PiModel): boolean {
|
|
3986
4057
|
if (!isOpenAICompatibleProxyApi(model.api)) return false;
|
|
3987
4058
|
if (isOfficialOpenAIBaseUrl(model)) return false;
|
|
4059
|
+
if (isPiBuiltInLlamaCppModel(model)) return false;
|
|
3988
4060
|
return getCompat(model).sendSessionAffinityHeaders !== true;
|
|
3989
4061
|
}
|
|
3990
4062
|
|
|
@@ -4026,6 +4098,13 @@ function describeRouterChannelDiagnostics(model: PiModel): string[] {
|
|
|
4026
4098
|
return notes;
|
|
4027
4099
|
}
|
|
4028
4100
|
|
|
4101
|
+
// Pi 0.81+ built-in llama.cpp uses an OpenAI-shaped transport, but its
|
|
4102
|
+
// untouched explicit compat fingerprint does not expose proxy-routing or
|
|
4103
|
+
// session-affinity configuration. Same-id overrides are not exempt.
|
|
4104
|
+
if (isPiBuiltInLlamaCppModel(model)) {
|
|
4105
|
+
return notes;
|
|
4106
|
+
}
|
|
4107
|
+
|
|
4029
4108
|
// ── 1. OpenRouter ────────────────────────────────────────────────
|
|
4030
4109
|
if (
|
|
4031
4110
|
baseUrl.includes("openrouter.ai") ||
|
|
@@ -5745,8 +5824,10 @@ export const __internals_for_tests = {
|
|
|
5745
5824
|
hasEffectivePromptCacheKey,
|
|
5746
5825
|
isNonEmptyString,
|
|
5747
5826
|
shouldInjectOpenAIPromptCacheKey,
|
|
5827
|
+
shouldInjectOpenAIPromptCacheKeyForModel,
|
|
5748
5828
|
isOpenAICompatibleApi,
|
|
5749
5829
|
isOpenAICompatibleProxyApi,
|
|
5830
|
+
isPiBuiltInLlamaCppModel,
|
|
5750
5831
|
isResponsesPromptRewriteBypassApi,
|
|
5751
5832
|
isMistralConversationsApi,
|
|
5752
5833
|
isOpenAIFamilyModel,
|
|
@@ -5884,8 +5965,10 @@ export const __internals_for_tests = {
|
|
|
5884
5965
|
getAssistantMessageModelTokenValues,
|
|
5885
5966
|
getCompat,
|
|
5886
5967
|
modelKey,
|
|
5968
|
+
modelFromAssistantMessage,
|
|
5887
5969
|
consolidateDirectProviderStatsModel,
|
|
5888
5970
|
// Platform-friendly path helpers
|
|
5971
|
+
getAgentDirDisplayPath,
|
|
5889
5972
|
getModelsJsonDisplayPath,
|
|
5890
5973
|
buildProviderCompatOverride,
|
|
5891
5974
|
buildModelCompatOverride,
|
|
@@ -6482,6 +6565,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
6482
6565
|
|
|
6483
6566
|
pi.on("before_agent_start", async (event, _ctx) => {
|
|
6484
6567
|
latestCacheHint = undefined;
|
|
6568
|
+
// Clear the legacy global before any bypass/disable early return. A valid
|
|
6569
|
+
// rewrite path republishes the current session key below; otherwise callers
|
|
6570
|
+
// must not observe a stale key from the previously selected model/route.
|
|
6571
|
+
delete getProtocolGlobal().__piCacheOptimizerCacheKey__;
|
|
6485
6572
|
const routeSnapshot = resolveActiveRouteSnapshot(_ctx.model, _ctx);
|
|
6486
6573
|
const routedModel = routeSnapshot
|
|
6487
6574
|
? findModelInRegistry(_ctx.modelRegistry, routeSnapshot.provider, routeSnapshot.modelId) ?? routeSnapshotToPiModel(routeSnapshot, _ctx.model)
|
|
@@ -6567,7 +6654,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
6567
6654
|
timestamp: Date.now(),
|
|
6568
6655
|
};
|
|
6569
6656
|
const globals = getProtocolGlobal();
|
|
6570
|
-
|
|
6657
|
+
if (promptCacheKey) {
|
|
6658
|
+
globals.__piCacheOptimizerCacheKey__ = promptCacheKey;
|
|
6659
|
+
} else {
|
|
6660
|
+
delete globals.__piCacheOptimizerCacheKey__;
|
|
6661
|
+
}
|
|
6571
6662
|
};
|
|
6572
6663
|
|
|
6573
6664
|
if (optimized.changed && optimized.systemPrompt.trim().length > 0) {
|
|
@@ -6628,7 +6719,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6628
6719
|
|
|
6629
6720
|
if (!shouldInjectOpenAIPromptCacheKey()) return undefined;
|
|
6630
6721
|
const requestModel = resolveRouteModel(ctx.model, ctx) ?? ctx.model;
|
|
6631
|
-
if (!
|
|
6722
|
+
if (!shouldInjectOpenAIPromptCacheKeyForModel(requestModel)) return undefined;
|
|
6632
6723
|
|
|
6633
6724
|
return addOpenAIPromptCacheKey(event.payload, getSessionPromptCacheKey(ctx));
|
|
6634
6725
|
});
|
package/package.json
CHANGED