pi-cache-optimizer 2.6.23 → 2.6.24

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 CHANGED
@@ -117,9 +117,9 @@ Notes:
117
117
 
118
118
  Anthropic processes cache breakpoints in `tools → system → messages` order and rejects a `ttl: "1h"` breakpoint that appears after a 5-minute breakpoint. An ephemeral `cache_control` without `ttl` uses the default 5-minute retention.
119
119
 
120
- For `anthropic-messages` channels, the extension checks the final serialized payload before sending. If it detects an invalid short-to-long transition, it conservatively downgrades that request's `1h` breakpoints to the default 5-minute TTL. Legal long-only and `1h → 5m` payloads are left unchanged. This is based on payload structure, not provider names such as `pipi-cc`.
120
+ For every `anthropic-messages` channel, the extension checks the final serialized payload and immediately downgrades a visibly invalid short-to-long transition. Legal long-only and `1h → 5m` payloads remain unchanged, including third-party endpoints that genuinely support 1-hour retention.
121
121
 
122
- If a proxy adds its own conflicting cache controls after Pi's request hook, set `compat.supportsLongCacheRetention: false` for that provider/model so Pi does not emit `ttl: "1h"`.
122
+ Some proxies rewrite or insert hidden 5-minute breakpoints after Pi's request hook. If a provider returns Anthropic's explicit TTL-ordering error, the extension records a process-local provider/model fallback so the next subsequent request uses the default 5-minute TTL. This error is a non-retryable HTTP 400 in Pi 0.82.1, so the extension does not claim that Pi's automatic retry will rerun the failed turn; if another layer retries it, that retry also uses the fallback. `/cache-optimizer doctor` reports the fallback and `/cache-optimizer fix` can persist a model-scoped `supportsLongCacheRetention: false` with the normal confirmation/backup flow. Other 400 errors and prompt-too-long failures do not activate this fallback. The runtime observation lasts until the current process exits and survives extension reloads within that process.
123
123
 
124
124
  ## Adaptive thinking models
125
125
 
package/README.zh-CN.md CHANGED
@@ -117,9 +117,9 @@ Pi 0.81+ 也内置了使用 OpenAI-shaped transport 的 `llama.cpp` provider。P
117
117
 
118
118
  Anthropic 按 `tools → system → messages` 顺序处理 cache breakpoint,并拒绝位于 5 分钟 breakpoint 之后的 `ttl: "1h"` breakpoint。省略 `ttl` 的 ephemeral `cache_control` 使用默认 5 分钟保留时间。
119
119
 
120
- 对于 `anthropic-messages` 渠道,本扩展会在发送前检查最终序列化 payload。如果检测到非法的短 TTL → 长 TTL 顺序,会保守地把该请求中的 `1h` breakpoint 降级为默认 5 分钟 TTL。合法的纯 `1h` 和 `1h → 5m` payload 保持不变。判断依据是 payload 结构,而不是 `pipi-cc` 等 provider 名称。
120
+ 对于所有 `anthropic-messages` 渠道,本扩展都会检查最终序列化 payload,并立即降级可见的非法短 TTL → 长 TTL 顺序。合法的纯 `1h` 和 `1h → 5m` payload 保持不变,包括真正支持 1 小时保留的第三方 endpoint。
121
121
 
122
- 如果代理在 Pi request hook 之后自行添加冲突的 cache control,可为该 provider/model 设置 `compat.supportsLongCacheRetention: false`,使 Pi 不再发送 `ttl: "1h"`。
122
+ 部分代理会在 Pi request hook 之后重写或插入隐藏的 5 分钟 breakpoint。如果 provider 返回 Anthropic 明确的 TTL 顺序错误,本扩展会记录当前进程内的 provider/model fallback,使下一次后续请求使用默认 5 分钟 TTL。Pi 0.82.1 将此错误视为不可自动重试的 HTTP 400,因此扩展不会声称 Pi 会自动重跑失败 turn;如果其它层发起重试,该重试也会使用 fallback。`/cache-optimizer doctor` 会显示该 fallback,`/cache-optimizer fix` 可通过现有确认/备份流程,以 model level 持久写入 `supportsLongCacheRetention: false`。其它 400 和 prompt-too-long 错误不会触发该 fallback。运行时观测会持续到当前进程退出,并会在同一进程内的扩展 reload 后保留。
123
123
 
124
124
  ## Adaptive thinking 模型
125
125
 
