@bd7pil/opencode-deep-memory 0.5.2 → 0.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.
- package/dist/index.js +136 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15166,6 +15166,53 @@ async function writeCheckpoint(args) {
|
|
|
15166
15166
|
|
|
15167
15167
|
// src/hooks/compacting.ts
|
|
15168
15168
|
import { readFile as readFile2 } from "fs/promises";
|
|
15169
|
+
|
|
15170
|
+
// src/extract/summarize.ts
|
|
15171
|
+
var HANDOFF_PREFIX = `Another OpenCode session started by the same user was working on this task. It was compacted mid-conversation to save context space. Review the summary below to understand what happened and continue from where it left off.`;
|
|
15172
|
+
var STRUCTURED_COMPACTION_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION.
|
|
15173
|
+
Create a structured handoff summary for an LLM that will resume the task.
|
|
15174
|
+
|
|
15175
|
+
SUMMARY SECTIONS (include all that apply):
|
|
15176
|
+
|
|
15177
|
+
## Task Overview
|
|
15178
|
+
One sentence: what the user is building, fixing, or investigating.
|
|
15179
|
+
|
|
15180
|
+
## Current Progress
|
|
15181
|
+
### Completed
|
|
15182
|
+
- Specific changes made (file paths, functions, tests)
|
|
15183
|
+
- Commands executed and their outcomes
|
|
15184
|
+
### In Progress
|
|
15185
|
+
- What was being worked on when compaction happened
|
|
15186
|
+
- Partial edits, unresolved questions
|
|
15187
|
+
|
|
15188
|
+
## Key Technical Decisions
|
|
15189
|
+
- Decision \u2192 Reasoning (e.g., "used Map over Array for O(1) lookup")
|
|
15190
|
+
- Architecture choices and tradeoffs discussed
|
|
15191
|
+
|
|
15192
|
+
## Constraints & Requirements
|
|
15193
|
+
- User preferences (coding style, libraries, patterns)
|
|
15194
|
+
- Explicit constraints (must not, always, never)
|
|
15195
|
+
- Environment details (OS, Node version, dependencies)
|
|
15196
|
+
|
|
15197
|
+
## Files Modified or Touched
|
|
15198
|
+
- path/to/file \u2014 what changed and why
|
|
15199
|
+
|
|
15200
|
+
## Errors Encountered & Fixes
|
|
15201
|
+
- Error message \u2192 Root cause \u2192 Fix applied
|
|
15202
|
+
- Unresolved errors that need attention
|
|
15203
|
+
|
|
15204
|
+
## Next Steps
|
|
15205
|
+
- Clear, actionable items to continue the work
|
|
15206
|
+
- Dependencies: what must be done first
|
|
15207
|
+
|
|
15208
|
+
## Critical Context
|
|
15209
|
+
- Specific values, API keys (do NOT include real secrets), configuration
|
|
15210
|
+
- User's exact phrasing when it matters
|
|
15211
|
+
|
|
15212
|
+
Be concise. Prefer structured lists over prose. Focus on what the next LLM NEEDS to know to continue seamlessly.
|
|
15213
|
+
`;
|
|
15214
|
+
|
|
15215
|
+
// src/hooks/compacting.ts
|
|
15169
15216
|
function createCompactingHandler(args) {
|
|
15170
15217
|
const { client, state, projectPath, logger, tracker } = args;
|
|
15171
15218
|
return async (input, output) => {
|
|
@@ -15211,6 +15258,10 @@ function createCompactingHandler(args) {
|
|
|
15211
15258
|
logger
|
|
15212
15259
|
});
|
|
15213
15260
|
state.setPendingEnrichment(sessionID);
|
|
15261
|
+
if (capture.messageCount >= 20) {
|
|
15262
|
+
output.prompt = STRUCTURED_COMPACTION_PROMPT;
|
|
15263
|
+
}
|
|
15264
|
+
output.context.push(HANDOFF_PREFIX);
|
|
15214
15265
|
output.context.push(
|
|
15215
15266
|
`Prior conversation archived to ${checkpointPath}`
|
|
15216
15267
|
);
|
|
@@ -15233,12 +15284,10 @@ function createCompactingHandler(args) {
|
|
|
15233
15284
|
}
|
|
15234
15285
|
|
|
15235
15286
|
// src/compress/pressure.ts
|
|
15236
|
-
var FALLBACK_MAX_CONTEXT =
|
|
15287
|
+
var FALLBACK_MAX_CONTEXT = 1e6;
|
|
15237
15288
|
var OPENCODE_COMPACTION_RATIO = 0.75;
|
|
15238
|
-
var
|
|
15239
|
-
|
|
15240
|
-
high: 0.4
|
|
15241
|
-
};
|
|
15289
|
+
var PRESSURE_MEDIUM_TOKENS = 5e4;
|
|
15290
|
+
var PRESSURE_HIGH_TOKENS = 15e4;
|
|
15242
15291
|
var calibratedMaxContext = 0;
|
|
15243
15292
|
function calibrateFromCompaction(lastInputTokens) {
|
|
15244
15293
|
if (lastInputTokens <= 0) return;
|
|
@@ -15252,67 +15301,33 @@ function maxContextFrom(modelContextWindow) {
|
|
|
15252
15301
|
if (calibratedMaxContext > 0) return calibratedMaxContext;
|
|
15253
15302
|
return FALLBACK_MAX_CONTEXT;
|
|
15254
15303
|
}
|
|
15255
|
-
function estimateTokens2(text) {
|
|
15256
|
-
let cjk = 0;
|
|
15257
|
-
let other = 0;
|
|
15258
|
-
for (const ch of text) {
|
|
15259
|
-
if (/[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef\u3040-\u309f\u30a0-\u30ff]/.test(ch)) {
|
|
15260
|
-
cjk++;
|
|
15261
|
-
} else {
|
|
15262
|
-
other++;
|
|
15263
|
-
}
|
|
15264
|
-
}
|
|
15265
|
-
return Math.ceil(cjk * 0.7 + other / 3.8);
|
|
15266
|
-
}
|
|
15267
|
-
function extractTokensFromMessages(messages) {
|
|
15268
|
-
let total = 0;
|
|
15269
|
-
for (const msg of messages) {
|
|
15270
|
-
for (const part of msg.parts) {
|
|
15271
|
-
if (typeof part !== "object" || part === null) continue;
|
|
15272
|
-
const p = part;
|
|
15273
|
-
if (p["type"] === "text" && typeof p["text"] === "string") {
|
|
15274
|
-
total += estimateTokens2(p["text"]);
|
|
15275
|
-
} else if (p["type"] === "tool") {
|
|
15276
|
-
const state = p["state"];
|
|
15277
|
-
if (state?.["output"] && typeof state["output"] === "string") {
|
|
15278
|
-
total += estimateTokens2(state["output"]);
|
|
15279
|
-
}
|
|
15280
|
-
if (state?.["error"] && typeof state["error"] === "string") {
|
|
15281
|
-
total += estimateTokens2(state["error"]);
|
|
15282
|
-
}
|
|
15283
|
-
} else if (p["type"] === "reasoning" || p["type"] === "thinking") {
|
|
15284
|
-
if (typeof p["text"] === "string") {
|
|
15285
|
-
total += estimateTokens2(p["text"]);
|
|
15286
|
-
}
|
|
15287
|
-
}
|
|
15288
|
-
}
|
|
15289
|
-
}
|
|
15290
|
-
return total;
|
|
15291
|
-
}
|
|
15292
15304
|
function extractInputTokensFromMessages(messages) {
|
|
15305
|
+
let best = 0;
|
|
15293
15306
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
15294
15307
|
const msg = messages[i];
|
|
15295
15308
|
for (const part of msg.parts) {
|
|
15296
15309
|
if (typeof part !== "object" || part === null) continue;
|
|
15297
15310
|
const p = part;
|
|
15298
15311
|
if (p["type"] === "step-finish") {
|
|
15299
|
-
const tokens = p
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
|
|
15312
|
+
const tokens = p;
|
|
15313
|
+
const input = tokens.tokens?.input ?? 0;
|
|
15314
|
+
const cached2 = tokens.tokens?.cached ?? 0;
|
|
15315
|
+
const total = input + cached2;
|
|
15316
|
+
if (total > best) best = total;
|
|
15317
|
+
if (best > 0) return best;
|
|
15303
15318
|
}
|
|
15304
15319
|
}
|
|
15305
15320
|
}
|
|
15306
|
-
return
|
|
15321
|
+
return best;
|
|
15307
15322
|
}
|
|
15308
15323
|
function detectPressure(messages, modelContextWindow) {
|
|
15309
15324
|
const ctx = maxContextFrom(modelContextWindow || 0);
|
|
15310
15325
|
const inputTokens = extractInputTokensFromMessages(messages);
|
|
15311
|
-
const estimated = inputTokens > 0 ? inputTokens :
|
|
15326
|
+
const estimated = inputTokens > 0 ? inputTokens : 0;
|
|
15312
15327
|
const ratio = Math.min(estimated / ctx, 1);
|
|
15313
15328
|
let level;
|
|
15314
|
-
if (
|
|
15315
|
-
else if (
|
|
15329
|
+
if (estimated >= PRESSURE_HIGH_TOKENS) level = "high";
|
|
15330
|
+
else if (estimated >= PRESSURE_MEDIUM_TOKENS) level = "medium";
|
|
15316
15331
|
else level = "low";
|
|
15317
15332
|
return { level, ratio, estimatedTokens: estimated, maxContext: ctx };
|
|
15318
15333
|
}
|
|
@@ -15576,6 +15591,8 @@ var PROTECTED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
15576
15591
|
var NEVER_DEDUP = /* @__PURE__ */ new Set(["read", "bash", "grep", "glob", "find", "search"]);
|
|
15577
15592
|
var ERROR_PURGE_TURN_THRESHOLD = 4;
|
|
15578
15593
|
var PROTECTED_HEAD_SINGLE = 2;
|
|
15594
|
+
var ASSISTANT_COMPRESS_MIN_LENGTH = 500;
|
|
15595
|
+
var ASSISTANT_COMPRESS_SAVINGS_RATIO = 0.6;
|
|
15579
15596
|
function simpleHash(s) {
|
|
15580
15597
|
const len = s.length;
|
|
15581
15598
|
const sampleSize = 500;
|
|
@@ -15589,12 +15606,32 @@ function simpleHash(s) {
|
|
|
15589
15606
|
}
|
|
15590
15607
|
return `${len}:${h.toString(36)}`;
|
|
15591
15608
|
}
|
|
15609
|
+
function compressAssistantText(text) {
|
|
15610
|
+
if (text.length < ASSISTANT_COMPRESS_MIN_LENGTH) return text;
|
|
15611
|
+
const lines = text.split("\n");
|
|
15612
|
+
const head = 3;
|
|
15613
|
+
const tail = 3;
|
|
15614
|
+
const kept = [];
|
|
15615
|
+
for (let i = 0; i < lines.length; i++) {
|
|
15616
|
+
const line = lines[i];
|
|
15617
|
+
if (i < head || i >= lines.length - tail) {
|
|
15618
|
+
kept.push(line);
|
|
15619
|
+
continue;
|
|
15620
|
+
}
|
|
15621
|
+
if (/^#{1,3}\s/.test(line) || /error|fail|warning|critical|important/i.test(line) || /^\s*[-*]\s/.test(line) || /^\s*\d+\.\s/.test(line) || line.trim().startsWith("```") || /^\/[^\s:]+/.test(line)) {
|
|
15622
|
+
kept.push(line);
|
|
15623
|
+
}
|
|
15624
|
+
}
|
|
15625
|
+
const result = kept.join("\n");
|
|
15626
|
+
return result.length < text.length * ASSISTANT_COMPRESS_SAVINGS_RATIO ? result : text;
|
|
15627
|
+
}
|
|
15592
15628
|
function singlePassCompress(messages, state, protectedTail) {
|
|
15593
15629
|
const stats = {
|
|
15594
15630
|
toolDedup: 0,
|
|
15595
15631
|
errorPurge: 0,
|
|
15596
15632
|
toolOutputCompressed: 0,
|
|
15597
15633
|
jsonCrushed: 0,
|
|
15634
|
+
assistantCompressed: 0,
|
|
15598
15635
|
ccrStored: 0
|
|
15599
15636
|
};
|
|
15600
15637
|
const totalMessages = messages.length;
|
|
@@ -15674,6 +15711,21 @@ function singlePassCompress(messages, state, protectedTail) {
|
|
|
15674
15711
|
}
|
|
15675
15712
|
}
|
|
15676
15713
|
}
|
|
15714
|
+
if (i < protectedTail && msg.info.role === "assistant") {
|
|
15715
|
+
for (let j = msg.parts.length - 1; j >= 0; j--) {
|
|
15716
|
+
const part = msg.parts[j];
|
|
15717
|
+
if (typeof part !== "object" || part === null) continue;
|
|
15718
|
+
const p = part;
|
|
15719
|
+
if (p["type"] !== "text") continue;
|
|
15720
|
+
const text = p["text"];
|
|
15721
|
+
if (typeof text !== "string") continue;
|
|
15722
|
+
const compressed = compressAssistantText(text);
|
|
15723
|
+
if (compressed !== text) {
|
|
15724
|
+
p["text"] = compressed;
|
|
15725
|
+
stats.assistantCompressed++;
|
|
15726
|
+
}
|
|
15727
|
+
}
|
|
15728
|
+
}
|
|
15677
15729
|
}
|
|
15678
15730
|
return stats;
|
|
15679
15731
|
}
|
|
@@ -15715,6 +15767,7 @@ function runCompressionPipeline(ctx) {
|
|
|
15715
15767
|
errorPurge: spStats.errorPurge,
|
|
15716
15768
|
toolOutputCompressed: spStats.toolOutputCompressed,
|
|
15717
15769
|
jsonCrushed: spStats.jsonCrushed,
|
|
15770
|
+
assistantCompressed: spStats.assistantCompressed,
|
|
15718
15771
|
ccrStored: spStats.ccrStored,
|
|
15719
15772
|
nudgeInjected: false,
|
|
15720
15773
|
pressureLevel: pressure.level,
|
|
@@ -15735,7 +15788,7 @@ function runCompressionPipeline(ctx) {
|
|
|
15735
15788
|
logger?.debug("compress: memory nudge", { type: memoryNudge.type });
|
|
15736
15789
|
}
|
|
15737
15790
|
}
|
|
15738
|
-
const active = stats.toolDedup > 0 || stats.errorPurge > 0 || stats.toolOutputCompressed > 0 || stats.jsonCrushed > 0 || stats.nudgeInjected;
|
|
15791
|
+
const active = stats.toolDedup > 0 || stats.errorPurge > 0 || stats.toolOutputCompressed > 0 || stats.jsonCrushed > 0 || stats.assistantCompressed > 0 || stats.nudgeInjected;
|
|
15739
15792
|
if (active) {
|
|
15740
15793
|
logger?.debug("compress: pipeline result", { ...stats });
|
|
15741
15794
|
} else {
|
|
@@ -15925,7 +15978,7 @@ function createMessagesTransformHandler(state, logger) {
|
|
|
15925
15978
|
logger
|
|
15926
15979
|
});
|
|
15927
15980
|
const ds = pipelineResult.stats;
|
|
15928
|
-
if (ds.toolDedup > 0 || ds.errorPurge > 0 || ds.toolOutputCompressed > 0 || ds.jsonCrushed > 0 || ds.nudgeInjected) {
|
|
15981
|
+
if (ds.toolDedup > 0 || ds.errorPurge > 0 || ds.toolOutputCompressed > 0 || ds.jsonCrushed > 0 || ds.assistantCompressed > 0 || ds.nudgeInjected) {
|
|
15929
15982
|
logger?.debug("messages.transform: deep compression", { ...ds });
|
|
15930
15983
|
state.mergeNotify({
|
|
15931
15984
|
compression: stats,
|
|
@@ -16065,6 +16118,29 @@ function createNotifyHandler(client, logger) {
|
|
|
16065
16118
|
};
|
|
16066
16119
|
}
|
|
16067
16120
|
|
|
16121
|
+
// src/shared/model-limits.ts
|
|
16122
|
+
var KNOWN_MODEL_LIMITS = {
|
|
16123
|
+
"deepseek-v4-pro": 1e6,
|
|
16124
|
+
"deepseek-v4": 1e6,
|
|
16125
|
+
"deepseek-v3": 64e3,
|
|
16126
|
+
"deepseek-r1": 64e3,
|
|
16127
|
+
"claude-opus-4": 2e5,
|
|
16128
|
+
"claude-sonnet-4": 2e5,
|
|
16129
|
+
"gpt-4o": 128e3,
|
|
16130
|
+
"o1": 2e5,
|
|
16131
|
+
"o3-mini": 2e5,
|
|
16132
|
+
"gemini-2.5-pro": 1e6,
|
|
16133
|
+
"gemini-2.5-flash": 1e6,
|
|
16134
|
+
"qwen-max": 131072
|
|
16135
|
+
};
|
|
16136
|
+
function lookupModelLimit(modelID) {
|
|
16137
|
+
if (KNOWN_MODEL_LIMITS[modelID]) return KNOWN_MODEL_LIMITS[modelID];
|
|
16138
|
+
for (const [key, limit] of Object.entries(KNOWN_MODEL_LIMITS)) {
|
|
16139
|
+
if (modelID.includes(key)) return limit;
|
|
16140
|
+
}
|
|
16141
|
+
return void 0;
|
|
16142
|
+
}
|
|
16143
|
+
|
|
16068
16144
|
// src/extract/enrich.ts
|
|
16069
16145
|
import { stat } from "fs/promises";
|
|
16070
16146
|
|
|
@@ -16396,11 +16472,14 @@ var deepMemoryPlugin = async (input) => {
|
|
|
16396
16472
|
const defaultModel = configResult.data?.model;
|
|
16397
16473
|
if (typeof defaultModel === "string" && defaultModel.includes("/")) {
|
|
16398
16474
|
const slashIdx = defaultModel.indexOf("/");
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16475
|
+
const providerID = defaultModel.slice(0, slashIdx);
|
|
16476
|
+
const modelID = defaultModel.slice(slashIdx + 1);
|
|
16477
|
+
state.recordFallbackModel({ providerID, modelID });
|
|
16478
|
+
const limit = lookupModelLimit(modelID);
|
|
16479
|
+
if (limit) {
|
|
16480
|
+
state.setModelContextWindow(limit);
|
|
16481
|
+
logger.debug("resolved model context window", { modelID, limit });
|
|
16482
|
+
}
|
|
16404
16483
|
}
|
|
16405
16484
|
}).catch((err) => {
|
|
16406
16485
|
logger.debug("config.get failed, dream/distill will omit model", {
|