pi-cache-optimizer 2.6.13 → 2.6.15
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/index.ts +118 -17
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -1992,10 +1992,16 @@ function describeMissingOpenAICompatibleProxyCompat(model: PiModel): string[] {
|
|
|
1992
1992
|
if (!isOpenAICompatibleProxyApi(model.api)) return missing;
|
|
1993
1993
|
if (isOfficialOpenAIBaseUrl(model)) return missing;
|
|
1994
1994
|
|
|
1995
|
-
if (compat.sendSessionAffinityHeaders
|
|
1995
|
+
if (compat.sendSessionAffinityHeaders === undefined) {
|
|
1996
1996
|
missing.push("sendSessionAffinityHeaders");
|
|
1997
1997
|
}
|
|
1998
1998
|
|
|
1999
|
+
// Explicit `sendSessionAffinityHeaders: false` is a valid safe opt-out for
|
|
2000
|
+
// proxies/CDNs/WAFs that block Pi's custom affinity headers with HTTP 403.
|
|
2001
|
+
// Treat only a missing/undefined value as missing compat; do not mark an
|
|
2002
|
+
// intentional false override as ⚠️ compat or let /cache-optimizer fix turn it
|
|
2003
|
+
// back to true.
|
|
2004
|
+
//
|
|
1999
2005
|
// NOTE: supportsLongCacheRetention is intentionally NOT checked here.
|
|
2000
2006
|
// Per spec, it is optional/risky advisory text only and must NOT trigger
|
|
2001
2007
|
// the ⚠️ compat marker. The before_provider_request hook proactively
|
|
@@ -3921,6 +3927,22 @@ function isPromptCacheRetention400Applicable(model: PiModel): boolean {
|
|
|
3921
3927
|
getCompat(model).supportsLongCacheRetention === true;
|
|
3922
3928
|
}
|
|
3923
3929
|
|
|
3930
|
+
/**
|
|
3931
|
+
* Whether the 403 sendSessionAffinityHeaders diagnostic applies to a model.
|
|
3932
|
+
*
|
|
3933
|
+
* Pi's openai-completions / openai-responses adapters send three custom HTTP
|
|
3934
|
+
* headers (session_id, x-client-request-id, x-session-affinity) when
|
|
3935
|
+
* `sendSessionAffinityHeaders` is enabled. Some third-party proxies / CDNs /
|
|
3936
|
+
* WAFs block these custom headers and return HTTP 403 "Your request was
|
|
3937
|
+
* blocked". This guard returns true only for OpenAI-compatible APIs whose
|
|
3938
|
+
* merged compat currently enables session-affinity headers, so the 403
|
|
3939
|
+
* monitoring and doctor/fix paths can advise disabling the flag.
|
|
3940
|
+
*/
|
|
3941
|
+
function isSessionAffinity403Applicable(model: PiModel): boolean {
|
|
3942
|
+
if (!isOpenAICompatibleApi(model.api)) return false;
|
|
3943
|
+
return getCompat(model).sendSessionAffinityHeaders === true;
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3924
3946
|
/**
|
|
3925
3947
|
* Detect router / channel profiles from a PiModel and return diagnostic notes.
|
|
3926
3948
|
*
|
|
@@ -4118,7 +4140,7 @@ function getCompatCheckNotApplicableLines(model: PiModel): string[] {
|
|
|
4118
4140
|
return ["ℹ️ Compat check not applicable for this model."];
|
|
4119
4141
|
}
|
|
4120
4142
|
|
|
4121
|
-
function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400?: boolean } = {}): string {
|
|
4143
|
+
function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400?: boolean; sessionAffinity403?: boolean } = {}): string {
|
|
4122
4144
|
const lines: string[] = [];
|
|
4123
4145
|
lines.push(`Provider: ${model.provider}`);
|
|
4124
4146
|
lines.push(`Model: ${model.id}`);
|
|
@@ -4177,6 +4199,25 @@ function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400
|
|
|
4177
4199
|
}
|
|
4178
4200
|
}
|
|
4179
4201
|
|
|
4202
|
+
// ── Session affinity 403 diagnostics ──
|
|
4203
|
+
// Advises the user when sendSessionAffinityHeaders is enabled, since some
|
|
4204
|
+
// third-party proxies / CDNs / WAFs block Pi's custom session-affinity HTTP
|
|
4205
|
+
// headers (session_id, x-client-request-id, x-session-affinity) and return
|
|
4206
|
+
// 403. If a 403 was already observed for this model, show a stronger hint.
|
|
4207
|
+
if (isSessionAffinity403Applicable(model)) {
|
|
4208
|
+
lines.push("");
|
|
4209
|
+
if (options.sessionAffinity403) {
|
|
4210
|
+
lines.push("⚠️ A 403 response was observed while sendSessionAffinityHeaders is enabled.");
|
|
4211
|
+
lines.push(" The proxy/CDN likely blocks Pi's custom session-affinity headers (session_id,");
|
|
4212
|
+
lines.push(" x-client-request-id, x-session-affinity). Run /cache-optimizer fix to");
|
|
4213
|
+
lines.push(` set sendSessionAffinityHeaders: false in ${getModelsJsonDisplayPath()}.`);
|
|
4214
|
+
} else {
|
|
4215
|
+
lines.push("ℹ️ Session affinity headers are enabled. Some CDNs/WAFs block custom headers");
|
|
4216
|
+
lines.push(" (session_id, x-client-request-id, x-session-affinity) and return 403. If you");
|
|
4217
|
+
lines.push(" see 403 errors, run /cache-optimizer fix to set sendSessionAffinityHeaders: false.");
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4180
4221
|
// ── Router/channel diagnostics ──
|
|
4181
4222
|
const routerNotes = describeRouterChannelDiagnostics(model);
|
|
4182
4223
|
if (routerNotes.length > 0) {
|
|
@@ -4367,6 +4408,13 @@ function buildCompatDiagnosis(model: PiModel): string | undefined {
|
|
|
4367
4408
|
if (isPromptCacheRetention400Applicable(model)) {
|
|
4368
4409
|
lines.push(getPromptCacheRetentionUnsupportedHint());
|
|
4369
4410
|
}
|
|
4411
|
+
// Advisory for 403 session-affinity header blocking (only when enabled).
|
|
4412
|
+
if (isSessionAffinity403Applicable(model)) {
|
|
4413
|
+
lines.push(
|
|
4414
|
+
"ℹ️ Session affinity headers are enabled. If you see 403 \"blocked\" errors,",
|
|
4415
|
+
" the proxy/CDN may be blocking Pi's custom headers. Set sendSessionAffinityHeaders: false.",
|
|
4416
|
+
);
|
|
4417
|
+
}
|
|
4370
4418
|
appendOptionalOpenAIProxyCompatAdviceLines(lines, optionalOpenAIProxyCompat);
|
|
4371
4419
|
} else {
|
|
4372
4420
|
lines.push(...getCompatCheckNotApplicableLines(model));
|
|
@@ -5643,6 +5691,7 @@ export const __internals_for_tests = {
|
|
|
5643
5691
|
isOfficialOpenAIBaseUrl,
|
|
5644
5692
|
isCompatCheckApplicable,
|
|
5645
5693
|
isPromptCacheRetention400Applicable,
|
|
5694
|
+
isSessionAffinity403Applicable,
|
|
5646
5695
|
hasPromptCacheRetentionUnsupportedSignal,
|
|
5647
5696
|
// Non-GPT OpenAI-compatible model detection
|
|
5648
5697
|
isKimiLikeModel,
|
|
@@ -5859,6 +5908,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
5859
5908
|
const warnedModels = new Set<string>();
|
|
5860
5909
|
const promptCacheRetention400Models = new Set<string>();
|
|
5861
5910
|
const warnedPromptCacheRetention400Models = new Set<string>();
|
|
5911
|
+
const sendSessionAffinityHeaders403Models = new Set<string>();
|
|
5912
|
+
const warnedSendSessionAffinityHeaders403Models = new Set<string>();
|
|
5862
5913
|
let cacheStatsByModel: Record<string, CacheStats> = {};
|
|
5863
5914
|
let cacheStatsTotalsByModel: Record<string, CacheStats> = {};
|
|
5864
5915
|
let cacheStatsLegacyFamily: Partial<Record<CacheProviderId, CacheStats>> = emptyAllCacheStats();
|
|
@@ -6506,20 +6557,48 @@ export default function (pi: ExtensionAPI) {
|
|
|
6506
6557
|
pi.on("after_provider_response", (event, ctx) => {
|
|
6507
6558
|
const model = resolveRouteModel(ctx.model, ctx) ?? ctx.model;
|
|
6508
6559
|
if (!runtimeOptimizerEnabled || !model) return;
|
|
6509
|
-
if (event.status !== 400) return;
|
|
6510
|
-
if (!isPromptCacheRetention400Applicable(model)) return;
|
|
6511
|
-
if (!hasPromptCacheRetentionUnsupportedSignal(event.headers)) return;
|
|
6512
6560
|
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6561
|
+
// ── 400: prompt_cache_retention unsupported ──
|
|
6562
|
+
if (event.status === 400) {
|
|
6563
|
+
if (!isPromptCacheRetention400Applicable(model)) return;
|
|
6564
|
+
if (!hasPromptCacheRetentionUnsupportedSignal(event.headers)) return;
|
|
6565
|
+
|
|
6566
|
+
const key = modelKey(model);
|
|
6567
|
+
promptCacheRetention400Models.add(key);
|
|
6568
|
+
if (warnedPromptCacheRetention400Models.has(key)) return;
|
|
6569
|
+
warnedPromptCacheRetention400Models.add(key);
|
|
6570
|
+
ctx.ui.notify(
|
|
6571
|
+
`⚠️ ${LOG_PREFIX}: ${key} returned HTTP 400 while supportsLongCacheRetention is enabled. ` +
|
|
6572
|
+
getPromptCacheRetentionUnsupportedHint() +
|
|
6573
|
+
` Run /cache-optimizer doctor for the exact edit location.`,
|
|
6574
|
+
"warning",
|
|
6575
|
+
);
|
|
6576
|
+
return;
|
|
6577
|
+
}
|
|
6578
|
+
|
|
6579
|
+
// ── 403: sendSessionAffinityHeaders blocked by CDN/WAF ──
|
|
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.
|
|
6587
|
+
if (event.status === 403) {
|
|
6588
|
+
if (!isSessionAffinity403Applicable(model)) return;
|
|
6589
|
+
|
|
6590
|
+
const key403 = modelKey(model);
|
|
6591
|
+
sendSessionAffinityHeaders403Models.add(key403);
|
|
6592
|
+
if (warnedSendSessionAffinityHeaders403Models.has(key403)) return;
|
|
6593
|
+
warnedSendSessionAffinityHeaders403Models.add(key403);
|
|
6594
|
+
ctx.ui.notify(
|
|
6595
|
+
`⚠️ ${LOG_PREFIX}: ${key403} returned HTTP 403 while sendSessionAffinityHeaders is enabled. ` +
|
|
6596
|
+
`The proxy/CDN may be blocking Pi's custom session-affinity headers (session_id, x-client-request-id, x-session-affinity). ` +
|
|
6597
|
+
`Run /cache-optimizer doctor for details and /cache-optimizer fix to set sendSessionAffinityHeaders: false.`,
|
|
6598
|
+
"warning",
|
|
6599
|
+
);
|
|
6600
|
+
return;
|
|
6601
|
+
}
|
|
6523
6602
|
});
|
|
6524
6603
|
|
|
6525
6604
|
pi.on("message_end", async (event, ctx) => {
|
|
@@ -6641,7 +6720,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6641
6720
|
cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
|
|
6642
6721
|
return;
|
|
6643
6722
|
}
|
|
6644
|
-
const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)) });
|
|
6723
|
+
const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)) });
|
|
6645
6724
|
const adapter = selectAdapterForModel(model);
|
|
6646
6725
|
const sk = model ? sessionModelKey(model) : undefined;
|
|
6647
6726
|
const statsState = model ? cacheStatsTotalsByModel[modelKey(model)] : undefined;
|
|
@@ -6731,6 +6810,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
6731
6810
|
};
|
|
6732
6811
|
}
|
|
6733
6812
|
|
|
6813
|
+
// If no regular missing compat flags but the model has a recorded
|
|
6814
|
+
// 403 while sendSessionAffinityHeaders was enabled (Pi sent custom
|
|
6815
|
+
// session-affinity HTTP headers and the proxy/CDN blocked them), offer
|
|
6816
|
+
// to override `sendSessionAffinityHeaders` to false in models.json.
|
|
6817
|
+
if (!suggestion && isSessionAffinity403Applicable(model) && sendSessionAffinityHeaders403Models.has(modelKey(model))) {
|
|
6818
|
+
const key = modelKey(model);
|
|
6819
|
+
const slashIdx = key.indexOf("/");
|
|
6820
|
+
const providerLabel = slashIdx > 0 ? key.slice(0, slashIdx) : key;
|
|
6821
|
+
suggestion = {
|
|
6822
|
+
providerLabel,
|
|
6823
|
+
modelId: model.id,
|
|
6824
|
+
compatKeys: { sendSessionAffinityHeaders: false },
|
|
6825
|
+
};
|
|
6826
|
+
}
|
|
6827
|
+
|
|
6734
6828
|
if (!suggestion) {
|
|
6735
6829
|
const key = modelKey(model);
|
|
6736
6830
|
cmdCtx.ui.notify(`✅ Nothing to fix for "${key}". Compat already configured.`, "info");
|
|
@@ -6755,6 +6849,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
6755
6849
|
"Create or edit the entry below to override supportsLongCacheRetention to false.",
|
|
6756
6850
|
);
|
|
6757
6851
|
}
|
|
6852
|
+
if (sendSessionAffinityHeaders403Models.has(modelKey(model))) {
|
|
6853
|
+
manualLines.push(
|
|
6854
|
+
"",
|
|
6855
|
+
"💡 This model returned HTTP 403 while sendSessionAffinityHeaders was enabled.",
|
|
6856
|
+
"Create or edit the entry below to override sendSessionAffinityHeaders to false.",
|
|
6857
|
+
);
|
|
6858
|
+
}
|
|
6758
6859
|
manualLines.push(
|
|
6759
6860
|
"",
|
|
6760
6861
|
"If the provider/model already exists in models.json, add these compat keys under",
|
|
@@ -7040,7 +7141,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7040
7141
|
if (!model) {
|
|
7041
7142
|
cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
|
|
7042
7143
|
} else {
|
|
7043
|
-
const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)) });
|
|
7144
|
+
const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)) });
|
|
7044
7145
|
const adapter = selectAdapterForModel(model);
|
|
7045
7146
|
const sk = model ? sessionModelKey(model) : undefined;
|
|
7046
7147
|
const statsState = model ? cacheStatsTotalsByModel[modelKey(model)] : undefined;
|
package/package.json
CHANGED