binario 0.1.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/LICENSE +21 -0
- package/README.md +1140 -0
- package/dist/cloudflare.cjs +437 -0
- package/dist/cloudflare.cjs.map +1 -0
- package/dist/cloudflare.d.cts +293 -0
- package/dist/cloudflare.d.ts +293 -0
- package/dist/cloudflare.js +421 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/index.cjs +2476 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1104 -0
- package/dist/index.d.ts +1104 -0
- package/dist/index.js +2427 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.cjs +957 -0
- package/dist/memory.cjs.map +1 -0
- package/dist/memory.d.cts +542 -0
- package/dist/memory.d.ts +542 -0
- package/dist/memory.js +931 -0
- package/dist/memory.js.map +1 -0
- package/dist/react.cjs +1836 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +555 -0
- package/dist/react.d.ts +555 -0
- package/dist/react.js +1825 -0
- package/dist/react.js.map +1 -0
- package/package.json +104 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,1825 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// src/hooks.ts
|
|
5
|
+
function zodToJsonSchema(schema) {
|
|
6
|
+
return convertZodToJson(schema);
|
|
7
|
+
}
|
|
8
|
+
function convertZodToJson(schema) {
|
|
9
|
+
const def = schema._def;
|
|
10
|
+
const result = {};
|
|
11
|
+
const desc = def && def.description || schema.description;
|
|
12
|
+
if (desc && typeof desc === "string") {
|
|
13
|
+
result.description = desc;
|
|
14
|
+
}
|
|
15
|
+
if (schema instanceof z.ZodString) {
|
|
16
|
+
result.type = "string";
|
|
17
|
+
} else if (schema instanceof z.ZodNumber) {
|
|
18
|
+
result.type = "number";
|
|
19
|
+
} else if (schema instanceof z.ZodBoolean) {
|
|
20
|
+
result.type = "boolean";
|
|
21
|
+
} else if (schema instanceof z.ZodArray) {
|
|
22
|
+
result.type = "array";
|
|
23
|
+
const innerType = def.type || def.element || schema._def?.type || schema.element;
|
|
24
|
+
if (innerType) {
|
|
25
|
+
result.items = convertZodToJson(innerType);
|
|
26
|
+
}
|
|
27
|
+
} else if (schema instanceof z.ZodObject) {
|
|
28
|
+
result.type = "object";
|
|
29
|
+
const shape = schema.shape;
|
|
30
|
+
const properties = {};
|
|
31
|
+
const required = [];
|
|
32
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
33
|
+
properties[key] = convertZodToJson(value);
|
|
34
|
+
if (!(value instanceof z.ZodOptional)) {
|
|
35
|
+
required.push(key);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
result.properties = properties;
|
|
39
|
+
if (required.length > 0) {
|
|
40
|
+
result.required = required;
|
|
41
|
+
}
|
|
42
|
+
} else if (schema instanceof z.ZodEnum) {
|
|
43
|
+
result.type = "string";
|
|
44
|
+
result.enum = def.values || schema.options || schema._def?.values;
|
|
45
|
+
} else if (schema instanceof z.ZodOptional) {
|
|
46
|
+
return convertZodToJson(schema._def.innerType);
|
|
47
|
+
} else if (schema instanceof z.ZodNullable) {
|
|
48
|
+
const inner = convertZodToJson(schema._def.innerType);
|
|
49
|
+
return { ...inner, nullable: true };
|
|
50
|
+
} else if (schema instanceof z.ZodDefault) {
|
|
51
|
+
const defaultDef = schema._def;
|
|
52
|
+
const inner = convertZodToJson(defaultDef.innerType);
|
|
53
|
+
return { ...inner, default: typeof defaultDef.defaultValue === "function" ? defaultDef.defaultValue() : defaultDef.defaultValue };
|
|
54
|
+
} else if (schema instanceof z.ZodUnion) {
|
|
55
|
+
result.oneOf = schema._def.options.map(convertZodToJson);
|
|
56
|
+
} else if (schema instanceof z.ZodLiteral) {
|
|
57
|
+
const litDef = schema._def;
|
|
58
|
+
result.const = litDef.value ?? litDef.values;
|
|
59
|
+
} else if (schema instanceof z.ZodRecord) {
|
|
60
|
+
result.type = "object";
|
|
61
|
+
result.additionalProperties = convertZodToJson(schema._def.valueType);
|
|
62
|
+
} else if (schema instanceof z.ZodTuple) {
|
|
63
|
+
result.type = "array";
|
|
64
|
+
result.items = schema._def.items.map(convertZodToJson);
|
|
65
|
+
} else {
|
|
66
|
+
result.type = "string";
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/memory/utils.ts
|
|
72
|
+
function countTokens(text) {
|
|
73
|
+
if (!text) return 0;
|
|
74
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
75
|
+
const avgCharsPerWord = text.length / Math.max(words.length, 1);
|
|
76
|
+
const ratio = avgCharsPerWord > 6 ? 3.5 : 4;
|
|
77
|
+
return Math.ceil(text.length / ratio);
|
|
78
|
+
}
|
|
79
|
+
function countMessageTokens(message) {
|
|
80
|
+
let tokens = 0;
|
|
81
|
+
tokens += 4;
|
|
82
|
+
tokens += countTokens(message.content);
|
|
83
|
+
if (message.name) {
|
|
84
|
+
tokens += countTokens(message.name) + 1;
|
|
85
|
+
}
|
|
86
|
+
if (message.tool_calls) {
|
|
87
|
+
for (const call of message.tool_calls) {
|
|
88
|
+
tokens += countTokens(call.function.name);
|
|
89
|
+
tokens += countTokens(call.function.arguments);
|
|
90
|
+
tokens += 10;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return tokens;
|
|
94
|
+
}
|
|
95
|
+
function countMessagesTokens(messages) {
|
|
96
|
+
return messages.reduce((sum, msg) => sum + countMessageTokens(msg), 0);
|
|
97
|
+
}
|
|
98
|
+
function generateId() {
|
|
99
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
100
|
+
}
|
|
101
|
+
function createStoredMessage(message, conversationId, options) {
|
|
102
|
+
return {
|
|
103
|
+
id: generateId(),
|
|
104
|
+
message,
|
|
105
|
+
timestamp: Date.now(),
|
|
106
|
+
conversationId,
|
|
107
|
+
tokenCount: countMessageTokens(message),
|
|
108
|
+
embedding: options?.embedding,
|
|
109
|
+
metadata: options?.metadata
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function truncateMessages(messages, maxTokens, options) {
|
|
113
|
+
const { keepSystemMessages = true } = options ?? {};
|
|
114
|
+
const systemMessages = keepSystemMessages ? messages.filter((m) => m.role === "system") : [];
|
|
115
|
+
const nonSystemMessages = keepSystemMessages ? messages.filter((m) => m.role !== "system") : [...messages];
|
|
116
|
+
const systemTokens = countMessagesTokens(systemMessages);
|
|
117
|
+
const remainingTokens = maxTokens - systemTokens;
|
|
118
|
+
if (remainingTokens <= 0) {
|
|
119
|
+
return truncateMessagesSimple(messages, maxTokens);
|
|
120
|
+
}
|
|
121
|
+
const truncated = truncateMessagesSimple(nonSystemMessages, remainingTokens);
|
|
122
|
+
return [...systemMessages, ...truncated];
|
|
123
|
+
}
|
|
124
|
+
function truncateMessagesSimple(messages, maxTokens) {
|
|
125
|
+
let totalTokens = 0;
|
|
126
|
+
const result = [];
|
|
127
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
128
|
+
const msgTokens = countMessageTokens(messages[i]);
|
|
129
|
+
if (totalTokens + msgTokens > maxTokens) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
totalTokens += msgTokens;
|
|
133
|
+
result.unshift(messages[i]);
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
function truncateMessagesByCount(messages, maxMessages, options) {
|
|
138
|
+
const { keepSystemMessages = true } = options ?? {};
|
|
139
|
+
if (messages.length <= maxMessages) {
|
|
140
|
+
return messages;
|
|
141
|
+
}
|
|
142
|
+
if (keepSystemMessages) {
|
|
143
|
+
const systemMessages = messages.filter((m) => m.role === "system");
|
|
144
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
145
|
+
const remainingSlots = maxMessages - systemMessages.length;
|
|
146
|
+
if (remainingSlots <= 0) {
|
|
147
|
+
return systemMessages.slice(-maxMessages);
|
|
148
|
+
}
|
|
149
|
+
return [...systemMessages, ...nonSystemMessages.slice(-remainingSlots)];
|
|
150
|
+
}
|
|
151
|
+
return messages.slice(-maxMessages);
|
|
152
|
+
}
|
|
153
|
+
function createContext(messages, summary) {
|
|
154
|
+
return {
|
|
155
|
+
messages,
|
|
156
|
+
summary: summary ?? void 0,
|
|
157
|
+
tokenCount: countMessagesTokens(messages),
|
|
158
|
+
messageCount: messages.length
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function cosineSimilarity(a, b) {
|
|
162
|
+
if (a.length !== b.length) {
|
|
163
|
+
throw new Error("Vectors must have the same length");
|
|
164
|
+
}
|
|
165
|
+
let dotProduct = 0;
|
|
166
|
+
let normA = 0;
|
|
167
|
+
let normB = 0;
|
|
168
|
+
for (let i = 0; i < a.length; i++) {
|
|
169
|
+
dotProduct += a[i] * b[i];
|
|
170
|
+
normA += a[i] * a[i];
|
|
171
|
+
normB += b[i] * b[i];
|
|
172
|
+
}
|
|
173
|
+
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
|
174
|
+
if (magnitude === 0) return 0;
|
|
175
|
+
return dotProduct / magnitude;
|
|
176
|
+
}
|
|
177
|
+
var DEFAULT_SUMMARY_PROMPT = `Summarize the following conversation concisely, preserving key information, decisions, and context that would be useful for continuing the conversation. Focus on:
|
|
178
|
+
- Main topics discussed
|
|
179
|
+
- Key decisions or conclusions
|
|
180
|
+
- Important user preferences or requirements
|
|
181
|
+
- Any pending questions or tasks
|
|
182
|
+
|
|
183
|
+
Conversation:
|
|
184
|
+
{conversation}
|
|
185
|
+
|
|
186
|
+
Summary:`;
|
|
187
|
+
function formatConversationForSummary(messages) {
|
|
188
|
+
return messages.filter((m) => m.role !== "system").map((m) => `${m.role.toUpperCase()}: ${m.content}`).join("\n\n");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/memory/stores/in-memory.ts
|
|
192
|
+
var InMemoryStore = class {
|
|
193
|
+
messages = /* @__PURE__ */ new Map();
|
|
194
|
+
metadata = /* @__PURE__ */ new Map();
|
|
195
|
+
async getMessages(conversationId) {
|
|
196
|
+
return this.messages.get(conversationId) ?? [];
|
|
197
|
+
}
|
|
198
|
+
async addMessage(message) {
|
|
199
|
+
const messages = this.messages.get(message.conversationId) ?? [];
|
|
200
|
+
messages.push(message);
|
|
201
|
+
this.messages.set(message.conversationId, messages);
|
|
202
|
+
}
|
|
203
|
+
async updateMessage(id, updates) {
|
|
204
|
+
for (const [conversationId, messages] of this.messages) {
|
|
205
|
+
const index = messages.findIndex((m) => m.id === id);
|
|
206
|
+
if (index !== -1) {
|
|
207
|
+
messages[index] = { ...messages[index], ...updates };
|
|
208
|
+
this.messages.set(conversationId, messages);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async deleteMessage(id) {
|
|
214
|
+
for (const [conversationId, messages] of this.messages) {
|
|
215
|
+
const filtered = messages.filter((m) => m.id !== id);
|
|
216
|
+
if (filtered.length !== messages.length) {
|
|
217
|
+
this.messages.set(conversationId, filtered);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async clear(conversationId) {
|
|
223
|
+
this.messages.delete(conversationId);
|
|
224
|
+
this.metadata.delete(conversationId);
|
|
225
|
+
}
|
|
226
|
+
async getMetadata(conversationId) {
|
|
227
|
+
return this.metadata.get(conversationId) ?? null;
|
|
228
|
+
}
|
|
229
|
+
async setMetadata(conversationId, metadata) {
|
|
230
|
+
this.metadata.set(conversationId, metadata);
|
|
231
|
+
}
|
|
232
|
+
/** Clear all data (useful for testing) */
|
|
233
|
+
clearAll() {
|
|
234
|
+
this.messages.clear();
|
|
235
|
+
this.metadata.clear();
|
|
236
|
+
}
|
|
237
|
+
/** Get all conversation IDs */
|
|
238
|
+
getConversationIds() {
|
|
239
|
+
return Array.from(this.messages.keys());
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/memory/buffer.ts
|
|
244
|
+
var DEFAULT_MAX_MESSAGES = 50;
|
|
245
|
+
var DEFAULT_MAX_TOKENS = 4e3;
|
|
246
|
+
var BufferMemory = class {
|
|
247
|
+
type = "buffer";
|
|
248
|
+
store;
|
|
249
|
+
conversationId;
|
|
250
|
+
maxMessages;
|
|
251
|
+
maxTokens;
|
|
252
|
+
includeSystemMessages;
|
|
253
|
+
strategy;
|
|
254
|
+
constructor(options = {}) {
|
|
255
|
+
this.store = options.store ?? new InMemoryStore();
|
|
256
|
+
this.conversationId = options.conversationId ?? generateId();
|
|
257
|
+
this.maxMessages = options.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
258
|
+
this.maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
259
|
+
this.includeSystemMessages = options.includeSystemMessages ?? true;
|
|
260
|
+
this.strategy = options.strategy ?? "sliding";
|
|
261
|
+
}
|
|
262
|
+
async add(message) {
|
|
263
|
+
const stored = createStoredMessage(message, this.conversationId);
|
|
264
|
+
await this.store.addMessage(stored);
|
|
265
|
+
await this.trim();
|
|
266
|
+
}
|
|
267
|
+
async addMany(messages) {
|
|
268
|
+
for (const message of messages) {
|
|
269
|
+
const stored = createStoredMessage(message, this.conversationId);
|
|
270
|
+
await this.store.addMessage(stored);
|
|
271
|
+
}
|
|
272
|
+
await this.trim();
|
|
273
|
+
}
|
|
274
|
+
async getMessages() {
|
|
275
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
276
|
+
return this.extractMessages(stored);
|
|
277
|
+
}
|
|
278
|
+
async getContext() {
|
|
279
|
+
const messages = await this.getMessages();
|
|
280
|
+
return createContext(messages);
|
|
281
|
+
}
|
|
282
|
+
async getContextWindow(maxTokens) {
|
|
283
|
+
const messages = await this.getMessages();
|
|
284
|
+
const truncated = truncateMessages(messages, maxTokens, {
|
|
285
|
+
keepSystemMessages: this.includeSystemMessages
|
|
286
|
+
});
|
|
287
|
+
return createContext(truncated);
|
|
288
|
+
}
|
|
289
|
+
async clear() {
|
|
290
|
+
await this.store.clear(this.conversationId);
|
|
291
|
+
}
|
|
292
|
+
async getMessageCount() {
|
|
293
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
294
|
+
return stored.length;
|
|
295
|
+
}
|
|
296
|
+
async getTokenCount() {
|
|
297
|
+
const messages = await this.getMessages();
|
|
298
|
+
return countMessagesTokens(messages);
|
|
299
|
+
}
|
|
300
|
+
/** Get the conversation ID */
|
|
301
|
+
getConversationId() {
|
|
302
|
+
return this.conversationId;
|
|
303
|
+
}
|
|
304
|
+
/** Switch to a different conversation */
|
|
305
|
+
setConversationId(id) {
|
|
306
|
+
this.conversationId = id;
|
|
307
|
+
}
|
|
308
|
+
extractMessages(stored) {
|
|
309
|
+
return stored.sort((a, b) => a.timestamp - b.timestamp).map((s) => s.message);
|
|
310
|
+
}
|
|
311
|
+
async trim() {
|
|
312
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
313
|
+
const messages = this.extractMessages(stored);
|
|
314
|
+
let trimmed = truncateMessagesByCount(messages, this.maxMessages, {
|
|
315
|
+
keepSystemMessages: this.includeSystemMessages
|
|
316
|
+
});
|
|
317
|
+
trimmed = truncateMessages(trimmed, this.maxTokens, {
|
|
318
|
+
keepSystemMessages: this.includeSystemMessages
|
|
319
|
+
});
|
|
320
|
+
if (trimmed.length < messages.length) {
|
|
321
|
+
await this.store.clear(this.conversationId);
|
|
322
|
+
for (const message of trimmed) {
|
|
323
|
+
const storedMsg = createStoredMessage(message, this.conversationId);
|
|
324
|
+
await this.store.addMessage(storedMsg);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/memory/summary.ts
|
|
331
|
+
var DEFAULT_SUMMARIZE_THRESHOLD = 2e3;
|
|
332
|
+
var DEFAULT_SUMMARY_MODEL = "google/gemini-2.5-flash";
|
|
333
|
+
var SummaryMemory = class {
|
|
334
|
+
type = "summary";
|
|
335
|
+
store;
|
|
336
|
+
conversationId;
|
|
337
|
+
summarizeThreshold;
|
|
338
|
+
summaryModel;
|
|
339
|
+
summaryPrompt;
|
|
340
|
+
summarizer;
|
|
341
|
+
includeSystemMessages;
|
|
342
|
+
constructor(options = {}) {
|
|
343
|
+
this.store = options.store ?? new InMemoryStore();
|
|
344
|
+
this.conversationId = options.conversationId ?? generateId();
|
|
345
|
+
this.summarizeThreshold = options.summarizeThreshold ?? DEFAULT_SUMMARIZE_THRESHOLD;
|
|
346
|
+
this.summaryModel = options.summaryModel ?? DEFAULT_SUMMARY_MODEL;
|
|
347
|
+
this.summaryPrompt = options.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT;
|
|
348
|
+
this.summarizer = options.summarizer;
|
|
349
|
+
this.includeSystemMessages = options.includeSystemMessages ?? true;
|
|
350
|
+
}
|
|
351
|
+
/** Set the summarizer function */
|
|
352
|
+
setSummarizer(fn) {
|
|
353
|
+
this.summarizer = fn;
|
|
354
|
+
}
|
|
355
|
+
async add(message) {
|
|
356
|
+
const stored = createStoredMessage(message, this.conversationId);
|
|
357
|
+
await this.store.addMessage(stored);
|
|
358
|
+
await this.checkAndSummarize();
|
|
359
|
+
}
|
|
360
|
+
async addMany(messages) {
|
|
361
|
+
for (const message of messages) {
|
|
362
|
+
const stored = createStoredMessage(message, this.conversationId);
|
|
363
|
+
await this.store.addMessage(stored);
|
|
364
|
+
}
|
|
365
|
+
await this.checkAndSummarize();
|
|
366
|
+
}
|
|
367
|
+
async getMessages() {
|
|
368
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
369
|
+
const messages = this.extractMessages(stored);
|
|
370
|
+
const summary = await this.getSummary();
|
|
371
|
+
if (summary) {
|
|
372
|
+
const summaryMsg = {
|
|
373
|
+
role: "system",
|
|
374
|
+
content: `Previous conversation summary:
|
|
375
|
+
${summary}`
|
|
376
|
+
};
|
|
377
|
+
const filtered = messages.filter(
|
|
378
|
+
(m) => !(m.role === "system" && m.content.startsWith("Previous conversation summary:"))
|
|
379
|
+
);
|
|
380
|
+
return [summaryMsg, ...filtered];
|
|
381
|
+
}
|
|
382
|
+
return messages;
|
|
383
|
+
}
|
|
384
|
+
async getContext() {
|
|
385
|
+
const messages = await this.getMessages();
|
|
386
|
+
const summary = await this.getSummary();
|
|
387
|
+
return createContext(messages, summary);
|
|
388
|
+
}
|
|
389
|
+
async getContextWindow(maxTokens) {
|
|
390
|
+
const messages = await this.getMessages();
|
|
391
|
+
const summary = await this.getSummary();
|
|
392
|
+
const truncated = truncateMessages(messages, maxTokens, {
|
|
393
|
+
keepSystemMessages: this.includeSystemMessages
|
|
394
|
+
});
|
|
395
|
+
return createContext(truncated, summary);
|
|
396
|
+
}
|
|
397
|
+
async clear() {
|
|
398
|
+
await this.store.clear(this.conversationId);
|
|
399
|
+
}
|
|
400
|
+
async getMessageCount() {
|
|
401
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
402
|
+
return stored.length;
|
|
403
|
+
}
|
|
404
|
+
async getTokenCount() {
|
|
405
|
+
const messages = await this.getMessages();
|
|
406
|
+
return countMessagesTokens(messages);
|
|
407
|
+
}
|
|
408
|
+
async getSummary() {
|
|
409
|
+
const metadata = await this.store.getMetadata(this.conversationId);
|
|
410
|
+
return metadata?.summary ?? null;
|
|
411
|
+
}
|
|
412
|
+
async summarize() {
|
|
413
|
+
if (!this.summarizer) {
|
|
414
|
+
throw new Error("No summarizer function provided. Set one with setSummarizer()");
|
|
415
|
+
}
|
|
416
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
417
|
+
const messages = this.extractMessages(stored);
|
|
418
|
+
if (messages.length === 0) {
|
|
419
|
+
return "";
|
|
420
|
+
}
|
|
421
|
+
const conversationText = formatConversationForSummary(messages);
|
|
422
|
+
const prompt = this.summaryPrompt.replace("{conversation}", conversationText);
|
|
423
|
+
const summary = await this.summarizer(messages, prompt);
|
|
424
|
+
await this.store.setMetadata(this.conversationId, { summary });
|
|
425
|
+
return summary;
|
|
426
|
+
}
|
|
427
|
+
/** Get the conversation ID */
|
|
428
|
+
getConversationId() {
|
|
429
|
+
return this.conversationId;
|
|
430
|
+
}
|
|
431
|
+
/** Switch to a different conversation */
|
|
432
|
+
setConversationId(id) {
|
|
433
|
+
this.conversationId = id;
|
|
434
|
+
}
|
|
435
|
+
extractMessages(stored) {
|
|
436
|
+
return stored.sort((a, b) => a.timestamp - b.timestamp).map((s) => s.message);
|
|
437
|
+
}
|
|
438
|
+
async checkAndSummarize() {
|
|
439
|
+
if (!this.summarizer) return;
|
|
440
|
+
const tokenCount = await this.getTokenCount();
|
|
441
|
+
if (tokenCount > this.summarizeThreshold) {
|
|
442
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
443
|
+
const messages = this.extractMessages(stored);
|
|
444
|
+
const keepCount = Math.max(4, Math.floor(messages.length / 4));
|
|
445
|
+
const toSummarize = messages.slice(0, -keepCount);
|
|
446
|
+
const toKeep = messages.slice(-keepCount);
|
|
447
|
+
if (toSummarize.length > 0) {
|
|
448
|
+
const existingSummary = await this.getSummary();
|
|
449
|
+
const messagesForSummary = existingSummary ? [{ role: "system", content: `Previous summary: ${existingSummary}` }, ...toSummarize] : toSummarize;
|
|
450
|
+
const conversationText = formatConversationForSummary(messagesForSummary);
|
|
451
|
+
const prompt = this.summaryPrompt.replace("{conversation}", conversationText);
|
|
452
|
+
const summary = await this.summarizer(messagesForSummary, prompt);
|
|
453
|
+
await this.store.clear(this.conversationId);
|
|
454
|
+
await this.store.setMetadata(this.conversationId, { summary });
|
|
455
|
+
for (const message of toKeep) {
|
|
456
|
+
const storedMsg = createStoredMessage(message, this.conversationId);
|
|
457
|
+
await this.store.addMessage(storedMsg);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// src/memory/summary-buffer.ts
|
|
465
|
+
var DEFAULT_BUFFER_SIZE = 10;
|
|
466
|
+
var DEFAULT_SUMMARIZE_THRESHOLD2 = 2e3;
|
|
467
|
+
var SummaryBufferMemory = class {
|
|
468
|
+
type = "summary-buffer";
|
|
469
|
+
store;
|
|
470
|
+
conversationId;
|
|
471
|
+
bufferSize;
|
|
472
|
+
summarizeThreshold;
|
|
473
|
+
summaryPrompt;
|
|
474
|
+
summarizer;
|
|
475
|
+
includeSystemMessages;
|
|
476
|
+
constructor(options = {}) {
|
|
477
|
+
this.store = options.store ?? new InMemoryStore();
|
|
478
|
+
this.conversationId = options.conversationId ?? generateId();
|
|
479
|
+
this.bufferSize = options.bufferSize ?? DEFAULT_BUFFER_SIZE;
|
|
480
|
+
this.summarizeThreshold = options.summarizeThreshold ?? DEFAULT_SUMMARIZE_THRESHOLD2;
|
|
481
|
+
this.summaryPrompt = options.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT;
|
|
482
|
+
this.summarizer = options.summarizer;
|
|
483
|
+
this.includeSystemMessages = options.includeSystemMessages ?? true;
|
|
484
|
+
}
|
|
485
|
+
/** Set the summarizer function */
|
|
486
|
+
setSummarizer(fn) {
|
|
487
|
+
this.summarizer = fn;
|
|
488
|
+
}
|
|
489
|
+
async add(message) {
|
|
490
|
+
const stored = createStoredMessage(message, this.conversationId);
|
|
491
|
+
await this.store.addMessage(stored);
|
|
492
|
+
await this.checkAndSummarize();
|
|
493
|
+
}
|
|
494
|
+
async addMany(messages) {
|
|
495
|
+
for (const message of messages) {
|
|
496
|
+
const stored = createStoredMessage(message, this.conversationId);
|
|
497
|
+
await this.store.addMessage(stored);
|
|
498
|
+
}
|
|
499
|
+
await this.checkAndSummarize();
|
|
500
|
+
}
|
|
501
|
+
async getMessages() {
|
|
502
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
503
|
+
const messages = this.extractMessages(stored);
|
|
504
|
+
const summary = await this.getSummary();
|
|
505
|
+
const buffer = messages.slice(-this.bufferSize);
|
|
506
|
+
if (summary) {
|
|
507
|
+
const summaryMsg = {
|
|
508
|
+
role: "system",
|
|
509
|
+
content: `Previous conversation summary:
|
|
510
|
+
${summary}`
|
|
511
|
+
};
|
|
512
|
+
const systemMessages = messages.slice(0, -this.bufferSize).filter((m) => m.role === "system" && !m.content.startsWith("Previous conversation summary:"));
|
|
513
|
+
return [...systemMessages, summaryMsg, ...buffer];
|
|
514
|
+
}
|
|
515
|
+
return messages;
|
|
516
|
+
}
|
|
517
|
+
async getContext() {
|
|
518
|
+
const messages = await this.getMessages();
|
|
519
|
+
const summary = await this.getSummary();
|
|
520
|
+
return createContext(messages, summary);
|
|
521
|
+
}
|
|
522
|
+
async getContextWindow(maxTokens) {
|
|
523
|
+
const messages = await this.getMessages();
|
|
524
|
+
const summary = await this.getSummary();
|
|
525
|
+
const truncated = truncateMessages(messages, maxTokens, {
|
|
526
|
+
keepSystemMessages: this.includeSystemMessages
|
|
527
|
+
});
|
|
528
|
+
return createContext(truncated, summary);
|
|
529
|
+
}
|
|
530
|
+
async clear() {
|
|
531
|
+
await this.store.clear(this.conversationId);
|
|
532
|
+
}
|
|
533
|
+
async getMessageCount() {
|
|
534
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
535
|
+
return stored.length;
|
|
536
|
+
}
|
|
537
|
+
async getTokenCount() {
|
|
538
|
+
const messages = await this.getMessages();
|
|
539
|
+
return countMessagesTokens(messages);
|
|
540
|
+
}
|
|
541
|
+
async getSummary() {
|
|
542
|
+
const metadata = await this.store.getMetadata(this.conversationId);
|
|
543
|
+
return metadata?.summary ?? null;
|
|
544
|
+
}
|
|
545
|
+
async summarize() {
|
|
546
|
+
if (!this.summarizer) {
|
|
547
|
+
throw new Error("No summarizer function provided. Set one with setSummarizer()");
|
|
548
|
+
}
|
|
549
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
550
|
+
const messages = this.extractMessages(stored);
|
|
551
|
+
const toSummarize = messages.slice(0, -this.bufferSize);
|
|
552
|
+
if (toSummarize.length === 0) {
|
|
553
|
+
return "";
|
|
554
|
+
}
|
|
555
|
+
const existingSummary = await this.getSummary();
|
|
556
|
+
const messagesForSummary = existingSummary ? [{ role: "system", content: `Previous summary: ${existingSummary}` }, ...toSummarize] : toSummarize;
|
|
557
|
+
const conversationText = formatConversationForSummary(messagesForSummary);
|
|
558
|
+
const prompt = this.summaryPrompt.replace("{conversation}", conversationText);
|
|
559
|
+
const summary = await this.summarizer(messagesForSummary, prompt);
|
|
560
|
+
await this.store.setMetadata(this.conversationId, { summary });
|
|
561
|
+
return summary;
|
|
562
|
+
}
|
|
563
|
+
/** Get buffer size */
|
|
564
|
+
getBufferSize() {
|
|
565
|
+
return this.bufferSize;
|
|
566
|
+
}
|
|
567
|
+
/** Get the conversation ID */
|
|
568
|
+
getConversationId() {
|
|
569
|
+
return this.conversationId;
|
|
570
|
+
}
|
|
571
|
+
/** Switch to a different conversation */
|
|
572
|
+
setConversationId(id) {
|
|
573
|
+
this.conversationId = id;
|
|
574
|
+
}
|
|
575
|
+
extractMessages(stored) {
|
|
576
|
+
return stored.sort((a, b) => a.timestamp - b.timestamp).map((s) => s.message);
|
|
577
|
+
}
|
|
578
|
+
async checkAndSummarize() {
|
|
579
|
+
if (!this.summarizer) return;
|
|
580
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
581
|
+
const messages = this.extractMessages(stored);
|
|
582
|
+
if (messages.length <= this.bufferSize) return;
|
|
583
|
+
const tokenCount = await this.getTokenCount();
|
|
584
|
+
if (tokenCount > this.summarizeThreshold) {
|
|
585
|
+
const buffer = messages.slice(-this.bufferSize);
|
|
586
|
+
const toSummarize = messages.slice(0, -this.bufferSize);
|
|
587
|
+
if (toSummarize.length > 0) {
|
|
588
|
+
const existingSummary = await this.getSummary();
|
|
589
|
+
const messagesForSummary = existingSummary ? [{ role: "system", content: `Previous summary: ${existingSummary}` }, ...toSummarize] : toSummarize;
|
|
590
|
+
const conversationText = formatConversationForSummary(messagesForSummary);
|
|
591
|
+
const prompt = this.summaryPrompt.replace("{conversation}", conversationText);
|
|
592
|
+
const summary = await this.summarizer(messagesForSummary, prompt);
|
|
593
|
+
await this.store.clear(this.conversationId);
|
|
594
|
+
await this.store.setMetadata(this.conversationId, { summary });
|
|
595
|
+
for (const message of buffer) {
|
|
596
|
+
const storedMsg = createStoredMessage(message, this.conversationId);
|
|
597
|
+
await this.store.addMessage(storedMsg);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// src/memory/vector.ts
|
|
605
|
+
var DEFAULT_TOP_K = 5;
|
|
606
|
+
var DEFAULT_MIN_SCORE = 0.5;
|
|
607
|
+
var VectorMemory = class {
|
|
608
|
+
type = "vector";
|
|
609
|
+
store;
|
|
610
|
+
embeddings;
|
|
611
|
+
conversationId;
|
|
612
|
+
topK;
|
|
613
|
+
minScore;
|
|
614
|
+
maxMessages;
|
|
615
|
+
maxTokens;
|
|
616
|
+
includeSystemMessages;
|
|
617
|
+
constructor(options) {
|
|
618
|
+
if (!options.embeddings) {
|
|
619
|
+
throw new Error("VectorMemory requires an embeddings provider");
|
|
620
|
+
}
|
|
621
|
+
this.store = options.store ?? new InMemoryStore();
|
|
622
|
+
this.embeddings = options.embeddings;
|
|
623
|
+
this.conversationId = options.conversationId ?? generateId();
|
|
624
|
+
this.topK = options.topK ?? DEFAULT_TOP_K;
|
|
625
|
+
this.minScore = options.minScore ?? DEFAULT_MIN_SCORE;
|
|
626
|
+
this.maxMessages = options.maxMessages;
|
|
627
|
+
this.maxTokens = options.maxTokens;
|
|
628
|
+
this.includeSystemMessages = options.includeSystemMessages ?? true;
|
|
629
|
+
}
|
|
630
|
+
async add(message) {
|
|
631
|
+
const { embedding } = await this.embeddings.embed(message.content);
|
|
632
|
+
const stored = createStoredMessage(message, this.conversationId, {
|
|
633
|
+
embedding
|
|
634
|
+
});
|
|
635
|
+
await this.store.addMessage(stored);
|
|
636
|
+
}
|
|
637
|
+
async addMany(messages) {
|
|
638
|
+
if (messages.length === 0) return;
|
|
639
|
+
const texts = messages.map((m) => m.content);
|
|
640
|
+
const { embeddings } = await this.embeddings.embedMany(texts);
|
|
641
|
+
for (let i = 0; i < messages.length; i++) {
|
|
642
|
+
const stored = createStoredMessage(messages[i], this.conversationId, {
|
|
643
|
+
embedding: embeddings[i].embedding
|
|
644
|
+
});
|
|
645
|
+
await this.store.addMessage(stored);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async getMessages() {
|
|
649
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
650
|
+
return this.extractMessages(stored);
|
|
651
|
+
}
|
|
652
|
+
async getContext() {
|
|
653
|
+
const messages = await this.getMessages();
|
|
654
|
+
return createContext(messages);
|
|
655
|
+
}
|
|
656
|
+
async getContextWindow(maxTokens) {
|
|
657
|
+
const messages = await this.getMessages();
|
|
658
|
+
const truncated = truncateMessages(messages, maxTokens, {
|
|
659
|
+
keepSystemMessages: this.includeSystemMessages
|
|
660
|
+
});
|
|
661
|
+
return createContext(truncated);
|
|
662
|
+
}
|
|
663
|
+
async clear() {
|
|
664
|
+
await this.store.clear(this.conversationId);
|
|
665
|
+
}
|
|
666
|
+
async getMessageCount() {
|
|
667
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
668
|
+
return stored.length;
|
|
669
|
+
}
|
|
670
|
+
async getTokenCount() {
|
|
671
|
+
const messages = await this.getMessages();
|
|
672
|
+
return countMessagesTokens(messages);
|
|
673
|
+
}
|
|
674
|
+
async search(query, topK) {
|
|
675
|
+
const k = topK ?? this.topK;
|
|
676
|
+
const { embedding: queryEmbedding } = await this.embeddings.embed(query);
|
|
677
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
678
|
+
const results = [];
|
|
679
|
+
for (const msg of stored) {
|
|
680
|
+
if (!msg.embedding) continue;
|
|
681
|
+
const score = cosineSimilarity(queryEmbedding, msg.embedding);
|
|
682
|
+
if (score >= this.minScore) {
|
|
683
|
+
results.push({ message: msg, score });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return results.sort((a, b) => b.score - a.score).slice(0, k);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Get relevant context for a query.
|
|
690
|
+
* Returns messages most semantically similar to the query.
|
|
691
|
+
*/
|
|
692
|
+
async getRelevantContext(query, topK) {
|
|
693
|
+
const results = await this.search(query, topK);
|
|
694
|
+
const sorted = results.sort((a, b) => a.message.timestamp - b.message.timestamp);
|
|
695
|
+
return sorted.map((r) => r.message.message);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Build context with recent messages + relevant history.
|
|
699
|
+
* Useful for including both recency and relevance.
|
|
700
|
+
*/
|
|
701
|
+
async buildContext(query, options) {
|
|
702
|
+
const { recentCount = 5, relevantCount = 3 } = options ?? {};
|
|
703
|
+
const stored = await this.store.getMessages(this.conversationId);
|
|
704
|
+
const messages = this.extractMessages(stored);
|
|
705
|
+
const recent = messages.slice(-recentCount);
|
|
706
|
+
const recentIds = new Set(stored.slice(-recentCount).map((s) => s.id));
|
|
707
|
+
const relevantResults = await this.search(query, relevantCount + recentCount);
|
|
708
|
+
const relevant = relevantResults.filter((r) => !recentIds.has(r.message.id)).slice(0, relevantCount).sort((a, b) => a.message.timestamp - b.message.timestamp).map((r) => r.message.message);
|
|
709
|
+
return [...relevant, ...recent];
|
|
710
|
+
}
|
|
711
|
+
/** Get the conversation ID */
|
|
712
|
+
getConversationId() {
|
|
713
|
+
return this.conversationId;
|
|
714
|
+
}
|
|
715
|
+
/** Switch to a different conversation */
|
|
716
|
+
setConversationId(id) {
|
|
717
|
+
this.conversationId = id;
|
|
718
|
+
}
|
|
719
|
+
extractMessages(stored) {
|
|
720
|
+
return stored.sort((a, b) => a.timestamp - b.timestamp).map((s) => s.message);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
// src/memory/stores/local-storage.ts
|
|
725
|
+
var MESSAGES_PREFIX = "binario:memory:messages:";
|
|
726
|
+
var METADATA_PREFIX = "binario:memory:metadata:";
|
|
727
|
+
var LocalStorageStore = class {
|
|
728
|
+
prefix;
|
|
729
|
+
constructor(prefix = "") {
|
|
730
|
+
this.prefix = prefix;
|
|
731
|
+
}
|
|
732
|
+
getMessagesKey(conversationId) {
|
|
733
|
+
return `${MESSAGES_PREFIX}${this.prefix}${conversationId}`;
|
|
734
|
+
}
|
|
735
|
+
getMetadataKey(conversationId) {
|
|
736
|
+
return `${METADATA_PREFIX}${this.prefix}${conversationId}`;
|
|
737
|
+
}
|
|
738
|
+
getStorage() {
|
|
739
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
740
|
+
return window.localStorage;
|
|
741
|
+
}
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
async getMessages(conversationId) {
|
|
745
|
+
const storage = this.getStorage();
|
|
746
|
+
if (!storage) return [];
|
|
747
|
+
try {
|
|
748
|
+
const data = storage.getItem(this.getMessagesKey(conversationId));
|
|
749
|
+
return data ? JSON.parse(data) : [];
|
|
750
|
+
} catch {
|
|
751
|
+
return [];
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
async addMessage(message) {
|
|
755
|
+
const storage = this.getStorage();
|
|
756
|
+
if (!storage) return;
|
|
757
|
+
const messages = await this.getMessages(message.conversationId);
|
|
758
|
+
messages.push(message);
|
|
759
|
+
try {
|
|
760
|
+
storage.setItem(
|
|
761
|
+
this.getMessagesKey(message.conversationId),
|
|
762
|
+
JSON.stringify(messages)
|
|
763
|
+
);
|
|
764
|
+
} catch (e) {
|
|
765
|
+
if (e instanceof Error && e.name === "QuotaExceededError") {
|
|
766
|
+
try {
|
|
767
|
+
const trimmed = messages.slice(-Math.floor(messages.length / 2));
|
|
768
|
+
storage.setItem(
|
|
769
|
+
this.getMessagesKey(message.conversationId),
|
|
770
|
+
JSON.stringify(trimmed)
|
|
771
|
+
);
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async updateMessage(id, updates) {
|
|
778
|
+
const storage = this.getStorage();
|
|
779
|
+
if (!storage) return;
|
|
780
|
+
const keys = Object.keys(storage).filter((k) => k.startsWith(MESSAGES_PREFIX + this.prefix));
|
|
781
|
+
for (const key of keys) {
|
|
782
|
+
try {
|
|
783
|
+
const data = storage.getItem(key);
|
|
784
|
+
if (!data) continue;
|
|
785
|
+
const messages = JSON.parse(data);
|
|
786
|
+
const index = messages.findIndex((m) => m.id === id);
|
|
787
|
+
if (index !== -1) {
|
|
788
|
+
messages[index] = { ...messages[index], ...updates };
|
|
789
|
+
storage.setItem(key, JSON.stringify(messages));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
} catch {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async deleteMessage(id) {
|
|
798
|
+
const storage = this.getStorage();
|
|
799
|
+
if (!storage) return;
|
|
800
|
+
const keys = Object.keys(storage).filter((k) => k.startsWith(MESSAGES_PREFIX + this.prefix));
|
|
801
|
+
for (const key of keys) {
|
|
802
|
+
try {
|
|
803
|
+
const data = storage.getItem(key);
|
|
804
|
+
if (!data) continue;
|
|
805
|
+
const messages = JSON.parse(data);
|
|
806
|
+
const filtered = messages.filter((m) => m.id !== id);
|
|
807
|
+
if (filtered.length !== messages.length) {
|
|
808
|
+
storage.setItem(key, JSON.stringify(filtered));
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
} catch {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async clear(conversationId) {
|
|
817
|
+
const storage = this.getStorage();
|
|
818
|
+
if (!storage) return;
|
|
819
|
+
storage.removeItem(this.getMessagesKey(conversationId));
|
|
820
|
+
storage.removeItem(this.getMetadataKey(conversationId));
|
|
821
|
+
}
|
|
822
|
+
async getMetadata(conversationId) {
|
|
823
|
+
const storage = this.getStorage();
|
|
824
|
+
if (!storage) return null;
|
|
825
|
+
try {
|
|
826
|
+
const data = storage.getItem(this.getMetadataKey(conversationId));
|
|
827
|
+
return data ? JSON.parse(data) : null;
|
|
828
|
+
} catch {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
async setMetadata(conversationId, metadata) {
|
|
833
|
+
const storage = this.getStorage();
|
|
834
|
+
if (!storage) return;
|
|
835
|
+
storage.setItem(this.getMetadataKey(conversationId), JSON.stringify(metadata));
|
|
836
|
+
}
|
|
837
|
+
/** Clear all binario memory data */
|
|
838
|
+
clearAll() {
|
|
839
|
+
const storage = this.getStorage();
|
|
840
|
+
if (!storage) return;
|
|
841
|
+
const keysToRemove = Object.keys(storage).filter(
|
|
842
|
+
(k) => k.startsWith(MESSAGES_PREFIX + this.prefix) || k.startsWith(METADATA_PREFIX + this.prefix)
|
|
843
|
+
);
|
|
844
|
+
keysToRemove.forEach((key) => storage.removeItem(key));
|
|
845
|
+
}
|
|
846
|
+
/** Get all conversation IDs */
|
|
847
|
+
getConversationIds() {
|
|
848
|
+
const storage = this.getStorage();
|
|
849
|
+
if (!storage) return [];
|
|
850
|
+
const prefix = MESSAGES_PREFIX + this.prefix;
|
|
851
|
+
return Object.keys(storage).filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// src/embeddings/cloudflare.ts
|
|
856
|
+
var CLOUDFLARE_EMBEDDING_MODELS = {
|
|
857
|
+
"bge-base-en": "@cf/baai/bge-base-en-v1.5",
|
|
858
|
+
"bge-large-en": "@cf/baai/bge-large-en-v1.5",
|
|
859
|
+
"bge-small-en": "@cf/baai/bge-small-en-v1.5"
|
|
860
|
+
};
|
|
861
|
+
var DEFAULT_MODEL = CLOUDFLARE_EMBEDDING_MODELS["bge-base-en"];
|
|
862
|
+
var CloudflareEmbeddings = class {
|
|
863
|
+
name = "cloudflare";
|
|
864
|
+
binding;
|
|
865
|
+
accountId;
|
|
866
|
+
apiKey;
|
|
867
|
+
model;
|
|
868
|
+
constructor(config = {}) {
|
|
869
|
+
this.binding = config.binding;
|
|
870
|
+
this.accountId = config.accountId;
|
|
871
|
+
this.apiKey = config.apiKey;
|
|
872
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
873
|
+
if (this.model in CLOUDFLARE_EMBEDDING_MODELS) {
|
|
874
|
+
this.model = CLOUDFLARE_EMBEDDING_MODELS[this.model];
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async embed(text) {
|
|
878
|
+
const result = await this.embedMany([text]);
|
|
879
|
+
return result.embeddings[0];
|
|
880
|
+
}
|
|
881
|
+
async embedMany(texts) {
|
|
882
|
+
if (texts.length === 0) {
|
|
883
|
+
return {
|
|
884
|
+
embeddings: [],
|
|
885
|
+
model: this.model,
|
|
886
|
+
usage: { promptTokens: 0, totalTokens: 0 }
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
let embeddings;
|
|
890
|
+
if (this.binding) {
|
|
891
|
+
const response = await this.binding.run(this.model, { text: texts });
|
|
892
|
+
embeddings = response.data;
|
|
893
|
+
} else if (this.accountId && this.apiKey) {
|
|
894
|
+
embeddings = await this.embedViaRest(texts);
|
|
895
|
+
} else {
|
|
896
|
+
throw new Error("CloudflareEmbeddings requires either a binding or accountId + apiKey");
|
|
897
|
+
}
|
|
898
|
+
const promptTokens = texts.reduce((sum, t) => sum + countTokens(t), 0);
|
|
899
|
+
return {
|
|
900
|
+
embeddings: texts.map((text, i) => ({
|
|
901
|
+
text,
|
|
902
|
+
embedding: embeddings[i],
|
|
903
|
+
tokenCount: countTokens(text)
|
|
904
|
+
})),
|
|
905
|
+
model: this.model,
|
|
906
|
+
usage: {
|
|
907
|
+
promptTokens,
|
|
908
|
+
totalTokens: promptTokens
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
async embedViaRest(texts) {
|
|
913
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${this.accountId}/ai/run/${this.model}`;
|
|
914
|
+
const response = await fetch(url, {
|
|
915
|
+
method: "POST",
|
|
916
|
+
headers: {
|
|
917
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
918
|
+
"Content-Type": "application/json"
|
|
919
|
+
},
|
|
920
|
+
body: JSON.stringify({ text: texts })
|
|
921
|
+
});
|
|
922
|
+
if (!response.ok) {
|
|
923
|
+
const error = await response.text();
|
|
924
|
+
throw new Error(`Cloudflare embeddings failed: ${response.status} ${error}`);
|
|
925
|
+
}
|
|
926
|
+
const data = await response.json();
|
|
927
|
+
return data.result.data;
|
|
928
|
+
}
|
|
929
|
+
/** Get the current model */
|
|
930
|
+
getModel() {
|
|
931
|
+
return this.model;
|
|
932
|
+
}
|
|
933
|
+
/** Set a new model */
|
|
934
|
+
setModel(model) {
|
|
935
|
+
this.model = model in CLOUDFLARE_EMBEDDING_MODELS ? CLOUDFLARE_EMBEDDING_MODELS[model] : model;
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
// src/hooks.ts
|
|
940
|
+
function useBinarioChat(binario, options = {}) {
|
|
941
|
+
const [messages, setMessages] = useState(options.initialMessages || []);
|
|
942
|
+
const [input, setInput] = useState("");
|
|
943
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
944
|
+
const [error, setError] = useState(null);
|
|
945
|
+
const [lastResponse, setLastResponse] = useState(null);
|
|
946
|
+
const abortControllerRef = useRef(null);
|
|
947
|
+
const stop = useCallback(() => {
|
|
948
|
+
abortControllerRef.current?.abort();
|
|
949
|
+
abortControllerRef.current = null;
|
|
950
|
+
setIsLoading(false);
|
|
951
|
+
}, []);
|
|
952
|
+
const append = useCallback(
|
|
953
|
+
async (message) => {
|
|
954
|
+
setIsLoading(true);
|
|
955
|
+
setError(null);
|
|
956
|
+
abortControllerRef.current = new AbortController();
|
|
957
|
+
const newMessages = [...messages, message];
|
|
958
|
+
setMessages(newMessages);
|
|
959
|
+
try {
|
|
960
|
+
const response = await binario.chat(newMessages, options);
|
|
961
|
+
const assistantMessage = {
|
|
962
|
+
role: "assistant",
|
|
963
|
+
content: response.content,
|
|
964
|
+
tool_calls: response.toolCalls
|
|
965
|
+
};
|
|
966
|
+
setMessages([...newMessages, assistantMessage]);
|
|
967
|
+
setLastResponse(response);
|
|
968
|
+
options.onFinish?.(response);
|
|
969
|
+
} catch (err) {
|
|
970
|
+
const error2 = err;
|
|
971
|
+
setError(error2);
|
|
972
|
+
options.onError?.(error2);
|
|
973
|
+
} finally {
|
|
974
|
+
setIsLoading(false);
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
[messages, binario, options]
|
|
978
|
+
);
|
|
979
|
+
const reload = useCallback(async () => {
|
|
980
|
+
if (messages.length === 0) return;
|
|
981
|
+
const lastUserMessageIndex = messages.map((m) => m.role).lastIndexOf("user");
|
|
982
|
+
if (lastUserMessageIndex === -1) return;
|
|
983
|
+
const messagesToReload = messages.slice(0, lastUserMessageIndex + 1);
|
|
984
|
+
setMessages(messagesToReload);
|
|
985
|
+
setIsLoading(true);
|
|
986
|
+
setError(null);
|
|
987
|
+
try {
|
|
988
|
+
const response = await binario.chat(messagesToReload, options);
|
|
989
|
+
const assistantMessage = {
|
|
990
|
+
role: "assistant",
|
|
991
|
+
content: response.content,
|
|
992
|
+
tool_calls: response.toolCalls
|
|
993
|
+
};
|
|
994
|
+
setMessages([...messagesToReload, assistantMessage]);
|
|
995
|
+
setLastResponse(response);
|
|
996
|
+
options.onFinish?.(response);
|
|
997
|
+
} catch (err) {
|
|
998
|
+
const error2 = err;
|
|
999
|
+
setError(error2);
|
|
1000
|
+
options.onError?.(error2);
|
|
1001
|
+
} finally {
|
|
1002
|
+
setIsLoading(false);
|
|
1003
|
+
}
|
|
1004
|
+
}, [messages, binario, options]);
|
|
1005
|
+
useEffect(() => {
|
|
1006
|
+
return () => {
|
|
1007
|
+
abortControllerRef.current?.abort();
|
|
1008
|
+
};
|
|
1009
|
+
}, []);
|
|
1010
|
+
return {
|
|
1011
|
+
messages,
|
|
1012
|
+
input,
|
|
1013
|
+
setInput,
|
|
1014
|
+
isLoading,
|
|
1015
|
+
error,
|
|
1016
|
+
append,
|
|
1017
|
+
reload,
|
|
1018
|
+
stop,
|
|
1019
|
+
setMessages,
|
|
1020
|
+
lastResponse
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
function useBinarioStream(binario, options = {}) {
|
|
1024
|
+
const [messages, setMessages] = useState(options.initialMessages || []);
|
|
1025
|
+
const [input, setInput] = useState("");
|
|
1026
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
1027
|
+
const [error, setError] = useState(null);
|
|
1028
|
+
const [streamingContent, setStreamingContent] = useState("");
|
|
1029
|
+
const abortRef = useRef(false);
|
|
1030
|
+
const stop = useCallback(() => {
|
|
1031
|
+
abortRef.current = true;
|
|
1032
|
+
setIsStreaming(false);
|
|
1033
|
+
}, []);
|
|
1034
|
+
const send = useCallback(
|
|
1035
|
+
async (content) => {
|
|
1036
|
+
setIsStreaming(true);
|
|
1037
|
+
setError(null);
|
|
1038
|
+
setStreamingContent("");
|
|
1039
|
+
abortRef.current = false;
|
|
1040
|
+
const userMessage = { role: "user", content };
|
|
1041
|
+
const newMessages = [...messages, userMessage];
|
|
1042
|
+
setMessages(newMessages);
|
|
1043
|
+
let fullContent = "";
|
|
1044
|
+
const callbacks = {
|
|
1045
|
+
onToken: (token) => {
|
|
1046
|
+
if (abortRef.current) return;
|
|
1047
|
+
fullContent += token;
|
|
1048
|
+
setStreamingContent(fullContent);
|
|
1049
|
+
options.onToken?.(token);
|
|
1050
|
+
},
|
|
1051
|
+
onComplete: (response) => {
|
|
1052
|
+
const assistantMessage = { role: "assistant", content: fullContent };
|
|
1053
|
+
setMessages([...newMessages, assistantMessage]);
|
|
1054
|
+
setStreamingContent("");
|
|
1055
|
+
options.onFinish?.(response);
|
|
1056
|
+
},
|
|
1057
|
+
onError: (err) => {
|
|
1058
|
+
setError(err);
|
|
1059
|
+
options.onError?.(err);
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
try {
|
|
1063
|
+
const stream = binario.streamChat(newMessages, options, callbacks);
|
|
1064
|
+
for await (const _ of stream) {
|
|
1065
|
+
if (abortRef.current) break;
|
|
1066
|
+
}
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
const error2 = err;
|
|
1069
|
+
setError(error2);
|
|
1070
|
+
options.onError?.(error2);
|
|
1071
|
+
} finally {
|
|
1072
|
+
setIsStreaming(false);
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
[messages, binario, options]
|
|
1076
|
+
);
|
|
1077
|
+
return {
|
|
1078
|
+
messages,
|
|
1079
|
+
input,
|
|
1080
|
+
setInput,
|
|
1081
|
+
isStreaming,
|
|
1082
|
+
error,
|
|
1083
|
+
streamingContent,
|
|
1084
|
+
send,
|
|
1085
|
+
stop,
|
|
1086
|
+
setMessages
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function useBinarioCompletion(binario, options = {}) {
|
|
1090
|
+
const [completion, setCompletion] = useState("");
|
|
1091
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1092
|
+
const [error, setError] = useState(null);
|
|
1093
|
+
const complete = useCallback(
|
|
1094
|
+
async (prompt, systemPrompt) => {
|
|
1095
|
+
setIsLoading(true);
|
|
1096
|
+
setError(null);
|
|
1097
|
+
setCompletion("");
|
|
1098
|
+
const messages = [];
|
|
1099
|
+
if (systemPrompt) {
|
|
1100
|
+
messages.push({ role: "system", content: systemPrompt });
|
|
1101
|
+
}
|
|
1102
|
+
messages.push({ role: "user", content: prompt });
|
|
1103
|
+
try {
|
|
1104
|
+
const response = await binario.chat(messages, options);
|
|
1105
|
+
setCompletion(response.content);
|
|
1106
|
+
options.onFinish?.(response);
|
|
1107
|
+
return response.content;
|
|
1108
|
+
} catch (err) {
|
|
1109
|
+
const error2 = err;
|
|
1110
|
+
setError(error2);
|
|
1111
|
+
options.onError?.(error2);
|
|
1112
|
+
return null;
|
|
1113
|
+
} finally {
|
|
1114
|
+
setIsLoading(false);
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
[binario, options]
|
|
1118
|
+
);
|
|
1119
|
+
return {
|
|
1120
|
+
completion,
|
|
1121
|
+
isLoading,
|
|
1122
|
+
error,
|
|
1123
|
+
complete
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function useBinarioAgent(agent, options = {}) {
|
|
1127
|
+
const [output, setOutput] = useState("");
|
|
1128
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
1129
|
+
const [error, setError] = useState(null);
|
|
1130
|
+
const [steps, setSteps] = useState([]);
|
|
1131
|
+
const abortRef = useRef(false);
|
|
1132
|
+
const stop = useCallback(() => {
|
|
1133
|
+
abortRef.current = true;
|
|
1134
|
+
setIsRunning(false);
|
|
1135
|
+
}, []);
|
|
1136
|
+
const run = useCallback(
|
|
1137
|
+
async (input, context) => {
|
|
1138
|
+
setIsRunning(true);
|
|
1139
|
+
setError(null);
|
|
1140
|
+
setOutput("");
|
|
1141
|
+
setSteps([]);
|
|
1142
|
+
abortRef.current = false;
|
|
1143
|
+
try {
|
|
1144
|
+
const agentWithContext = context ? agent.withContext(context) : agent;
|
|
1145
|
+
const result = await agentWithContext.run(input, {
|
|
1146
|
+
maxSteps: options.maxSteps,
|
|
1147
|
+
onStep: (step) => {
|
|
1148
|
+
if (abortRef.current) return;
|
|
1149
|
+
setSteps((prev) => [...prev, step]);
|
|
1150
|
+
options.onStep?.(step);
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
setOutput(result.output);
|
|
1154
|
+
return result.output;
|
|
1155
|
+
} catch (err) {
|
|
1156
|
+
const error2 = err;
|
|
1157
|
+
setError(error2);
|
|
1158
|
+
options.onError?.(error2);
|
|
1159
|
+
return null;
|
|
1160
|
+
} finally {
|
|
1161
|
+
setIsRunning(false);
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
[agent, options]
|
|
1165
|
+
);
|
|
1166
|
+
const runStructured = useCallback(
|
|
1167
|
+
async (input, schema, context) => {
|
|
1168
|
+
setIsRunning(true);
|
|
1169
|
+
setError(null);
|
|
1170
|
+
setOutput("");
|
|
1171
|
+
setSteps([]);
|
|
1172
|
+
abortRef.current = false;
|
|
1173
|
+
try {
|
|
1174
|
+
const agentWithContext = context ? agent.withContext(context) : agent;
|
|
1175
|
+
const result = await agentWithContext.runStructured(input, schema, {
|
|
1176
|
+
maxSteps: options.maxSteps
|
|
1177
|
+
});
|
|
1178
|
+
setOutput(JSON.stringify(result.output, null, 2));
|
|
1179
|
+
return result.output;
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
const error2 = err;
|
|
1182
|
+
setError(error2);
|
|
1183
|
+
options.onError?.(error2);
|
|
1184
|
+
return null;
|
|
1185
|
+
} finally {
|
|
1186
|
+
setIsRunning(false);
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
[agent, options]
|
|
1190
|
+
);
|
|
1191
|
+
return {
|
|
1192
|
+
output,
|
|
1193
|
+
isRunning,
|
|
1194
|
+
error,
|
|
1195
|
+
steps,
|
|
1196
|
+
run,
|
|
1197
|
+
runStructured,
|
|
1198
|
+
stop
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function useBinarioStructured(binario, options) {
|
|
1202
|
+
const [data, setData] = useState(null);
|
|
1203
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1204
|
+
const [error, setError] = useState(null);
|
|
1205
|
+
const generate = useCallback(
|
|
1206
|
+
async (prompt, systemPrompt) => {
|
|
1207
|
+
setIsLoading(true);
|
|
1208
|
+
setError(null);
|
|
1209
|
+
setData(null);
|
|
1210
|
+
const messages = [];
|
|
1211
|
+
if (systemPrompt) {
|
|
1212
|
+
messages.push({ role: "system", content: systemPrompt });
|
|
1213
|
+
}
|
|
1214
|
+
messages.push({ role: "user", content: prompt });
|
|
1215
|
+
try {
|
|
1216
|
+
const response = await binario.chat(messages, {
|
|
1217
|
+
...options,
|
|
1218
|
+
outputSchema: options.schema
|
|
1219
|
+
});
|
|
1220
|
+
const parsedData = response.data;
|
|
1221
|
+
setData(parsedData);
|
|
1222
|
+
options.onFinish?.(parsedData);
|
|
1223
|
+
return parsedData;
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
const error2 = err;
|
|
1226
|
+
setError(error2);
|
|
1227
|
+
options.onError?.(error2);
|
|
1228
|
+
return null;
|
|
1229
|
+
} finally {
|
|
1230
|
+
setIsLoading(false);
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
[binario, options]
|
|
1234
|
+
);
|
|
1235
|
+
return {
|
|
1236
|
+
data,
|
|
1237
|
+
isLoading,
|
|
1238
|
+
error,
|
|
1239
|
+
generate
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
function useBinarioTools(binario, options) {
|
|
1243
|
+
const [messages, setMessages] = useState([]);
|
|
1244
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
1245
|
+
const [error, setError] = useState(null);
|
|
1246
|
+
const send = useCallback(
|
|
1247
|
+
async (content) => {
|
|
1248
|
+
setIsProcessing(true);
|
|
1249
|
+
setError(null);
|
|
1250
|
+
const userMessage = { role: "user", content };
|
|
1251
|
+
const newMessages = [...messages, userMessage];
|
|
1252
|
+
setMessages(newMessages);
|
|
1253
|
+
try {
|
|
1254
|
+
const apiTools = options.tools.map((tool) => ({
|
|
1255
|
+
type: "function",
|
|
1256
|
+
function: {
|
|
1257
|
+
name: tool.name,
|
|
1258
|
+
description: tool.description,
|
|
1259
|
+
parameters: zodToJsonSchema(tool.parameters)
|
|
1260
|
+
}
|
|
1261
|
+
}));
|
|
1262
|
+
let currentMessages = newMessages;
|
|
1263
|
+
let continueLoop = true;
|
|
1264
|
+
while (continueLoop) {
|
|
1265
|
+
const response = await binario.chat(currentMessages, {
|
|
1266
|
+
provider: options.provider,
|
|
1267
|
+
model: options.model,
|
|
1268
|
+
tools: apiTools
|
|
1269
|
+
});
|
|
1270
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
1271
|
+
const toolResults = [];
|
|
1272
|
+
for (const toolCall of response.toolCalls) {
|
|
1273
|
+
const tool = options.tools.find((t) => t.name === toolCall.function.name);
|
|
1274
|
+
if (tool) {
|
|
1275
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
1276
|
+
options.onToolCall?.(toolCall.function.name, args);
|
|
1277
|
+
const result = await tool.execute(args);
|
|
1278
|
+
options.onToolResult?.(toolCall.function.name, result);
|
|
1279
|
+
toolResults.push({
|
|
1280
|
+
role: "tool",
|
|
1281
|
+
content: JSON.stringify(result),
|
|
1282
|
+
tool_call_id: toolCall.id
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
currentMessages = [
|
|
1287
|
+
...currentMessages,
|
|
1288
|
+
{ role: "assistant", content: response.content, tool_calls: response.toolCalls },
|
|
1289
|
+
...toolResults
|
|
1290
|
+
];
|
|
1291
|
+
} else {
|
|
1292
|
+
const assistantMessage = {
|
|
1293
|
+
role: "assistant",
|
|
1294
|
+
content: response.content
|
|
1295
|
+
};
|
|
1296
|
+
currentMessages = [...currentMessages, assistantMessage];
|
|
1297
|
+
continueLoop = false;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
setMessages(currentMessages);
|
|
1301
|
+
} catch (err) {
|
|
1302
|
+
const error2 = err;
|
|
1303
|
+
setError(error2);
|
|
1304
|
+
options.onError?.(error2);
|
|
1305
|
+
} finally {
|
|
1306
|
+
setIsProcessing(false);
|
|
1307
|
+
}
|
|
1308
|
+
},
|
|
1309
|
+
[messages, binario, options]
|
|
1310
|
+
);
|
|
1311
|
+
return {
|
|
1312
|
+
messages,
|
|
1313
|
+
isProcessing,
|
|
1314
|
+
error,
|
|
1315
|
+
send,
|
|
1316
|
+
setMessages
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
function useBinarioMemory(options = {}) {
|
|
1320
|
+
const {
|
|
1321
|
+
type = "buffer",
|
|
1322
|
+
conversationId: initialConversationId,
|
|
1323
|
+
store: storeOption = "memory",
|
|
1324
|
+
storagePrefix = "",
|
|
1325
|
+
maxMessages = 50,
|
|
1326
|
+
maxTokens = 4e3,
|
|
1327
|
+
bufferSize = 10,
|
|
1328
|
+
summarizer,
|
|
1329
|
+
embeddings,
|
|
1330
|
+
topK = 5,
|
|
1331
|
+
minScore = 0.5
|
|
1332
|
+
} = options;
|
|
1333
|
+
const store = useRef(
|
|
1334
|
+
typeof storeOption === "string" ? storeOption === "localStorage" ? new LocalStorageStore(storagePrefix) : new InMemoryStore() : storeOption
|
|
1335
|
+
);
|
|
1336
|
+
const memory = useRef(null);
|
|
1337
|
+
const [memoryState, setMemoryState] = useState(null);
|
|
1338
|
+
const [conversationId, setConversationId] = useState(
|
|
1339
|
+
initialConversationId || `conv-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
|
|
1340
|
+
);
|
|
1341
|
+
const [messages, setMessages] = useState([]);
|
|
1342
|
+
const [context, setContext] = useState(null);
|
|
1343
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1344
|
+
const isInitialLoad = useRef(true);
|
|
1345
|
+
const createMemory = useCallback(() => {
|
|
1346
|
+
const memoryOptions = {
|
|
1347
|
+
store: store.current,
|
|
1348
|
+
conversationId,
|
|
1349
|
+
maxMessages,
|
|
1350
|
+
maxTokens
|
|
1351
|
+
};
|
|
1352
|
+
let mem;
|
|
1353
|
+
switch (type) {
|
|
1354
|
+
case "summary": {
|
|
1355
|
+
const summaryMem = new SummaryMemory({ ...memoryOptions, summarizer });
|
|
1356
|
+
if (summarizer) summaryMem.setSummarizer(summarizer);
|
|
1357
|
+
mem = summaryMem;
|
|
1358
|
+
break;
|
|
1359
|
+
}
|
|
1360
|
+
case "summary-buffer": {
|
|
1361
|
+
const sbMem = new SummaryBufferMemory({ ...memoryOptions, bufferSize, summarizer });
|
|
1362
|
+
if (summarizer) sbMem.setSummarizer(summarizer);
|
|
1363
|
+
mem = sbMem;
|
|
1364
|
+
break;
|
|
1365
|
+
}
|
|
1366
|
+
case "vector":
|
|
1367
|
+
if (!embeddings) {
|
|
1368
|
+
throw new Error("VectorMemory requires an embeddings provider");
|
|
1369
|
+
}
|
|
1370
|
+
mem = new VectorMemory({ ...memoryOptions, embeddings, topK, minScore });
|
|
1371
|
+
break;
|
|
1372
|
+
default:
|
|
1373
|
+
mem = new BufferMemory(memoryOptions);
|
|
1374
|
+
}
|
|
1375
|
+
return mem;
|
|
1376
|
+
}, [type, conversationId, maxMessages, maxTokens, summarizer, bufferSize, embeddings, topK, minScore]);
|
|
1377
|
+
if (!memory.current) {
|
|
1378
|
+
memory.current = createMemory();
|
|
1379
|
+
setMemoryState(memory.current);
|
|
1380
|
+
}
|
|
1381
|
+
useEffect(() => {
|
|
1382
|
+
memory.current = createMemory();
|
|
1383
|
+
setMemoryState(memory.current);
|
|
1384
|
+
loadMessages(isInitialLoad.current);
|
|
1385
|
+
isInitialLoad.current = false;
|
|
1386
|
+
}, [type, conversationId]);
|
|
1387
|
+
const loadMessages = useCallback(async (skipLoadingState = false) => {
|
|
1388
|
+
if (!memory.current) return;
|
|
1389
|
+
if (!skipLoadingState) {
|
|
1390
|
+
setIsLoading(true);
|
|
1391
|
+
}
|
|
1392
|
+
try {
|
|
1393
|
+
const msgs = await memory.current.getMessages();
|
|
1394
|
+
const ctx = await memory.current.getContext();
|
|
1395
|
+
setMessages(msgs);
|
|
1396
|
+
setContext(ctx);
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
console.error("Failed to load memory:", error);
|
|
1399
|
+
} finally {
|
|
1400
|
+
if (!skipLoadingState) {
|
|
1401
|
+
setIsLoading(false);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}, []);
|
|
1405
|
+
const addMessage = useCallback(async (message) => {
|
|
1406
|
+
if (!memory.current) return;
|
|
1407
|
+
await memory.current.add(message);
|
|
1408
|
+
await loadMessages();
|
|
1409
|
+
}, [loadMessages]);
|
|
1410
|
+
const addMessages = useCallback(async (msgs) => {
|
|
1411
|
+
if (!memory.current) return;
|
|
1412
|
+
await memory.current.addMany(msgs);
|
|
1413
|
+
await loadMessages();
|
|
1414
|
+
}, [loadMessages]);
|
|
1415
|
+
const clear = useCallback(async () => {
|
|
1416
|
+
if (!memory.current) return;
|
|
1417
|
+
await memory.current.clear();
|
|
1418
|
+
setMessages([]);
|
|
1419
|
+
setContext(null);
|
|
1420
|
+
}, []);
|
|
1421
|
+
const refresh = useCallback(async () => {
|
|
1422
|
+
await loadMessages();
|
|
1423
|
+
}, [loadMessages]);
|
|
1424
|
+
const getContextWindow = useCallback(async (maxTkns) => {
|
|
1425
|
+
if (!memory.current) {
|
|
1426
|
+
return { messages: [], tokenCount: 0, messageCount: 0 };
|
|
1427
|
+
}
|
|
1428
|
+
return memory.current.getContextWindow(maxTkns);
|
|
1429
|
+
}, []);
|
|
1430
|
+
const switchConversation = useCallback((id) => {
|
|
1431
|
+
setConversationId(id);
|
|
1432
|
+
}, []);
|
|
1433
|
+
return {
|
|
1434
|
+
memory: memoryState,
|
|
1435
|
+
messages,
|
|
1436
|
+
context,
|
|
1437
|
+
isLoading,
|
|
1438
|
+
addMessage,
|
|
1439
|
+
addMessages,
|
|
1440
|
+
clear,
|
|
1441
|
+
refresh,
|
|
1442
|
+
getContextWindow,
|
|
1443
|
+
conversationId,
|
|
1444
|
+
switchConversation
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
function useBinarioChatWithMemory(binario, options = {}) {
|
|
1448
|
+
const {
|
|
1449
|
+
memory: memoryOptions = {},
|
|
1450
|
+
autoSave = true,
|
|
1451
|
+
contextWindowSize = 4e3,
|
|
1452
|
+
...chatOptions
|
|
1453
|
+
} = options;
|
|
1454
|
+
const memoryHook = useBinarioMemory(memoryOptions);
|
|
1455
|
+
const [messages, setMessages] = useState([]);
|
|
1456
|
+
const [input, setInput] = useState("");
|
|
1457
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1458
|
+
const [error, setError] = useState(null);
|
|
1459
|
+
const [lastResponse, setLastResponse] = useState(null);
|
|
1460
|
+
const abortControllerRef = useRef(null);
|
|
1461
|
+
useEffect(() => {
|
|
1462
|
+
if (memoryHook.messages.length > 0 && messages.length === 0) {
|
|
1463
|
+
setMessages(memoryHook.messages);
|
|
1464
|
+
}
|
|
1465
|
+
}, [memoryHook.messages]);
|
|
1466
|
+
const stop = useCallback(() => {
|
|
1467
|
+
abortControllerRef.current?.abort();
|
|
1468
|
+
abortControllerRef.current = null;
|
|
1469
|
+
setIsLoading(false);
|
|
1470
|
+
}, []);
|
|
1471
|
+
const append = useCallback(
|
|
1472
|
+
async (message) => {
|
|
1473
|
+
setIsLoading(true);
|
|
1474
|
+
setError(null);
|
|
1475
|
+
abortControllerRef.current = new AbortController();
|
|
1476
|
+
const newMessages = [...messages, message];
|
|
1477
|
+
setMessages(newMessages);
|
|
1478
|
+
if (autoSave) {
|
|
1479
|
+
await memoryHook.addMessage(message);
|
|
1480
|
+
}
|
|
1481
|
+
try {
|
|
1482
|
+
const contextWindow = await memoryHook.getContextWindow(contextWindowSize);
|
|
1483
|
+
const contextMessages = contextWindow.messages.length > 0 ? contextWindow.messages : newMessages;
|
|
1484
|
+
const response = await binario.chat(contextMessages, chatOptions);
|
|
1485
|
+
const assistantMessage = {
|
|
1486
|
+
role: "assistant",
|
|
1487
|
+
content: response.content,
|
|
1488
|
+
tool_calls: response.toolCalls
|
|
1489
|
+
};
|
|
1490
|
+
const updatedMessages = [...newMessages, assistantMessage];
|
|
1491
|
+
setMessages(updatedMessages);
|
|
1492
|
+
setLastResponse(response);
|
|
1493
|
+
if (autoSave) {
|
|
1494
|
+
await memoryHook.addMessage(assistantMessage);
|
|
1495
|
+
}
|
|
1496
|
+
chatOptions.onFinish?.(response);
|
|
1497
|
+
} catch (err) {
|
|
1498
|
+
const error2 = err;
|
|
1499
|
+
setError(error2);
|
|
1500
|
+
chatOptions.onError?.(error2);
|
|
1501
|
+
} finally {
|
|
1502
|
+
setIsLoading(false);
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
[messages, binario, chatOptions, autoSave, memoryHook, contextWindowSize]
|
|
1506
|
+
);
|
|
1507
|
+
const reload = useCallback(async () => {
|
|
1508
|
+
if (messages.length === 0) return;
|
|
1509
|
+
const lastUserMessageIndex = messages.map((m) => m.role).lastIndexOf("user");
|
|
1510
|
+
if (lastUserMessageIndex === -1) return;
|
|
1511
|
+
const messagesToReload = messages.slice(0, lastUserMessageIndex + 1);
|
|
1512
|
+
setMessages(messagesToReload);
|
|
1513
|
+
setIsLoading(true);
|
|
1514
|
+
setError(null);
|
|
1515
|
+
try {
|
|
1516
|
+
const response = await binario.chat(messagesToReload, chatOptions);
|
|
1517
|
+
const assistantMessage = {
|
|
1518
|
+
role: "assistant",
|
|
1519
|
+
content: response.content,
|
|
1520
|
+
tool_calls: response.toolCalls
|
|
1521
|
+
};
|
|
1522
|
+
setMessages([...messagesToReload, assistantMessage]);
|
|
1523
|
+
setLastResponse(response);
|
|
1524
|
+
chatOptions.onFinish?.(response);
|
|
1525
|
+
} catch (err) {
|
|
1526
|
+
const error2 = err;
|
|
1527
|
+
setError(error2);
|
|
1528
|
+
chatOptions.onError?.(error2);
|
|
1529
|
+
} finally {
|
|
1530
|
+
setIsLoading(false);
|
|
1531
|
+
}
|
|
1532
|
+
}, [messages, binario, chatOptions]);
|
|
1533
|
+
const loadConversation = useCallback(async (conversationId) => {
|
|
1534
|
+
if (conversationId) {
|
|
1535
|
+
memoryHook.switchConversation(conversationId);
|
|
1536
|
+
}
|
|
1537
|
+
await memoryHook.refresh();
|
|
1538
|
+
setMessages(memoryHook.messages);
|
|
1539
|
+
}, [memoryHook]);
|
|
1540
|
+
const saveConversation = useCallback(async () => {
|
|
1541
|
+
await memoryHook.clear();
|
|
1542
|
+
await memoryHook.addMessages(messages);
|
|
1543
|
+
}, [memoryHook, messages]);
|
|
1544
|
+
useEffect(() => {
|
|
1545
|
+
return () => {
|
|
1546
|
+
abortControllerRef.current?.abort();
|
|
1547
|
+
};
|
|
1548
|
+
}, []);
|
|
1549
|
+
return {
|
|
1550
|
+
messages,
|
|
1551
|
+
input,
|
|
1552
|
+
setInput,
|
|
1553
|
+
isLoading,
|
|
1554
|
+
error,
|
|
1555
|
+
append,
|
|
1556
|
+
reload,
|
|
1557
|
+
stop,
|
|
1558
|
+
setMessages,
|
|
1559
|
+
lastResponse,
|
|
1560
|
+
memory: memoryHook,
|
|
1561
|
+
loadConversation,
|
|
1562
|
+
saveConversation
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function useBinarioEmbed(options = {}) {
|
|
1566
|
+
const {
|
|
1567
|
+
provider: providerType = "cloudflare",
|
|
1568
|
+
customProvider,
|
|
1569
|
+
model,
|
|
1570
|
+
accountId,
|
|
1571
|
+
apiKey,
|
|
1572
|
+
cache = true,
|
|
1573
|
+
onStart,
|
|
1574
|
+
onComplete,
|
|
1575
|
+
onError
|
|
1576
|
+
} = options;
|
|
1577
|
+
const [result, setResult] = useState(null);
|
|
1578
|
+
const [batchResult, setBatchResult] = useState(null);
|
|
1579
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1580
|
+
const [error, setError] = useState(null);
|
|
1581
|
+
const cacheRef = useRef(/* @__PURE__ */ new Map());
|
|
1582
|
+
const providerRef = useRef(null);
|
|
1583
|
+
useEffect(() => {
|
|
1584
|
+
if (customProvider) {
|
|
1585
|
+
providerRef.current = customProvider;
|
|
1586
|
+
} else if (providerType === "cloudflare") {
|
|
1587
|
+
providerRef.current = new CloudflareEmbeddings({
|
|
1588
|
+
model,
|
|
1589
|
+
accountId,
|
|
1590
|
+
apiKey
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
}, [providerType, customProvider, model, accountId, apiKey]);
|
|
1594
|
+
const embed = useCallback(async (text) => {
|
|
1595
|
+
if (!providerRef.current) {
|
|
1596
|
+
const err = new Error("Embeddings provider not initialized");
|
|
1597
|
+
setError(err);
|
|
1598
|
+
onError?.(err);
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
if (cache && cacheRef.current.has(text)) {
|
|
1602
|
+
const cached = {
|
|
1603
|
+
text,
|
|
1604
|
+
embedding: cacheRef.current.get(text)
|
|
1605
|
+
};
|
|
1606
|
+
setResult(cached);
|
|
1607
|
+
return cached;
|
|
1608
|
+
}
|
|
1609
|
+
setIsLoading(true);
|
|
1610
|
+
setError(null);
|
|
1611
|
+
onStart?.();
|
|
1612
|
+
try {
|
|
1613
|
+
const embeddingResult = await providerRef.current.embed(text);
|
|
1614
|
+
if (cache) {
|
|
1615
|
+
cacheRef.current.set(text, embeddingResult.embedding);
|
|
1616
|
+
}
|
|
1617
|
+
setResult(embeddingResult);
|
|
1618
|
+
onComplete?.(embeddingResult);
|
|
1619
|
+
return embeddingResult;
|
|
1620
|
+
} catch (err) {
|
|
1621
|
+
const error2 = err;
|
|
1622
|
+
setError(error2);
|
|
1623
|
+
onError?.(error2);
|
|
1624
|
+
return null;
|
|
1625
|
+
} finally {
|
|
1626
|
+
setIsLoading(false);
|
|
1627
|
+
}
|
|
1628
|
+
}, [cache, onStart, onComplete, onError]);
|
|
1629
|
+
const embedMany = useCallback(async (texts) => {
|
|
1630
|
+
if (!providerRef.current) {
|
|
1631
|
+
const err = new Error("Embeddings provider not initialized");
|
|
1632
|
+
setError(err);
|
|
1633
|
+
onError?.(err);
|
|
1634
|
+
return null;
|
|
1635
|
+
}
|
|
1636
|
+
if (texts.length === 0) {
|
|
1637
|
+
return { embeddings: [], model: "", usage: { promptTokens: 0, totalTokens: 0 } };
|
|
1638
|
+
}
|
|
1639
|
+
const uncached = [];
|
|
1640
|
+
const cachedResults = /* @__PURE__ */ new Map();
|
|
1641
|
+
if (cache) {
|
|
1642
|
+
for (const text of texts) {
|
|
1643
|
+
if (cacheRef.current.has(text)) {
|
|
1644
|
+
cachedResults.set(text, cacheRef.current.get(text));
|
|
1645
|
+
} else {
|
|
1646
|
+
uncached.push(text);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
} else {
|
|
1650
|
+
uncached.push(...texts);
|
|
1651
|
+
}
|
|
1652
|
+
setIsLoading(true);
|
|
1653
|
+
setError(null);
|
|
1654
|
+
onStart?.();
|
|
1655
|
+
try {
|
|
1656
|
+
let newEmbeddings = {
|
|
1657
|
+
embeddings: [],
|
|
1658
|
+
model: "",
|
|
1659
|
+
usage: { promptTokens: 0, totalTokens: 0 }
|
|
1660
|
+
};
|
|
1661
|
+
if (uncached.length > 0) {
|
|
1662
|
+
newEmbeddings = await providerRef.current.embedMany(uncached);
|
|
1663
|
+
if (cache) {
|
|
1664
|
+
for (const emb of newEmbeddings.embeddings) {
|
|
1665
|
+
cacheRef.current.set(emb.text, emb.embedding);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
const allEmbeddings = texts.map((text) => {
|
|
1670
|
+
if (cachedResults.has(text)) {
|
|
1671
|
+
return { text, embedding: cachedResults.get(text) };
|
|
1672
|
+
}
|
|
1673
|
+
const found = newEmbeddings.embeddings.find((e) => e.text === text);
|
|
1674
|
+
return found || { text, embedding: [] };
|
|
1675
|
+
});
|
|
1676
|
+
const batchResult2 = {
|
|
1677
|
+
embeddings: allEmbeddings,
|
|
1678
|
+
model: newEmbeddings.model,
|
|
1679
|
+
usage: newEmbeddings.usage
|
|
1680
|
+
};
|
|
1681
|
+
setBatchResult(batchResult2);
|
|
1682
|
+
onComplete?.(batchResult2);
|
|
1683
|
+
return batchResult2;
|
|
1684
|
+
} catch (err) {
|
|
1685
|
+
const error2 = err;
|
|
1686
|
+
setError(error2);
|
|
1687
|
+
onError?.(error2);
|
|
1688
|
+
return null;
|
|
1689
|
+
} finally {
|
|
1690
|
+
setIsLoading(false);
|
|
1691
|
+
}
|
|
1692
|
+
}, [cache, onStart, onComplete, onError]);
|
|
1693
|
+
const similarity = useCallback(async (text1, text2) => {
|
|
1694
|
+
const results = await embedMany([text1, text2]);
|
|
1695
|
+
if (!results || results.embeddings.length < 2) {
|
|
1696
|
+
return null;
|
|
1697
|
+
}
|
|
1698
|
+
const [emb1, emb2] = results.embeddings;
|
|
1699
|
+
return cosineSimilarity(emb1.embedding, emb2.embedding);
|
|
1700
|
+
}, [embedMany]);
|
|
1701
|
+
const findSimilar = useCallback(async (query, candidates, topK = 5) => {
|
|
1702
|
+
if (candidates.length === 0) return [];
|
|
1703
|
+
const allTexts = [query, ...candidates];
|
|
1704
|
+
const results = await embedMany(allTexts);
|
|
1705
|
+
if (!results || results.embeddings.length === 0) {
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
const queryEmbedding = results.embeddings[0].embedding;
|
|
1709
|
+
const candidateEmbeddings = results.embeddings.slice(1);
|
|
1710
|
+
const similarities = candidateEmbeddings.map((emb, i) => ({
|
|
1711
|
+
text: candidates[i],
|
|
1712
|
+
score: cosineSimilarity(queryEmbedding, emb.embedding)
|
|
1713
|
+
}));
|
|
1714
|
+
return similarities.sort((a, b) => b.score - a.score).slice(0, topK);
|
|
1715
|
+
}, [embedMany]);
|
|
1716
|
+
const clearCache = useCallback(() => {
|
|
1717
|
+
cacheRef.current.clear();
|
|
1718
|
+
}, []);
|
|
1719
|
+
const getCached = useCallback((text) => {
|
|
1720
|
+
return cacheRef.current.get(text) ?? null;
|
|
1721
|
+
}, []);
|
|
1722
|
+
return {
|
|
1723
|
+
embed,
|
|
1724
|
+
embedMany,
|
|
1725
|
+
similarity,
|
|
1726
|
+
findSimilar,
|
|
1727
|
+
result,
|
|
1728
|
+
batchResult,
|
|
1729
|
+
isLoading,
|
|
1730
|
+
error,
|
|
1731
|
+
clearCache,
|
|
1732
|
+
getCached
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
function useBinarioSemanticSearch(options = {}) {
|
|
1736
|
+
const { minScore = 0.5, maxResults = 10, ...embedOptions } = options;
|
|
1737
|
+
const { embedMany, clearCache, error: embedError } = useBinarioEmbed(embedOptions);
|
|
1738
|
+
const indexRef = useRef(/* @__PURE__ */ new Map());
|
|
1739
|
+
const [documentCount, setDocumentCount] = useState(0);
|
|
1740
|
+
const [isIndexing, setIsIndexing] = useState(false);
|
|
1741
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
1742
|
+
const [error, setError] = useState(null);
|
|
1743
|
+
const addDocuments = useCallback(async (documents) => {
|
|
1744
|
+
if (documents.length === 0) return;
|
|
1745
|
+
setIsIndexing(true);
|
|
1746
|
+
setError(null);
|
|
1747
|
+
try {
|
|
1748
|
+
const texts = documents.map((d) => d.text);
|
|
1749
|
+
let embeddings;
|
|
1750
|
+
try {
|
|
1751
|
+
embeddings = await embedMany(texts);
|
|
1752
|
+
} catch (err) {
|
|
1753
|
+
setError(err);
|
|
1754
|
+
setIsIndexing(false);
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (!embeddings) {
|
|
1758
|
+
setError(embedError || new Error("Failed to generate embeddings"));
|
|
1759
|
+
setIsIndexing(false);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
for (let i = 0; i < documents.length; i++) {
|
|
1763
|
+
indexRef.current.set(documents[i].id, {
|
|
1764
|
+
document: documents[i],
|
|
1765
|
+
embedding: embeddings.embeddings[i].embedding
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
setDocumentCount(indexRef.current.size);
|
|
1769
|
+
} catch (err) {
|
|
1770
|
+
setError(err);
|
|
1771
|
+
} finally {
|
|
1772
|
+
setIsIndexing(false);
|
|
1773
|
+
}
|
|
1774
|
+
}, [embedMany]);
|
|
1775
|
+
const search = useCallback(async (query) => {
|
|
1776
|
+
if (indexRef.current.size === 0) return [];
|
|
1777
|
+
setIsSearching(true);
|
|
1778
|
+
setError(null);
|
|
1779
|
+
try {
|
|
1780
|
+
const queryEmbeddings = await embedMany([query]);
|
|
1781
|
+
if (!queryEmbeddings || queryEmbeddings.embeddings.length === 0) {
|
|
1782
|
+
throw new Error("Failed to generate query embedding");
|
|
1783
|
+
}
|
|
1784
|
+
const queryEmbedding = queryEmbeddings.embeddings[0].embedding;
|
|
1785
|
+
const results = [];
|
|
1786
|
+
for (const { document, embedding } of indexRef.current.values()) {
|
|
1787
|
+
const score = cosineSimilarity(queryEmbedding, embedding);
|
|
1788
|
+
if (score >= minScore) {
|
|
1789
|
+
results.push({ document, score });
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return results.sort((a, b) => b.score - a.score).slice(0, maxResults);
|
|
1793
|
+
} catch (err) {
|
|
1794
|
+
setError(err);
|
|
1795
|
+
return [];
|
|
1796
|
+
} finally {
|
|
1797
|
+
setIsSearching(false);
|
|
1798
|
+
}
|
|
1799
|
+
}, [embedMany, minScore, maxResults]);
|
|
1800
|
+
const removeDocuments = useCallback((ids) => {
|
|
1801
|
+
for (const id of ids) {
|
|
1802
|
+
indexRef.current.delete(id);
|
|
1803
|
+
}
|
|
1804
|
+
setDocumentCount(indexRef.current.size);
|
|
1805
|
+
}, []);
|
|
1806
|
+
const clear = useCallback(() => {
|
|
1807
|
+
indexRef.current.clear();
|
|
1808
|
+
clearCache();
|
|
1809
|
+
setDocumentCount(0);
|
|
1810
|
+
}, [clearCache]);
|
|
1811
|
+
return {
|
|
1812
|
+
addDocuments,
|
|
1813
|
+
search,
|
|
1814
|
+
removeDocuments,
|
|
1815
|
+
clear,
|
|
1816
|
+
documentCount,
|
|
1817
|
+
isIndexing,
|
|
1818
|
+
isSearching,
|
|
1819
|
+
error
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
export { useBinarioAgent, useBinarioChat, useBinarioChatWithMemory, useBinarioCompletion, useBinarioEmbed, useBinarioMemory, useBinarioSemanticSearch, useBinarioStream, useBinarioStructured, useBinarioTools };
|
|
1824
|
+
//# sourceMappingURL=react.js.map
|
|
1825
|
+
//# sourceMappingURL=react.js.map
|