@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/server/index.cjs
CHANGED
|
@@ -16643,14 +16643,19 @@ async function upsertEntitiesOp(ctx, entities) {
|
|
|
16643
16643
|
return out;
|
|
16644
16644
|
});
|
|
16645
16645
|
}
|
|
16646
|
-
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs) {
|
|
16646
|
+
async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs, options) {
|
|
16647
16647
|
if (entityInputs.length === 0) return [];
|
|
16648
16648
|
const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
16649
16649
|
const byName = await upsertEntitiesOp(ctx, normalized);
|
|
16650
16650
|
const entities = Array.from(byName.values());
|
|
16651
16651
|
if (entities.length === 0) return [];
|
|
16652
16652
|
const userId = ctx.userId;
|
|
16653
|
+
let skipped = false;
|
|
16653
16654
|
await ctx.database.write(async () => {
|
|
16655
|
+
if (options?.unlessTopicsUserManaged && await autoLinkBlocked(ctx, memoryId)) {
|
|
16656
|
+
skipped = true;
|
|
16657
|
+
return;
|
|
16658
|
+
}
|
|
16654
16659
|
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb16.Q.where("memory_id", memoryId)).fetch();
|
|
16655
16660
|
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
16656
16661
|
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
@@ -16664,7 +16669,46 @@ async function linkMemoryEntitiesOp(ctx, memoryId, entityInputs) {
|
|
|
16664
16669
|
);
|
|
16665
16670
|
await ctx.database.batch(...prepared);
|
|
16666
16671
|
});
|
|
16667
|
-
return entities;
|
|
16672
|
+
return skipped ? [] : entities;
|
|
16673
|
+
}
|
|
16674
|
+
async function autoLinkBlocked(ctx, memoryId) {
|
|
16675
|
+
try {
|
|
16676
|
+
const rows = await ctx.database.get("memory_vault").query(import_watermelondb16.Q.where("id", memoryId)).fetch();
|
|
16677
|
+
const row = rows[0];
|
|
16678
|
+
return !row || !!row.isDeleted || !!row.topicsUserManaged;
|
|
16679
|
+
} catch {
|
|
16680
|
+
return true;
|
|
16681
|
+
}
|
|
16682
|
+
}
|
|
16683
|
+
async function replaceMemoryEntitiesGuardedOp(ctx, memoryId, entityInputs) {
|
|
16684
|
+
const normalized = entityInputs.map((e) => typeof e === "string" ? { name: e } : e);
|
|
16685
|
+
const byName = await upsertEntitiesOp(ctx, normalized);
|
|
16686
|
+
const entities = Array.from(byName.values());
|
|
16687
|
+
const userId = ctx.userId;
|
|
16688
|
+
let skipped = false;
|
|
16689
|
+
await ctx.database.write(async () => {
|
|
16690
|
+
if (await autoLinkBlocked(ctx, memoryId)) {
|
|
16691
|
+
skipped = true;
|
|
16692
|
+
return;
|
|
16693
|
+
}
|
|
16694
|
+
const existingLinks = await ctx.memoryEntityCollection.query(import_watermelondb16.Q.where("memory_id", memoryId)).fetch();
|
|
16695
|
+
const keep = new Set(entities.map((e) => e.uniqueId));
|
|
16696
|
+
const existingEntityIds = new Set(existingLinks.map((l) => String(l.entityId)));
|
|
16697
|
+
const toCreate = entities.filter((e) => !existingEntityIds.has(e.uniqueId));
|
|
16698
|
+
const toDestroy = existingLinks.filter((l) => !keep.has(String(l.entityId)));
|
|
16699
|
+
if (toCreate.length === 0 && toDestroy.length === 0) return;
|
|
16700
|
+
await ctx.database.batch(
|
|
16701
|
+
...toCreate.map(
|
|
16702
|
+
(e) => ctx.memoryEntityCollection.prepareCreate((record) => {
|
|
16703
|
+
record._setRaw("memory_id", memoryId);
|
|
16704
|
+
record._setRaw("entity_id", e.uniqueId);
|
|
16705
|
+
if (userId !== void 0) record._setRaw("user_id", userId);
|
|
16706
|
+
})
|
|
16707
|
+
),
|
|
16708
|
+
...toDestroy.map((l) => l.prepareDestroyPermanently())
|
|
16709
|
+
);
|
|
16710
|
+
});
|
|
16711
|
+
return skipped ? null : entities;
|
|
16668
16712
|
}
|
|
16669
16713
|
async function unlinkMemoryEntitiesOp(ctx, memoryIds) {
|
|
16670
16714
|
if (memoryIds.length === 0) return;
|
|
@@ -16800,6 +16844,7 @@ function vaultMemoryToStoredRaw(memory) {
|
|
|
16800
16844
|
eventTimeEnd: memory.eventTimeEnd ?? null,
|
|
16801
16845
|
eventTimeKind: memory.eventTimeKind ?? null,
|
|
16802
16846
|
topicsUserManaged: memory.topicsUserManaged ?? false,
|
|
16847
|
+
topicsExtractedAt: memory.topicsExtractedAt ?? null,
|
|
16803
16848
|
createdAt: memory.createdAt,
|
|
16804
16849
|
updatedAt: memory.updatedAt,
|
|
16805
16850
|
isDeleted: memory.isDeleted
|
|
@@ -16983,6 +17028,7 @@ function vaultMemoryRawToStoredRaw(raw) {
|
|
|
16983
17028
|
eventTimeKind: raw.event_time_kind ?? null,
|
|
16984
17029
|
// SQLite stores booleans as 0/1, LokiJS as true/false — coerce both.
|
|
16985
17030
|
topicsUserManaged: raw.topics_user_managed === true || raw.topics_user_managed === 1,
|
|
17031
|
+
topicsExtractedAt: raw.topics_extracted_at ?? null,
|
|
16986
17032
|
createdAt: new Date(raw.created_at),
|
|
16987
17033
|
updatedAt: new Date(raw.updated_at),
|
|
16988
17034
|
isDeleted: raw.is_deleted === true || raw.is_deleted === 1
|
|
@@ -17125,6 +17171,87 @@ async function getUnfiledVaultMemoriesOp(ctx) {
|
|
|
17125
17171
|
(record) => vaultMemoryToStored(record, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
17126
17172
|
);
|
|
17127
17173
|
}
|
|
17174
|
+
async function getMemoriesNeedingTopicExtractionOp(ctx, options) {
|
|
17175
|
+
const entityCtx = ctx.entityCtx;
|
|
17176
|
+
if (!entityCtx) {
|
|
17177
|
+
throw new Error("getMemoriesNeedingTopicExtractionOp requires ctx.entityCtx");
|
|
17178
|
+
}
|
|
17179
|
+
const conditions = [
|
|
17180
|
+
// Boolean optional column: null (legacy) and false both mean auto-managed.
|
|
17181
|
+
// Q.notEq(true) is a NULL trap on SQLite (NULL <> TRUE is NULL) — enumerate.
|
|
17182
|
+
import_watermelondb17.Q.or(import_watermelondb17.Q.where("topics_user_managed", null), import_watermelondb17.Q.where("topics_user_managed", false)),
|
|
17183
|
+
...baseVaultConditions(ctx),
|
|
17184
|
+
import_watermelondb17.Q.sortBy("created_at", import_watermelondb17.Q.desc)
|
|
17185
|
+
];
|
|
17186
|
+
const rows = await ctx.vaultMemoryCollection.query(...conditions).unsafeFetchRaw();
|
|
17187
|
+
const stampedPending = [];
|
|
17188
|
+
const unstamped = [];
|
|
17189
|
+
for (const raw of rows) {
|
|
17190
|
+
const stamp = raw.topics_extracted_at ?? null;
|
|
17191
|
+
if (stamp !== null) {
|
|
17192
|
+
if (raw.updated_at > stamp) stampedPending.push(raw);
|
|
17193
|
+
} else {
|
|
17194
|
+
unstamped.push(raw);
|
|
17195
|
+
}
|
|
17196
|
+
}
|
|
17197
|
+
const linkedIds = /* @__PURE__ */ new Set();
|
|
17198
|
+
const CHUNK = 500;
|
|
17199
|
+
for (let i = 0; i < unstamped.length; i += CHUNK) {
|
|
17200
|
+
const ids = unstamped.slice(i, i + CHUNK).map((r) => r.id);
|
|
17201
|
+
const links = await entityCtx.memoryEntityCollection.query(import_watermelondb17.Q.where("memory_id", import_watermelondb17.Q.oneOf(ids))).unsafeFetchRaw();
|
|
17202
|
+
for (const link of links) linkedIds.add(String(link.memory_id));
|
|
17203
|
+
}
|
|
17204
|
+
const linkedUnstampedAll = [];
|
|
17205
|
+
const pendingRaw = [...stampedPending];
|
|
17206
|
+
for (const raw of unstamped) {
|
|
17207
|
+
if (linkedIds.has(raw.id)) {
|
|
17208
|
+
linkedUnstampedAll.push(raw.id);
|
|
17209
|
+
} else {
|
|
17210
|
+
pendingRaw.push(raw);
|
|
17211
|
+
}
|
|
17212
|
+
}
|
|
17213
|
+
const cap = options?.limit !== void 0 && options.limit > 0 ? options.limit : void 0;
|
|
17214
|
+
const limitedPendingRaw = cap !== void 0 ? pendingRaw.slice(0, cap) : pendingRaw;
|
|
17215
|
+
const linkedUnstamped = cap !== void 0 ? linkedUnstampedAll.slice(0, cap) : linkedUnstampedAll;
|
|
17216
|
+
const pending = await mapInBatches(
|
|
17217
|
+
limitedPendingRaw,
|
|
17218
|
+
(raw) => vaultMemoryRawToStored(raw, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner)
|
|
17219
|
+
);
|
|
17220
|
+
return { pending, linkedUnstamped };
|
|
17221
|
+
}
|
|
17222
|
+
async function stampTopicsExtractedAtOp(ctx, memoryIds, extractedAt) {
|
|
17223
|
+
if (memoryIds.length === 0) return [];
|
|
17224
|
+
const uniqueIds = Array.from(new Set(memoryIds));
|
|
17225
|
+
const CHUNK = 500;
|
|
17226
|
+
const stamped = [];
|
|
17227
|
+
for (let i = 0; i < uniqueIds.length; i += CHUNK) {
|
|
17228
|
+
const chunkIds = uniqueIds.slice(i, i + CHUNK);
|
|
17229
|
+
await ctx.database.write(async () => {
|
|
17230
|
+
const prepared = [];
|
|
17231
|
+
for (const id of chunkIds) {
|
|
17232
|
+
let record;
|
|
17233
|
+
try {
|
|
17234
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
17235
|
+
} catch {
|
|
17236
|
+
continue;
|
|
17237
|
+
}
|
|
17238
|
+
if (record.isDeleted || !isOwnedByCtxUser(ctx, record) || record.topicsUserManaged) {
|
|
17239
|
+
continue;
|
|
17240
|
+
}
|
|
17241
|
+
const originalUpdatedAt = record.updatedAt.getTime();
|
|
17242
|
+
prepared.push(
|
|
17243
|
+
record.prepareUpdate((r) => {
|
|
17244
|
+
r._setRaw("topics_extracted_at", extractedAt);
|
|
17245
|
+
r._setRaw("updated_at", originalUpdatedAt);
|
|
17246
|
+
})
|
|
17247
|
+
);
|
|
17248
|
+
stamped.push(id);
|
|
17249
|
+
}
|
|
17250
|
+
if (prepared.length > 0) await ctx.database.batch(...prepared);
|
|
17251
|
+
});
|
|
17252
|
+
}
|
|
17253
|
+
return stamped;
|
|
17254
|
+
}
|
|
17128
17255
|
async function deleteAllVaultMemoriesForUserOp(ctx, userId) {
|
|
17129
17256
|
if (ctx.userId !== void 0 && ctx.userId !== userId) return 0;
|
|
17130
17257
|
const records = await ctx.vaultMemoryCollection.query(import_watermelondb17.Q.where("user_id", userId), import_watermelondb17.Q.where("is_deleted", false)).fetch();
|
|
@@ -21469,6 +21596,7 @@ __export(index_exports, {
|
|
|
21469
21596
|
STREAM_RESUMABLE_HEADER: () => STREAM_RESUMABLE_HEADER,
|
|
21470
21597
|
StreamExpiredError: () => StreamExpiredError,
|
|
21471
21598
|
TOOL_CATALOG: () => TOOL_CATALOG,
|
|
21599
|
+
TOPIC_EXTRACTION_BATCH_SIZE: () => TOPIC_EXTRACTION_BATCH_SIZE,
|
|
21472
21600
|
TextProcessor: () => TextProcessor,
|
|
21473
21601
|
UserPreference: () => UserPreference,
|
|
21474
21602
|
VaultFolder: () => VaultFolder,
|
|
@@ -21549,7 +21677,9 @@ __export(index_exports, {
|
|
|
21549
21677
|
estimateMessagesTokens: () => estimateMessagesTokens,
|
|
21550
21678
|
estimateTokens: () => estimateTokens,
|
|
21551
21679
|
exportPublicKey: () => exportPublicKey,
|
|
21680
|
+
extractAndLinkEntitiesForMemoriesOp: () => extractAndLinkEntitiesForMemoriesOp,
|
|
21552
21681
|
extractAndRetain: () => extractAndRetain,
|
|
21682
|
+
extractEntitiesForMemories: () => extractEntitiesForMemories,
|
|
21553
21683
|
extractFacts: () => extractFacts,
|
|
21554
21684
|
findMatchingTools: () => findMatchingTools,
|
|
21555
21685
|
generateConversationId: () => generateConversationId,
|
|
@@ -21585,6 +21715,7 @@ __export(index_exports, {
|
|
|
21585
21715
|
getMediaCountsByTypeOp: () => getMediaCountsByTypeOp,
|
|
21586
21716
|
getMediaOp: () => getMediaOp,
|
|
21587
21717
|
getMediaTypeFromMime: () => getMediaTypeFromMime,
|
|
21718
|
+
getMemoriesNeedingTopicExtractionOp: () => getMemoriesNeedingTopicExtractionOp,
|
|
21588
21719
|
getMessagesOp: () => getMessagesOp,
|
|
21589
21720
|
getModelPreferenceOp: () => getModelPreferenceOp,
|
|
21590
21721
|
getProjectConversationCountOp: () => getProjectConversationCountOp,
|
|
@@ -21644,6 +21775,7 @@ __export(index_exports, {
|
|
|
21644
21775
|
shouldChunkMessage: () => shouldChunkMessage,
|
|
21645
21776
|
shouldRefreshTools: () => shouldRefreshTools,
|
|
21646
21777
|
splitMessagesAtThreshold: () => splitMessagesAtThreshold,
|
|
21778
|
+
stampTopicsExtractedAtOp: () => stampTopicsExtractedAtOp,
|
|
21647
21779
|
streamCancelPath: () => streamCancelPath,
|
|
21648
21780
|
streamReplayPath: () => streamReplayPath,
|
|
21649
21781
|
summaryToSystemMessage: () => summaryToSystemMessage,
|
|
@@ -21998,6 +22130,9 @@ __decorateClass([
|
|
|
21998
22130
|
__decorateClass([
|
|
21999
22131
|
(0, import_decorators6.field)("topics_user_managed")
|
|
22000
22132
|
], VaultMemory.prototype, "topicsUserManaged", 2);
|
|
22133
|
+
__decorateClass([
|
|
22134
|
+
(0, import_decorators6.field)("topics_extracted_at")
|
|
22135
|
+
], VaultMemory.prototype, "topicsExtractedAt", 2);
|
|
22001
22136
|
__decorateClass([
|
|
22002
22137
|
import_decorators6.readonly,
|
|
22003
22138
|
(0, import_decorators6.date)("created_at")
|
|
@@ -22145,7 +22280,7 @@ __decorateClass([
|
|
|
22145
22280
|
], VaultFolder.prototype, "context", 2);
|
|
22146
22281
|
|
|
22147
22282
|
// src/lib/db/schema.ts
|
|
22148
|
-
var SDK_SCHEMA_VERSION =
|
|
22283
|
+
var SDK_SCHEMA_VERSION = 36;
|
|
22149
22284
|
var sdkSchema = (0, import_watermelondb12.appSchema)({
|
|
22150
22285
|
version: SDK_SCHEMA_VERSION,
|
|
22151
22286
|
tables: [
|
|
@@ -22266,7 +22401,11 @@ var sdkSchema = (0, import_watermelondb12.appSchema)({
|
|
|
22266
22401
|
// When true, the user has manually set this memory's topics (entity
|
|
22267
22402
|
// links). Auto-extraction then leaves its links alone — the user owns
|
|
22268
22403
|
// them. Null/false = topics are auto-derived (default).
|
|
22269
|
-
{ name: "topics_user_managed", type: "boolean", isOptional: true }
|
|
22404
|
+
{ name: "topics_user_managed", type: "boolean", isOptional: true },
|
|
22405
|
+
// Unix ms of the last LLM topic-extraction pass over this memory's
|
|
22406
|
+
// content. Null = never extracted standalone (legacy rows with entity
|
|
22407
|
+
// links are grandfathered — see getMemoriesNeedingTopicExtractionOp).
|
|
22408
|
+
{ name: "topics_extracted_at", type: "number", isOptional: true }
|
|
22270
22409
|
]
|
|
22271
22410
|
}),
|
|
22272
22411
|
// Entity table — canonical names extracted from auto-extraction (W5).
|
|
@@ -22864,6 +23003,19 @@ var sdkMigrations = (0, import_migrations.schemaMigrations)({
|
|
|
22864
23003
|
]
|
|
22865
23004
|
})
|
|
22866
23005
|
]
|
|
23006
|
+
},
|
|
23007
|
+
// v35 -> v36: topics_extracted_at watermark on memory_vault. Existing rows
|
|
23008
|
+
// keep it NULL — rows that already have entity links are grandfathered as
|
|
23009
|
+
// extracted (no mass re-extraction on upgrade), rows without links are the
|
|
23010
|
+
// backfill target. See getMemoriesNeedingTopicExtractionOp.
|
|
23011
|
+
{
|
|
23012
|
+
toVersion: 36,
|
|
23013
|
+
steps: [
|
|
23014
|
+
(0, import_migrations.addColumns)({
|
|
23015
|
+
table: "memory_vault",
|
|
23016
|
+
columns: [{ name: "topics_extracted_at", type: "number", isOptional: true }]
|
|
23017
|
+
})
|
|
23018
|
+
]
|
|
22867
23019
|
}
|
|
22868
23020
|
]
|
|
22869
23021
|
});
|
|
@@ -25790,7 +25942,7 @@ async function isMemoryTopicsUserManaged(ctx, memoryId) {
|
|
|
25790
25942
|
return true;
|
|
25791
25943
|
}
|
|
25792
25944
|
}
|
|
25793
|
-
var
|
|
25945
|
+
var DEFAULT_EXTRACTION_MODEL = "gpt-oss/gpt-oss-120b";
|
|
25794
25946
|
var DEFAULT_MIN_CONFIDENCE = 0.7;
|
|
25795
25947
|
var MAX_CONTENT_LENGTH = 200;
|
|
25796
25948
|
var MIN_CONTENT_LENGTH = 3;
|
|
@@ -25803,6 +25955,16 @@ var FACT_TYPES = [
|
|
|
25803
25955
|
"constraint",
|
|
25804
25956
|
"other"
|
|
25805
25957
|
];
|
|
25958
|
+
var ENTITY_KIND_GUIDELINES = `Choose the MOST SPECIFIC kind:
|
|
25959
|
+
- person: a named individual ("Sara", "Dr. Lee")
|
|
25960
|
+
- organization: a company, team, school, or group ("Google", "the Warriors", "Stanford")
|
|
25961
|
+
- place: a geographic location or venue ("San Francisco", "Blue Bottle on Valencia")
|
|
25962
|
+
- event: a dated or nameable occurrence ("Chicago Marathon", "the Japan trip", "their wedding")
|
|
25963
|
+
- product: a named app, tool, brand, device, book, film, or other media title ("Linear", "iPhone", "Dune", "Ableton")
|
|
25964
|
+
- thing: a concrete physical object that isn't a branded product ("road bike", "sourdough starter")
|
|
25965
|
+
- concept: a topic, skill, field, language, or idea ("machine learning", "Spanish", "async communication")
|
|
25966
|
+
- other: a named entity that genuinely fits none of the above \u2014 use sparingly
|
|
25967
|
+
Prefer organization / product / event / place over the generic "thing"; fall back to "thing" or "other" only when no specific kind fits.`;
|
|
25806
25968
|
var SYSTEM_PROMPT3 = `You extract durable user facts from conversations for a personal memory system.
|
|
25807
25969
|
|
|
25808
25970
|
A "durable fact" is something the user shared about themselves that should still be true in future conversations. Examples that ARE durable:
|
|
@@ -25831,16 +25993,7 @@ For each durable fact, output:
|
|
|
25831
25993
|
- type: one of identity | preference | relationship | plan | ongoing_context | constraint | other
|
|
25832
25994
|
- confidence: 0.0-1.0; how sure you are this is durable AND true. Only include facts >= 0.7.
|
|
25833
25995
|
- sourceMessageIds: which message IDs from the conversation contained the evidence
|
|
25834
|
-
- 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.
|
|
25835
|
-
- person: a named individual ("Sara", "Dr. Lee")
|
|
25836
|
-
- organization: a company, team, school, or group ("Google", "the Warriors", "Stanford")
|
|
25837
|
-
- place: a geographic location or venue ("San Francisco", "Blue Bottle on Valencia")
|
|
25838
|
-
- event: a dated or nameable occurrence ("Chicago Marathon", "the Japan trip", "their wedding")
|
|
25839
|
-
- product: a named app, tool, brand, device, book, film, or other media title ("Linear", "iPhone", "Dune", "Ableton")
|
|
25840
|
-
- thing: a concrete physical object that isn't a branded product ("road bike", "sourdough starter")
|
|
25841
|
-
- concept: a topic, skill, field, language, or idea ("machine learning", "Spanish", "async communication")
|
|
25842
|
-
- other: a named entity that genuinely fits none of the above \u2014 use sparingly
|
|
25843
|
-
Prefer organization / product / event / place over the generic "thing"; fall back to "thing" or "other" only when no specific kind fits. Examples:
|
|
25996
|
+
- 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:
|
|
25844
25997
|
- "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" }]
|
|
25845
25998
|
- "running the Berlin Half in May and I track workouts in Whoop" \u2192 entities: [{ "name": "Berlin Half", "kind": "event" }, { "name": "Whoop", "kind": "product" }]
|
|
25846
25999
|
- 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:
|
|
@@ -25866,7 +26019,7 @@ async function extractFacts(messages, options) {
|
|
|
25866
26019
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
25867
26020
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
25868
26021
|
...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
|
|
25869
|
-
model: options.model ??
|
|
26022
|
+
model: options.model ?? DEFAULT_EXTRACTION_MODEL,
|
|
25870
26023
|
systemPrompt: SYSTEM_PROMPT3,
|
|
25871
26024
|
userMessage: `Today's date is ${today}. Resolve any relative dates ("yesterday", "next week") against it.
|
|
25872
26025
|
|
|
@@ -25960,7 +26113,9 @@ async function extractAndRetain(messages, retainCtx, options) {
|
|
|
25960
26113
|
if (options.entityCtx && candidate.entities.length > 0) {
|
|
25961
26114
|
try {
|
|
25962
26115
|
if (!await isMemoryTopicsUserManaged(options.entityCtx, result.memoryId)) {
|
|
25963
|
-
await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities
|
|
26116
|
+
await linkMemoryEntitiesOp(options.entityCtx, result.memoryId, candidate.entities, {
|
|
26117
|
+
unlessTopicsUserManaged: true
|
|
26118
|
+
});
|
|
25964
26119
|
}
|
|
25965
26120
|
} catch (err) {
|
|
25966
26121
|
log.warn("[memory/extract] linkMemoryEntitiesOp failed", err);
|
|
@@ -26365,7 +26520,7 @@ init_logger();
|
|
|
26365
26520
|
init_portalLlm();
|
|
26366
26521
|
init_recall();
|
|
26367
26522
|
var DEFAULT_BASE_URL2 = BASE_URL;
|
|
26368
|
-
var
|
|
26523
|
+
var DEFAULT_MODEL3 = "anthropic/claude-sonnet-4-6";
|
|
26369
26524
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
26370
26525
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
26371
26526
|
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.
|
|
@@ -26397,7 +26552,7 @@ async function reflect(query, ctx, options) {
|
|
|
26397
26552
|
const evidence = recalled.memories.map((m, i) => `[${i + 1}] (id: ${m.id}, kind: ${m.kind})
|
|
26398
26553
|
${m.content}`).join("\n\n");
|
|
26399
26554
|
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL2;
|
|
26400
|
-
const model = options.llmModel ??
|
|
26555
|
+
const model = options.llmModel ?? DEFAULT_MODEL3;
|
|
26401
26556
|
const wantsStructured = !!options.responseSchema;
|
|
26402
26557
|
const sendResponseFormat = wantsStructured && supportsResponseFormat(model, "json_schema");
|
|
26403
26558
|
const basePrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
@@ -26507,6 +26662,168 @@ function parseAnswer(body, base, parseSchema) {
|
|
|
26507
26662
|
};
|
|
26508
26663
|
}
|
|
26509
26664
|
|
|
26665
|
+
// src/lib/memory/topicExtract.ts
|
|
26666
|
+
init_operations2();
|
|
26667
|
+
init_operations3();
|
|
26668
|
+
init_logger();
|
|
26669
|
+
init_redactor();
|
|
26670
|
+
init_portalLlm();
|
|
26671
|
+
var TOPIC_EXTRACTION_BATCH_SIZE = 10;
|
|
26672
|
+
var MAX_CHARS_PER_MEMORY = 300;
|
|
26673
|
+
var MAX_VOCABULARY_NAMES = 100;
|
|
26674
|
+
var SYSTEM_PROMPT4 = `You assign topics to saved memories for a personal memory system.
|
|
26675
|
+
|
|
26676
|
+
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.
|
|
26677
|
+
|
|
26678
|
+
Include only NAMED entities, skip generic/common nouns. ${ENTITY_KIND_GUIDELINES}
|
|
26679
|
+
|
|
26680
|
+
Output strict JSON: {"memories": [{"id": string, "entities": [{"name": string, "kind": string}]}]}
|
|
26681
|
+
- exactly one element per input memory, echoing its id verbatim
|
|
26682
|
+
- "entities" may be empty when a memory mentions no named entities \u2014 most short memories have 0-3
|
|
26683
|
+
No prose.`;
|
|
26684
|
+
async function extractEntitiesForMemories(memories, options) {
|
|
26685
|
+
const out = /* @__PURE__ */ new Map();
|
|
26686
|
+
if (memories.length === 0) return out;
|
|
26687
|
+
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
26688
|
+
const vocabulary = (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES).map((n) => redactor ? redactor.redactText(n).text : n);
|
|
26689
|
+
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
26690
|
+
When a memory refers to one of these, reuse the EXACT existing name (same spelling and casing) instead of a variant.
|
|
26691
|
+
|
|
26692
|
+
` : "";
|
|
26693
|
+
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
26694
|
+
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
26695
|
+
const listing = batch.map((m) => {
|
|
26696
|
+
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY);
|
|
26697
|
+
return `[${m.id}] ${redactor ? redactor.redactText(content).text : content}`;
|
|
26698
|
+
}).join("\n");
|
|
26699
|
+
const parsed = await callPortalJsonCompletion({
|
|
26700
|
+
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
26701
|
+
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
26702
|
+
...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
|
|
26703
|
+
model: options.model ?? DEFAULT_EXTRACTION_MODEL,
|
|
26704
|
+
systemPrompt: SYSTEM_PROMPT4,
|
|
26705
|
+
userMessage: `${vocabularyNote}Memories:
|
|
26706
|
+
${listing}
|
|
26707
|
+
|
|
26708
|
+
List each memory's named entities.`,
|
|
26709
|
+
tag: "memory/topics",
|
|
26710
|
+
...options.fetchFn && { fetchFn: options.fetchFn },
|
|
26711
|
+
...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
|
|
26712
|
+
...options.timeoutMs !== void 0 && { timeoutMs: options.timeoutMs },
|
|
26713
|
+
...options.totalTimeoutMs !== void 0 && { totalTimeoutMs: options.totalTimeoutMs },
|
|
26714
|
+
...options.backoffMs && { backoffMs: options.backoffMs }
|
|
26715
|
+
});
|
|
26716
|
+
if (parsed === null) {
|
|
26717
|
+
getLogger().warn(
|
|
26718
|
+
`[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
|
|
26719
|
+
);
|
|
26720
|
+
continue;
|
|
26721
|
+
}
|
|
26722
|
+
const validIds = new Set(batch.map((m) => m.id));
|
|
26723
|
+
const byId = parseTopicResponse(parsed, validIds);
|
|
26724
|
+
if (byId === null) {
|
|
26725
|
+
getLogger().warn(
|
|
26726
|
+
`[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
|
|
26727
|
+
);
|
|
26728
|
+
continue;
|
|
26729
|
+
}
|
|
26730
|
+
for (const m of batch) {
|
|
26731
|
+
const entities = byId.get(m.id);
|
|
26732
|
+
if (entities === void 0) {
|
|
26733
|
+
continue;
|
|
26734
|
+
}
|
|
26735
|
+
out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
|
|
26736
|
+
}
|
|
26737
|
+
}
|
|
26738
|
+
return out;
|
|
26739
|
+
}
|
|
26740
|
+
function restoreEntities(entities, redactor) {
|
|
26741
|
+
return entities.map((e) => ({ kind: e.kind, restored: redactor.restoreForStorage(e.name) })).filter((e) => !e.restored.unresolved).map(
|
|
26742
|
+
(e) => e.kind !== void 0 ? { name: e.restored.text, kind: e.kind } : { name: e.restored.text }
|
|
26743
|
+
);
|
|
26744
|
+
}
|
|
26745
|
+
function parseTopicResponse(parsed, validIds) {
|
|
26746
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
26747
|
+
const list = parsed.memories;
|
|
26748
|
+
if (!Array.isArray(list)) return null;
|
|
26749
|
+
const out = /* @__PURE__ */ new Map();
|
|
26750
|
+
for (const raw of list) {
|
|
26751
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
26752
|
+
const obj = raw;
|
|
26753
|
+
if (typeof obj.id !== "string" || !validIds.has(obj.id) || out.has(obj.id)) continue;
|
|
26754
|
+
const entities = Array.isArray(obj.entities) ? parseEntities(obj.entities) : [];
|
|
26755
|
+
out.set(obj.id, entities);
|
|
26756
|
+
}
|
|
26757
|
+
return out;
|
|
26758
|
+
}
|
|
26759
|
+
async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
|
|
26760
|
+
const entityCtx = ctx.entityCtx;
|
|
26761
|
+
if (!entityCtx) {
|
|
26762
|
+
throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
|
|
26763
|
+
}
|
|
26764
|
+
const log = getLogger();
|
|
26765
|
+
const extractedAt = options.now ?? Date.now();
|
|
26766
|
+
const skippedIds = [];
|
|
26767
|
+
const inputs = [];
|
|
26768
|
+
for (const id of memoryIds) {
|
|
26769
|
+
let record;
|
|
26770
|
+
try {
|
|
26771
|
+
record = await ctx.vaultMemoryCollection.find(id);
|
|
26772
|
+
} catch {
|
|
26773
|
+
skippedIds.push(id);
|
|
26774
|
+
continue;
|
|
26775
|
+
}
|
|
26776
|
+
if (record.isDeleted || ctx.userId !== void 0 && record.userId !== ctx.userId || record.topicsUserManaged) {
|
|
26777
|
+
skippedIds.push(id);
|
|
26778
|
+
continue;
|
|
26779
|
+
}
|
|
26780
|
+
try {
|
|
26781
|
+
const stored = await vaultMemoryToStored(
|
|
26782
|
+
record,
|
|
26783
|
+
ctx.walletAddress,
|
|
26784
|
+
ctx.signMessage,
|
|
26785
|
+
ctx.embeddedWalletSigner
|
|
26786
|
+
);
|
|
26787
|
+
inputs.push({ id, content: stored.content });
|
|
26788
|
+
} catch (err) {
|
|
26789
|
+
log.warn("[memory/topics] failed to load memory for extraction", err);
|
|
26790
|
+
skippedIds.push(id);
|
|
26791
|
+
}
|
|
26792
|
+
}
|
|
26793
|
+
const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
|
|
26794
|
+
const toStamp = [];
|
|
26795
|
+
for (const input of inputs) {
|
|
26796
|
+
const entities = entitiesByMemory.get(input.id);
|
|
26797
|
+
if (entities === void 0) {
|
|
26798
|
+
skippedIds.push(input.id);
|
|
26799
|
+
continue;
|
|
26800
|
+
}
|
|
26801
|
+
try {
|
|
26802
|
+
const linked = await replaceMemoryEntitiesGuardedOp(entityCtx, input.id, entities);
|
|
26803
|
+
if (linked === null) {
|
|
26804
|
+
skippedIds.push(input.id);
|
|
26805
|
+
entitiesByMemory.delete(input.id);
|
|
26806
|
+
continue;
|
|
26807
|
+
}
|
|
26808
|
+
} catch (err) {
|
|
26809
|
+
log.warn("[memory/topics] replaceMemoryEntitiesGuardedOp failed", err);
|
|
26810
|
+
skippedIds.push(input.id);
|
|
26811
|
+
entitiesByMemory.delete(input.id);
|
|
26812
|
+
continue;
|
|
26813
|
+
}
|
|
26814
|
+
toStamp.push(input.id);
|
|
26815
|
+
}
|
|
26816
|
+
const stampedIds = await stampTopicsExtractedAtOp(ctx, toStamp, extractedAt);
|
|
26817
|
+
const stampedSet = new Set(stampedIds);
|
|
26818
|
+
for (const id of toStamp) {
|
|
26819
|
+
if (!stampedSet.has(id)) {
|
|
26820
|
+
skippedIds.push(id);
|
|
26821
|
+
entitiesByMemory.delete(id);
|
|
26822
|
+
}
|
|
26823
|
+
}
|
|
26824
|
+
return { entitiesByMemory, stampedIds, skippedIds };
|
|
26825
|
+
}
|
|
26826
|
+
|
|
26510
26827
|
// src/tools/appBuilderPrompt.ts
|
|
26511
26828
|
var APP_BUILDER_PROMPT = `App Builder tools (create_file, patch_file, and the rest described below) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should build anything.
|
|
26512
26829
|
|
|
@@ -32092,6 +32409,7 @@ function buildSystemPrompt(ctx, options) {
|
|
|
32092
32409
|
STREAM_RESUMABLE_HEADER,
|
|
32093
32410
|
StreamExpiredError,
|
|
32094
32411
|
TOOL_CATALOG,
|
|
32412
|
+
TOPIC_EXTRACTION_BATCH_SIZE,
|
|
32095
32413
|
TextProcessor,
|
|
32096
32414
|
UserPreference,
|
|
32097
32415
|
VaultFolder,
|
|
@@ -32172,7 +32490,9 @@ function buildSystemPrompt(ctx, options) {
|
|
|
32172
32490
|
estimateMessagesTokens,
|
|
32173
32491
|
estimateTokens,
|
|
32174
32492
|
exportPublicKey,
|
|
32493
|
+
extractAndLinkEntitiesForMemoriesOp,
|
|
32175
32494
|
extractAndRetain,
|
|
32495
|
+
extractEntitiesForMemories,
|
|
32176
32496
|
extractFacts,
|
|
32177
32497
|
findMatchingTools,
|
|
32178
32498
|
generateConversationId,
|
|
@@ -32208,6 +32528,7 @@ function buildSystemPrompt(ctx, options) {
|
|
|
32208
32528
|
getMediaCountsByTypeOp,
|
|
32209
32529
|
getMediaOp,
|
|
32210
32530
|
getMediaTypeFromMime,
|
|
32531
|
+
getMemoriesNeedingTopicExtractionOp,
|
|
32211
32532
|
getMessagesOp,
|
|
32212
32533
|
getModelPreferenceOp,
|
|
32213
32534
|
getProjectConversationCountOp,
|
|
@@ -32267,6 +32588,7 @@ function buildSystemPrompt(ctx, options) {
|
|
|
32267
32588
|
shouldChunkMessage,
|
|
32268
32589
|
shouldRefreshTools,
|
|
32269
32590
|
splitMessagesAtThreshold,
|
|
32591
|
+
stampTopicsExtractedAtOp,
|
|
32270
32592
|
streamCancelPath,
|
|
32271
32593
|
streamReplayPath,
|
|
32272
32594
|
summaryToSystemMessage,
|