package/index.ts CHANGED
@@ -104,6 +104,32 @@ const NO_PROMPT_REWRITE_ENV = "PI_CACHE_OPTIMIZER_NO_PROMPT_REWRITE";
104
104
  const PI_ROUTING_REGISTRY_SYMBOL = Symbol.for("pi.routing.registry.v1");
105
105
  const PI_CACHE_HINTS_SYMBOL = Symbol.for("pi.cache.hints.v1");
106
106
  const PI_CACHE_HINTS_OWNER_SYMBOL = Symbol.for("pi.cache.optimizer.hints-owner.v1");
107
+ const ANTHROPIC_TTL_FALLBACK_SYMBOL = Symbol.for("pi.cache.optimizer.anthropic-ttl-fallback.v1");
108
+
109
+ type AnthropicTtlFallbackStateV1 = {
110
+ version: 1;
111
+ modelKeys: Set<string>;
112
+ warnedModelKeys: Set<string>;
113
+ };
114
+
115
+ function getAnthropicTtlFallbackState(): AnthropicTtlFallbackStateV1 {
116
+ const globals = globalThis as Record<symbol, unknown>;
117
+ const existing = globals[ANTHROPIC_TTL_FALLBACK_SYMBOL] as Partial<AnthropicTtlFallbackStateV1> | undefined;
118
+ if (
119
+ existing?.version === 1 &&
120
+ existing.modelKeys instanceof Set &&
121
+ existing.warnedModelKeys instanceof Set
122
+ ) {
123
+ return existing as AnthropicTtlFallbackStateV1;
124
+ }
125
+ const state: AnthropicTtlFallbackStateV1 = {
126
+ version: 1,
127
+ modelKeys: new Set<string>(),
128
+ warnedModelKeys: new Set<string>(),
129
+ };
130
+ globals[ANTHROPIC_TTL_FALLBACK_SYMBOL] = state;
131
+ return state;
132
+ }
107
133
 
108
134
  let runtimeOptimizerEnabled = true;
109
135
 
@@ -1234,6 +1260,28 @@ function collectAnthropicCacheControlsInWireOrder(payload: unknown): UnknownReco
1234
1260
  * If the final serialized payload contains a short → long transition, downgrade
1235
1261
  * all 1h breakpoints to the default 5m so the request remains valid.
1236
1262
  */
1263
+ function downgradeAnthropicLongCacheControls(payload: unknown): boolean {
1264
+ let changed = false;
1265
+ for (const control of collectAnthropicCacheControlsInWireOrder(payload)) {
1266
+ if (control.ttl === "1h") {
1267
+ delete control.ttl;
1268
+ changed = true;
1269
+ }
1270
+ }
1271
+ return changed;
1272
+ }
1273
+
1274
+ function hasAnthropicCacheTtlOrderError(message: unknown): boolean {
1275
+ const record = getAssistantRecord(message);
1276
+ if (record?.stopReason !== "error" || typeof record.errorMessage !== "string") return false;
1277
+
1278
+ const error = lower(record.errorMessage);
1279
+ return error.includes("cache_control") &&
1280
+ error.includes("ttl='1h'") &&
1281
+ error.includes("ttl='5m'") &&
1282
+ error.includes("must not come after");
1283
+ }
1284
+
1237
1285
  function normalizeAnthropicCacheControlTtlOrder(payload: unknown): boolean {
1238
1286
  const controls = collectAnthropicCacheControlsInWireOrder(payload);
1239
1287
  let seenShort = false;
@@ -1250,10 +1298,7 @@ function normalizeAnthropicCacheControlTtlOrder(payload: unknown): boolean {
1250
1298
  }
1251
1299
  if (!hasInvalidLongAfterShort) return false;
1252
1300
 
1253
- for (const control of controls) {
1254
- if (control.ttl === "1h") delete control.ttl;
1255
- }
1256
- return true;
1301
+ return downgradeAnthropicLongCacheControls(payload);
1257
1302
  }
1258
1303
 
