@smeltjs/core 0.1.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/LICENSE +202 -0
- package/README.md +55 -0
- package/THIRD-PARTY.md +140 -0
- package/dist/apply.d.ts +145 -0
- package/dist/apply.d.ts.map +1 -0
- package/dist/apply.js +244 -0
- package/dist/apply.js.map +1 -0
- package/dist/cache/prefix.d.ts +164 -0
- package/dist/cache/prefix.d.ts.map +1 -0
- package/dist/cache/prefix.js +361 -0
- package/dist/cache/prefix.js.map +1 -0
- package/dist/cli/args.d.ts +71 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +308 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +161 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/config.d.ts +73 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +144 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/init.d.ts +56 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +449 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/report.d.ts +44 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +113 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/resolve.d.ts +103 -0
- package/dist/cli/resolve.d.ts.map +1 -0
- package/dist/cli/resolve.js +106 -0
- package/dist/cli/resolve.js.map +1 -0
- package/dist/cli/run.d.ts +99 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +351 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/detect.d.ts +16 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +18 -0
- package/dist/detect.js.map +1 -0
- package/dist/errors.d.ts +88 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +107 -0
- package/dist/errors.js.map +1 -0
- package/dist/hash.d.ts +13 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +16 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/lang/bash.d.ts +3 -0
- package/dist/lang/bash.d.ts.map +1 -0
- package/dist/lang/bash.js +28 -0
- package/dist/lang/bash.js.map +1 -0
- package/dist/lang/c.d.ts +3 -0
- package/dist/lang/c.d.ts.map +1 -0
- package/dist/lang/c.js +39 -0
- package/dist/lang/c.js.map +1 -0
- package/dist/lang/c_sharp.d.ts +3 -0
- package/dist/lang/c_sharp.d.ts.map +1 -0
- package/dist/lang/c_sharp.js +37 -0
- package/dist/lang/c_sharp.js.map +1 -0
- package/dist/lang/cpp.d.ts +3 -0
- package/dist/lang/cpp.d.ts.map +1 -0
- package/dist/lang/cpp.js +46 -0
- package/dist/lang/cpp.js.map +1 -0
- package/dist/lang/go.d.ts +3 -0
- package/dist/lang/go.d.ts.map +1 -0
- package/dist/lang/go.js +31 -0
- package/dist/lang/go.js.map +1 -0
- package/dist/lang/java.d.ts +3 -0
- package/dist/lang/java.d.ts.map +1 -0
- package/dist/lang/java.js +34 -0
- package/dist/lang/java.js.map +1 -0
- package/dist/lang/javascript.d.ts +3 -0
- package/dist/lang/javascript.d.ts.map +1 -0
- package/dist/lang/javascript.js +28 -0
- package/dist/lang/javascript.js.map +1 -0
- package/dist/lang/kotlin.d.ts +3 -0
- package/dist/lang/kotlin.d.ts.map +1 -0
- package/dist/lang/kotlin.js +37 -0
- package/dist/lang/kotlin.js.map +1 -0
- package/dist/lang/php.d.ts +3 -0
- package/dist/lang/php.d.ts.map +1 -0
- package/dist/lang/php.js +37 -0
- package/dist/lang/php.js.map +1 -0
- package/dist/lang/profile.d.ts +130 -0
- package/dist/lang/profile.d.ts.map +1 -0
- package/dist/lang/profile.js +2 -0
- package/dist/lang/profile.js.map +1 -0
- package/dist/lang/python.d.ts +3 -0
- package/dist/lang/python.d.ts.map +1 -0
- package/dist/lang/python.js +33 -0
- package/dist/lang/python.js.map +1 -0
- package/dist/lang/registry.d.ts +31 -0
- package/dist/lang/registry.d.ts.map +1 -0
- package/dist/lang/registry.js +89 -0
- package/dist/lang/registry.js.map +1 -0
- package/dist/lang/ruby.d.ts +3 -0
- package/dist/lang/ruby.d.ts.map +1 -0
- package/dist/lang/ruby.js +47 -0
- package/dist/lang/ruby.js.map +1 -0
- package/dist/lang/rust.d.ts +3 -0
- package/dist/lang/rust.d.ts.map +1 -0
- package/dist/lang/rust.js +45 -0
- package/dist/lang/rust.js.map +1 -0
- package/dist/lang/swift.d.ts +3 -0
- package/dist/lang/swift.d.ts.map +1 -0
- package/dist/lang/swift.js +34 -0
- package/dist/lang/swift.js.map +1 -0
- package/dist/lang/tsx.d.ts +4 -0
- package/dist/lang/tsx.d.ts.map +1 -0
- package/dist/lang/tsx.js +11 -0
- package/dist/lang/tsx.js.map +1 -0
- package/dist/lang/typescript.d.ts +18 -0
- package/dist/lang/typescript.d.ts.map +1 -0
- package/dist/lang/typescript.js +61 -0
- package/dist/lang/typescript.js.map +1 -0
- package/dist/net/policy.d.ts +49 -0
- package/dist/net/policy.d.ts.map +1 -0
- package/dist/net/policy.js +120 -0
- package/dist/net/policy.js.map +1 -0
- package/dist/plan/grammar.d.ts +38 -0
- package/dist/plan/grammar.d.ts.map +1 -0
- package/dist/plan/grammar.js +85 -0
- package/dist/plan/grammar.js.map +1 -0
- package/dist/plan/lexical.d.ts +51 -0
- package/dist/plan/lexical.d.ts.map +1 -0
- package/dist/plan/lexical.js +167 -0
- package/dist/plan/lexical.js.map +1 -0
- package/dist/plan/planners.d.ts +35 -0
- package/dist/plan/planners.d.ts.map +1 -0
- package/dist/plan/planners.js +26 -0
- package/dist/plan/planners.js.map +1 -0
- package/dist/plan/structural.d.ts +68 -0
- package/dist/plan/structural.d.ts.map +1 -0
- package/dist/plan/structural.js +458 -0
- package/dist/plan/structural.js.map +1 -0
- package/dist/repomap/cache.d.ts +53 -0
- package/dist/repomap/cache.d.ts.map +1 -0
- package/dist/repomap/cache.js +139 -0
- package/dist/repomap/cache.js.map +1 -0
- package/dist/repomap/map.d.ts +161 -0
- package/dist/repomap/map.d.ts.map +1 -0
- package/dist/repomap/map.js +302 -0
- package/dist/repomap/map.js.map +1 -0
- package/dist/repomap/rank.d.ts +52 -0
- package/dist/repomap/rank.d.ts.map +1 -0
- package/dist/repomap/rank.js +155 -0
- package/dist/repomap/rank.js.map +1 -0
- package/dist/repomap/tags.d.ts +47 -0
- package/dist/repomap/tags.d.ts.map +1 -0
- package/dist/repomap/tags.js +101 -0
- package/dist/repomap/tags.js.map +1 -0
- package/dist/retrieve.d.ts +14 -0
- package/dist/retrieve.d.ts.map +1 -0
- package/dist/retrieve.js +44 -0
- package/dist/retrieve.js.map +1 -0
- package/dist/stages.d.ts +22 -0
- package/dist/stages.d.ts.map +1 -0
- package/dist/stages.js +33 -0
- package/dist/stages.js.map +1 -0
- package/dist/stats.d.ts +40 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +37 -0
- package/dist/stats.js.map +1 -0
- package/dist/store-dir.d.ts +98 -0
- package/dist/store-dir.d.ts.map +1 -0
- package/dist/store-dir.js +379 -0
- package/dist/store-dir.js.map +1 -0
- package/dist/store.d.ts +36 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +56 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +298 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +68 -0
package/dist/apply.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { OverlappingElisionError, RangeOutOfBoundsError, UnknownHashError } from './errors.js';
|
|
2
|
+
import { HASH_LENGTH } from './hash.js';
|
|
3
|
+
import { LANGUAGE_PROFILES } from './lang/registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* The version of the marker format itself, carried **in band** in every marker.
|
|
6
|
+
*
|
|
7
|
+
* The marker is the one part of smelt a *model* sees, and it goes into prompts.
|
|
8
|
+
* Changing its shape changes model behaviour downstream and shows up as worse output
|
|
9
|
+
* with no error anywhere — this project's signature failure mode, shipped as a patch
|
|
10
|
+
* release. So the wire surface is frozen from 0.1 and treated as 1.0
|
|
11
|
+
* (`CONTRIBUTING.md` § "Two promises, not one"), and a future format is *additive and
|
|
12
|
+
* identifiable*: `smelt/v2` markers can coexist with `smelt/v1` ones, and a consumer
|
|
13
|
+
* parsing markers can tell which it is holding. A format that changed silently would
|
|
14
|
+
* be a substitution; this makes it a declaration.
|
|
15
|
+
*
|
|
16
|
+
* `test/guards/marker-format.test.ts` pins the rendered marker per version and fails if
|
|
17
|
+
* the format moves without the version moving.
|
|
18
|
+
*/
|
|
19
|
+
export const MARKER_FORMAT_VERSION = 'v1';
|
|
20
|
+
/**
|
|
21
|
+
* The default marker.
|
|
22
|
+
*
|
|
23
|
+
* Its shape is the user-facing form of Laws 2 and 3, in one line the model reads:
|
|
24
|
+
* *which format this is* (the version), *what was removed* (the explanation), *how
|
|
25
|
+
* much* (the byte count), and *how to get it back* (the hash). Anything that cannot
|
|
26
|
+
* fill in all of those is not allowed to be an elision.
|
|
27
|
+
*
|
|
28
|
+
* `<<…>>` rather than a Unicode bracket because it survives every tokenizer, terminal,
|
|
29
|
+
* and diff tool without becoming three tokens of nothing.
|
|
30
|
+
*/
|
|
31
|
+
export const defaultMarker = ({ explanation, bytes, hash }) => `<<smelt/${MARKER_FORMAT_VERSION}: ${explanation} (${String(bytes)}B) — retrieve("${hash}")>>`;
|
|
32
|
+
/**
|
|
33
|
+
* Line-comment leaders per language: the marker always lands as a comment in the
|
|
34
|
+
* survivor's own syntax, because a bare marker line breaks the syntax of what remains
|
|
35
|
+
* around it — in **every** grammar this was tested against, not just the indented ones.
|
|
36
|
+
*
|
|
37
|
+
* The failure classes, each verified by reparsing a bare-marker survivor:
|
|
38
|
+
*
|
|
39
|
+
* - **python** — significant indentation means a parse error does not stay local.
|
|
40
|
+
* The ERROR node swallows the *neighbouring definitions too* — the survivor stops
|
|
41
|
+
* being Python at all, not just at the marker line.
|
|
42
|
+
* - **ruby** and **bash** — the marker *begins with* `<<`, which both languages read
|
|
43
|
+
* as a heredoc operator. A bare marker line opens a heredoc whose terminator never
|
|
44
|
+
* arrives, and everything after it — every kept declaration — is swallowed into a
|
|
45
|
+
* string literal.
|
|
46
|
+
* - **php** — `<<` is an operator here too: the kept function after a bare marker is
|
|
47
|
+
* re-typed into an anonymous-function operand inside the marker's binary
|
|
48
|
+
* expression. The kept declaration is no longer a declaration in the survivor.
|
|
49
|
+
* - **kotlin**, **swift**, and the rest of the brace-delimited set (typescript, tsx,
|
|
50
|
+
* javascript, rust, go, java, c, cpp, c_sharp) — the folk claim that braces keep a
|
|
51
|
+
* parse error local is **empirically false**: reparsing each language's fixture
|
|
52
|
+
* survivor with its own bundled grammar shows ERROR nodes spanning the kept
|
|
53
|
+
* declarations (a C function's signature absorbed into an ERROR, fifteen cascading
|
|
54
|
+
* ERRORs in swift, and so on). The survivor-reparse guard in
|
|
55
|
+
* `test/guards/structural.test.ts` now asserts the opposite property for every
|
|
56
|
+
* structural language: the survivor reparses with no new issues.
|
|
57
|
+
*
|
|
58
|
+
* Only `'unknown'` keeps the bare marker — lexical text has no syntax to break.
|
|
59
|
+
*
|
|
60
|
+
* This does **not** move the frozen wire surface. The `<<smelt/v1: … >>` core is
|
|
61
|
+
* rendered by {@link defaultMarker}, byte-identical and still versioned in band; the
|
|
62
|
+
* leader is part of the substituted marker text, so `outputRange` covers it and
|
|
63
|
+
* reconstruction stays byte-exact. A comment leader in the survivor's own syntax is
|
|
64
|
+
* the one wrapping that cannot change what a model reads out of the marker.
|
|
65
|
+
*
|
|
66
|
+
* A derived view: each leader is the `markerLeader` fact on the language's
|
|
67
|
+
* {@link LanguageProfile} (`src/lang/`), collected here so marker construction keeps
|
|
68
|
+
* one lookup table.
|
|
69
|
+
*/
|
|
70
|
+
export const MARKER_LINE_COMMENT_LEADERS = Object.fromEntries(Object.values(LANGUAGE_PROFILES)
|
|
71
|
+
.filter((profile) => profile.markerLeader !== undefined)
|
|
72
|
+
.map((profile) => [profile.id, profile.markerLeader]));
|
|
73
|
+
/**
|
|
74
|
+
* The marker builder for a language: {@link defaultMarker}, wrapped in the language's
|
|
75
|
+
* line-comment leader when {@link MARKER_LINE_COMMENT_LEADERS} names one — so a Python
|
|
76
|
+
* survivor still parses as Python. Everything else gets `base` unchanged.
|
|
77
|
+
*/
|
|
78
|
+
export function markerForLanguage(language, base = defaultMarker) {
|
|
79
|
+
const leader = MARKER_LINE_COMMENT_LEADERS[language];
|
|
80
|
+
if (leader === undefined)
|
|
81
|
+
return base;
|
|
82
|
+
return (info) => `${leader}${base(info)}`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A stand-in hash of the real length, so a marker can be priced before the cut that
|
|
86
|
+
* would earn it exists. Marker cost depends on the hash's *length*, never its value.
|
|
87
|
+
*/
|
|
88
|
+
const PLACEHOLDER_HASH = '0'.repeat(HASH_LENGTH);
|
|
89
|
+
/**
|
|
90
|
+
* The one adapter behind the {@link MarkerPricing} seam.
|
|
91
|
+
*
|
|
92
|
+
* Marker cost is this module's fact: `applyPlan` renders the marker, so only this
|
|
93
|
+
* module can price it without guessing. The pricing is built from the **exact builder
|
|
94
|
+
* `applyPlan` will use** — the same resolution, in the same order: a caller-supplied
|
|
95
|
+
* builder (`SmelterConfig.marker` / `ApplyOptions.marker`) wins wholesale, otherwise
|
|
96
|
+
* the language's leader-wrapped default via {@link markerForLanguage}.
|
|
97
|
+
*
|
|
98
|
+
* The custom-builder leg is load-bearing, not a convenience: a caller who installs a
|
|
99
|
+
* longer `MarkerBuilder` changes what every elision costs, and a planner still pricing
|
|
100
|
+
* the *default* marker would keep planning elisions the real marker makes
|
|
101
|
+
* unprofitable — cuts that grow the output, silently. Pricing with the builder's own
|
|
102
|
+
* rendering closes that hole: `costBytes` measures the marker *that builder* would
|
|
103
|
+
* emit, byte for byte.
|
|
104
|
+
*
|
|
105
|
+
* `createSmelter` (and through it, the CLI) calls this centrally, once per smelt call;
|
|
106
|
+
* a caller driving `planLexical`/`planStructural` directly builds its own and puts it
|
|
107
|
+
* on the {@link PlanInput}.
|
|
108
|
+
*/
|
|
109
|
+
export function markerPricing(language = 'unknown', markerBuilder) {
|
|
110
|
+
// The same resolution applyPlan performs: a supplied builder wins wholesale.
|
|
111
|
+
const build = markerBuilder ?? markerForLanguage(language);
|
|
112
|
+
return {
|
|
113
|
+
costBytes: (reason, elidedBytes) => Buffer.byteLength(build({
|
|
114
|
+
hash: PLACEHOLDER_HASH,
|
|
115
|
+
bytes: elidedBytes,
|
|
116
|
+
rule: reason.rule,
|
|
117
|
+
explanation: reason.explanation,
|
|
118
|
+
}), 'utf8'),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Turn a plan into text.
|
|
123
|
+
*
|
|
124
|
+
* This is the only function in smelt that removes anything, and it contains no
|
|
125
|
+
* judgement at all: it validates the plan, stores every removed run, substitutes
|
|
126
|
+
* markers, and records where each marker landed. All the deciding happens in a
|
|
127
|
+
* {@link Planner}, which is why a plan can be reviewed before a byte moves.
|
|
128
|
+
*
|
|
129
|
+
* @throws {RangeOutOfBoundsError} if a range falls outside the input's UTF-8 bytes.
|
|
130
|
+
* @throws {OverlappingElisionError} if two ranges overlap — applying both would
|
|
131
|
+
* corrupt the output, and picking a winner would be a silent guess.
|
|
132
|
+
*/
|
|
133
|
+
export function applyPlan(text, plan, store, options = {}) {
|
|
134
|
+
const buildMarker = options.marker ?? markerForLanguage(plan.language);
|
|
135
|
+
const input = Buffer.from(text, 'utf8');
|
|
136
|
+
const ordered = plan.elisions.toSorted((a, b) => a.range.start - b.range.start);
|
|
137
|
+
for (const { range } of ordered)
|
|
138
|
+
assertInBounds(range, input.length);
|
|
139
|
+
for (let i = 1; i < ordered.length; i += 1) {
|
|
140
|
+
const previous = ordered[i - 1];
|
|
141
|
+
const current = ordered[i];
|
|
142
|
+
if (current.range.start < previous.range.end) {
|
|
143
|
+
throw new OverlappingElisionError(`smelt: plan from "${plan.planner}" elides overlapping ranges ` +
|
|
144
|
+
`[${previous.range.start},${previous.range.end}) and ` +
|
|
145
|
+
`[${current.range.start},${current.range.end}). A plan must be a partition.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const pieces = [];
|
|
149
|
+
const applied = [];
|
|
150
|
+
let cursor = 0;
|
|
151
|
+
let outputBytes = 0;
|
|
152
|
+
for (const { range, reason } of ordered) {
|
|
153
|
+
const kept = input.subarray(cursor, range.start);
|
|
154
|
+
pieces.push(kept);
|
|
155
|
+
outputBytes += kept.length;
|
|
156
|
+
const removed = input.subarray(range.start, range.end);
|
|
157
|
+
const removedText = removed.toString('utf8');
|
|
158
|
+
const hash = store.put(removedText);
|
|
159
|
+
const marker = buildMarker({
|
|
160
|
+
hash,
|
|
161
|
+
bytes: removed.length,
|
|
162
|
+
rule: reason.rule,
|
|
163
|
+
explanation: reason.explanation,
|
|
164
|
+
});
|
|
165
|
+
const markerBuffer = Buffer.from(marker, 'utf8');
|
|
166
|
+
pieces.push(markerBuffer);
|
|
167
|
+
applied.push({
|
|
168
|
+
hash,
|
|
169
|
+
range,
|
|
170
|
+
outputRange: { start: outputBytes, end: outputBytes + markerBuffer.length },
|
|
171
|
+
bytes: removed.length,
|
|
172
|
+
reason,
|
|
173
|
+
marker,
|
|
174
|
+
});
|
|
175
|
+
outputBytes += markerBuffer.length;
|
|
176
|
+
cursor = range.end;
|
|
177
|
+
}
|
|
178
|
+
const tail = input.subarray(cursor);
|
|
179
|
+
pieces.push(tail);
|
|
180
|
+
outputBytes += tail.length;
|
|
181
|
+
const output = Buffer.concat(pieces).toString('utf8');
|
|
182
|
+
const measure = options.measure;
|
|
183
|
+
return {
|
|
184
|
+
text: output,
|
|
185
|
+
inputBytes: input.length,
|
|
186
|
+
outputBytes,
|
|
187
|
+
planner: plan.planner,
|
|
188
|
+
language: plan.language,
|
|
189
|
+
elisions: applied,
|
|
190
|
+
...(measure === undefined
|
|
191
|
+
? {}
|
|
192
|
+
: {
|
|
193
|
+
measured: {
|
|
194
|
+
measure: measure.id,
|
|
195
|
+
unit: measure.unit,
|
|
196
|
+
input: measure.count(text),
|
|
197
|
+
output: measure.count(output),
|
|
198
|
+
},
|
|
199
|
+
}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Put it all back. `reconstruct(smelt(x), store) === x`, byte for byte — this is Law 3
|
|
204
|
+
* expressed as an executable equation, and `test/guards/reversibility.test.ts` asserts
|
|
205
|
+
* it on every input the suite knows about.
|
|
206
|
+
*
|
|
207
|
+
* Reads through `peek`, **not** `retrieve`: `retrieveCalls` and the expansion rate
|
|
208
|
+
* exist to count *the model asking for hidden material back* — the honest signal this
|
|
209
|
+
* whole project sells. A caller reassembling the original (to diff it, to verify a
|
|
210
|
+
* round trip, to write it to disk) is not that, and counting it would inflate the one
|
|
211
|
+
* number that must never flatter. The guard in
|
|
212
|
+
* `test/guards/expansion-counter.test.ts` pins this: reconstruction leaves every
|
|
213
|
+
* counter exactly where it was.
|
|
214
|
+
*
|
|
215
|
+
* @throws {UnknownHashError} if the store no longer holds an elision's bytes.
|
|
216
|
+
*/
|
|
217
|
+
export function reconstruct(result, store) {
|
|
218
|
+
const output = Buffer.from(result.text, 'utf8');
|
|
219
|
+
const ordered = result.elisions.toSorted((a, b) => a.outputRange.start - b.outputRange.start);
|
|
220
|
+
const pieces = [];
|
|
221
|
+
let cursor = 0;
|
|
222
|
+
for (const elision of ordered) {
|
|
223
|
+
assertInBounds(elision.outputRange, output.length);
|
|
224
|
+
const content = store.peek(elision.hash);
|
|
225
|
+
if (content === undefined)
|
|
226
|
+
throw new UnknownHashError(elision.hash);
|
|
227
|
+
pieces.push(output.subarray(cursor, elision.outputRange.start));
|
|
228
|
+
pieces.push(Buffer.from(content, 'utf8'));
|
|
229
|
+
cursor = elision.outputRange.end;
|
|
230
|
+
}
|
|
231
|
+
pieces.push(output.subarray(cursor));
|
|
232
|
+
return Buffer.concat(pieces).toString('utf8');
|
|
233
|
+
}
|
|
234
|
+
function assertInBounds(range, length) {
|
|
235
|
+
if (!Number.isInteger(range.start) ||
|
|
236
|
+
!Number.isInteger(range.end) ||
|
|
237
|
+
range.start < 0 ||
|
|
238
|
+
range.end > length ||
|
|
239
|
+
range.start >= range.end) {
|
|
240
|
+
throw new RangeOutOfBoundsError(`smelt: range [${range.start},${range.end}) is not a non-empty range inside ` +
|
|
241
|
+
`${String(length)} bytes.`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache-prefix hygiene — Slice 6. **Detect and warn, never rewrite.**
|
|
3
|
+
*
|
|
4
|
+
* **This module is a consumer-facing surface**, exported from the package
|
|
5
|
+
* entrypoint and stable in intent: hand it your prompt structure, get warnings
|
|
6
|
+
* back, decide for yourself. It composes with nothing else in smelt — no planner
|
|
7
|
+
* calls it, no store holds its output — because cache hygiene is a property of the
|
|
8
|
+
* *request* a consumer assembles, which smelt never sees or intercepts. Use it
|
|
9
|
+
* from your own send path:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Both functions come from the package entrypoint:
|
|
13
|
+
* // detectCacheBreakers, findPrefixDivergence
|
|
14
|
+
*
|
|
15
|
+
* // Before sending: name the silent cache-breakers in what you are about to send.
|
|
16
|
+
* const warnings = detectCacheBreakers(
|
|
17
|
+
* { tools, system }, // this call
|
|
18
|
+
* { tools: previousTools }, // optional: the previous call, for tool-set drift
|
|
19
|
+
* );
|
|
20
|
+
* // → [{ rule: 'system-timestamp', explanation: 'system prompt contains …' }, …]
|
|
21
|
+
*
|
|
22
|
+
* // Between calls: where exactly did the cached prefix stop matching, and what
|
|
23
|
+
* // did the change cost? `undefined` means intact (identical, or a pure append).
|
|
24
|
+
* const divergence = findPrefixDivergence(previousPrefix, nextPrefix);
|
|
25
|
+
* // → { byteOffset, invalidatedBytes, description } | undefined
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Nothing is ever rewritten on your behalf, and no rate is ever claimed — the
|
|
29
|
+
* paragraphs below say why both refusals are load-bearing.
|
|
30
|
+
*
|
|
31
|
+
* Provider prompt caches match the request prefix byte for byte, so a context
|
|
32
|
+
* optimizer that reorders or rewrites a prompt prefix to "help" a cache can cost
|
|
33
|
+
* more than it saves — and worse, a reordering that changes model behaviour is an
|
|
34
|
+
* unexplainable elision wearing a different hat. Headroom's CacheAligner made the
|
|
35
|
+
* same call: it detects prefix volatility and warns; it never edits the prompt.
|
|
36
|
+
* Neither does this module. Every function here is a pure read over its inputs:
|
|
37
|
+
* nothing is mutated, and nothing "fixed" is ever returned. That is not a comment,
|
|
38
|
+
* it is a guarantee — `test/guards/cache-hygiene.test.ts` asserts it on frozen
|
|
39
|
+
* inputs, and `pnpm mutate` proves the guard goes red when a helpful in-place fix
|
|
40
|
+
* appears.
|
|
41
|
+
*
|
|
42
|
+
* Warnings follow the `ElisionReason` two-field discipline from `types.ts`:
|
|
43
|
+
* a stable `rule` id for counters, and an `explanation` a human reads. A warning
|
|
44
|
+
* that cannot be written as a sentence is a rule nobody understands.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Facts about Anthropic's prompt cache, as published in Anthropic's prompt-caching
|
|
48
|
+
* documentation (docs.anthropic.com), verified 2026-09-01.
|
|
49
|
+
*
|
|
50
|
+
* These are *cited provider facts*, not smelt measurements — the only numbers Law 4
|
|
51
|
+
* permits are someone else's, with their source and date named. In particular this
|
|
52
|
+
* module claims nothing about how often anyone's cache hits: the price multipliers
|
|
53
|
+
* below are prices, and a price is not a frequency.
|
|
54
|
+
*/
|
|
55
|
+
export declare const ANTHROPIC_PROMPT_CACHE_FACTS: {
|
|
56
|
+
/** Where every number in this object comes from, and when it was checked. */
|
|
57
|
+
readonly source: 'Anthropic prompt-caching documentation (docs.anthropic.com), verified 2026-09-01';
|
|
58
|
+
/**
|
|
59
|
+
* The cached prefix is matched byte for byte over the request in this order.
|
|
60
|
+
* A byte change in `tools` therefore invalidates `system` and `messages` too.
|
|
61
|
+
*/
|
|
62
|
+
readonly prefixOrder: readonly ['tools', 'system', 'messages'];
|
|
63
|
+
/** Any byte change invalidates the cache from that byte to the end of the prefix. */
|
|
64
|
+
readonly invalidation: 'byte-exact; any change invalidates everything after it';
|
|
65
|
+
/** Minimum cacheable prefix, in tokens — approximately 1024 for most models. */
|
|
66
|
+
readonly minCacheablePrefixTokensApprox: 1024;
|
|
67
|
+
/** Maximum number of cache breakpoints per request. */
|
|
68
|
+
readonly maxCacheBreakpoints: 4;
|
|
69
|
+
/** Default time-to-live of a cache entry, in minutes. */
|
|
70
|
+
readonly defaultTtlMinutes: 5;
|
|
71
|
+
/** Optional extended time-to-live, in minutes (one hour). */
|
|
72
|
+
readonly extendedTtlMinutes: 60;
|
|
73
|
+
/** Writing a 5-minute cache entry costs 1.25x the base input-token price. */
|
|
74
|
+
readonly writeCostMultiplier5m: 1.25;
|
|
75
|
+
/** Writing a 1-hour cache entry costs 2x the base input-token price. */
|
|
76
|
+
readonly writeCostMultiplier1h: 2;
|
|
77
|
+
/** Reading a cached prefix costs approximately 0.1x the base input-token price. */
|
|
78
|
+
readonly readCostMultiplierApprox: 0.1;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* A cache-hygiene warning. Deliberately the same two-field shape as
|
|
82
|
+
* `ElisionReason` in `types.ts`, and for the same reason: a stable id for counters, a
|
|
83
|
+
* sentence for humans, and nothing else — no patch, no replacement text, no
|
|
84
|
+
* "fixed" prompt. Warning is all this module does.
|
|
85
|
+
*/
|
|
86
|
+
export interface CacheWarning {
|
|
87
|
+
/** Stable machine id, e.g. `'system-timestamp'`. */
|
|
88
|
+
readonly rule: string;
|
|
89
|
+
/** Present tense, no trailing period, names the evidence. */
|
|
90
|
+
readonly explanation: string;
|
|
91
|
+
}
|
|
92
|
+
/** The stable rule ids this module can emit. Additive over time, never renamed. */
|
|
93
|
+
export declare const CACHE_BREAKER_RULES: {
|
|
94
|
+
/** A timestamp-shaped value in the system prompt. */
|
|
95
|
+
readonly systemTimestamp: 'system-timestamp';
|
|
96
|
+
/** A UUID in the system prompt. */
|
|
97
|
+
readonly systemUuid: 'system-uuid';
|
|
98
|
+
/** An object in a tool definition whose keys are not in sorted order. */
|
|
99
|
+
readonly unsortedJsonKeys: 'unsorted-json-keys';
|
|
100
|
+
/** The tool set changed between two successive calls. */
|
|
101
|
+
readonly toolSetVaries: 'tool-set-varies';
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The first byte where two successive prompt prefixes diverge, with a description
|
|
105
|
+
* of what changed around it. Offsets are UTF-8 **byte** offsets, because that is
|
|
106
|
+
* the unit the provider's cache matches in — a code-unit index would be a lie for
|
|
107
|
+
* any prompt containing a multi-byte character.
|
|
108
|
+
*/
|
|
109
|
+
export interface PrefixDivergence {
|
|
110
|
+
/** UTF-8 byte offset of the first byte at which the two prefixes differ. */
|
|
111
|
+
readonly byteOffset: number;
|
|
112
|
+
/** Bytes of `previous` from the divergence to its end — the span a cache no longer matches. */
|
|
113
|
+
readonly invalidatedBytes: number;
|
|
114
|
+
/** What changed around the divergence. Excerpts never split a multi-byte character. */
|
|
115
|
+
readonly description: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Compare two successive prompt prefixes the way the provider's cache does: byte
|
|
119
|
+
* for byte, in UTF-8.
|
|
120
|
+
*
|
|
121
|
+
* Returns `undefined` when `next` starts with every byte of `previous` — identical
|
|
122
|
+
* prefixes, or a pure append, both of which leave a cached prefix intact. Anything
|
|
123
|
+
* else returns the first divergent byte offset and a description of what changed
|
|
124
|
+
* around it. This function reports; it does not repair.
|
|
125
|
+
*/
|
|
126
|
+
export declare function findPrefixDivergence(previous: string, next: string): PrefixDivergence | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* One tool definition as the caller sends it. Only `name` is required here —
|
|
129
|
+
* the rest of the definition is scanned structurally, whatever shape it has.
|
|
130
|
+
*/
|
|
131
|
+
export interface PromptTool {
|
|
132
|
+
readonly name: string;
|
|
133
|
+
readonly [key: string]: unknown;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The prompt structure smelt is shown for hygiene checks — the two parts that
|
|
137
|
+
* serialize ahead of the messages in the cached prefix. smelt never sees, holds,
|
|
138
|
+
* or edits the real request; the caller hands in a description and gets warnings
|
|
139
|
+
* back.
|
|
140
|
+
*/
|
|
141
|
+
export interface PromptStructure {
|
|
142
|
+
/** Tool definitions, in the order they are sent. */
|
|
143
|
+
readonly tools?: readonly PromptTool[];
|
|
144
|
+
/** The system prompt text. */
|
|
145
|
+
readonly system?: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Detect the named silent cache-breakers in a prompt structure.
|
|
149
|
+
*
|
|
150
|
+
* Three rules over `current` and, when `previous` is supplied, one across the pair:
|
|
151
|
+
*
|
|
152
|
+
* - `system-timestamp` — a timestamp-shaped value in the system prompt
|
|
153
|
+
* - `system-uuid` — a UUID in the system prompt
|
|
154
|
+
* - `unsorted-json-keys` — a tool-definition object whose keys are not sorted
|
|
155
|
+
* - `tool-set-varies` — the tools differ from the previous call's, by content:
|
|
156
|
+
* an added, removed or duplicated tool, a reorder, or a definition rewritten
|
|
157
|
+
* under an unchanged name
|
|
158
|
+
*
|
|
159
|
+
* Pure and read-only: inputs are never mutated, and the return value is warnings
|
|
160
|
+
* only — never a corrected prompt. Deciding what to do about a warning is the
|
|
161
|
+
* caller's call, in the caller's code.
|
|
162
|
+
*/
|
|
163
|
+
export declare function detectCacheBreakers(current: PromptStructure, previous?: PromptStructure): readonly CacheWarning[];
|
|
164
|
+
//# sourceMappingURL=prefix.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefix.d.ts","sourceRoot":"","sources":["../../src/cache/prefix.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B;IACvC,6EAA6E;aAC7E,MAAM,EAAE,kFAAkF;IAC1F;;;OAGG;aACH,WAAW,YAAG,OAAO,EAAE,QAAQ,EAAE,UAAU;IAC3C,qFAAqF;aACrF,YAAY,EAAE,wDAAwD;IACtE,gFAAgF;aAChF,8BAA8B,EAAE,IAAI;IACpC,uDAAuD;aACvD,mBAAmB,EAAE,CAAC;IACtB,yDAAyD;aACzD,iBAAiB,EAAE,CAAC;IACpB,6DAA6D;aAC7D,kBAAkB,EAAE,EAAE;IACtB,6EAA6E;aAC7E,qBAAqB,EAAE,IAAI;IAC3B,wEAAwE;aACxE,qBAAqB,EAAE,CAAC;IACxB,mFAAmF;aACnF,wBAAwB,EAAE,GAAG;CACrB,CAAC;AAEX;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAQD,mFAAmF;AACnF,eAAO,MAAM,mBAAmB;IAC9B,qDAAqD;aACrD,eAAe,EAAE,kBAAkB;IACnC,mCAAmC;aACnC,UAAU,EAAE,aAAa;IACzB,yEAAyE;aACzE,gBAAgB,EAAE,oBAAoB;IACtC,yDAAyD;aACzD,aAAa,EAAE,iBAAiB;CACxB,CAAC;AAEX;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+FAA+F;IAC/F,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,uFAAuF;IACvF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAsBD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAuBjG;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AA4ND;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EACxB,QAAQ,CAAC,EAAE,eAAe,GACzB,SAAS,YAAY,EAAE,CASzB"}
|