pi-cache-optimizer 2.6.9 → 2.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.ts +50 -15
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -4833,6 +4833,19 @@ function composeMissingEntryInsertion(
4833
4833
  modelId: string,
4834
4834
  compatKeys: Record<string, unknown>,
4835
4835
  ): { modifiedText: string; placementLabel: string } {
4836
+ // Comments preserve length when stripped (`stripJsoncComments` replaces
4837
+ // comment bytes 1-for-1 with spaces), so offsets derived from the
4838
+ // comment-stripped text map cleanly back to the original. However,
4839
+ // `lastIndexOf("{", pos)` / `lastIndexOf("[", pos)` must NOT be run
4840
+ // against the raw original: a comment like `// add [more] here with a {
4841
+ // brace` would surface `[` / `{` bytes that have no structural meaning,
4842
+ // contaminating the `hasExisting`/`hasExistingElements` decision below
4843
+ // and producing a stray leading comma. Run the structural searches
4844
+ // against the comment-stripped version; keep the indentation lookups
4845
+ // (which only care about newlines + leading whitespace) on the
4846
+ // original since comments never contain forward-scan-relevant bytes.
4847
+ const cleanText = stripJsoncComments(originalText);
4848
+
4836
4849
  // Resolve a sensible indentation step from an arbitrary byte offset in
4837
4850
  // the original file.
4838
4851
  const indentUnitAt = (offset: number): string => {
@@ -4867,9 +4880,11 @@ function composeMissingEntryInsertion(
4867
4880
  const inner1 = inner0 + unit; // indent of compat keys inside the model
4868
4881
  const inner2 = inner1 + unit; // indent of compat values
4869
4882
 
4870
- // Determine whether the array is empty (need to skip the leading newline).
4871
- const arrayInterior = originalText.slice(
4872
- originalText.lastIndexOf("[", diagnosis.modelsEnd) + 1,
4883
+ // Determine whether the array is empty (need to skip the leading comma).
4884
+ // Search for the models `[` on the comment-stripped text so a `[` inside
4885
+ // a comment cannot be mistaken for the array opener.
4886
+ const arrayInterior = cleanText.slice(
4887
+ cleanText.lastIndexOf("[", diagnosis.modelsEnd) + 1,
4873
4888
  diagnosis.modelsEnd,
4874
4889
  ).trim();
4875
4890
  const hasExistingElements = arrayInterior.length > 0;
@@ -4903,8 +4918,10 @@ function composeMissingEntryInsertion(
4903
4918
  const inner3 = inner2 + unit;
4904
4919
 
4905
4920
  const compatBlock = formatCompactCompat(inner3);
4906
- const providersInterior = originalText.slice(
4907
- originalText.lastIndexOf("{", diagnosis.providersEnd) + 1,
4921
+ // Search for the providers `{` on the comment-stripped text so a `{`
4922
+ // inside a comment cannot be mistaken for the providers object opener.
4923
+ const providersInterior = cleanText.slice(
4924
+ cleanText.lastIndexOf("{", diagnosis.providersEnd) + 1,
4908
4925
  diagnosis.providersEnd,
4909
4926
  ).trim();
4910
4927
  const hasExisting = providersInterior.length > 0;
@@ -5279,6 +5296,7 @@ function selfCheckFix(
5279
5296
  providerLabel: string,
5280
5297
  modelId: string,
5281
5298
  compatKeys: Record<string, unknown>,
5299
+ placement: "provider" | "model" = "model",
5282
5300
  ): string | null {
5283
5301
  try {
5284
5302
  // Step 1: Parse both versions as JSONC (comments + trailing commas allowed).
@@ -5371,7 +5389,18 @@ function selfCheckFix(
5371
5389
  } else {
5372
5390
  const origCompat = origObj[key] as Record<string, unknown>;
5373
5391
  const modCompat = modObj[key] as Record<string, unknown>;
5374
- const mayRepairThisCompat = origObj === origProvider || origObj === origTargetModelRecord;
5392
+ // Only the compat object at the level ACTUALLY edited may have
5393
+ // its values repaired by this fix. The un-edited level must
5394
+ // remain byte/structure-equivalent, so its same-name keys stay
5395
+ // under full validation. Using a disjunction OR (provider ||
5396
+ // target) here would silently skip validation at the un-edited
5397
+ // level, masking corruption (e.g. a buggy editor accidentally
5398
+ // breaking provider.compat.sendSessionAffinityHeaders while
5399
+ // the fix was a model-level repair). Track placement — only
5400
+ // the placement-resolved object's own compat may be exempt.
5401
+ const mayRepairThisCompat =
5402
+ (placement === "provider" && origObj === origProvider) ||
5403
+ (placement === "model" && origObj === origTargetModelRecord);
5375
5404
  for (const ck of Object.keys(origCompat)) {
5376
5405
  if (!(ck in modCompat)) return false;
5377
5406
  // The fix may repair an existing wrong compat value (for example
@@ -5393,12 +5422,15 @@ function selfCheckFix(
5393
5422
  return "Modified file: original structure was altered (data loss detected)";
5394
5423
  }
5395
5424
 
5396
- // Step 8: Basic format sanity checks
5397
- if (modified.length < original.length) {
5398
- return "Modified file: content is shorter than original (possible truncation)";
5399
- }
5425
+ // Note: we intentionally do NOT enforce `modified.length >= original.length`.
5426
+ // The surgical editor may replace an existing compat value with a shorter one
5427
+ // (e.g. `false` -> `true`), which legitimately shrinks the file by a byte.
5428
+ // Real data loss / truncation is already caught by Step 7's isSubset
5429
+ // (every original key still present) and Step 8's root-bracket integrity
5430
+ // check below — a surviving length heuristic would false-positive on every
5431
+ // such value repair. (Tracked: the mofas glm-5.2 self-check failure path.)
5400
5432
 
5401
- // Step 9: Validate root bracket integrity with the same string/comment-aware
5433
+ // Step 8: Validate root bracket integrity with the same string/comment-aware
5402
5434
  // scanner used for edits. Do not count raw braces: comments or strings may
5403
5435
  // legitimately contain unmatched `{` / `}` bytes.
5404
5436
  const modifiedClean = stripJsoncComments(modified);
@@ -5673,6 +5705,9 @@ export const __internals_for_tests = {
5673
5705
  locateModelInJsonc,
5674
5706
  composeFixInsertion,
5675
5707
  selfCheckFix,
5708
+ analyzeModelsJsonForMissingEntry,
5709
+ composeMissingEntryInsertion,
5710
+ selfCheckMissingEntryInsertion,
5676
5711
  decideFixPlacement,
5677
5712
  chooseFixPlacement,
5678
5713
  findExistingCompatKeysInJsonc,
@@ -6692,7 +6727,7 @@ export default function (pi: ExtensionAPI) {
6692
6727
  const modifiedText = composeFixInsertion(originalText, location, suggestion.compatKeys, decision.placement);
6693
6728
 
6694
6729
  // Self-check
6695
- const checkError = selfCheckFix(originalText, modifiedText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys);
6730
+ const checkError = selfCheckFix(originalText, modifiedText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, decision.placement);
6696
6731
  if (checkError !== null) {
6697
6732
  cmdCtx.ui.notify(
6698
6733
  `❌ Self-check failed before write: ${checkError}\n` +
@@ -6761,7 +6796,7 @@ export default function (pi: ExtensionAPI) {
6761
6796
 
6762
6797
  // Post-write self-check (read back)
6763
6798
  const writtenText = await readFile(MODELS_JSON_PATH, "utf8");
6764
- const postCheckError = selfCheckFix(originalText, writtenText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys);
6799
+ const postCheckError = selfCheckFix(originalText, writtenText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, decision.placement);
6765
6800
  if (postCheckError !== null) {
6766
6801
  // Restore from backup
6767
6802
  await copyFile(backupPath, MODELS_JSON_PATH);
@@ -6890,7 +6925,7 @@ export default function (pi: ExtensionAPI) {
6890
6925
 
6891
6926
  const menuDecision = chooseFixPlacement(originalText, location, suggestion.compatKeys, suggestion.providerLabel);
6892
6927
  const modifiedText = composeFixInsertion(originalText, location, suggestion.compatKeys, menuDecision.placement);
6893
- const checkError = selfCheckFix(originalText, modifiedText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys);
6928
+ const checkError = selfCheckFix(originalText, modifiedText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, menuDecision.placement);
6894
6929
  if (checkError !== null) {
6895
6930
  cmdCtx.ui.notify(`❌ Self-check failed: ${checkError}\nNo changes made.`, "error");
6896
6931
  return;
@@ -6936,7 +6971,7 @@ export default function (pi: ExtensionAPI) {
6936
6971
  await rename(tempPath, MODELS_JSON_PATH);
6937
6972
 
6938
6973
  const writtenText = await readFile(MODELS_JSON_PATH, "utf8");
6939
- const postCheck = selfCheckFix(originalText, writtenText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys);
6974
+ const postCheck = selfCheckFix(originalText, writtenText, suggestion.providerLabel, suggestion.modelId, suggestion.compatKeys, menuDecision.placement);
6940
6975
  if (postCheck !== null) {
6941
6976
  await copyFile(backupPath, MODELS_JSON_PATH);
6942
6977
  cmdCtx.ui.notify(`❌ Post-write check failed: ${postCheck}\nBackup restored.`, "error");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cache-optimizer",
3
- "version": "2.6.9",
3
+ "version": "2.6.10",
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",