pi-cache-optimizer 2.6.24 → 2.7.0

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 (4) hide show
  1. package/README.md +36 -12
  2. package/README.zh-CN.md +36 -12
  3. package/index.ts +676 -170
  4. package/package.json +16 -1
package/index.ts CHANGED
@@ -95,12 +95,20 @@ const STATUS_KEY = "pi-cache-stats";
95
95
  const STATE_DIR = getAgentDir();
96
96
  const STATE_FILE_PATH = join(STATE_DIR, "pi-cache-optimizer-stats.json");
97
97
  const LEGACY_STATE_FILE_PATH = join(STATE_DIR, "deepseek-cache-optimizer-stats.json");
98
+ const CONFIG_FILE_PATH = join(STATE_DIR, "pi-cache-optimizer-config.json");
98
99
  const CACHE_PROVIDER_IDS: CacheProviderId[] = ["deepseek", "openai", "claude", "gemini"];
99
100
  const OPENAI_CACHE_KEY_ENV = "PI_CACHE_OPTIMIZER_OPENAI_CACHE_KEY";
100
101
  const NO_OPENAI_CACHE_KEY_ENV = "PI_CACHE_OPTIMIZER_NO_OPENAI_CACHE_KEY";
101
102
  const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
102
103
  const NO_SKILL_COMPRESSION_ENV = "PI_CACHE_OPTIMIZER_NO_SKILL_COMPRESSION";
103
104
  const NO_PROMPT_REWRITE_ENV = "PI_CACHE_OPTIMIZER_NO_PROMPT_REWRITE";
105
+ const FOOTER_MODE_ENV = "PI_CACHE_OPTIMIZER_FOOTER_MODE";
106
+ type FooterStatsMode = "session" | "total";
107
+ type FooterStatsModeSource = "config" | "env" | "default";
108
+ type PersistedCacheOptimizerConfigV1 = {
109
+ version: 1;
110
+ footerMode?: FooterStatsMode;
111
+ };
104
112
  const PI_ROUTING_REGISTRY_SYMBOL = Symbol.for("pi.routing.registry.v1");
105
113
  const PI_CACHE_HINTS_SYMBOL = Symbol.for("pi.cache.hints.v1");
106
114
  const PI_CACHE_HINTS_OWNER_SYMBOL = Symbol.for("pi.cache.optimizer.hints-owner.v1");
