greprag 5.48.0 → 5.49.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.
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ // adr: adr/opencode-context-compressor.md
3
+ /** opencode-plugin-crush.ts — the inline outbound-context CRUSHER for opencode.
4
+ *
5
+ * WHY THIS EXISTS — Claude Code's harness can only REWRITE a bash command to
6
+ * pipe its output through `greprag crush` (the pipe-wrap PreToolUse hook); it
7
+ * cannot replace context already sitting in the outbound message array. opencode
8
+ * CAN: `experimental.chat.messages.transform` fires before every LLM call with
9
+ * the full outbound `output.messages` in hand. This module is the engine the
10
+ * plugin's transform hook runs — it walks the message parts and crushes the
11
+ * bulky ones IN-PROCESS (no per-part subprocess), reversibly, reusing greprag's
12
+ * existing crush engine.
13
+ *
14
+ * HARD CONSTRAINTS this design serves:
15
+ * - DEP-FREE. The deployed plugin loads its sidecars by absolute path from
16
+ * ~/.greprag/ under Bun, where neither relative `require('./proc')` nor
17
+ * npm deps like `sql.js` resolve. So this file imports ONLY the pure crush
18
+ * engine (`./crush`, zero deps, deployed as ~/.greprag/crush/) — nothing
19
+ * else. The CCR store (sql.js) and the spawn wrapper (proc) are NOT imported
20
+ * here; CCR persistence is INJECTED via the `stash` callback so the sidecar
21
+ * stays loadable inside the plugin.
22
+ * - IN-PROCESS transform. The crush compression (CPU-bound, runs for every
23
+ * crushable part of every message on every LLM call) never spawns a process.
24
+ * Only the rare PERSISTENCE of an original is delegated (the plugin's stash
25
+ * fires `greprag ccr put` once per part actually crushed — off the hot path).
26
+ *
27
+ * INVARIANTS (mirror adr/crush-engine.md, enforced per-field here):
28
+ * - Reversible: a crushed field becomes `<crushed>\n<<ccr:HASH>>`; the stash
29
+ * callback persists the byte-exact original so `greprag retrieve <hash>`
30
+ * recovers it. HASH is sha256(original)[:16] — the SAME value CcrStore.put
31
+ * computes, so the plugin can compute it in-process and the CLI store agrees.
32
+ * - token-validate-or-revert (Headroom): a field is replaced ONLY when the
33
+ * crushed + marker text costs strictly fewer estimated tokens; else the
34
+ * original is kept untouched.
35
+ * - Idempotent: a field already carrying a `<<ccr:` marker is never re-crushed.
36
+ * - Non-destructive: mutates ONLY the message objects it is handed (the
37
+ * request-scoped `output.messages` copy). It never touches opencode's
38
+ * persisted session history — that contract is the caller's to honor by
39
+ * passing the request-scoped array.
40
+ * - Defensive: every part is processed under try/catch — a crush failure is
41
+ * swallowed and the part is left verbatim, so a turn is NEVER broken.
42
+ */
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.DEFAULT_THRESHOLD_BYTES = void 0;
45
+ exports.ccrMarker = ccrMarker;
46
+ exports.classifyContent = classifyContent;
47
+ exports.crushField = crushField;
48
+ exports.crushMessages = crushMessages;
49
+ const crush_1 = require("./crush");
50
+ // ---------- CCR marker (inlined — see header: ccr-store.ts drags sql.js) ------
51
+ /** First 16 hex of sha256 — must match ccr-store.ts CCR_MARKER_HASH_LEN. */
52
+ const CCR_MARKER_HASH_LEN = 16;
53
+ /** `<<ccr:HASH>>` — must match ccr-store.ts ccrMarker(). */
54
+ function ccrMarker(hash) {
55
+ return `<<ccr:${hash.slice(0, CCR_MARKER_HASH_LEN)}>>`;
56
+ }
57
+ /** Already-crushed detector — idempotency guard. */
58
+ const CCR_MARKER_RE = /<<ccr:/;
59
+ /** Fixed length of `\n` + `<<ccr:` + 16-hex + `>>` appended to a crushed field. */
60
+ const FINAL_MARKER_OVERHEAD = 1 + '<<ccr:'.length + CCR_MARKER_HASH_LEN + '>>'.length; // 25
61
+ /** Default byte floor — fields smaller than this pass through untouched. */
62
+ exports.DEFAULT_THRESHOLD_BYTES = 2048;
63
+ // ---------- Content classifier (pure) ----------------------------------------
64
+ /** Pick the crush engine for a raw content string. Output-shape heuristics,
65
+ * since (unlike pipe-wrap) we see the OUTPUT, not the producing command:
66
+ * - leading `{` / `[` → json
67
+ * - majority `file:line:` → search (grep/rg match lines, incl. `-`-context)
68
+ * - else → log (the safe generalist: keeps errors + traces,
69
+ * dedupes spam; a misclassified field just
70
+ * compresses less, never wrongly). */
71
+ function classifyContent(text) {
72
+ const t = text.trimStart();
73
+ const head = t[0];
74
+ if (head === '{' || head === '[')
75
+ return 'json';
76
+ const lines = t.split('\n', 21).map(l => l.trim()).filter(Boolean).slice(0, 20);
77
+ if (lines.length) {
78
+ const matchLike = lines.filter(l => /^.+?[:\-]\d+[:\-]/.test(l)).length;
79
+ if (matchLike / lines.length >= 0.6)
80
+ return 'search';
81
+ }
82
+ return 'log';
83
+ }
84
+ function runEngine(type, content) {
85
+ switch (type) {
86
+ case 'search': return (0, crush_1.crushSearch)(content, '');
87
+ case 'json': return (0, crush_1.crushJson)(content);
88
+ default: return (0, crush_1.crushLog)(content);
89
+ }
90
+ }
91
+ // ---------- Single-field crush (pure except injected stash) -------------------
92
+ /** Crush one content string. Returns the replacement (`<crushed>\n<<ccr:HASH>>`)
93
+ * or null to leave the original UNTOUCHED. Honors byte threshold, idempotency,
94
+ * passthrough, and token-validate-or-revert. The stash side effect fires ONLY
95
+ * on a real crush (after validation passes), so a reverted/skipped field is
96
+ * never persisted. */
97
+ function crushField(content, stash, thresholdBytes = exports.DEFAULT_THRESHOLD_BYTES) {
98
+ if (typeof content !== 'string')
99
+ return null;
100
+ if (CCR_MARKER_RE.test(content))
101
+ return null; // idempotent
102
+ if (Buffer.byteLength(content, 'utf8') < thresholdBytes)
103
+ return null; // below floor
104
+ const type = classifyContent(content);
105
+ const result = runEngine(type, content);
106
+ if (result.stats.passthrough)
107
+ return null; // engine declined
108
+ const crushed = result.output;
109
+ // token-validate-or-revert: the FINAL field (crushed + fixed-size marker)
110
+ // must cost strictly fewer estimated tokens than the original. Estimate from
111
+ // length (same ceil(chars/4) basis as estimateTokens) so we never run the
112
+ // stash side effect for a non-win.
113
+ const finalTokens = Math.ceil((crushed.length + FINAL_MARKER_OVERHEAD) / 4);
114
+ if (finalTokens >= (0, crush_1.estimateTokens)(content))
115
+ return null;
116
+ const hash = stash(content, type);
117
+ return `${crushed}\n${ccrMarker(hash)}`;
118
+ }
119
+ // ---------- Message-array walker ----------------------------------------------
120
+ /** Apply crushField to one object's string field, accounting stats and
121
+ * mutating in place on a real crush. */
122
+ function crushAndApply(obj, key, stash, thresholdBytes, stats) {
123
+ const content = obj[key];
124
+ if (typeof content !== 'string')
125
+ return;
126
+ const bytes = Buffer.byteLength(content, 'utf8');
127
+ if (bytes < thresholdBytes) {
128
+ stats.partsSkipped++;
129
+ return;
130
+ }
131
+ if (CCR_MARKER_RE.test(content)) {
132
+ stats.partsSkipped++;
133
+ return;
134
+ }
135
+ stats.bytesBefore += bytes;
136
+ const replacement = crushField(content, stash, thresholdBytes);
137
+ if (replacement === null) {
138
+ stats.partsReverted++;
139
+ stats.bytesAfter += bytes;
140
+ return;
141
+ }
142
+ obj[key] = replacement;
143
+ stats.partsCrushed++;
144
+ stats.bytesAfter += Buffer.byteLength(replacement, 'utf8');
145
+ }
146
+ /** Crush the bulky fields of one opencode message part:
147
+ * - text part → `.text`
148
+ * - tool part → `.state.output` (only when state.status === 'completed';
149
+ * an 'error' state's `.error` is precious — left alone). */
150
+ function processPart(part, stash, thresholdBytes, stats) {
151
+ if (!part || typeof part !== 'object')
152
+ return;
153
+ const p = part;
154
+ if (p.type === 'text' && typeof p.text === 'string') {
155
+ crushAndApply(p, 'text', stash, thresholdBytes, stats);
156
+ return;
157
+ }
158
+ if (p.type === 'tool' && p.state && typeof p.state === 'object') {
159
+ const state = p.state;
160
+ if (state.status === 'completed' && typeof state.output === 'string') {
161
+ crushAndApply(state, 'output', stash, thresholdBytes, stats);
162
+ }
163
+ }
164
+ }
165
+ /** Walk an opencode outbound message array (`output.messages`) and crush its
166
+ * bulky parts in place. Each message is `{ info, parts[] }`. Every part is
167
+ * processed under try/catch — a thrown crush (or a thrown stash) is swallowed,
168
+ * the part is left verbatim, and the walk continues: a crush failure NEVER
169
+ * breaks the turn. Non-array / malformed input is handled defensively. */
170
+ function crushMessages(messages, opts) {
171
+ const stats = {
172
+ partsCrushed: 0, partsSkipped: 0, partsReverted: 0, bytesBefore: 0, bytesAfter: 0,
173
+ };
174
+ const thresholdBytes = opts.thresholdBytes ?? exports.DEFAULT_THRESHOLD_BYTES;
175
+ if (!Array.isArray(messages))
176
+ return stats;
177
+ for (const msg of messages) {
178
+ const parts = msg && typeof msg === 'object' ? msg.parts : null;
179
+ if (!Array.isArray(parts))
180
+ continue;
181
+ for (const part of parts) {
182
+ try {
183
+ processPart(part, opts.stash, thresholdBytes, stats);
184
+ }
185
+ catch {
186
+ // per-part swallow — a crush/stash failure must never break the turn
187
+ stats.partsSkipped++;
188
+ }
189
+ }
190
+ }
191
+ return stats;
192
+ }
193
+ //# sourceMappingURL=opencode-plugin-crush.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-plugin-crush.js","sourceRoot":"","sources":["../src/opencode-plugin-crush.ts"],"names":[],"mappings":"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"}