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 +2 -2
- package/README.zh-CN.md +2 -2
- package/index.ts +169 -51
- package/package.json +1 -1
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`
|
|
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
|
-
|
|
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
|
-
|
|
120
|
+
对于所有 `anthropic-messages` 渠道,本扩展都会检查最终序列化 payload,并立即降级可见的非法短 TTL → 长 TTL 顺序。合法的纯 `1h` 和 `1h → 5m` payload 保持不变,包括真正支持 1 小时保留的第三方 endpoint。
|
|
121
121
|
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
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).
|
|
6853
|
-
//
|
|
6854
|
-
//
|
|
6855
|
-
|
|
6856
|
-
|
|
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
|
-
|
|
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 —
|
|
7369
|
-
//
|
|
7370
|
-
const decision = chooseFixPlacement(
|
|
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 =
|
|
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(
|
|
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