@bd7pil/opencode-deep-memory 0.4.3 → 0.5.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.
package/dist/index.js CHANGED
@@ -261,6 +261,7 @@ var PluginState = class {
261
261
  _ccrCache = /* @__PURE__ */ new Map();
262
262
  _lastInputTokens = 0;
263
263
  _lastNudgeMessageCount = 0;
264
+ _modelContextWindow = 0;
264
265
  agentOf(sessionID) {
265
266
  return this._agents.get(sessionID);
266
267
  }
@@ -415,6 +416,12 @@ var PluginState = class {
415
416
  messagesSinceLastNudge(currentMessageCount) {
416
417
  return currentMessageCount - this._lastNudgeMessageCount;
417
418
  }
419
+ setModelContextWindow(tokens) {
420
+ if (tokens > 0) this._modelContextWindow = tokens;
421
+ }
422
+ getModelContextWindow() {
423
+ return this._modelContextWindow;
424
+ }
418
425
  };
419
426
  function createPluginState() {
420
427
  return new PluginState();
@@ -15219,19 +15226,25 @@ function createCompactingHandler(args) {
15219
15226
  }
15220
15227
 
15221
15228
  // src/compress/pressure.ts
15222
- var DEFAULT_MAX_CONTEXT = 128e3;
15229
+ var FALLBACK_MAX_CONTEXT = 128e3;
15230
+ var OPENCODE_COMPACTION_RATIO = 0.75;
15223
15231
  var THRESHOLDS = {
15224
15232
  medium: 0.3,
15225
15233
  high: 0.5
15226
15234
  };
15227
- var MODEL_CONTEXT_LIMITS = {
15228
- "deepseek-chat": 64e3,
15229
- "deepseek-reasoner": 64e3,
15230
- "mimo-v2.5-pro": 128e3,
15231
- "mimo-v2.5": 128e3,
15232
- "claude-sonnet-4-20250514": 2e5,
15233
- "gpt-4o": 128e3
15234
- };
15235
+ var calibratedMaxContext = 0;
15236
+ function calibrateFromCompaction(lastInputTokens) {
15237
+ if (lastInputTokens <= 0) return;
15238
+ calibratedMaxContext = Math.round(lastInputTokens / OPENCODE_COMPACTION_RATIO);
15239
+ }
15240
+ function getCalibratedMaxContext() {
15241
+ return calibratedMaxContext;
15242
+ }
15243
+ function maxContextFrom(modelContextWindow) {
15244
+ if (modelContextWindow > 0) return modelContextWindow;
15245
+ if (calibratedMaxContext > 0) return calibratedMaxContext;
15246
+ return FALLBACK_MAX_CONTEXT;
15247
+ }
15235
15248
  function estimateTokens2(text) {
15236
15249
  let cjk = 0;
15237
15250
  let other = 0;
@@ -15285,16 +15298,16 @@ function extractInputTokensFromMessages(messages) {
15285
15298
  }
15286
15299
  return 0;
15287
15300
  }
15288
- function detectPressure(messages, modelId) {
15289
- const maxContext = (modelId ? MODEL_CONTEXT_LIMITS[modelId] : void 0) ?? DEFAULT_MAX_CONTEXT;
15301
+ function detectPressure(messages, modelContextWindow) {
15302
+ const ctx = maxContextFrom(modelContextWindow || 0);
15290
15303
  const inputTokens = extractInputTokensFromMessages(messages);
15291
15304
  const estimated = inputTokens > 0 ? inputTokens : extractTokensFromMessages(messages);
15292
- const ratio = Math.min(estimated / maxContext, 1);
15305
+ const ratio = Math.min(estimated / ctx, 1);
15293
15306
  let level;
15294
15307
  if (ratio >= THRESHOLDS.high) level = "high";
15295
15308
  else if (ratio >= THRESHOLDS.medium) level = "medium";
15296
15309
  else level = "low";
15297
- return { level, ratio, estimatedTokens: estimated };
15310
+ return { level, ratio, estimatedTokens: estimated, maxContext: ctx };
15298
15311
  }
15299
15312
 
15300
15313
  // src/compress/dedup.ts
@@ -15402,7 +15415,7 @@ function purgeOldErrors(messages) {
15402
15415
  const input = toolState["input"];
15403
15416
  for (const key of Object.keys(input)) {
15404
15417
  if (key === "command" || key === "query" || key === "path" || key === "filePath") continue;
15405
- input[key] = "[purged]";
15418
+ delete input[key];
15406
15419
  }
15407
15420
  }
15408
15421
  purged++;
@@ -15558,55 +15571,6 @@ function sha2562(data) {
15558
15571
  return createHash3("sha256").update(data).digest("hex");
15559
15572
  }
15560
15573
 
15561
- // src/compress/message-prune.ts
15562
- var PRUNE_THRESHOLD = 8;
15563
- function pruneOldMessages(messages) {
15564
- let pruned = 0;
15565
- const protectedTail = messages.length - PRUNE_THRESHOLD;
15566
- for (let i = 3; i < protectedTail; i++) {
15567
- const msg = messages[i];
15568
- if (msg.info.role !== "assistant") continue;
15569
- for (const part of msg.parts) {
15570
- if (typeof part !== "object" || part === null) continue;
15571
- const p = part;
15572
- if (p["type"] !== "text" || typeof p["text"] !== "string") continue;
15573
- const text = p["text"];
15574
- if (text.length < 500) continue;
15575
- if (text === "[cleared]" || text === "[stripped]") continue;
15576
- if (text.includes("[compressed from")) continue;
15577
- const keyInfo = extractKeyInfo(text);
15578
- if (keyInfo.length < text.length * 0.6) {
15579
- p["text"] = keyInfo + "\n[compressed from " + text.length + " chars]";
15580
- pruned++;
15581
- }
15582
- }
15583
- }
15584
- return pruned;
15585
- }
15586
- function extractKeyInfo(text) {
15587
- const lines = text.split("\n");
15588
- const keyLines = [];
15589
- let inCodeBlock = false;
15590
- for (const line of lines) {
15591
- if (line.trim().startsWith("```")) {
15592
- inCodeBlock = !inCodeBlock;
15593
- if (inCodeBlock) keyLines.push(line);
15594
- continue;
15595
- }
15596
- if (inCodeBlock) {
15597
- if (keyLines.length < 30 && line.trim()) keyLines.push(line);
15598
- continue;
15599
- }
15600
- if (/^#{1,3}\s/.test(line) || /error|fail|warning|important|critical|decision|constraint/i.test(line) || /^\s*[-*]\s/.test(line) || /^\s*\d+\.\s/.test(line)) {
15601
- keyLines.push(line);
15602
- }
15603
- }
15604
- if (keyLines.length < 3) {
15605
- return lines.slice(0, 5).join("\n");
15606
- }
15607
- return keyLines.join("\n");
15608
- }
15609
-
15610
15574
  // src/compress/nudge.ts
15611
15575
  var NUDGE_COOLDOWN = 5;
15612
15576
  function shouldInjectNudge(level, messagesSinceLastNudge) {
@@ -15700,14 +15664,13 @@ function detectContentType(content) {
15700
15664
  // src/compress/index.ts
15701
15665
  function runCompressionPipeline(ctx) {
15702
15666
  const { messages, state, logger } = ctx;
15703
- const pressure = detectPressure(messages, ctx.modelId);
15667
+ const pressure = detectPressure(messages, state.getModelContextWindow());
15704
15668
  state.recordInputTokens(pressure.estimatedTokens);
15705
15669
  const stats = {
15706
15670
  toolDedup: 0,
15707
15671
  errorPurge: 0,
15708
15672
  toolOutputCompressed: 0,
15709
15673
  jsonCrushed: 0,
15710
- messagePruned: 0,
15711
15674
  ccrStored: 0,
15712
15675
  nudgeInjected: false,
15713
15676
  pressureLevel: pressure.level,
@@ -15717,9 +15680,6 @@ function runCompressionPipeline(ctx) {
15717
15680
  stats.errorPurge = purgeOldErrors(messages);
15718
15681
  stats.jsonCrushed = crushJsonToolOutputs(messages, state);
15719
15682
  stats.toolOutputCompressed = compressOldToolOutputs(messages, state);
15720
- if (pressure.level === "medium" || pressure.level === "high") {
15721
- stats.messagePruned = pruneOldMessages(messages);
15722
- }
15723
15683
  const messagesSinceNudge = state.messagesSinceLastNudge(messages.length);
15724
15684
  if (shouldInjectNudge(pressure.level, messagesSinceNudge)) {
15725
15685
  if (injectIntoLastAssistant(messages, buildNudgeText(pressure.level))) {
@@ -15734,7 +15694,7 @@ function runCompressionPipeline(ctx) {
15734
15694
  logger?.debug("compress: memory nudge", { type: memoryNudge.type });
15735
15695
  }
15736
15696
  }
15737
- const active = stats.toolDedup > 0 || stats.errorPurge > 0 || stats.toolOutputCompressed > 0 || stats.jsonCrushed > 0 || stats.messagePruned > 0 || stats.nudgeInjected;
15697
+ const active = stats.toolDedup > 0 || stats.errorPurge > 0 || stats.toolOutputCompressed > 0 || stats.jsonCrushed > 0 || stats.nudgeInjected;
15738
15698
  if (active) {
15739
15699
  logger?.debug("compress: pipeline result", { ...stats });
15740
15700
  } else {
@@ -15906,11 +15866,12 @@ function createMessagesTransformHandler(state, logger) {
15906
15866
  tool_errors_truncated: 0,
15907
15867
  thinking_stripped: 0
15908
15868
  };
15869
+ const toRemove = [];
15909
15870
  for (let i = PROTECTED_HEAD2; i < protectedTailStart; i++) {
15910
15871
  const msg = messages[i];
15911
15872
  if (!msg?.parts?.length) continue;
15912
15873
  if (msg.info.role === "user") continue;
15913
- for (let j = 0; j < msg.parts.length; j++) {
15874
+ for (let j = msg.parts.length - 1; j >= 0; j--) {
15914
15875
  const part = msg.parts[j];
15915
15876
  if (typeof part !== "object" || part === null) continue;
15916
15877
  const p = part;
@@ -15926,10 +15887,9 @@ function createMessagesTransformHandler(state, logger) {
15926
15887
  stats.metadata_stripped++;
15927
15888
  }
15928
15889
  }
15929
- if (typeof p["text"] === "string" && p["text"] !== "[cleared]") {
15930
- p["text"] = "[cleared]";
15931
- stats.reasoning_cleared++;
15932
- }
15890
+ msg.parts.splice(j, 1);
15891
+ stats.reasoning_cleared++;
15892
+ continue;
15933
15893
  }
15934
15894
  if (partType === "tool") {
15935
15895
  const meta = p["metadata"];
@@ -15957,11 +15917,13 @@ function createMessagesTransformHandler(state, logger) {
15957
15917
  }
15958
15918
  }
15959
15919
  if (isSystemInjected(msg)) {
15960
- msg.parts.length = 0;
15961
- msg.parts.push({ type: "text", text: "[stripped]" });
15920
+ toRemove.push(i);
15962
15921
  stats.system_neutralized++;
15963
15922
  }
15964
15923
  }
15924
+ for (let r = toRemove.length - 1; r >= 0; r--) {
15925
+ messages.splice(toRemove[r], 1);
15926
+ }
15965
15927
  repairOrphanedToolCalls(messages);
15966
15928
  if (Object.values(stats).some((v) => v > 0)) {
15967
15929
  logger?.debug("messages.transform: stripped", stats);
@@ -15972,7 +15934,7 @@ function createMessagesTransformHandler(state, logger) {
15972
15934
  logger
15973
15935
  });
15974
15936
  const ds = pipelineResult.stats;
15975
- if (ds.toolDedup > 0 || ds.errorPurge > 0 || ds.toolOutputCompressed > 0 || ds.jsonCrushed > 0 || ds.messagePruned > 0 || ds.nudgeInjected) {
15937
+ if (ds.toolDedup > 0 || ds.errorPurge > 0 || ds.toolOutputCompressed > 0 || ds.jsonCrushed > 0 || ds.nudgeInjected) {
15976
15938
  logger?.debug("messages.transform: deep compression", { ...ds });
15977
15939
  state.mergeNotify({
15978
15940
  compression: stats,
@@ -16557,6 +16519,15 @@ var deepMemoryPlugin = async (input) => {
16557
16519
  if (event.type === "session.compacted") {
16558
16520
  const compactedSessionID = event.properties.sessionID;
16559
16521
  logger.info("event session.compacted", { sessionID: compactedSessionID });
16522
+ const lastTokens = state.lastInputTokens();
16523
+ if (lastTokens > 0) {
16524
+ calibrateFromCompaction(lastTokens);
16525
+ logger.info("pressure calibrated", {
16526
+ trigger: "compaction",
16527
+ lastInputTokens: lastTokens,
16528
+ derivedMaxContext: getCalibratedMaxContext()
16529
+ });
16530
+ }
16560
16531
  try {
16561
16532
  const auditLogDir = projectMemoryDir(projectPath);
16562
16533
  await mkdir4(auditLogDir, { recursive: true });