pi-cache-optimizer 2.6.15 → 2.6.17
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 +4 -4
- package/README.zh-CN.md +4 -4
- package/index.ts +81 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Pi extension for improving provider-side KV / prompt cache hit rates. It keeps s
|
|
|
32
32
|
- Requests long cache retention when Pi/provider compat supports it.
|
|
33
33
|
- Adds a session-id `prompt_cache_key` fallback for `openai-completions` / `openai-responses` payloads when no effective key exists.
|
|
34
34
|
- Warns once for third-party OpenAI-compatible proxies missing cache/session-affinity compat flags.
|
|
35
|
-
- Detects Anthropic adaptive thinking models (opus-4.6+, sonnet-4.6
|
|
35
|
+
- Detects Anthropic adaptive thinking models (opus-4.6+, sonnet-4.6+ including Sonnet 5, fable-5+) missing `forceAdaptiveThinking: true` compat.
|
|
36
36
|
- Shows restart-persistent provider/model footer stats for supported model families.
|
|
37
37
|
- 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
38
|
|
|
@@ -111,7 +111,7 @@ Notes:
|
|
|
111
111
|
|
|
112
112
|
## Anthropic adaptive thinking models
|
|
113
113
|
|
|
114
|
-
Claude models from opus-4.6 / sonnet-4.6 / fable-5 onwards require `forceAdaptiveThinking: true` in compat. Without it, Pi sends the legacy thinking format and Anthropic rejects the request.
|
|
114
|
+
Claude models from opus-4.6 / sonnet-4.6 (including Sonnet 5) / fable-5 onwards require `forceAdaptiveThinking: true` in compat. Without it, Pi sends the legacy thinking format and Anthropic rejects the request.
|
|
115
115
|
|
|
116
116
|
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:
|
|
117
117
|
|
|
@@ -126,7 +126,7 @@ Pi's built-in catalog already sets this flag for official models. Custom channel
|
|
|
126
126
|
"forceAdaptiveThinking": true
|
|
127
127
|
},
|
|
128
128
|
"models": [
|
|
129
|
-
{ "id": "claude-
|
|
129
|
+
{ "id": "claude-sonnet-5", "name": "Claude Sonnet 5" }
|
|
130
130
|
]
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -140,7 +140,7 @@ Or use model-level override:
|
|
|
140
140
|
"providers": {
|
|
141
141
|
"your-claude-channel": {
|
|
142
142
|
"modelOverrides": {
|
|
143
|
-
"claude-
|
|
143
|
+
"claude-sonnet-5": {
|
|
144
144
|
"compat": {
|
|
145
145
|
"forceAdaptiveThinking": true
|
|
146
146
|
}
|
package/README.zh-CN.md
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
- 在 Pi / provider compat 支持时请求长缓存保留。
|
|
33
33
|
- 对 `openai-completions` / `openai-responses` 请求,在没有有效 key 时使用 Pi session id 补 `prompt_cache_key`。
|
|
34
34
|
- 对缺少缓存 / session-affinity compat 的第三方 OpenAI-compatible 代理给出一次性提醒。
|
|
35
|
-
- 检测 Anthropic adaptive thinking 模型(opus-4.6+、sonnet-4.6
|
|
35
|
+
- 检测 Anthropic adaptive thinking 模型(opus-4.6+、sonnet-4.6+ 含 Sonnet 5、fable-5+)是否缺少 `forceAdaptiveThinking: true` compat。
|
|
36
36
|
- 为支持的模型家族显示可跨 Pi 进程 / 终端重启延续的 provider/model footer 缓存统计。
|
|
37
37
|
- 通过版本化全局协议(`Symbol.for("pi.routing.registry.v1")` 与 `Symbol.for("pi.cache.hints.v1")`)支持可选的 router extension 集成,而不导入任何 router 包。
|
|
38
38
|
|
|
@@ -111,7 +111,7 @@ LiteLLM / OneAPI / NewAPI / 类 OpenRouter 渠道等第三方 `openai-completion
|
|
|
111
111
|
|
|
112
112
|
## Anthropic adaptive thinking 模型
|
|
113
113
|
|
|
114
|
-
Claude 从 opus-4.6 / sonnet-4.6
|
|
114
|
+
Claude 从 opus-4.6 / sonnet-4.6(含 Sonnet 5)/ fable-5 开始需要在 compat 中设置 `forceAdaptiveThinking: true`。缺少此 flag 时,Pi 会发送旧版 thinking 格式,Anthropic 会拒绝请求。
|
|
115
115
|
|
|
116
116
|
Pi 内置 catalog 已为官方模型设置此 flag。`models.json` 中覆盖这些模型的自定义渠道必须包含该 flag:
|
|
117
117
|
|
|
@@ -126,7 +126,7 @@ Pi 内置 catalog 已为官方模型设置此 flag。`models.json` 中覆盖这
|
|
|
126
126
|
"forceAdaptiveThinking": true
|
|
127
127
|
},
|
|
128
128
|
"models": [
|
|
129
|
-
{ "id": "claude-
|
|
129
|
+
{ "id": "claude-sonnet-5", "name": "Claude Sonnet 5" }
|
|
130
130
|
]
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -140,7 +140,7 @@ Pi 内置 catalog 已为官方模型设置此 flag。`models.json` 中覆盖这
|
|
|
140
140
|
"providers": {
|
|
141
141
|
"your-claude-channel": {
|
|
142
142
|
"modelOverrides": {
|
|
143
|
-
"claude-
|
|
143
|
+
"claude-sonnet-5": {
|
|
144
144
|
"compat": {
|
|
145
145
|
"forceAdaptiveThinking": true
|
|
146
146
|
}
|
package/index.ts
CHANGED
|
@@ -1143,14 +1143,14 @@ function isGeminiLikeAssistantMessage(message: unknown, model: PiModel | undefin
|
|
|
1143
1143
|
*
|
|
1144
1144
|
* Adaptive-generation models (from pi-ai built-in catalog) include:
|
|
1145
1145
|
* claude-opus-4-6, claude-opus-4-7, claude-opus-4-8 (also dotted 4.6/4.7/4.8)
|
|
1146
|
-
* claude-sonnet-4-6
|
|
1146
|
+
* claude-sonnet-4-6, claude-sonnet-5
|
|
1147
1147
|
* claude-fable-5
|
|
1148
1148
|
*
|
|
1149
1149
|
* We match broadly: opus >= 4-6, sonnet >= 4-6, fable >= 5.
|
|
1150
1150
|
* Ids may carry date-stamp or size suffixes like "[1M]".
|
|
1151
1151
|
*/
|
|
1152
|
-
const ADAPTIVE_OPUS_PATTERN = /(^|[\/\s:_-])(opus-4[.-][6-9]|opus-4-[1-9][0-9])($|[-_.:\/\s\[])/i;
|
|
1153
|
-
const ADAPTIVE_SONNET_PATTERN = /(^|[\/\s:_-])(sonnet-4[.-][6-9]|sonnet-4-[1-9][0-9])($|[-_.:\/\s\[])/i;
|
|
1152
|
+
const ADAPTIVE_OPUS_PATTERN = /(^|[\/\s:_-])(opus-4[.-][6-9]|opus-4-[1-9][0-9]|opus-([5-9]|[1-9][0-9]))($|[-_.:\/\s\[])/i;
|
|
1153
|
+
const ADAPTIVE_SONNET_PATTERN = /(^|[\/\s:_-])(sonnet-4[.-][6-9]|sonnet-4-[1-9][0-9]|sonnet-([5-9]|[1-9][0-9]))($|[-_.:\/\s\[])/i;
|
|
1154
1154
|
const ADAPTIVE_FABLE_PATTERN = /(^|[\/\s:_-])fable-([5-9]|[1-9][0-9])($|[-_.:\/\s\[])/i;
|
|
1155
1155
|
|
|
1156
1156
|
function isAdaptiveGenerationModel(model: PiModel | undefined): boolean {
|
|
@@ -3943,6 +3943,22 @@ function isSessionAffinity403Applicable(model: PiModel): boolean {
|
|
|
3943
3943
|
return getCompat(model).sendSessionAffinityHeaders === true;
|
|
3944
3944
|
}
|
|
3945
3945
|
|
|
3946
|
+
/**
|
|
3947
|
+
* Whether an OpenAI SDK header/User-Agent 403 diagnostic applies.
|
|
3948
|
+
*
|
|
3949
|
+
* Some third-party OpenAI-compatible proxies/CDNs/WAFs allow a plain curl
|
|
3950
|
+
* request but block the OpenAI JS SDK's default request fingerprint, especially
|
|
3951
|
+
* its `User-Agent: OpenAI/JS ...` and `X-Stainless-*` headers. This is distinct
|
|
3952
|
+
* from session-affinity header blocking: it applies when session-affinity
|
|
3953
|
+
* headers are NOT enabled but the model still uses a non-official
|
|
3954
|
+
* OpenAI-compatible proxy transport.
|
|
3955
|
+
*/
|
|
3956
|
+
function isOpenAISdkHeader403Applicable(model: PiModel): boolean {
|
|
3957
|
+
if (!isOpenAICompatibleProxyApi(model.api)) return false;
|
|
3958
|
+
if (isOfficialOpenAIBaseUrl(model)) return false;
|
|
3959
|
+
return getCompat(model).sendSessionAffinityHeaders !== true;
|
|
3960
|
+
}
|
|
3961
|
+
|
|
3946
3962
|
/**
|
|
3947
3963
|
* Detect router / channel profiles from a PiModel and return diagnostic notes.
|
|
3948
3964
|
*
|
|
@@ -4140,7 +4156,7 @@ function getCompatCheckNotApplicableLines(model: PiModel): string[] {
|
|
|
4140
4156
|
return ["ℹ️ Compat check not applicable for this model."];
|
|
4141
4157
|
}
|
|
4142
4158
|
|
|
4143
|
-
function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400?: boolean; sessionAffinity403?: boolean } = {}): string {
|
|
4159
|
+
function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400?: boolean; sessionAffinity403?: boolean; openAISdkHeader403?: boolean } = {}): string {
|
|
4144
4160
|
const lines: string[] = [];
|
|
4145
4161
|
lines.push(`Provider: ${model.provider}`);
|
|
4146
4162
|
lines.push(`Model: ${model.id}`);
|
|
@@ -4216,6 +4232,20 @@ function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400
|
|
|
4216
4232
|
lines.push(" (session_id, x-client-request-id, x-session-affinity) and return 403. If you");
|
|
4217
4233
|
lines.push(" see 403 errors, run /cache-optimizer fix to set sendSessionAffinityHeaders: false.");
|
|
4218
4234
|
}
|
|
4235
|
+
} else if (isOpenAISdkHeader403Applicable(model)) {
|
|
4236
|
+
lines.push("");
|
|
4237
|
+
if (options.openAISdkHeader403) {
|
|
4238
|
+
lines.push("⚠️ A 403 response was observed while sendSessionAffinityHeaders is not enabled.");
|
|
4239
|
+
lines.push(" The proxy/CDN may be blocking the OpenAI JS SDK request fingerprint");
|
|
4240
|
+
lines.push(" (for example User-Agent: OpenAI/JS ... or X-Stainless-* headers). This");
|
|
4241
|
+
lines.push(" is provider/WAF-specific; /cache-optimizer fix will not auto-write headers.");
|
|
4242
|
+
lines.push(` Manual workaround: add a provider-level headers.User-Agent override in ${getModelsJsonDisplayPath()}`);
|
|
4243
|
+
lines.push(" only after testing the value with the affected provider.");
|
|
4244
|
+
} else {
|
|
4245
|
+
lines.push("ℹ️ If 403 persists after disabling sendSessionAffinityHeaders, some CDNs/WAFs");
|
|
4246
|
+
lines.push(" may block the OpenAI JS SDK User-Agent / X-Stainless-* headers. Test the");
|
|
4247
|
+
lines.push(" provider manually before adding a provider-level headers.User-Agent override.");
|
|
4248
|
+
}
|
|
4219
4249
|
}
|
|
4220
4250
|
|
|
4221
4251
|
// ── Router/channel diagnostics ──
|
|
@@ -4408,12 +4438,19 @@ function buildCompatDiagnosis(model: PiModel): string | undefined {
|
|
|
4408
4438
|
if (isPromptCacheRetention400Applicable(model)) {
|
|
4409
4439
|
lines.push(getPromptCacheRetentionUnsupportedHint());
|
|
4410
4440
|
}
|
|
4411
|
-
// Advisory for 403 session-affinity header blocking (only when enabled)
|
|
4441
|
+
// Advisory for 403 session-affinity header blocking (only when enabled),
|
|
4442
|
+
// or OpenAI SDK header/User-Agent blocking after session affinity is disabled.
|
|
4412
4443
|
if (isSessionAffinity403Applicable(model)) {
|
|
4413
4444
|
lines.push(
|
|
4414
4445
|
"ℹ️ Session affinity headers are enabled. If you see 403 \"blocked\" errors,",
|
|
4415
4446
|
" the proxy/CDN may be blocking Pi's custom headers. Set sendSessionAffinityHeaders: false.",
|
|
4416
4447
|
);
|
|
4448
|
+
} else if (isOpenAISdkHeader403Applicable(model)) {
|
|
4449
|
+
lines.push(
|
|
4450
|
+
"ℹ️ If 403 persists with sendSessionAffinityHeaders disabled, the proxy/CDN may",
|
|
4451
|
+
" be blocking the OpenAI JS SDK User-Agent / X-Stainless-* headers. Test a",
|
|
4452
|
+
" provider-level headers.User-Agent override manually; /fix does not auto-write it.",
|
|
4453
|
+
);
|
|
4417
4454
|
}
|
|
4418
4455
|
appendOptionalOpenAIProxyCompatAdviceLines(lines, optionalOpenAIProxyCompat);
|
|
4419
4456
|
} else {
|
|
@@ -5692,6 +5729,7 @@ export const __internals_for_tests = {
|
|
|
5692
5729
|
isCompatCheckApplicable,
|
|
5693
5730
|
isPromptCacheRetention400Applicable,
|
|
5694
5731
|
isSessionAffinity403Applicable,
|
|
5732
|
+
isOpenAISdkHeader403Applicable,
|
|
5695
5733
|
hasPromptCacheRetentionUnsupportedSignal,
|
|
5696
5734
|
// Non-GPT OpenAI-compatible model detection
|
|
5697
5735
|
isKimiLikeModel,
|
|
@@ -5910,6 +5948,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
5910
5948
|
const warnedPromptCacheRetention400Models = new Set<string>();
|
|
5911
5949
|
const sendSessionAffinityHeaders403Models = new Set<string>();
|
|
5912
5950
|
const warnedSendSessionAffinityHeaders403Models = new Set<string>();
|
|
5951
|
+
const openAISdkHeader403Models = new Set<string>();
|
|
5952
|
+
const warnedOpenAISdkHeader403Models = new Set<string>();
|
|
5913
5953
|
let cacheStatsByModel: Record<string, CacheStats> = {};
|
|
5914
5954
|
let cacheStatsTotalsByModel: Record<string, CacheStats> = {};
|
|
5915
5955
|
let cacheStatsLegacyFamily: Partial<Record<CacheProviderId, CacheStats>> = emptyAllCacheStats();
|
|
@@ -6576,28 +6616,42 @@ export default function (pi: ExtensionAPI) {
|
|
|
6576
6616
|
return;
|
|
6577
6617
|
}
|
|
6578
6618
|
|
|
6579
|
-
// ── 403:
|
|
6580
|
-
//
|
|
6581
|
-
// Pi's openai-completions/responses adapters send three custom HTTP
|
|
6582
|
-
// headers (session_id, x-client-request-id, x-session-affinity) when
|
|
6583
|
-
// sendSessionAffinityHeaders is enabled. Some third-party proxies /
|
|
6584
|
-
// CDNs / WAFs block these custom headers and return 403. We record a
|
|
6585
|
-
// one-time per-session notification and surface it in doctor/fix so the
|
|
6586
|
-
// user can disable the flag for the blocked model.
|
|
6619
|
+
// ── 403: proxy/CDN/WAF blocking ──
|
|
6587
6620
|
if (event.status === 403) {
|
|
6588
|
-
if (!isSessionAffinity403Applicable(model)) return;
|
|
6589
|
-
|
|
6590
6621
|
const key403 = modelKey(model);
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6622
|
+
|
|
6623
|
+
// Case 1: Pi's custom session-affinity headers are enabled. Offer the
|
|
6624
|
+
// existing safe compat fix (`sendSessionAffinityHeaders: false`).
|
|
6625
|
+
if (isSessionAffinity403Applicable(model)) {
|
|
6626
|
+
sendSessionAffinityHeaders403Models.add(key403);
|
|
6627
|
+
if (warnedSendSessionAffinityHeaders403Models.has(key403)) return;
|
|
6628
|
+
warnedSendSessionAffinityHeaders403Models.add(key403);
|
|
6629
|
+
ctx.ui.notify(
|
|
6630
|
+
`⚠️ ${LOG_PREFIX}: ${key403} returned HTTP 403 while sendSessionAffinityHeaders is enabled. ` +
|
|
6631
|
+
`The proxy/CDN may be blocking Pi's custom session-affinity headers (session_id, x-client-request-id, x-session-affinity). ` +
|
|
6632
|
+
`Run /cache-optimizer doctor for details and /cache-optimizer fix to set sendSessionAffinityHeaders: false.`,
|
|
6633
|
+
"warning",
|
|
6634
|
+
);
|
|
6635
|
+
return;
|
|
6636
|
+
}
|
|
6637
|
+
|
|
6638
|
+
// Case 2: session-affinity headers are already absent/disabled, but the
|
|
6639
|
+
// provider still returns 403. Some CDNs/WAFs block the OpenAI JS SDK
|
|
6640
|
+
// default request fingerprint (User-Agent: OpenAI/JS ... or
|
|
6641
|
+
// X-Stainless-* headers). This is provider-specific; do NOT auto-fix by
|
|
6642
|
+
// writing a User-Agent because the right value depends on the endpoint.
|
|
6643
|
+
if (isOpenAISdkHeader403Applicable(model)) {
|
|
6644
|
+
openAISdkHeader403Models.add(key403);
|
|
6645
|
+
if (warnedOpenAISdkHeader403Models.has(key403)) return;
|
|
6646
|
+
warnedOpenAISdkHeader403Models.add(key403);
|
|
6647
|
+
ctx.ui.notify(
|
|
6648
|
+
`⚠️ ${LOG_PREFIX}: ${key403} returned HTTP 403 even though sendSessionAffinityHeaders is not enabled. ` +
|
|
6649
|
+
`The proxy/CDN may be blocking the OpenAI JS SDK User-Agent / X-Stainless-* headers. ` +
|
|
6650
|
+
`Run /cache-optimizer doctor for manual diagnostic guidance; /cache-optimizer fix will not auto-write User-Agent headers.`,
|
|
6651
|
+
"warning",
|
|
6652
|
+
);
|
|
6653
|
+
return;
|
|
6654
|
+
}
|
|
6601
6655
|
}
|
|
6602
6656
|
});
|
|
6603
6657
|
|
|
@@ -6720,7 +6774,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6720
6774
|
cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
|
|
6721
6775
|
return;
|
|
6722
6776
|
}
|
|
6723
|
-
const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)) });
|
|
6777
|
+
const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)), openAISdkHeader403: openAISdkHeader403Models.has(modelKey(model)) });
|
|
6724
6778
|
const adapter = selectAdapterForModel(model);
|
|
6725
6779
|
const sk = model ? sessionModelKey(model) : undefined;
|
|
6726
6780
|
const statsState = model ? cacheStatsTotalsByModel[modelKey(model)] : undefined;
|
|
@@ -7141,7 +7195,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7141
7195
|
if (!model) {
|
|
7142
7196
|
cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
|
|
7143
7197
|
} else {
|
|
7144
|
-
const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)) });
|
|
7198
|
+
const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)), openAISdkHeader403: openAISdkHeader403Models.has(modelKey(model)) });
|
|
7145
7199
|
const adapter = selectAdapterForModel(model);
|
|
7146
7200
|
const sk = model ? sessionModelKey(model) : undefined;
|
|
7147
7201
|
const statsState = model ? cacheStatsTotalsByModel[modelKey(model)] : undefined;
|
package/package.json
CHANGED