enigma-memory 0.1.3 → 0.1.5
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/docs/benchmark-reproducibility.md +128 -30
- package/docs/developer-ecosystem.md +13 -3
- package/docs/memory-benchmarks.md +53 -15
- package/docs/sdk-api.md +1 -1
- package/examples/ci/github-actions.yml +27 -1
- package/package.json +5 -1
- package/packages/mcp-server/src/index.js +1 -1
- package/packages/passport/src/index.js +125 -4
- package/scripts/build-installer-assets.mjs +1 -1
- package/scripts/download-standard-benchmarks.mjs +399 -0
- package/scripts/run-memory-benchmarks.mjs +11 -0
- package/scripts/run-standard-memory-benchmarks.mjs +1070 -0
|
@@ -36,6 +36,125 @@ const PUBLIC_PLAINTEXT_KEYS = new Set(['body', 'content', 'context', 'contexttex
|
|
|
36
36
|
const SOURCE_REF_ROOT_RE = /^sha256:[a-f0-9]{64}$/;
|
|
37
37
|
const PUBLIC_KEY_REQUIRED_FOR_CONTEXT_PACK_VERIFICATION = 'PUBLIC_KEY_REQUIRED_FOR_CONTEXT_PACK_VERIFICATION';
|
|
38
38
|
|
|
39
|
+
const QUERY_RELEVANCE_STOPWORDS = new Set([
|
|
40
|
+
'about',
|
|
41
|
+
'after',
|
|
42
|
+
'again',
|
|
43
|
+
'against',
|
|
44
|
+
'also',
|
|
45
|
+
'and',
|
|
46
|
+
'any',
|
|
47
|
+
'are',
|
|
48
|
+
'assistant',
|
|
49
|
+
'because',
|
|
50
|
+
'been',
|
|
51
|
+
'before',
|
|
52
|
+
'being',
|
|
53
|
+
'between',
|
|
54
|
+
'can',
|
|
55
|
+
'could',
|
|
56
|
+
'current',
|
|
57
|
+
'does',
|
|
58
|
+
'from',
|
|
59
|
+
'has',
|
|
60
|
+
'have',
|
|
61
|
+
'how',
|
|
62
|
+
'into',
|
|
63
|
+
'its',
|
|
64
|
+
'latest',
|
|
65
|
+
'more',
|
|
66
|
+
'most',
|
|
67
|
+
'number',
|
|
68
|
+
'own',
|
|
69
|
+
'owns',
|
|
70
|
+
'please',
|
|
71
|
+
'should',
|
|
72
|
+
'that',
|
|
73
|
+
'the',
|
|
74
|
+
'their',
|
|
75
|
+
'then',
|
|
76
|
+
'there',
|
|
77
|
+
'these',
|
|
78
|
+
'they',
|
|
79
|
+
'this',
|
|
80
|
+
'use',
|
|
81
|
+
'using',
|
|
82
|
+
'was',
|
|
83
|
+
'what',
|
|
84
|
+
'when',
|
|
85
|
+
'where',
|
|
86
|
+
'which',
|
|
87
|
+
'who',
|
|
88
|
+
'whose',
|
|
89
|
+
'why',
|
|
90
|
+
'with',
|
|
91
|
+
'would',
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
function addMeaningfulToken(tokens, token) {
|
|
95
|
+
if (token.length < 3) return;
|
|
96
|
+
if (!/[a-z]/u.test(token)) return;
|
|
97
|
+
if (QUERY_RELEVANCE_STOPWORDS.has(token)) return;
|
|
98
|
+
tokens.add(token);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function meaningfulTokensFrom(value) {
|
|
102
|
+
const tokens = new Set();
|
|
103
|
+
if (value === undefined || value === null) return tokens;
|
|
104
|
+
for (const match of String(value).toLowerCase().matchAll(/[a-z0-9]+(?:[-_][a-z0-9]+)*/gu)) {
|
|
105
|
+
const token = match[0];
|
|
106
|
+
addMeaningfulToken(tokens, token);
|
|
107
|
+
if (token.includes('-') || token.includes('_')) {
|
|
108
|
+
for (const part of token.split(/[-_]+/u)) addMeaningfulToken(tokens, part);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return tokens;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function addTokensFromValue(tokens, value) {
|
|
115
|
+
for (const token of meaningfulTokensFrom(value)) tokens.add(token);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function candidateRelevanceTokens(candidate) {
|
|
119
|
+
const tokens = new Set();
|
|
120
|
+
addTokensFromValue(tokens, candidate.content);
|
|
121
|
+
addTokensFromValue(tokens, candidate.metadata?.kind);
|
|
122
|
+
for (const tag of candidate.metadata?.purpose_tags ?? []) addTokensFromValue(tokens, tag);
|
|
123
|
+
return tokens;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function hasTokenOverlap(left, right) {
|
|
127
|
+
for (const token of left) {
|
|
128
|
+
if (right.has(token)) return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function strictQueryRelevance(args) {
|
|
134
|
+
return args.strict_relevance === true
|
|
135
|
+
|| args.strictRelevance === true
|
|
136
|
+
|| args.require_relevance === true
|
|
137
|
+
|| args.requireRelevance === true
|
|
138
|
+
|| args.query_relevance === 'strict'
|
|
139
|
+
|| args.queryRelevance === 'strict';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function relevanceCandidateSet(args, candidates) {
|
|
143
|
+
if (args.queryAwareRelevance !== true) return candidates;
|
|
144
|
+
const query = typeof args.query === 'string' ? args.query.trim() : String(args.query ?? '').trim();
|
|
145
|
+
if (query.length === 0) return candidates;
|
|
146
|
+
const queryTokens = meaningfulTokensFrom(query);
|
|
147
|
+
if (queryTokens.size === 0) return candidates;
|
|
148
|
+
|
|
149
|
+
const relevant = [];
|
|
150
|
+
for (const candidate of candidates) {
|
|
151
|
+
if (hasTokenOverlap(queryTokens, candidateRelevanceTokens(candidate))) relevant.push(candidate);
|
|
152
|
+
}
|
|
153
|
+
if (relevant.length > 0) return relevant;
|
|
154
|
+
return strictQueryRelevance(args) ? [] : candidates;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
39
158
|
function normalizedPublicKey(key) {
|
|
40
159
|
return String(key).toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
41
160
|
}
|
|
@@ -215,8 +334,9 @@ function optimizedSelectionFrom(args, candidateAddresses, limit) {
|
|
|
215
334
|
const candidate = optimizationCandidateFrom(args.vault, memoryAddr);
|
|
216
335
|
if (candidate) candidates.push(candidate);
|
|
217
336
|
}
|
|
337
|
+
const planCandidates = relevanceCandidateSet(args, candidates);
|
|
218
338
|
const plan = createMemoryOptimizationPlan({
|
|
219
|
-
candidates,
|
|
339
|
+
candidates: planCandidates,
|
|
220
340
|
prompt: args.query ?? '',
|
|
221
341
|
pricing: contextPackPricing(args),
|
|
222
342
|
now: args.now,
|
|
@@ -237,7 +357,7 @@ function optimizedSelectionFrom(args, candidateAddresses, limit) {
|
|
|
237
357
|
}
|
|
238
358
|
const selectedSet = new Set(selected);
|
|
239
359
|
const selectedPlan = createMemoryOptimizationPlan({
|
|
240
|
-
candidates:
|
|
360
|
+
candidates: planCandidates.filter((candidate) => selectedSet.has(candidate.address)),
|
|
241
361
|
prompt: args.query ?? '',
|
|
242
362
|
pricing: contextPackPricing(args),
|
|
243
363
|
now: args.now,
|
|
@@ -503,11 +623,12 @@ export function compileContextPack(args = {}) {
|
|
|
503
623
|
const tombstones = tombstoneAddressesFrom(vault);
|
|
504
624
|
const limit = Number(args.limit ?? args.max_memories ?? args.maxMemories ?? 12);
|
|
505
625
|
if (!Number.isInteger(limit) || limit < 0) throw new Error('compileContextPack limit must be a non-negative integer');
|
|
506
|
-
const
|
|
626
|
+
const hasExplicitMemoryAddresses = Boolean(requested);
|
|
627
|
+
const candidateAddresses = hasExplicitMemoryAddresses ? [...requested] : [...active];
|
|
507
628
|
let selected = candidateAddresses.slice(0, limit);
|
|
508
629
|
let optimizationPlan = null;
|
|
509
630
|
if (optimizerEnabled(args)) {
|
|
510
|
-
const optimized = optimizedSelectionFrom({ ...args, vault }, candidateAddresses, limit);
|
|
631
|
+
const optimized = optimizedSelectionFrom({ ...args, vault, queryAwareRelevance: !hasExplicitMemoryAddresses }, candidateAddresses, limit);
|
|
511
632
|
selected = optimized.selected;
|
|
512
633
|
optimizationPlan = optimized.plan;
|
|
513
634
|
}
|
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
|
|
7
7
|
export const INSTALLER_ASSET_SCHEMA = 'enigma.installer_assets.v1';
|
|
8
8
|
export const INSTALLER_ASSET_PACKAGE = 'enigma-memory';
|
|
9
|
-
export const INSTALLER_ASSET_VERSION = '0.1.
|
|
9
|
+
export const INSTALLER_ASSET_VERSION = '0.1.5';
|
|
10
10
|
export const INSTALLER_ASSET_GENERATED_AT = '1970-01-01T00:00:00.000Z';
|
|
11
11
|
|
|
12
12
|
const SCRIPT_PATH = fileURLToPath(import.meta.url);
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { createWriteStream as defaultCreateWriteStream } from 'node:fs';
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { mkdir as defaultMkdir, writeFile as defaultWriteFile } from 'node:fs/promises';
|
|
7
|
+
|
|
8
|
+
export const STANDARD_BENCHMARK_DATASET_MANIFEST_SCHEMA = 'enigma.standard_benchmark_dataset_manifest.v1';
|
|
9
|
+
export const STANDARD_BENCHMARK_DATASET_PLAN_SCHEMA = 'enigma.standard_benchmark_dataset_download_plan.v1';
|
|
10
|
+
export const DEFAULT_DATASET_DIR = '.enigma/benchmarks/datasets';
|
|
11
|
+
export const DEFAULT_MANIFEST_FILE_NAME = 'standard-benchmark-dataset-manifest.json';
|
|
12
|
+
|
|
13
|
+
export const DATASET_IDS = Object.freeze([
|
|
14
|
+
'locomo',
|
|
15
|
+
'longmemeval-oracle',
|
|
16
|
+
'longmemeval-s',
|
|
17
|
+
'longmemeval-m',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export const DATASET_SELECTIONS = Object.freeze([...DATASET_IDS, 'all']);
|
|
21
|
+
|
|
22
|
+
export const STANDARD_BENCHMARK_DATASETS = Object.freeze({
|
|
23
|
+
locomo: Object.freeze({
|
|
24
|
+
id: 'locomo',
|
|
25
|
+
display_name: 'LoCoMo',
|
|
26
|
+
file_name: 'locomo10.json',
|
|
27
|
+
source_url: 'https://raw.githubusercontent.com/snap-research/locomo/main/data/locomo10.json',
|
|
28
|
+
license: 'CC BY-NC 4.0',
|
|
29
|
+
usage_boundaries: Object.freeze([
|
|
30
|
+
'Official LoCoMo data is non-commercial; review the upstream license before use or redistribution.',
|
|
31
|
+
'Use as a long-term conversational-memory benchmark source, not as proof of provider deletion, model forgetting, ROI, savings, compliance, or benchmark leadership.',
|
|
32
|
+
'Public reports must keep raw conversation text out of generated manifests and shared summaries.',
|
|
33
|
+
]),
|
|
34
|
+
}),
|
|
35
|
+
'longmemeval-oracle': Object.freeze({
|
|
36
|
+
id: 'longmemeval-oracle',
|
|
37
|
+
display_name: 'LongMemEval Oracle',
|
|
38
|
+
file_name: 'longmemeval_oracle.json',
|
|
39
|
+
source_url: 'https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_oracle.json',
|
|
40
|
+
license: 'Review the upstream Hugging Face dataset card and LongMemEval repository terms before use or redistribution.',
|
|
41
|
+
usage_boundaries: Object.freeze([
|
|
42
|
+
'Oracle split includes evidence sessions and is useful for retrieval/proxy controls; it is not a live provider comparison by itself.',
|
|
43
|
+
'LongMemEval covers information extraction, multi-session reasoning, temporal reasoning, knowledge updates, and abstention.',
|
|
44
|
+
'Public reports must keep raw question, answer, and conversation text out of generated manifests and shared summaries.',
|
|
45
|
+
]),
|
|
46
|
+
}),
|
|
47
|
+
'longmemeval-s': Object.freeze({
|
|
48
|
+
id: 'longmemeval-s',
|
|
49
|
+
display_name: 'LongMemEval S cleaned',
|
|
50
|
+
file_name: 'longmemeval_s_cleaned.json',
|
|
51
|
+
source_url: 'https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_s_cleaned.json',
|
|
52
|
+
license: 'Review the upstream Hugging Face dataset card and LongMemEval repository terms before use or redistribution.',
|
|
53
|
+
usage_boundaries: Object.freeze([
|
|
54
|
+
'Cleaned LongMemEval S is for reproducible benchmark preparation; it is not a live provider comparison by itself.',
|
|
55
|
+
'LongMemEval covers information extraction, multi-session reasoning, temporal reasoning, knowledge updates, and abstention.',
|
|
56
|
+
'Public reports must keep raw question, answer, and conversation text out of generated manifests and shared summaries.',
|
|
57
|
+
]),
|
|
58
|
+
}),
|
|
59
|
+
'longmemeval-m': Object.freeze({
|
|
60
|
+
id: 'longmemeval-m',
|
|
61
|
+
display_name: 'LongMemEval M cleaned',
|
|
62
|
+
file_name: 'longmemeval_m_cleaned.json',
|
|
63
|
+
source_url: 'https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_m_cleaned.json',
|
|
64
|
+
license: 'Review the upstream Hugging Face dataset card and LongMemEval repository terms before use or redistribution.',
|
|
65
|
+
usage_boundaries: Object.freeze([
|
|
66
|
+
'Cleaned LongMemEval M is large long-memory benchmark data; it is not a live provider comparison by itself.',
|
|
67
|
+
'LongMemEval covers information extraction, multi-session reasoning, temporal reasoning, knowledge updates, and abstention.',
|
|
68
|
+
'Public reports must keep raw question, answer, and conversation text out of generated manifests and shared summaries.',
|
|
69
|
+
]),
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
function joinOutputPath(base, fileName) {
|
|
74
|
+
const trimmed = String(base).replace(/[\\/]+$/, '');
|
|
75
|
+
if (!trimmed) {
|
|
76
|
+
return fileName;
|
|
77
|
+
}
|
|
78
|
+
return trimmed.includes('\\') ? join(trimmed, fileName) : `${trimmed}/${fileName}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const DEFAULT_DATASET_OUTPUT_FILES = Object.freeze(
|
|
82
|
+
Object.fromEntries(DATASET_IDS.map((id) => [id, joinOutputPath(DEFAULT_DATASET_DIR, STANDARD_BENCHMARK_DATASETS[id].file_name)])),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
export const STANDARD_BENCHMARK_DATASET_URLS = Object.freeze(
|
|
86
|
+
Object.fromEntries(DATASET_IDS.map((id) => [id, STANDARD_BENCHMARK_DATASETS[id].source_url])),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export const STANDARD_BENCHMARK_DATASET_FILE_NAMES = Object.freeze(
|
|
90
|
+
Object.fromEntries(DATASET_IDS.map((id) => [id, STANDARD_BENCHMARK_DATASETS[id].file_name])),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
export const LONGMEMEVAL_TASK_CATEGORIES = Object.freeze([
|
|
94
|
+
'information extraction',
|
|
95
|
+
'multi-session reasoning',
|
|
96
|
+
'temporal reasoning',
|
|
97
|
+
'knowledge updates',
|
|
98
|
+
'abstention',
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
function fail(message) {
|
|
102
|
+
throw new Error(message);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function takeValue(argv, index, flag) {
|
|
106
|
+
const value = argv[index + 1];
|
|
107
|
+
if (!value || value.startsWith('--')) {
|
|
108
|
+
fail(`${flag} requires a value`);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function parseDownloadArgs(argv = process.argv.slice(2)) {
|
|
114
|
+
const options = {
|
|
115
|
+
outDir: DEFAULT_DATASET_DIR,
|
|
116
|
+
dataset: 'all',
|
|
117
|
+
dryRun: true,
|
|
118
|
+
manifestPath: undefined,
|
|
119
|
+
help: false,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
123
|
+
const arg = argv[index];
|
|
124
|
+
if (arg === '--help' || arg === '-h') {
|
|
125
|
+
options.help = true;
|
|
126
|
+
} else if (arg === '--out-dir') {
|
|
127
|
+
options.outDir = takeValue(argv, index, arg);
|
|
128
|
+
index += 1;
|
|
129
|
+
} else if (arg === '--dataset') {
|
|
130
|
+
options.dataset = takeValue(argv, index, arg);
|
|
131
|
+
index += 1;
|
|
132
|
+
} else if (arg === '--dry-run') {
|
|
133
|
+
options.dryRun = true;
|
|
134
|
+
} else if (arg === '--execute') {
|
|
135
|
+
options.dryRun = false;
|
|
136
|
+
} else if (arg === '--manifest') {
|
|
137
|
+
options.manifestPath = takeValue(argv, index, arg);
|
|
138
|
+
index += 1;
|
|
139
|
+
} else {
|
|
140
|
+
fail(`Unknown option: ${arg}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!DATASET_SELECTIONS.includes(options.dataset)) {
|
|
145
|
+
fail(`Unsupported dataset "${options.dataset}". Expected one of: ${DATASET_SELECTIONS.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return options;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function selectedDatasetIds(selection = 'all') {
|
|
152
|
+
if (!DATASET_SELECTIONS.includes(selection)) {
|
|
153
|
+
fail(`Unsupported dataset "${selection}". Expected one of: ${DATASET_SELECTIONS.join(', ')}`);
|
|
154
|
+
}
|
|
155
|
+
return selection === 'all' ? [...DATASET_IDS] : [selection];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function createDatasetDownloadPlan(options = {}) {
|
|
159
|
+
const outDir = options.outDir ?? DEFAULT_DATASET_DIR;
|
|
160
|
+
const dataset = options.dataset ?? 'all';
|
|
161
|
+
const dryRun = options.dryRun ?? true;
|
|
162
|
+
const manifestPath = options.manifestPath ?? joinOutputPath(outDir, DEFAULT_MANIFEST_FILE_NAME);
|
|
163
|
+
const datasetIds = selectedDatasetIds(dataset);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
schema: STANDARD_BENCHMARK_DATASET_PLAN_SCHEMA,
|
|
167
|
+
public_safe: true,
|
|
168
|
+
dry_run: Boolean(dryRun),
|
|
169
|
+
execute_required_for_download: Boolean(dryRun),
|
|
170
|
+
raw_dataset_content_included: false,
|
|
171
|
+
selected_dataset: dataset,
|
|
172
|
+
output_directory: outDir,
|
|
173
|
+
manifest_path: manifestPath,
|
|
174
|
+
planned_fetches: datasetIds.map((id) => {
|
|
175
|
+
const datasetInfo = STANDARD_BENCHMARK_DATASETS[id];
|
|
176
|
+
return {
|
|
177
|
+
dataset: datasetInfo.id,
|
|
178
|
+
display_name: datasetInfo.display_name,
|
|
179
|
+
source_url: datasetInfo.source_url,
|
|
180
|
+
license: datasetInfo.license,
|
|
181
|
+
usage_boundaries: [...datasetInfo.usage_boundaries],
|
|
182
|
+
file_name: datasetInfo.file_name,
|
|
183
|
+
output_file: joinOutputPath(outDir, datasetInfo.file_name),
|
|
184
|
+
content_included: false,
|
|
185
|
+
};
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function assertFetchResponse(response, datasetId) {
|
|
191
|
+
if (!response || response.ok === false) {
|
|
192
|
+
const status = response?.status ? ` HTTP ${response.status}` : '';
|
|
193
|
+
fail(`Failed to fetch ${datasetId}.${status}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function sha256Hex(buffer) {
|
|
198
|
+
return createHash('sha256').update(buffer).digest('hex');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function responseHasStreamBody(response) {
|
|
202
|
+
return response?.body
|
|
203
|
+
&& (typeof response.body.getReader === 'function' || typeof response.body[Symbol.asyncIterator] === 'function');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function normalizeBodyChunk(chunk, datasetId) {
|
|
207
|
+
if (typeof chunk === 'string') {
|
|
208
|
+
return Buffer.from(chunk, 'utf8');
|
|
209
|
+
}
|
|
210
|
+
if (chunk instanceof ArrayBuffer) {
|
|
211
|
+
return new Uint8Array(chunk);
|
|
212
|
+
}
|
|
213
|
+
if (ArrayBuffer.isView(chunk)) {
|
|
214
|
+
return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
215
|
+
}
|
|
216
|
+
fail(`Fetch response stream for ${datasetId} yielded an unsupported chunk type`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function waitForWritableEvent(writer, eventName) {
|
|
220
|
+
await new Promise((resolveEvent, rejectEvent) => {
|
|
221
|
+
const cleanup = () => {
|
|
222
|
+
writer.off(eventName, onEvent);
|
|
223
|
+
writer.off('error', onError);
|
|
224
|
+
};
|
|
225
|
+
const onEvent = () => {
|
|
226
|
+
cleanup();
|
|
227
|
+
resolveEvent();
|
|
228
|
+
};
|
|
229
|
+
const onError = (error) => {
|
|
230
|
+
cleanup();
|
|
231
|
+
rejectEvent(error);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
writer.once(eventName, onEvent);
|
|
235
|
+
writer.once('error', onError);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function writeStreamChunk(writer, chunk) {
|
|
240
|
+
if (!writer.write(chunk)) {
|
|
241
|
+
await waitForWritableEvent(writer, 'drain');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function* responseBodyChunks(body) {
|
|
246
|
+
if (typeof body.getReader === 'function') {
|
|
247
|
+
const reader = body.getReader();
|
|
248
|
+
try {
|
|
249
|
+
while (true) {
|
|
250
|
+
const { done, value } = await reader.read();
|
|
251
|
+
if (done) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
yield value;
|
|
255
|
+
}
|
|
256
|
+
} finally {
|
|
257
|
+
reader.releaseLock?.();
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
yield* body;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function streamResponseToFile(response, outputFile, datasetId, hooks) {
|
|
266
|
+
const createWriteStreamImpl = hooks.createWriteStream ?? defaultCreateWriteStream;
|
|
267
|
+
const writer = createWriteStreamImpl(outputFile);
|
|
268
|
+
const finished = new Promise((resolveFinished, rejectFinished) => {
|
|
269
|
+
writer.once('finish', resolveFinished);
|
|
270
|
+
writer.once('error', rejectFinished);
|
|
271
|
+
});
|
|
272
|
+
finished.catch(() => {});
|
|
273
|
+
const hash = createHash('sha256');
|
|
274
|
+
let byteSize = 0;
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
for await (const chunk of responseBodyChunks(response.body)) {
|
|
278
|
+
const normalizedChunk = normalizeBodyChunk(chunk, datasetId);
|
|
279
|
+
byteSize += normalizedChunk.byteLength;
|
|
280
|
+
hash.update(normalizedChunk);
|
|
281
|
+
await writeStreamChunk(writer, normalizedChunk);
|
|
282
|
+
}
|
|
283
|
+
writer.end();
|
|
284
|
+
await finished;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
writer.destroy?.(error);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
byteSize,
|
|
292
|
+
sha256: hash.digest('hex'),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function responseToBuffer(response, datasetId) {
|
|
297
|
+
assertFetchResponse(response, datasetId);
|
|
298
|
+
if (typeof response.arrayBuffer === 'function') {
|
|
299
|
+
return Buffer.from(await response.arrayBuffer());
|
|
300
|
+
}
|
|
301
|
+
if (typeof response.text === 'function') {
|
|
302
|
+
return Buffer.from(await response.text(), 'utf8');
|
|
303
|
+
}
|
|
304
|
+
fail(`Fetch response for ${datasetId} does not expose body, arrayBuffer() or text()`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function executeDatasetDownloadPlan(plan, hooks = {}) {
|
|
308
|
+
const fetchImpl = hooks.fetch ?? globalThis.fetch;
|
|
309
|
+
if (typeof fetchImpl !== 'function') {
|
|
310
|
+
fail('No fetch implementation is available; use Node 24+ or pass a fetch hook.');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const mkdirImpl = hooks.mkdir ?? defaultMkdir;
|
|
314
|
+
const writeFileImpl = hooks.writeFile ?? defaultWriteFile;
|
|
315
|
+
const now = hooks.now ?? (() => new Date().toISOString());
|
|
316
|
+
const fetchedAt = now();
|
|
317
|
+
const records = [];
|
|
318
|
+
|
|
319
|
+
for (const fetchPlan of plan.planned_fetches) {
|
|
320
|
+
const response = await fetchImpl(fetchPlan.source_url, { redirect: 'follow' });
|
|
321
|
+
assertFetchResponse(response, fetchPlan.dataset);
|
|
322
|
+
await mkdirImpl(dirname(fetchPlan.output_file), { recursive: true });
|
|
323
|
+
|
|
324
|
+
let downloaded;
|
|
325
|
+
if (responseHasStreamBody(response)) {
|
|
326
|
+
downloaded = await streamResponseToFile(response, fetchPlan.output_file, fetchPlan.dataset, hooks);
|
|
327
|
+
} else {
|
|
328
|
+
const bytes = await responseToBuffer(response, fetchPlan.dataset);
|
|
329
|
+
await writeFileImpl(fetchPlan.output_file, bytes);
|
|
330
|
+
downloaded = {
|
|
331
|
+
byteSize: bytes.byteLength,
|
|
332
|
+
sha256: sha256Hex(bytes),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
records.push({
|
|
336
|
+
dataset: fetchPlan.dataset,
|
|
337
|
+
display_name: fetchPlan.display_name,
|
|
338
|
+
source_url: fetchPlan.source_url,
|
|
339
|
+
license: fetchPlan.license,
|
|
340
|
+
usage_boundaries: fetchPlan.usage_boundaries,
|
|
341
|
+
file_name: fetchPlan.file_name,
|
|
342
|
+
output_file: fetchPlan.output_file,
|
|
343
|
+
byte_size: downloaded.byteSize,
|
|
344
|
+
sha256: downloaded.sha256,
|
|
345
|
+
fetched_at: fetchedAt,
|
|
346
|
+
content_included: false,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const manifest = {
|
|
351
|
+
schema: STANDARD_BENCHMARK_DATASET_MANIFEST_SCHEMA,
|
|
352
|
+
public_safe: true,
|
|
353
|
+
raw_dataset_content_included: false,
|
|
354
|
+
generated_at: now(),
|
|
355
|
+
output_directory: plan.output_directory,
|
|
356
|
+
datasets: records,
|
|
357
|
+
claim_boundaries: [
|
|
358
|
+
'Manifest records downloaded file sizes, checksums, licenses, and source URLs only; it contains no raw dataset records.',
|
|
359
|
+
'Downloaded datasets support retrieval/evidence-coverage or other reviewed benchmark scoring; they do not create provider, competitor, model-forgetting, deletion, ROI, savings, compliance, or benchmark-leadership claims.',
|
|
360
|
+
'Provider-key LLM answer scoring is out of scope for this downloader and must be added only with reviewed credentials and scorer boundaries.',
|
|
361
|
+
],
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
await mkdirImpl(dirname(plan.manifest_path), { recursive: true });
|
|
365
|
+
await writeFileImpl(plan.manifest_path, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
366
|
+
return manifest;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export async function runDownloadCommand(options = {}, hooks = {}) {
|
|
370
|
+
const plan = createDatasetDownloadPlan(options);
|
|
371
|
+
if (plan.dry_run) {
|
|
372
|
+
return plan;
|
|
373
|
+
}
|
|
374
|
+
return executeDatasetDownloadPlan(plan, hooks);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function usage() {
|
|
378
|
+
return `Usage: node scripts/download-standard-benchmarks.mjs [options]\n\nOptions:\n --out-dir <path> Dataset output directory (default: ${DEFAULT_DATASET_DIR})\n --dataset <name> One of: ${DATASET_SELECTIONS.join(', ')} (default: all)\n --dry-run Print planned public-safe fetches without downloading (default)\n --execute Download selected datasets and write a public-safe manifest\n --manifest <path> Manifest path (default: <out-dir>/${DEFAULT_MANIFEST_FILE_NAME})\n -h, --help Show this help\n`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function main() {
|
|
382
|
+
const options = parseDownloadArgs();
|
|
383
|
+
if (options.help) {
|
|
384
|
+
process.stdout.write(usage());
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const result = await runDownloadCommand(options);
|
|
388
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : '';
|
|
392
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
393
|
+
|
|
394
|
+
if (isAbsolute(invokedPath) && invokedPath === modulePath) {
|
|
395
|
+
main().catch((error) => {
|
|
396
|
+
process.stderr.write(`${error.message}\n`);
|
|
397
|
+
process.exitCode = 1;
|
|
398
|
+
});
|
|
399
|
+
}
|
|
@@ -69,6 +69,7 @@ const EXTERNAL_COMPETITOR_ADAPTERS = Object.freeze([
|
|
|
69
69
|
'Operator-supplied benchmark dataset or fixture mapping',
|
|
70
70
|
],
|
|
71
71
|
official_doc: 'https://docs.letta.com/concepts/memgpt/',
|
|
72
|
+
official_positioning: 'Letta/MemGPT provides an agent memory runtime; this harness requires operator-supplied credentials/runtime and pinned SDK artifacts before any comparison.',
|
|
72
73
|
can_run_in_this_harness: false,
|
|
73
74
|
boundary_reason: 'This local harness has no Letta credentials, SDK installation, hosted/self-hosted Letta runtime, fixed agent loop, or approved external dataset, so no Letta score is produced.',
|
|
74
75
|
scores_included: false,
|
|
@@ -84,6 +85,7 @@ const EXTERNAL_COMPETITOR_ADAPTERS = Object.freeze([
|
|
|
84
85
|
'Fixed graph, model, tools, and dataset mapping',
|
|
85
86
|
],
|
|
86
87
|
official_doc: 'https://docs.langchain.com/oss/python/langgraph/memory',
|
|
88
|
+
official_positioning: 'LangGraph memory includes short-term checkpointer memory and a long-term namespaced store; this harness does not execute that runtime.',
|
|
87
89
|
can_run_in_this_harness: false,
|
|
88
90
|
boundary_reason: 'This Node local package harness does not install or execute a LangGraph runtime, checkpointer, namespaced store, graph, model, or dataset adapter, so no LangGraph score is produced.',
|
|
89
91
|
scores_included: false,
|
|
@@ -99,6 +101,7 @@ const EXTERNAL_COMPETITOR_ADAPTERS = Object.freeze([
|
|
|
99
101
|
'Dataset ingestion and retrieval mapping',
|
|
100
102
|
],
|
|
101
103
|
official_doc: 'https://help.getzep.com/',
|
|
104
|
+
official_positioning: 'Zep positions memory around a temporal Context Graph/Context Lake and sub-200ms retrieval; this harness does not verify that retrieval claim.',
|
|
102
105
|
can_run_in_this_harness: false,
|
|
103
106
|
boundary_reason: 'This local harness has no Zep credentials, client package, Context Graph or Context Lake runtime, ingestion job, or provider-approved retrieval dataset, so no Zep score or latency claim is produced.',
|
|
104
107
|
scores_included: false,
|
|
@@ -114,6 +117,7 @@ const EXTERNAL_COMPETITOR_ADAPTERS = Object.freeze([
|
|
|
114
117
|
'Dataset ingestion and scoring mapping',
|
|
115
118
|
],
|
|
116
119
|
official_doc: 'https://docs.mem0.ai/',
|
|
120
|
+
official_positioning: 'Mem0 positions itself as a universal self-improving memory layer with platform and open-source stack options; this harness does not run either stack.',
|
|
117
121
|
can_run_in_this_harness: false,
|
|
118
122
|
boundary_reason: 'This local harness has no Mem0 credentials, SDK/runtime, configured extraction/retrieval loop, model/tool environment, or external dataset adapter, so no Mem0 score is produced.',
|
|
119
123
|
scores_included: false,
|
|
@@ -129,6 +133,7 @@ const EXTERNAL_COMPETITOR_ADAPTERS = Object.freeze([
|
|
|
129
133
|
'Evidence capture that excludes personal data and credentials',
|
|
130
134
|
],
|
|
131
135
|
official_doc: 'https://help.openai.com/en/articles/8590148-memory-faq',
|
|
136
|
+
official_positioning: 'OpenAI ChatGPT native memory is a consumer-app/native capability and is not directly available through this local public-API harness.',
|
|
132
137
|
can_run_in_this_harness: false,
|
|
133
138
|
boundary_reason: 'ChatGPT native memory is a consumer-app feature rather than a public API surface available to this local package harness, so no OpenAI native-memory score is produced.',
|
|
134
139
|
scores_included: false,
|
|
@@ -144,6 +149,7 @@ const EXTERNAL_COMPETITOR_ADAPTERS = Object.freeze([
|
|
|
144
149
|
'Evidence capture that excludes personal data and credentials',
|
|
145
150
|
],
|
|
146
151
|
official_doc: 'https://support.anthropic.com/en/articles/11145838-using-claude-memory',
|
|
152
|
+
official_positioning: 'Claude memory tooling is provider/client-side and requires a Claude runtime plus tool environment outside this local package harness.',
|
|
147
153
|
can_run_in_this_harness: false,
|
|
148
154
|
boundary_reason: 'The Claude memory tool is provider/client-side and requires a Claude runtime plus tool environment that this local package benchmark does not control, so no Claude memory-tool score is produced.',
|
|
149
155
|
scores_included: false,
|
|
@@ -675,6 +681,7 @@ function compareLocalBaselines(vault, passport) {
|
|
|
675
681
|
|
|
676
682
|
rows.push({
|
|
677
683
|
id: baseline.id,
|
|
684
|
+
baseline: baseline.id,
|
|
678
685
|
label: baseline.label,
|
|
679
686
|
boundary: baseline.boundary,
|
|
680
687
|
local_fixture_only: true,
|
|
@@ -684,6 +691,7 @@ function compareLocalBaselines(vault, passport) {
|
|
|
684
691
|
exact_answer_questions: exactTotal,
|
|
685
692
|
exact_answer_correct: exactCorrect,
|
|
686
693
|
exact_answer_recall: exactTotal === 0 ? 0 : Number((exactCorrect / exactTotal).toFixed(6)),
|
|
694
|
+
recall: exactTotal === 0 ? 0 : Number((exactCorrect / exactTotal).toFixed(6)),
|
|
687
695
|
abstention_questions: abstainTotal,
|
|
688
696
|
abstention_correct: abstainCorrect,
|
|
689
697
|
abstention_correctness: abstainTotal === 0 ? 0 : Number((abstainCorrect / abstainTotal).toFixed(6)),
|
|
@@ -740,6 +748,8 @@ function compareProviders(vault, passport, samples) {
|
|
|
740
748
|
provider_runtime_observed: false,
|
|
741
749
|
external_provider_called: false,
|
|
742
750
|
same_enigma_context_pack_boundary: true,
|
|
751
|
+
scores_included: false,
|
|
752
|
+
not_external_competitor_score: true,
|
|
743
753
|
boundary: {
|
|
744
754
|
optimize: CONTEXT_BOUNDARY.optimize,
|
|
745
755
|
max_estimated_tokens: CONTEXT_BOUNDARY.max_estimated_tokens,
|
|
@@ -863,6 +873,7 @@ export function runMemoryBenchmarkSuite(options = {}) {
|
|
|
863
873
|
context_pack_verify_valid: contextVerification.valid === true,
|
|
864
874
|
},
|
|
865
875
|
},
|
|
876
|
+
local_baseline_comparisons: localBaselineRows,
|
|
866
877
|
cross_provider_profiles: providerRows,
|
|
867
878
|
external_competitor_adapters: EXTERNAL_COMPETITOR_ADAPTERS,
|
|
868
879
|
public_claims_allowed: PUBLIC_CLAIMS_ALLOWED,
|