@lobehub/lobehub 2.0.0-next.110 → 2.0.0-next.111

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 (36) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/changelog/v1.json +9 -0
  3. package/docs/development/database-schema.dbml +2 -1
  4. package/package.json +1 -1
  5. package/packages/context-engine/src/index.ts +1 -1
  6. package/packages/context-engine/src/providers/KnowledgeInjector.ts +78 -0
  7. package/packages/context-engine/src/providers/index.ts +2 -0
  8. package/packages/database/migrations/0047_add_slug_document.sql +1 -5
  9. package/packages/database/migrations/meta/0047_snapshot.json +30 -14
  10. package/packages/database/migrations/meta/_journal.json +1 -1
  11. package/packages/database/src/core/migrations.json +3 -3
  12. package/packages/database/src/models/__tests__/agent.test.ts +172 -3
  13. package/packages/database/src/models/__tests__/userMemories.test.ts +1382 -0
  14. package/packages/database/src/models/agent.ts +17 -0
  15. package/packages/database/src/models/userMemory.ts +993 -0
  16. package/packages/database/src/schemas/userMemories.ts +22 -5
  17. package/packages/prompts/src/prompts/files/__snapshots__/knowledgeBase.test.ts.snap +103 -0
  18. package/packages/prompts/src/prompts/files/index.ts +3 -0
  19. package/packages/prompts/src/prompts/files/knowledgeBase.test.ts +167 -0
  20. package/packages/prompts/src/prompts/files/knowledgeBase.ts +85 -0
  21. package/packages/types/src/files/index.ts +1 -0
  22. package/packages/types/src/index.ts +1 -0
  23. package/packages/types/src/knowledgeBase/index.ts +1 -0
  24. package/packages/types/src/userMemory/index.ts +3 -0
  25. package/packages/types/src/userMemory/layers.ts +54 -0
  26. package/packages/types/src/userMemory/shared.ts +64 -0
  27. package/packages/types/src/userMemory/tools.ts +240 -0
  28. package/src/features/ChatList/Messages/index.tsx +16 -19
  29. package/src/features/ChatList/components/ContextMenu.tsx +23 -16
  30. package/src/helpers/toolEngineering/index.ts +5 -9
  31. package/src/hooks/useQueryParam.ts +24 -22
  32. package/src/server/routers/async/file.ts +2 -7
  33. package/src/services/chat/contextEngineering.ts +19 -0
  34. package/src/store/agent/slices/chat/selectors/agent.ts +4 -0
  35. package/src/store/chat/slices/builtinTool/actions/knowledgeBase.ts +5 -15
  36. package/src/tools/knowledge-base/ExecutionRuntime/index.ts +3 -3
