@sentry/junior-memory 0.130.0 → 0.132.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/dist/ranking.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { MemoryRecord } from "./store";
2
2
  export interface MemoryMatch {
3
- exactIdentifier: boolean;
4
3
  lexical?: {
5
4
  rank: number;
6
5
  };
@@ -10,10 +9,7 @@ export interface MemoryMatch {
10
9
  rank: number;
11
10
  };
12
11
  }
13
- /**
14
- * Keep exact structured-identifier matches in the strongest relevance tier,
15
- * then fuse lexical and vector ranks without comparing provider raw scores.
16
- */
12
+ /** Fuse lexical and vector ranks without comparing provider raw scores. */
17
13
  export declare function rankMemoryMatches(matches: MemoryMatch[], options: {
18
14
  channelPrefix?: string;
19
15
  nowMs: number;
@@ -0,0 +1,4 @@
1
+ CREATE EXTENSION IF NOT EXISTS btree_gin;--> statement-breakpoint
2
+ DROP INDEX "junior_memory_memories_search_idx";--> statement-breakpoint
3
+ ALTER TABLE "junior_memory_memories" ADD COLUMN "search_vector" "tsvector" GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED;--> statement-breakpoint
4
+ CREATE INDEX "junior_memory_memories_search_idx" ON "junior_memory_memories" USING gin ("scope","scope_key","search_vector") WHERE "junior_memory_memories"."archived_at_ms" IS NULL AND "junior_memory_memories"."superseded_at_ms" IS NULL AND "junior_memory_memories"."superseded_by_id" IS NULL;
@@ -0,0 +1,386 @@
1
+ {
2
+ "id": "f18fa309-6485-40f3-921a-5d65552fbdf6",
3
+ "prevId": "6948ea3f-2e44-49ce-aa94-8a993ce61cec",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.junior_memory_embeddings": {
8
+ "name": "junior_memory_embeddings",
9
+ "schema": "",
10
+ "columns": {
11
+ "memory_id": {
12
+ "name": "memory_id",
13
+ "type": "text",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ },
17
+ "provider": {
18
+ "name": "provider",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true
22
+ },
23
+ "model": {
24
+ "name": "model",
25
+ "type": "text",
26
+ "primaryKey": false,
27
+ "notNull": true
28
+ },
29
+ "dimensions": {
30
+ "name": "dimensions",
31
+ "type": "integer",
32
+ "primaryKey": false,
33
+ "notNull": true
34
+ },
35
+ "metric": {
36
+ "name": "metric",
37
+ "type": "text",
38
+ "primaryKey": false,
39
+ "notNull": true
40
+ },
41
+ "content_hash": {
42
+ "name": "content_hash",
43
+ "type": "text",
44
+ "primaryKey": false,
45
+ "notNull": true
46
+ },
47
+ "embedding": {
48
+ "name": "embedding",
49
+ "type": "vector(1536)",
50
+ "primaryKey": false,
51
+ "notNull": true
52
+ },
53
+ "created_at_ms": {
54
+ "name": "created_at_ms",
55
+ "type": "bigint",
56
+ "primaryKey": false,
57
+ "notNull": true
58
+ }
59
+ },
60
+ "indexes": {
61
+ "junior_memory_embeddings_model_idx": {
62
+ "name": "junior_memory_embeddings_model_idx",
63
+ "columns": [
64
+ {
65
+ "expression": "provider",
66
+ "isExpression": false,
67
+ "asc": true,
68
+ "nulls": "last"
69
+ },
70
+ {
71
+ "expression": "model",
72
+ "isExpression": false,
73
+ "asc": true,
74
+ "nulls": "last"
75
+ },
76
+ {
77
+ "expression": "dimensions",
78
+ "isExpression": false,
79
+ "asc": true,
80
+ "nulls": "last"
81
+ },
82
+ {
83
+ "expression": "metric",
84
+ "isExpression": false,
85
+ "asc": true,
86
+ "nulls": "last"
87
+ }
88
+ ],
89
+ "isUnique": false,
90
+ "concurrently": false,
91
+ "method": "btree",
92
+ "with": {}
93
+ }
94
+ },
95
+ "foreignKeys": {
96
+ "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": {
97
+ "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk",
98
+ "tableFrom": "junior_memory_embeddings",
99
+ "tableTo": "junior_memory_memories",
100
+ "columnsFrom": ["memory_id"],
101
+ "columnsTo": ["id"],
102
+ "onDelete": "cascade",
103
+ "onUpdate": "no action"
104
+ }
105
+ },
106
+ "compositePrimaryKeys": {},
107
+ "uniqueConstraints": {},
108
+ "policies": {},
109
+ "checkConstraints": {
110
+ "junior_memory_embeddings_metric_check": {
111
+ "name": "junior_memory_embeddings_metric_check",
112
+ "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')"
113
+ },
114
+ "junior_memory_embeddings_dimensions_check": {
115
+ "name": "junior_memory_embeddings_dimensions_check",
116
+ "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536"
117
+ }
118
+ },
119
+ "isRLSEnabled": false
120
+ },
121
+ "public.junior_memory_memories": {
122
+ "name": "junior_memory_memories",
123
+ "schema": "",
124
+ "columns": {
125
+ "id": {
126
+ "name": "id",
127
+ "type": "text",
128
+ "primaryKey": true,
129
+ "notNull": true
130
+ },
131
+ "scope": {
132
+ "name": "scope",
133
+ "type": "text",
134
+ "primaryKey": false,
135
+ "notNull": true
136
+ },
137
+ "scope_key": {
138
+ "name": "scope_key",
139
+ "type": "text",
140
+ "primaryKey": false,
141
+ "notNull": true
142
+ },
143
+ "type": {
144
+ "name": "type",
145
+ "type": "text",
146
+ "primaryKey": false,
147
+ "notNull": true
148
+ },
149
+ "subject_type": {
150
+ "name": "subject_type",
151
+ "type": "text",
152
+ "primaryKey": false,
153
+ "notNull": true
154
+ },
155
+ "subject_key": {
156
+ "name": "subject_key",
157
+ "type": "text",
158
+ "primaryKey": false,
159
+ "notNull": false
160
+ },
161
+ "content": {
162
+ "name": "content",
163
+ "type": "text",
164
+ "primaryKey": false,
165
+ "notNull": true
166
+ },
167
+ "search_vector": {
168
+ "name": "search_vector",
169
+ "type": "tsvector",
170
+ "primaryKey": false,
171
+ "notNull": false,
172
+ "generated": {
173
+ "as": "to_tsvector('english', \"content\")",
174
+ "type": "stored"
175
+ }
176
+ },
177
+ "source_platform": {
178
+ "name": "source_platform",
179
+ "type": "text",
180
+ "primaryKey": false,
181
+ "notNull": true
182
+ },
183
+ "source_key": {
184
+ "name": "source_key",
185
+ "type": "text",
186
+ "primaryKey": false,
187
+ "notNull": true
188
+ },
189
+ "idempotency_key": {
190
+ "name": "idempotency_key",
191
+ "type": "text",
192
+ "primaryKey": false,
193
+ "notNull": false
194
+ },
195
+ "observed_at_ms": {
196
+ "name": "observed_at_ms",
197
+ "type": "bigint",
198
+ "primaryKey": false,
199
+ "notNull": true
200
+ },
201
+ "created_at_ms": {
202
+ "name": "created_at_ms",
203
+ "type": "bigint",
204
+ "primaryKey": false,
205
+ "notNull": true
206
+ },
207
+ "expires_at_ms": {
208
+ "name": "expires_at_ms",
209
+ "type": "bigint",
210
+ "primaryKey": false,
211
+ "notNull": false
212
+ },
213
+ "superseded_at_ms": {
214
+ "name": "superseded_at_ms",
215
+ "type": "bigint",
216
+ "primaryKey": false,
217
+ "notNull": false
218
+ },
219
+ "superseded_by_id": {
220
+ "name": "superseded_by_id",
221
+ "type": "text",
222
+ "primaryKey": false,
223
+ "notNull": false
224
+ },
225
+ "archived_at_ms": {
226
+ "name": "archived_at_ms",
227
+ "type": "bigint",
228
+ "primaryKey": false,
229
+ "notNull": false
230
+ },
231
+ "archive_reason": {
232
+ "name": "archive_reason",
233
+ "type": "text",
234
+ "primaryKey": false,
235
+ "notNull": false
236
+ }
237
+ },
238
+ "indexes": {
239
+ "junior_memory_memories_visible_idx": {
240
+ "name": "junior_memory_memories_visible_idx",
241
+ "columns": [
242
+ {
243
+ "expression": "scope",
244
+ "isExpression": false,
245
+ "asc": true,
246
+ "nulls": "last"
247
+ },
248
+ {
249
+ "expression": "scope_key",
250
+ "isExpression": false,
251
+ "asc": true,
252
+ "nulls": "last"
253
+ },
254
+ {
255
+ "expression": "created_at_ms",
256
+ "isExpression": false,
257
+ "asc": false,
258
+ "nulls": "last"
259
+ },
260
+ {
261
+ "expression": "id",
262
+ "isExpression": false,
263
+ "asc": true,
264
+ "nulls": "last"
265
+ }
266
+ ],
267
+ "isUnique": false,
268
+ "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL",
269
+ "concurrently": false,
270
+ "method": "btree",
271
+ "with": {}
272
+ },
273
+ "junior_memory_memories_expiration_idx": {
274
+ "name": "junior_memory_memories_expiration_idx",
275
+ "columns": [
276
+ {
277
+ "expression": "expires_at_ms",
278
+ "isExpression": false,
279
+ "asc": true,
280
+ "nulls": "last"
281
+ }
282
+ ],
283
+ "isUnique": false,
284
+ "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL",
285
+ "concurrently": false,
286
+ "method": "btree",
287
+ "with": {}
288
+ },
289
+ "junior_memory_memories_search_idx": {
290
+ "name": "junior_memory_memories_search_idx",
291
+ "columns": [
292
+ {
293
+ "expression": "scope",
294
+ "isExpression": false,
295
+ "asc": true,
296
+ "nulls": "last"
297
+ },
298
+ {
299
+ "expression": "scope_key",
300
+ "isExpression": false,
301
+ "asc": true,
302
+ "nulls": "last"
303
+ },
304
+ {
305
+ "expression": "search_vector",
306
+ "isExpression": false,
307
+ "asc": true,
308
+ "nulls": "last"
309
+ }
310
+ ],
311
+ "isUnique": false,
312
+ "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL",
313
+ "concurrently": false,
314
+ "method": "gin",
315
+ "with": {}
316
+ },
317
+ "junior_memory_memories_idempotency_idx": {
318
+ "name": "junior_memory_memories_idempotency_idx",
319
+ "columns": [
320
+ {
321
+ "expression": "scope",
322
+ "isExpression": false,
323
+ "asc": true,
324
+ "nulls": "last"
325
+ },
326
+ {
327
+ "expression": "scope_key",
328
+ "isExpression": false,
329
+ "asc": true,
330
+ "nulls": "last"
331
+ },
332
+ {
333
+ "expression": "idempotency_key",
334
+ "isExpression": false,
335
+ "asc": true,
336
+ "nulls": "last"
337
+ }
338
+ ],
339
+ "isUnique": true,
340
+ "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL",
341
+ "concurrently": false,
342
+ "method": "btree",
343
+ "with": {}
344
+ }
345
+ },
346
+ "foreignKeys": {},
347
+ "compositePrimaryKeys": {},
348
+ "uniqueConstraints": {},
349
+ "policies": {},
350
+ "checkConstraints": {
351
+ "junior_memory_memories_scope_check": {
352
+ "name": "junior_memory_memories_scope_check",
353
+ "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')"
354
+ },
355
+ "junior_memory_memories_kind_check": {
356
+ "name": "junior_memory_memories_kind_check",
357
+ "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )"
358
+ },
359
+ "junior_memory_memories_subject_type_check": {
360
+ "name": "junior_memory_memories_subject_type_check",
361
+ "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')"
362
+ },
363
+ "junior_memory_memories_subject_key_check": {
364
+ "name": "junior_memory_memories_subject_key_check",
365
+ "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)"
366
+ },
367
+ "junior_memory_memories_source_platform_check": {
368
+ "name": "junior_memory_memories_source_platform_check",
369
+ "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')"
370
+ }
371
+ },
372
+ "isRLSEnabled": false
373
+ }
374
+ },
375
+ "enums": {},
376
+ "schemas": {},
377
+ "sequences": {},
378
+ "roles": {},
379
+ "policies": {},
380
+ "views": {},
381
+ "_meta": {
382
+ "columns": {},
383
+ "schemas": {},
384
+ "tables": {}
385
+ }
386
+ }
@@ -43,6 +43,13 @@
43
43
  "when": 1785293874649,