1259
1304
  function isResponsesPromptRewriteBypassApi(api: unknown): boolean {
@@ -4374,7 +4419,7 @@ function getCompatCheckNotApplicableLines(model: PiModel): string[] {
4374
4419
  return ["ℹ️ Compat check not applicable for this model."];
4375
4420
  }
4376
4421
 
4377
- function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400?: boolean; sessionAffinity403?: boolean; openAISdkHeader403?: boolean } = {}): string {
4422
+ function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400?: boolean; anthropicTtlOrderError?: boolean; sessionAffinity403?: boolean; openAISdkHeader403?: boolean } = {}): string {
4378
4423
  const lines: string[] = [];
4379
4424
  lines.push(`Provider: ${model.provider}`);
4380
4425
  lines.push(`Model: ${model.id}`);
@@ -4433,6 +4478,13 @@ function buildDoctorDiagnosis(model: PiModel, options: { promptCacheRetention400
4433
4478
  }
4434
4479
  }
4435
4480
 
4481
+ if (options.anthropicTtlOrderError) {
4482
+ lines.push("");
4483
+ lines.push("⚠️ An Anthropic cache-control TTL ordering error was observed for this model.");
4484
+ lines.push(" Runtime requests now fall back from 1h to the default 5-minute cache TTL.");
4485
+ lines.push(` Run /cache-optimizer fix to set model-level supportsLongCacheRetention: false in ${getModelsJsonDisplayPath()}.`);
4486
+ }
4487
+
4436
4488
  // ── Session affinity 403 diagnostics ──
4437
4489
  // Advises the user when sendSessionAffinityHeaders is enabled, since some
4438
4490
  // third-party proxies / CDNs / WAFs block Pi's custom session-affinity HTTP
@@ -4858,6 +4910,8 @@ interface FixSuggestion {
4858
4910
  providerLabel: string;
4859
4911
  modelId: string;
4860
4912
  compatKeys: Record<string, unknown>;
4913
+ /** Runtime-observed failures must never broaden a model-specific fix. */
4914
+ forceModelLevel?: boolean;
4861
4915
  }
4862
4916
 
4863
4917
  /**
@@ -5519,8 +5573,9 @@ function syntheticModelForId(providerLabel: string, id: string): PiModel {
5519
5573
  * Decide whether the fix should write provider-level or model-level compat.
5520
5574
  *
5521
5575
  * Strategy (auto-detect, prefer provider level when safe):
5522
- * - Channel-capability keys (session affinity / long retention) are always
5523
- * provider-safe.
5576
+ * - Channel-capability keys (session affinity / long retention) are normally
5577
+ * provider-safe; runtime-observed model failures explicitly override this
5578
+ * default through chooseFixPlacement(..., forceModelLevel=true).
5524
5579
  * - Model-behavior keys (forceAdaptiveThinking, allowEmptySignature,
5525
5580
  * thinkingFormat, ...) are provider-safe ONLY when every sibling model in
5526
5581
  * the provider also matches the same detection (all adaptive-generation /
@@ -5599,7 +5654,15 @@ function chooseFixPlacement(
5599
5654
  location: ModelNodeLocation,
5600
5655
  compatKeys: Record<string, unknown>,
5601
5656
  providerLabel: string,
5657
+ forceModelLevel = false,
5602
5658
  ): { placement: "provider" | "model"; reason: string } {
5659
+ if (forceModelLevel) {
5660
+ return {
5661
+ placement: "model",
5662
+ reason: "runtime-observed provider/model failure — scoping the repair to the affected model",
5663
+ };
5664
+ }
5665
+
5603
5666
  const decision = decideFixPlacement(compatKeys, providerLabel, location.allModelIds);
5604
5667
  const existingModelKeys = findExistingCompatKeysInJsonc(
5605
5668
  original,
@@ -5939,6 +6002,8 @@ export const __internals_for_tests = {
5939
6002
  isAnthropicMessagesApi,
5940
6003
  isOpenAICompatibleProxyApi,
5941
6004
  collectAnthropicCacheControlsInWireOrder,
6005
+ downgradeAnthropicLongCacheControls,
6006
+ hasAnthropicCacheTtlOrderError,
5942
6007
  normalizeAnthropicCacheControlTtlOrder,
5943
6008
  isPiBuiltInLlamaCppModel,
5944
6009
  isResponsesPromptRewriteBypassApi,
@@ -6186,6 +6251,9 @@ export default function (pi: ExtensionAPI) {
6186
6251
  const warnedModels = new Set<string>();
6187
6252
  const promptCacheRetention400Models = new Set<string>();
6188
6253
  const warnedPromptCacheRetention400Models = new Set<string>();
6254
+ const anthropicTtlFallbackState = getAnthropicTtlFallbackState();
6255
+ const anthropicTtlOrderErrorModels = anthropicTtlFallbackState.modelKeys;
6256
+ const warnedAnthropicTtlOrderErrorModels = anthropicTtlFallbackState.warnedModelKeys;
6189
6257
  const sendSessionAffinityHeaders403Models = new Set<string>();
6190
6258
  const warnedSendSessionAffinityHeaders403Models = new Set<string>();
6191
6259
  const openAISdkHeader403Models = new Set<string>();
@@ -6207,6 +6275,41 @@ export default function (pi: ExtensionAPI) {
6207
6275
  let lastActualRoutedModel: PersistedRoutedModelRef | undefined;
6208
6276
  let latestCacheHint: PiCacheHintSnapshot | undefined;
6209
6277
  const PERSIST_DEBOUNCE_MS = 2000;
6278
+
6279
+ function buildObservedRuntimeFixSuggestion(model: PiModel): FixSuggestion | undefined {
6280
+ const key = modelKey(model);
6281
+ const slashIdx = key.indexOf("/");
6282
+ const providerLabel = slashIdx > 0 ? key.slice(0, slashIdx) : key;
6283
+
6284
+ if (
6285
+ anthropicTtlOrderErrorModels.has(key) ||
6286
+ (isPromptCacheRetention400Applicable(model) && promptCacheRetention400Models.has(key))
6287
+ ) {
6288
+ return {
6289
+ providerLabel,
6290
+ modelId: model.id,
6291
+ compatKeys: { supportsLongCacheRetention: false },
6292
+ forceModelLevel: true,
6293
+ };
6294
+ }
6295
+ if (isSessionAffinity403Applicable(model) && sendSessionAffinityHeaders403Models.has(key)) {
6296
+ return { providerLabel, modelId: model.id, compatKeys: { sendSessionAffinityHeaders: false } };
6297
+ }
6298
+ return undefined;
6299
+ }
6300
+
6301
+ function buildCommandFixSuggestion(model: PiModel): FixSuggestion | undefined {
6302
+ const regular = buildFixSuggestion(model);
6303
+ const observed = buildObservedRuntimeFixSuggestion(model);
6304
+ if (!regular) return observed;
6305
+ if (!observed) return regular;
6306
+ return {
6307
+ providerLabel: regular.providerLabel,
6308
+ modelId: regular.modelId,
6309
+ compatKeys: { ...regular.compatKeys, ...observed.compatKeys },
6310
+ forceModelLevel: regular.forceModelLevel || observed.forceModelLevel,
6311
+ };
6312
+ }
6210
6313
  /** In-memory recent usage samples per model key (not persisted, cleared on reload). */
6211
6314
  const recentSamplesByModelKey = new Map<string, CacheUsageSample[]>();
6212
6315
 
@@ -6849,11 +6952,15 @@ export default function (pi: ExtensionAPI) {
6849
6952
  const requestModel = resolveRouteModel(ctx.model, ctx) ?? ctx.model;
6850
6953
 
6851
6954
  // Anthropic rejects mixed cache breakpoints when a 1h block appears after
6852
- // a 5m/default block in wire order (tools → system → messages). Normalize
6853
- // only provably-invalid final payloads; legal long-only and long→short
6854
- // payloads keep their original retention.
6855
- if (isAnthropicMessagesApi(requestModel?.api)) {
6856
- normalizeAnthropicCacheControlTtlOrder(event.payload);
6955
+ // a 5m/default block in wire order (tools → system → messages). Repair any
6956
+ // conflict visible in Pi's final payload immediately. Some proxies inject
6957
+ // hidden short breakpoints after this hook; only models that have actually
6958
+ // returned the explicit TTL-order error receive the process-local 5m fallback.
6959
+ if (requestModel && isAnthropicMessagesApi(requestModel.api)) {
6960
+ const visibleConflictFixed = normalizeAnthropicCacheControlTtlOrder(event.payload);
6961
+ if (!visibleConflictFixed && anthropicTtlOrderErrorModels.has(modelKey(requestModel))) {
6962
+ downgradeAnthropicLongCacheControls(event.payload);
6963
+ }
6857
6964
  }
6858
6965
 
6859
6966
  // ── Safety: strip prompt_cache_retention from payload for models that
@@ -6957,6 +7064,29 @@ export default function (pi: ExtensionAPI) {
6957
7064
 
6958
7065
  pi.on("message_end", async (event, ctx) => {
6959
7066
  syncSessionHash(ctx);
7067
+ const msgRecord = asRecord(event.message);
7068
+
7069
+ // Record only Anthropic's explicit mixed-TTL ordering error. This is a
7070
+ // non-retryable 400 in Pi 0.82.1, so the fallback applies to the next
7071
+ // subsequent request (and to a retry only if another layer initiates one).
7072
+ if (hasAnthropicCacheTtlOrderError(event.message)) {
7073
+ const fallbackModel = resolveRouteModel(ctx.model, ctx) ?? ctx.model;
7074
+ const errorModel = modelFromAssistantMessage(event.message, fallbackModel) ?? fallbackModel;
7075
+ if (errorModel && isAnthropicMessagesApi(errorModel.api)) {
7076
+ const key = modelKey(errorModel);
7077
+ anthropicTtlOrderErrorModels.add(key);
7078
+ if (!warnedAnthropicTtlOrderErrorModels.has(key)) {
7079
+ warnedAnthropicTtlOrderErrorModels.add(key);
7080
+ ctx.ui.notify(
7081
+ `⚠️ ${LOG_PREFIX}: ${key} returned an Anthropic cache-control TTL ordering error. ` +
7082
+ `The next request will fall back to the default 5-minute cache TTL. ` +
7083
+ `Run /cache-optimizer fix to set model-level supportsLongCacheRetention: false persistently.`,
7084
+ "warning",
7085
+ );
7086
+ }
7087
+ }
7088
+ }
7089
+
6960
7090
  const adapter = selectAdapterForAssistantMessage(event.message, ctx.model);
6961
7091
  if (!adapter) return;
6962
7092
 
@@ -6967,7 +7097,6 @@ export default function (pi: ExtensionAPI) {
6967
7097
  // returns as a valid (non-undefined) snapshot, which would inflate
6968
7098
  // totalRequests and skew cache hit-rate accuracy. The final successful
6969
7099
  // response is emitted as a separate message_end with real usage data.
6970
- const msgRecord = asRecord(event.message);
6971
7100
  if (msgRecord?.stopReason === "error" || msgRecord?.stopReason === "aborted") {
6972
7101
  return;
6973
7102
  }
@@ -7074,7 +7203,7 @@ export default function (pi: ExtensionAPI) {
7074
7203
  cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
7075
7204
  return;
7076
7205
  }
7077
- const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)), openAISdkHeader403: openAISdkHeader403Models.has(modelKey(model)) });
7206
+ const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), anthropicTtlOrderError: anthropicTtlOrderErrorModels.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)), openAISdkHeader403: openAISdkHeader403Models.has(modelKey(model)) });
7078
7207
  const adapter = selectAdapterForModel(model);
