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