@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.
Files changed (128) hide show
  1. package/README.md +107 -0
  2. package/dist/types/cli.d.ts +35 -0
  3. package/dist/types/config.d.ts +77 -0
  4. package/dist/types/core/aaak.d.ts +55 -0
  5. package/dist/types/core/annotations.d.ts +75 -0
  6. package/dist/types/core/banks.d.ts +33 -0
  7. package/dist/types/core/beam/consolidate.d.ts +32 -0
  8. package/dist/types/core/beam/helpers.d.ts +76 -0
  9. package/dist/types/core/beam/index.d.ts +59 -0
  10. package/dist/types/core/beam/recall.d.ts +32 -0
  11. package/dist/types/core/beam/schema.d.ts +2 -0
  12. package/dist/types/core/beam/store.d.ts +35 -0
  13. package/dist/types/core/beam/types.d.ts +233 -0
  14. package/dist/types/core/binary-vectors.d.ts +54 -0
  15. package/dist/types/core/chat-normalize.d.ts +13 -0
  16. package/dist/types/core/content-sanitizer.d.ts +18 -0
  17. package/dist/types/core/cost-log.d.ts +13 -0
  18. package/dist/types/core/embeddings.d.ts +44 -0
  19. package/dist/types/core/entities.d.ts +7 -0
  20. package/dist/types/core/episodic-graph.d.ts +89 -0
  21. package/dist/types/core/extraction/client.d.ts +31 -0
  22. package/dist/types/core/extraction/diagnostics.d.ts +51 -0
  23. package/dist/types/core/extraction/prompts.d.ts +2 -0
  24. package/dist/types/core/extraction.d.ts +6 -0
  25. package/dist/types/core/index.d.ts +4 -0
  26. package/dist/types/core/llm-backends.d.ts +21 -0
  27. package/dist/types/core/local-llm.d.ts +15 -0
  28. package/dist/types/core/memory.d.ts +160 -0
  29. package/dist/types/core/migrations/e6-triplestore-split.d.ts +17 -0
  30. package/dist/types/core/migrations/index.d.ts +1 -0
  31. package/dist/types/core/mmr.d.ts +8 -0
  32. package/dist/types/core/orchestrator.d.ts +20 -0
  33. package/dist/types/core/patterns.d.ts +61 -0
  34. package/dist/types/core/plugins.d.ts +109 -0
  35. package/dist/types/core/polyphonic-recall.d.ts +66 -0
  36. package/dist/types/core/query-cache.d.ts +46 -0
  37. package/dist/types/core/query-intent.d.ts +20 -0
  38. package/dist/types/core/recall-diagnostics.d.ts +48 -0
  39. package/dist/types/core/runtime-options.d.ts +68 -0
  40. package/dist/types/core/shmr.d.ts +56 -0
  41. package/dist/types/core/streaming.d.ts +136 -0
  42. package/dist/types/core/synonyms.d.ts +46 -0
  43. package/dist/types/core/temporal-parser.d.ts +16 -0
  44. package/dist/types/core/token-counter.d.ts +8 -0
  45. package/dist/types/core/triples.d.ts +63 -0
  46. package/dist/types/core/typed-memory.d.ts +39 -0
  47. package/dist/types/core/vector-math.d.ts +1 -0
  48. package/dist/types/core/veracity-consolidation.d.ts +60 -0
  49. package/dist/types/core/weibull.d.ts +96 -0
  50. package/dist/types/db.d.ts +16 -0
  51. package/dist/types/diagnose.d.ts +24 -0
  52. package/dist/types/dr/index.d.ts +1 -0
  53. package/dist/types/dr/recovery.d.ts +68 -0
  54. package/dist/types/index.d.ts +5 -0
  55. package/dist/types/mcp-server.d.ts +40 -0
  56. package/dist/types/mcp-tools.d.ts +484 -0
  57. package/dist/types/migrations/e6-triplestore-split.d.ts +1 -0
  58. package/dist/types/migrations/index.d.ts +1 -0
  59. package/dist/types/types.d.ts +145 -0
  60. package/dist/types/util/datetime.d.ts +8 -0
  61. package/dist/types/util/env.d.ts +10 -0
  62. package/dist/types/util/ids.d.ts +3 -0
  63. package/dist/types/util/lru.d.ts +12 -0
  64. package/dist/types/util/regex.d.ts +10 -0
  65. package/package.json +85 -0
  66. package/src/cli.ts +398 -0
  67. package/src/config.ts +326 -0
  68. package/src/core/aaak.ts +142 -0
  69. package/src/core/annotations.ts +457 -0
  70. package/src/core/banks.ts +133 -0
  71. package/src/core/beam/consolidate.ts +965 -0
  72. package/src/core/beam/helpers.ts +977 -0
  73. package/src/core/beam/index.ts +353 -0
  74. package/src/core/beam/recall.ts +1100 -0
  75. package/src/core/beam/schema.ts +423 -0
  76. package/src/core/beam/store.ts +829 -0
  77. package/src/core/beam/types.ts +268 -0
  78. package/src/core/binary-vectors.ts +317 -0
  79. package/src/core/chat-normalize.ts +160 -0
  80. package/src/core/content-sanitizer.ts +136 -0
  81. package/src/core/cost-log.ts +103 -0
  82. package/src/core/embeddings.ts +423 -0
  83. package/src/core/entities.ts +259 -0
  84. package/src/core/episodic-graph.ts +708 -0
  85. package/src/core/extraction/client.ts +162 -0
  86. package/src/core/extraction/diagnostics.ts +193 -0
  87. package/src/core/extraction/prompts.ts +31 -0
  88. package/src/core/extraction.ts +335 -0
  89. package/src/core/index.ts +30 -0
  90. package/src/core/llm-backends.ts +51 -0
  91. package/src/core/local-llm.ts +436 -0
  92. package/src/core/memory.ts +630 -0
  93. package/src/core/migrations/e6-triplestore-split.ts +211 -0
  94. package/src/core/migrations/index.ts +1 -0
  95. package/src/core/mmr.ts +71 -0
  96. package/src/core/orchestrator.ts +62 -0
  97. package/src/core/patterns.ts +484 -0
  98. package/src/core/plugins.ts +375 -0
  99. package/src/core/polyphonic-recall.ts +563 -0
  100. package/src/core/query-cache.ts +354 -0
  101. package/src/core/query-intent.ts +139 -0
  102. package/src/core/recall-diagnostics.ts +157 -0
  103. package/src/core/runtime-options.ts +119 -0
  104. package/src/core/shmr.ts +460 -0
  105. package/src/core/streaming.ts +419 -0
  106. package/src/core/synonyms.ts +197 -0
  107. package/src/core/temporal-parser.ts +363 -0
  108. package/src/core/token-counter.ts +30 -0
  109. package/src/core/triples.ts +454 -0
  110. package/src/core/typed-memory.ts +407 -0
  111. package/src/core/vector-math.ts +23 -0
  112. package/src/core/veracity-consolidation.ts +477 -0
  113. package/src/core/weibull.ts +124 -0
  114. package/src/db.ts +128 -0
  115. package/src/diagnose.ts +174 -0
  116. package/src/dr/index.ts +1 -0
  117. package/src/dr/recovery.ts +405 -0
  118. package/src/index.ts +33 -0
  119. package/src/mcp-server.ts +155 -0
  120. package/src/mcp-tools.ts +970 -0
  121. package/src/migrations/e6-triplestore-split.ts +1 -0
  122. package/src/migrations/index.ts +1 -0
  123. package/src/types.ts +157 -0
  124. package/src/util/datetime.ts +69 -0
  125. package/src/util/env.ts +65 -0
  126. package/src/util/ids.ts +19 -0
  127. package/src/util/lru.ts +48 -0
  128. package/src/util/regex.ts +165 -0
