dsh-context-pruner 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +784 -2
- package/cordis.patch.yml +19 -0
- package/lib/backend.js +190 -0
- package/lib/client.js +1230 -0
- package/lib/compact.js +407 -0
- package/lib/index.js +795 -0
- package/lib/inspect.js +390 -0
- package/lib/rounds.js +160 -0
- package/package.json +59 -4
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# dsh-context-pruner bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# Mounts the plugin into a dsh profile's plugin tree. This build registers the
|
|
4
|
+
# read-only `/prune` command; it does NOT replace `ctx.compaction`, so it
|
|
5
|
+
# composes safely alongside the shipped `compaction-basic` row.
|
|
6
|
+
#
|
|
7
|
+
# Install:
|
|
8
|
+
# dsh plugin --profile web add G:\Projects\dsh-context-pruner
|
|
9
|
+
#
|
|
10
|
+
# The plugin command pnpm-installs the package into the profile's node_modules
|
|
11
|
+
# and reconciles it into the profile's `dsh.profile.bundles` list, after which
|
|
12
|
+
# this patch's insert row is applied. Nothing outside the profile directory is
|
|
13
|
+
# modified, and removing the row (or the bundle entry) fully reverts it.
|
|
14
|
+
- insert:
|
|
15
|
+
- id: context-pruner
|
|
16
|
+
# Node-resolvable package name — must stay in sync with package.json
|
|
17
|
+
# `name`. Quoted because it is a plain scalar that may be read as a
|
|
18
|
+
# reserved token.
|
|
19
|
+
name: 'dsh-context-pruner'
|
package/lib/backend.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-context-pruner corrective compaction backend.
|
|
3
|
+
*
|
|
4
|
+
* The whole point of this plugin lives in one method: `summarize()`. DSH's
|
|
5
|
+
* `BasicCompactionEngine` documents it as the *sole* subclass customization
|
|
6
|
+
* hook — pressure policy, retention, range selection, shrink validation and
|
|
7
|
+
* token accounting all stay on the parent, so every pricing decision keeps
|
|
8
|
+
* using the singleton token meter.
|
|
9
|
+
*
|
|
10
|
+
* We do NOT reimplement the region transaction and we do NOT select a range.
|
|
11
|
+
* We only change *what the summary says*: instead of a neutral "here is what
|
|
12
|
+
* happened" checkpoint, we emit a **correction checkpoint** that explicitly
|
|
13
|
+
* retires the failed exploration and states the conclusion that actually
|
|
14
|
+
* holds.
|
|
15
|
+
*
|
|
16
|
+
* ## Why this reimplements the call instead of swapping the instruction
|
|
17
|
+
*
|
|
18
|
+
* `dsh-compaction-basic` builds its summarization directive from a
|
|
19
|
+
* module-scoped `COMPACTION_INSTRUCTION` constant that it does not export, and
|
|
20
|
+
* `summarizeWithLlm()` is a plain module function. There is no seam to
|
|
21
|
+
* substitute the instruction, so the honest options are (a) monkey-patch
|
|
22
|
+
* module scope or (b) reimplement the call here. We reimplement it, mirroring
|
|
23
|
+
* the shipped shape exactly so the provider's warm prefix cache is still
|
|
24
|
+
* reused: the conversation's own system prompt, tools, and leading messages go
|
|
25
|
+
* first, and our instruction is the single novel trailing user message.
|
|
26
|
+
*
|
|
27
|
+
* @module dsh-context-pruner/backend
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { BasicCompactionEngine } from "@deepseek-ai/dsh-compaction-basic";
|
|
31
|
+
import { BlockAssembler, LlmError, contentHasImage } from "@deepseek-ai/dsh-llm";
|
|
32
|
+
import { deepFreeze } from "@deepseek-ai/dsh-util-values";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Default instruction appended as the final user message of the auxiliary
|
|
36
|
+
* summarization call.
|
|
37
|
+
*
|
|
38
|
+
* It deliberately contradicts the stock compaction instruction. The stock one
|
|
39
|
+
* says "Do NOT mention this summarization request or that the context was
|
|
40
|
+
* compacted"; ours must say the opposite, because the entire value of the
|
|
41
|
+
* feature is that the model *stops* treating the retired exploration as live
|
|
42
|
+
* reasoning.
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_CORRECTIVE_INSTRUCTION = [
|
|
45
|
+
"You are now acting as a CORRECTION CHECKPOINT writer for this AI coding assistant.",
|
|
46
|
+
"",
|
|
47
|
+
"The conversation ABOVE is being replaced by your checkpoint. It contains an exploration that reached a WRONG conclusion.",
|
|
48
|
+
"Your checkpoint must let another model resume the work WITHOUT re-trusting that wrong conclusion.",
|
|
49
|
+
"",
|
|
50
|
+
"Output EXACTLY the Markdown structure below: keep every section, in order. Use terse bullets, not prose paragraphs. Write \"(none)\" for an empty section — never drop a section.",
|
|
51
|
+
"",
|
|
52
|
+
"## Original Request and Intent",
|
|
53
|
+
"- [the user's original and evolving goals; quote verbatim where the exact wording matters]",
|
|
54
|
+
"",
|
|
55
|
+
"## Retired Exploration — DO NOT TRUST",
|
|
56
|
+
"- [one or two bullets naming what was attempted above and why it was wrong or abandoned]",
|
|
57
|
+
"",
|
|
58
|
+
"## Authoritative Conclusion",
|
|
59
|
+
"- [what is actually true now, or the correct next step]",
|
|
60
|
+
"",
|
|
61
|
+
"## Carried-Forward Facts",
|
|
62
|
+
"- [only facts from above that are STILL TRUE and still needed: file paths, identifiers, hard constraints, user preferences, explicit user instructions]",
|
|
63
|
+
"",
|
|
64
|
+
"## Next Step",
|
|
65
|
+
"- [the single next action in line with the most recent request, or \"(none)\"]",
|
|
66
|
+
"",
|
|
67
|
+
"Rules:",
|
|
68
|
+
"- Name the wrong conclusion ONLY as a rejected direction. Never restate it as a finding, and never re-derive it.",
|
|
69
|
+
"- Do NOT reproduce long transcripts, code dumps, or command output from the replaced span.",
|
|
70
|
+
"- Preserve exact file paths, commands, error strings, identifiers, numeric values, function signatures, and syntax fragments verbatim.",
|
|
71
|
+
"- Capture user feedback and explicit instructions faithfully, especially corrections.",
|
|
72
|
+
"- Be brief: this checkpoint must be much shorter than the span it replaces.",
|
|
73
|
+
"- Output only the checkpoint text: do not call any tool and do not take any other action.",
|
|
74
|
+
"- If the conversation already contains a <compacted-summary> block, it is a PRIOR checkpoint. Do not copy it forward verbatim: preserve still-true facts, drop stale ones, and merge everything into ONE consolidated checkpoint under the same structure."
|
|
75
|
+
].join("\n");
|
|
76
|
+
|
|
77
|
+
/** Reject visual output and keep only text, mirroring the shipped projection. */
|
|
78
|
+
function summaryText(blocks) {
|
|
79
|
+
if (contentHasImage(blocks)) {
|
|
80
|
+
throw new LlmError("compaction summary cannot contain image output", "UNSUPPORTED_CONTENT");
|
|
81
|
+
}
|
|
82
|
+
return blocks.filter((block) => block.type === "text");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Map a terminal finish reason to the failure the shipped backend raises,
|
|
87
|
+
* mirroring `finishError` in `dsh-compaction-basic`.
|
|
88
|
+
*/
|
|
89
|
+
function finishError(finish) {
|
|
90
|
+
switch (finish.kind) {
|
|
91
|
+
case "error":
|
|
92
|
+
case "aborted":
|
|
93
|
+
return new LlmError(finish.failure.message, finish.failure.code, finish.failure);
|
|
94
|
+
case "max-tokens": {
|
|
95
|
+
const error = new Error("summarization truncated at the token cap (incomplete checkpoint)");
|
|
96
|
+
error.code = "MAX_TOKENS";
|
|
97
|
+
return error;
|
|
98
|
+
}
|
|
99
|
+
default:
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Compaction backend that reuses the shipped engine's transaction, policy and
|
|
106
|
+
* metering, changing only the summarization directive.
|
|
107
|
+
*
|
|
108
|
+
* Inherited behavior is deliberate:
|
|
109
|
+
* - `compactNow()` — the idle-safe manual path behind `/compact` and `/prune`.
|
|
110
|
+
* - `compactRegion()` — needs an open turn; never used by this plugin.
|
|
111
|
+
* - automatic pressure / overflow recovery, token metering, shrink validation.
|
|
112
|
+
*/
|
|
113
|
+
export class CorrectiveCompactionEngine extends BasicCompactionEngine {
|
|
114
|
+
/**
|
|
115
|
+
* Override the sole summarization hook to emit a correction checkpoint.
|
|
116
|
+
*
|
|
117
|
+
* Mirrors the shipped implementation's call shape so the provider's warm
|
|
118
|
+
* prefix cache is still reused, but substitutes the corrective directive.
|
|
119
|
+
*
|
|
120
|
+
* @param input - replayed conversation prefix (system, tools, leading messages) to condense.
|
|
121
|
+
* @param agent - supplies routed-model history and fallback model.
|
|
122
|
+
* @param signal - optional cancellation forwarded to the adapter.
|
|
123
|
+
* @returns safe text summary blocks plus the exact call envelope.
|
|
124
|
+
*/
|
|
125
|
+
async summarize(input, agent, signal) {
|
|
126
|
+
const config = this.config;
|
|
127
|
+
const latest = agent.session.requestHeader()?.config;
|
|
128
|
+
const configured =
|
|
129
|
+
config.summarizationProvider.length === 0
|
|
130
|
+
? undefined
|
|
131
|
+
: { provider: config.summarizationProvider, model: config.summarizationModel };
|
|
132
|
+
const agentTarget =
|
|
133
|
+
agent.options.provider !== undefined &&
|
|
134
|
+
agent.options.provider.length > 0 &&
|
|
135
|
+
agent.options.model !== undefined &&
|
|
136
|
+
agent.options.model.length > 0
|
|
137
|
+
? { provider: agent.options.provider, model: agent.options.model }
|
|
138
|
+
: undefined;
|
|
139
|
+
const target = configured ?? latest ?? agentTarget;
|
|
140
|
+
if (target === undefined) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
"no provider/model available for summarization: set both summarization fields, route one request, or set both AgentOptions fields"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const instruction =
|
|
147
|
+
typeof this.correctiveInstruction === "string" && this.correctiveInstruction.length > 0
|
|
148
|
+
? this.correctiveInstruction
|
|
149
|
+
: DEFAULT_CORRECTIVE_INSTRUCTION;
|
|
150
|
+
|
|
151
|
+
const assembler = new BlockAssembler();
|
|
152
|
+
const messages = [
|
|
153
|
+
...input.messages,
|
|
154
|
+
deepFreeze({ role: "user", content: [{ type: "text", text: instruction }] })
|
|
155
|
+
];
|
|
156
|
+
const options = {
|
|
157
|
+
provider: target.provider,
|
|
158
|
+
model: target.model,
|
|
159
|
+
messages,
|
|
160
|
+
toolHistory: agent.session.toolHistory(),
|
|
161
|
+
...(input.tools === undefined ? {} : { tools: [...input.tools] }),
|
|
162
|
+
maxTokens: config.maxTokens,
|
|
163
|
+
sessionId: agent.session.id,
|
|
164
|
+
purpose: "compaction",
|
|
165
|
+
...(signal === undefined ? {} : { signal })
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
for await (const chunk of this.ctx.llm.stream(options)) assembler.push(chunk);
|
|
169
|
+
const error = finishError(assembler.finish);
|
|
170
|
+
if (error !== undefined) throw error;
|
|
171
|
+
|
|
172
|
+
const rawOutput = assembler.blocks();
|
|
173
|
+
const summary = summaryText(rawOutput);
|
|
174
|
+
if (!summary.some((block) => block.text.trim().length > 0)) {
|
|
175
|
+
throw new Error("summarization produced no text summary content");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
summary,
|
|
180
|
+
rawOutput,
|
|
181
|
+
llmStreamCall: true,
|
|
182
|
+
provider: options.provider,
|
|
183
|
+
model: options.model,
|
|
184
|
+
maxTokens: config.maxTokens,
|
|
185
|
+
...(assembler.usage === undefined ? {} : { usage: assembler.usage })
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default CorrectiveCompactionEngine;
|