@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,160 @@
|
|
|
1
|
+
type ExtractionRate = {
|
|
2
|
+
total: number;
|
|
3
|
+
survived: number;
|
|
4
|
+
dropped: number;
|
|
5
|
+
rate: number;
|
|
6
|
+
dropped_samples: string[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const CONTRACTIONS: readonly [RegExp, string][] = [
|
|
10
|
+
[/\bu\b/g, "you"],
|
|
11
|
+
[/\bur\b/g, "your"],
|
|
12
|
+
[/\bu're\b/g, "you are"],
|
|
13
|
+
[/\br\b/g, "are"],
|
|
14
|
+
[/\by\b/g, "why"],
|
|
15
|
+
[/\bb4\b/g, "before"],
|
|
16
|
+
[/\bbc\b/g, "because"],
|
|
17
|
+
[/\bcuz\b/g, "because"],
|
|
18
|
+
[/\bgonna\b/g, "going to"],
|
|
19
|
+
[/\bwanna\b/g, "want to"],
|
|
20
|
+
[/\bgotta\b/g, "got to"],
|
|
21
|
+
[/\bkinda\b/g, "kind of"],
|
|
22
|
+
[/\bsorta\b/g, "sort of"],
|
|
23
|
+
[/\bdunno\b/g, "don't know"],
|
|
24
|
+
[/\blemme\b/g, "let me"],
|
|
25
|
+
[/\bgimme\b/g, "give me"],
|
|
26
|
+
[/\boutta\b/g, "out of"],
|
|
27
|
+
[/\bhafta\b/g, "have to"],
|
|
28
|
+
[/\bshoulda\b/g, "should have"],
|
|
29
|
+
[/\bwoulda\b/g, "would have"],
|
|
30
|
+
[/\bcoulda\b/g, "could have"],
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const FILLER_WORDS: Readonly<Record<string, true>> = {
|
|
34
|
+
afaik: true,
|
|
35
|
+
brb: true,
|
|
36
|
+
fr: true,
|
|
37
|
+
fwiw: true,
|
|
38
|
+
idc: true,
|
|
39
|
+
idk: true,
|
|
40
|
+
iirc: true,
|
|
41
|
+
ikr: true,
|
|
42
|
+
imho: true,
|
|
43
|
+
imo: true,
|
|
44
|
+
irl: true,
|
|
45
|
+
istg: true,
|
|
46
|
+
lmao: true,
|
|
47
|
+
lmaoo: true,
|
|
48
|
+
lmfao: true,
|
|
49
|
+
lol: true,
|
|
50
|
+
ngl: true,
|
|
51
|
+
nvm: true,
|
|
52
|
+
omg: true,
|
|
53
|
+
omgg: true,
|
|
54
|
+
omggg: true,
|
|
55
|
+
rofl: true,
|
|
56
|
+
smh: true,
|
|
57
|
+
tbh: true,
|
|
58
|
+
tldr: true,
|
|
59
|
+
w: true,
|
|
60
|
+
wdym: true,
|
|
61
|
+
wtf: true,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const FRAGMENT_STARTERS: Readonly<Record<string, true>> = {
|
|
65
|
+
building: true,
|
|
66
|
+
checking: true,
|
|
67
|
+
coming: true,
|
|
68
|
+
deploying: true,
|
|
69
|
+
feeling: true,
|
|
70
|
+
fixing: true,
|
|
71
|
+
going: true,
|
|
72
|
+
hoping: true,
|
|
73
|
+
looking: true,
|
|
74
|
+
planning: true,
|
|
75
|
+
running: true,
|
|
76
|
+
testing: true,
|
|
77
|
+
thinking: true,
|
|
78
|
+
trying: true,
|
|
79
|
+
wondering: true,
|
|
80
|
+
working: true,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const EDGE_PUNCTUATION_RE = /^[.,!?;:'"]+|[.,!?;:'"]+$/g;
|
|
84
|
+
const REPEATED_CHARS_RE = /(.)\1{2,}/g;
|
|
85
|
+
function replaceNonAsciiRuns(value: string): string {
|
|
86
|
+
let normalized = "";
|
|
87
|
+
let inNonAsciiRun = false;
|
|
88
|
+
for (let index = 0; index < value.length; index++) {
|
|
89
|
+
const char = value[index];
|
|
90
|
+
if (char === undefined) continue;
|
|
91
|
+
if (char.charCodeAt(0) > 0x7f) {
|
|
92
|
+
if (!inNonAsciiRun) normalized += " ";
|
|
93
|
+
inNonAsciiRun = true;
|
|
94
|
+
} else {
|
|
95
|
+
normalized += char;
|
|
96
|
+
inNonAsciiRun = false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function normalizeChat(text: string, options: { add_implicit_subjects?: boolean } = {}): string | null {
|
|
103
|
+
const addImplicitSubjects = options.add_implicit_subjects ?? true;
|
|
104
|
+
if (text.trim().length === 0) return null;
|
|
105
|
+
|
|
106
|
+
let normalized = text.toLowerCase().trim();
|
|
107
|
+
for (const [pattern, replacement] of CONTRACTIONS) {
|
|
108
|
+
normalized = normalized.replace(pattern, replacement);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const meaningful = normalized
|
|
112
|
+
.split(/\s+/)
|
|
113
|
+
.filter(word => FILLER_WORDS[word.replace(EDGE_PUNCTUATION_RE, "")] !== true);
|
|
114
|
+
if (meaningful.length === 0) return null;
|
|
115
|
+
|
|
116
|
+
normalized = meaningful.join(" ");
|
|
117
|
+
normalized = normalized.replace(REPEATED_CHARS_RE, "$1");
|
|
118
|
+
normalized = replaceNonAsciiRuns(normalized);
|
|
119
|
+
normalized = normalized.split(/\s+/).filter(Boolean).join(" ");
|
|
120
|
+
|
|
121
|
+
const words = normalized.length === 0 ? [] : normalized.split(" ");
|
|
122
|
+
const wordCount = words.length;
|
|
123
|
+
if (wordCount < 2) {
|
|
124
|
+
if (wordCount === 1 && (words[0]?.length ?? 0) > 5) return normalized;
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (addImplicitSubjects && wordCount === 2) {
|
|
129
|
+
const firstWord = words[0] ?? "";
|
|
130
|
+
if (FRAGMENT_STARTERS[firstWord] === true) normalized = `i am ${normalized}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return normalized;
|
|
134
|
+
}
|
|
135
|
+
export function normalizeBatch(messages: string[]): (string | null)[] {
|
|
136
|
+
return messages.map(message => normalizeChat(message));
|
|
137
|
+
}
|
|
138
|
+
export function extractionRate(messages: string[]): ExtractionRate {
|
|
139
|
+
const normalized = normalizeBatch(messages);
|
|
140
|
+
let survived = 0;
|
|
141
|
+
const droppedSamples: string[] = [];
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
144
|
+
if (normalized[i] !== null) {
|
|
145
|
+
survived += 1;
|
|
146
|
+
} else if (droppedSamples.length < 5) {
|
|
147
|
+
const message = messages[i];
|
|
148
|
+
if (message !== undefined) droppedSamples.push(message);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const dropped = messages.length - survived;
|
|
153
|
+
return {
|
|
154
|
+
total: messages.length,
|
|
155
|
+
survived,
|
|
156
|
+
dropped,
|
|
157
|
+
rate: messages.length === 0 ? 0.0 : Math.round((survived / messages.length) * 1000) / 1000,
|
|
158
|
+
dropped_samples: droppedSamples,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
export const SIZE_HARD_CAP = 1_000_000;
|
|
7
|
+
export const SIZE_BASE64_CHECK = 100_000;
|
|
8
|
+
export const ENTROPY_THRESHOLD = 5.0;
|
|
9
|
+
|
|
10
|
+
const DATA_URI_RE = /^data:(?<mime>[^;]+)?(?:;base64)?,(?<payload>.*)/i;
|
|
11
|
+
const BASE64_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
12
|
+
|
|
13
|
+
export interface BlobMetadata {
|
|
14
|
+
blob_ref?: string;
|
|
15
|
+
original_size?: number;
|
|
16
|
+
mime?: string;
|
|
17
|
+
extraction_reason?: "data_uri" | "size_cap" | "high_entropy";
|
|
18
|
+
entropy?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function blobRoot(env: NodeJS.ProcessEnv = process.env): string {
|
|
22
|
+
return env.PROMETHEUS_MEMORY_BLOB_DIR && env.PROMETHEUS_MEMORY_BLOB_DIR.length > 0
|
|
23
|
+
? env.PROMETHEUS_MEMORY_BLOB_DIR
|
|
24
|
+
: join(homedir(), ".prometheus", "memory", "blobs");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function computeSha256(data: Uint8Array | string): string {
|
|
28
|
+
return createHash("sha256").update(data).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isDataUri(content: string): boolean {
|
|
32
|
+
return content.startsWith("data:");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseDataUri(content: string): [mimeType: string, raw: Buffer] | null {
|
|
36
|
+
const match = DATA_URI_RE.exec(content);
|
|
37
|
+
if (match?.groups === undefined) return null;
|
|
38
|
+
|
|
39
|
+
const mimeType = match.groups.mime ?? "application/octet-stream";
|
|
40
|
+
const payload = match.groups.payload ?? "";
|
|
41
|
+
if (!isValidBase64(payload)) return null;
|
|
42
|
+
|
|
43
|
+
return [mimeType, Buffer.from(payload, "base64")];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function shannonEntropy(text: string): number {
|
|
47
|
+
if (text.length === 0) return 0.0;
|
|
48
|
+
|
|
49
|
+
const counts = new Map<string, number>();
|
|
50
|
+
for (const char of text) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
51
|
+
|
|
52
|
+
let entropy = 0.0;
|
|
53
|
+
for (const count of counts.values()) {
|
|
54
|
+
const p = count / text.length;
|
|
55
|
+
entropy -= p * Math.log2(p);
|
|
56
|
+
}
|
|
57
|
+
return entropy;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function looksLikeBase64Blob(content: string): boolean {
|
|
61
|
+
if (content.length < SIZE_BASE64_CHECK) return false;
|
|
62
|
+
return shannonEntropy(content) > ENTROPY_THRESHOLD;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function storeBlob(rawBytes: Uint8Array): string {
|
|
66
|
+
const sha256 = computeSha256(rawBytes);
|
|
67
|
+
const blobDir = join(blobRoot(), sha256.slice(0, 2), sha256.slice(0, 4));
|
|
68
|
+
mkdirSync(blobDir, { recursive: true });
|
|
69
|
+
const blobPath = join(blobDir, sha256);
|
|
70
|
+
if (!existsSync(blobPath)) writeFileSync(blobPath, rawBytes);
|
|
71
|
+
return sha256;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function sanitizeContent(content: string): [sanitizedContent: string, blobMetadata: BlobMetadata] {
|
|
75
|
+
const originalSize = Buffer.byteLength(content, "utf8");
|
|
76
|
+
|
|
77
|
+
if (isDataUri(content)) {
|
|
78
|
+
const parsed = parseDataUri(content);
|
|
79
|
+
if (parsed !== null) {
|
|
80
|
+
const [mimeType, rawBytes] = parsed;
|
|
81
|
+
const sha256 = storeBlob(rawBytes);
|
|
82
|
+
const blobRef = `blob://sha256/${sha256}`;
|
|
83
|
+
return [
|
|
84
|
+
`[Binary content extracted: ${mimeType}, ${rawBytes.length.toLocaleString("en-US")} bytes → ${blobRef}]`,
|
|
85
|
+
{
|
|
86
|
+
blob_ref: blobRef,
|
|
87
|
+
original_size: rawBytes.length,
|
|
88
|
+
mime: mimeType,
|
|
89
|
+
extraction_reason: "data_uri",
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (originalSize > SIZE_HARD_CAP) {
|
|
96
|
+
const rawBytes = Buffer.from(content, "utf8");
|
|
97
|
+
const sha256 = storeBlob(rawBytes);
|
|
98
|
+
const blobRef = `blob://sha256/${sha256}`;
|
|
99
|
+
return [
|
|
100
|
+
`[Large content extracted: ${originalSize.toLocaleString("en-US")} bytes → ${blobRef}]`,
|
|
101
|
+
{
|
|
102
|
+
blob_ref: blobRef,
|
|
103
|
+
original_size: originalSize,
|
|
104
|
+
extraction_reason: "size_cap",
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (originalSize > SIZE_BASE64_CHECK && looksLikeBase64Blob(content)) {
|
|
110
|
+
const rawBytes = Buffer.from(content, "utf8");
|
|
111
|
+
const sha256 = storeBlob(rawBytes);
|
|
112
|
+
const entropy = Math.round(shannonEntropy(content) * 100) / 100;
|
|
113
|
+
const blobRef = `blob://sha256/${sha256}`;
|
|
114
|
+
return [
|
|
115
|
+
`[Encoded content extracted: ${originalSize.toLocaleString("en-US")} bytes, entropy ${entropy.toFixed(1)} bits/char → ${blobRef}]`,
|
|
116
|
+
{
|
|
117
|
+
blob_ref: blobRef,
|
|
118
|
+
original_size: originalSize,
|
|
119
|
+
entropy,
|
|
120
|
+
extraction_reason: "high_entropy",
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return [content, {}];
|
|
126
|
+
}
|
|
127
|
+
function isValidBase64(payload: string): boolean {
|
|
128
|
+
if (payload.length % 4 !== 0) return false;
|
|
129
|
+
if (!BASE64_RE.test(payload)) return false;
|
|
130
|
+
try {
|
|
131
|
+
Buffer.from(payload, "base64");
|
|
132
|
+
return true;
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_LOG_DIR = join(homedir(), ".prometheus", "memory", "data");
|
|
7
|
+
export const DEFAULT_LOG_DB = join(DEFAULT_LOG_DIR, "cost_log.db");
|
|
8
|
+
|
|
9
|
+
export interface CostStats {
|
|
10
|
+
total_calls: number;
|
|
11
|
+
total_memories_injected: number;
|
|
12
|
+
total_tokens: number;
|
|
13
|
+
total_estimated_cost_usd: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type AggregateRow = {
|
|
17
|
+
calls: number | null;
|
|
18
|
+
total_memories: number | null;
|
|
19
|
+
total_tokens: number | null;
|
|
20
|
+
total_cost: number | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function getConn(dbPath?: string): Database {
|
|
24
|
+
const path = dbPath ?? DEFAULT_LOG_DB;
|
|
25
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
26
|
+
return new Database(path, { create: true, readwrite: true, strict: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function initCostLog(dbPath?: string): void {
|
|
30
|
+
const conn = getConn(dbPath);
|
|
31
|
+
try {
|
|
32
|
+
conn.run(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS cost_entries (
|
|
34
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
35
|
+
session_id TEXT,
|
|
36
|
+
memory_count INTEGER,
|
|
37
|
+
token_count INTEGER,
|
|
38
|
+
estimated_cost_usd REAL,
|
|
39
|
+
model TEXT DEFAULT 'default',
|
|
40
|
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
41
|
+
)
|
|
42
|
+
`);
|
|
43
|
+
} finally {
|
|
44
|
+
conn.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function logCost(
|
|
48
|
+
sessionId: string,
|
|
49
|
+
memoryCount: number,
|
|
50
|
+
tokenCount: number,
|
|
51
|
+
estimatedCostUsd: number,
|
|
52
|
+
model = "default",
|
|
53
|
+
dbPath?: string,
|
|
54
|
+
): void {
|
|
55
|
+
initCostLog(dbPath);
|
|
56
|
+
const conn = getConn(dbPath);
|
|
57
|
+
try {
|
|
58
|
+
conn
|
|
59
|
+
.query(`
|
|
60
|
+
INSERT INTO cost_entries (session_id, memory_count, token_count, estimated_cost_usd, model, timestamp)
|
|
61
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
62
|
+
`)
|
|
63
|
+
.run(sessionId, memoryCount, tokenCount, estimatedCostUsd, model, localIsoTimestamp(new Date()));
|
|
64
|
+
} finally {
|
|
65
|
+
conn.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function getCostStats(sessionId?: string, dbPath?: string): CostStats {
|
|
69
|
+
initCostLog(dbPath);
|
|
70
|
+
const conn = getConn(dbPath);
|
|
71
|
+
try {
|
|
72
|
+
const row = (
|
|
73
|
+
sessionId
|
|
74
|
+
? conn
|
|
75
|
+
.query(`
|
|
76
|
+
SELECT COUNT(*) as calls, SUM(memory_count) as total_memories,
|
|
77
|
+
SUM(token_count) as total_tokens, SUM(estimated_cost_usd) as total_cost
|
|
78
|
+
FROM cost_entries WHERE session_id = ?
|
|
79
|
+
`)
|
|
80
|
+
.get(sessionId)
|
|
81
|
+
: conn
|
|
82
|
+
.query(`
|
|
83
|
+
SELECT COUNT(*) as calls, SUM(memory_count) as total_memories,
|
|
84
|
+
SUM(token_count) as total_tokens, SUM(estimated_cost_usd) as total_cost
|
|
85
|
+
FROM cost_entries
|
|
86
|
+
`)
|
|
87
|
+
.get()
|
|
88
|
+
) as AggregateRow | null;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
total_calls: row?.calls ?? 0,
|
|
92
|
+
total_memories_injected: row?.total_memories ?? 0,
|
|
93
|
+
total_tokens: row?.total_tokens ?? 0,
|
|
94
|
+
total_estimated_cost_usd: Math.round((row?.total_cost ?? 0) * 1_000_000) / 1_000_000,
|
|
95
|
+
};
|
|
96
|
+
} finally {
|
|
97
|
+
conn.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function localIsoTimestamp(date: Date): string {
|
|
101
|
+
const offsetMs = date.getTimezoneOffset() * 60_000;
|
|
102
|
+
return new Date(date.getTime() - offsetMs).toISOString().replace("Z", "");
|
|
103
|
+
}
|