@@ -0,0 +1,363 @@
1
+ import { parseQueryTime, type QueryTime } from "../util/datetime";
2
+
3
+ export type DatePrecision = "day" | "week" | "month" | "year" | "relative" | "unknown";
4
+ export type ParsedNaturalDate = [eventDate: Date, precision: Exclude<DatePrecision, "unknown">, temporalTags: string[]];
5
+
6
+ export interface TemporalInfo {
7
+ event_date: string | null;
8
+ event_date_precision: DatePrecision;
9
+ temporal_tags: string[];
10
+ primary_signal: string | null;
11
+ }
12
+
13
+ const MS_PER_DAY = 86_400_000;
14
+
15
+ // Day name -> weekday number (Monday=0, Sunday=6), matching Python datetime.weekday().
16
+ export const DAY_MAP: Readonly<Record<string, number>> = {
17
+ monday: 0,
18
+ tuesday: 1,
19
+ wednesday: 2,
20
+ thursday: 3,
21
+ friday: 4,
22
+ saturday: 5,
23
+ sunday: 6,
24
+ mon: 0,
25
+ tue: 1,
26
+ wed: 2,
27
+ thu: 3,
28
+ fri: 4,
29
+ sat: 5,
30
+ sun: 6,
31
+ };
32
+
33
+ export const MONTH_MAP: Readonly<Record<string, number>> = {
34
+ january: 1,
35
+ february: 2,
36
+ march: 3,
37
+ april: 4,
38
+ may: 5,
39
+ june: 6,
40
+ july: 7,
41
+ august: 8,
42
+ september: 9,
43
+ october: 10,
44
+ november: 11,
45
+ december: 12,
46
+ jan: 1,
47
+ feb: 2,
48
+ mar: 3,
49
+ apr: 4,
50
+ jun: 6,
51
+ jul: 7,
52
+ aug: 8,
53
+ sep: 9,
54
+ oct: 10,
55
+ nov: 11,
56
+ dec: 12,
57
+ };
58
+
59
+ export const NAMED_TIMES: Readonly<Record<string, readonly [startHour: number, endHour: number]>> = {
60
+ morning: [6, 12],
61
+ afternoon: [12, 17],
62
+ evening: [17, 21],
63
+ night: [21, 6],
64
+ midnight: [0, 1],
65
+ noon: [12, 13],
66
+ dawn: [5, 7],
67
+ dusk: [18, 21],
68
+ };
69
+
70
+ const NAMED_TIME_KEYS = ["morning", "afternoon", "evening", "night", "midnight", "noon", "dawn", "dusk"] as const;
71
+
72
+ function dateUtc(year: number, month: number, day: number): Date | undefined {
73
+ const value = new Date(Date.UTC(year, month - 1, day));
74
+ if (value.getUTCFullYear() !== year || value.getUTCMonth() !== month - 1 || value.getUTCDate() !== day) {
75
+ return undefined;
76
+ }
77
+ return value;
78
+ }
79
+
80
+ function addDays(value: Date, days: number): Date {
81
+ return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate() + days));
82
+ }
83
+
84
+ function addSeconds(value: Date, seconds: number): Date {
85
+ return addDays(new Date(value.getTime() + seconds * 1000), 0);
86
+ }
87
+
88
+ function dateOnly(value: Date): Date {
89
+ return dateUtc(value.getUTCFullYear(), value.getUTCMonth() + 1, value.getUTCDate()) as Date;
90
+ }
91
+
92
+ function isoDate(value: Date): string {
93
+ return value.toISOString().slice(0, 10);
94
+ }
95
+
96
+ function pythonWeekday(value: Date): number {
97
+ return (value.getUTCDay() + 6) % 7;
98
+ }
99
+
100
+ function dayName(value: Date): string {
101
+ const names = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
102
+ return names[value.getUTCDay()] as string;
103
+ }
104
+
105
+ function isoWeek(value: Date): number {
106
+ const d = dateOnly(value);
107
+ d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
108
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
109
+ return Math.ceil(((d.getTime() - yearStart.getTime()) / MS_PER_DAY + 1) / 7);
110
+ }
111
+
112
+ function finiteDate(value: Date): Date | undefined {
113
+ return Number.isFinite(value.getTime()) ? value : undefined;
114
+ }
115
+
116
+ function parseReference(reference?: QueryTime): Date {
117
+ return parseQueryTime(reference);
118
+ }
119
+
120
+ export function resolveRelativeDay(reference: Date, dayNameText: string, qualifier = "this"): Date {
121
+ const targetWd = DAY_MAP[dayNameText.toLowerCase()];
122
+ if (targetWd === undefined) return dateOnly(reference);
123
+
124
+ const currentWd = pythonWeekday(reference);
125
+ if (qualifier === "this") {
126
+ const diff = (currentWd - targetWd + 7) % 7;
127
+ return addDays(reference, -diff);
128
+ }
129
+ if (qualifier === "last") {
130
+ const diff = ((currentWd - targetWd + 7) % 7) + 7;
131
+ return addDays(reference, -diff);
132
+ }
133
+ if (qualifier === "next") {
134
+ let diff = (targetWd - currentWd + 7) % 7;
135
+ if (diff === 0) diff = 7;
136
+ return addDays(reference, diff);
137
+ }
138
+ return dateOnly(reference);
139
+ }
140
+
141
+ function tagsForDay(value: Date): string[] {
142
+ return [isoDate(value), `week-${isoWeek(value)}-${value.getUTCFullYear()}`, dayName(value)];
143
+ }
144
+
145
+ function deltaDate(reference: Date, num: number, unit: string, direction: 1 | -1): Date | undefined {
146
+ if (!Number.isSafeInteger(num)) return undefined;
147
+ let days = 0;
148
+ switch (unit) {
149
+ case "second":
150
+ return finiteDate(addSeconds(reference, direction * num));
151
+ case "minute":
152
+ return finiteDate(addSeconds(reference, direction * num * 60));
153
+ case "hour":
154
+ return finiteDate(addSeconds(reference, direction * num * 3600));
155
+ case "day":
156
+ days = num;
157
+ break;
158
+ case "week":
159
+ days = num * 7;
160
+ break;
161
+ case "month":
162
+ days = num * 30;
163
+ break;
164
+ case "year":
165
+ days = num * 365;
166
+ break;
167
+ default:
168
+ return undefined;
169
+ }
170
+ return finiteDate(addDays(reference, direction * days));
171
+ }
172
+
173
+ export function parseNlDate(text: string, reference?: QueryTime): ParsedNaturalDate | null {
174
+ const ref = parseReference(reference);
175
+ const textLower = text.toLowerCase().trim();
176
+
177
+ let m = /\b(\d{4})-(\d{2})-(\d{2})\b/.exec(text);
178
+ if (m !== null) {
179
+ const year = Number.parseInt(m[1] as string, 10);
180
+ const month = Number.parseInt(m[2] as string, 10);
181
+ const day = Number.parseInt(m[3] as string, 10);
182
+ const d = dateUtc(year, month, day);
183
+ if (d !== undefined) return [d, "day", tagsForDay(d)];
184
+ }
185
+
186
+ m = /\b(\d{1,2})\/(\d{1,2})\/(\d{2,4})\b/.exec(text);
187
+ if (m !== null) {
188
+ const a = Number.parseInt(m[1] as string, 10);
189
+ const b = Number.parseInt(m[2] as string, 10);
190
+ let y = Number.parseInt(m[3] as string, 10);
191
+ if (y < 100) y += 2000;
192
+ const d = a > 12 ? dateUtc(y, b, a) : dateUtc(y, a, b);
193
+ if (d !== undefined) return [d, "day", tagsForDay(d)];
194
+ }
195
+
196
+ m =
197
+ /\b(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)\s+(\d{1,2})(?:st|nd|rd|th)?(?:,?\s*(\d{4}))?\b/.exec(
198
+ textLower,
199
+ );
200
+ if (m !== null) {
201
+ const month = MONTH_MAP[m[1] as string] ?? 1;
202
+ const day = Number.parseInt(m[2] as string, 10);
203
+ const year = m[3] === undefined ? ref.getUTCFullYear() : Number.parseInt(m[3], 10);
204
+ const d = dateUtc(year, month, day);
205
+ if (d !== undefined) return [d, "day", tagsForDay(d)];
206
+ }
207
+
208
+ if (/\btoday\b/.test(textLower)) {
209
+ const d = dateOnly(ref);
210
+ return [d, "day", [isoDate(d), dayName(d)]];
211
+ }
212
+
213
+ if (/\byesterday\b/.test(textLower)) {
214
+ const d = addDays(ref, -1);
215
+ return [d, "day", [isoDate(d), dayName(d), "yesterday"]];
216
+ }
217
+
218
+ if (/\btomorrow\b/.test(textLower)) {
219
+ const d = addDays(ref, 1);
220
+ return [d, "day", [isoDate(d), dayName(d), "tomorrow"]];
221
+ }
222
+
223
+ if (/\bday before yesterday\b/.test(textLower) || /\bday\s+before\s+yesterday\b/.test(textLower)) {
224
+ const d = addDays(ref, -2);
225
+ return [d, "day", [isoDate(d)]];
226
+ }
227
+
228
+ m =
229
+ /\b(last|this|next)\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|tue|wed|thu|fri|sat|sun)\b/.exec(
230
+ textLower,
231
+ );
232
+ if (m !== null) {
233
+ const qualifier = m[1] as string;
234
+ const parsedDayName = m[2] as string;
235
+ const d = resolveRelativeDay(ref, parsedDayName, qualifier);
236
+ return [d, "day", [isoDate(d), `week-${isoWeek(d)}-${d.getUTCFullYear()}`, parsedDayName, qualifier]];
237
+ }
238
+
239
+ m = /\b(on\s+)?(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/.exec(textLower);
240
+ if (m !== null) {
241
+ const parsedDayName = m[2] as string;
242
+ const d = resolveRelativeDay(ref, parsedDayName, "this");
243
+ return [d, "day", [isoDate(d), `week-${isoWeek(d)}-${d.getUTCFullYear()}`, parsedDayName]];
244
+ }
245
+
246
+ m = /\b(this|last|next)\s+(week|month|year)\b/.exec(textLower);
247
+ if (m !== null) {
248
+ const qualifier = m[1] as string;
249
+ const unit = m[2] as string;
250
+ const refDate = dateOnly(ref);
251
+ if (qualifier === "this") {
252
+ if (unit === "week")
253
+ return [refDate, "week", [`week-${isoWeek(refDate)}-${refDate.getUTCFullYear()}`, "this-week"]];
254
+ if (unit === "month")
255
+ return [
256
+ refDate,
257
+ "month",
258
+ [`${refDate.getUTCFullYear()}-${String(refDate.getUTCMonth() + 1).padStart(2, "0")}`, "this-month"],
259
+ ];
260
+ if (unit === "year") return [refDate, "year", [String(refDate.getUTCFullYear()), "this-year"]];
261
+ } else if (qualifier === "last") {
262
+ if (unit === "week") {
263
+ const d = addDays(ref, -7);
264
+ return [d, "week", [`week-${isoWeek(d)}-${d.getUTCFullYear()}`, "last-week"]];
265
+ }
266
+ if (unit === "month") {
267
+ const year = ref.getUTCMonth() === 0 ? ref.getUTCFullYear() - 1 : ref.getUTCFullYear();
268
+ const month = ref.getUTCMonth() === 0 ? 12 : ref.getUTCMonth();
269
+ const d = dateUtc(year, month, 1) as Date;
270
+ return [
271
+ d,
272
+ "month",
273
+ [`${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`, "last-month"],
274
+ ];
275
+ }
276
+ if (unit === "year") {
277
+ const d = dateUtc(ref.getUTCFullYear() - 1, 1, 1) as Date;
278
+ return [d, "year", [String(d.getUTCFullYear()), "last-year"]];
279
+ }
280
+ } else if (qualifier === "next") {
281
+ if (unit === "week") {
282
+ const d = addDays(ref, 7);
283
+ return [d, "week", [`week-${isoWeek(d)}-${d.getUTCFullYear()}`, "next-week"]];
284
+ }
285
+ if (unit === "month") {
286
+ const year = ref.getUTCMonth() === 11 ? ref.getUTCFullYear() + 1 : ref.getUTCFullYear();
287
+ const month = ref.getUTCMonth() === 11 ? 1 : ref.getUTCMonth() + 2;
288
+ const d = dateUtc(year, month, 1) as Date;
289
+ return [
290
+ d,
291
+ "month",
292
+ [`${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`, "next-month"],
293
+ ];
294
+ }
295
+ if (unit === "year") {
296
+ const d = dateUtc(ref.getUTCFullYear() + 1, 1, 1) as Date;
297
+ return [d, "year", [String(d.getUTCFullYear()), "next-year"]];
298
+ }
299
+ }
300
+ }
301
+
302
+ m = /\b(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+(ago|before|earlier|back)\b/.exec(textLower);
303
+ if (m !== null) {
304
+ const num = Number.parseInt(m[1] as string, 10);
305
+ const unit = m[2] as string;
306
+ const d = deltaDate(ref, num, unit, -1);
307
+ if (d === undefined) return null;
308
+ return [d, unit === "day" || unit === "hour" ? "day" : "week", [isoDate(d), `${num}-${unit}s-ago`]];
309
+ }
310
+
311
+ m = /\bin\s+(\d+)\s+(second|minute|hour|day|week|month|year)s?\b/.exec(textLower);
312
+ if (m !== null) {
313
+ const num = Number.parseInt(m[1] as string, 10);
314
+ const unit = m[2] as string;
315
+ const d = deltaDate(ref, num, unit, 1);
316
+ if (d === undefined) return null;
317
+ return [d, unit === "day" || unit === "hour" ? "day" : "week", [isoDate(d), `in-${num}-${unit}s`]];
318
+ }
319
+
320
+ if (/\b(recently|lately|not long ago)\b/.test(textLower)) {
321
+ return [dateOnly(ref), "relative", ["recently"]];
322
+ }
323
+
324
+ if (/\b(a while ago|some time ago|long ago)\b/.test(textLower)) {
325
+ return [dateOnly(ref), "relative", ["vague"]];
326
+ }
327
+
328
+ return null;
329
+ }
330
+
331
+ export function extractTemporal(text: string, reference?: QueryTime): TemporalInfo {
332
+ const result = parseNlDate(text, reference);
333
+ const tags: string[] = [];
334
+ const textLower = text.toLowerCase();
335
+ for (const timeName of NAMED_TIME_KEYS) {
336
+ if (textLower.includes(timeName)) {
337
+ tags.push(timeName);
338
+ break;
339
+ }
340
+ }
341
+
342
+ if (result === null) {
343
+ return {
344
+ event_date: null,
345
+ event_date_precision: "unknown",
346
+ temporal_tags: tags,
347
+ primary_signal: tags[0] ?? null,
348
+ };
349
+ }
350
+
351
+ const [eventDate, precision, parsedTags] = result;
352
+ const allTags = parsedTags.concat(tags);
353
+ return {
354
+ event_date: isoDate(eventDate),
355
+ event_date_precision: precision,
356
+ temporal_tags: allTags,
357
+ primary_signal: allTags[0] ?? null,
358
+ };
359
+ }
360
+
361
+ export function extractDateFromText(text: string, reference?: QueryTime): string | null {
362
+ return extractTemporal(text, reference).event_date;
363
+ }
@@ -0,0 +1,30 @@
1
+ const PRICING: Readonly<Record<string, number>> = {
2
+ "claude-sonnet-4": 3.0,
3
+ "claude-haiku": 0.8,
4
+ "gpt-4o": 2.5,
5
+ "gpt-4o-mini": 0.15,
6
+ default: 3.0,
7
+ };
8
+ const DEFAULT_RATE_PER_1M = 3.0;
9
+
10
+ export interface CostEstimate {
11
+ tokens: number;
12
+ model: string;
13
+ cost_usd: number;
14
+ rate_per_1m: number;
15
+ }
16
+
17
+ export function estimateTokens(text: string, _model = "default"): number {
18
+ if (text.length === 0) return 0;
19
+ return Math.floor(text.length / 4);
20
+ }
21
+ export function estimateCost(tokens: number, model = "claude-sonnet-4"): CostEstimate {
22
+ const rate = PRICING[model] ?? DEFAULT_RATE_PER_1M;
23
+ const cost = (tokens / 1_000_000) * rate;
24
+ return {
25
+ tokens,
26
+ model,
27
+ cost_usd: Math.round(cost * 1_000_000) / 1_000_000,
28
+ rate_per_1m: rate,
29
+ };
30
+ }