@@ -0,0 +1,993 @@
1
+ import {
2
+ IdentityTypeEnum,
3
+ LayersEnum,
4
+ MergeStrategyEnum,
5
+ RelationshipEnum,
6
+ TypesEnum,
7
+ } from '@lobechat/types';
8
+ import { and, cosineDistance, desc, eq, inArray, sql } from 'drizzle-orm';
9
+
10
+ import { merge } from '@/utils/merge';
11
+
12
+ import {
13
+ UserMemoryContext,
14
+ UserMemoryContextsWithoutVectors,
15
+ UserMemoryExperience,
16
+ UserMemoryExperiencesWithoutVectors,
17
+ UserMemoryIdentitiesWithoutVectors,
18
+ UserMemoryIdentity,
19
+ UserMemoryItem,
20
+ UserMemoryPreference,
21
+ UserMemoryPreferencesWithoutVectors,
22
+ userMemories,
23
+ userMemoriesContexts,
24
+ userMemoriesExperiences,
25
+ userMemoriesIdentities,
26
+ userMemoriesPreferences,
27
+ } from '../schemas';
28
+ import { LobeChatDatabase } from '../type';
29
+
30
+ const normalizeRelationshipValue = (input: unknown): RelationshipEnum | null => {
31
+ if (input === null) return null;
32
+ if (typeof input !== 'string') return null;
33
+ const normalized = input.trim().toLowerCase();
34
+ return Object.values(RelationshipEnum).includes(normalized as RelationshipEnum)
35
+ ? (normalized as RelationshipEnum)
36
+ : null;
37
+ };
38
+
39
+ const normalizeIdentityTypeValue = (input: unknown): IdentityTypeEnum | null => {
40
+ if (input === null) return null;
41
+ if (typeof input !== 'string') return null;
42
+ const normalized = input.trim().toLowerCase();
43
+ return Object.values(IdentityTypeEnum).includes(normalized as IdentityTypeEnum)
44
+ ? (normalized as IdentityTypeEnum)
45
+ : null;
46
+ };
47
+
48
+ const coerceDate = (input: unknown): Date | null => {
49
+ if (input === null || input === undefined) return null;
50
+ if (input instanceof Date) {
51
+ return Number.isNaN(input.getTime()) ? null : input;
52
+ }
53
+ if (typeof input === 'string' || typeof input === 'number') {
54
+ const date = new Date(input);
55
+ return Number.isNaN(date.getTime()) ? null : date;
56
+ }
57
+
58
+ return null;
59
+ };
60
+
61
+ export interface BaseCreateUserMemoryParams {
62
+ details: string;
63
+ detailsEmbedding?: number[];
64
+ memoryCategory: string;
65
+ memoryLayer: LayersEnum;
66
+ memoryType: TypesEnum;
67
+ summary: string;
68
+ summaryEmbedding?: number[];
69
+ title: string;
70
+ titleEmbedding?: number[];
71
+ }
72
+
73
+ export interface CreateUserMemoryContextParams extends BaseCreateUserMemoryParams {
74
+ context: Omit<
75
+ UserMemoryContext,
76
+ 'id' | 'userId' | 'createdAt' | 'updatedAt' | 'accessedAt' | 'userMemoryIds'
77
+ >;
78
+ }
79
+
80
+ export interface CreateUserMemoryExperienceParams extends BaseCreateUserMemoryParams {
81
+ experience: Omit<
82
+ UserMemoryExperience,
83
+ 'id' | 'userId' | 'createdAt' | 'updatedAt' | 'accessedAt' | 'userMemoryId'
84
+ >;
85
+ }
86
+
87
+ export interface CreateUserMemoryIdentityParams extends BaseCreateUserMemoryParams {
88
+ identity: Omit<
89
+ UserMemoryIdentity,
90
+ 'id' | 'userId' | 'createdAt' | 'updatedAt' | 'accessedAt' | 'userMemoryId'
91
+ >;
92
+ }
93
+
94
+ export interface CreateUserMemoryPreferenceParams extends BaseCreateUserMemoryParams {
95
+ preference: Omit<
96
+ UserMemoryPreference,
97
+ 'id' | 'userId' | 'createdAt' | 'updatedAt' | 'accessedAt' | 'userMemoryId'
98
+ >;
99
+ }
100
+
101
+ export type CreateUserMemoryParams =
102
+ | CreateUserMemoryContextParams
103
+ | CreateUserMemoryExperienceParams
104
+ | CreateUserMemoryIdentityParams
105
+ | CreateUserMemoryPreferenceParams;
106
+
107
+ export interface SearchUserMemoryParams {
108
+ embedding?: number[];
109
+ limit?: number;
110
+ limits?: Partial<Record<'contexts' | 'experiences' | 'preferences', number>>;
111
+ memoryCategory?: string;
112
+ memoryType?: string;
113
+ query?: string;
114
+ }
115
+
116
+ export interface SearchUserMemoryWithEmbeddingParams {
117
+ embedding: number[];
118
+ limits?: Partial<Record<'contexts' | 'experiences' | 'preferences', number>>;
119
+ memoryCategory?: string;
120
+ memoryType?: string;
121
+ }
122
+
123
+ export interface UserMemorySearchAggregatedResult {
124
+ contexts: UserMemoryContextsWithoutVectors[];
125
+ experiences: UserMemoryExperiencesWithoutVectors[];
126
+ preferences: UserMemoryPreferencesWithoutVectors[];
127
+ }
128
+
129
+ export interface UpdateUserMemoryVectorsParams {
130
+ detailsVector1024?: number[] | null;
131
+ summaryVector1024?: number[] | null;
132
+ }
133
+
134
+ export interface UpdateContextVectorsParams {
135
+ descriptionVector?: number[] | null;
136
+ titleVector?: number[] | null;
137
+ }
138
+
139
+ export interface UpdatePreferenceVectorsParams {
140
+ conclusionDirectivesVector?: number[] | null;
141
+ }
142
+
143
+ export interface UpdateIdentityVectorsParams {
144
+ descriptionVector?: number[] | null;
145
+ }
146
+
147
+ export interface UpdateExperienceVectorsParams {
148
+ actionVector?: number[] | null;
149
+ keyLearningVector?: number[] | null;
150
+ situationVector?: number[] | null;
151
+ }
152
+
153
+ export interface IdentityEntryPayload {
154
+ description?: string | null;
155
+ descriptionVector?: number[] | null;
156
+ episodicDate?: string | Date | null;
157
+ metadata?: Record<string, unknown> | null;
158
+ relationship?: RelationshipEnum | string | null;
159
+ role?: string | null;
160
+ tags?: string[] | null;
161
+ type?: IdentityTypeEnum | string | null;
162
+ }
163
+
164
+ export interface IdentityEntryBasePayload {
165
+ details?: string | null;
166
+ detailsVector1024?: number[] | null;
167
+ lastAccessedAt?: string | Date | null;
168
+ memoryCategory?: string | null;
169
+ memoryLayer?: string | null;
170
+ memoryType?: string | null;
171
+ metadata?: Record<string, unknown> | null;
172
+ status?: string | null;
173
+ summary?: string | null;
174
+ summaryVector1024?: number[] | null;
175
+ tags?: string[] | null;
176
+ title?: string | null;
177
+ }
178
+
179
+ export interface AddIdentityEntryParams {
180
+ base: IdentityEntryBasePayload;
181
+ identity: IdentityEntryPayload;
182
+ }
183
+
184
+ export interface AddIdentityEntryResult {
185
+ identityId: string;
186
+ userMemoryId: string;
187
+ }
188
+
189
+ export interface UpdateIdentityEntryParams {
190
+ base?: IdentityEntryBasePayload;
191
+ identity?: IdentityEntryPayload;
192
+ identityId: string;
193
+ mergeStrategy?: MergeStrategyEnum;
194
+ }
195
+
196
+ export class UserMemoryModel {
197
+ private userId: string;
198
+ private db: LobeChatDatabase;
199
+
200
+ constructor(db: LobeChatDatabase, userId: string) {
201
+ this.userId = userId;
202
+ this.db = db;
203
+ }
204
+
205
+ private buildBaseMemoryInsertValues(
206
+ params: BaseCreateUserMemoryParams,
207
+ options?: {
208
+ metadata?: Record<string, unknown> | null;
209
+ status?: string | null;
210
+ tags?: string[] | null;
211
+ },
212
+ ): typeof userMemories.$inferInsert {
213
+ const now = new Date();
214
+
215
+ return {
216
+ accessedCount: 0,
217
+ details: params.details ?? null,
218
+ detailsVector1024: params.detailsEmbedding ?? null,
219
+ lastAccessedAt: now,
220
+ memoryCategory: params.memoryCategory ?? null,
221
+ memoryLayer: params.memoryLayer,
222
+ memoryType: params.memoryType ?? null,
223
+ metadata: options?.metadata ?? null,
224
+ status: options?.status ?? null,
225
+ summary: params.summary ?? null,
226
+ summaryVector1024: params.summaryEmbedding ?? null,
227
+ tags: options?.tags ?? null,
228
+ title: params.title ?? null,
229
+ userId: this.userId,
230
+ } satisfies typeof userMemories.$inferInsert;
231
+ }
232
+
233
+ create = async (params: CreateUserMemoryParams): Promise<UserMemoryItem> => {
234
+ const [result] = await this.db
235
+ .insert(userMemories)
236
+ .values(this.buildBaseMemoryInsertValues(params))
237
+ .returning();
238
+
239
+ return result;
240
+ };
241
+
242
+ createContextMemory = async (
243
+ params: CreateUserMemoryContextParams,
244
+ ): Promise<{ context: UserMemoryContext; memory: UserMemoryItem }> => {
245
+ return this.db.transaction(async (tx) => {
246
+ const baseValues = this.buildBaseMemoryInsertValues(params, {
247
+ metadata: params.context.metadata ?? null,
248
+ tags: params.context.tags ?? null,
249
+ });
250
+
251
+ const [memory] = await tx.insert(userMemories).values(baseValues).returning();
252
+ if (!memory) throw new Error('Failed to create user memory context');
253
+
254
+ const contextValues = {
255
+ associatedObjects: params.context.associatedObjects ?? [],
256
+ associatedSubjects: params.context.associatedSubjects ?? [],
257
+ currentStatus: params.context.currentStatus ?? null,
258
+ description: params.context.description ?? null,
259
+ descriptionVector: params.context.descriptionVector ?? null,
260
+ metadata: params.context.metadata ?? null,
261
+ scoreImpact: params.context.scoreImpact ?? null,
262
+ scoreUrgency: params.context.scoreUrgency ?? null,
263
+ tags: params.context.tags ?? [],
264
+ title: params.context.title ?? null,
265
+ type: params.context.type ?? null,
266
+ userId: this.userId,
267
+ userMemoryIds: [memory.id],
268
+ } satisfies typeof userMemoriesContexts.$inferInsert;
269
+
270
+ const [context] = await tx.insert(userMemoriesContexts).values(contextValues).returning();
271
+
272
+ return { context, memory };
273
+ });
274
+ };
275
+
276
+ createExperienceMemory = async (
277
+ params: CreateUserMemoryExperienceParams,
278
+ ): Promise<{ experience: UserMemoryExperience; memory: UserMemoryItem }> => {
279
+ return this.db.transaction(async (tx) => {
280
+ const baseValues = this.buildBaseMemoryInsertValues(params, {
281
+ metadata: params.experience.metadata ?? null,
282
+ tags: params.experience.tags ?? null,
283
+ });
284
+
285
+ const [memory] = await tx.insert(userMemories).values(baseValues).returning();
286
+ if (!memory) throw new Error('Failed to create user memory experience');
287
+
288
+ const experienceValues = {
289
+ action: params.experience.action ?? null,
290
+ actionVector: params.experience.actionVector ?? null,
291
+ keyLearning: params.experience.keyLearning ?? null,
292
+ keyLearningVector: params.experience.keyLearningVector ?? null,
293
+ metadata: params.experience.metadata ?? null,
294
+ possibleOutcome: params.experience.possibleOutcome ?? null,
295
+ reasoning: params.experience.reasoning ?? null,
296
+ scoreConfidence: params.experience.scoreConfidence ?? null,
297
+ situation: params.experience.situation ?? null,
298
+ situationVector: params.experience.situationVector ?? null,
299
+ tags: params.experience.tags ?? [],
300
+ type: params.experience.type ?? params.memoryType ?? null,
301
+ userId: this.userId,
302
+ userMemoryId: memory.id,
303
+ } satisfies typeof userMemoriesExperiences.$inferInsert;
304
+
305
+ const [experience] = await tx
306
+ .insert(userMemoriesExperiences)
307
+ .values(experienceValues)
308
+ .returning();
309
+
310
+ return { experience, memory };
311
+ });
312
+ };
313
+
314
+ createPreferenceMemory = async (
315
+ params: CreateUserMemoryPreferenceParams,
316
+ ): Promise<{ memory: UserMemoryItem; preference: UserMemoryPreference }> => {
317
+ return this.db.transaction(async (tx) => {
318
+ const baseValues = this.buildBaseMemoryInsertValues(params, {
319
+ metadata: params.preference.metadata ?? null,
320
+ tags: params.preference.tags ?? null,
321
+ });
322
+
323
+ const [memory] = await tx.insert(userMemories).values(baseValues).returning();
324
+ if (!memory) throw new Error('Failed to create user memory preference');
325
+
326
+ const preferenceValues = {
327
+ conclusionDirectives: params.preference.conclusionDirectives ?? null,
328
+ conclusionDirectivesVector: params.preference.conclusionDirectivesVector ?? null,
329
+ metadata: params.preference.metadata ?? null,
330
+ scorePriority: params.preference.scorePriority ?? null,
331
+ suggestions: params.preference.suggestions ?? null,
332
+ tags: params.preference.tags ?? [],
333
+ type: params.preference.type ?? params.memoryType ?? null,
334
+ userId: this.userId,
335
+ userMemoryId: memory.id,
336
+ } satisfies typeof userMemoriesPreferences.$inferInsert;
337
+
338
+ const [preference] = await tx
339
+ .insert(userMemoriesPreferences)
340
+ .values(preferenceValues)
341
+ .returning();
342
+
343
+ return { memory, preference };
344
+ });
345
+ };
346
+
347
+ search = async (params: SearchUserMemoryParams): Promise<UserMemorySearchAggregatedResult> => {
348
+ const { embedding, limits } = params;
349
+
350
+ const resolvedLimits = {
351
+ contexts: limits?.contexts,
352
+ experiences: limits?.experiences,
353
+ preferences: limits?.preferences,
354
+ };
355
+
356
+ const [experiences, contexts, preferences] = await Promise.all([
357
+ this.searchExperiences({
358
+ embedding,
359
+ limit: resolvedLimits.experiences,
360
+ }),
361
+ this.searchContexts({
362
+ embedding,
363
+ limit: resolvedLimits.contexts,
364
+ }),
365
+ this.searchPreferences({
366
+ embedding,
367
+ limit: resolvedLimits.preferences,
368
+ }),
369
+ ]);
370
+
371
+ const accessedMemoryIds = new Set<string>();
372
+ experiences.forEach((experience) => {
373
+ if (experience.userMemoryId) accessedMemoryIds.add(experience.userMemoryId);
374
+ });
375
+ preferences.forEach((preference) => {
376
+ if (preference.userMemoryId) accessedMemoryIds.add(preference.userMemoryId);
377
+ });
378
+ const contextLinkIds: string[] = [];
379
+ contexts.forEach((context) => {
380
+ const ids = Array.isArray(context.userMemoryIds) ? (context.userMemoryIds as string[]) : [];
381
+ ids.forEach((id) => accessedMemoryIds.add(id));
382
+ contextLinkIds.push(context.id);
383
+ });
384
+
385
+ if (accessedMemoryIds.size > 0 || contextLinkIds.length > 0) {
386
+ await this.updateAccessMetrics([...accessedMemoryIds], {
387
+ contextIds: contextLinkIds,
388
+ timestamp: new Date(),
389
+ });
390
+ }
391
+
392
+ return {
393
+ contexts,
394
+ experiences,
395
+ preferences,
396
+ };
397
+ };
398
+
399
+ searchWithEmbedding = async (
400
+ params: SearchUserMemoryWithEmbeddingParams,
401
+ ): Promise<UserMemorySearchAggregatedResult> => {
402
+ return this.search(params);
403
+ };
404
+
405
+ findById = async (id: string): Promise<UserMemoryItem | undefined> => {
406
+ const result = await this.db.query.userMemories.findFirst({
407
+ where: and(eq(userMemories.id, id), eq(userMemories.userId, this.userId)),
408
+ });
409
+
410
+ if (result) {
411
+ await this.updateAccessMetrics([id]);
412
+ }
413
+
414
+ return result;
415
+ };
416
+
417
+ update = async (id: string, params: Partial<CreateUserMemoryParams>): Promise<void> => {
418
+ await this.db
419
+ .update(userMemories)
420
+ .set({ ...params, updatedAt: new Date() })
421
+ .where(and(eq(userMemories.id, id), eq(userMemories.userId, this.userId)));
422
+ };
423
+
424
+ updateUserMemoryVectors = async (
425
+ id: string,
426
+ vectors: UpdateUserMemoryVectorsParams,
427
+ ): Promise<void> => {
428
+ const vectorUpdates: Partial<typeof userMemories.$inferInsert> = {};
429
+ if (vectors.detailsVector1024 !== undefined) {
430
+ vectorUpdates.detailsVector1024 = vectors.detailsVector1024;
431
+ }
432
+ if (vectors.summaryVector1024 !== undefined) {
433
+ vectorUpdates.summaryVector1024 = vectors.summaryVector1024;
434
+ }
435
+
436
+ if (Object.keys(vectorUpdates).length === 0) {
437
+ return;
438
+ }
439
+
440
+ await this.db
441
+ .update(userMemories)
442
+ .set({
443
+ ...vectorUpdates,
444
+ updatedAt: new Date(),
445
+ })
446
+ .where(and(eq(userMemories.id, id), eq(userMemories.userId, this.userId)));
447
+ };
448
+
449
+ updateContextVectors = async (id: string, vectors: UpdateContextVectorsParams): Promise<void> => {
450
+ const vectorUpdates: Partial<typeof userMemoriesContexts.$inferInsert> = {};
451
+ if (vectors.descriptionVector !== undefined) {
452
+ vectorUpdates.descriptionVector = vectors.descriptionVector;
453
+ }
454
+ if (Object.keys(vectorUpdates).length === 0) {
455
+ return;
456
+ }
457
+
458
+ await this.db
459
+ .update(userMemoriesContexts)
460
+ .set({
461
+ ...vectorUpdates,
462
+ updatedAt: new Date(),
463
+ })
464
+ .where(and(eq(userMemoriesContexts.id, id), eq(userMemoriesContexts.userId, this.userId)));
465
+ };
466
+
467
+ updatePreferenceVectors = async (
468
+ id: string,
469
+ vectors: UpdatePreferenceVectorsParams,
470
+ ): Promise<void> => {
471
+ const vectorUpdates: Partial<typeof userMemoriesPreferences.$inferInsert> = {};
472
+ if (vectors.conclusionDirectivesVector !== undefined) {
473
+ vectorUpdates.conclusionDirectivesVector = vectors.conclusionDirectivesVector;
474
+ }
475
+
476
+ if (Object.keys(vectorUpdates).length === 0) {
477
+ return;
478
+ }
479
+
480
+ await this.db
481
+ .update(userMemoriesPreferences)
482
+ .set({
483
+ ...vectorUpdates,
484
+ updatedAt: new Date(),
485
+ })
486
+ .where(
487
+ and(eq(userMemoriesPreferences.id, id), eq(userMemoriesPreferences.userId, this.userId)),
488
+ );
489
+ };
490
+
491
+ updateIdentityVectors = async (
492
+ id: string,
493
+ vectors: UpdateIdentityVectorsParams,
494
+ ): Promise<void> => {
495
+ const vectorUpdates: Partial<typeof userMemoriesIdentities.$inferInsert> = {};
496
+ if (vectors.descriptionVector !== undefined) {
497
+ vectorUpdates.descriptionVector = vectors.descriptionVector;
498
+ }
499
+
500
+ if (Object.keys(vectorUpdates).length === 0) {
501
+ return;
502
+ }
503
+
504
+ await this.db
505
+ .update(userMemoriesIdentities)
506
+ .set({
507
+ ...vectorUpdates,
508
+ updatedAt: new Date(),
509
+ })
510
+ .where(
511
+ and(eq(userMemoriesIdentities.id, id), eq(userMemoriesIdentities.userId, this.userId)),
512
+ );
513
+ };
514
+
515
+ updateExperienceVectors = async (
516
+ id: string,
517
+ vectors: UpdateExperienceVectorsParams,
518
+ ): Promise<void> => {
519
+ const vectorUpdates: Partial<typeof userMemoriesExperiences.$inferInsert> = {};
520
+ if (vectors.actionVector !== undefined) {
521
+ vectorUpdates.actionVector = vectors.actionVector;
522
+ }
523
+ if (vectors.keyLearningVector !== undefined) {
524
+ vectorUpdates.keyLearningVector = vectors.keyLearningVector;
525
+ }
526
+ if (vectors.situationVector !== undefined) {
527
+ vectorUpdates.situationVector = vectors.situationVector;
528
+ }
529
+
530
+ if (Object.keys(vectorUpdates).length === 0) {
531
+ return;
532
+ }
533
+
534
+ await this.db
535
+ .update(userMemoriesExperiences)
536
+ .set({
537
+ ...vectorUpdates,
538
+ updatedAt: new Date(),
539
+ })
540
+ .where(
541
+ and(eq(userMemoriesExperiences.id, id), eq(userMemoriesExperiences.userId, this.userId)),
542
+ );
543
+ };
544
+
545
+ addIdentityEntry = async (params: AddIdentityEntryParams): Promise<AddIdentityEntryResult> => {
546
+ const now = new Date();
547
+
548
+ return this.db.transaction(async (tx) => {
549
+ const base = params.base ?? {};
550
+ const baseValues: typeof userMemories.$inferInsert = {
551
+ accessedCount: 0,
552
+ details: base.details ?? null,
553
+ detailsVector1024: base.detailsVector1024 ?? null,
554
+ lastAccessedAt: coerceDate(base.lastAccessedAt) ?? now,
555
+ memoryCategory: base.memoryCategory ?? null,
556
+ memoryLayer: base.memoryLayer ?? 'identity',
557
+ memoryType: base.memoryType ?? null,
558
+ metadata: base.metadata ?? null,
559
+ status: base.status === undefined ? 'active' : base.status,
560
+ summary: base.summary ?? null,
561
+ summaryVector1024: base.summaryVector1024 ?? null,
562
+ tags: base.tags ?? null,
563
+ title: base.title ?? null,
564
+ userId: this.userId,
565
+ };
566
+
567
+ const [userMemoryRecord] = await tx
568
+ .insert(userMemories)
569
+ .values(baseValues)
570
+ .returning({ id: userMemories.id });
571
+
572
+ const identity = params.identity ?? {};
573
+ const identityValues: typeof userMemoriesIdentities.$inferInsert = {
574
+ description: identity.description ?? null,
575
+ descriptionVector: identity.descriptionVector ?? null,
576
+ episodicDate:
577
+ identity.episodicDate === undefined ? null : coerceDate(identity.episodicDate),
578
+ metadata: identity.metadata ?? null,
579
+ relationship:
580
+ identity.relationship === undefined
581
+ ? null
582
+ : identity.relationship === null
583
+ ? null
584
+ : (normalizeRelationshipValue(identity.relationship) ?? null),
585
+ role: identity.role ?? null,
586
+ tags: identity.tags ?? null,
587
+ type:
588
+ identity.type === undefined
589
+ ? null
590
+ : identity.type === null
591
+ ? null
592
+ : (normalizeIdentityTypeValue(identity.type) ?? null),
593
+ userId: this.userId,
594
+ userMemoryId: userMemoryRecord.id,
595
+ };
596
+
597
+ const [identityRecord] = await tx
598
+ .insert(userMemoriesIdentities)
599
+ .values(identityValues)
600
+ .returning({ id: userMemoriesIdentities.id });
601
+
602
+ return {
603
+ identityId: identityRecord.id,
604
+ userMemoryId: userMemoryRecord.id,
605
+ };
606
+ });
607
+ };
608
+
609
+ updateIdentityEntry = async (params: UpdateIdentityEntryParams): Promise<boolean> => {
610
+ const mergeStrategy = params.mergeStrategy ?? 'merge';
611
+
612
+ return this.db.transaction(async (tx) => {
613
+ const identity = await tx.query.userMemoriesIdentities.findFirst({
614
+ where: and(
615
+ eq(userMemoriesIdentities.id, params.identityId),
616
+ eq(userMemoriesIdentities.userId, this.userId),
617
+ ),
618
+ });
619
+ if (!identity || !identity.userMemoryId) {
620
+ return false;
621
+ }
622
+
623
+ let baseUpdate: Partial<typeof userMemories.$inferInsert> = {};
624
+ let identityUpdate: Partial<typeof userMemoriesIdentities.$inferInsert> = {};
625
+
626
+ if (params.base) {
627
+ baseUpdate = merge(baseUpdate, params.base);
628
+ if (baseUpdate.lastAccessedAt !== undefined) {
629
+ baseUpdate.lastAccessedAt = coerceDate(baseUpdate.lastAccessedAt) ?? new Date();
630
+ }
631
+
632
+ if (Object.keys(baseUpdate).length > 0) {
633
+ baseUpdate.updatedAt = new Date();
634
+ await tx
635
+ .update(userMemories)
636
+ .set(baseUpdate)
637
+ .where(
638
+ and(eq(userMemories.id, identity.userMemoryId), eq(userMemories.userId, this.userId)),
639
+ );
640
+ }
641
+ }
642
+
643
+ if (params.identity) {
644
+ if (mergeStrategy === 'replace') {
645
+ const identity = params.identity;
646
+
647
+ identityUpdate = {
648
+ description: identity.description ?? null,
649
+ descriptionVector: identity.descriptionVector ?? null,
650
+ episodicDate:
651
+ identity.episodicDate === undefined ? null : coerceDate(identity.episodicDate),
652
+ metadata: identity.metadata ?? null,
653
+ relationship:
654
+ identity.relationship === undefined
655
+ ? null
656
+ : identity.relationship === null
657
+ ? null
658
+ : (normalizeRelationshipValue(identity.relationship) ?? null),
659
+ role: identity.role ?? null,
660
+ tags: identity.tags ?? null,
661
+ type:
662
+ identity.type === undefined
663
+ ? null
664
+ : identity.type === null
665
+ ? null
666
+ : (normalizeIdentityTypeValue(identity.type) ?? null),
667
+ };
668
+ } else {
669
+ identityUpdate = merge(identityUpdate, params.identity);
670
+
671
+ if (params.identity.type !== undefined) {
672
+ identityUpdate.type =
673
+ params.identity.type === null
674
+ ? null
675
+ : (normalizeIdentityTypeValue(params.identity.type) ?? null);
676
+ }
677
+
678
+ if (params.identity.relationship !== undefined) {
679
+ identityUpdate.relationship =
680
+ params.identity.relationship === null
681
+ ? null
682
+ : (normalizeRelationshipValue(params.identity.relationship) ?? null);
683
+ }
684
+
685
+ if (params.identity.episodicDate !== undefined) {
686
+ identityUpdate.episodicDate = coerceDate(params.identity.episodicDate);
687
+ }
688
+ }
689
+
690
+ if (Object.keys(identityUpdate).length > 0) {
691
+ identityUpdate.updatedAt = new Date();
692
+ await tx
693
+ .update(userMemoriesIdentities)
694
+ .set(identityUpdate)
695
+ .where(
696
+ and(
697
+ eq(userMemoriesIdentities.id, params.identityId),
698
+ eq(userMemoriesIdentities.userId, this.userId),
699
+ ),
700
+ );
701
+ }
702
+ }
703
+
704
+ return true;
705
+ });
706
+ };
707
+
708
+ removeIdentityEntry = async (identityId: string): Promise<boolean> => {
709
+ return this.db.transaction(async (tx) => {
710
+ const identity = await tx.query.userMemoriesIdentities.findFirst({
711
+ where: and(
712
+ eq(userMemoriesIdentities.id, identityId),
713
+ eq(userMemoriesIdentities.userId, this.userId),
714
+ ),
715
+ });
716
+
717
+ if (!identity || !identity.userMemoryId) {
718
+ return false;
719
+ }
720
+
721
+ await tx
722
+ .delete(userMemories)
723
+ .where(
724
+ and(eq(userMemories.id, identity.userMemoryId), eq(userMemories.userId, this.userId)),
725
+ );
726
+
727
+ return true;
728
+ });
729
+ };
730
+
731
+ delete = async (id: string): Promise<void> => {
732
+ await this.db
733
+ .delete(userMemories)
734
+ .where(and(eq(userMemories.id, id), eq(userMemories.userId, this.userId)));
735
+ };
736
+
737
+ deleteAll = async (): Promise<void> => {
738
+ await this.db.delete(userMemories).where(eq(userMemories.userId, this.userId));
739
+ };
740
+
741
+ searchContexts = async (params: {
742
+ embedding?: number[];
743
+ limit?: number;
744
+ type?: string;
745
+ }): Promise<UserMemoryContextsWithoutVectors[]> => {
746
+ const { embedding, limit = 5, type } = params;
747
+ if (limit <= 0) {
748
+ return [];
749
+ }
750
+
751
+ let query = this.db
752
+ .select({
753
+ accessedAt: userMemoriesContexts.accessedAt,
754
+ associatedObjects: userMemoriesContexts.associatedObjects,
755
+ associatedSubjects: userMemoriesContexts.associatedSubjects,
756
+ createdAt: userMemoriesContexts.createdAt,
757
+ currentStatus: userMemoriesContexts.currentStatus,
758
+ description: userMemoriesContexts.description,
759
+ id: userMemoriesContexts.id,
760
+ metadata: userMemoriesContexts.metadata,
761
+ scoreImpact: userMemoriesContexts.scoreImpact,
762
+ scoreUrgency: userMemoriesContexts.scoreUrgency,
763
+ tags: userMemoriesContexts.tags,
764
+ title: userMemoriesContexts.title,
765
+ type: userMemoriesContexts.type,
766
+ updatedAt: userMemoriesContexts.updatedAt,
767
+ userId: userMemoriesContexts.userId,
768
+ userMemoryIds: userMemoriesContexts.userMemoryIds,
769
+ ...(embedding && {
770
+ similarity: sql<number>`1 - (${cosineDistance(userMemoriesContexts.descriptionVector, embedding)}) AS similarity`,
771
+ }),
772
+ })
773
+ .from(userMemoriesContexts)
774
+ .$dynamic();
775
+
776
+ const conditions = [eq(userMemoriesContexts.userId, this.userId)];
777
+ if (type) {
778
+ conditions.push(eq(userMemoriesContexts.type, type));
779
+ }
780
+
781
+ query = query.where(and(...conditions));
782
+
783
+ if (embedding) {
784
+ query = query.orderBy(desc(sql`similarity`));
785
+ } else {
786
+ query = query.orderBy(desc(userMemoriesContexts.createdAt));
787
+ }
788
+
789
+ return query.limit(limit);
790
+ };
791
+
792
+ searchExperiences = async (params: {
793
+ embedding?: number[];
794
+ limit?: number;
795
+ type?: string;
796
+ }): Promise<UserMemoryExperiencesWithoutVectors[]> => {
797
+ const { embedding, limit = 5, type } = params;
798
+ if (limit <= 0) {
799
+ return [];
800
+ }
801
+
802
+ let query = this.db
803
+ .select({
804
+ accessedAt: userMemoriesExperiences.accessedAt,
805
+ action: userMemoriesExperiences.action,
806
+ createdAt: userMemoriesExperiences.createdAt,
807
+ id: userMemoriesExperiences.id,
808
+ keyLearning: userMemoriesExperiences.keyLearning,
809
+ metadata: userMemoriesExperiences.metadata,
810
+ possibleOutcome: userMemoriesExperiences.possibleOutcome,
811
+ reasoning: userMemoriesExperiences.reasoning,
812
+ scoreConfidence: userMemoriesExperiences.scoreConfidence,
813
+ situation: userMemoriesExperiences.situation,
814
+ tags: userMemoriesExperiences.tags,
815
+ type: userMemoriesExperiences.type,
816
+ updatedAt: userMemoriesExperiences.updatedAt,
817
+ userId: userMemoriesExperiences.userId,
818
+ userMemoryId: userMemoriesExperiences.userMemoryId,
819
+ ...(embedding && {
820
+ similarity: sql<number>`1 - (${cosineDistance(userMemoriesExperiences.situationVector, embedding)}) AS similarity`,
821
+ }),
822
+ })
823
+ .from(userMemoriesExperiences)
824
+ .$dynamic();
825
+
826
+ const conditions = [eq(userMemoriesExperiences.userId, this.userId)];
827
+ if (type) {
828
+ conditions.push(eq(userMemoriesExperiences.type, type));
829
+ }
830
+
831
+ query = query.where(and(...conditions));
832
+
833
+ if (embedding) {
834
+ query = query.orderBy(desc(sql`similarity`));
835
+ } else {
836
+ query = query.orderBy(desc(userMemoriesExperiences.createdAt));
837
+ }
838
+
839
+ return query.limit(limit);
840
+ };
841
+
842
+ searchPreferences = async (params: {
843
+ embedding?: number[];
844
+ limit?: number;
845
+ type?: string;
846
+ }): Promise<UserMemoryPreferencesWithoutVectors[]> => {
847
+ const { embedding, limit = 5, type } = params;
848
+ if (limit <= 0) {
849
+ return [];
850
+ }
851
+
852
+ let query = this.db
853
+ .select({
854
+ accessedAt: userMemoriesPreferences.accessedAt,
855
+ conclusionDirectives: userMemoriesPreferences.conclusionDirectives,
856
+ createdAt: userMemoriesPreferences.createdAt,
857
+ id: userMemoriesPreferences.id,
858
+ metadata: userMemoriesPreferences.metadata,
859
+ scorePriority: userMemoriesPreferences.scorePriority,
860
+ suggestions: userMemoriesPreferences.suggestions,
861
+ tags: userMemoriesPreferences.tags,
862
+ type: userMemoriesPreferences.type,
863
+ updatedAt: userMemoriesPreferences.updatedAt,
864
+ userId: userMemoriesPreferences.userId,
865
+ userMemoryId: userMemoriesPreferences.userMemoryId,
866
+ ...(embedding && {
867
+ similarity: sql<number>`1 - (${cosineDistance(userMemoriesPreferences.conclusionDirectivesVector, embedding)}) AS similarity`,
868
+ }),
869
+ })
870
+ .from(userMemoriesPreferences)
871
+ .$dynamic();
872
+
873
+ const conditions = [eq(userMemoriesPreferences.userId, this.userId)];
874
+ if (type) {
875
+ conditions.push(eq(userMemoriesPreferences.type, type));
876
+ }
877
+
878
+ query = query.where(and(...conditions));
879
+
880
+ if (embedding) {
881
+ query = query.orderBy(desc(sql`similarity`));
882
+ } else {
883
+ query = query.orderBy(desc(userMemoriesPreferences.createdAt));
884
+ }
885
+
886
+ return query.limit(limit);
887
+ };
888
+
889
+ getAllIdentities = async (): Promise<UserMemoryIdentitiesWithoutVectors[]> => {
890
+ return this.db.query.userMemoriesIdentities.findMany({
891
+ orderBy: [desc(userMemoriesIdentities.createdAt)],
892
+ where: eq(userMemoriesIdentities.userId, this.userId),
893
+ });
894
+ };
895
+
896
+ getIdentitiesByType = async (type: string): Promise<UserMemoryIdentitiesWithoutVectors[]> => {
897
+ return this.db.query.userMemoriesIdentities.findMany({
898
+ orderBy: [desc(userMemoriesIdentities.createdAt)],
899
+ where: and(
900
+ eq(userMemoriesIdentities.userId, this.userId),
901
+ eq(userMemoriesIdentities.type, type),
902
+ ),
903
+ });
904
+ };
905
+
906
+ private updateAccessMetrics = async (
907
+ memoryIds: string[],
908
+ options?: { contextIds?: string[]; timestamp?: Date },
909
+ ): Promise<void> => {
910
+ const contextIds = options?.contextIds ?? [];
911
+ if (memoryIds.length === 0 && contextIds.length === 0) return;
912
+
913
+ const now = options?.timestamp ?? new Date();
914
+
915
+ await this.db.transaction(async (tx) => {
916
+ if (memoryIds.length > 0) {
917
+ await tx
918
+ .update(userMemories)
919
+ .set({
920
+ accessedAt: now,
921
+ accessedCount: sql`${userMemories.accessedCount} + 1`,
922
+ lastAccessedAt: now,
923
+ })
924
+ .where(and(eq(userMemories.userId, this.userId), inArray(userMemories.id, memoryIds)));
925
+
926
+ const memories = await tx
927
+ .select({
928
+ id: userMemories.id,
929
+ layer: userMemories.memoryLayer,
930
+ })
931
+ .from(userMemories)
932
+ .where(and(eq(userMemories.userId, this.userId), inArray(userMemories.id, memoryIds)));
933
+
934
+ const experienceIds = memories
935
+ .filter((memory) => memory.layer === 'experience')
936
+ .map((memory) => memory.id);
937
+ if (experienceIds.length > 0) {
938
+ await tx
939
+ .update(userMemoriesExperiences)
940
+ .set({ accessedAt: now })
941
+ .where(
942
+ and(
943
+ eq(userMemoriesExperiences.userId, this.userId),
944
+ inArray(userMemoriesExperiences.userMemoryId, experienceIds),
945
+ ),
946
+ );
947
+ }
948
+
949
+ const identityIds = memories
950
+ .filter((memory) => memory.layer === 'identity')
951
+ .map((memory) => memory.id);
952
+ if (identityIds.length > 0) {
953
+ await tx
954
+ .update(userMemoriesIdentities)
955
+ .set({ accessedAt: now })
956
+ .where(
957
+ and(
958
+ eq(userMemoriesIdentities.userId, this.userId),
959
+ inArray(userMemoriesIdentities.userMemoryId, identityIds),
960
+ ),
961
+ );
962
+ }
963
+
964
+ const preferenceIds = memories
965
+ .filter((memory) => memory.layer === 'preference')
966
+ .map((memory) => memory.id);
967
+ if (preferenceIds.length > 0) {
968
+ await tx
969
+ .update(userMemoriesPreferences)
970
+ .set({ accessedAt: now })
971
+ .where(
972
+ and(
973
+ eq(userMemoriesPreferences.userId, this.userId),
974
+ inArray(userMemoriesPreferences.userMemoryId, preferenceIds),
975
+ ),
976
+ );
977
+ }
978
+ }
979
+
980
+ if (contextIds.length > 0) {
981
+ await tx
982
+ .update(userMemoriesContexts)
983
+ .set({ accessedAt: now })
984
+ .where(
985
+ and(
986
+ eq(userMemoriesContexts.userId, this.userId),
987
+ inArray(userMemoriesContexts.id, contextIds),
988
+ ),
989
+ );
990
+ }
991
+ });
992
+ };
993
+ }