pi-cache-optimizer 2.6.13 → 2.6.14

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.
Files changed (2) hide show
  1. package/index.ts +111 -16
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -3921,6 +3921,22 @@ function isPromptCacheRetention400Applicable(model: PiModel): boolean {
3921
3921
  getCompat(model).supportsLongCacheRetention === true;
3922
3922
  }
3923
3923
 
3924
+ /**
3925
+ * Whether the 403 sendSessionAffinityHeaders diagnostic applies to a model.
3926
+ *
3927
+ * Pi's openai-completions / openai-responses adapters send three custom HTTP
3928
+ * headers (session_id, x-client-request-id, x-session-affinity) when
3929
+ * `sendSessionAffinityHeaders` is enabled. Some third-party proxies / CDNs /
3930
+ * WAFs block these custom headers and return HTTP 403 "Your request was
3931
+ * blocked". This guard returns true only for OpenAI-compatible APIs whose
3932
+ * merged compat currently enables session-affinity headers, so the 403
3933
+ * monitoring and doctor/fix paths can advise disabling the flag.
3934
+ */
3935
+ function isSessionAffinity403Applicable(model: PiModel): boolean {
3936
+ if (!isOpenAICompatibleApi(model.api)) return false;
3937
+ return getCompat(model).sendSessionAffinityHeaders === true;
3938
+ }
3939
+
3924
3940
  /**
3925
3941
  * Detect router / channel profiles from a PiModel and return diagnostic notes.
3926
3942
  *
@@ -4118,7 +4134,7 @@ function getCompatCheckNotApplicableLines(model: PiModel): string[] {
4118
4134
  return ["ℹ️ Compat check not applicable for this model."];
4119
4135
  }
4120
4136
 
4121
- function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400?: boolean } = {}): string {
4137
+ function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400?: boolean; sessionAffinity403?: boolean } = {}): string {
4122
4138
  const lines: string[] = [];
4123
4139
  lines.push(`Provider: ${model.provider}`);
4124
4140
  lines.push(`Model: ${model.id}`);
@@ -4177,6 +4193,25 @@ function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400
4177
4193
  }
4178
4194
  }
4179
4195
 
4196
+ // ── Session affinity 403 diagnostics ──
4197
+ // Advises the user when sendSessionAffinityHeaders is enabled, since some
4198
+ // third-party proxies / CDNs / WAFs block Pi's custom session-affinity HTTP
4199
+ // headers (session_id, x-client-request-id, x-session-affinity) and return
4200
+ // 403. If a 403 was already observed for this model, show a stronger hint.
4201
+ if (isSessionAffinity403Applicable(model)) {
4202
+ lines.push("");
4203
+ if (options.sessionAffinity403) {
4204
+ lines.push("⚠️ A 403 response was observed while sendSessionAffinityHeaders is enabled.");
4205
+ lines.push(" The proxy/CDN likely blocks Pi's custom session-affinity headers (session_id,");
4206
+ lines.push(" x-client-request-id, x-session-affinity). Run /cache-optimizer fix to");
4207
+ lines.push(` set sendSessionAffinityHeaders: false in ${getModelsJsonDisplayPath()}.`);
4208
+ } else {
4209
+ lines.push("ℹ️ Session affinity headers are enabled. Some CDNs/WAFs block custom headers");
4210
+ lines.push(" (session_id, x-client-request-id, x-session-affinity) and return 403. If you");
4211
+ lines.push(" see 403 errors, run /cache-optimizer fix to set sendSessionAffinityHeaders: false.");
4212
+ }
4213
+ }
4214
+
4180
4215
  // ── Router/channel diagnostics ──
4181
4216
  const routerNotes = describeRouterChannelDiagnostics(model);
4182
4217
  if (routerNotes.length > 0) {
@@ -4367,6 +4402,13 @@ function buildCompatDiagnosis(model: PiModel): string | undefined {
4367
4402
  if (isPromptCacheRetention400Applicable(model)) {
4368
4403
  lines.push(getPromptCacheRetentionUnsupportedHint());
4369
4404
  }
4405
+ // Advisory for 403 session-affinity header blocking (only when enabled).
4406
+ if (isSessionAffinity403Applicable(model)) {
4407
+ lines.push(
4408
+ "ℹ️ Session affinity headers are enabled. If you see 403 \"blocked\" errors,",
4409
+ " the proxy/CDN may be blocking Pi's custom headers. Set sendSessionAffinityHeaders: false.",
4410
+ );
4411
+ }
4370
4412
  appendOptionalOpenAIProxyCompatAdviceLines(lines, optionalOpenAIProxyCompat);
4371
4413
  } else {
4372
4414
  lines.push(...getCompatCheckNotApplicableLines(model));
@@ -5643,6 +5685,7 @@ export const __internals_for_tests = {
5643
5685
  isOfficialOpenAIBaseUrl,
5644
5686
  isCompatCheckApplicable,
5645
5687
  isPromptCacheRetention400Applicable,
5688
+ isSessionAffinity403Applicable,
5646
5689
  hasPromptCacheRetentionUnsupportedSignal,
5647
5690
  // Non-GPT OpenAI-compatible model detection
5648
5691
  isKimiLikeModel,
@@ -5859,6 +5902,8 @@ export default function (pi: ExtensionAPI) {
5859
5902
  const warnedModels = new Set<string>();
5860
5903
  const promptCacheRetention400Models = new Set<string>();
5861
5904
  const warnedPromptCacheRetention400Models = new Set<string>();
5905
+ const sendSessionAffinityHeaders403Models = new Set<string>();
5906
+ const warnedSendSessionAffinityHeaders403Models = new Set<string>();
5862
5907
  let cacheStatsByModel: Record<string, CacheStats> = {};
5863
5908
  let cacheStatsTotalsByModel: Record<string, CacheStats> = {};
5864
5909
  let cacheStatsLegacyFamily: Partial<Record<CacheProviderId, CacheStats>> = emptyAllCacheStats();
@@ -6506,20 +6551,48 @@ export default function (pi: ExtensionAPI) {
6506
6551
  pi.on("after_provider_response", (event, ctx) => {
6507
6552
  const model = resolveRouteModel(ctx.model, ctx) ?? ctx.model;
6508
6553
  if (!runtimeOptimizerEnabled || !model) return;
6509
- if (event.status !== 400) return;
6510
- if (!isPromptCacheRetention400Applicable(model)) return;
6511
- if (!hasPromptCacheRetentionUnsupportedSignal(event.headers)) return;
6512
6554
 
6513
- const key = modelKey(model);
6514
- promptCacheRetention400Models.add(key);
6515
- if (warnedPromptCacheRetention400Models.has(key)) return;
6516
- warnedPromptCacheRetention400Models.add(key);
6517
- ctx.ui.notify(
6518
- `⚠️ ${LOG_PREFIX}: ${key} returned HTTP 400 while supportsLongCacheRetention is enabled. ` +
6519
- getPromptCacheRetentionUnsupportedHint() +
6520
- ` Run /cache-optimizer doctor for the exact edit location.`,
6521
- "warning",
6522
- );
6555
+ // ── 400: prompt_cache_retention unsupported ──
6556
+ if (event.status === 400) {
6557
+ if (!isPromptCacheRetention400Applicable(model)) return;
6558
+ if (!hasPromptCacheRetentionUnsupportedSignal(event.headers)) return;
6559
+
6560
+ const key = modelKey(model);
6561
+ promptCacheRetention400Models.add(key);
6562
+ if (warnedPromptCacheRetention400Models.has(key)) return;
6563
+ warnedPromptCacheRetention400Models.add(key);
6564
+ ctx.ui.notify(
6565
+ `⚠️ ${LOG_PREFIX}: ${key} returned HTTP 400 while supportsLongCacheRetention is enabled. ` +
6566
+ getPromptCacheRetentionUnsupportedHint() +
6567
+ ` Run /cache-optimizer doctor for the exact edit location.`,
6568
+ "warning",
6569
+ );
6570
+ return;
6571
+ }
6572
+
6573
+ // ── 403: sendSessionAffinityHeaders blocked by CDN/WAF ──
6574
+ //
6575
+ // Pi's openai-completions/responses adapters send three custom HTTP
6576
+ // headers (session_id, x-client-request-id, x-session-affinity) when
6577
+ // sendSessionAffinityHeaders is enabled. Some third-party proxies /
6578
+ // CDNs / WAFs block these custom headers and return 403. We record a
6579
+ // one-time per-session notification and surface it in doctor/fix so the
6580
+ // user can disable the flag for the blocked model.
6581
+ if (event.status === 403) {
6582
+ if (!isSessionAffinity403Applicable(model)) return;
6583
+
6584
+ const key403 = modelKey(model);
6585
+ sendSessionAffinityHeaders403Models.add(key403);
6586
+ if (warnedSendSessionAffinityHeaders403Models.has(key403)) return;
6587
+ warnedSendSessionAffinityHeaders403Models.add(key403);
6588
+ ctx.ui.notify(
6589
+ `⚠️ ${LOG_PREFIX}: ${key403} returned HTTP 403 while sendSessionAffinityHeaders is enabled. ` +
6590
+ `The proxy/CDN may be blocking Pi's custom session-affinity headers (session_id, x-client-request-id, x-session-affinity). ` +
6591
+ `Run /cache-optimizer doctor for details and /cache-optimizer fix to set sendSessionAffinityHeaders: false.`,
6592
+ "warning",
6593
+ );
6594
+ return;
6595
+ }
6523
6596
  });
6524
6597
 
6525
6598
  pi.on("message_end", async (event, ctx) => {
@@ -6641,7 +6714,7 @@ export default function (pi: ExtensionAPI) {
6641
6714
  cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
6642
6715
  return;
6643
6716
  }
6644
- const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)) });
6717
+ const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)) });
6645
6718
  const adapter = selectAdapterForModel(model);
6646
6719
  const sk = model ? sessionModelKey(model) : undefined;
6647
6720
  const statsState = model ? cacheStatsTotalsByModel[modelKey(model)] : undefined;
@@ -6731,6 +6804,21 @@ export default function (pi: ExtensionAPI) {
6731
6804
  };
6732
6805
  }
6733
6806
 
6807
+ // If no regular missing compat flags but the model has a recorded
6808
+ // 403 while sendSessionAffinityHeaders was enabled (Pi sent custom
6809
+ // session-affinity HTTP headers and the proxy/CDN blocked them), offer
6810
+ // to override `sendSessionAffinityHeaders` to false in models.json.
6811
+ if (!suggestion && isSessionAffinity403Applicable(model) && sendSessionAffinityHeaders403Models.has(modelKey(model))) {
6812
+ const key = modelKey(model);
6813
+ const slashIdx = key.indexOf("/");
6814
+ const providerLabel = slashIdx > 0 ? key.slice(0, slashIdx) : key;
6815
+ suggestion = {
6816
+ providerLabel,
6817
+ modelId: model.id,
6818
+ compatKeys: { sendSessionAffinityHeaders: false },
6819
+ };
6820
+ }
6821
+
6734
6822
  if (!suggestion) {
6735
6823
  const key = modelKey(model);
6736
6824
  cmdCtx.ui.notify(`✅ Nothing to fix for "${key}". Compat already configured.`, "info");
@@ -6755,6 +6843,13 @@ export default function (pi: ExtensionAPI) {
6755
6843
  "Create or edit the entry below to override supportsLongCacheRetention to false.",
6756
6844
  );
6757
6845
  }
6846
+ if (sendSessionAffinityHeaders403Models.has(modelKey(model))) {
6847
+ manualLines.push(
6848
+ "",
6849
+ "💡 This model returned HTTP 403 while sendSessionAffinityHeaders was enabled.",
6850
+ "Create or edit the entry below to override sendSessionAffinityHeaders to false.",
6851
+ );
6852
+ }
6758
6853
  manualLines.push(
6759
6854
  "",
6760
6855
  "If the provider/model already exists in models.json, add these compat keys under",
@@ -7040,7 +7135,7 @@ export default function (pi: ExtensionAPI) {
7040
7135
  if (!model) {
7041
7136
  cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
7042
7137
  } else {
7043
- const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)) });
7138
+ const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)) });
7044
7139
  const adapter = selectAdapterForModel(model);
7045
7140
  const sk = model ? sessionModelKey(model) : undefined;
7046
7141
  const statsState = model ? cacheStatsTotalsByModel[modelKey(model)] : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cache-optimizer",
3
- "version": "2.6.13",
3
+ "version": "2.6.14",
4
4
  "description": "Improve Pi prompt/KV cache hit rates with stable prompts, OpenAI-compatible cache keys, proxy compat warnings, and footer cache stats.",
5
5
  "keywords": [
6
6
  "pi-package",