@anuma/sdk 2.0.1 → 2.2.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/expo/{chunk-N4FEY34G.mjs → chunk-7PAISHUN.mjs} +134 -3
- package/dist/expo/index.cjs +341 -19
- package/dist/expo/index.d.mts +176 -2
- package/dist/expo/index.d.ts +176 -2
- package/dist/expo/index.mjs +208 -19
- package/dist/expo/{recall-DHOR5WWW.mjs → recall-62ZPSDHR.mjs} +1 -1
- package/dist/react/{chunk-63IW5BS5.mjs → chunk-KRNJVLI6.mjs} +135 -3
- package/dist/react/index.cjs +361 -26
- package/dist/react/index.d.mts +216 -6
- package/dist/react/index.d.ts +216 -6
- package/dist/react/index.mjs +226 -26
- package/dist/react/{recall-PUXEQHAP.mjs → recall-ASTJPM5Z.mjs} +1 -1
- package/dist/server/{chunk-45GOZ3AB.mjs → chunk-KEMPJE6C.mjs} +134 -3
- package/dist/server/index.cjs +341 -19
- package/dist/server/index.d.mts +176 -2
- package/dist/server/index.d.ts +176 -2
- package/dist/server/index.mjs +208 -19
- package/dist/server/{recall-RMBNCEG6.mjs → recall-K2YHLBBV.mjs} +1 -1
- package/package.json +1 -1
package/dist/expo/index.cjs
CHANGED
|
@@ -3904,14 +3904,19 @@ async function upsertEntitiesOp(ctx, entities) {
|
|
|
3904
3904
|
return out;
|
|
3905
3905
|
});
|
|
3906
3906
|
}
|
|
3907
|
-
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs) {
|
|
3907
|
+
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
3908
3908
|
if (entityInputs.length === 0) return [];
|
|
3909
3909
|
const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
3910
3910
|
const byName = await upsertEntitiesOp(ctx, normalized);
|
|
3911
3911
|
const entities = Array.from(byName.values());
|
|
3912
3912
|
if (entities.length === 0) return [];
|
|
3913
3913
|
const userId = ctx.userId;
|
|
3914
|
+
let skipped = false;
|
|
3914
3915
|
await ctx.database.write(async () => {
|
|
3916
|
+
if (options?.unlessTopicsUserManaged && await autoLinkBlocked(ctx, memoryId)) {
|
|
3917
|
+
skipped = true;
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3915
3920
|
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb8.Q.where("memory_id", memoryId)).fetch();
|
|
3916
3921
|
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
3917
3922
|
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
@@ -3925,7 +3930,46 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs) {
|
|
|
3925
3930
|
);
|
|
3926
3931
|
await ctx.database.batch(...prepared);
|
|
3927
3932
|
});
|
|
3928
|
-
return entities;
|
|
3933
|
+
return skipped ? [] : entities;
|
|
3934
|
+
}
|
|
3935
|
+
async function autoLinkBlocked(ctx, memoryId) {
|
|
3936
|
+
try {
|
|
3937
|
+
const rows = await ctx.database.get("memory_vault").query(import_watermelondb8.Q.where("id", memoryId)).fetch();
|
|
3938
|
+
const row = rows[0];
|
|
3939
|
+
return !row || !!row.isDeleted || !!row.topicsUserManaged;
|
|
3940
|
+
} catch {
|
|
3941
|
+
return true;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
3945
|
+
const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
3946
|
+
const byName = await upsertEntitiesOp(ctx, normalized);
|
|
3947
|
+
const entities = Array.from(byName.values());
|
|
3948
|
+
const userId = ctx.userId;
|
|
3949
|
+
let skipped = false;
|
|
3950
|
+
await ctx.database.write(async () => {
|
|
3951
|
+
if (await autoLinkBlocked(ctx, memoryId)) {
|
|
3952
|
+
skipped = true;
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb8.Q.where("memory_id", memoryId)).fetch();
|
|
3956
|
+
const keep = new Set(entities.map((e) => e.uniqueId));
|
|
3957
|
+
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
3958
|
+
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
3959
|
+
const toDestroy = existingLinks.filter((l) => !keep.has(String(l.entityId)));
|
|
3960
|
+
if (toCreate.length === 0 && toDestroy.length === 0) return;
|
|
3961
|
+
await ctx.database.batch(
|
|
3962
|
+
...toCreate.map(
|
|
3963
|
+
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
3964
|
+
record._setRaw("memory_id", memoryId);
|
|
3965
|
+
record._setRaw("entity_id", e.uniqueId);
|
|
3966
|
+
if (userId !== void 0) record._setRaw("user_id", userId);
|
|
3967
|
+
})
|
|
3968
|
+
),
|
|
3969
|
+
...toDestroy.map((l) => l.prepareDestroyPermanently())
|
|
3970
|
+
);
|
|
3971
|
+
});
|
|
3972
|
+
return skipped ? null : entities;
|
|
3929
3973
|
}
|
|
3930
3974
|
async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
|
|
3931
3975
|
if (memoryIds.length === 0) return;
|
|
@@ -4061,6 +4105,7 @@ function vaultMemoryToStoredRaw(memory) {
|
|
|
4061
4105
|
eventTimeEnd: memory.eventTimeEnd ?? null,
|
|
4062
4106
|
eventTimeKind: memory.eventTimeKind ?? null,
|
|
4063
4107
|
topicsUserManaged: memory.topicsUserManaged ?? false,
|
|
4108
|
+
topicsExtractedAt: memory.topicsExtractedAt ?? null,
|
|
4064
4109
|
createdAt: memory.createdAt,
|
|
4065
4110
|
updatedAt: memory.updatedAt,
|
|
4066
4111
|
isDeleted: memory.isDeleted
|
|
@@ -4244,6 +4289,7 @@ function vaultMemoryRawToStoredRaw(raw) {
|
|
|
4244
4289
|
eventTimeKind: raw.event_time_kind ?? null,
|
|
4245
4290
|
// SQLite stores booleans as 0/1, LokiJS as true/false — coerce both.
|
|
4246
4291
|
topicsUserManaged: raw.topics_user_managed === true || raw.topics_user_managed === 1,
|
|
4292
|
+
topicsExtractedAt: raw.topics_extracted_at ?? null,
|
|
4247
4293
|
createdAt: new Date(raw.created_at),
|
|
4248
4294
|
updatedAt: new Date(raw.updated_at),
|
|
4249
4295
|
isDeleted: raw.is_deleted === true || raw.is_deleted === 1
|
|
@@ -4386,6 +4432,87 @@ async function getUnfiledVaultMemoriesOp(ctx) {
|
|
|
4386
4432
|
(record) => vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
4387
4433
|
);
|
|
4388
4434
|
}
|
|
4435
|
+
async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
|
|
4436
|
+
const entityCtx = ctx.entityCtx;
|
|
4437
|
+
if (!entityCtx) {
|
|
4438
|
+
throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
|
|
4439
|
+
}
|
|
4440
|
+
const conditions = [
|
|
4441
|
+
// Boolean optional column: null (legacy) and false both mean auto-managed.
|
|
4442
|
+
// Q.notEq(true) is a NULL trap on SQLite (NULL <> TRUE is NULL) — enumerate.
|
|
4443
|
+
import_watermelondb9.Q.or(import_watermelondb9.Q.where("topics_user_managed", null), import_watermelondb9.Q.where("topics_user_managed", false)),
|
|
4444
|
+
...baseVaultConditions(ctx),
|
|
4445
|
+
import_watermelondb9.Q.sortBy("created_at", import_watermelondb9.Q.desc)
|
|
4446
|
+
];
|
|
4447
|
+
const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
|
|
4448
|
+
const stampedPending = [];
|
|
4449
|
+
const unstamped = [];
|
|
4450
|
+
for (const raw of rows) {
|
|
4451
|
+
const stamp = raw.topics_extracted_at ?? null;
|
|
4452
|
+
if (stamp !== null) {
|
|
4453
|
+
if (raw.updated_at > stamp) stampedPending.push(raw);
|
|
4454
|
+
} else {
|
|
4455
|
+
unstamped.push(raw);
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
const linkedIds = /* @__PURE__ */ new Set();
|
|
4459
|
+
const CHUNK = 500;
|
|
4460
|
+
for (let i = 0; i < unstamped.length; i += CHUNK) {
|
|
4461
|
+
const ids = unstamped.slice(i, i + CHUNK).map((r) => r.id);
|
|
4462
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb9.Q.where("memory_id", import_watermelondb9.Q.oneOf(ids))).unsafeFetchRaw();
|
|
4463
|
+
for (const link of links) linkedIds.add(String(link.memory_id));
|
|
4464
|
+
}
|
|
4465
|
+
const linkedUnstampedAll = [];
|
|
4466
|
+
const pendingRaw = [...stampedPending];
|
|
4467
|
+
for (const raw of unstamped) {
|
|
4468
|
+
if (linkedIds.has(raw.id)) {
|
|
4469
|
+
linkedUnstampedAll.push(raw.id);
|
|
4470
|
+
} else {
|
|
4471
|
+
pendingRaw.push(raw);
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
|
|
4475
|
+
const limitedPendingRaw = cap !== void 0 ? pendingRaw.slice(0, cap) : pendingRaw;
|
|
4476
|
+
const linkedUnstamped = cap !== void 0 ? linkedUnstampedAll.slice(0, cap) : linkedUnstampedAll;
|
|
4477
|
+
const pending = await mapInBatches(
|
|
4478
|
+
limitedPendingRaw,
|
|
4479
|
+
(raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
4480
|
+
);
|
|
4481
|
+
return { pending, linkedUnstamped };
|
|
4482
|
+
}
|
|
4483
|
+
async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt) {
|
|
4484
|
+
if (memoryIds.length === 0) return [];
|
|
4485
|
+
const uniqueIds = Array.from(new Set(memoryIds));
|
|
4486
|
+
const CHUNK = 500;
|
|
4487
|
+
const stamped = [];
|
|
4488
|
+
for (let i = 0; i < uniqueIds.length; i += CHUNK) {
|
|
4489
|
+
const chunkIds = uniqueIds.slice(i, i + CHUNK);
|
|
4490
|
+
await ctx.database.write(async () => {
|
|
4491
|
+
const prepared = [];
|
|
4492
|
+
for (const id of chunkIds) {
|
|
4493
|
+
let record;
|
|
4494
|
+
try {
|
|
4495
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
4496
|
+
} catch {
|
|
4497
|
+
continue;
|
|
4498
|
+
}
|
|
4499
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.topicsUserManaged) {
|
|
4500
|
+
continue;
|
|
4501
|
+
}
|
|
4502
|
+
const originalUpdatedAt = record.updatedAt.getTime();
|
|
4503
|
+
prepared.push(
|
|
4504
|
+
record.prepareUpdate((r) => {
|
|
4505
|
+
r._setRaw("topics_extracted_at", extractedAt);
|
|
4506
|
+
r._setRaw("updated_at", originalUpdatedAt);
|
|
4507
|
+
})
|
|
4508
|
+
);
|
|
4509
|
+
stamped.push(id);
|
|
4510
|
+
}
|
|
4511
|
+
if (prepared.length > 0) await ctx.database.batch(...prepared);
|
|
4512
|
+
});
|
|
4513
|
+
}
|
|
4514
|
+
return stamped;
|
|
4515
|
+
}
|
|
4389
4516
|
async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
|
|
4390
4517
|
if (ctx.userId !== void 0 && ctx.userId !== userId) return 0;
|
|
4391
4518
|
const records = await ctx.vaultMemoryCollection.query(import_watermelondb9.Q.where("user_id", userId), import_watermelondb9.Q.where("is_deleted", false)).fetch();
|
|
@@ -6658,6 +6785,7 @@ __export(index_exports, {
|
|
|
6658
6785
|
StoredVaultFolderModel: () => VaultFolder,
|
|
6659
6786
|
StoredVaultMemoryModel: () => VaultMemory,
|
|
6660
6787
|
StreamExpiredError: () => StreamExpiredError,
|
|
6788
|
+
TOPIC_EXTRACTION_BATCH_SIZE: () => TOPIC_EXTRACTION_BATCH_SIZE,
|
|
6661
6789
|
WalletPoller: () => WalletPoller,
|
|
6662
6790
|
addConversationMemoriesOp: () => addConversationMemoriesOp,
|
|
6663
6791
|
buildNotionAuthUrl: () => buildNotionAuthUrl,
|
|
@@ -6709,7 +6837,9 @@ __export(index_exports, {
|
|
|
6709
6837
|
embedMessage: () => embedMessage,
|
|
6710
6838
|
ensureDefaultFoldersOp: () => ensureDefaultFoldersOp,
|
|
6711
6839
|
exchangeNotionCode: () => exchangeNotionCode,
|
|
6840
|
+
extractAndLinkEntitiesForMemoriesOp: () => extractAndLinkEntitiesForMemoriesOp,
|
|
6712
6841
|
extractAndRetain: () => extractAndRetain,
|
|
6842
|
+
extractEntitiesForMemories: () => extractEntitiesForMemories,
|
|
6713
6843
|
extractFacts: () => extractFacts,
|
|
6714
6844
|
generateConversationId: () => generateConversationId,
|
|
6715
6845
|
generateEmbedding: () => generateEmbedding,
|
|
@@ -6723,6 +6853,7 @@ __export(index_exports, {
|
|
|
6723
6853
|
getConversationsByProjectLazyOp: () => getConversationsByProjectLazyOp,
|
|
6724
6854
|
getConversationsLazyOp: () => getConversationsLazyOp,
|
|
6725
6855
|
getLogger: () => getLogger,
|
|
6856
|
+
getMemoriesNeedingTopicExtractionOp: () => getMemoriesNeedingTopicExtractionOp,
|
|
6726
6857
|
getServerTools: () => getServerTools,
|
|
6727
6858
|
getUnfiledVaultMemoriesOp: () => getUnfiledVaultMemoriesOp,
|
|
6728
6859
|
getVaultFolderMemoryCountOp: () => getVaultFolderMemoryCountOp,
|
|
@@ -6749,6 +6880,7 @@ __export(index_exports, {
|
|
|
6749
6880
|
searchVaultMemories: () => searchVaultMemories,
|
|
6750
6881
|
setLogger: () => setLogger,
|
|
6751
6882
|
shouldChunkMessage: () => shouldChunkMessage,
|
|
6883
|
+
stampTopicsExtractedAtOp: () => stampTopicsExtractedAtOp,
|
|
6752
6884
|
streamCancelPath: () => streamCancelPath,
|
|
6753
6885
|
streamReplayPath: () => streamReplayPath,
|
|
6754
6886
|
updateVaultFolderContextOp: () => updateVaultFolderContextOp,
|
|
@@ -12212,6 +12344,9 @@ __decorateClass([
|
|
|
12212
12344
|
__decorateClass([
|
|
12213
12345
|
(0, import_decorators3.field)("topics_user_managed")
|
|
12214
12346
|
], VaultMemory.prototype, "topicsUserManaged", 2);
|
|
12347
|
+
__decorateClass([
|
|
12348
|
+
(0, import_decorators3.field)("topics_extracted_at")
|
|
12349
|
+
], VaultMemory.prototype, "topicsExtractedAt", 2);
|
|
12215
12350
|
__decorateClass([
|
|
12216
12351
|
import_decorators3.readonly,
|
|
12217
12352
|
(0, import_decorators3.date)("created_at")
|
|
@@ -12783,7 +12918,7 @@ async function isMemoryTopicsUserManaged(ctx, memoryId) {
|
|
|
12783
12918
|
return true;
|
|
12784
12919
|
}
|
|
12785
12920
|
}
|
|
12786
|
-
var
|
|
12921
|
+
var DEFAULT_EXTRACTION_MODEL = "gpt-oss/gpt-oss-120b";
|
|
12787
12922
|
var DEFAULT_MIN_CONFIDENCE = 0.7;
|
|
12788
12923
|
var MAX_CONTENT_LENGTH = 200;
|
|
12789
12924
|
var MIN_CONTENT_LENGTH = 3;
|
|
@@ -12796,6 +12931,16 @@ var FACT_TYPES = [
|
|
|
12796
12931
|
"constraint",
|
|
12797
12932
|
"other"
|
|
12798
12933
|
];
|
|
12934
|
+
var ENTITY_KIND_GUIDELINES = `Choose the MOST SPECIFIC kind:
|
|
12935
|
+
- person: a named individual ("Sara", "Dr. Lee")
|
|
12936
|
+
- organization: a company, team, school, or group ("Google", "the Warriors", "Stanford")
|
|
12937
|
+
- place: a geographic location or venue ("San Francisco", "Blue Bottle on Valencia")
|
|
12938
|
+
- event: a dated or nameable occurrence ("Chicago Marathon", "the Japan trip", "their wedding")
|
|
12939
|
+
- product: a named app, tool, brand, device, book, film, or other media title ("Linear", "iPhone", "Dune", "Ableton")
|
|
12940
|
+
- thing: a concrete physical object that isn't a branded product ("road bike", "sourdough starter")
|
|
12941
|
+
- concept: a topic, skill, field, language, or idea ("machine learning", "Spanish", "async communication")
|
|
12942
|
+
- other: a named entity that genuinely fits none of the above \u2014 use sparingly
|
|
12943
|
+
Prefer organization / product / event / place over the generic "thing"; fall back to "thing" or "other" only when no specific kind fits.`;
|
|
12799
12944
|
var SYSTEM_PROMPT3 = `You extract durable user facts from conversations for a personal memory system.
|
|
12800
12945
|
|
|
12801
12946
|
A "durable fact" is something the user shared about themselves that should still be true in future conversations. Examples that ARE durable:
|
|
@@ -12824,16 +12969,7 @@ For each durable fact, output:
|
|
|
12824
12969
|
- type: one of identity | preference | relationship | plan | ongoing_context | constraint | other
|
|
12825
12970
|
- confidence: 0.0-1.0; how sure you are this is durable AND true. Only include facts >= 0.7.
|
|
12826
12971
|
- sourceMessageIds: which message IDs from the conversation contained the evidence
|
|
12827
|
-
- entities: named entities mentioned, each as an object { "name": string, "kind": one of "person" | "organization" | "place" | "event" | "product" | "thing" | "concept" | "other" }. Optional \u2014 include only NAMED entities, skip generic/common nouns.
|
|
12828
|
-
- person: a named individual ("Sara", "Dr. Lee")
|
|
12829
|
-
- organization: a company, team, school, or group ("Google", "the Warriors", "Stanford")
|
|
12830
|
-
- place: a geographic location or venue ("San Francisco", "Blue Bottle on Valencia")
|
|
12831
|
-
- event: a dated or nameable occurrence ("Chicago Marathon", "the Japan trip", "their wedding")
|
|
12832
|
-
- product: a named app, tool, brand, device, book, film, or other media title ("Linear", "iPhone", "Dune", "Ableton")
|
|
12833
|
-
- thing: a concrete physical object that isn't a branded product ("road bike", "sourdough starter")
|
|
12834
|
-
- concept: a topic, skill, field, language, or idea ("machine learning", "Spanish", "async communication")
|
|
12835
|
-
- other: a named entity that genuinely fits none of the above \u2014 use sparingly
|
|
12836
|
-
Prefer organization / product / event / place over the generic "thing"; fall back to "thing" or "other" only when no specific kind fits. Examples:
|
|
12972
|
+
- entities: named entities mentioned, each as an object { "name": string, "kind": one of "person" | "organization" | "place" | "event" | "product" | "thing" | "concept" | "other" }. Optional \u2014 include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES} Examples:
|
|
12837
12973
|
- "started a new job at Hollowpoint Labs and I'm learning Haskell for it" \u2192 entities: [{ "name": "Hollowpoint Labs", "kind": "organization" }, { "name": "Haskell", "kind": "concept" }]
|
|
12838
12974
|
- "running the Berlin Half in May and I track workouts in Whoop" \u2192 entities: [{ "name": "Berlin Half", "kind": "event" }, { "name": "Whoop", "kind": "product" }]
|
|
12839
12975
|
- eventTime: when the event in this fact occurs/occurred. Resolve relative phrases ("yesterday", "next week") against the current date given in the user message. Output one of:
|
|
@@ -12859,7 +12995,7 @@ async function extractFacts(messages, options) {
|
|
|
12859
12995
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
12860
12996
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
12861
12997
|
...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
|
|
12862
|
-
model: options.model ??
|
|
12998
|
+
model: options.model ?? DEFAULT_EXTRACTION_MODEL,
|
|
12863
12999
|
systemPrompt: SYSTEM_PROMPT3,
|
|
12864
13000
|
userMessage: `Today's date is ${today}. Resolve any relative dates ("yesterday", "next week") against it.
|
|
12865
13001
|
|
|
@@ -12953,7 +13089,9 @@ async function extractAndRetain(messages, retainCtx, options) {
|
|
|
12953
13089
|
if (options.entityCtx && candidate.entities.length > 0) {
|
|
12954
13090
|
try {
|
|
12955
13091
|
if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
|
|
12956
|
-
await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities
|
|
13092
|
+
await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
|
|
13093
|
+
unlessTopicsUserManaged: true
|
|
13094
|
+
});
|
|
12957
13095
|
}
|
|
12958
13096
|
} catch (err) {
|
|
12959
13097
|
log.warn("[memory/extract] linkMemoryEntitiesOp failed", err);
|
|
@@ -13358,7 +13496,7 @@ init_logger();
|
|
|
13358
13496
|
init_portalLlm();
|
|
13359
13497
|
init_recall();
|
|
13360
13498
|
var DEFAULT_BASE_URL2 = BASE_URL;
|
|
13361
|
-
var
|
|
13499
|
+
var DEFAULT_MODEL3 = "anthropic/claude-sonnet-4-6";
|
|
13362
13500
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
13363
13501
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
13364
13502
|
var DEFAULT_SYSTEM_PROMPT = `You are a personal assistant with access to the user's memory. Answer the user's question using the supplied memories as evidence.
|
|
@@ -13390,7 +13528,7 @@ async function reflect(query, ctx, options) {
|
|
|
13390
13528
|
const evidence = recalled.memories.map((m, i) => `[${i + 1}] (id: ${m.id}, kind: ${m.kind})
|
|
13391
13529
|
${m.content}`).join("\n\n");
|
|
13392
13530
|
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL2;
|
|
13393
|
-
const model = options.llmModel ??
|
|
13531
|
+
const model = options.llmModel ?? DEFAULT_MODEL3;
|
|
13394
13532
|
const wantsStructured = !!options.responseSchema;
|
|
13395
13533
|
const sendResponseFormat = wantsStructured && supportsResponseFormat(model, "json_schema");
|
|
13396
13534
|
const basePrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
@@ -13500,6 +13638,168 @@ function parseAnswer(body, base, parseSchema) {
|
|
|
13500
13638
|
};
|
|
13501
13639
|
}
|
|
13502
13640
|
|
|
13641
|
+
// src/lib/memory/topicExtract.ts
|
|
13642
|
+
init_operations2();
|
|
13643
|
+
init_operations3();
|
|
13644
|
+
init_logger();
|
|
13645
|
+
init_redactor();
|
|
13646
|
+
init_portalLlm();
|
|
13647
|
+
var TOPIC_EXTRACTION_BATCH_SIZE = 10;
|
|
13648
|
+
var MAX_CHARS_PER_MEMORY = 300;
|
|
13649
|
+
var MAX_VOCABULARY_NAMES = 100;
|
|
13650
|
+
var SYSTEM_PROMPT4 = `You assign topics to saved memories for a personal memory system.
|
|
13651
|
+
|
|
13652
|
+
Each memory is a short statement about the user. For each memory, list the NAMED entities it mentions \u2014 these become the memory's topics, used to connect related memories in a knowledge graph.
|
|
13653
|
+
|
|
13654
|
+
Include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES}
|
|
13655
|
+
|
|
13656
|
+
Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
|
|
13657
|
+
- exactly one element per input memory, echoing its id verbatim
|
|
13658
|
+
- "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
|
|
13659
|
+
No prose.`;
|
|
13660
|
+
async function extractEntitiesForMemories(memories, options) {
|
|
13661
|
+
const out = /* @__PURE__ */ new Map();
|
|
13662
|
+
if (memories.length === 0) return out;
|
|
13663
|
+
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
13664
|
+
const vocabulary = (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES).map((n) => redactor ? redactor.redactText(n).text : n);
|
|
13665
|
+
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
13666
|
+
When a memory refers to one of these, reuse the EXACT existing name (same spelling and casing) instead of a variant.
|
|
13667
|
+
|
|
13668
|
+
` : "";
|
|
13669
|
+
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
13670
|
+
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
13671
|
+
const listing = batch.map((m) => {
|
|
13672
|
+
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY);
|
|
13673
|
+
return `[${m.id}] ${redactor ? redactor.redactText(content).text : content}`;
|
|
13674
|
+
}).join("\n");
|
|
13675
|
+
const parsed = await callPortalJsonCompletion({
|
|
13676
|
+
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
13677
|
+
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
13678
|
+
...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
|
|
13679
|
+
model: options.model ?? DEFAULT_EXTRACTION_MODEL,
|
|
13680
|
+
systemPrompt: SYSTEM_PROMPT4,
|
|
13681
|
+
userMessage: `${vocabularyNote}Memories:
|
|
13682
|
+
${listing}
|
|
13683
|
+
|
|
13684
|
+
List each memory's named entities.`,
|
|
13685
|
+
tag: "memory/topics",
|
|
13686
|
+
...options.fetchFn && { fetchFn: options.fetchFn },
|
|
13687
|
+
...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
|
|
13688
|
+
...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
13689
|
+
...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
|
|
13690
|
+
...options.backoffMs && { backoffMs: options.backoffMs }
|
|
13691
|
+
});
|
|
13692
|
+
if (parsed === null) {
|
|
13693
|
+
getLogger().warn(
|
|
13694
|
+
`[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
|
|
13695
|
+
);
|
|
13696
|
+
continue;
|
|
13697
|
+
}
|
|
13698
|
+
const validIds = new Set(batch.map((m) => m.id));
|
|
13699
|
+
const byId = parseTopicResponse(parsed, validIds);
|
|
13700
|
+
if (byId === null) {
|
|
13701
|
+
getLogger().warn(
|
|
13702
|
+
`[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
|
|
13703
|
+
);
|
|
13704
|
+
continue;
|
|
13705
|
+
}
|
|
13706
|
+
for (const m of batch) {
|
|
13707
|
+
const entities = byId.get(m.id);
|
|
13708
|
+
if (entities === void 0) {
|
|
13709
|
+
continue;
|
|
13710
|
+
}
|
|
13711
|
+
out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
|
|
13712
|
+
}
|
|
13713
|
+
}
|
|
13714
|
+
return out;
|
|
13715
|
+
}
|
|
13716
|
+
function restoreEntities(entities, redactor) {
|
|
13717
|
+
return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
|
|
13718
|
+
(e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
|
|
13719
|
+
);
|
|
13720
|
+
}
|
|
13721
|
+
function parseTopicResponse(parsed, validIds) {
|
|
13722
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
13723
|
+
const list = parsed.memories;
|
|
13724
|
+
if (!Array.isArray(list)) return null;
|
|
13725
|
+
const out = /* @__PURE__ */ new Map();
|
|
13726
|
+
for (const raw of list) {
|
|
13727
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
13728
|
+
const obj = raw;
|
|
13729
|
+
if (typeof obj.id !== "string" || !validIds.has(obj.id) || out.has(obj.id)) continue;
|
|
13730
|
+
const entities = Array.isArray(obj.entities) ? parseEntities(obj.entities) : [];
|
|
13731
|
+
out.set(obj.id, entities);
|
|
13732
|
+
}
|
|
13733
|
+
return out;
|
|
13734
|
+
}
|
|
13735
|
+
async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
|
|
13736
|
+
const entityCtx = ctx.entityCtx;
|
|
13737
|
+
if (!entityCtx) {
|
|
13738
|
+
throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
|
|
13739
|
+
}
|
|
13740
|
+
const log = getLogger();
|
|
13741
|
+
const extractedAt = options.now ?? Date.now();
|
|
13742
|
+
const skippedIds = [];
|
|
13743
|
+
const inputs = [];
|
|
13744
|
+
for (const id of memoryIds) {
|
|
13745
|
+
let record;
|
|
13746
|
+
try {
|
|
13747
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
13748
|
+
} catch {
|
|
13749
|
+
skippedIds.push(id);
|
|
13750
|
+
continue;
|
|
13751
|
+
}
|
|
13752
|
+
if (record.isDeleted || ctx.userId !== void 0 && record.userId !== ctx.userId || record.topicsUserManaged) {
|
|
13753
|
+
skippedIds.push(id);
|
|
13754
|
+
continue;
|
|
13755
|
+
}
|
|
13756
|
+
try {
|
|
13757
|
+
const stored = await vaultMemoryToStored(
|
|
13758
|
+
record,
|
|
13759
|
+
ctx.walletAddress,
|
|
13760
|
+
ctx.signMessage,
|
|
13761
|
+
ctx.embeddedWalletSigner
|
|
13762
|
+
);
|
|
13763
|
+
inputs.push({ id, content: stored.content });
|
|
13764
|
+
} catch (err) {
|
|
13765
|
+
log.warn("[memory/topics] failed to load memory for extraction", err);
|
|
13766
|
+
skippedIds.push(id);
|
|
13767
|
+
}
|
|
13768
|
+
}
|
|
13769
|
+
const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
|
|
13770
|
+
const toStamp = [];
|
|
13771
|
+
for (const input of inputs) {
|
|
13772
|
+
const entities = entitiesByMemory.get(input.id);
|
|
13773
|
+
if (entities === void 0) {
|
|
13774
|
+
skippedIds.push(input.id);
|
|
13775
|
+
continue;
|
|
13776
|
+
}
|
|
13777
|
+
try {
|
|
13778
|
+
const linked = await replaceMemoryEntitiesGuardedOp(entityCtx, input.id, entities);
|
|
13779
|
+
if (linked === null) {
|
|
13780
|
+
skippedIds.push(input.id);
|
|
13781
|
+
entitiesByMemory.delete(input.id);
|
|
13782
|
+
continue;
|
|
13783
|
+
}
|
|
13784
|
+
} catch (err) {
|
|
13785
|
+
log.warn("[memory/topics] replaceMemoryEntitiesGuardedOp failed", err);
|
|
13786
|
+
skippedIds.push(input.id);
|
|
13787
|
+
entitiesByMemory.delete(input.id);
|
|
13788
|
+
continue;
|
|
13789
|
+
}
|
|
13790
|
+
toStamp.push(input.id);
|
|
13791
|
+
}
|
|
13792
|
+
const stampedIds = await stampTopicsExtractedAtOp(ctx, toStamp, extractedAt);
|
|
13793
|
+
const stampedSet = new Set(stampedIds);
|
|
13794
|
+
for (const id of toStamp) {
|
|
13795
|
+
if (!stampedSet.has(id)) {
|
|
13796
|
+
skippedIds.push(id);
|
|
13797
|
+
entitiesByMemory.delete(id);
|
|
13798
|
+
}
|
|
13799
|
+
}
|
|
13800
|
+
return { entitiesByMemory, stampedIds, skippedIds };
|
|
13801
|
+
}
|
|
13802
|
+
|
|
13503
13803
|
// src/lib/memoryEngine/index.ts
|
|
13504
13804
|
init_chunking();
|
|
13505
13805
|
init_embeddings();
|
|
@@ -16024,7 +16324,7 @@ __decorateClass([
|
|
|
16024
16324
|
], VaultFolder.prototype, "context", 2);
|
|
16025
16325
|
|
|
16026
16326
|
// src/lib/db/schema.ts
|
|
16027
|
-
var SDK_SCHEMA_VERSION =
|
|
16327
|
+
var SDK_SCHEMA_VERSION = 36;
|
|
16028
16328
|
var sdkSchema = (0, import_watermelondb19.appSchema)({
|
|
16029
16329
|
version: SDK_SCHEMA_VERSION,
|
|
16030
16330
|
tables: [
|
|
@@ -16145,7 +16445,11 @@ var sdkSchema = (0, import_watermelondb19.appSchema)({
|
|
|
16145
16445
|
// When true, the user has manually set this memory's topics (entity
|
|
16146
16446
|
// links). Auto-extraction then leaves its links alone — the user owns
|
|
16147
16447
|
// them. Null/false = topics are auto-derived (default).
|
|
16148
|
-
{ name: "topics_user_managed", type: "boolean", isOptional: true }
|
|
16448
|
+
{ name: "topics_user_managed", type: "boolean", isOptional: true },
|
|
16449
|
+
// Unix ms of the last LLM topic-extraction pass over this memory's
|
|
16450
|
+
// content. Null = never extracted standalone (legacy rows with entity
|
|
16451
|
+
// links are grandfathered — see getMemoriesNeedingTopicExtractionOp).
|
|
16452
|
+
{ name: "topics_extracted_at", type: "number", isOptional: true }
|
|
16149
16453
|
]
|
|
16150
16454
|
}),
|
|
16151
16455
|
// Entity table — canonical names extracted from auto-extraction (W5).
|
|
@@ -16743,6 +17047,19 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
|
|
|
16743
17047
|
]
|
|
16744
17048
|
})
|
|
16745
17049
|
]
|
|
17050
|
+
},
|
|
17051
|
+
// v35 -> v36: topics_extracted_at watermark on memory_vault. Existing rows
|
|
17052
|
+
// keep it NULL — rows that already have entity links are grandfathered as
|
|
17053
|
+
// extracted (no mass re-extraction on upgrade), rows without links are the
|
|
17054
|
+
// backfill target. See getMemoriesNeedingTopicExtractionOp.
|
|
17055
|
+
{
|
|
17056
|
+
toVersion: 36,
|
|
17057
|
+
steps: [
|
|
17058
|
+
(0, import_migrations2.addColumns)({
|
|
17059
|
+
table: "memory_vault",
|
|
17060
|
+
columns: [{ name: "topics_extracted_at", type: "number", isOptional: true }]
|
|
17061
|
+
})
|
|
17062
|
+
]
|
|
16746
17063
|
}
|
|
16747
17064
|
]
|
|
16748
17065
|
});
|
|
@@ -18105,6 +18422,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
18105
18422
|
StoredVaultFolderModel,
|
|
18106
18423
|
StoredVaultMemoryModel,
|
|
18107
18424
|
StreamExpiredError,
|
|
18425
|
+
TOPIC_EXTRACTION_BATCH_SIZE,
|
|
18108
18426
|
WalletPoller,
|
|
18109
18427
|
addConversationMemoriesOp,
|
|
18110
18428
|
buildNotionAuthUrl,
|
|
@@ -18156,7 +18474,9 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
18156
18474
|
embedMessage,
|
|
18157
18475
|
ensureDefaultFoldersOp,
|
|
18158
18476
|
exchangeNotionCode,
|
|
18477
|
+
extractAndLinkEntitiesForMemoriesOp,
|
|
18159
18478
|
extractAndRetain,
|
|
18479
|
+
extractEntitiesForMemories,
|
|
18160
18480
|
extractFacts,
|
|
18161
18481
|
generateConversationId,
|
|
18162
18482
|
generateEmbedding,
|
|
@@ -18170,6 +18490,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
18170
18490
|
getConversationsByProjectLazyOp,
|
|
18171
18491
|
getConversationsLazyOp,
|
|
18172
18492
|
getLogger,
|
|
18493
|
+
getMemoriesNeedingTopicExtractionOp,
|
|
18173
18494
|
getServerTools,
|
|
18174
18495
|
getUnfiledVaultMemoriesOp,
|
|
18175
18496
|
getVaultFolderMemoryCountOp,
|
|
@@ -18196,6 +18517,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
18196
18517
|
searchVaultMemories,
|
|
18197
18518
|
setLogger,
|
|
18198
18519
|
shouldChunkMessage,
|
|
18520
|
+
stampTopicsExtractedAtOp,
|
|
18199
18521
|
streamCancelPath,
|
|
18200
18522
|
streamReplayPath,
|
|
18201
18523
|
updateVaultFolderContextOp,
|