@@ -133,8 +141,8 @@ function getAnthropicTtlFallbackState(): AnthropicTtlFallbackStateV1 {
133
141
 
134
142
  let runtimeOptimizerEnabled = true;
135
143
 
136
- // WORM-flag: if optimizeSystemPrompt ever detects that its blind-replace
137
- // logic has accidentally truncated a structural marker (any XML tag or
144
+ // WORM-flag: if optimizeSystemPrompt ever detects that candidate extraction
145
+ // has accidentally truncated a structural marker (any XML tag or
138
146
  // HTML comment boundary marker present in the original prompt), we flip
139
147
  // this. publishStatus reads it once, appends a footer warning, then
140
148
  // resets it. The flag surface is kept separate from the regular
@@ -161,12 +169,10 @@ const SKILL_COMPRESSION_MIN_COUNT = 4;
161
169
 
162
170
  // Minimum trimmed length for a candidate to qualify as a stable-prefix "part".
163
171
  //
164
- // `optimizeSystemPrompt` removes each accepted candidate from the dynamic
165
- // remainder via `rest.replace(part, "")`. Short or character-class candidates
166
- // (think: `S`, `- u`, `- (`, `- }`) match the FIRST occurrence of those bytes
167
- // anywhere in `rest`, ripping unrelated text out of the prompt and yielding a
168
- // non-deterministic dynamic remainder per request. Both behaviors poison the
169
- // provider's prompt-prefix cache.
172
+ // `optimizeSystemPrompt` removes each uniquely occurring accepted candidate
173
+ // from the dynamic remainder. Short or character-class candidates (think: `S`,
174
+ // `- u`, `- (`, `- }`) are too likely to match unrelated prompt text, so they
175
+ // are never eligible for lifting.
170
176
  //
171
177
  // The threshold also caps the upstream string-vs-array regression we saw with
172
178
  // trellis 0.5.16 / 0.6.0-beta.17 (subagent tool registration passing
@@ -644,7 +650,7 @@ function stripSessionOverviewChurn(prompt: string): string {
644
650
  * Extract structural markers from a prompt for the integrity guard.
645
651
  *
646
652
  * The guard runs in `optimizeSystemPrompt` to catch cases where the
647
- * blind `rest.replace(part, "")` reorder accidentally eats text inside
653
+ * candidate extraction accidentally eats text inside
648
654
  * an extension-injected structural block (e.g., trellis
649
655
  * `<workflow-state>`, a hypothetical `<task-tracker>`, or AGENTS.md
650
656
  * `<!-- TRELLIS:START -->` markers). When the original prompt contains
@@ -704,16 +710,43 @@ function optimizeSystemPrompt(
704
710
  const seen = new Set<string>();
705
711
  let rest = original;
706
712
 
707
- // Stable layer: content likely to be identical across sessions/turns.
708
- // Short / single-char candidates are dropped: see MIN_STABLE_CANDIDATE_LENGTH.
713
+ // Classify candidate ambiguity against one immutable snapshot. Candidates
714
+ // can be nested (a full context-file block also contains its bare content),
715
+ // so recomputing occurrence counts after each removal is unsafe: deleting
716
+ // the full block can make the dynamic copy of its bare content appear unique.
717
+ const candidates: string[] = [];
709
718
  for (const candidate of buildStableCandidates(opts)) {
710
719
  const part = candidate.trim();
711
- if (!part || part.length < MIN_STABLE_CANDIDATE_LENGTH) continue;
712
- if (seen.has(part) || !rest.includes(part)) continue;
720
+ if (!part || part.length < MIN_STABLE_CANDIDATE_LENGTH || seen.has(part)) continue;
721
+ seen.add(part);
722
+ candidates.push(part);
723
+ }
724
+
725
+ const initialRemainder = rest;
726
+ const occurrenceCount = new Map<string, number>();
727
+ for (const part of candidates) {
728
+ let count = 0;
729
+ let searchFrom = 0;
730
+ while (searchFrom < initialRemainder.length) {
731
+ const occurrence = initialRemainder.indexOf(part, searchFrom);
732
+ if (occurrence < 0) break;
733
+ count++;
734
+ if (count > 1) break;
735
+ searchFrom = occurrence + 1;
736
+ }
737
+ occurrenceCount.set(part, count);
738
+ }
739
+
740
+ // Stable layer: content likely to be identical across sessions/turns.
741
+ // Short / single-char candidates are dropped: see MIN_STABLE_CANDIDATE_LENGTH.
742
+ for (const part of candidates) {
743
+ if (occurrenceCount.get(part) !== 1) continue;
744
+
745
+ const firstOccurrence = rest.indexOf(part);
746
+ if (firstOccurrence < 0) continue;
713
747
 
714
748
  stableParts.push(part);
715
- seen.add(part);
716
- rest = rest.replace(part, "");
749
+ rest = rest.slice(0, firstOccurrence) + rest.slice(firstOccurrence + part.length);
717
750
  }
718
751
 
719
752
  const stablePrefix = stableParts.join("\n\n");
@@ -731,8 +764,8 @@ function optimizeSystemPrompt(
731
764
 
732
765
  // Sanity check: scan ALL structural markers (XML tags + HTML comment
733
766
  // boundary markers) in the original and verify each one survives the
734
- // reorder. If any marker drops, the blind `rest.replace(part, "")`
735
- // logic ate something it shouldn't have — fall back to the original
767
+ // reorder. If any marker drops, candidate extraction ate something it
768
+ // shouldn't have — fall back to the original
736
769
  // prompt and flag the footer warning. This is provider-agnostic and
737
770
  // extension-agnostic: trellis `<workflow-state>`, a hypothetical
738
771
  // `<task-tracker>`, AGENTS.md `<!-- TRELLIS:START -->`, etc., are all
@@ -1107,6 +1140,62 @@ function isEnabledEnv(value: string | undefined): boolean {
1107
1140
  return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
1108
1141
  }
1109
1142
 
1143
+ function parseFooterStatsMode(value: unknown): FooterStatsMode | undefined {
1144
+ if (typeof value !== "string") return undefined;
1145
+ const normalized = value.trim().toLowerCase();
1146
+ return normalized === "session" || normalized === "total" ? normalized : undefined;
1147
+ }
1148
+
1149
+ function parsePersistedCacheOptimizerConfig(value: unknown): PersistedCacheOptimizerConfigV1 | undefined {
1150
+ const record = asRecord(value);
1151
+ if (!record || record.version !== 1) return undefined;
1152
+ const footerMode = parseFooterStatsMode(record.footerMode);
1153
+ if (record.footerMode !== undefined && !footerMode) return undefined;
1154
+ return { version: 1, ...(footerMode ? { footerMode } : {}) };
1155
+ }
1156
+
1157
+ function readPersistedFooterMode(configPath: string = CONFIG_FILE_PATH): FooterStatsMode | undefined {
1158
+ try {
1159
+ const parsed = parsePersistedCacheOptimizerConfig(JSON.parse(readFileSync(configPath, "utf8")));
1160
+ if (!parsed) throw new Error("invalid footer config schema");
1161
+ return parsed.footerMode;
1162
+ } catch (error) {
1163
+ if (getErrorCode(error) !== "ENOENT") {
1164
+ console.warn(`${LOG_PREFIX}: failed to read footer config; using environment/default mode`, error);
1165
+ }
1166
+ return undefined;
1167
+ }
1168
+ }
1169
+
1170
+ async function writePersistedFooterMode(
1171
+ mode: FooterStatsMode,
1172
+ configPath: string = CONFIG_FILE_PATH,
1173
+ ): Promise<void> {
1174
+ await mkdir(dirname(configPath), { recursive: true });
1175
+ const payload: PersistedCacheOptimizerConfigV1 = { version: 1, footerMode: mode };
1176
+ const tempPath = `${configPath}.${process.pid}.${Date.now()}.tmp`;
1177
+ await writeFile(tempPath, JSON.stringify(payload, null, 2) + "\n", "utf8");
1178
+ await rename(tempPath, configPath);
1179
+ }
1180
+
1181
+ function resolveFooterStatsMode(
1182
+ configuredMode: FooterStatsMode | undefined,
1183
+ env: MutableEnv = process.env,
1184
+ ): { mode: FooterStatsMode; source: FooterStatsModeSource } {
1185
+ if (configuredMode) return { mode: configuredMode, source: "config" };
1186
+ const envMode = parseFooterStatsMode(env[FOOTER_MODE_ENV]);
1187
+ return envMode ? { mode: envMode, source: "env" } : { mode: "total", source: "default" };
1188
+ }
1189
+
1190
+ function footerStatsMode(
1191
+ env: MutableEnv = process.env,
1192
+ configuredMode: FooterStatsMode | undefined = persistedFooterStatsMode,
1193
+ ): FooterStatsMode {
1194
+ return resolveFooterStatsMode(configuredMode, env).mode;
1195
+ }
1196
+
1197
+ let persistedFooterStatsMode = readPersistedFooterMode();
1198
+
1110
1199
  function isDisabledEnv(value: string | undefined): boolean {
1111
1200
  if (!value) return false;
1112
1201
  const normalized = value.trim().toLowerCase();
@@ -3802,11 +3891,24 @@ function routedModelRefToPiModel(ref: PersistedRoutedModelRef): PiModel {
3802
3891
  } as PiModel;
3803
3892
  }
3804
3893
 
3894
+ function selectFooterStatsForModel(
3895
+ mode: FooterStatsMode,
3896
+ sessionHash: string | undefined,
3897
+ statsByModel: Record<string, CacheStats>,
3898
+ totalsByModel: Record<string, CacheStats>,
3899
+ model: { provider: string; id: string },
3900
+ ): CacheStats | undefined {
3901
+ if (mode === "total") return totalsByModel[modelKey(model)];
3902
+ if (!sessionHash) return undefined;
3903
+ return statsByModel[makeSessionModelKey(sessionHash, model.provider, model.id)];
3904
+ }
3905
+
3805
3906
  function buildExactRouterStatusEntry(
3806
3907
  sessionHash: string | undefined,
3807
3908
  statsByModel: Record<string, CacheStats>,
3808
3909
  lastRoutedModel: PersistedRoutedModelRef | undefined,
3809
3910
  totalsByModel: Record<string, CacheStats> = {},
3911
+ mode: FooterStatsMode = "total",
3810
3912
  ): { model: PiModel; adapter: CacheProviderAdapter; stats: CacheStats } | undefined {
3811
3913
  if (!sessionHash || !lastRoutedModel) return undefined;
3812
3914
 
@@ -3814,8 +3916,43 @@ function buildExactRouterStatusEntry(
3814
3916
  const adapter = selectAdapterForModel(model);
3815
3917
  if (!adapter) return undefined;
3816
3918
 
3817
- const key = makeSessionModelKey(sessionHash, lastRoutedModel.provider, lastRoutedModel.id);
3818
- return { model, adapter, stats: totalsByModel[modelKey(model)] ?? statsByModel[key] ?? emptyCacheStats() };
3919
+ return {
3920
+ model,
3921
+ adapter,
3922
+ stats: selectFooterStatsForModel(mode, sessionHash, statsByModel, totalsByModel, model) ?? emptyCacheStats(),
3923
+ };
3924
+ }
3925
+
3926
+ function findBestRouterModelStats(
3927
+ mode: FooterStatsMode,
3928
+ sessionHash: string | undefined,
3929
+ statsByModel: Record<string, CacheStats>,
3930
+ totalsByModel: Record<string, CacheStats>,
3931
+ ): { model: PiModel; adapter: CacheProviderAdapter; stats: CacheStats } | undefined {
3932
+ const entries = mode === "total"
3933
+ ? Object.entries(totalsByModel)
3934
+ : sessionHash
3935
+ ? Object.entries(statsByModel)
3936
+ .filter(([key]) => key.startsWith(`${sessionHash}:`))
3937
+ .map(([key, stats]) => [key.slice(sessionHash.length + 1), stats] as const)
3938
+ : [];
3939
+ let best: { model: PiModel; adapter: CacheProviderAdapter; stats: CacheStats; total: number } | undefined;
3940
+
3941
+ for (const [modelKeyPart, stats] of entries) {
3942
+ const slashIdx = modelKeyPart.indexOf("/");
3943
+ if (slashIdx < 1 || slashIdx >= modelKeyPart.length - 1) continue;
3944
+ const model = routedModelRefToPiModel({
3945
+ provider: modelKeyPart.slice(0, slashIdx),
3946
+ id: modelKeyPart.slice(slashIdx + 1),
3947
+ });
3948
+ const adapter = selectAdapterForModel(model);
3949
+ if (!adapter) continue;
3950
+ if (!best || stats.totalRequests > best.total) {
3951
+ best = { model, adapter, stats, total: stats.totalRequests };
3952
+ }
3953
+ }
3954
+
3955
+ return best ? { model: best.model, adapter: best.adapter, stats: best.stats } : undefined;
3819
3956
  }
3820
3957
 
3821
3958
  function parsePersistedCacheStats(value: unknown): CacheStatsState | undefined {
@@ -5040,6 +5177,63 @@ function parseJsonc(text: string): unknown {
5040
5177
  return JSON.parse(stripJsoncTrailingCommas(stripJsoncComments(text)));
5041
5178
  }
5042
5179
 
5180
+ type ExplicitCompatSource = "modelOverride" | "model" | "provider";
5181
+
5182
+ interface ExplicitCompatValue {
5183
+ source: ExplicitCompatSource;
5184
+ value: unknown;
5185
+ }
5186
+
5187
+ /**
5188
+ * Resolve one explicitly configured compat value using Pi's models.json
5189
+ * precedence. Built-in model overrides win over custom model definitions,
5190
+ * which in turn win over provider defaults.
5191
+ */
5192
+ function resolveExplicitCompatValue(
5193
+ config: unknown,
5194
+ providerLabel: string,
5195
+ modelId: string,
5196
+ compatKey: string,
5197
+ ): ExplicitCompatValue | undefined {
5198
+ const providers = asRecord(asRecord(config)?.providers);
5199
+ const provider = asRecord(providers?.[providerLabel]);
5200
+ if (!provider) return undefined;
5201
+
5202
+ const override = asRecord(asRecord(provider.modelOverrides)?.[modelId]);
5203
+ const overrideCompat = asRecord(override?.compat);
5204
+ if (overrideCompat && Object.prototype.hasOwnProperty.call(overrideCompat, compatKey)) {
5205
+ return { source: "modelOverride", value: overrideCompat[compatKey] };
5206
+ }
5207
+
5208
+ if (Array.isArray(provider.models)) {
5209
+ const model = provider.models.find((entry: unknown) => asRecord(entry)?.id === modelId);
5210
+ const modelCompat = asRecord(asRecord(model)?.compat);
5211
+ if (modelCompat && Object.prototype.hasOwnProperty.call(modelCompat, compatKey)) {
5212
+ return { source: "model", value: modelCompat[compatKey] };
5213
+ }
5214
+ }
5215
+
5216
+ const providerCompat = asRecord(provider.compat);
5217
+ if (providerCompat && Object.prototype.hasOwnProperty.call(providerCompat, compatKey)) {
5218
+ return { source: "provider", value: providerCompat[compatKey] };
5219
+ }
5220
+
5221
+ return undefined;
5222
+ }
5223
+
5224
+ function hasExplicitLongRetentionOptInFromConfig(
5225
+ config: unknown,
5226
+ providerLabel: string,
5227
+ modelId: string,
5228
+ ): boolean {
5229
+ return resolveExplicitCompatValue(
5230
+ config,
5231
+ providerLabel,
5232
+ modelId,
5233
+ "supportsLongCacheRetention",
5234
+ )?.value === true;
5235
+ }
5236
+
5043
5237
  /**
5044
5238
  * JSONC scanner: locate the provider block and model entry in models.json text.
5045
5239
  * Returns the byte offsets for surgical insertion, or undefined if ambiguous.
@@ -5065,10 +5259,99 @@ interface ModelNodeLocation {
5065
5259
  providerCompatBrace: number;
5066
5260
  /** Offset of the provider-level compat object's closing `}`, or -1 if absent */
5067
5261
  providerCompatEnd: number;
5262
+ /** Offset of the target modelOverrides entry's opening `{`, or -1 if absent */
5263
+ modelOverrideObjectBrace: number;
5264
+ /** Offset of the target modelOverrides entry's closing `}`, or -1 if absent */
5265
+ modelOverrideObjectEnd: number;
5266
+ /** Offset of the target override compat object's opening `{`, or -1 if absent */
5267
+ modelOverrideCompatBrace: number;
5268
+ /** Offset of the target override compat object's closing `}`, or -1 if absent */
5269
+ modelOverrideCompatEnd: number;
5068
5270
  /** All model ids found in this provider's models array (for placement safety analysis) */
5069
5271
  allModelIds: string[];
5070
5272
  }
5071
5273
 
5274
+ interface ModelOverrideNodeLocation {
5275
+ providerObjectBrace: number;
5276
+ providerObjectEnd: number;
5277
+ modelOverridesObjectBrace: number;
5278
+ modelOverridesObjectEnd: number;
5279
+ modelOverrideObjectBrace: number;
5280
+ modelOverrideObjectEnd: number;
5281
+ modelOverrideCompatBrace: number;
5282
+ modelOverrideCompatEnd: number;
5283
+ }
5284
+
5285
+ /** Locate a provider and optional modelOverrides entry without requiring models[]. */
5286
+ function locateModelOverrideInJsonc(
5287
+ text: string,
5288
+ providerLabel: string,
5289
+ modelId: string,
5290
+ ): ModelOverrideNodeLocation | undefined {
5291
+ const clean = stripJsoncComments(text);
5292
+ const rootBrace = skipJsonWhitespace(clean, 0);
5293
+ if (clean[rootBrace] !== "{") return undefined;
5294
+ const providersKey = findJsonObjectKey(clean, rootBrace, "providers");
5295
+ if (!providersKey) return undefined;
5296
+ const providersBrace = skipJsonWhitespace(clean, providersKey.valueStart);
5297
+ if (clean[providersBrace] !== "{") return undefined;
5298
+ const providersEnd = findMatchingBracket(clean, providersBrace);
5299
+ if (providersEnd === undefined) return undefined;
5300
+ const providerKey = findJsonObjectKey(clean, providersBrace, providerLabel);
5301
+ if (!providerKey || providerKey.keyStart > providersEnd) return undefined;
5302
+ const providerObjectBrace = skipJsonWhitespace(clean, providerKey.valueStart);
5303
+ if (clean[providerObjectBrace] !== "{") return undefined;
5304
+ const providerObjectEnd = findMatchingBracket(clean, providerObjectBrace);
5305
+ if (providerObjectEnd === undefined || providerObjectEnd > providersEnd) return undefined;
5306
+
5307
+ let modelOverridesObjectBrace = -1;
5308
+ let modelOverridesObjectEnd = -1;
5309
+ let modelOverrideObjectBrace = -1;
5310
+ let modelOverrideObjectEnd = -1;
5311
+ let modelOverrideCompatBrace = -1;
5312
+ let modelOverrideCompatEnd = -1;
5313
+ const overridesKey = findJsonObjectKey(clean, providerObjectBrace, "modelOverrides");
5314
+ if (overridesKey && overridesKey.keyStart < providerObjectEnd) {
5315
+ const brace = skipJsonWhitespace(clean, overridesKey.valueStart);
5316
+ if (clean[brace] !== "{") return undefined;
5317
+ const end = findMatchingBracket(clean, brace);
5318
+ if (end === undefined || end > providerObjectEnd) return undefined;
5319
+ modelOverridesObjectBrace = brace;
5320
+ modelOverridesObjectEnd = end;
5321
+
5322
+ const overrideKey = findJsonObjectKey(clean, brace, modelId);
5323
+ if (overrideKey && overrideKey.keyStart < end) {
5324
+ const entryBrace = skipJsonWhitespace(clean, overrideKey.valueStart);
5325
+ if (clean[entryBrace] !== "{") return undefined;
5326
+ const entryEnd = findMatchingBracket(clean, entryBrace);
5327
+ if (entryEnd === undefined || entryEnd > end) return undefined;
5328
+ modelOverrideObjectBrace = entryBrace;
5329
+ modelOverrideObjectEnd = entryEnd;
5330
+
5331
+ const compatKey = findJsonObjectKey(clean, entryBrace, "compat");
5332
+ if (compatKey && compatKey.keyStart < entryEnd) {
5333
+ const compatBrace = skipJsonWhitespace(clean, compatKey.valueStart);
5334
+ if (clean[compatBrace] !== "{") return undefined;
5335
+ const compatEnd = findMatchingBracket(clean, compatBrace);
5336
+ if (compatEnd === undefined || compatEnd > entryEnd) return undefined;
5337
+ modelOverrideCompatBrace = compatBrace;
5338
+ modelOverrideCompatEnd = compatEnd;
5339
+ }
5340
+ }
5341
+ }
5342
+
5343
+ return {
5344
+ providerObjectBrace,
5345
+ providerObjectEnd,
5346
+ modelOverridesObjectBrace,
5347
+ modelOverridesObjectEnd,
5348
+ modelOverrideObjectBrace,
5349
+ modelOverrideObjectEnd,
5350
+ modelOverrideCompatBrace,
5351
+ modelOverrideCompatEnd,
5352
+ };
5353
+ }
5354
+
5072
5355
  /**
5073
5356
  * Locate the provider + model entry in raw JSONC text.
5074
5357
  * Returns the positions needed for surgical insertion, or undefined on failure.
@@ -5121,6 +5404,12 @@ function locateModelInJsonc(
5121
5404
  }
5122
5405
  }
5123
5406
 
5407
+ const overrideLocation = locateModelOverrideInJsonc(text, providerLabel, modelId);
5408
+ const modelOverrideObjectBrace = overrideLocation?.modelOverrideObjectBrace ?? -1;
5409
+ const modelOverrideObjectEnd = overrideLocation?.modelOverrideObjectEnd ?? -1;
5410
+ const modelOverrideCompatBrace = overrideLocation?.modelOverrideCompatBrace ?? -1;
5411
+ const modelOverrideCompatEnd = overrideLocation?.modelOverrideCompatEnd ?? -1;
5412
+
5124
5413
  const modelsKey = findJsonObjectKey(clean, providerBrace, "models");
5125
5414
  if (!modelsKey || modelsKey.keyStart > providerEndBrace) return undefined;
5126
5415
 
@@ -5208,6 +5497,10 @@ function locateModelInJsonc(
5208
5497
  providerObjectEnd: providerEndBrace,
5209
5498
  providerCompatBrace,
5210
5499
  providerCompatEnd,
5500
+ modelOverrideObjectBrace,
5501
+ modelOverrideObjectEnd,
5502
+ modelOverrideCompatBrace,
5503
+ modelOverrideCompatEnd,
5211
5504
  allModelIds,
5212
5505
  };
5213
5506
  }
@@ -5217,7 +5510,7 @@ function locateModelInJsonc(
5217
5510
  * `locateModelInJsonc` cannot find the target provider/model.
5218
5511
  */
5219
5512
  type MissingEntryDiagnosis =
5220
- | { scenario: "provider_missing"; providersEnd: number }
5513
+ | { scenario: "provider_missing"; providersBrace: number; providersEnd: number }
5221
5514
  | { scenario: "model_missing"; modelsEnd: number; providerBrace: number; providerEndBrace: number }
5222
5515
  | { scenario: "provider_without_models"; providerBrace: number; providerEndBrace: number };
5223
5516
 
@@ -5248,7 +5541,7 @@ function analyzeModelsJsonForMissingEntry(
5248
5541
 
5249
5542
  const providerKey = findJsonObjectKey(clean, providersBrace, providerLabel);
5250
5543
  if (!providerKey || providerKey.keyStart > providersEnd) {
5251
- return { scenario: "provider_missing", providersEnd };
5544
+ return { scenario: "provider_missing", providersBrace, providersEnd };
5252
5545
  }
5253
5546
 
5254
5547
  // Provider exists. Check for a models array so we know where to append.
@@ -5286,20 +5579,126 @@ function formatMissingEntryManualSnippet(
5286
5579
  ): string {
5287
5580
  const lines: string[] = [];
5288
5581
  const sorted = Object.entries(compatKeys).sort(([a], [b]) => a.localeCompare(b));
5289
- const compatItems = sorted.map(([k, v]) => ` ${JSON.stringify(k)}: ${JSON.stringify(v)}`);
5582
+ const compatItems = sorted.map(([k, v]) => ` ${JSON.stringify(k)}: ${JSON.stringify(v)}`);
5290
5583
  lines.push(`"${providerLabel}": {`);
5291
- lines.push(` "models": [`);
5292
- lines.push(` {`);
5293
- lines.push(` "id": ${JSON.stringify(modelId)},`);
5584
+ lines.push(` "modelOverrides": {`);
5585
+ lines.push(` ${JSON.stringify(modelId)}: {`);
5294
5586
  lines.push(` "compat": {`);
5295
5587
  lines.push(compatItems.join(",\n"));
5296
5588
  lines.push(` }`);
5297
5589
  lines.push(` }`);
5298
- lines.push(` ]`);
5590
+ lines.push(` }`);
5299
5591
  lines.push(` }`);
5300
5592
  return lines.join("\n");
5301
5593
  }
5302
5594
 
5595
+ /**
5596
+ * Insert or repair a modelOverrides entry without creating a custom models[]
5597
+ * definition. This is the safest representation for built-in/API-login models
5598
+ * and has the same highest precedence Pi applies at runtime.
5599
+ */
5600
+ function composeModelOverrideInsertion(
5601
+ originalText: string,
5602
+ providerLabel: string,
5603
+ modelId: string,
5604
+ compatKeys: Record<string, unknown>,
5605
+ ): { modifiedText: string; placementLabel: string } | undefined {
5606
+ const location = locateModelOverrideInJsonc(originalText, providerLabel, modelId);
5607
+
5608
+ if (location?.modelOverrideObjectBrace !== undefined && location.modelOverrideObjectBrace >= 0) {
5609
+ const modelLocation: ModelNodeLocation = {
5610
+ modelObjectBrace: -1,
5611
+ modelObjectEnd: -1,
5612
+ compatKeyStart: -1,
5613
+ compatObjectBrace: -1,
5614
+ compatObjectEnd: -1,
5615
+ indent: "",
5616
+ providerObjectBrace: location.providerObjectBrace,
5617
+ providerObjectEnd: location.providerObjectEnd,
5618
+ providerCompatBrace: -1,
5619
+ providerCompatEnd: -1,
5620
+ modelOverrideObjectBrace: location.modelOverrideObjectBrace,
5621
+ modelOverrideObjectEnd: location.modelOverrideObjectEnd,
5622
+ modelOverrideCompatBrace: location.modelOverrideCompatBrace,
5623
+ modelOverrideCompatEnd: location.modelOverrideCompatEnd,
5624
+ allModelIds: [],
5625
+ };
5626
+ return {
5627
+ modifiedText: composeFixInsertion(originalText, modelLocation, compatKeys, "modelOverride"),
5628
+ placementLabel: `providers["${providerLabel}"] -> modelOverrides["${modelId}"] -> compat`,
5629
+ };
5630
+ }
5631
+
5632
+ const sortedEntries = Object.entries(compatKeys).sort(([a], [b]) => a.localeCompare(b));
5633
+ const formatOverrideEntry = (keyIndent: string, unit: string): string => {
5634
+ const propertyIndent = keyIndent + unit;
5635
+ const compatIndent = propertyIndent + unit;
5636
+ const compatLines = sortedEntries
5637
+ .map(([key, value]) => `${compatIndent}${JSON.stringify(key)}: ${JSON.stringify(value)}`)
5638
+ .join(",\n");
5639
+ return `${keyIndent}${JSON.stringify(modelId)}: {\n` +
5640
+ `${propertyIndent}"compat": {\n${compatLines}\n${propertyIndent}}\n${keyIndent}}`;
5641
+ };
5642
+ const previousTokenNeedsComma = (clean: string, closeBrace: number): boolean => {
5643
+ let pos = closeBrace - 1;
5644
+ while (pos >= 0 && isJsonWhitespace(clean[pos])) pos--;
5645
+ return clean[pos] !== "{" && clean[pos] !== ",";
5646
+ };
5647
+
5648
+ if (location) {
5649
+ const clean = stripJsoncComments(originalText);
5650
+ if (location.modelOverridesObjectBrace >= 0) {
5651
+ const containerIndent = lineIndentOf(originalText, location.modelOverridesObjectBrace);
5652
+ const keyIndent = deriveInnerIndent(
5653
+ originalText,
5654
+ location.modelOverridesObjectBrace,
5655
+ location.modelOverridesObjectEnd,
5656
+ );
5657
+ const unit = keyIndent.length > containerIndent.length
5658
+ ? keyIndent.slice(containerIndent.length)
5659
+ : " ";
5660
+ const comma = previousTokenNeedsComma(clean, location.modelOverridesObjectEnd) ? "," : "";
5661
+ const insertion = `${comma}\n${formatOverrideEntry(keyIndent, unit)}\n${containerIndent}`;
5662
+ return {
5663
+ modifiedText: originalText.slice(0, location.modelOverridesObjectEnd) + insertion + originalText.slice(location.modelOverridesObjectEnd),
5664
+ placementLabel: `providers["${providerLabel}"] -> modelOverrides -> (new entry "${modelId}")`,
5665
+ };
5666
+ }
5667
+
5668
+ const providerIndent = lineIndentOf(originalText, location.providerObjectBrace);
5669
+ const propertyIndent = deriveInnerIndent(originalText, location.providerObjectBrace, location.providerObjectEnd);
5670
+ const unit = propertyIndent.length > providerIndent.length
5671
+ ? propertyIndent.slice(providerIndent.length)
5672
+ : " ";
5673
+ const entryIndent = propertyIndent + unit;
5674
+ const block = `\n${propertyIndent}"modelOverrides": {\n` +
5675
+ `${formatOverrideEntry(entryIndent, unit)}\n${propertyIndent}},`;
5676
+ return {
5677
+ modifiedText: originalText.slice(0, location.providerObjectBrace + 1) + block + originalText.slice(location.providerObjectBrace + 1),
5678
+ placementLabel: `providers["${providerLabel}"] -> (new modelOverrides entry "${modelId}")`,
5679
+ };
5680
+ }
5681
+
5682
+ const diagnosis = analyzeModelsJsonForMissingEntry(originalText, providerLabel, modelId);
5683
+ if (!diagnosis || diagnosis.scenario !== "provider_missing") return undefined;
5684
+ const clean = stripJsoncComments(originalText);
5685
+ const providersIndent = lineIndentOf(originalText, diagnosis.providersEnd);
5686
+ const providerIndent = deriveInnerIndent(originalText, diagnosis.providersBrace, diagnosis.providersEnd);
5687
+ const unit = providerIndent.length > providersIndent.length
5688
+ ? providerIndent.slice(providersIndent.length)
5689
+ : " ";
5690
+ const overridesIndent = providerIndent + unit;
5691
+ const entryIndent = overridesIndent + unit;
5692
+ const comma = previousTokenNeedsComma(clean, diagnosis.providersEnd) ? "," : "";
5693
+ const block = `${comma}\n${providerIndent}${JSON.stringify(providerLabel)}: {\n` +
5694
+ `${overridesIndent}"modelOverrides": {\n${formatOverrideEntry(entryIndent, unit)}\n` +
5695
+ `${overridesIndent}}\n${providerIndent}}\n${providersIndent}`;
5696
+ return {
5697
+ modifiedText: originalText.slice(0, diagnosis.providersEnd) + block + originalText.slice(diagnosis.providersEnd),
5698
+ placementLabel: `providers -> (new modelOverrides-only entry "${providerLabel}/${modelId}")`,
5699
+ };
5700
+ }
5701
+
5303
5702
  /**
5304
5703
  * Surgically insert the missing provider/model entry into the original
5305
5704
  * JSONC text. Returns the modified text and placement descriptor.
@@ -5458,10 +5857,10 @@ function composeMissingEntryInsertion(
5458
5857
  }
5459
5858
 
5460
5859
  /**
5461
- * Lightweight self-check for a newly inserted entry.
5860
+ * Lightweight self-check for a newly inserted model or modelOverrides entry.
5462
5861
  * Parses the modified text as JSONC and confirms:
5463
- * 1. The target model exists under the provider.
5464
- * 2. Every compat key has the expected value (merged provider+model).
5862
+ * 1. The target exists under models[] or modelOverrides.
5863
+ * 2. Every compat key has the expected effective three-layer value.
5465
5864
  * Returns null on success, an error string on failure.
5466
5865
  */
5467
5866
  function selfCheckMissingEntryInsertion(
@@ -5472,34 +5871,88 @@ function selfCheckMissingEntryInsertion(
5472
5871
  compatKeys: Record<string, unknown>,
5473
5872
  ): string | null {
5474
5873
  try {
5874
+ const origParsed = parseJsonc(originalText);
5475
5875
  const modParsed = parseJsonc(modifiedText);
5476
5876
  const providers = asRecord(asRecord(modParsed)?.providers);
5477
5877
  if (!providers) return "Modified file: providers object missing or invalid";
5478
5878
  const provider = asRecord(providers[providerLabel]);
5479
5879
  if (!provider) return `Modified file: provider "${providerLabel}" not found`;
5480
5880
  const models = provider.models;
5481
- if (!Array.isArray(models)) return `Modified file: provider "${providerLabel}".models is not an array`;
5482
- const targetModel = models.find((m: unknown) => asRecord(m)?.id === modelId);
5483
- if (!targetModel || typeof targetModel !== "object")
5484
- return `Modified file: model "${modelId}" not found in provider after insertion`;
5881
+ const targetModel = Array.isArray(models)
5882
+ ? models.find((m: unknown) => asRecord(m)?.id === modelId)
5883
+ : undefined;
5884
+ const targetOverride = asRecord(asRecord(provider.modelOverrides)?.[modelId]);
5885
+ if (!targetModel && !targetOverride) {
5886
+ return `Modified file: model or modelOverrides entry "${modelId}" not found in provider after insertion`;
5887
+ }
5485
5888
 
5486
- // Validate effective merged compat
5487
- const provCompatRaw = (provider as Record<string, unknown>).compat;
5488
- const provCompat = (provCompatRaw && typeof provCompatRaw === "object" && !Array.isArray(provCompatRaw))
5489
- ? provCompatRaw as Record<string, unknown>
5490
- : {};
5491
- const mdlCompatRaw = (targetModel as Record<string, unknown>).compat;
5492
- const mdlCompat = (mdlCompatRaw && typeof mdlCompatRaw === "object" && !Array.isArray(mdlCompatRaw))
5493
- ? mdlCompatRaw as Record<string, unknown>
5494
- : {};
5495
- const merged = { ...provCompat, ...mdlCompat };
5496
5889
  for (const [k, v] of Object.entries(compatKeys)) {
5497
- if (!(k in merged)) return `Modified file: effective compat.${k} not found`;
5498
- if (merged[k] !== v) return `Modified file: effective compat.${k} wrong value`;
5890
+ const effective = resolveExplicitCompatValue(modParsed, providerLabel, modelId, k);
5891
+ if (!effective) return `Modified file: effective compat.${k} not found`;
5892
+ if (effective.value !== v) {
5893
+ return `Modified file: effective compat.${k} wrong value: expected ${JSON.stringify(v)}, got ${JSON.stringify(effective.value)} from ${effective.source}`;
5894
+ }
5499
5895
  }
5500
5896
 
5501
- if (modifiedText.length < originalText.length)
5502
- return "Modified file: content is shorter than original (possible truncation)";
5897
+ // Normalize only the intended override edit back to its original shape,
5898
+ // then require the complete parsed structure to match. This catches data
5899
+ // loss while allowing legitimate shorter repairs such as false -> true.
5900
+ const normalized = JSON.parse(JSON.stringify(modParsed)) as Record<string, unknown>;
5901
+ const origProviders = asRecord(asRecord(origParsed)?.providers);
5902
+ const origProvider = asRecord(origProviders?.[providerLabel]);
5903
+ const normalizedProviders = asRecord(normalized.providers);
5904
+ if (!normalizedProviders) return "Modified file: normalized providers object missing";
5905
+
5906
+ if (!origProvider) {
5907
+ delete normalizedProviders[providerLabel];
5908
+ } else if (targetOverride) {
5909
+ const normalizedProvider = asRecord(normalizedProviders[providerLabel]);
5910
+ const normalizedOverrides = asRecord(normalizedProvider?.modelOverrides);
5911
+ const origOverrides = asRecord(origProvider.modelOverrides);
5912
+ const origOverride = asRecord(origOverrides?.[modelId]);
5913
+
5914
+ if (!normalizedProvider || !normalizedOverrides) {
5915
+ return `Modified file: modelOverrides["${modelId}"] missing during structure validation`;
5916
+ }
5917
+ if (!origOverride) {
5918
+ delete normalizedOverrides[modelId];
5919
+ if (!origOverrides) delete normalizedProvider.modelOverrides;
5920
+ } else {
5921
+ const normalizedOverride = asRecord(normalizedOverrides[modelId]);
5922
+ if (!normalizedOverride) return `Modified file: modelOverrides["${modelId}"] invalid`;
5923
+ const origCompat = asRecord(origOverride.compat);
5924
+ const normalizedCompat = asRecord(normalizedOverride.compat);
5925
+ if (!origCompat) {
5926
+ delete normalizedOverride.compat;
5927
+ } else {
5928
+ if (!normalizedCompat) return `Modified file: modelOverrides["${modelId}"].compat invalid`;
5929
+ for (const key of Object.keys(compatKeys)) {
5930
+ if (Object.prototype.hasOwnProperty.call(origCompat, key)) {
5931
+ normalizedCompat[key] = origCompat[key];
5932
+ } else {
5933
+ delete normalizedCompat[key];
5934
+ }
5935
+ }
5936
+ }
5937
+ }
5938
+ } else {
5939
+ // Backward-compatible validation for the legacy models[] insertion
5940
+ // helper retained in __internals_for_tests.
5941
+ const normalizedProvider = asRecord(normalizedProviders[providerLabel]);
5942
+ const normalizedModels = normalizedProvider?.models;
5943
+ const origModels = origProvider.models;
5944
+ if (!normalizedProvider || !Array.isArray(normalizedModels)) {
5945
+ return `Modified file: provider "${providerLabel}".models missing during structure validation`;
5946
+ }
5947
+ normalizedProvider.models = normalizedModels.filter(
5948
+ (entry: unknown) => asRecord(entry)?.id !== modelId,
5949
+ );
5950
+ if (!Array.isArray(origModels)) delete normalizedProvider.models;
5951
+ }
5952
+
5953
+ if (!deepEqualIgnoringKeys(normalized, origParsed, [])) {
5954
+ return "Modified file: original structure was altered (data loss detected)";
5955
+ }
5503
5956
 
5504
5957
  const modClean = stripJsoncComments(modifiedText);
5505
5958
  const rootStart = skipJsonWhitespace(modClean, 0);
@@ -5655,7 +6108,14 @@ function chooseFixPlacement(
5655
6108
  compatKeys: Record<string, unknown>,
5656
6109
  providerLabel: string,
5657
6110
  forceModelLevel = false,
5658
- ): { placement: "provider" | "model"; reason: string } {
6111
+ ): { placement: "provider" | "model" | "modelOverride"; reason: string } {
6112
+ if (location.modelOverrideObjectBrace >= 0) {
6113
+ return {
6114
+ placement: "modelOverride",
6115
+ reason: "an existing modelOverrides entry has Pi's highest precedence — repairing it directly",
6116
+ };
6117
+ }
6118
+
5659
6119
  if (forceModelLevel) {
5660
6120
  return {
5661
6121
  placement: "model",
@@ -5689,12 +6149,24 @@ function composeFixInsertion(
5689
6149
  original: string,
5690
6150
  location: ModelNodeLocation,
5691
6151
  compatKeys: Record<string, unknown>,
5692
- placement: "provider" | "model" = "model",
6152
+ placement: "provider" | "model" | "modelOverride" = "model",
5693
6153
  ): string {
5694
6154
  // Resolve the target compat object and its container based on placement.
5695
- const targetCompatBrace = placement === "provider" ? location.providerCompatBrace : location.compatObjectBrace;
5696
- const targetCompatEnd = placement === "provider" ? location.providerCompatEnd : location.compatObjectEnd;
5697
- const containerBrace = placement === "provider" ? location.providerObjectBrace : location.modelObjectBrace;
6155
+ const targetCompatBrace = placement === "provider"
6156
+ ? location.providerCompatBrace
6157
+ : placement === "modelOverride"
6158
+ ? location.modelOverrideCompatBrace
6159
+ : location.compatObjectBrace;
6160
+ const targetCompatEnd = placement === "provider"
6161
+ ? location.providerCompatEnd
6162
+ : placement === "modelOverride"
6163
+ ? location.modelOverrideCompatEnd
6164
+ : location.compatObjectEnd;
6165
+ const containerBrace = placement === "provider"
6166
+ ? location.providerObjectBrace
6167
+ : placement === "modelOverride"
6168
+ ? location.modelOverrideObjectBrace
6169
+ : location.modelObjectBrace;
5698
6170
 
5699
6171
  // Helper: format key/value pairs as lines with the given indent,
5700
6172
  // alphabetically sorted for stable previews and deterministic edits.
@@ -5796,7 +6268,7 @@ function selfCheckFix(
5796
6268
  providerLabel: string,
5797
6269
  modelId: string,
5798
6270
  compatKeys: Record<string, unknown>,
5799
- placement: "provider" | "model" = "model",
6271
+ placement: "provider" | "model" | "modelOverride" = "model",
5800
6272
  ): string | null {
5801
6273
  try {
5802
6274
  // Step 1: Parse both versions as JSONC (comments + trailing commas allowed).
@@ -5840,9 +6312,9 @@ function selfCheckFix(
5840
6312
  return `Original file: provider/model "${providerLabel}/${modelId}" not found`;
5841
6313
  }
5842
6314
 
5843
- // Step 5: Compute the EFFECTIVE merged compat (provider-level + model-level),
5844
- // mirroring Pi's mergeCompat behavior (model wins on conflicts). The fix may
5845
- // have written either level, so validation must check the merged result.
6315
+ // Step 5: Compute the EFFECTIVE merged compat using Pi's precedence:
6316
+ // provider, custom model, then modelOverrides. The fix may have written any
6317
+ // level, so validation must check what Pi will actually use.
5846
6318
  const provCompatRaw = (provider as Record<string, unknown>).compat;
5847
6319
  const provCompat = (provCompatRaw && typeof provCompatRaw === 'object' && !Array.isArray(provCompatRaw))
5848
6320
  ? provCompatRaw as Record<string, unknown>
@@ -5852,7 +6324,13 @@ function selfCheckFix(
5852
6324
  return `Modified file: model "${modelId}" compat is not an object`;
5853
6325
  }
5854
6326
  const mdlCompat = (modelCompatRaw ?? {}) as Record<string, unknown>;
5855
- const mergedCompat: Record<string, unknown> = { ...provCompat, ...mdlCompat };
6327
+ const override = asRecord(asRecord(provider.modelOverrides)?.[modelId]);
6328
+ const overrideCompatRaw = override?.compat;
6329
+ if (overrideCompatRaw !== undefined && !asRecord(overrideCompatRaw)) {
6330
+ return `Modified file: modelOverrides["${modelId}"].compat is not an object`;
6331
+ }
6332
+ const overrideCompat = asRecord(overrideCompatRaw) ?? {};
6333
+ const mergedCompat: Record<string, unknown> = { ...provCompat, ...mdlCompat, ...overrideCompat };
5856
6334
 
5857
6335
  // Step 6: Validate all inserted keys are effective in the merged compat
5858
6336
  for (const [k, v] of Object.entries(compatKeys)) {
@@ -5900,7 +6378,8 @@ function selfCheckFix(
5900
6378
  // the placement-resolved object's own compat may be exempt.
5901
6379
  const mayRepairThisCompat =
5902
6380
  (placement === "provider" && origObj === origProvider) ||
5903
- (placement === "model" && origObj === origTargetModelRecord);
6381
+ (placement === "model" && origObj === origTargetModelRecord) ||
6382
+ (placement === "modelOverride" && origObj === asRecord(asRecord(origProvider.modelOverrides)?.[modelId]));
5904
6383
  for (const ck of Object.keys(origCompat)) {
5905
6384
  if (!(ck in modCompat)) return false;
5906
6385
  // The fix may repair an existing wrong compat value (for example
@@ -6191,6 +6670,16 @@ export const __internals_for_tests = {
6191
6670
  parsePersistedRoutedModelRef,
6192
6671
  routedModelRefToPiModel,
6193
6672
  buildExactRouterStatusEntry,
6673
+ findBestRouterModelStats,
6674
+ selectFooterStatsForModel,
6675
+ parseFooterStatsMode,
6676
+ parsePersistedCacheOptimizerConfig,
6677
+ readPersistedFooterMode,
6678
+ writePersistedFooterMode,
6679
+ resolveFooterStatsMode,
6680
+ footerStatsMode,
6681
+ CONFIG_FILE_PATH,
6682
+ FOOTER_MODE_ENV,
6194
6683
  // Routing-provider protocol helpers
6195
6684
  PI_ROUTING_REGISTRY_SYMBOL,
6196
6685
  PI_CACHE_HINTS_SYMBOL,
@@ -6222,11 +6711,15 @@ export const __internals_for_tests = {
6222
6711
  stripJsoncComments,
6223
6712
  stripJsoncTrailingCommas,
6224
6713
  parseJsonc,
6714
+ resolveExplicitCompatValue,
6715
+ hasExplicitLongRetentionOptInFromConfig,
6225
6716
  locateModelInJsonc,
6717
+ locateModelOverrideInJsonc,
6226
6718
  composeFixInsertion,
6227
6719
  selfCheckFix,
6228
6720
  analyzeModelsJsonForMissingEntry,
6229
6721
  composeMissingEntryInsertion,
6722
+ composeModelOverrideInsertion,
6230
6723
  selfCheckMissingEntryInsertion,
6231
6724
  decideFixPlacement,
6232
6725
  chooseFixPlacement,
@@ -6625,50 +7118,6 @@ export default function (pi: ExtensionAPI) {
6625
7118
  await rollOverStatsIfNeeded(ctx);
6626
7119
  }
6627
7120
 
6628
- /**
6629
- * Fallback for older persisted files that do not yet carry exact
6630
- * last-routed-model metadata. When the current model is a router channel
6631
- * (e.g. router/auto), restorable stats are stored under the real upstream
6632
- * model's provider/id key, not under router/auto. Find the best valid entry
6633
- * (highest totalRequests among adapter-detectable model keys) so we can show
6634
- * meaningful footer content on session_start after reload.
6635
- */
6636
- function findBestRouterModelStats(): { adapter: CacheProviderAdapter; stats: CacheStats } | undefined {
6637
- let best: { adapter: CacheProviderAdapter; stats: CacheStats; total: number } | undefined;
6638
-
6639
- for (const [modelKeyPart, stats] of Object.entries(cacheStatsTotalsByModel)) {
6640
- const slashIdx = modelKeyPart.indexOf("/");
6641
- if (slashIdx < 0 || slashIdx >= modelKeyPart.length - 1) continue;
6642
- const modelId = modelKeyPart.slice(slashIdx + 1);
6643
- const providerName = modelKeyPart.slice(0, slashIdx);
6644
-
6645
- // Construct a minimal model for adapter detection.
6646
- // Every is*LikeModel function only accesses model.id and model.name
6647
- // via getModelIdNameTokenValues, so { id, name } is sufficient.
6648
- const mockModel = {
6649
- id: modelId,
6650
- name: modelId,
6651
- provider: providerName,
6652
- api: "",
6653
- baseUrl: "",
6654
- reasoning: false,
6655
- input: ["text"],
6656
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
6657
- contextWindow: 0,
6658
- maxTokens: 0,
6659
- } as PiModel;
6660
-
6661
- const adapter = selectAdapterForModel(mockModel);
6662
- if (!adapter) continue;
6663
-
6664
- if (!best || stats.totalRequests > best.total) {
6665
- best = { adapter, stats, total: stats.totalRequests };
6666
- }
6667
- }
6668
-
6669
- return best ? { adapter: best.adapter, stats: best.stats } : undefined;
6670
- }
6671
-
6672
7121
  async function publishStatus(ctx: ExtensionContext, model: PiModel | undefined = ctx.model): Promise<void> {
6673
7122
  syncSessionHash(ctx);
6674
7123
  await rollOverStatsIfNeeded(ctx);
@@ -6678,6 +7127,8 @@ export default function (pi: ExtensionAPI) {
6678
7127
  const adapter = selectAdapterForModel(displayModel);
6679
7128
  const activeIsVirtualRoute = !!routedModel || isVirtualRoutingModel(model, ctx);
6680
7129
  let statusText: string | undefined;
7130
+ const mode = footerStatsMode();
7131
+ const sessionHash = currentSessionHashSet ? currentSessionHash : undefined;
6681
7132
 
6682
7133
  if (!adapter && !routedModel && activeIsVirtualRoute) {
6683
7134
  // On model_select (existing footer), keep the existing cache footer
@@ -6687,11 +7138,17 @@ export default function (pi: ExtensionAPI) {
6687
7138
  // heuristics only when no exact metadata exists.
6688
7139
  if (lastStatusText !== undefined) return;
6689
7140
  const realEntry = buildExactRouterStatusEntry(
6690
- currentSessionHashSet ? currentSessionHash : undefined,
7141
+ sessionHash,
6691
7142
  cacheStatsByModel,
6692
7143
  lastActualRoutedModel,
6693
7144
  cacheStatsTotalsByModel,
6694
- ) ?? findBestRouterModelStats();
7145
+ mode,
7146
+ ) ?? findBestRouterModelStats(
7147
+ mode,
7148
+ sessionHash,
7149
+ cacheStatsByModel,
7150
+ cacheStatsTotalsByModel,
7151
+ );
6695
7152
  if (realEntry) {
6696
7153
  const statsText = formatCacheStats(realEntry.adapter, realEntry.stats);
6697
7154
  statusText = runtimeOptimizerEnabled
@@ -6701,10 +7158,11 @@ export default function (pi: ExtensionAPI) {
6701
7158
  }
6702
7159
 
6703
7160
  if (adapter) {
6704
- // Display provider/model cumulative stats. A model that has never
6705
- // been used locally shows 0/0. The message_end hook updates both the
6706
- // current session bucket and this restart-persistent totals bucket.
6707
- const stats = displayModel ? cacheStatsTotalsByModel[modelKey(displayModel)] : undefined;
7161
+ // Footer mode defaults to daily provider/model totals. Users may select
7162
+ // current-session counters through persistent command config or the env var.
7163
+ const stats = displayModel
7164
+ ? selectFooterStatsForModel(mode, sessionHash, cacheStatsByModel, cacheStatsTotalsByModel, displayModel)
7165
+ : undefined;
6708
7166
  const statsText = formatCacheStats(adapter, stats ?? emptyCacheStats());
6709
7167
  statusText = runtimeOptimizerEnabled ? statsText : `Cache Optimizer disabled · ${statsText}`;
6710
7168
  }
@@ -6760,9 +7218,8 @@ export default function (pi: ExtensionAPI) {
6760
7218
 
6761
7219
  /**
6762
7220
  * Check whether a model has an EXPLICIT supportsLongCacheRetention: true
6763
- * opt-in in models.json (either at provider-level or model-level).
6764
- * Model-level compat takes precedence over provider-level (mirrors Pi's
6765
- * mergeCompat behaviour: model wins on conflicts).
7221
+ * opt-in in models.json. Precedence mirrors Pi's effective model config:
7222
+ * modelOverrides[model.id].compat, then models[].compat, then provider.compat.
6766
7223
  *
6767
7224
  * Returns true ONLY when the user explicitly opted in. Returns false for:
6768
7225
  * - Explicit false (opt-out)
@@ -6776,31 +7233,7 @@ export default function (pi: ExtensionAPI) {
6776
7233
  try {
6777
7234
  const text = readFileSync(MODELS_JSON_PATH, "utf8");
6778
7235
  const parsed = parseJsonc(text);
6779
- const providers = asRecord(asRecord(parsed)?.providers);
6780
- if (!providers) return false;
6781
-
6782
- const prov = asRecord(providers[model.provider]);
6783
- if (!prov) return false;
6784
-
6785
- // Check model-level first (higher priority in Pi's merge logic)
6786
- const models = prov.models;
6787
- if (Array.isArray(models)) {
6788
- const modelEntry = models.find(m => asRecord(m)?.id === model.id);
6789
- if (modelEntry) {
6790
- const modelCompat = asRecord(asRecord(modelEntry)?.compat);
6791
- if (modelCompat?.supportsLongCacheRetention !== undefined) {
6792
- return modelCompat.supportsLongCacheRetention === true;
6793
- }
6794
- }
6795
- }
6796
-
6797
- // Check provider-level
6798
- const provCompat = asRecord(prov.compat);
6799
- if (provCompat?.supportsLongCacheRetention !== undefined) {
6800
- return provCompat.supportsLongCacheRetention === true;
6801
- }
6802
-
6803
- return false;
7236
+ return hasExplicitLongRetentionOptInFromConfig(parsed, model.provider, model.id);
6804
7237
  } catch {
6805
7238
  return false;
6806
7239
  }
@@ -7174,17 +7607,19 @@ export default function (pi: ExtensionAPI) {
7174
7607
  // with low-hit diagnosis
7175
7608
  // stats — show active model stats bucket, recent trend, usage
7176
7609
  // compat — show compat suggestion with file path
7610
+ // config footer-mode session|total — persist footer mode override
7177
7611
  // fix — auto-fix compat issues (writes models.json, requires UI)
7178
7612
  // reset — reset current provider/model footer stats bucket (local only)
7179
7613
  // (no args) — interactive menu (with UI) or help summary
7180
7614
  // ────────────────────────────────────────────────────────────────
7181
7615
  pi.registerCommand("cache-optimizer", {
7182
- description: "Diagnose Pi cache configuration",
7616
+ description: "Configure and diagnose Pi cache behavior",
7183
7617
  handler: async (args: string, cmdCtx) => {
7184
7618
  syncSessionHash(cmdCtx);
7185
7619
  const selectedModel = cmdCtx.model;
7186
7620
  const model = resolveRouteModel(selectedModel, cmdCtx as unknown as ExtensionContext) ?? selectedModel;
7187
- const subcommand = args.trim().toLowerCase().split(/\s+/)[0] || "help";
7621
+ const commandParts = args.trim().toLowerCase().split(/\s+/).filter(Boolean);
7622
+ const subcommand = commandParts[0] || "help";
7188
7623
 
7189
7624
  if (subcommand === "enable") {
7190
7625
  setRuntimeOptimizerEnabled(true);
@@ -7224,6 +7659,36 @@ export default function (pi: ExtensionAPI) {
7224
7659
  const samples = sk ? getRecentSamples(sk) : [];
7225
7660
  const output = buildStatsOutput(model, adapter, statsState, samples);
7226
7661
  cmdCtx.ui.notify(output, "info");
7662
+ } else if (subcommand === "config") {
7663
+ const configKey = commandParts[1];
7664
+ const requestedMode = commandParts[2];
7665
+ if (configKey !== "footer-mode" || !requestedMode || !["session", "total"].includes(requestedMode)) {
7666
+ const resolved = resolveFooterStatsMode(persistedFooterStatsMode);
7667
+ cmdCtx.ui.notify(
7668
+ `Usage: /cache-optimizer config footer-mode session|total\n` +
7669
+ `Current footer mode: ${resolved.mode} (${resolved.source})`,
7670
+ "info",
7671
+ );
7672
+ return;
7673
+ }
7674
+
7675
+ const nextMode = requestedMode as FooterStatsMode;
7676
+ try {
7677
+ await writePersistedFooterMode(nextMode);
7678
+ persistedFooterStatsMode = nextMode;
7679
+ lastStatusText = undefined;
7680
+ await publishStatus(cmdCtx as unknown as ExtensionContext, model);
7681
+ const resolved = resolveFooterStatsMode(persistedFooterStatsMode);
7682
+ cmdCtx.ui.notify(
7683
+ `✅ Footer mode set to ${resolved.mode}. Persistent config overrides ${FOOTER_MODE_ENV}.`,
7684
+ "info",
7685
+ );
7686
+ } catch (error) {
7687
+ cmdCtx.ui.notify(
7688
+ `❌ Could not update footer mode config: ${error instanceof Error ? error.message : String(error)}`,
7689
+ "error",
7690
+ );
7691
+ }
7227
7692
  } else if (subcommand === "compat") {
7228
7693
  if (!model) {
7229
7694
  cmdCtx.ui.notify("No active model selected. Select a model first with /model or pi --model.", "warning");
@@ -7318,8 +7783,8 @@ export default function (pi: ExtensionAPI) {
7318
7783
  }
7319
7784
  manualLines.push(
7320
7785
  "",
7321
- "If the provider/model already exists in models.json, add these compat keys under",
7322
- `providers["${suggestion.providerLabel}"] -> models -> entry with id "${suggestion.modelId}" -> compat:`,
7786
+ "Add these compat keys at Pi's highest-precedence model override path:",
7787
+ `providers["${suggestion.providerLabel}"] -> modelOverrides -> "${suggestion.modelId}" -> compat:`,
7323
7788
  formatCompatKeysForInsertion(suggestion.compatKeys),
7324
7789
  );
7325
7790
  if (snippet.length > 0) {
@@ -7353,11 +7818,21 @@ export default function (pi: ExtensionAPI) {
7353
7818
  if (!location) {
7354
7819
  const diagnosis = analyzeModelsJsonForMissingEntry(originalText, suggestion.providerLabel, suggestion.modelId);
7355
7820
  if (diagnosis && cmdCtx.hasUI) {
7356
- // Offer to create the missing entry.
7357
- const plan = composeMissingEntryInsertion(
7358
- originalText, diagnosis,
7359
- suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys,
7821
+ const overrideLocation = locateModelOverrideInJsonc(
7822
+ originalText, suggestion.providerLabel, suggestion.modelId,
7823
+ );
7824
+ const repairsExistingOverride = (overrideLocation?.modelOverrideObjectBrace ?? -1) >= 0;
7825
+ // Prefer a modelOverrides edit when models[] has no target entry.
7826
+ const plan = composeModelOverrideInsertion(
7827
+ originalText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys,
7360
7828
  );
7829
+ if (!plan) {
7830
+ cmdCtx.ui.notify(
7831
+ `❌ Could not safely locate a modelOverrides insertion point.\n` +
7832
+ `Falling back to manual guidance. No changes were made.`,
7833
+ "error",
7834
+ );
7835
+ } else {
7361
7836
  const checkError = selfCheckMissingEntryInsertion(
7362
7837
  originalText, plan.modifiedText,
7363
7838
  suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys,
@@ -7382,7 +7857,9 @@ export default function (pi: ExtensionAPI) {
7382
7857
  keysPreview,
7383
7858
  ``,
7384
7859
  `⚠️ Risk notice:`,
7385
- ` 1. This creates a new entry in models.json. Existing auth (e.g. login API tokens) is not affected.`,
7860
+ repairsExistingOverride
7861
+ ? ` 1. This updates the existing modelOverrides entry for "${suggestion.modelId}". Existing auth is not affected.`
7862
+ : ` 1. This creates a modelOverrides entry in models.json. Existing auth (e.g. login API tokens) is not affected.`,
7386
7863
  ` 2. A timestamped backup will be written to: ${backupPath}`,
7387
7864
  ` 3. You must run /reload or restart Pi for the change to take effect.`,
7388
7865
  ` 4. If the file contains comments or unusual formatting, please verify the result after write.`,
@@ -7397,7 +7874,10 @@ export default function (pi: ExtensionAPI) {
7397
7874
  );
7398
7875
  }
7399
7876
  previewLines.push("", `Apply these changes?`);
7400
- const confirmed = await cmdCtx.ui.confirm("Cache Optimizer — Fix (new entry)", previewLines.join("\n"));
7877
+ const confirmed = await cmdCtx.ui.confirm(
7878
+ repairsExistingOverride ? "Cache Optimizer — Fix (model override)" : "Cache Optimizer — Fix (new override)",
7879
+ previewLines.join("\n"),
7880
+ );
7401
7881
  if (confirmed) {
7402
7882
  try {
7403
7883
  await copyFile(MODELS_JSON_PATH, backupPath);
@@ -7436,6 +7916,7 @@ export default function (pi: ExtensionAPI) {
7436
7916
  cmdCtx.ui.notify("No changes were made. Canceled by user.", "info");
7437
7917
  return;
7438
7918
  }
7919
+ }
7439
7920
  }
7440
7921
 
7441
7922
  // Non-interactive or no diagnosis: show manual guidance.
@@ -7448,7 +7929,7 @@ export default function (pi: ExtensionAPI) {
7448
7929
  `❌ Could not locate model "${suggestion.modelId}" or provider "${suggestion.providerLabel}" in ${getModelsJsonDisplayPath()}.`,
7449
7930
  "",
7450
7931
  "Providers that were added via Pi /login API (e.g. opencode go) do not have",
7451
- "entries in models.json. You can create a minimal compat-only entry by hand:",
7932
+ "entries in models.json. You can create a minimal modelOverrides entry by hand:",
7452
7933
  );
7453
7934
  } else if (diagnosis.scenario === "provider_missing") {
7454
7935
  adviceLines.push(
@@ -7463,7 +7944,7 @@ export default function (pi: ExtensionAPI) {
7463
7944
  `ℹ️ Model "${suggestion.modelId}" was not found in ${getModelsJsonDisplayPath()}`,
7464
7945
  `under providers["${suggestion.providerLabel}"].`,
7465
7946
  "",
7466
- "Add the following entry to the models array (keep existing auth):",
7947
+ "Add the following modelOverrides entry (keep existing auth):",
7467
7948
  );
7468
7949
  }
7469
7950
  adviceLines.push("", snippet, "", "Then save and run /reload.");
@@ -7496,11 +7977,17 @@ export default function (pi: ExtensionAPI) {
7496
7977
  // Build preview snippet as copyable JSON (the surgical editor will
7497
7978
  // insert or repair these exact compat key/value pairs).
7498
7979
  const keysPreview = JSON.stringify(suggestion.compatKeys, null, 2);
7499
- const targetHasCompat = decision.placement === "provider" ? location.providerCompatBrace >= 0 : location.compatObjectBrace >= 0;
7980
+ const targetHasCompat = decision.placement === "provider"
7981
+ ? location.providerCompatBrace >= 0
7982
+ : decision.placement === "modelOverride"
7983
+ ? location.modelOverrideCompatBrace >= 0
7984
+ : location.compatObjectBrace >= 0;
7500
7985
  const placementDesc = targetHasCompat ? `existing "compat" object` : `new "compat" object`;
7501
7986
  const locationDesc = decision.placement === "provider"
7502
7987
  ? `providers["${suggestion.providerLabel}"] -> compat (provider level, ${placementDesc})`
7503
- : `providers["${suggestion.providerLabel}"] -> models -> "${suggestion.modelId}" -> compat (model level, ${placementDesc})`;
7988
+ : decision.placement === "modelOverride"
7989
+ ? `providers["${suggestion.providerLabel}"] -> modelOverrides -> "${suggestion.modelId}" -> compat (${placementDesc})`
7990
+ : `providers["${suggestion.providerLabel}"] -> models -> "${suggestion.modelId}" -> compat (model level, ${placementDesc})`;
7504
7991
 
7505
7992
  const ts = backupTimestamp();
7506
7993
  const backupPath = `${MODELS_JSON_PATH}.backup-cache-optimizer-${ts}`;
@@ -7668,26 +8155,38 @@ export default function (pi: ExtensionAPI) {
7668
8155
  }
7669
8156
 
7670
8157
  const location = locateModelInJsonc(originalText, suggestion.providerLabel, suggestion.modelId);
7671
- if (!location) {
8158
+ const menuDecision = location
8159
+ ? chooseFixPlacement(
8160
+ originalText,
8161
+ location,
8162
+ suggestion.compatKeys,
8163
+ suggestion.providerLabel,
8164
+ suggestion.forceModelLevel,
8165
+ )
8166
+ : undefined;
8167
+ const overridePlan = location
8168
+ ? undefined
8169
+ : composeModelOverrideInsertion(
8170
+ originalText,
8171
+ suggestion.providerLabel,
8172
+ suggestion.modelId,
8173
+ suggestion.compatKeys,
8174
+ );
8175
+ if (!location && !overridePlan) {
7672
8176
  cmdCtx.ui.notify(
7673
- `❌ Could not locate model "${suggestion.modelId}" in ${getModelsJsonDisplayPath()}.\n` +
7674
- `Manual edit required: open the file and add:\n` +
7675
- `${formatCompatKeysForInsertion(suggestion.compatKeys)}\n` +
8177
+ `❌ Could not safely locate a modelOverrides insertion point in ${getModelsJsonDisplayPath()}.\n` +
8178
+ `Manual edit required:\n${formatMissingEntryManualSnippet(suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys)}\n` +
7676
8179
  `Then run /reload.`,
7677
8180
  "warning",
7678
8181
  );
7679
8182
  return;
7680
8183
  }
7681
-
7682
- const menuDecision = chooseFixPlacement(
7683
- originalText,
7684
- location,
7685
- suggestion.compatKeys,
7686
- suggestion.providerLabel,
7687
- suggestion.forceModelLevel,
7688
- );
7689
- const modifiedText = composeFixInsertion(originalText, location, suggestion.compatKeys, menuDecision.placement);
7690
- const checkError = selfCheckFix(originalText, modifiedText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, menuDecision.placement);
8184
+ const modifiedText = location && menuDecision
8185
+ ? composeFixInsertion(originalText, location, suggestion.compatKeys, menuDecision.placement)
8186
+ : overridePlan!.modifiedText;
8187
+ const checkError = location && menuDecision
8188
+ ? selfCheckFix(originalText, modifiedText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, menuDecision.placement)
8189
+ : selfCheckMissingEntryInsertion(originalText, modifiedText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys);
7691
8190
  if (checkError !== null) {
7692
8191
  cmdCtx.ui.notify(`❌ Self-check failed: ${checkError}\nNo changes made.`, "error");
7693
8192
  return;
@@ -7697,17 +8196,19 @@ export default function (pi: ExtensionAPI) {
7697
8196
  const ts = backupTimestamp();
7698
8197
  const backupPath = `${MODELS_JSON_PATH}.backup-cache-optimizer-${ts}`;
7699
8198
 
7700
- const menuLocationDesc = menuDecision.placement === "provider"
8199
+ const menuLocationDesc = overridePlan?.placementLabel ?? (menuDecision?.placement === "provider"
7701
8200
  ? `providers["${suggestion.providerLabel}"] -> compat (provider level)`
7702
- : `providers["${suggestion.providerLabel}"] -> models -> "${suggestion.modelId}" -> compat (model level)`;
7703
- const menuScopeRiskLine = menuDecision.placement === "provider"
7704
- ? ` 1. This change applies to ALL ${location.allModelIds.length || 1} model(s) in the "${suggestion.providerLabel}" provider, across all sessions.`
8201
+ : menuDecision?.placement === "modelOverride"
8202
+ ? `providers["${suggestion.providerLabel}"] -> modelOverrides -> "${suggestion.modelId}" -> compat`
8203
+ : `providers["${suggestion.providerLabel}"] -> models -> "${suggestion.modelId}" -> compat (model level)`);
8204
+ const menuScopeRiskLine = menuDecision?.placement === "provider"
8205
+ ? ` 1. This change applies to ALL ${location?.allModelIds.length || 1} model(s) in the "${suggestion.providerLabel}" provider, across all sessions.`
7705
8206
  : ` 1. This change affects ALL sessions using the "${suggestion.providerLabel}" provider/channel (scoped to model "${suggestion.modelId}").`;
7706
8207
 
7707
8208
  const previewLines = [
7708
8209
  `📝 Preview of changes to ${getModelsJsonDisplayPath()}:`,
7709
8210
  `Location: ${menuLocationDesc}`,
7710
- `Placement: ${menuDecision.placement} level — ${menuDecision.reason}`,
8211
+ `Placement: ${menuDecision ? `${menuDecision.placement} level — ${menuDecision.reason}` : "modelOverrides — built-in/API-login-safe model override"}`,
7711
8212
  `Compat JSON to write:`,
7712
8213
  keysPreview,
7713
8214
  ``,
@@ -7733,7 +8234,9 @@ export default function (pi: ExtensionAPI) {
7733
8234
  await rename(tempPath, MODELS_JSON_PATH);
7734
8235
 
7735
8236
  const writtenText = await readFile(MODELS_JSON_PATH, "utf8");
7736
- const postCheck = selfCheckFix(originalText, writtenText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, menuDecision.placement);
8237
+ const postCheck = location && menuDecision
8238
+ ? selfCheckFix(originalText, writtenText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, menuDecision.placement)
8239
+ : selfCheckMissingEntryInsertion(originalText, writtenText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys);
7737
8240
  if (postCheck !== null) {
7738
8241
  await copyFile(backupPath, MODELS_JSON_PATH);
7739
8242
  cmdCtx.ui.notify(`❌ Post-write check failed: ${postCheck}\nBackup restored.`, "error");
@@ -7784,10 +8287,13 @@ export default function (pi: ExtensionAPI) {
7784
8287
  diagnosis.push(" doctor — Show current model/provider/api/baseUrl/compat and low-hit diagnosis");
7785
8288
  diagnosis.push(" stats — Show active model stats bucket and recent trend");
7786
8289
  diagnosis.push(" compat — Show compat suggestion with edit location");
8290
+ diagnosis.push(" config footer-mode session|total — Persist the footer stats mode");
7787
8291
  diagnosis.push(" fix — Auto-fix compat issues (writes models.json, requires UI)");
7788
8292
  diagnosis.push(" reset — Reset local provider/model stats for current model (does not affect upstream)");
7789
8293
  diagnosis.push("");
7790
8294
  diagnosis.push(formatOptimizerRuntimeMode());
8295
+ const resolvedFooterMode = resolveFooterStatsMode(persistedFooterStatsMode);
8296
+ diagnosis.push(`Footer stats mode: ${resolvedFooterMode.mode} (${resolvedFooterMode.source})`);
7791
8297
  diagnosis.push("");
7792
8298
  if (model) {
7793
8299
  const displayKey = modelKey(model);