@reverbia/sdk 1.0.0-next.20251121094738 → 1.0.0-next.20251125084024
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/react/index.cjs +375 -286
- package/dist/react/index.d.mts +57 -3
- package/dist/react/index.d.ts +57 -3
- package/dist/react/index.mjs +379 -292
- package/package.json +1 -1
package/dist/react/index.cjs
CHANGED
|
@@ -40,287 +40,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
40
40
|
|
|
41
41
|
// src/react/useChat.ts
|
|
42
42
|
var import_react = require("react");
|
|
43
|
-
var import_client = require("@reverbia/sdk");
|
|
44
|
-
function useChat(options) {
|
|
45
|
-
const { getToken } = options || {};
|
|
46
|
-
const [isLoading, setIsLoading] = (0, import_react.useState)(false);
|
|
47
|
-
const sendMessage = (0, import_react.useCallback)(
|
|
48
|
-
async ({
|
|
49
|
-
messages,
|
|
50
|
-
model
|
|
51
|
-
}) => {
|
|
52
|
-
if (!messages?.length) {
|
|
53
|
-
const error = "messages are required to call sendMessage.";
|
|
54
|
-
return { data: null, error };
|
|
55
|
-
}
|
|
56
|
-
if (!model) {
|
|
57
|
-
const error = "model is required to call sendMessage.";
|
|
58
|
-
return { data: null, error };
|
|
59
|
-
}
|
|
60
|
-
if (!getToken) {
|
|
61
|
-
const error = "Token getter function is required.";
|
|
62
|
-
return { data: null, error };
|
|
63
|
-
}
|
|
64
|
-
setIsLoading(true);
|
|
65
|
-
try {
|
|
66
|
-
const token = await getToken();
|
|
67
|
-
if (!token) {
|
|
68
|
-
const error = "No access token available.";
|
|
69
|
-
setIsLoading(false);
|
|
70
|
-
return { data: null, error };
|
|
71
|
-
}
|
|
72
|
-
const completion = await (0, import_client.postApiV1ChatCompletions)({
|
|
73
|
-
body: {
|
|
74
|
-
messages,
|
|
75
|
-
model
|
|
76
|
-
}
|
|
77
|
-
// headers: {
|
|
78
|
-
// Authorization: `Bearer ${token}`,
|
|
79
|
-
// },
|
|
80
|
-
});
|
|
81
|
-
if (!completion.data) {
|
|
82
|
-
const error = completion.error?.error ?? "API did not return a completion response.";
|
|
83
|
-
setIsLoading(false);
|
|
84
|
-
return { data: null, error };
|
|
85
|
-
}
|
|
86
|
-
if (typeof completion.data === "string") {
|
|
87
|
-
const error = "API returned a string response instead of a completion object.";
|
|
88
|
-
setIsLoading(false);
|
|
89
|
-
return { data: null, error };
|
|
90
|
-
}
|
|
91
|
-
setIsLoading(false);
|
|
92
|
-
return { data: completion.data, error: null };
|
|
93
|
-
} catch (err) {
|
|
94
|
-
const error = err instanceof Error ? err.message : "Failed to send message.";
|
|
95
|
-
setIsLoading(false);
|
|
96
|
-
return { data: null, error };
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
[getToken]
|
|
100
|
-
);
|
|
101
|
-
return {
|
|
102
|
-
isLoading,
|
|
103
|
-
sendMessage
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/react/useMemory.ts
|
|
108
|
-
var import_react2 = require("react");
|
|
109
|
-
var import_client6 = require("@reverbia/sdk");
|
|
110
|
-
|
|
111
|
-
// src/lib/memory/service.ts
|
|
112
|
-
var FACT_EXTRACTION_PROMPT = `You are a memory extraction system. Extract durable user memories from chat messages.
|
|
113
|
-
|
|
114
|
-
CRITICAL: You MUST respond with ONLY valid JSON. No explanations, no markdown, no code blocks, just pure JSON.
|
|
115
|
-
|
|
116
|
-
Only extract facts that will be useful in future conversations, such as identity, stable preferences, ongoing projects, skills, and constraints.
|
|
117
|
-
|
|
118
|
-
Do not extract sensitive attributes, temporary things, or single-use instructions.
|
|
119
|
-
|
|
120
|
-
If there are no memories to extract, return: {"items": []}
|
|
121
|
-
|
|
122
|
-
Response format (JSON only, no other text):
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
"items": [
|
|
126
|
-
{
|
|
127
|
-
"type": "identity",
|
|
128
|
-
"namespace": "identity",
|
|
129
|
-
"key": "name",
|
|
130
|
-
"value": "Charlie",
|
|
131
|
-
"rawEvidence": "I'm Charlie",
|
|
132
|
-
"confidence": 0.98,
|
|
133
|
-
"pii": true
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
"type": "identity",
|
|
137
|
-
"namespace": "work",
|
|
138
|
-
"key": "company",
|
|
139
|
-
"value": "ZetaChain",
|
|
140
|
-
"rawEvidence": "called ZetaChain",
|
|
141
|
-
"confidence": 0.99,
|
|
142
|
-
"pii": false
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
"type": "preference",
|
|
146
|
-
"namespace": "answer_style",
|
|
147
|
-
"key": "verbosity",
|
|
148
|
-
"value": "concise_direct",
|
|
149
|
-
"rawEvidence": "I prefer concise, direct answers",
|
|
150
|
-
"confidence": 0.96,
|
|
151
|
-
"pii": false
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"type": "identity",
|
|
155
|
-
"namespace": "timezone",
|
|
156
|
-
"key": "tz",
|
|
157
|
-
"value": "America/Los_Angeles",
|
|
158
|
-
"rawEvidence": "I'm in PST",
|
|
159
|
-
"confidence": 0.9,
|
|
160
|
-
"pii": false
|
|
161
|
-
}
|
|
162
|
-
]
|
|
163
|
-
}`;
|
|
164
|
-
var preprocessMemories = (items, minConfidence = 0.6) => {
|
|
165
|
-
if (!items || !Array.isArray(items)) {
|
|
166
|
-
return [];
|
|
167
|
-
}
|
|
168
|
-
const validItems = items.filter((item) => {
|
|
169
|
-
if (item.namespace == null || item.key == null || item.value == null) {
|
|
170
|
-
console.warn(
|
|
171
|
-
"Dropping memory item with null/undefined namespace, key, or value:",
|
|
172
|
-
item
|
|
173
|
-
);
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
const namespace = String(item.namespace).trim();
|
|
177
|
-
const key = String(item.key).trim();
|
|
178
|
-
const value = String(item.value).trim();
|
|
179
|
-
if (namespace === "" || key === "" || value === "") {
|
|
180
|
-
console.warn(
|
|
181
|
-
"Dropping memory item with empty namespace, key, or value after trimming:",
|
|
182
|
-
item
|
|
183
|
-
);
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
if (typeof item.confidence !== "number" || item.confidence < minConfidence) {
|
|
187
|
-
console.warn(
|
|
188
|
-
`Dropping memory item with confidence ${item.confidence} below threshold ${minConfidence}:`,
|
|
189
|
-
item
|
|
190
|
-
);
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
return true;
|
|
194
|
-
});
|
|
195
|
-
const deduplicatedMap = /* @__PURE__ */ new Map();
|
|
196
|
-
for (const item of validItems) {
|
|
197
|
-
const uniqueKey = `${item.namespace}:${item.key}:${item.value}`;
|
|
198
|
-
const existing = deduplicatedMap.get(uniqueKey);
|
|
199
|
-
if (!existing || item.confidence > existing.confidence) {
|
|
200
|
-
deduplicatedMap.set(uniqueKey, item);
|
|
201
|
-
} else {
|
|
202
|
-
console.debug(
|
|
203
|
-
`Deduplicating memory item: keeping entry with higher confidence (${existing.confidence} > ${item.confidence})`,
|
|
204
|
-
{ namespace: item.namespace, key: item.key, value: item.value }
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return Array.from(deduplicatedMap.values());
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// src/lib/memory/db.ts
|
|
212
|
-
var import_dexie = __toESM(require("dexie"));
|
|
213
|
-
var MemoryDatabase = class extends import_dexie.default {
|
|
214
|
-
constructor() {
|
|
215
|
-
super("MemoryDatabase");
|
|
216
|
-
this.version(2).stores({
|
|
217
|
-
memories: "++id, uniqueKey, compositeKey, namespace, key, type, createdAt, updatedAt"
|
|
218
|
-
});
|
|
219
|
-
this.version(3).stores({
|
|
220
|
-
memories: "++id, uniqueKey, compositeKey, namespace, key, type, createdAt, updatedAt"
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
var memoryDb = new MemoryDatabase();
|
|
225
|
-
var saveMemory = async (memory) => {
|
|
226
|
-
const compositeKey = `${memory.namespace}:${memory.key}`;
|
|
227
|
-
const uniqueKey = `${memory.namespace}:${memory.key}:${memory.value}`;
|
|
228
|
-
const now = Date.now();
|
|
229
|
-
const existing = await memoryDb.memories.where("uniqueKey").equals(uniqueKey).first();
|
|
230
|
-
if (existing) {
|
|
231
|
-
const shouldPreserveEmbedding = existing.value === memory.value && existing.rawEvidence === memory.rawEvidence && existing.type === memory.type && existing.namespace === memory.namespace && existing.key === memory.key && existing.embedding !== void 0 && existing.embedding.length > 0;
|
|
232
|
-
const updateData = {
|
|
233
|
-
...memory,
|
|
234
|
-
compositeKey,
|
|
235
|
-
uniqueKey,
|
|
236
|
-
updatedAt: now,
|
|
237
|
-
createdAt: existing.createdAt
|
|
238
|
-
};
|
|
239
|
-
if (shouldPreserveEmbedding) {
|
|
240
|
-
updateData.embedding = existing.embedding;
|
|
241
|
-
updateData.embeddingModel = existing.embeddingModel;
|
|
242
|
-
} else {
|
|
243
|
-
updateData.embedding = [];
|
|
244
|
-
updateData.embeddingModel = void 0;
|
|
245
|
-
}
|
|
246
|
-
await memoryDb.memories.update(existing.id, updateData);
|
|
247
|
-
} else {
|
|
248
|
-
await memoryDb.memories.add({
|
|
249
|
-
...memory,
|
|
250
|
-
compositeKey,
|
|
251
|
-
uniqueKey,
|
|
252
|
-
createdAt: now,
|
|
253
|
-
updatedAt: now
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
var saveMemories = async (memories) => {
|
|
258
|
-
await Promise.all(memories.map((memory) => saveMemory(memory)));
|
|
259
|
-
};
|
|
260
|
-
var getAllMemories = async () => {
|
|
261
|
-
return memoryDb.memories.toArray();
|
|
262
|
-
};
|
|
263
|
-
var cosineSimilarity = (a, b) => {
|
|
264
|
-
if (a.length !== b.length) {
|
|
265
|
-
throw new Error("Vectors must have the same length");
|
|
266
|
-
}
|
|
267
|
-
let dotProduct = 0;
|
|
268
|
-
let normA = 0;
|
|
269
|
-
let normB = 0;
|
|
270
|
-
for (let i = 0; i < a.length; i++) {
|
|
271
|
-
dotProduct += a[i] * b[i];
|
|
272
|
-
normA += a[i] * a[i];
|
|
273
|
-
normB += b[i] * b[i];
|
|
274
|
-
}
|
|
275
|
-
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
276
|
-
if (denominator === 0) {
|
|
277
|
-
return 0;
|
|
278
|
-
}
|
|
279
|
-
return dotProduct / denominator;
|
|
280
|
-
};
|
|
281
|
-
var searchSimilarMemories = async (queryEmbedding, limit = 10, minSimilarity = 0.6) => {
|
|
282
|
-
const allMemories = await getAllMemories();
|
|
283
|
-
const memoriesWithEmbeddings = allMemories.filter(
|
|
284
|
-
(m) => m.embedding && m.embedding.length > 0
|
|
285
|
-
);
|
|
286
|
-
console.log(
|
|
287
|
-
`[Memory Search] Total memories: ${allMemories.length}, memories with embeddings: ${memoriesWithEmbeddings.length}`
|
|
288
|
-
);
|
|
289
|
-
if (memoriesWithEmbeddings.length === 0) {
|
|
290
|
-
console.warn(
|
|
291
|
-
"[Memory Search] No memories with embeddings found. Memories may need embeddings generated. Use generateAndStoreEmbeddings() to generate embeddings for existing memories."
|
|
292
|
-
);
|
|
293
|
-
return [];
|
|
294
|
-
}
|
|
295
|
-
const allResults = memoriesWithEmbeddings.map((memory) => {
|
|
296
|
-
const similarity = cosineSimilarity(queryEmbedding, memory.embedding);
|
|
297
|
-
return {
|
|
298
|
-
...memory,
|
|
299
|
-
similarity
|
|
300
|
-
};
|
|
301
|
-
}).sort((a, b) => b.similarity - a.similarity);
|
|
302
|
-
console.log(
|
|
303
|
-
`[Memory Search] All similarity scores:`,
|
|
304
|
-
allResults.map((r) => ({
|
|
305
|
-
key: `${r.namespace}:${r.key}`,
|
|
306
|
-
value: r.value,
|
|
307
|
-
similarity: r.similarity.toFixed(4)
|
|
308
|
-
}))
|
|
309
|
-
);
|
|
310
|
-
const results = allResults.filter((result) => result.similarity >= minSimilarity).slice(0, limit);
|
|
311
|
-
if (results.length === 0 && allResults.length > 0) {
|
|
312
|
-
const topSimilarity = allResults[0].similarity;
|
|
313
|
-
const suggestedThreshold = Math.max(0.3, topSimilarity - 0.1);
|
|
314
|
-
console.warn(
|
|
315
|
-
`[Memory Search] No memories above threshold ${minSimilarity}. Highest similarity was ${topSimilarity.toFixed(4)}. Consider lowering the threshold to ${suggestedThreshold.toFixed(2)}`
|
|
316
|
-
);
|
|
317
|
-
} else {
|
|
318
|
-
console.log(
|
|
319
|
-
`[Memory Search] Found ${results.length} memories above similarity threshold ${minSimilarity}. Top similarity: ${results[0]?.similarity.toFixed(4) || "N/A"}`
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
return results;
|
|
323
|
-
};
|
|
324
43
|
|
|
325
44
|
// src/client/core/bodySerializer.gen.ts
|
|
326
45
|
var jsonBodySerializer = {
|
|
@@ -1136,6 +855,376 @@ var createClientConfig = (config) => ({
|
|
|
1136
855
|
// src/client/client.gen.ts
|
|
1137
856
|
var client = createClient(createClientConfig(createConfig()));
|
|
1138
857
|
|
|
858
|
+
// src/react/useChat.ts
|
|
859
|
+
function useChat(options) {
|
|
860
|
+
const { getToken, onData: globalOnData, onFinish, onError } = options || {};
|
|
861
|
+
const [isLoading, setIsLoading] = (0, import_react.useState)(false);
|
|
862
|
+
const abortControllerRef = (0, import_react.useRef)(null);
|
|
863
|
+
const stop = (0, import_react.useCallback)(() => {
|
|
864
|
+
if (abortControllerRef.current) {
|
|
865
|
+
abortControllerRef.current.abort();
|
|
866
|
+
abortControllerRef.current = null;
|
|
867
|
+
}
|
|
868
|
+
}, []);
|
|
869
|
+
(0, import_react.useEffect)(() => {
|
|
870
|
+
return () => {
|
|
871
|
+
if (abortControllerRef.current) {
|
|
872
|
+
abortControllerRef.current.abort();
|
|
873
|
+
abortControllerRef.current = null;
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
}, []);
|
|
877
|
+
const sendMessage = (0, import_react.useCallback)(
|
|
878
|
+
async ({
|
|
879
|
+
messages,
|
|
880
|
+
model,
|
|
881
|
+
onData
|
|
882
|
+
}) => {
|
|
883
|
+
if (!messages?.length) {
|
|
884
|
+
const errorMsg = "messages are required to call sendMessage.";
|
|
885
|
+
if (onError) onError(new Error(errorMsg));
|
|
886
|
+
return { data: null, error: errorMsg };
|
|
887
|
+
}
|
|
888
|
+
if (!model) {
|
|
889
|
+
const errorMsg = "model is required to call sendMessage.";
|
|
890
|
+
if (onError) onError(new Error(errorMsg));
|
|
891
|
+
return { data: null, error: errorMsg };
|
|
892
|
+
}
|
|
893
|
+
if (!getToken) {
|
|
894
|
+
const errorMsg = "Token getter function is required.";
|
|
895
|
+
if (onError) onError(new Error(errorMsg));
|
|
896
|
+
return { data: null, error: errorMsg };
|
|
897
|
+
}
|
|
898
|
+
if (abortControllerRef.current) {
|
|
899
|
+
abortControllerRef.current.abort();
|
|
900
|
+
}
|
|
901
|
+
const abortController = new AbortController();
|
|
902
|
+
abortControllerRef.current = abortController;
|
|
903
|
+
setIsLoading(true);
|
|
904
|
+
try {
|
|
905
|
+
const token = await getToken();
|
|
906
|
+
if (!token) {
|
|
907
|
+
const errorMsg = "No access token available.";
|
|
908
|
+
setIsLoading(false);
|
|
909
|
+
if (onError) onError(new Error(errorMsg));
|
|
910
|
+
return { data: null, error: errorMsg };
|
|
911
|
+
}
|
|
912
|
+
const sseResult = await client.sse.post({
|
|
913
|
+
url: "/api/v1/chat/completions",
|
|
914
|
+
body: {
|
|
915
|
+
messages,
|
|
916
|
+
model,
|
|
917
|
+
stream: true
|
|
918
|
+
},
|
|
919
|
+
headers: {
|
|
920
|
+
"Content-Type": "application/json",
|
|
921
|
+
Authorization: `Bearer ${token}`
|
|
922
|
+
},
|
|
923
|
+
signal: abortController.signal
|
|
924
|
+
});
|
|
925
|
+
let accumulatedContent = "";
|
|
926
|
+
let completionId = "";
|
|
927
|
+
let completionModel = "";
|
|
928
|
+
let usage;
|
|
929
|
+
let finishReason;
|
|
930
|
+
for await (const chunk of sseResult.stream) {
|
|
931
|
+
if (typeof chunk === "string" && (chunk.trim() === "[DONE]" || chunk.includes("[DONE]"))) {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
if (chunk && typeof chunk === "object") {
|
|
935
|
+
const chunkData = chunk;
|
|
936
|
+
if (chunkData.id && !completionId) {
|
|
937
|
+
completionId = chunkData.id;
|
|
938
|
+
}
|
|
939
|
+
if (chunkData.model && !completionModel) {
|
|
940
|
+
completionModel = chunkData.model;
|
|
941
|
+
}
|
|
942
|
+
if (chunkData.usage) {
|
|
943
|
+
usage = chunkData.usage;
|
|
944
|
+
}
|
|
945
|
+
if (chunkData.choices && Array.isArray(chunkData.choices) && chunkData.choices.length > 0) {
|
|
946
|
+
const choice = chunkData.choices[0];
|
|
947
|
+
if (choice.delta?.content) {
|
|
948
|
+
const content = choice.delta.content;
|
|
949
|
+
accumulatedContent += content;
|
|
950
|
+
if (onData) {
|
|
951
|
+
onData(content);
|
|
952
|
+
}
|
|
953
|
+
if (globalOnData) {
|
|
954
|
+
globalOnData(content);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (choice.finish_reason) {
|
|
958
|
+
finishReason = choice.finish_reason;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const completion = {
|
|
964
|
+
id: completionId,
|
|
965
|
+
model: completionModel,
|
|
966
|
+
choices: [
|
|
967
|
+
{
|
|
968
|
+
index: 0,
|
|
969
|
+
message: {
|
|
970
|
+
role: "assistant",
|
|
971
|
+
content: accumulatedContent
|
|
972
|
+
},
|
|
973
|
+
finish_reason: finishReason
|
|
974
|
+
}
|
|
975
|
+
],
|
|
976
|
+
usage
|
|
977
|
+
};
|
|
978
|
+
setIsLoading(false);
|
|
979
|
+
if (onFinish) {
|
|
980
|
+
onFinish(completion);
|
|
981
|
+
}
|
|
982
|
+
return { data: completion, error: null };
|
|
983
|
+
} catch (err) {
|
|
984
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
985
|
+
setIsLoading(false);
|
|
986
|
+
return { data: null, error: "Request aborted" };
|
|
987
|
+
}
|
|
988
|
+
const errorMsg = err instanceof Error ? err.message : "Failed to send message.";
|
|
989
|
+
const errorObj = err instanceof Error ? err : new Error(errorMsg);
|
|
990
|
+
setIsLoading(false);
|
|
991
|
+
if (onError) {
|
|
992
|
+
onError(errorObj);
|
|
993
|
+
}
|
|
994
|
+
return { data: null, error: errorMsg };
|
|
995
|
+
} finally {
|
|
996
|
+
if (abortControllerRef.current === abortController) {
|
|
997
|
+
abortControllerRef.current = null;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
[getToken, globalOnData, onFinish, onError]
|
|
1002
|
+
);
|
|
1003
|
+
return {
|
|
1004
|
+
isLoading,
|
|
1005
|
+
sendMessage,
|
|
1006
|
+
stop
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/react/useMemory.ts
|
|
1011
|
+
var import_react2 = require("react");
|
|
1012
|
+
var import_client6 = require("@reverbia/sdk");
|
|
1013
|
+
|
|
1014
|
+
// src/lib/memory/service.ts
|
|
1015
|
+
var FACT_EXTRACTION_PROMPT = `You are a memory extraction system. Extract durable user memories from chat messages.
|
|
1016
|
+
|
|
1017
|
+
CRITICAL: You MUST respond with ONLY valid JSON. No explanations, no markdown, no code blocks, just pure JSON.
|
|
1018
|
+
|
|
1019
|
+
Only extract facts that will be useful in future conversations, such as identity, stable preferences, ongoing projects, skills, and constraints.
|
|
1020
|
+
|
|
1021
|
+
Do not extract sensitive attributes, temporary things, or single-use instructions.
|
|
1022
|
+
|
|
1023
|
+
If there are no memories to extract, return: {"items": []}
|
|
1024
|
+
|
|
1025
|
+
Response format (JSON only, no other text):
|
|
1026
|
+
|
|
1027
|
+
{
|
|
1028
|
+
"items": [
|
|
1029
|
+
{
|
|
1030
|
+
"type": "identity",
|
|
1031
|
+
"namespace": "identity",
|
|
1032
|
+
"key": "name",
|
|
1033
|
+
"value": "Charlie",
|
|
1034
|
+
"rawEvidence": "I'm Charlie",
|
|
1035
|
+
"confidence": 0.98,
|
|
1036
|
+
"pii": true
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
"type": "identity",
|
|
1040
|
+
"namespace": "work",
|
|
1041
|
+
"key": "company",
|
|
1042
|
+
"value": "ZetaChain",
|
|
1043
|
+
"rawEvidence": "called ZetaChain",
|
|
1044
|
+
"confidence": 0.99,
|
|
1045
|
+
"pii": false
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
"type": "preference",
|
|
1049
|
+
"namespace": "answer_style",
|
|
1050
|
+
"key": "verbosity",
|
|
1051
|
+
"value": "concise_direct",
|
|
1052
|
+
"rawEvidence": "I prefer concise, direct answers",
|
|
1053
|
+
"confidence": 0.96,
|
|
1054
|
+
"pii": false
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
"type": "identity",
|
|
1058
|
+
"namespace": "timezone",
|
|
1059
|
+
"key": "tz",
|
|
1060
|
+
"value": "America/Los_Angeles",
|
|
1061
|
+
"rawEvidence": "I'm in PST",
|
|
1062
|
+
"confidence": 0.9,
|
|
1063
|
+
"pii": false
|
|
1064
|
+
}
|
|
1065
|
+
]
|
|
1066
|
+
}`;
|
|
1067
|
+
var preprocessMemories = (items, minConfidence = 0.6) => {
|
|
1068
|
+
if (!items || !Array.isArray(items)) {
|
|
1069
|
+
return [];
|
|
1070
|
+
}
|
|
1071
|
+
const validItems = items.filter((item) => {
|
|
1072
|
+
if (item.namespace == null || item.key == null || item.value == null) {
|
|
1073
|
+
console.warn(
|
|
1074
|
+
"Dropping memory item with null/undefined namespace, key, or value:",
|
|
1075
|
+
item
|
|
1076
|
+
);
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
const namespace = String(item.namespace).trim();
|
|
1080
|
+
const key = String(item.key).trim();
|
|
1081
|
+
const value = String(item.value).trim();
|
|
1082
|
+
if (namespace === "" || key === "" || value === "") {
|
|
1083
|
+
console.warn(
|
|
1084
|
+
"Dropping memory item with empty namespace, key, or value after trimming:",
|
|
1085
|
+
item
|
|
1086
|
+
);
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
if (typeof item.confidence !== "number" || item.confidence < minConfidence) {
|
|
1090
|
+
console.warn(
|
|
1091
|
+
`Dropping memory item with confidence ${item.confidence} below threshold ${minConfidence}:`,
|
|
1092
|
+
item
|
|
1093
|
+
);
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
return true;
|
|
1097
|
+
});
|
|
1098
|
+
const deduplicatedMap = /* @__PURE__ */ new Map();
|
|
1099
|
+
for (const item of validItems) {
|
|
1100
|
+
const uniqueKey = `${item.namespace}:${item.key}:${item.value}`;
|
|
1101
|
+
const existing = deduplicatedMap.get(uniqueKey);
|
|
1102
|
+
if (!existing || item.confidence > existing.confidence) {
|
|
1103
|
+
deduplicatedMap.set(uniqueKey, item);
|
|
1104
|
+
} else {
|
|
1105
|
+
console.debug(
|
|
1106
|
+
`Deduplicating memory item: keeping entry with higher confidence (${existing.confidence} > ${item.confidence})`,
|
|
1107
|
+
{ namespace: item.namespace, key: item.key, value: item.value }
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return Array.from(deduplicatedMap.values());
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
// src/lib/memory/db.ts
|
|
1115
|
+
var import_dexie = __toESM(require("dexie"));
|
|
1116
|
+
var MemoryDatabase = class extends import_dexie.default {
|
|
1117
|
+
constructor() {
|
|
1118
|
+
super("MemoryDatabase");
|
|
1119
|
+
this.version(2).stores({
|
|
1120
|
+
memories: "++id, uniqueKey, compositeKey, namespace, key, type, createdAt, updatedAt"
|
|
1121
|
+
});
|
|
1122
|
+
this.version(3).stores({
|
|
1123
|
+
memories: "++id, uniqueKey, compositeKey, namespace, key, type, createdAt, updatedAt"
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
var memoryDb = new MemoryDatabase();
|
|
1128
|
+
var saveMemory = async (memory) => {
|
|
1129
|
+
const compositeKey = `${memory.namespace}:${memory.key}`;
|
|
1130
|
+
const uniqueKey = `${memory.namespace}:${memory.key}:${memory.value}`;
|
|
1131
|
+
const now = Date.now();
|
|
1132
|
+
const existing = await memoryDb.memories.where("uniqueKey").equals(uniqueKey).first();
|
|
1133
|
+
if (existing) {
|
|
1134
|
+
const shouldPreserveEmbedding = existing.value === memory.value && existing.rawEvidence === memory.rawEvidence && existing.type === memory.type && existing.namespace === memory.namespace && existing.key === memory.key && existing.embedding !== void 0 && existing.embedding.length > 0;
|
|
1135
|
+
const updateData = {
|
|
1136
|
+
...memory,
|
|
1137
|
+
compositeKey,
|
|
1138
|
+
uniqueKey,
|
|
1139
|
+
updatedAt: now,
|
|
1140
|
+
createdAt: existing.createdAt
|
|
1141
|
+
};
|
|
1142
|
+
if (shouldPreserveEmbedding) {
|
|
1143
|
+
updateData.embedding = existing.embedding;
|
|
1144
|
+
updateData.embeddingModel = existing.embeddingModel;
|
|
1145
|
+
} else {
|
|
1146
|
+
updateData.embedding = [];
|
|
1147
|
+
updateData.embeddingModel = void 0;
|
|
1148
|
+
}
|
|
1149
|
+
await memoryDb.memories.update(existing.id, updateData);
|
|
1150
|
+
} else {
|
|
1151
|
+
await memoryDb.memories.add({
|
|
1152
|
+
...memory,
|
|
1153
|
+
compositeKey,
|
|
1154
|
+
uniqueKey,
|
|
1155
|
+
createdAt: now,
|
|
1156
|
+
updatedAt: now
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
var saveMemories = async (memories) => {
|
|
1161
|
+
await Promise.all(memories.map((memory) => saveMemory(memory)));
|
|
1162
|
+
};
|
|
1163
|
+
var getAllMemories = async () => {
|
|
1164
|
+
return memoryDb.memories.toArray();
|
|
1165
|
+
};
|
|
1166
|
+
var cosineSimilarity = (a, b) => {
|
|
1167
|
+
if (a.length !== b.length) {
|
|
1168
|
+
throw new Error("Vectors must have the same length");
|
|
1169
|
+
}
|
|
1170
|
+
let dotProduct = 0;
|
|
1171
|
+
let normA = 0;
|
|
1172
|
+
let normB = 0;
|
|
1173
|
+
for (let i = 0; i < a.length; i++) {
|
|
1174
|
+
dotProduct += a[i] * b[i];
|
|
1175
|
+
normA += a[i] * a[i];
|
|
1176
|
+
normB += b[i] * b[i];
|
|
1177
|
+
}
|
|
1178
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1179
|
+
if (denominator === 0) {
|
|
1180
|
+
return 0;
|
|
1181
|
+
}
|
|
1182
|
+
return dotProduct / denominator;
|
|
1183
|
+
};
|
|
1184
|
+
var searchSimilarMemories = async (queryEmbedding, limit = 10, minSimilarity = 0.6) => {
|
|
1185
|
+
const allMemories = await getAllMemories();
|
|
1186
|
+
const memoriesWithEmbeddings = allMemories.filter(
|
|
1187
|
+
(m) => m.embedding && m.embedding.length > 0
|
|
1188
|
+
);
|
|
1189
|
+
console.log(
|
|
1190
|
+
`[Memory Search] Total memories: ${allMemories.length}, memories with embeddings: ${memoriesWithEmbeddings.length}`
|
|
1191
|
+
);
|
|
1192
|
+
if (memoriesWithEmbeddings.length === 0) {
|
|
1193
|
+
console.warn(
|
|
1194
|
+
"[Memory Search] No memories with embeddings found. Memories may need embeddings generated. Use generateAndStoreEmbeddings() to generate embeddings for existing memories."
|
|
1195
|
+
);
|
|
1196
|
+
return [];
|
|
1197
|
+
}
|
|
1198
|
+
const allResults = memoriesWithEmbeddings.map((memory) => {
|
|
1199
|
+
const similarity = cosineSimilarity(queryEmbedding, memory.embedding);
|
|
1200
|
+
return {
|
|
1201
|
+
...memory,
|
|
1202
|
+
similarity
|
|
1203
|
+
};
|
|
1204
|
+
}).sort((a, b) => b.similarity - a.similarity);
|
|
1205
|
+
console.log(
|
|
1206
|
+
`[Memory Search] All similarity scores:`,
|
|
1207
|
+
allResults.map((r) => ({
|
|
1208
|
+
key: `${r.namespace}:${r.key}`,
|
|
1209
|
+
value: r.value,
|
|
1210
|
+
similarity: r.similarity.toFixed(4)
|
|
1211
|
+
}))
|
|
1212
|
+
);
|
|
1213
|
+
const results = allResults.filter((result) => result.similarity >= minSimilarity).slice(0, limit);
|
|
1214
|
+
if (results.length === 0 && allResults.length > 0) {
|
|
1215
|
+
const topSimilarity = allResults[0].similarity;
|
|
1216
|
+
const suggestedThreshold = Math.max(0.3, topSimilarity - 0.1);
|
|
1217
|
+
console.warn(
|
|
1218
|
+
`[Memory Search] No memories above threshold ${minSimilarity}. Highest similarity was ${topSimilarity.toFixed(4)}. Consider lowering the threshold to ${suggestedThreshold.toFixed(2)}`
|
|
1219
|
+
);
|
|
1220
|
+
} else {
|
|
1221
|
+
console.log(
|
|
1222
|
+
`[Memory Search] Found ${results.length} memories above similarity threshold ${minSimilarity}. Top similarity: ${results[0]?.similarity.toFixed(4) || "N/A"}`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
return results;
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1139
1228
|
// src/client/sdk.gen.ts
|
|
1140
1229
|
var postApiV1Embeddings = (options) => {
|
|
1141
1230
|
return (options.client ?? client).post({
|
|
@@ -1161,8 +1250,8 @@ var generateEmbeddingForText = async (text, options = {}) => {
|
|
|
1161
1250
|
body: {
|
|
1162
1251
|
input: text,
|
|
1163
1252
|
model
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1253
|
+
},
|
|
1254
|
+
headers
|
|
1166
1255
|
});
|
|
1167
1256
|
if (!response.data || !response.data.data || response.data.data.length === 0) {
|
|
1168
1257
|
throw new Error(
|
|
@@ -1276,10 +1365,10 @@ function useMemory(options = {}) {
|
|
|
1276
1365
|
...messages
|
|
1277
1366
|
],
|
|
1278
1367
|
model: model || memoryModel
|
|
1368
|
+
},
|
|
1369
|
+
headers: {
|
|
1370
|
+
Authorization: `Bearer ${token}`
|
|
1279
1371
|
}
|
|
1280
|
-
// headers: {
|
|
1281
|
-
// Authorization: `Bearer ${token}`,
|
|
1282
|
-
// },
|
|
1283
1372
|
});
|
|
1284
1373
|
if (!completion.data) {
|
|
1285
1374
|
console.error(
|