lossless-core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +10 -3
- package/dist/index.d.mts +1622 -1115
- package/dist/index.mjs +4214 -2584
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,310 +1,1002 @@
|
|
|
1
1
|
import { Static, TSchema, Type } from "typebox";
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
/** Basename of the JSONL metrics log inside a sidecar directory. */
|
|
3
|
+
export declare const METRICS_FILE = "metrics.jsonl";
|
|
4
|
+
/** Size in bytes that triggers rotation (5 MiB); the full log is renamed aside. */
|
|
5
|
+
export declare const METRICS_MAX_BYTES: number;
|
|
6
|
+
/** Rotated generations kept beside the live log; older timestamped files are deleted. */
|
|
7
|
+
export declare const METRICS_KEEP_GENERATIONS = 3;
|
|
8
|
+
/** One metrics row: free-form fields plus an optional unix-ms stamp, session id, and kind. */
|
|
9
|
+
export interface MetricSinkRow extends Record<string, unknown> {
|
|
10
|
+
ts?: number;
|
|
11
|
+
session?: string;
|
|
12
|
+
kind?: string;
|
|
13
|
+
}
|
|
14
|
+
/** The live metrics file inside a sidecar directory. */
|
|
15
|
+
export declare function metricsFilePath(dir: string): string;
|
|
16
|
+
/** Rename an over-budget log to `<file>.<Date.now()>` and keep only the newest generations. Never throws. */
|
|
17
|
+
export declare function rotateMetricsIfNeeded(dir: string): void;
|
|
18
|
+
/** Append one JSONL row, stamping `ts` in unix ms when absent; creates the dir with mode 0700. Never throws. */
|
|
19
|
+
export declare function appendMetricRow(dir: string, row: Record<string, unknown>): void;
|
|
20
|
+
/** Read rotated generations oldest-first plus the live log, skipping missing files and malformed lines. */
|
|
21
|
+
export declare function readMetricRows(dir: string): unknown[];
|
|
22
|
+
/** Characters of a body's description that stay in the preview. The message row
|
|
23
|
+
* keeps every character, so this bounds what the summarizer reads, not what is
|
|
24
|
+
* retained. */
|
|
25
|
+
export declare const FILE_PREVIEW_CHARS = 1200;
|
|
26
|
+
export declare const FILE_KINDS: readonly ["json", "jsonl", "csv", "tsv", "code", "text"];
|
|
27
|
+
export type FileKind = (typeof FILE_KINDS)[number];
|
|
28
|
+
export declare function isFileKind(value: unknown): value is FileKind;
|
|
29
|
+
export interface FileDescriptor {
|
|
30
|
+
fileId: string;
|
|
31
|
+
path: string;
|
|
32
|
+
/** Characters retained in the message, not bytes on disk. */
|
|
33
|
+
bytes: number;
|
|
34
|
+
sha256: string;
|
|
35
|
+
kind: FileKind;
|
|
36
|
+
preview: string;
|
|
8
37
|
}
|
|
9
|
-
export
|
|
38
|
+
export declare function fileHandle(d: FileDescriptor): string;
|
|
39
|
+
export declare function filePreviewText(d: FileDescriptor): string;
|
|
40
|
+
export declare function describeFile(path: string, text: string): FileDescriptor;
|
|
41
|
+
/** What the body is, decided by structure rather than by extension alone: a
|
|
42
|
+
* `.ts` file holding a JSON array is JSON, and a `.txt` file holding JSON is
|
|
43
|
+
* JSON too. The parse is tried before the extension for that reason. */
|
|
44
|
+
export declare function kindOf(path: string, text: string): FileKind;
|
|
45
|
+
export declare function structureOf(kind: FileKind, path: string, text: string): string;
|
|
46
|
+
export declare function previewOf(kind: FileKind, path: string, text: string): string;
|
|
47
|
+
/** One row the pattern path scans. The text is the stored message, whole: the
|
|
48
|
+
* FTS index holds a prefix, and reading only that is the defect this path
|
|
49
|
+
* exists to fix. */
|
|
50
|
+
export interface ScannedRow {
|
|
51
|
+
rowid: number;
|
|
10
52
|
entryId: string;
|
|
11
53
|
role: string;
|
|
12
54
|
text: string;
|
|
13
|
-
|
|
14
|
-
* (`payloadOf`). Carried so ingest stores the same bytes at every call site. */
|
|
15
|
-
payload?: string;
|
|
16
|
-
fileHint?: {
|
|
17
|
-
path: string;
|
|
18
|
-
};
|
|
55
|
+
removed: boolean;
|
|
19
56
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
57
|
+
/** A row that matched, with the first match's position and how many the row
|
|
58
|
+
* held. `partial` is set when the row was longer than the per-row cap, so a
|
|
59
|
+
* reader knows the scan stopped before the end of that row. */
|
|
60
|
+
export interface PatternHit {
|
|
61
|
+
row: ScannedRow;
|
|
62
|
+
matches: number;
|
|
63
|
+
at: number;
|
|
64
|
+
partial: boolean;
|
|
65
|
+
scannedChars: number;
|
|
66
|
+
}
|
|
67
|
+
export type PatternStop = "end" | "deadline" | "page";
|
|
68
|
+
export interface PatternScanOutcome {
|
|
69
|
+
hits: PatternHit[];
|
|
70
|
+
rows: number;
|
|
71
|
+
partialRows: number;
|
|
72
|
+
more: boolean;
|
|
73
|
+
stopped: PatternStop;
|
|
74
|
+
lastRowId?: number;
|
|
75
|
+
lastEntryId?: string;
|
|
76
|
+
}
|
|
77
|
+
export interface PatternScanOptions {
|
|
78
|
+
pattern: CompiledPattern;
|
|
79
|
+
offset: number;
|
|
80
|
+
limit: number;
|
|
81
|
+
/** Characters of one row the scan reads. Above the FTS prefix on purpose,
|
|
82
|
+
* below the size of an externalized body, and named on the hit when it bites. */
|
|
83
|
+
perRowChars: number;
|
|
84
|
+
deadlineAt: number;
|
|
85
|
+
now: () => number;
|
|
86
|
+
}
|
|
87
|
+
/** The characters a caller can hand a regular expression. A row longer than this
|
|
88
|
+
* is read up to the cap and the hit says so: a partial scan that looks complete
|
|
89
|
+
* is the same defect as an index that stops at 100,000 characters. */
|
|
90
|
+
export declare const PATTERN_ROW_CHARS = 200000;
|
|
91
|
+
/** How long one pattern call may scan. Long enough for a session's messages at
|
|
92
|
+
* 200,000 characters a row, short enough that a pathological pattern is a slow
|
|
93
|
+
* call rather than a hung session. */
|
|
94
|
+
export declare const PATTERN_DEADLINE_MS = 1000;
|
|
95
|
+
export declare const PATTERN_PAGE_ROWS = 200;
|
|
96
|
+
/** Matches counted in one row before the scan moves on. A row that mentions a
|
|
97
|
+
* path a thousand times is one hit line, and counting the thousandth costs the
|
|
98
|
+
* deadline that the next row needs. */
|
|
99
|
+
export declare const MAX_MATCHES_PER_ROW = 100;
|
|
100
|
+
export declare function scanRows(rows: Iterable<ScannedRow>, opts: PatternScanOptions): PatternScanOutcome;
|
|
101
|
+
/** A pattern this module compiled and validated, so a scan cannot receive a
|
|
102
|
+
* caller's own stateful `RegExp`. */
|
|
103
|
+
export type CompiledPattern = RegExp & {
|
|
104
|
+
readonly __brand: "CompiledPattern";
|
|
105
|
+
};
|
|
106
|
+
export type PatternCompileResult = {
|
|
107
|
+
ok: true;
|
|
108
|
+
pattern: CompiledPattern;
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
reason: string;
|
|
112
|
+
};
|
|
113
|
+
/** Compile a caller's pattern, with `g` for iteration and `i` unless the caller
|
|
114
|
+
* asked for case sensitivity, which is the reading the FTS path already has. A
|
|
115
|
+
* refusal is returned before any row is read. */
|
|
116
|
+
export declare function compilePattern(pattern: string, caseSensitive: boolean): PatternCompileResult;
|
|
117
|
+
export type LcmRole = "user" | "assistant" | "toolResult" | "custom";
|
|
118
|
+
/** What a stored summary's text is worth: `clean` came from an unclipped source
|
|
119
|
+
* of clean children, `clipped` lost source before the model saw it, `mechanical`
|
|
120
|
+
* is the deterministic truncate, and `derived` was built from a non-clean source.
|
|
121
|
+
* The value is durable so a failed ancestor stays discoverable after the child
|
|
122
|
+
* that made it stale has been repaired. */
|
|
123
|
+
export type SummaryQuality = "clean" | "clipped" | "mechanical" | "derived";
|
|
124
|
+
export declare function isSummaryQuality(value: unknown): value is SummaryQuality;
|
|
125
|
+
/** The level-3 ladder stores this prefix ahead of the mechanical truncate. It is
|
|
126
|
+
* a storage marker, so a store migration can recognise predated rows without
|
|
127
|
+
* running the summarizer. */
|
|
128
|
+
export declare const TRUNCATED_SUMMARY_PREFIX = "[lcm:truncated]";
|
|
129
|
+
/** Any role string Pi may emit (custom, branchSummary, bashExecution...) folds to the four stored roles. */
|
|
130
|
+
export declare function normalizeRole(r: string): LcmRole;
|
|
131
|
+
export interface StoredMessage {
|
|
132
|
+
id: number;
|
|
133
|
+
entryId: string;
|
|
134
|
+
role: LcmRole;
|
|
135
|
+
text: string;
|
|
136
|
+
tokens: number;
|
|
30
137
|
timestamp: number;
|
|
138
|
+
/** JSON of the content blocks, present only when `text` is not a faithful
|
|
139
|
+
* rendering of them (a thinking block, an image, a signature). Absent means
|
|
140
|
+
* the derived text is the whole message. */
|
|
141
|
+
payload?: string;
|
|
142
|
+
/** Set when the entry has left the active branch. The bytes stay; search
|
|
143
|
+
* excludes the row unless a caller asks for it. */
|
|
144
|
+
removedAt?: number;
|
|
31
145
|
}
|
|
32
|
-
export
|
|
33
|
-
|
|
146
|
+
export interface GrepHit {
|
|
147
|
+
entryId: string;
|
|
148
|
+
role: LcmRole;
|
|
149
|
+
text: string;
|
|
150
|
+
/** The entry has left the active branch; its text is retained. */
|
|
151
|
+
removed?: boolean;
|
|
152
|
+
coveringSummaryId?: number;
|
|
153
|
+
}
|
|
154
|
+
export interface SummaryNode {
|
|
34
155
|
id: number;
|
|
156
|
+
kind: "leaf" | "condensed";
|
|
157
|
+
text: string;
|
|
158
|
+
tokens: number;
|
|
35
159
|
depth: number;
|
|
36
160
|
firstEntryId: string;
|
|
37
161
|
lastEntryId: string;
|
|
38
|
-
|
|
162
|
+
createdAt: number;
|
|
163
|
+
quality: SummaryQuality;
|
|
164
|
+
/** A richer sibling text the ladder produced and did not store as primary,
|
|
165
|
+
* so the projection can serve more detail when the budget has room. */
|
|
39
166
|
thoroughText?: string;
|
|
40
|
-
|
|
167
|
+
thoroughTokens?: number;
|
|
41
168
|
}
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
169
|
+
export interface IntegritySnapshot {
|
|
170
|
+
ftsOk: boolean;
|
|
171
|
+
messageRows: number;
|
|
172
|
+
shapes: Array<{
|
|
173
|
+
id: number;
|
|
174
|
+
kind: "leaf" | "condensed";
|
|
175
|
+
depth: number;
|
|
176
|
+
messages: number;
|
|
177
|
+
children: number;
|
|
178
|
+
}>;
|
|
179
|
+
spans: Array<{
|
|
180
|
+
id: number;
|
|
181
|
+
first: string;
|
|
182
|
+
last: string;
|
|
183
|
+
firstRowid: number | null;
|
|
184
|
+
lastRowid: number | null;
|
|
185
|
+
}>;
|
|
186
|
+
duplicateSpans: Array<{
|
|
187
|
+
first: string;
|
|
188
|
+
last: string;
|
|
189
|
+
kind: string;
|
|
190
|
+
count: number;
|
|
191
|
+
}>;
|
|
192
|
+
orphanChildParents: number[];
|
|
193
|
+
deadRuns: Array<{
|
|
194
|
+
session: string | null;
|
|
195
|
+
rows: number[];
|
|
196
|
+
}>;
|
|
197
|
+
untrackedPending: number[];
|
|
198
|
+
foreignKeyViolations: number;
|
|
199
|
+
schemaVersion: number;
|
|
200
|
+
uncoveredMessages: number;
|
|
201
|
+
}
|
|
202
|
+
/** A fresh row, or the row that already owns the span; `existingTokens` comes
|
|
203
|
+
* from that row, so a caller reports the collision, not its intended write.
|
|
204
|
+
* `own` says whether the run that asked may count this row as its own and replace
|
|
205
|
+
* its text: a row it wrote, a committed row, or its own pending row. Another live
|
|
206
|
+
* run's pending row is none of those. */
|
|
207
|
+
export type InsertSummaryResult = {
|
|
50
208
|
id: number;
|
|
51
|
-
|
|
209
|
+
created: true;
|
|
210
|
+
own: true;
|
|
211
|
+
} | {
|
|
212
|
+
id: number;
|
|
213
|
+
created: false;
|
|
214
|
+
own: boolean;
|
|
215
|
+
existingTokens: number;
|
|
216
|
+
/** The colliding row's summary text. A caller whose view cannot read the row
|
|
217
|
+
* still has to answer for the span it was given, and this is that span's
|
|
218
|
+
* summary to reuse. */
|
|
219
|
+
existingText: string;
|
|
220
|
+
foreignLive: boolean;
|
|
221
|
+
};
|
|
222
|
+
/** What a caller hands `insertSummary`. `run` stamps the row pending for that run;
|
|
223
|
+
* without it the row is committed memory. */
|
|
224
|
+
export type InsertSummaryInput = {
|
|
225
|
+
kind: "leaf" | "condensed";
|
|
52
226
|
text: string;
|
|
53
|
-
|
|
227
|
+
tokens: number;
|
|
228
|
+
depth: number;
|
|
54
229
|
firstEntryId: string;
|
|
55
230
|
lastEntryId: string;
|
|
56
|
-
|
|
231
|
+
/** Defaults to `clean`: a caller that does not know the provenance quality
|
|
232
|
+
* must not invent a loss the store would then try to repair. */
|
|
233
|
+
quality?: SummaryQuality;
|
|
234
|
+
messageIds?: number[];
|
|
235
|
+
childSummaryIds?: number[];
|
|
236
|
+
fileIds?: string[];
|
|
237
|
+
thoroughText?: string;
|
|
238
|
+
run?: string;
|
|
239
|
+
};
|
|
240
|
+
/** What a compaction pass needs from the store: the committed memory plus, when
|
|
241
|
+
* the pass reads through `LcmStore.forRun`, the rows of its own run. `LcmStore`
|
|
242
|
+
* satisfies this with the committed-only meaning. */
|
|
243
|
+
export interface PassStore extends IngestStore {
|
|
244
|
+
/** The branch range a set of entry ids occupies: the oldest and the newest row
|
|
245
|
+
* the store holds among them, or null when it holds none. Pi's context array
|
|
246
|
+
* is not branch order (it hoists the newest compaction entry to the front),
|
|
247
|
+
* while a span is a row range, so the store, whose rows are appended in
|
|
248
|
+
* branch order, is what decides which end comes first. */
|
|
249
|
+
spanBounds(entryIds: readonly string[]): {
|
|
250
|
+
firstEntryId: string;
|
|
251
|
+
lastEntryId: string;
|
|
252
|
+
} | null;
|
|
253
|
+
uncoveredMessagesInSpan(firstEntryId: string, lastEntryId: string): StoredMessage[];
|
|
254
|
+
filesForMessages(ids: readonly number[]): Array<FileDescriptor & {
|
|
255
|
+
messageId: number;
|
|
256
|
+
}>;
|
|
257
|
+
frontier(firstEntryId: string, lastEntryId: string): SummaryNode[];
|
|
258
|
+
getSummary(id: number): SummaryNode | undefined;
|
|
259
|
+
parentSummaryIds(summaryId: number): number[];
|
|
260
|
+
fileIdsForSummaries(ids: readonly number[]): string[];
|
|
261
|
+
/** Stored messages in row order, so a pass can re-read the exact source a
|
|
262
|
+
* leaf's provenance names. */
|
|
263
|
+
messagesByIds(ids: readonly number[]): StoredMessage[];
|
|
264
|
+
insertSummary(s: InsertSummaryInput): InsertSummaryResult;
|
|
265
|
+
/** `quality` is optional so a caller rewriting text it did not derive keeps
|
|
266
|
+
* the stored verdict; a replacement that knows its source passes it. */
|
|
267
|
+
updateSummaryText(id: number, text: string, tokens: number, quality?: SummaryQuality): void;
|
|
268
|
+
setThoroughText(id: number, thoroughText: string): void;
|
|
57
269
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
summaryId: number;
|
|
64
|
-
appliedAtOccupancy: number;
|
|
65
|
-
tailKey: string;
|
|
270
|
+
/** A node's provenance split into the two child types, each in the order the
|
|
271
|
+
* source ran: messages by row, child summaries by the row their span starts at. */
|
|
272
|
+
export interface SummarySources {
|
|
273
|
+
messages: number[];
|
|
274
|
+
summaries: number[];
|
|
66
275
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
export
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
export declare function nextAction(state: CommitState | null, occupancy: number, swapRatio: number, recutRatio: number): AssemblyAction;
|
|
92
|
-
export declare function cutMayReplace(previous: CommitState | null, newCutCount: number): boolean;
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/metrics.d.ts
|
|
95
|
-
export declare function metricsPath(): string;
|
|
96
|
-
/** The metrics log, oldest generation first, so a window longer than one
|
|
97
|
-
* generation still reads as one history. */
|
|
98
|
-
export declare function metricsLogFiles(): string[];
|
|
99
|
-
export declare function rotateIfNeeded(path: string, maxBytes: number): void;
|
|
100
|
-
export declare function pruneGenerations(path: string, keep: number): void;
|
|
101
|
-
export declare function appendMetric(record: MetricRecord): void;
|
|
102
|
-
export interface UsageLike {
|
|
103
|
-
input?: unknown;
|
|
104
|
-
output?: unknown;
|
|
105
|
-
cacheRead?: unknown;
|
|
106
|
-
cacheWrite?: unknown;
|
|
107
|
-
cost?: {
|
|
108
|
-
input?: unknown;
|
|
109
|
-
output?: unknown;
|
|
110
|
-
total?: unknown;
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
interface MetricBase {
|
|
114
|
-
/** Session tag. Absent on rows written outside a session (`gc`, `import`). */
|
|
115
|
-
session?: string;
|
|
276
|
+
/** Replace a node's text only if it still holds the text the caller read. The
|
|
277
|
+
* rich tier is cleared in the same statement: it described the old source. */
|
|
278
|
+
export interface SummaryTextCas {
|
|
279
|
+
id: number;
|
|
280
|
+
expectedText: string;
|
|
281
|
+
expectedTokens: number;
|
|
282
|
+
text: string;
|
|
283
|
+
tokens: number;
|
|
284
|
+
/** The verdict for the new text. A repair always writes `clean`. */
|
|
285
|
+
quality: SummaryQuality;
|
|
286
|
+
}
|
|
287
|
+
/** The store surface a heal run needs. It is a `PassStore` plus the committed
|
|
288
|
+
* scan and compare-and-swap write that a repair uses, so a heal cannot write
|
|
289
|
+
* through a run view whose pending rows the projection does not serve. */
|
|
290
|
+
export interface HealStore extends PassStore {
|
|
291
|
+
/** Committed nodes whose quality is not clean, or whose text carries the
|
|
292
|
+
* mechanical prefix as a fallback for imported and predated rows. */
|
|
293
|
+
taintedSummaries(prefix: string): SummaryNode[];
|
|
294
|
+
summarySources(summaryId: number): SummarySources;
|
|
295
|
+
casUpdateSummaryText(update: SummaryTextCas): boolean;
|
|
296
|
+
/** Mark a clean node stale because a child changed under it. A node already
|
|
297
|
+
* non-clean keeps its verdict: mechanical and clipped are facts about its own
|
|
298
|
+
* text, and derived is already the retry marker. */
|
|
299
|
+
markSummaryDerived(id: number): boolean;
|
|
116
300
|
}
|
|
117
|
-
export
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
};
|
|
130
|
-
export type UsageCounters = {
|
|
131
|
-
input: number | null;
|
|
132
|
-
output: number | null;
|
|
133
|
-
cacheRead: number | null;
|
|
134
|
-
cacheWrite: number | null;
|
|
135
|
-
costInput: number | null;
|
|
136
|
-
costOutput: number | null;
|
|
137
|
-
costTotal: number | null;
|
|
138
|
-
};
|
|
139
|
-
/** The counters to put on a row, or `undefined` when the provider billed
|
|
140
|
-
* nothing (an aborted call, or a provider that reports no usage). */
|
|
141
|
-
export declare function billedCounters(usage: UsageLike | undefined): UsageCounters | undefined;
|
|
142
|
-
/** One metrics row per measured assistant message: no numeric counter at all
|
|
143
|
-
* (aborted turn, legacy row) yields `undefined`, so it does not count as a turn
|
|
144
|
-
* in the report's cache-economics averages. */
|
|
145
|
-
export declare function usageRecord(usage: UsageLike | undefined): UsageRow | undefined;
|
|
146
|
-
export type SummarizerStage = "blocking" | "async";
|
|
147
|
-
/** Why a summarizer call failed, as far as the provider's own text says. A
|
|
148
|
-
* closed set, and `unknown` is a real answer: a classification that guesses is
|
|
149
|
-
* worse than a bucket that says it could not tell, and the raw message stays on
|
|
150
|
-
* the row either way. `capped` is not a provider failure: it is the pass giving
|
|
151
|
-
* up after too many of them, and the category beside it says which one. */
|
|
152
|
-
export type SummarizerFailureCategory = "auth" | "rate-limit" | "timeout" | "unavailable" | "length" | "empty" | "capped" | "unknown";
|
|
153
|
-
/** `ok` answered with usable text, `empty` answered with none, `failure` was rejected. */
|
|
154
|
-
export type SummarizerOutcome = "ok" | "empty" | "failure";
|
|
155
|
-
export interface SummarizerUsageOptions {
|
|
156
|
-
/** The resolved model key, as `modelKey` renders it. */
|
|
157
|
-
model: string;
|
|
158
|
-
stage?: SummarizerStage;
|
|
159
|
-
outcome: SummarizerOutcome;
|
|
160
|
-
/** The provider's own stop word, so `length` and `error` are told apart. */
|
|
161
|
-
stopReason?: string;
|
|
162
|
-
session?: string;
|
|
301
|
+
export interface IngestStore {
|
|
302
|
+
hasEntry(entryId: string): boolean;
|
|
303
|
+
insertMessage(m: {
|
|
304
|
+
entryId: string;
|
|
305
|
+
role: LcmRole;
|
|
306
|
+
text: string;
|
|
307
|
+
tokens: number;
|
|
308
|
+
timestamp: number;
|
|
309
|
+
payload?: string;
|
|
310
|
+
}): number;
|
|
311
|
+
insertFile(d: FileDescriptor, firstEntryId: string): void;
|
|
312
|
+
linkMessageFile(messageId: number, fileId: string): void;
|
|
163
313
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
export type UsageRow = MetricBase & UsageCounters & {
|
|
178
|
-
event: "usage";
|
|
179
|
-
};
|
|
180
|
-
interface LcmRowBase extends MetricBase {
|
|
181
|
-
event: "lcm";
|
|
314
|
+
/** Characters of a message the full-text index covers. Storage is verbatim, so
|
|
315
|
+
* this bounds the index and not the row. */
|
|
316
|
+
export declare const FTS_PREFIX_CHARS = 100000;
|
|
317
|
+
/** First published schema: pre-publish development stores were wiped, so every
|
|
318
|
+
* store in the wild starts here and later generations count from this one.
|
|
319
|
+
* Generation 2 adds the summary quality verdict. */
|
|
320
|
+
export declare const SCHEMA_VERSION = 2;
|
|
321
|
+
/** A store left behind by an older build. A read-only open refuses it rather than
|
|
322
|
+
* migrating it: a search must not rewrite a file it is only reading, and a store
|
|
323
|
+
* that cannot answer this build's statements has to be a reported skip. */
|
|
324
|
+
export declare class StaleStoreError extends Error {
|
|
325
|
+
readonly generation: number;
|
|
326
|
+
constructor(generation: number);
|
|
182
327
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
commit: number;
|
|
191
|
-
zone?: string;
|
|
192
|
-
committed: boolean;
|
|
193
|
-
/** Present when `tokens` is the estimator's count rather than Pi's: the
|
|
194
|
-
* reported figure described a context other than the one on hand. */
|
|
195
|
-
tokensEstimated?: true;
|
|
196
|
-
};
|
|
197
|
-
export type ProjectionOverBudgetRow = LcmRowBase & {
|
|
198
|
-
kind: "projection-over-budget";
|
|
199
|
-
nodes: number;
|
|
200
|
-
renderedTokens: number;
|
|
201
|
-
budgetTokens: number;
|
|
202
|
-
};
|
|
203
|
-
export type SwapAppliedRow = LcmRowBase & {
|
|
204
|
-
kind: "swap-applied";
|
|
205
|
-
summaryId: number;
|
|
206
|
-
cutCount: number;
|
|
207
|
-
distinctBoundary: boolean;
|
|
208
|
-
appliedAtOccupancy: number;
|
|
209
|
-
nodes: number;
|
|
210
|
-
stubbed: number;
|
|
211
|
-
renderedTokens: number;
|
|
212
|
-
budgetTokens: number;
|
|
213
|
-
};
|
|
214
|
-
/**
|
|
215
|
-
* A swap the policy did not commit. `tokens` is the occupancy that triggered
|
|
216
|
-
* the bail; the kick-backoff writer names the entry it declined to kick instead.
|
|
217
|
-
*/
|
|
218
|
-
export type SwapBlockedRow = LcmRowBase & {
|
|
219
|
-
kind: "swap-blocked";
|
|
220
|
-
reason: string;
|
|
221
|
-
occupancy: number;
|
|
222
|
-
tokens?: number;
|
|
223
|
-
entryId?: string;
|
|
224
|
-
};
|
|
225
|
-
export type PiCompactionRow = LcmRowBase & {
|
|
226
|
-
kind: "pi-compaction";
|
|
227
|
-
entryId: string;
|
|
228
|
-
tokens?: number;
|
|
229
|
-
window?: number;
|
|
230
|
-
};
|
|
231
|
-
/** One calibration sample against Pi's own count: the characters this build
|
|
232
|
-
* counted for the growth of the context, the tokens Pi reported for it, and the
|
|
233
|
-
* ratio the samples now support. A null ratio is the answer "cannot say", which is
|
|
234
|
-
* why the row carries the sample beside it. */
|
|
235
|
-
export type EstimateCalibratedRow = LcmRowBase & {
|
|
236
|
-
kind: "estimate-calibrated";
|
|
237
|
-
chars: number;
|
|
238
|
-
tokens: number;
|
|
328
|
+
/** What a store knows about a model rather than about a session: the token ratio
|
|
329
|
+
* measured for it against Pi's reported usage, and the context window a provider
|
|
330
|
+
* stated when it refused an over-window request. A null ratio is a measurement that
|
|
331
|
+
* disagree with itself, which is a state the estimator reads as "keep the
|
|
332
|
+
* constant" and the report names. A null window is a model no provider has
|
|
333
|
+
* corrected, which leaves Pi's belief in place. */
|
|
334
|
+
export interface ModelState {
|
|
239
335
|
charsPerToken: number | null;
|
|
240
336
|
samples: number;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
export
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
337
|
+
contextWindow: number | null;
|
|
338
|
+
windowSource: string | null;
|
|
339
|
+
}
|
|
340
|
+
export declare const WINDOW_SOURCE_PROVIDER = "provider-error";
|
|
341
|
+
export declare class LcmStore {
|
|
342
|
+
private db;
|
|
343
|
+
private dbPath;
|
|
344
|
+
/** Opened for reading a past session's store: no migration ran, so `generation`
|
|
345
|
+
* is the file's own and a caller may have to skip it. */
|
|
346
|
+
private readOnly;
|
|
347
|
+
/** The schema generation this file carries, which read-only opens do not
|
|
348
|
+
* change. A writer holds `SCHEMA_VERSION` by the time its constructor returns. */
|
|
349
|
+
readonly generation: number;
|
|
350
|
+
/** True while a `transaction` callback is running, so a reentrant call joins
|
|
351
|
+
* the open transaction instead of nesting one, which SQLite rejects. */
|
|
352
|
+
private inTransaction;
|
|
353
|
+
/** Tokens of the runs this handle has open. Two runs can overlap inside one
|
|
354
|
+
* handle, because the blocking path stops waiting for the async pass, so a
|
|
355
|
+
* token names a run and not the handle. Whether a run this handle does not
|
|
356
|
+
* hold is still running is read from the `runs` table, never from here. */
|
|
357
|
+
private liveTokens;
|
|
358
|
+
private reapedInRun;
|
|
359
|
+
private stmts;
|
|
360
|
+
constructor(dbPath: string, opts?: {
|
|
361
|
+
readOnly?: boolean;
|
|
362
|
+
queryOnly?: boolean;
|
|
363
|
+
});
|
|
364
|
+
/** Close the handle: no run of this handle survives it, and the connection is
|
|
365
|
+
* released even when a run could not be abandoned, so a failure here cannot
|
|
366
|
+
* leave a connection and a set of invisible rows behind. */
|
|
367
|
+
close(): void;
|
|
368
|
+
/** Open a run: rows written with its token are pending until `commitRun`, and
|
|
369
|
+
* the row this writes is what lets another handle tell it from a run whose
|
|
370
|
+
* writer is gone. Reaps what such a run left, which this handle cannot be. */
|
|
371
|
+
openRun(opts?: {
|
|
372
|
+
session?: string;
|
|
373
|
+
}): {
|
|
374
|
+
token: string;
|
|
375
|
+
dropped: number;
|
|
376
|
+
};
|
|
377
|
+
/** True while any run is open whose writer is still there, this handle's or
|
|
378
|
+
* another process's, which is what an import has to wait for. */
|
|
379
|
+
hasLiveRun(): boolean;
|
|
380
|
+
/** Commit a run: drop its pending rows whose child no longer resolves, then flip
|
|
381
|
+
* the rest. One transaction, so a reader never sees a half-committed run, and
|
|
382
|
+
* idempotent for a token this handle no longer holds. */
|
|
383
|
+
commitRun(token: string): {
|
|
384
|
+
committed: number;
|
|
385
|
+
dropped: number;
|
|
386
|
+
};
|
|
387
|
+
/** Delete a run's pending rows and close it. Idempotent, so a `finally` can call
|
|
388
|
+
* it after a commit has already closed the run. */
|
|
389
|
+
abandonRun(token: string): number;
|
|
390
|
+
/** A view of this store for one run: the committed memory plus that run's own
|
|
391
|
+
* rows, which is what a pass must read to see its own writes. */
|
|
392
|
+
forRun(token: string): PassStore;
|
|
393
|
+
/** True while the run that wrote a row is still going somewhere, as far as this
|
|
394
|
+
* store can tell. A run this handle holds is live; a run written by this
|
|
395
|
+
* process is live, because a pid cannot separate two handles in one process and
|
|
396
|
+
* refusing to delete is the safe answer; a run written by another process is
|
|
397
|
+
* live while its pid is. A row whose run has no record at all was written
|
|
398
|
+
* without one, which no open path does, so nothing owns it. */
|
|
399
|
+
private isRunLive;
|
|
400
|
+
/** Delete what a run left behind whose writer is gone, and its record with it.
|
|
401
|
+
* A live owner's rows are never touched, whatever handle asks, which is what
|
|
402
|
+
* makes a second handle safe to open. No reader can see the rows this removes,
|
|
403
|
+
* and each one holds the span it claims. */
|
|
404
|
+
private reapPending;
|
|
405
|
+
/** Delete this run's pending rows whose child no longer exists, deepest first: a
|
|
406
|
+
* parent that loses a child must not leave a grandparent behind. A child can go
|
|
407
|
+
* away between the frontier read that selected it and this commit. */
|
|
408
|
+
private dropUnresolvable;
|
|
409
|
+
hasEntry(entryId: string): boolean;
|
|
410
|
+
insertMessage(m: {
|
|
411
|
+
entryId: string;
|
|
412
|
+
role: LcmRole;
|
|
413
|
+
text: string;
|
|
414
|
+
tokens: number;
|
|
415
|
+
timestamp: number;
|
|
416
|
+
payload?: string;
|
|
417
|
+
}): number;
|
|
418
|
+
/** One write transaction around a group of statements, so a compaction is
|
|
419
|
+
* atomic: the node row, its provenance rows and its file links either all
|
|
420
|
+
* land or none do. `node:sqlite` ships no transaction helper, so the
|
|
421
|
+
* statements are explicit. A reentrant call joins the open transaction: the
|
|
422
|
+
* caller that opened it owns the commit, so an inner throw rolls the whole
|
|
423
|
+
* group back rather than leaving a half-applied write. */
|
|
424
|
+
transaction<T>(fn: () => T): T;
|
|
425
|
+
insertSummary(s: InsertSummaryInput): InsertSummaryResult;
|
|
426
|
+
insertFile(d: FileDescriptor, firstEntryId: string): void;
|
|
427
|
+
linkMessageFile(messageId: number, fileId: string): void;
|
|
428
|
+
filesForMessages(ids: readonly number[]): Array<FileDescriptor & {
|
|
429
|
+
messageId: number;
|
|
430
|
+
}>;
|
|
431
|
+
fileIdsForMessages(ids: readonly number[]): string[];
|
|
432
|
+
fileIdsForSummaries(ids: readonly number[]): string[];
|
|
433
|
+
filesForSummaries(ids: readonly number[]): FileDescriptor[];
|
|
434
|
+
allFiles(): Array<FileDescriptor & {
|
|
435
|
+
entryIds: string[];
|
|
436
|
+
}>;
|
|
437
|
+
/** Replace a node's text in place: the span, provenance, and created_at stay.
|
|
438
|
+
* A caller that knows the new text's verdict passes it; a caller that does not
|
|
439
|
+
* leaves the stored verdict alone. */
|
|
440
|
+
updateSummaryText(id: number, text: string, tokens: number, quality?: SummaryQuality): void;
|
|
441
|
+
setThoroughText(id: number, thoroughText: string): void;
|
|
442
|
+
messagesInSpan(firstEntryId: string, lastEntryId: string): StoredMessage[];
|
|
443
|
+
spanBounds(entryIds: readonly string[]): {
|
|
444
|
+
firstEntryId: string;
|
|
445
|
+
lastEntryId: string;
|
|
446
|
+
} | null;
|
|
447
|
+
uncoveredMessagesInSpan(firstEntryId: string, lastEntryId: string, run?: string): StoredMessage[];
|
|
448
|
+
/** Minimal top-level cover: no summary's child, oldest first, dropping any node
|
|
449
|
+
* whose span sits inside the previous kept one, which is what keeps the cover
|
|
450
|
+
* non-nested: condensation's group span must be one no row already holds. */
|
|
451
|
+
frontier(firstEntryId: string, lastEntryId: string, run?: string): SummaryNode[];
|
|
452
|
+
grep(pattern: string, opts?: {
|
|
453
|
+
limit?: number;
|
|
454
|
+
offset?: number;
|
|
455
|
+
withinMessageIds?: ReadonlySet<number>;
|
|
456
|
+
includeRemoved?: boolean;
|
|
457
|
+
}): GrepHit[];
|
|
458
|
+
grepCount(pattern: string, opts?: {
|
|
459
|
+
withinMessageIds?: ReadonlySet<number>;
|
|
460
|
+
includeRemoved?: boolean;
|
|
461
|
+
}): number | undefined;
|
|
462
|
+
messagePage(afterRowId: number, limit: number, opts?: {
|
|
463
|
+
withinMessageIds?: ReadonlySet<number>;
|
|
464
|
+
includeRemoved?: boolean;
|
|
465
|
+
}): ScannedRow[];
|
|
466
|
+
coveringSummaryId(entryId: string): number | undefined;
|
|
467
|
+
messageByEntryId(entryId: string): StoredMessage | undefined;
|
|
468
|
+
parentSummaryIds(summaryId: number, run?: string): number[];
|
|
469
|
+
childSummaryIds(summaryId: number): number[];
|
|
470
|
+
taintedSummaries(prefix: string): SummaryNode[];
|
|
471
|
+
summarySources(summaryId: number): SummarySources;
|
|
472
|
+
/** One statement, so the text and its token count move together and a row
|
|
473
|
+
* another writer changed is reported as not updated rather than overwritten. */
|
|
474
|
+
casUpdateSummaryText(update: SummaryTextCas): boolean;
|
|
475
|
+
/** A clean node whose child changed becomes `derived`; a node that already
|
|
476
|
+
* carries a stronger verdict keeps it. Returns true when this call wrote. */
|
|
477
|
+
markSummaryDerived(id: number): boolean;
|
|
478
|
+
getSummary(id: number, run?: string): SummaryNode | undefined;
|
|
479
|
+
/** Bounded by a visited set, so a corrupt cycle cannot recurse forever. */
|
|
480
|
+
coveredMessageIds(summaryId: number, seen?: Set<number>): number[];
|
|
481
|
+
integritySnapshot(): IntegritySnapshot;
|
|
482
|
+
/** Restore the full-text index. The schema is re-applied first, because a
|
|
483
|
+
* dropped virtual table cannot be rebuilt, only recreated, and the DDL that
|
|
484
|
+
* knows how is the schema itself. Everything in it is `IF NOT EXISTS` apart
|
|
485
|
+
* from the two triggers, which are dropped and recreated on purpose. */
|
|
486
|
+
rebuildFts(): void;
|
|
487
|
+
deleteOrphanSpans(): number;
|
|
488
|
+
deleteOrphanChildren(): number;
|
|
489
|
+
reapDeadRuns(): number;
|
|
490
|
+
/** When one span is claimed twice, the row that carries provenance wins over
|
|
491
|
+
* one that cannot be expanded, and the newest wins between equals. */
|
|
492
|
+
dedupeSpans(): number;
|
|
493
|
+
/** Mark entries that left the active branch, or clear the mark when a
|
|
494
|
+
* navigation brings them back. Returns the rows the call actually changed, so
|
|
495
|
+
* a metric can tell a real change from a no-op reconciliation. */
|
|
496
|
+
setRemoved(entryIds: readonly string[], removedAt: number | null): number;
|
|
497
|
+
removedMessageCount(): number;
|
|
498
|
+
messagesByIds(ids: readonly number[]): StoredMessage[];
|
|
499
|
+
allMessages(): StoredMessage[];
|
|
500
|
+
entrySpan(): {
|
|
501
|
+
first: string;
|
|
502
|
+
last: string;
|
|
503
|
+
} | undefined;
|
|
504
|
+
coveredSize(summaryId: number): {
|
|
505
|
+
tokens: number;
|
|
506
|
+
messages: number;
|
|
507
|
+
};
|
|
508
|
+
allSummaries(): SummaryNode[];
|
|
509
|
+
messageCount(): number;
|
|
510
|
+
/** Number of stored messages whose entry id starts with `prefix`. Used by
|
|
511
|
+
* status reporting to count a family of rows (for example compaction marks
|
|
512
|
+
* and summaries) without scanning the table. */
|
|
513
|
+
entryCountWithPrefix(prefix: string): number;
|
|
514
|
+
backup(backupPath: string): void;
|
|
515
|
+
/** The calibration recorded for a model. `undefined` is a model nothing has
|
|
516
|
+
* sampled; a null ratio is one whose samples disagreed, which the caller reads
|
|
517
|
+
* as "keep the constant" rather than "no data". */
|
|
518
|
+
getModelState(modelKey: string): ModelState | undefined;
|
|
519
|
+
setModelState(modelKey: string, state: Pick<ModelState, "charsPerToken" | "samples">): void;
|
|
520
|
+
/** Records the window a provider stated. Its own statement rather than part of
|
|
521
|
+
* `setModelState`, because the two are learned at different moments: a later
|
|
522
|
+
* calibration must not clear a window, and a window must not touch samples. */
|
|
523
|
+
setModelWindow(modelKey: string, contextWindow: number, source: string): void;
|
|
524
|
+
stats(): {
|
|
525
|
+
messages: number;
|
|
526
|
+
summaries: number;
|
|
527
|
+
dbBytes: number;
|
|
528
|
+
payloadMessages: number;
|
|
529
|
+
payloadBytes: number;
|
|
530
|
+
files: number;
|
|
531
|
+
fileBytes: number;
|
|
532
|
+
removedMessages: number;
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
/** Reply when the caller has no store: nothing ingested yet, or the session is ephemeral. */
|
|
536
|
+
export declare const NO_STORE = "LCM: no stored history for this session yet (nothing has been ingested, or the session is ephemeral).";
|
|
537
|
+
/** Search scopes `lcm_grep` accepts: this session only, plus the past-session scopes. */
|
|
538
|
+
export declare const RECALL_SCOPES: readonly ["session", "sessions", "all_sessions"];
|
|
539
|
+
/** One of `RECALL_SCOPES`; `sessions` and `all_sessions` are equivalent and both read past sidecars. */
|
|
540
|
+
export type RecallScope = (typeof RECALL_SCOPES)[number];
|
|
541
|
+
/** What `lcm_expand` reads: a summary listing by id, or one message by entry id with an optional summary check. */
|
|
542
|
+
export type Address = {
|
|
543
|
+
readonly kind: "summary";
|
|
544
|
+
readonly id: number;
|
|
545
|
+
} | {
|
|
546
|
+
readonly kind: "entry";
|
|
547
|
+
readonly entryId: string;
|
|
548
|
+
readonly id?: number;
|
|
549
|
+
};
|
|
550
|
+
/** Past-search outcome: redacted text plus hit, scanned, and skipped session counts. */
|
|
551
|
+
export interface RecallPastSearchResult {
|
|
552
|
+
readonly text: string;
|
|
553
|
+
readonly hits: number;
|
|
554
|
+
readonly scanned: number;
|
|
555
|
+
readonly skipped: number;
|
|
556
|
+
}
|
|
557
|
+
/** Past-session open outcome: the read-only store and id, or why it could not be opened. */
|
|
558
|
+
export type OpenPastResult = {
|
|
559
|
+
readonly kind: "opened";
|
|
560
|
+
readonly sessionId: string;
|
|
561
|
+
readonly store: LcmStore;
|
|
562
|
+
} | {
|
|
563
|
+
readonly kind: "missing" | "unreadable";
|
|
564
|
+
};
|
|
565
|
+
/** Host seams the recall answers read through: store lookup, redactor, metrics recorder, and past-session hooks. */
|
|
566
|
+
export interface RecallToolDeps {
|
|
567
|
+
readonly storeFor: (sessionId: string | undefined) => LcmStore | undefined;
|
|
568
|
+
readonly redact: (text: string) => string;
|
|
569
|
+
readonly record?: (row: Record<string, unknown>) => void;
|
|
570
|
+
readonly sessionOf?: (sessionId: string) => string;
|
|
571
|
+
readonly isReader?: (sessionId: string) => boolean;
|
|
572
|
+
readonly searchPast?: (session: string, query: string, scope: Exclude<RecallScope, "session">, limit: unknown) => RecallPastSearchResult;
|
|
573
|
+
readonly openPast?: (session: string, pastSession: string) => OpenPastResult;
|
|
574
|
+
readonly pastSearchUnreachableText?: string;
|
|
575
|
+
readonly pastReadUnreachableText?: string;
|
|
576
|
+
}
|
|
577
|
+
/** Answer `lcm_grep`: the session `grepView`, or past sidecars when the scope names them; readers stay session-scoped. */
|
|
578
|
+
export declare function grepAnswer(deps: RecallToolDeps, callerSessionID: string, input: unknown): string;
|
|
579
|
+
/** Answer `lcm_describe`: the redacted cost and contents of one summary id, or why it missed. */
|
|
580
|
+
export declare function describeAnswer(deps: RecallToolDeps, callerSessionID: string, input: unknown): string;
|
|
581
|
+
/** Answer `lcm_expand`: list a summary or read one message, capped at `MAX_EXPAND_CHARS`; a foreign `session` id reads a past sidecar read-only. */
|
|
582
|
+
export declare function expandAnswer(deps: RecallToolDeps, callerSessionID: string, input: unknown): string;
|
|
583
|
+
/** Parse `id`/`entry_id` args into an `Address`: error text when malformed, undefined when neither is present. */
|
|
584
|
+
export declare function readAddress(args: Record<string, unknown>): Address | string | undefined;
|
|
585
|
+
/** Refusal text when an address names no stored summary or message, else undefined. */
|
|
586
|
+
export declare function addressProblem(store: LcmStore, address: Address): string | undefined;
|
|
587
|
+
/** Read a validated address with `char_offset` paging capped at `maxChars` characters. */
|
|
588
|
+
export declare function readAddressed(store: LcmStore, redact: (text: string) => string, address: Address, args: Record<string, unknown>, maxChars: number): {
|
|
589
|
+
ok: boolean;
|
|
590
|
+
text: string;
|
|
591
|
+
};
|
|
592
|
+
/** Classify an `lcm_expand` reply as hit, miss, refused, or error for metric rows. */
|
|
593
|
+
export declare function expandQueryOutcome(text: string): "hit" | "miss" | "refused" | "error";
|
|
594
|
+
/** Shared redaction seam for every harness adapter. Pi passes its live
|
|
595
|
+
* config getter so a cockpit toggle takes effect immediately; OpenCode
|
|
596
|
+
* passes its resolved plugin options (which today carry no flag, so the
|
|
597
|
+
* adapter always redacts, preserving the current ignore-flag behavior in
|
|
598
|
+
* one central place). Both shapes flow into core's `makeRedact`, so the
|
|
599
|
+
* flag check lives here, not at each call site. */
|
|
600
|
+
/** Flag carrier both adapters satisfy: unset redacts, `false` disables. */
|
|
601
|
+
export interface RedactConfig {
|
|
602
|
+
readonly redactSecrets?: boolean;
|
|
603
|
+
}
|
|
604
|
+
/** One adapter for `piConfig` (via getter) and `opencodeOpts` (via value):
|
|
605
|
+
* a config value or a getter for one, out a `(text) => string` redactor. */
|
|
606
|
+
export declare function makeRedactConfigAdapter(config: RedactConfig | (() => RedactConfig)): (text: string) => string;
|
|
607
|
+
/** One sidecar on disk, named by its file stem. */
|
|
608
|
+
export interface SidecarRef {
|
|
609
|
+
readonly id: string;
|
|
610
|
+
readonly path: string;
|
|
611
|
+
}
|
|
612
|
+
/** File suffix for sidecar databases. OpenCode uses `.sqlite`. */
|
|
613
|
+
export declare const SIDECAR_EXTENSION = ".sqlite";
|
|
614
|
+
/** Replace every character a session id cannot carry with `_`. */
|
|
615
|
+
export declare function sanitizeSidecarId(sessionId: string): string;
|
|
616
|
+
/** Create the sidecar directory when missing, then repair its mode. */
|
|
617
|
+
export declare function ensureSidecarDir(dir: string): void;
|
|
618
|
+
/** Sidecar file for a session id: sanitized stem plus extension, joined under the sidecar dir. */
|
|
619
|
+
export declare function sidecarPathFor(dir: string, sessionId: string, extension?: string): string;
|
|
620
|
+
/** Live sidecar refs newest-first (mtime, then id); skips non-files and one optionally excluded path. */
|
|
621
|
+
export declare function listSidecarRefs(dir: string, opts?: {
|
|
622
|
+
extension?: string;
|
|
623
|
+
excludePath?: string;
|
|
624
|
+
}): SidecarRef[];
|
|
625
|
+
/** Delete a sidecar database and its WAL and shared-memory files. */
|
|
626
|
+
export declare function dropSidecarFiles(path: string): void;
|
|
627
|
+
/** Where sidecars live and which extension names them; defaults to `.sqlite`. */
|
|
628
|
+
export interface SidecarStoresOptions {
|
|
629
|
+
readonly dir: string;
|
|
630
|
+
readonly extension?: string;
|
|
631
|
+
}
|
|
632
|
+
/** One SQLite sidecar per session, opened on first use and held for reuse. */
|
|
633
|
+
export declare class SidecarStores {
|
|
634
|
+
private readonly stores;
|
|
635
|
+
/** Sidecar directory this cache reads and writes. */
|
|
636
|
+
readonly dir: string;
|
|
637
|
+
/** File extension naming sidecar databases, including the leading dot. */
|
|
638
|
+
readonly extension: string;
|
|
639
|
+
/** Open the cache, creating the dir (mode 0700) when missing; stores open lazily. */
|
|
640
|
+
constructor(options: SidecarStoresOptions);
|
|
641
|
+
/** File path for one session id under this cache's dir and extension. */
|
|
642
|
+
pathFor(sessionId: string): string;
|
|
643
|
+
/** Open-once store for a session id, reused across calls until dropped or closed. */
|
|
644
|
+
for(sessionId: string): LcmStore;
|
|
645
|
+
/** Open store already held for a session id, if any, without opening one. */
|
|
646
|
+
peek(sessionId: string): LcmStore | undefined;
|
|
647
|
+
/** Session ids with an open store in this cache. */
|
|
648
|
+
ids(): string[];
|
|
649
|
+
/** Live sidecar refs newest-first, optionally excluding one session id. */
|
|
650
|
+
list(excludeSessionId?: string): SidecarRef[];
|
|
651
|
+
/** Close the held store, forget it, and delete its database plus WAL and SHM files. */
|
|
652
|
+
drop(sessionId: string): void;
|
|
653
|
+
/** Close every held store and forget them; files stay on disk. */
|
|
654
|
+
close(): void;
|
|
655
|
+
}
|
|
656
|
+
/** What one call was billed, in numbers, so a pass can sum its spend.
|
|
657
|
+
* `UsageLike` in `metrics.ts` is the tolerant reader of the same facts at the
|
|
658
|
+
* metrics boundary, and a `Usage` satisfies it. */
|
|
659
|
+
export interface Usage {
|
|
660
|
+
input: number;
|
|
661
|
+
output: number;
|
|
662
|
+
cacheRead: number;
|
|
663
|
+
cacheWrite: number;
|
|
664
|
+
/** A subset of `cacheWrite`, reported by hosts that split retention. */
|
|
665
|
+
cacheWrite1h?: number;
|
|
666
|
+
/** A subset of `output`, reported by hosts that expose the breakdown. */
|
|
667
|
+
reasoning?: number;
|
|
668
|
+
/** The host derived the counters from characters because the provider
|
|
669
|
+
* reported none. A cost beside it is a bounded estimate, not a bill. */
|
|
670
|
+
estimated?: true;
|
|
671
|
+
totalTokens: number;
|
|
672
|
+
cost: {
|
|
673
|
+
input: number;
|
|
674
|
+
output: number;
|
|
675
|
+
cacheRead: number;
|
|
676
|
+
cacheWrite: number;
|
|
677
|
+
total: number;
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
/** How core tells the operator something. A host with no UI is `none` rather
|
|
681
|
+
* than a dropped callback, so a caller has one branch and no optional to miss. */
|
|
682
|
+
export type Notices = {
|
|
683
|
+
readonly kind: "none";
|
|
684
|
+
} | {
|
|
685
|
+
readonly kind: "ui";
|
|
686
|
+
notify(text: string, level: "info" | "warning"): void;
|
|
687
|
+
};
|
|
688
|
+
/** One model the host can call. `key` is the spelling config zones and metric
|
|
689
|
+
* rows already use; `model` is the host's own handle, opaque here. */
|
|
690
|
+
export interface ModelRef<Model> {
|
|
691
|
+
readonly key: string;
|
|
692
|
+
readonly reasoning: boolean;
|
|
693
|
+
readonly model: Model;
|
|
694
|
+
}
|
|
695
|
+
export interface ModelRequest {
|
|
696
|
+
readonly provider: string;
|
|
697
|
+
readonly id: string;
|
|
698
|
+
}
|
|
699
|
+
export interface CompletionRequest {
|
|
700
|
+
readonly prompt: string;
|
|
701
|
+
readonly maxTokens: number;
|
|
702
|
+
}
|
|
703
|
+
/** What a call came back with. `stop` is the host's own word, kept verbatim for
|
|
704
|
+
* metric rows; `capped` is the host saying the answer was cut short, which is
|
|
705
|
+
* the one thing a caller must never store. */
|
|
706
|
+
export type ModelAnswer = {
|
|
707
|
+
outcome: "answer";
|
|
708
|
+
text: string;
|
|
709
|
+
stop: string;
|
|
710
|
+
usage: Usage | undefined;
|
|
711
|
+
} | {
|
|
712
|
+
outcome: "failed";
|
|
713
|
+
reason: string;
|
|
714
|
+
capped: boolean;
|
|
715
|
+
stop: string;
|
|
716
|
+
usage: Usage | undefined;
|
|
717
|
+
};
|
|
718
|
+
export interface ModelHost<Model> {
|
|
719
|
+
readonly notices: Notices;
|
|
720
|
+
session(): ModelRef<Model> | undefined;
|
|
721
|
+
find(requested: ModelRequest): ModelRef<Model> | undefined;
|
|
722
|
+
complete(model: ModelRef<Model>, request: CompletionRequest): Promise<ModelAnswer>;
|
|
723
|
+
}
|
|
724
|
+
/** A tool the reader may call. The schema is the host's, passed through rather
|
|
725
|
+
* than rebuilt. */
|
|
726
|
+
export interface ReaderTool {
|
|
727
|
+
name: string;
|
|
728
|
+
description: string;
|
|
729
|
+
parameters: TSchema;
|
|
730
|
+
}
|
|
731
|
+
export interface ReaderCall {
|
|
732
|
+
readonly id: string;
|
|
733
|
+
readonly name: string;
|
|
734
|
+
readonly arguments: Record<string, unknown>;
|
|
735
|
+
}
|
|
736
|
+
export interface ReaderResult {
|
|
737
|
+
readonly call: ReaderCall;
|
|
738
|
+
readonly text: string;
|
|
739
|
+
}
|
|
740
|
+
/** What one reader step produced. `reads` asks the caller to run the calls;
|
|
741
|
+
* `answered` means the reader is done, with whatever text it accumulated. */
|
|
742
|
+
export type ReaderReply = {
|
|
743
|
+
outcome: "reads";
|
|
744
|
+
calls: readonly ReaderCall[];
|
|
745
|
+
text: string;
|
|
746
|
+
usage: Usage | undefined;
|
|
747
|
+
} | {
|
|
748
|
+
outcome: "answered";
|
|
749
|
+
text: string;
|
|
750
|
+
usage: Usage | undefined;
|
|
751
|
+
} | {
|
|
752
|
+
outcome: "failed";
|
|
753
|
+
reason: string;
|
|
754
|
+
usage: Usage | undefined;
|
|
755
|
+
};
|
|
756
|
+
export interface ReaderRequest {
|
|
757
|
+
readonly systemPrompt: string;
|
|
758
|
+
readonly brief: string;
|
|
759
|
+
readonly tools: readonly ReaderTool[];
|
|
760
|
+
}
|
|
761
|
+
/** One reader conversation. The host owns the transcript: `next` appends the
|
|
762
|
+
* results it is handed, appends its own reply, and calls the model. */
|
|
763
|
+
export interface Reading {
|
|
764
|
+
next(results: readonly ReaderResult[]): Promise<ReaderReply>;
|
|
765
|
+
}
|
|
766
|
+
export interface Reader {
|
|
767
|
+
begin(request: ReaderRequest): Reading;
|
|
768
|
+
}
|
|
769
|
+
export declare function metricsPath(): string;
|
|
770
|
+
/** The metrics log, oldest generation first, so a window longer than one
|
|
771
|
+
* generation still reads as one history. */
|
|
772
|
+
export declare function metricsLogFiles(): string[];
|
|
773
|
+
export declare function rotateIfNeeded(path: string, maxBytes: number): void;
|
|
774
|
+
export declare function pruneGenerations(path: string, keep: number): void;
|
|
775
|
+
export declare function appendMetric(record: MetricRecord): void;
|
|
776
|
+
/** Where a row goes. A pass injects one sink so every row it causes reaches the
|
|
777
|
+
* same place, including the calls its completer makes. */
|
|
778
|
+
export type MetricsSink = (record: MetricRecord) => void;
|
|
779
|
+
export interface UsageLike {
|
|
780
|
+
input?: unknown;
|
|
781
|
+
output?: unknown;
|
|
782
|
+
cacheRead?: unknown;
|
|
783
|
+
cacheWrite?: unknown;
|
|
784
|
+
cost?: {
|
|
785
|
+
input?: unknown;
|
|
786
|
+
output?: unknown;
|
|
787
|
+
total?: unknown;
|
|
788
|
+
};
|
|
789
|
+
/** The host counted characters because the provider reported no usage. */
|
|
790
|
+
estimated?: unknown;
|
|
791
|
+
}
|
|
792
|
+
interface MetricBase {
|
|
793
|
+
/** Session tag. Absent on rows written outside a session (`gc`, `import`). */
|
|
794
|
+
session?: string;
|
|
795
|
+
}
|
|
796
|
+
export type MetricSpan = [string, string];
|
|
797
|
+
export type SummaryStage = "leaf" | "condensed";
|
|
798
|
+
export type CompactionStage = "blocking" | "async" | "direct" | "heal";
|
|
799
|
+
export type SummaryLevel = 1 | 2 | 3;
|
|
800
|
+
/** One model's turn in a summarizer chain, in the order it was tried. */
|
|
801
|
+
type SummarizerAttempt = {
|
|
802
|
+
model: string;
|
|
803
|
+
outcome: "ok" | "empty";
|
|
804
|
+
} | {
|
|
805
|
+
model: string;
|
|
806
|
+
outcome: "threw";
|
|
807
|
+
error: string;
|
|
808
|
+
};
|
|
809
|
+
export type UsageCounters = {
|
|
810
|
+
input: number | null;
|
|
811
|
+
output: number | null;
|
|
812
|
+
cacheRead: number | null;
|
|
813
|
+
cacheWrite: number | null;
|
|
814
|
+
costInput: number | null;
|
|
815
|
+
costOutput: number | null;
|
|
816
|
+
costTotal: number | null;
|
|
817
|
+
};
|
|
818
|
+
/** The counters to put on a row, or `undefined` when the provider billed
|
|
819
|
+
* nothing (an aborted call, or a provider that reports no usage). */
|
|
820
|
+
export declare function billedCounters(usage: UsageLike | undefined): UsageCounters | undefined;
|
|
821
|
+
/** One metrics row per measured assistant message: no numeric counter at all
|
|
822
|
+
* (aborted turn, legacy row) yields `undefined`, so it does not count as a turn
|
|
823
|
+
* in the report's cache-economics averages. */
|
|
824
|
+
export declare function usageRecord(usage: UsageLike | undefined): UsageRow | undefined;
|
|
825
|
+
/** The stage a completer ran under. Same vocabulary as `CompactionStage`, so a
|
|
826
|
+
* direct pass labels its completer rows and its engine rows alike. */
|
|
827
|
+
export type SummarizerStage = CompactionStage;
|
|
828
|
+
/** Why a summarizer call failed, as far as the provider's own text says. A
|
|
829
|
+
* closed set, and `unknown` is a real answer: a classification that guesses is
|
|
830
|
+
* worse than a bucket that says it could not tell, and the raw message stays on
|
|
831
|
+
* the row either way. `capped` is not a provider failure: it is the pass giving
|
|
832
|
+
* up after too many of them, and the category beside it says which one. */
|
|
833
|
+
export type SummarizerFailureCategory = "auth" | "rate-limit" | "timeout" | "unavailable" | "length" | "empty" | "capped" | "unknown";
|
|
834
|
+
/** `ok` answered with usable text, `empty` answered with none, `failure` was rejected. */
|
|
835
|
+
export type SummarizerOutcome = "ok" | "empty" | "failure";
|
|
836
|
+
export interface SummarizerUsageOptions {
|
|
837
|
+
/** The resolved model key, as `modelKey` renders it. */
|
|
838
|
+
model: string;
|
|
839
|
+
stage?: SummarizerStage;
|
|
840
|
+
outcome: SummarizerOutcome;
|
|
841
|
+
/** The provider's own stop word, so `length` and `error` are told apart. */
|
|
842
|
+
stopReason?: string;
|
|
843
|
+
session?: string;
|
|
844
|
+
}
|
|
845
|
+
export type SummarizerUsageRow = UsageCounters & {
|
|
846
|
+
event: "lcm";
|
|
847
|
+
kind: "summarizer-usage";
|
|
848
|
+
model: string;
|
|
849
|
+
stage?: SummarizerStage;
|
|
850
|
+
outcome: SummarizerOutcome;
|
|
851
|
+
stopReason?: string;
|
|
852
|
+
session?: string;
|
|
853
|
+
/** The counters were derived from characters, not reported by the provider. */
|
|
854
|
+
estimated?: true;
|
|
855
|
+
};
|
|
856
|
+
/** One provider call the summarizer made. `usage` rows cannot carry it, since
|
|
857
|
+
* those come from Pi's `message_end` for the session's own messages while the
|
|
858
|
+
* summarizer talks to the model registry directly. */
|
|
859
|
+
export declare function summarizerUsageRecord(usage: UsageLike | undefined, opts: SummarizerUsageOptions): SummarizerUsageRow | undefined;
|
|
860
|
+
export type UsageRow = MetricBase & UsageCounters & {
|
|
861
|
+
event: "usage";
|
|
862
|
+
};
|
|
863
|
+
interface LcmRowBase extends MetricBase {
|
|
864
|
+
event: "lcm";
|
|
865
|
+
}
|
|
866
|
+
export type ContextDecisionRow = LcmRowBase & {
|
|
867
|
+
kind: "context-decision";
|
|
868
|
+
action: string;
|
|
869
|
+
occupancy: number;
|
|
870
|
+
tokens: number;
|
|
871
|
+
window: number;
|
|
872
|
+
soft: number;
|
|
873
|
+
commit: number;
|
|
874
|
+
zone?: string;
|
|
875
|
+
committed: boolean;
|
|
876
|
+
/** Present when `tokens` is the estimator's count rather than Pi's: the
|
|
877
|
+
* reported figure described a context other than the one on hand. */
|
|
878
|
+
tokensEstimated?: true;
|
|
879
|
+
};
|
|
880
|
+
export type ProjectionOverBudgetRow = LcmRowBase & {
|
|
881
|
+
kind: "projection-over-budget";
|
|
882
|
+
nodes: number;
|
|
883
|
+
renderedTokens: number;
|
|
884
|
+
budgetTokens: number;
|
|
885
|
+
};
|
|
886
|
+
export type SwapAppliedRow = LcmRowBase & {
|
|
887
|
+
kind: "swap-applied";
|
|
888
|
+
summaryId: number;
|
|
889
|
+
cutCount: number;
|
|
890
|
+
distinctBoundary: boolean;
|
|
891
|
+
appliedAtOccupancy: number;
|
|
892
|
+
nodes: number;
|
|
893
|
+
stubbed: number;
|
|
894
|
+
renderedTokens: number;
|
|
895
|
+
budgetTokens: number;
|
|
896
|
+
};
|
|
897
|
+
/**
|
|
898
|
+
* A swap the policy did not commit. `tokens` is the occupancy that triggered
|
|
899
|
+
* the bail; the kick-backoff writer names the entry it declined to kick instead.
|
|
900
|
+
*/
|
|
901
|
+
export type SwapBlockedRow = LcmRowBase & {
|
|
902
|
+
kind: "swap-blocked";
|
|
903
|
+
reason: string;
|
|
904
|
+
occupancy: number;
|
|
905
|
+
tokens?: number;
|
|
906
|
+
entryId?: string;
|
|
907
|
+
};
|
|
908
|
+
export type PiCompactionRow = LcmRowBase & {
|
|
909
|
+
kind: "pi-compaction";
|
|
910
|
+
entryId: string;
|
|
911
|
+
tokens?: number;
|
|
912
|
+
window?: number;
|
|
913
|
+
};
|
|
914
|
+
/** One calibration sample against Pi's own count: the characters this build
|
|
915
|
+
* counted for the growth of the context, the tokens Pi reported for it, and the
|
|
916
|
+
* ratio the samples now support. A null ratio is the answer "cannot say", which is
|
|
917
|
+
* why the row carries the sample beside it. */
|
|
918
|
+
export type EstimateCalibratedRow = LcmRowBase & {
|
|
919
|
+
kind: "estimate-calibrated";
|
|
920
|
+
chars: number;
|
|
921
|
+
tokens: number;
|
|
922
|
+
charsPerToken: number | null;
|
|
923
|
+
samples: number;
|
|
924
|
+
};
|
|
925
|
+
/** A provider stated the real context window in an overflow error, which is the
|
|
926
|
+
* one window number in this system that is not a belief. */
|
|
927
|
+
export type WindowCorrectedRow = LcmRowBase & {
|
|
928
|
+
kind: "window-corrected";
|
|
929
|
+
model: string;
|
|
930
|
+
window: number;
|
|
931
|
+
source: string;
|
|
932
|
+
};
|
|
933
|
+
/** A refused request proved the window in use was too big without naming the
|
|
934
|
+
* real one: the window for the next turn is floored at what the request carried,
|
|
935
|
+
* and `window` is the stored correction when one exists. */
|
|
936
|
+
export type WindowFloorRow = LcmRowBase & {
|
|
937
|
+
kind: "window-floor";
|
|
938
|
+
tokens: number;
|
|
939
|
+
window: number | null;
|
|
940
|
+
};
|
|
941
|
+
export type ConfigProblemRow = LcmRowBase & {
|
|
942
|
+
kind: "config-problem";
|
|
943
|
+
problems: string[];
|
|
944
|
+
};
|
|
945
|
+
export type SummaryInputClippedRow = LcmRowBase & {
|
|
946
|
+
kind: "summary-input-clipped";
|
|
947
|
+
entryId: string;
|
|
948
|
+
keptChars: number;
|
|
949
|
+
droppedChars: number;
|
|
950
|
+
};
|
|
951
|
+
export type SummaryFallbackStoredRow = LcmRowBase & {
|
|
952
|
+
kind: "summary-fallback-stored";
|
|
953
|
+
stage: SummaryStage;
|
|
954
|
+
span: MetricSpan;
|
|
955
|
+
sourceChars: number;
|
|
956
|
+
/** Absent on rows written before the node id rode along. */
|
|
957
|
+
summaryId?: number;
|
|
958
|
+
};
|
|
959
|
+
export type SummaryStoredRow = LcmRowBase & {
|
|
960
|
+
kind: "summary-stored";
|
|
961
|
+
stage: SummaryStage;
|
|
962
|
+
level: SummaryLevel;
|
|
963
|
+
/** Absent on rows written before quality was recorded. */
|
|
964
|
+
quality?: SummaryQuality;
|
|
965
|
+
depth: number;
|
|
966
|
+
tokens: number;
|
|
967
|
+
summaryId: number;
|
|
968
|
+
span: MetricSpan;
|
|
969
|
+
/** From just before the summarizer call to after the node row was written. */
|
|
970
|
+
durationMs: number;
|
|
971
|
+
};
|
|
972
|
+
export type SummarySkippedCoveredRow = LcmRowBase & {
|
|
973
|
+
kind: "summary-skipped-covered";
|
|
974
|
+
stage: SummaryStage;
|
|
975
|
+
span: MetricSpan;
|
|
976
|
+
messages: number;
|
|
977
|
+
};
|
|
978
|
+
export type SummarySkippedExistingRow = LcmRowBase & {
|
|
979
|
+
kind: "summary-skipped-existing";
|
|
980
|
+
stage: SummaryStage;
|
|
981
|
+
summaryId: number;
|
|
982
|
+
depth: number;
|
|
983
|
+
span: MetricSpan;
|
|
984
|
+
attemptedTokens: number;
|
|
985
|
+
existingTokens: number;
|
|
986
|
+
durationMs: number;
|
|
987
|
+
};
|
|
988
|
+
export type SummaryOversizedRow = LcmRowBase & {
|
|
989
|
+
kind: "summary-oversized";
|
|
990
|
+
stage: SummaryStage;
|
|
991
|
+
depth: number;
|
|
302
992
|
tokens: number;
|
|
303
993
|
targetTokens: number;
|
|
304
994
|
sourceTokens: number;
|
|
305
995
|
retried: boolean;
|
|
306
996
|
level: SummaryLevel;
|
|
307
997
|
span: MetricSpan;
|
|
998
|
+
/** Absent on rows written before the node id rode along. */
|
|
999
|
+
summaryId?: number;
|
|
308
1000
|
};
|
|
309
1001
|
export type SummaryReplacedRow = LcmRowBase & {
|
|
310
1002
|
kind: "summary-replaced";
|
|
@@ -312,6 +1004,10 @@ export type SummaryReplacedRow = LcmRowBase & {
|
|
|
312
1004
|
summaryId: number;
|
|
313
1005
|
beforeTokens: number;
|
|
314
1006
|
afterTokens: number;
|
|
1007
|
+
/** Absent when the row being displaced could not be read. */
|
|
1008
|
+
qualityBefore?: SummaryQuality;
|
|
1009
|
+
qualityAfter: SummaryQuality;
|
|
1010
|
+
durationMs: number;
|
|
315
1011
|
};
|
|
316
1012
|
export type SummaryFinalTruncatedRow = LcmRowBase & {
|
|
317
1013
|
kind: "summary-final-truncated";
|
|
@@ -346,6 +1042,69 @@ export type CompactionCompleteRow = LcmRowBase & {
|
|
|
346
1042
|
ingested: number;
|
|
347
1043
|
span: MetricSpan;
|
|
348
1044
|
};
|
|
1045
|
+
/** What one heal node did. `levelBefore` is 3 only when the stored text carries
|
|
1046
|
+
* the mechanical prefix; a clean-looking node derived from tainted children has
|
|
1047
|
+
* no level of its own, which is why the detection walks provenance. `levelAfter`
|
|
1048
|
+
* is null when nothing was stored, so a rejected level-3 answer leaves the old
|
|
1049
|
+
* text and no quality claim behind. */
|
|
1050
|
+
export type HealNodeOutcomeKind = "repaired" | "improved" | "failed" | "blocked" | "skipped" | "changed";
|
|
1051
|
+
/** Why a heal run stopped. `dry-run` reports a plan without a model call. */
|
|
1052
|
+
export type HealStopReason = "done" | "dry-run" | "max-nodes" | "max-calls" | "token-budget" | "llm-capped";
|
|
1053
|
+
export type HealStartRow = LcmRowBase & {
|
|
1054
|
+
kind: "heal-start";
|
|
1055
|
+
candidates: number;
|
|
1056
|
+
taintedNodes: number;
|
|
1057
|
+
maxNodes: number;
|
|
1058
|
+
maxCalls: number;
|
|
1059
|
+
maxTokens: number;
|
|
1060
|
+
targetTokens: number;
|
|
1061
|
+
/** The effective whole-node source window and the request cap that bounded it. */
|
|
1062
|
+
sourceTokens: number;
|
|
1063
|
+
callBudgetTokens: number;
|
|
1064
|
+
dryRun: boolean;
|
|
1065
|
+
};
|
|
1066
|
+
export type HealNodeRow = LcmRowBase & {
|
|
1067
|
+
kind: "heal-node";
|
|
1068
|
+
summaryId: number;
|
|
1069
|
+
depth: number;
|
|
1070
|
+
nodeKind: SummaryStage;
|
|
1071
|
+
tainted: boolean;
|
|
1072
|
+
qualityBefore: SummaryQuality;
|
|
1073
|
+
/** Present only when this run stored a replacement; a failed node keeps the
|
|
1074
|
+
* quality the store already holds. */
|
|
1075
|
+
qualityAfter?: SummaryQuality;
|
|
1076
|
+
levelBefore: SummaryLevel | null;
|
|
1077
|
+
levelAfter: SummaryLevel | null;
|
|
1078
|
+
beforeTokens: number;
|
|
1079
|
+
afterTokens: number | null;
|
|
1080
|
+
calls: number;
|
|
1081
|
+
outcome: HealNodeOutcomeKind;
|
|
1082
|
+
reason?: string;
|
|
1083
|
+
/** Source facts only. Source and summary text never reach the metrics log. */
|
|
1084
|
+
sourceTokens?: number;
|
|
1085
|
+
sourceChars?: number;
|
|
1086
|
+
sourceClipped?: boolean;
|
|
1087
|
+
};
|
|
1088
|
+
export type HealCompleteRow = LcmRowBase & {
|
|
1089
|
+
kind: "heal-complete";
|
|
1090
|
+
stop: HealStopReason;
|
|
1091
|
+
candidates: number;
|
|
1092
|
+
attempted: number;
|
|
1093
|
+
repaired: number;
|
|
1094
|
+
improved: number;
|
|
1095
|
+
failed: number;
|
|
1096
|
+
blocked: number;
|
|
1097
|
+
blockedSource: number;
|
|
1098
|
+
skipped: number;
|
|
1099
|
+
changedCount: number;
|
|
1100
|
+
notAttempted: number;
|
|
1101
|
+
calls: number;
|
|
1102
|
+
tokens: number;
|
|
1103
|
+
sourceTokens: number;
|
|
1104
|
+
callBudgetTokens: number;
|
|
1105
|
+
repairedIds: number[];
|
|
1106
|
+
improvedIds: number[];
|
|
1107
|
+
};
|
|
349
1108
|
/** A summarizer call that answered with nothing, or that threw. An empty answer
|
|
350
1109
|
* is its own reason, so only a throw needs the category that says why. */
|
|
351
1110
|
export type SummarizerErrorRow = LcmRowBase & {
|
|
@@ -534,505 +1293,369 @@ export type RetrievalRow = LcmRowBase & {
|
|
|
534
1293
|
steps: number;
|
|
535
1294
|
retrievedTokens: number;
|
|
536
1295
|
stop: string;
|
|
537
|
-
reason?: string;
|
|
538
|
-
scope?: "summary" | "entry";
|
|
539
|
-
summaryId?: number;
|
|
540
|
-
entryId?: string;
|
|
541
|
-
} & Partial<UsageCounters>;
|
|
542
|
-
export type ImportRow = MetricBase & {
|
|
543
|
-
event: "lcm";
|
|
544
|
-
kind: "import";
|
|
545
|
-
db: string;
|
|
546
|
-
} & ({
|
|
547
|
-
refused: string;
|
|
548
|
-
} | {
|
|
549
|
-
messagesInserted: number;
|
|
550
|
-
messagesSkipped: number;
|
|
551
|
-
summariesInserted: number;
|
|
552
|
-
summariesSkipped: number;
|
|
553
|
-
});
|
|
554
|
-
export type LcmMetricRecord = ContextDecisionRow | ProjectionOverBudgetRow | SwapAppliedRow | SwapBlockedRow | PiCompactionRow | EstimateCalibratedRow | WindowCorrectedRow | WindowFloorRow | ConfigProblemRow | SummaryInputClippedRow | SummaryFallbackStoredRow | SummaryStoredRow | SummarySkippedCoveredRow | SummarySkippedExistingRow | SummaryOversizedRow | SummaryReplacedRow | SummaryFinalTruncatedRow | CondensationStalledRow | CondensationReducedRow | CompactionCompleteRow | SummarizerErrorRow | SummarizerCappedRow | SummarizerFallbackRow | SummarizerModelRow | SummarizerUsageRow | CompactionErrorRow | CompactionAwaitedRow | CompactionQueuedRow | CompactionQueuedDroppedRow | CompactionTimeoutClearedRow | CompactionStaleRow | CompactionAbandonedRow | CompactionNoopRow | GcDeletedRow | StoreMigratedRow | BranchRemovedRow | FileExternalizedRow | LargeInlineRow | RecallRow | RetrievalRow | EvalRow | ImportRow;
|
|
555
|
-
export type MetricRecord = UsageRow | LcmMetricRecord;
|
|
556
|
-
//#endregion
|
|
557
|
-
//#region src/files.d.ts
|
|
558
|
-
/** Characters of a body's description that stay in the preview. The message row
|
|
559
|
-
* keeps every character, so this bounds what the summarizer reads, not what is
|
|
560
|
-
* retained. */
|
|
561
|
-
export declare const FILE_PREVIEW_CHARS = 1200;
|
|
562
|
-
export declare const FILE_KINDS: readonly ["json", "jsonl", "csv", "tsv", "code", "text"];
|
|
563
|
-
export type FileKind = (typeof FILE_KINDS)[number];
|
|
564
|
-
export declare function isFileKind(value: unknown): value is FileKind;
|
|
565
|
-
export interface FileDescriptor {
|
|
566
|
-
fileId: string;
|
|
567
|
-
path: string;
|
|
568
|
-
/** Characters retained in the message, not bytes on disk. */
|
|
569
|
-
bytes: number;
|
|
570
|
-
sha256: string;
|
|
571
|
-
kind: FileKind;
|
|
572
|
-
preview: string;
|
|
573
|
-
}
|
|
574
|
-
export declare function fileHandle(d: FileDescriptor): string;
|
|
575
|
-
export declare function filePreviewText(d: FileDescriptor): string;
|
|
576
|
-
export declare function describeFile(path: string, text: string): FileDescriptor;
|
|
577
|
-
/** What the body is, decided by structure rather than by extension alone: a
|
|
578
|
-
* `.ts` file holding a JSON array is JSON, and a `.txt` file holding JSON is
|
|
579
|
-
* JSON too. The parse is tried before the extension for that reason. */
|
|
580
|
-
export declare function kindOf(path: string, text: string): FileKind;
|
|
581
|
-
export declare function structureOf(kind: FileKind, path: string, text: string): string;
|
|
582
|
-
export declare function previewOf(kind: FileKind, path: string, text: string): string;
|
|
583
|
-
//#endregion
|
|
584
|
-
//#region src/pattern-scan.d.ts
|
|
585
|
-
/** One row the pattern path scans. The text is the stored message, whole: the
|
|
586
|
-
* FTS index holds a prefix, and reading only that is the defect this path
|
|
587
|
-
* exists to fix. */
|
|
588
|
-
export interface ScannedRow {
|
|
589
|
-
rowid: number;
|
|
590
|
-
entryId: string;
|
|
591
|
-
role: string;
|
|
592
|
-
text: string;
|
|
593
|
-
removed: boolean;
|
|
594
|
-
}
|
|
595
|
-
/** A row that matched, with the first match's position and how many the row
|
|
596
|
-
* held. `partial` is set when the row was longer than the per-row cap, so a
|
|
597
|
-
* reader knows the scan stopped before the end of that row. */
|
|
598
|
-
export interface PatternHit {
|
|
599
|
-
row: ScannedRow;
|
|
600
|
-
matches: number;
|
|
601
|
-
at: number;
|
|
602
|
-
partial: boolean;
|
|
603
|
-
scannedChars: number;
|
|
604
|
-
}
|
|
605
|
-
export type PatternStop = "end" | "deadline" | "page";
|
|
606
|
-
export interface PatternScanOutcome {
|
|
607
|
-
hits: PatternHit[];
|
|
608
|
-
rows: number;
|
|
609
|
-
partialRows: number;
|
|
610
|
-
more: boolean;
|
|
611
|
-
stopped: PatternStop;
|
|
612
|
-
lastRowId?: number;
|
|
613
|
-
lastEntryId?: string;
|
|
614
|
-
}
|
|
615
|
-
export interface PatternScanOptions {
|
|
616
|
-
pattern: CompiledPattern;
|
|
617
|
-
offset: number;
|
|
618
|
-
limit: number;
|
|
619
|
-
/** Characters of one row the scan reads. Above the FTS prefix on purpose,
|
|
620
|
-
* below the size of an externalized body, and named on the hit when it bites. */
|
|
621
|
-
perRowChars: number;
|
|
622
|
-
deadlineAt: number;
|
|
623
|
-
now: () => number;
|
|
624
|
-
}
|
|
625
|
-
/** The characters a caller can hand a regular expression. A row longer than this
|
|
626
|
-
* is read up to the cap and the hit says so: a partial scan that looks complete
|
|
627
|
-
* is the same defect as an index that stops at 100,000 characters. */
|
|
628
|
-
export declare const PATTERN_ROW_CHARS = 200000;
|
|
629
|
-
/** How long one pattern call may scan. Long enough for a session's messages at
|
|
630
|
-
* 200,000 characters a row, short enough that a pathological pattern is a slow
|
|
631
|
-
* call rather than a hung session. */
|
|
632
|
-
export declare const PATTERN_DEADLINE_MS = 1000;
|
|
633
|
-
export declare const PATTERN_PAGE_ROWS = 200;
|
|
634
|
-
/** Matches counted in one row before the scan moves on. A row that mentions a
|
|
635
|
-
* path a thousand times is one hit line, and counting the thousandth costs the
|
|
636
|
-
* deadline that the next row needs. */
|
|
637
|
-
export declare const MAX_MATCHES_PER_ROW = 100;
|
|
638
|
-
export declare function scanRows(rows: Iterable<ScannedRow>, opts: PatternScanOptions): PatternScanOutcome;
|
|
639
|
-
/** A pattern this module compiled and validated, so a scan cannot receive a
|
|
640
|
-
* caller's own stateful `RegExp`. */
|
|
641
|
-
export type CompiledPattern = RegExp & {
|
|
642
|
-
readonly __brand: "CompiledPattern";
|
|
643
|
-
};
|
|
644
|
-
export type PatternCompileResult = {
|
|
645
|
-
ok: true;
|
|
646
|
-
pattern: CompiledPattern;
|
|
1296
|
+
reason?: string;
|
|
1297
|
+
scope?: "summary" | "entry";
|
|
1298
|
+
summaryId?: number;
|
|
1299
|
+
entryId?: string;
|
|
1300
|
+
} & Partial<UsageCounters>;
|
|
1301
|
+
export type ImportRow = MetricBase & {
|
|
1302
|
+
event: "lcm";
|
|
1303
|
+
kind: "import";
|
|
1304
|
+
db: string;
|
|
1305
|
+
} & ({
|
|
1306
|
+
refused: string;
|
|
647
1307
|
} | {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
export
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
export declare function normalizeRole(r: string): LcmRole;
|
|
660
|
-
export interface StoredMessage {
|
|
661
|
-
id: number;
|
|
662
|
-
entryId: string;
|
|
663
|
-
role: LcmRole;
|
|
664
|
-
text: string;
|
|
665
|
-
tokens: number;
|
|
666
|
-
timestamp: number;
|
|
667
|
-
/** JSON of the content blocks, present only when `text` is not a faithful
|
|
668
|
-
* rendering of them (a thinking block, an image, a signature). Absent means
|
|
669
|
-
* the derived text is the whole message. */
|
|
670
|
-
payload?: string;
|
|
671
|
-
/** Set when the entry has left the active branch. The bytes stay; search
|
|
672
|
-
* excludes the row unless a caller asks for it. */
|
|
673
|
-
removedAt?: number;
|
|
1308
|
+
messagesInserted: number;
|
|
1309
|
+
messagesSkipped: number;
|
|
1310
|
+
summariesInserted: number;
|
|
1311
|
+
summariesSkipped: number;
|
|
1312
|
+
});
|
|
1313
|
+
export type LcmMetricRecord = ContextDecisionRow | ProjectionOverBudgetRow | SwapAppliedRow | SwapBlockedRow | PiCompactionRow | EstimateCalibratedRow | WindowCorrectedRow | WindowFloorRow | ConfigProblemRow | SummaryInputClippedRow | SummaryFallbackStoredRow | SummaryStoredRow | SummarySkippedCoveredRow | SummarySkippedExistingRow | SummaryOversizedRow | SummaryReplacedRow | SummaryFinalTruncatedRow | CondensationStalledRow | CondensationReducedRow | CompactionCompleteRow | HealStartRow | HealNodeRow | HealCompleteRow | SummarizerErrorRow | SummarizerCappedRow | SummarizerFallbackRow | SummarizerModelRow | SummarizerUsageRow | CompactionErrorRow | CompactionAwaitedRow | CompactionQueuedRow | CompactionQueuedDroppedRow | CompactionTimeoutClearedRow | CompactionStaleRow | CompactionAbandonedRow | CompactionNoopRow | GcDeletedRow | StoreMigratedRow | BranchRemovedRow | FileExternalizedRow | LargeInlineRow | RecallRow | RetrievalRow | EvalRow | ImportRow;
|
|
1314
|
+
export type MetricRecord = UsageRow | LcmMetricRecord;
|
|
1315
|
+
export interface LlmComplete {
|
|
1316
|
+
(systemPrompt: string, userText: string, maxTokens: number): Promise<string>;
|
|
1317
|
+
/** Where this completer's rows go. Absent means the metrics log. */
|
|
1318
|
+
readonly metrics?: MetricsSink;
|
|
674
1319
|
}
|
|
675
|
-
export interface
|
|
676
|
-
entryId: string;
|
|
677
|
-
role: LcmRole;
|
|
1320
|
+
export interface EscalationResult {
|
|
678
1321
|
text: string;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
1322
|
+
level: 1 | 2 | 3;
|
|
1323
|
+
sourceTokens: number;
|
|
1324
|
+
retried: boolean;
|
|
1325
|
+
thoroughText?: string;
|
|
682
1326
|
}
|
|
683
|
-
export
|
|
684
|
-
|
|
685
|
-
kind: "leaf" | "condensed";
|
|
1327
|
+
export declare function summarizeWithEscalation(items: Array<{
|
|
1328
|
+
role: string;
|
|
686
1329
|
text: string;
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1330
|
+
}>, targetTokens: number, llm: LlmComplete, session?: string): Promise<EscalationResult>;
|
|
1331
|
+
/** Summarize with the terse rung only, for a source that is already a summary:
|
|
1332
|
+
* the thorough ladder would spend two provider calls re-summarizing text that
|
|
1333
|
+
* was made thorough once. Here the terse answer is the only ask, so a result
|
|
1334
|
+
* that does not reduce the source falls to the deterministic truncate. */
|
|
1335
|
+
export declare function summarizeTerse(items: Array<{
|
|
1336
|
+
role: string;
|
|
1337
|
+
text: string;
|
|
1338
|
+
}>, targetTokens: number, llm: LlmComplete, session?: string): Promise<EscalationResult>;
|
|
1339
|
+
export declare function deterministicTruncate(text: string, targetTokens: number): string;
|
|
1340
|
+
export interface SummarizerConfig {
|
|
1341
|
+
/** One model id, or an ordered fallback chain. `auto` is the session model. */
|
|
1342
|
+
summarizer?: string | string[];
|
|
1343
|
+
/** Tokens one request may cost, estimate plus output cap, before it is refused
|
|
1344
|
+
* rather than paid for. A backstop, not a throttle. */
|
|
1345
|
+
summarizerTokensPerCall?: number;
|
|
1346
|
+
/** Tokens one pass may spend across all its calls, counted from what the
|
|
1347
|
+
* provider reported. Reaching it finishes the pass mechanically. */
|
|
1348
|
+
summarizerTokensPerPass?: number;
|
|
696
1349
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
first: string;
|
|
710
|
-
last: string;
|
|
711
|
-
firstRowid: number | null;
|
|
712
|
-
lastRowid: number | null;
|
|
713
|
-
}>;
|
|
714
|
-
duplicateSpans: Array<{
|
|
715
|
-
first: string;
|
|
716
|
-
last: string;
|
|
717
|
-
kind: string;
|
|
718
|
-
count: number;
|
|
719
|
-
}>;
|
|
720
|
-
orphanChildParents: number[];
|
|
721
|
-
deadRuns: Array<{
|
|
722
|
-
session: string | null;
|
|
723
|
-
rows: number[];
|
|
724
|
-
}>;
|
|
725
|
-
untrackedPending: number[];
|
|
726
|
-
foreignKeyViolations: number;
|
|
727
|
-
schemaVersion: number;
|
|
728
|
-
uncoveredMessages: number;
|
|
1350
|
+
/**
|
|
1351
|
+
* Thinking tokens are charged against the completion budget, so a reasoning
|
|
1352
|
+
* model can spend the whole cap before it emits any text. The headroom sits on
|
|
1353
|
+
* top of the caller's request, so the requested output size does not change.
|
|
1354
|
+
*/
|
|
1355
|
+
export declare const REASONING_HEADROOM = 4096;
|
|
1356
|
+
export interface ResolvedChain<Model> {
|
|
1357
|
+
/** The models to try, in order and deduped. Empty when nothing resolves. */
|
|
1358
|
+
models: ModelRef<Model>[];
|
|
1359
|
+
/** Configured entries the host could not resolve, in order. */
|
|
1360
|
+
unresolved: string[];
|
|
1361
|
+
requested: string[];
|
|
729
1362
|
}
|
|
730
|
-
/**
|
|
731
|
-
*
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
*
|
|
735
|
-
export type
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
own: true;
|
|
1363
|
+
/** Expand the configured chain: `auto` (the session model) goes last unless the
|
|
1364
|
+
* chain names it, so one bad entry still leaves the session able to summarize. */
|
|
1365
|
+
export declare function resolveModels<Model>(host: ModelHost<Model>, config: SummarizerConfig): ResolvedChain<Model>;
|
|
1366
|
+
/** The one rendering of a resolved chain. The level travels with the warning so
|
|
1367
|
+
* no caller can pick a different notify level for the same chain. */
|
|
1368
|
+
export type ChainReport = {
|
|
1369
|
+
level: "info";
|
|
1370
|
+
state: string;
|
|
739
1371
|
} | {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
existingTokens: number;
|
|
744
|
-
/** The colliding row's summary text. A caller whose view cannot read the row
|
|
745
|
-
* still has to answer for the span it was given, and this is that span's
|
|
746
|
-
* summary to reuse. */
|
|
747
|
-
existingText: string;
|
|
748
|
-
foreignLive: boolean;
|
|
1372
|
+
level: "warning";
|
|
1373
|
+
state: string;
|
|
1374
|
+
warning: string;
|
|
749
1375
|
};
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
text: string;
|
|
755
|
-
tokens: number;
|
|
756
|
-
depth: number;
|
|
1376
|
+
export declare function describeSummarizerChain<Model>(chain: ResolvedChain<Model>): ChainReport;
|
|
1377
|
+
/** The span every call from one completer summarizes. The failure cap is per
|
|
1378
|
+
* span, so a completer is built with the span it will be asked about. */
|
|
1379
|
+
export interface SummarizedSpan {
|
|
757
1380
|
firstEntryId: string;
|
|
758
1381
|
lastEntryId: string;
|
|
759
|
-
messageIds?: number[];
|
|
760
|
-
childSummaryIds?: number[];
|
|
761
|
-
fileIds?: string[];
|
|
762
|
-
thoroughText?: string;
|
|
763
|
-
run?: string;
|
|
764
|
-
};
|
|
765
|
-
/** What a compaction pass needs from the store: the committed memory plus, when
|
|
766
|
-
* the pass reads through `LcmStore.forRun`, the rows of its own run. `LcmStore`
|
|
767
|
-
* satisfies this with the committed-only meaning. */
|
|
768
|
-
export interface PassStore extends IngestStore {
|
|
769
|
-
/** The branch range a set of entry ids occupies: the oldest and the newest row
|
|
770
|
-
* the store holds among them, or null when it holds none. Pi's context array
|
|
771
|
-
* is not branch order (it hoists the newest compaction entry to the front),
|
|
772
|
-
* while a span is a row range, so the store, whose rows are appended in
|
|
773
|
-
* branch order, is what decides which end comes first. */
|
|
774
|
-
spanBounds(entryIds: readonly string[]): {
|
|
775
|
-
firstEntryId: string;
|
|
776
|
-
lastEntryId: string;
|
|
777
|
-
} | null;
|
|
778
|
-
uncoveredMessagesInSpan(firstEntryId: string, lastEntryId: string): StoredMessage[];
|
|
779
|
-
filesForMessages(ids: readonly number[]): Array<FileDescriptor & {
|
|
780
|
-
messageId: number;
|
|
781
|
-
}>;
|
|
782
|
-
frontier(firstEntryId: string, lastEntryId: string): SummaryNode[];
|
|
783
|
-
getSummary(id: number): SummaryNode | undefined;
|
|
784
|
-
parentSummaryIds(summaryId: number): number[];
|
|
785
|
-
fileIdsForSummaries(ids: readonly number[]): string[];
|
|
786
|
-
insertSummary(s: InsertSummaryInput): InsertSummaryResult;
|
|
787
|
-
updateSummaryText(id: number, text: string, tokens: number): void;
|
|
788
|
-
setThoroughText(id: number, thoroughText: string): void;
|
|
789
1382
|
}
|
|
790
|
-
export interface
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1383
|
+
export interface MakeLlmOptions {
|
|
1384
|
+
session: string;
|
|
1385
|
+
stage?: SummarizerStage;
|
|
1386
|
+
span: SummarizedSpan;
|
|
1387
|
+
/** Where every row this completer writes goes. Defaults to the metrics log. */
|
|
1388
|
+
sink?: MetricsSink;
|
|
1389
|
+
}
|
|
1390
|
+
/** Failed walks in a row before a pass stops calling the model and lets the
|
|
1391
|
+
* ladder's deterministic truncate finish the span. One is too eager, because a
|
|
1392
|
+
* single transport blip would turn a whole span mechanical; past three, another
|
|
1393
|
+
* call carries no new information about the provider and costs its latency. */
|
|
1394
|
+
export declare const SUMMARIZER_FAILURE_CAP = 3;
|
|
1395
|
+
/** What one request may cost before it is refused instead of paid for. It exists
|
|
1396
|
+
* for a pathological chunk rather than for a normal one. */
|
|
1397
|
+
export declare const SUMMARIZER_CALL_TOKENS = 60000;
|
|
1398
|
+
/** What one pass may spend across all of its calls. */
|
|
1399
|
+
export declare const SUMMARIZER_PASS_TOKENS = 250000;
|
|
1400
|
+
export declare function retriableCategory(category: SummarizerFailureCategory): boolean;
|
|
1401
|
+
/** How long a span whose calls were capped is left alone. Shorter than the kick
|
|
1402
|
+
* backoff (`KICK_BACKOFF_MS`) on purpose: this guards a retry inside one turn,
|
|
1403
|
+
* it is not the breaker that stops re-kicks across turns. */
|
|
1404
|
+
export declare const FAILED_SPAN_TTL_MS = 60000;
|
|
1405
|
+
/** What a failed span is identified by: the two ends a caller asked about and
|
|
1406
|
+
* the chain it asked. A different model is a different attempt, not the same
|
|
1407
|
+
* failure, so the chain is part of the key. */
|
|
1408
|
+
export declare function spanFingerprint(span: SummarizedSpan, chain: readonly string[]): string;
|
|
1409
|
+
/**
|
|
1410
|
+
* A call that reached no usable answer. `category` is what the provider's own
|
|
1411
|
+
* text says: a best-effort read that answers `unknown` rather than guessing, and
|
|
1412
|
+
* `capped` when the caller should stop asking rather than the provider failing.
|
|
1413
|
+
*/
|
|
1414
|
+
export declare class SummarizerFailure extends Error {
|
|
1415
|
+
readonly category: SummarizerFailureCategory;
|
|
1416
|
+
constructor(message: string, category: SummarizerFailureCategory);
|
|
1417
|
+
}
|
|
1418
|
+
/** Which bucket a provider's error text falls in. A 5xx or a dropped connection
|
|
1419
|
+
* is `unavailable` before it is `timeout`, because the gateway's own timeout is
|
|
1420
|
+
* a request that never reached a model. */
|
|
1421
|
+
export declare function classifyFailure(text: string | undefined): SummarizerFailureCategory;
|
|
1422
|
+
export declare function makeLlm<Model>(host: ModelHost<Model>, config: SummarizerConfig, opts: MakeLlmOptions): LlmComplete | null;
|
|
1423
|
+
/** Sessions share the process, so each one gets to see the config it runs, and
|
|
1424
|
+
* a session that starts fresh should not inherit a refusal from the last one. */
|
|
1425
|
+
export declare function resetSummarizerState(): void;
|
|
1426
|
+
/** Tokens one bounded repair run may spend across all of its calls. A heal
|
|
1427
|
+
* re-derives nodes a pass already paid for, so it carries its own budget. */
|
|
1428
|
+
export declare const SUMMARIZER_HEAL_TOKENS = 120000;
|
|
1429
|
+
/** Tokens one repaired node targets when the caller names none. It matches
|
|
1430
|
+
* the heal engine's own fallback, so an unresolved option still repairs. */
|
|
1431
|
+
export declare const DEFAULT_SUMMARY_TOKENS = 1500;
|
|
1432
|
+
export interface SummarizerOptions {
|
|
1433
|
+
/** One model id, or an ordered fallback chain. `auto` is the session model. */
|
|
1434
|
+
readonly summarizer?: string | string[];
|
|
1435
|
+
/** Tokens one request may cost, estimate plus output cap, before it is
|
|
1436
|
+
* refused rather than paid for. */
|
|
1437
|
+
readonly summarizerTokensPerCall?: number;
|
|
1438
|
+
/** Tokens one pass may spend across all of its calls. */
|
|
1439
|
+
readonly summarizerTokensPerPass?: number;
|
|
1440
|
+
/** Tokens one on-demand heal run may spend across all of its calls. */
|
|
1441
|
+
readonly summarizerTokensPerHeal?: number;
|
|
1442
|
+
}
|
|
1443
|
+
/** The engine config for these options. An unresolved budget keeps the core
|
|
1444
|
+
* default, which is also what a direct caller that passes no options gets. */
|
|
1445
|
+
export declare function summarizerConfig(options: SummarizerOptions): SummarizerConfig;
|
|
1446
|
+
export interface HealBudgetOptions {
|
|
1447
|
+
/** Tokens one repaired node targets. */
|
|
1448
|
+
readonly summaryTokens?: number;
|
|
1449
|
+
/** Estimated tokens one heal node may read as source. Unset derives the
|
|
1450
|
+
* engine default; this is the explicit override for large sources. */
|
|
1451
|
+
readonly healSourceTokens?: number;
|
|
1452
|
+
/** Tokens one summarizer request may cost. */
|
|
1453
|
+
readonly summarizerTokensPerCall?: number;
|
|
1454
|
+
}
|
|
1455
|
+
/** Calibrated heal budgets in tokens: node target, per-call cap, and optional source window. */
|
|
1456
|
+
export interface HealBudgets {
|
|
1457
|
+
readonly targetTokens: number;
|
|
1458
|
+
readonly callBudgetTokens: number;
|
|
1459
|
+
readonly sourceTokens?: number;
|
|
1460
|
+
}
|
|
1461
|
+
/** The calibrated heal target and source window. A null ratio keeps the
|
|
1462
|
+
* configured reading, which is what an uncalibrated host passes. */
|
|
1463
|
+
export declare function healBudgets(options?: HealBudgetOptions, charsPerToken?: number | null): HealBudgets;
|
|
1464
|
+
export declare const CHARS_PER_TOKEN = 4;
|
|
1465
|
+
/** Samples below this count say nothing: with two points any line fits, and the
|
|
1466
|
+
* first turns of a session are exactly where the overhead has not cancelled. */
|
|
1467
|
+
export declare const CALIBRATION_MIN_SAMPLES = 3;
|
|
1468
|
+
/** Samples kept in memory. A cap keeps the median describing the recent turns of
|
|
1469
|
+
* the session rather than its whole history, which is what a model switch inside
|
|
1470
|
+
* one session would otherwise be unable to move. */
|
|
1471
|
+
export declare const CALIBRATION_SAMPLE_CAP = 8;
|
|
1472
|
+
/** One observation of the same session: how many characters this build counted in
|
|
1473
|
+
* the messages, and how many tokens Pi reported for the request that carried
|
|
1474
|
+
* them. Sampling differences rather than totals is what cancels the system prompt
|
|
1475
|
+
* and tool definitions, which this build never counts. */
|
|
1476
|
+
export interface EstimateSample {
|
|
1477
|
+
chars: number;
|
|
1478
|
+
tokens: number;
|
|
1479
|
+
}
|
|
1480
|
+
export declare function estimateTokens(text: string): number;
|
|
1481
|
+
export declare function tokensFromChars(chars: number): number;
|
|
1482
|
+
/** A reported count below this share of the estimate describes a context other
|
|
1483
|
+
* than the one on hand: Pi reports its last measurement, and after a resume that
|
|
1484
|
+
* measurement belongs to the previous process. The estimator's own error is
|
|
1485
|
+
* bounded by the calibration band, which is a factor of two in either direction
|
|
1486
|
+
* around the constant, so anything below a quarter is a different turn rather
|
|
1487
|
+
* than a miscount. A reported count too high only compacts early, which is why
|
|
1488
|
+
* this looks one way. */
|
|
1489
|
+
export declare const STALE_REPORT_SHARE = 0.25;
|
|
1490
|
+
export declare function calibrate(samples: readonly EstimateSample[]): number | null;
|
|
1491
|
+
/** A configured token budget in the units `estimateTokens` counts. The estimator
|
|
1492
|
+
* counts chars / 4, so a model whose real ratio is 6 needs 1.5 times the
|
|
1493
|
+
* configured number of estimated tokens accumulated before it has kept the
|
|
1494
|
+
* context the user asked for. Without a ratio the budget is left alone, which is
|
|
1495
|
+
* the constant's own reading of it. */
|
|
1496
|
+
export declare function scaledBudget(tokens: number, charsPerToken: number | null): number;
|
|
1497
|
+
/** A window outside this band is a misread rather than a model: no provider
|
|
1498
|
+
* serves a context under a thousand tokens or over a hundred million, and a
|
|
1499
|
+
* number outside it means a pattern caught the request count, a status code, or
|
|
1500
|
+
* something else that was not a limit. A refused parse is the floor path, which
|
|
1501
|
+
* costs one conservative turn instead of a wrong permanent window. */
|
|
1502
|
+
export declare const WINDOW_MIN = 1000;
|
|
1503
|
+
export declare const WINDOW_MAX = 100000000;
|
|
1504
|
+
/** The window in tokens a provider stated in an overflow message, or null when
|
|
1505
|
+
* the message does not name one. Null is an answer: the caller keeps Pi's belief
|
|
1506
|
+
* and arms the one-shot floor, which is all a body like "400/413 status code (no
|
|
1507
|
+
* body)" can support. */
|
|
1508
|
+
export declare function windowFromError(errorMessage: string | undefined): number | null;
|
|
1509
|
+
export declare const WINDOW_PATTERN_EXAMPLES: readonly string[];
|
|
1510
|
+
/** Either provider spelling hosts use: OpenCode names `providerID`, Pi names
|
|
1511
|
+
* `provider`. The key ignores any other field, including an OpenCode variant,
|
|
1512
|
+
* because a variant changes settings and not the model itself. */
|
|
1513
|
+
export interface ModelIdentity {
|
|
1514
|
+
readonly providerID?: string;
|
|
1515
|
+
readonly provider?: string;
|
|
1516
|
+
readonly id?: string;
|
|
1517
|
+
}
|
|
1518
|
+
/** A ref wrapper hosts pass around: the model beside its computed key. Only
|
|
1519
|
+
* the nested model matters for the spelling. */
|
|
1520
|
+
export interface ModelKeyRef {
|
|
1521
|
+
readonly model?: ModelIdentity | undefined;
|
|
1522
|
+
readonly key?: string;
|
|
1523
|
+
readonly reasoning?: boolean;
|
|
1524
|
+
}
|
|
1525
|
+
/** The spelling config zones and metric rows use: `provider/id`. A wrapper
|
|
1526
|
+
* with a `model` field (a core `ModelRef` or a Pi context) is unwrapped, so a
|
|
1527
|
+
* direct model and a ref to one spell the same. Missing or empty parts spell
|
|
1528
|
+
* empty, the way Pi names no model before one is active. */
|
|
1529
|
+
export declare function modelKey(input: ModelIdentity | ModelKeyRef | undefined | null): string;
|
|
1530
|
+
/** The fields a window lookup matches on. An OpenCode `ModelRef` carries an
|
|
1531
|
+
* optional variant besides these, which still satisfies this shape. */
|
|
1532
|
+
export interface WindowModel {
|
|
1533
|
+
readonly providerID: string;
|
|
1534
|
+
readonly id: string;
|
|
802
1535
|
}
|
|
803
|
-
/**
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
* migrating it: a search must not rewrite a file it is only reading, and a store
|
|
811
|
-
* that cannot answer this build's statements has to be a reported skip. */
|
|
812
|
-
export declare class StaleStoreError extends Error {
|
|
813
|
-
readonly generation: number;
|
|
814
|
-
constructor(generation: number);
|
|
1536
|
+
/** One catalogue entry reduced to what the lookup reads. */
|
|
1537
|
+
export interface WindowCatalogEntry {
|
|
1538
|
+
readonly providerID: string;
|
|
1539
|
+
readonly id: string;
|
|
1540
|
+
readonly limit: {
|
|
1541
|
+
readonly context: number;
|
|
1542
|
+
};
|
|
815
1543
|
}
|
|
816
|
-
/**
|
|
817
|
-
*
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1544
|
+
/** The catalogue source the lookup reads. It is the `list` half of the host
|
|
1545
|
+
* model domain; the synchronous `get` stays host side. */
|
|
1546
|
+
export interface WindowListDomain {
|
|
1547
|
+
list(input?: unknown): Promise<unknown>;
|
|
1548
|
+
}
|
|
1549
|
+
/** The context the lookup reads the catalogue from. Only the optional model
|
|
1550
|
+
* domain matters, so any host context with that shape satisfies it. */
|
|
1551
|
+
export interface WindowLookupContext {
|
|
1552
|
+
readonly model?: WindowListDomain | undefined;
|
|
1553
|
+
}
|
|
1554
|
+
/** The model window lookup both assembly seams read. */
|
|
1555
|
+
export type WindowOf<M extends WindowModel = WindowModel> = (model: M) => Promise<number>;
|
|
1556
|
+
/** The model window both assembly seams read. The context projection measures
|
|
1557
|
+
* occupancy against it, and the blocking checkpoint reads the same figure for
|
|
1558
|
+
* its metric rows, so the catalogue is asked once for the plugin lifetime. A
|
|
1559
|
+
* catalogue that failed once is asked again on the next request: the host may
|
|
1560
|
+
* still be starting. */
|
|
1561
|
+
export declare function createWindowLookup<M extends WindowModel = WindowModel>(ctx: WindowLookupContext, fallback: number): WindowOf<M>;
|
|
1562
|
+
export interface AssemblyMsg {
|
|
1563
|
+
role: string;
|
|
1564
|
+
content?: unknown;
|
|
1565
|
+
/** AgentMessage UserMessage compatibility (Pi requires it on outgoing messages). */
|
|
1566
|
+
timestamp?: number;
|
|
827
1567
|
}
|
|
828
|
-
export
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
/**
|
|
833
|
-
*
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
readonly generation: number;
|
|
838
|
-
/** True while a `transaction` callback is running, so a reentrant call joins
|
|
839
|
-
* the open transaction instead of nesting one, which SQLite rejects. */
|
|
840
|
-
private inTransaction;
|
|
841
|
-
/** Tokens of the runs this handle has open. Two runs can overlap inside one
|
|
842
|
-
* handle, because the blocking path stops waiting for the async pass, so a
|
|
843
|
-
* token names a run and not the handle. Whether a run this handle does not
|
|
844
|
-
* hold is still running is read from the `runs` table, never from here. */
|
|
845
|
-
private liveTokens;
|
|
846
|
-
private reapedInRun;
|
|
847
|
-
private stmts;
|
|
848
|
-
constructor(dbPath: string, opts?: {
|
|
849
|
-
readOnly?: boolean;
|
|
850
|
-
queryOnly?: boolean;
|
|
851
|
-
});
|
|
852
|
-
/** Close the handle: no run of this handle survives it, and the connection is
|
|
853
|
-
* released even when a run could not be abandoned, so a failure here cannot
|
|
854
|
-
* leave a connection and a set of invisible rows behind. */
|
|
855
|
-
close(): void;
|
|
856
|
-
/** Open a run: rows written with its token are pending until `commitRun`, and
|
|
857
|
-
* the row this writes is what lets another handle tell it from a run whose
|
|
858
|
-
* writer is gone. Reaps what such a run left, which this handle cannot be. */
|
|
859
|
-
openRun(opts?: {
|
|
860
|
-
session?: string;
|
|
861
|
-
}): {
|
|
862
|
-
token: string;
|
|
863
|
-
dropped: number;
|
|
864
|
-
};
|
|
865
|
-
/** True while any run is open whose writer is still there, this handle's or
|
|
866
|
-
* another process's, which is what an import has to wait for. */
|
|
867
|
-
hasLiveRun(): boolean;
|
|
868
|
-
/** Commit a run: drop its pending rows whose child no longer resolves, then flip
|
|
869
|
-
* the rest. One transaction, so a reader never sees a half-committed run, and
|
|
870
|
-
* idempotent for a token this handle no longer holds. */
|
|
871
|
-
commitRun(token: string): {
|
|
872
|
-
committed: number;
|
|
873
|
-
dropped: number;
|
|
874
|
-
};
|
|
875
|
-
/** Delete a run's pending rows and close it. Idempotent, so a `finally` can call
|
|
876
|
-
* it after a commit has already closed the run. */
|
|
877
|
-
abandonRun(token: string): number;
|
|
878
|
-
/** A view of this store for one run: the committed memory plus that run's own
|
|
879
|
-
* rows, which is what a pass must read to see its own writes. */
|
|
880
|
-
forRun(token: string): PassStore;
|
|
881
|
-
/** True while the run that wrote a row is still going somewhere, as far as this
|
|
882
|
-
* store can tell. A run this handle holds is live; a run written by this
|
|
883
|
-
* process is live, because a pid cannot separate two handles in one process and
|
|
884
|
-
* refusing to delete is the safe answer; a run written by another process is
|
|
885
|
-
* live while its pid is. A row whose run has no record at all was written
|
|
886
|
-
* without one, which no open path does, so nothing owns it. */
|
|
887
|
-
private isRunLive;
|
|
888
|
-
/** Delete what a run left behind whose writer is gone, and its record with it.
|
|
889
|
-
* A live owner's rows are never touched, whatever handle asks, which is what
|
|
890
|
-
* makes a second handle safe to open. No reader can see the rows this removes,
|
|
891
|
-
* and each one holds the span it claims. */
|
|
892
|
-
private reapPending;
|
|
893
|
-
/** Delete this run's pending rows whose child no longer exists, deepest first: a
|
|
894
|
-
* parent that loses a child must not leave a grandparent behind. A child can go
|
|
895
|
-
* away between the frontier read that selected it and this commit. */
|
|
896
|
-
private dropUnresolvable;
|
|
897
|
-
hasEntry(entryId: string): boolean;
|
|
898
|
-
insertMessage(m: {
|
|
899
|
-
entryId: string;
|
|
900
|
-
role: LcmRole;
|
|
901
|
-
text: string;
|
|
902
|
-
tokens: number;
|
|
903
|
-
timestamp: number;
|
|
904
|
-
payload?: string;
|
|
905
|
-
}): number;
|
|
906
|
-
/** One write transaction around a group of statements, so a compaction is
|
|
907
|
-
* atomic: the node row, its provenance rows and its file links either all
|
|
908
|
-
* land or none do. `node:sqlite` ships no transaction helper, so the
|
|
909
|
-
* statements are explicit. A reentrant call joins the open transaction: the
|
|
910
|
-
* caller that opened it owns the commit, so an inner throw rolls the whole
|
|
911
|
-
* group back rather than leaving a half-applied write. */
|
|
912
|
-
transaction<T>(fn: () => T): T;
|
|
913
|
-
insertSummary(s: InsertSummaryInput): InsertSummaryResult;
|
|
914
|
-
insertFile(d: FileDescriptor, firstEntryId: string): void;
|
|
915
|
-
linkMessageFile(messageId: number, fileId: string): void;
|
|
916
|
-
filesForMessages(ids: readonly number[]): Array<FileDescriptor & {
|
|
917
|
-
messageId: number;
|
|
918
|
-
}>;
|
|
919
|
-
fileIdsForMessages(ids: readonly number[]): string[];
|
|
920
|
-
fileIdsForSummaries(ids: readonly number[]): string[];
|
|
921
|
-
filesForSummaries(ids: readonly number[]): FileDescriptor[];
|
|
922
|
-
allFiles(): Array<FileDescriptor & {
|
|
923
|
-
entryIds: string[];
|
|
924
|
-
}>;
|
|
925
|
-
/** Replace a node's text in place: the span, provenance, and created_at stay. */
|
|
926
|
-
updateSummaryText(id: number, text: string, tokens: number): void;
|
|
927
|
-
setThoroughText(id: number, thoroughText: string): void;
|
|
928
|
-
messagesInSpan(firstEntryId: string, lastEntryId: string): StoredMessage[];
|
|
929
|
-
spanBounds(entryIds: readonly string[]): {
|
|
930
|
-
firstEntryId: string;
|
|
931
|
-
lastEntryId: string;
|
|
932
|
-
} | null;
|
|
933
|
-
uncoveredMessagesInSpan(firstEntryId: string, lastEntryId: string, run?: string): StoredMessage[];
|
|
934
|
-
/** Minimal top-level cover: no summary's child, oldest first, dropping any node
|
|
935
|
-
* whose span sits inside the previous kept one, which is what keeps the cover
|
|
936
|
-
* non-nested: condensation's group span must be one no row already holds. */
|
|
937
|
-
frontier(firstEntryId: string, lastEntryId: string, run?: string): SummaryNode[];
|
|
938
|
-
grep(pattern: string, opts?: {
|
|
939
|
-
limit?: number;
|
|
940
|
-
offset?: number;
|
|
941
|
-
withinMessageIds?: ReadonlySet<number>;
|
|
942
|
-
includeRemoved?: boolean;
|
|
943
|
-
}): GrepHit[];
|
|
944
|
-
grepCount(pattern: string, opts?: {
|
|
945
|
-
withinMessageIds?: ReadonlySet<number>;
|
|
946
|
-
includeRemoved?: boolean;
|
|
947
|
-
}): number | undefined;
|
|
948
|
-
messagePage(afterRowId: number, limit: number, opts?: {
|
|
949
|
-
withinMessageIds?: ReadonlySet<number>;
|
|
950
|
-
includeRemoved?: boolean;
|
|
951
|
-
}): ScannedRow[];
|
|
952
|
-
coveringSummaryId(entryId: string): number | undefined;
|
|
953
|
-
messageByEntryId(entryId: string): StoredMessage | undefined;
|
|
954
|
-
parentSummaryIds(summaryId: number, run?: string): number[];
|
|
955
|
-
childSummaryIds(summaryId: number): number[];
|
|
956
|
-
getSummary(id: number, run?: string): SummaryNode | undefined;
|
|
957
|
-
/** Bounded by a visited set, so a corrupt cycle cannot recurse forever. */
|
|
958
|
-
coveredMessageIds(summaryId: number, seen?: Set<number>): number[];
|
|
959
|
-
integritySnapshot(): IntegritySnapshot;
|
|
960
|
-
/** Restore the full-text index. The schema is re-applied first, because a
|
|
961
|
-
* dropped virtual table cannot be rebuilt, only recreated, and the DDL that
|
|
962
|
-
* knows how is the schema itself. Everything in it is `IF NOT EXISTS` apart
|
|
963
|
-
* from the two triggers, which are dropped and recreated on purpose. */
|
|
964
|
-
rebuildFts(): void;
|
|
965
|
-
deleteOrphanSpans(): number;
|
|
966
|
-
deleteOrphanChildren(): number;
|
|
967
|
-
reapDeadRuns(): number;
|
|
968
|
-
/** When one span is claimed twice, the row that carries provenance wins over
|
|
969
|
-
* one that cannot be expanded, and the newest wins between equals. */
|
|
970
|
-
dedupeSpans(): number;
|
|
971
|
-
/** Mark entries that left the active branch, or clear the mark when a
|
|
972
|
-
* navigation brings them back. Returns the rows the call actually changed, so
|
|
973
|
-
* a metric can tell a real change from a no-op reconciliation. */
|
|
974
|
-
setRemoved(entryIds: readonly string[], removedAt: number | null): number;
|
|
975
|
-
removedMessageCount(): number;
|
|
976
|
-
messagesByIds(ids: number[]): StoredMessage[];
|
|
977
|
-
allMessages(): StoredMessage[];
|
|
978
|
-
entrySpan(): {
|
|
979
|
-
first: string;
|
|
980
|
-
last: string;
|
|
981
|
-
} | undefined;
|
|
982
|
-
coveredSize(summaryId: number): {
|
|
983
|
-
tokens: number;
|
|
984
|
-
messages: number;
|
|
985
|
-
};
|
|
986
|
-
allSummaries(): SummaryNode[];
|
|
987
|
-
messageCount(): number;
|
|
988
|
-
backup(backupPath: string): void;
|
|
989
|
-
/** The calibration recorded for a model. `undefined` is a model nothing has
|
|
990
|
-
* sampled; a null ratio is one whose samples disagreed, which the caller reads
|
|
991
|
-
* as "keep the constant" rather than "no data". */
|
|
992
|
-
getModelState(modelKey: string): ModelState | undefined;
|
|
993
|
-
setModelState(modelKey: string, state: Pick<ModelState, "charsPerToken" | "samples">): void;
|
|
994
|
-
/** Records the window a provider stated. Its own statement rather than part of
|
|
995
|
-
* `setModelState`, because the two are learned at different moments: a later
|
|
996
|
-
* calibration must not clear a window, and a window must not touch samples. */
|
|
997
|
-
setModelWindow(modelKey: string, contextWindow: number, source: string): void;
|
|
998
|
-
stats(): {
|
|
999
|
-
messages: number;
|
|
1000
|
-
summaries: number;
|
|
1001
|
-
dbBytes: number;
|
|
1002
|
-
payloadMessages: number;
|
|
1003
|
-
payloadBytes: number;
|
|
1004
|
-
files: number;
|
|
1005
|
-
fileBytes: number;
|
|
1006
|
-
removedMessages: number;
|
|
1568
|
+
export interface AlignedEntry {
|
|
1569
|
+
entryId: string;
|
|
1570
|
+
role: string;
|
|
1571
|
+
text: string;
|
|
1572
|
+
/** Raw content blocks when `text` is not a faithful rendering of them
|
|
1573
|
+
* (`payloadOf`). Carried so ingest stores the same bytes at every call site. */
|
|
1574
|
+
payload?: string;
|
|
1575
|
+
fileHint?: {
|
|
1576
|
+
path: string;
|
|
1007
1577
|
};
|
|
1008
1578
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1579
|
+
export declare const SYNTHETIC_MARKER = "[LCM session memory";
|
|
1580
|
+
export declare function tailKeyOf(m: AssemblyMsg | undefined): string;
|
|
1581
|
+
/** Characters the estimator counts for a context: the same rendering `cutIndexFor`
|
|
1582
|
+
* measures, so a calibration describes the quantity every threshold here is
|
|
1583
|
+
* compared against. */
|
|
1584
|
+
export declare function contextChars(messages: readonly AssemblyMsg[]): number;
|
|
1585
|
+
export declare function cutIndexFor(messages: readonly AssemblyMsg[], keepRecentTokens: number): number | null;
|
|
1586
|
+
export interface SyntheticMessage {
|
|
1587
|
+
role: "user";
|
|
1588
|
+
content: string;
|
|
1589
|
+
timestamp: number;
|
|
1013
1590
|
}
|
|
1014
|
-
export
|
|
1591
|
+
export type SummaryTier = "rich" | "terse" | "stub";
|
|
1592
|
+
export interface RenderableSummary {
|
|
1593
|
+
id: number;
|
|
1594
|
+
depth: number;
|
|
1595
|
+
firstEntryId: string;
|
|
1596
|
+
lastEntryId: string;
|
|
1015
1597
|
text: string;
|
|
1016
|
-
level: 1 | 2 | 3;
|
|
1017
|
-
sourceTokens: number;
|
|
1018
|
-
retried: boolean;
|
|
1019
1598
|
thoroughText?: string;
|
|
1599
|
+
tier: SummaryTier;
|
|
1020
1600
|
}
|
|
1021
|
-
export declare
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
export declare function summarizeTerse(items: Array<{
|
|
1030
|
-
role: string;
|
|
1601
|
+
export declare const STUB_CHARS = 200;
|
|
1602
|
+
export type PlainSummary = Omit<RenderableSummary, "tier" | "thoroughText">;
|
|
1603
|
+
export declare function renderSummaries(summaries: readonly RenderableSummary[]): string;
|
|
1604
|
+
export declare function renderFrontier(nodes: readonly PlainSummary[]): string;
|
|
1605
|
+
export declare function buildSynthetic(summaries: readonly RenderableSummary[]): SyntheticMessage;
|
|
1606
|
+
export interface AppliedSummary {
|
|
1607
|
+
id: number;
|
|
1608
|
+
depth: number;
|
|
1031
1609
|
text: string;
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1610
|
+
thoroughText?: string;
|
|
1611
|
+
firstEntryId: string;
|
|
1612
|
+
lastEntryId: string;
|
|
1613
|
+
tier: SummaryTier;
|
|
1614
|
+
}
|
|
1615
|
+
export type AssemblyAction = "quiet" | "consider-apply" | "stable" | "recommit";
|
|
1616
|
+
export interface CommitState {
|
|
1617
|
+
cutCount: number;
|
|
1618
|
+
synthetic: SyntheticMessage;
|
|
1619
|
+
applied: AppliedSummary[];
|
|
1620
|
+
summaryId: number;
|
|
1621
|
+
appliedAtOccupancy: number;
|
|
1622
|
+
tailKey: string;
|
|
1623
|
+
}
|
|
1624
|
+
export declare const DEFAULT_SWAP_RATIO = 0.7;
|
|
1625
|
+
export declare const DEFAULT_RECUT_RATIO = 0.85;
|
|
1626
|
+
export declare const WINDOW_SAFETY_CEILING = 0.9;
|
|
1627
|
+
/** Ceiling for the resolved recut fraction: a line above 1.0 is unreachable, so
|
|
1628
|
+
* the pin never re-cuts and the raw tail grows past the window while "stable"
|
|
1629
|
+
* is reported forever. Strictly above WINDOW_SAFETY_CEILING + RECUT_ABOVE_SWAP_MARGIN. */
|
|
1630
|
+
export declare const RECUT_CEILING = 0.95;
|
|
1631
|
+
export declare const REZONE_MARGIN = 1.18;
|
|
1632
|
+
/** One model's thresholds. A token field takes precedence over its ratio
|
|
1633
|
+
* counterpart when both are set. */
|
|
1634
|
+
export interface ThresholdConfig {
|
|
1635
|
+
/** Swap when the request reaches this share of the model window. */
|
|
1636
|
+
swapAtRatio?: number;
|
|
1637
|
+
/** Swap when the request reaches this many tokens; takes precedence over
|
|
1638
|
+
* `swapAtRatio`. */
|
|
1639
|
+
swapAtTokens?: number;
|
|
1640
|
+
/** Re-cut when the request reaches this share of the model window. */
|
|
1641
|
+
recutAtRatio?: number;
|
|
1642
|
+
/** Re-cut when the request reaches this many tokens; takes precedence over
|
|
1643
|
+
* `recutAtRatio`. */
|
|
1644
|
+
recutAtTokens?: number;
|
|
1645
|
+
/** Swap at this absolute token count, deriving the recut from it. */
|
|
1646
|
+
smartZone?: number;
|
|
1647
|
+
}
|
|
1648
|
+
export type ZoneThresholds = ThresholdConfig;
|
|
1649
|
+
export interface ResolvedThresholds {
|
|
1650
|
+
swap: number;
|
|
1651
|
+
recut: number;
|
|
1652
|
+
clamped: boolean;
|
|
1653
|
+
}
|
|
1654
|
+
export declare function mergeZone(global: ThresholdConfig, zone?: ZoneThresholds): ThresholdConfig;
|
|
1655
|
+
export declare function thresholdConflicts(cfg: ThresholdConfig, scope?: string): string[];
|
|
1656
|
+
export declare function effectiveThresholds(cfg: ThresholdConfig, contextWindow: number): ResolvedThresholds;
|
|
1657
|
+
export declare function nextAction(state: CommitState | null, occupancy: number, swapRatio: number, recutRatio: number): AssemblyAction;
|
|
1658
|
+
export declare function cutMayReplace(previous: CommitState | null, newCutCount: number): boolean;
|
|
1036
1659
|
/** Deepest condensed node the DAG may grow. With MAX_DAG_FANOUT children this
|
|
1037
1660
|
* covers 8^8 leaf chunks, so it is a structural assert, not a tuning knob: a
|
|
1038
1661
|
* pass that cannot meet its budget reports `condensation-stalled` rather than
|
|
@@ -1081,7 +1704,7 @@ export declare function runCompaction(store: PassStore, input: CompactionInput,
|
|
|
1081
1704
|
* fallback event cannot be attributed to a session. */
|
|
1082
1705
|
session?: string;
|
|
1083
1706
|
maxDepth?: number;
|
|
1084
|
-
stage?:
|
|
1707
|
+
stage?: CompactionStage;
|
|
1085
1708
|
}): Promise<CompactionOutcome>;
|
|
1086
1709
|
/** The next group to condense: the oldest run of adjacent same-depth nodes, at
|
|
1087
1710
|
* the shallowest depth with a run of two or more, capped at MAX_DAG_FANOUT.
|
|
@@ -1090,8 +1713,6 @@ export declare function runCompaction(store: PassStore, input: CompactionInput,
|
|
|
1090
1713
|
export declare function condensationGroup(frontier: readonly SummaryNode[], opts?: {
|
|
1091
1714
|
maxDepth?: number;
|
|
1092
1715
|
}): SummaryNode[] | undefined;
|
|
1093
|
-
//#endregion
|
|
1094
|
-
//#region src/context-policy.d.ts
|
|
1095
1716
|
export interface ContextPolicyInput {
|
|
1096
1717
|
rawMessages: readonly AssemblyMsg[];
|
|
1097
1718
|
getCtxEntries: () => readonly AlignedEntry[];
|
|
@@ -1120,299 +1741,105 @@ export type BailReason = "nothing-to-age" | "no-boundary" | "tail-exceeds-entrie
|
|
|
1120
1741
|
export type NonEmpty<T> = readonly [T, ...T[]];
|
|
1121
1742
|
export interface ContextPolicyBase {
|
|
1122
1743
|
commitState: CommitState | null;
|
|
1123
|
-
metrics: LcmMetricRecord[];
|
|
1124
|
-
pinnedModelKey: string;
|
|
1125
|
-
pinnedWindow: number;
|
|
1126
|
-
clampNotified: boolean;
|
|
1127
|
-
clampWarning?: string;
|
|
1128
|
-
}
|
|
1129
|
-
export type ContextPolicyResult = ContextPolicyBase & ({
|
|
1130
|
-
outcome: "quiet";
|
|
1131
|
-
} | {
|
|
1132
|
-
outcome: "reapply";
|
|
1133
|
-
messages: AssemblyMsg[];
|
|
1134
|
-
} | {
|
|
1135
|
-
outcome: "swap";
|
|
1136
|
-
messages: AssemblyMsg[];
|
|
1137
|
-
} | {
|
|
1138
|
-
outcome: "bail";
|
|
1139
|
-
reason: BailReason;
|
|
1140
|
-
messages?: AssemblyMsg[];
|
|
1141
|
-
} | {
|
|
1142
|
-
outcome: "kick";
|
|
1143
|
-
reason: "summary-missing";
|
|
1144
|
-
aged: NonEmpty<AlignedEntry>;
|
|
1145
|
-
/** The span this kick is about, in the order the store resolves one. The
|
|
1146
|
-
* adapter keys its latch on it, so the latch and the coverage check
|
|
1147
|
-
* cannot disagree about which boundary is missing. */
|
|
1148
|
-
span: {
|
|
1149
|
-
firstEntryId: string;
|
|
1150
|
-
lastEntryId: string;
|
|
1151
|
-
};
|
|
1152
|
-
messages?: AssemblyMsg[];
|
|
1153
|
-
});
|
|
1154
|
-
export interface ContextPolicyDeps {
|
|
1155
|
-
thresholds: (cfg: ThresholdConfig, window: number) => ResolvedThresholds;
|
|
1156
|
-
nextAction: (state: CommitState | null, occupancy: number, swap: number, recut: number) => AssemblyAction;
|
|
1157
|
-
cutIndexFor: (msgs: readonly AssemblyMsg[], keepRecentTokens: number) => number | null;
|
|
1158
|
-
cutMayReplace: (previous: CommitState | null, newCutCount: number) => boolean;
|
|
1159
|
-
frontier: (firstEntryId: string, lastEntryId: string) => SummaryNode[];
|
|
1160
|
-
/** The aged set's own ends as a span: branch order, and only entries the store
|
|
1161
|
-
* holds. Pi's context array is not branch order (it hoists the newest
|
|
1162
|
-
* compaction entry to the front), and a span whose ends are inverted resolves
|
|
1163
|
-
* to nothing, so this answers with the end pair a store query can use. Null
|
|
1164
|
-
* when the store holds none of the aged entries, which is a state rather than
|
|
1165
|
-
* a kick. */
|
|
1166
|
-
spanBounds: (aged: NonEmpty<AlignedEntry>) => {
|
|
1167
|
-
firstEntryId: string;
|
|
1168
|
-
lastEntryId: string;
|
|
1169
|
-
} | null;
|
|
1170
|
-
buildSynthetic: (summaries: readonly RenderableSummary[]) => CommitState["synthetic"];
|
|
1171
|
-
/** The mask node text passes through on its way to a model. The store keeps
|
|
1172
|
-
* the bytes, so every read seam takes this rather than storing a masked
|
|
1173
|
-
* copy; `makeRedact` is the provider a real caller passes. */
|
|
1174
|
-
redact: (text: string) => string;
|
|
1175
|
-
}
|
|
1176
|
-
export interface FailedBoundary {
|
|
1177
|
-
attempts: number;
|
|
1178
|
-
lastAt: number;
|
|
1179
|
-
}
|
|
1180
|
-
export declare const KICK_MAX_ATTEMPTS = 3;
|
|
1181
|
-
export declare const KICK_BACKOFF_MS: number;
|
|
1182
|
-
export declare function shouldKickBoundary(latch: FailedBoundary | undefined, now: number): boolean;
|
|
1183
|
-
export declare function applyContextPolicy(input: ContextPolicyInput, deps: ContextPolicyDeps): ContextPolicyResult;
|
|
1184
|
-
export interface CompactionBounds {
|
|
1185
|
-
startEntryId?: string;
|
|
1186
|
-
firstKeptEntryId: string;
|
|
1187
|
-
}
|
|
1188
|
-
/** Blocking-regime span (session_before_compact), computed the way Pi's
|
|
1189
|
-
* prepareCompaction does: branch entries from the previous compaction's first
|
|
1190
|
-
* kept entry up to but excluding the new one, minus compaction rows. Entry ids,
|
|
1191
|
-
* not message text, so bash and branch-summary rows align. An unknown
|
|
1192
|
-
* firstKeptEntryId yields an empty span, so the caller falls back to Pi. */
|
|
1193
|
-
export declare function resolveCompactionSpan(branchMsgs: readonly AlignedEntry[], bounds: CompactionBounds): AlignedEntry[];
|
|
1194
|
-
//#endregion
|
|
1195
|
-
//#region src/integrity.d.ts
|
|
1196
|
-
export type FindingKind = "fts-index" | "summary-span" | "summary-shape" | "orphan-child" | "duplicate-span" | "foreign-key" | "schema-version" | "dead-run" | "coverage";
|
|
1197
|
-
export interface Finding {
|
|
1198
|
-
kind: FindingKind;
|
|
1199
|
-
/** `violation` is an invariant the DAG depends on; `coverage` is a count. */
|
|
1200
|
-
severity: "violation" | "info";
|
|
1201
|
-
count: number;
|
|
1202
|
-
detail: string;
|
|
1203
|
-
sample: string[];
|
|
1204
|
-
}
|
|
1205
|
-
export interface IntegrityReport {
|
|
1206
|
-
/** How many checks ran, so "no findings" cannot be confused with "no checks". */
|
|
1207
|
-
checks: number;
|
|
1208
|
-
findings: Finding[];
|
|
1209
|
-
}
|
|
1210
|
-
export declare function checkIntegrity(store: LcmStore): IntegrityReport;
|
|
1211
|
-
/** Fix what can be fixed without guessing: the derived index, rows whose span no
|
|
1212
|
-
* longer exists, nodes whose child no longer resolves, rows a run whose writer is
|
|
1213
|
-
* gone left behind, and predated duplicate spans. An inverted span is left
|
|
1214
|
-
* alone, because which end is right needs the session file. */
|
|
1215
|
-
export declare function repairIntegrity(store: LcmStore): string[];
|
|
1216
|
-
//#endregion
|
|
1217
|
-
//#region src/host.d.ts
|
|
1218
|
-
/** What one call was billed, in numbers, so a pass can sum its spend.
|
|
1219
|
-
* `UsageLike` in `metrics.ts` is the tolerant reader of the same facts at the
|
|
1220
|
-
* metrics boundary, and a `Usage` satisfies it. */
|
|
1221
|
-
export interface Usage {
|
|
1222
|
-
input: number;
|
|
1223
|
-
output: number;
|
|
1224
|
-
cacheRead: number;
|
|
1225
|
-
cacheWrite: number;
|
|
1226
|
-
/** A subset of `cacheWrite`, reported by hosts that split retention. */
|
|
1227
|
-
cacheWrite1h?: number;
|
|
1228
|
-
/** A subset of `output`, reported by hosts that expose the breakdown. */
|
|
1229
|
-
reasoning?: number;
|
|
1230
|
-
totalTokens: number;
|
|
1231
|
-
cost: {
|
|
1232
|
-
input: number;
|
|
1233
|
-
output: number;
|
|
1234
|
-
cacheRead: number;
|
|
1235
|
-
cacheWrite: number;
|
|
1236
|
-
total: number;
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
/** How core tells the operator something. A host with no UI is `none` rather
|
|
1240
|
-
* than a dropped callback, so a caller has one branch and no optional to miss. */
|
|
1241
|
-
export type Notices = {
|
|
1242
|
-
readonly kind: "none";
|
|
1243
|
-
} | {
|
|
1244
|
-
readonly kind: "ui";
|
|
1245
|
-
notify(text: string, level: "info" | "warning"): void;
|
|
1246
|
-
};
|
|
1247
|
-
/** One model the host can call. `key` is the spelling config zones and metric
|
|
1248
|
-
* rows already use; `model` is the host's own handle, opaque here. */
|
|
1249
|
-
export interface ModelRef<Model> {
|
|
1250
|
-
readonly key: string;
|
|
1251
|
-
readonly reasoning: boolean;
|
|
1252
|
-
readonly model: Model;
|
|
1253
|
-
}
|
|
1254
|
-
export interface ModelRequest {
|
|
1255
|
-
readonly provider: string;
|
|
1256
|
-
readonly id: string;
|
|
1257
|
-
}
|
|
1258
|
-
export interface CompletionRequest {
|
|
1259
|
-
readonly prompt: string;
|
|
1260
|
-
readonly maxTokens: number;
|
|
1261
|
-
}
|
|
1262
|
-
/** What a call came back with. `stop` is the host's own word, kept verbatim for
|
|
1263
|
-
* metric rows; `capped` is the host saying the answer was cut short, which is
|
|
1264
|
-
* the one thing a caller must never store. */
|
|
1265
|
-
export type ModelAnswer = {
|
|
1266
|
-
outcome: "answer";
|
|
1267
|
-
text: string;
|
|
1268
|
-
stop: string;
|
|
1269
|
-
usage: Usage | undefined;
|
|
1270
|
-
} | {
|
|
1271
|
-
outcome: "failed";
|
|
1272
|
-
reason: string;
|
|
1273
|
-
capped: boolean;
|
|
1274
|
-
stop: string;
|
|
1275
|
-
usage: Usage | undefined;
|
|
1276
|
-
};
|
|
1277
|
-
export interface ModelHost<Model> {
|
|
1278
|
-
readonly notices: Notices;
|
|
1279
|
-
session(): ModelRef<Model> | undefined;
|
|
1280
|
-
find(requested: ModelRequest): ModelRef<Model> | undefined;
|
|
1281
|
-
complete(model: ModelRef<Model>, request: CompletionRequest): Promise<ModelAnswer>;
|
|
1282
|
-
}
|
|
1283
|
-
/** A tool the reader may call. The schema is the host's, passed through rather
|
|
1284
|
-
* than rebuilt. */
|
|
1285
|
-
export interface ReaderTool {
|
|
1286
|
-
name: string;
|
|
1287
|
-
description: string;
|
|
1288
|
-
parameters: TSchema;
|
|
1289
|
-
}
|
|
1290
|
-
export interface ReaderCall {
|
|
1291
|
-
readonly id: string;
|
|
1292
|
-
readonly name: string;
|
|
1293
|
-
readonly arguments: Record<string, unknown>;
|
|
1294
|
-
}
|
|
1295
|
-
export interface ReaderResult {
|
|
1296
|
-
readonly call: ReaderCall;
|
|
1297
|
-
readonly text: string;
|
|
1744
|
+
metrics: LcmMetricRecord[];
|
|
1745
|
+
pinnedModelKey: string;
|
|
1746
|
+
pinnedWindow: number;
|
|
1747
|
+
clampNotified: boolean;
|
|
1748
|
+
clampWarning?: string;
|
|
1298
1749
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
export type ReaderReply = {
|
|
1302
|
-
outcome: "reads";
|
|
1303
|
-
calls: readonly ReaderCall[];
|
|
1304
|
-
text: string;
|
|
1305
|
-
usage: Usage | undefined;
|
|
1750
|
+
export type ContextPolicyResult = ContextPolicyBase & ({
|
|
1751
|
+
outcome: "quiet";
|
|
1306
1752
|
} | {
|
|
1307
|
-
outcome: "
|
|
1308
|
-
|
|
1309
|
-
usage: Usage | undefined;
|
|
1753
|
+
outcome: "reapply";
|
|
1754
|
+
messages: AssemblyMsg[];
|
|
1310
1755
|
} | {
|
|
1311
|
-
outcome: "
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
export interface
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
/**
|
|
1337
|
-
*
|
|
1338
|
-
|
|
1756
|
+
outcome: "swap";
|
|
1757
|
+
messages: AssemblyMsg[];
|
|
1758
|
+
} | {
|
|
1759
|
+
outcome: "bail";
|
|
1760
|
+
reason: BailReason;
|
|
1761
|
+
messages?: AssemblyMsg[];
|
|
1762
|
+
} | {
|
|
1763
|
+
outcome: "kick";
|
|
1764
|
+
reason: "summary-missing";
|
|
1765
|
+
aged: NonEmpty<AlignedEntry>;
|
|
1766
|
+
/** The span this kick is about, in the order the store resolves one. The
|
|
1767
|
+
* adapter keys its latch on it, so the latch and the coverage check
|
|
1768
|
+
* cannot disagree about which boundary is missing. */
|
|
1769
|
+
span: {
|
|
1770
|
+
firstEntryId: string;
|
|
1771
|
+
lastEntryId: string;
|
|
1772
|
+
};
|
|
1773
|
+
messages?: AssemblyMsg[];
|
|
1774
|
+
});
|
|
1775
|
+
export interface ContextPolicyDeps {
|
|
1776
|
+
thresholds: (cfg: ThresholdConfig, window: number) => ResolvedThresholds;
|
|
1777
|
+
nextAction: (state: CommitState | null, occupancy: number, swap: number, recut: number) => AssemblyAction;
|
|
1778
|
+
cutIndexFor: (msgs: readonly AssemblyMsg[], keepRecentTokens: number) => number | null;
|
|
1779
|
+
cutMayReplace: (previous: CommitState | null, newCutCount: number) => boolean;
|
|
1780
|
+
frontier: (firstEntryId: string, lastEntryId: string) => SummaryNode[];
|
|
1781
|
+
/** The aged set's own ends as a span: branch order, and only entries the store
|
|
1782
|
+
* holds. Pi's context array is not branch order (it hoists the newest
|
|
1783
|
+
* compaction entry to the front), and a span whose ends are inverted resolves
|
|
1784
|
+
* to nothing, so this answers with the end pair a store query can use. Null
|
|
1785
|
+
* when the store holds none of the aged entries, which is a state rather than
|
|
1786
|
+
* a kick. */
|
|
1787
|
+
spanBounds: (aged: NonEmpty<AlignedEntry>) => {
|
|
1788
|
+
firstEntryId: string;
|
|
1789
|
+
lastEntryId: string;
|
|
1790
|
+
} | null;
|
|
1791
|
+
buildSynthetic: (summaries: readonly RenderableSummary[]) => CommitState["synthetic"];
|
|
1792
|
+
/** The mask node text passes through on its way to a model. The store keeps
|
|
1793
|
+
* the bytes, so every read seam takes this rather than storing a masked
|
|
1794
|
+
* copy; `makeRedact` is the provider a real caller passes. */
|
|
1795
|
+
redact: (text: string) => string;
|
|
1339
1796
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
* top of the caller's request, so the requested output size does not change.
|
|
1344
|
-
*/
|
|
1345
|
-
export declare const REASONING_HEADROOM = 4096;
|
|
1346
|
-
export interface ResolvedChain<Model> {
|
|
1347
|
-
/** The models to try, in order and deduped. Empty when nothing resolves. */
|
|
1348
|
-
models: ModelRef<Model>[];
|
|
1349
|
-
/** Configured entries the host could not resolve, in order. */
|
|
1350
|
-
unresolved: string[];
|
|
1351
|
-
requested: string[];
|
|
1797
|
+
export interface FailedBoundary {
|
|
1798
|
+
attempts: number;
|
|
1799
|
+
lastAt: number;
|
|
1352
1800
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
export declare function
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
warning: string;
|
|
1801
|
+
export declare const KICK_MAX_ATTEMPTS = 3;
|
|
1802
|
+
export declare const KICK_BACKOFF_MS: number;
|
|
1803
|
+
export declare function shouldKickBoundary(latch: FailedBoundary | undefined, now: number): boolean;
|
|
1804
|
+
export declare function applyContextPolicy(input: ContextPolicyInput, deps: ContextPolicyDeps): ContextPolicyResult;
|
|
1805
|
+
/** The frontier as the projection renders it: every node redacted on its way to
|
|
1806
|
+
* a model, and the degradation ladder walked only until the render fits the
|
|
1807
|
+
* summary budget. The blocking compaction hook calls this too, so the text it
|
|
1808
|
+
* hands the host is the text the projection would have sent. */
|
|
1809
|
+
export declare function renderableFrontier(frontier: readonly SummaryNode[], summaryTokens: number, redact: (text: string) => string): {
|
|
1810
|
+
applied: AppliedSummary[];
|
|
1811
|
+
renderedTokens: number;
|
|
1365
1812
|
};
|
|
1366
|
-
export
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
export interface SummarizedSpan {
|
|
1370
|
-
firstEntryId: string;
|
|
1371
|
-
lastEntryId: string;
|
|
1813
|
+
export interface CompactionBounds {
|
|
1814
|
+
startEntryId?: string;
|
|
1815
|
+
firstKeptEntryId: string;
|
|
1372
1816
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1817
|
+
/** Blocking-regime span (session_before_compact), computed the way Pi's
|
|
1818
|
+
* prepareCompaction does: branch entries from the previous compaction's first
|
|
1819
|
+
* kept entry up to but excluding the new one, minus compaction rows. Entry ids,
|
|
1820
|
+
* not message text, so bash and branch-summary rows align. An unknown
|
|
1821
|
+
* firstKeptEntryId yields an empty span, so the caller falls back to Pi. */
|
|
1822
|
+
export declare function resolveCompactionSpan(branchMsgs: readonly AlignedEntry[], bounds: CompactionBounds): AlignedEntry[];
|
|
1823
|
+
export type FindingKind = "fts-index" | "summary-span" | "summary-shape" | "orphan-child" | "duplicate-span" | "foreign-key" | "schema-version" | "dead-run" | "coverage";
|
|
1824
|
+
export interface Finding {
|
|
1825
|
+
kind: FindingKind;
|
|
1826
|
+
/** `violation` is an invariant the DAG depends on; `coverage` is a count. */
|
|
1827
|
+
severity: "violation" | "info";
|
|
1828
|
+
count: number;
|
|
1829
|
+
detail: string;
|
|
1830
|
+
sample: string[];
|
|
1377
1831
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
export declare const SUMMARIZER_FAILURE_CAP = 3;
|
|
1383
|
-
/** What one request may cost before it is refused instead of paid for. It exists
|
|
1384
|
-
* for a pathological chunk rather than for a normal one. */
|
|
1385
|
-
export declare const SUMMARIZER_CALL_TOKENS = 60000;
|
|
1386
|
-
/** What one pass may spend across all of its calls. */
|
|
1387
|
-
export declare const SUMMARIZER_PASS_TOKENS = 250000;
|
|
1388
|
-
export declare function retriableCategory(category: SummarizerFailureCategory): boolean;
|
|
1389
|
-
/** How long a span whose calls were capped is left alone. Shorter than the kick
|
|
1390
|
-
* backoff (`KICK_BACKOFF_MS`) on purpose: this guards a retry inside one turn,
|
|
1391
|
-
* it is not the breaker that stops re-kicks across turns. */
|
|
1392
|
-
export declare const FAILED_SPAN_TTL_MS = 60000;
|
|
1393
|
-
/** What a failed span is identified by: the two ends a caller asked about and
|
|
1394
|
-
* the chain it asked. A different model is a different attempt, not the same
|
|
1395
|
-
* failure, so the chain is part of the key. */
|
|
1396
|
-
export declare function spanFingerprint(span: SummarizedSpan, chain: readonly string[]): string;
|
|
1397
|
-
/**
|
|
1398
|
-
* A call that reached no usable answer. `category` is what the provider's own
|
|
1399
|
-
* text says: a best-effort read that answers `unknown` rather than guessing, and
|
|
1400
|
-
* `capped` when the caller should stop asking rather than the provider failing.
|
|
1401
|
-
*/
|
|
1402
|
-
export declare class SummarizerFailure extends Error {
|
|
1403
|
-
readonly category: SummarizerFailureCategory;
|
|
1404
|
-
constructor(message: string, category: SummarizerFailureCategory);
|
|
1832
|
+
export interface IntegrityReport {
|
|
1833
|
+
/** How many checks ran, so "no findings" cannot be confused with "no checks". */
|
|
1834
|
+
checks: number;
|
|
1835
|
+
findings: Finding[];
|
|
1405
1836
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
*
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
* a session that starts fresh should not inherit a refusal from the last one. */
|
|
1413
|
-
export declare function resetSummarizerState(): void;
|
|
1414
|
-
//#endregion
|
|
1415
|
-
//#region src/report.d.ts
|
|
1837
|
+
export declare function checkIntegrity(store: LcmStore): IntegrityReport;
|
|
1838
|
+
/** Fix what can be fixed without guessing: the derived index, rows whose span no
|
|
1839
|
+
* longer exists, nodes whose child no longer resolves, rows a run whose writer is
|
|
1840
|
+
* gone left behind, and predated duplicate spans. An inverted span is left
|
|
1841
|
+
* alone, because which end is right needs the session file. */
|
|
1842
|
+
export declare function repairIntegrity(store: LcmStore): string[];
|
|
1416
1843
|
type Row = MetricRecord;
|
|
1417
1844
|
type KeysOf<T> = T extends unknown ? keyof T : never;
|
|
1418
1845
|
type ValueOf<T, K> = T extends unknown ? (K extends keyof T ? T[K] : never) : never;
|
|
@@ -1435,8 +1862,6 @@ export declare function depthStats(summaries: readonly {
|
|
|
1435
1862
|
tokens: number;
|
|
1436
1863
|
}[]): DepthStat[];
|
|
1437
1864
|
export declare function buildReport(events: readonly MetricsEvent[]): string;
|
|
1438
|
-
//#endregion
|
|
1439
|
-
//#region src/diagnose.d.ts
|
|
1440
1865
|
export interface FactConfig {
|
|
1441
1866
|
swapAtTokens?: number;
|
|
1442
1867
|
recutAtTokens?: number;
|
|
@@ -1523,43 +1948,6 @@ export declare function hostVersion(hostPackage: string, from: string | URL): st
|
|
|
1523
1948
|
* or named something else is not the answer, so a wrong anchor fails the name
|
|
1524
1949
|
* check instead of reporting a stranger's version. */
|
|
1525
1950
|
export declare function packageVersionNear(from: string, name: string): string | undefined;
|
|
1526
|
-
//#endregion
|
|
1527
|
-
//#region src/estimate-tokens.d.ts
|
|
1528
|
-
export declare const CHARS_PER_TOKEN = 4;
|
|
1529
|
-
/** Samples below this count say nothing: with two points any line fits, and the
|
|
1530
|
-
* first turns of a session are exactly where the overhead has not cancelled. */
|
|
1531
|
-
export declare const CALIBRATION_MIN_SAMPLES = 3;
|
|
1532
|
-
/** Samples kept in memory. A cap keeps the median describing the recent turns of
|
|
1533
|
-
* the session rather than its whole history, which is what a model switch inside
|
|
1534
|
-
* one session would otherwise be unable to move. */
|
|
1535
|
-
export declare const CALIBRATION_SAMPLE_CAP = 8;
|
|
1536
|
-
/** One observation of the same session: how many characters this build counted in
|
|
1537
|
-
* the messages, and how many tokens Pi reported for the request that carried
|
|
1538
|
-
* them. Sampling differences rather than totals is what cancels the system prompt
|
|
1539
|
-
* and tool definitions, which this build never counts. */
|
|
1540
|
-
export interface EstimateSample {
|
|
1541
|
-
chars: number;
|
|
1542
|
-
tokens: number;
|
|
1543
|
-
}
|
|
1544
|
-
export declare function estimateTokens(text: string): number;
|
|
1545
|
-
export declare function tokensFromChars(chars: number): number;
|
|
1546
|
-
/** A reported count below this share of the estimate describes a context other
|
|
1547
|
-
* than the one on hand: Pi reports its last measurement, and after a resume that
|
|
1548
|
-
* measurement belongs to the previous process. The estimator's own error is
|
|
1549
|
-
* bounded by the calibration band, which is a factor of two in either direction
|
|
1550
|
-
* around the constant, so anything below a quarter is a different turn rather
|
|
1551
|
-
* than a miscount. A reported count too high only compacts early, which is why
|
|
1552
|
-
* this looks one way. */
|
|
1553
|
-
export declare const STALE_REPORT_SHARE = 0.25;
|
|
1554
|
-
export declare function calibrate(samples: readonly EstimateSample[]): number | null;
|
|
1555
|
-
/** A configured token budget in the units `estimateTokens` counts. The estimator
|
|
1556
|
-
* counts chars / 4, so a model whose real ratio is 6 needs 1.5 times the
|
|
1557
|
-
* configured number of estimated tokens accumulated before it has kept the
|
|
1558
|
-
* context the user asked for. Without a ratio the budget is left alone, which is
|
|
1559
|
-
* the constant's own reading of it. */
|
|
1560
|
-
export declare function scaledBudget(tokens: number, charsPerToken: number | null): number;
|
|
1561
|
-
//#endregion
|
|
1562
|
-
//#region src/eval.d.ts
|
|
1563
1951
|
/** The seed CI runs. Fixed, so a change in the answer is a change in the engine
|
|
1564
1952
|
* rather than a change in the fixture. */
|
|
1565
1953
|
export declare const SMALL_SEED = 20260915;
|
|
@@ -1629,8 +2017,6 @@ export interface EvalRunOptions {
|
|
|
1629
2017
|
}
|
|
1630
2018
|
export declare function runEval(opts: EvalOptions, run: EvalRunOptions): Promise<EvalResult>;
|
|
1631
2019
|
export declare function formatEvalTable(results: readonly EvalResult[]): string;
|
|
1632
|
-
//#endregion
|
|
1633
|
-
//#region src/export.d.ts
|
|
1634
2020
|
/** The format this build writes, and the only one it reads. A range reads as a
|
|
1635
2021
|
* compatibility promise while the row readers ignore the number, and the store,
|
|
1636
2022
|
* not the file, holds the truth: a file that does not fit is re-exported. */
|
|
@@ -1663,6 +2049,8 @@ export interface ExportSummaryRow {
|
|
|
1663
2049
|
depth: number;
|
|
1664
2050
|
firstEntryId: string;
|
|
1665
2051
|
lastEntryId: string;
|
|
2052
|
+
/** Absent in files written before the quality verdict existed. */
|
|
2053
|
+
quality?: SummaryQuality;
|
|
1666
2054
|
messageEntryIds: string[];
|
|
1667
2055
|
childOrdinals: number[];
|
|
1668
2056
|
}
|
|
@@ -1683,12 +2071,158 @@ export declare function exportTranscript(store: LcmStore): {
|
|
|
1683
2071
|
summaries: SummaryNode[];
|
|
1684
2072
|
toJSONL: () => string;
|
|
1685
2073
|
};
|
|
1686
|
-
//#endregion
|
|
1687
|
-
//#region src/format.d.ts
|
|
1688
2074
|
export declare function fmtTokens(n: number): string;
|
|
1689
2075
|
export declare function fmtBytes(n: number): string;
|
|
1690
|
-
|
|
1691
|
-
|
|
2076
|
+
export declare function fmtDuration(ms: number): string;
|
|
2077
|
+
/** The mechanical marker shared with the store migration and import fallback.
|
|
2078
|
+
* Detection prefers the quality verdict and keeps this as the legacy signal. */
|
|
2079
|
+
export declare const HEAL_TRUNCATED_PREFIX = "[lcm:truncated]";
|
|
2080
|
+
export declare const HEAL_TARGET_TOKENS = 1500;
|
|
2081
|
+
/** The source-to-summary ratio the default window follows. A heal reads more
|
|
2082
|
+
* than it writes, but a repair that cannot see the source cannot be clean. */
|
|
2083
|
+
export declare const HEAL_SOURCE_TARGET_FACTOR = 8;
|
|
2084
|
+
export declare const HEAL_SOURCE_MIN_TOKENS = 3000;
|
|
2085
|
+
export declare const HEAL_SOURCE_MAX_TOKENS = 24000;
|
|
2086
|
+
/** One repair may read a source larger than its window, but only as a bounded
|
|
2087
|
+
* map-reduce: the fanout matches the DAG's maximum child count, so a windowed
|
|
2088
|
+
* repair cannot grow calls without a limit. */
|
|
2089
|
+
export declare const HEAL_MAX_WINDOWS_PER_NODE = 8;
|
|
2090
|
+
/** Room for the system prompt and the summary instruction under a call cap. */
|
|
2091
|
+
export declare const HEAL_PROMPT_TOKENS = 1024;
|
|
2092
|
+
/** Kept for callers that still name the old constant. The value is the floor,
|
|
2093
|
+
* not a fixed per-message clip. */
|
|
2094
|
+
export declare const HEAL_LEAF_CHUNK_TOKENS = 3000;
|
|
2095
|
+
export declare const HEAL_MAX_NODES = 32;
|
|
2096
|
+
export declare const HEAL_TOKEN_BUDGET = 250000;
|
|
2097
|
+
export interface HealSourceBudgetInput {
|
|
2098
|
+
targetTokens?: number;
|
|
2099
|
+
/** An explicit source window. The derived default is `8 * targetTokens`,
|
|
2100
|
+
* clamped to the source floor and ceiling and then to the call budget. */
|
|
2101
|
+
sourceTokens?: number;
|
|
2102
|
+
callBudgetTokens?: number;
|
|
2103
|
+
}
|
|
2104
|
+
export interface HealInput {
|
|
2105
|
+
/** Session tag for every engine row and every ladder call this run makes. */
|
|
2106
|
+
session?: string;
|
|
2107
|
+
/** Tokens one repaired node targets. A result above twice this is rejected. */
|
|
2108
|
+
targetTokens?: number;
|
|
2109
|
+
/** Estimated tokens one map window may read. Defaults to `8 * targetTokens`,
|
|
2110
|
+
* clamped to 3,000 through 24,000 and to the request budget. A larger leaf
|
|
2111
|
+
* source uses up to eight windows and one reduce; it is never clipped to a
|
|
2112
|
+
* per-message slice. */
|
|
2113
|
+
sourceTokens?: number;
|
|
2114
|
+
/** @deprecated Use `sourceTokens`. Kept so a caller from the first heal
|
|
2115
|
+
* release keeps working; it now means the same per-window budget. */
|
|
2116
|
+
leafChunkTokens?: number;
|
|
2117
|
+
/** Tokens one summarizer request may cost. The source window is clamped so
|
|
2118
|
+
* its prompt, output, and reserve fit this cap. */
|
|
2119
|
+
callBudgetTokens?: number;
|
|
2120
|
+
/** Nodes this run may attempt. Ancestors only run after their children. */
|
|
2121
|
+
maxNodes?: number;
|
|
2122
|
+
/** Model calls this run may make. Defaults to twice `maxNodes`. */
|
|
2123
|
+
maxCalls?: number;
|
|
2124
|
+
/** Estimated tokens this run may spend. The provider's own budget still runs. */
|
|
2125
|
+
maxTokens?: number;
|
|
2126
|
+
/** Report the plan and make no model call. */
|
|
2127
|
+
dryRun?: boolean;
|
|
2128
|
+
/** Masks text on its way to the model and into the replacement node. */
|
|
2129
|
+
redact?: (text: string) => string;
|
|
2130
|
+
/** Override for tests and imports; the deterministic prefix is the default. */
|
|
2131
|
+
truncatedPrefix?: string;
|
|
2132
|
+
}
|
|
2133
|
+
export interface HealCandidate {
|
|
2134
|
+
id: number;
|
|
2135
|
+
nodeKind: "leaf" | "condensed";
|
|
2136
|
+
depth: number;
|
|
2137
|
+
quality: SummaryQuality;
|
|
2138
|
+
/** The node's own text is the deterministic truncate, either by verdict or by
|
|
2139
|
+
* the prefix fallback. */
|
|
2140
|
+
mechanical: boolean;
|
|
2141
|
+
/** The node is a seed: its quality is not clean, or its text carries the
|
|
2142
|
+
* mechanical prefix. */
|
|
2143
|
+
tainted: boolean;
|
|
2144
|
+
/** Candidate parents, in id order. A dry run reports the dependency shape
|
|
2145
|
+
* through these edges; repair order is always child before parent. */
|
|
2146
|
+
parents: number[];
|
|
2147
|
+
}
|
|
2148
|
+
export interface HealNodeResult {
|
|
2149
|
+
id: number;
|
|
2150
|
+
nodeKind: "leaf" | "condensed";
|
|
2151
|
+
depth: number;
|
|
2152
|
+
tainted: boolean;
|
|
2153
|
+
qualityBefore: SummaryQuality;
|
|
2154
|
+
/** The quality this run stored, or absent when the old row stands. A failed
|
|
2155
|
+
* node keeps its persisted verdict, which is what the next run seeds on. */
|
|
2156
|
+
qualityAfter?: SummaryQuality;
|
|
2157
|
+
outcome: HealNodeOutcomeKind;
|
|
2158
|
+
reason?: string;
|
|
2159
|
+
/** 3 when the text is the deterministic truncate, null otherwise. */
|
|
2160
|
+
levelBefore: SummaryLevel | null;
|
|
2161
|
+
/** The level stored by this run, or null when the old text stands. */
|
|
2162
|
+
levelAfter: SummaryLevel | null;
|
|
2163
|
+
beforeTokens: number;
|
|
2164
|
+
afterTokens: number | null;
|
|
2165
|
+
calls: number;
|
|
2166
|
+
/** Source facts for the attempted node. They never include source text. */
|
|
2167
|
+
sourceTokens?: number;
|
|
2168
|
+
sourceChars?: number;
|
|
2169
|
+
sourceClipped?: boolean;
|
|
2170
|
+
}
|
|
2171
|
+
export interface HealCounts {
|
|
2172
|
+
candidates: number;
|
|
2173
|
+
tainted: number;
|
|
2174
|
+
attempted: number;
|
|
2175
|
+
/** Nodes whose new text is clean. An improved node is not a repair. */
|
|
2176
|
+
repaired: number;
|
|
2177
|
+
/** Nodes whose text changed but remains clipped or derived. */
|
|
2178
|
+
improved: number;
|
|
2179
|
+
failed: number;
|
|
2180
|
+
/** Dependency-blocked plus source-blocked nodes. */
|
|
2181
|
+
blocked: number;
|
|
2182
|
+
/** The source-blocked subset of `blocked`. */
|
|
2183
|
+
blockedSource: number;
|
|
2184
|
+
skipped: number;
|
|
2185
|
+
changed: number;
|
|
2186
|
+
notAttempted: number;
|
|
2187
|
+
}
|
|
2188
|
+
export interface HealOutcome {
|
|
2189
|
+
dryRun: boolean;
|
|
2190
|
+
stop: HealStopReason;
|
|
2191
|
+
/** The effective map window for this run, in estimated tokens. */
|
|
2192
|
+
sourceTokens: number;
|
|
2193
|
+
callBudgetTokens: number;
|
|
2194
|
+
candidates: HealCandidate[];
|
|
2195
|
+
nodes: HealNodeResult[];
|
|
2196
|
+
repaired: number[];
|
|
2197
|
+
improved: number[];
|
|
2198
|
+
failed: number[];
|
|
2199
|
+
blocked: number[];
|
|
2200
|
+
/** The subset of `blocked` whose source exceeded the window. */
|
|
2201
|
+
blockedSource: number[];
|
|
2202
|
+
skipped: number[];
|
|
2203
|
+
changed: number[];
|
|
2204
|
+
notAttempted: number[];
|
|
2205
|
+
calls: number;
|
|
2206
|
+
tokens: number;
|
|
2207
|
+
counts: HealCounts;
|
|
2208
|
+
}
|
|
2209
|
+
/** The source window a repair is allowed to read, in estimated tokens.
|
|
2210
|
+
*
|
|
2211
|
+
* The default is a multiple of the summary target, so the one quality knob a
|
|
2212
|
+
* user already tunes moves the source with it. A fixed source window has the
|
|
2213
|
+
* failure this policy exists to remove: every larger input clips at the same
|
|
2214
|
+
* place forever. The window is still bounded by the request cap, so a large
|
|
2215
|
+
* source cannot turn into a prompt the provider will refuse.
|
|
2216
|
+
*/
|
|
2217
|
+
export declare function healSourceBudget(input?: HealSourceBudgetInput): number;
|
|
2218
|
+
/**
|
|
2219
|
+
* Re-derive mechanical summaries from the source they still point at, then
|
|
2220
|
+
* recompute every ancestor whose child changed. The messages table is never
|
|
2221
|
+
* written: a leaf is rebuilt from its exact provenance rows, a condensed node
|
|
2222
|
+
* from its exact children, and only a node whose text still matches the read
|
|
2223
|
+
* that produced the replacement is written at all.
|
|
2224
|
+
*/
|
|
2225
|
+
export declare function runHeal(store: HealStore, input: HealInput, llm: LlmComplete): Promise<HealOutcome>;
|
|
1692
2226
|
/** Escape hatch for a live session that carries a test signal for another
|
|
1693
2227
|
* reason, so the guard can never brick one. */
|
|
1694
2228
|
export declare const ALLOW_REAL_HOME_FLAG = "LCM_ALLOW_REAL_HOME";
|
|
@@ -1697,8 +2231,6 @@ export declare function isUnderTempDir(path: string): boolean;
|
|
|
1697
2231
|
* guard covers them all instead of one per call site. A test run refuses a path
|
|
1698
2232
|
* outside its temp home rather than touching the real config. */
|
|
1699
2233
|
export declare function lcmHomePath(...segments: string[]): string;
|
|
1700
|
-
//#endregion
|
|
1701
|
-
//#region src/import.d.ts
|
|
1702
2234
|
export interface ImportResult {
|
|
1703
2235
|
messagesInserted: number;
|
|
1704
2236
|
messagesSkipped: number;
|
|
@@ -1719,8 +2251,6 @@ export type ParsedExport = {
|
|
|
1719
2251
|
* the caller can report a line number. */
|
|
1720
2252
|
export declare function parseExport(text: string): ParsedExport;
|
|
1721
2253
|
export declare function importTranscript(store: LcmStore, rows: readonly ExportRow[]): ImportResult;
|
|
1722
|
-
//#endregion
|
|
1723
|
-
//#region src/ingest.d.ts
|
|
1724
2254
|
export interface IngestibleEntry {
|
|
1725
2255
|
entryId: string;
|
|
1726
2256
|
role: LcmRole;
|
|
@@ -1789,23 +2319,6 @@ export declare function ingestEntries(store: IngestStore, entries: readonly Inge
|
|
|
1789
2319
|
largeFileChars: number;
|
|
1790
2320
|
};
|
|
1791
2321
|
}): IngestStats;
|
|
1792
|
-
//#endregion
|
|
1793
|
-
//#region src/overflow-window.d.ts
|
|
1794
|
-
/** A window outside this band is a misread rather than a model: no provider
|
|
1795
|
-
* serves a context under a thousand tokens or over a hundred million, and a
|
|
1796
|
-
* number outside it means a pattern caught the request count, a status code, or
|
|
1797
|
-
* something else that was not a limit. A refused parse is the floor path, which
|
|
1798
|
-
* costs one conservative turn instead of a wrong permanent window. */
|
|
1799
|
-
export declare const WINDOW_MIN = 1000;
|
|
1800
|
-
export declare const WINDOW_MAX = 100000000;
|
|
1801
|
-
/** The window in tokens a provider stated in an overflow message, or null when
|
|
1802
|
-
* the message does not name one. Null is an answer: the caller keeps Pi's belief
|
|
1803
|
-
* and arms the one-shot floor, which is all a body like "400/413 status code (no
|
|
1804
|
-
* body)" can support. */
|
|
1805
|
-
export declare function windowFromError(errorMessage: string | undefined): number | null;
|
|
1806
|
-
export declare const WINDOW_PATTERN_EXAMPLES: readonly string[];
|
|
1807
|
-
//#endregion
|
|
1808
|
-
//#region src/recall-view.d.ts
|
|
1809
2322
|
export declare const GREP_PAGE_HITS = 20;
|
|
1810
2323
|
export type ViewResult = {
|
|
1811
2324
|
ok: true;
|
|
@@ -1827,6 +2340,8 @@ export declare function grepView(store: LcmStore, redact: (text: string) => stri
|
|
|
1827
2340
|
* names. A page cannot express where a clock ran out. */
|
|
1828
2341
|
after?: string;
|
|
1829
2342
|
now?: () => number;
|
|
2343
|
+
/** Tool the continuation marker names. Defaults to `lcm_expand_query`. */
|
|
2344
|
+
expandTool?: string;
|
|
1830
2345
|
}): ViewResult;
|
|
1831
2346
|
export declare function describeView(store: LcmStore, redact: (text: string) => string, id: number): ViewResult;
|
|
1832
2347
|
/** List the summary's messages, or read one of them as a window of characters.
|
|
@@ -1847,8 +2362,6 @@ export type ExpandParams = {
|
|
|
1847
2362
|
};
|
|
1848
2363
|
export declare function expandView(store: LcmStore, redact: (text: string) => string, params: ExpandParams): ViewResult;
|
|
1849
2364
|
export declare function historyManifest(store: LcmStore, limit: number): string;
|
|
1850
|
-
//#endregion
|
|
1851
|
-
//#region src/redact.d.ts
|
|
1852
2365
|
/** Redactor that re-reads the flag on every call, so a cockpit toggle takes
|
|
1853
2366
|
* effect immediately: a getter, because patchConfig replaces the config object
|
|
1854
2367
|
* and closing over it would pin the session-start value. */
|
|
@@ -1856,8 +2369,6 @@ export declare function makeRedact(getConfig: () => {
|
|
|
1856
2369
|
redactSecrets?: boolean;
|
|
1857
2370
|
}): (text: string) => string;
|
|
1858
2371
|
export declare function redactSecrets(text: string): string;
|
|
1859
|
-
//#endregion
|
|
1860
|
-
//#region src/retention.d.ts
|
|
1861
2372
|
export declare const SCRATCH_MAX_AGE_MS: number;
|
|
1862
2373
|
export interface DbFootprint {
|
|
1863
2374
|
path: string;
|
|
@@ -1880,8 +2391,6 @@ export declare function retentionCandidates(opts: {
|
|
|
1880
2391
|
now: number;
|
|
1881
2392
|
scratchMaxAgeMs: number;
|
|
1882
2393
|
}): GcCandidate[];
|
|
1883
|
-
//#endregion
|
|
1884
|
-
//#region src/retrieval.d.ts
|
|
1885
2394
|
export declare const RETRIEVAL_STEPS = 6;
|
|
1886
2395
|
export declare const MANIFEST_NODES = 20;
|
|
1887
2396
|
export declare const MAX_EXPAND_CHARS = 20000;
|
|
@@ -1927,8 +2436,6 @@ export type RetrievalRun = (RetrievalBase & {
|
|
|
1927
2436
|
reason: string;
|
|
1928
2437
|
});
|
|
1929
2438
|
export declare function runRetrieval(store: LcmStore, request: RetrievalRequest, reader: Reader): Promise<RetrievalRun>;
|
|
1930
|
-
//#endregion
|
|
1931
|
-
//#region src/sessions.d.ts
|
|
1932
2439
|
/** Most recent past sessions one scan considers. A cap, not a promise: the
|
|
1933
2440
|
* deadline below is what bounds the work. */
|
|
1934
2441
|
export declare const SESSION_SCAN_LIMIT = 20;
|
|
@@ -2015,5 +2522,5 @@ export declare function sessionsForScope(opts: {
|
|
|
2015
2522
|
excludeHash?: string;
|
|
2016
2523
|
limit?: number;
|
|
2017
2524
|
}): SessionRef[];
|
|
2018
|
-
|
|
2525
|
+
export {};
|
|
2019
2526
|
//# sourceMappingURL=index.d.mts.map
|