7079
7208
  const sk = model ? sessionModelKey(model) : undefined;
7080
7209
  const statsState = model ? cacheStatsTotalsByModel[modelKey(model)] : undefined;
@@ -7147,37 +7276,7 @@ export default function (pi: ExtensionAPI) {
7147
7276
  return;
7148
7277
  }
7149
7278
 
7150
- let suggestion = buildFixSuggestion(model);
7151
-
7152
- // If no regular missing compat flags but the model has a recorded
7153
- // prompt_cache_retention 400 (Pi sent `prompt_cache_retention` and
7154
- // the provider rejected it), offer to override
7155
- // `supportsLongCacheRetention` to false in models.json.
7156
- if (!suggestion && isPromptCacheRetention400Applicable(model) && promptCacheRetention400Models.has(modelKey(model))) {
7157
- const key = modelKey(model);
7158
- const slashIdx = key.indexOf("/");
7159
- const providerLabel = slashIdx > 0 ? key.slice(0, slashIdx) : key;
7160
- suggestion = {
7161
- providerLabel,
7162
- modelId: model.id,
7163
- compatKeys: { supportsLongCacheRetention: false },
7164
- };
7165
- }
7166
-
7167
- // If no regular missing compat flags but the model has a recorded
7168
- // 403 while sendSessionAffinityHeaders was enabled (Pi sent custom
7169
- // session-affinity HTTP headers and the proxy/CDN blocked them), offer
7170
- // to override `sendSessionAffinityHeaders` to false in models.json.
7171
- if (!suggestion && isSessionAffinity403Applicable(model) && sendSessionAffinityHeaders403Models.has(modelKey(model))) {
7172
- const key = modelKey(model);
7173
- const slashIdx = key.indexOf("/");
7174
- const providerLabel = slashIdx > 0 ? key.slice(0, slashIdx) : key;
7175
- suggestion = {
7176
- providerLabel,
7177
- modelId: model.id,
7178
- compatKeys: { sendSessionAffinityHeaders: false },
7179
- };
7180
- }
7279
+ const suggestion = buildCommandFixSuggestion(model);
7181
7280
 
