bantamkit-mcp 0.30.0 → 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/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 -5
- package/dist/mcp/identity.d.ts +80 -0
- package/dist/mcp/identity.js +466 -4
- package/dist/mcp/pyargs.js +10 -0
- package/dist/mcp/server.d.ts +3 -2
- package/dist/mcp/server.js +58 -1
- 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/store.d.ts +56 -0
- package/dist/memory/store.js +71 -2
- package/dist/pricing.d.ts +100 -0
- package/dist/pricing.js +357 -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/mcp/status.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { EventLog } from '../eventlog.js';
|
|
2
2
|
import { Memory } from '../memory/component.js';
|
|
3
|
+
import { INDEX_PRESSURE_PERCENT } from '../memory/store.js';
|
|
4
|
+
import type { Install } from './identity.js';
|
|
3
5
|
/**
|
|
4
6
|
* The tool AND the prompt answer to this one name. Deliberately the same word: the operator
|
|
5
7
|
* who read `bantamkit_status` in a footer must find it in their prompt menu without
|
|
@@ -31,14 +33,13 @@ export declare const STATUS_PROMPT_TAIL: string;
|
|
|
31
33
|
export declare const SERVED_PROMPTS = 1;
|
|
32
34
|
export declare const SERVED_RESOURCE_TEMPLATES = 2;
|
|
33
35
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* on opposite sides of the line through a float they rounded differently.
|
|
36
|
+
* `INDEX_PRESSURE_PERCENT` is re-exported here, where it used to be DEFINED, so that
|
|
37
|
+
* `import { INDEX_PRESSURE_PERCENT } from './mcp/status.js'` keeps resolving. It moved down
|
|
38
|
+
* to `memory/store.ts` in job46 (J46-5, mirroring the reference's J46-4):
|
|
39
|
+
* `MemoryStore.compact` is the remedy the sentence below names, and it cannot clear a warning
|
|
40
|
+
* whose line it cannot see. The comment that says why the number is 90 moved with it.
|
|
40
41
|
*/
|
|
41
|
-
export
|
|
42
|
+
export { INDEX_PRESSURE_PERCENT };
|
|
42
43
|
/**
|
|
43
44
|
* One thing that is wrong, carried in the two forms the two surfaces need.
|
|
44
45
|
*
|
|
@@ -135,6 +136,20 @@ export declare function unreadableLayerCondition(memory: Memory): Condition | nu
|
|
|
135
136
|
* not a divergence and is not fixed here; it is registered in `docs/porting.md`. The sentence
|
|
136
137
|
* says "archive or shorten facts", which is what the operator has to do either way.
|
|
137
138
|
*
|
|
139
|
+
* AMENDMENT (job46, J46-5): THE PARAGRAPH ABOVE IS A RECORD OF A DEFECT THAT IS NOW CLOSED,
|
|
140
|
+
* and it is left standing because it is the measurement that motivated the fix. What it says
|
|
141
|
+
* was true until this job: the band was real, and on this machine's own project store
|
|
142
|
+
* (101 facts, index.md 21819 bytes of a 24000-byte budget = 90.91%, largest index line 361
|
|
143
|
+
* bytes so the old target was 23639 = 98.50%) `compact()` answered `archived: []` with this
|
|
144
|
+
* very sentence on screen. What has changed is the second half — "is not fixed here". It is
|
|
145
|
+
* fixed now, in the layer that owns it: `MemoryStore.compact`'s default `reserve` is measured
|
|
146
|
+
* from `undegradedIndexCeiling(budget)` rather than from `budget`, so running the command
|
|
147
|
+
* this sentence names clears the condition this sentence reports. Both runtimes moved in the
|
|
148
|
+
* same job (the reference in `87cc1f7`), because a band that closed on one side only would be
|
|
149
|
+
* the divergence the paragraph above correctly said this was not. The register entry's own
|
|
150
|
+
* closure is not written here — that is a `docs/porting.md` edit, and it waits on a
|
|
151
|
+
* conformance case comparing the two answers.
|
|
152
|
+
*
|
|
138
153
|
* THIS SENTENCE IS FOR THE OPERATOR; THE MODEL HAS ITS OWN REMEDY. Since job42 the model
|
|
139
154
|
* can call `memory_compact` (the ninth served tool) when a save is actually refused, and
|
|
140
155
|
* the refused-budget reply in `memory/component.ts` names it. That does not change this
|
|
@@ -152,13 +167,40 @@ export declare function indexPressureCondition(memory: Memory): Condition | null
|
|
|
152
167
|
* BOTH on and known to have lost a record is a problem anyone can act on.
|
|
153
168
|
*/
|
|
154
169
|
export declare function eventLogCondition(log: EventLog): Condition | null;
|
|
170
|
+
/**
|
|
171
|
+
* This install came from a path on this machine, and that path is gone.
|
|
172
|
+
*
|
|
173
|
+
* THE ONE CONDITION THAT NAMES A PATH, and the exception is deliberate. Every other sentence
|
|
174
|
+
* here refuses one because `assetsRoot()` resolves differently in the two runtimes by
|
|
175
|
+
* construction, so a path would be an uncomparable value bought for nothing. This path is not
|
|
176
|
+
* the server's own location — it is the origin THE INSTALLER WROTE DOWN, it is the entire
|
|
177
|
+
* actionable content of the finding (AS-7's measured case is a tarball under a
|
|
178
|
+
* `/private/tmp/.../scratchpad` that no longer exists), and a sentence saying "something is
|
|
179
|
+
* missing" without saying what would be a sentence nobody can act on.
|
|
180
|
+
*
|
|
181
|
+
* THE REMEDY IS THE ONE THAT ACTUALLY MOVES SOMETHING. J46-4 spent a unit removing a condition
|
|
182
|
+
* whose remedy exited 0 having changed nothing, and this is exactly the shape that invites
|
|
183
|
+
* another: `npm update` where a dangling `file:` install lives is a no-op BY CONSTRUCTION. So
|
|
184
|
+
* the sentence sends the reader at a reinstall BY NAME from a registry, which replaces the
|
|
185
|
+
* install rather than trying to refresh it in place. `bantamkit` is the PRODUCT name and is
|
|
186
|
+
* copied verbatim from the reference — it is not the npm package id, and turning it into one
|
|
187
|
+
* is how `git_commit` diverged on a noun.
|
|
188
|
+
*
|
|
189
|
+
* THE DERIVED HALF IS MEMOISED AND THIS HALF IS NOT: the shape cannot change under a running
|
|
190
|
+
* process, the path's existence can, and it is the one that has to be read now.
|
|
191
|
+
*/
|
|
192
|
+
export declare function installSourceCondition(install: Install | null): Condition | null;
|
|
155
193
|
/**
|
|
156
194
|
* Everything wrong right now, worst first. An empty array means healthy.
|
|
157
195
|
*
|
|
158
196
|
* ORDER IS SEVERITY AND IT IS LOAD-BEARING, because the footer shows the first one: a pack
|
|
159
197
|
* that vanished breaks every asset-backed surface; an unreadable layer makes recall ANSWER
|
|
160
198
|
* WRONGLY rather than fail; a full index refuses the next save; a broken event log costs
|
|
161
|
-
* diagnostics only
|
|
199
|
+
* diagnostics only; and a dangling install origin costs nothing AT ALL right now — the server
|
|
200
|
+
* is serving correctly, and what is broken is the next attempt to update it. That is why it is
|
|
201
|
+
* last despite being the one that went unnoticed for five releases
|
|
202
|
+
* (`docs/roadmap-agent-stack.md` AS-7): severity here is what is failing, not what has been
|
|
203
|
+
* failing longest.
|
|
162
204
|
*
|
|
163
205
|
* EVERY CONDITION IS OBSERVED, NOT INFERRED — no heartbeat, no timer, no last-seen timestamp.
|
|
164
206
|
* Each one is a state a test can construct and then watch this report: delete the pack, make a
|
|
@@ -167,8 +209,10 @@ export declare function eventLogCondition(log: EventLog): Condition | null;
|
|
|
167
209
|
* claimed.
|
|
168
210
|
*
|
|
169
211
|
* THE COST, because this runs on every tool call: one `stat` for the index, one `stat` for the
|
|
170
|
-
* pack, one directory listing per memory layer (two to four),
|
|
171
|
-
* fact file is opened
|
|
212
|
+
* pack, one directory listing per memory layer (two to four), a field read for the log, and one
|
|
213
|
+
* `stat` for the install origin. No fact file is opened, no index is parsed, and
|
|
214
|
+
* `node_modules` is NOT walked for a hidden lockfile — `currentInstall` does that once per
|
|
215
|
+
* process, because the bytes that were imported cannot change under a running one.
|
|
172
216
|
*/
|
|
173
217
|
export declare function degradedConditions(memory: Memory, log: EventLog): Condition[];
|
|
174
218
|
/**
|
package/dist/mcp/status.js
CHANGED
|
@@ -33,6 +33,8 @@ import { statSync } from 'node:fs';
|
|
|
33
33
|
import { join } from 'node:path';
|
|
34
34
|
import { AssetNotFound, assetsRoot } from '../assets.js';
|
|
35
35
|
import { Memory } from '../memory/component.js';
|
|
36
|
+
import { INDEX_PRESSURE_PERCENT } from '../memory/store.js';
|
|
37
|
+
import { Undetermined, currentInstall, originStat } from './identity.js';
|
|
36
38
|
/**
|
|
37
39
|
* The tool AND the prompt answer to this one name. Deliberately the same word: the operator
|
|
38
40
|
* who read `bantamkit_status` in a footer must find it in their prompt menu without
|
|
@@ -68,14 +70,13 @@ export const STATUS_PROMPT_TAIL = 'Show me that report as it stands. If it says
|
|
|
68
70
|
export const SERVED_PROMPTS = 1;
|
|
69
71
|
export const SERVED_RESOURCE_TEMPLATES = 2;
|
|
70
72
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* on opposite sides of the line through a float they rounded differently.
|
|
73
|
+
* `INDEX_PRESSURE_PERCENT` is re-exported here, where it used to be DEFINED, so that
|
|
74
|
+
* `import { INDEX_PRESSURE_PERCENT } from './mcp/status.js'` keeps resolving. It moved down
|
|
75
|
+
* to `memory/store.ts` in job46 (J46-5, mirroring the reference's J46-4):
|
|
76
|
+
* `MemoryStore.compact` is the remedy the sentence below names, and it cannot clear a warning
|
|
77
|
+
* whose line it cannot see. The comment that says why the number is 90 moved with it.
|
|
77
78
|
*/
|
|
78
|
-
export
|
|
79
|
+
export { INDEX_PRESSURE_PERCENT };
|
|
79
80
|
/** `1 prompt` / `2 prompts`. Spelled once so the two runtimes cannot disagree twice. */
|
|
80
81
|
export function plural(count, word) {
|
|
81
82
|
return count === 1 ? `${count} ${word}` : `${count} ${word}s`;
|
|
@@ -221,6 +222,20 @@ export function unreadableLayerCondition(memory) {
|
|
|
221
222
|
* not a divergence and is not fixed here; it is registered in `docs/porting.md`. The sentence
|
|
222
223
|
* says "archive or shorten facts", which is what the operator has to do either way.
|
|
223
224
|
*
|
|
225
|
+
* AMENDMENT (job46, J46-5): THE PARAGRAPH ABOVE IS A RECORD OF A DEFECT THAT IS NOW CLOSED,
|
|
226
|
+
* and it is left standing because it is the measurement that motivated the fix. What it says
|
|
227
|
+
* was true until this job: the band was real, and on this machine's own project store
|
|
228
|
+
* (101 facts, index.md 21819 bytes of a 24000-byte budget = 90.91%, largest index line 361
|
|
229
|
+
* bytes so the old target was 23639 = 98.50%) `compact()` answered `archived: []` with this
|
|
230
|
+
* very sentence on screen. What has changed is the second half — "is not fixed here". It is
|
|
231
|
+
* fixed now, in the layer that owns it: `MemoryStore.compact`'s default `reserve` is measured
|
|
232
|
+
* from `undegradedIndexCeiling(budget)` rather than from `budget`, so running the command
|
|
233
|
+
* this sentence names clears the condition this sentence reports. Both runtimes moved in the
|
|
234
|
+
* same job (the reference in `87cc1f7`), because a band that closed on one side only would be
|
|
235
|
+
* the divergence the paragraph above correctly said this was not. The register entry's own
|
|
236
|
+
* closure is not written here — that is a `docs/porting.md` edit, and it waits on a
|
|
237
|
+
* conformance case comparing the two answers.
|
|
238
|
+
*
|
|
224
239
|
* THIS SENTENCE IS FOR THE OPERATOR; THE MODEL HAS ITS OWN REMEDY. Since job42 the model
|
|
225
240
|
* can call `memory_compact` (the ninth served tool) when a save is actually refused, and
|
|
226
241
|
* the refused-budget reply in `memory/component.ts` names it. That does not change this
|
|
@@ -261,13 +276,71 @@ export function eventLogCondition(log) {
|
|
|
261
276
|
'whether its directory is writable.',
|
|
262
277
|
};
|
|
263
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* This install came from a path on this machine, and that path is gone.
|
|
281
|
+
*
|
|
282
|
+
* THE ONE CONDITION THAT NAMES A PATH, and the exception is deliberate. Every other sentence
|
|
283
|
+
* here refuses one because `assetsRoot()` resolves differently in the two runtimes by
|
|
284
|
+
* construction, so a path would be an uncomparable value bought for nothing. This path is not
|
|
285
|
+
* the server's own location — it is the origin THE INSTALLER WROTE DOWN, it is the entire
|
|
286
|
+
* actionable content of the finding (AS-7's measured case is a tarball under a
|
|
287
|
+
* `/private/tmp/.../scratchpad` that no longer exists), and a sentence saying "something is
|
|
288
|
+
* missing" without saying what would be a sentence nobody can act on.
|
|
289
|
+
*
|
|
290
|
+
* THE REMEDY IS THE ONE THAT ACTUALLY MOVES SOMETHING. J46-4 spent a unit removing a condition
|
|
291
|
+
* whose remedy exited 0 having changed nothing, and this is exactly the shape that invites
|
|
292
|
+
* another: `npm update` where a dangling `file:` install lives is a no-op BY CONSTRUCTION. So
|
|
293
|
+
* the sentence sends the reader at a reinstall BY NAME from a registry, which replaces the
|
|
294
|
+
* install rather than trying to refresh it in place. `bantamkit` is the PRODUCT name and is
|
|
295
|
+
* copied verbatim from the reference — it is not the npm package id, and turning it into one
|
|
296
|
+
* is how `git_commit` diverged on a noun.
|
|
297
|
+
*
|
|
298
|
+
* THE DERIVED HALF IS MEMOISED AND THIS HALF IS NOT: the shape cannot change under a running
|
|
299
|
+
* process, the path's existence can, and it is the one that has to be read now.
|
|
300
|
+
*/
|
|
301
|
+
export function installSourceCondition(install) {
|
|
302
|
+
if (install === null || install.source === null)
|
|
303
|
+
return null;
|
|
304
|
+
// `present: null` is a check that could not be made, and the reference reports no condition
|
|
305
|
+
// for one either: an origin nobody may stat is not, on that evidence, an origin that is gone.
|
|
306
|
+
if (originStat(install.source).present !== false)
|
|
307
|
+
return null;
|
|
308
|
+
return {
|
|
309
|
+
key: 'install-source-missing',
|
|
310
|
+
sentence: `this server was installed from ${install.source}, which no longer exists, so nothing ` +
|
|
311
|
+
'can be refreshed in place there — reinstall bantamkit by name from a package registry ' +
|
|
312
|
+
'and restart the server.',
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* The shape, or nothing. A shape that could not be derived is a REPORTED gap.
|
|
317
|
+
*
|
|
318
|
+
* `buildIdentity` is where that gap is named, with its reason. It is not a degraded condition:
|
|
319
|
+
* an install this code cannot classify is not, on that evidence, an install that is broken, and
|
|
320
|
+
* a footer on every tool call saying otherwise would be the noise `degradedNotice` exists to
|
|
321
|
+
* avoid.
|
|
322
|
+
*/
|
|
323
|
+
function currentInstallOrNull() {
|
|
324
|
+
try {
|
|
325
|
+
return currentInstall();
|
|
326
|
+
}
|
|
327
|
+
catch (e) {
|
|
328
|
+
if (!(e instanceof Undetermined))
|
|
329
|
+
throw e;
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
264
333
|
/**
|
|
265
334
|
* Everything wrong right now, worst first. An empty array means healthy.
|
|
266
335
|
*
|
|
267
336
|
* ORDER IS SEVERITY AND IT IS LOAD-BEARING, because the footer shows the first one: a pack
|
|
268
337
|
* that vanished breaks every asset-backed surface; an unreadable layer makes recall ANSWER
|
|
269
338
|
* WRONGLY rather than fail; a full index refuses the next save; a broken event log costs
|
|
270
|
-
* diagnostics only
|
|
339
|
+
* diagnostics only; and a dangling install origin costs nothing AT ALL right now — the server
|
|
340
|
+
* is serving correctly, and what is broken is the next attempt to update it. That is why it is
|
|
341
|
+
* last despite being the one that went unnoticed for five releases
|
|
342
|
+
* (`docs/roadmap-agent-stack.md` AS-7): severity here is what is failing, not what has been
|
|
343
|
+
* failing longest.
|
|
271
344
|
*
|
|
272
345
|
* EVERY CONDITION IS OBSERVED, NOT INFERRED — no heartbeat, no timer, no last-seen timestamp.
|
|
273
346
|
* Each one is a state a test can construct and then watch this report: delete the pack, make a
|
|
@@ -276,8 +349,10 @@ export function eventLogCondition(log) {
|
|
|
276
349
|
* claimed.
|
|
277
350
|
*
|
|
278
351
|
* THE COST, because this runs on every tool call: one `stat` for the index, one `stat` for the
|
|
279
|
-
* pack, one directory listing per memory layer (two to four),
|
|
280
|
-
* fact file is opened
|
|
352
|
+
* pack, one directory listing per memory layer (two to four), a field read for the log, and one
|
|
353
|
+
* `stat` for the install origin. No fact file is opened, no index is parsed, and
|
|
354
|
+
* `node_modules` is NOT walked for a hidden lockfile — `currentInstall` does that once per
|
|
355
|
+
* process, because the bytes that were imported cannot change under a running one.
|
|
281
356
|
*/
|
|
282
357
|
export function degradedConditions(memory, log) {
|
|
283
358
|
return [
|
|
@@ -285,6 +360,7 @@ export function degradedConditions(memory, log) {
|
|
|
285
360
|
unreadableLayerCondition(memory),
|
|
286
361
|
indexPressureCondition(memory),
|
|
287
362
|
eventLogCondition(log),
|
|
363
|
+
installSourceCondition(currentInstallOrNull()),
|
|
288
364
|
].filter((condition) => condition !== null);
|
|
289
365
|
}
|
|
290
366
|
/**
|
package/dist/memory/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* `docs/memory.md` states, as a design decision, that `lint`, `archived` and `restore`
|
|
6
6
|
* are **not** agent tools — "lifecycle is an operator decision, not a model decision" —
|
|
7
|
-
* and the
|
|
7
|
+
* and the fourteen-tool surface holds to it; `compact` is the one exception since job42
|
|
8
8
|
* (`memory_compact`, the on-refusal path the model reaches itself). `runtime-py` made that
|
|
9
9
|
* position coherent in August 2026 by giving the operator `python -m bantamkit.memory`.
|
|
10
10
|
* `runtime-ts` did not. MEASURED 2026-08-24 before this file existed: `node dist/cli.js -h`
|
package/dist/memory/store.d.ts
CHANGED
|
@@ -18,6 +18,40 @@ export declare const DUPLICATE_JACCARD = 0.5;
|
|
|
18
18
|
export declare const RECALL_MIN_SCORE_RATIO = 0;
|
|
19
19
|
/** See the reference's comment: measured against a real store, not chosen. */
|
|
20
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;
|
|
21
55
|
export declare class MemoryValidationError extends BantamError {
|
|
22
56
|
}
|
|
23
57
|
export declare class MemoryBudgetExceeded extends BantamError {
|
|
@@ -245,6 +279,28 @@ export declare class MemoryStore {
|
|
|
245
279
|
* store currently holds (capped at half the budget), so the headroom bought is "a fact as
|
|
246
280
|
* big as your biggest one will fit", measured from this store's own data.
|
|
247
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
|
+
*
|
|
248
304
|
* `facts()` runs FIRST, before any rename, so an unreadable store moves nothing.
|
|
249
305
|
*
|
|
250
306
|
* THE MOVE IS `os.replace`, AND THE REFERENCE'S IS NOW TOO — the difference this comment
|
package/dist/memory/store.js
CHANGED
|
@@ -119,6 +119,42 @@ export const RECALL_MIN_SCORE_RATIO = 0.0;
|
|
|
119
119
|
const MIN_RATIO_RANGE = 'recall min-score ratio must be between 0.0 and 1.0';
|
|
120
120
|
/** See the reference's comment: measured against a real store, not chosen. */
|
|
121
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
|
+
}
|
|
122
158
|
// The second half of the "unreadable" sentence, one per directory this store lists — and
|
|
123
159
|
// then the same distinction one syscall down, for the stats `restore` does instead of a
|
|
124
160
|
// listing. They are separate strings because the failures do different damage, and an error
|
|
@@ -635,6 +671,28 @@ export class MemoryStore {
|
|
|
635
671
|
* store currently holds (capped at half the budget), so the headroom bought is "a fact as
|
|
636
672
|
* big as your biggest one will fit", measured from this store's own data.
|
|
637
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
|
+
*
|
|
638
696
|
* `facts()` runs FIRST, before any rename, so an unreadable store moves nothing.
|
|
639
697
|
*
|
|
640
698
|
* THE MOVE IS `os.replace`, AND THE REFERENCE'S IS NOW TOO — the difference this comment
|
|
@@ -689,8 +747,19 @@ export class MemoryStore {
|
|
|
689
747
|
sizes.set(pyHashKey(fact.name), Buffer.byteLength(this.indexLine(fact), 'utf8'));
|
|
690
748
|
}
|
|
691
749
|
const all = [...sizes.values()];
|
|
692
|
-
if (reserve === null)
|
|
693
|
-
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
|
+
}
|
|
694
763
|
// `min(reserve, self.index_budget // 2)`: floor division, and the budget is >= 1 here
|
|
695
764
|
// because both CLIs refuse a smaller one at the edge.
|
|
696
765
|
reserve = Math.max(0, Math.min(reserve, Math.floor(this.indexBudget / 2)));
|
|
@@ -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;
|