bantamkit-mcp 0.29.2 → 0.31.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/README.md +83 -2
- package/assets/pricing/default.json +7 -0
- package/assets/schemas/shiftwork-checkpoint.json +12 -1
- package/assets/tools/memory_dream.json +20 -0
- package/assets/tools/repo_map.json +44 -0
- package/assets/tools/shiftwork_clock_out.json +1 -1
- package/assets/tools/token_ledger.json +40 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +135 -4
- package/dist/eventlog.js +6 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -1
- package/dist/mcp/identity.d.ts +80 -0
- package/dist/mcp/identity.js +466 -4
- package/dist/mcp/pyargs.d.ts +1 -1
- package/dist/mcp/pyargs.js +78 -0
- package/dist/mcp/server.d.ts +37 -6
- package/dist/mcp/server.js +209 -5
- package/dist/mcp/status.d.ts +54 -10
- package/dist/mcp/status.js +86 -10
- package/dist/memory/cli.js +1 -1
- package/dist/memory/component.d.ts +73 -2
- package/dist/memory/component.js +157 -5
- package/dist/memory/dream.d.ts +247 -0
- package/dist/memory/dream.js +903 -0
- package/dist/memory/store.d.ts +101 -1
- package/dist/memory/store.js +178 -4
- package/dist/pricing.d.ts +100 -0
- package/dist/pricing.js +357 -0
- package/dist/repomap.d.ts +247 -0
- package/dist/repomap.js +1040 -0
- package/dist/selfupdate.d.ts +320 -0
- package/dist/selfupdate.js +594 -0
- package/dist/shiftwork.d.ts +5 -3
- package/dist/shiftwork.js +82 -4
- package/dist/tokenledger.d.ts +91 -0
- package/dist/tokenledger.js +372 -0
- package/package.json +1 -1
package/dist/memory/store.d.ts
CHANGED
|
@@ -15,8 +15,43 @@ export declare const VALID_TYPES: readonly ["feedback", "project", "reference",
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const DURABLE_TYPES: readonly ["feedback", "user"];
|
|
17
17
|
export declare const DUPLICATE_JACCARD = 0.5;
|
|
18
|
+
export declare const RECALL_MIN_SCORE_RATIO = 0;
|
|
18
19
|
/** See the reference's comment: measured against a real store, not chosen. */
|
|
19
20
|
export declare const DEFAULT_INDEX_BUDGET = 24000;
|
|
21
|
+
/**
|
|
22
|
+
* Percent of the index budget that has to be SPENT before the store is called degraded.
|
|
23
|
+
*
|
|
24
|
+
* 90 and not 100 because the useful moment is before the refusal, not after it: at 100% the
|
|
25
|
+
* next `memory_save` has already failed and the operator has already seen the error. An
|
|
26
|
+
* INTEGER percent, compared by cross-multiplication where it is read, so the two runtimes
|
|
27
|
+
* cannot land on opposite sides of the line through a float they rounded differently.
|
|
28
|
+
*
|
|
29
|
+
* AMENDMENT (job46, J46-5, mirroring the reference's J46-4). It used to live in
|
|
30
|
+
* `mcp/status.ts`, next to the only thing that read it, and that is exactly what
|
|
31
|
+
* `docs/porting.md`'s register item 7 is about: the report warned at THIS line while the
|
|
32
|
+
* remedy it named — `compact` — aimed at a different one, so between the two the command
|
|
33
|
+
* exited 0 having archived nothing. `compact` has to know where the warning is to be able to
|
|
34
|
+
* clear it, and `mcp/status.ts` is above this layer, so the number moved DOWN to the layer
|
|
35
|
+
* both readers can reach. Same name, same value, same integer comparison; `mcp/status.ts`
|
|
36
|
+
* imports it and re-exports the name rather than spelling a second 90.
|
|
37
|
+
*/
|
|
38
|
+
export declare const INDEX_PRESSURE_PERCENT = 90;
|
|
39
|
+
/**
|
|
40
|
+
* The largest index size `INDEX_PRESSURE_PERCENT` does NOT call degraded, in bytes.
|
|
41
|
+
*
|
|
42
|
+
* Integer arithmetic only, and the identity it holds is
|
|
43
|
+
* `size > undegradedIndexCeiling(b)` exactly when `size * 100 >= PERCENT * b` — the
|
|
44
|
+
* cross-multiplied comparison `mcp/status.ts`'s `indexPressureCondition` writes. The two
|
|
45
|
+
* spellings are pinned against each other by a boundary sweep in `test/server.test.mjs`,
|
|
46
|
+
* because they are two spellings and a test is the only thing that can keep them one line.
|
|
47
|
+
*
|
|
48
|
+
* `Math.floor` AND NOT `Math.trunc`, because the reference spells this `//` — floor division,
|
|
49
|
+
* which rounds toward -infinity — and `Math.trunc` rounds toward zero. The two differ on
|
|
50
|
+
* every negative budget, and a budget is not guaranteed positive by anything in this
|
|
51
|
+
* function's own signature. `test/store.test.mjs` sweeps both signs against a BigInt oracle
|
|
52
|
+
* rather than against a second copy of this expression.
|
|
53
|
+
*/
|
|
54
|
+
export declare function undegradedIndexCeiling(budget: number): number;
|
|
20
55
|
export declare class MemoryValidationError extends BantamError {
|
|
21
56
|
}
|
|
22
57
|
export declare class MemoryBudgetExceeded extends BantamError {
|
|
@@ -55,6 +90,17 @@ export interface CompactResult {
|
|
|
55
90
|
headroom: number;
|
|
56
91
|
archiveDir: string;
|
|
57
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* The store members `dream.ts` needs and no caller outside it should have. See
|
|
95
|
+
* `MemoryStore.internals`.
|
|
96
|
+
*/
|
|
97
|
+
export interface StoreInternals {
|
|
98
|
+
facts(): Fact[];
|
|
99
|
+
factPath(name: FactValue): string;
|
|
100
|
+
indexLine(fact: Fact): string;
|
|
101
|
+
writeFact(fact: Fact): void;
|
|
102
|
+
rebuildIndex(): void;
|
|
103
|
+
}
|
|
58
104
|
export interface MemoryStoreOptions {
|
|
59
105
|
indexBudget?: number;
|
|
60
106
|
k?: number;
|
|
@@ -186,8 +232,25 @@ export declare class MemoryStore {
|
|
|
186
232
|
* `[]` — the same answer as a real miss, which `component.Memory` used to turn into "no
|
|
187
233
|
* memories matched. Try different words", telling a person to rephrase a question at a
|
|
188
234
|
* filing cabinet nobody could open.
|
|
235
|
+
*
|
|
236
|
+
* `minRatio` is roadmap #6's precision gate: keep a fact only if its score is at least
|
|
237
|
+
* that fraction of the BEST score in this same recall. The default is
|
|
238
|
+
* `RECALL_MIN_SCORE_RATIO`, which is 0.0 and gates nothing — see the paragraph at that
|
|
239
|
+
* constant for why the number is a no-op today and what has to be measured before it
|
|
240
|
+
* stops being one. The comparison is relative to this store's own best because that is
|
|
241
|
+
* the only quantity in reach here that cancels query length; a layered `Memory` therefore
|
|
242
|
+
* applies the gate once per layer, against each layer's own top hit, and never across
|
|
243
|
+
* layers.
|
|
244
|
+
*
|
|
245
|
+
* THE RANGE CHECK RUNS BEFORE ANY FILE IS READ. A ratio outside `[0.0, 1.0]` is a
|
|
246
|
+
* caller's bug, and reporting it as "your store has no matches" would send someone to
|
|
247
|
+
* look at their memories for a defect that is in the argument. It is spelled
|
|
248
|
+
* `!(minRatio >= 0 && minRatio <= 1)` and NOT `(minRatio < 0 || minRatio > 1)`: every
|
|
249
|
+
* comparison against `NaN` is false, so the second spelling ADMITS `NaN` where the
|
|
250
|
+
* reference's `not 0.0 <= min_ratio <= 1.0` refuses it. That is a real mutant (M7b) and
|
|
251
|
+
* it is killed on both sides.
|
|
189
252
|
*/
|
|
190
|
-
recall(query: string, k?: number | null, stamp?: boolean): Fact[];
|
|
253
|
+
recall(query: string, k?: number | null, stamp?: boolean, minRatio?: number): Fact[];
|
|
191
254
|
/**
|
|
192
255
|
* `len(store._facts())` at the operator CLI, which is the only caller.
|
|
193
256
|
*
|
|
@@ -216,6 +279,28 @@ export declare class MemoryStore {
|
|
|
216
279
|
* store currently holds (capped at half the budget), so the headroom bought is "a fact as
|
|
217
280
|
* big as your biggest one will fit", measured from this store's own data.
|
|
218
281
|
*
|
|
282
|
+
* AMENDMENT (job46, J46-5, the port of the reference's J46-4), and it supersedes the
|
|
283
|
+
* sentence above about where that reserve is measured FROM. WAS: `budget - largest index
|
|
284
|
+
* line`. NOW: that same largest line plus the headroom `INDEX_PRESSURE_PERCENT` demands, so
|
|
285
|
+
* the target is `undegradedIndexCeiling(budget) - largest index line`. THE REASON IS
|
|
286
|
+
* `docs/porting.md`'s register item 7: the degraded report warns at 90% of the budget and
|
|
287
|
+
* names THIS command, while the old target sat at `budget - largest line`, so on any store
|
|
288
|
+
* whose biggest line is under a tenth of its budget the command the operator was told to
|
|
289
|
+
* run archived nothing and the warning stayed up. MEASURED on a read-only copy of this
|
|
290
|
+
* machine's project store (101 facts, index.md 21819 bytes of 24000 = 90.91%, largest index
|
|
291
|
+
* line 361 bytes, so the old target was 23639 = 98.50%): `compact()` answered
|
|
292
|
+
* `archived: []` and `index-budget-low` was still firing afterwards — the same two answers
|
|
293
|
+
* the reference gave on the same store, which is why the register calls this a defect of
|
|
294
|
+
* the reference and NOT a divergence. The paragraph above is why the fix is a substitution
|
|
295
|
+
* and not a new number — "a loop that stopped at 'fits' would archive nothing at the only
|
|
296
|
+
* moment the remedy is ever named" is the same argument one line lower down, so the reserve
|
|
297
|
+
* is measured from the line the WARNING draws instead of the one the REFUSAL draws.
|
|
298
|
+
*
|
|
299
|
+
* WHAT THIS DOES NOT CHANGE, deliberately: the eviction ORDER (`byEviction`), the
|
|
300
|
+
* half-the-budget cap, and an EXPLICIT `reserve`. A caller that passes one gets the
|
|
301
|
+
* arithmetic it always got, byte for byte — every eviction-order node in `store.test.mjs`
|
|
302
|
+
* passes one for exactly that reason, and so may any caller that needs the old default back.
|
|
303
|
+
*
|
|
219
304
|
* `facts()` runs FIRST, before any rename, so an unreadable store moves nothing.
|
|
220
305
|
*
|
|
221
306
|
* THE MOVE IS `os.replace`, AND THE REFERENCE'S IS NOW TOO — the difference this comment
|
|
@@ -388,6 +473,21 @@ export declare class MemoryStore {
|
|
|
388
473
|
* Registered as a runtime-py defect, not fixed here (invariant 8).
|
|
389
474
|
*/
|
|
390
475
|
indexText(): string;
|
|
476
|
+
/**
|
|
477
|
+
* The `_`-prefixed members `dream.ts` reaches for, handed out as ONE named seam.
|
|
478
|
+
*
|
|
479
|
+
* The reference's `dream.py` calls `store._facts()`, `_fact_path`, `_index_line`,
|
|
480
|
+
* `_write_fact` and `_rebuild_index` directly — Python's underscore is a convention and
|
|
481
|
+
* costs nothing to cross. TypeScript's `private` is enforced, so the port needs a door,
|
|
482
|
+
* and this is deliberately one door rather than five relaxed modifiers: it is greppable,
|
|
483
|
+
* it names its one caller, and a sixth internal does not become reachable by accident.
|
|
484
|
+
*
|
|
485
|
+
* `_fact_text` has no member here because it never needed splitting out on this side —
|
|
486
|
+
* `formatFact` in `factfile.ts` has always been the serialiser `writeFact` calls, so the
|
|
487
|
+
* bytes `dream` MEASURES and the bytes the store WRITES are already one function. That is
|
|
488
|
+
* the code-shape difference the Python half had to manufacture, not a behaviour one.
|
|
489
|
+
*/
|
|
490
|
+
internals(): StoreInternals;
|
|
391
491
|
private factPath;
|
|
392
492
|
private indexLine;
|
|
393
493
|
/**
|
package/dist/memory/store.js
CHANGED
|
@@ -68,8 +68,93 @@ export const DURABLE_TYPES = ['feedback', 'user'];
|
|
|
68
68
|
const NAME_RE = /^[a-z0-9][a-z0-9-]*\n?$/;
|
|
69
69
|
const NAME_PATTERN = '^[a-z0-9][a-z0-9-]*$';
|
|
70
70
|
export const DUPLICATE_JACCARD = 0.5;
|
|
71
|
+
// ---- the precision gate (roadmap #6) ---------------------------------------------
|
|
72
|
+
//
|
|
73
|
+
// `recall` keeps a fact whose score is at least `minRatio` of the BEST score in the same
|
|
74
|
+
// recall. WHY THIS NUMBER IS 0.0, and what will replace it:
|
|
75
|
+
//
|
|
76
|
+
// 0.0 IS A DELIBERATE NO-OP, not a tuned value. `score >= 0.0 * best` is true for every
|
|
77
|
+
// fact `recall` was ever going to return — the scoring loop keeps only `score > 0` — so
|
|
78
|
+
// shipping this gate at its default changes not one recall, not one injected header, and
|
|
79
|
+
// not one byte of any reply. That is the whole point. The threshold that would actually
|
|
80
|
+
// cut something has to come from `tools/ledger/injection-precision.mjs`, and on the day
|
|
81
|
+
// that tool shipped (2026-09-06) it REFUSED to report a rate: 491 injection records, 3 of
|
|
82
|
+
// them carrying names+scores+session, 2 distinct sessions, and no control arm, against a
|
|
83
|
+
// floor of 100 joinable injections across 5 sessions. There is no retroactive baseline —
|
|
84
|
+
// the 488 older records carry `hits` and `bytes` and nothing joinable — so a number chosen
|
|
85
|
+
// today would be a number chosen off three rows, shipped as a silent suppressor of memory
|
|
86
|
+
// injection. The mechanism lands now; the number lands when that tool answers instead of
|
|
87
|
+
// refusing.
|
|
88
|
+
//
|
|
89
|
+
// WHY A RATIO AND NOT A COUNT. `score` is an unnormalised intersection size,
|
|
90
|
+
// `len(tokens(name + " " + description) & tokens(query))`, so it scales with how long the
|
|
91
|
+
// QUERY is. Measured on the three instrumented records: the same two-fact shape scored 2
|
|
92
|
+
// and 2 on a 452-character prompt, 4 and 4 on a 453-character prompt, and 22 and 21 on a
|
|
93
|
+
// 7855-character one. An absolute cut of, say, 5 would gate out both short prompts
|
|
94
|
+
// entirely and admit everything on the long one, which is a rule about prompt length
|
|
95
|
+
// wearing a relevance costume. Within ONE recall the query is fixed, so dividing by the
|
|
96
|
+
// best score in that same recall cancels the length term exactly. Jaccard was the other
|
|
97
|
+
// candidate and was rejected for the mirror-image bias: `|a & b| / |a | b|` puts the
|
|
98
|
+
// query's own token count in the denominator, so it would gate out LONG prompts instead.
|
|
99
|
+
//
|
|
100
|
+
// THE GATE IS RELATIVE, SO IT CAN NEVER EMPTY A RECALL. The top hit IS the max, so
|
|
101
|
+
// `best >= minRatio * best` holds for every ratio in range, 1.0 included. It narrows an
|
|
102
|
+
// injection; it never suppresses one, and the COUNT of prompts that get an injection is
|
|
103
|
+
// invariant at every setting.
|
|
104
|
+
//
|
|
105
|
+
// WHAT THE RATIO DOES NOT FIX: `tokens` is `re.findall(r"[a-z0-9]+", text.lower())`,
|
|
106
|
+
// ASCII-only. A wholly non-Latin prompt tokenises to the empty set and scores 0 against
|
|
107
|
+
// every fact, so it never reaches this gate at all — it is already an empty recall. This
|
|
108
|
+
// store's operator writes Thai; a threshold tuned on English prompts would be tuned on a
|
|
109
|
+
// population that structurally excludes theirs.
|
|
110
|
+
//
|
|
111
|
+
// The number lives in TWO places, one per runtime, and they must hold the SAME float:
|
|
112
|
+
// `runtime-py/src/bantamkit/memory/store.py`'s `RECALL_MIN_SCORE_RATIO` is the reference.
|
|
113
|
+
// `Memory.recallOutcome` and the `UserPromptSubmit` hook that calls it inherit it rather
|
|
114
|
+
// than restating it.
|
|
115
|
+
export const RECALL_MIN_SCORE_RATIO = 0.0;
|
|
116
|
+
// Spelled once because both runtimes raise it verbatim. The offending value is NOT
|
|
117
|
+
// interpolated: Python renders `2.0` as `2.0` and JavaScript renders it as `2`, so a
|
|
118
|
+
// sentence carrying the number would be a divergence manufactured by float formatting.
|
|
119
|
+
const MIN_RATIO_RANGE = 'recall min-score ratio must be between 0.0 and 1.0';
|
|
71
120
|
/** See the reference's comment: measured against a real store, not chosen. */
|
|
72
121
|
export const DEFAULT_INDEX_BUDGET = 24_000;
|
|
122
|
+
/**
|
|
123
|
+
* Percent of the index budget that has to be SPENT before the store is called degraded.
|
|
124
|
+
*
|
|
125
|
+
* 90 and not 100 because the useful moment is before the refusal, not after it: at 100% the
|
|
126
|
+
* next `memory_save` has already failed and the operator has already seen the error. An
|
|
127
|
+
* INTEGER percent, compared by cross-multiplication where it is read, so the two runtimes
|
|
128
|
+
* cannot land on opposite sides of the line through a float they rounded differently.
|
|
129
|
+
*
|
|
130
|
+
* AMENDMENT (job46, J46-5, mirroring the reference's J46-4). It used to live in
|
|
131
|
+
* `mcp/status.ts`, next to the only thing that read it, and that is exactly what
|
|
132
|
+
* `docs/porting.md`'s register item 7 is about: the report warned at THIS line while the
|
|
133
|
+
* remedy it named — `compact` — aimed at a different one, so between the two the command
|
|
134
|
+
* exited 0 having archived nothing. `compact` has to know where the warning is to be able to
|
|
135
|
+
* clear it, and `mcp/status.ts` is above this layer, so the number moved DOWN to the layer
|
|
136
|
+
* both readers can reach. Same name, same value, same integer comparison; `mcp/status.ts`
|
|
137
|
+
* imports it and re-exports the name rather than spelling a second 90.
|
|
138
|
+
*/
|
|
139
|
+
export const INDEX_PRESSURE_PERCENT = 90;
|
|
140
|
+
/**
|
|
141
|
+
* The largest index size `INDEX_PRESSURE_PERCENT` does NOT call degraded, in bytes.
|
|
142
|
+
*
|
|
143
|
+
* Integer arithmetic only, and the identity it holds is
|
|
144
|
+
* `size > undegradedIndexCeiling(b)` exactly when `size * 100 >= PERCENT * b` — the
|
|
145
|
+
* cross-multiplied comparison `mcp/status.ts`'s `indexPressureCondition` writes. The two
|
|
146
|
+
* spellings are pinned against each other by a boundary sweep in `test/server.test.mjs`,
|
|
147
|
+
* because they are two spellings and a test is the only thing that can keep them one line.
|
|
148
|
+
*
|
|
149
|
+
* `Math.floor` AND NOT `Math.trunc`, because the reference spells this `//` — floor division,
|
|
150
|
+
* which rounds toward -infinity — and `Math.trunc` rounds toward zero. The two differ on
|
|
151
|
+
* every negative budget, and a budget is not guaranteed positive by anything in this
|
|
152
|
+
* function's own signature. `test/store.test.mjs` sweeps both signs against a BigInt oracle
|
|
153
|
+
* rather than against a second copy of this expression.
|
|
154
|
+
*/
|
|
155
|
+
export function undegradedIndexCeiling(budget) {
|
|
156
|
+
return Math.floor((INDEX_PRESSURE_PERCENT * budget - 1) / 100);
|
|
157
|
+
}
|
|
73
158
|
// The second half of the "unreadable" sentence, one per directory this store lists — and
|
|
74
159
|
// then the same distinction one syscall down, for the stats `restore` does instead of a
|
|
75
160
|
// listing. They are separate strings because the failures do different damage, and an error
|
|
@@ -494,11 +579,30 @@ export class MemoryStore {
|
|
|
494
579
|
* `[]` — the same answer as a real miss, which `component.Memory` used to turn into "no
|
|
495
580
|
* memories matched. Try different words", telling a person to rephrase a question at a
|
|
496
581
|
* filing cabinet nobody could open.
|
|
582
|
+
*
|
|
583
|
+
* `minRatio` is roadmap #6's precision gate: keep a fact only if its score is at least
|
|
584
|
+
* that fraction of the BEST score in this same recall. The default is
|
|
585
|
+
* `RECALL_MIN_SCORE_RATIO`, which is 0.0 and gates nothing — see the paragraph at that
|
|
586
|
+
* constant for why the number is a no-op today and what has to be measured before it
|
|
587
|
+
* stops being one. The comparison is relative to this store's own best because that is
|
|
588
|
+
* the only quantity in reach here that cancels query length; a layered `Memory` therefore
|
|
589
|
+
* applies the gate once per layer, against each layer's own top hit, and never across
|
|
590
|
+
* layers.
|
|
591
|
+
*
|
|
592
|
+
* THE RANGE CHECK RUNS BEFORE ANY FILE IS READ. A ratio outside `[0.0, 1.0]` is a
|
|
593
|
+
* caller's bug, and reporting it as "your store has no matches" would send someone to
|
|
594
|
+
* look at their memories for a defect that is in the argument. It is spelled
|
|
595
|
+
* `!(minRatio >= 0 && minRatio <= 1)` and NOT `(minRatio < 0 || minRatio > 1)`: every
|
|
596
|
+
* comparison against `NaN` is false, so the second spelling ADMITS `NaN` where the
|
|
597
|
+
* reference's `not 0.0 <= min_ratio <= 1.0` refuses it. That is a real mutant (M7b) and
|
|
598
|
+
* it is killed on both sides.
|
|
497
599
|
*/
|
|
498
|
-
recall(query, k = null, stamp = true) {
|
|
600
|
+
recall(query, k = null, stamp = true, minRatio = RECALL_MIN_SCORE_RATIO) {
|
|
601
|
+
if (!(minRatio >= 0 && minRatio <= 1))
|
|
602
|
+
throw new MemoryValidationError(MIN_RATIO_RANGE);
|
|
499
603
|
const limit = k ?? this.k;
|
|
500
604
|
const q = tokens(query);
|
|
501
|
-
|
|
605
|
+
let scored = [];
|
|
502
606
|
for (const fact of this.facts()) {
|
|
503
607
|
let score = 0;
|
|
504
608
|
for (const t of tokens(`${pyText(fact.name)} ${pyText(fact.description)}`))
|
|
@@ -507,6 +611,20 @@ export class MemoryStore {
|
|
|
507
611
|
if (score > 0)
|
|
508
612
|
scored.push({ score, name: fact.name, fact });
|
|
509
613
|
}
|
|
614
|
+
if (scored.length > 0) {
|
|
615
|
+
// No `if (minRatio > 0)` shortcut on purpose: at 0.0 this line still runs and still
|
|
616
|
+
// keeps everything, so "the default gates nothing" is a fact about the arithmetic
|
|
617
|
+
// rather than about a branch that could be edited away. `Math.max(...scored)` is NOT
|
|
618
|
+
// used: a spread is one argument per fact and a large store would overflow the
|
|
619
|
+
// argument limit, which is a crash the reference's `max(...)` over a generator does
|
|
620
|
+
// not have.
|
|
621
|
+
let best = scored[0].score;
|
|
622
|
+
for (const s of scored)
|
|
623
|
+
if (s.score > best)
|
|
624
|
+
best = s.score;
|
|
625
|
+
const floor = minRatio * best;
|
|
626
|
+
scored = scored.filter((s) => s.score >= floor);
|
|
627
|
+
}
|
|
510
628
|
// `key=lambda pair: (-pair[0], pair[1].name)`. The name comparison is CODEPOINT order
|
|
511
629
|
// for two strings — which is not what `Array.prototype.sort` does — and Python's `<`
|
|
512
630
|
// for everything else, refusals included. See `sortScored`.
|
|
@@ -553,6 +671,28 @@ export class MemoryStore {
|
|
|
553
671
|
* store currently holds (capped at half the budget), so the headroom bought is "a fact as
|
|
554
672
|
* big as your biggest one will fit", measured from this store's own data.
|
|
555
673
|
*
|
|
674
|
+
* AMENDMENT (job46, J46-5, the port of the reference's J46-4), and it supersedes the
|
|
675
|
+
* sentence above about where that reserve is measured FROM. WAS: `budget - largest index
|
|
676
|
+
* line`. NOW: that same largest line plus the headroom `INDEX_PRESSURE_PERCENT` demands, so
|
|
677
|
+
* the target is `undegradedIndexCeiling(budget) - largest index line`. THE REASON IS
|
|
678
|
+
* `docs/porting.md`'s register item 7: the degraded report warns at 90% of the budget and
|
|
679
|
+
* names THIS command, while the old target sat at `budget - largest line`, so on any store
|
|
680
|
+
* whose biggest line is under a tenth of its budget the command the operator was told to
|
|
681
|
+
* run archived nothing and the warning stayed up. MEASURED on a read-only copy of this
|
|
682
|
+
* machine's project store (101 facts, index.md 21819 bytes of 24000 = 90.91%, largest index
|
|
683
|
+
* line 361 bytes, so the old target was 23639 = 98.50%): `compact()` answered
|
|
684
|
+
* `archived: []` and `index-budget-low` was still firing afterwards — the same two answers
|
|
685
|
+
* the reference gave on the same store, which is why the register calls this a defect of
|
|
686
|
+
* the reference and NOT a divergence. The paragraph above is why the fix is a substitution
|
|
687
|
+
* and not a new number — "a loop that stopped at 'fits' would archive nothing at the only
|
|
688
|
+
* moment the remedy is ever named" is the same argument one line lower down, so the reserve
|
|
689
|
+
* is measured from the line the WARNING draws instead of the one the REFUSAL draws.
|
|
690
|
+
*
|
|
691
|
+
* WHAT THIS DOES NOT CHANGE, deliberately: the eviction ORDER (`byEviction`), the
|
|
692
|
+
* half-the-budget cap, and an EXPLICIT `reserve`. A caller that passes one gets the
|
|
693
|
+
* arithmetic it always got, byte for byte — every eviction-order node in `store.test.mjs`
|
|
694
|
+
* passes one for exactly that reason, and so may any caller that needs the old default back.
|
|
695
|
+
*
|
|
556
696
|
* `facts()` runs FIRST, before any rename, so an unreadable store moves nothing.
|
|
557
697
|
*
|
|
558
698
|
* THE MOVE IS `os.replace`, AND THE REFERENCE'S IS NOW TOO — the difference this comment
|
|
@@ -607,8 +747,19 @@ export class MemoryStore {
|
|
|
607
747
|
sizes.set(pyHashKey(fact.name), Buffer.byteLength(this.indexLine(fact), 'utf8'));
|
|
608
748
|
}
|
|
609
749
|
const all = [...sizes.values()];
|
|
610
|
-
if (reserve === null)
|
|
611
|
-
reserve
|
|
750
|
+
if (reserve === null) {
|
|
751
|
+
// The default reserve is measured from the WARNING LINE, not from the budget. One
|
|
752
|
+
// substitution, and it is the whole of `docs/porting.md` item 7: the sentence that
|
|
753
|
+
// names this command fires at `INDEX_PRESSURE_PERCENT`, so a remedy that only reaches
|
|
754
|
+
// `budget - largest line` is a no-op everywhere between them. `undegradedIndexCeiling`
|
|
755
|
+
// is that line; `+ largest line` keeps this method's own promise on the other side of
|
|
756
|
+
// it, unchanged in words: "a fact as big as your biggest one will fit" — before the
|
|
757
|
+
// index is degraded AGAIN, rather than before it is over budget.
|
|
758
|
+
reserve =
|
|
759
|
+
this.indexBudget -
|
|
760
|
+
undegradedIndexCeiling(this.indexBudget) +
|
|
761
|
+
(all.length === 0 ? 0 : Math.max(...all));
|
|
762
|
+
}
|
|
612
763
|
// `min(reserve, self.index_budget // 2)`: floor division, and the budget is >= 1 here
|
|
613
764
|
// because both CLIs refuse a smaller one at the edge.
|
|
614
765
|
reserve = Math.max(0, Math.min(reserve, Math.floor(this.indexBudget / 2)));
|
|
@@ -839,6 +990,29 @@ export class MemoryStore {
|
|
|
839
990
|
indexText() {
|
|
840
991
|
return this.facts().map((fact) => this.indexLine(fact)).join('');
|
|
841
992
|
}
|
|
993
|
+
/**
|
|
994
|
+
* The `_`-prefixed members `dream.ts` reaches for, handed out as ONE named seam.
|
|
995
|
+
*
|
|
996
|
+
* The reference's `dream.py` calls `store._facts()`, `_fact_path`, `_index_line`,
|
|
997
|
+
* `_write_fact` and `_rebuild_index` directly — Python's underscore is a convention and
|
|
998
|
+
* costs nothing to cross. TypeScript's `private` is enforced, so the port needs a door,
|
|
999
|
+
* and this is deliberately one door rather than five relaxed modifiers: it is greppable,
|
|
1000
|
+
* it names its one caller, and a sixth internal does not become reachable by accident.
|
|
1001
|
+
*
|
|
1002
|
+
* `_fact_text` has no member here because it never needed splitting out on this side —
|
|
1003
|
+
* `formatFact` in `factfile.ts` has always been the serialiser `writeFact` calls, so the
|
|
1004
|
+
* bytes `dream` MEASURES and the bytes the store WRITES are already one function. That is
|
|
1005
|
+
* the code-shape difference the Python half had to manufacture, not a behaviour one.
|
|
1006
|
+
*/
|
|
1007
|
+
internals() {
|
|
1008
|
+
return {
|
|
1009
|
+
facts: () => this.facts(),
|
|
1010
|
+
factPath: (name) => this.factPath(name),
|
|
1011
|
+
indexLine: (fact) => this.indexLine(fact),
|
|
1012
|
+
writeFact: (fact) => this.writeFact(fact),
|
|
1013
|
+
rebuildIndex: () => this.rebuildIndex(),
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
842
1016
|
// ---- internals ----
|
|
843
1017
|
factPath(name) {
|
|
844
1018
|
return pyJoin(this.root, 'facts', `${pyText(name)}.md`);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { BantamError } from './errors.js';
|
|
2
|
+
export declare const PRICES_ENV = "BANTAMKIT_PRICES";
|
|
3
|
+
export declare const PRICE_SCHEMA_VERSION = 1;
|
|
4
|
+
export declare const CURRENCY = "USD";
|
|
5
|
+
export declare const RATE_UNIT = "micro_usd_per_million_tokens";
|
|
6
|
+
/** A rate is micro-USD per this many tokens. */
|
|
7
|
+
export declare const TOKENS_PER_RATE_UNIT = 1000000;
|
|
8
|
+
/** Micro-USD in one USD. Also the rounding divisor, hence `HALF` below. */
|
|
9
|
+
export declare const MICROS_PER_UNIT = 1000000;
|
|
10
|
+
/** The largest integer both runtimes represent exactly. */
|
|
11
|
+
export declare const MAX_SAFE_INT: number;
|
|
12
|
+
/**
|
|
13
|
+
* The four classes the host's own `usage` block distinguishes, in the order the ledger
|
|
14
|
+
* accumulates them. This tuple is the unit of pricing; see property 1 in the reference.
|
|
15
|
+
*/
|
|
16
|
+
export declare const TOKEN_CLASSES: readonly ["input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens", "output_tokens"];
|
|
17
|
+
export type TokenClass = (typeof TOKEN_CLASSES)[number];
|
|
18
|
+
export interface Rate {
|
|
19
|
+
recorded: string;
|
|
20
|
+
source: string;
|
|
21
|
+
[cls: string]: string | number;
|
|
22
|
+
}
|
|
23
|
+
export interface PriceTable {
|
|
24
|
+
schema_version: number;
|
|
25
|
+
currency: string;
|
|
26
|
+
unit: string;
|
|
27
|
+
note?: string;
|
|
28
|
+
rates: Record<string, Rate>;
|
|
29
|
+
}
|
|
30
|
+
/** A cost, or a refusal. Never both, and never a zero standing in for a refusal. */
|
|
31
|
+
export type PriceResult = {
|
|
32
|
+
unavailable: string;
|
|
33
|
+
} | {
|
|
34
|
+
model: string;
|
|
35
|
+
currency: string;
|
|
36
|
+
micros: number;
|
|
37
|
+
amount: string;
|
|
38
|
+
breakdown: Record<string, number>;
|
|
39
|
+
rate: Rate;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The table on disk is malformed.
|
|
43
|
+
*
|
|
44
|
+
* This THROWS rather than returning `{unavailable}`, and the split is deliberate: a broken
|
|
45
|
+
* price table is an operator configuration fault that must stop and name itself, while an
|
|
46
|
+
* unpriced model is a normal, expected answer that a caller keeps working past.
|
|
47
|
+
*/
|
|
48
|
+
export declare class PriceTableError extends BantamError {
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `$BANTAMKIT_PRICES` verbatim if set, else the shipped table in the asset pack.
|
|
52
|
+
*
|
|
53
|
+
* The override wins even when it is wrong -- same arm order and same reasoning as
|
|
54
|
+
* `assetsRoot()`: an override that points nowhere must fail naming itself rather than fall
|
|
55
|
+
* through to a table the operator did not ask for.
|
|
56
|
+
*/
|
|
57
|
+
export declare function priceTablePath(): string;
|
|
58
|
+
/**
|
|
59
|
+
* Read and validate a price table. `path` is used VERBATIM in every message it appears in,
|
|
60
|
+
* never normalised, because `Path('./x')` is `x` on the reference side and `./x` here and the
|
|
61
|
+
* fault sentence would then differ over a path neither runtime chose.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadPriceTable(path?: string): PriceTable;
|
|
64
|
+
/**
|
|
65
|
+
* The decode both runtimes must agree on, in one place so the gate can reach it.
|
|
66
|
+
*
|
|
67
|
+
* This side is already the strict one -- `JSON.parse` rejects the bare `NaN` / `Infinity` /
|
|
68
|
+
* `-Infinity` tokens that `json.loads` accepts -- so the function is a plain `JSON.parse`
|
|
69
|
+
* and the reference is what had to move. It exists as a named arm anyway, because a rule
|
|
70
|
+
* that lives in only one of two runtimes is a rule the conformance harness cannot reach.
|
|
71
|
+
*/
|
|
72
|
+
export declare function decodePriceJson(text: string): unknown;
|
|
73
|
+
/**
|
|
74
|
+
* Every rule the table must satisfy, each with its own sentence.
|
|
75
|
+
*
|
|
76
|
+
* Returns a NORMALISED table: the top-level keys in a fixed order, rate names in code-point
|
|
77
|
+
* order, and every count narrowed. See the reference for the ONE thing normalisation cannot
|
|
78
|
+
* buy -- the iteration order of `rates` for a model name JavaScript reads as an array index.
|
|
79
|
+
*/
|
|
80
|
+
export declare function validatePriceTable(raw: unknown): PriceTable;
|
|
81
|
+
/**
|
|
82
|
+
* Micro-USD as a fixed-6-decimal string, built from integers only.
|
|
83
|
+
*
|
|
84
|
+
* Non-negative input only; every producer in this module is a sum of non-negative products.
|
|
85
|
+
*/
|
|
86
|
+
export declare function formatMicros(micros: number): string;
|
|
87
|
+
/**
|
|
88
|
+
* The cost of one `usage` block under one model's rate, or a named refusal.
|
|
89
|
+
*
|
|
90
|
+
* `table` must be a table `validatePriceTable` returned.
|
|
91
|
+
*
|
|
92
|
+
* THE ORDER OF THE CHECKS IS PART OF THE CONTRACT. The usage is validated BEFORE the model is
|
|
93
|
+
* looked up, so a caller who passed a malformed usage is told that, rather than being sent to
|
|
94
|
+
* edit a price table over a bug in the call.
|
|
95
|
+
*
|
|
96
|
+
* THE USAGE MUST CARRY ALL FOUR CLASSES AND NOTHING ELSE. See the reference: a token class
|
|
97
|
+
* silently ignored is money silently dropped, and a class silently defaulted to zero is money
|
|
98
|
+
* silently invented.
|
|
99
|
+
*/
|
|
100
|
+
export declare function priceTokens(table: PriceTable, model: string, usage: unknown): PriceResult;
|