44
44
  "tag": "0005_short_jamie_braddock",
45
45
  "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "7",
50
+ "when": 1785878952301,
51
+ "tag": "0006_friendly_hydra",
52
+ "breakpoints": true
46
53
  }
47
54
  ]
48
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-memory",
3
- "version": "0.130.0",
3
+ "version": "0.132.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -27,7 +27,7 @@
27
27
  "commander": "^14.0.3",
28
28
  "drizzle-orm": "^0.45.2",
29
29
  "zod": "^4.4.3",
30
- "@sentry/junior-plugin-api": "0.130.0"
30
+ "@sentry/junior-plugin-api": "0.132.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.9.1",
package/src/db/schema.ts CHANGED
@@ -8,6 +8,7 @@ import { sql } from "drizzle-orm";
8
8
  import {
9
9
  bigint,
10
10
  check,
11
+ customType,
11
12
  index,
12
13
  integer,
13
14
  pgTable,
@@ -24,6 +25,12 @@ import {
24
25
  MEMORY_KINDS,
25
26
  } from "../types";
26
27
 
28
+ const tsvector = customType<{ data: string }>({
29
+ dataType() {
30
+ return "tsvector";
31
+ },
32
+ });
33
+
27
34
  export const juniorMemoryMemories = pgTable(
28
35
  "junior_memory_memories",
29
36
  {
@@ -34,6 +41,9 @@ export const juniorMemoryMemories = pgTable(
34
41
  subjectType: text("subject_type", { enum: MEMORY_SUBJECT_TYPES }).notNull(),
35
42
  subjectKey: text("subject_key"),
36
43
  content: text("content").notNull(),
44
+ searchVector: tsvector("search_vector").generatedAlwaysAs(
45
+ sql`to_tsvector('english', "content")`,
46
+ ),
37
47
  sourcePlatform: text("source_platform", {
38
48
  enum: MEMORY_SOURCE_PLATFORMS,
39
49
  }).notNull(),
@@ -59,7 +69,7 @@ export const juniorMemoryMemories = pgTable(
59
69
  sql`${table.archivedAtMs} IS NULL AND ${table.expiresAtMs} IS NOT NULL`,
60
70
  ),
61
71
  index("junior_memory_memories_search_idx")
62
- .using("gin", sql`to_tsvector('english', ${table.content})`)
72
+ .using("gin", table.scope, table.scopeKey, table.searchVector)
63
73
  .where(
64
74
  sql`${table.archivedAtMs} IS NULL AND ${table.supersededAtMs} IS NULL AND ${table.supersededById} IS NULL`,
65
75
  ),
package/src/ranking.ts CHANGED
@@ -4,7 +4,6 @@ const RECIPROCAL_RANK_FUSION_K = 60;
4
4
  const ONE_DAY_MS = 24 * 60 * 60 * 1000;
5
5
 
6
6
  export interface MemoryMatch {
7
- exactIdentifier: boolean;
8
7
  lexical?: {
9
8
  rank: number;
10
9
  };
@@ -47,10 +46,7 @@ function observedAgeRank(memory: MemoryRecord, nowMs: number): number {
47
46
  return 0;
48
47
  }
49
48
 
50
- /**
51
- * Keep exact structured-identifier matches in the strongest relevance tier,
52
- * then fuse lexical and vector ranks without comparing provider raw scores.
53
- */
49
+ /** Fuse lexical and vector ranks without comparing provider raw scores. */
54
50
  export function rankMemoryMatches(
55
51
  matches: MemoryMatch[],
56
52
  options: {
@@ -67,17 +63,11 @@ export function rankMemoryMatches(
67
63
  }
68
64
  byId.set(match.memory.id, {
69
65
  ...existing,
70
- exactIdentifier: existing.exactIdentifier || match.exactIdentifier,
71
66
  ...(match.lexical ? { lexical: match.lexical } : {}),
72
67
  ...(match.vector ? { vector: match.vector } : {}),
73
68
  });
74
69
  }
75
70
  return [...byId.values()].sort((left, right) => {
76
- const identifierDelta =
77
- Number(right.exactIdentifier) - Number(left.exactIdentifier);
78
- if (identifierDelta !== 0) {
79
- return identifierDelta;
80
- }
81
71
  const scoreDelta = matchScore(right) - matchScore(left);
82
72
  if (scoreDelta !== 0) {
83
73
  return scoreDelta;
package/src/store.ts CHANGED
@@ -128,6 +128,7 @@ const memoryRowSchema = z
128
128
  id: z.string().min(1),
129
129
  idempotencyKey: optionalStringSchema,
130
130
  observedAtMs: z.coerce.number(),
131
+ searchVector: z.string().optional(),
131
132
  scope: z.enum(MEMORY_SCOPES),
132
133
  scopeKey: z.string().min(1),
133
134
  sourceKey: z.string().min(1),
@@ -591,22 +592,6 @@ export async function archiveExpiredMemoryBatch(args: {
591
592
  return { archivedCount: archivedIds.length };
592
593
  }
593
594
 
594
- /**
595
- * Find path-like, dotted, underscored, and multi-part hyphenated identifiers.
596
- * Two-part hyphenated prose such as "long-running" remains ordinary text.
597
- */
598
- function structuredQueryIdentifiers(query: string): string[] {
599
- const matches =
600
- query
601
- .toLowerCase()
602
- .match(
603
- /(?:[a-z0-9][a-z0-9._-]*\/)+[a-z0-9][a-z0-9._-]*|[a-z0-9]+(?:[._][a-z0-9]+)+|[a-z0-9]+(?:-[a-z0-9]+){2,}/g,
604
- ) ?? [];
605
- return [
606
- ...new Set(matches.map((identifier) => identifier.replace(/\.+$/, ""))),
607
- ];
608
- }
609
-
610
595
  function denseRanks<T>(
611
596
  values: T[],
612
597
  key: (value: T) => string | number,
@@ -982,50 +967,23 @@ async function searchVisibleLexicalMemories(args: {
982
967
  )
983
968
  FROM unnest(tsvector_to_array(${queryVector})) AS query_terms(term)
984
969
  )`;
985
- const textsearch = sql`to_tsvector('english', ${juniorMemoryMemories.content})`;
970
+ const textsearch = juniorMemoryMemories.searchVector;
986
971
  const textRank = sql<number>`ts_rank_cd(${textsearch}, ${tsquery})`;
987
- const identifiers = structuredQueryIdentifiers(args.query);
988
- const contentTokens = sql`regexp_split_to_array(lower(${juniorMemoryMemories.content}), '[^a-z0-9._/-]+')`;
989
- const identifierArray =
990
- identifiers.length > 0
991
- ? sql`ARRAY[${sql.join(
992
- identifiers.map((identifier) => sql`${identifier}`),
993
- sql`, `,
994
- )}]::text[]`
995
- : undefined;
996
- const exactIdentifierMatch =
997
- identifierArray !== undefined
998
- ? sql<boolean>`EXISTS (
999
- SELECT 1
1000
- FROM unnest(${contentTokens}) AS content_token(value)
1001
- CROSS JOIN unnest(${identifierArray}) AS query_identifier(value)
1002
- WHERE strpos(
1003
- '/' || regexp_replace(content_token.value, '\\.+$', '') || '/',
1004
- '/' || query_identifier.value || '/'
1005
- ) > 0
1006
- )`
1007
- : sql<boolean>`false`;
1008
972
  const rows = await args.db
1009
973
  .select({
1010
- exactIdentifierMatch,
1011
974
  memory: juniorMemoryMemories,
1012
975
  textRank,
1013
976
  })
1014
977
  .from(juniorMemoryMemories)
1015
978
  .where(and(predicate, sql`${textsearch} @@ ${tsquery}`))
1016
979
  .orderBy(
1017
- ...(identifiers.length > 0 ? [desc(exactIdentifierMatch)] : []),
1018
980
  desc(textRank),
1019
981
  desc(juniorMemoryMemories.observedAtMs),
1020
982
  asc(juniorMemoryMemories.id),
1021
983
  )
1022
984
  .limit(args.limit);
1023
- const ranks = denseRanks(
1024
- rows,
1025
- (row) => `${row.exactIdentifierMatch}:${Number(row.textRank)}`,
1026
- );
985
+ const ranks = denseRanks(rows, (row) => Number(row.textRank));
1027
986
  return rows.map((row, index) => ({
1028
- exactIdentifier: row.exactIdentifierMatch,
1029
987
  lexical: { rank: ranks[index] },
1030
988
  memory: parseMemoryRow(row.memory),
1031
989
  sourceKey: row.memory.sourceKey,
@@ -1100,7 +1058,6 @@ async function searchVisibleVectorMemories(args: {
1100
1058
  }
1101
1059
  return [
1102
1060
  {
1103
- exactIdentifier: false,
1104
1061
  memory: parseMemoryRow(row.memory),
1105
1062
  sourceKey: row.memory.sourceKey,
1106
1063
  vector: {