@prometheus-ai/memory 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -0
- package/dist/types/cli.d.ts +35 -0
- package/dist/types/config.d.ts +77 -0
- package/dist/types/core/aaak.d.ts +55 -0
- package/dist/types/core/annotations.d.ts +75 -0
- package/dist/types/core/banks.d.ts +33 -0
- package/dist/types/core/beam/consolidate.d.ts +32 -0
- package/dist/types/core/beam/helpers.d.ts +76 -0
- package/dist/types/core/beam/index.d.ts +59 -0
- package/dist/types/core/beam/recall.d.ts +32 -0
- package/dist/types/core/beam/schema.d.ts +2 -0
- package/dist/types/core/beam/store.d.ts +35 -0
- package/dist/types/core/beam/types.d.ts +233 -0
- package/dist/types/core/binary-vectors.d.ts +54 -0
- package/dist/types/core/chat-normalize.d.ts +13 -0
- package/dist/types/core/content-sanitizer.d.ts +18 -0
- package/dist/types/core/cost-log.d.ts +13 -0
- package/dist/types/core/embeddings.d.ts +44 -0
- package/dist/types/core/entities.d.ts +7 -0
- package/dist/types/core/episodic-graph.d.ts +89 -0
- package/dist/types/core/extraction/client.d.ts +31 -0
- package/dist/types/core/extraction/diagnostics.d.ts +51 -0
- package/dist/types/core/extraction/prompts.d.ts +2 -0
- package/dist/types/core/extraction.d.ts +6 -0
- package/dist/types/core/index.d.ts +4 -0
- package/dist/types/core/llm-backends.d.ts +21 -0
- package/dist/types/core/local-llm.d.ts +15 -0
- package/dist/types/core/memory.d.ts +160 -0
- package/dist/types/core/migrations/e6-triplestore-split.d.ts +17 -0
- package/dist/types/core/migrations/index.d.ts +1 -0
- package/dist/types/core/mmr.d.ts +8 -0
- package/dist/types/core/orchestrator.d.ts +20 -0
- package/dist/types/core/patterns.d.ts +61 -0
- package/dist/types/core/plugins.d.ts +109 -0
- package/dist/types/core/polyphonic-recall.d.ts +66 -0
- package/dist/types/core/query-cache.d.ts +46 -0
- package/dist/types/core/query-intent.d.ts +20 -0
- package/dist/types/core/recall-diagnostics.d.ts +48 -0
- package/dist/types/core/runtime-options.d.ts +68 -0
- package/dist/types/core/shmr.d.ts +56 -0
- package/dist/types/core/streaming.d.ts +136 -0
- package/dist/types/core/synonyms.d.ts +46 -0
- package/dist/types/core/temporal-parser.d.ts +16 -0
- package/dist/types/core/token-counter.d.ts +8 -0
- package/dist/types/core/triples.d.ts +63 -0
- package/dist/types/core/typed-memory.d.ts +39 -0
- package/dist/types/core/vector-math.d.ts +1 -0
- package/dist/types/core/veracity-consolidation.d.ts +60 -0
- package/dist/types/core/weibull.d.ts +96 -0
- package/dist/types/db.d.ts +16 -0
- package/dist/types/diagnose.d.ts +24 -0
- package/dist/types/dr/index.d.ts +1 -0
- package/dist/types/dr/recovery.d.ts +68 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/mcp-server.d.ts +40 -0
- package/dist/types/mcp-tools.d.ts +484 -0
- package/dist/types/migrations/e6-triplestore-split.d.ts +1 -0
- package/dist/types/migrations/index.d.ts +1 -0
- package/dist/types/types.d.ts +145 -0
- package/dist/types/util/datetime.d.ts +8 -0
- package/dist/types/util/env.d.ts +10 -0
- package/dist/types/util/ids.d.ts +3 -0
- package/dist/types/util/lru.d.ts +12 -0
- package/dist/types/util/regex.d.ts +10 -0
- package/package.json +85 -0
- package/src/cli.ts +398 -0
- package/src/config.ts +326 -0
- package/src/core/aaak.ts +142 -0
- package/src/core/annotations.ts +457 -0
- package/src/core/banks.ts +133 -0
- package/src/core/beam/consolidate.ts +965 -0
- package/src/core/beam/helpers.ts +977 -0
- package/src/core/beam/index.ts +353 -0
- package/src/core/beam/recall.ts +1100 -0
- package/src/core/beam/schema.ts +423 -0
- package/src/core/beam/store.ts +829 -0
- package/src/core/beam/types.ts +268 -0
- package/src/core/binary-vectors.ts +317 -0
- package/src/core/chat-normalize.ts +160 -0
- package/src/core/content-sanitizer.ts +136 -0
- package/src/core/cost-log.ts +103 -0
- package/src/core/embeddings.ts +423 -0
- package/src/core/entities.ts +259 -0
- package/src/core/episodic-graph.ts +708 -0
- package/src/core/extraction/client.ts +162 -0
- package/src/core/extraction/diagnostics.ts +193 -0
- package/src/core/extraction/prompts.ts +31 -0
- package/src/core/extraction.ts +335 -0
- package/src/core/index.ts +30 -0
- package/src/core/llm-backends.ts +51 -0
- package/src/core/local-llm.ts +436 -0
- package/src/core/memory.ts +630 -0
- package/src/core/migrations/e6-triplestore-split.ts +211 -0
- package/src/core/migrations/index.ts +1 -0
- package/src/core/mmr.ts +71 -0
- package/src/core/orchestrator.ts +62 -0
- package/src/core/patterns.ts +484 -0
- package/src/core/plugins.ts +375 -0
- package/src/core/polyphonic-recall.ts +563 -0
- package/src/core/query-cache.ts +354 -0
- package/src/core/query-intent.ts +139 -0
- package/src/core/recall-diagnostics.ts +157 -0
- package/src/core/runtime-options.ts +119 -0
- package/src/core/shmr.ts +460 -0
- package/src/core/streaming.ts +419 -0
- package/src/core/synonyms.ts +197 -0
- package/src/core/temporal-parser.ts +363 -0
- package/src/core/token-counter.ts +30 -0
- package/src/core/triples.ts +454 -0
- package/src/core/typed-memory.ts +407 -0
- package/src/core/vector-math.ts +23 -0
- package/src/core/veracity-consolidation.ts +477 -0
- package/src/core/weibull.ts +124 -0
- package/src/db.ts +128 -0
- package/src/diagnose.ts +174 -0
- package/src/dr/index.ts +1 -0
- package/src/dr/recovery.ts +405 -0
- package/src/index.ts +33 -0
- package/src/mcp-server.ts +155 -0
- package/src/mcp-tools.ts +970 -0
- package/src/migrations/e6-triplestore-split.ts +1 -0
- package/src/migrations/index.ts +1 -0
- package/src/types.ts +157 -0
- package/src/util/datetime.ts +69 -0
- package/src/util/env.ts +65 -0
- package/src/util/ids.ts +19 -0
- package/src/util/lru.ts +48 -0
- package/src/util/regex.ts +165 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import { dbPath } from "../config";
|
|
4
|
+
import { closeQuietly, openDatabase, transaction } from "../db";
|
|
5
|
+
|
|
6
|
+
const ENTITY_STOP_WORD_VALUES = [
|
|
7
|
+
"assistant",
|
|
8
|
+
"user",
|
|
9
|
+
"skill",
|
|
10
|
+
"review",
|
|
11
|
+
"target",
|
|
12
|
+
"class",
|
|
13
|
+
"level",
|
|
14
|
+
"signals",
|
|
15
|
+
"phase",
|
|
16
|
+
"api",
|
|
17
|
+
"pi",
|
|
18
|
+
"summary",
|
|
19
|
+
"added",
|
|
20
|
+
"active",
|
|
21
|
+
"be",
|
|
22
|
+
"not",
|
|
23
|
+
"whether",
|
|
24
|
+
"all",
|
|
25
|
+
"no",
|
|
26
|
+
"replying",
|
|
27
|
+
"ai",
|
|
28
|
+
"memory",
|
|
29
|
+
"mnemopi",
|
|
30
|
+
"conversation",
|
|
31
|
+
"fact",
|
|
32
|
+
"false",
|
|
33
|
+
"true",
|
|
34
|
+
"none",
|
|
35
|
+
"null",
|
|
36
|
+
"signal",
|
|
37
|
+
"hermes",
|
|
38
|
+
"agent",
|
|
39
|
+
"model",
|
|
40
|
+
"system",
|
|
41
|
+
"note",
|
|
42
|
+
"task",
|
|
43
|
+
"project",
|
|
44
|
+
"result",
|
|
45
|
+
"output",
|
|
46
|
+
"input",
|
|
47
|
+
"data",
|
|
48
|
+
"step",
|
|
49
|
+
"process",
|
|
50
|
+
"point",
|
|
51
|
+
"way",
|
|
52
|
+
"thing",
|
|
53
|
+
"time",
|
|
54
|
+
"work",
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
const ANNOTATION_KIND_VALUES = ["mentions", "fact", "occurred_on", "has_source"] as const;
|
|
58
|
+
|
|
59
|
+
export type AnnotationKind = (typeof ANNOTATION_KIND_VALUES)[number] | (string & {});
|
|
60
|
+
|
|
61
|
+
export const ENTITY_STOP_WORDS: ReadonlySet<string> = new Set(ENTITY_STOP_WORD_VALUES);
|
|
62
|
+
export const ANNOTATION_KINDS: ReadonlySet<string> = new Set(ANNOTATION_KIND_VALUES);
|
|
63
|
+
export const MIN_FACT_LENGTH = 10;
|
|
64
|
+
|
|
65
|
+
export interface AnnotationRow {
|
|
66
|
+
readonly id: number;
|
|
67
|
+
readonly memory_id: string;
|
|
68
|
+
readonly kind: string;
|
|
69
|
+
readonly value: string;
|
|
70
|
+
readonly source: string | null;
|
|
71
|
+
readonly confidence: number | null;
|
|
72
|
+
readonly created_at: string | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface AnnotationInput {
|
|
76
|
+
readonly id?: number | bigint | null;
|
|
77
|
+
readonly memory_id: string;
|
|
78
|
+
readonly kind: string;
|
|
79
|
+
readonly value: string;
|
|
80
|
+
readonly source?: string | null;
|
|
81
|
+
readonly confidence?: number | null;
|
|
82
|
+
readonly created_at?: string | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface AnnotationImportStats {
|
|
86
|
+
inserted: number;
|
|
87
|
+
skipped: number;
|
|
88
|
+
overwritten: number;
|
|
89
|
+
imported_renumbered: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AnnotationStoreOptions {
|
|
93
|
+
readonly dbPath?: string;
|
|
94
|
+
readonly db_path?: string;
|
|
95
|
+
readonly db?: Database;
|
|
96
|
+
readonly conn?: Database;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface StoredAnnotationContent {
|
|
100
|
+
readonly memory_id: string;
|
|
101
|
+
readonly kind: string;
|
|
102
|
+
readonly value: string;
|
|
103
|
+
readonly source: string | null;
|
|
104
|
+
readonly confidence: number | null;
|
|
105
|
+
readonly created_at: string | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface StatementRunResult {
|
|
109
|
+
readonly changes: number;
|
|
110
|
+
readonly lastInsertRowid: number | bigint;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface WritableStatement {
|
|
114
|
+
run(...params: SqlValue[]): StatementRunResult;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type SqlValue = string | number | bigint | null;
|
|
118
|
+
|
|
119
|
+
function normalizeRow(row: AnnotationRow): AnnotationRow {
|
|
120
|
+
return {
|
|
121
|
+
id: Number(row.id),
|
|
122
|
+
memory_id: row.memory_id,
|
|
123
|
+
kind: row.kind,
|
|
124
|
+
value: row.value,
|
|
125
|
+
source: row.source,
|
|
126
|
+
confidence: row.confidence === null ? null : Number(row.confidence),
|
|
127
|
+
created_at: row.created_at,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function normalizeContent(item: AnnotationInput): StoredAnnotationContent {
|
|
132
|
+
return {
|
|
133
|
+
memory_id: item.memory_id,
|
|
134
|
+
kind: item.kind,
|
|
135
|
+
value: item.value,
|
|
136
|
+
source: item.source ?? "imported",
|
|
137
|
+
confidence: item.confidence ?? 1.0,
|
|
138
|
+
created_at: item.created_at ?? null,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function rowId(value: number | bigint | null | undefined): number | null {
|
|
143
|
+
if (value === null || value === undefined) return null;
|
|
144
|
+
return Number(value);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isNoisyMention(value: string): boolean {
|
|
148
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
149
|
+
if (words.length === 0) return false;
|
|
150
|
+
for (const word of words) {
|
|
151
|
+
if (ENTITY_STOP_WORDS.has(word.toLowerCase())) return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sameContent(item: AnnotationInput, existing: StoredAnnotationContent): boolean {
|
|
157
|
+
const normalized = normalizeContent(item);
|
|
158
|
+
return (
|
|
159
|
+
normalized.memory_id === existing.memory_id &&
|
|
160
|
+
normalized.kind === existing.kind &&
|
|
161
|
+
normalized.value === existing.value &&
|
|
162
|
+
normalized.source === existing.source &&
|
|
163
|
+
normalized.confidence === existing.confidence &&
|
|
164
|
+
normalized.created_at === existing.created_at
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function isSqliteConstraint(error: unknown): boolean {
|
|
169
|
+
return error instanceof Error && /constraint/i.test(error.message);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function insertAnnotation(statement: WritableStatement, item: AnnotationInput, id?: number): void {
|
|
173
|
+
if (id === undefined) {
|
|
174
|
+
statement.run(
|
|
175
|
+
item.memory_id,
|
|
176
|
+
item.kind,
|
|
177
|
+
item.value,
|
|
178
|
+
item.source ?? "imported",
|
|
179
|
+
item.confidence ?? 1.0,
|
|
180
|
+
item.created_at ?? null,
|
|
181
|
+
);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
statement.run(
|
|
185
|
+
id,
|
|
186
|
+
item.memory_id,
|
|
187
|
+
item.kind,
|
|
188
|
+
item.value,
|
|
189
|
+
item.source ?? "imported",
|
|
190
|
+
item.confidence ?? 1.0,
|
|
191
|
+
item.created_at ?? null,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function filterCleanMentions<T extends { readonly value?: string | null }>(rows: readonly T[]): T[] {
|
|
196
|
+
return rows.filter(row => !isNoisyMention(row.value ?? ""));
|
|
197
|
+
}
|
|
198
|
+
export function filterFacts(facts: readonly string[] | null | undefined): string[] {
|
|
199
|
+
if (!facts) return [];
|
|
200
|
+
return facts.filter(fact => fact.length > MIN_FACT_LENGTH);
|
|
201
|
+
}
|
|
202
|
+
export function initAnnotations(path: string = dbPath()): void {
|
|
203
|
+
const db = openDatabase(path);
|
|
204
|
+
try {
|
|
205
|
+
initAnnotationsWithConn(db);
|
|
206
|
+
} finally {
|
|
207
|
+
closeQuietly(db);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export function initAnnotationsWithConn(db: Database): void {
|
|
211
|
+
db.exec(`
|
|
212
|
+
CREATE TABLE IF NOT EXISTS annotations (
|
|
213
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
214
|
+
memory_id TEXT NOT NULL,
|
|
215
|
+
kind TEXT NOT NULL,
|
|
216
|
+
value TEXT NOT NULL,
|
|
217
|
+
source TEXT,
|
|
218
|
+
confidence REAL DEFAULT 1.0,
|
|
219
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
220
|
+
)
|
|
221
|
+
`);
|
|
222
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_annot_memory_kind ON annotations(memory_id, kind)");
|
|
223
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_annot_kind_value ON annotations(kind, value)");
|
|
224
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_annot_unique ON annotations(memory_id, kind, value)");
|
|
225
|
+
}
|
|
226
|
+
export class AnnotationStore {
|
|
227
|
+
readonly dbPath: string;
|
|
228
|
+
readonly db: Database;
|
|
229
|
+
readonly conn: Database;
|
|
230
|
+
private readonly ownsConnection: boolean;
|
|
231
|
+
|
|
232
|
+
constructor(options: AnnotationStoreOptions | string = {}) {
|
|
233
|
+
if (typeof options === "string") {
|
|
234
|
+
this.dbPath = options;
|
|
235
|
+
this.db = openDatabase(options);
|
|
236
|
+
this.ownsConnection = true;
|
|
237
|
+
} else {
|
|
238
|
+
const shared = options.conn ?? options.db;
|
|
239
|
+
this.dbPath = options.dbPath ?? options.db_path ?? dbPath();
|
|
240
|
+
this.db = shared ?? openDatabase(this.dbPath);
|
|
241
|
+
this.ownsConnection = shared === undefined;
|
|
242
|
+
}
|
|
243
|
+
this.conn = this.db;
|
|
244
|
+
initAnnotationsWithConn(this.db);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
close(): void {
|
|
248
|
+
if (this.ownsConnection) closeQuietly(this.db);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
add(memoryId: string, kind: string, value: string, source = "", confidence = 1.0): number {
|
|
252
|
+
const result = this.db
|
|
253
|
+
.prepare(
|
|
254
|
+
"INSERT OR IGNORE INTO annotations (memory_id, kind, value, source, confidence) VALUES (?, ?, ?, ?, ?)",
|
|
255
|
+
)
|
|
256
|
+
.run(memoryId, kind, value, source, confidence);
|
|
257
|
+
return Number(result.lastInsertRowid);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
addMany(
|
|
261
|
+
memoryId: string,
|
|
262
|
+
kind: string,
|
|
263
|
+
values: readonly string[] | null | undefined,
|
|
264
|
+
source = "",
|
|
265
|
+
confidence = 1.0,
|
|
266
|
+
): number {
|
|
267
|
+
if (!values || values.length === 0) return 0;
|
|
268
|
+
const rows = values.filter(value => value.length > 0 && value.trim().length > 0);
|
|
269
|
+
if (rows.length === 0) return 0;
|
|
270
|
+
const insert = this.db.prepare(
|
|
271
|
+
"INSERT OR IGNORE INTO annotations (memory_id, kind, value, source, confidence) VALUES (?, ?, ?, ?, ?)",
|
|
272
|
+
);
|
|
273
|
+
transaction(this.db, () => {
|
|
274
|
+
for (const value of rows) insert.run(memoryId, kind, value, source, confidence);
|
|
275
|
+
});
|
|
276
|
+
return rows.length;
|
|
277
|
+
}
|
|
278
|
+
queryByMemory(memoryId: string, kind?: string | null): AnnotationRow[] {
|
|
279
|
+
const sql =
|
|
280
|
+
kind === null || kind === undefined
|
|
281
|
+
? "SELECT * FROM annotations WHERE memory_id = ? ORDER BY created_at ASC, id ASC"
|
|
282
|
+
: "SELECT * FROM annotations WHERE memory_id = ? AND kind = ? ORDER BY created_at ASC, id ASC";
|
|
283
|
+
const rows =
|
|
284
|
+
kind === null || kind === undefined
|
|
285
|
+
? this.db.prepare(sql).all(memoryId)
|
|
286
|
+
: this.db.prepare(sql).all(memoryId, kind);
|
|
287
|
+
return (rows as AnnotationRow[]).map(normalizeRow);
|
|
288
|
+
}
|
|
289
|
+
queryByKind(
|
|
290
|
+
kind: string,
|
|
291
|
+
options: {
|
|
292
|
+
readonly value?: string | null;
|
|
293
|
+
readonly memory_id?: string | null;
|
|
294
|
+
readonly memoryId?: string | null;
|
|
295
|
+
readonly filter_noise?: boolean;
|
|
296
|
+
readonly filterNoise?: boolean;
|
|
297
|
+
} = {},
|
|
298
|
+
): AnnotationRow[] {
|
|
299
|
+
const conditions = ["kind = ?"];
|
|
300
|
+
const params: SqlValue[] = [kind];
|
|
301
|
+
if (options.value !== null && options.value !== undefined) {
|
|
302
|
+
conditions.push("value = ?");
|
|
303
|
+
params.push(options.value);
|
|
304
|
+
}
|
|
305
|
+
const memoryId = options.memory_id ?? options.memoryId;
|
|
306
|
+
if (memoryId !== null && memoryId !== undefined) {
|
|
307
|
+
conditions.push("memory_id = ?");
|
|
308
|
+
params.push(memoryId);
|
|
309
|
+
}
|
|
310
|
+
const rows = this.db
|
|
311
|
+
.prepare(`SELECT * FROM annotations WHERE ${conditions.join(" AND ")} ORDER BY created_at ASC, id ASC`)
|
|
312
|
+
.all(...params) as AnnotationRow[];
|
|
313
|
+
const normalized = rows.map(normalizeRow);
|
|
314
|
+
const filterNoise = options.filter_noise ?? options.filterNoise ?? true;
|
|
315
|
+
return filterNoise && kind === "mentions" ? filterCleanMentions(normalized) : normalized;
|
|
316
|
+
}
|
|
317
|
+
getDistinctValues(kind: string): string[] {
|
|
318
|
+
const rows = this.db
|
|
319
|
+
.prepare("SELECT DISTINCT value FROM annotations WHERE kind = ? ORDER BY value")
|
|
320
|
+
.all(kind) as { value: string }[];
|
|
321
|
+
return rows.map(row => row.value);
|
|
322
|
+
}
|
|
323
|
+
exportAll(): AnnotationRow[] {
|
|
324
|
+
const rows = this.db
|
|
325
|
+
.prepare("SELECT id, memory_id, kind, value, source, confidence, created_at FROM annotations ORDER BY id")
|
|
326
|
+
.all() as AnnotationRow[];
|
|
327
|
+
return rows.map(normalizeRow);
|
|
328
|
+
}
|
|
329
|
+
importAll(annotations: readonly AnnotationInput[], force = false): AnnotationImportStats {
|
|
330
|
+
const stats: AnnotationImportStats = {
|
|
331
|
+
inserted: 0,
|
|
332
|
+
skipped: 0,
|
|
333
|
+
overwritten: 0,
|
|
334
|
+
imported_renumbered: 0,
|
|
335
|
+
};
|
|
336
|
+
const seenIds = new Set<number>();
|
|
337
|
+
for (const item of annotations) {
|
|
338
|
+
const id = rowId(item.id);
|
|
339
|
+
if (id === null) continue;
|
|
340
|
+
if (seenIds.has(id)) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`import_all: duplicate id ${id} in the imported batch. Deduplicate the input before calling.`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
seenIds.add(id);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
transaction(this.db, () => {
|
|
349
|
+
const existingRows = this.db
|
|
350
|
+
.prepare("SELECT id, memory_id, kind, value, source, confidence, created_at FROM annotations")
|
|
351
|
+
.all() as AnnotationRow[];
|
|
352
|
+
const existing = new Map<number, StoredAnnotationContent>();
|
|
353
|
+
for (const row of existingRows) existing.set(Number(row.id), normalizeRow(row));
|
|
354
|
+
|
|
355
|
+
const insertWithId = this.db.prepare(
|
|
356
|
+
"INSERT INTO annotations (id, memory_id, kind, value, source, confidence, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
357
|
+
) as WritableStatement;
|
|
358
|
+
const insertWithoutId = this.db.prepare(
|
|
359
|
+
"INSERT INTO annotations (memory_id, kind, value, source, confidence, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
360
|
+
) as WritableStatement;
|
|
361
|
+
const deleteById = this.db.prepare("DELETE FROM annotations WHERE id = ?");
|
|
362
|
+
|
|
363
|
+
for (const item of annotations) {
|
|
364
|
+
const id = rowId(item.id);
|
|
365
|
+
const current = id === null ? undefined : existing.get(id);
|
|
366
|
+
if (id === null) {
|
|
367
|
+
insertAnnotation(insertWithoutId, item);
|
|
368
|
+
stats.inserted++;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (current === undefined) {
|
|
372
|
+
insertAnnotation(insertWithId, item, id);
|
|
373
|
+
stats.inserted++;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (force) {
|
|
377
|
+
deleteById.run(id);
|
|
378
|
+
insertAnnotation(insertWithId, item, id);
|
|
379
|
+
stats.overwritten++;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (sameContent(item, current)) {
|
|
383
|
+
stats.skipped++;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
insertAnnotation(insertWithoutId, item);
|
|
388
|
+
stats.imported_renumbered++;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
if (isSqliteConstraint(error)) stats.skipped++;
|
|
391
|
+
else throw error;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
return stats;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function addAnnotation(
|
|
400
|
+
memoryId: string,
|
|
401
|
+
kind: string,
|
|
402
|
+
value: string,
|
|
403
|
+
source = "",
|
|
404
|
+
confidence = 1.0,
|
|
405
|
+
path?: string,
|
|
406
|
+
): number {
|
|
407
|
+
const store = new AnnotationStore(path === undefined ? {} : path);
|
|
408
|
+
try {
|
|
409
|
+
return store.add(memoryId, kind, value, source, confidence);
|
|
410
|
+
} finally {
|
|
411
|
+
store.close();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
export interface QueryAnnotationsOptions {
|
|
415
|
+
readonly memory_id?: string | null;
|
|
416
|
+
readonly memoryId?: string | null;
|
|
417
|
+
readonly kind?: string | null;
|
|
418
|
+
readonly value?: string | null;
|
|
419
|
+
readonly db_path?: string | null;
|
|
420
|
+
readonly dbPath?: string | null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function queryAnnotations(options?: QueryAnnotationsOptions): AnnotationRow[];
|
|
424
|
+
export function queryAnnotations(
|
|
425
|
+
memoryId?: string | null,
|
|
426
|
+
kind?: string | null,
|
|
427
|
+
value?: string | null,
|
|
428
|
+
dbPath?: string | null,
|
|
429
|
+
): AnnotationRow[];
|
|
430
|
+
export function queryAnnotations(
|
|
431
|
+
first: QueryAnnotationsOptions | string | null = {},
|
|
432
|
+
kindArg?: string | null,
|
|
433
|
+
valueArg?: string | null,
|
|
434
|
+
dbPathArg?: string | null,
|
|
435
|
+
): AnnotationRow[] {
|
|
436
|
+
const options: QueryAnnotationsOptions =
|
|
437
|
+
typeof first === "object" && first !== null
|
|
438
|
+
? first
|
|
439
|
+
: { memory_id: first, kind: kindArg, value: valueArg, db_path: dbPathArg };
|
|
440
|
+
const memoryId = options.memory_id ?? options.memoryId;
|
|
441
|
+
const kind = options.kind;
|
|
442
|
+
const value = options.value;
|
|
443
|
+
const path = options.db_path ?? options.dbPath ?? undefined;
|
|
444
|
+
const store = new AnnotationStore(path === undefined || path === null ? {} : path);
|
|
445
|
+
try {
|
|
446
|
+
if (memoryId !== null && memoryId !== undefined && kind === undefined && value === undefined) {
|
|
447
|
+
return store.queryByMemory(memoryId);
|
|
448
|
+
}
|
|
449
|
+
if (memoryId !== null && memoryId !== undefined && kind !== null && kind !== undefined && value === undefined) {
|
|
450
|
+
return store.queryByMemory(memoryId, kind);
|
|
451
|
+
}
|
|
452
|
+
if (kind !== null && kind !== undefined) return store.queryByKind(kind, { value, memory_id: memoryId });
|
|
453
|
+
return store.exportAll();
|
|
454
|
+
} finally {
|
|
455
|
+
store.close();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { dataDir as configuredDataDir } from "../config";
|
|
5
|
+
import { closeQuietly, openDatabase } from "../db";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_DATA_DIR = join(homedir(), ".prometheus", "memory", "data");
|
|
8
|
+
export const BANKS_DIR = join(DEFAULT_DATA_DIR, "banks");
|
|
9
|
+
const DB_FILENAME = "prometheus-memory.db";
|
|
10
|
+
|
|
11
|
+
export class ValueError extends Error {
|
|
12
|
+
override name = "ValueError";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BankStats {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly exists: boolean;
|
|
18
|
+
readonly db_path: string;
|
|
19
|
+
readonly dbSizeBytes: number;
|
|
20
|
+
readonly db_size_bytes: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class BankManager {
|
|
24
|
+
readonly dataDir: string;
|
|
25
|
+
readonly banksDir: string;
|
|
26
|
+
|
|
27
|
+
constructor(dataDir?: string) {
|
|
28
|
+
this.dataDir = dataDir ?? configuredDataDir();
|
|
29
|
+
this.banksDir = join(this.dataDir, "banks");
|
|
30
|
+
mkdirSync(this.banksDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
createBank(name: string): string {
|
|
34
|
+
this.validateName(name);
|
|
35
|
+
const bankDir = join(this.banksDir, name);
|
|
36
|
+
if (existsSync(bankDir)) throw new ValueError(`Bank '${name}' already exists`);
|
|
37
|
+
mkdirSync(bankDir, { recursive: true });
|
|
38
|
+
const dbPath = join(bankDir, DB_FILENAME);
|
|
39
|
+
const db = openDatabase(dbPath);
|
|
40
|
+
closeQuietly(db);
|
|
41
|
+
return dbPath;
|
|
42
|
+
}
|
|
43
|
+
deleteBank(name: string, force = false): boolean {
|
|
44
|
+
this.validateName(name);
|
|
45
|
+
if (name === "default" && !force) throw new ValueError("Cannot delete 'default' bank without force=True");
|
|
46
|
+
const bankDir = join(this.banksDir, name);
|
|
47
|
+
if (!existsSync(bankDir)) return false;
|
|
48
|
+
rmSync(bankDir, { recursive: true, force: true });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
listBanks(): string[] {
|
|
52
|
+
const banks: string[] = ["default"];
|
|
53
|
+
if (existsSync(this.banksDir)) {
|
|
54
|
+
for (const entry of readdirSync(this.banksDir, { withFileTypes: true })) {
|
|
55
|
+
if (entry.isDirectory() && entry.name !== "default") banks.push(entry.name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return banks.sort();
|
|
59
|
+
}
|
|
60
|
+
bankExists(name: string): boolean {
|
|
61
|
+
if (name === "default") return true;
|
|
62
|
+
return existsSync(join(this.banksDir, name));
|
|
63
|
+
}
|
|
64
|
+
getBankDbPath(name: string): string {
|
|
65
|
+
if (name.length === 0 || name === "default") return join(this.dataDir, DB_FILENAME);
|
|
66
|
+
this.validateName(name);
|
|
67
|
+
return join(this.banksDir, name, DB_FILENAME);
|
|
68
|
+
}
|
|
69
|
+
renameBank(oldName: string, newName: string): string {
|
|
70
|
+
if (oldName === "default") throw new ValueError("Cannot rename 'default' bank");
|
|
71
|
+
this.validateName(newName);
|
|
72
|
+
const oldDir = join(this.banksDir, oldName);
|
|
73
|
+
const newDir = join(this.banksDir, newName);
|
|
74
|
+
if (!existsSync(oldDir)) throw new ValueError(`Bank '${oldName}' does not exist`);
|
|
75
|
+
if (existsSync(newDir)) throw new ValueError(`Bank '${newName}' already exists`);
|
|
76
|
+
renameSync(oldDir, newDir);
|
|
77
|
+
return join(newDir, DB_FILENAME);
|
|
78
|
+
}
|
|
79
|
+
getBankStats(name: string): BankStats {
|
|
80
|
+
const dbPath = this.getBankDbPath(name);
|
|
81
|
+
const present = existsSync(dbPath);
|
|
82
|
+
const size = present ? statSync(dbPath).size : 0;
|
|
83
|
+
return { name, exists: present, db_path: dbPath, dbSizeBytes: size, db_size_bytes: size };
|
|
84
|
+
}
|
|
85
|
+
private validateName(name: string): void {
|
|
86
|
+
if (name.length === 0) throw new ValueError("Bank name cannot be empty");
|
|
87
|
+
if (name === "default") return;
|
|
88
|
+
if (name.length > 64) throw new ValueError(`Bank name '${name}' exceeds 64 characters`);
|
|
89
|
+
for (let i = 0; i < name.length; i++) {
|
|
90
|
+
const code = name.charCodeAt(i);
|
|
91
|
+
const ok =
|
|
92
|
+
(code >= 48 && code <= 57) ||
|
|
93
|
+
(code >= 65 && code <= 90) ||
|
|
94
|
+
(code >= 97 && code <= 122) ||
|
|
95
|
+
code === 45 ||
|
|
96
|
+
code === 95;
|
|
97
|
+
if (!ok) throw new ValueError(`Invalid bank name '${name}'. Use alphanumeric, hyphens, underscores only.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let defaultBank = "default";
|
|
103
|
+
|
|
104
|
+
export function createBank(name: string, dataDir?: string): string {
|
|
105
|
+
const manager = new BankManager(dataDir);
|
|
106
|
+
return manager.createBank(name);
|
|
107
|
+
}
|
|
108
|
+
export function deleteBank(name: string, dataDir?: string, force = false): boolean {
|
|
109
|
+
const manager = new BankManager(dataDir);
|
|
110
|
+
return manager.deleteBank(name, force);
|
|
111
|
+
}
|
|
112
|
+
export function listBanks(dataDir?: string): string[] {
|
|
113
|
+
const manager = new BankManager(dataDir);
|
|
114
|
+
return manager.listBanks();
|
|
115
|
+
}
|
|
116
|
+
export function bankExists(name: string, dataDir?: string): boolean {
|
|
117
|
+
const manager = new BankManager(dataDir);
|
|
118
|
+
return manager.bankExists(name);
|
|
119
|
+
}
|
|
120
|
+
export function bankDbPath(name = defaultBank, dataDir?: string): string {
|
|
121
|
+
const manager = new BankManager(dataDir);
|
|
122
|
+
return manager.getBankDbPath(name);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function setBank(bank: string): void {
|
|
126
|
+
defaultBank = bank;
|
|
127
|
+
}
|
|
128
|
+
export function getBank(): string {
|
|
129
|
+
return defaultBank;
|
|
130
|
+
}
|
|
131
|
+
export function resetBankForTests(): void {
|
|
132
|
+
defaultBank = "default";
|
|
133
|
+
}
|