@hypercarrier/rarebit 0.1.0-alpha.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/AGENTS.md +14 -0
- package/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/RUNBOOK.md +36 -0
- package/SECURITY.md +11 -0
- package/VISUAL-LANGUAGE.md +89 -0
- package/bin/rarebit.mjs +20 -0
- package/package.json +96 -0
- package/schemas/rarebit.schema.json +618 -0
- package/scripts/e2e-recall-pi083.mjs +199 -0
- package/scripts/e2e-summary-cache.mjs +257 -0
- package/scripts/verify-package.mjs +81 -0
- package/src/automatic-summary-policy.mjs +210 -0
- package/src/extension.mjs +516 -0
- package/src/herdr-activity.mjs +111 -0
- package/src/index.mjs +13 -0
- package/src/lifecycle.mjs +322 -0
- package/src/rarebit-activity.mjs +48 -0
- package/src/rarebit-artifact-state.mjs +839 -0
- package/src/rarebit-cli.mjs +305 -0
- package/src/rarebit-command.mjs +281 -0
- package/src/rarebit-core.mjs +525 -0
- package/src/rarebit-model.mjs +226 -0
- package/src/rarebit-recall.mjs +182 -0
- package/src/rarebit-service.mjs +579 -0
- package/src/rarebit-session.mjs +227 -0
- package/src/rarebit-settings.mjs +79 -0
- package/src/rarebit-store.mjs +771 -0
- package/src/rarebit-visual-language.mjs +110 -0
- package/src/types.d.ts +601 -0
- package/test/automatic-summary-policy.test.mjs +476 -0
- package/test/extension-activity.test.mjs +73 -0
- package/test/fixtures/branch-mixed.json +117 -0
- package/test/fixtures/conformance-corpus.v1.json +306 -0
- package/test/fixtures/truncated-mixed.jsonl +2 -0
- package/test/herdr-activity.test.mjs +93 -0
- package/test/lifecycle-contract-independent.test.mjs +627 -0
- package/test/rarebit-activity.test.mjs +82 -0
- package/test/rarebit-artifact-state.test.mjs +440 -0
- package/test/rarebit-cli.test.mjs +222 -0
- package/test/rarebit-command.test.mjs +127 -0
- package/test/rarebit-core.test.mjs +461 -0
- package/test/rarebit-cross-consumer-conformance.test.mjs +150 -0
- package/test/rarebit-independent-contract.test.mjs +317 -0
- package/test/rarebit-recall.test.mjs +434 -0
- package/test/rarebit-sidecar-protocol.test.mjs +538 -0
- package/test/rarebit-visual-language.test.mjs +75 -0
- package/test/summary-attention-independent.test.mjs +172 -0
- package/test/types-history.ts +13 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/** Pi-facing model adapter. It is deliberately outside the functional core. */
|
|
2
|
+
|
|
3
|
+
function nonEmptyString(value) {
|
|
4
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function finiteNonNegative(value) {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
9
|
+
? value
|
|
10
|
+
: null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveRarebitModelConfiguration(config = {}) {
|
|
14
|
+
if (config.modelConfigurationError) {
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
model: null,
|
|
18
|
+
provenance: {
|
|
19
|
+
source: "configuration",
|
|
20
|
+
settingsKey: "rarebit.model",
|
|
21
|
+
status: "invalid",
|
|
22
|
+
...config.modelProvenance,
|
|
23
|
+
},
|
|
24
|
+
error: String(config.modelConfigurationError),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const provider = nonEmptyString(config.model?.provider);
|
|
28
|
+
const id = nonEmptyString(config.model?.id);
|
|
29
|
+
if (!provider || !id) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
model: null,
|
|
33
|
+
provenance: {
|
|
34
|
+
source: "configuration",
|
|
35
|
+
settingsKey: "rarebit.model",
|
|
36
|
+
status: "missing",
|
|
37
|
+
...config.modelProvenance,
|
|
38
|
+
},
|
|
39
|
+
error:
|
|
40
|
+
"No Rarebit model resolved; set rarebit.model in Pi settings or inject config.model",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
model: { provider, id },
|
|
46
|
+
provenance: {
|
|
47
|
+
source: "explicit_config",
|
|
48
|
+
status: "resolved",
|
|
49
|
+
...config.modelProvenance,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function createPiRarebitModelClient(ctx, config = {}) {
|
|
55
|
+
const { complete, getModel } = config.piAi ?? {};
|
|
56
|
+
if (typeof complete !== "function")
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Pi AI contract is unavailable; load the Rarebit Pi extension or provide modelClient/piAi",
|
|
59
|
+
);
|
|
60
|
+
const resolution = resolveRarebitModelConfiguration(config);
|
|
61
|
+
if (!resolution.ok) throw new Error(resolution.error);
|
|
62
|
+
const model =
|
|
63
|
+
ctx?.modelRegistry?.find?.(
|
|
64
|
+
resolution.model.provider,
|
|
65
|
+
resolution.model.id,
|
|
66
|
+
) ??
|
|
67
|
+
(typeof getModel === "function"
|
|
68
|
+
? getModel(resolution.model.provider, resolution.model.id)
|
|
69
|
+
: undefined);
|
|
70
|
+
if (!model)
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Pi model not found: ${resolution.model.provider}/${resolution.model.id}`,
|
|
73
|
+
);
|
|
74
|
+
const auth = await ctx?.modelRegistry?.getApiKeyAndHeaders?.(model);
|
|
75
|
+
if (!auth?.ok)
|
|
76
|
+
throw new Error(
|
|
77
|
+
auth?.error ??
|
|
78
|
+
`No request auth for ${resolution.model.provider}/${resolution.model.id}`,
|
|
79
|
+
);
|
|
80
|
+
return {
|
|
81
|
+
async complete(request) {
|
|
82
|
+
return complete(
|
|
83
|
+
model,
|
|
84
|
+
{
|
|
85
|
+
messages: [
|
|
86
|
+
{
|
|
87
|
+
role: "user",
|
|
88
|
+
content: [{ type: "text", text: request.prompt }],
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
apiKey: auth.apiKey,
|
|
95
|
+
headers: auth.headers,
|
|
96
|
+
env: auth.env,
|
|
97
|
+
reasoningEffort: "low",
|
|
98
|
+
sessionId: nonEmptyString(request.cacheSessionId),
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function firstNumber(response, paths) {
|
|
106
|
+
for (const [source, get] of paths) {
|
|
107
|
+
const value = finiteNonNegative(get(response));
|
|
108
|
+
if (value !== null) return { value, source };
|
|
109
|
+
}
|
|
110
|
+
return { value: null, source: null };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function firstString(response, paths) {
|
|
114
|
+
for (const [source, get] of paths) {
|
|
115
|
+
const value = nonEmptyString(get(response));
|
|
116
|
+
if (value) return { value, source };
|
|
117
|
+
}
|
|
118
|
+
return { value: null, source: null };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A bounded, machine-only provider receipt. Prompts and raw responses never enter it. */
|
|
122
|
+
export function extractRarebitSynthesisReceipt(
|
|
123
|
+
response,
|
|
124
|
+
{
|
|
125
|
+
requestedModel,
|
|
126
|
+
startedAt,
|
|
127
|
+
completedAt,
|
|
128
|
+
durationMs,
|
|
129
|
+
outcome = "response",
|
|
130
|
+
} = {},
|
|
131
|
+
) {
|
|
132
|
+
const fields = {
|
|
133
|
+
inputTokens: firstNumber(response, [
|
|
134
|
+
["response.usage.input", (r) => r?.usage?.input],
|
|
135
|
+
["response.usage.input_tokens", (r) => r?.usage?.input_tokens],
|
|
136
|
+
["response.usage.prompt_tokens", (r) => r?.usage?.prompt_tokens],
|
|
137
|
+
]),
|
|
138
|
+
outputTokens: firstNumber(response, [
|
|
139
|
+
["response.usage.output", (r) => r?.usage?.output],
|
|
140
|
+
["response.usage.output_tokens", (r) => r?.usage?.output_tokens],
|
|
141
|
+
["response.usage.completion_tokens", (r) => r?.usage?.completion_tokens],
|
|
142
|
+
]),
|
|
143
|
+
totalTokens: firstNumber(response, [
|
|
144
|
+
["response.usage.totalTokens", (r) => r?.usage?.totalTokens],
|
|
145
|
+
["response.usage.total_tokens", (r) => r?.usage?.total_tokens],
|
|
146
|
+
]),
|
|
147
|
+
cacheReadTokens: firstNumber(response, [
|
|
148
|
+
["response.usage.cacheRead", (r) => r?.usage?.cacheRead],
|
|
149
|
+
["response.usage.cached_tokens", (r) => r?.usage?.cached_tokens],
|
|
150
|
+
]),
|
|
151
|
+
cacheWriteTokens: firstNumber(response, [
|
|
152
|
+
["response.usage.cacheWrite", (r) => r?.usage?.cacheWrite],
|
|
153
|
+
]),
|
|
154
|
+
reasoningTokens: firstNumber(response, [
|
|
155
|
+
["response.usage.reasoning", (r) => r?.usage?.reasoning],
|
|
156
|
+
[
|
|
157
|
+
"response.usage.output_tokens_details.reasoning_tokens",
|
|
158
|
+
(r) => r?.usage?.output_tokens_details?.reasoning_tokens,
|
|
159
|
+
],
|
|
160
|
+
]),
|
|
161
|
+
estimatedCostUsd: firstNumber(response, [
|
|
162
|
+
["response.usage.cost.total", (r) => r?.usage?.cost?.total],
|
|
163
|
+
["response.usage.total_cost", (r) => r?.usage?.total_cost],
|
|
164
|
+
]),
|
|
165
|
+
};
|
|
166
|
+
const measured = Object.values(fields).filter(
|
|
167
|
+
({ value }) => value !== null,
|
|
168
|
+
).length;
|
|
169
|
+
const availability =
|
|
170
|
+
measured === 0
|
|
171
|
+
? "unavailable"
|
|
172
|
+
: fields.inputTokens.value !== null &&
|
|
173
|
+
fields.outputTokens.value !== null &&
|
|
174
|
+
fields.totalTokens.value !== null
|
|
175
|
+
? "reported"
|
|
176
|
+
: "partial";
|
|
177
|
+
const responseId = firstString(response, [
|
|
178
|
+
["response.responseId", (r) => r?.responseId],
|
|
179
|
+
["response.id", (r) => r?.id],
|
|
180
|
+
]);
|
|
181
|
+
const requestId = firstString(response, [
|
|
182
|
+
["response.requestId", (r) => r?.requestId],
|
|
183
|
+
]);
|
|
184
|
+
const responseProvider = firstString(response, [
|
|
185
|
+
["response.provider", (r) => r?.provider],
|
|
186
|
+
]);
|
|
187
|
+
const responseModel = firstString(response, [
|
|
188
|
+
["response.responseModel", (r) => r?.responseModel],
|
|
189
|
+
["response.model", (r) => r?.model],
|
|
190
|
+
]);
|
|
191
|
+
return {
|
|
192
|
+
schemaVersion: 1,
|
|
193
|
+
kind: "rarebit_model_synthesis",
|
|
194
|
+
outcome,
|
|
195
|
+
requestedModel: requestedModel ?? null,
|
|
196
|
+
timing: {
|
|
197
|
+
startedAt: startedAt ?? null,
|
|
198
|
+
completedAt: completedAt ?? null,
|
|
199
|
+
durationMs: finiteNonNegative(durationMs),
|
|
200
|
+
provenance: "local_monotonic_clock",
|
|
201
|
+
},
|
|
202
|
+
provider: {
|
|
203
|
+
responseProvider: responseProvider.value,
|
|
204
|
+
responseProviderSource: responseProvider.source,
|
|
205
|
+
responseModel: responseModel.value,
|
|
206
|
+
responseModelSource: responseModel.source,
|
|
207
|
+
responseId: responseId.value,
|
|
208
|
+
responseIdSource: responseId.source,
|
|
209
|
+
requestId: requestId.value,
|
|
210
|
+
requestIdSource: requestId.source,
|
|
211
|
+
},
|
|
212
|
+
usage: {
|
|
213
|
+
availability,
|
|
214
|
+
inputTokens: fields.inputTokens.value,
|
|
215
|
+
outputTokens: fields.outputTokens.value,
|
|
216
|
+
totalTokens: fields.totalTokens.value,
|
|
217
|
+
cacheReadTokens: fields.cacheReadTokens.value,
|
|
218
|
+
cacheWriteTokens: fields.cacheWriteTokens.value,
|
|
219
|
+
reasoningTokens: fields.reasoningTokens.value,
|
|
220
|
+
estimatedCostUsd: fields.estimatedCostUsd.value,
|
|
221
|
+
provenance: Object.fromEntries(
|
|
222
|
+
Object.entries(fields).map(([name, field]) => [name, field.source]),
|
|
223
|
+
),
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, chmod, mkdtemp, open, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { selectRarebits } from "./rarebit-core.mjs";
|
|
7
|
+
|
|
8
|
+
export const RAREBIT_RECALL_SCHEMA_VERSION = 1;
|
|
9
|
+
export const RAREBIT_CONVERSATION_SCHEMA_VERSION = 1;
|
|
10
|
+
export const RAREBIT_RECALL_DETAILED_FILENAME = "rarebit-evidence.json";
|
|
11
|
+
export const RAREBIT_RECALL_CONVERSATION_FILENAME = "rarebit-conversation.json";
|
|
12
|
+
|
|
13
|
+
function requiredSessionIdentity(ctx) {
|
|
14
|
+
const sessionFile = ctx?.sessionManager?.getSessionFile?.();
|
|
15
|
+
if (typeof sessionFile !== "string" || !sessionFile.trim()) {
|
|
16
|
+
throw new Error("the current Pi Session has no persisted JSONL file");
|
|
17
|
+
}
|
|
18
|
+
const sessionId = ctx?.sessionManager?.getHeader?.()?.id ?? ctx?.sessionId;
|
|
19
|
+
if (typeof sessionId !== "string" || !sessionId.trim()) {
|
|
20
|
+
throw new Error("the current Pi Session has no stable ID");
|
|
21
|
+
}
|
|
22
|
+
return { sessionId, sessionFile: resolve(sessionFile) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function activeBranchFrom(ctx) {
|
|
26
|
+
const branch = ctx?.sessionManager?.getBranch?.();
|
|
27
|
+
if (!Array.isArray(branch)) {
|
|
28
|
+
throw new Error("the current Pi Session active branch is unavailable");
|
|
29
|
+
}
|
|
30
|
+
return branch;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function recallDocument({ sessionId, sessionFile, branch, selection, now }) {
|
|
34
|
+
return {
|
|
35
|
+
schemaVersion: RAREBIT_RECALL_SCHEMA_VERSION,
|
|
36
|
+
type: "rarebit_message_recall",
|
|
37
|
+
createdAt: now().toISOString(),
|
|
38
|
+
evidenceAuthority: {
|
|
39
|
+
type: "pi_session_jsonl",
|
|
40
|
+
path: sessionFile,
|
|
41
|
+
},
|
|
42
|
+
session: {
|
|
43
|
+
id: sessionId,
|
|
44
|
+
activeBranch: {
|
|
45
|
+
leafEntryId: branch.at(-1)?.id ?? null,
|
|
46
|
+
entryIds: branch.map((entry) => entry?.id ?? null),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
selection: {
|
|
50
|
+
selectorVersion: selection.manifest.selectorVersion,
|
|
51
|
+
manifestHash: selection.manifestHash,
|
|
52
|
+
occurrenceCount: selection.occurrences.length,
|
|
53
|
+
uniquePayloadCount: selection.payloads.length,
|
|
54
|
+
occurrences: selection.occurrences.map(
|
|
55
|
+
({
|
|
56
|
+
occurrenceId,
|
|
57
|
+
sourceEntryId,
|
|
58
|
+
order,
|
|
59
|
+
timestamp,
|
|
60
|
+
role,
|
|
61
|
+
outcome,
|
|
62
|
+
contentHash,
|
|
63
|
+
text,
|
|
64
|
+
}) => ({
|
|
65
|
+
occurrenceId,
|
|
66
|
+
sourceEntryId,
|
|
67
|
+
order,
|
|
68
|
+
timestamp,
|
|
69
|
+
role,
|
|
70
|
+
outcome,
|
|
71
|
+
contentHash,
|
|
72
|
+
text,
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function conversationHour(timestamp) {
|
|
80
|
+
if (typeof timestamp !== "string") return null;
|
|
81
|
+
const value = new Date(timestamp);
|
|
82
|
+
if (Number.isNaN(value.getTime())) return null;
|
|
83
|
+
return `${value.toISOString().slice(0, 13)}:00Z`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function conversationDocument(selection) {
|
|
87
|
+
const byHour = new Map();
|
|
88
|
+
for (const occurrence of selection.occurrences) {
|
|
89
|
+
const hour = conversationHour(occurrence.timestamp);
|
|
90
|
+
let bucket = byHour.get(hour);
|
|
91
|
+
if (!bucket) {
|
|
92
|
+
bucket = { hour, messages: [] };
|
|
93
|
+
byHour.set(hour, bucket);
|
|
94
|
+
}
|
|
95
|
+
bucket.messages.push({
|
|
96
|
+
role: occurrence.role === "user" ? "user" : "agent",
|
|
97
|
+
content: occurrence.text,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const hours = [...byHour.values()].sort((left, right) => {
|
|
101
|
+
if (left.hour === null) return right.hour === null ? 0 : 1;
|
|
102
|
+
if (right.hour === null) return -1;
|
|
103
|
+
return left.hour.localeCompare(right.hour);
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
schemaVersion: RAREBIT_CONVERSATION_SCHEMA_VERSION,
|
|
107
|
+
type: "rarebit_conversation",
|
|
108
|
+
timeZone: "UTC",
|
|
109
|
+
hours,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function writePrivateJson(path, document) {
|
|
114
|
+
const handle = await open(path, "wx", 0o600);
|
|
115
|
+
try {
|
|
116
|
+
await handle.writeFile(`${JSON.stringify(document, null, 2)}\n`, "utf8");
|
|
117
|
+
await handle.sync();
|
|
118
|
+
} finally {
|
|
119
|
+
await handle.close();
|
|
120
|
+
}
|
|
121
|
+
await chmod(path, 0o600);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Materialize an exact, active-branch Rarebit evidence projection for one
|
|
126
|
+
* one-off Recall. The native Session remains authority; this bundle is
|
|
127
|
+
* an ephemeral, private transport artifact and is never written to the repo.
|
|
128
|
+
*/
|
|
129
|
+
export async function materializeRarebitRecall(
|
|
130
|
+
ctx,
|
|
131
|
+
{
|
|
132
|
+
tempRoot = tmpdir(),
|
|
133
|
+
now = () => new Date(),
|
|
134
|
+
writeJson = writePrivateJson,
|
|
135
|
+
} = {},
|
|
136
|
+
) {
|
|
137
|
+
const { sessionId, sessionFile } = requiredSessionIdentity(ctx);
|
|
138
|
+
await access(sessionFile, constants.R_OK);
|
|
139
|
+
const branch = activeBranchFrom(ctx);
|
|
140
|
+
const selection = selectRarebits(branch);
|
|
141
|
+
const root = resolve(tempRoot);
|
|
142
|
+
const directory = await mkdtemp(join(root, "hc-rarebit-recall-"));
|
|
143
|
+
const conversationPath = join(
|
|
144
|
+
directory,
|
|
145
|
+
RAREBIT_RECALL_CONVERSATION_FILENAME,
|
|
146
|
+
);
|
|
147
|
+
const detailedPath = join(directory, RAREBIT_RECALL_DETAILED_FILENAME);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await chmod(directory, 0o700);
|
|
151
|
+
const detailed = recallDocument({
|
|
152
|
+
sessionId,
|
|
153
|
+
sessionFile,
|
|
154
|
+
branch,
|
|
155
|
+
selection,
|
|
156
|
+
now,
|
|
157
|
+
});
|
|
158
|
+
const conversation = conversationDocument(selection);
|
|
159
|
+
await writeJson(conversationPath, conversation);
|
|
160
|
+
await writeJson(detailedPath, detailed);
|
|
161
|
+
if (!isAbsolute(conversationPath) || !isAbsolute(detailedPath)) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"Rarebit recall materialization did not produce absolute paths",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
conversationPath,
|
|
168
|
+
detailedPath,
|
|
169
|
+
directory,
|
|
170
|
+
sessionId,
|
|
171
|
+
sessionFile,
|
|
172
|
+
branchLeafId: detailed.session.activeBranch.leafEntryId,
|
|
173
|
+
selectedMessageCount: selection.occurrences.length,
|
|
174
|
+
selectorVersion: selection.manifest.selectorVersion,
|
|
175
|
+
manifestHash: selection.manifestHash,
|
|
176
|
+
discard: () => rm(directory, { recursive: true, force: true }),
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
await rm(directory, { recursive: true, force: true }).catch(() => {});
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|