7182
7281
  if (!suggestion) {
7183
7282
  const key = modelKey(model);
@@ -7203,6 +7302,13 @@ export default function (pi: ExtensionAPI) {
7203
7302
  "Create or edit the entry below to override supportsLongCacheRetention to false.",
7204
7303
  );
7205
7304
  }
7305
+ if (anthropicTtlOrderErrorModels.has(modelKey(model))) {
7306
+ manualLines.push(
7307
+ "",
7308
+ "💡 This model returned an Anthropic cache-control TTL ordering error.",
7309
+ "Create or edit the entry below to override supportsLongCacheRetention to false.",
7310
+ );
7311
+ }
7206
7312
  if (sendSessionAffinityHeaders403Models.has(modelKey(model))) {
7207
7313
  manualLines.push(
7208
7314
  "",
@@ -7365,9 +7471,15 @@ export default function (pi: ExtensionAPI) {
7365
7471
  return;
7366
7472
  }
7367
7473
 
7368
- // Compose the modified text — auto-detect the best placement level:
7369
- // provider level (channel-wide) when safe for all sibling models, else model level.
7370
- const decision = chooseFixPlacement(originalText, location, suggestion.compatKeys, suggestion.providerLabel);
7474
+ // Compose the modified text — observed runtime failures are always
7475
+ // model-scoped; ordinary compat fixes use the safety-based placement.
7476
+ const decision = chooseFixPlacement(
7477
+ originalText,
7478
+ location,
7479
+ suggestion.compatKeys,
7480
+ suggestion.providerLabel,
7481
+ suggestion.forceModelLevel,
7482
+ );
7371
7483
  const modifiedText = composeFixInsertion(originalText, location, suggestion.compatKeys, decision.placement);
7372
7484
 
7373
7485
  // Self-check
@@ -7495,7 +7607,7 @@ export default function (pi: ExtensionAPI) {
7495
7607
  if (!model) {
7496
7608
  cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
7497
7609
  } else {
7498
- const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)), openAISdkHeader403: openAISdkHeader403Models.has(modelKey(model)) });
7610
+ const diagnosis = buildDoctorDiagnosis(model, { promptCacheRetention400: promptCacheRetention400Models.has(modelKey(model)), anthropicTtlOrderError: anthropicTtlOrderErrorModels.has(modelKey(model)), sessionAffinity403: sendSessionAffinityHeaders403Models.has(modelKey(model)), openAISdkHeader403: openAISdkHeader403Models.has(modelKey(model)) });
7499
7611
  const adapter = selectAdapterForModel(model);
