@hypercarrier/rarebit 0.1.0-alpha.4 → 0.1.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,241 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, rename, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { selectRarebits, sha256 } from "./rarebit-core.mjs";
5
+ import {
6
+ RAREBIT_FORK_ENTRY_MARKER_VERSION,
7
+ RAREBIT_FORK_LINEAGE_CUSTOM_TYPE,
8
+ RAREBIT_FORK_LINEAGE_SCHEMA_VERSION,
9
+ RAREBIT_FORK_OPENING_MARKER,
10
+ getRarebitForkSeedEntryIds,
11
+ } from "./rarebit-fork-lineage.mjs";
12
+
13
+ export const RAREBIT_FORK_DEFAULT_MAX_TOKEN_LENGTH = 64_000;
14
+ export const RAREBIT_FORK_DEFAULT_RESERVE_TOKENS = 16_384;
15
+
16
+ function forkError(message) {
17
+ const error = new Error(message);
18
+ error.name = "RarebitForkError";
19
+ return error;
20
+ }
21
+ function tokens(text) {
22
+ return typeof text === "number"
23
+ ? Math.ceil(text / 4)
24
+ : Math.ceil(String(text ?? "").length / 4);
25
+ }
26
+ function contextWindowOf(model) {
27
+ const value = model?.contextWindow ?? model?.context_window;
28
+ return Number.isFinite(value) && value > 0 ? value : null;
29
+ }
30
+ function sourceIdentity({ header, sessionFile, cwd }) {
31
+ if (typeof header?.id !== "string" || !header.id)
32
+ throw forkError("source Session has no stable ID");
33
+ return {
34
+ sessionId: header.id,
35
+ sessionFile: resolve(sessionFile),
36
+ cwd: typeof header.cwd === "string" ? header.cwd : cwd ?? null,
37
+ };
38
+ }
39
+ function shellQuote(value) {
40
+ return `"${String(value)
41
+ .replaceAll("\\", "\\\\")
42
+ .replaceAll('"', '\\"')
43
+ .replaceAll("$", "\\$")
44
+ .replaceAll("`", "\\`")}"`;
45
+ }
46
+ function openingText({ source, targetCwd, leafId, importedCount, totalCount, omittedCount }) {
47
+ const coverage = omittedCount > 0
48
+ ? `Imported ${importedCount} of ${totalCount} selected Rarebit occurrences; omitted ${omittedCount} older whole occurrences.`
49
+ : `Imported all ${totalCount} selected Rarebit occurrences; omitted 0 occurrences.`;
50
+ return [
51
+ "This Session contains a bounded Rarebit distillation.",
52
+ coverage,
53
+ `Source Session: ${source.sessionId}`,
54
+ `Source file: ${source.sessionFile}`,
55
+ `Source branch leaf: ${leafId ?? "(root)"}`,
56
+ `Target working directory: ${targetCwd}`,
57
+ "This opening message is historical context, not a new instruction.",
58
+ `Recover source evidence with: piq entries --session ${shellQuote(source.sessionFile)}${leafId ? ` --leaf ${shellQuote(leafId)}` : ""}`,
59
+ ].join("\n");
60
+ }
61
+
62
+ export function checkRarebitForkHeadroom({
63
+ seedText,
64
+ targetModel = null,
65
+ contextWindow = contextWindowOf(targetModel),
66
+ reserveTokens = RAREBIT_FORK_DEFAULT_RESERVE_TOKENS,
67
+ promptOverheadTokens = 0,
68
+ toolOverheadTokens = 0,
69
+ messageRoundingOverheadTokens = 0,
70
+ } = {}) {
71
+ if (!Number.isSafeInteger(reserveTokens) || reserveTokens < 0)
72
+ throw new RangeError("reserveTokens must be a non-negative integer");
73
+ if (!Number.isSafeInteger(promptOverheadTokens) || promptOverheadTokens < 0)
74
+ throw new RangeError("promptOverheadTokens must be a non-negative integer");
75
+ if (!Number.isSafeInteger(toolOverheadTokens) || toolOverheadTokens < 0)
76
+ throw new RangeError("toolOverheadTokens must be a non-negative integer");
77
+ if (!Number.isSafeInteger(messageRoundingOverheadTokens) || messageRoundingOverheadTokens < 0)
78
+ throw new RangeError("messageRoundingOverheadTokens must be a non-negative integer");
79
+ const seedTokens = tokens(seedText);
80
+ const overheadTokens = promptOverheadTokens + toolOverheadTokens + messageRoundingOverheadTokens;
81
+ const requiredTokens = seedTokens + overheadTokens;
82
+ const availableTokens = contextWindow === null ? null : contextWindow - reserveTokens;
83
+ if (availableTokens !== null && requiredTokens > availableTokens) {
84
+ const label = `${targetModel?.provider ?? "unknown"}/${targetModel?.id ?? "unknown"}`;
85
+ throw forkError(
86
+ `Rarebit fork seed needs about ${requiredTokens} tokens, but ${label} leaves ${Math.max(0, availableTokens)} tokens after the native reserve of ${reserveTokens}; increase the target model context window or lower --max-token-length before switching`,
87
+ );
88
+ }
89
+ return { seedTokens, overheadTokens, requiredTokens, contextWindow, reserveTokens, availableTokens,
90
+ basis: "native_contextWindow_minus_reserveTokens_plus_prompt_and_tool_overhead" };
91
+ }
92
+
93
+ function previousMappings(branch) {
94
+ const map = new Map();
95
+ for (const entry of branch) {
96
+ if (entry?.rarebitFork?.kind === "import" && Array.isArray(entry.rarebitFork.ancestry)) {
97
+ map.set(entry.id, { importedEntryId: entry.id, ancestry: entry.rarebitFork.ancestry });
98
+ }
99
+ if (entry?.type !== "custom" || entry.customType !== RAREBIT_FORK_LINEAGE_CUSTOM_TYPE) continue;
100
+ for (const mapping of entry.data?.mappings ?? []) {
101
+ if (typeof mapping?.importedEntryId === "string" && !map.has(mapping.importedEntryId)) map.set(mapping.importedEntryId, mapping);
102
+ }
103
+ }
104
+ return map;
105
+ }
106
+
107
+ export function buildRarebitForkPlan({
108
+ header, sessionFile, cwd, branch, targetCwd, maxTokenLength = RAREBIT_FORK_DEFAULT_MAX_TOKEN_LENGTH,
109
+ targetModel, contextWindow, reserveTokens = RAREBIT_FORK_DEFAULT_RESERVE_TOKENS,
110
+ promptOverheadTokens = 0, toolOverheadTokens = 0, forkCreatedAt = new Date().toISOString(),
111
+ } = {}) {
112
+ if (!Number.isSafeInteger(maxTokenLength) || maxTokenLength < 1)
113
+ throw new RangeError("maxTokenLength must be a positive integer");
114
+ if (!Array.isArray(branch)) throw forkError("source active branch is unavailable");
115
+ const source = sourceIdentity({ header, sessionFile, cwd });
116
+ const selection = selectRarebits(branch);
117
+ const seedIds = getRarebitForkSeedEntryIds(branch);
118
+ const candidates = selection.occurrences.filter((item) => !seedIds.has(item.sourceEntryId));
119
+ const selected = [];
120
+ let retainedChars = 0;
121
+ for (let index = candidates.length - 1; index >= 0; index -= 1) {
122
+ const item = candidates[index];
123
+ const candidateChars = retainedChars + item.text.length;
124
+ if (tokens(candidateChars) > maxTokenLength) {
125
+ if (selected.length === 0)
126
+ throw forkError(`The newest Rarebit occurrence needs about ${tokens(item.text)} tokens, which exceeds --max-token-length ${maxTokenLength}; raise the budget or shorten the newest source message before forking`);
127
+ break;
128
+ }
129
+ selected.unshift(item);
130
+ retainedChars = candidateChars;
131
+ }
132
+ const omittedCount = candidates.length - selected.length;
133
+ const leafId = branch.at(-1)?.id ?? null;
134
+ const resolvedTargetCwd = resolve(targetCwd ?? cwd ?? process.cwd());
135
+ const text = openingText({ source, targetCwd: resolvedTargetCwd, leafId, importedCount: selected.length, totalCount: candidates.length, omittedCount });
136
+ const headroom = checkRarebitForkHeadroom({
137
+ seedText: `${text}\n${selected.map((item) => item.text).join("\n")}`,
138
+ targetModel, contextWindow: contextWindow ?? contextWindowOf(targetModel), reserveTokens,
139
+ promptOverheadTokens, toolOverheadTokens,
140
+ messageRoundingOverheadTokens: selected.length + 1,
141
+ });
142
+ return {
143
+ schemaVersion: RAREBIT_FORK_LINEAGE_SCHEMA_VERSION, source, targetCwd: resolvedTargetCwd,
144
+ branchLeafId: leafId, forkCreatedAt, openingText: text, candidates, selected,
145
+ importedChars: retainedChars,
146
+ importedTokens: tokens(retainedChars),
147
+ sourceModels: new Map(selection.occurrences.map((item) => {
148
+ const sourceEntry = branch.find((entry) => entry?.id === item.sourceEntryId)
149
+ ?? branch[item.order];
150
+ const marker = sourceEntry?.rarebitFork;
151
+ const message = sourceEntry?.message;
152
+ const originalModel = marker && Object.hasOwn(marker, "originalModel")
153
+ ? marker.originalModel
154
+ : (message?.provider && message?.model
155
+ ? { provider: message.provider, id: message.model }
156
+ : null);
157
+ return [item.occurrenceId, originalModel];
158
+ })),
159
+ omittedOccurrences: omittedCount, maxTokenLength, headroom,
160
+ omission: { strategy: "newest_contiguous_suffix_whole_occurrences", totalOccurrenceCount: candidates.length,
161
+ importedOccurrenceCount: selected.length, omittedOccurrenceCount: omittedCount,
162
+ importedTokens: tokens(retainedChars), maxTokenLength },
163
+ priorMappings: previousMappings(branch), selectionManifestHash: selection.manifestHash, targetModel,
164
+ };
165
+ }
166
+
167
+ function randomEntryId(used) {
168
+ let id;
169
+ do id = randomUUID().replaceAll("-", "").slice(0, 8); while (used.has(id));
170
+ used.add(id); return id;
171
+ }
172
+ function sourceLocation(plan, occurrence) {
173
+ return { sessionId: plan.source.sessionId, entryId: occurrence.sourceEntryId,
174
+ sourceOrder: occurrence.order,
175
+ ...(typeof occurrence.timestamp === "string" ? { timestamp: occurrence.timestamp } : {}) };
176
+ }
177
+ function zeroUsage() {
178
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
179
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
180
+ }
181
+ function importedMessage(item, timestamp) {
182
+ if (item.role === "user") return { role: "user", content: item.text, timestamp: Date.parse(timestamp) };
183
+ return { role: "assistant", content: [{ type: "text", text: item.text }],
184
+ api: "rarebit-import", provider: "rarebit-import", model: "rarebit-import", usage: zeroUsage(),
185
+ stopReason: item.outcome === "continuation" ? "toolUse" : "stop", timestamp: Date.parse(timestamp) };
186
+ }
187
+ function marker(plan, item, ancestry, kind = "import") {
188
+ return { version: RAREBIT_FORK_ENTRY_MARKER_VERSION, kind,
189
+ ...(kind === "seed" ? { opening: RAREBIT_FORK_OPENING_MARKER } : {
190
+ sourceSessionId: plan.source.sessionId, sourceEntryId: item.sourceEntryId, occurrenceId: item.occurrenceId,
191
+ outcome: item.outcome, role: item.role, originalTimestamp: item.timestamp,
192
+ originalModel: plan.sourceModels.get(item.occurrenceId) ?? null, ancestry,
193
+ }) };
194
+ }
195
+
196
+ export function createRarebitForkEntries(plan, { sessionId = randomUUID() } = {}) {
197
+ const used = new Set(); const timestamp = plan.forkCreatedAt; const entries = [];
198
+ let parentId = null;
199
+ const seedEntryId = randomEntryId(used);
200
+ entries.push({ type: "message", id: seedEntryId, parentId, timestamp,
201
+ rarebitFork: marker(plan, null, [], "seed"), message: { role: "user", content: plan.openingText, timestamp: Date.parse(timestamp) } });
202
+ parentId = seedEntryId;
203
+ const importedEntryIds = []; const mappings = [];
204
+ for (const item of plan.selected) {
205
+ const id = randomEntryId(used); const prior = plan.priorMappings.get(item.sourceEntryId);
206
+ const ancestry = [...(prior?.ancestry ?? []), sourceLocation(plan, item)];
207
+ entries.push({ type: "message", id, parentId, timestamp, rarebitFork: marker(plan, item, ancestry), message: importedMessage(item, timestamp) });
208
+ importedEntryIds.push(id);
209
+ mappings.push({ importedEntryId: id, sourceEntryId: item.sourceEntryId, sourceOccurrenceId: item.occurrenceId,
210
+ role: item.role, outcome: item.outcome, originalTimestamp: item.timestamp,
211
+ originalModel: plan.sourceModels.get(item.occurrenceId) ?? null, ancestry });
212
+ parentId = id;
213
+ }
214
+ const manifest = { schemaVersion: RAREBIT_FORK_LINEAGE_SCHEMA_VERSION, type: "rarebit_fork_lineage",
215
+ source: plan.source, targetCwd: plan.targetCwd, sourceLeafEntryId: plan.branchLeafId, forkCreatedAt: timestamp,
216
+ opening: { marker: RAREBIT_FORK_OPENING_MARKER, seedEntryId, textHash: sha256(plan.openingText) }, omission: plan.omission,
217
+ selectionManifestHash: plan.selectionManifestHash, importedEntryIds, mappings };
218
+ entries.push({ type: "custom", id: randomEntryId(used), parentId, timestamp, customType: RAREBIT_FORK_LINEAGE_CUSTOM_TYPE, data: manifest });
219
+ if (plan.targetModel?.provider && plan.targetModel?.id) {
220
+ entries.push({ type: "model_change", id: randomEntryId(used), parentId: entries.at(-1).id, timestamp,
221
+ provider: plan.targetModel.provider, modelId: plan.targetModel.id });
222
+ }
223
+ return { sessionId, header: { type: "session", version: 3, id: sessionId, timestamp, cwd: plan.targetCwd, parentSession: plan.source.sessionFile }, entries, manifest };
224
+ }
225
+
226
+ export async function writeRarebitForkFile(path, session, { writeFileImpl = writeFile, renameImpl = rename, removeImpl = rm } = {}) {
227
+ const target = resolve(path); await mkdir(dirname(target), { recursive: true, mode: 0o700 });
228
+ const temporary = join(dirname(target), `.${target.split("/").at(-1)}.${randomUUID()}.tmp`);
229
+ try {
230
+ await writeFileImpl(temporary, `${[session.header, ...session.entries].map(JSON.stringify).join("\n")}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
231
+ await renameImpl(temporary, target); return target;
232
+ } catch (error) { await removeImpl(temporary, { force: true }).catch(() => {}); throw error; }
233
+ }
234
+ export function sessionFilenameFor({ sessionDir, forkCreatedAt, sessionId }) {
235
+ return join(resolve(sessionDir), `${new Date(forkCreatedAt).toISOString().replace(/[:.]/g, "-")}_${sessionId}.jsonl`);
236
+ }
237
+ export function forkPlanSummary(plan) {
238
+ return { maxTokenLength: plan.maxTokenLength, importedTokens: plan.importedTokens, importedOccurrenceCount: plan.selected.length,
239
+ omittedOccurrenceCount: plan.omittedOccurrences, sourceSessionId: plan.source.sessionId, sourceLeafEntryId: plan.branchLeafId,
240
+ targetCwd: plan.targetCwd, headroom: plan.headroom };
241
+ }
@@ -52,10 +52,17 @@ export function resolveRarebitModelConfiguration(config = {}) {
52
52
  }
53
53
 
54
54
  export async function createPiRarebitModelClient(ctx, config = {}) {
55
- const { complete, getModel } = config.piAi ?? {};
56
- if (typeof complete !== "function")
55
+ const { complete: fallbackComplete, getModel } = config.piAi ?? {};
56
+ const runtimeComplete =
57
+ typeof ctx?.modelRegistry?.complete === "function"
58
+ ? ctx.modelRegistry.complete.bind(ctx.modelRegistry)
59
+ : undefined;
60
+ if (
61
+ typeof runtimeComplete !== "function" &&
62
+ typeof fallbackComplete !== "function"
63
+ )
57
64
  throw new Error(
58
- "Pi AI contract is unavailable; load the Rarebit Pi extension or provide modelClient/piAi",
65
+ "Pi model completion is unavailable; load the Rarebit Pi extension or provide modelClient/piAi",
59
66
  );
60
67
  const resolution = resolveRarebitModelConfiguration(config);
61
68
  if (!resolution.ok) throw new Error(resolution.error);
@@ -79,7 +86,8 @@ export async function createPiRarebitModelClient(ctx, config = {}) {
79
86
  );
80
87
  return {
81
88
  async complete(request) {
82
- return complete(
89
+ const completion = runtimeComplete ?? fallbackComplete;
90
+ return completion(
83
91
  model,
84
92
  {
85
93
  messages: [
@@ -1,163 +1,17 @@
1
- import { open, readFile, readdir, stat } from "node:fs/promises";
2
- import { homedir } from "node:os";
3
- import { join, resolve, sep } from "node:path";
1
+ import {
2
+ DEFAULT_PI_SESSION_ROOT,
3
+ readPiSession,
4
+ resolvePiSessionFile,
5
+ } from "./pi-session.mjs";
4
6
  import {
5
7
  RAREBIT_SELECTOR_VERSION,
6
8
  measureRarebits,
7
9
  selectRarebits,
8
10
  } from "./rarebit-core.mjs";
9
11
 
10
- export const DEFAULT_PI_SESSION_ROOT = join(
11
- homedir(),
12
- ".pi",
13
- "agent",
14
- "sessions",
15
- );
12
+ export { DEFAULT_PI_SESSION_ROOT, parseNativeSession, resolveActiveBranch } from "./pi-session.mjs";
16
13
  export const RAREBIT_QUERY_SCHEMA_VERSION = 1;
17
-
18
- function queryError(message) {
19
- const error = new Error(message);
20
- error.name = "RarebitQueryError";
21
- return error;
22
- }
23
-
24
- async function isRegularFile(path) {
25
- try {
26
- return (await stat(path)).isFile();
27
- } catch (error) {
28
- if (error?.code === "ENOENT") return false;
29
- throw error;
30
- }
31
- }
32
-
33
- async function* jsonlFiles(root) {
34
- let entries;
35
- try {
36
- entries = await readdir(root, { withFileTypes: true });
37
- } catch (error) {
38
- if (error?.code === "ENOENT") return;
39
- throw error;
40
- }
41
- for (const entry of entries) {
42
- const path = join(root, entry.name);
43
- if (entry.isDirectory()) yield* jsonlFiles(path);
44
- else if (entry.isFile() && entry.name.endsWith(".jsonl")) yield path;
45
- }
46
- }
47
-
48
- async function readHeader(path) {
49
- const handle = await open(path, "r");
50
- try {
51
- const buffer = Buffer.alloc(16384);
52
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
53
- const firstLine = buffer
54
- .subarray(0, bytesRead)
55
- .toString("utf8")
56
- .split("\n", 1)[0]
57
- ?.trim();
58
- if (!firstLine) return null;
59
- try {
60
- const header = JSON.parse(firstLine);
61
- return header?.type === "session" ? header : null;
62
- } catch {
63
- return null;
64
- }
65
- } finally {
66
- await handle.close();
67
- }
68
- }
69
-
70
- function looksLikePath(reference) {
71
- return (
72
- reference.includes(sep) ||
73
- reference.startsWith(".") ||
74
- reference.endsWith(".jsonl")
75
- );
76
- }
77
-
78
- /** Resolve either an existing JSONL path or one exact Pi Session ID. */
79
- export async function resolveRarebitSessionFile(
80
- reference,
81
- { sessionRoot = DEFAULT_PI_SESSION_ROOT } = {},
82
- ) {
83
- if (typeof reference !== "string" || !reference.trim())
84
- throw queryError("--session requires an exact Pi Session path or ID");
85
- const input = reference.trim();
86
- const path = resolve(input);
87
- if (await isRegularFile(path)) return path;
88
- if (looksLikePath(input))
89
- throw queryError(`Pi Session file not found: ${input}`);
90
- const matches = [];
91
- for await (const candidate of jsonlFiles(resolve(sessionRoot))) {
92
- const header = await readHeader(candidate);
93
- if (header?.id === input) matches.push(candidate);
94
- }
95
- if (matches.length === 0)
96
- throw queryError(`No persisted Pi Session has exact ID ${input}`);
97
- if (matches.length > 1)
98
- throw queryError(
99
- `Pi Session ID ${input} is ambiguous across ${matches.length} files; pass an exact path`,
100
- );
101
- return resolve(matches[0]);
102
- }
103
-
104
- export function parseNativeSession(content, source = "Pi Session") {
105
- const records = [];
106
- for (const [index, line] of String(content).split("\n").entries()) {
107
- if (!line.trim()) continue;
108
- try {
109
- records.push(JSON.parse(line));
110
- } catch {
111
- throw queryError(
112
- `${source} contains malformed JSON at line ${index + 1}`,
113
- );
114
- }
115
- }
116
- const header = records[0];
117
- if (header?.type !== "session" || typeof header.id !== "string" || !header.id)
118
- throw queryError(`${source} does not begin with a valid Pi Session header`);
119
- const entries = records.slice(1);
120
- const byId = new Map();
121
- if ((header.version ?? 1) < 2) return { header, entries, byId, linear: true };
122
- for (const entry of entries) {
123
- if (typeof entry?.id !== "string" || !entry.id)
124
- throw queryError(`${source} contains a Session entry without an ID`);
125
- if (byId.has(entry.id))
126
- throw queryError(
127
- `${source} contains duplicate Session entry ID ${entry.id}`,
128
- );
129
- byId.set(entry.id, entry);
130
- }
131
- return { header, entries, byId, linear: false };
132
- }
133
-
134
- /** Match Pi's persisted active branch: the final entry is the active leaf. */
135
- export function resolveActiveBranch(
136
- { entries, byId, linear },
137
- source = "Pi Session",
138
- ) {
139
- if (linear) return entries.slice();
140
- const leaf = entries.at(-1);
141
- if (!leaf) return [];
142
- const reversed = [];
143
- const visited = new Set();
144
- let current = leaf;
145
- while (current) {
146
- if (visited.has(current.id))
147
- throw queryError(
148
- `${source} contains a cycle at Session entry ${current.id}`,
149
- );
150
- visited.add(current.id);
151
- reversed.push(current);
152
- if (current.parentId === null || current.parentId === undefined) break;
153
- current = byId.get(current.parentId);
154
- if (!current)
155
- throw queryError(
156
- `${source} is missing parent Session entry ${reversed.at(-1).parentId}`,
157
- );
158
- }
159
- return reversed.reverse();
160
- }
14
+ export const resolveRarebitSessionFile = resolvePiSessionFile;
161
15
 
162
16
  function sessionProjection(parsed, branch) {
163
17
  const selection = selectRarebits(branch);
@@ -181,13 +35,11 @@ function sessionProjection(parsed, branch) {
181
35
  }
182
36
 
183
37
  export async function readRarebitSession(reference, options = {}) {
184
- const sessionFile = await resolveRarebitSessionFile(reference, options);
185
- const parsed = parseNativeSession(
186
- await readFile(sessionFile, "utf8"),
187
- sessionFile,
188
- );
189
- const branch = resolveActiveBranch(parsed, sessionFile);
190
- return { sessionFile, parsed, branch, ...sessionProjection(parsed, branch) };
38
+ const loaded = await readPiSession(reference, options);
39
+ return {
40
+ ...loaded,
41
+ ...sessionProjection(loaded.parsed, loaded.branch),
42
+ };
191
43
  }
192
44
 
193
45
  /** Metadata-only projection, safe for a dashboard or programmatic inspection. */
package/src/types.d.ts CHANGED
@@ -1,5 +1,35 @@
1
1
  export type RarebitModel = { provider: string; id: string };
2
2
 
3
+ /** Seeded destination entries, including the generated fork notice. */
4
+ export function getImportedRarebitEntryIds(entries: readonly unknown[]): Set<string>;
5
+ export const RAREBIT_FORK_LINEAGE_SCHEMA_VERSION: 1;
6
+ export const RAREBIT_FORK_ENTRY_MARKER_VERSION: "rarebit-fork-entry/1";
7
+ export const RAREBIT_FORK_LINEAGE_CUSTOM_TYPE: "rarebit-fork-lineage";
8
+ export const RAREBIT_FORK_OPENING_MARKER: "rarebit-fork-opening-v1";
9
+ export const RAREBIT_FORK_DEFAULT_MAX_TOKEN_LENGTH: 64000;
10
+ export const RAREBIT_FORK_DEFAULT_RESERVE_TOKENS: 16384;
11
+ export type RarebitForkHeadroom = {
12
+ seedTokens: number;
13
+ overheadTokens: number;
14
+ requiredTokens: number;
15
+ contextWindow: number | null;
16
+ reserveTokens: number;
17
+ availableTokens: number | null;
18
+ basis: string;
19
+ };
20
+ /** A null context window yields an estimate without admission. Runtime adapters must require a known window before creating a Session. */
21
+ export function checkRarebitForkHeadroom(options: {
22
+ seedText: string;
23
+ targetModel?: RarebitModel | null;
24
+ contextWindow?: number | null;
25
+ reserveTokens?: number;
26
+ promptOverheadTokens?: number;
27
+ toolOverheadTokens?: number;
28
+ messageRoundingOverheadTokens?: number;
29
+ }): RarebitForkHeadroom;
30
+ export function getRarebitForkSeedEntryIds(entries: readonly unknown[]): Set<string>;
31
+ export function getRarebitForkLineageRecords(entries: readonly unknown[]): readonly Record<string, unknown>[];
32
+
3
33
  export type RarebitAutomaticSummaryPolicyDecision =
4
34
  | {
5
35
  contractVersion: "rarebit-automatic-summary-policy/1";
@@ -119,6 +149,7 @@ export function projectRarebitSessionActivity(selection: {
119
149
  occurrences?: RarebitOccurrence[];
120
150
  manifest?: { selectorVersion?: string };
121
151
  manifestHash?: string;
152
+ entries?: readonly unknown[];
122
153
  }): RarebitSessionActivity;
123
154
 
124
155
  export type RarebitVisualTone =