@reverbia/sdk 1.0.0-next.20251212012743 → 1.0.0-next.20251215143604

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.
@@ -280,8 +280,404 @@ function useChat(options) {
280
280
  };
281
281
  }
282
282
 
283
+ // src/expo/useChatStorage.ts
284
+ import { useCallback as useCallback2, useState as useState2, useMemo } from "react";
285
+
286
+ // src/lib/chatStorage/types.ts
287
+ function convertUsageToStored(usage) {
288
+ if (!usage) return void 0;
289
+ return {
290
+ promptTokens: usage.prompt_tokens,
291
+ completionTokens: usage.completion_tokens,
292
+ totalTokens: usage.total_tokens,
293
+ costMicroUsd: usage.cost_micro_usd
294
+ };
295
+ }
296
+ function generateConversationId() {
297
+ return `conv_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
298
+ }
299
+
300
+ // src/lib/chatStorage/operations.ts
301
+ import { Q } from "@nozbe/watermelondb";
302
+ function messageToStored(message) {
303
+ return {
304
+ uniqueId: message.id,
305
+ messageId: message.messageId,
306
+ conversationId: message.conversationId,
307
+ role: message.role,
308
+ content: message.content,
309
+ model: message.model,
310
+ files: message.files,
311
+ createdAt: message.createdAt,
312
+ updatedAt: message.updatedAt,
313
+ vector: message.vector,
314
+ embeddingModel: message.embeddingModel,
315
+ usage: message.usage,
316
+ sources: message.sources,
317
+ responseDuration: message.responseDuration
318
+ };
319
+ }
320
+ function conversationToStored(conversation) {
321
+ return {
322
+ uniqueId: conversation.id,
323
+ conversationId: conversation.conversationId,
324
+ title: conversation.title,
325
+ createdAt: conversation.createdAt,
326
+ updatedAt: conversation.updatedAt,
327
+ isDeleted: conversation.isDeleted
328
+ };
329
+ }
330
+ async function createConversationOp(ctx, opts, defaultTitle = "New Conversation") {
331
+ const convId = opts?.conversationId || generateConversationId();
332
+ const title = opts?.title || defaultTitle;
333
+ const created = await ctx.database.write(async () => {
334
+ return await ctx.conversationsCollection.create((conv) => {
335
+ conv._setRaw("conversation_id", convId);
336
+ conv._setRaw("title", title);
337
+ conv._setRaw("is_deleted", false);
338
+ });
339
+ });
340
+ return conversationToStored(created);
341
+ }
342
+ async function getConversationOp(ctx, id) {
343
+ const results = await ctx.conversationsCollection.query(Q.where("conversation_id", id), Q.where("is_deleted", false)).fetch();
344
+ return results.length > 0 ? conversationToStored(results[0]) : null;
345
+ }
346
+ async function getConversationsOp(ctx) {
347
+ const results = await ctx.conversationsCollection.query(Q.where("is_deleted", false), Q.sortBy("created_at", Q.desc)).fetch();
348
+ return results.map(conversationToStored);
349
+ }
350
+ async function updateConversationTitleOp(ctx, id, title) {
351
+ const results = await ctx.conversationsCollection.query(Q.where("conversation_id", id), Q.where("is_deleted", false)).fetch();
352
+ if (results.length > 0) {
353
+ await ctx.database.write(async () => {
354
+ await results[0].update((conv) => {
355
+ conv._setRaw("title", title);
356
+ });
357
+ });
358
+ return true;
359
+ }
360
+ return false;
361
+ }
362
+ async function deleteConversationOp(ctx, id) {
363
+ const results = await ctx.conversationsCollection.query(Q.where("conversation_id", id), Q.where("is_deleted", false)).fetch();
364
+ if (results.length > 0) {
365
+ await ctx.database.write(async () => {
366
+ await results[0].update((conv) => {
367
+ conv._setRaw("is_deleted", true);
368
+ });
369
+ });
370
+ return true;
371
+ }
372
+ return false;
373
+ }
374
+ async function getMessagesOp(ctx, convId) {
375
+ const results = await ctx.messagesCollection.query(Q.where("conversation_id", convId), Q.sortBy("message_id", Q.asc)).fetch();
376
+ return results.map(messageToStored);
377
+ }
378
+ async function getMessageCountOp(ctx, convId) {
379
+ return await ctx.messagesCollection.query(Q.where("conversation_id", convId)).fetchCount();
380
+ }
381
+ async function clearMessagesOp(ctx, convId) {
382
+ const messages = await ctx.messagesCollection.query(Q.where("conversation_id", convId)).fetch();
383
+ await ctx.database.write(async () => {
384
+ for (const message of messages) {
385
+ await message.destroyPermanently();
386
+ }
387
+ });
388
+ }
389
+ async function createMessageOp(ctx, opts) {
390
+ const existingCount = await getMessageCountOp(ctx, opts.conversationId);
391
+ const messageId = existingCount + 1;
392
+ const created = await ctx.database.write(async () => {
393
+ return await ctx.messagesCollection.create((msg) => {
394
+ msg._setRaw("message_id", messageId);
395
+ msg._setRaw("conversation_id", opts.conversationId);
396
+ msg._setRaw("role", opts.role);
397
+ msg._setRaw("content", opts.content);
398
+ if (opts.model) msg._setRaw("model", opts.model);
399
+ if (opts.files) msg._setRaw("files", JSON.stringify(opts.files));
400
+ if (opts.usage) msg._setRaw("usage", JSON.stringify(opts.usage));
401
+ if (opts.sources) msg._setRaw("sources", JSON.stringify(opts.sources));
402
+ if (opts.responseDuration !== void 0)
403
+ msg._setRaw("response_duration", opts.responseDuration);
404
+ if (opts.vector) msg._setRaw("vector", JSON.stringify(opts.vector));
405
+ if (opts.embeddingModel) msg._setRaw("embedding_model", opts.embeddingModel);
406
+ });
407
+ });
408
+ return messageToStored(created);
409
+ }
410
+
411
+ // src/expo/useChatStorage.ts
412
+ function storedToLlmapiMessage(stored) {
413
+ const content = [
414
+ { type: "text", text: stored.content }
415
+ ];
416
+ if (stored.files?.length) {
417
+ for (const file of stored.files) {
418
+ if (file.url) {
419
+ content.push({
420
+ type: "image_url",
421
+ image_url: { url: file.url }
422
+ });
423
+ }
424
+ }
425
+ }
426
+ return {
427
+ role: stored.role,
428
+ content
429
+ };
430
+ }
431
+ function useChatStorage(options) {
432
+ const {
433
+ database,
434
+ conversationId: initialConversationId,
435
+ autoCreateConversation = true,
436
+ defaultConversationTitle = "New Conversation",
437
+ getToken,
438
+ baseUrl,
439
+ onData,
440
+ onFinish,
441
+ onError
442
+ } = options;
443
+ const [currentConversationId, setCurrentConversationId] = useState2(initialConversationId || null);
444
+ const messagesCollection = useMemo(
445
+ () => database.get("history"),
446
+ [database]
447
+ );
448
+ const conversationsCollection = useMemo(
449
+ () => database.get("conversations"),
450
+ [database]
451
+ );
452
+ const storageCtx = useMemo(
453
+ () => ({
454
+ database,
455
+ messagesCollection,
456
+ conversationsCollection
457
+ }),
458
+ [database, messagesCollection, conversationsCollection]
459
+ );
460
+ const {
461
+ isLoading,
462
+ sendMessage: baseSendMessage,
463
+ stop
464
+ } = useChat({
465
+ getToken,
466
+ baseUrl,
467
+ onData,
468
+ onFinish,
469
+ onError
470
+ });
471
+ const createConversation = useCallback2(
472
+ async (opts) => {
473
+ const created = await createConversationOp(
474
+ storageCtx,
475
+ opts,
476
+ defaultConversationTitle
477
+ );
478
+ setCurrentConversationId(created.conversationId);
479
+ return created;
480
+ },
481
+ [storageCtx, defaultConversationTitle]
482
+ );
483
+ const getConversation = useCallback2(
484
+ async (id) => {
485
+ return getConversationOp(storageCtx, id);
486
+ },
487
+ [storageCtx]
488
+ );
489
+ const getConversations = useCallback2(async () => {
490
+ return getConversationsOp(storageCtx);
491
+ }, [storageCtx]);
492
+ const updateConversationTitle = useCallback2(
493
+ async (id, title) => {
494
+ return updateConversationTitleOp(storageCtx, id, title);
495
+ },
496
+ [storageCtx]
497
+ );
498
+ const deleteConversation = useCallback2(
499
+ async (id) => {
500
+ const deleted = await deleteConversationOp(storageCtx, id);
501
+ if (deleted && currentConversationId === id) {
502
+ setCurrentConversationId(null);
503
+ }
504
+ return deleted;
505
+ },
506
+ [storageCtx, currentConversationId]
507
+ );
508
+ const getMessages = useCallback2(
509
+ async (convId) => {
510
+ return getMessagesOp(storageCtx, convId);
511
+ },
512
+ [storageCtx]
513
+ );
514
+ const getMessageCount = useCallback2(
515
+ async (convId) => {
516
+ return getMessageCountOp(storageCtx, convId);
517
+ },
518
+ [storageCtx]
519
+ );
520
+ const clearMessages = useCallback2(
521
+ async (convId) => {
522
+ return clearMessagesOp(storageCtx, convId);
523
+ },
524
+ [storageCtx]
525
+ );
526
+ const ensureConversation = useCallback2(async () => {
527
+ if (currentConversationId) {
528
+ const existing = await getConversation(currentConversationId);
529
+ if (existing) {
530
+ return currentConversationId;
531
+ }
532
+ if (autoCreateConversation) {
533
+ const newConv = await createConversation({
534
+ conversationId: currentConversationId
535
+ });
536
+ return newConv.conversationId;
537
+ }
538
+ }
539
+ if (autoCreateConversation) {
540
+ const newConv = await createConversation();
541
+ return newConv.conversationId;
542
+ }
543
+ throw new Error(
544
+ "No conversation ID provided and autoCreateConversation is disabled"
545
+ );
546
+ }, [
547
+ currentConversationId,
548
+ getConversation,
549
+ autoCreateConversation,
550
+ createConversation
551
+ ]);
552
+ const sendMessage = useCallback2(
553
+ async (args) => {
554
+ const {
555
+ content,
556
+ model,
557
+ messages: providedMessages,
558
+ includeHistory = true,
559
+ maxHistoryMessages = 50,
560
+ files,
561
+ onData: perRequestOnData
562
+ } = args;
563
+ let convId;
564
+ try {
565
+ convId = await ensureConversation();
566
+ } catch (err) {
567
+ return {
568
+ data: null,
569
+ error: err instanceof Error ? err.message : "Failed to ensure conversation"
570
+ };
571
+ }
572
+ let messagesToSend = [];
573
+ if (includeHistory && !providedMessages) {
574
+ const storedMessages = await getMessages(convId);
575
+ const limitedMessages = storedMessages.slice(-maxHistoryMessages);
576
+ messagesToSend = limitedMessages.map(storedToLlmapiMessage);
577
+ } else if (providedMessages) {
578
+ messagesToSend = providedMessages;
579
+ }
580
+ const userMessageContent = [
581
+ { type: "text", text: content }
582
+ ];
583
+ if (files?.length) {
584
+ for (const file of files) {
585
+ if (file.url) {
586
+ userMessageContent.push({
587
+ type: "image_url",
588
+ image_url: { url: file.url }
589
+ });
590
+ }
591
+ }
592
+ }
593
+ const userMessage = {
594
+ role: "user",
595
+ content: userMessageContent
596
+ };
597
+ messagesToSend.push(userMessage);
598
+ const sanitizedFiles = files?.map((file) => ({
599
+ id: file.id,
600
+ name: file.name,
601
+ type: file.type,
602
+ size: file.size,
603
+ // Only keep URL if it's not a data URI (e.g., external URLs)
604
+ url: file.url && !file.url.startsWith("data:") ? file.url : void 0
605
+ }));
606
+ let storedUserMessage;
607
+ try {
608
+ storedUserMessage = await createMessageOp(storageCtx, {
609
+ conversationId: convId,
610
+ role: "user",
611
+ content,
612
+ files: sanitizedFiles
613
+ });
614
+ } catch (err) {
615
+ return {
616
+ data: null,
617
+ error: err instanceof Error ? err.message : "Failed to store user message"
618
+ };
619
+ }
620
+ const startTime = Date.now();
621
+ const result = await baseSendMessage({
622
+ messages: messagesToSend,
623
+ model,
624
+ onData: perRequestOnData
625
+ });
626
+ const responseDuration = (Date.now() - startTime) / 1e3;
627
+ if (result.error || !result.data) {
628
+ return {
629
+ data: null,
630
+ error: result.error || "No response data received",
631
+ userMessage: storedUserMessage
632
+ };
633
+ }
634
+ const responseData = result.data;
635
+ const assistantContent = responseData.choices?.[0]?.message?.content?.map((part) => part.text || "").join("") || "";
636
+ let storedAssistantMessage;
637
+ try {
638
+ storedAssistantMessage = await createMessageOp(storageCtx, {
639
+ conversationId: convId,
640
+ role: "assistant",
641
+ content: assistantContent,
642
+ model: responseData.model,
643
+ usage: convertUsageToStored(responseData.usage),
644
+ responseDuration
645
+ });
646
+ } catch (err) {
647
+ return {
648
+ data: null,
649
+ error: err instanceof Error ? err.message : "Failed to store assistant message",
650
+ userMessage: storedUserMessage
651
+ };
652
+ }
653
+ return {
654
+ data: responseData,
655
+ error: null,
656
+ userMessage: storedUserMessage,
657
+ assistantMessage: storedAssistantMessage
658
+ };
659
+ },
660
+ [ensureConversation, getMessages, storageCtx, baseSendMessage]
661
+ );
662
+ return {
663
+ isLoading,
664
+ sendMessage,
665
+ stop,
666
+ conversationId: currentConversationId,
667
+ setConversationId: setCurrentConversationId,
668
+ createConversation,
669
+ getConversation,
670
+ getConversations,
671
+ updateConversationTitle,
672
+ deleteConversation,
673
+ getMessages,
674
+ getMessageCount,
675
+ clearMessages
676
+ };
677
+ }
678
+
283
679
  // src/react/useImageGeneration.ts
284
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
680
+ import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
285
681
 
286
682
  // src/client/core/bodySerializer.gen.ts
287
683
  var jsonBodySerializer = {
@@ -1112,7 +1508,7 @@ var getApiV1Models = (options) => {
1112
1508
  // src/react/useImageGeneration.ts
1113
1509
  function useImageGeneration(options = {}) {
1114
1510
  const { getToken, baseUrl = BASE_URL, onFinish, onError } = options;
1115
- const [isLoading, setIsLoading] = useState2(false);
1511
+ const [isLoading, setIsLoading] = useState3(false);
1116
1512
  const abortControllerRef = useRef2(null);
1117
1513
  useEffect2(() => {
1118
1514
  return () => {
@@ -1122,13 +1518,13 @@ function useImageGeneration(options = {}) {
1122
1518
  }
1123
1519
  };
1124
1520
  }, []);
1125
- const stop = useCallback2(() => {
1521
+ const stop = useCallback3(() => {
1126
1522
  if (abortControllerRef.current) {
1127
1523
  abortControllerRef.current.abort();
1128
1524
  abortControllerRef.current = null;
1129
1525
  }
1130
1526
  }, []);
1131
- const generateImage = useCallback2(
1527
+ const generateImage = useCallback3(
1132
1528
  async (args) => {
1133
1529
  if (abortControllerRef.current) {
1134
1530
  abortControllerRef.current.abort();
@@ -1191,12 +1587,12 @@ function useImageGeneration(options = {}) {
1191
1587
  }
1192
1588
 
1193
1589
  // src/react/useModels.ts
1194
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1590
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1195
1591
  function useModels(options = {}) {
1196
1592
  const { getToken, baseUrl = BASE_URL, provider, autoFetch = true } = options;
1197
- const [models, setModels] = useState3([]);
1198
- const [isLoading, setIsLoading] = useState3(false);
1199
- const [error, setError] = useState3(null);
1593
+ const [models, setModels] = useState4([]);
1594
+ const [isLoading, setIsLoading] = useState4(false);
1595
+ const [error, setError] = useState4(null);
1200
1596
  const getTokenRef = useRef3(getToken);
1201
1597
  const baseUrlRef = useRef3(baseUrl);
1202
1598
  const providerRef = useRef3(provider);
@@ -1214,7 +1610,7 @@ function useModels(options = {}) {
1214
1610
  }
1215
1611
  };
1216
1612
  }, []);
1217
- const fetchModels = useCallback3(async () => {
1613
+ const fetchModels = useCallback4(async () => {
1218
1614
  if (abortControllerRef.current) {
1219
1615
  abortControllerRef.current.abort();
1220
1616
  }
@@ -1272,7 +1668,7 @@ function useModels(options = {}) {
1272
1668
  }
1273
1669
  }
1274
1670
  }, []);
1275
- const refetch = useCallback3(async () => {
1671
+ const refetch = useCallback4(async () => {
1276
1672
  setModels([]);
1277
1673
  await fetchModels();
1278
1674
  }, [fetchModels]);
@@ -1293,8 +1689,1243 @@ function useModels(options = {}) {
1293
1689
  refetch
1294
1690
  };
1295
1691
  }
1692
+
1693
+ // src/expo/useMemoryStorage.ts
1694
+ import { useCallback as useCallback5, useState as useState5, useMemo as useMemo2, useRef as useRef4 } from "react";
1695
+ import { postApiV1ChatCompletions } from "@reverbia/sdk";
1696
+
1697
+ // src/lib/memoryStorage/operations.ts
1698
+ import { Q as Q2 } from "@nozbe/watermelondb";
1699
+
1700
+ // src/lib/memoryStorage/types.ts
1701
+ function generateCompositeKey(namespace, key) {
1702
+ return `${namespace}:${key}`;
1703
+ }
1704
+ function generateUniqueKey(namespace, key, value) {
1705
+ return `${namespace}:${key}:${value}`;
1706
+ }
1707
+ function cosineSimilarity(a, b) {
1708
+ if (a.length !== b.length) {
1709
+ throw new Error("Vectors must have the same length");
1710
+ }
1711
+ let dotProduct = 0;
1712
+ let normA = 0;
1713
+ let normB = 0;
1714
+ for (let i = 0; i < a.length; i++) {
1715
+ dotProduct += a[i] * b[i];
1716
+ normA += a[i] * a[i];
1717
+ normB += b[i] * b[i];
1718
+ }
1719
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
1720
+ if (denominator === 0) {
1721
+ return 0;
1722
+ }
1723
+ return dotProduct / denominator;
1724
+ }
1725
+
1726
+ // src/lib/memoryStorage/operations.ts
1727
+ function memoryToStored(memory) {
1728
+ return {
1729
+ uniqueId: memory.id,
1730
+ type: memory.type,
1731
+ namespace: memory.namespace,
1732
+ key: memory.key,
1733
+ value: memory.value,
1734
+ rawEvidence: memory.rawEvidence,
1735
+ confidence: memory.confidence,
1736
+ pii: memory.pii,
1737
+ compositeKey: memory.compositeKey,
1738
+ uniqueKey: memory.uniqueKey,
1739
+ createdAt: memory.createdAt,
1740
+ updatedAt: memory.updatedAt,
1741
+ embedding: memory.embedding,
1742
+ embeddingModel: memory.embeddingModel,
1743
+ isDeleted: memory.isDeleted
1744
+ };
1745
+ }
1746
+ async function getAllMemoriesOp(ctx) {
1747
+ const results = await ctx.memoriesCollection.query(Q2.where("is_deleted", false), Q2.sortBy("created_at", Q2.desc)).fetch();
1748
+ return results.map(memoryToStored);
1749
+ }
1750
+ async function getMemoryByIdOp(ctx, id) {
1751
+ try {
1752
+ const memory = await ctx.memoriesCollection.find(id);
1753
+ if (memory.isDeleted) return null;
1754
+ return memoryToStored(memory);
1755
+ } catch {
1756
+ return null;
1757
+ }
1758
+ }
1759
+ async function getMemoriesByNamespaceOp(ctx, namespace) {
1760
+ const results = await ctx.memoriesCollection.query(
1761
+ Q2.where("namespace", namespace),
1762
+ Q2.where("is_deleted", false),
1763
+ Q2.sortBy("created_at", Q2.desc)
1764
+ ).fetch();
1765
+ return results.map(memoryToStored);
1766
+ }
1767
+ async function getMemoriesByKeyOp(ctx, namespace, key) {
1768
+ const compositeKey = generateCompositeKey(namespace, key);
1769
+ const results = await ctx.memoriesCollection.query(
1770
+ Q2.where("composite_key", compositeKey),
1771
+ Q2.where("is_deleted", false),
1772
+ Q2.sortBy("created_at", Q2.desc)
1773
+ ).fetch();
1774
+ return results.map(memoryToStored);
1775
+ }
1776
+ async function saveMemoryOp(ctx, opts) {
1777
+ const compositeKey = generateCompositeKey(opts.namespace, opts.key);
1778
+ const uniqueKey = generateUniqueKey(opts.namespace, opts.key, opts.value);
1779
+ const result = await ctx.database.write(async () => {
1780
+ const existing = await ctx.memoriesCollection.query(Q2.where("unique_key", uniqueKey)).fetch();
1781
+ if (existing.length > 0) {
1782
+ const existingMemory = existing[0];
1783
+ const shouldPreserveEmbedding = existingMemory.value === opts.value && existingMemory.rawEvidence === opts.rawEvidence && existingMemory.type === opts.type && existingMemory.namespace === opts.namespace && existingMemory.key === opts.key && existingMemory.embedding !== void 0 && existingMemory.embedding.length > 0 && !opts.embedding;
1784
+ await existingMemory.update((mem) => {
1785
+ mem._setRaw("type", opts.type);
1786
+ mem._setRaw("namespace", opts.namespace);
1787
+ mem._setRaw("key", opts.key);
1788
+ mem._setRaw("value", opts.value);
1789
+ mem._setRaw("raw_evidence", opts.rawEvidence);
1790
+ mem._setRaw("confidence", opts.confidence);
1791
+ mem._setRaw("pii", opts.pii);
1792
+ mem._setRaw("composite_key", compositeKey);
1793
+ mem._setRaw("unique_key", uniqueKey);
1794
+ mem._setRaw("is_deleted", false);
1795
+ if (shouldPreserveEmbedding) {
1796
+ } else if (opts.embedding) {
1797
+ mem._setRaw("embedding", JSON.stringify(opts.embedding));
1798
+ if (opts.embeddingModel) {
1799
+ mem._setRaw("embedding_model", opts.embeddingModel);
1800
+ }
1801
+ } else {
1802
+ mem._setRaw("embedding", null);
1803
+ mem._setRaw("embedding_model", null);
1804
+ }
1805
+ });
1806
+ return existingMemory;
1807
+ }
1808
+ return await ctx.memoriesCollection.create((mem) => {
1809
+ mem._setRaw("type", opts.type);
1810
+ mem._setRaw("namespace", opts.namespace);
1811
+ mem._setRaw("key", opts.key);
1812
+ mem._setRaw("value", opts.value);
1813
+ mem._setRaw("raw_evidence", opts.rawEvidence);
1814
+ mem._setRaw("confidence", opts.confidence);
1815
+ mem._setRaw("pii", opts.pii);
1816
+ mem._setRaw("composite_key", compositeKey);
1817
+ mem._setRaw("unique_key", uniqueKey);
1818
+ mem._setRaw("is_deleted", false);
1819
+ if (opts.embedding) {
1820
+ mem._setRaw("embedding", JSON.stringify(opts.embedding));
1821
+ if (opts.embeddingModel) {
1822
+ mem._setRaw("embedding_model", opts.embeddingModel);
1823
+ }
1824
+ }
1825
+ });
1826
+ });
1827
+ return memoryToStored(result);
1828
+ }
1829
+ async function saveMemoriesOp(ctx, memories) {
1830
+ const results = [];
1831
+ for (const memory of memories) {
1832
+ const saved = await saveMemoryOp(ctx, memory);
1833
+ results.push(saved);
1834
+ }
1835
+ return results;
1836
+ }
1837
+ async function updateMemoryOp(ctx, id, updates) {
1838
+ let memory;
1839
+ try {
1840
+ memory = await ctx.memoriesCollection.find(id);
1841
+ } catch {
1842
+ return { ok: false, reason: "not_found" };
1843
+ }
1844
+ if (memory.isDeleted) {
1845
+ return { ok: false, reason: "not_found" };
1846
+ }
1847
+ const newNamespace = updates.namespace ?? memory.namespace;
1848
+ const newKey = updates.key ?? memory.key;
1849
+ const newValue = updates.value ?? memory.value;
1850
+ const newCompositeKey = generateCompositeKey(newNamespace, newKey);
1851
+ const newUniqueKey = generateUniqueKey(newNamespace, newKey, newValue);
1852
+ if (newUniqueKey !== memory.uniqueKey) {
1853
+ const existing = await ctx.memoriesCollection.query(Q2.where("unique_key", newUniqueKey), Q2.where("is_deleted", false)).fetch();
1854
+ if (existing.length > 0) {
1855
+ return { ok: false, reason: "conflict", conflictingKey: newUniqueKey };
1856
+ }
1857
+ }
1858
+ try {
1859
+ const updated = await ctx.database.write(async () => {
1860
+ await memory.update((mem) => {
1861
+ if (updates.type !== void 0) mem._setRaw("type", updates.type);
1862
+ if (updates.namespace !== void 0)
1863
+ mem._setRaw("namespace", updates.namespace);
1864
+ if (updates.key !== void 0) mem._setRaw("key", updates.key);
1865
+ if (updates.value !== void 0) mem._setRaw("value", updates.value);
1866
+ if (updates.rawEvidence !== void 0)
1867
+ mem._setRaw("raw_evidence", updates.rawEvidence);
1868
+ if (updates.confidence !== void 0)
1869
+ mem._setRaw("confidence", updates.confidence);
1870
+ if (updates.pii !== void 0) mem._setRaw("pii", updates.pii);
1871
+ if (updates.namespace !== void 0 || updates.key !== void 0 || updates.value !== void 0) {
1872
+ mem._setRaw("composite_key", newCompositeKey);
1873
+ mem._setRaw("unique_key", newUniqueKey);
1874
+ }
1875
+ if (updates.embedding !== void 0) {
1876
+ mem._setRaw(
1877
+ "embedding",
1878
+ updates.embedding ? JSON.stringify(updates.embedding) : null
1879
+ );
1880
+ }
1881
+ if (updates.embeddingModel !== void 0) {
1882
+ mem._setRaw("embedding_model", updates.embeddingModel || null);
1883
+ }
1884
+ });
1885
+ return memory;
1886
+ });
1887
+ return { ok: true, memory: memoryToStored(updated) };
1888
+ } catch (err) {
1889
+ return {
1890
+ ok: false,
1891
+ reason: "error",
1892
+ error: err instanceof Error ? err : new Error(String(err))
1893
+ };
1894
+ }
1895
+ }
1896
+ async function deleteMemoryByIdOp(ctx, id) {
1897
+ try {
1898
+ const memory = await ctx.memoriesCollection.find(id);
1899
+ await ctx.database.write(async () => {
1900
+ await memory.update((mem) => {
1901
+ mem._setRaw("is_deleted", true);
1902
+ });
1903
+ });
1904
+ } catch {
1905
+ }
1906
+ }
1907
+ async function deleteMemoryOp(ctx, namespace, key, value) {
1908
+ const uniqueKey = generateUniqueKey(namespace, key, value);
1909
+ const results = await ctx.memoriesCollection.query(Q2.where("unique_key", uniqueKey)).fetch();
1910
+ if (results.length > 0) {
1911
+ await ctx.database.write(async () => {
1912
+ await results[0].update((mem) => {
1913
+ mem._setRaw("is_deleted", true);
1914
+ });
1915
+ });
1916
+ }
1917
+ }
1918
+ async function deleteMemoriesByKeyOp(ctx, namespace, key) {
1919
+ const compositeKey = generateCompositeKey(namespace, key);
1920
+ const results = await ctx.memoriesCollection.query(Q2.where("composite_key", compositeKey), Q2.where("is_deleted", false)).fetch();
1921
+ await ctx.database.write(async () => {
1922
+ for (const memory of results) {
1923
+ await memory.update((mem) => {
1924
+ mem._setRaw("is_deleted", true);
1925
+ });
1926
+ }
1927
+ });
1928
+ }
1929
+ async function clearAllMemoriesOp(ctx) {
1930
+ const results = await ctx.memoriesCollection.query(Q2.where("is_deleted", false)).fetch();
1931
+ await ctx.database.write(async () => {
1932
+ for (const memory of results) {
1933
+ await memory.update((mem) => {
1934
+ mem._setRaw("is_deleted", true);
1935
+ });
1936
+ }
1937
+ });
1938
+ }
1939
+ async function searchSimilarMemoriesOp(ctx, queryEmbedding, limit = 10, minSimilarity = 0.6) {
1940
+ const allMemories = await ctx.memoriesCollection.query(Q2.where("is_deleted", false)).fetch();
1941
+ const memoriesWithEmbeddings = allMemories.filter(
1942
+ (m) => m.embedding && m.embedding.length > 0
1943
+ );
1944
+ if (memoriesWithEmbeddings.length === 0) {
1945
+ return [];
1946
+ }
1947
+ const results = memoriesWithEmbeddings.map((memory) => {
1948
+ const similarity = cosineSimilarity(queryEmbedding, memory.embedding);
1949
+ return {
1950
+ ...memoryToStored(memory),
1951
+ similarity
1952
+ };
1953
+ }).filter((result) => result.similarity >= minSimilarity).sort((a, b) => b.similarity - a.similarity).slice(0, limit);
1954
+ return results;
1955
+ }
1956
+ async function updateMemoryEmbeddingOp(ctx, id, embedding, embeddingModel) {
1957
+ try {
1958
+ const memory = await ctx.memoriesCollection.find(id);
1959
+ await ctx.database.write(async () => {
1960
+ await memory.update((mem) => {
1961
+ mem._setRaw("embedding", JSON.stringify(embedding));
1962
+ mem._setRaw("embedding_model", embeddingModel);
1963
+ });
1964
+ });
1965
+ } catch {
1966
+ }
1967
+ }
1968
+
1969
+ // src/lib/memory/service.ts
1970
+ var FACT_EXTRACTION_PROMPT = `You are a memory extraction system. Extract durable user memories from chat messages.
1971
+
1972
+ CRITICAL: You MUST respond with ONLY valid JSON. No explanations, no markdown, no code blocks, just pure JSON.
1973
+
1974
+ Only store clear, factual statements that might be relevant for future context or reference. Extract facts that will be useful in future conversations, such as identity, stable preferences, ongoing projects, skills, locations, favorites, and constraints.
1975
+
1976
+ Do not extract sensitive attributes, temporary things, or single-use instructions.
1977
+
1978
+ You must also extract stable personal preferences, including food likes/dislikes, hobbies, favorite items, favorite genres, or other enduring tastes.
1979
+
1980
+ If there are no memories to extract, return: {"items": []}
1981
+
1982
+ Response format (JSON only, no other text):
1983
+
1984
+ {
1985
+ "items": [
1986
+ {
1987
+ "type": "identity",
1988
+ "namespace": "identity",
1989
+ "key": "name",
1990
+ "value": "Charlie",
1991
+ "rawEvidence": "I'm Charlie",
1992
+ "confidence": 0.98,
1993
+ "pii": true
1994
+ },
1995
+ {
1996
+ "type": "identity",
1997
+ "namespace": "work",
1998
+ "key": "company",
1999
+ "value": "ZetaChain",
2000
+ "rawEvidence": "called ZetaChain",
2001
+ "confidence": 0.99,
2002
+ "pii": false
2003
+ },
2004
+ {
2005
+ "type": "identity",
2006
+ "namespace": "location",
2007
+ "key": "city",
2008
+ "value": "San Francisco",
2009
+ "rawEvidence": "I live in San Francisco",
2010
+ "confidence": 0.99,
2011
+ "pii": false
2012
+ },
2013
+ {
2014
+ "type": "preference",
2015
+ "namespace": "location",
2016
+ "key": "country",
2017
+ "value": "Japan",
2018
+ "rawEvidence": "I like to travel to the Japan",
2019
+ "confidence": 0.94,
2020
+ "pii": false
2021
+ },
2022
+ {
2023
+ "type": "preference",
2024
+ "namespace": "answer_style",
2025
+ "key": "verbosity",
2026
+ "value": "concise_direct",
2027
+ "rawEvidence": "I prefer concise, direct answers",
2028
+ "confidence": 0.96,
2029
+ "pii": false
2030
+ },
2031
+ {
2032
+ "type": "identity",
2033
+ "namespace": "timezone",
2034
+ "key": "tz",
2035
+ "value": "America/Los_Angeles",
2036
+ "rawEvidence": "I'm in PST",
2037
+ "confidence": 0.9,
2038
+ "pii": false
2039
+ },
2040
+ {
2041
+ "type": "preference",
2042
+ "namespace": "food",
2043
+ "key": "likes_ice_cream",
2044
+ "value": "ice cream",
2045
+ "rawEvidence": "I like ice cream",
2046
+ "confidence": 0.95,
2047
+ "pii": false
2048
+ }
2049
+ ]
2050
+ }`;
2051
+ var preprocessMemories = (items, minConfidence = 0.6) => {
2052
+ if (!items || !Array.isArray(items)) {
2053
+ return [];
2054
+ }
2055
+ const validItems = items.filter((item) => {
2056
+ if (item.namespace == null || item.key == null || item.value == null) {
2057
+ console.warn(
2058
+ "Dropping memory item with null/undefined namespace, key, or value:",
2059
+ item
2060
+ );
2061
+ return false;
2062
+ }
2063
+ const namespace = String(item.namespace).trim();
2064
+ const key = String(item.key).trim();
2065
+ const value = String(item.value).trim();
2066
+ if (namespace === "" || key === "" || value === "") {
2067
+ console.warn(
2068
+ "Dropping memory item with empty namespace, key, or value after trimming:",
2069
+ item
2070
+ );
2071
+ return false;
2072
+ }
2073
+ if (typeof item.confidence !== "number" || item.confidence < minConfidence) {
2074
+ console.warn(
2075
+ `Dropping memory item with confidence ${item.confidence} below threshold ${minConfidence}:`,
2076
+ item
2077
+ );
2078
+ return false;
2079
+ }
2080
+ return true;
2081
+ });
2082
+ const deduplicatedMap = /* @__PURE__ */ new Map();
2083
+ for (const item of validItems) {
2084
+ const uniqueKey = `${item.namespace}:${item.key}:${item.value}`;
2085
+ const existing = deduplicatedMap.get(uniqueKey);
2086
+ if (!existing || item.confidence > existing.confidence) {
2087
+ deduplicatedMap.set(uniqueKey, item);
2088
+ } else {
2089
+ console.debug(
2090
+ `Deduplicating memory item: keeping entry with higher confidence (${existing.confidence} > ${item.confidence})`,
2091
+ { namespace: item.namespace, key: item.key, value: item.value }
2092
+ );
2093
+ }
2094
+ }
2095
+ return Array.from(deduplicatedMap.values());
2096
+ };
2097
+
2098
+ // src/lib/memory/constants.ts
2099
+ var DEFAULT_API_EMBEDDING_MODEL = "openai/text-embedding-3-small";
2100
+ var DEFAULT_COMPLETION_MODEL = "openai/gpt-4o";
2101
+
2102
+ // src/expo/useMemoryStorage.ts
2103
+ import { postApiV1Embeddings } from "@reverbia/sdk";
2104
+ async function generateEmbeddingForTextApi(text, options) {
2105
+ const token = options.getToken ? await options.getToken() : null;
2106
+ if (!token) {
2107
+ throw new Error("No auth token available for embedding generation");
2108
+ }
2109
+ const response = await postApiV1Embeddings({
2110
+ baseUrl: options.baseUrl,
2111
+ body: {
2112
+ input: text,
2113
+ model: options.model
2114
+ },
2115
+ headers: {
2116
+ Authorization: `Bearer ${token}`
2117
+ }
2118
+ });
2119
+ if (!response.data || typeof response.data === "string") {
2120
+ throw new Error("Failed to generate embedding");
2121
+ }
2122
+ const embedding = response.data.data?.[0]?.embedding;
2123
+ if (!embedding) {
2124
+ throw new Error("No embedding in response");
2125
+ }
2126
+ return embedding;
2127
+ }
2128
+ async function generateEmbeddingForMemoryApi(memory, options) {
2129
+ const text = `${memory.type}: ${memory.namespace}/${memory.key} = ${memory.value}. Evidence: ${memory.rawEvidence}`;
2130
+ return generateEmbeddingForTextApi(text, options);
2131
+ }
2132
+ function useMemoryStorage(options) {
2133
+ const {
2134
+ database,
2135
+ completionsModel = DEFAULT_COMPLETION_MODEL,
2136
+ embeddingModel: userEmbeddingModel,
2137
+ embeddingProvider = "api",
2138
+ // Default to API for Expo
2139
+ generateEmbeddings = true,
2140
+ onFactsExtracted,
2141
+ getToken,
2142
+ baseUrl = BASE_URL
2143
+ } = options;
2144
+ const embeddingModel = userEmbeddingModel === void 0 ? DEFAULT_API_EMBEDDING_MODEL : userEmbeddingModel;
2145
+ const [memories, setMemories] = useState5([]);
2146
+ const extractionInProgressRef = useRef4(false);
2147
+ const memoriesCollection = useMemo2(
2148
+ () => database.get("memories"),
2149
+ [database]
2150
+ );
2151
+ const storageCtx = useMemo2(
2152
+ () => ({
2153
+ database,
2154
+ memoriesCollection
2155
+ }),
2156
+ [database, memoriesCollection]
2157
+ );
2158
+ const embeddingOptions = useMemo2(
2159
+ () => ({
2160
+ model: embeddingModel ?? DEFAULT_API_EMBEDDING_MODEL,
2161
+ getToken: getToken || void 0,
2162
+ baseUrl
2163
+ }),
2164
+ [embeddingModel, getToken, baseUrl]
2165
+ );
2166
+ const refreshMemories = useCallback5(async () => {
2167
+ const storedMemories = await getAllMemoriesOp(storageCtx);
2168
+ setMemories(storedMemories);
2169
+ }, [storageCtx]);
2170
+ const extractMemoriesFromMessage = useCallback5(
2171
+ async (opts) => {
2172
+ const { messages, model } = opts;
2173
+ if (!getToken || extractionInProgressRef.current) {
2174
+ return null;
2175
+ }
2176
+ extractionInProgressRef.current = true;
2177
+ try {
2178
+ const token = await getToken();
2179
+ if (!token) {
2180
+ console.error("No access token available for memory extraction");
2181
+ return null;
2182
+ }
2183
+ const completion = await postApiV1ChatCompletions({
2184
+ baseUrl,
2185
+ body: {
2186
+ messages: [
2187
+ {
2188
+ role: "system",
2189
+ content: [{ type: "text", text: FACT_EXTRACTION_PROMPT }]
2190
+ },
2191
+ ...messages.map((m) => ({
2192
+ role: m.role,
2193
+ content: [{ type: "text", text: m.content }]
2194
+ }))
2195
+ ],
2196
+ model: model || completionsModel
2197
+ },
2198
+ headers: {
2199
+ Authorization: `Bearer ${token}`
2200
+ }
2201
+ });
2202
+ if (!completion.data) {
2203
+ console.error(
2204
+ "Memory extraction failed:",
2205
+ completion.error?.error ?? "API did not return a response"
2206
+ );
2207
+ return null;
2208
+ }
2209
+ if (typeof completion.data === "string") {
2210
+ console.error(
2211
+ "Memory extraction failed: API returned a string response"
2212
+ );
2213
+ return null;
2214
+ }
2215
+ const messageContent = completion.data.choices?.[0]?.message?.content;
2216
+ let content = "";
2217
+ if (Array.isArray(messageContent)) {
2218
+ content = messageContent.map((p) => p.text || "").join("").trim();
2219
+ } else if (typeof messageContent === "string") {
2220
+ content = messageContent.trim();
2221
+ }
2222
+ if (!content) {
2223
+ console.error("No content in memory extraction response");
2224
+ return null;
2225
+ }
2226
+ let jsonContent = content;
2227
+ jsonContent = jsonContent.replace(/^data:\s*/gm, "").trim();
2228
+ if (jsonContent.startsWith("{")) {
2229
+ let braceCount = 0;
2230
+ let jsonStart = -1;
2231
+ let jsonEnd = -1;
2232
+ for (let i = 0; i < jsonContent.length; i++) {
2233
+ if (jsonContent[i] === "{") {
2234
+ if (jsonStart === -1) jsonStart = i;
2235
+ braceCount++;
2236
+ } else if (jsonContent[i] === "}") {
2237
+ braceCount--;
2238
+ if (braceCount === 0 && jsonStart !== -1) {
2239
+ jsonEnd = i + 1;
2240
+ break;
2241
+ }
2242
+ }
2243
+ }
2244
+ if (jsonStart !== -1 && jsonEnd !== -1) {
2245
+ jsonContent = jsonContent.substring(jsonStart, jsonEnd);
2246
+ }
2247
+ } else {
2248
+ const jsonMatch = jsonContent.match(
2249
+ /```(?:json)?\s*(\{[\s\S]*?\})\s*```/
2250
+ );
2251
+ if (jsonMatch && jsonMatch[1]) {
2252
+ jsonContent = jsonMatch[1].trim();
2253
+ } else {
2254
+ const jsonObjectMatch = jsonContent.match(/\{[\s\S]*\}/);
2255
+ if (jsonObjectMatch && jsonObjectMatch[0]) {
2256
+ jsonContent = jsonObjectMatch[0];
2257
+ } else {
2258
+ console.warn("Memory extraction returned non-JSON response.");
2259
+ return { items: [] };
2260
+ }
2261
+ }
2262
+ }
2263
+ const trimmedJson = jsonContent.trim();
2264
+ if (!trimmedJson.startsWith("{") || !trimmedJson.includes("items")) {
2265
+ console.warn("Memory extraction response doesn't appear to be valid JSON.");
2266
+ return { items: [] };
2267
+ }
2268
+ let result;
2269
+ try {
2270
+ result = JSON.parse(jsonContent);
2271
+ if (!result || typeof result !== "object") {
2272
+ throw new Error("Invalid JSON structure: not an object");
2273
+ }
2274
+ if (!Array.isArray(result.items)) {
2275
+ console.warn("Memory extraction result missing 'items' array.");
2276
+ return { items: [] };
2277
+ }
2278
+ } catch (parseError) {
2279
+ console.error(
2280
+ "Failed to parse memory extraction JSON:",
2281
+ parseError instanceof Error ? parseError.message : "Unknown error"
2282
+ );
2283
+ return { items: [] };
2284
+ }
2285
+ if (result.items && Array.isArray(result.items)) {
2286
+ result.items = preprocessMemories(result.items);
2287
+ }
2288
+ if (result.items && result.items.length > 0) {
2289
+ try {
2290
+ const createOptions = result.items.map(
2291
+ (item) => ({
2292
+ type: item.type,
2293
+ namespace: item.namespace,
2294
+ key: item.key,
2295
+ value: item.value,
2296
+ rawEvidence: item.rawEvidence,
2297
+ confidence: item.confidence,
2298
+ pii: item.pii
2299
+ })
2300
+ );
2301
+ const savedMemories = await saveMemoriesOp(storageCtx, createOptions);
2302
+ console.log(`Saved ${savedMemories.length} memories to WatermelonDB`);
2303
+ if (generateEmbeddings && embeddingModel) {
2304
+ try {
2305
+ for (const saved of savedMemories) {
2306
+ const memoryItem = {
2307
+ type: saved.type,
2308
+ namespace: saved.namespace,
2309
+ key: saved.key,
2310
+ value: saved.value,
2311
+ rawEvidence: saved.rawEvidence,
2312
+ confidence: saved.confidence,
2313
+ pii: saved.pii
2314
+ };
2315
+ const embedding = await generateEmbeddingForMemoryApi(
2316
+ memoryItem,
2317
+ embeddingOptions
2318
+ );
2319
+ await updateMemoryEmbeddingOp(
2320
+ storageCtx,
2321
+ saved.uniqueId,
2322
+ embedding,
2323
+ embeddingOptions.model
2324
+ );
2325
+ }
2326
+ console.log(`Generated embeddings for ${savedMemories.length} memories`);
2327
+ } catch (error) {
2328
+ console.error("Failed to generate embeddings:", error);
2329
+ }
2330
+ }
2331
+ await refreshMemories();
2332
+ } catch (error) {
2333
+ console.error("Failed to save memories to WatermelonDB:", error);
2334
+ }
2335
+ }
2336
+ if (onFactsExtracted) {
2337
+ onFactsExtracted(result);
2338
+ }
2339
+ return result;
2340
+ } catch (error) {
2341
+ console.error("Failed to extract facts:", error);
2342
+ return null;
2343
+ } finally {
2344
+ extractionInProgressRef.current = false;
2345
+ }
2346
+ },
2347
+ [
2348
+ completionsModel,
2349
+ embeddingModel,
2350
+ embeddingOptions,
2351
+ generateEmbeddings,
2352
+ getToken,
2353
+ onFactsExtracted,
2354
+ baseUrl,
2355
+ storageCtx,
2356
+ refreshMemories
2357
+ ]
2358
+ );
2359
+ const searchMemories = useCallback5(
2360
+ async (query, limit = 10, minSimilarity = 0.6) => {
2361
+ if (!embeddingModel) {
2362
+ console.warn("Cannot search memories: embeddingModel not provided");
2363
+ return [];
2364
+ }
2365
+ try {
2366
+ const queryEmbedding = await generateEmbeddingForTextApi(
2367
+ query,
2368
+ embeddingOptions
2369
+ );
2370
+ const results = await searchSimilarMemoriesOp(
2371
+ storageCtx,
2372
+ queryEmbedding,
2373
+ limit,
2374
+ minSimilarity
2375
+ );
2376
+ if (results.length === 0) {
2377
+ console.warn(
2378
+ `[Memory Search] No memories found above similarity threshold ${minSimilarity}.`
2379
+ );
2380
+ } else {
2381
+ console.log(
2382
+ `[Memory Search] Found ${results.length} memories. Similarity scores: ${results.map((r) => r.similarity.toFixed(3)).join(", ")}`
2383
+ );
2384
+ }
2385
+ return results;
2386
+ } catch {
2387
+ return [];
2388
+ }
2389
+ },
2390
+ [embeddingModel, embeddingOptions, storageCtx]
2391
+ );
2392
+ const fetchAllMemories = useCallback5(async () => {
2393
+ try {
2394
+ return await getAllMemoriesOp(storageCtx);
2395
+ } catch (error) {
2396
+ throw new Error(
2397
+ "Failed to fetch all memories: " + (error instanceof Error ? error.message : String(error))
2398
+ );
2399
+ }
2400
+ }, [storageCtx]);
2401
+ const fetchMemoriesByNamespace = useCallback5(
2402
+ async (namespace) => {
2403
+ if (!namespace) {
2404
+ throw new Error("Missing required field: namespace");
2405
+ }
2406
+ try {
2407
+ return await getMemoriesByNamespaceOp(storageCtx, namespace);
2408
+ } catch (error) {
2409
+ throw new Error(
2410
+ `Failed to fetch memories for namespace "${namespace}": ` + (error instanceof Error ? error.message : String(error))
2411
+ );
2412
+ }
2413
+ },
2414
+ [storageCtx]
2415
+ );
2416
+ const fetchMemoriesByKey = useCallback5(
2417
+ async (namespace, key) => {
2418
+ if (!namespace || !key) {
2419
+ throw new Error("Missing required fields: namespace, key");
2420
+ }
2421
+ try {
2422
+ return await getMemoriesByKeyOp(storageCtx, namespace, key);
2423
+ } catch (error) {
2424
+ throw new Error(
2425
+ `Failed to fetch memories for "${namespace}:${key}": ` + (error instanceof Error ? error.message : String(error))
2426
+ );
2427
+ }
2428
+ },
2429
+ [storageCtx]
2430
+ );
2431
+ const getMemoryById = useCallback5(
2432
+ async (id) => {
2433
+ try {
2434
+ return await getMemoryByIdOp(storageCtx, id);
2435
+ } catch (error) {
2436
+ throw new Error(
2437
+ `Failed to get memory ${id}: ` + (error instanceof Error ? error.message : String(error))
2438
+ );
2439
+ }
2440
+ },
2441
+ [storageCtx]
2442
+ );
2443
+ const saveMemory = useCallback5(
2444
+ async (memory) => {
2445
+ try {
2446
+ const saved = await saveMemoryOp(storageCtx, memory);
2447
+ if (generateEmbeddings && embeddingModel && !memory.embedding) {
2448
+ try {
2449
+ const memoryItem = {
2450
+ type: memory.type,
2451
+ namespace: memory.namespace,
2452
+ key: memory.key,
2453
+ value: memory.value,
2454
+ rawEvidence: memory.rawEvidence,
2455
+ confidence: memory.confidence,
2456
+ pii: memory.pii
2457
+ };
2458
+ const embedding = await generateEmbeddingForMemoryApi(
2459
+ memoryItem,
2460
+ embeddingOptions
2461
+ );
2462
+ await updateMemoryEmbeddingOp(
2463
+ storageCtx,
2464
+ saved.uniqueId,
2465
+ embedding,
2466
+ embeddingOptions.model
2467
+ );
2468
+ } catch (error) {
2469
+ console.error("Failed to generate embedding:", error);
2470
+ }
2471
+ }
2472
+ setMemories((prev) => {
2473
+ const existing = prev.find((m) => m.uniqueId === saved.uniqueId);
2474
+ if (existing) {
2475
+ return prev.map((m) => m.uniqueId === saved.uniqueId ? saved : m);
2476
+ }
2477
+ return [saved, ...prev];
2478
+ });
2479
+ return saved;
2480
+ } catch (error) {
2481
+ throw new Error(
2482
+ "Failed to save memory: " + (error instanceof Error ? error.message : String(error))
2483
+ );
2484
+ }
2485
+ },
2486
+ [storageCtx, generateEmbeddings, embeddingModel, embeddingOptions]
2487
+ );
2488
+ const saveMemories = useCallback5(
2489
+ async (memoriesToSave) => {
2490
+ try {
2491
+ const saved = await saveMemoriesOp(storageCtx, memoriesToSave);
2492
+ if (generateEmbeddings && embeddingModel) {
2493
+ for (let i = 0; i < saved.length; i++) {
2494
+ const memory = memoriesToSave[i];
2495
+ if (!memory.embedding) {
2496
+ try {
2497
+ const memoryItem = {
2498
+ type: memory.type,
2499
+ namespace: memory.namespace,
2500
+ key: memory.key,
2501
+ value: memory.value,
2502
+ rawEvidence: memory.rawEvidence,
2503
+ confidence: memory.confidence,
2504
+ pii: memory.pii
2505
+ };
2506
+ const embedding = await generateEmbeddingForMemoryApi(
2507
+ memoryItem,
2508
+ embeddingOptions
2509
+ );
2510
+ await updateMemoryEmbeddingOp(
2511
+ storageCtx,
2512
+ saved[i].uniqueId,
2513
+ embedding,
2514
+ embeddingOptions.model
2515
+ );
2516
+ } catch (error) {
2517
+ console.error("Failed to generate embedding:", error);
2518
+ }
2519
+ }
2520
+ }
2521
+ }
2522
+ await refreshMemories();
2523
+ return saved;
2524
+ } catch (error) {
2525
+ throw new Error(
2526
+ "Failed to save memories: " + (error instanceof Error ? error.message : String(error))
2527
+ );
2528
+ }
2529
+ },
2530
+ [storageCtx, generateEmbeddings, embeddingModel, embeddingOptions, refreshMemories]
2531
+ );
2532
+ const updateMemory = useCallback5(
2533
+ async (id, updates) => {
2534
+ const result = await updateMemoryOp(storageCtx, id, updates);
2535
+ if (!result.ok) {
2536
+ if (result.reason === "not_found") {
2537
+ return null;
2538
+ }
2539
+ if (result.reason === "conflict") {
2540
+ throw new Error(
2541
+ `Cannot update memory: a memory with key "${result.conflictingKey}" already exists`
2542
+ );
2543
+ }
2544
+ throw new Error(
2545
+ `Failed to update memory ${id}: ${result.error.message}`
2546
+ );
2547
+ }
2548
+ const updated = result.memory;
2549
+ const contentChanged = updates.value !== void 0 || updates.rawEvidence !== void 0 || updates.type !== void 0 || updates.namespace !== void 0 || updates.key !== void 0;
2550
+ if (contentChanged && generateEmbeddings && embeddingModel && !updates.embedding) {
2551
+ try {
2552
+ const memoryItem = {
2553
+ type: updated.type,
2554
+ namespace: updated.namespace,
2555
+ key: updated.key,
2556
+ value: updated.value,
2557
+ rawEvidence: updated.rawEvidence,
2558
+ confidence: updated.confidence,
2559
+ pii: updated.pii
2560
+ };
2561
+ const embedding = await generateEmbeddingForMemoryApi(
2562
+ memoryItem,
2563
+ embeddingOptions
2564
+ );
2565
+ await updateMemoryEmbeddingOp(storageCtx, id, embedding, embeddingOptions.model);
2566
+ } catch (error) {
2567
+ console.error("Failed to regenerate embedding:", error);
2568
+ }
2569
+ }
2570
+ setMemories((prev) => prev.map((m) => m.uniqueId === id ? updated : m));
2571
+ return updated;
2572
+ },
2573
+ [storageCtx, generateEmbeddings, embeddingModel, embeddingOptions]
2574
+ );
2575
+ const removeMemory = useCallback5(
2576
+ async (namespace, key, value) => {
2577
+ if (!namespace || !key || !value) {
2578
+ throw new Error("Missing required fields: namespace, key, value");
2579
+ }
2580
+ try {
2581
+ await deleteMemoryOp(storageCtx, namespace, key, value);
2582
+ setMemories(
2583
+ (prev) => prev.filter(
2584
+ (m) => !(m.namespace === namespace && m.key === key && m.value === value)
2585
+ )
2586
+ );
2587
+ } catch (error) {
2588
+ throw new Error(
2589
+ `Failed to delete memory "${namespace}:${key}:${value}": ` + (error instanceof Error ? error.message : String(error))
2590
+ );
2591
+ }
2592
+ },
2593
+ [storageCtx]
2594
+ );
2595
+ const removeMemoryById = useCallback5(
2596
+ async (id) => {
2597
+ try {
2598
+ await deleteMemoryByIdOp(storageCtx, id);
2599
+ setMemories((prev) => prev.filter((m) => m.uniqueId !== id));
2600
+ } catch (error) {
2601
+ throw new Error(
2602
+ `Failed to delete memory with id ${id}: ` + (error instanceof Error ? error.message : String(error))
2603
+ );
2604
+ }
2605
+ },
2606
+ [storageCtx]
2607
+ );
2608
+ const removeMemories = useCallback5(
2609
+ async (namespace, key) => {
2610
+ if (!namespace || !key) {
2611
+ throw new Error("Missing required fields: namespace, key");
2612
+ }
2613
+ try {
2614
+ await deleteMemoriesByKeyOp(storageCtx, namespace, key);
2615
+ setMemories(
2616
+ (prev) => prev.filter((m) => !(m.namespace === namespace && m.key === key))
2617
+ );
2618
+ } catch (error) {
2619
+ throw new Error(
2620
+ `Failed to delete memories for "${namespace}:${key}": ` + (error instanceof Error ? error.message : String(error))
2621
+ );
2622
+ }
2623
+ },
2624
+ [storageCtx]
2625
+ );
2626
+ const clearMemories = useCallback5(async () => {
2627
+ try {
2628
+ await clearAllMemoriesOp(storageCtx);
2629
+ setMemories([]);
2630
+ } catch (error) {
2631
+ throw new Error(
2632
+ "Failed to clear all memories: " + (error instanceof Error ? error.message : String(error))
2633
+ );
2634
+ }
2635
+ }, [storageCtx]);
2636
+ return {
2637
+ memories,
2638
+ refreshMemories,
2639
+ extractMemoriesFromMessage,
2640
+ searchMemories,
2641
+ fetchAllMemories,
2642
+ fetchMemoriesByNamespace,
2643
+ fetchMemoriesByKey,
2644
+ getMemoryById,
2645
+ saveMemory,
2646
+ saveMemories,
2647
+ updateMemory,
2648
+ removeMemory,
2649
+ removeMemoryById,
2650
+ removeMemories,
2651
+ clearMemories
2652
+ };
2653
+ }
2654
+
2655
+ // src/lib/chatStorage/schema.ts
2656
+ import { appSchema, tableSchema } from "@nozbe/watermelondb";
2657
+ var chatStorageSchema = appSchema({
2658
+ version: 1,
2659
+ tables: [
2660
+ tableSchema({
2661
+ name: "history",
2662
+ columns: [
2663
+ { name: "message_id", type: "number" },
2664
+ // Sequential ID within conversation
2665
+ { name: "conversation_id", type: "string", isIndexed: true },
2666
+ { name: "role", type: "string", isIndexed: true },
2667
+ // 'user' | 'assistant' | 'system'
2668
+ { name: "content", type: "string" },
2669
+ { name: "model", type: "string", isOptional: true },
2670
+ { name: "files", type: "string", isOptional: true },
2671
+ // JSON stringified FileMetadata[]
2672
+ { name: "created_at", type: "number", isIndexed: true },
2673
+ { name: "updated_at", type: "number" },
2674
+ { name: "vector", type: "string", isOptional: true },
2675
+ // JSON stringified number[]
2676
+ { name: "embedding_model", type: "string", isOptional: true },
2677
+ { name: "usage", type: "string", isOptional: true },
2678
+ // JSON stringified ChatCompletionUsage
2679
+ { name: "sources", type: "string", isOptional: true },
2680
+ // JSON stringified SearchSource[]
2681
+ { name: "response_duration", type: "number", isOptional: true }
2682
+ ]
2683
+ }),
2684
+ tableSchema({
2685
+ name: "conversations",
2686
+ columns: [
2687
+ { name: "conversation_id", type: "string", isIndexed: true },
2688
+ { name: "title", type: "string" },
2689
+ { name: "created_at", type: "number" },
2690
+ { name: "updated_at", type: "number" },
2691
+ { name: "is_deleted", type: "boolean", isIndexed: true }
2692
+ ]
2693
+ })
2694
+ ]
2695
+ });
2696
+
2697
+ // src/lib/chatStorage/models.ts
2698
+ import { Model } from "@nozbe/watermelondb";
2699
+ var Message = class extends Model {
2700
+ /** Sequential message ID within conversation */
2701
+ get messageId() {
2702
+ return this._getRaw("message_id");
2703
+ }
2704
+ /** Links message to its conversation */
2705
+ get conversationId() {
2706
+ return this._getRaw("conversation_id");
2707
+ }
2708
+ /** Who sent the message: 'user' | 'assistant' | 'system' */
2709
+ get role() {
2710
+ return this._getRaw("role");
2711
+ }
2712
+ /** The message text content */
2713
+ get content() {
2714
+ return this._getRaw("content");
2715
+ }
2716
+ /** LLM model used (e.g., GPT-4, Claude) */
2717
+ get model() {
2718
+ const value = this._getRaw("model");
2719
+ return value ? value : void 0;
2720
+ }
2721
+ /** Optional attached files */
2722
+ get files() {
2723
+ const raw = this._getRaw("files");
2724
+ if (!raw) return void 0;
2725
+ try {
2726
+ return JSON.parse(raw);
2727
+ } catch {
2728
+ return void 0;
2729
+ }
2730
+ }
2731
+ /** Created timestamp */
2732
+ get createdAt() {
2733
+ return new Date(this._getRaw("created_at"));
2734
+ }
2735
+ /** Updated timestamp */
2736
+ get updatedAt() {
2737
+ return new Date(this._getRaw("updated_at"));
2738
+ }
2739
+ /** Embedding vector for semantic search */
2740
+ get vector() {
2741
+ const raw = this._getRaw("vector");
2742
+ if (!raw) return void 0;
2743
+ try {
2744
+ return JSON.parse(raw);
2745
+ } catch {
2746
+ return void 0;
2747
+ }
2748
+ }
2749
+ /** Model used to generate embedding */
2750
+ get embeddingModel() {
2751
+ const value = this._getRaw("embedding_model");
2752
+ return value ? value : void 0;
2753
+ }
2754
+ /** Token counts and cost */
2755
+ get usage() {
2756
+ const raw = this._getRaw("usage");
2757
+ if (!raw) return void 0;
2758
+ try {
2759
+ return JSON.parse(raw);
2760
+ } catch {
2761
+ return void 0;
2762
+ }
2763
+ }
2764
+ /** Web search sources */
2765
+ get sources() {
2766
+ const raw = this._getRaw("sources");
2767
+ if (!raw) return void 0;
2768
+ try {
2769
+ return JSON.parse(raw);
2770
+ } catch {
2771
+ return void 0;
2772
+ }
2773
+ }
2774
+ /** Response time in seconds */
2775
+ get responseDuration() {
2776
+ const value = this._getRaw("response_duration");
2777
+ return value !== null && value !== void 0 ? value : void 0;
2778
+ }
2779
+ };
2780
+ Message.table = "history";
2781
+ Message.associations = {
2782
+ conversations: { type: "belongs_to", key: "conversation_id" }
2783
+ };
2784
+ var Conversation = class extends Model {
2785
+ /** Unique conversation identifier */
2786
+ get conversationId() {
2787
+ return this._getRaw("conversation_id");
2788
+ }
2789
+ /** Conversation title */
2790
+ get title() {
2791
+ return this._getRaw("title");
2792
+ }
2793
+ /** Created timestamp */
2794
+ get createdAt() {
2795
+ return new Date(this._getRaw("created_at"));
2796
+ }
2797
+ /** Updated timestamp */
2798
+ get updatedAt() {
2799
+ return new Date(this._getRaw("updated_at"));
2800
+ }
2801
+ /** Soft delete flag */
2802
+ get isDeleted() {
2803
+ return this._getRaw("is_deleted");
2804
+ }
2805
+ };
2806
+ Conversation.table = "conversations";
2807
+ Conversation.associations = {
2808
+ history: { type: "has_many", foreignKey: "conversation_id" }
2809
+ };
2810
+
2811
+ // src/lib/memoryStorage/schema.ts
2812
+ import { appSchema as appSchema2, tableSchema as tableSchema2 } from "@nozbe/watermelondb";
2813
+ var memoryStorageSchema = appSchema2({
2814
+ version: 1,
2815
+ tables: [
2816
+ tableSchema2({
2817
+ name: "memories",
2818
+ columns: [
2819
+ // Memory type classification
2820
+ { name: "type", type: "string", isIndexed: true },
2821
+ // 'identity' | 'preference' | 'project' | 'skill' | 'constraint'
2822
+ // Hierarchical key structure
2823
+ { name: "namespace", type: "string", isIndexed: true },
2824
+ { name: "key", type: "string", isIndexed: true },
2825
+ { name: "value", type: "string" },
2826
+ // Evidence and confidence
2827
+ { name: "raw_evidence", type: "string" },
2828
+ { name: "confidence", type: "number" },
2829
+ { name: "pii", type: "boolean", isIndexed: true },
2830
+ // Composite keys for efficient lookups
2831
+ { name: "composite_key", type: "string", isIndexed: true },
2832
+ // namespace:key
2833
+ { name: "unique_key", type: "string", isIndexed: true },
2834
+ // namespace:key:value
2835
+ // Timestamps
2836
+ { name: "created_at", type: "number", isIndexed: true },
2837
+ { name: "updated_at", type: "number" },
2838
+ // Vector embeddings for semantic search
2839
+ { name: "embedding", type: "string", isOptional: true },
2840
+ // JSON stringified number[]
2841
+ { name: "embedding_model", type: "string", isOptional: true },
2842
+ // Soft delete flag
2843
+ { name: "is_deleted", type: "boolean", isIndexed: true }
2844
+ ]
2845
+ })
2846
+ ]
2847
+ });
2848
+
2849
+ // src/lib/memoryStorage/models.ts
2850
+ import { Model as Model2 } from "@nozbe/watermelondb";
2851
+ var Memory = class extends Model2 {
2852
+ /** Memory type classification */
2853
+ get type() {
2854
+ return this._getRaw("type");
2855
+ }
2856
+ /** Namespace for grouping related memories */
2857
+ get namespace() {
2858
+ return this._getRaw("namespace");
2859
+ }
2860
+ /** Key within the namespace */
2861
+ get key() {
2862
+ return this._getRaw("key");
2863
+ }
2864
+ /** The memory value/content */
2865
+ get value() {
2866
+ return this._getRaw("value");
2867
+ }
2868
+ /** Raw evidence from which this memory was extracted */
2869
+ get rawEvidence() {
2870
+ return this._getRaw("raw_evidence");
2871
+ }
2872
+ /** Confidence score (0-1) */
2873
+ get confidence() {
2874
+ return this._getRaw("confidence");
2875
+ }
2876
+ /** Whether this memory contains PII */
2877
+ get pii() {
2878
+ return this._getRaw("pii");
2879
+ }
2880
+ /** Composite key (namespace:key) for efficient lookups */
2881
+ get compositeKey() {
2882
+ return this._getRaw("composite_key");
2883
+ }
2884
+ /** Unique key (namespace:key:value) for deduplication */
2885
+ get uniqueKey() {
2886
+ return this._getRaw("unique_key");
2887
+ }
2888
+ /** Created timestamp */
2889
+ get createdAt() {
2890
+ return new Date(this._getRaw("created_at"));
2891
+ }
2892
+ /** Updated timestamp */
2893
+ get updatedAt() {
2894
+ return new Date(this._getRaw("updated_at"));
2895
+ }
2896
+ /** Embedding vector for semantic search */
2897
+ get embedding() {
2898
+ const raw = this._getRaw("embedding");
2899
+ if (!raw) return void 0;
2900
+ try {
2901
+ return JSON.parse(raw);
2902
+ } catch {
2903
+ return void 0;
2904
+ }
2905
+ }
2906
+ /** Model used to generate embedding */
2907
+ get embeddingModel() {
2908
+ const value = this._getRaw("embedding_model");
2909
+ return value ? value : void 0;
2910
+ }
2911
+ /** Soft delete flag */
2912
+ get isDeleted() {
2913
+ return this._getRaw("is_deleted");
2914
+ }
2915
+ };
2916
+ Memory.table = "memories";
1296
2917
  export {
2918
+ Conversation as ChatConversation,
2919
+ Message as ChatMessage,
2920
+ Memory as StoredMemoryModel,
2921
+ chatStorageSchema,
2922
+ generateCompositeKey,
2923
+ generateConversationId,
2924
+ generateUniqueKey,
2925
+ memoryStorageSchema,
1297
2926
  useChat,
2927
+ useChatStorage,
1298
2928
  useImageGeneration,
2929
+ useMemoryStorage,
1299
2930
  useModels
1300
2931
  };