7500
7612
  const sk = model ? sessionModelKey(model) : undefined;
7501
7613
  const statsState = model ? cacheStatsTotalsByModel[modelKey(model)] : undefined;
@@ -7539,7 +7651,7 @@ export default function (pi: ExtensionAPI) {
7539
7651
  cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
7540
7652
  return;
7541
7653
  }
7542
- const suggestion = buildFixSuggestion(model);
7654
+ const suggestion = buildCommandFixSuggestion(model);
7543
7655
  if (!suggestion) {
7544
7656
  const key = modelKey(model);
7545
7657
  cmdCtx.ui.notify(`✅ Nothing to fix for "${key}". Compat already configured.`, "info");
@@ -7567,7 +7679,13 @@ export default function (pi: ExtensionAPI) {
7567
7679
  return;
7568
7680
  }
7569
7681
 
7570
- const menuDecision = chooseFixPlacement(originalText, location, suggestion.compatKeys, suggestion.providerLabel);
7682
+ const menuDecision = chooseFixPlacement(
7683
+ originalText,
7684
+ location,
7685
+ suggestion.compatKeys,
7686
+ suggestion.providerLabel,
7687
+ suggestion.forceModelLevel,
7688
+ );
7571
7689
  const modifiedText = composeFixInsertion(originalText, location, suggestion.compatKeys, menuDecision.placement);
7572
7690
  const checkError = selfCheckFix(originalText, modifiedText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, menuDecision.placement);
7573
7691
  if (checkError !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cache-optimizer",
3
- "version": "2.6.23",
3
+ "version": "2.6.24",
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",